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

@decentnetwork/beagle-connect

v0.1.2

Published

Sign in with Beagle from a website: identity, friend requests, and app launches.

Readme

Sign in with Beagle

Three things a website can do with a visitor's Beagle identity. No passwords, no OAuth, no server round-trip to anyone else: the proof is a signature made by the user's own key and checked against their userid.

npm install @decentnetwork/beagle-connect

| you want | you call | |---|---| | know who the visitor is | signIn({ nonce }) | | they arrived from Beagle's Apps tab | readLaunch() | | introduce them to someone | addFriend({ address, name }) |


Why this is a package and not a snippet

Which Beagle answers is not a constant. An installed client serves http://localhost:8766 and its popup opens instantly. https://app.beagle.chat serves the same routes for someone who installed nothing. Every site that hardcoded one of them got the other case wrong — and the failure is silent: the popup opens, nothing ever posts back, and the user waits.

pickUiOrigin() probes local first and falls back to the web client. You can override the list, but you should not have to write it.


1. Sign in

import { signIn } from "@decentnetwork/beagle-connect";

// The nonce MUST come from your server and be single-use. That is the whole
// replay defence.
const { nonce } = await fetch("/api/auth/nonce").then((r) => r.json());

const who = await signIn({ nonce });
// { userid, address, name, avatar, sig, nonce, uiOrigin }

await fetch("/api/auth/beagle", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify(who),
});

A popup opens, the user sees your origin and their identity, and clicks Approve. signIn verifies the signature before resolving — but that check is for your UI. Verify on your server before you mint a session (below).

2. They came from Beagle's Apps tab

A user who opens your app from inside their own Beagle should not be asked to sign in to it. Beagle hands over an assertion in the URL fragment:

https://your.app/#beagle=<base64url payload>
import { readLaunch } from "@decentnetwork/beagle-connect";

const who = await readLaunch();   // null when there is nothing to read
if (who) {
  // { v, userid, address, name, punkId, ts, sig, assertion }
  await fetch("/api/auth/beagle-launch", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(who),
  });
}

readLaunch() verifies the signature, rejects anything older than two minutes, and strips the fragment from the address bar so a copied URL carries no credential. It throws when a fragment is present but does not check out — a bad assertion is worth surfacing, not silently treating as "not signed in".

The fragment, and why it is the fragment

A fragment is never sent to a server, never appears in a Referer header and never lands in an access log. The credential stays in the browser. This is the same reason OAuth's implicit flow used one.

This is weaker than signIn, and here is exactly how

signIn uses a nonce your server issued, so your server can retire it after one use. A launch has no such nonce — you were not asked anything before the user clicked. The client timestamps instead.

So your server must:

  1. verify the signature over `decent-launch\n${yourOrigin}\n${ts}`
  2. reject |now − ts| > 120s
  3. remember every sig it has accepted for at least that window, and refuse a repeat

Step 3 is not optional. Without it the assertion is a bearer token valid for two minutes, and two minutes is long enough.

3. Send a friend request

The common case: two people met in a meeting and want to stay in touch.

import { addFriend } from "@decentnetwork/beagle-connect";

await addFriend({ address: theirCarrierAddress, name: theirDisplayName });

theirCarrierAddress is the address field from their signIn — a friend request needs the full address (key + nospam + checksum) and a userid alone does not give you one.

The popup names your site, names who the request would go to, and names the identity it would be sent from. Nothing is disclosed to you: you already had the address, and approving only tells you the request went out.

  • Installed client — the daemon sends it.
  • Browser client — the tab that owns the identity sends it. If none is open, the popup opens Beagle itself (under a fixed window name, so it reuses an existing tab) and sends once it is connected.

Consent is per action, per popup. There is no token, no session, and nothing your site can keep and reuse.


Verifying on your server

Both flows are the same primitive with a different prefix. XEdDSA, not Ed25519 — a Carrier key is X25519 and the public half is the userid.

import { verifyDetached, base58ToBytes } from "@decentnetwork/peer";

const hexToBytes = (h) => Uint8Array.from(h.match(/../g).map((b) => parseInt(b, 16)));

function check(prefix, userid, origin, salt, sigHex) {
  const msg = new TextEncoder().encode(`${prefix}\n${origin}\n${salt}`);
  return verifyDetached(base58ToBytes(userid), msg, hexToBytes(sigHex));
}

check("decent-auth",   userid, MY_ORIGIN, nonce, sig);   // signIn
check("decent-launch", userid, MY_ORIGIN, ts,    sig);   // readLaunch

The prefixes differ on purpose. An assertion minted for a launch must never be replayable as "a site asked me to sign in", nor the reverse. Pin origin to your own — never to whatever arrived in the payload.

What is signed, and what is not

Signed: the prefix, your origin, and the nonce or timestamp.

Not signed: name, avatar, punkId, address. These are display hints so you can render a person instead of a key — nothing public maps a userid to a name unless that user registered a beagles.eth name. Treat them as profile data, never as identity, and never as authorisation.


API

| | | |---|---| | signIn({ nonce, origins?, features? }) | → { userid, address, name, avatar, sig, nonce, uiOrigin }; throws on deny/timeout/bad signature | | readLaunch({ maxAgeMs?, strip? }) | → assertion, or null; throws when a present fragment is invalid or stale | | addFriend({ address, name?, origins? }) | → { ok: true, address } | | pickUiOrigin(origins?) | → the first Beagle that answers | | verifySignIn(userid, origin, nonce, sigHex) | → boolean | | verifyLaunch(userid, origin, ts, sigHex) | → boolean | | DEFAULT_UI_ORIGINS | 127.0.0.1:8766, localhost:8766, app.beagle.chat | | LAUNCH_MAX_AGE_MS | 120_000 |

Pop-ups need a user gesture: call signIn and addFriend from a click handler, not from a promise chain that has already awaited something.