feat(soak): RoomCoordinator with host→joiners handoff

Lazy Deferred per roomId with a timeout on await. Lets concurrent
joiner sessions block until their host announces the room code
without polling or page scraping.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
adlee-was-taken
2026-04-11 17:11:05 -04:00
parent 1565046ab7
commit 02642840da
2 changed files with 71 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
/**
* RoomCoordinator — tiny host→joiners handoff primitive.
*
* Lazy Deferred per roomId. `announce` resolves the promise; `await`
* blocks until the promise resolves or a per-call timeout fires.
* Rooms are keyed by string; each key has at most one Deferred.
*/
import { deferred, Deferred } from './deferred';
import type { RoomCoordinatorApi } from './types';
export class RoomCoordinator implements RoomCoordinatorApi {
private rooms = new Map<string, Deferred<string>>();
announce(roomId: string, code: string): void {
this.getOrCreate(roomId).resolve(code);
}
async await(roomId: string, timeoutMs: number = 30_000): Promise<string> {
const d = this.getOrCreate(roomId);
let timer: NodeJS.Timeout | undefined;
const timeout = new Promise<never>((_, reject) => {
timer = setTimeout(() => {
reject(new Error(`RoomCoordinator: room "${roomId}" timed out after ${timeoutMs}ms`));
}, timeoutMs);
});
try {
return await Promise.race([d.promise, timeout]);
} finally {
if (timer) clearTimeout(timer);
}
}
private getOrCreate(roomId: string): Deferred<string> {
let d = this.rooms.get(roomId);
if (!d) {
d = deferred<string>();
this.rooms.set(roomId, d);
}
return d;
}
}