支持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) {
|
||||
let instant = target;
|
||||
@@ -64,12 +312,18 @@ export function exportSDK(lib, funcs, ...initNames) {
|
||||
let methodItem = methods[method];
|
||||
let methodName = methodItem && methodItem.method || method;
|
||||
hook(library, method, (...params) => {
|
||||
if (isEmbedded()) {
|
||||
return embedInvoke(methodName, params);
|
||||
}
|
||||
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)}`
|
||||
}
|
||||
return _exec(lib, methodName, ...params);
|
||||
});
|
||||
});
|
||||
if (typeof window !== "undefined" && isEmbedded()) {
|
||||
ensureEmbedChildListener();
|
||||
}
|
||||
freezeObj(library);
|
||||
return library;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import core from "./_core";
|
||||
import _global from './polyfill/_global';
|
||||
import { exportSDK } from './_export';
|
||||
import { exportSDK, installEmbedHost } from './_export';
|
||||
|
||||
const IScan = exportSDK(core, null, "config", "setStatusListener", "onScanListener",
|
||||
"offScanListener", "stopScan", "startScan", "scanImage", "clear");
|
||||
|
||||
installEmbedHost(core);
|
||||
|
||||
function dispatchIScanReady() {
|
||||
_global.__IScanReady__ && _global.__IScanReady__();
|
||||
if (!_global.dispatchEvent) {
|
||||
|
||||
Reference in New Issue
Block a user