Skip to content
64 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Merge branch 'socket-tickets'1/**
2 * Opening the site's live sockets from the browser. A page signed in by a
3 * session opens them at once, its cookie going along as always. A page
4 * opened with an access token has no cookie, and a socket cannot carry the
5 * token's header, so it first asks for a socket ticket and adds it to the
6 * address (lib/socket-ticket.ts). Browser-only.
7 */
8
9/** As lib/socket-ticket.ts's, kept apart so the page does not load its sealing code. */
10const TICKET_PARAM = "ticket";
11const TICKET_ROUTE = "/-/live/ticket";
12
13let viaToken = false;
14
15/** Set by the root as it renders: whether this page was opened with an access token. */
16export function setLiveViaToken(on: boolean): void {
17 viaToken = on;
18}
19
20/** `wss://<this site><path>?<params>`, with a ticket when one was given. */
21export function liveAddress(path: string, params: Record<string, string | null | undefined>, ticket: string | null): string {
22 const url = new URL(path, location.href);
23 url.protocol = location.protocol === "https:" ? "wss:" : "ws:";
24 for (const [name, value] of Object.entries(params)) if (value != null && value !== "") url.searchParams.set(name, value);
25 if (ticket) url.searchParams.set(TICKET_PARAM, ticket);
26 return url.toString();
27}
28
29async function ticketFor(path: string): Promise<string | null> {
30 try {
31 const answer = await fetch(`${TICKET_ROUTE}?path=${encodeURIComponent(path)}`, {
32 headers: { accept: "application/json" },
33 cache: "no-store",
34 credentials: "same-origin",
35 });
36 if (!answer.ok) return null;
37 const body = (await answer.json()) as { ticket?: unknown };
38 return typeof body.ticket === "string" ? body.ticket : null;
39 } catch {
40 return null;
41 }
42}
43
44/**
45 * Calls `open` with the address of the socket at `path`: at once for a
46 * session, after fetching a fresh ticket for a token's page (each attempt
47 * gets its own, as one lasts a minute). `params` is read when the address
48 * is made, so it sees any change meanwhile. Nothing is opened once
49 * `cancelled` says so.
50 */
51export function openLive(
52 path: string,
53 params: () => Record<string, string | null | undefined>,
54 open: (address: string) => void,
55 cancelled: () => boolean,
56): void {
57 if (!viaToken) {
58 open(liveAddress(path, params(), null));
59 return;
60 }
61 void ticketFor(path).then((ticket) => {
62 if (!cancelled()) open(liveAddress(path, params(), ticket));
63 });
64}

This file's history is long; its oldest lines are credited to the oldest commit read.