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

@conet.project/chat-sdk

v0.1.0

Published

Beamio CoNET Chat SDK — gossip messaging in a Web Worker (zero main-thread openpgp/verify) + encrypted fragmented IPFS history. UI-agnostic, reusable across SilentPassUI / bizSite / Alliance / POS.

Downloads

109

Readme

@conet.project/chat-sdk

Beamio CoNET Chat SDK — runs the gossip transport entirely inside a Web Worker (zero main-thread openpgp decrypt/encrypt, zero ethers.verifyMessage), plus fragmented, symmetrically-encrypted IPFS history. UI-agnostic, reusable across SilentPassUI / bizSite / Alliance / POS.

Motivation: running openpgp.decrypt + ethers.verifyMessage on the main thread caused a "UI freeze for tens of seconds after startup". This SDK moves all inbound decryption, outbound encryption, signing, and SSE connect/reconnect into a Worker. The main thread only orchestrates postMessage and dispatches events.


Features

  • Worker-first: every heavy crypto operation runs inside the Worker; the UI never blocks.
  • UI-agnostic: pure TS/ESM + .d.ts, no React and no app-specific imports. The host injects private keys, RPC, node discovery, IPFS, and persistence.
  • Zero-trust routing built in: send via entry A, listen via entry C (≠ mailbox B), listenKind:'chat', business messages encrypted to the recipient's EOA user PGP, delivery ACK encrypted to the route B key.
  • Encrypted fragmented history: HKDF domain separation + ratcheting fragment keys + AES-256-GCM fragments + encrypted index manifest + server-side point- pointers + IndexedDB local-first.
  • Private keys never persist inside the SDK: keys are used only in Worker memory to derive an ethers.Wallet and the history master. The host decides whether to persist locally (consumers/POS may store; bizSite keeps session memory only).

Architecture

┌────────────────────────── Main thread (host UI) ──────────────────────────┐
│  BeamioChatClient                                                          │
│   • init() → getNodes() → postMessage(init)                               │
│   • sendMessage / queryPresence / setRoutes / setNodes                    │
│   • on('message'|'delivery'|'presence'|'status'|'log'|historyBuffer)      │
│   • history.load / history.append / history.onBuffer                      │
│   ▲ plaintext env.line → host's existing addNewMessage serial queue       │
└──────────────┬───────────────────────────────▲───────────────────────────┘
        postMessage(Command)                  postMessage(Event/Receipt)
┌──────────────▼───────────────────────────────┴───────────────────────────┐
│  Web Worker (worker/entry.ts)                                             │
│   • GossipCore: SSE connect/reconnect, openpgp decrypt/encrypt,           │
│                 EIP-191 sign, presence wallet_online_query, delivery ACK  │
│   • HistoryStore: master + HKDF + ratcheting fragments + AES-GCM + IPFS   │
└──────────────────────────────────────────────────────────────────────────┘

Installation

Install as a dependency (per src-subprojects-are-independent: each sub-project owns formal dependencies; never cross-../.. import).

npm install @conet.project/chat-sdk

Peer dependencies (provided by the host):

| Package | Version | |---|---| | ethers | ^6.0.0 | | openpgp | ^5.0.0 \|\| ^6.0.0 |

Runtime: a browser / WebView with Worker + crypto.subtle (PWA inside iOS/Android native shells is supported). Node >=18 for build/test only.


Quick start

The host creates the Worker (the SDK does not hardcode a worker URL, to stay bundler-agnostic):

import { createBeamioChatClient, type BeamioChatConfig } from '@conet.project/chat-sdk'

const config: BeamioChatConfig = {
  identity: {
    eoaAddress,                 // 0x… (SDK normalizes internally)
    privateKeyHex,              // raw EOA private key hex, used only inside the Worker
    pgpPrivateKeyArmored,       // armored PGP private key (decrypt inbound)
    pgpPassphrase: '',
    pgpPublicKeyArmored,        // armored PGP public key (keyID / diagnostics)
    ownRouteArmoredPublicKey,   // your mailbox B route public key
  },
  conetRpcUrl: 'https://rpc1.conet.network',
  addressPgpContractAddress: CONET_ADDRESS_PGP,
  getNodes: async () => fetchHealthyNodes(),   // host owns node discovery/caching
  ipfsBaseUrl: 'https://ipfs.conet.network/api',
  chainId: 224422,             // chainId used in the history-master derivation domain (default 224422 CoNET L1)
}

const client = createBeamioChatClient(config, {
  workerFactory: () =>
    new Worker(new URL('@conet.project/chat-sdk/worker', import.meta.url), { type: 'module' }),
})

await client.init()                 // start Worker + inject keys/nodes; the Worker begins listening

// inbound plaintext lines → hand to the host's existing serial checkSign/parse queue
const off = client.on('message', (env) => {
  addNewMessage(env.line)           // env.line already carries _beamioPgpArmorHash for delivery ACK
})

// later, update the contacts to listen for / probe
client.setRoutes(myContactRoutes)

Worker creation per bundler

| Environment | workerFactory | |---|---| | Vite | () => new Worker(new URL('@conet.project/chat-sdk/worker', import.meta.url), { type: 'module' }) | | Webpack 5 / CRA (Craco) | same as above; Webpack 5 statically recognizes new Worker(new URL(...)) and emits a separate worker chunk | | Vendored (see below) | () => new Worker(new URL('../vendor/beamio-chat-sdk/worker/entry.ts', import.meta.url), { type: 'module', name: 'beamio-chat-gossip' }) |


Configuration: BeamioChatConfig

| Field | Type | Description | |---|---|---| | identity | ChatIdentity | see below | | conetRpcUrl | string | CoNET DePIN RPC (reads AddressPGP, etc.) | | addressPgpContractAddress | string | AddressPGP contract address | | getNodes | () => Promise<NodeInfo[]> | returns a snapshot of currently healthy nodes (host owns discovery/caching) | | ipfsBaseUrl | string | IPFS fragment gateway base, e.g. https://ipfs.conet.network/api | | ipfsWriteBaseUrl? | string | IPFS write base (defaults to ipfsBaseUrl) | | persistence? | PersistenceAdapter | IndexedDB adapter (optional; history is memory-only without it) | | runtime? | ChatRuntimeOptions | sendFanout (default 3), reconnectBaseMs (4000), reconnectMaxMs (30000) | | chainId? | number | chainId in the history-master derivation domain (default 224422 CoNET L1) |

ChatIdentity (key injection)

| Field | Description | |---|---| | eoaAddress | EOA address | | privateKeyHex | raw private key hex (with or without 0x). Used only inside the Worker for EIP-191 signing and history-master derivation | | pgpPrivateKeyArmored | armored PGP private key, decrypts inbound | | pgpPassphrase? | PGP private key passphrase (if any) | | pgpPublicKeyArmored? | armored PGP public key | | ownRouteArmoredPublicKey? | your mailbox B route public key (listen encryption target) |

One-time key generation + registerChatRoute route registration stay on the host (curve25519 generation is fast and not a freeze source). Inject the generated keys via identity.

ChatRoute (contact routes)

| Field | Description | |---|---| | address | contact EOA (lowercase) | | userPublicKeyArmored | recipient's user PGP public key — business-message encryption target | | routerArmoredPublicKey? | recipient's mailbox B route public key — listen / ACK encryption target | | routePgpKeyID? | route keyID (optional) |


API reference

BeamioChatClient

| Method | Description | |---|---| | init(): Promise<void> | start Worker, inject config/nodes; the Worker begins listening. Idempotent (no-op if already running) | | sendMessage(to, payload, opts?) | encrypt and send a business payload to a contact via entry A ≠ B. Returns { sendId } | | queryPresence(contacts) | probe mailbox listen-pool presence; returns Record<addrLower, boolean> (only ok:true counts) | | setRoutes(routes) | update the set of contacts to listen for / probe | | setNodes(nodes) | push a refreshed node snapshot (host owns discovery) | | postMailboxCommand(routerArmoredPublicKey, command) | encrypt an arbitrary mailbox command (e.g. gossip_delivery_ack) to route B, sent via entry C ≠ B | | on(event, cb): Unsubscribe | subscribe to events (below); returns an unsubscribe function | | history | BeamioChatHistory (below) | | pause() / resume() | pause/resume Worker listening | | destroy() | tear down the Worker and all listeners (idempotent) |

Events (client.on(...))

| Event | Payload | Description | |---|---|---| | message | InboundEnvelope | env.line = host-ready JSON plaintext line (carries _beamioPgpArmorHash), handed to the host's serial checkSign/parse queue | | delivery | DeliveryReceiptEvent | delivery receipt (sendId / deliveredAt / from); host marks its own bubble as delivered | | presence | PresenceEvent | online: Record<addr, boolean>, reliable results only | | status | StatusEvent | idle/connecting/listening/reconnecting/paused/error; listening fires on each heartbeat to refresh main-thread staleness | | log | ChatLogEvent | structured log (never contains private keys / plaintext / full ciphertext) | | historyBuffer | HistoryBufferEvent | incremental batches during history restore/append, fed to the UI incrementally |

client.history

| Method | Description | |---|---| | load(options?) | restore encrypted history: locate index (point-${L} / IndexedDB) → decrypt → decrypt the tail first → backfill in the background; emits historyBuffer incrementally | | append(entry) | write a newly received/sent entry to encrypted history + local mirror | | onBuffer(cb): Unsubscribe | subscribe to incremental batches during restore/append |

HistoryLoadOptions: peer? (single contact or all), tailCount? (default 60, i.e. "last 2 screens"), localOnly? (IndexedDB only, for instant display).


Zero-trust routing (built into the SDK)

Follows conet-p2p-mailbox-routing-protocol and beamio-conet-chat-protocol:

  • Send business messages: encrypt to the recipient's EOA user PGP, POST to a healthy entry A ≠ mailbox B.
  • Recipient listen: encrypt to the mailbox B route key, SSE-connect via entry C ≠ B, and always include listenKind:'chat'.
  • Delivery ACK: encrypt to the mailbox B route key.
  • Never connect directly to mailbox B; never target the recipient's AA (must be EOA user PGP).

Encrypted fragmented history

master     = keccak256( EOA_sign("beamio.chat.history.v1|chainId|eoa") )   // private key never leaves the Worker
locator L  = HKDF(master, "index-locator")     // hidden in a hash ocean; server-side point-${L} points to the current index
indexKey   = HKDF(master, "index-enc")         // AES-256-GCM encrypts the ordered index manifest
fragment ratchet  k_i = HKDF(master, `frag|${seq}|${cid_{i-1}}`)   // cid_{-1}=HKDF(master,"frag-genesis")
           cipher_i = AES-GCM(k_i, plaintext_i);  cid_i = keccak256(cipher_i)   // uploaded as a fragment
  • Every cid_{i-1} is recorded in the index, so any k_i is O(1) to derive — enabling newest-first restore.
  • IndexedDB local-first: localOnly for instant display; network failures do not overwrite trusted local history (beamio-trusted-vs-untrusted-fetch).
  • Server-side point- pointers: content is still stored as keccak(content)=hash1, with an extra alias point-${L}hash1. Requires EOA signature + owner binding + monotonic timestamp (blocks hijacking/replay). See x402sdk/src/endpoint/fragmentClusterServer.ts.

Integrating into SilentPassUI (vendored example)

Because sub-projects live in separate Git repos and file: deps are awkward on remote builds, SilentPassUI vendors the SDK source into src/vendor/beamio-chat-sdk/, bridged via services/chatWorkerBridge.ts:

// services/chatWorkerBridge.ts (key points)
import { createBeamioChatClient } from '../vendor/beamio-chat-sdk'

function makeGossipWorker(): Worker {
  return new Worker(new URL('../vendor/beamio-chat-sdk/worker/entry.ts', import.meta.url), {
    type: 'module',
    name: 'beamio-chat-gossip',
  })
}

const client = createBeamioChatClient(config, { workerFactory: makeGossipWorker })
client.on('message', (env) => { if (env.line) onLine(env.line) })   // → App.tsx addNewMessage
client.on('status', (st) => { if (st.status === 'listening') noteGossipActivity() })
await client.init()

In services/chat.ts, connectToGossipNode only parses routes + healthy entries (for delivery ACK) on the main thread; the inbound LISTEN loop is fully delegated to the Worker. Decrypted plaintext lines flow through onLine → newMessage → App.tsx addNewMessage (unchanged serial queue).

Keep the vendored copy in sync with the source; changes to this SDK must be mirrored into src/SilentPassUI/src/vendor/beamio-chat-sdk/. Vendored relative imports strip .js suffixes to match CRA moduleResolution:"node".


Security & compliance

  • Private keys / plaintext / full ciphertext must never be logged; log events emit structured summaries only.
  • The history master lives only in Worker memory; consumers/POS may store keys locally, bizSite keeps session memory only (policy owned by the host; the SDK never self-persists).
  • Always send listenKind:'chat'; never connect directly to mailbox B.
  • point- has CoNET-private semantics; public-gateway retrieval relies on CIDv1 mapping and must be documented.

Build / directory layout

npm run build       # tsc -p tsconfig.json → dist/ (ESM + .d.ts)
npm run typecheck   # tsc --noEmit
npm run clean       # rm -rf dist
src/
  index.ts          # public entry: createBeamioChatClient + types
  client.ts         # main-thread BeamioChatClient (starts worker + postMessage protocol)
  protocol.ts       # main ↔ worker postMessage protocol
  types.ts          # public types (UI-agnostic)
  crypto.ts         # WebCrypto + keccak/HKDF/AES-GCM (worker & main thread)
  nodes.ts          # node probing (worker & main thread)
  worker/
    entry.ts        # worker entry (imported only in a Worker context)
    gossip-core.ts  # SSE + openpgp decrypt/encrypt + sign + presence + ACK
    history.ts      # encrypted fragmented IPFS history + IndexedDB

Package exports:

| Entry | Usage | |---|---| | @conet.project/chat-sdk | main-thread API (createBeamioChatClient + types) | | @conet.project/chat-sdk/worker | worker entry; only used as the target of new Worker(...) |


Related rules / references

  • conet-p2p-mailbox-routing-protocol / beamio-conet-chat-protocol — routing and envelopes
  • conet-depin-chat-app-dev — app practices (receipts / presence / listenKind)
  • beamio-trusted-vs-untrusted-fetch — failures never overwrite trusted local state
  • x402sdk/src/endpoint/fragmentClusterServer.tspoint- pointer server
  • src-subprojects-are-independent — sub-projects are independent; no cross-repo imports

License

MIT (private release, publishConfig.access = restricted).