Public: anyone can read this; the crew can change it.
/signin and /signup name their Server-Timing phases and answer under 10 ms #4116
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/4106-signin-and-signup-name-their-server-timi → main 1 commit fork 2 threads · 2 unresolved
3 files
changed +58
−19
src/web/pages/site.ts +15 −17
| @@ -268,7 +268,7 @@ function authForm( | ||
| 268 | 268 | const action = query.size === 0 ? `/${kind}` : `/${kind}?${query.toString()}`; |
| 269 | 269 | // "Is this the first account?" is a `LIMIT 1` existence check, not a count |
| 270 | 270 | // of the whole user table — and the sign-in screen paints on every session. |
| 271 | const firstAccount = kind === "signup" && !hasAnyUser(ctx.app.db); | |
| 271 | const firstAccount = kind === "signup" && !timePhase(ctx.request, "users", () => hasAnyUser(ctx.app.db)); | |
| 272 | 272 | |
| 273 | 273 | // A newcomer the edge vouched for is already through it: no door to go |
| 274 | 274 | // round again, and the name is theirs alone. |
| @@ -302,7 +302,7 @@ function authForm( | ||
| 302 | 302 | <button type="button" id="passkey-signin" class="btn" data-variant="primary" data-size="lg">Sign in with a passkey</button> |
| 303 | 303 | <p class="passkey-note small" id="passkey-signin-note" role="status"></p> |
| 304 | 304 | </div> |
| 305 | ${when(SIGNUP_SENTENCES[getSignup(ctx.app.db)], (said) => html`<p class="muted small mb" id="signup-mode">${said}</p>`)}`, | |
| 305 | ${when(SIGNUP_SENTENCES[timePhase(ctx.request, "settings", () => getSignup(ctx.app.db))], (said) => html`<p class="muted small mb" id="signup-mode">${said}</p>`)}`, | |
| 306 | 306 | ); |
| 307 | 307 | |
| 308 | 308 | const body = html`${unknownLink} |
| @@ -412,7 +412,7 @@ function authForm( | ||
| 412 | 412 | |
| 413 | 413 | // A spawned forge nobody has claimed yet: its person may arrive here with |
| 414 | 414 | // the code from their link but not the link itself (#730). |
| 415 | const unowned = !hasAdmin(ctx.app.db); | |
| 415 | const unowned = !timePhase(ctx.request, "claim", () => hasAdmin(ctx.app.db)); | |
| 416 | 416 | const claimable = |
| 417 | 417 | existsSync(join(ctx.app.config.dataDir, "parent.json")) && unowned; |
| 418 | 418 | return html`<div class="narrow"> |
| @@ -437,12 +437,11 @@ function authForm( | ||
| 437 | 437 | } |
| 438 | 438 | |
| 439 | 439 | export function signinPage(ctx: Ctx): Response { |
| 440 | return page({ | |
| 441 | ctx, | |
| 442 | title: "Sign in", | |
| 443 | body: authForm(ctx, "signin"), | |
| 444 | scriptJs: passkeyJs, | |
| 445 | }); | |
| 440 | notePhase(ctx.request, "session", takeSessionTime(ctx.request)); | |
| 441 | const body = authForm(ctx, "signin"); | |
| 442 | return timePhase(ctx.request, "render", () => | |
| 443 | page({ ctx, title: "Sign in", body, scriptJs: passkeyJs }), | |
| 444 | ); | |
| 446 | 445 | } |
| 447 | 446 | |
| 448 | 447 | /** The widget's public site key, when signup is gated on Turnstile. */ |
| @@ -471,16 +470,15 @@ function signupClosedPage(ctx: Ctx): Response { | ||
| 471 | 470 | } |
| 472 | 471 | |
| 473 | 472 | export function signupPage(ctx: Ctx): Response { |
| 474 | if (signupClosed(ctx.app, inviteOf(ctx))) return signupClosedPage(ctx); | |
| 473 | notePhase(ctx.request, "session", takeSessionTime(ctx.request)); | |
| 474 | if (timePhase(ctx.request, "signup", () => signupClosed(ctx.app, inviteOf(ctx)))) | |
| 475 | return timePhase(ctx.request, "render", () => signupClosedPage(ctx)); | |
| 475 | 476 | const first = throughEdgeFirst(ctx); |
| 476 | 477 | if (first) return first; |
| 477 | return page({ | |
| 478 | ctx, | |
| 479 | title: "Sign up", | |
| 480 | body: authForm(ctx, "signup"), | |
| 481 | turnstile: siteKey(ctx) !== undefined, | |
| 482 | scriptJs: passkeyJs, | |
| 483 | }); | |
| 478 | const body = authForm(ctx, "signup"); | |
| 479 | return timePhase(ctx.request, "render", () => | |
| 480 | page({ ctx, title: "Sign up", body, turnstile: siteKey(ctx) !== undefined, scriptJs: passkeyJs }), | |
| 481 | ); | |
| 484 | 482 | } |
| 485 | 483 | |
| 486 | 484 | /** |
test/server-timing-sweep.test.ts −2
| @@ -52,10 +52,8 @@ const UNNAMED: Array<[path: string, issue: string]> = [ | ||
| 52 | 52 | ["/settings/password", "#3390"], |
| 53 | 53 | ["/settings/tokens", "#3390"], |
| 54 | 54 | ["/settings/usage", "#3279"], |
| 55 | ["/signin", "#3390"], | |
| 56 | 55 | ["/signin/parent", "#3390"], |
| 57 | 56 | ["/signin/parent/callback", "#3390"], |
| 58 | ["/signup", "#3390"], | |
| 59 | 57 | ["/verify", "#3390"], |
| 60 | 58 | ]; |
| 61 | 59 |
test/signin-timing.test.ts added +43
| @@ -0,0 +1,43 @@ | ||
| 1 | /** `/signin` and `/signup` name session, each read and render in Server-Timing, and answer under 10 ms (#4106). */ | |
| 2 | import { afterAll, beforeAll, expect, test } from "bun:test"; | |
| 3 | import { mkdtempSync, rmSync } from "node:fs"; | |
| 4 | import { tmpdir } from "node:os"; | |
| 5 | import { join } from "node:path"; | |
| 6 | import { createApp, type App } from "../src/app.ts"; | |
| 7 | import { serve, type Servers } from "../src/http/server.ts"; | |
| 8 | import { quietLogs } from "./helpers/globals.ts"; | |
| 9 | ||
| 10 | quietLogs(); | |
| 11 | ||
| 12 | let root = ""; | |
| 13 | let app: App; | |
| 14 | let servers: Servers; | |
| 15 | ||
| 16 | beforeAll(async () => { | |
| 17 | root = mkdtempSync(join(tmpdir(), "forge-signin-timing-")); | |
| 18 | app = createApp({ dataDir: root, port: 0 }); | |
| 19 | servers = await serve(app); | |
| 20 | }); | |
| 21 | afterAll(() => { | |
| 22 | servers.stop(); | |
| 23 | app.close(); | |
| 24 | rmSync(root, { recursive: true, force: true }); | |
| 25 | }); | |
| 26 | ||
| 27 | const PAGES: [string, string[]][] = [ | |
| 28 | ["/signin", ["session", "settings", "claim", "render"]], | |
| 29 | ["/signup", ["session", "signup", "users", "claim", "render"]], | |
| 30 | ]; | |
| 31 | ||
| 32 | for (const [path, names] of PAGES) | |
| 33 | test(`GET ${path} names its phases and answers under 10 ms`, async () => { | |
| 34 | await fetch(`${servers.url}${path}`); // warm | |
| 35 | const res = await fetch(`${servers.url}${path}`); | |
| 36 | await res.text(); | |
| 37 | expect(res.status).toBe(200); | |
| 38 | const parts = (res.headers.get("server-timing") ?? "").split(", "); | |
| 39 | for (const name of ["app", ...names]) | |
| 40 | expect(parts.find((p) => p.startsWith(`${name};`))).toMatch(/^[a-z]+;dur=\d+\.\d$/); | |
| 41 | const app = parts.find((p) => p.startsWith("app;"))!; | |
This const app shadows the App declared at the top of the file. Rename it (e.g. appPart) to keep it readable. |
||
| 42 | expect(Number(app.slice("app;dur=".length))).toBeLessThan(10); | |
| 43 | }); |
new above
offline
authForm runs outside any timePhase, so its cost is unattributed. Wrap it in a named phase (e.g. "form") so a slow sign-in page shows where the time goes.