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

@rangorithm/scan-link

v0.1.0

Published

Pair a phone by QR and stream its barcode scans to the browser over SSE.

Readme

@rangorithm/scan-link

Turn a phone into a barcode scanner for any web app. Your page shows a QR code, the Rangorithm app scans it, and every barcode the phone reads appears in your UI over Server-Sent Events.

npm install @rangorithm/scan-link

How it works

  1. Your browser calls POST /sessions on your own server and opens an SSE stream.
  2. Your page renders a QR containing the pair code and your relay URL.
  3. The phone scans it, confirms your host, and pairs.
  4. Each scan is a POST from the phone; your server fans it out to the stream.

The phone never holds a socket open, so a WiFi-to-LTE handover costs nothing. The browser's native EventSource handles reconnect and replay.

Server

You host the relay. Mount the router anywhere; the client's baseUrl just has to match.

import express from "express";
import { createScanServer } from "@rangorithm/scan-link/server";

const app = express();
const scan = createScanServer();

app.use("/api", scan.express());
app.listen(4000);

Routes created under the mount point: POST /sessions, POST /sessions/pair, GET /sessions/:id/stream, POST /sessions/:id/scans, POST /sessions/:id/unpair.

Options:

| Option | Default | Notes | | --- | --- | --- | | store | new InMemorySessionStore() | Swap for Redis to run multiple instances | | pairCodeTtlMs | 5 min | How long an unscanned QR stays valid | | pairedSessionTtlMs | 8 hours | Session lifetime after pairing | | heartbeatMs | 20 s | :ping interval; keep under your proxy's idle timeout | | bodyLimit | 32kb | JSON body cap |

Express is a peer dependency and is only needed for scan.express().

React

"use client";
import { useScanSession } from "@rangorithm/scan-link/react";

export function Scanner() {
  const scan = useScanSession({ baseUrl: "/api", appName: "Acme WMS" });

  if (scan.status !== "paired") {
    return <QrCode value={scan.pairQrValue} caption={scan.pairCode} />;
  }

  return (
    <ul>
      {scan.scans.map((s) => (
        <li key={s.clientScanId}>{s.value}</li>
      ))}
    </ul>
  );
}

status is creating | waiting | paired | reconnecting | expired. Show reconnecting honestly — never a stale "connected". Scans arrive newest-first, deduplicated by clientScanId, so replay after a reconnect is safe.

The hook also returns deviceName, count, flashId (the newest scan for ~1s), unpair(), startNew(), copyAll(), exportCsv() and toCsv().

baseUrl may be a same-origin path. The QR needs an absolute URL, which is resolved from location.origin; pass publicUrl explicitly if you sit behind a proxy that rewrites the origin.

Without React

import { createScanSession } from "@rangorithm/scan-link";

const session = createScanSession({ baseUrl: "/api" });
session.subscribe(() => render(session.getState()));
await session.start();

Relay URL safety

The pairing QR tells the phone where to upload scans, so a forged QR is the main attack surface. normalizeRelayUrl rejects anything that is not https, except on localhost and private LAN ranges for development, and the app shows the host for confirmation before the first request. Keep that confirmation step if you build your own scanner.

Proxies

SSE needs an unbuffered, long-lived response:

Content-Type: text/event-stream; charset=utf-8
Cache-Control: no-cache, no-transform
Connection: keep-alive
X-Accel-Buffering: no

Do not gzip the stream route — a compression buffer makes it look hung. If compression middleware is already installed, this router calls res.flush() after every write, which keeps it working. Serve over HTTP/2: on HTTP/1.1 the browser's six-connections-per-origin limit starves other requests while the stream is held open. Set the proxy idle timeout above heartbeatMs.

Running more than one instance

InMemorySessionStore is single-process, which also rules out serverless. Implement SessionStore over Redis pub/sub and pass it as store; nothing else changes.

import type { SessionStore } from "@rangorithm/scan-link/server";