fix auth login state race
This commit is contained in:
@@ -118,6 +118,22 @@
|
||||
- 验证:`http://127.0.0.1:3000/api/auth/login-options` 返回至少 `{"availableLoginMethods":["phone","password"]}` 后,登录弹窗会恢复短信登录页签和“获取验证码”按钮。
|
||||
- 关联:`scripts/api-server-dev.mjs`、`scripts/api-server-maincloud.mjs`、`scripts/dev-rust-stack.sh`、`scripts/dev-web-rust.mjs`、`docs/technical/AUTH_LOGIN_OPTIONS_DESIGN_2026-04-21.md`。
|
||||
|
||||
## 手机验证码登录 500 先查短信 provider 语义
|
||||
|
||||
- 现象:登录弹窗手机号验证码登录失败,浏览器看到 `POST /api/auth/phone/login 500`,后端日志里同时出现阿里云短信 `UNKNOWN`、`biz.FREQUENCY` 或 `check frequency failed`。
|
||||
- 原因:真实短信 provider 的配置错误或上游失败曾被 `module-auth` 折叠成 `PhoneAuthError::Store`,HTTP 层只能按内部错误返回 `500`,掩盖了 provider 失败。
|
||||
- 处理:保留 provider 错误语义,配置错误映射 `503 Service Unavailable`,上游短信失败映射 `502 Bad Gateway`;本地只验证 UI/账号链路时可用 shell 临时覆盖 `SMS_AUTH_PROVIDER=mock` 后启动 `npm run api-server`。
|
||||
- 验证:`cargo test -p api-server phone_auth_sms_provider_errors_keep_upstream_http_semantics --manifest-path server-rs/Cargo.toml`,真实 provider 频控时接口不再返回 `500`。
|
||||
- 关联:`server-rs/crates/module-auth/src/errors.rs`、`server-rs/crates/api-server/src/phone_auth.rs`、`docs/technical/PHONE_SMS_PROVIDER_ERROR_HTTP_MAPPING_FIX_2026-05-08.md`。
|
||||
|
||||
## 手机验证码登录成功后又瞬间回到未登录
|
||||
|
||||
- 现象:手机号验证码登录先成功,随后 UI 又闪回“未登录”,登录弹窗可能重新出现。
|
||||
- 原因:`AuthGate` 首次 hydrate 会异步轮换 refresh cookie 并请求 `/api/auth/me`。如果用户在 hydrate 完成前已经登录,晚到的旧 hydrate 仍可能把刚写入的 `user` 覆盖成 `null`。
|
||||
- 处理:给 `AuthGate` 的 hydrate 增加版本号保护;登录成功、退出登录和全局 auth 事件都会推进版本号,旧 hydrate 结果到达后直接丢弃。
|
||||
- 验证:`npm run test -- src/components/auth/AuthGate.test.tsx`,新增用例应覆盖“旧 guest hydrate 不覆盖新登录态”。
|
||||
- 关联:`src/components/auth/AuthGate.tsx`、`src/components/auth/AuthGate.test.tsx`、`docs/technical/AUTH_GATE_LOGIN_RACE_GUARD_FIX_2026-05-09.md`。
|
||||
|
||||
## Rust 冷编译导致 api-server 健康检查误超时
|
||||
|
||||
- 现象:`npm run dev:rust` 在 Windows 冷编译/链接阶段误判 `/healthz` 等待超时并杀掉 `cargo run`。
|
||||
|
||||
37
docs/technical/AUTH_GATE_LOGIN_RACE_GUARD_FIX_2026-05-09.md
Normal file
37
docs/technical/AUTH_GATE_LOGIN_RACE_GUARD_FIX_2026-05-09.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# `AuthGate` 登录后又回到未登录状态修复
|
||||
|
||||
日期:`2026-05-09`
|
||||
|
||||
## 背景
|
||||
|
||||
本地联调中,手机号验证码登录有时会先显示登录成功,随后又瞬间回到未登录态。
|
||||
|
||||
## 根因
|
||||
|
||||
`AuthGate` 首次挂载时会异步 hydrate:
|
||||
|
||||
1. 先轮换 refresh cookie
|
||||
2. 再请求 `/api/auth/me`
|
||||
3. 再根据结果写入 `user` 和 `status`
|
||||
|
||||
如果用户在这轮 hydrate 尚未完成时已经完成了登录,后到达的旧 hydrate 结果仍可能把刚写入的 `user` 覆盖回 `null`,导致登录态闪回未登录。
|
||||
|
||||
## 修复
|
||||
|
||||
`AuthGate` 增加 hydrate 版本号保护:
|
||||
|
||||
1. 每次启动 hydrate 都分配独立版本号。
|
||||
2. 登录成功、退出登录、收到全局 auth state 事件时递增版本号。
|
||||
3. 旧版本 hydrate 的结果到达后直接丢弃,不再覆盖当前 `user` / `status`。
|
||||
|
||||
## 验证
|
||||
|
||||
1. `npm run test -- src/components/auth/AuthGate.test.tsx`
|
||||
2. `npm run test -- src/services/apiClient.test.ts src/services/authService.test.ts`
|
||||
3. `npm run check:encoding`
|
||||
|
||||
## 关联
|
||||
|
||||
- `src/components/auth/AuthGate.tsx`
|
||||
- `src/components/auth/AuthGate.test.tsx`
|
||||
- `.hermes/shared-memory/pitfalls.md`
|
||||
@@ -0,0 +1,66 @@
|
||||
# 手机验证码短信 Provider 错误 HTTP 映射修复
|
||||
|
||||
日期:`2026-05-08`
|
||||
|
||||
## 背景
|
||||
|
||||
本地登录弹窗点击手机号验证码登录时,浏览器报:
|
||||
|
||||
```text
|
||||
POST /api/auth/phone/login 500
|
||||
```
|
||||
|
||||
排查发现当前 `.env.local` 使用:
|
||||
|
||||
```text
|
||||
SMS_AUTH_PROVIDER=aliyun
|
||||
```
|
||||
|
||||
因此 `send-code` 会走真实阿里云短信 provider。真实 provider 返回 `UNKNOWN` 或 `biz.FREQUENCY / check frequency failed` 时,`module-auth` 曾把 provider 失败统一折叠成 `PhoneAuthError::Store`,`api-server` 再映射为 `500 Internal Server Error`,前端只能看到登录失败。
|
||||
|
||||
## 根因
|
||||
|
||||
短信 provider 失败不是认证仓储内部错误:
|
||||
|
||||
1. 阿里云配置缺失或配置非法属于服务配置问题。
|
||||
2. 阿里云返回频控、网关失败或业务失败属于上游短信 provider 问题。
|
||||
3. 这些错误不应被映射成 `Store`,否则 HTTP 层无法区分真实内部错误与外部 provider 失败。
|
||||
|
||||
## 修复
|
||||
|
||||
`module-auth` 新增短信 provider 错误分类:
|
||||
|
||||
1. `PhoneAuthError::SmsProviderInvalidConfig`
|
||||
2. `PhoneAuthError::SmsProviderUpstream`
|
||||
|
||||
`api-server` 映射规则调整为:
|
||||
|
||||
1. provider 配置错误返回 `503 Service Unavailable`
|
||||
2. provider 上游失败返回 `502 Bad Gateway`
|
||||
3. 验证码不存在、错误、过期仍返回 `400`
|
||||
4. 本地仓储或签发错误仍返回 `500`
|
||||
|
||||
## 本地排查
|
||||
|
||||
如果本地只想验证登录 UI 和账号链路,可以临时用 shell 环境覆盖真实短信 provider:
|
||||
|
||||
```powershell
|
||||
$env:SMS_AUTH_PROVIDER="mock"
|
||||
npm run api-server
|
||||
```
|
||||
|
||||
若要验证真实短信链路,保持 `SMS_AUTH_PROVIDER=aliyun`,并查看 `api-server` 日志中的:
|
||||
|
||||
1. `阿里云短信发送接口返回响应`
|
||||
2. `阿里云短信发送接口返回业务失败`
|
||||
3. `手机号验证码发送失败`
|
||||
|
||||
看到 `biz.FREQUENCY` / `check frequency failed` 时,说明请求已到达短信 provider,但被 provider 频控或业务规则拒绝。
|
||||
|
||||
## 验收
|
||||
|
||||
1. `cargo test -p api-server phone_auth_sms_provider_errors_keep_upstream_http_semantics --manifest-path server-rs/Cargo.toml`
|
||||
2. `cargo test -p api-server send_phone --manifest-path server-rs/Cargo.toml`
|
||||
3. `cargo test -p api-server phone_login_creates_user_and_sets_refresh_cookie --manifest-path server-rs/Cargo.toml`
|
||||
4. `cargo check -p api-server --manifest-path server-rs/Cargo.toml`
|
||||
5. `npm run check:encoding`
|
||||
@@ -5,6 +5,7 @@
|
||||
## 文档列表
|
||||
|
||||
- [RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md](./RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md):记录 `server-rs` Cargo 依赖集中配置口径,第三方版本和 workspace 内部 crate path 统一维护在根 `server-rs/Cargo.toml`,成员 crate 只保留 feature/optional 差异。
|
||||
- [AUTH_GATE_LOGIN_RACE_GUARD_FIX_2026-05-09.md](./AUTH_GATE_LOGIN_RACE_GUARD_FIX_2026-05-09.md):记录 `AuthGate` 登录成功后又被旧 hydrate 覆盖回未登录态的竞态根因、版本号保护修复与回归测试。
|
||||
- [VOLCENGINE_SPEECH_STREAMING_INTEGRATION_2026-05-08.md](./VOLCENGINE_SPEECH_STREAMING_INTEGRATION_2026-05-08.md):记录火山引擎大模型 ASR 双向流式、TTS WebSocket 双向流式和 TTS HTTP SSE 单向流式的后端代理、环境变量、协议帧和验收边界。
|
||||
- [VECTOR_ENGINE_AUDIO_GENERATION_SUNO_VIDU_2026-05-08.md](./VECTOR_ENGINE_AUDIO_GENERATION_SUNO_VIDU_2026-05-08.md):记录视觉小说结果页接入 VectorEngine Suno 文生背景音乐与 Vidu 文生音效的接口、环境变量、后端路由、OSS 资产回写和前端弹层交互边界。
|
||||
- [PROFILE_FEEDBACK_BACKEND_INTEGRATION_2026-05-08.md](./PROFILE_FEEDBACK_BACKEND_INTEGRATION_2026-05-08.md):冻结“我的”页签帮助与反馈入口的后端接入方案,覆盖 `POST /api/profile/feedback`、`profile_feedback_submission`、凭证图片 Data URL 校验和前端预览/提交边界。
|
||||
@@ -163,6 +164,7 @@
|
||||
- [PHONE_AUTH_AXUM_REAL_SMS_PROVIDER_DESIGN_2026-04-22.md](./PHONE_AUTH_AXUM_REAL_SMS_PROVIDER_DESIGN_2026-04-22.md):冻结 Rust `api-server + module-auth + platform-auth` 接入真实阿里云短信 provider 的 crate 边界、发送与校验职责、配置项和错误语义。
|
||||
- [PHONE_SMS_ALIYUN_RESPONSE_FIELD_MAPPING_FIX_2026-04-23.md](./PHONE_SMS_ALIYUN_RESPONSE_FIELD_MAPPING_FIX_2026-04-23.md):记录 Rust `platform-auth` 把阿里云 PascalCase 响应字段误判成空值的问题根因,并冻结字段映射修复与回归标准。
|
||||
- [PHONE_SMS_SEND_CODE_OBSERVABILITY_FIX_2026-04-23.md](./PHONE_SMS_SEND_CODE_OBSERVABILITY_FIX_2026-04-23.md):冻结手机号验证码发送链路的日志补强口径,确保 `api-server`、`module-auth`、`platform-auth` 能直接暴露发送前后与错误分类关键字段。
|
||||
- [PHONE_SMS_PROVIDER_ERROR_HTTP_MAPPING_FIX_2026-05-08.md](./PHONE_SMS_PROVIDER_ERROR_HTTP_MAPPING_FIX_2026-05-08.md):记录真实短信 provider 返回 `UNKNOWN` / `biz.FREQUENCY` 时被误映射成登录 `500` 的根因,冻结 provider 配置错误 `503`、上游失败 `502` 的 HTTP 映射。
|
||||
- [PHONE_SMS_DELIVERY_OBSERVABILITY_AND_RECEIPT_DESIGN_2026-04-22.md](./PHONE_SMS_DELIVERY_OBSERVABILITY_AND_RECEIPT_DESIGN_2026-04-22.md):冻结短信平台受理成功与最终送达状态的区分方式、追踪字段、送达回执接口和前端提示文案边界。
|
||||
- [PHONE_SMS_REAL_PROVIDER_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md](./PHONE_SMS_REAL_PROVIDER_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md):冻结验证清单第一项“真实短信验证码链路”的本地启动、前端操作、日志观察点、通过标准与失败排查步骤。
|
||||
- [ASSET_EXTERNAL_GENERATION_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md](./ASSET_EXTERNAL_GENERATION_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md):冻结验证清单第四项“图片、视频、动作真实外部生成”的人工联调口径,明确哪些入口已接真实外部图片服务、哪些入口仍是 Stage 1 占位链,以及前端点击路径、日志观察点和通过标准。
|
||||
|
||||
@@ -3269,6 +3269,31 @@ mod tests {
|
||||
assert_eq!(login_response.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn phone_auth_sms_provider_errors_keep_upstream_http_semantics() {
|
||||
let invalid_config = crate::phone_auth::map_phone_auth_error(
|
||||
module_auth::PhoneAuthError::SmsProviderInvalidConfig(
|
||||
"阿里云短信 AccessKeyId 未配置".to_string(),
|
||||
),
|
||||
);
|
||||
assert_eq!(
|
||||
invalid_config.status_code(),
|
||||
StatusCode::SERVICE_UNAVAILABLE
|
||||
);
|
||||
assert_eq!(invalid_config.message(), "阿里云短信 AccessKeyId 未配置");
|
||||
|
||||
let upstream = crate::phone_auth::map_phone_auth_error(
|
||||
module_auth::PhoneAuthError::SmsProviderUpstream(
|
||||
"短信验证码发送失败:check frequency failed".to_string(),
|
||||
),
|
||||
);
|
||||
assert_eq!(upstream.status_code(), StatusCode::BAD_GATEWAY);
|
||||
assert_eq!(
|
||||
upstream.message(),
|
||||
"短信验证码发送失败:check frequency failed"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn wechat_start_returns_mock_callback_url_with_state() {
|
||||
let config = AppConfig {
|
||||
|
||||
@@ -320,6 +320,12 @@ pub fn map_phone_auth_error(error: PhoneAuthError) -> AppError {
|
||||
PhoneAuthError::UserNotFound => {
|
||||
AppError::from_status(StatusCode::UNAUTHORIZED).with_message(error.to_string())
|
||||
}
|
||||
PhoneAuthError::SmsProviderInvalidConfig(_) => {
|
||||
AppError::from_status(StatusCode::SERVICE_UNAVAILABLE).with_message(error.to_string())
|
||||
}
|
||||
PhoneAuthError::SmsProviderUpstream(_) => {
|
||||
AppError::from_status(StatusCode::BAD_GATEWAY).with_message(error.to_string())
|
||||
}
|
||||
PhoneAuthError::Store(_) | PhoneAuthError::PasswordHash(_) => {
|
||||
map_phone_auth_platform_store_error(error.to_string())
|
||||
}
|
||||
|
||||
@@ -396,6 +396,12 @@ fn map_wechat_bind_phone_error(error: module_auth::PhoneAuthError) -> AppError {
|
||||
module_auth::PhoneAuthError::UserNotFound => {
|
||||
AppError::from_status(StatusCode::UNAUTHORIZED).with_message(error.to_string())
|
||||
}
|
||||
module_auth::PhoneAuthError::SmsProviderInvalidConfig(_) => {
|
||||
AppError::from_status(StatusCode::SERVICE_UNAVAILABLE).with_message(error.to_string())
|
||||
}
|
||||
module_auth::PhoneAuthError::SmsProviderUpstream(_) => {
|
||||
AppError::from_status(StatusCode::BAD_GATEWAY).with_message(error.to_string())
|
||||
}
|
||||
module_auth::PhoneAuthError::Store(_) | module_auth::PhoneAuthError::PasswordHash(_) => {
|
||||
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_message(error.to_string())
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ pub enum PhoneAuthError {
|
||||
VerifyAttemptsExceeded,
|
||||
UserNotFound,
|
||||
UserStateMismatch,
|
||||
SmsProviderInvalidConfig(String),
|
||||
SmsProviderUpstream(String),
|
||||
Store(String),
|
||||
PasswordHash(String),
|
||||
}
|
||||
@@ -88,6 +90,9 @@ impl fmt::Display for PhoneAuthError {
|
||||
Self::VerifyAttemptsExceeded => f.write_str("验证码错误次数过多,请重新获取验证码"),
|
||||
Self::UserNotFound => f.write_str("用户不存在"),
|
||||
Self::UserStateMismatch => f.write_str("当前账号状态不允许执行该操作"),
|
||||
Self::SmsProviderInvalidConfig(message) | Self::SmsProviderUpstream(message) => {
|
||||
f.write_str(message)
|
||||
}
|
||||
Self::Store(message) | Self::PasswordHash(message) => f.write_str(message),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1862,9 +1862,10 @@ impl InMemoryAuthStore {
|
||||
fn map_sms_provider_error_to_phone_error(error: SmsProviderError) -> PhoneAuthError {
|
||||
match error {
|
||||
SmsProviderError::InvalidVerifyCode => PhoneAuthError::InvalidVerifyCode,
|
||||
SmsProviderError::InvalidConfig(message) | SmsProviderError::Upstream(message) => {
|
||||
PhoneAuthError::Store(message)
|
||||
SmsProviderError::InvalidConfig(message) => {
|
||||
PhoneAuthError::SmsProviderInvalidConfig(message)
|
||||
}
|
||||
SmsProviderError::Upstream(message) => PhoneAuthError::SmsProviderUpstream(message),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -330,6 +330,42 @@ test('auth gate opens a login modal for protected actions and resumes after logi
|
||||
expect(screen.queryByRole('dialog', { name: '账号入口' })).toBeNull();
|
||||
});
|
||||
|
||||
test('phone login result is not overwritten by an older guest hydrate', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onAuthenticated = vi.fn();
|
||||
authMocks.getAuthLoginOptions.mockResolvedValue({
|
||||
availableLoginMethods: ['phone'],
|
||||
});
|
||||
authMocks.getCurrentAuthUser
|
||||
.mockResolvedValueOnce({
|
||||
user: null,
|
||||
availableLoginMethods: ['phone'],
|
||||
})
|
||||
.mockResolvedValue({
|
||||
user: mockUser,
|
||||
availableLoginMethods: ['phone'],
|
||||
});
|
||||
|
||||
render(
|
||||
<AuthGate>
|
||||
<ProtectedActionButton onAuthenticated={onAuthenticated} />
|
||||
<LogoutStateProbe />
|
||||
</AuthGate>,
|
||||
);
|
||||
|
||||
await user.click(await screen.findByRole('button', { name: '进入作品' }));
|
||||
const dialog = screen.getByRole('dialog', { name: '账号入口' });
|
||||
await user.type(within(dialog).getByLabelText('手机号'), '13800000000');
|
||||
await user.type(within(dialog).getByLabelText('验证码'), '123456');
|
||||
await user.click(within(dialog).getByRole('button', { name: '登录' }));
|
||||
|
||||
expect(await screen.findByText('当前用户:测试玩家')).toBeTruthy();
|
||||
expect(onAuthenticated).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(screen.getByText('当前用户:测试玩家')).toBeTruthy();
|
||||
expect(screen.queryByRole('dialog', { name: '账号入口' })).toBeNull();
|
||||
});
|
||||
|
||||
test('auth gate hides register entry and opens invite modal for new sms account', async () => {
|
||||
const user = userEvent.setup();
|
||||
window.history.replaceState(null, '', '/?inviteCode=spring-2026');
|
||||
|
||||
@@ -120,6 +120,7 @@ export function AuthGate({ children }: AuthGateProps) {
|
||||
const pendingProtectedActionRef = useRef<(() => void) | null>(null);
|
||||
const autoOpenedInviteCodeRef = useRef<string | null>(null);
|
||||
const hasRenderedPlatformContentRef = useRef(false);
|
||||
const authHydrateVersionRef = useRef(0);
|
||||
const canKeepPlatformContentMounted =
|
||||
hasRenderedPlatformContentRef.current &&
|
||||
(status === 'checking' || status === 'recovering');
|
||||
@@ -134,6 +135,7 @@ export function AuthGate({ children }: AuthGateProps) {
|
||||
|
||||
const activateReadyUser = useCallback((nextUser: AuthUser) => {
|
||||
// 受保护业务 hook 只在 readyUser 暴露后启动,必须先保证请求层能带 Bearer token。
|
||||
authHydrateVersionRef.current += 1;
|
||||
setUser(nextUser);
|
||||
setStatus('ready');
|
||||
}, []);
|
||||
@@ -141,6 +143,7 @@ export function AuthGate({ children }: AuthGateProps) {
|
||||
const clearLocalAuthenticatedState = useCallback(() => {
|
||||
// 退出动作必须先收回前端鉴权上下文,再等待后端吊销完成。
|
||||
// 否则平台壳层会在无刷新状态下继续暴露旧用户的私有作品缓存。
|
||||
authHydrateVersionRef.current += 1;
|
||||
pendingProtectedActionRef.current = null;
|
||||
setUser(null);
|
||||
setStatus('unauthenticated');
|
||||
@@ -268,11 +271,13 @@ export function AuthGate({ children }: AuthGateProps) {
|
||||
useEffect(() => {
|
||||
let isActive = true;
|
||||
|
||||
const hydrate = async () => {
|
||||
const hydrate = async (hydrateToken: number) => {
|
||||
const isCurrentHydrate = () =>
|
||||
isActive && hydrateToken === authHydrateVersionRef.current;
|
||||
const callbackResult = consumeAuthCallbackResult();
|
||||
const loadLoginOptions = async () => {
|
||||
const options = await getAuthLoginOptions();
|
||||
if (!isActive) {
|
||||
if (!isCurrentHydrate()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -285,14 +290,14 @@ export function AuthGate({ children }: AuthGateProps) {
|
||||
const resolveGuestFallback = async () => {
|
||||
try {
|
||||
await loadLoginOptions();
|
||||
if (!isActive) {
|
||||
if (!isCurrentHydrate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setUser(null);
|
||||
setStatus('unauthenticated');
|
||||
} catch (optionsError) {
|
||||
if (!isActive) {
|
||||
if (!isCurrentHydrate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -305,7 +310,7 @@ export function AuthGate({ children }: AuthGateProps) {
|
||||
}
|
||||
};
|
||||
|
||||
if (callbackResult?.error && isActive) {
|
||||
if (callbackResult?.error && isCurrentHydrate()) {
|
||||
setError(callbackResult.error);
|
||||
setShowLoginModal(true);
|
||||
}
|
||||
@@ -315,8 +320,11 @@ export function AuthGate({ children }: AuthGateProps) {
|
||||
// 后端只在 refresh/session 成功续期时写每日登录埋点;如果本地 access token 尚未过期,
|
||||
// 仅调用 /auth/me 不会进入续期链路,导致“打开网页”没有登录埋点。
|
||||
await refreshStoredAccessToken();
|
||||
if (!isCurrentHydrate()) {
|
||||
return;
|
||||
}
|
||||
const nextSession = await getCurrentAuthUser();
|
||||
if (!isActive) {
|
||||
if (!isCurrentHydrate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -339,7 +347,7 @@ export function AuthGate({ children }: AuthGateProps) {
|
||||
);
|
||||
setError(callbackResult?.error ?? '');
|
||||
} catch {
|
||||
if (!isActive) {
|
||||
if (!isCurrentHydrate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -347,11 +355,11 @@ export function AuthGate({ children }: AuthGateProps) {
|
||||
}
|
||||
};
|
||||
|
||||
void hydrate();
|
||||
void hydrate(++authHydrateVersionRef.current);
|
||||
|
||||
const handleAuthStateChange = () => {
|
||||
setStatus('checking');
|
||||
void hydrate();
|
||||
void hydrate(++authHydrateVersionRef.current);
|
||||
};
|
||||
|
||||
window.addEventListener(AUTH_STATE_EVENT, handleAuthStateChange);
|
||||
|
||||
Reference in New Issue
Block a user