emblemscanner: native upload works

This commit is contained in:
Fam Zheng 2025-09-14 17:08:12 +01:00
parent 55d2ccb8fc
commit 4b10ad335a
8 changed files with 203 additions and 109 deletions

View File

@ -40,16 +40,15 @@ const {
} = require('./upload.js'); } = require('./upload.js');
// Import precheck utilities for image processing // Import precheck utilities for image processing
const { // Note: We now use our own data_url_from_frame from qrprocessor.js
data_url_from_frame
} = require('../../precheck.js');
// Import QR processing module // Import QR processing module
const { const {
load_qrtool, load_qrtool,
is_qrtool_ready, is_qrtool_ready,
process_frame, process_frame,
make_hint_text make_hint_text,
data_url_from_frame
} = require('./qrprocessor.js'); } = require('./qrprocessor.js');
Page({ Page({
@ -74,17 +73,19 @@ Page({
qrarc_class: 'sm', qrarc_class: 'sm',
qrmarkers_class: 'hidden', qrmarkers_class: 'hidden',
frame_upload_interval_ms: 2000, frame_upload_interval_ms: 2000,
return_page: '', // Page to navigate to after successful scan return_page: '/pages/test_result_page/test_result_page', // Page to navigate to after successful scan
server_url: 'https://themblem.com', // Default server URL server_url: 'https://themblem.com', // Default server URL
real_ip: '', // User's real IP address real_ip: '', // User's real IP address
tenant_id: '', // Tenant identifier tenant_id: '', // Tenant identifier
debug_msgs: [], debug_msgs: [],
debug_image_data_url: '', debug_image_data_url: '',
debug_last_result: null, debug_last_result: null,
debug_current_frame_url: '', // Current frame being processed
qrtool_ready: false, qrtool_ready: false,
// Frame processing statistics // Frame processing statistics
frames_processed: 0, frames_processed: 0,
frames_skipped: 0, frames_skipped: 0,
ok_frames: 0,
total_processing_time: 0, total_processing_time: 0,
avg_processing_time_ms: 0, avg_processing_time_ms: 0,
last_frame_time_ms: 0, last_frame_time_ms: 0,
@ -93,6 +94,7 @@ Page({
use_web_view: false, use_web_view: false,
use_worker: false, use_worker: false,
emblem_camera_url: null, emblem_camera_url: null,
first_qr_found: false, // Track if first QR has been detected to trigger zoom-in
// State machine: loading_rules -> loading_qrtool -> init_camera -> scanning -> verifying -> result // State machine: loading_rules -> loading_qrtool -> init_camera -> scanning -> verifying -> result
app_state: 'loading_rules', // 'loading_rules', 'loading_qrtool', 'init_camera', 'scanning', 'webview_scanning', 'verifying', 'result' app_state: 'loading_rules', // 'loading_rules', 'loading_qrtool', 'init_camera', 'scanning', 'webview_scanning', 'verifying', 'result'
scan_mode: 'unknown', // 'camera', 'webview' scan_mode: 'unknown', // 'camera', 'webview'
@ -105,7 +107,7 @@ Page({
const no_web_view = options.no_web_view === '1' || options.no_web_view === 'true'; const no_web_view = options.no_web_view === '1' || options.no_web_view === 'true';
this.setData({ this.setData({
return_page: options.return_page || '', return_page: options.return_page || '/pages/test_result_page/test_result_page',
no_web_view: no_web_view no_web_view: no_web_view
}); });
@ -202,17 +204,18 @@ Page({
if (result) { if (result) {
// For worker, we need to trigger image collection when we find a good QR // For worker, we need to trigger image collection when we find a good QR
if (result.ok && result.qrcode && result.valid_pattern && is_emblem_qr_pattern(result.qrcode)) { if (result.qrcode && result.valid_pattern && is_emblem_qr_pattern(result.qrcode)) {
this.addDebugMessage(`Worker QR detected: ${result.qrcode}`); this.addDebugMessage(`Worker QR detected: ${result.qrcode}`);
// Trigger zoom-in if function is set up // Trigger zoom-in if function is set up
if (this.on_qr_found && !this.data.qr_found) { if (this.on_first_qr_found && !this.data.first_qr_found) {
this.on_qr_found(); this.on_first_qr_found();
this.setData({ qr_found: true });
} }
// Request worker to submit image data if (result.ok) {
this.worker.postMessage({ type: "ready_to_submit" }); // Request worker to submit image data
this.worker.postMessage({ type: "ready_to_submit" });
}
} }
this.handleQRResult(result, this.lastWorkerFrame); this.handleQRResult(result, this.lastWorkerFrame);
@ -222,14 +225,20 @@ Page({
const result = msg.res; const result = msg.res;
const imageData = msg.image_data; const imageData = msg.image_data;
if (imageData) { if (imageData) {
const dataUrl = data_url_from_frame(imageData.width, imageData.height, new Uint8ClampedArray(imageData.data)); // Worker now sends data URL directly, no need to convert
this.image_data_urls.push(dataUrl); this.image_data_urls.push(imageData.data);
// Update ok frames counter
this.setData({
ok_frames: this.image_data_urls.length
});
if (this.image_data_urls.length >= 3) { if (this.image_data_urls.length >= 3) {
this.addDebugMessage('3 good images collected via worker, starting verification'); this.addDebugMessage('3 good images collected via worker, starting verification');
this.startVerifying(); this.startVerifying();
this.submitImageForVerification(this.image_data_urls, result.qrcode); this.submitImageForVerification(this.image_data_urls, result.qrcode);
this.image_data_urls = []; // Reset for next scan this.image_data_urls = []; // Reset for next scan
this.setData({ ok_frames: 0 }); // Reset counter
} else { } else {
this.addDebugMessage(`Collected ${this.image_data_urls.length}/3 worker images`); this.addDebugMessage(`Collected ${this.image_data_urls.length}/3 worker images`);
} }
@ -382,8 +391,9 @@ Page({
this.camera_context.setZoom({ zoom: initial_zoom }); this.camera_context.setZoom({ zoom: initial_zoom });
// Set up zoom-in behavior when QR is found // Set up zoom-in behavior when QR is found
this.on_qr_found = () => { this.on_first_qr_found = () => {
this.addDebugMessage(`QR found, zoom to ${zoom}x`); this.setData({ first_qr_found: true });
this.addDebugMessage(`First QR found, zoom to ${zoom}x`);
this.camera_context.setZoom({ zoom: zoom }); this.camera_context.setZoom({ zoom: zoom });
this.setData({ this.setData({
zoom: zoom, zoom: zoom,
@ -465,10 +475,22 @@ Page({
if (this.data.use_worker && this.worker) { if (this.data.use_worker && this.worker) {
// Worker processing (iPhone) // Worker processing (iPhone)
this.lastWorkerFrame = frame; // Store for handleQRResult this.lastWorkerFrame = frame; // Store for handleQRResult
// Copy frame data to avoid TOCTOU race condition (like camera.js)
var uca1 = new Uint8ClampedArray(frame.data);
var uca = new Uint8ClampedArray(uca1);
// Generate debug frame data URL if debug is enabled
if (this.data.enable_debug) {
const frameDataUrl = data_url_from_frame(frame.width, frame.height, uca);
this.updateDebugFrameUrls(frameDataUrl);
}
this.worker.postMessage({ this.worker.postMessage({
type: 'frame', type: 'frame',
width: frame.width, width: frame.width,
height: frame.height, height: frame.height,
data: uca, // Pass actual frame data
camera_sensitivity: this.data.camera_sensitivity camera_sensitivity: this.data.camera_sensitivity
}); });
} else { } else {
@ -484,7 +506,17 @@ Page({
const processStart = Date.now(); const processStart = Date.now();
try { try {
const result = process_frame(frame.width, frame.height, frame.data, this.data.camera_sensitivity, this.data.enable_debug); // Copy frame data to avoid TOCTOU race condition (like camera.js)
var uca1 = new Uint8ClampedArray(frame.data);
var uca = new Uint8ClampedArray(uca1);
// Generate debug frame data URL if debug is enabled
if (this.data.enable_debug) {
const frameDataUrl = data_url_from_frame(frame.width, frame.height, uca);
this.updateDebugFrameUrls(frameDataUrl);
}
const result = process_frame(frame.width, frame.height, uca, this.data.camera_sensitivity, this.data.enable_debug);
// Calculate processing time // Calculate processing time
const processEnd = Date.now(); const processEnd = Date.now();
@ -504,7 +536,7 @@ Page({
}); });
if (result) { if (result) {
this.handleQRResult(result, frame); this.handleQRResult(result, frame, uca);
} }
} catch (error) { } catch (error) {
this.addDebugMessage(`Frame processing error: ${error.message}`); this.addDebugMessage(`Frame processing error: ${error.message}`);
@ -529,7 +561,7 @@ Page({
/** /**
* Handle QR processing result * Handle QR processing result
*/ */
handleQRResult(result, frame) { handleQRResult(result, frame, copiedFrameData = null) {
// Update debug info if available // Update debug info if available
if (result.debug_data_url) { if (result.debug_data_url) {
this.setData({ this.setData({
@ -543,18 +575,17 @@ Page({
// Check if we have a valid QR code that's ready for upload (like camera.js) // Check if we have a valid QR code that's ready for upload (like camera.js)
// don't require ok as we only care about the view has a valid qrcode in it // don't require ok as we only care about the view has a valid qrcode in it
// zooming in so that it's more likely to be clear enough for upload // zooming in so that it's more likely to be clear enough for upload
if (!this.data.qr_found && result.qrcode && result.valid_pattern && is_emblem_qr_pattern(result.qrcode)) { if (result.qrcode && result.valid_pattern && is_emblem_qr_pattern(result.qrcode)) {
this.addDebugMessage(`QR detected and ready: ${result.qrcode}`); this.addDebugMessage(`QR detected and ready: ${result.qrcode}`);
// Trigger zoom-in if function is set up // Trigger zoom-in if function is set up
if (this.on_qr_found) { if (!this.data.first_qr_found && this.on_first_qr_found) {
this.on_qr_found(); this.on_first_qr_found();
} }
this.onQRCodeDetected(result.qrcode, frame); if (result.ok) {
this.setData({ this.onQRCodeDetected(result.qrcode, frame, copiedFrameData); // Pass the copied frame data
qr_found: true }
});
} else { } else {
// Update hint for user guidance // Update hint for user guidance
this.setData({ hint_text: hint }); this.setData({ hint_text: hint });
@ -566,23 +597,38 @@ Page({
}, },
/** /**
* Handle successful QR code detection - collect images from "ok" frames and verify * Handle successful QR code detection - collect images from "ok" frames and verify (non-worker case)
*/ */
onQRCodeDetected(qrCode, frameData) { onQRCodeDetected(qrCode, frameData, copiedFrameData = null) {
// Convert frame data to data URL for upload (only called for "ok" frames) // Only collect images for non-worker case (worker handles its own image collection)
if (frameData) { if (!this.data.use_worker && frameData && copiedFrameData) {
const dataUrl = data_url_from_frame(frameData.width, frameData.height, frameData.data); // Convert frame data to data URL for upload (only called for "ok" frames)
// Use the copied frame data instead of original frameData.data to avoid corruption
const dataUrl = data_url_from_frame(frameData.width, frameData.height, copiedFrameData);
this.image_data_urls.push(dataUrl); this.image_data_urls.push(dataUrl);
this.addDebugMessage(`Collected ${this.image_data_urls.length}/3 good images`);
} // Update ok frames counter
this.setData({
// Need 3 "ok" frames before verification (like camera.js) ok_frames: this.image_data_urls.length
if (this.image_data_urls.length >= 3) { });
this.addDebugMessage('3 good images collected, starting verification');
this.startVerifying(); this.addDebugMessage(`Collected ${this.image_data_urls.length}/3 good images (direct) - using copied data`);
this.submitImageForVerification(this.image_data_urls, qrCode);
this.image_data_urls = []; // Reset for next scan // Add debug info about the submitted image
if (this.data.enable_debug) {
this.addDebugMessage(`Submitted image preview: ${dataUrl.substring(0, 50)}...`);
}
// Need 3 "ok" frames before verification (like camera.js)
if (this.image_data_urls.length >= 3) {
this.addDebugMessage('3 good images collected, starting verification');
this.startVerifying();
this.submitImageForVerification(this.image_data_urls, qrCode);
this.image_data_urls = []; // Reset for next scan
this.setData({ ok_frames: 0 }); // Reset counter
}
} }
// For worker case, image collection is handled in setupWorker() message handler
// If less than 3 images, continue scanning to collect more // If less than 3 images, continue scanning to collect more
}, },
@ -671,25 +717,20 @@ Page({
goToResult(qrCode, serialCode) { goToResult(qrCode, serialCode) {
this.transitionToState('result'); this.transitionToState('result');
if (this.data.return_page) { // Pass both qr_code and serial_code parameters like camera-5.1 does
// Pass both qr_code and serial_code parameters like camera-5.1 does // serialCode comes from server verification response
// serialCode comes from server verification response const url = `${this.data.return_page}?qr_code=${encodeURIComponent(qrCode)}&serial_code=${encodeURIComponent(serialCode)}`;
const url = `${this.data.return_page}?qr_code=${encodeURIComponent(qrCode)}&serial_code=${encodeURIComponent(serialCode)}`;
wx.redirectTo({
wx.redirectTo({ url: url,
url: url, success: () => {
success: () => { this.addDebugMessage(`Navigated to: ${this.data.return_page}`);
this.addDebugMessage(`Navigated to: ${this.data.return_page}`); },
}, fail: (err) => {
fail: (err) => { this.addDebugMessage(`Navigation failed: ${err.errMsg}`);
this.addDebugMessage(`Navigation failed: ${err.errMsg}`); this.restartScanning();
this.restartScanning(); }
} });
});
} else {
this.addDebugMessage('No return page specified');
this.restartScanning();
}
}, },
/** /**
@ -708,9 +749,11 @@ Page({
// Reset frame processing statistics // Reset frame processing statistics
frames_processed: 0, frames_processed: 0,
frames_skipped: 0, frames_skipped: 0,
ok_frames: 0,
total_processing_time: 0, total_processing_time: 0,
avg_processing_time_ms: 0, avg_processing_time_ms: 0,
last_frame_time_ms: 0 last_frame_time_ms: 0,
first_qr_found: false
}); });
// Reset frame timing // Reset frame timing
@ -736,6 +779,18 @@ Page({
}); });
}, },
/**
* Update debug frame URLs for visualization
*/
updateDebugFrameUrls(frameDataUrl) {
if (!this.data.enable_debug) return;
// Just show the current frame
this.setData({
debug_current_frame_url: frameDataUrl
});
},
/** /**
* Log function for debugging * Log function for debugging
*/ */

View File

@ -71,6 +71,14 @@
<!-- Debug overlay (available in all states) --> <!-- Debug overlay (available in all states) -->
<view class="debug" wx:if="{{ enable_debug }}"> <view class="debug" wx:if="{{ enable_debug }}">
<view class="debug-top-row"> <view class="debug-top-row">
<view class="debug-image-box">
<image src="{{ debug_image_data_url }}"></image>
<view class="debug-label">Processed Frame</view>
</view>
<view class="debug-frame-box">
<image src="{{ debug_current_frame_url }}"></image>
<view class="debug-label">Raw Frame</view>
</view>
<view class="debug-info-panel"> <view class="debug-info-panel">
<view class="debug-items"> <view class="debug-items">
<!-- Application State --> <!-- Application State -->
@ -84,34 +92,18 @@
<text class="debug-label">model:</text> <text class="debug-label">model:</text>
<text class="debug-value">{{ phone_model }}</text> <text class="debug-value">{{ phone_model }}</text>
</view> </view>
<!-- Camera Configuration -->
<view class="debug-item">
<text class="debug-label">rule:</text>
<text class="debug-value">{{ camera_rule.model || 'default' }}</text>
</view>
<view class="debug-item"> <view class="debug-item">
<text class="debug-label">zoom:</text> <text class="debug-label">zoom:</text>
<text class="debug-value-number">{{ zoom }}/{{ max_zoom }}</text> <text class="debug-value-number">{{ zoom }}/{{ max_zoom }}</text>
</view> </view>
<view class="debug-item"> <view class="debug-item">
<text class="debug-label">sensitivity:</text> <text class="debug-label">sensitivity:</text>
<text class="debug-value-number">{{ camera_sensitivity }}</text> <text class="debug-value">{{ camera_sensitivity }}</text>
</view>
<view class="debug-item">
<text class="debug-label">web_view:</text>
<text class="debug-value">{{ use_web_view }}</text>
</view> </view>
<view class="debug-item debug-flag-box" wx:if="{{ no_web_view }}"> <view class="debug-item debug-flag-box" wx:if="{{ no_web_view }}">
<text class="debug-flag">no_web_view</text> <text class="debug-flag">no_web_view</text>
</view> </view>
<!-- Processing Status -->
<view class="debug-item">
<text class="debug-label">qrtool:</text>
<text class="debug-value">{{ qrtool_ready ? 'ready' : 'loading' }}</text>
</view>
<!-- Frame Statistics --> <!-- Frame Statistics -->
<view class="debug-item"> <view class="debug-item">
<text class="debug-label">frames:</text> <text class="debug-label">frames:</text>
@ -120,17 +112,14 @@
<text class="debug-value-number">{{ frames_processed + frames_skipped }}</text> <text class="debug-value-number">{{ frames_processed + frames_skipped }}</text>
</view> </view>
<view class="debug-item"> <view class="debug-item">
<text class="debug-label">skipped:</text> <text class="debug-label">ok:</text>
<text class="debug-value-number">{{ frames_skipped }}</text> <text class="debug-value">{{ ok_frames }}</text>
<text class="debug-value">/3</text>
</view> </view>
<view class="debug-item"> <view class="debug-item">
<text class="debug-label">avg:</text> <text class="debug-label">avg:</text>
<text class="debug-value-number">{{ avg_processing_time_ms }}ms</text> <text class="debug-value-number">{{ avg_processing_time_ms }}ms</text>
</view> </view>
<view class="debug-item">
<text class="debug-label">last:</text>
<text class="debug-value-number">{{ last_frame_time_ms }}ms</text>
</view>
<!-- QR Detection Results --> <!-- QR Detection Results -->
<view class="debug-item" wx:if="{{ debug_last_result }}"> <view class="debug-item" wx:if="{{ debug_last_result }}">
@ -153,9 +142,6 @@
</view> </view>
</view> </view>
</view> </view>
<view class="debug-image-box">
<image src="{{ debug_image_data_url }}"></image>
</view>
</view> </view>
<view class="debug-messages"> <view class="debug-messages">
<text wx:for="{{ debug_msgs }}" class="debug-msg">{{ item }}</text> <text wx:for="{{ debug_msgs }}" class="debug-msg">{{ item }}</text>

View File

@ -219,6 +219,25 @@ view.debug {
border: 1px solid rgba(239, 72, 35, 0.8); border: 1px solid rgba(239, 72, 35, 0.8);
} }
.debug-frame-box {
flex-shrink: 0;
}
.debug-frame-box image {
width: 64px;
height: 64px;
border: 1px solid rgba(35, 150, 239, 0.8);
}
.debug-frame-box .debug-label,
.debug-image-box .debug-label {
font-size: 8px;
color: #ffffff;
text-align: center;
margin-top: 2px;
font-weight: bold;
}
.debug-info-panel { .debug-info-panel {
flex: 1; flex: 1;
display: flex; display: flex;
@ -291,6 +310,7 @@ view.debug {
font-weight: bold; font-weight: bold;
} }
/* Tooltip */ /* Tooltip */
view.tooltip { view.tooltip {
position: fixed; position: fixed;

View File

@ -89,23 +89,31 @@ function process_frame(width, height, image_data, camera_sensitivity, enable_deb
/** /**
* Create offscreen canvas for debug image generation * Create offscreen canvas for debug image generation
*/ */
const offscreenCanvas = wx.createOffscreenCanvas({ let offscreenCanvas = null;
type: '2d',
width: 100, function getOffscreenCanvas() {
height: 100, if (!offscreenCanvas) {
}); offscreenCanvas = wx.createOffscreenCanvas({
type: '2d',
width: 100,
height: 100,
});
}
return offscreenCanvas;
}
/** /**
* Convert raw frame data to data URL for debug visualization * Convert raw frame data to data URL for image visualization
*/ */
function data_url_from_frame(width, height, image_data) { function data_url_from_frame(width, height, image_data) {
offscreenCanvas.width = width; const canvas = getOffscreenCanvas();
offscreenCanvas.height = height; canvas.width = width;
var ctx = offscreenCanvas.getContext('2d'); canvas.height = height;
var ctx = canvas.getContext('2d');
var imgd = ctx.createImageData(width, height); var imgd = ctx.createImageData(width, height);
imgd.data.set(image_data); imgd.data.set(image_data);
ctx.putImageData(imgd, 0, 0); ctx.putImageData(imgd, 0, 0);
return offscreenCanvas.toDataURL("image/jpeg", 1.0); return canvas.toDataURL("image/jpeg", 1.0);
} }
/** /**

View File

@ -1,7 +1,3 @@
function upload_image_data_urls(image_data_urls, success, fail, log) {
do_upload(image_data_urls, success, fail, log);
}
function check_auto_torch(qrcode, cb) { function check_auto_torch(qrcode, cb) {
var gd = getApp().globalData; var gd = getApp().globalData;
var url = gd.server_url + '/api/v1/check-auto-torch/?qrcode=' + encodeURIComponent(qrcode); var url = gd.server_url + '/api/v1/check-auto-torch/?qrcode=' + encodeURIComponent(qrcode);
@ -33,7 +29,7 @@ function check_auto_torch(qrcode, cb) {
}); });
} }
function do_upload(image_data_urls, success, fail, log) { function upload_image_data_urls(image_data_urls, success, fail, log) {
var ui = wx.getStorageSync('userinfo'); var ui = wx.getStorageSync('userinfo');
var gd = getApp().globalData; var gd = getApp().globalData;
var fd = { var fd = {

View File

@ -2,6 +2,34 @@ console.log("hello from emblemscanner worker");
let qrtool = require('../qrtool.wx.js'); let qrtool = require('../qrtool.wx.js');
// Create offscreen canvas for image generation (self-contained)
let offscreenCanvas = null;
function getOffscreenCanvas() {
if (!offscreenCanvas) {
offscreenCanvas = wx.createOffscreenCanvas({
type: '2d',
width: 100,
height: 100,
});
}
return offscreenCanvas;
}
/**
* Convert raw frame data to data URL for image visualization
*/
function data_url_from_frame(width, height, image_data) {
const canvas = getOffscreenCanvas();
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext('2d');
var imgd = ctx.createImageData(width, height);
imgd.data.set(image_data);
ctx.putImageData(imgd, 0, 0);
return canvas.toDataURL("image/jpeg", 1.0);
}
var qrtool_ready = false; var qrtool_ready = false;
qrtool.onRuntimeInitialized = () => { qrtool.onRuntimeInitialized = () => {
@ -23,7 +51,7 @@ function handle_frame(data, width, height, camera_sensitivity) {
ok: false, ok: false,
err: "qrtool not ready", err: "qrtool not ready",
}, },
processing_time: Date.now() - begin, processing_time: 0,
}); });
return; return;
} }
@ -43,10 +71,11 @@ function handle_frame(data, width, height, camera_sensitivity) {
if (res.ok) { if (res.ok) {
// Since image_data takes seconds to serialize, send it in a separate // Since image_data takes seconds to serialize, send it in a separate
// message to the UI can update with the good news // message to the UI can update with the good news
const dataUrl = data_url_from_frame(width, height, uca);
submit_message = { submit_message = {
type: "submit", type: "submit",
res, res,
image_data: { data, width, height }, image_data: { data: dataUrl, width, height },
}; };
} }
} }
@ -56,9 +85,9 @@ worker.onMessage((msg) => {
switch (msg.type) { switch (msg.type) {
case "frame": case "frame":
try { try {
const data = worker.getCameraFrameData(); // Use frame data from message instead of getCameraFrameData()
if (data) { if (msg.data) {
handle_frame(data, msg.width, msg.height, msg.camera_sensitivity); handle_frame(msg.data, msg.width, msg.height, msg.camera_sensitivity);
} }
} catch (e) { } catch (e) {
console.log(e); console.log(e);
@ -68,6 +97,6 @@ worker.onMessage((msg) => {
worker.postMessage(submit_message); worker.postMessage(submit_message);
break; break;
default: default:
console.log("Unknown message type:", msg.data.type); console.log("Unknown message type:", msg.type);
} }
}); });

View File

@ -129,7 +129,7 @@ Page({
goto_camera: function () { goto_camera: function () {
this.getUserProfile(() => { this.getUserProfile(() => {
wx.navigateTo({ wx.navigateTo({
url: '/pages/emblemscanner/emblemscanner?return_page=/pages/test_result_page/test_result_page' url: '/pages/emblemscanner/emblemscanner?return_page=/pages/productinfo/productinfo'
}); });
}); });
}, },

View File

@ -5,7 +5,7 @@
{ {
"name": "emblemscanner force camera", "name": "emblemscanner force camera",
"pathName": "pages/emblemscanner/emblemscanner", "pathName": "pages/emblemscanner/emblemscanner",
"query": "debug=1&no_web_view=1", "query": "debug=1&no_web_view=1&return_page=/pages/test_result_page/test_result_page",
"scene": null, "scene": null,
"launchMode": "default" "launchMode": "default"
}, },