143 lines
4.5 KiB
TypeScript
143 lines
4.5 KiB
TypeScript
|
|
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<void>;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 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<FileHandlerConfig, 'streamsBridge'>,
|
||
|
|
options: SetupRigOptions = {},
|
||
|
|
): Promise<FileTestRig> {
|
||
|
|
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();
|
||
|
|
},
|
||
|
|
};
|
||
|
|
}
|