This commit is contained in:
Evgeny @ SimpleX Chat
2026-05-11 18:03:31 +00:00
parent 1623aff87f
commit 8fe93fa63b
2 changed files with 853 additions and 71 deletions
+231 -71
View File
@@ -127,48 +127,144 @@ Signal could build a widget, but their conversations are 1:1. Customer wouldn't
- Can I continue this conversation later?
- Can I take this conversation with me to the app?
**Requirements:**
**Requirements (MVP):**
- No app install, no account creation
- Clear privacy indicator -- "encrypted" badge or similar
- Standard chat UX -- typing indicators, read receipts
- File/image sharing for support scenarios
- Notification when response arrives (browser notification permission)
- Session persistence across page navigation on same site
- Migration path to SimpleX app via QR code
- Graceful degradation -- clear message if connection fails
- Accessibility -- screen readers, keyboard navigation
- Clear privacy indicator -- "via SimpleX Network"
- Standard chat UX -- sent/delivered checkmarks
- File sharing via upload page link
- Session persistence across tabs and sessions
- Graceful degradation -- retry on failure
**Requirements (Post-MVP):**
- Migration path to SimpleX app
- In-widget file upload
- Typing indicators
- Full accessibility support
## Product Flow
### Site Owner Setup
1. Create SimpleX contact address or deploy support bot
1. Create SimpleX business address or deploy support bot
2. Add script tag to site with address parameter and version hash
3. Optionally customize appearance
3. Optionally set accent color and dark mode sync
4. Done
Recommended: Deploy open-source support bot that auto-accepts contact requests and routes to agents. We provide the bot.
### Visitor Experience
1. Visit site, see chat bubble
2. Click bubble, chat window opens
3. Type message, send
4. If bot: immediate connection, conversation starts
5. If manual address: widget shows "connecting..." with option to enable notification
6. Conversation proceeds in browser
7. If visitor wants to continue on app: scan QR code to migrate conversation
**Widget closed:**
- Chat bubble with shield icon in bottom-right
- Text: "Talk to us privately via SimpleX Network"
### Migration to App
**Widget opens (not connected yet):**
- Business name, logo, welcome message (from address data)
- Name field with placeholder "name or pseudonym"
- Incognito button (mask icon) to generate random name
- Message entry field
- Visitor sees name before sending -- conscious choice
Visitor can scan QR code from browser widget. This transfers:
- Conversation history
- Connection to site owner
- Encryption keys
**Send tapped:**
- First message included in connection request (single action)
- Single checkmark = sent, double checkmark = delivered
- "Keep this tab open to receive replies" instruction
- "Notify me when [Business] replies" button (browser notification)
- If SMP router down: retry button
Conversation continues in SimpleX Chat app with full durability.
**Conversation:**
- Standard chat UI
- Agent joins shown as pronounced system message
- Replies/reactions from site owner displayed (visitor can't create in MVP)
- Conversation persists across sessions and tabs
- Same conversation in all tabs on same site
**Mobile:**
- Full screen when expanded
- Back button to return to bubble
**Ending conversation:**
- Visitor can delete -- always notifies owner, reverts to fresh state
- Owner deletes -- visitor sees notification (if sent) or delivery fails on next send
- History preserved on visitor side until visitor deletes
## UX Specification
### Widget Closed (Bubble)
- Pointy bubble shape with shield icon, no SimpleX logo
- Text: "Talk to us privately via SimpleX Network"
- Position: bottom-right
- Unread indicator when response waiting
### Widget Opens
Connection happens on SEND, not on open. Widget shows:
- Business name, logo, welcome message (from address data)
- Name field with placeholder "name or pseudonym"
- Incognito button (mask icon) fills random name from reduced dictionary
- User sees random name before sending -- transparency, conscious choice
- Message entry field
### Send Tapped
- First message included in connection request (single action)
- "Notify me when [Business] replies" button above entry
- "Keep this tab open to receive replies" instruction
- If SMP router down: retry button
### Conversation
- Single checkmark = sent, double checkmark = delivered
- No typing indicators (bot is instant, mental model is async)
- No read receipts (SimpleX doesn't support them)
- Agent joins: pronounced system message ("Alex joined the conversation")
- Replies and reactions from site owner displayed; visitor cannot create them in MVP
- File sharing: link to simplex.chat/file upload page
### Persistence
- Conversation persists across sessions until visitor explicitly deletes
- Same conversation across all tabs on same site
- Return visitor sees history immediately
### Conversation End
**Owner ends:**
- Owner "deletes contact" in SimpleX Chat
- If owner notifies: visitor sees notification
- If owner deletes quietly: visitor discovers on next send (delivery fails)
- Visitor still sees history until they delete
**Visitor ends:**
- Always notifies owner (no quiet removal)
- Reverts to fresh state
- Can start new conversation immediately
### Mobile
- Full screen when expanded
- Chat bubble when closed
- Standard touch targets (48px minimum)
- Back button / X to return to bubble
### Customization
- Accent color matches site
- Dark mode with API to sync with site's dark mode
- Welcome message from business address data
- Position: bottom-right (configurable post-MVP)
### Notifications (MVP)
- "Keep this tab open to receive replies"
- Browser notification permission for in-tab alerts
- Web Push via notification router is post-MVP
## Trust Model
@@ -188,63 +284,127 @@ Site owner controls the JS. Visitor trusts site owner not to exfiltrate messages
The architectural guarantee: no party other than visitor and site owner can read the messages. SimpleX network, hosting providers, CDNs, ad networks -- none of them have access.
## MVP Scope
### In MVP
**Widget UI:**
- Chat bubble with shield icon, "Talk to us privately via SimpleX Network"
- Business name, logo, welcome message from address data
- Name field with incognito button (mask icon)
- First message in connection request
- Single/double checkmark for sent/delivered
- Agent join notifications (pronounced)
- Display replies and reactions from site owner
- "Keep this tab open" instruction
- Dark mode with API to sync with site
- Accent color customization
- Full screen on mobile
**Persistence:**
- Same conversation across sessions and tabs
- History preserved until visitor deletes
- Visitor deletion notifies owner
**File sharing:**
- Link to simplex.chat/file or site's upload page
**Documentation:**
- Setup guide for JS integration
- Bot setup guide
### Post-MVP
- Web Push notifications (via notification router + UnifiedPush protocol)
- Migration to app (QR code transfer)
- In-widget file upload
- Visitor replies and reactions
- Typing indicators
- Full accessibility support
- Full theme customization
- Position options
### First Adopters
1. simplex.chat
2. Evgeny's personal site
3. Friendly community sites
## Resolved Questions
### Spam/Abuse
Bot with captcha protection, same as directory groups. Not immediate concern at current network stage.
### Multi-tab
Same conversation across all tabs. Tech design will determine storage mechanism.
### Notifications (MVP)
"Keep this tab open" instruction. Web Push is post-MVP.
### Branding
"via SimpleX Network" visible. No white-label option -- branding IS the value.
## Business Model
The widget can be free or paid, at the discretion of each SMP router operator.
**How it works**: Browser WebSocket connections include an `Origin` header (browser-enforced, not spoofable). The SMP server sees which website is embedding the widget. The server controls access via CORS — it returns `Access-Control-Allow-Origin` only for allowed domains. Without the correct CORS header, the browser blocks the connection.
**What the server sees**: origin domain (which site) and usage volume (number of connections/commands). The server does NOT see who the users are, message content, or any per-user information.
**Billing model options for router operators**:
- Free for all domains (open access)
- Free with trial period, paid after (per-domain)
- Paid from start (per-domain, usage-based)
- Allowlist only (specific domains)
**Implementation**: server checks `Origin` header against configured domain list during WebSocket upgrade. Allowed domains get CORS headers and proceed. Unknown domains are rejected. Trial expiry, usage limits, and billing are operator concerns — the protocol layer just provides the access control mechanism.
This enables SimpleX router operators to monetize widget hosting without compromising user privacy — billing is per-site, not per-user.
## Privacy Model
**What SMP router operators can observe**:
- Which sites embed the widget (Origin header)
- Usage volume per site (connection/command counts)
- Visitor IP addresses (from TCP connections)
- Connection timing and patterns
**What they cannot observe**:
- Message content (end-to-end encrypted)
- User identities (no accounts, no cookies)
- Which queues belong to which visitors
**Visitor IP addresses**: SMP servers can observe visitor IP addresses from WebSocket connections. Visitors concerned about IP privacy should use Tor or a VPN.
## Open Questions
### Spam/Abuse Mitigation
Open contact is open to abuse. Primary mitigation: bot with captcha protection, same pattern as directory groups.
Current network state: spam happens in groups but not in direct contacts yet. This may change as network grows, but not an immediate concern.
Additional options if needed:
- Rate limiting at widget level
- Proof-of-work before connect
### Team Workflow
Real support teams have multiple agents, shifts, handoffs. The bot handles routing. But:
- Are all agents using SimpleX Chat app?
- Is there a dashboard for teams?
- How do handoffs work?
### Multi-tab / Multi-device
Visitor opens site in two tabs, or switches from phone to desktop. What happens?
- Ephemeral keys per tab? Conversation isolated.
- Shared keys via localStorage? Tabs can conflict.
- Server-side session? Adds complexity.
### Offline Message Queueing
Visitor sends message, site owner offline. Options:
- Message queued at SMP router (standard SimpleX behavior)
- Widget shows "message sent, waiting for response"
- Visitor enables notification, closes tab, gets notified when response arrives
- Agents use SimpleX Chat app
- Bot handles routing and escalation
- Dashboard is out of scope (agents use app)
### Trust Verification
Visitor sees chat bubble. How do they know this is actually SimpleX?
- Visual indicator in widget
- Link to verify on simplex.chat
- Certificate/signature verification (complex in browser)
- "via SimpleX Network" text provides some verification
- Link to simplex.chat for more info
- Full verification is complex in browser -- acceptable tradeoff
## Success Metrics
**Site Owner:**
**Measurable without tracking:**
- Number of sites embedding widget (observable)
- Integration time < 5 minutes
- Zero support requests for setup
- Uptime > 99.9%
**Visitor:**
- Time to first message < 10 seconds
- Conversion to app install (tracked via QR scan)
- Return visitor rate
**Not measurable (would require tracking that contradicts value proposition):**
- Message counts
- App conversion rates
- Visitor behavior
**Ecosystem:**
- Number of sites using widget
- Messages per day through widget
- New SimpleX app installs attributed to widget
We accept limited visibility as a feature, not a bug.
## Competitive Landscape
@@ -357,10 +517,10 @@ The customers we refuse are as important as the customers we serve. Trying to se
1. **Browser limitations** -- No TLS certificate pinning. Trust browser's CA system. Acceptable for threat model.
2. **Ephemeral by default** -- Visitor closes browser, conversation gone (unless migrated). Feature or bug depends on use case.
2. **Persistence complexity** -- Conversation must persist across sessions and sync across tabs. Storage mechanism TBD in tech design.
3. **JS supply chain** -- Site owner embeds our JS. Mitigated by including hash in script tag (Subresource Integrity). Browser refuses to execute if hash doesn't match. Self-host option also available.
4. **Adoption chicken-egg** -- Visitors don't know what SimpleX is. Widget must explain value without friction.
4. **Adoption chicken-egg** -- Visitors don't know what SimpleX is. "via SimpleX Network" text helps, but brand recognition takes time.
5. **Support complexity** -- We're building infrastructure for support teams. They have expectations from Intercom/Zendesk. We can't match feature parity, shouldn't try.
5. **Background tab limitations** -- Browsers throttle/suspend background tabs. MVP uses "keep tab open" instruction. Post-MVP may add Web Push via notification router.
+622
View File
@@ -0,0 +1,622 @@
# SimpleX Web Widget: Master Plan
Revision 1, 2026-03-15
**Status**: Planning complete. Spike next.
**Related documents**:
- [Product Plan](./2026-03-15-simplex-web-widget-product.md) -- Users, UX, scope
- [Spike Plan](./2026-03-15-simplex-web-widget/2026-03-15-simplex-web-widget-spike.md) -- LGET proof of concept
## Overview
This document defines what needs to be built for the web widget at each layer. It is a master plan -- high-level structure, not exhaustive function mapping.
The widget runs entirely in browser. It connects to SMP routers via WebSocket, implements the subset of agent functionality needed for messaging, and provides chat-level features (connection to any address type, message display, delivery status).
**Layers:**
```
┌─────────────────────────────────────────┐
│ Widget UI (React/Preact) │ ← User interaction
├─────────────────────────────────────────┤
│ Chat Layer (TypeScript) │ ← Address types, message handling
├─────────────────────────────────────────┤
│ Agent Layer (TypeScript) │ ← Connection lifecycle, encryption
├─────────────────────────────────────────┤
│ SMP Client Layer (TypeScript) │ ← WebSocket transport, protocol
└─────────────────────────────────────────┘
▼ WebSocket (TLS)
┌───────────────┐
│ SMP Router │
└───────────────┘
```
## Phase 0: Spike
**Goal**: Prove the whole thing works at minimum cost. No production code -- throwaway.
### What the Spike Does
1. Parse contact/business address URI
2. Open WebSocket to SMP router (direct, no proxy)
3. Send LGET command to fetch link data
4. Decrypt profile data
5. Display in browser console
### What This Proves
- WebSocket transport to SMP router works from browser
- Protocol encoding is correct (LGET parses, response decodes)
- Crypto is compatible (decryption works)
- The whole thing is buildable
### What Spike Does NOT Need
- Sending connection request (more complex, doesn't add to proof)
- SKEY/SEND commands
- Proxy/private routing
- Double ratchet
- Persistence
- Any UI
### Deliverable
- Browser console demo: paste address URI, see profile data
- Throwaway TypeScript code
- Notes on what was tricky (encoding, crypto, WebSocket framing)
## Phase 1: Router Infrastructure
**Prerequisite for spike.** SMP routers need WebSocket upgrade support for web clients.
### Current State
- `Transport/WebSockets.hs` exists but runs in exclusive mode
- Router is EITHER WebSocket OR normal HTTP -- not both
- Info page served on HTTP, no upgrade path for browser JS
### Required Changes
1. **WebSocket upgrade on same port**
- HTTP request with `Upgrade: websocket` header triggers upgrade
- Normal browser navigation still shows info page
- Same TLS connection, just protocol switch
2. **CORS headers**
- `Access-Control-Allow-Origin` for widget domains
- Allow cross-origin WebSocket connections
3. **No service certificate changes**
- Widget uses regular client auth, not service certs
- No changes needed for MVP
### Implementation Location
- `Simplex.Messaging.Server` -- HTTP handler modification
- `Simplex.Messaging.Transport.WebSockets` -- already has WebSocket logic
- Need to bridge: HTTP request → check upgrade → WebSocket transport
### Deliverable
- SMP routers accept WebSocket upgrade from browsers
- Info page still works for direct navigation
- CORS configured for widget embedding
## Phase 2: SMP Client Layer (TypeScript)
Minimal protocol client that can send/receive SMP commands over WebSocket.
### Transport
- WebSocket connection with TLS (browser handles certificate validation)
- Block framing: 16384 byte blocks, same as native client
- Reconnection with exponential backoff
### Protocol Subset
Commands needed for widget:
| Command | Purpose |
|---------|---------|
| NEW | Create receive queue (for visitor's side of connection) |
| KEY | Register sender key on receive queue |
| SKEY | Authenticate as sender on queue |
| SUB | Subscribe to queue for messages |
| ACK | Acknowledge message receipt |
| SEND | Send message to queue |
| OFF | Disable queue (when visitor deletes conversation) |
| DEL | Delete queue |
Proxied messaging (private routing):
| Command | Purpose |
|---------|---------|
| PRXY | Request proxy session to destination router |
| PFWD | Forward command through proxy |
### Correlation
- Generate correlation IDs for commands
- Match responses to pending requests
- Timeout handling for unresponsive routers
### Deliverable
- `SMPClient` class: connect, disconnect, send command, receive response/event
- Connection pooling (one connection per router)
- Automatic reconnection
## Phase 3: Agent Layer (TypeScript)
Connection lifecycle and encryption. This is the core complexity.
### Key Management
- Generate X25519 key pairs (DH for queue auth)
- Generate X448 key pairs (PQ-resistant ratchet init)
- Store keys in IndexedDB (encrypted with derived key from random secret in localStorage)
### Connection Establishment
Support joining via invitation URI (the common case for widget):
1. Parse invitation URI (extract queue address, DH keys)
2. Create local receive queue (NEW)
3. Initialize sending ratchet (X3DH key agreement)
4. Send confirmation to inviter's queue (SKEY + SEND)
5. Wait for reply with inviter's queue info
6. Complete ratchet initialization
7. Exchange HELLO messages
8. Connection ready
Also support creating invitation (for future use cases):
1. Create receive queue (NEW)
2. Generate invitation URI with queue address + DH keys
3. Wait for joiner's confirmation
4. Complete connection handshake
### Double Ratchet
- Implement Signal double ratchet for message encryption
- Header encryption (prevents metadata leakage)
- Ratchet state persistence in IndexedDB
- Key derivation using HKDF
### Message Processing
Receive path:
1. Decrypt with double ratchet
2. Verify message integrity (sequence + hash chain)
3. Store in local database
4. Emit event to chat layer
5. ACK to router (can be batched)
Send path:
1. Advance ratchet, store pending message with encryption key
2. Encrypt body with stored key
3. Encode agent message envelope
4. SEND to router
5. On OK, mark as sent; on delivery receipt, mark as delivered
### Persistence
- IndexedDB for: keys, ratchet state, messages, connection state
- Cross-tab coordination via BroadcastChannel or SharedWorker
- Single "active" tab owns WebSocket connections, others receive via broadcast
### Deliverable
- `AgentClient` class: createConnection, joinConnection, sendMessage, ackMessage
- Event emitter for received messages and status updates
- Persistence layer with cross-tab sync
## Phase 4: Chat Layer (TypeScript)
Address type handling and message semantics.
### Address Types
Widget must handle any SimpleX address type:
1. **Contact address** -- 1:1 conversation with site owner
2. **Group link** -- Join group, see all members
3. **Business address** -- Hybrid: looks like 1:1 to visitor, but is group internally
All three use same underlying protocol. Difference is in:
- How connection is established (contact request vs group join)
- What metadata is shown (single owner vs multiple agents)
- Message attribution (who sent what)
### Connection Flow by Type
**Contact address:**
- Parse contact URI
- Join connection with chosen display name
- First message included in connection request
- Owner accepts, connection established
**Group link:**
- Parse group link URI
- Join group with chosen display name
- Receive group info, member list
- Can see and message all members
**Business address:**
- Parse business address URI (same format as contact)
- Join with display name
- Internally creates business chat (group with special properties)
- Visitor sees business name, may see individual agent names on messages
- Agents can join/leave without visitor needing to know
### Message Types
Display in widget:
| Type | Handling |
|------|----------|
| Text | Display as message bubble |
| Agent join | System message: "Alex joined the conversation" |
| Delivery receipt | Update checkmarks (single → double) |
| File reference | Link to upload page |
| Reaction | Display on referenced message |
| Reply | Display with quoted content |
Create in widget (MVP):
| Type | Handling |
|------|----------|
| Text | Primary message type |
| Delete notification | When visitor deletes conversation |
Post-MVP:
| Type | Handling |
|------|----------|
| Reaction | Visitor can react to messages |
| Reply | Visitor can quote messages |
| File | Direct upload |
### State Management
- Current connection state (disconnected, connecting, connected)
- Message list with delivery status
- Unread count for bubble indicator
- Business/contact metadata for display
### Deliverable
- `ChatClient` class: connect to address, send message, receive events
- Address parsing for all three types
- Message rendering helpers
## Phase 5: Widget UI
React or Preact component library. Smallest reasonable bundle.
### Components
- `ChatBubble` -- Collapsed state, unread indicator
- `ChatWindow` -- Expanded state, message list, input
- `ConnectionScreen` -- Name input, incognito button, first message
- `MessageList` -- Scrolling message display
- `MessageInput` -- Text entry, send button
- `SystemMessage` -- Agent joins, connection status
### Customization API
```typescript
interface WidgetConfig {
address: string; // SimpleX address URI
accentColor?: string; // Hex color for buttons, links
darkMode?: boolean | 'auto'; // Sync with site or explicit
position?: 'bottom-right'; // MVP: only bottom-right
welcomeMessage?: string; // Override from address data
}
```
### Embedding
```html
<script
src="https://simplex.chat/widget.js"
data-address="simplex:/contact#..."
data-accent="#0066cc"
integrity="sha384-..."
crossorigin="anonymous"
></script>
```
Script tag creates widget automatically. Integrity hash ensures version pinning.
### Self-hosting
Site owners can host `widget.js` themselves for maximum trust. Same API, different src.
### Deliverable
- Minimal component library
- CSS-in-JS or minimal CSS (no external dependencies)
- Bundle size target: < 100KB gzipped (including crypto)
## Design Principles
### Mirror Haskell Structure
TypeScript code should model Haskell function names and module structure:
- `Simplex.Messaging.Protocol``protocol.ts` with same type/function names
- `Simplex.Messaging.Parsers``parser.ts`
- `serializeSMPCommand` in Haskell → `serializeSMPCommand` in TypeScript
- `smpCommandP` parser → `smpCommandP`
This enables:
- Easy cross-reference between codebases
- Sync as protocol evolves
- Code review by people who know Haskell side
## Prior Art: xftp-web
`../simplexmq-2/xftp-web/` is the production reference for browser TypeScript.
### Direct Reuse
**Crypto** (`src/crypto/`):
- `secretbox.ts` -- XSalsa20-Poly1305 streaming, matches Haskell Crypto.hs
- `digest.ts` -- SHA-256/512 via libsodium
- `keys.ts` -- Ed25519/X25519, DER encoding, key hash
- `identity.ts` -- X.509 certificate verification
**Encoding** (`src/protocol/encoding.ts`):
- `Decoder` class -- sequential binary parser
- Integer encoding (Word16, Word32, Int64 big-endian)
- ByteString, Large, Tail patterns
- Comments reference Haskell line numbers
### Libraries Used
```json
{
"libsodium-wrappers-sumo": "^0.7.13",
"@noble/curves": "^1.4.0"
}
```
libsodium for most crypto, @noble/curves for Ed448 (not in libsodium).
## Prior Art: simplexmq-js
`../simplexmq-js/` contains a working SMP client from ~2021. Protocol has evolved (v0.4 → v19), but patterns transfer.
### Reuse (patterns, not code)
**Parser** (`parser.ts`):
- Combinator-style scanner that avoids string splits
- `&&` chaining models Haskell grammar-based parsers
- Returns `undefined` on failure, enabling clean call sites:
```typescript
MSG: (p) => {
let msgId, msg: Uint8Array | undefined
let ts: Date | undefined
return (
p.space() && (msgId = p.base64()) && p.space() &&
(ts = p.date()) && p.space() && (msg = messageP(p)) &&
cMSG(msgId, ts, msg)
)
}
```
**Protocol types** (`protocol.ts`):
- One type per command, mirrors Haskell ADT
- Constructors: `cNEW`, `cMSG`, `cLGET`, etc.
- Binary tag tables for serialization
**Buffer utilities** (`buffer.ts`):
- Base64 encode/decode
- Binary concatenation
- Integer encoding
**Async bounded queue** (`queue.ts`):
- `ABQueue` with semaphore-based backpressure
- Async iterator protocol -- `for await (const msg of transport)`
- Clean close semantics with sentinel
- This pattern is solid, likely reusable
### Review Before Reuse
**WebSocket transport** (`transport.ts`):
- `WSTransport` is ~30 lines, simple enough
- Connect, wire handlers to ABQueue, wait for open
- May be fine as-is, review during spike
- Custom RSA handshake (`SMPTransport`) no longer applies -- now TLS
- No socket.io needed -- plain WebSocket is universal
### Do Not Reuse
**Crypto** (`crypto.ts`):
- Was RSA-OAEP + AES-GCM
- Now X25519 + XChaCha20-Poly1305
- Use `@noble/*` libraries instead
### Pre-implementation Task
Scan `simplexmq-js` for utilities worth adapting:
- `Parser` class
- `ABQueue` (async bounded queue)
- Buffer helpers
- Type patterns
Document what transfers vs what needs rewrite.
## Haskell Modules to Reference
TypeScript should mirror these modules:
### Protocol Layer (simplexmq)
| Haskell | TypeScript | Purpose |
|---------|------------|---------|
| `Simplex.Messaging.Protocol` | `protocol.ts` | Commands, responses, encoding |
| `Simplex.Messaging.Transport` | `transport.ts` | Handshake, block framing |
| `Simplex.Messaging.Encoding` | `encoding.ts` | smpEncode/smpP patterns |
| `Simplex.Messaging.Parsers` | `parser.ts` | Parser combinators |
| `Simplex.Messaging.Crypto` | `crypto.ts` | Encryption primitives |
| `Simplex.Messaging.Crypto.ShortLink` | `shortLink.ts` | KDF, link encryption |
### Agent Layer (simplexmq)
| Haskell | TypeScript | Purpose |
|---------|------------|---------|
| `Simplex.Messaging.Agent.Protocol` | `agentProtocol.ts` | Connection types, link data |
| `Simplex.Messaging.Agent.Client` | `agentClient.ts` | Connection management |
| `Simplex.Messaging.Crypto.Ratchet` | `ratchet.ts` | Double ratchet |
### Chat Layer (simplex-chat)
| Haskell | TypeScript | Purpose |
|---------|------------|---------|
| `Simplex.Chat.Protocol` | `chatProtocol.ts` | Message types |
| `Simplex.Chat.Types` | `chatTypes.ts` | Profile, contacts |
## Dependencies and Build
### Crypto Libraries
- `@noble/curves` -- X25519, X448, Ed25519 (audited, pure JS)
- `@noble/ciphers` -- ChaCha20-Poly1305, XSalsa20 (audited, pure JS)
- `@noble/hashes` -- SHA-256, SHA-512, HKDF (audited, pure JS)
These are the same libraries used by many crypto projects, well-audited, no WASM.
### Encoding
- CBOR for message encoding (same as native client)
- Custom binary encoding for SMP protocol frames
### Build
- esbuild or Vite for bundling
- Tree-shaking to minimize size
- Separate chunks for core vs UI (allow headless use)
## Testing Strategy
### Unit Tests
- Protocol encoding/decoding
- Crypto operations (encrypt/decrypt round-trip)
- Ratchet state transitions
- Address parsing
### Integration Tests
- Connect to test router
- Full connection establishment
- Message send/receive
- Reconnection handling
### E2E Tests
- Widget embedding in test page
- User flow: open, enter name, send message
- Cross-tab synchronization
- Persistence across page reload
## Milestones
### M0: Spike -- Prove Buildability
**Objective**: Fetch and display business profile from address URI. Minimum code to prove e2e works.
**Scope**:
- Parse address URI
- WebSocket to SMP router (direct, no proxy)
- LGET command
- Decrypt profile data
- Display in console
**Success criteria**: Paste address URI in browser console, see profile data displayed.
---
### M1: Protocol Foundation
- SMP client with WebSocket transport
- Full command set (NEW, KEY, SKEY, SUB, ACK, SEND, OFF, DEL)
- Connection pooling, reconnection with backoff
- Correlation ID handling
### M2: Connection Establishment
- Key generation and storage
- X3DH key agreement
- Join connection via invitation
- HELLO exchange
- Connection state machine
### M3: Encrypted Messaging
- Double ratchet implementation
- Send and receive messages
- Delivery receipts
### M4: Persistence
- IndexedDB storage
- Cross-tab coordination
- Session continuity
### M5: Chat Layer
- All address types
- Message type handling
- Agent join notifications
### M6: Widget UI
- Component library
- Embedding API
- Customization
### M7: Polish
- Error handling
- Reconnection UX
- Performance optimization
- Documentation
## Open Questions
### Encoding Approach
- XFTP web used direct Haskell-to-TypeScript port
- You mentioned showing a different encoding approach -- waiting for that example
### Bundle Size
- 100KB target may be aggressive with full crypto
- Could split: tiny loader + async load main bundle
- Measure actual size after M1
### Browser Support
- Modern browsers only (ES2020+)
- No IE11, no legacy Edge
- Safari WebSocket behavior needs testing
### Proxy Selection
- How does widget choose which proxy router to use for private routing?
- Hardcoded list? Dynamic discovery? Site owner config?
## Not In Scope
- Web Push notifications (post-MVP, uses notification router)
- Migration to app (post-MVP, QR code transfer)
- File upload (post-MVP, in-widget upload)
- Typing indicators (not supported in protocol yet)
- Full accessibility (deferred)
- White-label (counter to value proposition)