Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

2 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

@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.ts β€” point- pointer server
  • src-subprojects-are-independent β€” sub-projects are independent; no cross-repo imports

License

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

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages