781 lines
19 KiB
JavaScript
781 lines
19 KiB
JavaScript
import { inRuntime, bridgeAsync } from "../bridge";
|
||
import {
|
||
isSupportWebScan,
|
||
startScanForWeb,
|
||
stopScanForWeb,
|
||
isSupportImageScan,
|
||
startScanForImage,
|
||
unlockScanBeep,
|
||
playScanBeep,
|
||
isWebScanImageFallbackEnabled,
|
||
canUseWebCameraScan,
|
||
shouldSkipWebCameraProbe,
|
||
cleanupWebScanResiduals,
|
||
detectImageFileForScan
|
||
} from "../web";
|
||
import { isSupportWxScan, startScanForWx, isWxEnv } from "../wx";
|
||
import { startScanner, stopScanner } from "../scanner";
|
||
import { getConfig } from "../config";
|
||
import { toAny } from "../../utils/toany";
|
||
import { printDebug, printWarn } from "../../utils/logger";
|
||
import { forwardEmbedScanResultIfNeeded, forwardEmbedScanErrorIfNeeded } from "../embedScanBridge";
|
||
|
||
let _scan_status = "ready";
|
||
let _scan_status_listener = null;
|
||
let _scan_listener_list = [];
|
||
let _scan_error_listener_list = [];
|
||
let _scan_resolve = null;
|
||
let _scan_closing = false;
|
||
let _scan_next_start_time = 0;
|
||
let _embed_scan_host_enabled = false;
|
||
|
||
const SCAN_RESTART_DELAY = 500;
|
||
const BRIDGE_SCAN_TIMEOUT = 5000;
|
||
const SCAN_SESSION_TIMEOUT = 90000;
|
||
|
||
function getScanRestartDelay() {
|
||
return toAny(getConfig("scanRestartDelay"), SCAN_RESTART_DELAY);
|
||
}
|
||
|
||
function getBridgeScanTimeout() {
|
||
const timeout = getConfig("bridgeScanTimeout");
|
||
return typeof timeout === "number" && timeout > 0 ? timeout : BRIDGE_SCAN_TIMEOUT;
|
||
}
|
||
|
||
function getScanSessionTimeout() {
|
||
const timeout = getConfig("scanSessionTimeout");
|
||
return typeof timeout === "number" && timeout > 0 ? timeout : SCAN_SESSION_TIMEOUT;
|
||
}
|
||
|
||
function withScanSessionTimeout(scanPromise) {
|
||
return Promise.race([
|
||
scanPromise,
|
||
new Promise(resolve => {
|
||
setTimeout(() => {
|
||
printWarn("scan session timeout");
|
||
resolve({ cancel: 1, scanTimeout: true });
|
||
}, getScanSessionTimeout());
|
||
})
|
||
]);
|
||
}
|
||
|
||
function __fallbackScanAfterBridgeFailure(err) {
|
||
if (!isScanning()) {
|
||
throw err;
|
||
}
|
||
printWarn("bridge scan unavailable, fallback:", err);
|
||
return __startNonBridgeScan(err);
|
||
}
|
||
|
||
function __fallbackScanAfterWxFailure(err) {
|
||
if (!isScanning()) {
|
||
throw err;
|
||
}
|
||
printWarn("wx scan unavailable, fallback to web/image scan:", err);
|
||
return __startNonBridgeScan(err);
|
||
}
|
||
|
||
function __startNonBridgeScan(err) {
|
||
if (isSupportWebScan()) {
|
||
return __startWebScan();
|
||
}
|
||
if (isSupportImageScan()) {
|
||
return __startImageScan();
|
||
}
|
||
throw err;
|
||
}
|
||
|
||
function parseBarcodeString(input) {
|
||
// 标准化的类型映射表:将各种变体映射到统一标识
|
||
// 这样即使传入 EAN_13、EAN-13、EAN13 都能匹配成功
|
||
const normalizedTypeMap = {
|
||
// EAN 系列
|
||
"ean13": true, "ean-13": true, "ean_13": true,
|
||
"ean8": true, "ean-8": true, "ean_8": true,
|
||
// UPC 系列
|
||
"upca": true, "upc-a": true, "upc_a": true,
|
||
"upce": true, "upc-e": true, "upc_e": true,
|
||
// CODE 系列
|
||
"code128": true, "code-128": true, "code_128": true,
|
||
"code39": true, "code-39": true, "code_39": true,
|
||
"code93": true, "code-93": true, "code_93": true,
|
||
// 其他常见类型
|
||
"itf": true, "itf14": true, "itf-14": true, "itf_14": true,
|
||
"codabar": true,
|
||
"pdf417": true, "pdf-417": true, "pdf_417": true,
|
||
"qrcode": true, "qr-code": true, "qr_code": true,
|
||
"datamatrix": true, "data-matrix": true, "data_matrix": true,
|
||
"aztec": true
|
||
};
|
||
// 健壮性检查
|
||
if (typeof input !== 'string') {
|
||
return input;
|
||
}
|
||
// 按第一个逗号分割
|
||
const commaIndex = input.indexOf(',');
|
||
if (commaIndex === -1) {
|
||
return input;
|
||
}
|
||
const possibleType = input.substring(0, commaIndex).trim();
|
||
const value = input.substring(commaIndex + 1).trim();
|
||
// 类型或值为空,返回原值
|
||
if (possibleType === "" || value === "") {
|
||
return input;
|
||
}
|
||
// 标准化类型(转小写,便于匹配)
|
||
const normalizedType = possibleType.toLowerCase();
|
||
// 检查是否为已知的条形码类型(支持多种分隔符变体)
|
||
const isKnownType = normalizedTypeMap.hasOwnProperty(normalizedType);
|
||
|
||
if (isKnownType) {
|
||
return value;
|
||
} else {
|
||
return input;
|
||
}
|
||
}
|
||
|
||
function __checkScanner() {
|
||
if (_scan_listener_list.length > 0 || _embed_scan_host_enabled) {
|
||
startScanner((result) => {
|
||
result = parseBarcodeString(result);
|
||
__scannerResult(result, { source: "scanner", skipBeep: true });
|
||
});
|
||
} else {
|
||
stopScanner();
|
||
}
|
||
}
|
||
|
||
function __match(result, match) {
|
||
let reg = null;
|
||
if (!!match && typeof match === "string") {
|
||
reg = new RegExp(match);
|
||
}
|
||
if (!!reg) {
|
||
return reg.test(result);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
function __shouldSkipBeep(meta) {
|
||
if (meta && meta.skipBeep) {
|
||
return true;
|
||
}
|
||
const source = meta && meta.source;
|
||
return source === "scanner" || source === "bridge" || source === "wx";
|
||
}
|
||
|
||
function __result(result, meta) {
|
||
result = parseBarcodeString(result);
|
||
forwardEmbedScanResultIfNeeded(result, meta);
|
||
let matched = false;
|
||
for (let i = 0; i < _scan_listener_list.length; i++) {
|
||
const item = _scan_listener_list[i];
|
||
if (item.listener && __match(result, item.match)) {
|
||
matched = true;
|
||
item.listener({ result, key: item.key });
|
||
break;
|
||
}
|
||
}
|
||
if (matched) {
|
||
if (!__shouldSkipBeep(meta)) {
|
||
playScanBeep();
|
||
}
|
||
_scan_next_start_time = Date.now() + getScanRestartDelay();
|
||
}
|
||
return matched;
|
||
}
|
||
|
||
function normalizeScanError(raw) {
|
||
if (typeof raw === "string") {
|
||
return raw;
|
||
}
|
||
if (raw && raw.error != null && raw.error !== "") {
|
||
return String(raw.error);
|
||
}
|
||
if (raw && raw.message) {
|
||
return String(raw.message);
|
||
}
|
||
if (raw == null || raw === "") {
|
||
return "";
|
||
}
|
||
return String(raw);
|
||
}
|
||
|
||
function __scanError(raw, meta) {
|
||
const error = normalizeScanError(raw);
|
||
if (!error) {
|
||
return false;
|
||
}
|
||
forwardEmbedScanErrorIfNeeded(error);
|
||
let matched = false;
|
||
for (let i = 0; i < _scan_error_listener_list.length; i++) {
|
||
const item = _scan_error_listener_list[i];
|
||
if (item.listener && __match(error, item.match)) {
|
||
matched = true;
|
||
item.listener(Object.assign({
|
||
error,
|
||
key: item.key
|
||
}, meta || {}));
|
||
break;
|
||
}
|
||
}
|
||
return matched;
|
||
}
|
||
|
||
function __notifyImageScanFailure(raw, meta) {
|
||
if (raw && raw.cancel) {
|
||
return false;
|
||
}
|
||
const error = normalizeScanError(raw);
|
||
const notified = __scanError(raw, Object.assign({ source: "image" }, meta || {}));
|
||
if (!notified && error) {
|
||
printWarn("image scan failed:", error);
|
||
}
|
||
return notified;
|
||
}
|
||
|
||
function __notifyWebScanFailure(raw, meta) {
|
||
if (raw && raw.cancel) {
|
||
return false;
|
||
}
|
||
const payload = raw && raw.error != null ? raw.error : raw;
|
||
const error = normalizeScanError(payload);
|
||
const notified = __scanError(payload, Object.assign({ source: "web" }, meta || {}));
|
||
if (!notified && error) {
|
||
printWarn("web scan failed:", error);
|
||
}
|
||
return notified;
|
||
}
|
||
|
||
function __hasMatchedListener(result) {
|
||
for (let i = 0; i < _scan_listener_list.length; i++) {
|
||
const item = _scan_listener_list[i];
|
||
if (item.listener && __match(result, item.match)) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
function __scanning() {
|
||
if (_scan_status !== "scanning") {
|
||
_scan_status = "scanning";
|
||
if (_scan_status_listener) {
|
||
_scan_status_listener({ status: "scanning" });
|
||
}
|
||
}
|
||
}
|
||
|
||
function __closed() {
|
||
if (_scan_status !== "ready") {
|
||
_scan_status = "ready";
|
||
if (_scan_status_listener) {
|
||
_scan_status_listener({ status: "ready" });
|
||
}
|
||
}
|
||
}
|
||
|
||
function __finishScan() {
|
||
const resolve = _scan_resolve;
|
||
_scan_resolve = null;
|
||
__closed();
|
||
return resolve;
|
||
}
|
||
|
||
function __stopCurrentScan() {
|
||
if (inRuntime()) {
|
||
return bridgeAsync("stopScan").catch(() => {
|
||
// no thing
|
||
});
|
||
} else if (isSupportWebScan()) {
|
||
return stopScanForWeb().catch(() => {
|
||
// no thing
|
||
});
|
||
}
|
||
return Promise.resolve();
|
||
}
|
||
|
||
/**
|
||
* 父页通过 postMessage 将识别结果投递到嵌入 iframe 时调用(与本地扫码枪/监听同一链路)。
|
||
* @returns {boolean} 是否有监听消费了该结果
|
||
*/
|
||
export function dispatchEmbedScanResult(raw, meta) {
|
||
const result =
|
||
typeof raw === "string" ? parseBarcodeString(raw) : raw;
|
||
return __scannerResult(result, meta);
|
||
}
|
||
|
||
/**
|
||
* 父页通过 postMessage 将识别错误投递到嵌入 iframe 时调用。
|
||
* @returns {boolean} 是否有监听消费了该错误
|
||
*/
|
||
export function dispatchEmbedScanError(raw) {
|
||
const error = normalizeScanError(raw);
|
||
return __scanError(error, { source: "image" });
|
||
}
|
||
|
||
/**
|
||
* 嵌入 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, meta) {
|
||
if (!__hasMatchedListener(result)) {
|
||
return __result(result, meta);
|
||
}
|
||
if (isScanning()) {
|
||
const resolve = __finishScan();
|
||
_scan_closing = true;
|
||
__stopCurrentScan().then(() => {
|
||
setTimeout(() => {
|
||
_scan_closing = false;
|
||
}, 0);
|
||
});
|
||
const matched = __result(result, meta);
|
||
resolve && resolve({
|
||
result
|
||
});
|
||
return matched;
|
||
}
|
||
return __result(result, meta);
|
||
}
|
||
|
||
function __startBridgeScan() {
|
||
return bridgeAsync("startScan", {
|
||
closeable: true
|
||
}, getBridgeScanTimeout()).then(resp => {
|
||
if (!isScanning()) {
|
||
return resp;
|
||
}
|
||
if (!resp || !resp.result) {
|
||
return resp;
|
||
}
|
||
if (__result(resp.result, { source: "bridge" })) {
|
||
return resp;
|
||
}
|
||
if (isScanning()) {
|
||
return __startBridgeScan();
|
||
}
|
||
return resp;
|
||
}).catch(err => {
|
||
if (!isScanning()) {
|
||
throw err;
|
||
}
|
||
if (!err || !err.result) {
|
||
throw err;
|
||
}
|
||
if (__result(err.result, { source: "bridge" })) {
|
||
return err;
|
||
}
|
||
if (isScanning()) {
|
||
return __startBridgeScan();
|
||
}
|
||
return err;
|
||
});
|
||
}
|
||
|
||
function __startWxScan() {
|
||
return startScanForWx({
|
||
needResult: 1,
|
||
scanType: ["qrCode", "barCode"]
|
||
}).then(resp => {
|
||
if (!isScanning()) {
|
||
return resp;
|
||
}
|
||
if (resp && resp.error && !resp.result) {
|
||
throw resp.error;
|
||
}
|
||
if (!resp || !resp.result) {
|
||
return resp;
|
||
}
|
||
if (__result(resp.result, { source: "wx" })) {
|
||
return resp;
|
||
}
|
||
return resp;
|
||
}).catch(err => {
|
||
if (!isScanning()) {
|
||
throw err;
|
||
}
|
||
if (err && err.result) {
|
||
if (__result(err.result, { source: "wx" })) {
|
||
return err;
|
||
}
|
||
return err;
|
||
}
|
||
return __fallbackScanAfterWxFailure(err);
|
||
});
|
||
}
|
||
|
||
function __startWebScan(useImageScan = false) {
|
||
return startScanForWeb(__result, __scanError).then(resp => {
|
||
if (!isScanning()) {
|
||
return resp;
|
||
}
|
||
if (!resp || !resp.result) {
|
||
if (resp && resp.success === false) {
|
||
__notifyWebScanFailure(resp);
|
||
}
|
||
return resp;
|
||
}
|
||
__result(resp.result);
|
||
return resp;
|
||
}).catch(err => {
|
||
if (!isScanning()) {
|
||
return err;
|
||
}
|
||
if (err && err.cancel) {
|
||
return err;
|
||
}
|
||
if (err && err.scanTimeout) {
|
||
return err;
|
||
}
|
||
if (err && err.imageFallbackUsed) {
|
||
__notifyWebScanFailure(err);
|
||
return err;
|
||
}
|
||
if (isWebScanImageFallbackEnabled() && useImageScan) {
|
||
return __startImageScan();
|
||
}
|
||
__notifyWebScanFailure(err);
|
||
printWarn("web scan failed:", err);
|
||
return err;
|
||
});
|
||
}
|
||
|
||
function __startImageScan() {
|
||
return startScanForImage().then(resp => {
|
||
if (!isScanning()) {
|
||
return resp;
|
||
}
|
||
if (resp && resp.cancel) {
|
||
return resp;
|
||
}
|
||
if (!resp || !resp.result) {
|
||
__notifyImageScanFailure(resp);
|
||
return resp;
|
||
}
|
||
__result(resp.result);
|
||
return resp;
|
||
}).catch(err => {
|
||
if (!isScanning()) {
|
||
return err;
|
||
}
|
||
if (err && err.cancel) {
|
||
return err;
|
||
}
|
||
if (err && err.result) {
|
||
__result(err.result);
|
||
return err;
|
||
}
|
||
__notifyImageScanFailure(err);
|
||
return err;
|
||
});
|
||
}
|
||
|
||
export function isScanning() {
|
||
return _scan_status === "scanning";
|
||
}
|
||
|
||
export function setEmbedScanHostEnabled(enabled) {
|
||
const nextEnabled = enabled !== false;
|
||
if (_embed_scan_host_enabled === nextEnabled) {
|
||
return;
|
||
}
|
||
_embed_scan_host_enabled = nextEnabled;
|
||
__checkScanner();
|
||
}
|
||
|
||
export function clear() {
|
||
for (let i = 0; i < _scan_listener_list.length; i++) {
|
||
const item = _scan_listener_list[i];
|
||
item.cancel();
|
||
}
|
||
_scan_listener_list.length = 0;
|
||
for (let i = 0; i < _scan_error_listener_list.length; i++) {
|
||
const item = _scan_error_listener_list[i];
|
||
item.cancel();
|
||
}
|
||
_scan_error_listener_list.length = 0;
|
||
__checkScanner();
|
||
}
|
||
|
||
export function onScanListener(listener, key, match, level) {
|
||
if (!key || typeof key !== 'string') {
|
||
return;
|
||
}
|
||
if (typeof listener !== 'function') {
|
||
return;
|
||
}
|
||
let item = null;
|
||
for (let i = 0; i < _scan_listener_list.length; i++) {
|
||
const _i = _scan_listener_list[i];
|
||
if (_i.key === key) {
|
||
item = _i;
|
||
break;
|
||
}
|
||
}
|
||
if (item) {
|
||
item.level = level;
|
||
item.match = match;
|
||
item.listener = listener;
|
||
} else {
|
||
item = {
|
||
key,
|
||
match: match || "",
|
||
level: level || 0,
|
||
listener: listener,
|
||
cancel: () => {
|
||
const index = _scan_listener_list.indexOf(item);
|
||
if (index !== -1) {
|
||
const items = _scan_listener_list.splice(index, 1);
|
||
for (let i = 0; i < items.length; i++) {
|
||
const item = items[i];
|
||
item.listener && item.listener({ cancel: 1 });
|
||
}
|
||
}
|
||
}
|
||
};
|
||
_scan_listener_list.push(item);
|
||
}
|
||
// 根据level排序
|
||
_scan_listener_list.sort((a, b) => b.level - a.level);
|
||
__checkScanner();
|
||
return item;
|
||
}
|
||
|
||
export function offScanListener(listener) {
|
||
for (let i = 0; i < _scan_listener_list.length; i++) {
|
||
const _i = _scan_listener_list[i];
|
||
if (typeof listener === 'string') {
|
||
if (_i.key === listener) {
|
||
_i.cancel();
|
||
break;
|
||
}
|
||
} else if (_i.listener === listener) {
|
||
_i.cancel();
|
||
break;
|
||
}
|
||
}
|
||
__checkScanner();
|
||
}
|
||
|
||
function createScanErrorListenerItem(listener, key, match, level) {
|
||
const item = {
|
||
key,
|
||
match: match || "",
|
||
level: level || 0,
|
||
listener: listener,
|
||
cancel: () => {
|
||
const index = _scan_error_listener_list.indexOf(item);
|
||
if (index !== -1) {
|
||
const items = _scan_error_listener_list.splice(index, 1);
|
||
for (let i = 0; i < items.length; i++) {
|
||
const removed = items[i];
|
||
removed.listener && removed.listener({ cancel: 1, key: removed.key });
|
||
}
|
||
}
|
||
}
|
||
};
|
||
return item;
|
||
}
|
||
|
||
export function onScanErrorListener(listener, key, match, level) {
|
||
if (!key || typeof key !== 'string') {
|
||
return;
|
||
}
|
||
if (typeof listener !== 'function') {
|
||
return;
|
||
}
|
||
let item = null;
|
||
for (let i = 0; i < _scan_error_listener_list.length; i++) {
|
||
const _i = _scan_error_listener_list[i];
|
||
if (_i.key === key) {
|
||
item = _i;
|
||
break;
|
||
}
|
||
}
|
||
if (item) {
|
||
item.level = level;
|
||
item.match = match;
|
||
item.listener = listener;
|
||
} else {
|
||
item = createScanErrorListenerItem(listener, key, match, level);
|
||
_scan_error_listener_list.push(item);
|
||
}
|
||
_scan_error_listener_list.sort((a, b) => b.level - a.level);
|
||
return item;
|
||
}
|
||
|
||
export function offScanErrorListener(listener) {
|
||
for (let i = 0; i < _scan_error_listener_list.length; i++) {
|
||
const _i = _scan_error_listener_list[i];
|
||
if (typeof listener === 'string') {
|
||
if (_i.key === listener) {
|
||
_i.cancel();
|
||
break;
|
||
}
|
||
} else if (_i.listener === listener) {
|
||
_i.cancel();
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
export function setStatusListener(listener) {
|
||
if (typeof listener !== 'function') {
|
||
return;
|
||
}
|
||
_scan_status_listener = listener;
|
||
}
|
||
|
||
export function getStatus() {
|
||
return _scan_status;
|
||
}
|
||
|
||
export function stopScan() {
|
||
cleanupWebScanResiduals();
|
||
if (!isScanning()) {
|
||
return;
|
||
}
|
||
const resolve = __finishScan();
|
||
_scan_closing = true;
|
||
__stopCurrentScan().then(() => {
|
||
resolve && resolve({ cancel: 1 });
|
||
_scan_closing = false;
|
||
});
|
||
}
|
||
|
||
export function startScan() {
|
||
if (isScanning() || _scan_closing || Date.now() < _scan_next_start_time) {
|
||
return;
|
||
}
|
||
unlockScanBeep();
|
||
Promise.resolve().then(() => {
|
||
__scanning();
|
||
let scannerPromise = new Promise(resolve => {
|
||
_scan_resolve = resolve;
|
||
});
|
||
let scanPromise = Promise.resolve();
|
||
if (inRuntime()) {
|
||
scanPromise = __startBridgeScan().catch(__fallbackScanAfterBridgeFailure);
|
||
} else if (isSupportWxScan()) {
|
||
scanPromise = __startWxScan();
|
||
} else if (isSupportWebScan()) {
|
||
scanPromise = __startWebScan(true);
|
||
} else if (isSupportImageScan()) {
|
||
scanPromise = __startImageScan();
|
||
} else {
|
||
printWarn("Not support scanner");
|
||
}
|
||
return withScanSessionTimeout(Promise.race([scanPromise, scannerPromise]));
|
||
}).finally(() => {
|
||
_scan_resolve = null;
|
||
__closed();
|
||
});
|
||
}
|
||
|
||
export function scanImage() {
|
||
if (!isSupportImageScan()) {
|
||
printDebug("Not support image scanner");
|
||
return;
|
||
}
|
||
if (isScanning() || _scan_closing || Date.now() < _scan_next_start_time) {
|
||
return;
|
||
}
|
||
unlockScanBeep();
|
||
__scanning();
|
||
withScanSessionTimeout(__startImageScan()).catch(err => {
|
||
if (err && err.cancel) {
|
||
return;
|
||
}
|
||
__notifyImageScanFailure(err);
|
||
}).finally(() => {
|
||
__closed();
|
||
});
|
||
}
|
||
|
||
/** 由原生/业务传入已选图片 File,避免 WebView 无法从 input.files 取文件 */
|
||
export function scanImageFromFile(file) {
|
||
if (!isSupportImageScan()) {
|
||
printDebug("Not support image scanner");
|
||
return;
|
||
}
|
||
if (isScanning() || _scan_closing || Date.now() < _scan_next_start_time) {
|
||
return;
|
||
}
|
||
unlockScanBeep();
|
||
cleanupWebScanResiduals();
|
||
__scanning();
|
||
withScanSessionTimeout(
|
||
detectImageFileForScan(file).then(resp => {
|
||
if (!isScanning()) {
|
||
return resp;
|
||
}
|
||
if (resp && resp.cancel) {
|
||
return resp;
|
||
}
|
||
if (!resp || !resp.result) {
|
||
__notifyImageScanFailure(resp);
|
||
return resp;
|
||
}
|
||
__result(resp.result);
|
||
return resp;
|
||
})
|
||
).catch(err => {
|
||
if (err && err.cancel) {
|
||
return;
|
||
}
|
||
__notifyImageScanFailure(err);
|
||
}).finally(() => {
|
||
__closed();
|
||
});
|
||
}
|
||
|
||
export const supportList = [
|
||
{
|
||
name: "bridge",
|
||
get support() {
|
||
return !!inRuntime();
|
||
}
|
||
},
|
||
{
|
||
name: "wx",
|
||
get support() {
|
||
return !!isSupportWxScan();
|
||
}
|
||
},
|
||
{
|
||
name: "web",
|
||
get support() {
|
||
if (shouldSkipWebCameraProbe()) {
|
||
return false;
|
||
}
|
||
return !!canUseWebCameraScan();
|
||
}
|
||
},
|
||
{
|
||
name: "image",
|
||
get support() {
|
||
return !!isSupportImageScan();
|
||
}
|
||
},
|
||
{
|
||
name: "scanner",
|
||
support: true
|
||
}
|
||
]; |