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

@mgcrea/unifi-protect

v0.2.0

Published

A UniFi Protect client for Node: authenticated requests, the realtime update stream, and livestream access

Downloads

125

Readme

@mgcrea/unifi-protect


A client for the UniFi Protect private API: the UniFi OS credential handshake, the bootstrap document, and the realtime update stream — with the console's certificate pinned rather than its verification switched off.

This talks to the console's own private API, the one its web app uses. It is undocumented and Ubiquiti moves endpoints between releases. nvr.version is the first thing to check when something that worked stops working.

Why this exists

It was written for @mgcrea/homebridge-unifi-protect, which holds local-admin credentials for a security system. Owning that path end to end was the point, so the client is small, has three runtime dependencies, and reads like something you can audit in an afternoon.

Features

  • Certificate pinning that works by IP. UniFi consoles present a self-signed certificate with no IP SAN, so ordinary verification cannot succeed when you address the console by address — which is why everything in this space reaches for rejectUnauthorized: false. This client captures the certificate once, uses it as its own trust anchor, and replaces the host name check with a SHA-256 fingerprint comparison. See docs/protocol.md.
  • Session handling that survives a restart, cached owner-only. The password is never logged and never written to disk.
  • The realtime update stream, with a pure, tested codec for its binary frame format, a watchdog for the wedged-open socket TCP will not surface, and jittered reconnect backoff.
  • A live state store that applies the console's nested partial updates correctly — a temperature delta does not drop your humidity reading.
  • Tolerant bootstrap parsing. One device in an unrecognised shape is skipped and named, not allowed to take every other device down with it.

Install

pnpm add @mgcrea/unifi-protect

Requires Node 22 or newer.

Usage

import { connectProtect } from "@mgcrea/unifi-protect";

const protect = await connectProtect({
  host: "10.0.0.1",
  username: "bridge",
  password: process.env.PROTECT_PASSWORD!,
  stateDir: "./.cache",
  store: {
    onEvent: (event) => console.log(event.type, event.camera),
    onDeviceChanged: (kind, device) => console.log(kind, device.name),
  },
});

console.log(`Pinned certificate: ${protect.fingerprint}`);
for (const camera of protect.store.cameras()) console.log(camera.name);

await protect.disconnect();

connectProtect resolves once the first bootstrap has landed, so devices can be enumerated straight away; the update stream is then held open and reconnected for you.

Options

| Option | Default | Notes | |---|---|---| | host | — | Hostname or IP, no scheme. | | port | 443 | | | username / password | — | Use a Local Access Only user. A Ubiquiti cloud (SSO) account usually cannot log in locally. | | totp | — | Of little use to a long-running process; prefer a local user without 2FA. | | stateDir | — | Where session.json and certificate.json live. Both written 0600. | | fingerprint | — | A known SHA-256 fingerprint, which skips trust-on-first-use. | | insecureTls | false | Turns verification off entirely. Logged loudly. Don't. |

Credentials

Create a dedicated local user on the console rather than reusing your own account: Settings → Admins → Add Admin → Local Access Only, with Protect permissions. Give it View access if you only need to read, Admin if you intend to change camera settings.

Certificate pinning

On first connect the client records the console's certificate and prints its fingerprint:

Pinned the certificate for 10.0.0.1: 3F2A…C61D.
Set this as the configured fingerprint to make the trust explicit rather than learned.

Trust-on-first-use is only as good as that first moment, so pass the value back as fingerprint once you have it. If the certificate later changes, connections fail with a message naming the file to delete if the change was legitimate.

Verifying against your console

UNIFI_PROTECT_HOST=10.0.0.1 \
UNIFI_PROTECT_USERNAME=bridge \
UNIFI_PROTECT_PASSWORD=... \
pnpm probe

Logs in, prints the fingerprint, enumerates every device, flags any camera whose smart-detect zones ask for something the device switch does not allow, and then tails the update stream for thirty seconds so you can walk past a camera and watch the events arrive.

Development

pnpm install
pnpm test          # lint, typecheck, spec, format:check
pnpm spec          # vitest
pnpm build

docs/protocol.md is the annotated companion to src/const.ts — what the wire actually does, and the traps in it.

Releasing

pnpm release runs the test suite, bumps the version, tags and pushes. The tag triggers the publish workflow, which uses npm trusted publishing over OIDC — there is no NPM_TOKEN to store or rotate.

Credits

The wire protocol has been documented by several people over the years, most notably in hjdhjd/unifi-protect. This is an independent implementation; no code was taken from it.

License

MIT