// Camera-based QR scanning (mobile/claude.md web stub: "Scans QR codes via // the browser MediaDevices camera API"). Decode library: jsQR — pure JS, // no WASM, the standard lightweight choice for in-browser barcode reading; // not a crypto primitive, so it isn't subject to crypto.md's pinned-library // list. import jsQR from 'jsqr'; export interface ScannerHandle { stop: () => void; } /** * Opens the camera and polls frames via requestAnimationFrame until a QR * code decodes, then calls `onDecode` once and stops polling on its own * (the caller is still responsible for calling `stop()` to release the * camera stream once it's done with the decoded content). */ export async function startScanner( video: HTMLVideoElement, canvas: HTMLCanvasElement, onDecode: (content: string) => void, ): Promise { const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } }); video.srcObject = stream; await video.play(); const ctx = canvas.getContext('2d', { willReadFrequently: true }); if (!ctx) throw new Error('2D canvas context unavailable'); let stopped = false; let decoded = false; function tick(): void { if (stopped || decoded) return; if (video.readyState === video.HAVE_ENOUGH_DATA) { canvas.width = video.videoWidth; canvas.height = video.videoHeight; ctx!.drawImage(video, 0, 0, canvas.width, canvas.height); const frame = ctx!.getImageData(0, 0, canvas.width, canvas.height); const code = jsQR(frame.data, frame.width, frame.height); if (code) { decoded = true; onDecode(code.data); return; } } requestAnimationFrame(tick); } requestAnimationFrame(tick); return { stop: () => { stopped = true; stream.getTracks().forEach((track) => track.stop()); }, }; }