@oh-my-pi/omp-stats 17.0.4 → 17.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/server.ts CHANGED
@@ -20,6 +20,7 @@ import {
20
20
  import { decodeEmbeddedClientArchive } from "./embedded-client";
21
21
  import embeddedClientArchiveTxt from "./embedded-client.generated.txt";
22
22
  import { getGainDashboardStats } from "./gain-aggregator";
23
+ import { recoverStatsPort, STATS_DASHBOARD_HEADER } from "./port-conflict";
23
24
 
24
25
  const EMBEDDED_CLIENT_ARCHIVE = decodeEmbeddedClientArchive(embeddedClientArchiveTxt);
25
26
 
@@ -293,23 +294,20 @@ async function handleStatic(requestPath: string): Promise<Response> {
293
294
  return new Response("Not Found", { status: 404 });
294
295
  }
295
296
 
296
- /**
297
- * Start the HTTP server.
298
- */
299
- export async function startServer(port = 3847): Promise<{ port: number; stop: () => void }> {
300
- await ensureClientBuild();
301
-
297
+ function createDashboardServer(port: number) {
302
298
  const server = Bun.serve({
303
299
  port,
304
300
  async fetch(req) {
305
301
  const url = new URL(req.url);
306
302
  const path = url.pathname;
307
303
 
308
- // CORS headers for local development
309
- const corsHeaders = {
304
+ // CORS headers for local development; the identity header lets another
305
+ // omp session's reuse probe positively recognize this dashboard.
306
+ const corsHeaders: Record<string, string> = {
310
307
  "Access-Control-Allow-Origin": "*",
311
308
  "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
312
309
  "Access-Control-Allow-Headers": "Content-Type",
310
+ [STATS_DASHBOARD_HEADER]: "1",
313
311
  };
314
312
 
315
313
  if (req.method === "OPTIONS") {
@@ -327,8 +325,8 @@ export async function startServer(port = 3847): Promise<{ port: number; stop: ()
327
325
 
328
326
  // Add CORS headers to all responses
329
327
  const headers = new Headers(response.headers);
330
- for (const [key, value] of Object.entries(corsHeaders)) {
331
- headers.set(key, value);
328
+ for (const key in corsHeaders) {
329
+ headers.set(key, corsHeaders[key]);
332
330
  }
333
331
 
334
332
  return new Response(response.body, {
@@ -344,9 +342,39 @@ export async function startServer(port = 3847): Promise<{ port: number; stop: ()
344
342
  }
345
343
  },
346
344
  });
345
+ return server;
346
+ }
347
+
348
+ /**
349
+ * Start the HTTP server, reusing a live dashboard or reclaiming a stale omp listener.
350
+ */
351
+ export async function startServer(port = 3847): Promise<{ port: number; stop: () => void }> {
352
+ await ensureClientBuild();
353
+
354
+ try {
355
+ const server = createDashboardServer(port);
356
+ return {
357
+ port: server.port ?? port,
358
+ stop: () => server.stop(),
359
+ };
360
+ } catch (error) {
361
+ if (!(error instanceof Error && "code" in error && error.code === "EADDRINUSE")) throw error;
362
+
363
+ const recovery = await recoverStatsPort(port);
364
+ if (recovery === "reuse") {
365
+ return { port, stop: () => {} };
366
+ }
347
367
 
348
- return {
349
- port: server.port ?? port,
350
- stop: () => server.stop(),
351
- };
368
+ try {
369
+ const server = createDashboardServer(port);
370
+ return {
371
+ port: server.port ?? port,
372
+ stop: () => server.stop(),
373
+ };
374
+ } catch (retryError) {
375
+ throw new Error(`Failed to start stats dashboard on port ${port} after reclaiming it.`, {
376
+ cause: retryError,
377
+ });
378
+ }
379
+ }
352
380
  }