优化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, scanRestartDelay?: number,
/** /**
* iframe 场景下是否将 API 调用转发到父页面同名 SDKpostMessage * iframe 场景下是否将 API 调用转发到父页面同名 SDKpostMessage
* - `'auto'`(默认):处于子 frame`parent !== window`)即转发,对外 API `startScan`)均由父页 SDK 执行 * - `'auto'`(默认):处于子 frame `startScan`由父页执行`onScanListener` 仅注册在 iframe 内,识别结果由父页 `postMessage` 回传
* - `true` / `'on'` / `'parent'`:存在父 window 时强制转发 * - `true` / `'on'` / `'parent'`:存在父 window 时强制转发
* - `false` / `'off'` / `'local'`:始终在本页执行(子页自己要跑扫码时用) * - `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, resolveUseParentProxy,
} from "./services/embedProxy"; } from "./services/embedProxy";
import { createUUID } from "./utils/uuid"; import { createUUID } from "./utils/uuid";
import { dispatchHardwareScanResult } from "./services/provider/scan"; import {
import { startScanner, stopScanner } from "./services/scanner"; dispatchEmbedScanResult,
acknowledgeEmbedScanConsumed,
} from "./services/provider/scan";
import { setEmbedScanResultForwarder } from "./services/embedScanBridge";
const EMBED_SOURCE = "IScanEmbed"; const EMBED_SOURCE = "IScanEmbed";
const EMBED_V = 1; const EMBED_V = 1;
/** 嵌入子页已注册的监听 key与父页监听对应用于 refcount 决定是否在本页挂载扫码枪按键监听 */ /** 已向父页发起过 invoke 的嵌入子 frame用于父页识别结果回传 */
const childEmbedScanGunKeys = new Set(); const embedChildSources = new Set();
/** offScanListener(fn) 时反查对应的 key */
const embedScanGunWeakListenerKey = new WeakMap();
function syncEmbedScanGunForwardFromChild() { const EMBED_LISTENER_METHODS = new Set([
if (typeof window === "undefined" || !resolveUseParentProxy()) { "onScanListener",
return; "offScanListener",
} "clear",
if (childEmbedScanGunKeys.size === 0) { ]);
stopScanner();
return;
}
startScanner((result) => {
window.parent.postMessage(
{
source: EMBED_SOURCE,
v: EMBED_V,
kind: "forwardScanGun",
result,
},
"*"
);
});
}
function isEmbedMessage(data) { function isEmbedMessage(data) {
return data && data.source === EMBED_SOURCE && data.v === EMBED_V; return data && data.source === EMBED_SOURCE && data.v === EMBED_V;
@@ -119,36 +105,24 @@ function hydrateEmbedParams(params, messageSource, targetOrigin) {
return params.map(walk); return params.map(walk);
} }
function serializeEmbedInvokeResult(methodKey, result) { function broadcastScanResultToEmbedChildren(result) {
if ( if (embedChildSources.size === 0 || result == null || result === "") {
methodKey === "onScanListener" && return;
result &&
typeof result === "object" &&
typeof result.key === "string"
) {
return { __IScanEmbedListenerRef__: true, key: result.key };
} }
return result; embedChildSources.forEach((source) => {
} try {
source.postMessage(
function deserializeEmbedInvokeResult(methodKey, raw) { {
if ( source: EMBED_SOURCE,
methodKey === "onScanListener" && v: EMBED_V,
raw && kind: "forwardScanResult",
raw.__IScanEmbedListenerRef__ && result,
typeof raw.key === "string"
) {
const key = raw.key;
return {
key,
cancel: () => {
childEmbedScanGunKeys.delete(key);
syncEmbedScanGunForwardFromChild();
embedInvoke("offScanListener", [key]).catch(() => {});
}, },
}; "*"
);
} catch (e) {
} }
return raw; });
} }
let embedWxProbeScheduled = false; let embedWxProbeScheduled = false;
@@ -207,6 +181,23 @@ function embedChildOnMessage(ev) {
setParentWxEnvReport(!!data.wx); setParentWxEnvReport(!!data.wx);
return; 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") { if (data.kind === "invokeResult") {
const pending = pendingInvokes[data.id]; const pending = pendingInvokes[data.id];
if (!pending) { if (!pending) {
@@ -214,7 +205,7 @@ function embedChildOnMessage(ev) {
} }
delete pendingInvokes[data.id]; delete pendingInvokes[data.id];
if (data.ok) { if (data.ok) {
pending.resolve(deserializeEmbedInvokeResult(data.methodKey, data.result)); pending.resolve(data.result);
} else { } else {
pending.reject(new Error(data.error || "[IScan embed]: invoke failed")); pending.reject(new Error(data.error || "[IScan embed]: invoke failed"));
} }
@@ -277,7 +268,6 @@ function handleEmbedHostInvoke(lib, ev) {
return out; return out;
}) })
.then((result) => { .then((result) => {
const serializedResult = serializeEmbedInvokeResult(methodKey, result);
ev.source.postMessage( ev.source.postMessage(
{ {
source: EMBED_SOURCE, source: EMBED_SOURCE,
@@ -286,7 +276,7 @@ function handleEmbedHostInvoke(lib, ev) {
id, id,
methodKey, methodKey,
ok: true, ok: true,
result: serializedResult, result,
}, },
ev.origin ev.origin
); );
@@ -316,6 +306,7 @@ export function installEmbedHost(lib) {
return; return;
} }
embedHostInstalled = true; embedHostInstalled = true;
setEmbedScanResultForwarder(broadcastScanResultToEmbedChildren);
window.addEventListener("message", (ev) => { window.addEventListener("message", (ev) => {
const data = ev.data; const data = ev.data;
if (!isEmbedMessage(data)) { if (!isEmbedMessage(data)) {
@@ -338,15 +329,13 @@ export function installEmbedHost(lib) {
); );
return; return;
} }
/** 嵌入子页焦点下扫码枪的 keydown子页转成字符串后透出再走主页面监听与嵌入回调链路 */ if (data.kind === "scanResultConsumed") {
if (data.kind === "forwardScanGun") {
if (!ev.source || ev.source === window) { if (!ev.source || ev.source === window) {
return; return;
} }
if (typeof data.result !== "string") { if (typeof data.result === "string") {
return; acknowledgeEmbedScanConsumed(data.result);
} }
dispatchHardwareScanResult(data.result);
return; return;
} }
if (data.kind !== "invoke") { if (data.kind !== "invoke") {
@@ -358,6 +347,7 @@ export function installEmbedHost(lib) {
if (!ev.source || ev.source === window) { if (!ev.source || ev.source === window) {
return; return;
} }
embedChildSources.add(ev.source);
handleEmbedHostInvoke(lib, ev); handleEmbedHostInvoke(lib, ev);
}); });
} }
@@ -420,42 +410,11 @@ function freezeObj(obj) {
function createInvokeTransport(lib, method, methodName, initNames) { function createInvokeTransport(lib, method, methodName, initNames) {
return function IScanInvokeProxy(...params) { return function IScanInvokeProxy(...params) {
if (resolveUseParentProxy()) { if (resolveUseParentProxy()) {
if (methodName === "onScanListener") { if (EMBED_LISTENER_METHODS.has(methodName)) {
const listener = params[0]; if (!isReadyCalled() && initNames && initNames.indexOf(method) < 0) {
const key = params[1]; 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)}`
if (!key || typeof key !== "string" || typeof listener !== "function") {
return;
} }
embedScanGunWeakListenerKey.set(listener, key); return _exec(lib, methodName, ...params);
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 embedInvoke(methodName, params); return embedInvoke(methodName, params);
} }

View File

@@ -5,7 +5,7 @@ let _defineConfig = {
let _defConfig = { let _defConfig = {
/** /**
* iframe / 嵌入场景下是否把调用转发到父页面的同名 SDKpostMessage * iframe / 嵌入场景下是否把调用转发到父页面的同名 SDKpostMessage
* - 'auto'(默认):只要处于子 frameparent !== window即转发 startScan 等走父页逻辑 * - 'auto'(默认):处于子 frame startScan 等走父页onScanListener 仅注册在 iframe 内,识别结果由父页回传
* - true | 'on' | 'parent':在存在父 window 时强制转发 * - true | 'on' | 'parent':在存在父 window 时强制转发
* - false | 'off' | 'local':始终在本页执行(子页自己要跑扫码时用) * - 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 { getConfig } from "../config";
import { toAny } from "../../utils/toany"; import { toAny } from "../../utils/toany";
import { printDebug } from "../../utils/logger"; import { printDebug } from "../../utils/logger";
import { forwardEmbedScanResultIfNeeded } from "../embedScanBridge";
let _scan_status = "ready"; let _scan_status = "ready";
let _scan_status_listener = null; let _scan_status_listener = null;
@@ -93,6 +94,7 @@ function __match(result, match) {
function __result(result) { function __result(result) {
result = parseBarcodeString(result); result = parseBarcodeString(result);
forwardEmbedScanResultIfNeeded(result);
let matched = false; let matched = false;
for (let i = 0; i < _scan_listener_list.length; i++) { for (let i = 0; i < _scan_listener_list.length; i++) {
const item = _scan_listener_list[i]; const item = _scan_listener_list[i];
@@ -157,18 +159,38 @@ function __stopCurrentScan() {
} }
/** /**
* 由嵌入子页转发过来的扫码枪按键结果,与 startScanner 走同一匹配与回调链路 * 父页通过 postMessage 将识别结果投递到嵌入 iframe 时调用(与本地扫码枪/监听同一链路)。
*(仅在父页的 installEmbedHost 处理 forwardScanGun 时调用)。 * @returns {boolean} 是否有监听消费了该结果
*/ */
export function dispatchHardwareScanResult(raw) { export function dispatchEmbedScanResult(raw) {
const result = const result =
typeof raw === "string" ? parseBarcodeString(raw) : raw; 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) { function __scannerResult(result) {
if (!__hasMatchedListener(result)) { if (!__hasMatchedListener(result)) {
return; return __result(result);
} }
if (isScanning()) { if (isScanning()) {
const resolve = __finishScan(); const resolve = __finishScan();
@@ -178,13 +200,13 @@ function __scannerResult(result) {
_scan_closing = false; _scan_closing = false;
}, 0); }, 0);
}); });
__result(result); const matched = __result(result);
resolve && resolve({ resolve && resolve({
result result
}); });
return; return matched;
} }
__result(result); return __result(result);
} }
function __startBridgeScan() { function __startBridgeScan() {

2
types/index.d.ts vendored
View File

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