fix: 保留小程序登录上下文

This commit is contained in:
2026-06-05 20:58:40 +08:00
parent 5a6d69bebe
commit 6a03575d68
7 changed files with 112 additions and 17 deletions

View File

@@ -132,7 +132,37 @@ describe('appPageRoutes', () => {
expect(window.location.pathname).toBe('/creation/rpg/result');
expect(window.location.search).toBe(
'?sessionId=session-1&profileId=profile-1&draftId=draft-1&workId=work-1',
'?sessionId=session-1&profileId=profile-1&draftId=draft-1&workId=work-1&clientRuntime=wechat_mini_program',
);
});
it('preserves mini program runtime context while normalizing app paths', () => {
window.history.replaceState(
null,
'',
'/?clientType=mini_program&clientRuntime=wechat_mini_program&miniProgramEnv=trial',
);
pushAppHistoryPath('/');
expect(window.location.pathname).toBe('/');
expect(window.location.search).toBe(
'?clientType=mini_program&clientRuntime=wechat_mini_program&miniProgramEnv=trial',
);
});
it('keeps mini program runtime context when navigating to explicit query routes', () => {
window.history.replaceState(
null,
'',
'/?clientRuntime=wechat_mini_program',
);
pushAppHistoryPath('/works/detail?work=PZ-7A7B18D9');
expect(window.location.pathname).toBe('/works/detail');
expect(window.location.search).toBe(
'?work=PZ-7A7B18D9&clientRuntime=wechat_mini_program',
);
});

View File

@@ -72,6 +72,12 @@ const ROUTE_STAGE_BY_PATH = new Map(
STAGE_ROUTE_ENTRIES.map(([stage, path]) => [path, stage] as const),
) as Map<string, SelectionStage>;
const APP_RUNTIME_CONTEXT_QUERY_KEYS = [
'clientType',
'clientRuntime',
'miniProgramEnv',
] as const;
export function normalizeAppPath(pathname: string) {
const trimmedPathname = pathname.trim().toLowerCase();
@@ -135,13 +141,12 @@ export function isKnownMainAppPagePath(pathname: string) {
export function pushAppHistoryPath(path: string) {
const nextUrl = new URL(path, window.location.origin);
const normalizedPath = normalizeAppPath(nextUrl.pathname);
const nextSearch =
nextUrl.search ||
buildPreservedAppSearch(
window.location.pathname,
normalizedPath,
window.location.search,
);
const nextSearch = buildPreservedAppSearch(
nextUrl.search,
window.location.pathname,
normalizedPath,
window.location.search,
);
const nextRelativeUrl = `${normalizedPath}${nextSearch}`;
const currentRelativeUrl = `${normalizeAppPath(window.location.pathname)}${window.location.search}`;
if (currentRelativeUrl === nextRelativeUrl) {
@@ -153,16 +158,39 @@ export function pushAppHistoryPath(path: string) {
}
function buildPreservedAppSearch(
explicitNextSearch: string,
currentPathname: string,
normalizedPath: string,
search: string,
) {
const preservedParams = new URLSearchParams(explicitNextSearch);
if (
!isCreationRestorePath(normalizedPath) ||
!isSameCreationFlowPath(currentPathname, normalizedPath)
!explicitNextSearch &&
isCreationRestorePath(normalizedPath) &&
isSameCreationFlowPath(currentPathname, normalizedPath)
) {
return '';
const creationParams = new URLSearchParams(
buildCreationUrlSearchFromParams(search),
);
creationParams.forEach((value, key) => {
preservedParams.set(key, value);
});
}
return buildCreationUrlSearchFromParams(search);
const currentParams = new URLSearchParams(search);
// 中文注释:小程序 WebView 依赖这些宿主上下文判断登录和充值通道,不能被前端阶段导航清掉。
APP_RUNTIME_CONTEXT_QUERY_KEYS.forEach((key) => {
if (preservedParams.has(key)) {
return;
}
const value = currentParams.get(key)?.trim();
if (value) {
preservedParams.set(key, value);
}
});
const queryString = preservedParams.toString();
return queryString ? `?${queryString}` : '';
}

View File

@@ -31,6 +31,7 @@ import {
getCurrentAuthUser,
getPublicAuthUserById,
liftAuthRiskBlock,
isWechatMiniProgramWebViewRuntime,
loginWithPhoneCode,
logoutAllAuthSessions,
redeemRegistrationInviteCode,
@@ -80,6 +81,7 @@ function createWindowMock(overrides: Record<string, unknown> = {}) {
describe('authService', () => {
beforeEach(() => {
vi.unstubAllGlobals();
vi.clearAllMocks();
vi.stubGlobal('window', createWindowMock());
clearStoredAccessToken({ emit: false });
@@ -428,6 +430,26 @@ describe('authService', () => {
});
});
it('detects mini program user agent before the WeChat bridge is ready', () => {
vi.stubGlobal('navigator', {
userAgent:
'Mozilla/5.0 iPhone MicroMessenger/8.0.49 NetType/WIFI Language/zh_CN miniProgram',
});
vi.stubGlobal(
'window',
createWindowMock({
location: {
pathname: '/',
hash: '',
search: '',
assign: vi.fn(),
},
}),
);
expect(isWechatMiniProgramWebViewRuntime()).toBe(true);
});
it('waits for an existing WeChat JS SDK script before opening the native auth page', async () => {
const navigateTo = vi.fn((options: { url: string; success?: () => void }) => {
options.success?.();

View File

@@ -90,9 +90,15 @@ export function isWechatMiniProgramWebViewRuntime() {
}
const params = new URLSearchParams(window.location.search || '');
const userAgent =
typeof navigator === 'undefined' ? '' : navigator.userAgent || '';
const normalizedUserAgent = userAgent.toLowerCase();
return (
params.get('clientRuntime') === 'wechat_mini_program' ||
params.get('clientType') === 'mini_program' ||
(normalizedUserAgent.includes('micromessenger') &&
normalizedUserAgent.includes('miniprogram')) ||
Boolean(window.wx?.miniProgram?.postMessage)
);
}