Two encrypted chat clients built on the chat-xdk WASM binding: an
interactive browser app and a headless bot. Encryption runs in JavaScript (in
the browser, or in Node for the bot); REST calls go through the XDK
(@xdevplatform/xdk).
- Browser app (
public/) — an interactive encrypted chat client. All crypto runs in the browser via WASM; a tiny dev server (server.mjs) relays encrypted blobs to the X Chat API with the XDK, so the access token never reaches the page. - Headless bot (
src/bot.mjs) — the automated counterpart that auto-replies.
| File | Purpose |
|---|---|
src/chat-core.mjs |
All encryption logic — the only file that touches the WASM binding. |
src/x-api.mjs |
The X Chat API I/O layer (XDK client). |
src/bot.mjs |
The receive → decrypt → reply → encrypt → send loop. |
src/register.mjs |
One-time public-key registration (Juicebox-backed). |
server.mjs |
Dev server: serves the browser app + proxies the X API. |
public/ |
The browser chat UI. |
test/chat-core.test.mjs |
Offline test driving the real binding (no mocks). |
- Node.js 18+
- Rust +
wasm-packto build the WASM package (crates/wasm/pkg/is not committed): runnpm run build:wasmonce before the offline test or the browser app. The example then imports the chat-xdk WASM wrapper from this repo by relative path; for a standalone project, install the package and import from@xdevplatform/chat-xdkinstead. The REST client uses@xdevplatform/xdk(npm install @xdevplatform/xdk).
npm run build:wasm # once per Rust change; needs Rust + wasm-pack
node --testThe test imports the fixture private keys, checks the binding reproduces the committed public-key/signature vectors, and runs a real encrypt → decrypt round-trip plus a conversation-key ECIES round-trip.
cp .env.example .env # set X_ACCESS_TOKEN
npm run build:wasm # build a browser (web-target) WASM package
npm start # serve on http://localhost:8787Paste a private key blob (or generate one in the page — see the note below), enter a conversation ID, and chat.
cp .env.example .env # set X_ACCESS_TOKEN, CHAT_CONVERSATION_ID, CHAT_PRIVATE_KEYS_B64
npm run botThe bot echoes messages back (pong for ping, otherwise You said: …).
Customize generateReply in src/bot.mjs to change the reply logic.
Before a bot can send or receive, its public key must be registered on the account. This is a rate-limited write (only a few per 24h), so run it once:
npm run build:wasm # once per Rust change
X_ACCESS_TOKEN=... CHAT_PIN=... npm run register -- --confirmThe browser-safe WASM binding has no key export, so — unlike the other
language examples — this persists the identity in Juicebox under CHAT_PIN
(required) instead of a local blob; the bot then recovers it with unlock(pin).
It needs the optional juicebox-sdk peer dependency installed. A brand-new
account works too: the account's juicebox_config is created by the
public-key POST itself, so the script creates the chat with no config,
generates keys, POSTs the public key, then fetches the config and stores the
keys with updateConfig + setup(pin). Before POSTing, it checks whether the
key is already on the account and skips the write if so; on HTTP 429 it stops
and prints when the window resets rather than retrying. A marker under
state/ records that registration is done (--force overrides to mint a new
identity). state/ is gitignored — never commit key material.
The wasm32-unknown-unknown target has no system clock, so the binding reads the
key version from JavaScript's Date.now(). Key generation (generateKeypairs),
conversation-key creation (prepareConversationKeyChange), encryption, and both
decrypt paths therefore all run directly in the browser — the sample is fully
self-contained and never needs another language to mint keys.
MIT