diff --git a/README.md b/README.md index 81357bc..27cf3cf 100644 --- a/README.md +++ b/README.md @@ -179,4 +179,12 @@ case errors.As(err, &deviceErr): } ``` +Token 刷新失败且必须重新扫码授权时,可使用 `errors.Is` 稳定判断,同时仍可通过 `errors.As` 获取 `LoginError` 详情: + +```go +if errors.Is(err, mijia.ErrReauthenticationRequired) { + log.Print("认证已失效,请重新扫码授权") +} +``` + 写属性和执行 action 时,对应错误类型为 `DeviceSetError` 和 `DeviceActionError`;设备选择还可能返回 `DeviceNotFoundError` 或 `MultipleDevicesFoundError`。 diff --git a/auth.go b/auth.go index 313b3df..fe80196 100644 --- a/auth.go +++ b/auth.go @@ -356,7 +356,7 @@ func (client *Client) getLocation(ctx context.Context) (url.Values, bool, error) serviceTokenReceived := updateAuthDataFromCookies(&candidate, httpClient, response.Request.URL) candidate.Ssecurity = data.Ssecurity if !serviceTokenReceived || !candidate.complete() { - return nil, false, &LoginError{Code: -1, Message: "刷新Token响应认证信息不完整"} + return nil, false, fmt.Errorf("%w: %w", ErrReauthenticationRequired, &LoginError{Code: -1, Message: "刷新Token响应认证信息不完整"}) } candidate.ExpireTime = time.Now().Add(30 * 24 * time.Hour).UnixMilli() client.setAuthData(candidate) @@ -544,7 +544,7 @@ func (client *Client) refreshToken(ctx context.Context) error { return err } if !refreshed { - return &LoginError{Code: -1, Message: "刷新Token失败,请重新登录"} + return fmt.Errorf("%w: %w", ErrReauthenticationRequired, &LoginError{Code: -1, Message: "刷新Token失败,请重新登录"}) } if err := client.saveAuthData(); err != nil { return err diff --git a/auth_test.go b/auth_test.go index 382e318..9055f48 100644 --- a/auth_test.go +++ b/auth_test.go @@ -137,6 +137,9 @@ func TestRefreshRejectsMissingNewServiceTokenWithoutSaving(t *testing.T) { } err = client.refreshToken(context.Background()) + if !errors.Is(err, ErrReauthenticationRequired) { + t.Fatalf("refreshToken() error = %v, want ErrReauthenticationRequired", err) + } var loginErr *LoginError if !errors.As(err, &loginErr) { t.Fatalf("refreshToken() error = %v, want LoginError", err) @@ -153,6 +156,56 @@ func TestRefreshRejectsMissingNewServiceTokenWithoutSaving(t *testing.T) { } } +func TestRefreshWithoutNewTokenRequiresReauthentication(t *testing.T) { + var server *httptest.Server + server = httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + switch request.URL.Path { + case "/v2/message/v2/check_new_msg": + _, _ = io.WriteString(writer, `{"code":-10030,"message":"expired"}`) + case "/serviceLogin": + _, _ = io.WriteString(writer, `&&&START&&&{"code":70016,"location":"`+server.URL+`/qr"}`) + default: + http.NotFound(writer, request) + } + })) + defer server.Close() + + client := testClient(t, server.Client()) + client.baseURL = server.URL + client.serviceLoginURL = server.URL + "/serviceLogin" + client.availabilityValid = false + + err := client.refreshToken(context.Background()) + if !errors.Is(err, ErrReauthenticationRequired) { + t.Fatalf("refreshToken() error = %v, want ErrReauthenticationRequired", err) + } + var loginErr *LoginError + if !errors.As(err, &loginErr) { + t.Fatalf("refreshToken() error = %v, want LoginError", err) + } + if loginErr.Code != -1 || loginErr.Message != "刷新Token失败,请重新登录" { + t.Fatalf("LoginError = %#v", loginErr) + } +} + +func TestQRLoginTimeoutDoesNotRequireReauthentication(t *testing.T) { + ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(-time.Second)) + defer cancel() + + client := testClient(t, http.DefaultClient) + _, err := client.completeQRLogin(ctx, qrLoginData{LP: "https://example.invalid/long-poll"}) + var loginErr *LoginError + if !errors.As(err, &loginErr) { + t.Fatalf("completeQRLogin() error = %v, want LoginError", err) + } + if loginErr.Code != -1 { + t.Fatalf("LoginError.Code = %d, want -1", loginErr.Code) + } + if errors.Is(err, ErrReauthenticationRequired) { + t.Fatalf("completeQRLogin() error = %v, do not want ErrReauthenticationRequired", err) + } +} + func TestAuthDataReturnsDeepCopy(t *testing.T) { client := testClient(t, http.DefaultClient) client.updateAuthData(func(authData *AuthData) { authData.Extra = map[string]string{"cookie": "original"} }) diff --git a/errors.go b/errors.go index 7797187..e0d1051 100644 --- a/errors.go +++ b/errors.go @@ -1,6 +1,11 @@ package mijia -import "fmt" +import ( + "errors" + "fmt" +) + +var ErrReauthenticationRequired = errors.New("reauthentication required") var errorCodeMessages = map[int]string{ -10000: "未知错误",