browser, extracted from the platform — extension, relay and all
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>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
# Browser
|
||||
|
||||
Your own browser, driven from Officer. Not a headless one — the browser you are already signed into.
|
||||
|
||||
## The shape
|
||||
|
||||
```
|
||||
extension/ the Chrome extension: attaches the debugger to your tabs, talks to the relay
|
||||
sidecar/ the relay it connects to, on port 18792, plus the CDP bridge
|
||||
api/ pairing, status, and the target verbs
|
||||
db/ browser_pairings — one salt per account
|
||||
web/ the tab list and the preview panel
|
||||
```
|
||||
|
||||
## Why the relay is a sidecar now
|
||||
|
||||
It was a second `Bun.serve` inside the platform process, on its own port. That is precisely why it was
|
||||
switched off on 2026-08-13, with the note "the platform serves one port".
|
||||
|
||||
A sidecar is a process allowed its own listeners, so the objection stops applying rather than being
|
||||
argued with. Two listeners live there: the extension's WebSocket, and the registration socket back to the
|
||||
platform that `api/router.ts` asks questions over.
|
||||
|
||||
**The boundary is deliberately small.** Only two things cross it — 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.
|
||||
|
||||
## The port is a credential, not just an address
|
||||
|
||||
`deriveRelayToken` is `HMAC(key, 'officer-browser-relay-v1:${port}:${userId}:${salt}')`. The port is an
|
||||
**input**. Move it and every paired browser fails authentication, reporting "Relay not reachable" — which
|
||||
SETUP.md attributes to a wrong address or token, so the real cause is invisible.
|
||||
|
||||
It therefore cannot be kernel-assigned the way other sidecars' listeners are: the extension is configured
|
||||
by hand and stores the number. 18792 is what it was before the extraction and what it stays.
|
||||
|
||||
## Per-account, unlike desktop
|
||||
|
||||
The desktop plugin mirrors one physical screen and is owner-only because there is only one and it is the
|
||||
owner's. This is the opposite: each account pairs **its own** browser, the relay resolves an extension's
|
||||
token to the account that minted it, and two people on one server drive two different browsers. So the
|
||||
permission is ordinarily grantable.
|
||||
|
||||
## What changed on the way out
|
||||
|
||||
Three things that are normally breaking and were free exactly once — see `BLOCKERS.md` §1. Short version:
|
||||
its own signing key, its own table, and the same port on purpose.
|
||||
|
||||
## Iris
|
||||
|
||||
`plugins/iris` captures pages using a browser profile at `~/.config/officer/browser-profile`. That is the
|
||||
**desktop** plugin's browser, not this one — iris drives a browser it launches itself, while this attaches
|
||||
to one a person is using. They are neighbours, not layers.
|
||||
Reference in New Issue
Block a user