Files
Shade/packages/shade-transport-bridge
Sterister a98ea8a1bd
Some checks failed
Test / test (push) Has been cancelled
release(v4.8.4): server-side cross-channel dedup via BridgeDeliveryLog
V4.8.3 shipped client-side cross-channel dedup hook
(`Inbox.acceptBridgeFrame`), but recipients that didn't migrate to
the new wiring still observed the same envelope twice — once via
WS bridge push, again ~30 s later via inbox-poll. Prism re-verified
the FR after 4.8.3 and asked for a relay-side enforcement so app
code doesn't have to ack-via-DELETE on every bridge frame.

V4.8.4 adds an in-memory `BridgeDeliveryLog` (default 60 s grace,
8192-per-address cap) that records every successful WS / SSE /
long-poll push of `(address, msgId)`. The `/v1/inbox/:addr/fetch`
route filters out blobs in the log's grace window so a recipient
running both a bridge and the 30 s poll cadence sees exactly one
delivery. Cursor advances over the full fetched window so a poll
that straddles a suppressed blob doesn't stall.

The standalone server auto-wires the log between
`createBridgeRoutes` and `createInboxRoutes`. Custom mounts thread
the same instance through `bridgeDeliveryLog` on both factories.

Tests cover WS-then-poll, SSE-then-poll, and a negative control
(non-bridge-pushed blob still comes through inbox-fetch).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 16:31:42 +02:00
..

@shade/transport-bridge

Transport-agnostic delivery for Shade: WS → SSE → long-poll, in priority order, behind a single IncomingMessage interface.

import {
  FallbackBridgeTransport,
  WsBridge,
  SseBridge,
  LongPollBridge,
} from '@shade/transport-bridge';

const auth = { crypto, signingPrivateKey, address: 'bob' };

const bridge = new FallbackBridgeTransport([
  new WsBridge({       baseUrl, auth }),
  new SseBridge({      baseUrl, auth }),
  new LongPollBridge({ baseUrl, auth }),
]);

await bridge.connect({
  onMessage: (msg) => {
    // msg: { from: string; bytes: Uint8Array; receivedAt: number; msgId?: string }
  },
});

console.log(bridge.activeKind); // "ws" | "sse" | "long-poll"

Pair with createBridgeRoutes in @shade/inbox-server to expose the matching /v1/bridge/{stream,poll,ws} endpoints. Full design + threat model in docs/transport.md.

What it solves

Browser extensions, strict corporate proxies, and edge runtimes routinely block long-lived WebSockets. Apps that already use the Shade inbox shouldn't have to write three custom delivery paths to handle the realistic mix of hostile networks they ship into. This package is the canonical answer.

Status

V3.7. Stable wire format, additive change to @shade/inbox-server. See CHANGELOG.