diff --git a/src/core/services/stats-server.ts b/src/core/services/stats-server.ts index 4cf5a68..328d6d3 100644 --- a/src/core/services/stats-server.ts +++ b/src/core/services/stats-server.ts @@ -1,6 +1,5 @@ import { Hono } from 'hono'; import type { ImmersionTrackerService } from './immersion-tracker-service.js'; -import { createServer, type IncomingMessage, type ServerResponse } from 'node:http'; import { basename, extname, resolve, sep } from 'node:path'; import { readFileSync, existsSync, statSync } from 'node:fs'; import { MediaGenerator } from '../../media-generator.js'; @@ -156,26 +155,6 @@ export interface StatsServerConfig { resolveAnkiNoteId?: (noteId: number) => number; } -type StatsServerHandle = { - stop: () => void; -}; - -type StatsApp = ReturnType; - -type BunRuntime = { - Bun: { - serve: (options: { - fetch: StatsApp['fetch']; - port: number; - hostname: string; - }) => StatsServerHandle; - }; -}; - -type NodeRuntimeHandle = { - stop: () => void; -}; - const STATS_STATIC_CONTENT_TYPES: Record = { '.css': 'text/css; charset=utf-8', '.gif': 'image/gif', @@ -248,82 +227,6 @@ function createStatsStaticResponse(staticDir: string, requestPath: string): Resp }); } -async function readNodeRequestBody(req: IncomingMessage): Promise { - const chunks: Buffer[] = []; - for await (const chunk of req) { - chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : Buffer.from(chunk)); - } - return Buffer.concat(chunks); -} - -async function createNodeRequest(req: IncomingMessage): Promise { - const host = req.headers.host ?? '127.0.0.1'; - const url = new URL(req.url ?? '/', `http://${host}`); - const headers = new Headers(); - for (const [name, value] of Object.entries(req.headers)) { - if (value === undefined) continue; - if (Array.isArray(value)) { - headers.set(name, value.join(', ')); - } else { - headers.set(name, value); - } - } - - const method = req.method ?? 'GET'; - const body = method === 'GET' || method === 'HEAD' ? undefined : await readNodeRequestBody(req); - const init: RequestInit = { - method, - headers, - }; - if (body !== undefined && body.length > 0) { - init.body = new Uint8Array(body); - } - return new Request(url, init); -} - -async function writeNodeResponse( - res: ServerResponse, - response: Response, -): Promise { - res.statusCode = response.status; - res.statusMessage = response.statusText; - response.headers.forEach((value, key) => { - res.setHeader(key, value); - }); - - if (!response.body) { - res.end(); - return; - } - - const body = Buffer.from(await response.arrayBuffer()); - res.end(body); -} - -function startNodeStatsServer(app: StatsApp, port: number): NodeRuntimeHandle { - const server = createServer((req, res) => { - void (async () => { - try { - const response = await app.fetch(await createNodeRequest(req)); - await writeNodeResponse(res, response); - } catch { - if (!res.headersSent) { - res.statusCode = 500; - } - res.end('Internal Server Error'); - } - })(); - }); - - server.listen(port, '127.0.0.1'); - - return { - stop: () => { - server.close(); - }, - }; -} - export function createStatsApp( tracker: ImmersionTrackerService, options?: { @@ -1103,14 +1006,23 @@ export function startStatsServer(config: StatsServerConfig): { close: () => void resolveAnkiNoteId: config.resolveAnkiNoteId, }); - const bunServe = (globalThis as typeof globalThis & Partial).Bun?.serve; - const server = bunServe - ? bunServe({ - fetch: app.fetch, - port: config.port, - hostname: '127.0.0.1', - }) - : startNodeStatsServer(app, config.port); + const bunServe = ( + globalThis as typeof globalThis & { + Bun: { + serve: (options: { + fetch: (typeof app)['fetch']; + port: number; + hostname: string; + }) => { stop: () => void }; + }; + } + ).Bun.serve; + + const server = bunServe({ + fetch: app.fetch, + port: config.port, + hostname: '127.0.0.1', + }); return { close: () => {