优化++
This commit is contained in:
@@ -7,28 +7,83 @@ import {
|
||||
startScanForImage,
|
||||
unlockScanBeep,
|
||||
playScanBeep,
|
||||
isWebScanImageFallbackEnabled
|
||||
isWebScanImageFallbackEnabled,
|
||||
canUseWebCameraScan,
|
||||
shouldSkipWebCameraProbe,
|
||||
cleanupWebScanResiduals,
|
||||
detectImageFileForScan
|
||||
} from "../web";
|
||||
import { isSupportWxScan, startScanForWx } from "../wx";
|
||||
import { isSupportWxScan, startScanForWx, isWxEnv } from "../wx";
|
||||
import { startScanner, stopScanner } from "../scanner";
|
||||
import { getConfig } from "../config";
|
||||
import { toAny } from "../../utils/toany";
|
||||
import { printDebug } from "../../utils/logger";
|
||||
import { forwardEmbedScanResultIfNeeded } from "../embedScanBridge";
|
||||
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;
|
||||
|
||||
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 都能匹配成功
|
||||
@@ -120,6 +175,68 @@ function __result(result) {
|
||||
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];
|
||||
@@ -178,6 +295,15 @@ export function dispatchEmbedScanResult(raw) {
|
||||
return __scannerResult(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 父页通过 postMessage 将识别错误投递到嵌入 iframe 时调用。
|
||||
* @returns {boolean} 是否有监听消费了该错误
|
||||
*/
|
||||
export function dispatchEmbedScanError(raw) {
|
||||
const error = normalizeScanError(raw);
|
||||
return __scanError(error, { source: "image" });
|
||||
}
|
||||
|
||||
/**
|
||||
* 嵌入 iframe 已消费扫码结果时通知父页结束当前识别(关闭摄像头/UI 等)。
|
||||
*/
|
||||
@@ -222,7 +348,7 @@ function __scannerResult(result) {
|
||||
function __startBridgeScan() {
|
||||
return bridgeAsync("startScan", {
|
||||
closeable: true
|
||||
}).then(resp => {
|
||||
}, getBridgeScanTimeout()).then(resp => {
|
||||
if (!isScanning()) {
|
||||
return resp;
|
||||
}
|
||||
@@ -238,10 +364,10 @@ function __startBridgeScan() {
|
||||
return resp;
|
||||
}).catch(err => {
|
||||
if (!isScanning()) {
|
||||
return err;
|
||||
throw err;
|
||||
}
|
||||
if (!err || !err.result) {
|
||||
return err;
|
||||
throw err;
|
||||
}
|
||||
if (__result(err.result)) {
|
||||
return err;
|
||||
@@ -261,6 +387,9 @@ function __startWxScan() {
|
||||
if (!isScanning()) {
|
||||
return resp;
|
||||
}
|
||||
if (resp && resp.error && !resp.result) {
|
||||
throw resp.error;
|
||||
}
|
||||
if (!resp || !resp.result) {
|
||||
return resp;
|
||||
}
|
||||
@@ -270,43 +399,50 @@ function __startWxScan() {
|
||||
return resp;
|
||||
}).catch(err => {
|
||||
if (!isScanning()) {
|
||||
throw err;
|
||||
}
|
||||
if (err && err.result) {
|
||||
if (__result(err.result)) {
|
||||
return err;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
if (!err || !err.result) {
|
||||
return err;
|
||||
}
|
||||
if (__result(err.result)) {
|
||||
return err;
|
||||
}
|
||||
return err;
|
||||
return __fallbackScanAfterWxFailure(err);
|
||||
});
|
||||
}
|
||||
|
||||
function __startWebScan() {
|
||||
return startScanForWeb(__result).then(resp => {
|
||||
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;
|
||||
}
|
||||
if (__result(resp.result)) {
|
||||
return resp;
|
||||
}
|
||||
if (isScanning()) {
|
||||
return __startWebScan();
|
||||
}
|
||||
__result(resp.result);
|
||||
return resp;
|
||||
}).catch(err => {
|
||||
if (!isScanning()) {
|
||||
return err;
|
||||
}
|
||||
if (err && err.cancel || err && err.imageFallbackUsed) {
|
||||
if (err && err.cancel) {
|
||||
return err;
|
||||
}
|
||||
if (isWebScanImageFallbackEnabled()) {
|
||||
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;
|
||||
});
|
||||
}
|
||||
@@ -316,29 +452,27 @@ function __startImageScan() {
|
||||
if (!isScanning()) {
|
||||
return resp;
|
||||
}
|
||||
if (resp && resp.cancel) {
|
||||
return resp;
|
||||
}
|
||||
if (!resp || !resp.result) {
|
||||
__notifyImageScanFailure(resp);
|
||||
return resp;
|
||||
}
|
||||
if (__result(resp.result)) {
|
||||
return resp;
|
||||
}
|
||||
if (isScanning()) {
|
||||
return __startImageScan();
|
||||
}
|
||||
__result(resp.result);
|
||||
return resp;
|
||||
}).catch(err => {
|
||||
if (!isScanning()) {
|
||||
return err;
|
||||
}
|
||||
if (!err || !err.result) {
|
||||
if (err && err.cancel) {
|
||||
return err;
|
||||
}
|
||||
if (__result(err.result)) {
|
||||
if (err && err.result) {
|
||||
__result(err.result);
|
||||
return err;
|
||||
}
|
||||
if (isScanning()) {
|
||||
return __startImageScan();
|
||||
}
|
||||
__notifyImageScanFailure(err);
|
||||
return err;
|
||||
});
|
||||
}
|
||||
@@ -353,6 +487,11 @@ export function clear() {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -416,6 +555,68 @@ export function offScanListener(listener) {
|
||||
__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;
|
||||
@@ -428,11 +629,15 @@ export function getStatus() {
|
||||
}
|
||||
|
||||
export function stopScan() {
|
||||
cleanupWebScanResiduals();
|
||||
if (!isScanning()) {
|
||||
return;
|
||||
}
|
||||
const resolve = __finishScan();
|
||||
_scan_closing = true;
|
||||
__stopCurrentScan().then(() => {
|
||||
__closed();
|
||||
resolve && resolve({ cancel: 1 });
|
||||
_scan_closing = false;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -448,45 +653,23 @@ export function startScan() {
|
||||
});
|
||||
let scanPromise = Promise.resolve();
|
||||
if (inRuntime()) {
|
||||
scanPromise = __startBridgeScan();
|
||||
scanPromise = __startBridgeScan().catch(__fallbackScanAfterBridgeFailure);
|
||||
} else if (isSupportWxScan()) {
|
||||
scanPromise = __startWxScan();
|
||||
} else if (isSupportWebScan()) {
|
||||
scanPromise = __startWebScan();
|
||||
scanPromise = __startWebScan(true);
|
||||
} else if (isSupportImageScan()) {
|
||||
scanPromise = __startImageScan();
|
||||
} else {
|
||||
printDebug("Not support scanner");
|
||||
printWarn("Not support scanner");
|
||||
}
|
||||
return Promise.race([scanPromise, scannerPromise]);
|
||||
return withScanSessionTimeout(Promise.race([scanPromise, scannerPromise]));
|
||||
}).finally(() => {
|
||||
_scan_resolve = null;
|
||||
__closed();
|
||||
});
|
||||
}
|
||||
|
||||
export function scanVideo() {
|
||||
if (!isSupportWebScan()) {
|
||||
printDebug("Not support video scanner");
|
||||
return;
|
||||
}
|
||||
if (isScanning() || _scan_closing || Date.now() < _scan_next_start_time) {
|
||||
return;
|
||||
}
|
||||
unlockScanBeep();
|
||||
Promise.resolve().then(() => {
|
||||
__scanning();
|
||||
return startScanForWeb(__result).then(resp => {
|
||||
if (resp && resp.result) {
|
||||
__result(resp.result);
|
||||
}
|
||||
throw resp.error;
|
||||
}).catch(err => { });
|
||||
}).finally(() => {
|
||||
__closed();
|
||||
});
|
||||
}
|
||||
|
||||
export function scanImage() {
|
||||
if (!isSupportImageScan()) {
|
||||
printDebug("Not support image scanner");
|
||||
@@ -496,14 +679,49 @@ export function scanImage() {
|
||||
return;
|
||||
}
|
||||
unlockScanBeep();
|
||||
Promise.resolve().then(() => {
|
||||
__scanning();
|
||||
return startScanForImage().then(resp => {
|
||||
if (resp && resp.result) {
|
||||
__result(resp.result);
|
||||
__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;
|
||||
}
|
||||
throw resp.error;
|
||||
}).catch(err => { });
|
||||
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();
|
||||
});
|
||||
@@ -512,19 +730,30 @@ export function scanImage() {
|
||||
export const supportList = [
|
||||
{
|
||||
name: "bridge",
|
||||
support: !!inRuntime()
|
||||
get support() {
|
||||
return !!inRuntime();
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "wx",
|
||||
support: !!isSupportWxScan()
|
||||
get support() {
|
||||
return !!isSupportWxScan();
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "web",
|
||||
support: !!isSupportWebScan()
|
||||
get support() {
|
||||
if (shouldSkipWebCameraProbe()) {
|
||||
return false;
|
||||
}
|
||||
return !!canUseWebCameraScan();
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "image",
|
||||
support: !!isSupportImageScan()
|
||||
get support() {
|
||||
return !!isSupportImageScan();
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "scanner",
|
||||
|
||||
Reference in New Issue
Block a user