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
what it does#
- two separate PDSes exchange messages via custom XRPC endpoints (
xyz.fake.inbox.*) - first contact requires acceptance, like DM requests
- sender's PDS signs messages with service auth JWTs
- recipient's PDS verifies by resolving sender's DID via plc.directory
- separate labeler service can mark senders as spam
run locally#
git submodule update --init
bun install
bun dev
architecture
this demo uses two separate PDS deployments for real cross-server messaging:
- alice's PDS:
pds-message-demo.nate-8fe.workers.dev - bob's PDS:
pds-message-demo-2.nate-8fe.workers.dev - spam labeler:
did:plc:x6io7svnbth4pikg2e63vvkx
browser bob's PDS alice's PDS
│ │ │
│ 1. createSession(bob) │ │
│ ──────────────────────────>│ │
│ <- accessJwt │ │
│ │ │
│ 2. getServiceAuth │ │
│ (aud=alice's DID) │ │
│ ──────────────────────────>│ │
│ <- service JWT │ (signs w/ bob's key) │
│ │ │
│ 3. xyz.fake.inbox.send │ │
│ + service JWT ─────────────────────────────────────>│
│ │ │
│ │ 4. resolve bob's DID │
│ │ via plc.directory │
│ │ 5. verify JWT │
│ │ 6. check spam label │
│ │ 7. deliver/queue │
│ <- {status: ...} │ │
alice's PDS verifies bob's identity by resolving his DID via plc.directory - no way to forge the sender.
deploy
each PDS worker requires two secrets set via wrangler:
wrangler secret put PDS_PASSWORD --config pds-alice.toml
wrangler secret put JWT_SECRET --config pds-alice.toml
wrangler secret put PDS_PASSWORD --config pds-bob.toml
wrangler secret put JWT_SECRET --config pds-bob.toml
then deploy:
wrangler deploy --config pds-alice.toml
wrangler deploy --config pds-bob.toml
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 to alice → request created → alice accepts → message delivered. subsequent messages from bob deliver immediately.
what's real
| component | implementation |
|---|---|
| PDSes | two pds.js deployments 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 |
| labeler | real ATProto labeler with secp256k1 signing |
| cross-PDS messaging | bob's PDS signs, alice's PDS verifies via DID resolution |
pds.js modifications
our fork adds:
xyz.fake.inbox.*XRPC endpoints (send, list, listRequests, accept, reject, unblock, getState)- inbox tables in SQLite schema
- PLC resolution for DID → public key during JWT verification
com.atproto.server.getServiceAuthfor server-side JWT signing- spam labeler check before message delivery
references
- jacob.gold's thread
- pds.js - cloudflare workers PDS
- AT Protocol and SMTP - ngerakines on PDS as crypto service
- bourbon protocol - invitation-based messaging
- How Streamplace Works - embedded PDS pattern