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

Chat and workspace agents: channels, DMs and named agents you talk to1import { env } from "cloudflare:workers";
2
3import { CHAT_VIEWER_HEADER } from "@g1t/contracts";
4
5import type { Route } from "./+types/live";
Merge branch 'socket-tickets'6import { roleIn } from "../../../lib/session.server";
7import { socketViewer } from "../../../lib/socket-ticket.server";
Chat and workspace agents: channels, DMs and named agents you talk to8
9/**
10 * A conversation's live socket: `wss://<site>/<workspace>/-/chat/live?channel=<id>`.
11 * The site checks the session and that the page asking is the site's own,
12 * then hands the upgrade to the chat service with the viewer, which checks
13 * they may read the channel and keeps the socket (one Durable Object per
14 * channel, hibernating while nothing happens).
15 */
16export async function loader({ params, context, request }: Route.LoaderArgs) {
Merge branch 'socket-tickets'17 // A session, or a page opened with a token by its socket ticket.
18 const viewer = await socketViewer(context, request);
Chat and workspace agents: channels, DMs and named agents you talk to19 if (!viewer) return new Response("Sign in to use chat.", { status: 401 });
20 if (!roleIn(viewer, params.owner)) return new Response("Not found", { status: 404 });
21 if (request.headers.get("upgrade")?.toLowerCase() !== "websocket") {
22 return new Response("This address takes a WebSocket.", { status: 426, headers: { upgrade: "websocket" } });
23 }
24 // Only the site's own pages may open it: a page elsewhere carries the
25 // cookie too, so the origin is what tells them apart.
26 const origin = request.headers.get("origin");
27 if (origin && origin !== new URL(request.url).origin) return new Response("Cross-origin socket refused", { status: 403 });
28 const channel = new URL(request.url).searchParams.get("channel");
29 if (!channel) return new Response("Which channel?", { status: 400 });
30 const headers = new Headers(request.headers);
31 // Neither the session nor anything else of the browser's goes on.
32 headers.delete("cookie");
33 headers.set(CHAT_VIEWER_HEADER, JSON.stringify(viewer));
34 headers.set("x-g1t-workspace", params.owner.toLowerCase());
35 const target = `https://chat/live?channel=${encodeURIComponent(channel)}&workspace=${encodeURIComponent(params.owner.toLowerCase())}`;
36 try {
37 return await env.CHAT.fetch(new Request(target, { method: "GET", headers }));
38 } catch (error) {
39 console.error("chat: the live socket could not be handed over", error);
40 return new Response("Chat didn't answer.", { status: 503 });
41 }
42}

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