refactor: port backend and frontend to TypeScript classes (bun + vite)
Build and Publish / Metadata (push) Successful in 4s
Build and Publish / BuildAndDeployAmd64 (push) Successful in 14s
eventsourced-ai live check live check

Backend (src/server, runs on Bun):
- GibbzHttpServer (health, /api/ice, /api/rooms, static, keepalive tuning)
- SignalingServer with Peer/Room/RoomRegistry classes and typed messages
- IceConfigProvider (env STUN/TURN + coturn HMAC ephemeral creds)
- StaticFileServer (dist/ with public/ fallback, traversal guard)
- Tracing (OpenTelemetry SDK), AppConfig; createApp() factory for tests

Frontend (src/client, built by Vite):
- VoiceApp orchestrator implementing VoiceAppContext
- WebRtcManager + PeerSession (perfect negotiation, pc rebuild, quality monitor)
- SignalingClient + ReconnectController, AudioManager, SpatialAudio,
  Microphone/ScreenShare controllers, ScreensUi/RosterUi/SpatialMap/
  SpeakingMonitor, Settings/Welcome dialogs, LayoutManager, MatrixChat

Tooling: bun scripts (dev/start/test/typecheck), vite.config.ts root
src/client with publicDir public/static, vitest-config for CI-less runs,
bun-based Dockerfile (oven/bun:1), Makefile + README updated, legacy
server.js/tracing.js/test.js/public js removed.

Verified locally: tsc --noEmit clean, vitest run 6/6 green, vite build ok,
server smoke-tested serving dist + signaling endpoints under Node. Local
bun binary segfaults (SIGILL) on this host, so 'bun test' itself was not
executable here; tests are vitest-API compatible which bun test maps.
This commit is contained in:
developer committed 2026-09-24 07:43:04 +00:00
1 parent 9b8c2a9599
commit 2b950e4953
48 files changed
+4589 -9200

No files matched your search

+90
View File
@@ -0,0 +1,90 @@
import fs from 'node:fs';
import { afterAll, describe, expect, test } from 'vitest';
import { WebSocket } from 'ws';
import { createApp, type GibbzApp } from '../src/server/index.ts';
import { IceConfigProvider } from '../src/server/ice.ts';
let app: GibbzApp | undefined;
let port = 0;
async function startTestApp(): Promise<number> {
if (!app) {
app = createApp();
await app.http.start(0, '127.0.0.1');
port = app.http.address()!.port;
}
return port;
}
afterAll(async () => {
await app?.stop();
});
describe('server', () => {
test('static client files exist', () => {
expect(fs.existsSync('src/client/index.html'), 'index.html exists').toBe(true);
expect(fs.existsSync('src/client/main.ts'), 'main.ts exists').toBe(true);
expect(fs.existsSync('src/client/style.css'), 'style.css exists').toBe(true);
});
test('/healthz answers ok', async () => {
const p = await startTestApp();
const res = await fetch(`http://127.0.0.1:${p}/healthz`);
expect(res.status).toBe(200);
expect(await res.text()).toBe('ok');
});
test('/api/ice returns cluster stun/turn', async () => {
const p = await startTestApp();
const res = await fetch(`http://127.0.0.1:${p}/api/ice`);
expect(res.status).toBe(200);
const ice = await res.json();
expect(Array.isArray(ice.iceServers)).toBe(true);
const stunUrls = ice.iceServers.flatMap((s: { urls: string | string[] }) =>
Array.isArray(s.urls) ? s.urls : [s.urls],
);
expect(stunUrls.some((u: string) => u.includes('turn.k6n.net'))).toBe(true);
});
test('/api/rooms starts empty and reflects a connected peer', async () => {
const p = await startTestApp();
const empty = await (await fetch(`http://127.0.0.1:${p}/api/rooms`)).json();
expect(empty).toEqual({});
const ws = new WebSocket(`ws://127.0.0.1:${p}/ws?room=testroom&name=tester`);
const hello = await new Promise<{ id: string; room: string; type: string }>((resolve, reject) => {
ws.on('message', (raw) => {
const msg = JSON.parse(raw.toString());
if (msg.type === 'hello') resolve(msg);
});
ws.on('error', reject);
});
expect(hello.type).toBe('hello');
expect(hello.room).toBe('testroom');
expect(hello.id).toBeTruthy();
const res = await fetch(`http://127.0.0.1:${p}/api/rooms`);
const rooms = await res.json();
expect(rooms.testroom).toBeTruthy();
expect(rooms.testroom).toHaveLength(1);
expect(rooms.testroom[0].name).toBe('tester');
ws.close();
});
test('TURN secret generates ephemeral HMAC credentials', () => {
const provider = new IceConfigProvider({ TURN_SECRET: 'secret123' });
const config = provider.get();
const turnServer = config.iceServers.find((s) =>
(Array.isArray(s.urls) ? s.urls : [s.urls]).some((u) => u.startsWith('turn')),
);
expect(turnServer).toBeTruthy();
expect(turnServer!.username!.endsWith(':gibbz')).toBe(true);
expect(turnServer!.credential!.length).toBeGreaterThan(0);
});
test('ICE transport policy is passed through', () => {
const provider = new IceConfigProvider({ ICE_TRANSPORT_POLICY: 'relay' });
expect(provider.get().iceTransportPolicy).toBe('relay');
});
});