Files
Andriy Oblivantsev e1107256e8
CI / test (push) Successful in 3s
Vendor frontend CDN dependencies and serve them locally.
This switches demo pages and modules to local web/vendor assets, fixes Three GLTFLoader local import resolution, and documents the runtime-data/agent commit workflow updates.

Made-with: Cursor
2026-03-02 22:43:27 +00:00

60 lines
1.8 KiB
JavaScript

/**
* QR code scanner from camera. Decodes private key (pk) from QR.
*/
import jsQR from "./vendor/qr/jsqr.bundle.mjs";
/**
* 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<string>} 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();
});
}