init commit
This commit is contained in:
137
src/services/bridge/appbridge.js
Normal file
137
src/services/bridge/appbridge.js
Normal file
@@ -0,0 +1,137 @@
|
||||
import { toAny } from "../../utils/toany";
|
||||
import { printDebug, printWarn } from "../../utils/logger";
|
||||
import { createUUID } from "../../utils/uuid";
|
||||
|
||||
let _events = {};
|
||||
let _callbacks = {};
|
||||
let _bridge = "__bridge_client__";
|
||||
|
||||
function _callRuntime(func, ...options) {
|
||||
let funcs = func.split('.');
|
||||
let instant = window;
|
||||
while (funcs.length > 1) {
|
||||
instant = instant[funcs.shift()];
|
||||
}
|
||||
if (instant && funcs.length == 1) {
|
||||
if (instant.hasOwnProperty(funcs[0])) {
|
||||
return instant[funcs[0]](...options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onCallback(name, callback) {
|
||||
if (callback && typeof callback === 'function') {
|
||||
_callbacks[name] = callback;
|
||||
}
|
||||
}
|
||||
|
||||
function _checkInit() {
|
||||
let methodName = `${_bridge}_handle_callback`;
|
||||
if (!window[methodName]) {
|
||||
window[methodName] = (res) => {
|
||||
let { method, payload, code, request_id } = toAny(res, {});
|
||||
let data = toAny(payload, {});
|
||||
if (request_id) {
|
||||
_callbacks[request_id] && _callbacks[request_id](code, data);
|
||||
} else if (_events[method]) {
|
||||
_events[method].forEach((callback) => {
|
||||
callback && callback(data);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function inRuntime() {
|
||||
return !!window[_bridge]
|
||||
}
|
||||
|
||||
export function onEvent(method, callback) {
|
||||
_checkInit();
|
||||
if (method && callback && typeof callback == 'function') {
|
||||
let list = _events[method] || [];
|
||||
if (list.indexOf(callback) == -1) {
|
||||
list.push(callback);
|
||||
}
|
||||
_events[method] = list;
|
||||
}
|
||||
}
|
||||
|
||||
export function offEvent(method, callback) {
|
||||
_checkInit();
|
||||
if (method && callback && typeof callback == 'function') {
|
||||
let list = _events[method] || [];
|
||||
let index = list.indexOf(callback);
|
||||
if (index >= 0) {
|
||||
list.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function bridgeSync(method, data) {
|
||||
_checkInit();
|
||||
let result = toAny(_callRuntime(`${_bridge}.call`, method, toAny(data, "")));
|
||||
let options = [method];
|
||||
if (data) {
|
||||
options.push("params:")
|
||||
options.push(data)
|
||||
}
|
||||
if (result) {
|
||||
options.push("result:")
|
||||
options.push(result)
|
||||
}
|
||||
printDebug('bridge call >>>', ...options);
|
||||
return result;
|
||||
}
|
||||
|
||||
export function bridgeAsync(method, data, timeout) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (inRuntime()) {
|
||||
let called = false;
|
||||
let timeObj = null;
|
||||
if (timeout > 0) {
|
||||
timeObj = setTimeout(() => {
|
||||
called = true;
|
||||
timeObj = null;
|
||||
reject("bridgeAsync timeout");
|
||||
}, timeout);
|
||||
}
|
||||
let request_id = createUUID() + "_" + Date.now();
|
||||
onCallback(request_id, (code, data) => {
|
||||
if (called) {
|
||||
return;
|
||||
}
|
||||
if (timeObj) {
|
||||
clearTimeout(timeObj);
|
||||
}
|
||||
if (code == 0) {
|
||||
resolve(data);
|
||||
} else {
|
||||
reject(data);
|
||||
}
|
||||
});
|
||||
bridgeSync(method, Object.assign({ request_id }, data));
|
||||
} else {
|
||||
reject(`Can't bridgeAsync, because not in runtime`);
|
||||
}
|
||||
}).then(res => {
|
||||
let options = [method];
|
||||
if (data) {
|
||||
options.push("params:")
|
||||
options.push(data)
|
||||
}
|
||||
if (res) {
|
||||
options.push("resp:")
|
||||
options.push(res)
|
||||
}
|
||||
printDebug('bridge resp >>>', ...options);
|
||||
return res;
|
||||
}).catch(err => {
|
||||
if (data) {
|
||||
printWarn('bridge err >>>', method, "params:", data, err);
|
||||
} else {
|
||||
printWarn('bridge err >>>', method, err);
|
||||
}
|
||||
throw err;
|
||||
})
|
||||
}
|
||||
27
src/services/config.js
Normal file
27
src/services/config.js
Normal file
@@ -0,0 +1,27 @@
|
||||
let _defineConfig = {
|
||||
version: "${lib_version}"
|
||||
}
|
||||
|
||||
let _defConfig = {
|
||||
}
|
||||
|
||||
let _customConfig = {
|
||||
}
|
||||
|
||||
export function getVersion() {
|
||||
return _defineConfig.version;
|
||||
}
|
||||
|
||||
export function getConfig(key) {
|
||||
let item = _customConfig[key];
|
||||
if (!item) {
|
||||
item = _defConfig[key];
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
export function setConfig(config) {
|
||||
if (config && typeof config == "object") {
|
||||
Object.assign(_customConfig, config);
|
||||
}
|
||||
}
|
||||
50
src/services/encrypt.js
Normal file
50
src/services/encrypt.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import ase from "browserify-cipher";
|
||||
import { hex_md5 } from "../utils/md5";
|
||||
import { toAny } from "../utils/toany";
|
||||
import { printDebug } from "../utils/logger";
|
||||
import { getAppId } from "./env";
|
||||
|
||||
let _encryptConfig = {
|
||||
"transformation": "AES/CBC/PKCS5Padding",
|
||||
"algorithm": "aes-128-cbc",
|
||||
"key": "ohogame",
|
||||
"key_size": 16
|
||||
}
|
||||
|
||||
export function encryptData({ iv, data }) {
|
||||
try {
|
||||
let encrypt_data = data;
|
||||
let app = getAppId();
|
||||
let options = _encryptConfig;
|
||||
let realIv = Buffer.from(hex_md5(iv + "_" + app).substring(4, 4 + options.key_size));
|
||||
let realKey = hex_md5(options.key + "_" + app).substring(4, 4 + options.key_size);
|
||||
let realData = Buffer.from(encrypt_data);
|
||||
let decipher = ase.createCipheriv(options.algorithm, realKey, realIv)
|
||||
decipher.setAutoPadding(true)
|
||||
let decoded = decipher.update(realData, 'binary', 'base64');
|
||||
decoded += decipher.final('base64');
|
||||
return decoded;
|
||||
} catch (e) {
|
||||
printDebug("encryptData fail", e);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
export function decryptData({ iv, data }) {
|
||||
try {
|
||||
let encrypt_data = data;
|
||||
let app = getAppId();
|
||||
let options = _encryptConfig;
|
||||
let realIv = Buffer.from(hex_md5(iv + "_" + app).substring(4, 4 + options.key_size));
|
||||
let realKey = hex_md5(options.key + "_" + app).substring(4, 4 + options.key_size);
|
||||
let realData = Buffer.from(encrypt_data.split(" ").join("+"), 'base64');
|
||||
let decipher = ase.createDecipheriv(options.algorithm, realKey, realIv)
|
||||
decipher.setAutoPadding(true)
|
||||
let decoded = decipher.update(realData, 'binary', 'utf8');
|
||||
decoded += decipher.final('utf8');
|
||||
return toAny(decoded);
|
||||
} catch (e) {
|
||||
printDebug("decryptData fail", e);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
17
src/services/env.js
Normal file
17
src/services/env.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import _global from "../polyfill/_global";
|
||||
|
||||
|
||||
export function execFunc(target, func, ...options) {
|
||||
if (target) {
|
||||
let funcs = func.split('.');
|
||||
let instant = target;
|
||||
while (funcs.length > 1) {
|
||||
instant = instant[funcs.shift()];
|
||||
}
|
||||
if (instant && funcs.length == 1) {
|
||||
if (instant.hasOwnProperty(funcs[0])) {
|
||||
return instant[funcs[0]](...options);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
339
src/services/provider/scan.js
Normal file
339
src/services/provider/scan.js
Normal file
@@ -0,0 +1,339 @@
|
||||
import { inRuntime, bridgeAsync } from "../bridge/appbridge";
|
||||
import { isSupportWebScan, startScanForWeb, stopScanForWeb, isSupportImageScan, startScanForImage } from "../web";
|
||||
import { isSupportWxScan, startScanForWx } from "../wx";
|
||||
import { startScanner, stopScanner } from "../scanner";
|
||||
import { getConfig } from "../config";
|
||||
|
||||
let _scan_status = "closed";
|
||||
let _scan_status_listener = null;
|
||||
let _scan_listener_list = [];
|
||||
let _scan_resolve = null;
|
||||
let _scan_closing = false;
|
||||
let _scan_next_start_time = 0;
|
||||
|
||||
const SCAN_RESTART_DELAY = 2000;
|
||||
|
||||
function __checkScanner() {
|
||||
if (_scan_listener_list.length > 0) {
|
||||
startScanner((result) => {
|
||||
__scannerResult(result);
|
||||
});
|
||||
} 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 __result(result) {
|
||||
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 });
|
||||
console.log("__result", { result, key: item.key });
|
||||
}
|
||||
}
|
||||
if (matched) {
|
||||
_scan_next_start_time = Date.now() + SCAN_RESTART_DELAY;
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
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 !== "closed") {
|
||||
_scan_status = "closed";
|
||||
if (_scan_status_listener) {
|
||||
_scan_status_listener({ status: "closed" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
function __scannerResult(result) {
|
||||
if (!__hasMatchedListener(result)) {
|
||||
return;
|
||||
}
|
||||
if (isScanning()) {
|
||||
const resolve = __finishScan();
|
||||
_scan_closing = true;
|
||||
__stopCurrentScan().then(() => {
|
||||
setTimeout(() => {
|
||||
_scan_closing = false;
|
||||
}, 0);
|
||||
});
|
||||
__result(result);
|
||||
resolve && resolve({
|
||||
result
|
||||
});
|
||||
return;
|
||||
}
|
||||
__result(result);
|
||||
}
|
||||
|
||||
function __startBridgeScan() {
|
||||
return bridgeAsync("startScan", {
|
||||
closeable: true
|
||||
}).then(resp => {
|
||||
if (!isScanning()) {
|
||||
return resp;
|
||||
}
|
||||
if (!resp || !resp.result) {
|
||||
return resp;
|
||||
}
|
||||
if (__result(resp.result)) {
|
||||
return resp;
|
||||
}
|
||||
if (isScanning()) {
|
||||
return __startBridgeScan();
|
||||
}
|
||||
return resp;
|
||||
}).catch(err => {
|
||||
if (!isScanning()) {
|
||||
return err;
|
||||
}
|
||||
if (!err || !err.result) {
|
||||
return err;
|
||||
}
|
||||
if (__result(err.result)) {
|
||||
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.result) {
|
||||
return resp;
|
||||
}
|
||||
if (__result(resp.result)) {
|
||||
return resp;
|
||||
}
|
||||
if (isScanning()) {
|
||||
return __startWxScan();
|
||||
}
|
||||
return resp;
|
||||
}).catch(err => {
|
||||
if (!isScanning()) {
|
||||
return err;
|
||||
}
|
||||
if (!err || !err.result) {
|
||||
return err;
|
||||
}
|
||||
if (__result(err.result)) {
|
||||
return err;
|
||||
}
|
||||
if (isScanning()) {
|
||||
return __startWxScan();
|
||||
}
|
||||
return err;
|
||||
});
|
||||
}
|
||||
|
||||
function __startImageScan() {
|
||||
return startScanForImage().then(resp => {
|
||||
if (!isScanning()) {
|
||||
return resp;
|
||||
}
|
||||
if (!resp || !resp.result) {
|
||||
return resp;
|
||||
}
|
||||
if (__result(resp.result)) {
|
||||
return resp;
|
||||
}
|
||||
if (isScanning()) {
|
||||
return __startImageScan();
|
||||
}
|
||||
return resp;
|
||||
}).catch(err => {
|
||||
if (!isScanning()) {
|
||||
return err;
|
||||
}
|
||||
if (!err || !err.result) {
|
||||
return err;
|
||||
}
|
||||
if (__result(err.result)) {
|
||||
return err;
|
||||
}
|
||||
if (isScanning()) {
|
||||
return __startImageScan();
|
||||
}
|
||||
return err;
|
||||
});
|
||||
}
|
||||
|
||||
export function isScanning() {
|
||||
return _scan_status === "scanning";
|
||||
}
|
||||
|
||||
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;
|
||||
__checkScanner();
|
||||
}
|
||||
|
||||
export function onScanListener(listener, key, match, level) {
|
||||
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.listener === listener) {
|
||||
item = _i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (item) {
|
||||
item.key = key;
|
||||
item.level = level;
|
||||
item.match = match;
|
||||
} else {
|
||||
item = {
|
||||
key,
|
||||
match,
|
||||
level,
|
||||
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 (_i.listener === listener) {
|
||||
_i.cancel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
__checkScanner();
|
||||
}
|
||||
|
||||
export function setStatusListener(listener) {
|
||||
if (typeof listener !== 'function') {
|
||||
return;
|
||||
}
|
||||
_scan_status_listener = listener;
|
||||
}
|
||||
|
||||
export function getStatus() {
|
||||
return _scan_status;
|
||||
}
|
||||
|
||||
export function stopScan() {
|
||||
if (!isScanning()) {
|
||||
return;
|
||||
}
|
||||
__stopCurrentScan().then(() => {
|
||||
__closed();
|
||||
});
|
||||
}
|
||||
|
||||
export function startScan() {
|
||||
if (isScanning() || _scan_closing || Date.now() < _scan_next_start_time) {
|
||||
return;
|
||||
}
|
||||
Promise.resolve().then(() => {
|
||||
__scanning();
|
||||
let scannerPromise = new Promise(resolve => {
|
||||
_scan_resolve = resolve;
|
||||
});
|
||||
let scanPromise = Promise.resolve();
|
||||
if (inRuntime()) {
|
||||
console.log("startScanForBridge");
|
||||
scanPromise = __startBridgeScan();
|
||||
} else if (isSupportWxScan()) {
|
||||
console.log("startScanForWx");
|
||||
scanPromise = __startWxScan();
|
||||
} else if (isSupportWebScan()) {
|
||||
console.log("startScanForWeb");
|
||||
scanPromise = startScanForWeb(getConfig("webCanvasStyle"), __result);
|
||||
} else if (isSupportImageScan()) {
|
||||
console.log("startScanForImage");
|
||||
scanPromise = __startImageScan();
|
||||
} else {
|
||||
console.log("not support scanner");
|
||||
}
|
||||
return Promise.race([scanPromise, scannerPromise]);
|
||||
}).finally(() => {
|
||||
_scan_resolve = null;
|
||||
__closed();
|
||||
});
|
||||
}
|
||||
90
src/services/scanner/index.js
Normal file
90
src/services/scanner/index.js
Normal file
@@ -0,0 +1,90 @@
|
||||
let _scannerCallback = null;
|
||||
let _scannerStatus = "closed";
|
||||
let _scannerValue = "";
|
||||
let _scannerTimer = null;
|
||||
let _scannerLastInputTime = 0;
|
||||
|
||||
const SCANNER_INPUT_INTERVAL = 100;
|
||||
|
||||
function clearScannerValue() {
|
||||
_scannerValue = "";
|
||||
_scannerLastInputTime = 0;
|
||||
if (_scannerTimer) {
|
||||
clearTimeout(_scannerTimer);
|
||||
_scannerTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function delayClearScannerValue() {
|
||||
if (_scannerTimer) {
|
||||
clearTimeout(_scannerTimer);
|
||||
}
|
||||
_scannerTimer = setTimeout(() => {
|
||||
clearScannerValue();
|
||||
}, SCANNER_INPUT_INTERVAL);
|
||||
}
|
||||
|
||||
function normalizeScannerValue(value) {
|
||||
return value.replace(/[\uFF01-\uFF5E]/g, char => {
|
||||
return String.fromCharCode(char.charCodeAt(0) - 0xFEE0);
|
||||
}).replace(/\u3002/g, ".");
|
||||
}
|
||||
|
||||
function stopScannerEvent(event) {
|
||||
event.preventDefault && event.preventDefault();
|
||||
event.stopPropagation && event.stopPropagation();
|
||||
}
|
||||
|
||||
function onScannerKeydown(event) {
|
||||
if (_scannerStatus !== "scanning") {
|
||||
return;
|
||||
}
|
||||
if (event.ctrlKey || event.metaKey || event.altKey) {
|
||||
return;
|
||||
}
|
||||
if (event.key === "Enter") {
|
||||
const result = normalizeScannerValue(_scannerValue);
|
||||
if (result) {
|
||||
stopScannerEvent(event);
|
||||
}
|
||||
console.log("onScannerKeydown", result);
|
||||
clearScannerValue();
|
||||
if (result && _scannerCallback) {
|
||||
_scannerCallback(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!event.key || event.key.length !== 1) {
|
||||
return;
|
||||
}
|
||||
const now = Date.now();
|
||||
if (_scannerLastInputTime && now - _scannerLastInputTime > SCANNER_INPUT_INTERVAL) {
|
||||
clearScannerValue();
|
||||
}
|
||||
_scannerLastInputTime = now;
|
||||
_scannerValue += event.key;
|
||||
delayClearScannerValue();
|
||||
}
|
||||
|
||||
export function startScanner(callback){
|
||||
if (!callback || typeof callback !== "function") {
|
||||
return;
|
||||
}
|
||||
_scannerCallback = callback;
|
||||
if (_scannerStatus === "scanning") {
|
||||
return;
|
||||
}
|
||||
_scannerStatus = "scanning";
|
||||
clearScannerValue();
|
||||
window.addEventListener("keydown", onScannerKeydown);
|
||||
}
|
||||
|
||||
export function stopScanner(){
|
||||
if (_scannerStatus !== "scanning") {
|
||||
return;
|
||||
}
|
||||
_scannerStatus = "closed";
|
||||
_scannerCallback = null;
|
||||
clearScannerValue();
|
||||
window.removeEventListener("keydown", onScannerKeydown);
|
||||
}
|
||||
327
src/services/web/index.js
Normal file
327
src/services/web/index.js
Normal file
@@ -0,0 +1,327 @@
|
||||
import { createUUID } from "../../utils/uuid";
|
||||
import { getConfig } from "../config";
|
||||
|
||||
const scanWeb = {
|
||||
uuid: null,
|
||||
finish: true
|
||||
}
|
||||
|
||||
function removeEl(id) {
|
||||
try {
|
||||
let el = document.getElementById(id);
|
||||
document.body.removeChild(el);
|
||||
} catch (error) {
|
||||
}
|
||||
}
|
||||
|
||||
function createEl(tagName, id, style, appendChild) {
|
||||
let el = document.getElementById(id);
|
||||
if (!el) {
|
||||
el = document.createElement(tagName);
|
||||
el.id = id;
|
||||
el.style = style;
|
||||
appendChild && document.body.appendChild(el);
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
function canvasDrawLine(context, width, begin, end, color) {
|
||||
context.beginPath();
|
||||
context.moveTo(width - begin.x, begin.y);
|
||||
context.lineTo(width - end.x, end.y);
|
||||
context.lineWidth = 4;
|
||||
context.strokeStyle = color;
|
||||
context.stroke();
|
||||
}
|
||||
|
||||
function getBarcodeFormats(scanType) {
|
||||
let formats = [];
|
||||
if (!scanType) {
|
||||
scanType = ["qrCode", "barCode"];
|
||||
}
|
||||
if (scanType.includes('qrCode')) {
|
||||
formats.push('qr_code');
|
||||
}
|
||||
if (scanType.includes('barCode')) {
|
||||
formats.push(
|
||||
'ean_13',
|
||||
'ean_8',
|
||||
'code_128',
|
||||
'code_39',
|
||||
'codabar',
|
||||
'upc_a',
|
||||
'upc_e',
|
||||
'itf',
|
||||
'aztec',
|
||||
'data_matrix',
|
||||
'pdf417'
|
||||
);
|
||||
}
|
||||
return formats;
|
||||
}
|
||||
|
||||
function createBarcodeDetector(scanType) {
|
||||
return Promise.resolve().then(() => {
|
||||
if (typeof BarcodeDetector === 'undefined') {
|
||||
throw new Error("BarcodeDetector is not supported");
|
||||
}
|
||||
const formats = getBarcodeFormats(scanType);
|
||||
if (BarcodeDetector.getSupportedFormats) {
|
||||
return BarcodeDetector.getSupportedFormats().then(supportedFormats => {
|
||||
const supported = formats.filter(format => supportedFormats.indexOf(format) !== -1);
|
||||
if (!supported.length) {
|
||||
throw new Error("No supported barcode formats");
|
||||
}
|
||||
return new BarcodeDetector({ formats: supported });
|
||||
});
|
||||
}
|
||||
return new BarcodeDetector({ formats });
|
||||
});
|
||||
}
|
||||
|
||||
function drawBarcode(context, width, barcode) {
|
||||
const cornerPoints = barcode.cornerPoints;
|
||||
if (cornerPoints && cornerPoints.length) {
|
||||
for (let i = 0; i < cornerPoints.length; i++) {
|
||||
canvasDrawLine(context, width, cornerPoints[i], cornerPoints[(i + 1) % cornerPoints.length], "#FF3B58");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (barcode.boundingBox) {
|
||||
const rect = barcode.boundingBox;
|
||||
const points = [
|
||||
{ x: rect.x, y: rect.y },
|
||||
{ x: rect.x + rect.width, y: rect.y },
|
||||
{ x: rect.x + rect.width, y: rect.y + rect.height },
|
||||
{ x: rect.x, y: rect.y + rect.height }
|
||||
];
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
canvasDrawLine(context, width, points[i], points[(i + 1) % points.length], "#FF3B58");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function isSupportWebScan() {
|
||||
return typeof navigator !== 'undefined'
|
||||
&& navigator.mediaDevices
|
||||
&& navigator.mediaDevices.getUserMedia
|
||||
&& typeof BarcodeDetector !== 'undefined';
|
||||
}
|
||||
|
||||
export function isSupportImageScan() {
|
||||
return typeof document !== 'undefined'
|
||||
&& typeof BarcodeDetector !== 'undefined'
|
||||
&& typeof URL !== 'undefined'
|
||||
&& URL.createObjectURL;
|
||||
}
|
||||
|
||||
export function stopScanForWeb() {
|
||||
return Promise.resolve().then(() => {
|
||||
scanWeb.uuid = null;
|
||||
})
|
||||
}
|
||||
|
||||
function chooseImageFile() {
|
||||
return new Promise(resolve => {
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.accept = "image/*";
|
||||
input.style.display = "none";
|
||||
let finished = false;
|
||||
let finish = file => {
|
||||
if (finished) {
|
||||
return;
|
||||
}
|
||||
finished = true;
|
||||
removeEl("__webscan_image_input__");
|
||||
resolve(file);
|
||||
};
|
||||
input.id = "__webscan_image_input__";
|
||||
input.onchange = () => {
|
||||
finish(input.files && input.files[0]);
|
||||
};
|
||||
input.oncancel = () => {
|
||||
finish(null);
|
||||
};
|
||||
document.body.appendChild(input);
|
||||
input.click();
|
||||
});
|
||||
}
|
||||
|
||||
function detectImageFile(detector, file) {
|
||||
if (!file) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
if (typeof createImageBitmap !== 'undefined') {
|
||||
return createImageBitmap(file).then(image => {
|
||||
return detector.detect(image).then(barcodes => {
|
||||
image.close && image.close();
|
||||
return barcodes && barcodes[0];
|
||||
}).catch(err => {
|
||||
image.close && image.close();
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const image = new Image();
|
||||
const url = URL.createObjectURL(file);
|
||||
image.onload = () => {
|
||||
detector.detect(image).then(barcodes => {
|
||||
URL.revokeObjectURL(url);
|
||||
resolve(barcodes && barcodes[0]);
|
||||
}).catch(err => {
|
||||
URL.revokeObjectURL(url);
|
||||
reject(err);
|
||||
});
|
||||
};
|
||||
image.onerror = err => {
|
||||
URL.revokeObjectURL(url);
|
||||
reject(err);
|
||||
};
|
||||
image.src = url;
|
||||
});
|
||||
}
|
||||
|
||||
export function startScanForImage() {
|
||||
return createBarcodeDetector(getConfig("scanType")).then(detector => {
|
||||
return chooseImageFile().then(file => detectImageFile(detector, file));
|
||||
}).then(code => {
|
||||
if (code && code.rawValue) {
|
||||
return {
|
||||
result: code.rawValue
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
error: "未识别到二维码或条形码"
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function startScanForWeb(canvasStyle, onResult) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
scanWeb.uuid = createUUID();
|
||||
scanWeb.finish = false;
|
||||
let videoEl = createEl("video",
|
||||
"__webscan_video__",
|
||||
"display: none", false);
|
||||
let canvasDisplay = "";
|
||||
let canvasBaseStyle = canvasStyle || "position: fixed; width: 300px; height: 240px; top: 0; left: 0; z-index: 9999;";
|
||||
let canvasEl = createEl("canvas",
|
||||
"__webscan_canvas__",
|
||||
canvasBaseStyle + " display: none;", true);
|
||||
canvasDisplay = canvasEl.style.display;
|
||||
canvasEl.style.cssText = canvasBaseStyle;
|
||||
canvasDisplay = canvasEl.style.display;
|
||||
canvasEl.style.display = "none";
|
||||
let context = canvasEl.getContext("2d");
|
||||
let currentUuid = scanWeb.uuid;
|
||||
videoEl.width = 300;
|
||||
videoEl.height = 300;
|
||||
videoEl.uuid = scanWeb.uuid;
|
||||
createBarcodeDetector(getConfig("scanType")).then(detector => {
|
||||
return navigator.mediaDevices.getUserMedia({
|
||||
video: {
|
||||
facingMode: "environment"
|
||||
}
|
||||
}).then(stream => {
|
||||
return {
|
||||
detector,
|
||||
stream
|
||||
};
|
||||
});
|
||||
}).then(function (options) {
|
||||
const detector = options.detector;
|
||||
const stream = options.stream;
|
||||
videoEl.srcObject = stream;
|
||||
videoEl.setAttribute("playsinline", true); // iOS使用
|
||||
videoEl.play();
|
||||
let closeWebScan = getConfig("closeWebScan", () => {
|
||||
// no thing
|
||||
});
|
||||
let displayWebScan = getConfig("displayWebScan", (canvasEl, cancal) => {
|
||||
// no thing
|
||||
});
|
||||
displayWebScan && displayWebScan(canvasEl, () => {
|
||||
stopScanForWeb();
|
||||
});
|
||||
canvasEl.style.display = "none";
|
||||
let detecting = false;
|
||||
let displayed = false;
|
||||
let closed = false;
|
||||
let close = () => {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
closed = true;
|
||||
try {
|
||||
stream.getTracks()[0].stop();
|
||||
} catch (_e) { }
|
||||
closeWebScan && closeWebScan();
|
||||
};
|
||||
let tick = () => {
|
||||
try {
|
||||
if (videoEl.readyState === videoEl.HAVE_ENOUGH_DATA && !detecting) {
|
||||
canvasEl.height = videoEl.videoHeight;
|
||||
canvasEl.width = videoEl.videoWidth;
|
||||
context.setTransform(-1, 0, 0, 1, canvasEl.width, 0);
|
||||
context.drawImage(videoEl, 0, 0, canvasEl.width, canvasEl.height);
|
||||
context.setTransform(1, 0, 0, 1, 0, 0);
|
||||
if (!displayed) {
|
||||
displayed = true;
|
||||
canvasEl.style.display = canvasDisplay || "";
|
||||
}
|
||||
detecting = true;
|
||||
detector.detect(videoEl).then(barcodes => {
|
||||
const code = barcodes && barcodes[0];
|
||||
if (code && code.rawValue && scanWeb.uuid == currentUuid) {
|
||||
if (onResult && !onResult(code.rawValue)) {
|
||||
return;
|
||||
}
|
||||
drawBarcode(context, canvasEl.width, code);
|
||||
scanWeb.uuid = null;
|
||||
scanWeb.finish = true;
|
||||
close();
|
||||
resolve({
|
||||
result: code.rawValue
|
||||
})
|
||||
}
|
||||
}).catch(() => {
|
||||
}).finally(() => {
|
||||
detecting = false;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
detecting = false;
|
||||
}
|
||||
if (scanWeb.uuid == currentUuid) {
|
||||
requestAnimationFrame(() => {
|
||||
tick()
|
||||
});
|
||||
} else {
|
||||
if (!scanWeb.finish) {
|
||||
reject({ cancel: 1 });
|
||||
scanWeb.finish = true;
|
||||
}
|
||||
close();
|
||||
}
|
||||
if (scanWeb.finish) {
|
||||
close();
|
||||
}
|
||||
};
|
||||
requestAnimationFrame(() => {
|
||||
tick();
|
||||
});
|
||||
}).catch(err => {
|
||||
reject({ error: err });
|
||||
});
|
||||
} catch (e) {
|
||||
reject({ error: e });
|
||||
}
|
||||
}).finally(() => {
|
||||
removeEl("__webscan_video__");
|
||||
removeEl("__webscan_canvas__");
|
||||
})
|
||||
}
|
||||
162
src/services/wx/index.js
Normal file
162
src/services/wx/index.js
Normal file
@@ -0,0 +1,162 @@
|
||||
import { getConfig } from "../config";
|
||||
import { request } from "../../utils/request";
|
||||
import { toAny } from "../../utils/toany";
|
||||
|
||||
const WX_JS_SDK_URL = "https://res.wx.qq.com/open/js/jweixin-1.6.0.js";
|
||||
const WX_SCAN_API = "scanQRCode";
|
||||
|
||||
let _wxReadyPromise = null;
|
||||
let _wxReady = false;
|
||||
|
||||
function getWx() {
|
||||
if (typeof window === "undefined") {
|
||||
return null;
|
||||
}
|
||||
return window.wx;
|
||||
}
|
||||
|
||||
function loadWxScript() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const wx = getWx();
|
||||
if (wx && wx.config && wx.scanQRCode) {
|
||||
resolve(wx);
|
||||
return;
|
||||
}
|
||||
let initWechatJssdk = toAny(getConfig("initWechatJssdk"), {});
|
||||
let sdkUrl = toAny(initWechatJssdk.sdkUrl, WX_JS_SDK_URL);
|
||||
if (!sdkUrl) {
|
||||
reject(new Error("initWechatJssdk.sdkUrl is required, but not found"));
|
||||
return;
|
||||
}
|
||||
let script = document.getElementById("__wx_jssdk__");
|
||||
if (script) {
|
||||
script.addEventListener("load", () => resolve(getWx()));
|
||||
script.addEventListener("error", reject);
|
||||
return;
|
||||
}
|
||||
script = document.createElement("script");
|
||||
script.id = "__wx_jssdk__";
|
||||
script.src = sdkUrl;
|
||||
script.onload = () => resolve(getWx());
|
||||
script.onerror = reject;
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
function fetchWxConfig() {
|
||||
let initWechatJssdk = toAny(getConfig("initWechatJssdk"), {});
|
||||
let apiUrl = toAny(initWechatJssdk.apiUrl, "");
|
||||
if (!apiUrl) {
|
||||
return Promise.reject(new Error("initWechatJssdk.apiUrl is required, but not found"));
|
||||
}
|
||||
return request({
|
||||
url: apiUrl,
|
||||
method: "GET",
|
||||
data: {
|
||||
url: window.location.href.split("#")[0]
|
||||
}
|
||||
}).then(res => {
|
||||
let data = toAny(res.data, {});
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
if (data.code !== 0) {
|
||||
throw new Error(data.msg || "wx config fetch failed");
|
||||
}
|
||||
if (data.data) {
|
||||
return data.data;
|
||||
}
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
export function isWxEnv() {
|
||||
return typeof navigator !== "undefined"
|
||||
&& /micromessenger/i.test(navigator.userAgent || "");
|
||||
}
|
||||
|
||||
export function isSupportWxScan() {
|
||||
const wx = getWx();
|
||||
return isWxEnv()
|
||||
&& _wxReady
|
||||
&& wx
|
||||
&& wx.scanQRCode;
|
||||
}
|
||||
|
||||
export function initWxJssdk() {
|
||||
if (!isWxEnv()) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (_wxReadyPromise) {
|
||||
return _wxReadyPromise;
|
||||
}
|
||||
_wxReadyPromise = Promise.all([loadWxScript(), fetchWxConfig()]).then(items => {
|
||||
const wx = items[0];
|
||||
const config = items[1];
|
||||
if (!wx || !wx.config) {
|
||||
throw new Error("wx jssdk is not ready");
|
||||
}
|
||||
if (!config) {
|
||||
throw new Error("wx config is empty");
|
||||
}
|
||||
const jsApiList = config.jsApiList || [];
|
||||
if (jsApiList.indexOf(WX_SCAN_API) === -1) {
|
||||
jsApiList.push(WX_SCAN_API);
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.ready(() => {
|
||||
_wxReady = true;
|
||||
resolve(wx);
|
||||
});
|
||||
wx.error(err => {
|
||||
_wxReady = false;
|
||||
_wxReadyPromise = null;
|
||||
reject(err);
|
||||
});
|
||||
wx.config(Object.assign({}, config, {
|
||||
jsApiList
|
||||
}));
|
||||
});
|
||||
}).catch(err => {
|
||||
_wxReady = false;
|
||||
_wxReadyPromise = null;
|
||||
throw err;
|
||||
});
|
||||
return _wxReadyPromise;
|
||||
}
|
||||
|
||||
export function startScanForWx(options) {
|
||||
return initWxJssdk().then(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const {
|
||||
needResult = 1,
|
||||
scanType = ["qrCode", "barCode"]
|
||||
} = options || {};
|
||||
const wx = getWx();
|
||||
if (!wx || !wx.scanQRCode) {
|
||||
reject(new Error("wx.scanQRCode is not supported"));
|
||||
return;
|
||||
}
|
||||
wx.scanQRCode({
|
||||
needResult,
|
||||
scanType,
|
||||
success: res => {
|
||||
resolve({
|
||||
success: true,
|
||||
result: res.resultStr,
|
||||
code: res.resultStr
|
||||
});
|
||||
},
|
||||
cancel: () => {
|
||||
resolve({
|
||||
success: false,
|
||||
error: "用户取消扫码"
|
||||
});
|
||||
},
|
||||
fail: err => {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user