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

@cnct/connect

v0.3.0

Published

Sign in with Connections in two calls — a dependency-free SDK for the Connections OIDC provider (PKCE, rotation-safe single-flight refresh, userinfo + id_token identity, RFC 8252 loopback sign-in for native/daemon apps, 0600 file store) that composes with

Readme

@cnct/connect

Sign in with Connections, in two calls. Give your app real logins and real per-user data — no backend, no AWS, no OAuth console. Dependency-free (Web Crypto + fetch); works in browsers, Chrome-extension service workers, and Node ≥ 18.

npm install @cnct/connect @cnct/locker
import { createConnect } from "@cnct/connect";
import { createLocker } from "@cnct/locker";

const connect = createConnect({
  clientId: "your-public-client-id", // safe to embed — it only identifies your app
  redirectUri: "https://yourapp.com/callback",
  scopes: ["openid", "profile"],
});

// 1. send the user to sign in
await connect.signIn();

// 2. on your redirect page
const user = await connect.getUser(); // { sub, name, email, ... }

// per-user data, stored server-side, follows the user across devices — no database:
const locker = connect.locker(createLocker) as ReturnType<typeof createLocker>;
await locker.merge({ theme: "dark" });

Getting a client_id

Once, in a build step (or from the Studio console):

import { registerApp } from "@cnct/connect";
const { clientId } = await registerApp({ name: "My Extension", redirectUris: ["https://yourapp.com/callback"] });
// hard-code clientId; don't register on every load.

Chrome extension

Register your redirect as https://<extension-id>.chromiumapp.org/ and drive the redirect with chrome.identity.launchWebAuthFlow:

const url = await connect.signIn({ redirect: false });
const returned = await chrome.identity.launchWebAuthFlow({ url, interactive: true });
const user = await connect.handleCallback(returned);

Pass a chrome.storage-backed store in createConnect({ store }) to persist tokens across the extension service worker's lifecycle.

Desktop / daemon apps (Node)

For a local single-user daemon or desktop app (the DevWebUI / Reimagine / RepoYeti shape — a Node process that IS its own backend), use the RFC 8252 loopback flow plus the 0600 file store:

import { createConnect, nodeFileStore } from "@cnct/connect";

const connect = createConnect({
  clientId: "your-public-client-id",
  redirectUri: "http://127.0.0.1/oauth/callback", // loopback — Connections matches any port
  scopes: ["openid", "profile", "email", "photo"],
  store: nodeFileStore(`${process.env.HOME ?? process.env.USERPROFILE}/.myapp/connections.json`),
});

const user = await connect.signInLoopback(); // binds 127.0.0.1, opens the browser, waits, exchanges
console.log(user.name, user.picture); // display identity — see the identity note below

signInLoopback() binds an ephemeral loopback port, opens the system browser at the authorize URL (or hands it to your openBrowser), waits for the redirect, exchanges the code, and persists the session. Refresh is single-flight and rotation-safe: concurrent getAccessToken() calls share one refresh (Connections rotates third-party refresh tokens and revokes the whole family on replay — this client never double-spends), and a revoked session cleanly resolves to signed-out.

Headless devices (CLIs, SSH, TVs) — device flow

No browser on THIS machine? signInDeviceFlow() (RFC 8628) prints a short code the user enters on any other device, then resolves once they approve:

const user = await connect.signInDeviceFlow({
  onCode: ({ userCode, verificationUri }) => console.log(`Visit ${verificationUri} and enter ${userCode}`),
});

Verified identity (security inputs)

idTokenClaims() is display-only (no signature check). When the identity gates something real — account linking, an owner check — use verifiedIdTokenClaims(): RS256 verification against the issuer's published JWKS (Web Crypto, still zero dependencies) plus iss/aud/exp validation, with automatic JWKS refresh on key rotation. Throws a ConnectError naming exactly what failed.

Signing out of a "disconnect this app" surface? signOut({ revoke: true }) also revokes the grant server-side (RFC 7009) — the refresh-token family dies everywhere, not just locally.

Identity — where the name/email/picture come from

  • getUser() (authoritative) → GET /oauth/userinfo: sub, name, given_name, family_name, picture (with the photo scope), email, plus entitlements / is_paid / custom_answers.
  • idTokenClaims() (instant, no network) → the id_token now embeds the same scope-gated profile claims (Google's shape), so a display name is available the moment sign-in completes.
  • The email is a privacy relay for third-party apps — a stable, deliverable, per-(user, app) …@privaterelay.connections.icu address, never the user's real inbox (Apple Hide-My-Email model). Key your accounts on sub; treat email as a display/delivery address.

Native apps (Rust / Swift / anything non-Node)

Implement the same five steps this package does — they are deliberately boring: (1) PKCE S256 + state, loopback listener on 127.0.0.1:<any port>; (2) browser to {issuer}/oauth/authorize?...; (3) exchange the code at POST {issuer}/oauth/token (code_verifier, no secret — public client); (4) identity from the id_token claims or GET /oauth/userinfo; (5) on refresh, PERSIST the rotated refresh_token every time and serialize concurrent refreshes (family-revoke on replay). A worked Rust example lives in QuickDictate's src/sync.rs.

What this is (and isn't)

Security rests on PKCE + your redirect allowlist + the user's consent — the client_id is public by design (like Firebase's web apiKey / Stripe's pk_). Authorization for the locker always rides the user's own token, so there are no per-user security rules to write and none to forget. This is NOT a cnx_pub_ key — that's a separate, broader primitive for calling first-party data APIs from a browser.