Browser-based peer-to-peer chat using Helia (IPFS) + libp2p PubSub.
- Real-time chat over libp2p PubSub.
- Each room maps directly to a PubSub topic string (e.g.
helia-examples/chatroom). - Users can:
- Join/create rooms by topic name.
- Switch between rooms.
- Send and receive plain-text messages in each room.
- Messages are kept in memory per room for the current session only (no durable chat history yet).
- No automatic room discovery via DHT/manifests yet.
- Manual peer dial is supported via multiaddr input.
- Peer connections are tracked and shown in diagnostics/member views.
- App will attempt opportunistic auto-dial when it receives a message from an unknown sender peer (rate-limited), to improve mesh connectivity.
- Helia/libp2p node data is persisted in IndexedDB:
- libp2p datastore:
libp2p - helia datastore:
helia - blockstore:
helia-blockstore
- libp2p datastore:
- This gives a stable local Peer ID across reloads, unless browser site data is cleared.
- Default chat nickname is derived from local Peer ID (e.g.
anon-xxxxxxxx) and can be changed in UI.
- Main chat interface with:
- rooms sidebar
- chat workspace
- members sidebar
- Mobile drawer support (gesture + toggle based).
- Diagnostics panel:
- subscribed topics
- known subscribers in active topic
- connected peer IDs
- Debug log panel for local runtime events (connect/disconnect, subscribe/publish, dial attempts).
- Additional routes:
/aboutfor project/history metadata/settingsfor local settings storage UI
- Messaging is currently plaintext.
- No room-level auth, permissions, or access control.
- Anyone with topic knowledge + connectivity can join and read traffic.
- No end-to-end encryption or history encryption yet.
- DHT-backed room/member discovery.
- Manifest-based room identity.
- Durable chat history (local or distributed).
- OrbitDB integration (history, ACLs, accounts).
- Key rotation and encrypted history flows.
- Introduce a room manifest document as the canonical room definition.
- Room identifier format:
/<chatroom>/<manifest-cid>. - Manifest should hold metadata (display name, topic strategy, optional linked resources).
- Peers announce room membership by providing the manifest CID/multihash.
- Peers query providers for that manifest and dial discovered peers.
- This becomes the primary room join/discovery flow (instead of manual-only dialing).
- On receiving messages from previously unknown peers, clients may dial them when local PubSub peer count is low.
- Current opportunistic auto-dial behavior can evolve into policy-driven mesh maintenance.
- History DB for chat logs.
- Permissions DB for ownership/ACL rules (mutable or immutable models).
- Sub-room manifests and cross-linking rules so sub-rooms cannot be safely reused across unrelated parent rooms.
- DB rotation support to cap growth and reduce blast radius of partial data loss.
- Support encrypted history and (optionally) encrypted live room payloads.
- Key rotation on membership changes to gate historical access for removed users.
- Portable account identity/state across devices.
- Device onboarding that grants room access without manually authenticating each libp2p node independently.
- Room invite payloads (manifest CID + bootstrap peers + optional capability token).
- Connection manager policy (target peer counts, backoff, relay preference).
- Message envelope versioning (
v,roomId,sender,ts,sig, payload type) for migration safety. - Signed manifests to prevent room metadata spoofing.
- Moderation primitives (mute, ban list, role grants) layered on permissions DB.
- Offline outbox + resend for unstable connectivity.
- Telemetry hooks (local-only by default) for debugging mesh health and message propagation.
- Node.js + npm
npm install
npm run devnpm run buildThe publish flow is implemented in scripts/publish.ts and is executed by:
npm run publishThis command:
- Builds
dist/and packs it intodist.car(npm run build:car). - Imports the CAR into Kubo and gets a root CID.
- Copies that CID into MFS at
/self/ipfs-chat/<branch>/<describe>[_<timestamp-if-dirty>]. - Prints terminal QR code output:
- IPFS only when no IPNS key is provided.
- IPFS + IPNS side-by-side when IPNS is enabled.
IPNS publish only runs when a key/identity is provided by either CLI parameter or environment variable.
- CLI aliases:
--ipns-key,--key,--identity,-k - Env vars:
IPNS_KEY,IPNS_KEY_NAME,IPNS_IDENTITY
If no key is provided, the script skips IPNS and still completes the IPFS publish flow.
When an IPNS key is provided but does not exist in Kubo:
- The script prompts for confirmation before creating it.
- If declined, the script aborts.
- If no interactive TTY is available, it fails rather than silently creating a key.
When IPNS is enabled, the script resolves the current IPNS target first.
- If the current IPNS record already points to
/ipfs/<new-cid>(or a subpath under it), publish is skipped. - Otherwise, it publishes a new IPNS record.
KUBO_URL(default:http://127.0.0.1:5001)GATEWAY_URL(default:http://localhost:8080/)REPO_ROOT(default: current working directory)
IPFS only:
npm run publishIPFS + IPNS via CLI key name:
npm run publish -- --ipns-key ipfs-chat-mainIPFS + IPNS via environment variable:
IPNS_KEY=ipfs-chat-main npm run publishnpx playwright install
npm run test