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

@furious.luke/argus-js

v0.3.1

Published

Browser client for the Argus video streaming platform — publish WebRTC video to Argus media servers.

Readme

@furious.luke/argus-js

Browser client for the Argus video streaming platform. It publishes a MediaStream (camera, screen share, etc.) from a browser to an Argus media server over WebRTC, handling gateway selection, the signaling handshake, and ICE negotiation for you.

Zero runtime dependencies — it uses only the browser's built-in WebSocket and RTCPeerConnection.

Where this fits

Argus is a distributed video ingestion service. A stream flows through three parties:

  • Your server mints a short-lived join token using its secret API key (see the Go client, published as the github.com/furious-luke/argus-go module, or the control-plane HTTP API).
  • The browser (this library) uses that token to publish video to the nearest Argus media server. It never sees the API key.
  • Your server later fetches frames from the stream on demand.

This library is only the browser publishing half. It deliberately does not talk to the control plane or mint tokens — that requires your secret API key and must stay server-side.

┌──────────┐  1. request join token   ┌────────────┐
│  browser │ ───────────────────────▶ │ your server│ ──(API key)──▶ Argus control plane
│          │ ◀─────────────────────── │            │ ◀── token + gateway_urls ──
│ argus-js │  2. token + gateway_urls └────────────┘
│          │
│          │  3. publish WebRTC video ────────────────▶ Argus media server
└──────────┘

Installation

npm install @furious.luke/argus-js

Usage

Your server first creates a stream and returns the join token bundle to the browser (the token and gateway_urls from Argus's POST /api/streams response). Then:

import { Publisher, captureCamera } from "@furious.luke/argus-js";

// `join` is the { token, gateway_urls } bundle your server obtained from Argus.
const publisher = new Publisher({
  gatewayURLs: join.gateway_urls,
  token: join.token,
  callbacks: {
    onConnected: () => console.log("streaming live"),
    onConnectionStateChange: (state) => console.log("state:", state),
    onRecoveryStateChange: (event) => console.log("media recovery:", event),
    // Show a button or prompt. From its click handler, call captureScreen()
    // and then publisher.replaceStream(newStream).
    onRecoveryRequired: (event) => console.warn("screen share must be restarted", event),
    onError: (err) => console.error("publish error:", err),
  },
});

// captureCamera() applies sensible capture defaults; any MediaStream works too.
const stream = await captureCamera();
await publisher.start(stream);

// After start() resolves, this token lets your server fetch frames for the stream.
console.log("read token:", publisher.frameReadToken);

Capture helpers

captureCamera() and captureScreen() wrap getUserMedia / getDisplayMedia with defaults tuned for streaming — a capped resolution and modest frame rate, audio off. They matter most for screen sharing: on HiDPI/Retina displays a raw getDisplayMedia captures at native resolution (often 3456px+ / effectively 4k), wasting upload bandwidth and downstream decode for no benefit. captureScreen() caps the width instead.

import { captureScreen } from "@furious.luke/argus-js";

const stream = await captureScreen();
await publisher.start(stream);

Both accept overrides (shallow-merged over the defaults), and any MediaStream you build yourself still works if you'd rather manage constraints directly.

Switching sources without reconnecting

replaceStream renegotiates in place, e.g. to toggle between camera and screen:

const screen = await captureScreen();
await publisher.replaceStream(screen);

Stopping

publisher.stop(); // stops all tracks and tears down the peer connection

API

new Publisher(options)

| Option | Type | Description | | --- | --- | --- | | gatewayURLs | string[] | Required. The gateway_urls from the join response. All are raced simultaneously; the fastest to accept wins. | | token | string | Required. The short-lived join token from the join response. | | iceServers | RTCIceServer[] | Optional extra ICE servers (e.g. your own STUN). TURN is supplied automatically by the winning gateway. | | signalingReconnectTimeoutMs | number | How long to retry a dropped signaling socket against the selected regional gateway. Defaults to 20 seconds. | | callbacks | PublisherCallbacks | Optional lifecycle callbacks (see below). |

Methods & properties

| Member | Description | | --- | --- | | start(stream) | Races the gateways, completes the handshake, and sends the SDP offer. Resolves once the offer is sent — use onConnected to know when media is actually flowing. | | replaceStream(stream) | Replaces the published tracks and renegotiates in place. | | stop() | Stops all local tracks and closes the peer connection. | | frameReadToken | The one-hour read token from the gateway's ready message, or null before connecting. The publisher uses it internally to resume signaling; hand it to your server to fetch frames. | | peerConnection | The underlying RTCPeerConnection, or null if not started. | | isConnected | true when the peer connection state is "connected". |

PublisherCallbacks

| Callback | When | | --- | --- | | onConnected() | The peer connection reached "connected" — media is flowing. | | onConnectionStateChange(state) | The RTCPeerConnectionState changed. | | onRecoveryStateChange(event) | Argus detected stalled media and the publisher started, escalated, completed, or failed automatic recovery. | | onRecoveryRequired(event) | Automatic recovery could not restore media, or capture ended and the host must ask the user for a new screen share. | | onError(error) | A fatal error occurred (signaling error, initial gateway failure, or signaling resume timed out). |

How start() works

  1. Gateway race. Every URL in gatewayURLs is opened at once with the token in the query string. The first to complete the two-phase handshake (acceptedproceedready) wins; the rest are closed. This picks the lowest-latency region without a separate probe.
  2. TURN + read token. The winning gateway's ready message carries per-session TURN credentials (merged into the ICE configuration) and the read token exposed as frameReadToken.
  3. WebRTC. A peer connection is created, tracks are added, and an offer is sent. Remote ICE candidates that arrive before the SDP answer are buffered and flushed once the answer is applied.

After this initial race, the publisher is pinned to the selected regional gateway. If signaling drops, it reconnects to that same gateway with the one-hour read token and waits for resumed; it does not race regions, rebuild the peer connection, or repeat the ready handshake. If the retry deadline expires, the publisher closes the stream and calls onError.

Automatic media recovery

Argus measures freshness from complete encoded samples received by the media server, not from pixel changes. An unchanged screen therefore remains healthy. If samples stop for 15 seconds, the media server tells the publisher that the track has stalled and stops serving its retained frame as current. The publisher first detaches and reattaches the live sender and renegotiates. If samples do not resume within four seconds, it performs an ICE restart and waits another eight seconds. These fixed timings deliberately are not application configuration.

onRecoveryStateChange reports the recovery transitions. If both automatic steps fail, onRecoveryRequired is called and the host can acquire a replacement stream and pass it to replaceStream. A browser cannot silently reacquire a screen share after the user or operating system ends it, so capture_ended always requires host UI and a fresh captureScreen() call.

Browser support

Requires a browser with WebRTC (RTCPeerConnection) and WebSocket — all current evergreen browsers. There is no Node.js runtime support; this is a browser-only package.

Development

npm install
npm run build      # bundle ESM + CJS + types into dist/ via tsup
npm test           # run the vitest suite (jsdom)
npm run typecheck  # tsc --noEmit

Publishing

The package is published to npm under the @furious-luke scope. prepublishOnly runs typecheck, tests, and build first, so a release is:

npm version <patch|minor|major>
npm publish

(The scope is configured for public access via publishConfig.)