Bounded SSE result bridge for Glyph Wallet dApp integration, deployed on Cloudflare Workers with Durable Objects.
dApp ──── glyph:// deep link ──────→ wallet (Tauri)
dApp ← SSE /v2/stream/:session/:readCap ─ relay ← POST /v2/callback/:session/:callbackCap ─ wallet
| Method | Path | Description |
|---|---|---|
POST |
/v2/register/:session |
dApp initializes the session with readCap and callbackCap before launching the wallet |
POST |
/v2/callback/:session/:callbackCap |
Wallet posts the signed result here |
GET |
/v2/stream/:session/:readCap |
SSE stream; holds connection until result arrives |
GET |
/v2/result/:session/:readCap |
Polling; returns result or 404 if pending |
GET |
/v1/health, /v2/health |
Health check |
Each relay session gets its own Durable Object instance (RelaySession). This ensures the wallet's POST callback and the dApp's SSE stream hit the same isolate, so they share in-memory state. Results are persisted so they survive eviction, accepted once, and deleted after ten minutes.
SSE connections time out after five minutes. A session has at most five listeners. Edge rate limiting applies before a request creates or reaches a Durable Object, with a per-client, per-action key so generating fresh sessions cannot bypass it.
Protocol v2 uses an init-then-use flow with three URL-safe random values:
session: Durable Object routing key. It is not sufficient to read or write a result.readCap: read capability, prefixed withr_, used only by/v2/streamand/v2/result.callbackCap: callback capability, prefixed withc_, used only by/v2/callback.
The dApp first calls POST /v2/register/:session with JSON { "readCap": "r_...", "callbackCap": "c_..." }. The Durable Object validates prefixes and entropy, stores only SHA-256 hashes of both capabilities, rejects re-registration with 409, and sets a ten-minute cleanup alarm. Callback, stream, and poll requests are forbidden until registration exists. Every callback compares sha256(callbackCap) with the stored callback hash. Every stream and poll compares sha256(readCap) with the stored read hash. Mismatches return 403, including arbitrary but correctly shaped r_ or c_ strings.
The split prevents the wallet callback URL from also being a read bearer token. The dApp keeps readCap local and sends only session plus callbackCap to the wallet in the deep-link callback URL. All three values must carry at least 132 bits of entropy before encoding. Do not log capabilities, include them in analytics, or expose them to third parties. @glyph-oss/connect should still validate the returned nonce and request type before an application accepts a result.
bun installbun run devbun run deployimport {
createRelayCapabilities,
createTransferRequest,
createEnvelope,
launchGlyphRequest,
registerRelaySession,
subscribeViaRelay,
relayCallbackUrl,
} from "@glyph-oss/connect";
const request = createTransferRequest({
type: "transfer",
dapp: { name: "My App", origin: "https://my.app" },
to: "UVYAOYTNYCRBVFBHNFIJUEOUEPEDIDUWWEAXKFSJEBJVASCQEROJOVOEEATL",
amount: "1000",
});
const relay = createRelayCapabilities();
await registerRelaySession(relay);
const envelope = createEnvelope(request, {
callback: relayCallbackUrl({
session: relay.session,
callbackCap: relay.callbackCap,
}),
});
const resultPromise = subscribeViaRelay(request, {
session: relay.session,
readCap: relay.readCap,
});
launchGlyphRequest(envelope);
const result = await resultPromise;