-
Notifications
You must be signed in to change notification settings - Fork 0
/
QramScanner.js
133 lines (115 loc) · 3.42 KB
/
QramScanner.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/*!
* Copyright (c) 2019 Digital Bazaar, Inc. All rights reserved.
*/
'use strict';
import * as base64url from 'base64url-universal';
import {Decoder, getImageData} from 'qram';
import jsQR from 'jsqr';
export class QramScanner {
constructor() {
this.decoder = null;
this.detector = null;
this.scanning = false;
this.scans = 0;
}
async scan({source, progress = () => {}}) {
this.scans++;
const {scans: currentScan} = this;
if(!this.detector) {
this.detector = await createDetector();
}
if(currentScan !== this.scans) {
// another scan was started while the detector was being created, abort
return;
}
if(this.scanning) {
// cancel previous scan
this.cancel();
}
// start new scan
this.scanning = true;
const decoder = this.decoder = new Decoder();
const {detector} = this;
const enqueue = async () => {
// try to detect QR code
let detectedCode = null;
try {
([detectedCode] = await detector.detect(source));
} catch(e) {
// error during scan, log it but do not throw, treat as no code found
console.error(e);
}
if(currentScan !== this.scans) {
// current scan canceled during detection, abort
return;
}
if(!detectedCode) {
// no QR code found, try again on the next frame
return requestAnimationFrame(() => setTimeout(enqueue, 0));
}
// enqueue the packet data for decoding, ignoring any non-cancel errors
// and rescheduling until done or aborted
const data = base64url.decode(detectedCode.rawValue);
decoder.enqueue(data)
.then(async event => {
// if not done and still scanning, report progress and schedule
// next `enqueue`
if(!event.done && currentScan === this.scans) {
await progress(event);
setTimeout(enqueue, 0);
}
})
.catch(e => {
if(e.name === 'AbortError') {
return;
}
// if current scan was canceled, do not schedule `enqueue`
if(currentScan === this.scans) {
console.error(e);
setTimeout(enqueue, 0);
}
});
};
// use `requestAnimationFrame` so that scanning will not happen unless
// the user has focused the window/tab displaying the qr-code stream
requestAnimationFrame(() => setTimeout(enqueue, 0));
try {
return await decoder.decode();
} finally {
this.scanning = false;
this.decoder = null;
}
}
cancel() {
if(this.decoder) {
this.decoder.cancel();
}
}
}
async function createDetector() {
// check if the platform supports QR codes
if(typeof BarcodeDetector !== 'undefined' &&
typeof BarcodeDetector.getSupportedFormats === 'function' &&
(await BarcodeDetector.getSupportedFormats()).includes('qr_code')) {
// initialize the barcode detector
return new BarcodeDetector({formats: ['qr_code']});
}
// use jsQR to provide detector
return {
detect(image) {
// use qram helper to get image data
const imageData = getImageData({source: image});
const result = jsQR(imageData.data, imageData.width, imageData.height, {
inversionAttempts: 'dontInvert'
});
if(!result) {
return [];
}
const {data: rawValue} = result;
return [{
rawValue,
format: 'qr_code'
}];
}
};
}