Skip to content
32 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 type { Route } from "./+types/ticket";
2import { getViewer, roleIn } from "../../lib/session.server";
3import { issueTicket, socketPath } from "../../lib/socket-ticket";
4import { ticketSecret } from "../../lib/socket-ticket.server";
5import { bearerToken } from "../../lib/website-token";
6
7const PRIVATE = { "cache-control": "no-store", "x-robots-tag": "noindex" };
8
9/**
10 * A socket ticket for a page opened with an access token:
11 * `GET /-/live/ticket?path=/<workspace>/-/chat/live` answers
12 * `{ ticket, expires_at }`, good for a minute on that socket alone
13 * (lib/socket-ticket.ts). Only a request signed in by a token gets one: a
14 * session's sockets carry its cookie and need none.
15 */
16export async function loader({ context, request }: Route.LoaderArgs) {
17 const viewer = getViewer(context);
18 const token = bearerToken(request);
19 if (!viewer || !token || !viewer.token?.website) {
20 return Response.json(
21 { message: "Socket tickets are for pages opened with an access token; a signed-in browser's sockets use its session." },
22 { status: viewer ? 400 : 401, headers: PRIVATE },
23 );
24 }
25 const socket = socketPath(new URL(request.url).searchParams.get("path") ?? "");
26 if (!socket) return Response.json({ message: "Which socket? `path` is one of the site's live sockets." }, { status: 400, headers: PRIVATE });
27 if (socket.workspace && !roleIn(viewer, socket.workspace)) {
28 return Response.json({ message: "Not found" }, { status: 404, headers: PRIVATE });
29 }
30 const issued = await issueTicket(ticketSecret(), { token, userId: viewer.id, path: socket.path });
31 return Response.json(issued, { headers: PRIVATE });
32}