Replace QR login with OAuth button; auto-authorize register/bind; follow OA in settings; advisor text styling. Co-authored-by: Cursor <cursoragent@cursor.com>
114 lines
3.4 KiB
TypeScript
114 lines
3.4 KiB
TypeScript
/** 微信公众号 H5 网页授权(注册 / 绑定页无 code 时跳转) */
|
||
|
||
import {
|
||
clearStashedInviteRef,
|
||
readStashedInviteRef,
|
||
stashInviteRef,
|
||
} from '@/utils/inviteRefStorage';
|
||
|
||
export type WeChatH5Page = 'register' | 'bind' | 'login';
|
||
|
||
const DEFAULT_MP_APP_ID = 'wxd5141ba46c856e1f';
|
||
|
||
export function resolveWeChatMpAppId(): string {
|
||
const fromEnv = import.meta.env.VITE_WECHAT_MP_APPID as string | undefined;
|
||
return fromEnv?.trim() || DEFAULT_MP_APP_ID;
|
||
}
|
||
|
||
/** @deprecated 使用 stashInviteRef */
|
||
export function stashWeChatInviteRef(inviteCode?: string | null): void {
|
||
stashInviteRef(inviteCode);
|
||
}
|
||
|
||
/** @deprecated 使用 readStashedInviteRef */
|
||
export function readStashedWeChatInviteRef(): string {
|
||
return readStashedInviteRef();
|
||
}
|
||
|
||
/** @deprecated 使用 clearStashedInviteRef */
|
||
export function clearStashedWeChatInviteRef(): void {
|
||
clearStashedInviteRef();
|
||
}
|
||
|
||
/** 从 OAuth state 解析邀请码(格式 register:CODE) */
|
||
export function parseInviteRefFromOAuthState(state: unknown): string {
|
||
const raw = String(state ?? '').trim();
|
||
const match = raw.match(/^(?:register|bind):(.+)$/i);
|
||
if (!match?.[1]) return '';
|
||
try {
|
||
return decodeURIComponent(match[1]).trim();
|
||
} catch {
|
||
return match[1].trim();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 解析当前页邀请码:query.ref / query.invite → OAuth state → 7 天暂存
|
||
*/
|
||
export function resolveWeChatInviteRef(query: Record<string, unknown>): string {
|
||
const fromQuery = String(query.ref ?? query.invite ?? '').trim();
|
||
if (fromQuery) {
|
||
stashInviteRef(fromQuery);
|
||
return fromQuery;
|
||
}
|
||
const fromState = parseInviteRefFromOAuthState(query.state);
|
||
if (fromState) {
|
||
stashInviteRef(fromState);
|
||
return fromState;
|
||
}
|
||
return readStashedInviteRef();
|
||
}
|
||
|
||
function resolveOAuthRedirectPath(page: WeChatH5Page): string {
|
||
if (page === 'login') {
|
||
return `${window.location.origin}/h5/login`;
|
||
}
|
||
return `${window.location.origin}/wechat/${page}`;
|
||
}
|
||
|
||
/**
|
||
* 跳转微信 OAuth。
|
||
* - register/bind:邀请码写入 state + 7 天暂存
|
||
* - login:可选把登录后回跳路径写入 state(login:/h5/orders)
|
||
*/
|
||
export function redirectToWeChatOAuth(
|
||
page: WeChatH5Page,
|
||
inviteCode?: string | null,
|
||
options?: { afterLoginPath?: string | null },
|
||
): void {
|
||
const appId = resolveWeChatMpAppId();
|
||
const redirectUri = encodeURIComponent(resolveOAuthRedirectPath(page));
|
||
|
||
let state = page;
|
||
if (page === 'login') {
|
||
const after = String(options?.afterLoginPath ?? '').trim();
|
||
if (after.startsWith('/h5')) {
|
||
state = `login:${encodeURIComponent(after)}`;
|
||
}
|
||
} else {
|
||
const code = String(inviteCode ?? '').trim() || readStashedInviteRef();
|
||
if (code) stashInviteRef(code);
|
||
state = code ? `${page}:${encodeURIComponent(code)}` : page;
|
||
}
|
||
|
||
window.location.replace(
|
||
`https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}` +
|
||
`&redirect_uri=${redirectUri}` +
|
||
`&response_type=code&scope=snsapi_userinfo&state=${state}#wechat_redirect`,
|
||
);
|
||
}
|
||
|
||
/** 从 OAuth state 解析登录后回跳路径 */
|
||
export function parseAfterLoginPathFromOAuthState(state: unknown): string {
|
||
const raw = String(state ?? '').trim();
|
||
const match = raw.match(/^login:(.+)$/i);
|
||
if (!match?.[1]) return '';
|
||
try {
|
||
const path = decodeURIComponent(match[1]).trim();
|
||
return path.startsWith('/h5') ? path : '';
|
||
} catch {
|
||
const path = match[1].trim();
|
||
return path.startsWith('/h5') ? path : '';
|
||
}
|
||
}
|