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

@synonymdev/pubky

v0.6.0

Published

Pubky client

Readme

Pubky

JS/WASM SDK for Pubky.

Works in browsers and Node 20+.

Install

npm install @synonymdev/pubky

Node: requires Node v20+ (undici fetch, WebCrypto).

Module system + TS types: ESM and CommonJS both supported; TypeScript typings generated via tsify are included. Use import { Pubky } from "@synonymdev/pubky" (ESM) or const { Pubky } = require("@synonymdev/pubky") (CJS).

Getting Started

import { Pubky, PublicKey, Keypair, AuthFlowKind } from "@synonymdev/pubky";

// Initiate a Pubky SDK facade wired for default mainnet Pkarr relays.
const pubky = new Pubky(); // or: const pubky = Pubky.testnet(); for localhost testnet.

// 1) Create random user keys and bind to a new Signer.
const keypair = Keypair.random();
const signer = pubky.signer(keypair);

// 2) Sign up at a homeserver (optionally with an invite)
const homeserver = PublicKey.from(
  "8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo"
);
const signupToken = "<your-invite-code-or-null>";
const session = await signer.signup(homeserver, signupToken);

// 3) Write a public JSON file (session-scoped storage uses cookies automatically)
const path = "/pub/example.com/hello.json";
await session.storage.putJson(path, { hello: "world" });

// 4) Read it publicly (no auth needed)
const userPk = session.info.publicKey.toString();
const addr = `${userPk}/pub/example.com/hello.json`;
const json = await pubky.publicStorage.getJson(addr); // -> { hello: "world" }

// 5) Authenticate on a 3rd-party app
const authFlow = pubky.startAuthFlow("/pub/my-cool-app/:rw", AuthFlowKind::signin()); // require permissions to read and write into `my.app`
renderQr(authFlow.authorizationUrl); // show to user
const session = await authFlow.awaitApproval();

Find here ready-to-run examples.

Key formats (display vs transport)

PublicKey has two string forms:

  • Display format: pubky<z32> (for logs/UI/human-facing identifiers).
  • Transport/storage format: raw z32 (for hostnames, headers, query params, serde/JSON, DB storage).

Use publicKey.z32() for transport/storage. Use publicKey.toString() for display.

Initialization & events

The npm package bundles the WebAssembly module and initializes it before exposing any APIs. This avoids the common wasm-pack pitfall where events fire before the module finishes instantiating. Long-polling flows such as authFlow.awaitApproval() or authFlow.tryPollOnce() only start their relay calls after the underlying module is ready, so you won't miss approvals while the bundle is loading.

Reuse a single facade across your app

Use a shared Pubky (e.g, via context or prop drilling) instead of constructing one per request. This avoids reinitializing transports and keeps the same client available for repeated usage.

API Overview

Use new Pubky() to quickly get any flow started:

import { Pubky, Keypair, AuthFlowKind } from "@synonymdev/pubky";

// Mainnet (default relays)
const pubky = new Pubky();

// Local testnet wiring (Pkarr + HTTP mapping).
// Omit the argument for "localhost".
const pubkyLocal = Pubky.testnet("localhost");

// Signer (bind your keypair to a new Signer actor)
const signer = pubky.signer(Keypair.random());

// Pubky Auth flow (with capabilities)
const authFlow = pubky.startAuthFlow("/pub/my-cool-app/:rw", AuthFlowKind::signin());

// Public storage (read-only)
const publicStorage = pubky.publicStorage;

// Pkdns resolver
const pkdns = pubky.getHomeserverOf(publicKey);

// Optional: raw HTTP client for advanced use
const client = pubky.client;

Client (HTTP bridge)

import { Client, PublicKey, resolvePubky } from "@synonymdev/pubky";

const client = new Client(); // or: pubky.client.fetch(); instead of constructing a client manually

// Convert the identifier into a transport URL before fetching.
const userId = PublicKey.from("pubky<z32>").toString();
const url = resolvePubky(`${userId}/pub/example.com/file.txt`);
const res = await client.fetch(url);

Keys

import { Keypair, PublicKey } from "@synonymdev/pubky";

const keypair = Keypair.random();
const pubkey = keypair.publicKey;

// z-base-32 roundtrip
const parsed = PublicKey.from(pubkey.z32());
const displayId = pubkey.toString(); // pubky<z32> (display only)

Recovery file (encrypt/decrypt root secret)

// Encrypt to recovery file (Uint8Array)
const recoveryFile = keypair.createRecoveryFile("strong passphrase");

// Decrypt back into a Keypair
const restored = Keypair.fromRecoveryFile(recoveryFile, "strong passphrase");

// Build a Signer from a recovered key
const signer = pubky.signer(restored);
  • keypair: An instance of Keypair.
  • passphrase: A utf-8 string passphrase.
  • Returns: A recovery file with a spec line and an encrypted secret key.

Signer & Session

import { Pubky, PublicKey, Keypair } from "@synonymdev/pubky";

const pubky = new Pubky();

const keypair = Keypair.random();
const signer = pubky.signer(keypair);

const homeserver = PublicKey.from("8pinxxgq…");
const session = await signer.signup(homeserver, /* invite */ null);

const session2 = await signer.signin(); // fast, prefer this; publishes PKDNS in background
const session3 = await signer.signinBlocking(); // slower but safer; waits for PKDNS publish

await session.signout(); // invalidates server session

Session details

const userPk = session.info.publicKey.toString(); // -> pubky<z32> identifier
const caps = session.info.capabilities; // -> string[] permissions and paths

const storage = session.storage; // -> This User's storage API (absolute paths)

Persist a session across tab refreshes (browser)

// Save the session snapshot (no secrets inside; relies on the HTTP-only cookie).
const snapshot = session.export();
localStorage.setItem("pubky-session", snapshot);

// Later (after a reload), rehydrate using the browser's stored cookie.
const restored = await pubky.restoreSession(localStorage.getItem("pubky-session")!);

The exported string contains only public session metadata. The browser must keep the HTTP-only cookie alive for the restored session to remain authenticated.

Approve a pubkyauth request URL

await signer.approveAuthRequest("pubkyauth:///?caps=...&secret=...&relay=...");

AuthFlow (pubkyauth)

End-to-end auth (3rd-party app asks a user to approve via QR/deeplink, E.g. Pubky Ring).

import { Pubky, AuthFlowKind } from "@synonymdev/pubky";
const pubky = new Pubky();

// Comma-separated capabilities string
const caps = "/pub/my-cool-app/:rw,/pub/another-app/folder/:w";

// Optional relay; defaults to Synonym-hosted relay if omitted
const relay = "https://httprelay.pubky.app/link/"; // optional (defaults to this)

// Start the auth polling
const flow = pubky.startAuthFlow(caps, AuthFlowKind::signin(), relay);

renderQr(flow.authorizationUrl); // show to user

// Blocks until the signer approves; returns a ready Session
const session = await flow.awaitApproval();

Validate and normalize capabilities

If you accept capability strings from user input (forms, CLI arguments, etc.), use validateCapabilities before calling startAuthFlow. The helper returns a normalized string (ordering actions like :rw) and throws a structured error when the input is malformed.

import { Pubky, validateCapabilities, AuthFlowKind } from "@synonymdev/pubky";

const pubky = new Pubky();

const rawCaps = formData.get("caps");

try {
  const caps = validateCapabilities(rawCaps ?? "");
  const flow = pubky.startAuthFlow(caps, AuthFlowKind::signin());
  renderQr(flow.authorizationUrl);
  const session = await flow.awaitApproval();
  // ...
} catch (error) {
  if (error.name === "InvalidInput") {
    surfaceValidationError(error.message);
    return;
  }
  throw error;
}

On invalid input, validateCapabilities throws a PubkyError with { name: "InvalidInput", message: "Invalid capability entries: …" }, so you can surface precise feedback to the user.

Http Relay & reliability

  • If you don’t specify a relay, PubkyAuthFlow defaults to a Synonym-hosted relay. If that relay is down, logins won’t complete.
  • For production and larger apps, run your own http relay (MIT, Docker): https://httprelay.io. The channel is derived as base64url(hash(secret)); the token is end-to-end encrypted with the secret and cannot be decrypted by the relay.

Storage

PublicStorage (read-only)

const pub = pubky.publicStorage;

// Reads
const response = await pub.get(
  `${userPk}/pub/example.com/data.json`
); // -> Response (stream it)
await pub.getJson(`${userPk}/pub/example.com/data.json`);
await pub.getText(`${userPk}/pub/example.com/readme.txt`);
await pub.getBytes(`${userPk}/pub/example.com/icon.png`); // Uint8Array

// Metadata
await pub.exists(`${userPk}/pub/example.com/foo`); // boolean
await pub.stats(`${userPk}/pub/example.com/foo`); // { content_length, content_type, etag, last_modified } | null

// List directory (addressed path "<pubky>/pub/.../") must include trailing `/`.
// list(addr, cursor=null|suffix|fullUrl, reverse=false, limit?, shallow=false)
await pub.list(
  `${userPk}/pub/example.com/`,
  null,
  false,
  100,
  false
);

Use get() when you need the raw Response for streaming or custom parsing.

SessionStorage (read/write; uses cookies)

const s = session.storage;

// Writes
await s.putJson("/pub/example.com/data.json", { ok: true });
await s.putText("/pub/example.com/note.txt", "hello");
await s.putBytes("/pub/example.com/img.bin", new Uint8Array([1, 2, 3]));

// Reads
const response = await s.get("/pub/example.com/data.json"); // -> Response (stream it)
await s.getJson("/pub/example.com/data.json");
await s.getText("/pub/example.com/note.txt");
await s.getBytes("/pub/example.com/img.bin");

// Metadata
await s.exists("/pub/example.com/data.json");
await s.stats("/pub/example.com/data.json");

// Listing (session-scoped absolute dir)
await s.list("/pub/example.com/", null, false, 100, false);

// Delete
await s.delete("/pub/example.com/data.json");

get() exposes the underlying Response, which is handy for streaming bodies or inspecting headers before consuming content.

Path rules:

  • Session storage uses absolute paths like "/pub/app/file.txt".
  • Public storage uses addressed form pubky<user>/pub/app/file.txt (preferred) or pubky://<user>/....

Convention: put your app’s public data under a domain-like folder in /pub, e.g. /pub/my-new-app/.


PKDNS (Pkarr)

Resolve or publish _pubky records.

import { Pubky, PublicKey, Keypair } from "@synonymdev/pubky";

const pubky = new Pubky();

// Read-only resolver
const homeserver = await pubky.getHomeserverOf(PublicKey.from("pubky<z32>")); // string | undefined

// With keys (signer-bound)
const signer = pubky.signer(Keypair.random());

// Republish if missing or stale (reuses current host unless overridden)
await signer.pkdns.publishHomeserverIfStale();
// Or force an override now:
await signer.pkdns.publishHomeserverForce(/* optional override homeserver*/);
// Resolve your own homeserver:
await signer.pkdns.getHomeserver();

Logging

The SDK ships with a WASM logger that bridges Rust log output into the browser or Node console. Call setLogLevel once at application start, before constructing Pubky or other SDK actors, to choose how verbose the logs should be.

import { setLogLevel } from "@synonymdev/pubky";

setLogLevel("debug"); // "error" | "warn" | "info" | "debug" | "trace"

If the logger is already initialized, calling setLogLevel again will throw. Pick the most verbose level ("debug" or "trace") while developing to see pkarr resolution, network requests and storage operations in the console.

Resolve pubky identifiers into transport URLs

Use resolvePubky() when you need to feed an addressed resource into a raw HTTP client:

import { resolvePubky } from "@synonymdev/pubky";

const identifier =
  "pubkyoperrr8wsbpr3ue9d4qj41ge1kcc6r7fdiy6o3ugjrrhi4y77rdo/pub/pubky.app/posts/0033X02JAN0SG";
const url = resolvePubky(identifier);
// -> "https://_pubky.operrr8wsbpr3ue9d4qj41ge1kcc6r7fdiy6o3ugjrrhi4y77rdo/pub/pubky.app/posts/0033X02JAN0SG"

Both pubky<pk>/… (preferred) and pubky://<pk>/… resolve to the same HTTPS endpoint.


WASM memory (free() helpers)

wasm-bindgen generates free() methods on exported classes (for example Pubky, AuthFlow PublicKey). JavaScript's GC eventually releases the underlying Rust structs on its own, but calling free() lets you drop them immediately if you are creating many short-lived instances (e.g. in a long-running worker). It is safe to skip manual frees in typical browser or Node apps.


Errors

All async methods throw a structured PubkyError:

interface PubkyError extends Error {
  name:
    | "RequestError" // network/server/validation/JSON
    | "InvalidInput"
    | "AuthenticationError"
    | "PkarrError"
    | "InternalError";
  message: string;
  data?: unknown; // structured context when available (e.g. { statusCode: number })
}

Example:

try {
  await publicStorage.getJson(`${pk}/pub/example.com/missing.json`);
} catch (e) {
  const error = e as PubkyError;
  if (
    error.name === "RequestError" &&
    typeof error.data === "object" &&
    error.data !== null &&
    "statusCode" in error.data &&
    typeof (error.data as { statusCode?: number }).statusCode === "number" &&
    (error.data as { statusCode?: number }).statusCode === 404
  ) {
    // handle not found
  }
}

Browser environment notes

  • Keep the Pubky client UI and the homeserver on the same origin family (both local or both remote). Browsers partition cookies by scheme/host, and cross-site requests (e.g., http://localhost calling https://staging…​) can silently drop or cache SameSite/Secure session cookies.
  • If you must mix environments, use a reverse proxy so the browser always talks to one consistent origin (or disable caching via devtools and clear cookies between switches).
  • When troubleshooting auth/session caching: open a fresh incognito window, clear site data for the target origin, and verify the request includes credentials.

Local Test & Development

For test and development, you can run a local homeserver in a test network.

  1. Install Rust (for wasm and testnet builds):
curl https://sh.rustup.rs -sSf | sh
  1. Install and run the local testnet:
cargo install pubky-testnet
pubky-testnet
  1. Point the SDK at testnet:
import { Pubky } from "@synonymdev/pubky";

const pubky = Pubky.testnet(); // defaults to localhost
// or: const pubky = Pubky.testnet("testnet-host");  // custom host (e.g. Docker bridge)

MIT © Synonym