Files
Shade/packages/shade-transport-bridge
Sterister 1fb59a7076 release(v4.8.0): sender-fingerprint attribution + Inbox.start race fix
Two unblocking changes for first-contact flows.

Sender attribution: relay captures shortHash(senderSigningKey) at
PUT time (after signature verification, no new trust surface) and
surfaces it on bridge push (IncomingMessage.from) + inbox-fetch
(FetchedBlob.from) + DecryptHandler raw arg. Apps receiving a prekey
envelope from a never-before-seen peer can now bootstrap X3DH via
shade.receive('fp:<hex>', env) — pre-4.8 the wire envelope didn't
authenticate the sender and there was no out-of-band hint to use.
Idempotent ALTER TABLE migrations for SQLite + Postgres add a
sender_fp TEXT column; legacy rows surface as from=undefined
(inter-version compat).

Inbox.start() race: pre-4.8 start() called register() fire-and-forget
AND schedulePoll(0) synchronously, so the first poll on a fresh
address often beat the register HTTP RTT and got SHADE_NOT_FOUND.
start() now defers; register() success kicks schedulePoll(0). Manual
tick() is unaffected (deliberate user action, no gating).

Both reported by Prism. Tests cover all five acceptance criteria
from the sender-attribution request (PUT capture, bridge surface,
fetch surface, inter-version compat, end-to-end pair smoke) plus
the three from the race-fix request.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 00:11:59 +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.