fix: trigger login tracking on session restore

This commit is contained in:
2026-05-08 14:36:56 +08:00
parent 91d993dc6b
commit e694c6605a
5 changed files with 28 additions and 8 deletions

View File

@@ -13,6 +13,7 @@ const authMocks = vi.hoisted(() => ({
authEntry: vi.fn(),
changePassword: vi.fn(),
ensureStoredAccessToken: vi.fn(),
refreshStoredAccessToken: vi.fn(),
getAuthLoginOptions: vi.fn(),
getCurrentAuthUser: vi.fn(),
loginWithPhoneCode: vi.fn(),
@@ -28,6 +29,7 @@ const authMocks = vi.hoisted(() => ({
vi.mock('../../services/apiClient', () => ({
AUTH_STATE_EVENT: 'genarrative-auth-state-changed',
ensureStoredAccessToken: authMocks.ensureStoredAccessToken,
refreshStoredAccessToken: authMocks.refreshStoredAccessToken,
}));
vi.mock('../../services/authService', () => ({
@@ -94,6 +96,7 @@ beforeEach(() => {
window.history.replaceState(null, '', '/');
authMocks.consumeAuthCallbackResult.mockReturnValue(null);
authMocks.ensureStoredAccessToken.mockResolvedValue('jwt-existing-token');
authMocks.refreshStoredAccessToken.mockResolvedValue('jwt-refreshed-token');
authMocks.getCurrentAuthUser.mockResolvedValue({
user: null,
availableLoginMethods: ['phone'],
@@ -204,12 +207,12 @@ test('auth gate keeps platform content visible when phone login is available', a
expect(screen.queryByText('先登录账号,再同步你的冒险进度。')).toBeNull();
});
test('auth gate waits for access token refresh before exposing restored user content', async () => {
test('auth gate waits for refresh cookie rotation before exposing restored user content', async () => {
let resolveToken!: (token: string) => void;
const tokenPromise = new Promise<string>((resolve) => {
resolveToken = resolve;
});
authMocks.ensureStoredAccessToken.mockReturnValue(tokenPromise);
authMocks.refreshStoredAccessToken.mockReturnValue(tokenPromise);
authMocks.getCurrentAuthUser.mockResolvedValue({
user: mockUser,
availableLoginMethods: ['phone'],
@@ -224,10 +227,11 @@ test('auth gate waits for access token refresh before exposing restored user con
expect(screen.getByText('正在校验登录状态...')).toBeTruthy();
expect(authMocks.getCurrentAuthUser).not.toHaveBeenCalled();
resolveToken('jwt-restored-token');
resolveToken('jwt-refreshed-token');
expect(await screen.findByText('应用内容')).toBeTruthy();
expect(authMocks.ensureStoredAccessToken).toHaveBeenCalledTimes(1);
expect(authMocks.refreshStoredAccessToken).toHaveBeenCalledTimes(1);
expect(authMocks.ensureStoredAccessToken).not.toHaveBeenCalled();
expect(authMocks.getCurrentAuthUser).toHaveBeenCalledTimes(1);
});
@@ -440,7 +444,7 @@ test('auth state refresh keeps mounted platform content and local tab state', as
const tokenPromise = new Promise<string>((resolve) => {
resolveToken = resolve;
});
authMocks.ensureStoredAccessToken.mockReturnValueOnce(tokenPromise);
authMocks.refreshStoredAccessToken.mockReturnValueOnce(tokenPromise);
act(() => {
window.dispatchEvent(new Event('genarrative-auth-state-changed'));

View File

@@ -10,7 +10,7 @@ import {
import { useGameSettings } from '../../hooks/useGameSettings';
import {
AUTH_STATE_EVENT,
ensureStoredAccessToken,
refreshStoredAccessToken,
} from '../../services/apiClient';
import {
type AuthAuditLogEntry,
@@ -311,7 +311,10 @@ export function AuthGate({ children }: AuthGateProps) {
}
try {
await ensureStoredAccessToken();
// 中文注释:打开已登录页面也要主动轮换 refresh cookie。
// 后端只在 refresh/session 成功续期时写每日登录埋点;如果本地 access token 尚未过期,
// 仅调用 /auth/me 不会进入续期链路,导致“打开网页”没有登录埋点。
await refreshStoredAccessToken();
const nextSession = await getCurrentAuthUser();
if (!isActive) {
return;

View File

@@ -513,6 +513,10 @@ export async function ensureStoredAccessToken() {
return refreshAccessToken();
}
export async function refreshStoredAccessToken() {
return refreshAccessToken();
}
export async function fetchWithApiAuth(
input: string,
init: RequestInit = {},