@agicash/qr-scanner 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/index.cjs +93 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +93 -70
- package/dist/index.js.map +1 -1
- package/dist/worker.js +6 -4
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -94,6 +94,16 @@ QrScanner.scanImage(source, options?): Promise<ScanResult>
|
|
|
94
94
|
QrScanner.preload(): Promise<void>
|
|
95
95
|
QrScanner.configureWasm(overrides): void
|
|
96
96
|
QrScanner.setWorkerUrl(url): void
|
|
97
|
+
QrScanner.setDebug(enabled): void
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Debug Logging
|
|
101
|
+
|
|
102
|
+
The library includes performance profiling logs (camera acquisition timing, best-camera selection, etc.) that are **off by default**. Enable them at runtime for debugging:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
QrScanner.setDebug(true); // logs appear in console
|
|
106
|
+
QrScanner.setDebug(false); // back to silent (default)
|
|
97
107
|
```
|
|
98
108
|
|
|
99
109
|
## Worker Loading
|
package/dist/index.cjs
CHANGED
|
@@ -62,6 +62,12 @@ function setCachedDeviceId(facingMode, deviceId) {
|
|
|
62
62
|
} catch {
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
+
function clearCachedDeviceId(facingMode) {
|
|
66
|
+
try {
|
|
67
|
+
localStorage.removeItem(`${CACHE_KEY_PREFIX}${facingMode}`);
|
|
68
|
+
} catch {
|
|
69
|
+
}
|
|
70
|
+
}
|
|
65
71
|
var CameraManager = class {
|
|
66
72
|
constructor(config = {}) {
|
|
67
73
|
this.stream = null;
|
|
@@ -175,14 +181,20 @@ var CameraManager = class {
|
|
|
175
181
|
debug(
|
|
176
182
|
`[QrScanner] ensureBestCamera: current camera lacks autofocus (focusMode: ${JSON.stringify(capabilities.focusMode)})`
|
|
177
183
|
);
|
|
178
|
-
} catch {
|
|
184
|
+
} catch (err) {
|
|
185
|
+
debug(
|
|
186
|
+
`[QrScanner] ensureBestCamera: skipped (getCapabilities failed: ${err instanceof Error ? err.message : err})`
|
|
187
|
+
);
|
|
179
188
|
return;
|
|
180
189
|
}
|
|
181
190
|
const currentDeviceId = track.getSettings().deviceId;
|
|
182
191
|
let devices;
|
|
183
192
|
try {
|
|
184
193
|
devices = await navigator.mediaDevices.enumerateDevices();
|
|
185
|
-
} catch {
|
|
194
|
+
} catch (err) {
|
|
195
|
+
debug(
|
|
196
|
+
`[QrScanner] ensureBestCamera: skipped (enumerateDevices failed: ${err instanceof Error ? err.message : err})`
|
|
197
|
+
);
|
|
186
198
|
return;
|
|
187
199
|
}
|
|
188
200
|
if (!Array.isArray(devices)) return;
|
|
@@ -209,10 +221,13 @@ var CameraManager = class {
|
|
|
209
221
|
debug(
|
|
210
222
|
`[QrScanner] ensureBestCamera: candidate ${candidate.label || candidate.deviceId.slice(0, 8)}: getUserMedia ${(performance.now() - t).toFixed(0)}ms`
|
|
211
223
|
);
|
|
212
|
-
} catch {
|
|
224
|
+
} catch (err) {
|
|
213
225
|
debug(
|
|
214
|
-
`[QrScanner] ensureBestCamera: candidate ${candidate.label || candidate.deviceId.slice(0, 8)}: getUserMedia failed ${(performance.now() - t).toFixed(0)}ms`
|
|
226
|
+
`[QrScanner] ensureBestCamera: candidate ${candidate.label || candidate.deviceId.slice(0, 8)}: getUserMedia failed ${(performance.now() - t).toFixed(0)}ms \u2014 ${err instanceof Error ? err.name : err}`
|
|
215
227
|
);
|
|
228
|
+
if (err instanceof DOMException && err.name === "NotAllowedError") {
|
|
229
|
+
throw new CameraPermissionError();
|
|
230
|
+
}
|
|
216
231
|
continue;
|
|
217
232
|
}
|
|
218
233
|
const candidateTrack = candidateStream.getVideoTracks()[0];
|
|
@@ -231,27 +246,52 @@ var CameraManager = class {
|
|
|
231
246
|
this.stream = candidateStream;
|
|
232
247
|
return;
|
|
233
248
|
}
|
|
234
|
-
} catch {
|
|
249
|
+
} catch (err) {
|
|
250
|
+
debug(
|
|
251
|
+
`[QrScanner] ensureBestCamera: candidate getCapabilities failed \u2014 ${err instanceof Error ? err.message : err}`
|
|
252
|
+
);
|
|
235
253
|
}
|
|
236
254
|
for (const t2 of candidateStream.getTracks()) t2.stop();
|
|
237
255
|
}
|
|
238
|
-
|
|
239
|
-
|
|
256
|
+
const recoveryAttempts = [
|
|
257
|
+
// Original deviceId + resolution
|
|
258
|
+
{
|
|
240
259
|
video: {
|
|
241
260
|
deviceId: currentDeviceId ? { exact: currentDeviceId } : void 0,
|
|
242
261
|
width: this.resolution?.width ?? { ideal: 1920 },
|
|
243
262
|
height: this.resolution?.height ?? { ideal: 1080 }
|
|
244
263
|
},
|
|
245
264
|
audio: false
|
|
246
|
-
}
|
|
247
|
-
|
|
265
|
+
},
|
|
266
|
+
// facingMode + resolution
|
|
267
|
+
this.buildConstraints(),
|
|
268
|
+
// facingMode only
|
|
269
|
+
this.buildConstraints(false),
|
|
270
|
+
// Bare minimum — should always succeed if a camera exists
|
|
271
|
+
{ video: true, audio: false }
|
|
272
|
+
];
|
|
273
|
+
let lastRecoveryError;
|
|
274
|
+
for (const constraints of recoveryAttempts) {
|
|
248
275
|
try {
|
|
249
|
-
this.stream = await navigator.mediaDevices.getUserMedia(
|
|
250
|
-
|
|
276
|
+
this.stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
277
|
+
return;
|
|
278
|
+
} catch (err) {
|
|
279
|
+
if (err instanceof DOMException && err.name === "NotAllowedError") {
|
|
280
|
+
throw new CameraPermissionError();
|
|
281
|
+
}
|
|
282
|
+
if (err instanceof DOMException && err.name === "NotFoundError") {
|
|
283
|
+
throw new CameraNotFoundError();
|
|
284
|
+
}
|
|
285
|
+
lastRecoveryError = err;
|
|
286
|
+
debug(
|
|
287
|
+
`[QrScanner] ensureBestCamera recovery failed: ${err instanceof Error ? err.name : err}`
|
|
251
288
|
);
|
|
252
|
-
|
|
289
|
+
continue;
|
|
253
290
|
}
|
|
254
291
|
}
|
|
292
|
+
debug(
|
|
293
|
+
`[QrScanner] ensureBestCamera: all recovery attempts failed, last error: ${lastRecoveryError instanceof Error ? lastRecoveryError.message : lastRecoveryError}`
|
|
294
|
+
);
|
|
255
295
|
}
|
|
256
296
|
getVideoTrack() {
|
|
257
297
|
if (!this.stream) return null;
|
|
@@ -266,12 +306,10 @@ var CameraManager = class {
|
|
|
266
306
|
* fewer constraints lets us still open the camera on those browsers.
|
|
267
307
|
*/
|
|
268
308
|
async acquireStream() {
|
|
269
|
-
const labels = [];
|
|
270
309
|
const attempts = [];
|
|
271
310
|
if (this.facingMode === "environment" || this.facingMode === "user") {
|
|
272
311
|
const cachedId = getCachedDeviceId(this.facingMode);
|
|
273
312
|
if (cachedId) {
|
|
274
|
-
labels.push("cached deviceId");
|
|
275
313
|
const video = {
|
|
276
314
|
deviceId: { exact: cachedId }
|
|
277
315
|
};
|
|
@@ -279,42 +317,50 @@ var CameraManager = class {
|
|
|
279
317
|
else video.width = { ideal: 1920 };
|
|
280
318
|
if (this.resolution?.height) video.height = this.resolution.height;
|
|
281
319
|
else video.height = { ideal: 1080 };
|
|
282
|
-
attempts.push({
|
|
320
|
+
attempts.push({
|
|
321
|
+
label: "cached deviceId",
|
|
322
|
+
constraints: { video, audio: false },
|
|
323
|
+
isCachedDeviceId: true
|
|
324
|
+
});
|
|
283
325
|
}
|
|
284
326
|
}
|
|
285
|
-
labels.push("full constraints", "no resolution", "bare minimum");
|
|
286
327
|
attempts.push(
|
|
287
|
-
|
|
288
|
-
this.buildConstraints(),
|
|
289
|
-
|
|
290
|
-
this.buildConstraints(false),
|
|
291
|
-
// Bare minimum
|
|
292
|
-
{ video: true, audio: false }
|
|
328
|
+
{ label: "full constraints", constraints: this.buildConstraints() },
|
|
329
|
+
{ label: "no resolution", constraints: this.buildConstraints(false) },
|
|
330
|
+
{ label: "bare minimum", constraints: { video: true, audio: false } }
|
|
293
331
|
);
|
|
294
332
|
let lastError;
|
|
295
|
-
for (
|
|
333
|
+
for (const attempt of attempts) {
|
|
296
334
|
const t = performance.now();
|
|
297
335
|
try {
|
|
298
|
-
const stream = await navigator.mediaDevices.getUserMedia(
|
|
336
|
+
const stream = await navigator.mediaDevices.getUserMedia(
|
|
337
|
+
attempt.constraints
|
|
338
|
+
);
|
|
299
339
|
debug(
|
|
300
|
-
`[QrScanner] getUserMedia(${
|
|
340
|
+
`[QrScanner] getUserMedia(${attempt.label}): ${(performance.now() - t).toFixed(0)}ms \u2713`
|
|
301
341
|
);
|
|
302
342
|
return stream;
|
|
303
343
|
} catch (err) {
|
|
304
344
|
debug(
|
|
305
|
-
`[QrScanner] getUserMedia(${
|
|
345
|
+
`[QrScanner] getUserMedia(${attempt.label}): ${(performance.now() - t).toFixed(0)}ms \u2717 ${err instanceof Error ? err.name : err}`
|
|
306
346
|
);
|
|
307
|
-
if (err instanceof DOMException) {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
throw new CameraNotFoundError();
|
|
313
|
-
}
|
|
314
|
-
lastError = err;
|
|
315
|
-
continue;
|
|
347
|
+
if (err instanceof DOMException && err.name === "NotAllowedError") {
|
|
348
|
+
throw new CameraPermissionError();
|
|
349
|
+
}
|
|
350
|
+
if (err instanceof DOMException && err.name === "NotFoundError") {
|
|
351
|
+
throw new CameraNotFoundError();
|
|
316
352
|
}
|
|
317
|
-
|
|
353
|
+
if (!(err instanceof DOMException) && !(err instanceof OverconstrainedError)) {
|
|
354
|
+
throw err;
|
|
355
|
+
}
|
|
356
|
+
if (attempt.isCachedDeviceId) {
|
|
357
|
+
clearCachedDeviceId(this.facingMode);
|
|
358
|
+
debug(
|
|
359
|
+
`[QrScanner] cleared stale cached deviceId for "${this.facingMode}"`
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
lastError = err;
|
|
363
|
+
continue;
|
|
318
364
|
}
|
|
319
365
|
}
|
|
320
366
|
throw lastError;
|
|
@@ -424,9 +470,6 @@ var FrameExtractor = class {
|
|
|
424
470
|
markWorkerIdle() {
|
|
425
471
|
this.workerBusy = false;
|
|
426
472
|
}
|
|
427
|
-
markWorkerBusy() {
|
|
428
|
-
this.workerBusy = true;
|
|
429
|
-
}
|
|
430
473
|
};
|
|
431
474
|
|
|
432
475
|
// src/overlay.ts
|
|
@@ -436,22 +479,9 @@ function getRenderedVideoRect(video) {
|
|
|
436
479
|
const videoWidth = video.videoWidth || 1;
|
|
437
480
|
const videoHeight = video.videoHeight || 1;
|
|
438
481
|
const objectFit = getComputedStyle(video).objectFit;
|
|
439
|
-
if (objectFit === "cover") {
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
elementHeight / videoHeight
|
|
443
|
-
);
|
|
444
|
-
const renderedWidth = videoWidth * scale;
|
|
445
|
-
const renderedHeight = videoHeight * scale;
|
|
446
|
-
return {
|
|
447
|
-
offsetX: (elementWidth - renderedWidth) / 2,
|
|
448
|
-
offsetY: (elementHeight - renderedHeight) / 2,
|
|
449
|
-
width: renderedWidth,
|
|
450
|
-
height: renderedHeight
|
|
451
|
-
};
|
|
452
|
-
}
|
|
453
|
-
if (objectFit === "contain") {
|
|
454
|
-
const scale = Math.min(
|
|
482
|
+
if (objectFit === "cover" || objectFit === "contain") {
|
|
483
|
+
const scaleFn = objectFit === "cover" ? Math.max : Math.min;
|
|
484
|
+
const scale = scaleFn(
|
|
455
485
|
elementWidth / videoWidth,
|
|
456
486
|
elementHeight / videoHeight
|
|
457
487
|
);
|
|
@@ -773,18 +803,9 @@ var Scanner = class {
|
|
|
773
803
|
}
|
|
774
804
|
setInversionMode(mode) {
|
|
775
805
|
if (!this.worker) return;
|
|
776
|
-
const options = {
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
options.tryInvert = false;
|
|
780
|
-
break;
|
|
781
|
-
case "invert":
|
|
782
|
-
options.tryInvert = true;
|
|
783
|
-
break;
|
|
784
|
-
case "both":
|
|
785
|
-
options.tryInvert = true;
|
|
786
|
-
break;
|
|
787
|
-
}
|
|
806
|
+
const options = {
|
|
807
|
+
tryInvert: mode !== "original"
|
|
808
|
+
};
|
|
788
809
|
const msg = { type: "configure", options };
|
|
789
810
|
this.worker.postMessage(msg);
|
|
790
811
|
}
|
|
@@ -917,8 +938,8 @@ async function createImageBitmapFromBlob(blob) {
|
|
|
917
938
|
}
|
|
918
939
|
}
|
|
919
940
|
|
|
920
|
-
// src/
|
|
921
|
-
var
|
|
941
|
+
// src/decoder-utils.ts
|
|
942
|
+
var DEFAULT_READER_OPTIONS = {
|
|
922
943
|
formats: ["QRCode"],
|
|
923
944
|
tryHarder: true,
|
|
924
945
|
tryInvert: true,
|
|
@@ -935,12 +956,14 @@ function mapPosition(position) {
|
|
|
935
956
|
position.bottomLeft
|
|
936
957
|
];
|
|
937
958
|
}
|
|
959
|
+
|
|
960
|
+
// src/scan-image.ts
|
|
938
961
|
function isDirectInput(source) {
|
|
939
962
|
return source instanceof Blob || source instanceof ArrayBuffer || source instanceof Uint8Array || typeof ImageData !== "undefined" && source instanceof ImageData;
|
|
940
963
|
}
|
|
941
964
|
async function scanImage(source, options) {
|
|
942
965
|
const readerOptions = {
|
|
943
|
-
...
|
|
966
|
+
...DEFAULT_READER_OPTIONS,
|
|
944
967
|
...options?.decoderOptions,
|
|
945
968
|
formats: ["QRCode"]
|
|
946
969
|
};
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/debug.ts","../src/camera.ts","../src/frame-extractor.ts","../src/overlay.ts","../src/scan-region.ts","../src/scanner.ts","../src/scan-image.ts","../src/utils.ts"],"sourcesContent":["import { Scanner, setWorkerUrl } from './scanner.js';\nimport {\n CameraManager,\n CameraNotFoundError,\n CameraPermissionError,\n} from './camera.js';\nimport { setDebug } from './debug.js';\nimport { scanImage } from './scan-image.js';\nimport { setZXingModuleOverrides } from 'zxing-wasm/reader';\nimport type {\n ScanResult,\n ScanRegion,\n ScannerOptions,\n Camera,\n Point,\n FacingMode,\n DeviceId,\n InversionMode,\n} from './types.js';\nimport type { ReaderOptions } from 'zxing-wasm/reader';\n\nexport { CameraNotFoundError, CameraPermissionError };\n\nexport type {\n ScanResult,\n ScanRegion,\n ScannerOptions,\n Camera,\n Point,\n FacingMode,\n DeviceId,\n InversionMode,\n};\n\n/**\n * High-performance QR code scanner for the web, powered by ZXing-C++ WebAssembly.\n */\nclass QrScanner {\n private scanner: Scanner;\n\n constructor(\n videoElement: HTMLVideoElement,\n onDecode: (result: ScanResult) => void,\n options: ScannerOptions = {},\n ) {\n this.scanner = new Scanner(videoElement, onDecode, options);\n }\n\n /** Start camera and begin scanning. Resolves when camera is ready. */\n async start(): Promise<void> {\n return this.scanner.start();\n }\n\n /** Stop scanning and release the camera stream. */\n stop(): void {\n this.scanner.stop();\n }\n\n /** Stop scanning, release camera, terminate worker, clean up DOM. */\n destroy(): void {\n this.scanner.destroy();\n }\n\n /** Pause scanning. If stopStreamImmediately is false, camera stays on. */\n async pause(stopStreamImmediately?: boolean): Promise<boolean> {\n return this.scanner.pause(stopStreamImmediately);\n }\n\n /** Switch to a different camera by facing mode or device ID. */\n async setCamera(facingModeOrDeviceId: FacingMode | DeviceId): Promise<void> {\n return this.scanner.setCamera(facingModeOrDeviceId);\n }\n\n /** Check if the current camera supports flash/torch. */\n async hasFlash(): Promise<boolean> {\n return this.scanner.hasFlash();\n }\n\n /** Whether flash is currently on. */\n isFlashOn(): boolean {\n return this.scanner.isFlashOn();\n }\n\n /** Toggle flash on/off. */\n async toggleFlash(): Promise<void> {\n return this.scanner.toggleFlash();\n }\n\n /** Turn flash on. */\n async turnFlashOn(): Promise<void> {\n return this.scanner.turnFlashOn();\n }\n\n /** Turn flash off. */\n async turnFlashOff(): Promise<void> {\n return this.scanner.turnFlashOff();\n }\n\n /** Set the inversion mode for detecting inverted QR codes. */\n setInversionMode(mode: InversionMode): void {\n this.scanner.setInversionMode(mode);\n }\n\n // --- Static methods ---\n\n /** Check if the device has at least one camera. */\n static hasCamera(): Promise<boolean> {\n return CameraManager.hasCamera();\n }\n\n /** List available cameras. Pass true to request labels (triggers permission prompt). */\n static listCameras(requestLabels?: boolean): Promise<Camera[]> {\n return CameraManager.listCameras(requestLabels);\n }\n\n /**\n * Pre-load the WASM binary so it's ready when the scanner starts.\n * Call this early (e.g., on app init) to avoid delay on first scan.\n */\n static async preload(): Promise<void> {\n // Trigger WASM loading by doing a minimal scan\n const pixel = new Uint8ClampedArray([255, 255, 255, 255]);\n const img = new ImageData(pixel, 1, 1);\n try {\n await scanImage(img);\n } catch {\n // Expected — no QR code in a 1x1 image. The point was to load WASM.\n }\n }\n\n /**\n * Configure WASM loading. Call before creating any scanner instance.\n * @example\n * QrScanner.configureWasm({ locateFile: (filename) => `/wasm/${filename}` });\n */\n static configureWasm(overrides: Partial<EmscriptenModule>): void {\n setZXingModuleOverrides(overrides);\n }\n\n /**\n * Set a custom URL for the worker script. Call before creating any scanner.\n * Needed for CJS consumers or non-standard bundler setups.\n * By default, the worker URL is resolved via `new URL('./worker.js', import.meta.url)`,\n * which works with Vite, webpack 5, Parcel, and other modern bundlers.\n * @example\n * QrScanner.setWorkerUrl('/assets/qr-scanner-worker.js');\n */\n static setWorkerUrl(url: string | URL): void {\n setWorkerUrl(url);\n }\n\n /**\n * Enable or disable debug logging (performance timings, camera selection).\n * Off by default. Useful for diagnosing camera issues in the browser console.\n * @example\n * QrScanner.setDebug(true);\n */\n static setDebug(enabled: boolean): void {\n setDebug(enabled);\n }\n\n /** Scan a single image (not a video stream). */\n static scanImage(\n source:\n | HTMLImageElement\n | HTMLCanvasElement\n | OffscreenCanvas\n | ImageBitmap\n | ImageData\n | Blob\n | ArrayBuffer\n | Uint8Array\n | File\n | URL\n | string,\n options?: {\n scanRegion?: ScanRegion | null;\n canvas?: HTMLCanvasElement | null;\n decoderOptions?: Partial<ReaderOptions>;\n },\n ): Promise<ScanResult> {\n return scanImage(source, options);\n }\n}\n\nexport default QrScanner;\n","let enabled = false;\n\nexport function setDebug(on: boolean): void {\n enabled = on;\n}\n\nexport function debug(...args: unknown[]): void {\n if (enabled) console.debug(...args);\n}\n","import { debug } from './debug.js';\nimport type { FacingMode, DeviceId, Camera } from './types.js';\n\nexport class CameraPermissionError extends Error {\n constructor(\n message = 'Camera access denied. Please grant camera permission and try again.',\n ) {\n super(message);\n this.name = 'CameraPermissionError';\n }\n}\n\nexport class CameraNotFoundError extends Error {\n constructor(\n message = 'No camera found. Please connect a camera and try again.',\n ) {\n super(message);\n this.name = 'CameraNotFoundError';\n }\n}\n\nconst CACHE_KEY_PREFIX = '@agicash/qr-scanner:camera:';\n\nfunction getCachedDeviceId(facingMode: string): string | null {\n try {\n return localStorage.getItem(`${CACHE_KEY_PREFIX}${facingMode}`);\n } catch {\n return null;\n }\n}\n\nfunction setCachedDeviceId(facingMode: string, deviceId: string): void {\n try {\n localStorage.setItem(`${CACHE_KEY_PREFIX}${facingMode}`, deviceId);\n } catch {\n // localStorage unavailable or full — ignore\n }\n}\n\nexport interface CameraConfig {\n preferredCamera?: FacingMode | DeviceId;\n cameraResolution?: {\n width?: MediaTrackConstraintSet['width'];\n height?: MediaTrackConstraintSet['height'];\n };\n}\n\nexport class CameraManager {\n private stream: MediaStream | null = null;\n private facingMode: FacingMode | DeviceId;\n private resolution: CameraConfig['cameraResolution'];\n\n constructor(config: CameraConfig = {}) {\n this.facingMode = config.preferredCamera ?? 'environment';\n this.resolution = config.cameraResolution;\n }\n\n async start(video: HTMLVideoElement): Promise<MediaStream> {\n if (this.stream) {\n return this.stream;\n }\n\n const t0 = performance.now();\n\n this.stream = await this.acquireStream();\n const t1 = performance.now();\n debug(`[QrScanner] acquireStream: ${(t1 - t0).toFixed(0)}ms`);\n\n // On some devices (e.g. Samsung S24 + Brave), facingMode: 'environment'\n // picks an ultrawide camera that lacks autofocus. Check and switch to a\n // better camera BEFORE showing on screen to avoid visible flicker.\n await this.ensureBestCamera();\n const t2 = performance.now();\n debug(`[QrScanner] ensureBestCamera: ${(t2 - t1).toFixed(0)}ms`);\n\n video.srcObject = this.stream;\n video.setAttribute('playsinline', 'true');\n await video.play();\n const t3 = performance.now();\n debug(`[QrScanner] video.play: ${(t3 - t2).toFixed(0)}ms`);\n debug(`[QrScanner] camera.start total: ${(t3 - t0).toFixed(0)}ms`);\n\n // Cache the final camera so subsequent starts skip ensureBestCamera\n if (this.facingMode === 'environment' || this.facingMode === 'user') {\n const finalDeviceId = this.getVideoTrack()?.getSettings().deviceId;\n if (finalDeviceId) {\n setCachedDeviceId(this.facingMode, finalDeviceId);\n }\n }\n\n return this.stream;\n }\n\n stop(): void {\n if (this.stream) {\n for (const track of this.stream.getTracks()) {\n track.stop();\n }\n this.stream = null;\n }\n }\n\n async setCamera(\n facingModeOrDeviceId: FacingMode | DeviceId,\n video: HTMLVideoElement,\n ): Promise<void> {\n this.stop();\n this.facingMode = facingModeOrDeviceId;\n await this.start(video);\n }\n\n getStream(): MediaStream | null {\n return this.stream;\n }\n\n async hasFlash(): Promise<boolean> {\n const track = this.getVideoTrack();\n if (!track) return false;\n\n try {\n const capabilities = track.getCapabilities() as MediaTrackCapabilities & {\n torch?: boolean;\n };\n return capabilities.torch === true;\n } catch {\n return false;\n }\n }\n\n isFlashOn(): boolean {\n const track = this.getVideoTrack();\n if (!track) return false;\n\n const settings = track.getSettings() as MediaTrackSettings & {\n torch?: boolean;\n };\n return settings.torch === true;\n }\n\n async toggleFlash(): Promise<void> {\n if (this.isFlashOn()) {\n await this.turnFlashOff();\n } else {\n await this.turnFlashOn();\n }\n }\n\n async turnFlashOn(): Promise<void> {\n await this.setTorch(true);\n }\n\n async turnFlashOff(): Promise<void> {\n await this.setTorch(false);\n }\n\n private async setTorch(on: boolean): Promise<void> {\n const track = this.getVideoTrack();\n if (!track) {\n throw new Error('No active camera stream');\n }\n\n try {\n await track.applyConstraints({\n advanced: [{ torch: on } as MediaTrackConstraintSet],\n });\n } catch {\n throw new Error('Flash/torch is not supported on this device');\n }\n }\n\n /**\n * If the current camera lacks continuous autofocus (e.g. an ultrawide sensor\n * picked by facingMode: 'environment'), find a better camera with the same\n * facing mode and replace this.stream. Called before assigning to the video\n * element so the user never sees the wrong camera.\n */\n private async ensureBestCamera(): Promise<void> {\n if (this.facingMode !== 'environment' && this.facingMode !== 'user') {\n debug('[QrScanner] ensureBestCamera: skipped (specific deviceId)');\n return;\n }\n\n const track = this.getVideoTrack();\n if (!track) return;\n\n try {\n const capabilities = track.getCapabilities() as MediaTrackCapabilities & {\n focusMode?: string[];\n };\n if (\n !capabilities.focusMode ||\n capabilities.focusMode.includes('continuous')\n ) {\n // focusMode not reported (e.g. Safari/iOS) or has autofocus — skip.\n // Only enter the candidate loop when the browser explicitly reports\n // focusMode without 'continuous' (e.g. S24 + Brave ultrawide).\n debug(\n `[QrScanner] ensureBestCamera: skipped (focusMode: ${JSON.stringify(capabilities.focusMode)})`,\n );\n return;\n }\n debug(\n `[QrScanner] ensureBestCamera: current camera lacks autofocus (focusMode: ${JSON.stringify(capabilities.focusMode)})`,\n );\n } catch {\n return;\n }\n\n // Current camera lacks continuous autofocus.\n // Enumerate devices while stream is active (ensures deviceIds are available).\n const currentDeviceId = track.getSettings().deviceId;\n\n let devices: MediaDeviceInfo[];\n try {\n devices = await navigator.mediaDevices.enumerateDevices();\n } catch {\n return;\n }\n if (!Array.isArray(devices)) return;\n\n const candidates = devices.filter(\n (d) => d.kind === 'videoinput' && d.deviceId !== currentDeviceId,\n );\n debug(\n `[QrScanner] ensureBestCamera: testing ${candidates.length} candidate camera(s)`,\n );\n if (candidates.length === 0) return;\n\n // Stop current stream — mobile devices only allow one active camera\n this.stop();\n\n for (const candidate of candidates) {\n const t = performance.now();\n let candidateStream: MediaStream;\n try {\n candidateStream = await navigator.mediaDevices.getUserMedia({\n video: {\n deviceId: { exact: candidate.deviceId },\n width: this.resolution?.width ?? { ideal: 1920 },\n height: this.resolution?.height ?? { ideal: 1080 },\n },\n audio: false,\n });\n debug(\n `[QrScanner] ensureBestCamera: candidate ${candidate.label || candidate.deviceId.slice(0, 8)}: getUserMedia ${(performance.now() - t).toFixed(0)}ms`,\n );\n } catch {\n debug(\n `[QrScanner] ensureBestCamera: candidate ${candidate.label || candidate.deviceId.slice(0, 8)}: getUserMedia failed ${(performance.now() - t).toFixed(0)}ms`,\n );\n continue;\n }\n\n const candidateTrack = candidateStream.getVideoTracks()[0];\n if (!candidateTrack) {\n for (const t of candidateStream.getTracks()) t.stop();\n continue;\n }\n\n // Must match the desired facing mode\n const candidateSettings =\n candidateTrack.getSettings() as MediaTrackSettings & {\n facingMode?: string;\n };\n if (\n candidateSettings.facingMode &&\n candidateSettings.facingMode !== this.facingMode\n ) {\n for (const t of candidateStream.getTracks()) t.stop();\n continue;\n }\n\n // Check if this camera supports continuous autofocus\n try {\n const candidateCaps =\n candidateTrack.getCapabilities() as MediaTrackCapabilities & {\n focusMode?: string[];\n };\n if (candidateCaps.focusMode?.includes('continuous')) {\n this.stream = candidateStream;\n return;\n }\n } catch {\n // Can't check capabilities, skip\n }\n\n for (const t of candidateStream.getTracks()) t.stop();\n }\n\n // No better camera found — re-open the original\n try {\n this.stream = await navigator.mediaDevices.getUserMedia({\n video: {\n deviceId: currentDeviceId ? { exact: currentDeviceId } : undefined,\n width: this.resolution?.width ?? { ideal: 1920 },\n height: this.resolution?.height ?? { ideal: 1080 },\n },\n audio: false,\n });\n } catch {\n try {\n this.stream = await navigator.mediaDevices.getUserMedia(\n this.buildConstraints(),\n );\n } catch {\n // Could not recover camera\n }\n }\n }\n\n private getVideoTrack(): MediaStreamTrack | null {\n if (!this.stream) return null;\n const tracks = this.stream.getVideoTracks();\n return tracks[0] ?? null;\n }\n\n /**\n * Try getUserMedia with progressively simpler constraints.\n *\n * Some browsers (e.g. Brave on Samsung Galaxy S24) throw NotReadableError\n * when facingMode and resolution constraints are combined. Falling back to\n * fewer constraints lets us still open the camera on those browsers.\n */\n private async acquireStream(): Promise<MediaStream> {\n const labels: string[] = [];\n const attempts: MediaStreamConstraints[] = [];\n\n // If we've previously found a good camera for this facingMode, try it first\n if (this.facingMode === 'environment' || this.facingMode === 'user') {\n const cachedId = getCachedDeviceId(this.facingMode);\n if (cachedId) {\n labels.push('cached deviceId');\n const video: MediaTrackConstraints = {\n deviceId: { exact: cachedId },\n };\n if (this.resolution?.width) video.width = this.resolution.width;\n else video.width = { ideal: 1920 };\n if (this.resolution?.height) video.height = this.resolution.height;\n else video.height = { ideal: 1080 };\n attempts.push({ video, audio: false });\n }\n }\n\n // Standard fallback chain\n labels.push('full constraints', 'no resolution', 'bare minimum');\n attempts.push(\n // facingMode/deviceId + resolution\n this.buildConstraints(),\n // facingMode/deviceId only, no resolution\n this.buildConstraints(false),\n // Bare minimum\n { video: true, audio: false },\n );\n\n let lastError: unknown;\n for (let i = 0; i < attempts.length; i++) {\n const t = performance.now();\n try {\n const stream = await navigator.mediaDevices.getUserMedia(attempts[i]);\n debug(\n `[QrScanner] getUserMedia(${labels[i]}): ${(performance.now() - t).toFixed(0)}ms ✓`,\n );\n return stream;\n } catch (err) {\n debug(\n `[QrScanner] getUserMedia(${labels[i]}): ${(performance.now() - t).toFixed(0)}ms ✗ ${err instanceof DOMException ? err.name : err}`,\n );\n if (err instanceof DOMException) {\n if (err.name === 'NotAllowedError') {\n throw new CameraPermissionError();\n }\n if (err.name === 'NotFoundError') {\n throw new CameraNotFoundError();\n }\n // NotReadableError or OverconstrainedError — try next fallback\n lastError = err;\n continue;\n }\n throw err;\n }\n }\n\n throw lastError;\n }\n\n private buildConstraints(includeResolution = true): MediaStreamConstraints {\n const video: MediaTrackConstraints = {};\n\n if (includeResolution) {\n video.width = this.resolution?.width ?? { ideal: 1920 };\n video.height = this.resolution?.height ?? { ideal: 1080 };\n }\n\n if (this.facingMode === 'environment' || this.facingMode === 'user') {\n video.facingMode = this.facingMode;\n } else {\n video.deviceId = { exact: this.facingMode };\n }\n\n return { video, audio: false };\n }\n\n static async hasCamera(): Promise<boolean> {\n try {\n const devices = await navigator.mediaDevices.enumerateDevices();\n return devices.some((d) => d.kind === 'videoinput');\n } catch {\n return false;\n }\n }\n\n static async listCameras(requestLabels = false): Promise<Camera[]> {\n if (requestLabels) {\n // Requesting labels requires a temporary stream to trigger the permission prompt\n try {\n const stream = await navigator.mediaDevices.getUserMedia({\n video: true,\n });\n for (const track of stream.getTracks()) {\n track.stop();\n }\n } catch {\n // Permission denied — fall through with empty labels\n }\n }\n\n const devices = await navigator.mediaDevices.enumerateDevices();\n return devices\n .filter((d) => d.kind === 'videoinput')\n .map((d) => ({\n id: d.deviceId,\n label: d.label || `Camera ${d.deviceId.slice(0, 8)}`,\n }));\n }\n}\n","import type { ScanRegion } from './types.js';\n\nexport interface FrameExtractorConfig {\n maxScansPerSecond: number;\n getScanRegion: () => ScanRegion;\n}\n\nexport class FrameExtractor {\n private video: HTMLVideoElement;\n private canvas: HTMLCanvasElement | OffscreenCanvas;\n private ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;\n private rafId: number | null = null;\n private running = false;\n private workerBusy = false;\n private lastScanTime = -Infinity;\n private minInterval: number;\n private getScanRegion: () => ScanRegion;\n private onFrame: ((imageData: ImageData) => void) | null = null;\n\n constructor(video: HTMLVideoElement, config: FrameExtractorConfig) {\n this.video = video;\n this.minInterval = 1000 / config.maxScansPerSecond;\n this.getScanRegion = config.getScanRegion;\n\n if (typeof OffscreenCanvas !== 'undefined') {\n this.canvas = new OffscreenCanvas(1, 1);\n this.ctx = this.canvas.getContext('2d')! as OffscreenCanvasRenderingContext2D;\n } else {\n this.canvas = document.createElement('canvas');\n this.canvas.style.display = 'none';\n this.ctx = this.canvas.getContext('2d')!;\n }\n }\n\n start(onFrame: (imageData: ImageData) => void): void {\n if (this.running) return;\n this.running = true;\n this.onFrame = onFrame;\n this.rafId = requestAnimationFrame(this.tick);\n }\n\n stop(): void {\n this.running = false;\n this.onFrame = null;\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n }\n\n destroy(): void {\n this.stop();\n if (this.canvas instanceof HTMLCanvasElement && this.canvas.parentNode) {\n this.canvas.parentNode.removeChild(this.canvas);\n }\n }\n\n markWorkerIdle(): void {\n this.workerBusy = false;\n }\n\n markWorkerBusy(): void {\n this.workerBusy = true;\n }\n\n private tick = (): void => {\n if (!this.running) return;\n\n this.rafId = requestAnimationFrame(this.tick);\n\n // Skip if worker is still processing previous frame\n if (this.workerBusy) return;\n\n // Rate limiting\n const now = performance.now();\n if (now - this.lastScanTime < this.minInterval) return;\n\n // Skip if video isn't ready\n if (this.video.readyState < 2) return;\n\n this.lastScanTime = now;\n\n const region = this.getScanRegion();\n const sx = region.x ?? 0;\n const sy = region.y ?? 0;\n const sw = region.width ?? this.video.videoWidth;\n const sh = region.height ?? this.video.videoHeight;\n\n if (sw <= 0 || sh <= 0) return;\n\n this.canvas.width = sw;\n this.canvas.height = sh;\n\n this.ctx.drawImage(this.video, sx, sy, sw, sh, 0, 0, sw, sh);\n const imageData = this.ctx.getImageData(0, 0, sw, sh);\n\n this.workerBusy = true;\n this.onFrame?.(imageData);\n };\n}\n","import type { ScanRegion, Point } from './types.js';\n\n/**\n * Compute the actual rendered position and size of the video content\n * within the element, accounting for object-fit: cover.\n *\n * With object-fit: cover the video is scaled up to fill the element and\n * cropped. The rendered content is larger than the element, centered,\n * with negative offsets for the cropped portions.\n *\n * When object-fit is the default (fill), the rendered size equals the\n * element size and offsets are zero — so this is backwards-compatible.\n */\nfunction getRenderedVideoRect(video: HTMLVideoElement): {\n offsetX: number;\n offsetY: number;\n width: number;\n height: number;\n} {\n const elementWidth = video.clientWidth;\n const elementHeight = video.clientHeight;\n const videoWidth = video.videoWidth || 1;\n const videoHeight = video.videoHeight || 1;\n\n const objectFit = getComputedStyle(video).objectFit;\n\n if (objectFit === 'cover') {\n const scale = Math.max(\n elementWidth / videoWidth,\n elementHeight / videoHeight,\n );\n const renderedWidth = videoWidth * scale;\n const renderedHeight = videoHeight * scale;\n return {\n offsetX: (elementWidth - renderedWidth) / 2,\n offsetY: (elementHeight - renderedHeight) / 2,\n width: renderedWidth,\n height: renderedHeight,\n };\n }\n\n if (objectFit === 'contain') {\n const scale = Math.min(\n elementWidth / videoWidth,\n elementHeight / videoHeight,\n );\n const renderedWidth = videoWidth * scale;\n const renderedHeight = videoHeight * scale;\n return {\n offsetX: (elementWidth - renderedWidth) / 2,\n offsetY: (elementHeight - renderedHeight) / 2,\n width: renderedWidth,\n height: renderedHeight,\n };\n }\n\n // Default (fill / none / scale-down with no scaling needed): element dimensions\n return { offsetX: 0, offsetY: 0, width: elementWidth, height: elementHeight };\n}\n\nexport interface OverlayConfig {\n highlightScanRegion: boolean;\n highlightCodeOutline: boolean;\n customOverlay?: HTMLDivElement;\n}\n\nexport class ScanOverlay {\n private container: HTMLElement;\n private overlayEl: HTMLDivElement | null = null;\n private codeOutlineEl: SVGElement | null = null;\n private config: OverlayConfig;\n private video: HTMLVideoElement;\n\n constructor(video: HTMLVideoElement, config: OverlayConfig) {\n this.video = video;\n this.config = config;\n\n const parent = video.parentElement;\n if (!parent) {\n throw new Error(\n 'QrScanner: video element must have a parent element. ' +\n 'The parent should have position: relative.',\n );\n }\n this.container = parent;\n }\n\n setup(): void {\n if (this.config.customOverlay) {\n this.overlayEl = this.config.customOverlay;\n this.positionOverlay();\n return;\n }\n\n if (this.config.highlightScanRegion) {\n this.createScanRegionOverlay();\n }\n\n if (this.config.highlightCodeOutline) {\n this.createCodeOutline();\n }\n }\n\n updateScanRegion(region: ScanRegion): void {\n if (!this.overlayEl || this.config.customOverlay) return;\n this.positionOverlayToRegion(region);\n }\n\n updateCodeOutline(\n cornerPoints: Point[] | null,\n scanRegion?: ScanRegion,\n ): void {\n if (!this.codeOutlineEl) return;\n\n if (!cornerPoints || cornerPoints.length < 4) {\n this.codeOutlineEl.style.display = 'none';\n return;\n }\n\n this.codeOutlineEl.style.display = 'block';\n\n const polygon = this.codeOutlineEl.querySelector('polygon');\n if (!polygon) return;\n\n // Corner points are relative to the cropped scan region.\n // Add the scan region offset to get full video coordinates,\n // then scale to display coordinates (accounting for object-fit).\n const regionX = scanRegion?.x ?? 0;\n const regionY = scanRegion?.y ?? 0;\n const rendered = getRenderedVideoRect(this.video);\n const scaleX = rendered.width / this.video.videoWidth;\n const scaleY = rendered.height / this.video.videoHeight;\n\n const points = cornerPoints\n .map(\n (p) =>\n `${(p.x + regionX) * scaleX + rendered.offsetX},${(p.y + regionY) * scaleY + rendered.offsetY}`,\n )\n .join(' ');\n\n polygon.setAttribute('points', points);\n }\n\n destroy(): void {\n if (this.overlayEl && !this.config.customOverlay) {\n this.overlayEl.remove();\n }\n if (this.codeOutlineEl) {\n this.codeOutlineEl.remove();\n }\n this.overlayEl = null;\n this.codeOutlineEl = null;\n }\n\n private createScanRegionOverlay(): void {\n this.overlayEl = document.createElement('div');\n this.overlayEl.className = 'qr-scanner-region';\n\n // Overlay size: 3/4 of the smaller container dimension, centered.\n const cw = this.container.clientWidth;\n const ch = this.container.clientHeight;\n const size = Math.round((Math.min(cw, ch) * 3) / 4);\n\n Object.assign(this.overlayEl.style, {\n position: 'absolute',\n top: '50%',\n left: '50%',\n transform: 'translate(-50%, -50%)',\n width: `${size}px`,\n height: `${size}px`,\n border: '2px solid rgba(255, 255, 255, 0.5)',\n borderRadius: '8px',\n boxShadow: '0 0 0 9999px rgba(0, 0, 0, 0.5)',\n pointerEvents: 'none',\n zIndex: '10',\n });\n\n // Corner markers\n const corners = ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n for (const corner of corners) {\n const marker = document.createElement('div');\n marker.className = `qr-scanner-corner qr-scanner-corner-${corner}`;\n\n const [vertical, horizontal] = corner.split('-');\n\n Object.assign(marker.style, {\n position: 'absolute',\n width: '24px',\n height: '24px',\n [vertical]: '-2px',\n [horizontal]: '-2px',\n [`border-${vertical}`]: '3px solid white',\n [`border-${horizontal}`]: '3px solid white',\n [`border-${vertical}-${horizontal}-radius`]: '8px',\n });\n\n this.overlayEl.appendChild(marker);\n }\n\n this.container.appendChild(this.overlayEl);\n }\n\n private createCodeOutline(): void {\n const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n svg.setAttribute('class', 'qr-scanner-code-outline');\n\n Object.assign(svg.style, {\n position: 'absolute',\n top: '0',\n left: '0',\n width: '100%',\n height: '100%',\n pointerEvents: 'none',\n zIndex: '11',\n display: 'none',\n });\n\n const polygon = document.createElementNS(\n 'http://www.w3.org/2000/svg',\n 'polygon',\n );\n polygon.setAttribute('fill', 'none');\n polygon.setAttribute('stroke', '#00ff00');\n polygon.setAttribute('stroke-width', '3');\n polygon.setAttribute('stroke-linejoin', 'round');\n\n // Animated stroke\n const animate = document.createElementNS(\n 'http://www.w3.org/2000/svg',\n 'animate',\n );\n animate.setAttribute('attributeName', 'stroke-opacity');\n animate.setAttribute('values', '1;0.5;1');\n animate.setAttribute('dur', '1.5s');\n animate.setAttribute('repeatCount', 'indefinite');\n polygon.appendChild(animate);\n\n svg.appendChild(polygon);\n this.container.appendChild(svg);\n this.codeOutlineEl = svg;\n }\n\n private positionOverlay(): void {\n if (!this.overlayEl) return;\n\n Object.assign(this.overlayEl.style, {\n position: 'absolute',\n top: '0',\n left: '0',\n width: '100%',\n height: '100%',\n pointerEvents: 'none',\n zIndex: '10',\n });\n }\n\n private positionOverlayToRegion(_region: ScanRegion): void {\n if (!this.overlayEl) return;\n\n // Keep the overlay at a container-relative centered size rather than\n // mapping scan region coordinates to display coordinates. With\n // object-fit: cover on a portrait phone with a landscape camera, the\n // mapped region extends beyond the container — fine once the camera is\n // visible but causes a jarring jump from the initial placeholder.\n // The overlay is a visual guide; the actual scan area (in the frame\n // extractor) is unaffected and may be larger than what's shown.\n const cw = this.container.clientWidth;\n const ch = this.container.clientHeight;\n const size = Math.round((Math.min(cw, ch) * 3) / 4);\n\n // Only update size — initial CSS centering (top: 50%, left: 50%,\n // transform: translate(-50%, -50%)) persists and handles re-centering.\n Object.assign(this.overlayEl.style, {\n width: `${size}px`,\n height: `${size}px`,\n });\n }\n}\n","import type { ScanRegion } from './types.js';\n\n/**\n * Calculate the default scan region: a centered square covering\n * 2/3 of the smaller video dimension.\n */\nexport function calculateDefaultScanRegion(video: HTMLVideoElement): ScanRegion {\n const videoWidth = video.videoWidth || video.width;\n const videoHeight = video.videoHeight || video.height;\n\n const smallerDimension = Math.min(videoWidth, videoHeight);\n const size = Math.round((smallerDimension * 2) / 3);\n\n return {\n x: Math.round((videoWidth - size) / 2),\n y: Math.round((videoHeight - size) / 2),\n width: size,\n height: size,\n };\n}\n","import { CameraManager } from './camera.js';\nimport { debug } from './debug.js';\nimport { FrameExtractor } from './frame-extractor.js';\nimport { ScanOverlay } from './overlay.js';\nimport { calculateDefaultScanRegion } from './scan-region.js';\nimport type {\n ScannerOptions,\n ScanResult,\n ScanRegion,\n InversionMode,\n WorkerRequest,\n WorkerResponse,\n} from './types.js';\nimport type { ReaderOptions } from 'zxing-wasm/reader';\n\ntype OnDecodeCallback = (result: ScanResult) => void;\n\n/**\n * Custom worker URL override. When set, this URL is used instead of the\n * default bundler-resolved worker. Useful for CJS consumers or non-standard\n * bundler setups.\n */\nlet customWorkerUrl: string | URL | null = null;\n\nexport function setWorkerUrl(url: string | URL): void {\n customWorkerUrl = url;\n}\n\nfunction resolveWorkerUrl(): string | URL {\n if (customWorkerUrl) {\n return customWorkerUrl;\n }\n // Standard pattern: modern bundlers (Vite, webpack 5, Parcel, esbuild)\n // resolve `new URL('./file', import.meta.url)` at build time,\n // copying worker.js to the output directory and returning the correct URL.\n // Falls back gracefully for CJS builds where import.meta is unavailable.\n try {\n return new URL('./worker.js', import.meta.url);\n } catch {\n throw new Error(\n '@agicash/qr-scanner: Could not resolve worker URL. ' +\n 'Call QrScanner.setWorkerUrl() with the path to the worker script before creating a scanner. ' +\n 'Example: QrScanner.setWorkerUrl(\"/path/to/@agicash/qr-scanner/dist/worker.js\")',\n );\n }\n}\n\nexport class Scanner {\n private video: HTMLVideoElement;\n private onDecode: OnDecodeCallback;\n private options: ScannerOptions;\n private camera: CameraManager;\n private frameExtractor: FrameExtractor | null = null;\n private worker: Worker | null = null;\n private overlay: ScanOverlay | null = null;\n private active = false;\n private paused = false;\n private destroyed = false;\n\n constructor(\n video: HTMLVideoElement,\n onDecode: OnDecodeCallback,\n options: ScannerOptions = {},\n ) {\n this.video = video;\n this.onDecode = onDecode;\n this.options = options;\n\n this.camera = new CameraManager({\n preferredCamera: options.preferredCamera,\n cameraResolution: options.cameraResolution,\n });\n }\n\n async start(): Promise<void> {\n if (this.destroyed) {\n throw new Error('Scanner has been destroyed');\n }\n\n if (this.active && !this.paused) {\n return; // Already running\n }\n\n const t0 = performance.now();\n\n // Show overlay immediately (CSS-centered placeholder) so the UI looks\n // ready while the camera is still loading.\n if (\n !this.overlay &&\n (this.options.highlightScanRegion ||\n this.options.highlightCodeOutline ||\n this.options.overlay)\n ) {\n try {\n this.overlay = new ScanOverlay(this.video, {\n highlightScanRegion: this.options.highlightScanRegion ?? false,\n highlightCodeOutline: this.options.highlightCodeOutline ?? false,\n customOverlay: this.options.overlay,\n });\n this.overlay.setup();\n } catch {\n // Overlay setup failed (e.g., no parent element) — continue without overlay\n }\n }\n\n // Start camera\n await this.camera.start(this.video);\n debug(\n `[QrScanner] start: camera ready ${(performance.now() - t0).toFixed(0)}ms`,\n );\n\n // Now that video dimensions are known, position overlay exactly\n if (this.overlay) {\n this.overlay.updateScanRegion(this.getCurrentScanRegion());\n }\n\n // Create worker if needed\n if (!this.worker) {\n const tw = performance.now();\n this.worker = this.createWorker();\n debug(\n `[QrScanner] start: worker created ${(performance.now() - tw).toFixed(0)}ms`,\n );\n }\n\n // Create frame extractor if needed\n if (!this.frameExtractor) {\n this.frameExtractor = new FrameExtractor(this.video, {\n maxScansPerSecond: this.options.maxScansPerSecond ?? 15,\n getScanRegion: () => this.getCurrentScanRegion(),\n });\n }\n\n // Start frame extraction loop\n this.frameExtractor.start((imageData) => {\n this.sendToWorker(imageData);\n });\n\n this.active = true;\n this.paused = false;\n\n debug(`[QrScanner] start: total ${(performance.now() - t0).toFixed(0)}ms`);\n }\n\n stop(): void {\n this.frameExtractor?.stop();\n this.camera.stop();\n this.video.srcObject = null;\n this.active = false;\n this.paused = false;\n }\n\n destroy(): void {\n if (this.destroyed) return;\n this.stop();\n this.frameExtractor?.destroy();\n this.frameExtractor = null;\n this.overlay?.destroy();\n this.overlay = null;\n this.worker?.terminate();\n this.worker = null;\n this.destroyed = true;\n }\n\n async pause(stopStreamImmediately = true): Promise<boolean> {\n if (!this.active) return false;\n\n this.frameExtractor?.stop();\n this.paused = true;\n\n if (stopStreamImmediately) {\n this.camera.stop();\n this.video.srcObject = null;\n }\n\n return true;\n }\n\n async setCamera(facingModeOrDeviceId: string): Promise<void> {\n const wasActive = this.active && !this.paused;\n if (wasActive) {\n this.frameExtractor?.stop();\n }\n\n await this.camera.setCamera(facingModeOrDeviceId, this.video);\n\n if (wasActive) {\n this.frameExtractor?.start((imageData) => {\n this.sendToWorker(imageData);\n });\n }\n }\n\n async hasFlash(): Promise<boolean> {\n return this.camera.hasFlash();\n }\n\n isFlashOn(): boolean {\n return this.camera.isFlashOn();\n }\n\n async toggleFlash(): Promise<void> {\n return this.camera.toggleFlash();\n }\n\n async turnFlashOn(): Promise<void> {\n return this.camera.turnFlashOn();\n }\n\n async turnFlashOff(): Promise<void> {\n return this.camera.turnFlashOff();\n }\n\n setInversionMode(mode: InversionMode): void {\n if (!this.worker) return;\n\n const options: Partial<ReaderOptions> = {};\n switch (mode) {\n case 'original':\n options.tryInvert = false;\n break;\n case 'invert':\n options.tryInvert = true;\n break;\n case 'both':\n options.tryInvert = true;\n break;\n }\n\n const msg: WorkerRequest = { type: 'configure', options };\n this.worker.postMessage(msg);\n }\n\n isActive(): boolean {\n return this.active;\n }\n\n isPaused(): boolean {\n return this.paused;\n }\n\n isDestroyed(): boolean {\n return this.destroyed;\n }\n\n private getCurrentScanRegion(): ScanRegion {\n if (this.options.calculateScanRegion) {\n return this.options.calculateScanRegion(this.video);\n }\n return calculateDefaultScanRegion(this.video);\n }\n\n private createWorker(): Worker {\n const workerUrl = resolveWorkerUrl();\n const worker = new Worker(workerUrl, { type: 'module' });\n\n // Configure with custom decoder options\n if (this.options.decoderOptions) {\n const msg: WorkerRequest = {\n type: 'configure',\n options: this.options.decoderOptions,\n };\n worker.postMessage(msg);\n }\n\n worker.onmessage = (e: MessageEvent<WorkerResponse>) => {\n this.handleWorkerMessage(e.data);\n };\n\n worker.onerror = (err) => {\n console.error('QR Scanner worker error:', err);\n this.frameExtractor?.markWorkerIdle();\n };\n\n return worker;\n }\n\n private handleWorkerMessage(response: WorkerResponse): void {\n this.frameExtractor?.markWorkerIdle();\n\n if (response.type === 'ready') {\n return;\n }\n\n if (response.type === 'error') {\n this.options.onDecodeError?.(response.message);\n return;\n }\n\n if (response.type === 'result') {\n if (response.results.length > 0) {\n const result = response.results[0];\n this.onDecode(result);\n\n // Update overlay\n if (this.overlay) {\n const region = this.getCurrentScanRegion();\n this.overlay.updateScanRegion(region);\n this.overlay.updateCodeOutline(result.cornerPoints, region);\n }\n } else {\n this.options.onDecodeError?.('No QR code found');\n\n // Hide code outline when no QR found\n if (this.overlay) {\n this.overlay.updateCodeOutline(null);\n }\n }\n }\n }\n\n private sendToWorker(imageData: ImageData): void {\n if (!this.worker) return;\n\n const msg: WorkerRequest = { type: 'decode', imageData };\n this.worker.postMessage(msg, [imageData.data.buffer]);\n }\n}\n","import { readBarcodes, type ReaderOptions } from 'zxing-wasm/reader';\nimport type { ScanResult, ScanRegion, Point } from './types.js';\nimport { loadImageData } from './utils.js';\n\nconst defaultReaderOptions: ReaderOptions = {\n formats: ['QRCode'],\n tryHarder: true,\n tryInvert: true,\n tryRotate: true,\n tryDenoise: false,\n tryDownscale: true,\n maxNumberOfSymbols: 1,\n};\n\nfunction mapPosition(position: {\n topLeft: Point;\n topRight: Point;\n bottomLeft: Point;\n bottomRight: Point;\n}): Point[] {\n return [\n position.topLeft,\n position.topRight,\n position.bottomRight,\n position.bottomLeft,\n ];\n}\n\n/** Input types that zxing-wasm can handle directly (no canvas needed). */\ntype DirectInput = Blob | ArrayBuffer | Uint8Array | ImageData;\n\n/** Input types that need canvas-based pixel extraction. */\ntype CanvasInput = HTMLImageElement | HTMLCanvasElement | OffscreenCanvas | ImageBitmap;\n\nfunction isDirectInput(source: unknown): source is DirectInput {\n return (\n source instanceof Blob ||\n source instanceof ArrayBuffer ||\n source instanceof Uint8Array ||\n (typeof ImageData !== 'undefined' && source instanceof ImageData)\n );\n}\n\n/**\n * Scan a single image for QR codes. Does not require a camera or video stream.\n */\nexport async function scanImage(\n source:\n | CanvasInput\n | DirectInput\n | File\n | URL\n | string,\n options?: {\n scanRegion?: ScanRegion | null;\n canvas?: HTMLCanvasElement | null;\n decoderOptions?: Partial<ReaderOptions>;\n },\n): Promise<ScanResult> {\n const readerOptions: ReaderOptions = {\n ...defaultReaderOptions,\n ...options?.decoderOptions,\n formats: ['QRCode'],\n };\n\n let input: DirectInput;\n if (isDirectInput(source)) {\n input = source;\n } else if (typeof source === 'string' || source instanceof URL) {\n // URL string - fetch and pass as ArrayBuffer\n const url = source instanceof URL ? source.href : source;\n const response = await fetch(url);\n input = await response.arrayBuffer();\n } else {\n // Canvas-based sources - extract ImageData\n input = await loadImageData(source, options?.scanRegion, options?.canvas);\n }\n\n const results = await readBarcodes(input, readerOptions);\n const valid = results.filter((r) => r.isValid);\n\n if (valid.length === 0) {\n throw new Error('No QR code found in the image');\n }\n\n const first = valid[0];\n return {\n data: first.text,\n cornerPoints: mapPosition(first.position),\n };\n}\n","import type { ScanRegion } from './types.js';\n\n/**\n * Load an image source into an ImageData object, optionally cropping to a scan region.\n */\nexport async function loadImageData(\n source: HTMLImageElement | HTMLCanvasElement | OffscreenCanvas | ImageBitmap | File | Blob | URL | string,\n scanRegion?: ScanRegion | null,\n canvas?: HTMLCanvasElement | null,\n): Promise<ImageData> {\n const img = await resolveImageSource(source);\n\n const sx = scanRegion?.x ?? 0;\n const sy = scanRegion?.y ?? 0;\n const sw = scanRegion?.width ?? (img.width - sx);\n const sh = scanRegion?.height ?? (img.height - sy);\n\n let drawCanvas: HTMLCanvasElement | OffscreenCanvas;\n let ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;\n\n if (canvas) {\n drawCanvas = canvas;\n canvas.width = sw;\n canvas.height = sh;\n ctx = canvas.getContext('2d')!;\n } else if (typeof OffscreenCanvas !== 'undefined') {\n drawCanvas = new OffscreenCanvas(sw, sh);\n ctx = drawCanvas.getContext('2d')! as OffscreenCanvasRenderingContext2D;\n } else {\n drawCanvas = document.createElement('canvas');\n drawCanvas.width = sw;\n drawCanvas.height = sh;\n ctx = drawCanvas.getContext('2d')!;\n }\n\n ctx.drawImage(img as CanvasImageSource, sx, sy, sw, sh, 0, 0, sw, sh);\n return ctx.getImageData(0, 0, sw, sh);\n}\n\nasync function resolveImageSource(\n source: HTMLImageElement | HTMLCanvasElement | OffscreenCanvas | ImageBitmap | File | Blob | URL | string,\n): Promise<HTMLImageElement | HTMLCanvasElement | OffscreenCanvas | ImageBitmap> {\n if (source instanceof HTMLImageElement || source instanceof HTMLCanvasElement || source instanceof ImageBitmap) {\n return source;\n }\n\n if (typeof OffscreenCanvas !== 'undefined' && source instanceof OffscreenCanvas) {\n return source;\n }\n\n if (source instanceof File || source instanceof Blob) {\n return createImageBitmapFromBlob(source);\n }\n\n // URL or string\n const url = source instanceof URL ? source.href : source as string;\n const response = await fetch(url);\n const blob = await response.blob();\n return createImageBitmapFromBlob(blob);\n}\n\nasync function createImageBitmapFromBlob(blob: Blob): Promise<ImageBitmap> {\n if (typeof createImageBitmap !== 'undefined') {\n return createImageBitmap(blob);\n }\n // Fallback for environments without createImageBitmap\n const url = URL.createObjectURL(blob);\n try {\n const img = new Image();\n img.src = url;\n await new Promise<void>((resolve, reject) => {\n img.onload = () => resolve();\n img.onerror = () => reject(new Error('Failed to load image'));\n });\n return img as unknown as ImageBitmap;\n } finally {\n URL.revokeObjectURL(url);\n }\n}\n"],"mappings":"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","names":["t","import_reader","enabled"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/debug.ts","../src/camera.ts","../src/frame-extractor.ts","../src/overlay.ts","../src/scan-region.ts","../src/scanner.ts","../src/scan-image.ts","../src/utils.ts","../src/decoder-utils.ts"],"sourcesContent":["import { Scanner, setWorkerUrl } from './scanner.js';\nimport {\n CameraManager,\n CameraNotFoundError,\n CameraPermissionError,\n} from './camera.js';\nimport { setDebug } from './debug.js';\nimport { scanImage } from './scan-image.js';\nimport { setZXingModuleOverrides } from 'zxing-wasm/reader';\nimport type {\n ScanResult,\n ScanRegion,\n ScannerOptions,\n Camera,\n Point,\n FacingMode,\n DeviceId,\n InversionMode,\n} from './types.js';\nimport type { ReaderOptions } from 'zxing-wasm/reader';\n\nexport { CameraNotFoundError, CameraPermissionError };\n\nexport type {\n ScanResult,\n ScanRegion,\n ScannerOptions,\n Camera,\n Point,\n FacingMode,\n DeviceId,\n InversionMode,\n};\n\n/**\n * High-performance QR code scanner for the web, powered by ZXing-C++ WebAssembly.\n */\nclass QrScanner {\n private scanner: Scanner;\n\n constructor(\n videoElement: HTMLVideoElement,\n onDecode: (result: ScanResult) => void,\n options: ScannerOptions = {},\n ) {\n this.scanner = new Scanner(videoElement, onDecode, options);\n }\n\n /** Start camera and begin scanning. Resolves when camera is ready. */\n async start(): Promise<void> {\n return this.scanner.start();\n }\n\n /** Stop scanning and release the camera stream. */\n stop(): void {\n this.scanner.stop();\n }\n\n /** Stop scanning, release camera, terminate worker, clean up DOM. */\n destroy(): void {\n this.scanner.destroy();\n }\n\n /** Pause scanning. If stopStreamImmediately is false, camera stays on. */\n async pause(stopStreamImmediately?: boolean): Promise<boolean> {\n return this.scanner.pause(stopStreamImmediately);\n }\n\n /** Switch to a different camera by facing mode or device ID. */\n async setCamera(facingModeOrDeviceId: FacingMode | DeviceId): Promise<void> {\n return this.scanner.setCamera(facingModeOrDeviceId);\n }\n\n /** Check if the current camera supports flash/torch. */\n async hasFlash(): Promise<boolean> {\n return this.scanner.hasFlash();\n }\n\n /** Whether flash is currently on. */\n isFlashOn(): boolean {\n return this.scanner.isFlashOn();\n }\n\n /** Toggle flash on/off. */\n async toggleFlash(): Promise<void> {\n return this.scanner.toggleFlash();\n }\n\n /** Turn flash on. */\n async turnFlashOn(): Promise<void> {\n return this.scanner.turnFlashOn();\n }\n\n /** Turn flash off. */\n async turnFlashOff(): Promise<void> {\n return this.scanner.turnFlashOff();\n }\n\n /** Set the inversion mode for detecting inverted QR codes. */\n setInversionMode(mode: InversionMode): void {\n this.scanner.setInversionMode(mode);\n }\n\n // --- Static methods ---\n\n /** Check if the device has at least one camera. */\n static hasCamera(): Promise<boolean> {\n return CameraManager.hasCamera();\n }\n\n /** List available cameras. Pass true to request labels (triggers permission prompt). */\n static listCameras(requestLabels?: boolean): Promise<Camera[]> {\n return CameraManager.listCameras(requestLabels);\n }\n\n /**\n * Pre-load the WASM binary so it's ready when the scanner starts.\n * Call this early (e.g., on app init) to avoid delay on first scan.\n */\n static async preload(): Promise<void> {\n // Trigger WASM loading by doing a minimal scan\n const pixel = new Uint8ClampedArray([255, 255, 255, 255]);\n const img = new ImageData(pixel, 1, 1);\n try {\n await scanImage(img);\n } catch {\n // Expected — no QR code in a 1x1 image. The point was to load WASM.\n }\n }\n\n /**\n * Configure WASM loading. Call before creating any scanner instance.\n * @example\n * QrScanner.configureWasm({ locateFile: (filename) => `/wasm/${filename}` });\n */\n static configureWasm(overrides: Partial<EmscriptenModule>): void {\n setZXingModuleOverrides(overrides);\n }\n\n /**\n * Set a custom URL for the worker script. Call before creating any scanner.\n * Needed for CJS consumers or non-standard bundler setups.\n * By default, the worker URL is resolved via `new URL('./worker.js', import.meta.url)`,\n * which works with Vite, webpack 5, Parcel, and other modern bundlers.\n * @example\n * QrScanner.setWorkerUrl('/assets/qr-scanner-worker.js');\n */\n static setWorkerUrl(url: string | URL): void {\n setWorkerUrl(url);\n }\n\n /**\n * Enable or disable debug logging (performance timings, camera selection).\n * Off by default. Useful for diagnosing camera issues in the browser console.\n * @example\n * QrScanner.setDebug(true);\n */\n static setDebug(enabled: boolean): void {\n setDebug(enabled);\n }\n\n /** Scan a single image (not a video stream). */\n static scanImage(\n source:\n | HTMLImageElement\n | HTMLCanvasElement\n | OffscreenCanvas\n | ImageBitmap\n | ImageData\n | Blob\n | ArrayBuffer\n | Uint8Array\n | File\n | URL\n | string,\n options?: {\n scanRegion?: ScanRegion | null;\n canvas?: HTMLCanvasElement | null;\n decoderOptions?: Partial<ReaderOptions>;\n },\n ): Promise<ScanResult> {\n return scanImage(source, options);\n }\n}\n\nexport default QrScanner;\n","let enabled = false;\n\nexport function setDebug(on: boolean): void {\n enabled = on;\n}\n\nexport function debug(...args: unknown[]): void {\n if (enabled) console.debug(...args);\n}\n","import { debug } from './debug.js';\nimport type { FacingMode, DeviceId, Camera } from './types.js';\n\nexport class CameraPermissionError extends Error {\n constructor(\n message = 'Camera access denied. Please grant camera permission and try again.',\n ) {\n super(message);\n this.name = 'CameraPermissionError';\n }\n}\n\nexport class CameraNotFoundError extends Error {\n constructor(\n message = 'No camera found. Please connect a camera and try again.',\n ) {\n super(message);\n this.name = 'CameraNotFoundError';\n }\n}\n\nconst CACHE_KEY_PREFIX = '@agicash/qr-scanner:camera:';\n\nfunction getCachedDeviceId(facingMode: string): string | null {\n try {\n return localStorage.getItem(`${CACHE_KEY_PREFIX}${facingMode}`);\n } catch {\n return null;\n }\n}\n\nfunction setCachedDeviceId(facingMode: string, deviceId: string): void {\n try {\n localStorage.setItem(`${CACHE_KEY_PREFIX}${facingMode}`, deviceId);\n } catch {\n // localStorage unavailable or full — ignore\n }\n}\n\nfunction clearCachedDeviceId(facingMode: string): void {\n try {\n localStorage.removeItem(`${CACHE_KEY_PREFIX}${facingMode}`);\n } catch {\n // localStorage unavailable — ignore\n }\n}\n\nexport interface CameraConfig {\n preferredCamera?: FacingMode | DeviceId;\n cameraResolution?: {\n width?: MediaTrackConstraintSet['width'];\n height?: MediaTrackConstraintSet['height'];\n };\n}\n\nexport class CameraManager {\n private stream: MediaStream | null = null;\n private facingMode: FacingMode | DeviceId;\n private resolution: CameraConfig['cameraResolution'];\n\n constructor(config: CameraConfig = {}) {\n this.facingMode = config.preferredCamera ?? 'environment';\n this.resolution = config.cameraResolution;\n }\n\n async start(video: HTMLVideoElement): Promise<MediaStream> {\n if (this.stream) {\n return this.stream;\n }\n\n const t0 = performance.now();\n\n this.stream = await this.acquireStream();\n const t1 = performance.now();\n debug(`[QrScanner] acquireStream: ${(t1 - t0).toFixed(0)}ms`);\n\n // On some devices (e.g. Samsung S24 + Brave), facingMode: 'environment'\n // picks an ultrawide camera that lacks autofocus. Check and switch to a\n // better camera BEFORE showing on screen to avoid visible flicker.\n await this.ensureBestCamera();\n const t2 = performance.now();\n debug(`[QrScanner] ensureBestCamera: ${(t2 - t1).toFixed(0)}ms`);\n\n video.srcObject = this.stream;\n video.setAttribute('playsinline', 'true');\n await video.play();\n const t3 = performance.now();\n debug(`[QrScanner] video.play: ${(t3 - t2).toFixed(0)}ms`);\n debug(`[QrScanner] camera.start total: ${(t3 - t0).toFixed(0)}ms`);\n\n // Cache the final camera so subsequent starts skip ensureBestCamera\n if (this.facingMode === 'environment' || this.facingMode === 'user') {\n const finalDeviceId = this.getVideoTrack()?.getSettings().deviceId;\n if (finalDeviceId) {\n setCachedDeviceId(this.facingMode, finalDeviceId);\n }\n }\n\n return this.stream;\n }\n\n stop(): void {\n if (this.stream) {\n for (const track of this.stream.getTracks()) {\n track.stop();\n }\n this.stream = null;\n }\n }\n\n async setCamera(\n facingModeOrDeviceId: FacingMode | DeviceId,\n video: HTMLVideoElement,\n ): Promise<void> {\n this.stop();\n this.facingMode = facingModeOrDeviceId;\n await this.start(video);\n }\n\n getStream(): MediaStream | null {\n return this.stream;\n }\n\n async hasFlash(): Promise<boolean> {\n const track = this.getVideoTrack();\n if (!track) return false;\n\n try {\n const capabilities = track.getCapabilities() as MediaTrackCapabilities & {\n torch?: boolean;\n };\n return capabilities.torch === true;\n } catch {\n return false;\n }\n }\n\n isFlashOn(): boolean {\n const track = this.getVideoTrack();\n if (!track) return false;\n\n const settings = track.getSettings() as MediaTrackSettings & {\n torch?: boolean;\n };\n return settings.torch === true;\n }\n\n async toggleFlash(): Promise<void> {\n if (this.isFlashOn()) {\n await this.turnFlashOff();\n } else {\n await this.turnFlashOn();\n }\n }\n\n async turnFlashOn(): Promise<void> {\n await this.setTorch(true);\n }\n\n async turnFlashOff(): Promise<void> {\n await this.setTorch(false);\n }\n\n private async setTorch(on: boolean): Promise<void> {\n const track = this.getVideoTrack();\n if (!track) {\n throw new Error('No active camera stream');\n }\n\n try {\n await track.applyConstraints({\n advanced: [{ torch: on } as MediaTrackConstraintSet],\n });\n } catch {\n throw new Error('Flash/torch is not supported on this device');\n }\n }\n\n /**\n * If the current camera lacks continuous autofocus (e.g. an ultrawide sensor\n * picked by facingMode: 'environment'), find a better camera with the same\n * facing mode and replace this.stream. Called before assigning to the video\n * element so the user never sees the wrong camera.\n */\n private async ensureBestCamera(): Promise<void> {\n if (this.facingMode !== 'environment' && this.facingMode !== 'user') {\n debug('[QrScanner] ensureBestCamera: skipped (specific deviceId)');\n return;\n }\n\n const track = this.getVideoTrack();\n if (!track) return;\n\n try {\n const capabilities = track.getCapabilities() as MediaTrackCapabilities & {\n focusMode?: string[];\n };\n if (\n !capabilities.focusMode ||\n capabilities.focusMode.includes('continuous')\n ) {\n // focusMode not reported (e.g. Safari/iOS) or has autofocus — skip.\n // Only enter the candidate loop when the browser explicitly reports\n // focusMode without 'continuous' (e.g. S24 + Brave ultrawide).\n debug(\n `[QrScanner] ensureBestCamera: skipped (focusMode: ${JSON.stringify(capabilities.focusMode)})`,\n );\n return;\n }\n debug(\n `[QrScanner] ensureBestCamera: current camera lacks autofocus (focusMode: ${JSON.stringify(capabilities.focusMode)})`,\n );\n } catch (err) {\n debug(\n `[QrScanner] ensureBestCamera: skipped (getCapabilities failed: ${err instanceof Error ? err.message : err})`,\n );\n return;\n }\n\n // Current camera lacks continuous autofocus.\n // Enumerate devices while stream is active (ensures deviceIds are available).\n const currentDeviceId = track.getSettings().deviceId;\n\n let devices: MediaDeviceInfo[];\n try {\n devices = await navigator.mediaDevices.enumerateDevices();\n } catch (err) {\n debug(\n `[QrScanner] ensureBestCamera: skipped (enumerateDevices failed: ${err instanceof Error ? err.message : err})`,\n );\n return;\n }\n if (!Array.isArray(devices)) return;\n\n const candidates = devices.filter(\n (d) => d.kind === 'videoinput' && d.deviceId !== currentDeviceId,\n );\n debug(\n `[QrScanner] ensureBestCamera: testing ${candidates.length} candidate camera(s)`,\n );\n if (candidates.length === 0) return;\n\n // Stop current stream — mobile devices only allow one active camera\n this.stop();\n\n for (const candidate of candidates) {\n const t = performance.now();\n let candidateStream: MediaStream;\n try {\n candidateStream = await navigator.mediaDevices.getUserMedia({\n video: {\n deviceId: { exact: candidate.deviceId },\n width: this.resolution?.width ?? { ideal: 1920 },\n height: this.resolution?.height ?? { ideal: 1080 },\n },\n audio: false,\n });\n debug(\n `[QrScanner] ensureBestCamera: candidate ${candidate.label || candidate.deviceId.slice(0, 8)}: getUserMedia ${(performance.now() - t).toFixed(0)}ms`,\n );\n } catch (err) {\n debug(\n `[QrScanner] ensureBestCamera: candidate ${candidate.label || candidate.deviceId.slice(0, 8)}: getUserMedia failed ${(performance.now() - t).toFixed(0)}ms — ${err instanceof Error ? err.name : err}`,\n );\n if (err instanceof DOMException && err.name === 'NotAllowedError') {\n throw new CameraPermissionError();\n }\n continue;\n }\n\n const candidateTrack = candidateStream.getVideoTracks()[0];\n if (!candidateTrack) {\n for (const t of candidateStream.getTracks()) t.stop();\n continue;\n }\n\n // Must match the desired facing mode\n const candidateSettings =\n candidateTrack.getSettings() as MediaTrackSettings & {\n facingMode?: string;\n };\n if (\n candidateSettings.facingMode &&\n candidateSettings.facingMode !== this.facingMode\n ) {\n for (const t of candidateStream.getTracks()) t.stop();\n continue;\n }\n\n // Check if this camera supports continuous autofocus\n try {\n const candidateCaps =\n candidateTrack.getCapabilities() as MediaTrackCapabilities & {\n focusMode?: string[];\n };\n if (candidateCaps.focusMode?.includes('continuous')) {\n this.stream = candidateStream;\n return;\n }\n } catch (err) {\n debug(\n `[QrScanner] ensureBestCamera: candidate getCapabilities failed — ${err instanceof Error ? err.message : err}`,\n );\n }\n\n for (const t of candidateStream.getTracks()) t.stop();\n }\n\n // No better camera found — re-open the original with progressively\n // simpler constraints so we never leave this.stream as null.\n const recoveryAttempts: MediaStreamConstraints[] = [\n // Original deviceId + resolution\n {\n video: {\n deviceId: currentDeviceId ? { exact: currentDeviceId } : undefined,\n width: this.resolution?.width ?? { ideal: 1920 },\n height: this.resolution?.height ?? { ideal: 1080 },\n },\n audio: false,\n },\n // facingMode + resolution\n this.buildConstraints(),\n // facingMode only\n this.buildConstraints(false),\n // Bare minimum — should always succeed if a camera exists\n { video: true, audio: false },\n ];\n\n let lastRecoveryError: unknown;\n for (const constraints of recoveryAttempts) {\n try {\n this.stream = await navigator.mediaDevices.getUserMedia(constraints);\n return;\n } catch (err) {\n if (err instanceof DOMException && err.name === 'NotAllowedError') {\n throw new CameraPermissionError();\n }\n if (err instanceof DOMException && err.name === 'NotFoundError') {\n throw new CameraNotFoundError();\n }\n lastRecoveryError = err;\n debug(\n `[QrScanner] ensureBestCamera recovery failed: ${err instanceof Error ? err.name : err}`,\n );\n continue;\n }\n }\n debug(\n `[QrScanner] ensureBestCamera: all recovery attempts failed, last error: ${lastRecoveryError instanceof Error ? lastRecoveryError.message : lastRecoveryError}`,\n );\n }\n\n private getVideoTrack(): MediaStreamTrack | null {\n if (!this.stream) return null;\n const tracks = this.stream.getVideoTracks();\n return tracks[0] ?? null;\n }\n\n /**\n * Try getUserMedia with progressively simpler constraints.\n *\n * Some browsers (e.g. Brave on Samsung Galaxy S24) throw NotReadableError\n * when facingMode and resolution constraints are combined. Falling back to\n * fewer constraints lets us still open the camera on those browsers.\n */\n private async acquireStream(): Promise<MediaStream> {\n const attempts: {\n label: string;\n constraints: MediaStreamConstraints;\n isCachedDeviceId?: boolean;\n }[] = [];\n\n // If we've previously found a good camera for this facingMode, try it first\n if (this.facingMode === 'environment' || this.facingMode === 'user') {\n const cachedId = getCachedDeviceId(this.facingMode);\n if (cachedId) {\n const video: MediaTrackConstraints = {\n deviceId: { exact: cachedId },\n };\n if (this.resolution?.width) video.width = this.resolution.width;\n else video.width = { ideal: 1920 };\n if (this.resolution?.height) video.height = this.resolution.height;\n else video.height = { ideal: 1080 };\n attempts.push({\n label: 'cached deviceId',\n constraints: { video, audio: false },\n isCachedDeviceId: true,\n });\n }\n }\n\n // Standard fallback chain\n attempts.push(\n { label: 'full constraints', constraints: this.buildConstraints() },\n { label: 'no resolution', constraints: this.buildConstraints(false) },\n { label: 'bare minimum', constraints: { video: true, audio: false } },\n );\n\n let lastError: unknown;\n for (const attempt of attempts) {\n const t = performance.now();\n try {\n const stream = await navigator.mediaDevices.getUserMedia(\n attempt.constraints,\n );\n debug(\n `[QrScanner] getUserMedia(${attempt.label}): ${(performance.now() - t).toFixed(0)}ms ✓`,\n );\n return stream;\n } catch (err) {\n debug(\n `[QrScanner] getUserMedia(${attempt.label}): ${(performance.now() - t).toFixed(0)}ms ✗ ${err instanceof Error ? err.name : err}`,\n );\n // Permission denied and no camera are fatal — no fallback can help.\n if (err instanceof DOMException && err.name === 'NotAllowedError') {\n throw new CameraPermissionError();\n }\n if (err instanceof DOMException && err.name === 'NotFoundError') {\n throw new CameraNotFoundError();\n }\n // DOMException subtypes (NotReadableError, etc.) and\n // OverconstrainedError are retryable with simpler constraints.\n if (\n !(err instanceof DOMException) &&\n !(err instanceof OverconstrainedError)\n ) {\n throw err;\n }\n if (attempt.isCachedDeviceId) {\n // Stale cached deviceId (e.g. iOS regenerated device IDs after an\n // update). Clear it so subsequent attempts don't repeat the failure.\n clearCachedDeviceId(this.facingMode);\n debug(\n `[QrScanner] cleared stale cached deviceId for \"${this.facingMode}\"`,\n );\n }\n lastError = err;\n continue;\n }\n }\n\n throw lastError;\n }\n\n private buildConstraints(includeResolution = true): MediaStreamConstraints {\n const video: MediaTrackConstraints = {};\n\n if (includeResolution) {\n video.width = this.resolution?.width ?? { ideal: 1920 };\n video.height = this.resolution?.height ?? { ideal: 1080 };\n }\n\n if (this.facingMode === 'environment' || this.facingMode === 'user') {\n video.facingMode = this.facingMode;\n } else {\n video.deviceId = { exact: this.facingMode };\n }\n\n return { video, audio: false };\n }\n\n static async hasCamera(): Promise<boolean> {\n try {\n const devices = await navigator.mediaDevices.enumerateDevices();\n return devices.some((d) => d.kind === 'videoinput');\n } catch {\n return false;\n }\n }\n\n static async listCameras(requestLabels = false): Promise<Camera[]> {\n if (requestLabels) {\n // Requesting labels requires a temporary stream to trigger the permission prompt\n try {\n const stream = await navigator.mediaDevices.getUserMedia({\n video: true,\n });\n for (const track of stream.getTracks()) {\n track.stop();\n }\n } catch {\n // Permission denied — fall through with empty labels\n }\n }\n\n const devices = await navigator.mediaDevices.enumerateDevices();\n return devices\n .filter((d) => d.kind === 'videoinput')\n .map((d) => ({\n id: d.deviceId,\n label: d.label || `Camera ${d.deviceId.slice(0, 8)}`,\n }));\n }\n}\n","import type { ScanRegion } from './types.js';\n\nexport interface FrameExtractorConfig {\n maxScansPerSecond: number;\n getScanRegion: () => ScanRegion;\n}\n\nexport class FrameExtractor {\n private video: HTMLVideoElement;\n private canvas: HTMLCanvasElement | OffscreenCanvas;\n private ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;\n private rafId: number | null = null;\n private running = false;\n private workerBusy = false;\n private lastScanTime = -Infinity;\n private minInterval: number;\n private getScanRegion: () => ScanRegion;\n private onFrame: ((imageData: ImageData) => void) | null = null;\n\n constructor(video: HTMLVideoElement, config: FrameExtractorConfig) {\n this.video = video;\n this.minInterval = 1000 / config.maxScansPerSecond;\n this.getScanRegion = config.getScanRegion;\n\n if (typeof OffscreenCanvas !== 'undefined') {\n this.canvas = new OffscreenCanvas(1, 1);\n this.ctx = this.canvas.getContext('2d')! as OffscreenCanvasRenderingContext2D;\n } else {\n this.canvas = document.createElement('canvas');\n this.canvas.style.display = 'none';\n this.ctx = this.canvas.getContext('2d')!;\n }\n }\n\n start(onFrame: (imageData: ImageData) => void): void {\n if (this.running) return;\n this.running = true;\n this.onFrame = onFrame;\n this.rafId = requestAnimationFrame(this.tick);\n }\n\n stop(): void {\n this.running = false;\n this.onFrame = null;\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n }\n\n destroy(): void {\n this.stop();\n if (this.canvas instanceof HTMLCanvasElement && this.canvas.parentNode) {\n this.canvas.parentNode.removeChild(this.canvas);\n }\n }\n\n markWorkerIdle(): void {\n this.workerBusy = false;\n }\n\n private tick = (): void => {\n if (!this.running) return;\n\n this.rafId = requestAnimationFrame(this.tick);\n\n // Skip if worker is still processing previous frame\n if (this.workerBusy) return;\n\n // Rate limiting\n const now = performance.now();\n if (now - this.lastScanTime < this.minInterval) return;\n\n // Skip if video isn't ready\n if (this.video.readyState < 2) return;\n\n this.lastScanTime = now;\n\n const region = this.getScanRegion();\n const sx = region.x ?? 0;\n const sy = region.y ?? 0;\n const sw = region.width ?? this.video.videoWidth;\n const sh = region.height ?? this.video.videoHeight;\n\n if (sw <= 0 || sh <= 0) return;\n\n this.canvas.width = sw;\n this.canvas.height = sh;\n\n this.ctx.drawImage(this.video, sx, sy, sw, sh, 0, 0, sw, sh);\n const imageData = this.ctx.getImageData(0, 0, sw, sh);\n\n this.workerBusy = true;\n this.onFrame?.(imageData);\n };\n}\n","import type { ScanRegion, Point } from './types.js';\n\n/**\n * Compute the actual rendered position and size of the video content\n * within the element, accounting for object-fit: cover.\n *\n * With object-fit: cover the video is scaled up to fill the element and\n * cropped. The rendered content is larger than the element, centered,\n * with negative offsets for the cropped portions.\n *\n * When object-fit is the default (fill), the rendered size equals the\n * element size and offsets are zero — so this is backwards-compatible.\n */\nfunction getRenderedVideoRect(video: HTMLVideoElement): {\n offsetX: number;\n offsetY: number;\n width: number;\n height: number;\n} {\n const elementWidth = video.clientWidth;\n const elementHeight = video.clientHeight;\n const videoWidth = video.videoWidth || 1;\n const videoHeight = video.videoHeight || 1;\n\n const objectFit = getComputedStyle(video).objectFit;\n\n if (objectFit === 'cover' || objectFit === 'contain') {\n const scaleFn = objectFit === 'cover' ? Math.max : Math.min;\n const scale = scaleFn(\n elementWidth / videoWidth,\n elementHeight / videoHeight,\n );\n const renderedWidth = videoWidth * scale;\n const renderedHeight = videoHeight * scale;\n return {\n offsetX: (elementWidth - renderedWidth) / 2,\n offsetY: (elementHeight - renderedHeight) / 2,\n width: renderedWidth,\n height: renderedHeight,\n };\n }\n\n // Default (fill / none / scale-down with no scaling needed): element dimensions\n return { offsetX: 0, offsetY: 0, width: elementWidth, height: elementHeight };\n}\n\nexport interface OverlayConfig {\n highlightScanRegion: boolean;\n highlightCodeOutline: boolean;\n customOverlay?: HTMLDivElement;\n}\n\nexport class ScanOverlay {\n private container: HTMLElement;\n private overlayEl: HTMLDivElement | null = null;\n private codeOutlineEl: SVGElement | null = null;\n private config: OverlayConfig;\n private video: HTMLVideoElement;\n\n constructor(video: HTMLVideoElement, config: OverlayConfig) {\n this.video = video;\n this.config = config;\n\n const parent = video.parentElement;\n if (!parent) {\n throw new Error(\n 'QrScanner: video element must have a parent element. ' +\n 'The parent should have position: relative.',\n );\n }\n this.container = parent;\n }\n\n setup(): void {\n if (this.config.customOverlay) {\n this.overlayEl = this.config.customOverlay;\n this.positionOverlay();\n return;\n }\n\n if (this.config.highlightScanRegion) {\n this.createScanRegionOverlay();\n }\n\n if (this.config.highlightCodeOutline) {\n this.createCodeOutline();\n }\n }\n\n updateScanRegion(region: ScanRegion): void {\n if (!this.overlayEl || this.config.customOverlay) return;\n this.positionOverlayToRegion(region);\n }\n\n updateCodeOutline(\n cornerPoints: Point[] | null,\n scanRegion?: ScanRegion,\n ): void {\n if (!this.codeOutlineEl) return;\n\n if (!cornerPoints || cornerPoints.length < 4) {\n this.codeOutlineEl.style.display = 'none';\n return;\n }\n\n this.codeOutlineEl.style.display = 'block';\n\n const polygon = this.codeOutlineEl.querySelector('polygon');\n if (!polygon) return;\n\n // Corner points are relative to the cropped scan region.\n // Add the scan region offset to get full video coordinates,\n // then scale to display coordinates (accounting for object-fit).\n const regionX = scanRegion?.x ?? 0;\n const regionY = scanRegion?.y ?? 0;\n const rendered = getRenderedVideoRect(this.video);\n const scaleX = rendered.width / this.video.videoWidth;\n const scaleY = rendered.height / this.video.videoHeight;\n\n const points = cornerPoints\n .map(\n (p) =>\n `${(p.x + regionX) * scaleX + rendered.offsetX},${(p.y + regionY) * scaleY + rendered.offsetY}`,\n )\n .join(' ');\n\n polygon.setAttribute('points', points);\n }\n\n destroy(): void {\n if (this.overlayEl && !this.config.customOverlay) {\n this.overlayEl.remove();\n }\n if (this.codeOutlineEl) {\n this.codeOutlineEl.remove();\n }\n this.overlayEl = null;\n this.codeOutlineEl = null;\n }\n\n private createScanRegionOverlay(): void {\n this.overlayEl = document.createElement('div');\n this.overlayEl.className = 'qr-scanner-region';\n\n // Overlay size: 3/4 of the smaller container dimension, centered.\n const cw = this.container.clientWidth;\n const ch = this.container.clientHeight;\n const size = Math.round((Math.min(cw, ch) * 3) / 4);\n\n Object.assign(this.overlayEl.style, {\n position: 'absolute',\n top: '50%',\n left: '50%',\n transform: 'translate(-50%, -50%)',\n width: `${size}px`,\n height: `${size}px`,\n border: '2px solid rgba(255, 255, 255, 0.5)',\n borderRadius: '8px',\n boxShadow: '0 0 0 9999px rgba(0, 0, 0, 0.5)',\n pointerEvents: 'none',\n zIndex: '10',\n });\n\n // Corner markers\n const corners = ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n for (const corner of corners) {\n const marker = document.createElement('div');\n marker.className = `qr-scanner-corner qr-scanner-corner-${corner}`;\n\n const [vertical, horizontal] = corner.split('-');\n\n Object.assign(marker.style, {\n position: 'absolute',\n width: '24px',\n height: '24px',\n [vertical]: '-2px',\n [horizontal]: '-2px',\n [`border-${vertical}`]: '3px solid white',\n [`border-${horizontal}`]: '3px solid white',\n [`border-${vertical}-${horizontal}-radius`]: '8px',\n });\n\n this.overlayEl.appendChild(marker);\n }\n\n this.container.appendChild(this.overlayEl);\n }\n\n private createCodeOutline(): void {\n const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n svg.setAttribute('class', 'qr-scanner-code-outline');\n\n Object.assign(svg.style, {\n position: 'absolute',\n top: '0',\n left: '0',\n width: '100%',\n height: '100%',\n pointerEvents: 'none',\n zIndex: '11',\n display: 'none',\n });\n\n const polygon = document.createElementNS(\n 'http://www.w3.org/2000/svg',\n 'polygon',\n );\n polygon.setAttribute('fill', 'none');\n polygon.setAttribute('stroke', '#00ff00');\n polygon.setAttribute('stroke-width', '3');\n polygon.setAttribute('stroke-linejoin', 'round');\n\n // Animated stroke\n const animate = document.createElementNS(\n 'http://www.w3.org/2000/svg',\n 'animate',\n );\n animate.setAttribute('attributeName', 'stroke-opacity');\n animate.setAttribute('values', '1;0.5;1');\n animate.setAttribute('dur', '1.5s');\n animate.setAttribute('repeatCount', 'indefinite');\n polygon.appendChild(animate);\n\n svg.appendChild(polygon);\n this.container.appendChild(svg);\n this.codeOutlineEl = svg;\n }\n\n private positionOverlay(): void {\n if (!this.overlayEl) return;\n\n Object.assign(this.overlayEl.style, {\n position: 'absolute',\n top: '0',\n left: '0',\n width: '100%',\n height: '100%',\n pointerEvents: 'none',\n zIndex: '10',\n });\n }\n\n private positionOverlayToRegion(_region: ScanRegion): void {\n if (!this.overlayEl) return;\n\n // Keep the overlay at a container-relative centered size rather than\n // mapping scan region coordinates to display coordinates. With\n // object-fit: cover on a portrait phone with a landscape camera, the\n // mapped region extends beyond the container — fine once the camera is\n // visible but causes a jarring jump from the initial placeholder.\n // The overlay is a visual guide; the actual scan area (in the frame\n // extractor) is unaffected and may be larger than what's shown.\n const cw = this.container.clientWidth;\n const ch = this.container.clientHeight;\n const size = Math.round((Math.min(cw, ch) * 3) / 4);\n\n // Only update size — initial CSS centering (top: 50%, left: 50%,\n // transform: translate(-50%, -50%)) persists and handles re-centering.\n Object.assign(this.overlayEl.style, {\n width: `${size}px`,\n height: `${size}px`,\n });\n }\n}\n","import type { ScanRegion } from './types.js';\n\n/**\n * Calculate the default scan region: a centered square covering\n * 2/3 of the smaller video dimension.\n */\nexport function calculateDefaultScanRegion(video: HTMLVideoElement): ScanRegion {\n const videoWidth = video.videoWidth || video.width;\n const videoHeight = video.videoHeight || video.height;\n\n const smallerDimension = Math.min(videoWidth, videoHeight);\n const size = Math.round((smallerDimension * 2) / 3);\n\n return {\n x: Math.round((videoWidth - size) / 2),\n y: Math.round((videoHeight - size) / 2),\n width: size,\n height: size,\n };\n}\n","import { CameraManager } from './camera.js';\nimport { debug } from './debug.js';\nimport { FrameExtractor } from './frame-extractor.js';\nimport { ScanOverlay } from './overlay.js';\nimport { calculateDefaultScanRegion } from './scan-region.js';\nimport type {\n ScannerOptions,\n ScanResult,\n ScanRegion,\n InversionMode,\n WorkerRequest,\n WorkerResponse,\n} from './types.js';\nimport type { ReaderOptions } from 'zxing-wasm/reader';\n\ntype OnDecodeCallback = (result: ScanResult) => void;\n\n/**\n * Custom worker URL override. When set, this URL is used instead of the\n * default bundler-resolved worker. Useful for CJS consumers or non-standard\n * bundler setups.\n */\nlet customWorkerUrl: string | URL | null = null;\n\nexport function setWorkerUrl(url: string | URL): void {\n customWorkerUrl = url;\n}\n\nfunction resolveWorkerUrl(): string | URL {\n if (customWorkerUrl) {\n return customWorkerUrl;\n }\n // Standard pattern: modern bundlers (Vite, webpack 5, Parcel, esbuild)\n // resolve `new URL('./file', import.meta.url)` at build time,\n // copying worker.js to the output directory and returning the correct URL.\n // Falls back gracefully for CJS builds where import.meta is unavailable.\n try {\n return new URL('./worker.js', import.meta.url);\n } catch {\n throw new Error(\n '@agicash/qr-scanner: Could not resolve worker URL. ' +\n 'Call QrScanner.setWorkerUrl() with the path to the worker script before creating a scanner. ' +\n 'Example: QrScanner.setWorkerUrl(\"/path/to/@agicash/qr-scanner/dist/worker.js\")',\n );\n }\n}\n\nexport class Scanner {\n private video: HTMLVideoElement;\n private onDecode: OnDecodeCallback;\n private options: ScannerOptions;\n private camera: CameraManager;\n private frameExtractor: FrameExtractor | null = null;\n private worker: Worker | null = null;\n private overlay: ScanOverlay | null = null;\n private active = false;\n private paused = false;\n private destroyed = false;\n\n constructor(\n video: HTMLVideoElement,\n onDecode: OnDecodeCallback,\n options: ScannerOptions = {},\n ) {\n this.video = video;\n this.onDecode = onDecode;\n this.options = options;\n\n this.camera = new CameraManager({\n preferredCamera: options.preferredCamera,\n cameraResolution: options.cameraResolution,\n });\n }\n\n async start(): Promise<void> {\n if (this.destroyed) {\n throw new Error('Scanner has been destroyed');\n }\n\n if (this.active && !this.paused) {\n return; // Already running\n }\n\n const t0 = performance.now();\n\n // Show overlay immediately (CSS-centered placeholder) so the UI looks\n // ready while the camera is still loading.\n if (\n !this.overlay &&\n (this.options.highlightScanRegion ||\n this.options.highlightCodeOutline ||\n this.options.overlay)\n ) {\n try {\n this.overlay = new ScanOverlay(this.video, {\n highlightScanRegion: this.options.highlightScanRegion ?? false,\n highlightCodeOutline: this.options.highlightCodeOutline ?? false,\n customOverlay: this.options.overlay,\n });\n this.overlay.setup();\n } catch {\n // Overlay setup failed (e.g., no parent element) — continue without overlay\n }\n }\n\n // Start camera\n await this.camera.start(this.video);\n debug(\n `[QrScanner] start: camera ready ${(performance.now() - t0).toFixed(0)}ms`,\n );\n\n // Now that video dimensions are known, position overlay exactly\n if (this.overlay) {\n this.overlay.updateScanRegion(this.getCurrentScanRegion());\n }\n\n // Create worker if needed\n if (!this.worker) {\n const tw = performance.now();\n this.worker = this.createWorker();\n debug(\n `[QrScanner] start: worker created ${(performance.now() - tw).toFixed(0)}ms`,\n );\n }\n\n // Create frame extractor if needed\n if (!this.frameExtractor) {\n this.frameExtractor = new FrameExtractor(this.video, {\n maxScansPerSecond: this.options.maxScansPerSecond ?? 15,\n getScanRegion: () => this.getCurrentScanRegion(),\n });\n }\n\n // Start frame extraction loop\n this.frameExtractor.start((imageData) => {\n this.sendToWorker(imageData);\n });\n\n this.active = true;\n this.paused = false;\n\n debug(`[QrScanner] start: total ${(performance.now() - t0).toFixed(0)}ms`);\n }\n\n stop(): void {\n this.frameExtractor?.stop();\n this.camera.stop();\n this.video.srcObject = null;\n this.active = false;\n this.paused = false;\n }\n\n destroy(): void {\n if (this.destroyed) return;\n this.stop();\n this.frameExtractor?.destroy();\n this.frameExtractor = null;\n this.overlay?.destroy();\n this.overlay = null;\n this.worker?.terminate();\n this.worker = null;\n this.destroyed = true;\n }\n\n async pause(stopStreamImmediately = true): Promise<boolean> {\n if (!this.active) return false;\n\n this.frameExtractor?.stop();\n this.paused = true;\n\n if (stopStreamImmediately) {\n this.camera.stop();\n this.video.srcObject = null;\n }\n\n return true;\n }\n\n async setCamera(facingModeOrDeviceId: string): Promise<void> {\n const wasActive = this.active && !this.paused;\n if (wasActive) {\n this.frameExtractor?.stop();\n }\n\n await this.camera.setCamera(facingModeOrDeviceId, this.video);\n\n if (wasActive) {\n this.frameExtractor?.start((imageData) => {\n this.sendToWorker(imageData);\n });\n }\n }\n\n async hasFlash(): Promise<boolean> {\n return this.camera.hasFlash();\n }\n\n isFlashOn(): boolean {\n return this.camera.isFlashOn();\n }\n\n async toggleFlash(): Promise<void> {\n return this.camera.toggleFlash();\n }\n\n async turnFlashOn(): Promise<void> {\n return this.camera.turnFlashOn();\n }\n\n async turnFlashOff(): Promise<void> {\n return this.camera.turnFlashOff();\n }\n\n setInversionMode(mode: InversionMode): void {\n if (!this.worker) return;\n\n const options: Partial<ReaderOptions> = {\n tryInvert: mode !== 'original',\n };\n\n const msg: WorkerRequest = { type: 'configure', options };\n this.worker.postMessage(msg);\n }\n\n isActive(): boolean {\n return this.active;\n }\n\n isPaused(): boolean {\n return this.paused;\n }\n\n isDestroyed(): boolean {\n return this.destroyed;\n }\n\n private getCurrentScanRegion(): ScanRegion {\n if (this.options.calculateScanRegion) {\n return this.options.calculateScanRegion(this.video);\n }\n return calculateDefaultScanRegion(this.video);\n }\n\n private createWorker(): Worker {\n const workerUrl = resolveWorkerUrl();\n const worker = new Worker(workerUrl, { type: 'module' });\n\n // Configure with custom decoder options\n if (this.options.decoderOptions) {\n const msg: WorkerRequest = {\n type: 'configure',\n options: this.options.decoderOptions,\n };\n worker.postMessage(msg);\n }\n\n worker.onmessage = (e: MessageEvent<WorkerResponse>) => {\n this.handleWorkerMessage(e.data);\n };\n\n worker.onerror = (err) => {\n console.error('QR Scanner worker error:', err);\n this.frameExtractor?.markWorkerIdle();\n };\n\n return worker;\n }\n\n private handleWorkerMessage(response: WorkerResponse): void {\n this.frameExtractor?.markWorkerIdle();\n\n if (response.type === 'ready') {\n return;\n }\n\n if (response.type === 'error') {\n this.options.onDecodeError?.(response.message);\n return;\n }\n\n if (response.type === 'result') {\n if (response.results.length > 0) {\n const result = response.results[0];\n this.onDecode(result);\n\n // Update overlay\n if (this.overlay) {\n const region = this.getCurrentScanRegion();\n this.overlay.updateScanRegion(region);\n this.overlay.updateCodeOutline(result.cornerPoints, region);\n }\n } else {\n this.options.onDecodeError?.('No QR code found');\n\n // Hide code outline when no QR found\n if (this.overlay) {\n this.overlay.updateCodeOutline(null);\n }\n }\n }\n }\n\n private sendToWorker(imageData: ImageData): void {\n if (!this.worker) return;\n\n const msg: WorkerRequest = { type: 'decode', imageData };\n this.worker.postMessage(msg, [imageData.data.buffer]);\n }\n}\n","import { readBarcodes, type ReaderOptions } from 'zxing-wasm/reader';\nimport type { ScanResult, ScanRegion } from './types.js';\nimport { loadImageData } from './utils.js';\nimport { DEFAULT_READER_OPTIONS, mapPosition } from './decoder-utils.js';\n\n/** Input types that zxing-wasm can handle directly (no canvas needed). */\ntype DirectInput = Blob | ArrayBuffer | Uint8Array | ImageData;\n\n/** Input types that need canvas-based pixel extraction. */\ntype CanvasInput = HTMLImageElement | HTMLCanvasElement | OffscreenCanvas | ImageBitmap;\n\nfunction isDirectInput(source: unknown): source is DirectInput {\n return (\n source instanceof Blob ||\n source instanceof ArrayBuffer ||\n source instanceof Uint8Array ||\n (typeof ImageData !== 'undefined' && source instanceof ImageData)\n );\n}\n\n/**\n * Scan a single image for QR codes. Does not require a camera or video stream.\n */\nexport async function scanImage(\n source:\n | CanvasInput\n | DirectInput\n | File\n | URL\n | string,\n options?: {\n scanRegion?: ScanRegion | null;\n canvas?: HTMLCanvasElement | null;\n decoderOptions?: Partial<ReaderOptions>;\n },\n): Promise<ScanResult> {\n const readerOptions: ReaderOptions = {\n ...DEFAULT_READER_OPTIONS,\n ...options?.decoderOptions,\n formats: ['QRCode'],\n };\n\n let input: DirectInput;\n if (isDirectInput(source)) {\n input = source;\n } else if (typeof source === 'string' || source instanceof URL) {\n // URL string - fetch and pass as ArrayBuffer\n const url = source instanceof URL ? source.href : source;\n const response = await fetch(url);\n input = await response.arrayBuffer();\n } else {\n // Canvas-based sources - extract ImageData\n input = await loadImageData(source, options?.scanRegion, options?.canvas);\n }\n\n const results = await readBarcodes(input, readerOptions);\n const valid = results.filter((r) => r.isValid);\n\n if (valid.length === 0) {\n throw new Error('No QR code found in the image');\n }\n\n const first = valid[0];\n return {\n data: first.text,\n cornerPoints: mapPosition(first.position),\n };\n}\n","import type { ScanRegion } from './types.js';\n\n/**\n * Load an image source into an ImageData object, optionally cropping to a scan region.\n */\nexport async function loadImageData(\n source: HTMLImageElement | HTMLCanvasElement | OffscreenCanvas | ImageBitmap | File | Blob | URL | string,\n scanRegion?: ScanRegion | null,\n canvas?: HTMLCanvasElement | null,\n): Promise<ImageData> {\n const img = await resolveImageSource(source);\n\n const sx = scanRegion?.x ?? 0;\n const sy = scanRegion?.y ?? 0;\n const sw = scanRegion?.width ?? (img.width - sx);\n const sh = scanRegion?.height ?? (img.height - sy);\n\n let drawCanvas: HTMLCanvasElement | OffscreenCanvas;\n let ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;\n\n if (canvas) {\n drawCanvas = canvas;\n canvas.width = sw;\n canvas.height = sh;\n ctx = canvas.getContext('2d')!;\n } else if (typeof OffscreenCanvas !== 'undefined') {\n drawCanvas = new OffscreenCanvas(sw, sh);\n ctx = drawCanvas.getContext('2d')! as OffscreenCanvasRenderingContext2D;\n } else {\n drawCanvas = document.createElement('canvas');\n drawCanvas.width = sw;\n drawCanvas.height = sh;\n ctx = drawCanvas.getContext('2d')!;\n }\n\n ctx.drawImage(img as CanvasImageSource, sx, sy, sw, sh, 0, 0, sw, sh);\n return ctx.getImageData(0, 0, sw, sh);\n}\n\nasync function resolveImageSource(\n source: HTMLImageElement | HTMLCanvasElement | OffscreenCanvas | ImageBitmap | File | Blob | URL | string,\n): Promise<HTMLImageElement | HTMLCanvasElement | OffscreenCanvas | ImageBitmap> {\n if (source instanceof HTMLImageElement || source instanceof HTMLCanvasElement || source instanceof ImageBitmap) {\n return source;\n }\n\n if (typeof OffscreenCanvas !== 'undefined' && source instanceof OffscreenCanvas) {\n return source;\n }\n\n if (source instanceof File || source instanceof Blob) {\n return createImageBitmapFromBlob(source);\n }\n\n // URL or string\n const url = source instanceof URL ? source.href : source as string;\n const response = await fetch(url);\n const blob = await response.blob();\n return createImageBitmapFromBlob(blob);\n}\n\nasync function createImageBitmapFromBlob(blob: Blob): Promise<ImageBitmap> {\n if (typeof createImageBitmap !== 'undefined') {\n return createImageBitmap(blob);\n }\n // Fallback for environments without createImageBitmap\n const url = URL.createObjectURL(blob);\n try {\n const img = new Image();\n img.src = url;\n await new Promise<void>((resolve, reject) => {\n img.onload = () => resolve();\n img.onerror = () => reject(new Error('Failed to load image'));\n });\n return img as unknown as ImageBitmap;\n } finally {\n URL.revokeObjectURL(url);\n }\n}\n","import type { ReaderOptions } from 'zxing-wasm/reader';\nimport type { Point } from './types.js';\n\nexport const DEFAULT_READER_OPTIONS: ReaderOptions = {\n formats: ['QRCode'],\n tryHarder: true,\n tryInvert: true,\n tryRotate: true,\n tryDenoise: false,\n tryDownscale: true,\n maxNumberOfSymbols: 1,\n};\n\nexport function mapPosition(position: {\n topLeft: Point;\n topRight: Point;\n bottomLeft: Point;\n bottomRight: Point;\n}): Point[] {\n return [\n position.topLeft,\n position.topRight,\n position.bottomRight,\n position.bottomLeft,\n ];\n}\n"],"mappings":"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","names":["t","import_reader","enabled"]}
|