@reedscan/core
v0.2.4
Published
ReedSCAN: fast, steady barcode and QR code scanning in the browser. The camera, a box on every code, and a callback for each new one.
Maintainers
Readme
@reedscan/core
Barcode and QR scanning in the browser. You get the camera picture with a steady box on every code, in an element of yours, and a callback for each new code. It works on old Android phones and new iPhones alike, and decoding runs in workers, so the page stays smooth.
npm install @reedscan/coreimport { createScanner } from "@reedscan/core";
const scanner = createScanner(document.querySelector("#scanner"), {
licenseKey: "RS1.…",
formats: ["QRCode", "DataMatrix", "Code128", "EAN13", "UPCA"],
onScan: (scan) => console.log(scan.format, scan.value),
onError: (error) => console.error(error.code, error.message),
});
// From a tap or click: browsers ask for camera permission, and iOS plays the beep only after one.
button.onclick = () => scanner.start();Give the element a size (for example width: 100%; aspect-ratio: 3 / 4); the scanner fills it. The page must be served over HTTPS (or from localhost) for the camera to work.
Using React? See @reedscan/react.
Options
| Option | Default | |
|---|---|---|
| licenseKey | none | Your Reed Scan license key. |
| formats | QR, Data Matrix, Code 128, EAN-13, UPC-A | Symbologies to read. Fewer is faster and safer. Also: PDF417, Aztec, Code39, Code93, Codabar, ITF, EAN8, UPCE. |
| accept | anything | A RegExp or (value, format) => boolean. Codes that fail it are ignored. Use it for a length range too: (v, f) => f !== "Code128" \|\| v.length === 36. |
| onScan | | Called for each code as it's reported (see repeat). Return false to skip the beep and vibration for it. |
| onError | | Camera and decoder failures (ScannerError, with a code). |
| onLicense | | What the license allows here, once the decoder has loaded. |
| onEvaluationEnded | | Evaluation mode's time ran out and scanning stopped. |
| beep, vibrate | true | Feedback on each reported code. |
| boxes | true | Draw a box on each code. |
| boxStyle | | (code) => ({ color, fill, width, label, labelBackground, labelColor, hidden }) or null, per code. Asked once per code: call restyle() when your answer changes. |
| region | whole picture | { left, top, width, height }, fractions of the scanner element. Only codes whose centre is inside count; a code that moves in reports then. |
| repeat | "per-sighting" | When a code reports again: "per-sighting" (each time it comes into view), "once" (until remove()), or { afterMs } (again while it stays in view). |
| gapMs | 0 | One code at a time: after a report, nothing else reports for this long. A second code in view waits and reports when the gap ends, if it's still there. |
| enabled | true | Start paused, with the camera on. |
| facing | "back" | "front" for a fixed kiosk or desk camera. |
| cameraStorageKey | "reedscan.camera.deviceId" | Where the chosen lens is remembered (the front one under the key + .front). null to never remember. |
| onTracks | | Every frame, each code in view with its box in screen pixels, for drawing your own overlay. |
| decoder | | A decoder from createDecoder(), shared between scanners. See Still images and fast reopening. |
| resolution | "standard" | "dense" asks for 4K, for small codes far away. It's slower. |
| workers | "auto" | Decoder workers, or "auto" for as many as the device has spare cores for. |
| wasmUrl | jsDelivr | Where zxing_reader.wasm is served from. See Self-hosting. |
| workerUrl | built in | Where the decoder worker is served from, for sites whose CSP refuses blob: workers. |
The returned Scanner has:
start(),stop()(camera off),destroy()setEnabled(on): pause or resume reading with the camera kept on, for "done with this one" and hold-to-scan. Codes in view on resuming count as new sightings.scans(),remove(value)(it reports again, even while still in view),clear()setRegion(region),restyle(),tracks(),feedback()(the beep and vibration, for a code you accept later)decodeImage(image),setFormats(),setTorch(),setZoom(),switchCamera(),setFacing()- the read-only
running,enabled,cameraandlicense
A find-and-pick screen, for example: codes in the brackets only, green once your lookup says it's the one.
const wanted = new Set();
const scanner = createScanner(el, {
region: { left: 0.1, top: 0.35, width: 0.8, height: 0.3 },
boxStyle: ({ value }) => (wanted.has(value) ? { color: "#2fb344", label: "✓ Pick", labelBackground: "#2fb344" } : { color: "#8a8f98", fill: "none", label: "" }),
onScan: (scan) => {
lookUp(scan.value).then((ok) => {
if (ok) wanted.add(scan.value), scanner.restyle(), scanner.feedback();
});
return false; // no beep until the lookup says yes
},
});Still images and fast reopening
createDecoder() loads the decoding engine on its own. Share it between scanners that open and close often: a scanner given one starts without loading anything, and leaves it running when it's destroyed. It also reads still images, such as a QC photo:
import { createDecoder, createScanner } from "@reedscan/core";
const decoder = createDecoder({ licenseKey: "RS1.…" }); // at page load
const scanner = createScanner(el, { decoder, onScan }); // opens at once, each time
scanner.destroy(); // the decoder keeps running
const codes = await decoder.decodeImage(fileInput.files[0], { formats: ["Code128"] });
// [{ value, format, bytes, quad }] — quad in the image's pixelsdecodeImage takes an image or canvas element, a File or Blob, an ImageBitmap or ImageData, and spends far more effort per picture than a live frame gets.
License keys
Without a key, Reed Scan runs in evaluation mode: it shows a watermark and stops scanning after 60 seconds (reload the page to scan again). On localhost, and on LAN addresses such as a phone on your dev server, it works fully without a key.
A key is locked to your domains, for example acme.com, *.acme.com. It's checked offline, in the browser, so scanning never depends on a license server. After a key's end date it keeps working for a 14-day grace period, with a console warning.
Get a key at reedscan.com.
Self-hosting
By default the decoder's wasm file (about 925 KB) loads from jsDelivr, pinned to this package's version. For offline warehouses, intranets or a strict CSP, serve it yourself:
cp node_modules/@reedscan/core/dist/zxing_reader.wasm public/createScanner(el, { licenseKey, wasmUrl: "/zxing_reader.wasm" });If your Content-Security-Policy doesn't allow worker-src blob:, copy dist/decoder.worker.js too and pass workerUrl: "/decoder.worker.js".
Browsers
Chrome and Edge 99+ (Android and desktop), Safari 16.4+ (iOS and macOS), Firefox 114+, Samsung Internet 18+. These need module workers and OffscreenCanvas. The page must be HTTPS.
License
Proprietary: see LICENSE. Production use needs a license key. Third-party components (zxing-cpp, zxing-wasm) are listed in NOTICE.
© IUJI Group
