mirror of
https://github.com/vercel/chat.git
synced 2026-09-14 18:32:29 +08:00
caa63253c5
## summary new `@chat-adapter/xchat` adapter for XChat, X's encrypted messaging. write bot logic once and hold encrypted 1:1 and group conversations like the other Chat SDK adapters — all crypto handled inside the adapter via `@xdevplatform/chat-xdk` (wasm), all REST via the typed `@xdevplatform/xdk` client. ## background: chat-xdk [`@xdevplatform/chat-xdk`](https://www.npmjs.com/package/@xdevplatform/chat-xdk) is the official XChat cryptography SDK — a Rust core compiled to WebAssembly that implements the XChat encryption protocol. it handles per-conversation symmetric keys and key exchange, message encryption/decryption, event signing and signature verification, and encrypted media (secretstream). the bot's private keys live in a PIN-protected [Juicebox](https://juicebox.xyz) store (secret-shared across independent realms), so no key material sits in env vars or on disk — the adapter unlocks with a PIN at startup. this adapter is the glue: chat-xdk produces and consumes the encrypted envelopes, the typed `@xdevplatform/xdk` client moves them over the X API, and everything is normalized to the Chat SDK's `Thread`/`Message` model. what it supports: - encrypted send/receive in DMs and groups (webhook push + polling), signature verification on by default - mention detection from structured mention entities, swipe-replies to the bot, and a plain-text `@handle` fallback; group replies go out as quoted replies with TTL propagated - `openDM(userId)`: starts (or reuses) an encrypted 1:1 — cached/history-recovered conversation key, else a full key exchange so the bot can message first - media both ways: inbound attachments with lazy download+decrypt, outbound encrypted (secretstream) via the 3-step upload flow - edit and delete of the bot's own messages: edits are encrypted events targeting the original's sequence id; deletes are locally signed delete-for-all actions recipients verify - reactions in and out, typing keep-alive while handlers run, configurable group welcome message - read receipts sent per delivered inbound message (`sendReadReceipts`, default on) - cards by degradation: text + tappable entities, link buttons as `label: url` lines, primary link as a URL preview attachment with optional encrypted banner key design decisions: - mdast stays the canonical format; markdown passes through as raw text (XChat clients render plain text — no markdown), with URLs and @mentions made tappable via entity spans and tables degraded to ASCII code blocks - thread ids are `xchat:{conversationId}` (groups `g…`, 1:1s the sorted participant pair) - the first edit of a fresh message is age-gated (`editSafetyDelayMs`, default 5000ms): receiving clients park an edit whose original hasn't arrived, leaving the message permanently invisible — the gate prevents that race - undecryptable or unverified events are dropped, never delivered as empty messages - no core changes: the adapter implements the standard `Adapter` interface only also includes the `chat/adapters` catalog entry, docs page (with OG image), `adapters.json` registry entry, and `create-chat-sdk` scaffold spec, modeled on the `x` adapter's registration. <details><summary>usage</summary> ```bash XCHAT_BOT_TOKEN=... # OAuth2 user access token (identity resolved from GET /2/users/me) XCHAT_PIN=... # Juicebox PIN that unlocks the bot's keys X_CONSUMER_SECRET=... # optional: verifies webhook signatures ``` ```typescript import { Chat } from "chat"; import { createXchatAdapter } from "@chat-adapter/xchat"; import { createMemoryState } from "@chat-adapter/state-memory"; const bot = new Chat({ userName: "mybot", adapters: { xchat: createXchatAdapter() }, // credentials from env state: createMemoryState(), }); // DMs always bot.onDirectMessage(async (thread, message) => { await thread.post(`You said: ${message.text}`); }); // group chats when the bot is @mentioned bot.onNewMention(async (thread, message) => { await thread.post("You rang?"); }); // wire the webhook (e.g. a Next.js route) export async function POST(request: Request) { return bot.webhooks.xchat(request); } ``` </details> testing: 109 unit tests, including real-wasm-crypto round trips against vendored fixture vectors (decrypt + signature verification, webhook delivery, read receipts, edit age-gating, signed deletes). verified live against production XChat: DMs, group mentions, media, reactions, edits, deletes, openDM, cards. note on the lockfile: `@xdevplatform/xdk@0.6.6` was published <48h ago, so it was resolved with a one-shot `--config.minimumReleaseAge=0` override; the locked integrity hash was verified against the npm registry. the repo policy file is untouched. --------- Co-authored-by: dancer <josh@afterima.ge>
66 lines
1.4 KiB
JSON
66 lines
1.4 KiB
JSON
{
|
|
"$schema": "https://turbo.build/schema.json",
|
|
"globalEnv": [
|
|
"NODE_ENV",
|
|
"SLACK_BOT_TOKEN",
|
|
"SLACK_SIGNING_SECRET",
|
|
"TEAMS_APP_ID",
|
|
"TEAMS_APP_PASSWORD",
|
|
"TEAMS_APP_TENANT_ID",
|
|
"GOOGLE_CHAT_CREDENTIALS",
|
|
"GOOGLE_CHAT_PUBSUB_TOPIC",
|
|
"GOOGLE_CHAT_IMPERSONATE_USER",
|
|
"FACEBOOK_APP_SECRET",
|
|
"FACEBOOK_PAGE_ACCESS_TOKEN",
|
|
"FACEBOOK_VERIFY_TOKEN",
|
|
"WHATSAPP_ACCESS_TOKEN",
|
|
"WHATSAPP_APP_SECRET",
|
|
"WHATSAPP_PHONE_NUMBER_ID",
|
|
"WHATSAPP_VERIFY_TOKEN",
|
|
"TWILIO_ACCOUNT_SID",
|
|
"TWILIO_AUTH_TOKEN",
|
|
"TWILIO_PHONE_NUMBER",
|
|
"TWILIO_MESSAGING_SERVICE_SID",
|
|
"X_API_BASE_URL",
|
|
"X_CLIENT_ID",
|
|
"X_CLIENT_SECRET",
|
|
"X_CONSUMER_SECRET",
|
|
"X_ENCRYPTION_KEY",
|
|
"X_REFRESH_TOKEN",
|
|
"X_USER_ACCESS_TOKEN",
|
|
"X_USER_ID",
|
|
"X_USERNAME",
|
|
"XCHAT_BOT_TOKEN",
|
|
"XCHAT_PIN",
|
|
"X_ACCESS_TOKEN",
|
|
"X_BOT_USERNAME",
|
|
"X_VERIFY_SIGNATURES",
|
|
"X_DISABLE_WEBHOOK_VERIFICATION",
|
|
"X_SIGNING_KEY_VERSION",
|
|
"BOT_USERNAME",
|
|
"REDIS_URL",
|
|
"EDGE_CONFIG"
|
|
],
|
|
"tasks": {
|
|
"build": {
|
|
"dependsOn": ["^build"],
|
|
"outputs": ["dist/**", "docs/**", ".next/**", "!.next/cache/**"]
|
|
},
|
|
"dev": {
|
|
"cache": false,
|
|
"persistent": true
|
|
},
|
|
"test": {
|
|
"dependsOn": ["build"],
|
|
"outputs": []
|
|
},
|
|
"typecheck": {
|
|
"dependsOn": ["^build"],
|
|
"outputs": []
|
|
},
|
|
"clean": {
|
|
"cache": false
|
|
}
|
|
}
|
|
}
|