/** * QR code scanner from camera. Decodes private key (pk) from QR. */ import jsQR from "https://esm.sh/jsqr@1.4.0"; /** * Scan QR code from camera video stream. * @param {HTMLVideoElement} videoEl - Video element to render the stream. * @param {AbortSignal} [signal] - AbortSignal to stop scanning (e.g. when dialog closes). * @returns {Promise} Decoded QR text. */ export async function scanQRFromCamera(videoEl, signal) { const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" }, }); videoEl.srcObject = stream; await videoEl.play(); const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); return new Promise((resolve, reject) => { if (signal?.aborted) { stream.getTracks().forEach((t) => t.stop()); reject(new DOMException("Aborted", "AbortError")); return; } signal?.addEventListener("abort", () => { stream.getTracks().forEach((t) => t.stop()); videoEl.srcObject = null; reject(new DOMException("Aborted", "AbortError")); }); function tick() { if (signal?.aborted) return; if (videoEl.readyState !== videoEl.HAVE_ENOUGH_DATA) { requestAnimationFrame(tick); return; } canvas.width = videoEl.videoWidth; canvas.height = videoEl.videoHeight; if (canvas.width === 0 || canvas.height === 0) { requestAnimationFrame(tick); return; } ctx.drawImage(videoEl, 0, 0); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const result = jsQR(imageData.data, imageData.width, imageData.height); if (result) { stream.getTracks().forEach((t) => t.stop()); videoEl.srcObject = null; resolve(result.data); return; } requestAnimationFrame(tick); } tick(); }); }