pds-message-poc#
interactive browser demo of PDS-to-PDS message passing.
inspired by jacob.gold's post about PDSes having incoming message queues for DMs.
live demo: sites.wisp.place/zzstoatzz.io/pds-message-poc
architecture#
this demo uses a real PDS deployment:
- pds.js fork deployed to Cloudflare Workers at
pds-message-demo.nate-8fe.workers.dev - real DIDs registered with plc.directory
- real service auth - JWTs signed server-side via
com.atproto.server.getServiceAuth - real signature verification - recipient PDS resolves sender DID via PLC to get public key
┌─────────────────┐ ┌─────────────────┐
│ Browser │ │ PDS Worker │
│ (demo UI) │ │ (Cloudflare) │
├─────────────────┤ ├─────────────────┤
│ │ 1. createSession(bob) │ │
│ │ ────────────────────────────>│ bob's DO │
│ │ ← accessJwt │ │
│ │ │ │
│ │ 2. getServiceAuth(aud=alice)│ │
│ │ ────────────────────────────>│ signs JWT │
│ │ ← service JWT │ server-side │
│ │ │ │
│ │ 3. inbox.send + JWT │ │
│ │ ────────────────────────────>│ alice's DO: │
│ │ │ - resolve DID │
│ │ │ - verify sig │
│ │ │ - check spam │
│ │ │ - deliver/queue│
│ │ ← {status: ...} │ │
└─────────────────┘ └─────────────────┘
│
▼
┌───────────────┐
│ plc.directory │
│ (DID → pubkey)│
└───────────────┘
run locally#
git submodule update --init
npm install
npm run dev
usage#
- type a message, select sender → recipient
- send - initiates message (first message creates a request)
- accept - recipient accepts pending request, messages flow freely
- reject - recipient rejects request and blocks sender
- spam - labeler marks sender as spam (rejected by all PDSes)
invitation flow#
first contact requires acceptance (like DM requests):
- bob sends message to alice → creates request (message held)
- alice sees request in her "requests" section
- alice clicks accept → original message delivered, bob now accepted
- subsequent messages from bob deliver immediately (subject to rate limits)
alternatively:
- alice clicks reject → request deleted, bob blocked permanently
what's real#
| component | implementation |
|---|---|
| PDS | pds.js fork on Cloudflare Workers |
| DIDs | real did:plc registered with plc.directory |
| service auth | server-side JWT signing via com.atproto.server.getServiceAuth |
| signature verification | PLC resolution → public key → ES256 verify |
| invitation flow | persistent in Durable Object SQLite |
| block list | persistent per-user |
what's demonstrated#
| feature | implementation | ATProto pattern |
|---|---|---|
| service auth | JWT with iss/aud/exp/lxm | com.atproto.server.getServiceAuth |
| invitation flow | pending/accepted sets | similar to chat.bsky.convo request status |
| reputation | labeler with spam labels | com.atproto.label |
| block list | per-user set | existing pattern |
| rate limiting | per-sender, time-windowed | existing pattern |
what's simplified#
| component | current | path to production |
|---|---|---|
| labeler | in-memory (browser) | ozone |
| accounts | 3 demo users (alice/bob/charlie) | real account creation |
| encryption | none (messages in plaintext) | E2EE layer |
pds.js modifications#
our fork adds:
xyz.fake.inbox.*XRPC endpoints (send, list, listRequests, accept, reject)- inbox tables in SQLite schema
- PLC resolution for DID → public key during JWT verification
com.atproto.server.getServiceAuthfor server-side JWT signing
prior art#
- AT Protocol and SMTP - ngerakines on PDS as crypto service
- bourbon protocol - invitation-based messaging
- How Streamplace Works - embedded PDS pattern
references#
- jacob.gold's thread
- pds.js - cloudflare workers PDS
- official PDS
- service auth
- AT Protocol specs