{"_id":"@elchika-inc/moonqr-scanner","_rev":"3-5125ab755ae28098646630670316e25d","name":"@elchika-inc/moonqr-scanner","dist-tags":{"latest":"0.2.1"},"versions":{"0.1.0":{"name":"@elchika-inc/moonqr-scanner","version":"0.1.0","keywords":["qrcode","scanner","camera","moonbit"],"license":"Apache-2.0","_id":"@elchika-inc/moonqr-scanner@0.1.0","maintainers":[{"name":"naoto24kawa","email":"naoto24kawa@gmail.com"}],"homepage":"https://github.com/elchika-inc/moonqr/tree/main/packages/scanner#readme","bugs":{"url":"https://github.com/elchika-inc/moonqr/issues"},"dist":{"shasum":"b07bb236b846af687d5b480db23d24cec4386c4a","tarball":"https://registry.npmjs.org/@elchika-inc/moonqr-scanner/-/moonqr-scanner-0.1.0.tgz","fileCount":7,"integrity":"sha512-RLQlJKg5EXk7T/uAg/bC/QYxy34u5kXvgIyGWtQQpE3pvl0x4DLeHMnenR8aRGVR+MjP0ORcKUp+cSVztgeqvw==","signatures":[{"sig":"MEUCIQDF2VUxINgWeZDXu5jQbqS74PPeVeYYZhX+LIjYwukAvAIgQJxUJUw7BMsjjXj6TalGS+y6LHN4htaiBvwsn29Ht/Q=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":180712},"main":"./dist/index.js","type":"module","_from":"file:elchika-inc-moonqr-scanner-0.1.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18.18.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"},"./package.json":"./package.json"},"scripts":{"build":"node scripts/build-worker.mjs && tsup","test:unit":"node scripts/build-worker.mjs && vitest run","typecheck":"node scripts/build-worker.mjs && tsc --noEmit -p tsconfig.json","build:worker":"node scripts/build-worker.mjs"},"_npmUser":{"name":"naoto24kawa","email":"naoto24kawa@gmail.com"},"_resolved":"/private/var/folders/hy/dnckvhxs2rjczcpybl990ldw0000gn/T/2cbc55f245431f8841373545cc5740a5/elchika-inc-moonqr-scanner-0.1.0.tgz","_integrity":"sha512-RLQlJKg5EXk7T/uAg/bC/QYxy34u5kXvgIyGWtQQpE3pvl0x4DLeHMnenR8aRGVR+MjP0ORcKUp+cSVztgeqvw==","repository":{"url":"git+https://github.com/elchika-inc/moonqr.git","type":"git","directory":"packages/scanner"},"_npmVersion":"11.16.0","description":"QR code scanner with camera support for moonqr","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"@elchika-inc/moonqr":"^0.1.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","jsdom":"^25.0.1","vitest":"^4.1.10","esbuild":"^0.27.7","typescript":"^6.0.3"},"_npmOperationalInternal":{"tmp":"tmp/moonqr-scanner_0.1.0_1785227361889_0.8750726357144356","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@elchika-inc/moonqr-scanner","version":"0.2.0","keywords":["qrcode","scanner","camera","moonbit"],"license":"Apache-2.0","_id":"@elchika-inc/moonqr-scanner@0.2.0","maintainers":[{"name":"naoto24kawa","email":"naoto24kawa@gmail.com"}],"homepage":"https://github.com/elchika-inc/moonqr/tree/main/packages/scanner#readme","bugs":{"url":"https://github.com/elchika-inc/moonqr/issues"},"dist":{"shasum":"3ba71f95dfe1307f7ec1bee2b2b0891f752a0dc1","tarball":"https://registry.npmjs.org/@elchika-inc/moonqr-scanner/-/moonqr-scanner-0.2.0.tgz","fileCount":7,"integrity":"sha512-J/AqXCOdVQvlbn8LntfRtrNKhSzkKmVfC6Q0VLWcKvt7EEQ8rXqwZXeOz9uybYfF1FsuRqTM4LIKQgduTg/xJA==","signatures":[{"sig":"MEYCIQCJ4SuRFBrotq132CkdzQExOn0yaqA8FhIzTYdXeH73jgIhAPpNDKqaE65NLwxadgER9xsvk9mQTBjSmTFwAageIr70","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":180712},"main":"./dist/index.js","type":"module","_from":"file:elchika-inc-moonqr-scanner-0.2.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18.18.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"},"./package.json":"./package.json"},"scripts":{"build":"node scripts/build-worker.mjs && tsup","test:unit":"node scripts/build-worker.mjs && vitest run","typecheck":"node scripts/build-worker.mjs && tsc --noEmit -p tsconfig.json","build:worker":"node scripts/build-worker.mjs"},"_npmUser":{"name":"naoto24kawa","email":"naoto24kawa@gmail.com"},"_resolved":"/private/var/folders/hy/dnckvhxs2rjczcpybl990ldw0000gn/T/333a44537678fe5a1b76c5eef8a0ffb3/elchika-inc-moonqr-scanner-0.2.0.tgz","_integrity":"sha512-J/AqXCOdVQvlbn8LntfRtrNKhSzkKmVfC6Q0VLWcKvt7EEQ8rXqwZXeOz9uybYfF1FsuRqTM4LIKQgduTg/xJA==","repository":{"url":"git+https://github.com/elchika-inc/moonqr.git","type":"git","directory":"packages/scanner"},"_npmVersion":"11.16.0","description":"QR code scanner with camera support for moonqr","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"@elchika-inc/moonqr":"^0.2.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","jsdom":"^25.0.1","vitest":"^4.1.10","esbuild":"^0.27.7","typescript":"^6.0.3"},"_npmOperationalInternal":{"tmp":"tmp/moonqr-scanner_0.2.0_1785310226259_0.38906770410699787","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"_id":"@elchika-inc/moonqr-scanner@0.2.1","bugs":{"url":"https://github.com/elchika-inc/moonqr/issues"},"dist":{"shasum":"4b3ed217540aad70f12539a897909c73f01c1795","tarball":"https://registry.npmjs.org/@elchika-inc/moonqr-scanner/-/moonqr-scanner-0.2.1.tgz","fileCount":7,"integrity":"sha512-tNxrVZqqARmQgSfPfCltxnKVAapIZlk4PexSKb2XGLK0xxBUMSw4M2PVKbacJR+Z2+iknsmnMViskU2BDTwWsw==","signatures":[{"sig":"MEUCIC3RklVOBsGd8SlDR2te8rCkc/ETbaL0XZuUe0Meev3FAiEAhvYgtj0/zWYK/BoMNU8f9IGtqOFAS1EDgJtv5OUh99M=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCDK9mFAgfWTSUFzxBpKqu0gawds7G9rPnWJfmHTtNsqQIhAKFxP4Ux3irvu7GhaOJjuVrHzZx3P1aN9mDPl7mypJQ4"}],"unpackedSize":181183},"main":"./dist/index.js","name":"@elchika-inc/moonqr-scanner","type":"module","_from":"file:elchika-inc-moonqr-scanner-0.2.1.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18.18.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"},"./package.json":"./package.json"},"license":"Apache-2.0","scripts":{"build":"node scripts/build-worker.mjs && tsup","test:unit":"node scripts/build-worker.mjs && vitest run","typecheck":"node scripts/build-worker.mjs && tsc --noEmit -p tsconfig.json","build:worker":"node scripts/build-worker.mjs"},"version":"0.2.1","_npmUser":{"name":"naoto24kawa","email":"naoto24kawa@gmail.com"},"homepage":"https://github.com/elchika-inc/moonqr/tree/main/packages/scanner#readme","keywords":["qrcode","scanner","camera","moonbit"],"_resolved":"/private/var/folders/hy/dnckvhxs2rjczcpybl990ldw0000gn/T/5a544f6793c8e273ac45c209946ad5d5/elchika-inc-moonqr-scanner-0.2.1.tgz","_integrity":"sha512-tNxrVZqqARmQgSfPfCltxnKVAapIZlk4PexSKb2XGLK0xxBUMSw4M2PVKbacJR+Z2+iknsmnMViskU2BDTwWsw==","repository":{"url":"git+https://github.com/elchika-inc/moonqr.git","type":"git","directory":"packages/scanner"},"_npmVersion":"11.19.0","description":"QR code scanner with camera support for moonqr","directories":{},"maintainers":[{"name":"naoto24kawa","email":"naoto24kawa@gmail.com"}],"sideEffects":false,"_nodeVersion":"24.21.0","dependencies":{"@elchika-inc/moonqr":"^0.2.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","jsdom":"^25.0.1","vitest":"^4.1.10","esbuild":"^0.27.7","typescript":"^6.0.3"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/moonqr-scanner_0.2.1_1789877375044_0.7291215276050709"}}},"time":{"created":"2026-07-28T08:29:21.767Z","modified":"2026-09-20T04:09:35.302Z","0.1.0":"2026-07-28T08:29:22.043Z","0.2.0":"2026-07-29T07:30:26.399Z","0.2.1":"2026-09-20T04:09:35.126Z"},"bugs":{"url":"https://github.com/elchika-inc/moonqr/issues"},"license":"Apache-2.0","homepage":"https://github.com/elchika-inc/moonqr/tree/main/packages/scanner#readme","keywords":["qrcode","scanner","camera","moonbit"],"repository":{"url":"git+https://github.com/elchika-inc/moonqr.git","type":"git","directory":"packages/scanner"},"description":"QR code scanner with camera support for moonqr","maintainers":[{"name":"naoto24kawa","email":"naoto24kawa@gmail.com"}],"readme":"# @elchika-inc/moonqr-scanner\n\nLive camera QR scanner built on [`@elchika-inc/moonqr`](../moonqr). Decoding runs in a Web\nWorker off the main thread (frame buffers are transferred, not copied), with a transparent\nsame-thread fallback for environments without Worker/OffscreenCanvas support.\n\n## Install\n\n```sh\nnpm install @elchika-inc/moonqr-scanner\n```\n\n`@elchika-inc/moonqr` is a regular dependency (installed automatically) — you don't need to add\nit yourself unless you also want its `encode`/`decode`/`dom` subpaths directly.\n\n## HTTPS requirement\n\n`QrScanner` uses `navigator.mediaDevices.getUserMedia`, which browsers only expose in a\n[secure context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts) — i.e.\nHTTPS, or `http://localhost` during development. `QrScanner.hasCamera()` (below) checks this for\nyou and returns `false` on an insecure origin instead of letting `getUserMedia` throw.\n\n## Usage\n\n### Live camera scanning\n\n```ts\nimport { QrScanner } from \"@elchika-inc/moonqr-scanner\";\n\nconst video = document.querySelector(\"video\")!;\n\nconst scanner = new QrScanner(\n  video,\n  (result) => {\n    console.log(\"scanned:\", result.text, result.version, result.ecLevel);\n  },\n  {\n    preferredCamera: \"environment\", // \"environment\" (back camera, default) | \"user\"\n    onError: (error) => console.error(error),\n  },\n);\n\nawait scanner.start(); // requests camera permission, starts the video + decode loop\n// ...\nscanner.stop(); // releases the camera stream and worker (idempotent, safe to call anytime)\n```\n\n`start()` rejects if `getUserMedia` fails (permission denied, no camera, etc.) — it never fails\nsilently. `stop()` is idempotent and safe to call multiple times or before `start()` resolves.\n\n### One-shot image scanning\n\n```ts\nimport { QrScanner } from \"@elchika-inc/moonqr-scanner\";\n\n// source: HTMLImageElement | HTMLCanvasElement | ImageBitmap | Blob\nconst result = await QrScanner.scanImage(fileInput.files[0]);\nif (result) {\n  console.log(result.text);\n  console.log(`decoded at 1/${result.scale} scale`); // 1 = native resolution\n  console.log(\"scales tried (small -> large):\", result.attemptedScales);\n}\n```\n\n`scanImage()` runs a **multiscale retry**: it doesn't just try the image at native resolution.\nPhotos of a QR code shown on a monitor pick up the screen's sub-pixel grid as high-frequency\nnoise that defeats naive binarization — a single-step resize makes this *worse* (aliasing), so\n`scanImage()` builds a box-averaged (2x2 mean, a real low-pass filter) downscale pyramid and\ntries the smallest images first, escalating to the full-resolution image only as a last resort.\nThe returned `scale` (e.g. `8` for a 1/8-scale success) and `attemptedScales` tell you which\nlevel actually worked, so you're not left guessing why a \"successful\" scan took longer than\nexpected. In local benchmarks this small-to-large search order cut a real monitor-photo decode\nfrom ~5.4s (large-first) to ~60ms (small-first) — see [`bench/RESULT.md`](../../bench/RESULT.md)\nfor the full writeup.\n\nThe live-camera path (`QrScanner.start()`) does *not* run the full pyramid every frame — that\nwould tank the frame rate. It scans at native capture resolution on the fast path, and only\nescalates to a multiscale retry after enough consecutive frames have failed (see\n`DEFAULT_ESCALATE_AFTER_FAILURES` in `src/index.ts`), resetting immediately after one attempt so\nit doesn't camp on the slow path.\n\n## Options\n\n`new QrScanner(video, onResult, options?)`:\n\n| Option | Type | Default | Description |\n|---|---|---|---|\n| `preferredCamera` | `\"environment\" \\| \"user\"` | `\"environment\"` | Back or front camera. |\n| `maxScansPerSecond` | `number` | `25` | Throttles the decode loop. |\n| `invert` | `boolean` | `true` | Also try an inverted-color QR (black/white swapped). |\n| `onError` | `(error: Error) => void` | logs to `console.error` | Camera disconnects, worker crashes, etc. Never silently swallowed — the default still surfaces to the console rather than dropping the error. |\n\n## API reference\n\n- `new QrScanner(video: HTMLVideoElement, onResult: (result: DecodeResult) => void, options?: QrScannerOptions)`\n- `scanner.start(): Promise<void>` — requests camera access and starts scanning. Rejects on\n  failure.\n- `scanner.stop(): void` — releases the camera stream and worker. Idempotent.\n- `QrScanner.scanImage(source: HTMLImageElement | HTMLCanvasElement | ImageBitmap | Blob): Promise<ScanImageResult | null>` — one-shot decode of a still image with multiscale retry.\n- `QrScanner.hasCamera(): Promise<boolean>` — checks `getUserMedia` availability and secure-context status.\n\n`DecodeResult` (`text`, `bytes`, `version`, `ecLevel`, `corners`) and `ScanImageResult`\n(`DecodeResult` + `scale` + `attemptedScales`) are re-exported — see\n[`@elchika-inc/moonqr`'s README](../moonqr/README.md#api-reference) for the shared type shapes.\n\n## Browser support\n\nRequires `getUserMedia` (camera access), `Worker`, and ideally `OffscreenCanvas` for\noff-main-thread decoding — evergreen desktop and mobile browsers all support these. Where\n`Worker`/`OffscreenCanvas` aren't available, decoding transparently falls back to the main\nthread instead of failing. `scanImage()` only needs `Blob`/`ImageBitmap`/canvas support (no\ncamera), so it also works in Node.js test environments with a DOM shim.\n\n## License and attribution\n\nApache License 2.0 — see [LICENSE](../../LICENSE) (also bundled in this package's published\ntarball).\n\nThis package's own logic (camera capture, worker orchestration, multiscale retry) is original.\nIt depends on [`@elchika-inc/moonqr`](../moonqr), whose decoder is partly ported from\n[jsQR](https://github.com/cozmo/jsQR) (Apache-2.0) — see [NOTICE](../../NOTICE) and\n[THIRD_PARTY_LICENSES](../../THIRD_PARTY_LICENSES) (also bundled) for the full attribution.\n","readmeFilename":"README.md"}