423 lines
11 KiB
JavaScript
423 lines
11 KiB
JavaScript
var debug_taps = 0;
|
|
var debug_logs = [];
|
|
var track;
|
|
var frame_width;
|
|
var frame_height;
|
|
var scanguide_anime;
|
|
var busy = false;
|
|
const max_inflight = 5;
|
|
var should_check_auto_torch = true;
|
|
var done_checking_auto_torch = false;
|
|
var camera_sensitivity = 1.0;
|
|
var camera_capabilities = null;
|
|
|
|
var Module = {
|
|
onRuntimeInitialized: start,
|
|
};
|
|
|
|
function get_query(key) {
|
|
const qs = window.location.search;
|
|
const params = new URLSearchParams(qs);
|
|
return params.get(key) || "";
|
|
}
|
|
|
|
function show_debug() {
|
|
const debug_div = document.getElementById("debug_div");
|
|
debug_div.classList.remove("hidden");
|
|
}
|
|
function hide_debug() {
|
|
const debug_div = document.getElementById("debug_div");
|
|
debug_div.classList.add("hidden");
|
|
}
|
|
function debug_countdown() {
|
|
debug_taps += 1;
|
|
if (debug_taps > 10) {
|
|
show_debug();
|
|
}
|
|
}
|
|
|
|
function hide_loading() {
|
|
const loading = document.getElementById("loading");
|
|
loading.classList.add("hidden");
|
|
}
|
|
|
|
function handle_stream(stream) {
|
|
const video = document.querySelector('video');
|
|
const videoTracks = stream.getVideoTracks();
|
|
debug_log(`total video tracks: ${videoTracks.length} Using video device: ${videoTracks[0].label}`);
|
|
window.stream = stream; // make variable available to browser console
|
|
video.srcObject = stream;
|
|
video.play();
|
|
track = videoTracks[0];
|
|
camera_capabilities = track.getCapabilities();
|
|
const caps = document.getElementById("caps");
|
|
caps.innerHTML = JSON.stringify(camera_capabilities);
|
|
const settings = track.getSettings();
|
|
console.log(settings);
|
|
const canvas = document.getElementById("original");
|
|
frame_width = settings.width;
|
|
frame_height = settings.height;
|
|
video.width = frame_width;
|
|
video.height = frame_height;
|
|
canvas.width = frame_width;
|
|
canvas.height = frame_height;
|
|
}
|
|
|
|
function handleError(error) {
|
|
debug_log(error);
|
|
console.log(error);
|
|
}
|
|
|
|
function debug_log(msg) {
|
|
debug_logs.push(Date.now() / 1000 + ": " + msg);
|
|
const nentries = 20;
|
|
while (debug_logs.length > nentries) {
|
|
debug_logs.shift();
|
|
}
|
|
var output = "";
|
|
for (var l of debug_logs) {
|
|
output = `<div>${l}</div>` + output;
|
|
}
|
|
console.log(msg);
|
|
const di = document.getElementById("logs");
|
|
di.innerHTML = output;
|
|
}
|
|
|
|
async function start_camera(e) {
|
|
try {
|
|
const constraints = window.constraints = {
|
|
audio: false,
|
|
video: {
|
|
facingMode: "environment",
|
|
focusDistance: 0.12,
|
|
focusMode: "manual",
|
|
width: { ideal: 2000 },
|
|
},
|
|
};
|
|
const supportedConstraints = navigator.mediaDevices.getSupportedConstraints();
|
|
const cons = document.getElementById("cons");
|
|
cons.innerHTML = JSON.stringify(supportedConstraints);
|
|
debug_log(navigator.userAgent);
|
|
|
|
const stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
handle_stream(stream);
|
|
const zoom = get_query("zoom");
|
|
if (zoom) {
|
|
set_zoom(zoom);
|
|
}
|
|
|
|
} catch (e) {
|
|
handleError(e);
|
|
}
|
|
}
|
|
|
|
function is_emblem_qr_pattern(p)
|
|
{
|
|
if (p.search(/code=[0-9a-zA-Z]+/) >= 0) return true;
|
|
if (p.search(/id=[0-9a-zA-Z]+/) >= 0) return true;
|
|
if (p.search(/c=[0-9a-zA-Z]+/) >= 0) return true;
|
|
if (p.search(/https:\/\/xy.ltd\/v\/[0-9a-zA-Z]+/) >= 0) return true;
|
|
return false;
|
|
}
|
|
|
|
function make_hint_text(r) {
|
|
var qr_is_valid = false;
|
|
if (r.qrcode && r.qrcode.length > 0) {
|
|
qr_is_valid = is_emblem_qr_pattern(r.qrcode);
|
|
if (!qr_is_valid) {
|
|
return "无效编码";
|
|
}
|
|
}
|
|
if (qr_is_valid) {
|
|
var err = r.err || "";
|
|
if (err.includes("margin too small")) {
|
|
return "对齐定位点";
|
|
} else if (err.includes("energy check failed") || err.includes("cannot detect angle")) {
|
|
return "移近一点";
|
|
}
|
|
}
|
|
return "对齐定位点";
|
|
}
|
|
|
|
function handle_frame() {
|
|
try {
|
|
do_handle_frame();
|
|
} catch (e) {
|
|
debug_log("handle frame exception: " + e);
|
|
}
|
|
setTimeout(handle_frame, 20);
|
|
}
|
|
|
|
image_data_urls = [];
|
|
|
|
function do_handle_frame() {
|
|
if (busy) return;
|
|
const canvas = document.getElementById("original");
|
|
const video = document.getElementById("video");
|
|
|
|
canvas.width = video.width;
|
|
canvas.height = video.height;
|
|
|
|
canvas.style.width = video.width / 4 + "px";
|
|
canvas.style.height = video.height / 4 + "px";
|
|
|
|
const ctx = canvas.getContext("2d");
|
|
ctx.drawImage(video, 0, 0);
|
|
const id = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
console.log(Module);
|
|
var buf = Module._malloc(id.data.length * id.data.BYTES_PER_ELEMENT);
|
|
Module.HEAPU8.set(id.data, buf);
|
|
var r = Module.ccall('qrtool_angle', 'string', ['number', 'number', 'number', 'number', 'number'], [buf, id.width, id.height, 0, camera_sensitivity]);
|
|
Module._free(buf);
|
|
debug_log(r);
|
|
const res = JSON.parse(r);
|
|
const is_valid_pattern = res.qrcode && res.qrcode.length && is_emblem_qr_pattern(res.qrcode);
|
|
if (res.qrcode && should_check_auto_torch) {
|
|
start_check_auto_torch(res.qrcode);
|
|
}
|
|
if (done_checking_auto_torch && is_valid_pattern && res.ok) {
|
|
var data_url = canvas.toDataURL("image/jpeg", 1.0);
|
|
image_data_urls.push(data_url);
|
|
if (image_data_urls.length >= 3) {
|
|
submit_image(res.qrcode, res.angle, image_data_urls);
|
|
image_data_urls = [];
|
|
} else {
|
|
pending_hint = make_hint_text(res);
|
|
}
|
|
} else {
|
|
pending_hint = make_hint_text(res);
|
|
}
|
|
}
|
|
|
|
async function start_check_auto_torch(qrcode)
|
|
{
|
|
should_check_auto_torch = false;
|
|
var r = await fetch("https://themblem.com/api/v1/check-auto-torch/?qrcode=" + encodeURIComponent(qrcode), {
|
|
method: "GET",
|
|
});
|
|
var d = await r.json();
|
|
debug_log(JSON.stringify(d));
|
|
if (d.enable_auto_torch && !torch) {
|
|
toggle_torch();
|
|
}
|
|
camera_sensitivity = d.camera_sensitivity || 1.0;
|
|
setTimeout(() => {
|
|
done_checking_auto_torch = true;
|
|
}, 200);
|
|
}
|
|
|
|
function set_zoom(zoom) {
|
|
if (camera_capabilities.zoom) {
|
|
debug_log("set zoom by applying constraints");
|
|
track.applyConstraints({advanced: [ {zoom} ]});
|
|
} else {
|
|
debug_log("set zoom by scaling video element");
|
|
add_style_by_query("video.preview", "transform", `scale(${zoom})`);
|
|
}
|
|
}
|
|
|
|
let torch = false;
|
|
function toggle_torch() {
|
|
torch = !torch;
|
|
track.applyConstraints({advanced: [ {torch: torch} ]});
|
|
if (torch) {
|
|
add_class_by_query(".bottomfixed .torch.action", "highlight");
|
|
} else {
|
|
remove_class_by_query(".bottomfixed .torch.action", "highlight");
|
|
}
|
|
}
|
|
|
|
function show_spinner() {
|
|
remove_class_by_query('.verifyspin', 'hidden');
|
|
remove_class_by_query('.spin-image', 'spin-only');
|
|
remove_class_by_query('.spin-image', 'spin-and-shrink');
|
|
setTimeout(() => {
|
|
add_class_by_query('.spin-image', 'spin-and-shrink');
|
|
setTimeout(() => {
|
|
remove_class_by_query('.spin-image', 'spin-and-shrink');
|
|
add_class_by_query('.spin-image', 'spin-only');
|
|
}, 3000);
|
|
}, 0);
|
|
}
|
|
|
|
function hide_spinner() {
|
|
add_class_by_query('.verifyspin', 'hidden');
|
|
}
|
|
|
|
async function submit_image(qrcode, angle, image_data_urls) {
|
|
busy = true;
|
|
var begin = Date.now();
|
|
show_spinner();
|
|
debug_log(`submit: qrcode: ${qrcode} angle: ${angle}`);
|
|
try {
|
|
// TODO: pass these parameters from query string
|
|
var emblem_id = get_query("emblem_id");
|
|
var nick_name = get_query("nick_name");
|
|
var realip = get_query("realip");
|
|
var phonemodel = get_query("phonemodel");
|
|
var data = {
|
|
emblem_id,
|
|
nick_name,
|
|
realip,
|
|
qrcode,
|
|
angle,
|
|
phonemodel,
|
|
image_data_urls,
|
|
log: debug_logs.join("\n"),
|
|
};
|
|
var r = await fetch("https://themblem.com/api/v1/qr-verify/", {
|
|
method: "POST",
|
|
body: JSON.stringify(data),
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
});
|
|
var d = await r.json();
|
|
debug_log(JSON.stringify(d));
|
|
if (d.serial_code) {
|
|
if (wx) {
|
|
var delay = 3000 - (Date.now() - begin);
|
|
setTimeout(() => {
|
|
var wx_redirect_to = get_query("wx_redirect_to");
|
|
var query_params = `?qr_code=${encodeURIComponent(qrcode)}&serial_code=${encodeURIComponent(d.serial_code)}`;
|
|
|
|
if (wx_redirect_to) {
|
|
wx.miniProgram.redirectTo({
|
|
url: wx_redirect_to + query_params,
|
|
});
|
|
} else if (get_query("ai_chat_mode")) {
|
|
wx.miniProgram.redirectTo({
|
|
url: '/pages/chat/chat' + query_params,
|
|
});
|
|
} else {
|
|
wx.miniProgram.redirectTo({
|
|
url: '/pages/productinfo/productinfo' + query_params,
|
|
});
|
|
}
|
|
}, delay > 0 ? delay : 0);
|
|
}
|
|
} else {
|
|
this.show_modal('verifyfailed');
|
|
}
|
|
} catch (e) {
|
|
debug_log(`submission error: ${e}`);
|
|
this.show_modal('verifyfailed');
|
|
}
|
|
}
|
|
|
|
var pending_hint = null;
|
|
|
|
function update_hint() {
|
|
if (pending_hint) {
|
|
const hint_dev = document.getElementById("hint");
|
|
hint_dev.innerHTML = pending_hint;
|
|
pending_hint = null;
|
|
}
|
|
}
|
|
|
|
function start() {
|
|
console.log("start");
|
|
setTimeout(handle_frame, 100);
|
|
hide_loading();
|
|
if (get_query("debug")) {
|
|
show_debug();
|
|
}
|
|
setInterval(update_hint, 1000);
|
|
setTimeout(() => {
|
|
show_tooltip();
|
|
}, 15000);
|
|
}
|
|
|
|
function show_tooltip() {
|
|
remove_class_by_query(".tooltip", "hidden");
|
|
add_class_by_query(".bottomfixed .action", "highlight");
|
|
}
|
|
|
|
function hide_tooltip() {
|
|
add_class_by_query(".tooltip", "hidden");
|
|
remove_class_by_query(".bottomfixed .action", "highlight");
|
|
}
|
|
|
|
async function init_scanguide() {
|
|
var r = await fetch('https://emblem-resources.oss-cn-guangzhou.aliyuncs.com/scan-guide-1080x1920-3.json');
|
|
var d = await r.json();
|
|
console.log("start scanguide", d);
|
|
const elem = document.getElementById("scanguide");
|
|
scanguide_anime = bodymovin.loadAnimation({
|
|
container: elem,
|
|
animationData: d,
|
|
// path: 'data.json',
|
|
renderer: 'svg',
|
|
loop: true,
|
|
autoplay: true,
|
|
name: "Scan guide",
|
|
})
|
|
}
|
|
|
|
function add_style_by_query(query, property, style) {
|
|
var list = document.querySelectorAll(query);
|
|
for (var i = 0; i < list.length; ++i) {
|
|
list[i].style[property] = style;
|
|
|
|
}
|
|
}
|
|
|
|
function add_class_by_query(query, to_add) {
|
|
var list = document.querySelectorAll(query);
|
|
for (var i = 0; i < list.length; ++i) {
|
|
list[i].classList.add(to_add);
|
|
}
|
|
}
|
|
|
|
function remove_class_by_query(query, to_remove) {
|
|
var list = document.querySelectorAll(query);
|
|
for (var i = 0; i < list.length; ++i) {
|
|
list[i].classList.remove(to_remove);
|
|
}
|
|
}
|
|
|
|
var modals = ['serviceqr', 'scanguide', 'verifyfailed', 'verifyspin'];
|
|
|
|
function hide_modal() {
|
|
busy = false;
|
|
scanguide_anime.stop();
|
|
add_class_by_query(".modal", "hidden");
|
|
hide_tooltip();
|
|
}
|
|
|
|
function show_modal(which) {
|
|
busy = true;
|
|
hide_tooltip();
|
|
hide_spinner();
|
|
add_class_by_query(".verifyspin", "hidden");
|
|
remove_class_by_query(".modal", "hidden");
|
|
remove_class_by_query('.actions', 'hidden');
|
|
for (var m of modals) {
|
|
add_class_by_query('.' + m, 'hidden');
|
|
}
|
|
if (which == 'scanguide') {
|
|
scanguide_anime.goToAndPlay(0);
|
|
remove_class_by_query('.scanguide', 'hidden');
|
|
}
|
|
if (which == 'serviceqr') {
|
|
remove_class_by_query('.serviceqr', 'hidden');
|
|
add_class_by_query('.actions', 'hidden');
|
|
}
|
|
if (which == 'verifyfailed') {
|
|
remove_class_by_query('.verifyfailed', 'hidden');
|
|
}
|
|
}
|
|
|
|
function set_service_qr_img() {
|
|
var tid = get_query('tenant');
|
|
var url = '/api/v1/service-qr/?tenant=' + tid;
|
|
const elem = document.getElementById("service_img");
|
|
elem.src = url;
|
|
}
|
|
|
|
(function() {
|
|
start_camera();
|
|
init_scanguide();
|
|
set_service_qr_img();
|
|
})();
|