npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

appbrain-scanner-sdk

v0.2.0

Published

Web-first scanner SDK: live camera capture, document vision/auto-capture, OCR and multi-format barcode reading behind one package.

Readme

appbrain-scanner-sdk

Web-first, modular scanner SDK for browser apps. It captures frames from a live camera, runs a vision/quality stage, then runs independent barcode and OCR readers and returns the raw results. Domain rules (what an ID card, invoice or receipt means) stay in your code — the SDK is deliberately domain-agnostic.

One install gives you the whole SDK:

npm install appbrain-scanner-sdk

appbrain-scanner-sdk is a facade over the @appbrain/scanner-* packages. You never need to install those directly.

Requirements

  • Runtime: a modern browser with navigator.mediaDevices.getUserMedia (Chrome/Edge/Firefox/Safari, current versions). Camera access needs HTTPS or localhost.
  • React: >= 19.2 (peer dependency) — only if you use the <Scanner> component. The lower-level APIs are framework-agnostic.
  • Bundler: any modern ESM bundler (Vite, Next.js, etc.). The package is ESM only ("type": "module") and ships TypeScript declarations.
  • Node.js is not a runtime requirement — this SDK runs in the browser.

Features

Implemented and covered by tests:

| Capability | Status | Notes | |---|---|---| | Live camera capture (getUserMedia) | implemented | WebCameraSource, environment-facing by default | | Barcode reading via ZXing | implemented | PDF417 by default; QR, Data Matrix, Aztec, EAN, UPC, Code 39/93/128, ITF, Codabar available by config | | OCR via Tesseract.js | implemented | Spanish (spa) model by default; any Tesseract language | | Document detection + perspective normalization | implemented | ScanicFrameAnalyzer (Scanic engine, behind SDK contracts) | | Corner-stability tracking | implemented | CornerStabilityTracker | | Threshold auto-capture policy | implemented | ThresholdAutoCapturePolicy — deterministic, configurable thresholds | | React <Scanner> component with auto-capture loop | implemented | imperative handle: start, stop, reset, scanOnce, warmUp | | Generic raw-to-JSON parsers | implemented | DelimitedParser, OcrKeyValueParser (optional, not wired into the engine) |

Not included: any document-specific parser (ID cards, invoices…) or a native Capacitor plugin (the web adapter runs inside a Capacitor WebView as-is). ScanicFrameAnalyzer exposes the optional ML detector for evaluation, but the default pipeline remains classical. The ML model's asset, license, privacy and hosting review is still pending, so it is not an accepted production default.

Quick start (React)

"use client";

import { useMemo } from "react";
import {
  Scanner,
  createDefaultReaders,
  createDefaultVisionPipeline,
  type ScanResult,
} from "appbrain-scanner-sdk";

export function DocumentScanner() {
  // Keep these stable across renders — the component does not own their lifecycle.
  const readers = useMemo(() => createDefaultReaders(), []);
  const visionPipeline = useMemo(() => createDefaultVisionPipeline(), []);

  return (
    <Scanner
      readers={readers}
      visionPipeline={visionPipeline}
      mode="both"
      autoCapture
      cameraOptions={{ facingMode: "environment" }}
      onResult={(result: ScanResult) => {
        for (const reading of result.readings) {
          if (reading.kind === "code") console.log("barcode:", reading.raw);
          if (reading.kind === "ocr") console.log("text:", reading.raw);
        }
      }}
      onError={(err) => console.error(err)}
    />
  );
}

createDefaultReaders() returns a ZXing PDF417 reader plus a Spanish Tesseract OCR reader. createDefaultVisionPipeline() returns a ready VisionPipeline (analyzer + normalizer + stability tracker + auto-capture policy).

Use cameraOptions.facingMode to select the browser camera type:

<Scanner
  readers={readers}
  visionPipeline={visionPipeline}
  cameraOptions={{ facingMode: "user" }}
/>

"environment" requests the rear camera and remains the default. "user" requests the front camera. You can combine it with resolution hints, for example { facingMode: "environment", width: 1920, height: 1080 }.

For devices with several rear lenses, enumerate inputs after permission and pass an exact deviceId; it takes priority over facingMode:

import { WebCameraSource } from "appbrain-scanner-sdk";

const inputs = await WebCameraSource.listVideoInputs();
const camera = new WebCameraSource({ deviceId: inputs[0]?.deviceId });

focusMode, focusDistance and pointOfInterest are browser/media-device hints. They may be ignored when the selected camera does not support them. See the camera selection and focus guide.

OCR assets

By default Tesseract.js downloads its worker, WASM core and language data from a public CDN on first use. To self-host them (recommended for production and for mobile testing behind an HTTPS tunnel), serve the files from your app and point the reader at them:

const readers = createDefaultReaders({
  codes: { formats: ["PDF_417"] },
  ocr: {
    language: "spa",
    assets: {
      workerPath: "/tesseract/worker.min.js",
      corePath: "/tesseract/core",
      langPath: "/tesseract/lang",
    },
  },
});

The repo's demo has a prepare:ocr-assets script that copies these files from tesseract.js, tesseract.js-core and @tesseract.js-data/spa into public/tesseract/ — mirror it in your app.

Barcode scanning

The ZXing reader decodes PDF_417 only unless you configure formats:

import { ZxingCodeReader } from "appbrain-scanner-sdk";

const reader = new ZxingCodeReader({ formats: ["QR_CODE", "EAN_13", "CODE_128"] });

For harder frames, enable ZXing's TRY_HARDER hint without importing ZXing types:

const reader = new ZxingCodeReader({ formats: ["PDF_417"], tryHarder: true });

const readers = createDefaultReaders({
  codes: { formats: ["PDF_417"], tryHarder: true },
});

tryHarder defaults to false because it can increase per-frame decode work. tryRotated defaults to true and remains independent; keep it enabled for PDF417 cards that may appear sideways in a portrait camera view.

Supported CodeFormat values: PDF_417, QR_CODE, MICRO_QR_CODE, AZTEC, DATA_MATRIX, MAXICODE, EAN_13, EAN_8, UPC_A, UPC_E, CODE_39, CODE_93, CODE_128, CODABAR, ITF, RSS_14, RSS_EXPANDED.

ZxingCodeData exposes both text and, when the payload is byte-oriented, bytes (lossless — useful for PDF417 payloads that carry packed binary after the printed fields).

OCR

import { TesseractOcrReader } from "appbrain-scanner-sdk";

const ocr = new TesseractOcrReader({
  language: "spa+eng",
  includeBlocks: true,
  contrastIntensity: 1,
  preserve_interword_spaces: true,
});

Returns OcrData: { text, confidence?, blocks?, debugImage? }. blocks (word/line/block geometry) is present only with includeBlocks: true; debugImage requires debugPreprocessedImage: true and should be used only for diagnostics because PNG encoding adds work and result size.

With grayscale enabled, the default preprocessing is a global contrast stretch. useAdaptiveContrast: true currently selects global Otsu binarization despite the option name; it is not CLAHE or local adaptive histogram equalization. Whitelist/blacklist options are forwarded to Tesseract, and minBlockConfidence filters data.text while preserving the unfiltered raw. See the OCR troubleshooting guide. Call ocr.dispose() when done to terminate the worker.

Auto capture

<Scanner autoCapture> analyzes preview frames a few times per second at a reduced resolution (analysisMaxDimension, 640px default), tracks the document corners, and captures a full-resolution frame the moment ThresholdAutoCapturePolicy reports the frame is ready. Manual capture is always available through the ref (scannerRef.current.scanOnce()).

both mode: per-capability completion

In mode="both" each capability finishes on its own terms — the loop does not stop at the first accepted frame. A code reader keeps retrying fresh frames until it decodes; an OCR reader keeps going until its reading is good enough.

<Scanner
  readers={readers}
  visionPipeline={visionPipeline}
  mode="both"
  bothStrategy="sequential"      // default: PDF417 first (full cadence), then OCR
  readerAcceptance={{
    // "zxing": a non-null result is enough (the default)
    "tesseract": (r) => (r.confidence ?? 0) >= 80 && !!r.data.text?.trim(),
  }}
  captureTimeoutMs={15000}       // give up on a stuck capability, then move on
  onReading={(r) => console.log(`${r.kind} done`)}
  onResult={(result) => { /* both capabilities settled (or timed out) */ }}
/>
  • bothStrategy"sequential" (default) runs the readers one at a time in readers order; "parallel" dispatches every pending reader on each accepted frame. Sequential is the reliable choice for a dense PDF417: ZXing decodes synchronously on the main thread, so giving it the loop's full cadence before OCR starts matters.
  • readerAcceptanceRecord<readerId, (r: ReaderResult) => boolean>. When a reader has no entry, any non-null result counts. An OCR reader always returns something, so pass a predicate (a confidence gate) or it is treated as done on its first frame.
  • captureTimeoutMs (default 15000, 0 = forever) and maxCaptureAttempts (default 0 = unbounded) bound the wait — per stage in "sequential", for the whole run in "parallel". On timeout the loop reports whatever it collected.
  • onReading(result) fires as each capability closes, before onResult.

mode="ocr" / mode="codes" are the single-capability case and behave as before. scanOnce is unchanged: one frame, one pass, reports whatever it got.

Tune thresholds by constructing the policy yourself:

import {
  ScanicFrameAnalyzer,
  CornerStabilityTracker,
  ThresholdAutoCapturePolicy,
} from "appbrain-scanner-sdk";

const analyzer = new ScanicFrameAnalyzer({ maxProcessingDimension: 800 });
const visionPipeline = {
  analyzer,
  normalizer: analyzer,
  stabilityTracker: new CornerStabilityTracker(),
  autoCapturePolicy: new ThresholdAutoCapturePolicy({
    minSharpness: 0.2,
    minCoverage: 0.25,
  }),
};

The values above deliberately relax the current policy defaults (minSharpness: 0.6, minCoverage: 0.3). Record target-device measurements before changing capture gates.

Result

onResult (and scanOnce) give you a ScanResult:

interface ScanResult {
  capturedFrame: Frame;       // full-resolution frame that was read
  processedFrame: Frame;      // perspective-normalized frame handed to readers
  vision?: VisionResult;      // analysis + acceptance for this frame
  readings: ReaderResult[];   // one per reader that produced something
  failures: ReaderFailure[];  // reader id + message for readers that threw
}

interface ReaderResult<T = unknown> {
  readerId: string;
  kind: "ocr" | "code";
  data: T;                    // ZxingCodeData | OcrData
  raw?: string;               // raw text payload
  confidence?: number;
  metadata?: Record<string, unknown>;
}

A frame that was read but matched nothing still resolves to a ScanResult with empty readings — deciding whether that is worth surfacing is your call.

In a both-mode auto-capture, readings can come from different frames (each capability finishes on its own); capturedFrame/processedFrame are then the last frame processed. A single engine.process / processPrepared call and scanOnce always read one frame.

Without React

Use ScannerEngine directly with your own capture loop:

import { ScannerEngine, createDefaultReaders } from "appbrain-scanner-sdk";

const engine = new ScannerEngine({ readers: createDefaultReaders(), mode: "both" });
await engine.warmUp();                 // pre-download models/WASM (optional)
const result = await engine.process(frame);  // frame: { imageData, width, height, capturedAt }

WebCameraSource (from the same package) produces Frames from an HTMLVideoElement.

engine.process(frame, signal?) runs vision then all mode-enabled readers. engine.processPrepared(captured, processed, options?) skips the vision stage (for a frame already accepted by a VisionPipeline); options is { signal?, vision?, only?, frameFor? }only restricts to given reader ids, frameFor(reader) picks the frame per reader.

Parsing results

The SDK returns raw text. @appbrain/scanner-parser (re-exported here) has small generic helpers — DelimitedParser, OcrKeyValueParser — implementing the ResultParser<I, O> contract. Document-specific parsing belongs in your app.

React / Capacitor

  • React / Next.js: import Scanner in a client component ("use client").
  • Capacitor: the web camera adapter works inside a Capacitor WebView with no native plugin. Grant camera permission in the native project as usual.

Examples

A complete working demo (Next.js + React) is included in the repository at apps/scanner-demo. Clone the repo, npm install, then npm run dev to see it in action.

Public API

Re-exported from appbrain-scanner-sdk:

  • Facade helpers: createDefaultReaders, createDefaultVisionPipeline
  • React: Scanner, ScannerProps, ScannerHandle
  • Engine & contracts: ScannerEngine, and the types Frame, ScanResult, ReaderResult, ReaderFailure, ScannerReader, ProcessPreparedOptions, VisionPipeline, VisionResult, FrameAnalysis, CaptureDecision, ScannerMode, ScannerState, …
  • Camera: WebCameraSource, WebCameraError, WebCameraOptions, VideoInputDevice
  • Readers: ZxingCodeReader, CodeFormat, ZxingCodeData, TesseractOcrReader, OcrData
  • Vision: ScanicFrameAnalyzer, CornerStabilityTracker, ThresholdAutoCapturePolicy
  • Parsers: ResultParser, DelimitedParser, OcrKeyValueParser

Changelog

See CHANGELOG.md.

License

MIT © AppBrain. Bundled engines: ZXing (@zxing/library, Apache-2.0), Tesseract.js (Apache-2.0), Scanic (MIT) — all installed as transitive dependencies.