import { createShade, type Shade } from '@shade/sdk'; import { createPrekeyServer, MemoryPrekeyStore, PrekeyServerEvents, } from '@shade/server'; import { SubtleCryptoProvider } from '@shade/crypto-web'; import { PendingRpcRegistry, ShadeFileRpcChannel, attachClientRouting, attachFileHandler, createClientStreamsBridge, createFileClient, createFileHandler, createServerStreamsBridge, type ClientStreamsBridge, type FileClient, type FileHandler, type FileHandlerConfig, type ServerStreamsBridge, } from '../../../src/index.js'; const crypto = new SubtleCryptoProvider(); export interface FileTestRig { alice: Shade; bob: Shade; fs: FileClient; bobHandler: FileHandler; /** Server-side streams bridge (Bob). */ bobStreamsBridge: ServerStreamsBridge; /** Client-side streams bridge (Alice). */ aliceStreamsBridge: ClientStreamsBridge; /** Tear everything down (kills servers, shuts down shades). */ teardown(): Promise; } /** * Setup options. * * Defaults to wiring streams-bridges on both sides so content I/O tests * (`read-write-streams.test.ts`) work transparently. Pass `withStreams: false` * to skip — useful for the legacy `std-ops` tests that don't need them. */ export interface SetupRigOptions { withStreams?: boolean; } export async function setupFileRig( bobConfig: Omit, options: SetupRigOptions = {}, ): Promise { const withStreams = options.withStreams ?? true; // 1. Prekey server const prekeyEvents = new PrekeyServerEvents(); const prekey = createPrekeyServer({ crypto, store: new MemoryPrekeyStore(), disableRateLimit: true, events: prekeyEvents, }); const prekeyServer = Bun.serve({ port: 0, fetch: prekey.fetch }); const prekeyUrl = `http://localhost:${prekeyServer.port}`; // 2. Two Shades const alice = await createShade({ prekeyServer: prekeyUrl, address: 'alice' }); const bob = await createShade({ prekeyServer: prekeyUrl, address: 'bob' }); // 3. BOTH sides need a transferRoute mounted because file-RPC is // request/response — Bob's reply must reach Alice's HTTP endpoint // just as Alice's request reached Bob's. let bobBaseUrl = ''; let aliceBaseUrl = ''; alice.configureTransfers({ resolveBaseUrl: async (peer) => { if (peer === 'bob') return bobBaseUrl; throw new Error(`alice: unknown peer ${peer}`); }, }); bob.configureTransfers({ resolveBaseUrl: async (peer) => { if (peer === 'alice') return aliceBaseUrl; throw new Error(`bob: unknown peer ${peer}`); }, }); const bobApp = await bob.transferRoute(); const aliceApp = await alice.transferRoute(); const bobServer = Bun.serve({ port: 0, fetch: bobApp.fetch }); const aliceServer = Bun.serve({ port: 0, fetch: aliceApp.fetch }); bobBaseUrl = `http://localhost:${bobServer.port}`; aliceBaseUrl = `http://localhost:${aliceServer.port}`; // 4. Streams bridges (both sides) — required for content I/O > 256 KiB. let bobStreamsBridge: ServerStreamsBridge | undefined; let aliceStreamsBridge: ClientStreamsBridge | undefined; if (withStreams) { bobStreamsBridge = await createServerStreamsBridge(bob); aliceStreamsBridge = await createClientStreamsBridge(alice); } // 5. Bob: file handler + channel const bobChannel = new ShadeFileRpcChannel(bob); const fullBobConfig: FileHandlerConfig = { ...bobConfig, ...(bobStreamsBridge !== undefined ? { streamsBridge: bobStreamsBridge } : {}), }; const bobHandler = createFileHandler(bob, fullBobConfig); attachFileHandler(bobChannel, bobHandler); // 6. Alice: client + channel + pending registry const aliceChannel = new ShadeFileRpcChannel(alice); const alicePending = new PendingRpcRegistry(); attachClientRouting(aliceChannel, alicePending); const fs = createFileClient(alice, aliceChannel, alicePending, 'bob', { defaultTimeoutMs: 5000, ...(aliceStreamsBridge !== undefined ? { streamsBridge: aliceStreamsBridge } : {}), }); return { alice, bob, fs, bobHandler, bobStreamsBridge: bobStreamsBridge as ServerStreamsBridge, aliceStreamsBridge: aliceStreamsBridge as ClientStreamsBridge, async teardown() { bobChannel.destroy(); aliceChannel.destroy(); bobHandler.destroy(); if (bobStreamsBridge !== undefined) await bobStreamsBridge.destroy(); if (aliceStreamsBridge !== undefined) await aliceStreamsBridge.destroy(); await alice.shutdown(); await bob.shutdown(); bobServer.stop(); aliceServer.stop(); prekeyServer.stop(); }, }; }