Skip to content
36 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.

A page opened with an access token keeps its live sockets connected: just before it opens the feed, a conversation or an artifact's room, it asks GET /-/live/ticket with the token for a socket ticket and adds it to the socket's address, because a browser cannot put the Authorization header on a WebSocket. A ticket seals the token and its owner with a key derived from USERCONTENT_KEY, lasts 60 seconds, opens only the socket path it was made for, is read only by a WebSocket upgrade and never by a page, data request, form post or the API, and the token is checked again when the socket opens, so one deleted, expired, revoked or without Use the website as you opens nothing. Sessions open their sockets as before, with no ticket, and the authentication guide and the rate limits notes say how it works.1import { env } from "cloudflare:workers";
2
3import type { User } from "@g1t/contracts";
4
5import { getViewer } from "./session.server";
6import { identity } from "./services.server";
7import { ticketViewer } from "./socket-ticket";
8import { websiteUser } from "./website-token";
9
10let isolateSecret: string | null = null;
11
12/**
13 * What tickets are sealed with: the site's `USERCONTENT_KEY`, from which
14 * lib/socket-ticket.ts derives a key of their own. Without it (a local
15 * run), a key made for this isolate, which is enough where one process
16 * serves the site.
17 */
18export function ticketSecret(): string {
19 if (env.USERCONTENT_KEY) return env.USERCONTENT_KEY;
20 if (!isolateSecret) {
21 const bytes = crypto.getRandomValues(new Uint8Array(32));
22 isolateSecret = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
23 }
24 return isolateSecret;
25}
26
27/**
28 * Who opens a live socket: the session or token the request carries, as
29 * on any page, or else the person a socket ticket was made for
30 * (lib/socket-ticket.ts), whose token is checked again now.
31 */
32export async function socketViewer(context: Parameters<typeof getViewer>[0], request: Request): Promise<User | null> {
33 const viewer = getViewer(context);
34 if (viewer) return viewer;
35 return ticketViewer(request, ticketSecret(), async (token) => websiteUser(await identity.userForAccessToken(token)));
36}