interface CameraCapabilities { torch: boolean; zoom: { min: number; max: number; step: number; } | null; } type CameraErrorCode = 'insecure-context' | 'unsupported' | 'permission-denied' | 'camera-not-found' | 'camera-in-use' | 'stream-failed'; /** Typed camera/stream failure — match on `code`, not message strings. */ declare class CameraError extends Error { readonly code: CameraErrorCode; readonly cause?: unknown | undefined; readonly name = "CameraError"; constructor(code: CameraErrorCode, message: string, cause?: unknown | undefined); /** Maps a getUserMedia rejection to a typed CameraError. */ static from(error: unknown): CameraError; } /** * Best-effort classification of common QR payload conventions. Pure string * processing — never throws; unrecognized payloads come back as * `{ type: 'text' }`. */ type ParsedContent = { type: 'url'; url: string; } | { type: 'wifi'; ssid: string; password?: string; security?: 'WEP' | 'WPA' | 'WPA2-EAP' | 'nopass'; hidden?: boolean; } | { type: 'geo'; latitude: number; longitude: number; altitude?: number; } | { type: 'tel'; number: string; } | { type: 'sms'; number: string; message?: string; } | { type: 'email'; to: string; subject?: string; body?: string; } | { type: 'vcard'; raw: string; name?: string; org?: string; tel?: string; email?: string; } | { type: 'gs1'; raw: string; elements: Array<{ ai: string; value: string; }>; } | { type: 'text'; text: string; }; interface ParseContentHints { /** The symbol carried FNC1 in first position (GS1-formatted data). */ gs1?: boolean; } declare function parseContent(text: string, hints?: ParseContentHints): ParsedContent; type ErrorCorrectionLevel = 'L' | 'M' | 'Q' | 'H'; interface Point { x: number; y: number; } /** Minimal structural form of a canvas `ImageData`: RGBA bytes, row-major. */ interface ImageDataLike { data: Uint8ClampedArray | Uint8Array; width: number; height: number; } interface DecodeImageOptions { /** * Also try the inverted image (light modules on a dark background) when * the first pass finds nothing. Cheap; defaults to true. */ tryInverted?: boolean; /** * Spend more time per frame: try several finder-pattern triples (defeats * decoy patterns) and an extra 2× downscale pass (recovers blurry or * oversampled codes). Default false. */ tryHarder?: boolean; /** * Allow automatic downsampling of frames larger than ~1000px by up to * this integer factor before decoding — much faster on 4K frames, and * full resolution is still tried when the fast pass fails. Default 1 * (never downscale). */ maxDownscale?: number; /** * Classify the payload (URL, WiFi credentials, vCard, geo, tel, sms, * email) into `result.content`. Default true. */ parseContent?: boolean; } /** A located — not necessarily decoded — QR symbol candidate. */ interface Detection { /** * Symbol outline in image pixel coordinates: * top-left, top-right, bottom-right, bottom-left. */ cornerPoints: [Point, Point, Point, Point]; /** Estimated module size in pixels. */ moduleSize: number; } interface DetectImageOptions { /** Also look for inverted (light-on-dark) symbols. Default true. */ tryInverted?: boolean; /** Downscale huge frames by up to this factor first (see decode). Default 1. */ maxDownscale?: number; /** Maximum number of distinct candidates to return. Default 4. */ maxCandidates?: number; } type Segment = { mode: 'numeric'; text: string; } | { mode: 'alphanumeric'; text: string; } | { mode: 'byte'; bytes: Uint8Array; text: string; } | { mode: 'kanji'; bytes: Uint8Array; text: string; } | { mode: 'eci'; assignment: number; }; /** * FNC1 marker: first position marks GS1-formatted data (element strings * separated by GS, parsed into `content` when recognizable); second position * marks AIM data with an application indicator. */ type Fnc1 = { position: 'first'; } | { position: 'second'; applicationIndicator: string; }; interface StructuredAppend { /** 0-based position of this symbol in the sequence. */ index: number; /** Total symbols in the sequence. */ total: number; /** Parity byte shared by all symbols of the sequence. */ parity: number; } /** Result of decoding a sampled bit matrix (no image geometry attached). */ interface DecodedMatrix { text: string; bytes: Uint8Array; version: number; errorCorrectionLevel: ErrorCorrectionLevel; mask: number; segments: Segment[]; ecc: { /** Number of Reed–Solomon blocks in the symbol. */ blocks: number; /** Codewords that had to be corrected — a quality/damage signal. */ codewordsCorrected: number; }; structuredAppend?: StructuredAppend; /** Present when the symbol carries FNC1/GS1 markers. */ fnc1?: Fnc1; } /** Result of decoding a QR code found in an image. */ interface QrResult extends DecodedMatrix { /** * Symbol outline in image pixel coordinates: * top-left, top-right, bottom-right, bottom-left. */ cornerPoints: [Point, Point, Point, Point]; /** Measured module size in pixels — a proxy for scan distance/quality. */ moduleSize: number; /** Payload classification (unless parseContent: false). */ content?: ParsedContent; } interface Region { x: number; y: number; width: number; height: number; } /** Produces RGBA frames from a live source; a scanner-internal seam. */ interface FrameSource { /** * Returns the current frame (cropped to region), or null if not ready. The * region is clamped to the frame bounds via `clampRegion` — the scanner maps * decode coordinates back by that same clamp, so an implementation must not * crop to some other rectangle. */ grab(region?: Region): ImageDataLike | null; destroy(): void; } interface CameraOptions { /** Which way the camera faces; ignored when deviceId is set. */ facing?: 'environment' | 'user'; /** Exact camera device (from listCameras()). */ deviceId?: string; /** Ideal capture resolution; the browser picks the closest mode. */ resolution?: { width?: number; height?: number; }; } interface CameraDevice { id: string; label: string; } /** * Lists available cameras. Labels are only populated once the user has * granted camera permission (a platform privacy rule, not ours). */ declare function listCameras(mediaDevicesOverride?: MediaDevices): Promise; /** Options for scanning one frame; the superset the scanner loop uses. */ interface ScanFrameOptions extends DecodeImageOptions { /** Decode every symbol in the frame instead of stopping at the first. */ multiple?: boolean; } /** Everything one pipeline run learned about a frame. */ interface FrameScan { results: QrResult[]; detections: Detection[]; } /** * Finds and decodes a QR code in an RGBA image (e.g. a canvas ImageData). * Returns null when no decodable QR code is present; throws only on invalid * input (malformed dimensions). */ declare function decode(image: ImageDataLike, options?: DecodeImageOptions): QrResult | null; /** * Finds and decodes every QR code in the frame. Unlike decode(), this always * runs the full pass plan (all scales, plus inverted when enabled) and merges * the passes, so it costs roughly what a failing decode() costs even when the * frame contains a code. */ declare function decodeAll(image: ImageDataLike, options?: DecodeImageOptions): QrResult[]; /** * Locates QR symbol candidates without decoding them — corner points and * module size only. Cheaper than decode() and useful for live outline * overlays and framing feedback; candidates are plausibility-ranked and may * include finder-like decoys that would not survive a decode. */ declare function detect(image: ImageDataLike, options?: DetectImageOptions): Detection[]; type DecodeErrorCode = 'detect' | 'invalid-dimension' | 'format-info' | 'version-info' | 'codewords' | 'reed-solomon' | 'bitstream' | 'unsupported-mode' | 'runner-failed'; /** Thrown when a bit matrix cannot be decoded as a valid QR symbol. */ declare class DecodeError extends Error { readonly code: DecodeErrorCode; readonly cause?: unknown | undefined; readonly name = "DecodeError"; constructor(code: DecodeErrorCode, message: string, cause?: unknown | undefined); /** * Wraps a decode/worker fault as a typed DecodeError, passing an existing * DecodeError through unchanged. Used to keep runner failures out of the * camera-stream error channel (see QrScanner's error event). */ static from(error: unknown): DecodeError; } /** Executes frame scans, on a worker or inline; a scanner-internal seam. */ interface DecodeRunner { scan(image: ImageDataLike, options: ScanFrameOptions): Promise; destroy(): void; } interface QrScannerOptions { camera?: CameraOptions; /** Decode attempts per second; frames in between are skipped. Default 15. */ maxScansPerSecond?: number; /** Restrict decoding to a sub-rectangle of the video (a big CPU saver). */ scanRegion?: Region | ((video: HTMLVideoElement) => Region); /** Decode in a Web Worker when available. Default true. */ useWorker?: boolean; /** * Use the browser's BarcodeDetector when available, falling back to our * engine when it is missing or fails at runtime. Faster on supporting * Chromium, but native results carry placeholder codec metadata (version, * mask, ecc, EC level) — see the README. Off by default; fixed at start(). */ useNativeDetector?: boolean; /** Pause scanning while the page is hidden. Default true. */ pauseOnHidden?: boolean; /** Also try inverted (light-on-dark) symbols. Default true. */ tryInverted?: boolean; /** Extra decode passes per frame (see DecodeImageOptions). Default false. */ tryHarder?: boolean; /** Downscale huge frames by up to this factor before decoding. Default 2. */ maxDownscale?: number; /** * Quiet period before the same payload fires again, timed from when the code * was last *seen* — not from when it last fired. Every frame the symbol stays * in view refreshes its timer, so a code held steadily in front of the camera * fires **once**: it has to leave the frame for longer than this window before * it can fire again. `0` disables deduping entirely (every decoded frame * fires). Default 1500 ms. */ dedupeWindowMs?: number; /** Stop the camera after the first successful decode. Default false. */ stopOnDecode?: boolean; /** * Decode every code in the frame instead of stopping at the first; each * decoded symbol fires its own decode event (deduped per payload). * Default false. */ multiple?: boolean; /** * How to handle structured-append sequences (one payload split across * several symbols): 'reassemble' (default) withholds the parts and fires a * single decode event with the joined payload once every symbol has been * seen (combine with `multiple` when all symbols share the frame); * 'individual' fires each symbol as its own result. */ structuredAppend?: 'reassemble' | 'individual'; onDecode?: (result: QrResult) => void; /** * Fires on every scanned frame with the located symbol candidates (video * pixel coordinates, before/regardless of a successful decode), or null * when the frame contains none — drive live outline overlays with this. */ onDetect?: (detections: Detection[] | null) => void; onError?: (error: ScannerError) => void; } /** * The `error` event carries either flavor of the typed contract: `CameraError` * for camera/stream faults (permission, device, stream setup) and `DecodeError` * for decode-runner faults (a frame failing to decode, the Web Worker * crashing). Match on `error.name` (or `instanceof`) to tell them apart. */ type ScannerError = CameraError | DecodeError; /** Options changeable while the scanner runs (via update()). */ type QrScannerUpdate = Pick; interface ScannerEventMap { decode: QrResult; detect: Detection[] | null; error: ScannerError; start: undefined; stop: undefined; } /** * Internal dependency seams, injectable for tests (fake camera, manual * scheduling, controlled clock). Not part of the supported public API. */ interface ScannerInternals { mediaDevices?: MediaDevices; createFrameSource?: (video: HTMLVideoElement) => FrameSource; createRunner?: (useWorker: boolean, useNativeDetector: boolean) => DecodeRunner; now?: () => number; /** Schedules the next tick; returns a cancel function. */ schedule?: (video: HTMLVideoElement, callback: () => void) => () => void; } declare class QrScanner { private readonly video; private readonly options; private readonly internals; private readonly deduper; private readonly assembler; private readonly listeners; private state; private stream; /** * Generation token invalidating in-flight stream acquisitions. Every * acquisition takes a new generation, and stop()/destroy() bump it; an * awaited getUserMedia that resolves under a stale generation no longer * owns the scanner — it must stop the stream it acquired and bail instead * of resurrecting state that was torn down behind its back. */ private startGen; private frameSource; private runner; private cancelTick; private lastDecodeAt; private decoding; /** * Set only while the *automatic* hidden-page pause owns the suspension, so * returning to the page resumes it. Any explicit pause()/resume()/stop() * clears it — a caller who paused on purpose while the page was in the * background must not be resumed behind their back when it comes forward. */ private hiddenPause; /** A pause requested during the async 'starting' window, honored at start. */ private pendingPause; private readonly onVisibilityChange; constructor(video: HTMLVideoElement, options?: QrScannerOptions, internals?: ScannerInternals); /** Requests the camera, attaches it to the video, and starts scanning. */ start(): Promise; /** Stops scanning and releases the camera. */ stop(): void; /** Suspends decoding but keeps the camera stream alive. */ pause(): void; /** Resumes decoding after pause(). */ resume(): void; /** Full teardown; the instance cannot be reused afterwards. */ destroy(): void; /** Switches cameras (facing mode or explicit device) without stopping. */ setCamera(camera: CameraOptions): Promise; /** True if the torch was toggled; false when unsupported. */ setTorch(on: boolean): Promise; /** True if zoom was applied (clamped to range); false when unsupported. */ setZoom(zoom: number): Promise; getCapabilities(): CameraCapabilities; /** Adjusts runtime options without restarting the camera. */ update(options: QrScannerUpdate): void; on(event: E, listener: (payload: ScannerEventMap[E]) => void): void; off(event: E, listener: (payload: ScannerEventMap[E]) => void): void; private emit; private attachStream; private releaseStream; private videoTrack; private cancelLoop; private scheduleNext; private tick; private resolveRegion; } export { type CameraCapabilities as C, type DecodedMatrix as D, type ErrorCorrectionLevel as E, type Fnc1 as F, type ImageDataLike as I, type Point as P, type QrScannerOptions as Q, type Region as R, type ScannerError as S, type QrResult as a, QrScanner as b, type ScannerInternals as c, type CameraDevice as d, CameraError as e, type CameraErrorCode as f, type CameraOptions as g, DecodeError as h, type DecodeErrorCode as i, type DecodeImageOptions as j, type DetectImageOptions as k, type Detection as l, type FrameSource as m, type ParseContentHints as n, type ParsedContent as o, type QrScannerUpdate as p, type ScannerEventMap as q, type Segment as r, type StructuredAppend as s, decode as t, decodeAll as u, detect as v, listCameras as w, parseContent as x };