feat: 技术支持工单/企微权限/开发版本管理/消息推送等迭代
This commit is contained in:
@@ -1,178 +0,0 @@
|
||||
import type { WechatJssdkConfig } from '@dukang/shared-types';
|
||||
import { isWechatBrowser, isWechatDevTools } from './env';
|
||||
import { DEFAULT_JS_API_LIST } from './types';
|
||||
|
||||
const JSSDK_URL = 'https://res.wx.qq.com/open/js/jweixin-1.6.0.js';
|
||||
const SIGN_URL_CACHE_KEY = 'dukang_wx_sign_url_v2';
|
||||
|
||||
let scriptPromise: Promise<void> | null = null;
|
||||
let configured = false;
|
||||
let configuredUrl: string | null = null;
|
||||
|
||||
/** 清除 JSSDK 配置缓存(路由切换后须重新 wx.config) */
|
||||
export function resetJssdkConfig(): void {
|
||||
configured = false;
|
||||
configuredUrl = null;
|
||||
}
|
||||
|
||||
/** 参与 JSSDK 签名的页面 URL:与微信文档一致,取 location.href 去掉 # 后的部分;剔除 OAuth 回调参数 */
|
||||
export function normalizeJssdkPageUrl(rawUrl: string): string {
|
||||
try {
|
||||
const url = new URL(rawUrl);
|
||||
url.hash = '';
|
||||
url.searchParams.delete('code');
|
||||
url.searchParams.delete('state');
|
||||
const query = url.searchParams.toString();
|
||||
return `${url.origin}${url.pathname}${query ? `?${query}` : ''}`;
|
||||
} catch {
|
||||
const noHash = rawUrl.split('#')[0];
|
||||
try {
|
||||
const url = new URL(noHash, typeof window !== 'undefined' ? window.location.origin : 'https://localhost');
|
||||
url.searchParams.delete('code');
|
||||
url.searchParams.delete('state');
|
||||
const query = url.searchParams.toString();
|
||||
return `${url.origin}${url.pathname}${query ? `?${query}` : ''}`;
|
||||
} catch {
|
||||
return noHash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function signUrlChanged(prev: string | null, current: string): boolean {
|
||||
return prev !== current;
|
||||
}
|
||||
|
||||
/** 记录最近一次签名 URL;SPA 路由或 ?step= 变化时须重新 wx.config */
|
||||
export function captureIosJssdkEntryUrl(): void {
|
||||
if (typeof window === 'undefined') return;
|
||||
if (!isWechatBrowser() || isWechatDevTools()) return;
|
||||
const current = normalizeJssdkPageUrl(window.location.href);
|
||||
const existing = sessionStorage.getItem(SIGN_URL_CACHE_KEY);
|
||||
if (!existing) {
|
||||
sessionStorage.setItem(SIGN_URL_CACHE_KEY, current);
|
||||
return;
|
||||
}
|
||||
if (signUrlChanged(existing, current)) {
|
||||
sessionStorage.setItem(SIGN_URL_CACHE_KEY, current);
|
||||
resetJssdkConfig();
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取参与 JSSDK 签名的 URL(始终为当前页完整 URL,含 query) */
|
||||
export function getJssdkSignUrl(rawUrl?: string): string {
|
||||
return normalizeJssdkPageUrl(rawUrl ?? (typeof window !== 'undefined' ? window.location.href : ''));
|
||||
}
|
||||
|
||||
function isJssdkDebugEnabled(): boolean {
|
||||
if (typeof window === 'undefined') return false;
|
||||
try {
|
||||
return new URLSearchParams(window.location.search).get('wxdebug') === '1';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function stripOAuthParamsFromLocation(): void {
|
||||
if (typeof window === 'undefined') return;
|
||||
const url = new URL(window.location.href);
|
||||
if (!url.searchParams.has('code') && !url.searchParams.has('state')) return;
|
||||
url.searchParams.delete('code');
|
||||
url.searchParams.delete('state');
|
||||
const query = url.searchParams.toString();
|
||||
const next = `${url.pathname}${query ? `?${query}` : ''}${url.hash}`;
|
||||
window.history.replaceState({}, '', next);
|
||||
}
|
||||
|
||||
function loadScript(): Promise<void> {
|
||||
if (typeof document === 'undefined') return Promise.reject(new Error('非浏览器环境'));
|
||||
if (window.wx) return Promise.resolve();
|
||||
if (scriptPromise) return scriptPromise;
|
||||
|
||||
scriptPromise = new Promise((resolve, reject) => {
|
||||
const existing = document.querySelector<HTMLScriptElement>('script[data-dukang-wx-jssdk]');
|
||||
if (existing) {
|
||||
existing.addEventListener('load', () => resolve());
|
||||
existing.addEventListener('error', () => reject(new Error('微信 JSSDK 加载失败')));
|
||||
return;
|
||||
}
|
||||
const script = document.createElement('script');
|
||||
script.src = JSSDK_URL;
|
||||
script.async = true;
|
||||
script.dataset.dukangWxJssdk = '1';
|
||||
script.onload = () => resolve();
|
||||
script.onerror = () => reject(new Error('微信 JSSDK 加载失败'));
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
return scriptPromise;
|
||||
}
|
||||
|
||||
async function fetchJssdkConfig(apiBase: string, clientApp: string, url: string, token?: string | null) {
|
||||
const headers: Record<string, string> = { 'X-Client-App': clientApp };
|
||||
if (token) headers.Authorization = `Bearer ${token}`;
|
||||
const qs = new URLSearchParams({ url });
|
||||
const res = await fetch(`${apiBase}/common/wechat/jssdk-config?${qs}`, { headers });
|
||||
const json = await res.json();
|
||||
if (json.code !== 0) throw new Error(json.message || '获取 JSSDK 配置失败');
|
||||
return json.data as WechatJssdkConfig;
|
||||
}
|
||||
|
||||
/** 初始化微信 JSSDK(H5 公众号内) */
|
||||
export async function initWechatJssdk(options: {
|
||||
apiBase: string;
|
||||
clientApp: string;
|
||||
url?: string;
|
||||
getAccessToken?: () => string | null;
|
||||
jsApiList?: string[];
|
||||
}): Promise<void> {
|
||||
captureIosJssdkEntryUrl();
|
||||
const { apiBase, clientApp, getAccessToken } = options;
|
||||
const pageUrl = options.url ?? getJssdkSignUrl();
|
||||
await loadScript();
|
||||
if (!window.wx) throw new Error('微信 JSSDK 不可用');
|
||||
|
||||
const config = await fetchJssdkConfig(apiBase, clientApp, pageUrl, getAccessToken?.());
|
||||
const jsApiList = options.jsApiList ?? [...DEFAULT_JS_API_LIST];
|
||||
|
||||
configured = false;
|
||||
configuredUrl = null;
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
window.wx!.config({
|
||||
...config,
|
||||
jsApiList,
|
||||
debug: isJssdkDebugEnabled(),
|
||||
});
|
||||
window.wx!.ready(() => {
|
||||
configured = true;
|
||||
configuredUrl = pageUrl;
|
||||
resolve();
|
||||
});
|
||||
window.wx!.error((err) => reject(new Error(err.errMsg || 'wx.config 失败')));
|
||||
});
|
||||
}
|
||||
|
||||
export function isJssdkReady(): boolean {
|
||||
const signUrl = getJssdkSignUrl();
|
||||
return configured && !!window.wx && configuredUrl === signUrl;
|
||||
}
|
||||
|
||||
export async function ensureJssdkReady(options: {
|
||||
apiBase: string;
|
||||
clientApp: string;
|
||||
url?: string;
|
||||
getAccessToken?: () => string | null;
|
||||
jsApiList?: string[];
|
||||
}): Promise<void> {
|
||||
captureIosJssdkEntryUrl();
|
||||
const pageUrl = options.url ?? getJssdkSignUrl();
|
||||
if (configuredUrl && signUrlChanged(configuredUrl, pageUrl)) {
|
||||
resetJssdkConfig();
|
||||
}
|
||||
if (!isJssdkReady()) {
|
||||
await initWechatJssdk({ ...options, url: pageUrl });
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
captureIosJssdkEntryUrl();
|
||||
}
|
||||
Reference in New Issue
Block a user