fix: remove recommend login gate
This commit is contained in:
@@ -115,6 +115,7 @@ import { resolveWorkNotFoundRecoveryAction } from '../../routing/runtimeNotFound
|
||||
import {
|
||||
ApiClientError,
|
||||
BACKGROUND_AUTH_REQUEST_OPTIONS,
|
||||
getStoredAccessToken,
|
||||
} from '../../services/apiClient';
|
||||
import {
|
||||
ensureRuntimeGuestToken,
|
||||
@@ -559,6 +560,25 @@ async function buildRecommendRuntimeGuestOptions() {
|
||||
runtimeGuestToken: token,
|
||||
};
|
||||
}
|
||||
function shouldUseRecommendRuntimeGuestAuth(
|
||||
authUi: { user?: { id?: string } | null } | null | undefined,
|
||||
) {
|
||||
return !authUi?.user?.id?.trim() && !getStoredAccessToken();
|
||||
}
|
||||
async function buildRecommendRuntimeAuthOptions(
|
||||
authUi: { user?: { id?: string } | null } | null | undefined,
|
||||
embedded?: boolean,
|
||||
) {
|
||||
if (!embedded) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (shouldUseRecommendRuntimeGuestAuth(authUi)) {
|
||||
return buildRecommendRuntimeGuestOptions();
|
||||
}
|
||||
|
||||
return RECOMMEND_RUNTIME_BACKGROUND_AUTH_OPTIONS;
|
||||
}
|
||||
const PUZZLE_DRAFT_GENERATION_POINT_COST = 2;
|
||||
const MATCH3D_DRAFT_GENERATION_POINT_COST = 10;
|
||||
const BARK_BATTLE_DRAFT_GENERATION_POINT_COST = 3;
|
||||
@@ -7386,9 +7406,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
profileId: targetProfileId,
|
||||
mode: 'play' as const,
|
||||
};
|
||||
const runtimeGuestOptions = options.embedded
|
||||
? await buildRecommendRuntimeGuestOptions()
|
||||
: {};
|
||||
const runtimeGuestOptions = await buildRecommendRuntimeAuthOptions(
|
||||
authUi,
|
||||
options.embedded,
|
||||
);
|
||||
const { run } = options.embedded
|
||||
? await startVisualNovelRun(
|
||||
targetProfileId,
|
||||
@@ -7419,6 +7440,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
}
|
||||
},
|
||||
[
|
||||
authUi,
|
||||
resolvePuzzleErrorMessage,
|
||||
setIsVisualNovelBusy,
|
||||
setSelectionStage,
|
||||
@@ -7442,7 +7464,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
try {
|
||||
const runtimeGuestOptions =
|
||||
activeRecommendRuntimeKind === 'visual-novel'
|
||||
? await buildRecommendRuntimeGuestOptions()
|
||||
? await buildRecommendRuntimeAuthOptions(authUi, true)
|
||||
: {};
|
||||
const nextRun = await streamVisualNovelRuntimeAction(
|
||||
visualNovelRun.runId,
|
||||
@@ -7460,6 +7482,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
},
|
||||
[
|
||||
activeRecommendRuntimeKind,
|
||||
authUi,
|
||||
isVisualNovelBusy,
|
||||
resolvePuzzleErrorMessage,
|
||||
setIsVisualNovelBusy,
|
||||
@@ -7868,9 +7891,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
setJumpHopError(null);
|
||||
setJumpHopRuntimeReturnStage(options.returnStage ?? 'work-detail');
|
||||
try {
|
||||
const runtimeGuestOptions = options.embedded
|
||||
? await buildRecommendRuntimeGuestOptions()
|
||||
: {};
|
||||
const runtimeGuestOptions = await buildRecommendRuntimeAuthOptions(
|
||||
authUi,
|
||||
options.embedded,
|
||||
);
|
||||
const [detail, runResponse] = await Promise.all([
|
||||
jumpHopClient.getWorkDetail(normalizedProfileId).catch(() => null),
|
||||
jumpHopClient.startRun(normalizedProfileId, runtimeGuestOptions),
|
||||
@@ -7898,7 +7922,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
setIsJumpHopBusy(false);
|
||||
}
|
||||
},
|
||||
[setSelectionStage],
|
||||
[authUi, setSelectionStage],
|
||||
);
|
||||
|
||||
const restartJumpHopRuntimeRun = useCallback(async () => {
|
||||
@@ -8205,9 +8229,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
setWoodenFishError(null);
|
||||
setWoodenFishRuntimeReturnStage(options.returnStage ?? 'work-detail');
|
||||
try {
|
||||
const runtimeGuestOptions = options.embedded
|
||||
? await buildRecommendRuntimeGuestOptions()
|
||||
: {};
|
||||
const runtimeGuestOptions = await buildRecommendRuntimeAuthOptions(
|
||||
authUi,
|
||||
options.embedded,
|
||||
);
|
||||
const [detail, runResponse] = await Promise.all([
|
||||
woodenFishClient.getWorkDetail(normalizedProfileId).catch(() => null),
|
||||
options.embedded
|
||||
@@ -8237,7 +8262,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
setIsWoodenFishBusy(false);
|
||||
}
|
||||
},
|
||||
[setSelectionStage],
|
||||
[authUi, setSelectionStage],
|
||||
);
|
||||
|
||||
const checkpointWoodenFishRuntimeRun = useCallback(
|
||||
@@ -8640,12 +8665,14 @@ export function PlatformEntryFlowShellImpl({
|
||||
profileId: item.profileId,
|
||||
levelId: levelId ?? null,
|
||||
};
|
||||
const runtimeGuestOptions = options.embedded
|
||||
const canUseRuntimeGuestAuth =
|
||||
options.embedded || options.authMode === 'isolated';
|
||||
const useRuntimeGuestAuth =
|
||||
canUseRuntimeGuestAuth && shouldUseRecommendRuntimeGuestAuth(authUi);
|
||||
const runtimeGuestOptions = useRuntimeGuestAuth
|
||||
? await buildRecommendRuntimeGuestOptions()
|
||||
: {};
|
||||
const authMode = options.embedded
|
||||
? 'isolated'
|
||||
: (options.authMode ?? 'default');
|
||||
const authMode = useRuntimeGuestAuth ? 'isolated' : 'default';
|
||||
const { run } =
|
||||
authMode === 'isolated'
|
||||
? await startPuzzleRun(startRunPayload, runtimeGuestOptions)
|
||||
@@ -8692,6 +8719,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
},
|
||||
[
|
||||
isPuzzleBusy,
|
||||
authUi,
|
||||
resolvePuzzleErrorMessage,
|
||||
setIsPuzzleBusy,
|
||||
setPuzzleError,
|
||||
@@ -8744,9 +8772,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
runtimeProfile.generatedBackgroundAsset,
|
||||
{ expireSeconds: 300 },
|
||||
);
|
||||
const runtimeGuestOptions = options.embedded
|
||||
? await buildRecommendRuntimeGuestOptions()
|
||||
: {};
|
||||
const runtimeGuestOptions = await buildRecommendRuntimeAuthOptions(
|
||||
authUi,
|
||||
options.embedded,
|
||||
);
|
||||
const runtimeOptions = {
|
||||
...runtimeGuestOptions,
|
||||
...(typeof options.itemTypeCountOverride === 'number'
|
||||
@@ -8793,6 +8822,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
},
|
||||
[
|
||||
isMatch3DBusy,
|
||||
authUi,
|
||||
match3dFlow,
|
||||
match3dRuntimeAdapter,
|
||||
resolveMatch3DErrorMessage,
|
||||
@@ -8816,9 +8846,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
setSquareHoleError(null);
|
||||
|
||||
try {
|
||||
const runtimeGuestOptions = options.embedded
|
||||
? await buildRecommendRuntimeGuestOptions()
|
||||
: {};
|
||||
const runtimeGuestOptions = await buildRecommendRuntimeAuthOptions(
|
||||
authUi,
|
||||
options.embedded,
|
||||
);
|
||||
const { run } = options.embedded
|
||||
? await startSquareHoleRun(profile.profileId, runtimeGuestOptions)
|
||||
: await startSquareHoleRun(profile.profileId);
|
||||
@@ -8852,6 +8883,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
},
|
||||
[
|
||||
isSquareHoleBusy,
|
||||
authUi,
|
||||
resolveSquareHoleErrorMessage,
|
||||
setSelectionStage,
|
||||
setSquareHoleError,
|
||||
@@ -8974,7 +9006,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
try {
|
||||
const runtimeGuestOptions =
|
||||
activeRecommendRuntimeKind === 'big-fish'
|
||||
? await buildRecommendRuntimeGuestOptions()
|
||||
? await buildRecommendRuntimeAuthOptions(authUi, true)
|
||||
: {};
|
||||
const { run } = await submitBigFishRuntimeInput(
|
||||
bigFishRun.runId,
|
||||
@@ -8992,6 +9024,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
},
|
||||
[
|
||||
activeRecommendRuntimeKind,
|
||||
authUi,
|
||||
bigFishRun,
|
||||
resolveBigFishErrorMessage,
|
||||
setBigFishError,
|
||||
@@ -9008,10 +9041,9 @@ export function PlatformEntryFlowShellImpl({
|
||||
setBigFishRuntimeStartedAt(null);
|
||||
const reportPromise =
|
||||
activeRecommendRuntimeKind === 'big-fish'
|
||||
? recordBigFishPlay(
|
||||
sessionId,
|
||||
{ elapsedMs },
|
||||
RECOMMEND_RUNTIME_BACKGROUND_AUTH_OPTIONS,
|
||||
? buildRecommendRuntimeAuthOptions(authUi, true).then(
|
||||
(runtimeAuthOptions) =>
|
||||
recordBigFishPlay(sessionId, { elapsedMs }, runtimeAuthOptions),
|
||||
)
|
||||
: recordBigFishPlay(sessionId, { elapsedMs });
|
||||
void reportPromise.catch((error) => {
|
||||
@@ -9021,6 +9053,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
});
|
||||
}, [
|
||||
activeRecommendRuntimeKind,
|
||||
authUi,
|
||||
bigFishRun?.sessionId,
|
||||
bigFishRuntimeStartedAt,
|
||||
resolveBigFishErrorMessage,
|
||||
@@ -11315,9 +11348,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
setBigFishRuntimeReturnStage(returnStage);
|
||||
setBigFishRun(null);
|
||||
try {
|
||||
const runtimeGuestOptions = options.embedded
|
||||
? await buildRecommendRuntimeGuestOptions()
|
||||
: {};
|
||||
const runtimeGuestOptions = await buildRecommendRuntimeAuthOptions(
|
||||
authUi,
|
||||
options.embedded,
|
||||
);
|
||||
const { run } = options.embedded
|
||||
? await startBigFishRuntimeRun(sessionId, runtimeGuestOptions)
|
||||
: await startBigFishRuntimeRun(sessionId);
|
||||
@@ -11345,7 +11379,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
return false;
|
||||
}
|
||||
},
|
||||
[bigFishFlow, resolveBigFishErrorMessage, setBigFishError, setSelectionStage],
|
||||
[authUi, bigFishFlow, resolveBigFishErrorMessage, setBigFishError, setSelectionStage],
|
||||
);
|
||||
|
||||
const startBarkBattleRunFromWork = useCallback(
|
||||
@@ -11365,9 +11399,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
setBarkBattlePublishedConfig(mapBarkBattleWorkToPublishedConfig(item));
|
||||
setBarkBattleRuntimeReturnStage(returnStage);
|
||||
try {
|
||||
const runtimeGuestOptions = options.embedded
|
||||
? await buildRecommendRuntimeGuestOptions()
|
||||
: {};
|
||||
const runtimeGuestOptions = await buildRecommendRuntimeAuthOptions(
|
||||
authUi,
|
||||
options.embedded,
|
||||
);
|
||||
const runResponse = options.embedded
|
||||
? await startBarkBattleRun(item.workId, {}, runtimeGuestOptions)
|
||||
: await startBarkBattleRun(item.workId);
|
||||
@@ -11390,7 +11425,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
return false;
|
||||
}
|
||||
},
|
||||
[resolveBarkBattleErrorMessage, setSelectionStage],
|
||||
[authUi, resolveBarkBattleErrorMessage, setSelectionStage],
|
||||
);
|
||||
|
||||
const startSelectedPublicWork = useCallback(() => {
|
||||
|
||||
@@ -6121,11 +6121,52 @@ test('home recommendation starts embedded puzzle without global auth reset on lo
|
||||
profileId: 'puzzle-profile-public-1',
|
||||
levelId: null,
|
||||
},
|
||||
ISOLATED_RUNTIME_AUTH_OPTIONS,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('home recommendation keeps logged-in puzzle start on default auth instead of guest token', async () => {
|
||||
const publishedPuzzleWork = {
|
||||
workId: 'puzzle-work-public-2',
|
||||
profileId: 'puzzle-profile-public-2',
|
||||
ownerUserId: 'user-2',
|
||||
sourceSessionId: 'puzzle-session-public-2',
|
||||
authorDisplayName: '拼图作者',
|
||||
levelName: '星桥机关',
|
||||
summary: '旋转碎片并接通星桥机关。',
|
||||
themeTags: ['机关', '星桥'],
|
||||
coverImageSrc: null,
|
||||
coverAssetId: null,
|
||||
publicationStatus: 'published',
|
||||
updatedAt: '2026-04-25T09:00:00.000Z',
|
||||
publishedAt: '2026-04-25T09:00:00.000Z',
|
||||
playCount: 3,
|
||||
likeCount: 0,
|
||||
publishReady: true,
|
||||
} satisfies PuzzleWorkSummary;
|
||||
|
||||
vi.mocked(listPuzzleGallery).mockResolvedValue({
|
||||
items: [publishedPuzzleWork],
|
||||
});
|
||||
vi.mocked(getPuzzleGalleryDetail).mockResolvedValue({
|
||||
item: publishedPuzzleWork,
|
||||
});
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(startPuzzleRun).toHaveBeenCalledWith(
|
||||
{
|
||||
profileId: 'puzzle-profile-public-2',
|
||||
levelId: null,
|
||||
},
|
||||
);
|
||||
});
|
||||
expect(vi.mocked(startPuzzleRun).mock.calls[0]?.[1]).not.toEqual(
|
||||
ISOLATED_RUNTIME_AUTH_OPTIONS,
|
||||
);
|
||||
});
|
||||
|
||||
test('home recommendation Match3D runtime keeps profile generated models when card summary is stale', async () => {
|
||||
const match3dCard: Match3DWorkSummary = {
|
||||
workId: 'match3d-work-card-1',
|
||||
@@ -7135,7 +7176,6 @@ test('formal puzzle runtime uses frontend move merge logic and backend leaderboa
|
||||
profileId: 'puzzle-profile-public-1',
|
||||
levelId: null,
|
||||
},
|
||||
ISOLATED_RUNTIME_AUTH_OPTIONS,
|
||||
);
|
||||
vi.mocked(listProfileSaveArchives).mockClear();
|
||||
vi.mocked(listProfileSaveArchives).mockRejectedValueOnce(
|
||||
@@ -7159,7 +7199,6 @@ test('formal puzzle runtime uses frontend move merge logic and backend leaderboa
|
||||
elapsedMs: 18_000,
|
||||
nickname: '测试玩家',
|
||||
},
|
||||
ISOLATED_RUNTIME_AUTH_OPTIONS,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -7180,7 +7219,6 @@ test('formal puzzle runtime uses frontend move merge logic and backend leaderboa
|
||||
expect(advancePuzzleNextLevel).toHaveBeenCalledWith(
|
||||
clearedFirstLevel.runId,
|
||||
{},
|
||||
ISOLATED_RUNTIME_AUTH_OPTIONS,
|
||||
);
|
||||
});
|
||||
expect(
|
||||
@@ -7343,7 +7381,6 @@ test('formal puzzle similar work keeps current run level progression', async ()
|
||||
expect(advancePuzzleNextLevel).toHaveBeenCalledWith(
|
||||
clearedThirdLevel.runId,
|
||||
{ targetProfileId: 'puzzle-profile-similar-2' },
|
||||
ISOLATED_RUNTIME_AUTH_OPTIONS,
|
||||
);
|
||||
});
|
||||
expect(startPuzzleRun).not.toHaveBeenCalled();
|
||||
@@ -7527,7 +7564,6 @@ test('recommend puzzle remix return restarts recommendation instead of stale loa
|
||||
profileId: 'puzzle-profile-public-1',
|
||||
levelId: null,
|
||||
},
|
||||
ISOLATED_RUNTIME_AUTH_OPTIONS,
|
||||
);
|
||||
});
|
||||
expect(screen.queryByText('正在进入拼图关卡')).toBeNull();
|
||||
|
||||
@@ -2697,7 +2697,7 @@ test('logged out mobile shell defaults to discover tab', () => {
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
test('logged out recommend tab opens login modal and shows cover only', async () => {
|
||||
test('logged out recommend tab enters runtime without login modal', async () => {
|
||||
const user = userEvent.setup();
|
||||
const { container, openLoginModal } = renderStatefulLoggedOutHomeView({
|
||||
latestEntries: [puzzlePublicEntry],
|
||||
@@ -2712,20 +2712,18 @@ test('logged out recommend tab opens login modal and shows cover only', async ()
|
||||
within(bottomNav as HTMLElement).getByRole('button', { name: '推荐' }),
|
||||
);
|
||||
|
||||
expect(openLoginModal).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
container.querySelector('.platform-recommend-cover-only'),
|
||||
).toBeTruthy();
|
||||
expect(openLoginModal).not.toHaveBeenCalled();
|
||||
expect(container.querySelector('.platform-recommend-cover-only')).toBeNull();
|
||||
expect(container.querySelector('.platform-mobile-topbar')).toBeNull();
|
||||
expect(
|
||||
container.querySelector('.platform-mobile-entry-shell--recommend'),
|
||||
).toBeTruthy();
|
||||
expect(screen.queryByTestId('recommend-runtime')).toBeNull();
|
||||
expect(screen.queryByLabelText('奇幻拼图 作品信息')).toBeNull();
|
||||
expect(screen.getByTestId('recommend-runtime')).toBeTruthy();
|
||||
expect(screen.getByLabelText('奇幻拼图 作品信息')).toBeTruthy();
|
||||
expect(screen.getAllByText('奇幻拼图').length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('logged out recommend cover opens login modal again', async () => {
|
||||
test('logged out recommend page keeps runtime visible without login gate', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onOpenGalleryDetail = vi.fn();
|
||||
const { openLoginModal } = renderStatefulLoggedOutHomeView({
|
||||
@@ -2741,12 +2739,9 @@ test('logged out recommend cover opens login modal again', async () => {
|
||||
await user.click(
|
||||
within(bottomNav as HTMLElement).getByRole('button', { name: '推荐' }),
|
||||
);
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: /登录后游玩 奇幻拼图/u }),
|
||||
);
|
||||
|
||||
expect(openLoginModal).toHaveBeenCalledTimes(2);
|
||||
expect(openLoginModal).toHaveBeenLastCalledWith();
|
||||
expect(openLoginModal).not.toHaveBeenCalled();
|
||||
expect(screen.getByTestId('recommend-runtime')).toBeTruthy();
|
||||
expect(onOpenGalleryDetail).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -2780,6 +2775,26 @@ test('logged out recommend page can enter runtime without login gate', () => {
|
||||
expect(onOpenGalleryDetail).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('logged out desktop recommend rail enters runtime without login modal', async () => {
|
||||
mockDesktopLayout();
|
||||
const user = userEvent.setup();
|
||||
const openLoginModal = vi.fn();
|
||||
|
||||
renderLoggedOutHomeView(
|
||||
openLoginModal,
|
||||
{
|
||||
latestEntries: [puzzlePublicEntry],
|
||||
activeRecommendEntryKey: 'puzzle:user-2:puzzle-profile-public-1',
|
||||
},
|
||||
'category',
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '推荐' }));
|
||||
|
||||
expect(openLoginModal).not.toHaveBeenCalled();
|
||||
expect(screen.getByTestId('recommend-runtime')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('logged in recommend page uses gated recommend detail callback', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onOpenGalleryDetail = vi.fn();
|
||||
|
||||
@@ -5372,7 +5372,7 @@ export function RpgEntryHomeView({
|
||||
{recommendRuntimeError}
|
||||
</button>
|
||||
</section>
|
||||
) : isStartingRecommendEntry || !recommendRuntimeContent ? (
|
||||
) : isStartingRecommendEntry ? (
|
||||
<section className="platform-recommend-runtime-panel">
|
||||
<div className="platform-recommend-runtime-state">加载中...</div>
|
||||
</section>
|
||||
@@ -6761,12 +6761,6 @@ export function RpgEntryHomeView({
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isAuthenticated && tab === 'home') {
|
||||
onTabChange(tab);
|
||||
authUi?.openLoginModal();
|
||||
return;
|
||||
}
|
||||
|
||||
onTabChange(tab);
|
||||
}}
|
||||
/>
|
||||
@@ -6924,12 +6918,6 @@ export function RpgEntryHomeView({
|
||||
emphasized={tab === 'create'}
|
||||
showDot={tab === 'saves' && hasUnreadDraftUpdate}
|
||||
onClick={() => {
|
||||
if (!isAuthenticated && tab === 'home') {
|
||||
onTabChange(tab);
|
||||
authUi?.openLoginModal();
|
||||
return;
|
||||
}
|
||||
|
||||
onTabChange(tab);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -351,7 +351,7 @@ export function useRpgEntryBootstrap(
|
||||
!hasInitialAgentSession &&
|
||||
!hasExplicitPlatformTabSelectionRef.current
|
||||
) {
|
||||
// 中文注释:新用户先进入发现页;推荐页只在用户主动点击后作为登录门禁入口。
|
||||
// 中文注释:新用户先进入发现页;推荐页可直接进入,真正受保护的动作再单独做登录门禁。
|
||||
setPlatformTabState(isAuthenticated ? 'home' : 'category');
|
||||
}
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user