支持IFRAME嵌入模式,嵌入模式下都需要引入SDK
This commit is contained in:
2
dist/index.js
vendored
2
dist/index.js
vendored
File diff suppressed because one or more lines are too long
256
src/_export.js
256
src/_export.js
@@ -1,4 +1,252 @@
|
|||||||
import _core, { isReadyCalled } from "./_core";
|
import { isReadyCalled } from "./_core";
|
||||||
|
import { createUUID } from "./utils/uuid";
|
||||||
|
|
||||||
|
const EMBED_SOURCE = "IScanEmbed";
|
||||||
|
const EMBED_V = 1;
|
||||||
|
|
||||||
|
function isEmbedded() {
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return window.self !== window.top;
|
||||||
|
} catch (e) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmbedMessage(data) {
|
||||||
|
return data && data.source === EMBED_SOURCE && data.v === EMBED_V;
|
||||||
|
}
|
||||||
|
|
||||||
|
let embedHostInstalled = false;
|
||||||
|
let embedChildInstalled = false;
|
||||||
|
const pendingInvokes = Object.create(null);
|
||||||
|
const childCallbackFns = Object.create(null);
|
||||||
|
|
||||||
|
function cloneArgsReplacingFunctions(val, registry, seen) {
|
||||||
|
if (typeof val === "function") {
|
||||||
|
const id = createUUID();
|
||||||
|
registry[id] = val;
|
||||||
|
return { __IScanEmbedCb__: id };
|
||||||
|
}
|
||||||
|
if (val === null || typeof val !== "object") {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
if (!seen) {
|
||||||
|
seen = new WeakMap();
|
||||||
|
}
|
||||||
|
if (seen.has(val)) {
|
||||||
|
throw new Error("[IScan embed]: circular reference in arguments");
|
||||||
|
}
|
||||||
|
seen.set(val, true);
|
||||||
|
if (Array.isArray(val)) {
|
||||||
|
return val.map((item) => cloneArgsReplacingFunctions(item, registry, seen));
|
||||||
|
}
|
||||||
|
const out = {};
|
||||||
|
Object.keys(val).forEach((k) => {
|
||||||
|
out[k] = cloneArgsReplacingFunctions(val[k], registry, seen);
|
||||||
|
});
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function serializeEmbedParams(params) {
|
||||||
|
const serialized = [];
|
||||||
|
const registry = {};
|
||||||
|
for (let i = 0; i < params.length; i++) {
|
||||||
|
serialized.push(cloneArgsReplacingFunctions(params[i], registry));
|
||||||
|
}
|
||||||
|
return { serialized, registry };
|
||||||
|
}
|
||||||
|
|
||||||
|
function hydrateEmbedParams(params, messageSource, targetOrigin) {
|
||||||
|
function walk(val) {
|
||||||
|
if (val && typeof val === "object" && val.__IScanEmbedCb__) {
|
||||||
|
const cbId = val.__IScanEmbedCb__;
|
||||||
|
return function embedCbProxy() {
|
||||||
|
const args = Array.prototype.slice.call(arguments);
|
||||||
|
messageSource.postMessage(
|
||||||
|
{
|
||||||
|
source: EMBED_SOURCE,
|
||||||
|
v: EMBED_V,
|
||||||
|
kind: "callback",
|
||||||
|
cbId,
|
||||||
|
args,
|
||||||
|
},
|
||||||
|
targetOrigin
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (val === null || typeof val !== "object") {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
if (Array.isArray(val)) {
|
||||||
|
return val.map(walk);
|
||||||
|
}
|
||||||
|
const out = {};
|
||||||
|
Object.keys(val).forEach((k) => {
|
||||||
|
out[k] = walk(val[k]);
|
||||||
|
});
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
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 };
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deserializeEmbedInvokeResult(methodKey, raw) {
|
||||||
|
if (
|
||||||
|
methodKey === "onScanListener" &&
|
||||||
|
raw &&
|
||||||
|
raw.__IScanEmbedListenerRef__ &&
|
||||||
|
typeof raw.key === "string"
|
||||||
|
) {
|
||||||
|
const key = raw.key;
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
cancel: () => embedInvoke("offScanListener", [key]),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
function embedChildOnMessage(ev) {
|
||||||
|
const data = ev.data;
|
||||||
|
if (!isEmbedMessage(data)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.kind === "invokeResult") {
|
||||||
|
const pending = pendingInvokes[data.id];
|
||||||
|
if (!pending) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
delete pendingInvokes[data.id];
|
||||||
|
if (data.ok) {
|
||||||
|
pending.resolve(deserializeEmbedInvokeResult(data.methodKey, data.result));
|
||||||
|
} else {
|
||||||
|
pending.reject(new Error(data.error || "[IScan embed]: invoke failed"));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.kind === "callback") {
|
||||||
|
const fn = childCallbackFns[data.cbId];
|
||||||
|
if (typeof fn === "function") {
|
||||||
|
fn.apply(null, data.args || []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureEmbedChildListener() {
|
||||||
|
if (embedChildInstalled || typeof window === "undefined" || !isEmbedded()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
embedChildInstalled = true;
|
||||||
|
window.addEventListener("message", embedChildOnMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
function embedInvoke(methodKey, params) {
|
||||||
|
ensureEmbedChildListener();
|
||||||
|
const id = createUUID();
|
||||||
|
const { serialized, registry } = serializeEmbedParams(params);
|
||||||
|
Object.keys(registry).forEach((cbId) => {
|
||||||
|
childCallbackFns[cbId] = registry[cbId];
|
||||||
|
});
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
pendingInvokes[id] = { resolve, reject };
|
||||||
|
window.parent.postMessage(
|
||||||
|
{
|
||||||
|
source: EMBED_SOURCE,
|
||||||
|
v: EMBED_V,
|
||||||
|
kind: "invoke",
|
||||||
|
id,
|
||||||
|
methodKey,
|
||||||
|
params: serialized,
|
||||||
|
},
|
||||||
|
"*"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEmbedHostInvoke(lib, ev) {
|
||||||
|
const data = ev.data;
|
||||||
|
const { id, methodKey, params } = data;
|
||||||
|
const hydrated = hydrateEmbedParams(params || [], ev.source, ev.origin);
|
||||||
|
Promise.resolve()
|
||||||
|
.then(() => _exec(lib, methodKey, ...hydrated))
|
||||||
|
.then((result) => {
|
||||||
|
let out = result;
|
||||||
|
if (out && typeof out.then === "function") {
|
||||||
|
return out.then((r) => {
|
||||||
|
out = r;
|
||||||
|
return out;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
const serializedResult = serializeEmbedInvokeResult(methodKey, result);
|
||||||
|
ev.source.postMessage(
|
||||||
|
{
|
||||||
|
source: EMBED_SOURCE,
|
||||||
|
v: EMBED_V,
|
||||||
|
kind: "invokeResult",
|
||||||
|
id,
|
||||||
|
methodKey,
|
||||||
|
ok: true,
|
||||||
|
result: serializedResult,
|
||||||
|
},
|
||||||
|
ev.origin
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
ev.source.postMessage(
|
||||||
|
{
|
||||||
|
source: EMBED_SOURCE,
|
||||||
|
v: EMBED_V,
|
||||||
|
kind: "invokeResult",
|
||||||
|
id,
|
||||||
|
methodKey,
|
||||||
|
ok: false,
|
||||||
|
error: typeof err === "string" ? err : String((err && err.message) || err),
|
||||||
|
},
|
||||||
|
ev.origin
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在顶层页面注册 iframe 嵌入代理:子页面(嵌入模式)通过 postMessage 调用同一套 SDK。
|
||||||
|
* 需在加载 SDK 后调用一次,并传入与 exportSDK 相同的 lib(通常为 ./_core 默认导出)。
|
||||||
|
*/
|
||||||
|
export function installEmbedHost(lib) {
|
||||||
|
if (embedHostInstalled || typeof window === "undefined") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
embedHostInstalled = true;
|
||||||
|
window.addEventListener("message", (ev) => {
|
||||||
|
const data = ev.data;
|
||||||
|
if (!isEmbedMessage(data) || data.kind !== "invoke") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!data.id || !data.methodKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ev.source || ev.source === window) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleEmbedHostInvoke(lib, ev);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function _exec(target, func, ...params) {
|
function _exec(target, func, ...params) {
|
||||||
let instant = target;
|
let instant = target;
|
||||||
@@ -64,12 +312,18 @@ export function exportSDK(lib, funcs, ...initNames) {
|
|||||||
let methodItem = methods[method];
|
let methodItem = methods[method];
|
||||||
let methodName = methodItem && methodItem.method || method;
|
let methodName = methodItem && methodItem.method || method;
|
||||||
hook(library, method, (...params) => {
|
hook(library, method, (...params) => {
|
||||||
|
if (isEmbedded()) {
|
||||||
|
return embedInvoke(methodName, params);
|
||||||
|
}
|
||||||
if (!isReadyCalled() && initNames && initNames.indexOf(method) < 0) {
|
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)}`
|
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)}`
|
||||||
}
|
}
|
||||||
return _exec(lib, methodName, ...params);
|
return _exec(lib, methodName, ...params);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
if (typeof window !== "undefined" && isEmbedded()) {
|
||||||
|
ensureEmbedChildListener();
|
||||||
|
}
|
||||||
freezeObj(library);
|
freezeObj(library);
|
||||||
return library;
|
return library;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import core from "./_core";
|
import core from "./_core";
|
||||||
import _global from './polyfill/_global';
|
import _global from './polyfill/_global';
|
||||||
import { exportSDK } from './_export';
|
import { exportSDK, installEmbedHost } from './_export';
|
||||||
|
|
||||||
const IScan = exportSDK(core, null, "config", "setStatusListener", "onScanListener",
|
const IScan = exportSDK(core, null, "config", "setStatusListener", "onScanListener",
|
||||||
"offScanListener", "stopScan", "startScan", "scanImage", "clear");
|
"offScanListener", "stopScan", "startScan", "scanImage", "clear");
|
||||||
|
|
||||||
|
installEmbedHost(core);
|
||||||
|
|
||||||
function dispatchIScanReady() {
|
function dispatchIScanReady() {
|
||||||
_global.__IScanReady__ && _global.__IScanReady__();
|
_global.__IScanReady__ && _global.__IScanReady__();
|
||||||
if (!_global.dispatchEvent) {
|
if (!_global.dispatchEvent) {
|
||||||
|
|||||||
Reference in New Issue
Block a user