修复游戏详情匿名读取导致已购买用户看不到游玩入口
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
- gameDistributionClient.getGame 改用「可选鉴权」:已登录时带 Authorization: Bearer,未登录按匿名请求且不补票、不因 401 影响全局登录态 - 公开详情响应随查看者变化,读取加 cache: no-store,避免缓存串用他人购买态 - listGames 与评价等公开列表继续走跳过鉴权的匿名读取,行为不变 - 详情页在登录身份变化时重读公开详情,登录后不会再停在匿名的未购买结论 - 游玩页补充播放会话 401 的可读提示(未登录进付费作品) - 补测试:详情已登录带 bearer / 未登录匿名且只发一次请求、列表不带 bearer、付费已购买显示立即玩并可进入游玩页、已购买且公开入口仍在时仍走播放会话、未登录直接进游玩页不挂载 iframe
This commit is contained in:
@@ -84,6 +84,56 @@ describe('gameDistributionClient', () => {
|
||||
expect(await getGame('')).toBeNull();
|
||||
});
|
||||
|
||||
it('详情可选鉴权:已登录带 bearer,未登录匿名且不补票', async () => {
|
||||
const fetchMock = vi.fn().mockImplementation(() =>
|
||||
Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
id: 'game-1',
|
||||
title: '星轨防线',
|
||||
priceMudPoints: 120,
|
||||
purchased: false,
|
||||
}),
|
||||
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
||||
),
|
||||
),
|
||||
);
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
|
||||
// 未登录:匿名读取,且不为了补票多发一次 refresh 请求。
|
||||
await getGame('game-1');
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
expect(fetchMock.mock.calls[0]?.[0]).toBe(
|
||||
'/api/game-distribution/games/game-1',
|
||||
);
|
||||
expect(fetchMock.mock.calls[0]?.[1]?.headers?.Authorization).toBeUndefined();
|
||||
|
||||
// 已登录:带上 bearer,服务端才能算真实购买态与付费入口。
|
||||
setStoredAccessToken('viewer-token', { emit: false });
|
||||
await getGame('game-1');
|
||||
expect(fetchMock).toHaveBeenCalledTimes(2);
|
||||
expect(fetchMock.mock.calls[1]?.[1]?.headers?.Authorization).toBe(
|
||||
'Bearer viewer-token',
|
||||
);
|
||||
});
|
||||
|
||||
it('目录列表保持匿名公开读取,不带 bearer', async () => {
|
||||
setStoredAccessToken('viewer-token', { emit: false });
|
||||
const fetchMock = vi.fn().mockImplementation(() =>
|
||||
Promise.resolve(
|
||||
new Response(JSON.stringify({ games: [] }), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}),
|
||||
),
|
||||
);
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
|
||||
await listGames();
|
||||
|
||||
expect(fetchMock.mock.calls[0]?.[1]?.headers?.Authorization).toBeUndefined();
|
||||
});
|
||||
|
||||
it('游玩上报携带稳定的匿名 clientId,且不携带未登录 bearer', async () => {
|
||||
const fetchMock = vi.fn().mockImplementation(() =>
|
||||
Promise.resolve(
|
||||
|
||||
@@ -73,6 +73,18 @@ const PUBLIC_GAME_REQUEST_OPTIONS: ApiRequestOptions = {
|
||||
clearAuthOnUnauthorized: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* 公开读接口的「可选鉴权」。
|
||||
*
|
||||
* 已登录时公共封装配上 Bearer,服务端据此算真实购买态与是否下发付费入口;未登录时按匿名请求,
|
||||
* 不主动补票、也不因 401 影响全局登录态——游客浏览详情不会被要求登录。
|
||||
*/
|
||||
const OPTIONAL_AUTH_REQUEST_OPTIONS: ApiRequestOptions = {
|
||||
skipRefresh: true,
|
||||
notifyAuthStateChange: false,
|
||||
clearAuthOnUnauthorized: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* 游玩上报是后台尽力而为的请求:带上已登录 bearer(后端据此按 userId 去重),但即使遇到 401
|
||||
* 也不刷新会话、不改动全局登录态——直接复用后台请求已有的同一份鉴权降级策略。
|
||||
@@ -308,14 +320,20 @@ export async function unpublishGame(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取游戏公开详情。
|
||||
*
|
||||
* 走「可选鉴权」:公开资料对所有人可见,但已登录时必须带上身份,否则服务端算不出 `purchased`,
|
||||
* 已购买用户也会拿到空的付费入口。
|
||||
*/
|
||||
export async function getGame(gameId: string) {
|
||||
const normalizedId = gameId.trim();
|
||||
if (!normalizedId) return null;
|
||||
return requestJson<GameDistributionGame>(
|
||||
`/games/${encodeURIComponent(normalizedId)}`,
|
||||
{ method: 'GET' },
|
||||
{ method: 'GET', cache: 'no-store' },
|
||||
'读取游戏详情失败',
|
||||
PUBLIC_GAME_REQUEST_OPTIONS,
|
||||
OPTIONAL_AUTH_REQUEST_OPTIONS,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user