优化iframe内使用的优化

This commit is contained in:
iqudoo
2026-05-25 19:12:35 +08:00
parent c56c17589a
commit 6663fd6ab4
7 changed files with 103 additions and 110 deletions

2
dist/index.d.ts vendored
View File

@@ -8,7 +8,7 @@ interface ScanConfigOptions {
scanRestartDelay?: number,
/**
* iframe 场景下是否将 API 调用转发到父页面同名 SDKpostMessage
* - `'auto'`(默认):处于子 frame`parent !== window`)即转发,对外 API `startScan`)均由父页 SDK 执行
* - `'auto'`(默认):处于子 frame `startScan`由父页执行`onScanListener` 仅注册在 iframe 内,识别结果由父页 `postMessage` 回传
* - `true` / `'on'` / `'parent'`:存在父 window 时强制转发
* - `false` / `'off'` / `'local'`:始终在本页执行(子页自己要跑扫码时用)
*/

2
dist/index.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -10,37 +10,23 @@ import {
resolveUseParentProxy,
} from "./services/embedProxy";
import { createUUID } from "./utils/uuid";
import { dispatchHardwareScanResult } from "./services/provider/scan";
import { startScanner, stopScanner } from "./services/scanner";
import {
dispatchEmbedScanResult,
acknowledgeEmbedScanConsumed,
} from "./services/provider/scan";
import { setEmbedScanResultForwarder } from "./services/embedScanBridge";
const EMBED_SOURCE = "IScanEmbed";
const EMBED_V = 1;
/** 嵌入子页已注册的监听 key与父页监听对应用于 refcount 决定是否在本页挂载扫码枪按键监听 */
const childEmbedScanGunKeys = new Set();
/** offScanListener(fn) 时反查对应的 key */
const embedScanGunWeakListenerKey = new WeakMap();
/** 已向父页发起过 invoke 的嵌入子 frame用于父页识别结果回传 */
const embedChildSources = new Set();
function syncEmbedScanGunForwardFromChild() {
if (typeof window === "undefined" || !resolveUseParentProxy()) {
return;
}
if (childEmbedScanGunKeys.size === 0) {
stopScanner();
return;
}
startScanner((result) => {
window.parent.postMessage(
{
source: EMBED_SOURCE,
v: EMBED_V,
kind: "forwardScanGun",
result,
},
"*"
);
});
}
const EMBED_LISTENER_METHODS = new Set([
"onScanListener",
"offScanListener",
"clear",
]);
function isEmbedMessage(data) {
return data && data.source === EMBED_SOURCE && data.v === EMBED_V;
@@ -119,36 +105,24 @@ function hydrateEmbedParams(params, messageSource, targetOrigin) {
return params.map(walk);
}
function serializeEmbedInvokeResult(methodKey, result) {
if (
methodKey === "onScanListener" &&
result &&
typeof result === "object" &&
typeof result.key === "string"
) {
return { __IScanEmbedListenerRef__: true, key: result.key };
function broadcastScanResultToEmbedChildren(result) {
if (embedChildSources.size === 0 || result == null || result === "") {
return;
}
return result;
}
function deserializeEmbedInvokeResult(methodKey, raw) {
if (
methodKey === "onScanListener" &&
raw &&
raw.__IScanEmbedListenerRef__ &&
typeof raw.key === "string"
) {
const key = raw.key;
return {
key,
cancel: () => {
childEmbedScanGunKeys.delete(key);
syncEmbedScanGunForwardFromChild();
embedInvoke("offScanListener", [key]).catch(() => {});
},
};
}
return raw;
embedChildSources.forEach((source) => {
try {
source.postMessage(
{
source: EMBED_SOURCE,
v: EMBED_V,
kind: "forwardScanResult",
result,
},
"*"
);
} catch (e) {
}
});
}
let embedWxProbeScheduled = false;
@@ -207,6 +181,23 @@ function embedChildOnMessage(ev) {
setParentWxEnvReport(!!data.wx);
return;
}
if (data.kind === "forwardScanResult") {
if (typeof data.result === "string") {
const consumed = dispatchEmbedScanResult(data.result);
if (consumed && resolveUseParentProxy()) {
window.parent.postMessage(
{
source: EMBED_SOURCE,
v: EMBED_V,
kind: "scanResultConsumed",
result: data.result,
},
"*"
);
}
}
return;
}
if (data.kind === "invokeResult") {
const pending = pendingInvokes[data.id];
if (!pending) {
@@ -214,7 +205,7 @@ function embedChildOnMessage(ev) {
}
delete pendingInvokes[data.id];
if (data.ok) {
pending.resolve(deserializeEmbedInvokeResult(data.methodKey, data.result));
pending.resolve(data.result);
} else {
pending.reject(new Error(data.error || "[IScan embed]: invoke failed"));
}
@@ -277,7 +268,6 @@ function handleEmbedHostInvoke(lib, ev) {
return out;
})
.then((result) => {
const serializedResult = serializeEmbedInvokeResult(methodKey, result);
ev.source.postMessage(
{
source: EMBED_SOURCE,
@@ -286,7 +276,7 @@ function handleEmbedHostInvoke(lib, ev) {
id,
methodKey,
ok: true,
result: serializedResult,
result,
},
ev.origin
);
@@ -316,6 +306,7 @@ export function installEmbedHost(lib) {
return;
}
embedHostInstalled = true;
setEmbedScanResultForwarder(broadcastScanResultToEmbedChildren);
window.addEventListener("message", (ev) => {
const data = ev.data;
if (!isEmbedMessage(data)) {
@@ -338,15 +329,13 @@ export function installEmbedHost(lib) {
);
return;
}
/** 嵌入子页焦点下扫码枪的 keydown子页转成字符串后透出再走主页面监听与嵌入回调链路 */
if (data.kind === "forwardScanGun") {
if (data.kind === "scanResultConsumed") {
if (!ev.source || ev.source === window) {
return;
}
if (typeof data.result !== "string") {
return;
if (typeof data.result === "string") {
acknowledgeEmbedScanConsumed(data.result);
}
dispatchHardwareScanResult(data.result);
return;
}
if (data.kind !== "invoke") {
@@ -358,6 +347,7 @@ export function installEmbedHost(lib) {
if (!ev.source || ev.source === window) {
return;
}
embedChildSources.add(ev.source);
handleEmbedHostInvoke(lib, ev);
});
}
@@ -420,42 +410,11 @@ function freezeObj(obj) {
function createInvokeTransport(lib, method, methodName, initNames) {
return function IScanInvokeProxy(...params) {
if (resolveUseParentProxy()) {
if (methodName === "onScanListener") {
const listener = params[0];
const key = params[1];
if (!key || typeof key !== "string" || typeof listener !== "function") {
return;
if (EMBED_LISTENER_METHODS.has(methodName)) {
if (!isReadyCalled() && initNames && initNames.indexOf(method) < 0) {
throw `[IScan]:Can't call the "IScan.${method}" method, because "IScan" not ready, please confirm that "IScan.ready()" has been called. params: ${JSON.stringify(params)}`
}
embedScanGunWeakListenerKey.set(listener, key);
childEmbedScanGunKeys.add(key);
syncEmbedScanGunForwardFromChild();
embedInvoke(methodName, params).catch(() => {});
return {
key,
cancel: () => {
childEmbedScanGunKeys.delete(key);
syncEmbedScanGunForwardFromChild();
embedInvoke("offScanListener", [key]).catch(() => {});
},
};
}
if (methodName === "offScanListener") {
const p0 = params[0];
if (typeof p0 === "string") {
childEmbedScanGunKeys.delete(p0);
} else if (typeof p0 === "function") {
const rk = embedScanGunWeakListenerKey.get(p0);
if (rk != null && rk !== "") {
childEmbedScanGunKeys.delete(rk);
}
}
syncEmbedScanGunForwardFromChild();
return embedInvoke(methodName, params);
}
if (methodName === "clear") {
childEmbedScanGunKeys.clear();
syncEmbedScanGunForwardFromChild();
return embedInvoke(methodName, params);
return _exec(lib, methodName, ...params);
}
return embedInvoke(methodName, params);
}

View File

@@ -5,7 +5,7 @@ let _defineConfig = {
let _defConfig = {
/**
* iframe / 嵌入场景下是否把调用转发到父页面的同名 SDKpostMessage
* - 'auto'(默认):只要处于子 frameparent !== window即转发 startScan 等走父页逻辑
* - 'auto'(默认):处于子 frame startScan 等走父页onScanListener 仅注册在 iframe 内,识别结果由父页回传
* - true | 'on' | 'parent':在存在父 window 时强制转发
* - false | 'off' | 'local':始终在本页执行(子页自己要跑扫码时用)
*/

View File

@@ -0,0 +1,12 @@
/** 父页识别到扫码结果时,向嵌入 iframe 转发的回调(由 installEmbedHost 注册) */
let embedScanResultForwarder = null;
export function setEmbedScanResultForwarder(fn) {
embedScanResultForwarder = typeof fn === "function" ? fn : null;
}
export function forwardEmbedScanResultIfNeeded(result) {
if (embedScanResultForwarder && result != null && result !== "") {
embedScanResultForwarder(result);
}
}

View File

@@ -5,6 +5,7 @@ import { startScanner, stopScanner } from "../scanner";
import { getConfig } from "../config";
import { toAny } from "../../utils/toany";
import { printDebug } from "../../utils/logger";
import { forwardEmbedScanResultIfNeeded } from "../embedScanBridge";
let _scan_status = "ready";
let _scan_status_listener = null;
@@ -93,6 +94,7 @@ function __match(result, match) {
function __result(result) {
result = parseBarcodeString(result);
forwardEmbedScanResultIfNeeded(result);
let matched = false;
for (let i = 0; i < _scan_listener_list.length; i++) {
const item = _scan_listener_list[i];
@@ -157,18 +159,38 @@ function __stopCurrentScan() {
}
/**
* 由嵌入子页转发过来的扫码枪按键结果,与 startScanner 走同一匹配与回调链路
*(仅在父页的 installEmbedHost 处理 forwardScanGun 时调用)。
* 父页通过 postMessage 将识别结果投递到嵌入 iframe 时调用(与本地扫码枪/监听同一链路)。
* @returns {boolean} 是否有监听消费了该结果
*/
export function dispatchHardwareScanResult(raw) {
export function dispatchEmbedScanResult(raw) {
const result =
typeof raw === "string" ? parseBarcodeString(raw) : raw;
__scannerResult(result);
return __scannerResult(result);
}
/**
* 嵌入 iframe 已消费扫码结果时通知父页结束当前识别(关闭摄像头/UI 等)。
*/
export function acknowledgeEmbedScanConsumed(raw) {
const result =
typeof raw === "string" ? parseBarcodeString(raw) : raw;
if (!isScanning()) {
return;
}
const resolve = __finishScan();
_scan_closing = true;
_scan_next_start_time = Date.now() + getScanRestartDelay();
__stopCurrentScan().then(() => {
setTimeout(() => {
_scan_closing = false;
}, 0);
});
resolve && resolve({ result });
}
function __scannerResult(result) {
if (!__hasMatchedListener(result)) {
return;
return __result(result);
}
if (isScanning()) {
const resolve = __finishScan();
@@ -178,13 +200,13 @@ function __scannerResult(result) {
_scan_closing = false;
}, 0);
});
__result(result);
const matched = __result(result);
resolve && resolve({
result
});
return;
return matched;
}
__result(result);
return __result(result);
}
function __startBridgeScan() {

2
types/index.d.ts vendored
View File

@@ -8,7 +8,7 @@ interface ScanConfigOptions {
scanRestartDelay?: number,
/**
* iframe 场景下是否将 API 调用转发到父页面同名 SDKpostMessage
* - `'auto'`(默认):处于子 frame`parent !== window`)即转发,对外 API `startScan`)均由父页 SDK 执行
* - `'auto'`(默认):处于子 frame `startScan`由父页执行`onScanListener` 仅注册在 iframe 内,识别结果由父页 `postMessage` 回传
* - `true` / `'on'` / `'parent'`:存在父 window 时强制转发
* - `false` / `'off'` / `'local'`:始终在本页执行(子页自己要跑扫码时用)
*/