Viewing is open to everyone. Downloading and starring need a GitHub sign-in. Sign in with GitHub

scanner.mjs

v0.1.0-build.85 Latest Commit ede38f5

Size
3.0 KB
Lines
94
Language
javascript
SHA-256
00efe9d099c7…
View on GitHub

Sign in with GitHub to download. Each download is recorded with your GitHub username.

1// Continuous-frame scanner helper. Pure JavaScript over injected functions,
2// so its telemetry session logic is unit-tested without WebAssembly.
3//
4// Session semantics (docs/adoption-and-telemetry.md):
5// first processed frame -> session_begin
6// every processed frame -> frame(now, processing ms)
7// first valid decode -> session_success(now, profile); later frames are
8// skipped until reset() ("scan again")
9// stop() -> session_end (a session without a decode fails),
10// then a background send attempt
11
12/**
13 * deps: {
14 * decode(frame) -> null | result with a numeric `profileCode` (0..4),
15 * telemetry: { begin(now), frame(now, ms), success(now, profileCode), end(now) },
16 * now() -> monotonic ms,
17 * sender: { arm(), trySendSoon() } | null
18 * }
19 */
20export function makeScanner(deps, { onResult, onError } = {}) {
21 if (onResult !== undefined && typeof onResult !== 'function') throw new TypeError('onResult must be a function');
22 const { decode, telemetry, now, sender } = deps;
23 let active = false; // a session is open (or done, awaiting reset/stop)
24 let done = false; // the current session has decoded
25 let last = null;
26
27 const safe = (fn) => {
28 try {
29 fn();
30 } catch {
31 /* telemetry never breaks scanning */
32 }
33 };
34
35 return {
36 /** Decodes one frame (ImageData or { width, height, data } RGBA). Returns the result or null. */
37 processFrame(frame) {
38 if (done) return null;
39 if (!active) {
40 active = true;
41 safe(() => telemetry.begin(now()));
42 if (sender) safe(() => sender.arm());
43 }
44 const t0 = now();
45 let result = null;
46 try {
47 result = decode(frame);
48 } catch (err) {
49 const t1 = now();
50 safe(() => telemetry.frame(t1, Math.max(0, t1 - t0)));
51 if (onError) onError(err);
52 else throw err;
53 return null;
54 }
55 const t1 = now();
56 safe(() => telemetry.frame(t1, Math.max(0, t1 - t0)));
57 if (!result) return null;
58 done = true;
59 last = result;
60 safe(() => telemetry.success(t1, result.profileCode));
61 if (onResult) onResult(publicResult(result));
62 return publicResult(result);
63 },
64 /** Scan again after a decode: begins a new session at the next frame. */
65 reset() {
66 if (done) {
67 // The core moves DONE -> a new OPEN session at the next begin.
68 done = false;
69 active = false;
70 last = null;
71 }
72 },
73 /** The host stopped scanning (camera closed). Ends the session. */
74 stop() {
75 if (active) safe(() => telemetry.end(now()));
76 active = false;
77 done = false;
78 last = null;
79 if (sender) safe(() => sender.trySendSoon());
80 },
81 get decoded() {
82 return done ? publicResult(last) : null;
83 },
84 get scanning() {
85 return active && !done;
86 },
87 };
88}
89
90function publicResult(r) {
91 if (!r) return null;
92 const { profileCode, ...rest } = r; // eslint-disable-line no-unused-vars
93 return rest;
94}