add /server/status endpoint

This commit is contained in:
Devin Zuczek
2026-08-05 18:42:19 -04:00
parent cc43d57172
commit f185ef97df
8 changed files with 100 additions and 5 deletions
+6
View File
@@ -6,6 +6,12 @@ export type Env = SharedHonoEnv & {
DOMAIN: string
/** Static-asset fetcher for the built React SPA (see wrangler.jsonc `assets`). */
ASSETS: Fetcher
/**
* The shared `recflare` D1, bound READ-ONLY in practice: the only thing www asks it
* is the live presence head-count behind `/server-status`. Every table it can see is
* owned (and migrated) by another worker.
*/
DB: D1Database
/**
* Service binding to the `auth` worker — how the BFF reaches it, so the browser's real
* IP survives the hop (see wrangler.jsonc and src/upstream.ts `postAuthForm`).
+34
View File
@@ -1,6 +1,8 @@
import { adminSecretsStore, env, SELF } from 'cloudflare:test'
import { beforeAll, expect, it } from 'vitest'
import { PRESENCE_SCHEMA_DDL, PRESENCE_TTL_SECONDS } from '@repo/domain/src/presence-db'
import { DOCUMENTED_SERVICES } from '../../docs'
import { DISCORD_INVITE, ISSUES_URL, PRIVACY_EMAIL } from '../../links'
import { turnstileKeys } from '../../turnstile'
@@ -22,6 +24,9 @@ const TEST_SECRET_KEY = '1x0000000000000000000000000000000AA'
beforeAll(async () => {
await adminSecretsStore(env.TURNSTILE_SITE_KEY).create(TEST_SITE_KEY)
await adminSecretsStore(env.TURNSTILE_SECRET_KEY).create(TEST_SECRET_KEY)
// `presence` is owned (and migrated) by other workers — www only reads it — so the
// table has to be created here for the head-count behind /server-status.
for (const stmt of PRESENCE_SCHEMA_DDL) await env.DB.prepare(stmt).run()
})
// Web signup is open, but only behind the Turnstile check. These pin the closed door:
@@ -223,6 +228,35 @@ it('carries the browser IP across to auth instead of losing it to the edge', asy
expect(seen[1]!.headers.get('cf-connecting-ip')).toBeNull()
})
// The public status snapshot. Two things are pinned: it needs no auth and no origin (a
// status page or Discord bot fetches it from anywhere), and its player count is LIVE
// presence — a row whose TTL has run out is a player who crashed or hard-quit, and
// counting them would leave the number permanently inflated between sweeps.
it('serves a public head-count of the players actually online', async () => {
const now = Math.floor(Date.now() / 1000)
const write = (accountId: number, expiresAt: number) =>
env.DB.prepare('INSERT OR REPLACE INTO presence (data) VALUES (?1)')
.bind(JSON.stringify({ accountId, roomInstance: null, expiresAt }))
.run()
// Empty table: online, nobody playing.
let res = await SELF.fetch('https://example.com/server-status')
expect(res.status).toBe(200)
expect(await res.json()).toEqual({ status: 'online', players: 0 })
await write(1, now + PRESENCE_TTL_SECONDS) // in a lobby — still online
await write(2, now + PRESENCE_TTL_SECONDS)
await write(3, now - 1) // stopped heartbeating, not yet swept
res = await SELF.fetch('https://example.com/server-status', {
headers: { origin: 'https://s.example' },
})
expect(res.status).toBe(200)
// Readable from any origin — it's meant to be embedded elsewhere.
expect(res.headers.get('access-control-allow-origin')).toBe('*')
expect(await res.json()).toEqual({ status: 'online', players: 2 })
})
it('serves the aggregated docs page with a source per documented service', async () => {
const res = await SELF.fetch('https://example.com/docs')
expect(res.status).toBe(200)
+22 -1
View File
@@ -1,7 +1,8 @@
import { Hono } from 'hono'
import { useWorkersLogger } from 'workers-tagged-logger'
import { logger, withOnError } from '@repo/hono-helpers'
import { countOnlinePlayers } from '@repo/domain/src/presence-db'
import { logger, withDefaultCors, withOnError } from '@repo/hono-helpers'
import { authUnreachable } from './auth-messages'
import { docsPage, fetchSpec } from './docs'
@@ -70,6 +71,26 @@ const app = new Hono<App>()
})
})
// ---- Server status ------------------------------------------------------
// A public, unauthenticated snapshot of the server — what a status page, a Discord
// bot or the homepage can poll without a token. CORS is open on this one route (the
// rest of www is same-origin) so a page hosted anywhere can read it.
//
// `status` is a stub: this handler only runs when the worker is up, so there is no
// state in which it answers anything but "online". It's here so callers can key off
// a field rather than off HTTP 200, and so a real health signal can replace the
// constant without changing the payload's shape.
.get('/server-status', withDefaultCors(), async (c) => {
return c.json({
status: 'online',
// One presence row per account, expired rows excluded — see countOnlinePlayers.
// Players sitting in the lobby count as online, same as anywhere else we read
// presence.
players: await countOnlinePlayers(c.env.DB),
})
})
// ---- Signup -------------------------------------------------------------
// Create an account from the website, behind a Turnstile bot check. The check is what