553af7ca7a
Move recent session chips below the nav on mobile H5/WeChat with three visible tabs and horizontal swipe, and add a new-chat icon next to notifications. Co-authored-by: Cursor <cursoragent@cursor.com>
150 lines
5.1 KiB
TypeScript
150 lines
5.1 KiB
TypeScript
export function isWechatBrowser(userAgent = navigator.userAgent) {
|
|
const ua = userAgent || '';
|
|
return /MicroMessenger/i.test(ua) || /WindowsWechat/i.test(ua);
|
|
}
|
|
|
|
export function isMobileBrowser(userAgent = navigator.userAgent) {
|
|
return /Android|iPhone|iPad|iPod|Mobile/i.test(userAgent || '');
|
|
}
|
|
|
|
export function isIosBrowser(userAgent = navigator.userAgent) {
|
|
return /iPhone|iPad|iPod/i.test(userAgent || '');
|
|
}
|
|
|
|
/** 手机外部浏览器(Safari / Chrome 等),不在微信内 */
|
|
export function isMobileExternalBrowser(options?: {
|
|
userAgent?: string;
|
|
search?: string;
|
|
serverInWechat?: boolean;
|
|
}) {
|
|
return isMobileBrowser(options?.userAgent) && !isWechatContext(options);
|
|
}
|
|
|
|
/** 构建「在微信中打开」后应访问的链接(带上 from=wechat 以展示一键登录) */
|
|
export function buildPageUrlForWechatOpen(
|
|
search = window.location.search,
|
|
pathname = window.location.pathname,
|
|
) {
|
|
const params = new URLSearchParams(search);
|
|
params.set('from', 'wechat');
|
|
if (!params.get('utm_source')) params.set('utm_source', 'wechat');
|
|
const query = params.toString();
|
|
return `${window.location.origin}${pathname}${query ? `?${query}` : ''}${window.location.hash}`;
|
|
}
|
|
|
|
/** PC 兜底二维码:扫码后直接进入微信 OAuth,手机上会出现授权/确认页 */
|
|
export function buildWechatAuthorizePageUrl(
|
|
search = window.location.search,
|
|
) {
|
|
const params = new URLSearchParams(search);
|
|
const returnTo = params.get('return_to');
|
|
const authPath = buildWechatAuthorizeUrl({
|
|
returnTo: returnTo ?? undefined,
|
|
utmSource: params.get('utm_source') ?? params.get('from') ?? 'wechat',
|
|
utmMedium: params.get('utm_medium') ?? undefined,
|
|
utmCampaign: params.get('utm_campaign') ?? undefined,
|
|
intent: 'login',
|
|
});
|
|
return `${window.location.origin}${authPath}`;
|
|
}
|
|
|
|
export async function copyWechatOpenLink(): Promise<boolean> {
|
|
const url = buildPageUrlForWechatOpen();
|
|
try {
|
|
await navigator.clipboard.writeText(url);
|
|
return true;
|
|
} catch {
|
|
try {
|
|
const input = document.createElement('textarea');
|
|
input.value = url;
|
|
input.setAttribute('readonly', 'true');
|
|
input.style.position = 'fixed';
|
|
input.style.left = '-9999px';
|
|
document.body.appendChild(input);
|
|
input.select();
|
|
const ok = document.execCommand('copy');
|
|
document.body.removeChild(input);
|
|
return ok;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** 从服务号链接进入时带的参数(即使 UA 识别失败也展示微信登录) */
|
|
export function isWechatEntryUrl(search = window.location.search) {
|
|
const params = new URLSearchParams(search);
|
|
const from = (params.get('from') || '').toLowerCase();
|
|
const utm = (params.get('utm_source') || '').toLowerCase();
|
|
return from === 'wechat' || utm === 'wechat';
|
|
}
|
|
|
|
export function isWechatContext(options?: {
|
|
userAgent?: string;
|
|
search?: string;
|
|
serverInWechat?: boolean;
|
|
}) {
|
|
if (options?.serverInWechat) return true;
|
|
if (isWechatBrowser(options?.userAgent)) return true;
|
|
return isWechatEntryUrl(options?.search);
|
|
}
|
|
|
|
/** 手机 H5 或微信内置浏览器:导航会话标签采用下方悬浮条布局 */
|
|
export function isH5OrWechatClient(options?: {
|
|
userAgent?: string;
|
|
search?: string;
|
|
serverInWechat?: boolean;
|
|
}) {
|
|
return isMobileBrowser(options?.userAgent) || isWechatContext(options);
|
|
}
|
|
|
|
export function buildWechatAuthorizeUrl(options?: {
|
|
returnTo?: string;
|
|
utmSource?: string;
|
|
utmMedium?: string;
|
|
utmCampaign?: string;
|
|
intent?: 'login' | 'register' | 'bind';
|
|
authMode?: 'mp' | 'open';
|
|
}) {
|
|
const params = new URLSearchParams();
|
|
const returnTo =
|
|
options?.returnTo ??
|
|
`${window.location.pathname}${window.location.search}${window.location.hash}`;
|
|
if (returnTo && returnTo !== '/') {
|
|
params.set('return_to', returnTo);
|
|
}
|
|
if (options?.utmSource) params.set('utm_source', options.utmSource);
|
|
if (options?.utmMedium) params.set('utm_medium', options.utmMedium);
|
|
if (options?.utmCampaign) params.set('utm_campaign', options.utmCampaign);
|
|
if (options?.intent) params.set('intent', options.intent);
|
|
if (options?.authMode) params.set('auth_mode', options.authMode);
|
|
const query = params.toString();
|
|
return query ? `/auth/wechat/authorize?${query}` : '/auth/wechat/authorize';
|
|
}
|
|
|
|
export function readWechatPendingToken(): string | null {
|
|
const params = new URLSearchParams(window.location.search);
|
|
const pending = params.get('wechat_pending');
|
|
if (!pending) return null;
|
|
params.delete('wechat_pending');
|
|
const nextSearch = params.toString();
|
|
const nextUrl = `${window.location.pathname}${nextSearch ? `?${nextSearch}` : ''}${window.location.hash}`;
|
|
window.history.replaceState({}, '', nextUrl);
|
|
return pending;
|
|
}
|
|
|
|
export function readWechatAuthError(): string | null {
|
|
const params = new URLSearchParams(window.location.search);
|
|
const error = params.get('wechat_error');
|
|
if (!error) return null;
|
|
params.delete('wechat_error');
|
|
const nextSearch = params.toString();
|
|
const nextUrl = `${window.location.pathname}${nextSearch ? `?${nextSearch}` : ''}${window.location.hash}`;
|
|
window.history.replaceState({}, '', nextUrl);
|
|
try {
|
|
return decodeURIComponent(error);
|
|
} catch {
|
|
return error;
|
|
}
|
|
}
|