this repo has no description sites.wisp.place/zzstoatzz.io/pds-message-poc
pds messaging
Svelte 63.7%
JavaScript 30.9%
CSS 3.7%
HTML 1.6%
8 1 0

Clone this repository

https://tangled.org/zzstoatzz.io/pds-message-poc https://tangled.org/did:plc:xbtmt2zjwlrfegqvch7fboei/pds-message-poc
git@tangled.org:zzstoatzz.io/pds-message-poc git@tangled.org:did:plc:xbtmt2zjwlrfegqvch7fboei/pds-message-poc

For self-hosted knots, clone URLs may differ based on your setup.

Download tar.gz
README.md

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):

  1. bob sends message to alice → creates request (message held)
  2. alice sees request in her "requests" section
  3. alice clicks accept → original message delivered, bob now accepted
  4. 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.getServiceAuth for server-side JWT signing

prior art#

references#