Your own Chrome, driven from Officer. Not a headless one: it attaches to the browser you are already
signed into, which is the whole difference from /scrape, and /scrape stays behind in the platform.
The relay is a sidecar now, and that is the point. It was a second Bun.serve inside the platform
process on its own port, which is exactly why it was switched off on 2026-08-13 — "the platform serves
one port". A sidecar is a process allowed its own listeners, so the objection stops applying instead of
being argued with.
The boundary across it is deliberately small: two questions, who is connected and what tabs they have,
because only those live in memory on the relay side. Screenshots, evaluation and navigation still go
straight down cdp.ts's own WebSocket to the relay's /cdp endpoint, exactly as before — routing page
bytes through the platform would be work for nothing.
Three normally-breaking changes taken now because now they are free, each already recommended in a
comment by whoever switched the relay off. Its own signing key instead of borrowing getKey('jwt'). Its
own table instead of the platform's user_integrations. And the port kept at 18792 on purpose, because
the port is an input to the token HMAC as well as an address — moving it fails every pairing silently,
and the extension blames the address. user_integrations held zero browser-relay rows, checked in
Postgres rather than assumed, so there was nothing to unpair.
The screen became panels. BrowserScreen.tsx rendered TabList and TabPreview through appType: null
placeholders; both components were already self-contained and took no props, so the screen had nothing
to pass and simply went.
Not tested end to end — no browser has been paired since the move, and that needs a Chrome and a
person. BLOCKERS.md says so rather than the commit implying otherwise.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
45 lines
2.3 KiB
TypeScript
45 lines
2.3 KiB
TypeScript
import type { ConnectedTarget } from './relay-types';
|
|
|
|
// What the api half says to the relay process, and what comes back.
|
|
//
|
|
// The relay used to run INSIDE the platform, so `api/router.ts` simply called `getUserRelayStatus()`.
|
|
// It cannot now: the relay is its own process because it owns a second listener, and "the platform
|
|
// serves one port" is why the whole thing was switched off in the first place.
|
|
//
|
|
// So this is the boundary. Small on purpose — four questions and one passthrough — because everything
|
|
// stateful stays on the relay side where the sockets are.
|
|
|
|
export type BrowserCommand =
|
|
| { type: 'ping'; id: string }
|
|
| { type: 'relay:status'; id: string; userId: number }
|
|
| { type: 'relay:targets'; id: string; userId: number }
|
|
/** Register a derived token so the relay will accept an extension presenting it. */
|
|
| { type: 'relay:token-add'; id: string; userId: number; salt: string }
|
|
| { type: 'relay:token-remove'; id: string; userId: number; salt: string };
|
|
// NOT here: CDP. `cdp.ts` opens its own WebSocket to the relay's /cdp endpoint with a derived token, which
|
|
// already crosses the process boundary — routing screenshots through this channel would put page bytes
|
|
// through the platform for no reason.
|
|
|
|
export type BrowserEvent =
|
|
| { type: 'pong'; id: string }
|
|
| { type: 'error'; id: string; error: string }
|
|
| { type: 'relay:status'; id: string; extensionConnected: boolean; targetCount: number }
|
|
| { type: 'relay:targets'; id: string; targets: ConnectedTarget[] }
|
|
| { type: 'relay:token'; id: string; token: string; port: number }
|
|
| { type: 'relay:ok'; id: string };
|
|
|
|
export const BROWSER_SIDECAR_KIND = 'browser-relay';
|
|
|
|
/**
|
|
* The port the extension connects to.
|
|
*
|
|
* NOT kernel-assigned, unlike every other sidecar's listener. The extension is configured by hand and
|
|
* stores the number, so a port that changed each restart would break the pairing each restart — and the
|
|
* port is an input to the token HMAC, so changing it invalidates every paired browser silently. The
|
|
* extension then reports "Relay not reachable", which SETUP.md attributes to a wrong address or token.
|
|
*
|
|
* 18792 is what it was as `BROWSER_RELAY_PORT` before the extraction, kept so any extension configured
|
|
* back then still points at the right place.
|
|
*/
|
|
export const RELAY_PORT = Number(process.env.BROWSER_RELAY_PORT ?? 18792);
|