扫一扫优化

This commit is contained in:
iqudoo
2026-06-26 21:52:47 +08:00
parent bee953617d
commit 689eec085f
7 changed files with 233 additions and 69 deletions

4
dist/index.d.ts vendored
View File

@@ -22,6 +22,10 @@ interface ScanConfigOptions {
* 页面 URL 变化后是否自动重新初始化微信 JSSDK默认 true避免扫一扫失效
*/
wxJssdkAutoReinitOnUrlChange?: boolean,
/**
* URL 轮询检测间隔(毫秒),用于捕获未走 history API 的 SPA 路由;默认 1000设为 0 关闭
*/
wxJssdkUrlPollInterval?: number,
/**
* 桥接是否启用,默认启用
*/

2
dist/index.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -7,7 +7,7 @@ import {
startScan, stopScan, scanImage, scanImageFromFile, clear
} from './services/provider/scan';
import { setConfig, getVersion } from './services/config';
import { initWxJssdk } from './services/wx';
import { initWxJssdk, installWxJssdkUrlWatcher, resetWxJssdkState } from './services/wx';
import {
isSupportWebScan,
prepareWebScanBarcodeDetector,
@@ -15,6 +15,13 @@ import {
} from './services/web';
import { printDebug, printWarn } from './utils/logger';
import { requestPayment, closePaymentDialog } from './services/payment';
import {
getSdkRuntimeUrl,
setSdkRuntimeUrlBaseline,
isSdkRuntimeUrlChanged,
setSdkRuntimeUrlChangeHandler,
bindSdkConfigReadyPromise,
} from './services/sdkLifecycle';
let _readyPromise = null;
let _calledReady = false;
@@ -23,20 +30,26 @@ export function isReadyCalled() {
return _calledReady;
}
function config(config) {
if (config) {
setConfig(config);
}
if (_readyPromise) {
return _readyPromise;
}
function invalidateSdkReadyState() {
_readyPromise = null;
_calledReady = false;
setSdkRuntimeUrlBaseline("");
bindSdkConfigReadyPromise(null);
resetWxJssdkState();
}
function runSdkConfigInit() {
const urlAtStart = getSdkRuntimeUrl();
setSdkRuntimeUrlBaseline(urlAtStart);
_readyPromise = Promise.resolve().then(() => {
initWxJssdk().catch(err => {
installWxJssdkUrlWatcher();
return initWxJssdk().catch(err => {
printDebug('init wx jssdk failed:', err && err.message ? err.message : err);
});
}).then(() => {
printDebug('-------------------------------------');
printDebug('sdk_version:', getVersion());
printDebug('sdk_runtime_url:', getSdkRuntimeUrl());
printDebug('support_list:', supportList.map(item => item.name + ':' + item.support).join(', '));
printDebug('-------------------------------------');
if (isSupportWebScan() || isSupportImageScan()) {
@@ -46,13 +59,39 @@ function config(config) {
}
}).then(() => {
_calledReady = true;
setSdkRuntimeUrlBaseline(getSdkRuntimeUrl());
}).catch(err => {
_readyPromise = null;
invalidateSdkReadyState();
throw err;
});
bindSdkConfigReadyPromise(_readyPromise);
return _readyPromise;
}
function config(config) {
if (config) {
setConfig(config);
}
if (isSdkRuntimeUrlChanged()) {
invalidateSdkReadyState();
}
if (_readyPromise) {
return _readyPromise;
}
return runSdkConfigInit();
}
setSdkRuntimeUrlChangeHandler(() => {
if (!isSdkRuntimeUrlChanged()) {
return;
}
printDebug('sdk runtime url changed, reinitializing config:', getSdkRuntimeUrl());
invalidateSdkReadyState();
runSdkConfigInit().catch((err) => {
printDebug('sdk reconfig failed:', err && err.message ? err.message : err);
});
});
export default Object.assign({}, {
config,
onScanListener,

View File

@@ -18,6 +18,7 @@ import { startScanner, stopScanner } from "../scanner";
import { getConfig } from "../config";
import { toAny } from "../../utils/toany";
import { printDebug, printWarn } from "../../utils/logger";
import { waitForSdkConfigReady } from "../sdkLifecycle";
import { forwardEmbedScanResultIfNeeded, forwardEmbedScanErrorIfNeeded } from "../embedScanBridge";
let _scan_status = "ready";
@@ -81,6 +82,9 @@ function __fallbackScanAfterWxFailure(err) {
}
function __startNonBridgeScan(err) {
if (isWxEnv()) {
return __startWxScan();
}
if (isSupportWebScan()) {
return __startWebScan();
}
@@ -744,17 +748,17 @@ export function startScan(key) {
return;
}
unlockScanBeep();
Promise.resolve().then(() => {
Promise.resolve().then(() => waitForSdkConfigReady()).then(() => {
__beginScanSession(key);
__scanning();
let scannerPromise = new Promise(resolve => {
_scan_resolve = resolve;
});
let scanPromise = Promise.resolve();
if (inRuntime()) {
if (isWxEnv()) {
scanPromise = __startWxScan().catch(__fallbackScanAfterWxFailure);
} else if (inRuntime()) {
scanPromise = __startBridgeScan().catch(__fallbackScanAfterBridgeFailure);
} else if (isSupportWxScan()) {
scanPromise = __startWxScan();
} else if (isSupportWebScan()) {
scanPromise = __startWebScan(true);
} else if (isSupportImageScan()) {

View File

@@ -0,0 +1,59 @@
import { getConfig } from "./config";
import { resolveUseParentProxy } from "./embedProxy";
let _runtimeUrlBaseline = "";
let _runtimeUrlChangeHandler = null;
let _configReadyPromise = null;
export function bindSdkConfigReadyPromise(promise) {
_configReadyPromise = promise || null;
}
export function waitForSdkConfigReady() {
if (_configReadyPromise) {
return _configReadyPromise;
}
return Promise.resolve();
}
/**
* SDK 运行时 URL微信 JSSDK 签名、config 缓存均以此为准,不含 hash
*/
export function getSdkRuntimeUrl() {
const override = getConfig("wxJssdkSignatureUrl");
if (override) {
return String(override).split("#")[0];
}
if (resolveUseParentProxy()) {
try {
if (typeof window !== "undefined" && window.parent && window.parent !== window) {
return window.parent.location.href.split("#")[0];
}
} catch (e) {
// 跨域父页不可读 location
}
}
if (typeof window === "undefined" || !window.location) {
return "";
}
return window.location.href.split("#")[0];
}
export function setSdkRuntimeUrlBaseline(url) {
_runtimeUrlBaseline = url ? String(url) : "";
}
export function isSdkRuntimeUrlChanged() {
const current = getSdkRuntimeUrl();
return !!(_runtimeUrlBaseline && current && _runtimeUrlBaseline !== current);
}
export function setSdkRuntimeUrlChangeHandler(handler) {
_runtimeUrlChangeHandler = typeof handler === "function" ? handler : null;
}
export function notifySdkRuntimeUrlChanged() {
if (typeof _runtimeUrlChangeHandler === "function") {
_runtimeUrlChangeHandler();
}
}

View File

@@ -4,6 +4,11 @@ import { resolveUseParentProxy } from "../embedProxy";
import { request } from "../../utils/request";
import { toAny } from "../../utils/toany";
import { printDebug } from "../../utils/logger";
import {
getSdkRuntimeUrl,
isSdkRuntimeUrlChanged,
notifySdkRuntimeUrlChanged,
} from "../sdkLifecycle";
const WX_JS_SDK_URL = "https://res.wx.qq.com/open/js/jweixin-1.6.0.js";
const WX_SCAN_API = "scanQRCode";
@@ -11,7 +16,11 @@ const WX_SCAN_API = "scanQRCode";
let _wxReadyPromise = null;
let _wxReady = false;
let _wxSignatureUrl = "";
let _wxPendingSignatureUrl = "";
let _wxInitGeneration = 0;
let _urlWatchInstalled = false;
let _urlPollTimer = null;
let _urlReinitTimer = null;
function getWx() {
if (typeof wx !== "undefined") {
@@ -51,25 +60,9 @@ function loadWxScript() {
});
}
/** 微信 JS-SDK 签名 URL嵌入且走父页代理时用父页地址(与微信内打开的页面一致);可配置 wxJssdkSignatureUrl 覆盖 */
/** 微信 JS-SDK 签名 URL与 SDK 运行时 URL 一致 */
function getPageUrlForWxJssdkSignature() {
const override = getConfig("wxJssdkSignatureUrl");
if (override) {
return String(override).split("#")[0];
}
if (resolveUseParentProxy()) {
try {
if (typeof window !== "undefined" && window.parent && window.parent !== window) {
return window.parent.location.href.split("#")[0];
}
} catch (e) {
// 跨域父页不可读 location由调用方配置 wxJssdkSignatureUrl
}
}
if (typeof window === "undefined") {
return "";
}
return window.location.href.split("#")[0];
return getSdkRuntimeUrl();
}
function isWxJssdkAutoReinitEnabled() {
@@ -96,11 +89,82 @@ function getWxJssdkWatchWindow() {
function resetWxJssdkState() {
_wxReady = false;
_wxReadyPromise = null;
_wxPendingSignatureUrl = "";
_wxInitGeneration += 1;
}
export { resetWxJssdkState };
function isWxSignatureUrlChanged() {
const current = getPageUrlForWxJssdkSignature();
return !!(_wxSignatureUrl && current && _wxSignatureUrl !== current);
const baseline = _wxSignatureUrl || _wxPendingSignatureUrl;
return !!(baseline && current && baseline !== current);
}
function getWxJssdkUrlPollInterval() {
const interval = getConfig("wxJssdkUrlPollInterval");
if (interval === 0 || interval === false) {
return 0;
}
if (typeof interval === "number" && interval > 0) {
return interval;
}
return 1000;
}
function scheduleWxJssdkReinitIfNeeded() {
if (!isWxEnv() || !isWxJssdkAutoReinitEnabled()) {
return;
}
if (!isSdkRuntimeUrlChanged() && !isWxSignatureUrlChanged()) {
return;
}
if (_urlReinitTimer) {
return;
}
_urlReinitTimer = setTimeout(() => {
_urlReinitTimer = null;
if (!isSdkRuntimeUrlChanged() && !isWxSignatureUrlChanged()) {
return;
}
printDebug("sdk runtime url changed, notify reconfig:", getSdkRuntimeUrl());
notifySdkRuntimeUrlChanged();
}, 50);
}
function stopWxJssdkUrlPoll() {
if (_urlPollTimer) {
clearInterval(_urlPollTimer);
_urlPollTimer = null;
}
}
function startWxJssdkUrlPoll() {
const interval = getWxJssdkUrlPollInterval();
if (!interval || _urlPollTimer) {
return;
}
_urlPollTimer = setInterval(() => {
scheduleWxJssdkReinitIfNeeded();
}, interval);
}
export function installWxJssdkUrlWatcher() {
if (!isWxJssdkAutoReinitEnabled()) {
return;
}
const watchWin = getWxJssdkWatchWindow();
if (!watchWin || !watchWin.addEventListener) {
return;
}
if (!_urlWatchInstalled) {
_urlWatchInstalled = true;
watchWin.addEventListener("popstate", scheduleWxJssdkReinitIfNeeded);
watchWin.addEventListener("hashchange", scheduleWxJssdkReinitIfNeeded);
patchHistoryMethod(watchWin.history, "pushState", scheduleWxJssdkReinitIfNeeded);
patchHistoryMethod(watchWin.history, "replaceState", scheduleWxJssdkReinitIfNeeded);
}
startWxJssdkUrlPoll();
}
function patchHistoryMethod(history, methodName, callback) {
@@ -120,36 +184,6 @@ function patchHistoryMethod(history, methodName, callback) {
history[methodName] = patched;
}
function installWxJssdkUrlWatcher() {
if (_urlWatchInstalled || !isWxJssdkAutoReinitEnabled()) {
return;
}
const watchWin = getWxJssdkWatchWindow();
if (!watchWin || !watchWin.addEventListener) {
return;
}
_urlWatchInstalled = true;
const scheduleReinit = () => {
if (!isWxEnv() || !isWxJssdkAutoReinitEnabled()) {
return;
}
if (!isWxSignatureUrlChanged()) {
return;
}
printDebug("wx jssdk url changed, reinitializing:", getPageUrlForWxJssdkSignature());
resetWxJssdkState();
initWxJssdk().catch((err) => {
printDebug("wx jssdk reinit failed:", err && err.message ? err.message : err);
});
};
watchWin.addEventListener("popstate", scheduleReinit);
watchWin.addEventListener("hashchange", scheduleReinit);
patchHistoryMethod(watchWin.history, "pushState", scheduleReinit);
patchHistoryMethod(watchWin.history, "replaceState", scheduleReinit);
}
function fetchWxConfig() {
let initWechatJssdk = toAny(getConfig("initWechatJssdk"), {});
if (!!initWechatJssdk.sdkConfig) {
@@ -227,6 +261,9 @@ export function initWxJssdk() {
if (_wxReadyPromise) {
return _wxReadyPromise;
}
const generation = _wxInitGeneration;
const signatureUrlAtStart = getPageUrlForWxJssdkSignature();
_wxPendingSignatureUrl = signatureUrlAtStart;
_wxReadyPromise = Promise.all([loadWxScript(), fetchWxConfig()]).then(items => {
const wx = items[0];
const config = items[1];
@@ -242,13 +279,27 @@ export function initWxJssdk() {
}
return new Promise((resolve, reject) => {
wx.ready(() => {
if (generation !== _wxInitGeneration) {
return;
}
const currentUrl = getPageUrlForWxJssdkSignature();
if (signatureUrlAtStart && currentUrl && signatureUrlAtStart !== currentUrl) {
resetWxJssdkState();
initWxJssdk().then(resolve).catch(reject);
return;
}
_wxReady = true;
_wxSignatureUrl = getPageUrlForWxJssdkSignature();
_wxSignatureUrl = currentUrl;
_wxPendingSignatureUrl = "";
resolve(wx);
});
wx.error(err => {
if (generation !== _wxInitGeneration) {
return;
}
_wxReady = false;
_wxSignatureUrl = "";
_wxPendingSignatureUrl = "";
_wxReadyPromise = null;
reject(err);
});
@@ -257,9 +308,12 @@ export function initWxJssdk() {
}));
});
}).catch(err => {
_wxReady = false;
_wxSignatureUrl = "";
_wxReadyPromise = null;
if (generation === _wxInitGeneration) {
_wxReady = false;
_wxSignatureUrl = "";
_wxPendingSignatureUrl = "";
_wxReadyPromise = null;
}
throw err;
});
return _wxReadyPromise;

4
types/index.d.ts vendored
View File

@@ -22,6 +22,10 @@ interface ScanConfigOptions {
* 页面 URL 变化后是否自动重新初始化微信 JSSDK默认 true避免扫一扫失效
*/
wxJssdkAutoReinitOnUrlChange?: boolean,
/**
* URL 轮询检测间隔(毫秒),用于捕获未走 history API 的 SPA 路由;默认 1000设为 0 关闭
*/
wxJssdkUrlPollInterval?: number,
/**
* 桥接是否启用,默认启用
*/