Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,203 @@
/**
* Deterministic in-memory Trystero substitute used by the test suites. Models
* a single shared broker — every `FakeTrysteroRoom` constructed against the
* same `FakeTrysteroBroker` and the same `(appId, transport)` pair shares
* peer-join/leave + message broadcast.
*
* No mocks: these are real implementations of the `TrysteroRoomLike`
* structural contract. Tests that exercise the transport-specific adapters
* select one of the four `fake<X>RoomFactory` factories below — but all four
* factories share the same broker, the same `FakeTrysteroRoom` class, and
* are differentiated only by the `kind` they tag the room with for
* observability assertions.
*/
import type {
IpfsRoomFactory,
MqttRoomFactory,
NostrRoomFactory,
TorrentRoomFactory,
TrysteroMessage,
TrysteroRoomLike,
TrysteroTransport,
} from "../../src/types/types.js";
type MessageHandler = (peerId: string, data: TrysteroMessage) => void;
type PeerHandler = (peerId: string) => void;
let nextPeerSeq = 0;
interface RoomKey {
readonly appId: string;
readonly transport: TrysteroTransport;
}
function keyString(k: RoomKey): string {
return `${k.transport}:${k.appId}`;
}
export class FakeTrysteroBroker {
public broken = false;
public readonly rooms = new Map<string, Set<FakeTrysteroRoom>>();
public register(room: FakeTrysteroRoom): void {
const k = keyString({ appId: room.appId, transport: room.transport });
let set = this.rooms.get(k);
if (set === undefined) {
set = new Set();
this.rooms.set(k, set);
}
for (const other of set) {
other.notePeerJoined(room.myPeerId);
room.notePeerJoined(other.myPeerId);
}
set.add(room);
}
public unregister(room: FakeTrysteroRoom): void {
const k = keyString({ appId: room.appId, transport: room.transport });
const set = this.rooms.get(k);
if (set === undefined) return;
if (!set.delete(room)) return;
for (const other of set) {
other.notePeerLeft(room.myPeerId);
}
}
public broadcast(
room: FakeTrysteroRoom,
message: TrysteroMessage,
target?: string | readonly string[]
): void {
const k = keyString({ appId: room.appId, transport: room.transport });
const set = this.rooms.get(k);
if (set === undefined) return;
const targets =
typeof target === "string"
? new Set<string>([target])
: Array.isArray(target)
? new Set<string>(target as readonly string[])
: undefined;
for (const other of set) {
if (other === room) continue;
if (targets !== undefined && !targets.has(other.myPeerId)) continue;
other.deliver(room.myPeerId, message);
}
}
}
export class FakeTrysteroRoom implements TrysteroRoomLike {
public readonly appId: string;
public readonly transport: TrysteroTransport;
public readonly myPeerId: string;
private readonly broker: FakeTrysteroBroker;
private peerJoinHandlers = new Set<PeerHandler>();
private peerLeaveHandlers = new Set<PeerHandler>();
private messageHandlers = new Set<MessageHandler>();
private destroyed = false;
public constructor(
appId: string,
transport: TrysteroTransport,
broker: FakeTrysteroBroker
) {
this.appId = appId;
this.transport = transport;
this.broker = broker;
nextPeerSeq += 1;
this.myPeerId = `peer-${transport}-${nextPeerSeq}`;
if (broker.broken) {
throw new Error(`broker down [${transport}]`);
}
queueMicrotask(() => {
if (!this.destroyed) broker.register(this);
});
}
public getPeerId(): string {
return this.myPeerId;
}
public makeAction<T = TrysteroMessage>(
_namespace: string
): readonly [
send: (data: T, target?: string | readonly string[]) => Promise<void>,
get: (peerId: string, data: T) => void,
onReceive: (handler: (peerId: string, data: T) => void) => void,
] {
return [
async (data, target) =>
this.broker.broadcast(this, data as TrysteroMessage, target),
(_peerId, _data) => {
/* parity with Trystero's [send, get, on] triple */
},
(handler) =>
this.messageHandlers.add(handler as unknown as MessageHandler),
] as const;
}
public onPeerJoin(handler: (peerId: string) => void): () => void {
this.peerJoinHandlers.add(handler);
return () => this.peerJoinHandlers.delete(handler);
}
public onPeerLeave(handler: (peerId: string) => void): () => void {
this.peerLeaveHandlers.add(handler);
return () => this.peerLeaveHandlers.delete(handler);
}
public leave(): void {
if (this.destroyed) return;
this.destroyed = true;
this.broker.unregister(this);
this.peerJoinHandlers.clear();
this.peerLeaveHandlers.clear();
this.messageHandlers.clear();
}
public async ping(_peerId: string): Promise<number> {
return 1;
}
public notePeerJoined(peerId: string): void {
for (const h of this.peerJoinHandlers) h(peerId);
}
public notePeerLeft(peerId: string): void {
for (const h of this.peerLeaveHandlers) h(peerId);
}
public deliver(from: string, message: TrysteroMessage): void {
for (const h of this.messageHandlers) h(from, message);
}
}
export function fakeTorrentRoomFactory(
broker: FakeTrysteroBroker
): TorrentRoomFactory {
return async (appId) => new FakeTrysteroRoom(appId, "torrent", broker);
}
export function fakeNostrRoomFactory(
broker: FakeTrysteroBroker
): NostrRoomFactory {
return async (appId) => new FakeTrysteroRoom(appId, "nostr", broker);
}
export function fakeMqttRoomFactory(
broker: FakeTrysteroBroker
): MqttRoomFactory {
return async (appId) => new FakeTrysteroRoom(appId, "mqtt", broker);
}
export function fakeIpfsRoomFactory(
broker: FakeTrysteroBroker
): IpfsRoomFactory {
return async (appId) => new FakeTrysteroRoom(appId, "ipfs", broker);
}
/** Yield control until pending microtasks have drained. */
export async function flushMicrotasks(): Promise<void> {
for (let i = 0; i < 8; i += 1) {
await Promise.resolve();
}
}

View File

@@ -0,0 +1,8 @@
// Ensure `globalThis.crypto` is available in bare-Node vitest runs.
import { webcrypto } from "node:crypto";
type WritableGlobal = { crypto?: Crypto };
const g = globalThis as WritableGlobal;
if (g.crypto === undefined) {
g.crypto = webcrypto as unknown as Crypto;
}