diff --git a/public/js/qr.js b/public/js/qr.js new file mode 100644 index 0000000..265f7e6 --- /dev/null +++ b/public/js/qr.js @@ -0,0 +1,77 @@ +/** + * Lightweight QR scanning. Uses the native BarcodeDetector API when present + * (Android Chrome). Falls back to manual code entry where it isn't (iOS Safari + * has no BarcodeDetector). Returns the decoded payload string, or null if the + * user cancels. + */ +export async function scanQR() { + if ("BarcodeDetector" in window) { + try { + const formats = await window.BarcodeDetector.getSupportedFormats(); + if (formats.includes("qr_code")) return await scanWithCamera(); + } catch (_) { /* fall through to manual */ } + } + return manualEntry(); +} + +async function scanWithCamera() { + const detector = new window.BarcodeDetector({ formats: ["qr_code"] }); + const overlay = buildOverlay(); + const video = overlay.querySelector("video"); + document.body.appendChild(overlay); + + let stream; + try { + stream = await navigator.mediaDevices.getUserMedia({ + video: { facingMode: { ideal: "environment" } }, audio: false, + }); + } catch { + overlay.remove(); + return manualEntry(); + } + video.srcObject = stream; + await video.play().catch(() => {}); + + return new Promise((resolve) => { + let done = false; + const stop = (val) => { + if (done) return; done = true; + stream.getTracks().forEach((t) => t.stop()); + overlay.remove(); + resolve(val); + }; + overlay.querySelector(".qr-cancel").onclick = () => stop(null); + overlay.querySelector(".qr-manual").onclick = async () => { stop(null); resolve(await manualEntry()); }; + + const tick = async () => { + if (done) return; + try { + const codes = await detector.detect(video); + if (codes.length) return stop(codes[0].rawValue); + } catch (_) { /* keep trying */ } + requestAnimationFrame(tick); + }; + requestAnimationFrame(tick); + }); +} + +function manualEntry() { + const code = window.prompt( + "Enter the set code (printed beneath its QR), e.g. SET-GRAVEYARD:" + ); + return code ? code.trim() : null; +} + +function buildOverlay() { + const el = document.createElement("div"); + el.className = "qr-overlay"; + el.innerHTML = ` + +
+Point at a set's QR code
+