@jobshimo/browser-link 0.41.0 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +12 -4
  2. package/dist/admin/actions.d.ts +57 -0
  3. package/dist/admin/actions.js +163 -0
  4. package/dist/admin/actions.js.map +1 -0
  5. package/dist/admin/descriptor.d.ts +65 -0
  6. package/dist/admin/descriptor.js +205 -0
  7. package/dist/admin/descriptor.js.map +1 -0
  8. package/dist/admin/i18n.d.ts +90 -0
  9. package/dist/admin/i18n.js +203 -0
  10. package/dist/admin/i18n.js.map +1 -0
  11. package/dist/admin/primary.d.ts +21 -0
  12. package/dist/admin/primary.js +56 -0
  13. package/dist/admin/primary.js.map +1 -0
  14. package/dist/admin/sources.d.ts +22 -0
  15. package/dist/admin/sources.js +82 -0
  16. package/dist/admin/sources.js.map +1 -0
  17. package/dist/bridge/ws-bridge.d.ts +38 -1
  18. package/dist/bridge/ws-bridge.js +114 -0
  19. package/dist/bridge/ws-bridge.js.map +1 -1
  20. package/dist/extension/admin-view.d.ts +95 -0
  21. package/dist/extension/admin-view.js +164 -0
  22. package/dist/extension/admin-view.js.map +1 -0
  23. package/dist/extension/background.js +76 -0
  24. package/dist/extension/background.js.map +1 -1
  25. package/dist/extension/manifest.json +1 -1
  26. package/dist/extension/popup.html +113 -0
  27. package/dist/extension/popup.js +206 -0
  28. package/dist/extension/popup.js.map +1 -1
  29. package/dist/messages.d.ts +86 -2
  30. package/dist/server.js +14 -0
  31. package/dist/server.js.map +1 -1
  32. package/dist/ui/app.d.ts +4 -1
  33. package/dist/ui/app.js +55 -42
  34. package/dist/ui/app.js.map +1 -1
  35. package/dist/ui/screens/index.d.ts +1 -0
  36. package/dist/ui/screens/index.js +1 -0
  37. package/dist/ui/screens/index.js.map +1 -1
  38. package/dist/ui/screens/menu.d.ts +11 -1
  39. package/dist/ui/screens/menu.js +12 -4
  40. package/dist/ui/screens/menu.js.map +1 -1
  41. package/dist/ui/screens/status.d.ts +19 -0
  42. package/dist/ui/screens/status.js +67 -0
  43. package/dist/ui/screens/status.js.map +1 -0
  44. package/package.json +1 -1
@@ -0,0 +1,203 @@
1
+ const v = (version) => (version === null ? 'unversioned' : `v${version}`);
2
+ export const ADMIN_I18N = {
3
+ en: {
4
+ ui: {
5
+ confirm: 'Confirm',
6
+ cancel: 'Cancel',
7
+ apply: 'Apply',
8
+ notConnected: 'Connect a tab to manage browser-link from here.',
9
+ restartRequired: 'Restart your MCP client for this change to take effect.',
10
+ changed: 'Saved.',
11
+ },
12
+ sections: { status: 'Status', setup: 'Setup', diagnose: 'Diagnose', reference: 'Reference' },
13
+ on: 'on',
14
+ off: 'off',
15
+ unknown: 'unknown',
16
+ server: 'Server',
17
+ serverRunning: (version) => `running v${version}`,
18
+ serverNotRunning: 'not running',
19
+ serverNotRunningHint: 'Your MCP client starts it; open a session that uses browser-link.',
20
+ installed: 'Installed version',
21
+ installedOutdated: (installed) => `${installed} (outdated: restart needed)`,
22
+ pid: 'PID',
23
+ uptime: 'Uptime',
24
+ tabs: 'Tabs connected',
25
+ tabsUnknownHint: 'Only the running server knows; open the extension popup.',
26
+ ports: 'Ports',
27
+ multiAgentStatus: 'Multi-agent',
28
+ extension: 'Extension',
29
+ extensionAligned: (version) => `v${version} (aligned)`,
30
+ extensionMismatch: (ext, server) => `v${ext} (server v${server}: reload or update one)`,
31
+ extensionNotConnected: 'no tab connected',
32
+ instructionsFor: (client) => `Instructions — ${client}`,
33
+ instructionsState: (state) => {
34
+ switch (state.kind) {
35
+ case 'installed':
36
+ return `installed ${v(state.version)}`;
37
+ case 'installed-outdated':
38
+ return `outdated ${v(state.version)}`;
39
+ case 'not-installed':
40
+ return 'not installed';
41
+ case 'no-file':
42
+ return 'no instructions file';
43
+ case 'corrupt':
44
+ return 'corrupt (fix it by hand)';
45
+ }
46
+ },
47
+ cdpDirect: 'cdp-direct',
48
+ cdpDirectGranted: (minutes) => minutes === null ? 'on, granted (no expiry)' : `on, granted (${minutes} min left)`,
49
+ cdpDirectNoGrant: 'on, no active grant (browser-link cdp allow)',
50
+ update: 'Updates',
51
+ updateAvailable: (latest) => `v${latest} available`,
52
+ upToDate: 'up to date',
53
+ activity: 'Activity trail',
54
+ language: 'Language',
55
+ tool: (name) => `Tool ${name}`,
56
+ multiAgent: 'Multi-agent mode',
57
+ autoReelect: 'Auto-reelect on primary close',
58
+ instructionsAutoRefresh: 'Refresh the instructions block at server start',
59
+ cdpDirectEnabled: 'cdp-direct enabled',
60
+ cdpDirectPort: 'cdp-direct port',
61
+ cdpDirectGrantTtl: 'cdp-direct grant lifetime',
62
+ minutes: (n) => `${n} min`,
63
+ never: 'never expires',
64
+ activityEnabled: 'Record the activity trail',
65
+ activityRecordPayloads: 'Record tool payloads in the trail',
66
+ downloadsDir: 'Downloads folder',
67
+ idleTtl: 'Idle disconnect',
68
+ idleTtlHint: "Change it in the popup's Idle disconnect control or with browser-link config set idle-ttl",
69
+ flowRecording: 'Flow recording',
70
+ flowRecordingHint: "Change it in the popup's flow recording control or with browser-link config set flow-recording",
71
+ popupDefault: 'popup choice',
72
+ doctor: 'Run doctor',
73
+ updatesCheck: 'Check for updates',
74
+ instructionsInstall: (client) => `Install instructions block — ${client}`,
75
+ instructionsUninstall: (client) => `Remove instructions block — ${client}`,
76
+ register: (client) => `Register with ${client}`,
77
+ registered: 'registered',
78
+ notRegistered: 'not registered',
79
+ stop: 'Stop the server / free the port',
80
+ stopDescription: 'Ends the running server, so it is only available from a terminal.',
81
+ selfUpdate: 'Update browser-link',
82
+ selfUpdateDescription: 'Replaces the running server, so it is only available from a terminal.',
83
+ about: 'About',
84
+ aboutText: 'browser-link is an MCP server that lets agents observe and act on the Chrome tabs you connect.',
85
+ repo: 'GitHub repository',
86
+ extensionPath: 'Extension folder',
87
+ extensionNotFound: 'not found next to the server',
88
+ unknownAction: (id) => `Unknown admin action: ${id}`,
89
+ notAnAction: (id) => `${id} is read-only`,
90
+ invalidValue: (id) => `Invalid value for ${id}`,
91
+ previewToolOn: (name) => `Enable tool ${name} for every agent`,
92
+ previewToolOff: (name) => `Disable tool ${name} for every agent`,
93
+ previewToggle: (label, on) => `Turn ${on ? 'on' : 'off'}: ${label}`,
94
+ previewSelect: (label, option) => `Set ${label} to ${option}`,
95
+ previewInstall: (client) => `Write the browser-link instructions block into ${client}'s file`,
96
+ previewUninstall: (client) => `Remove the browser-link instructions block from ${client}'s file`,
97
+ previewRegister: (client) => `Register browser-link in ${client}'s MCP config`,
98
+ },
99
+ es: {
100
+ ui: {
101
+ confirm: 'Confirmar',
102
+ cancel: 'Cancelar',
103
+ apply: 'Aplicar',
104
+ notConnected: 'Conectá una pestaña para administrar browser-link desde acá.',
105
+ restartRequired: 'Reiniciá tu cliente MCP para que el cambio tenga efecto.',
106
+ changed: 'Guardado.',
107
+ },
108
+ sections: {
109
+ status: 'Estado',
110
+ setup: 'Configuración',
111
+ diagnose: 'Diagnóstico',
112
+ reference: 'Referencia',
113
+ },
114
+ on: 'activado',
115
+ off: 'desactivado',
116
+ unknown: 'desconocido',
117
+ server: 'Servidor',
118
+ serverRunning: (version) => `en marcha v${version}`,
119
+ serverNotRunning: 'detenido',
120
+ serverNotRunningHint: 'Lo arranca tu cliente MCP; abrí una sesión que use browser-link.',
121
+ installed: 'Versión instalada',
122
+ installedOutdated: (installed) => `${installed} (desactualizado: hace falta reiniciar)`,
123
+ pid: 'PID',
124
+ uptime: 'Tiempo en marcha',
125
+ tabs: 'Pestañas conectadas',
126
+ tabsUnknownHint: 'Solo lo sabe el servidor en marcha; abrí el popup de la extensión.',
127
+ ports: 'Puertos',
128
+ multiAgentStatus: 'Multi-agente',
129
+ extension: 'Extensión',
130
+ extensionAligned: (version) => `v${version} (alineada)`,
131
+ extensionMismatch: (ext, server) => `v${ext} (servidor v${server}: recargá o actualizá uno de los dos)`,
132
+ extensionNotConnected: 'ninguna pestaña conectada',
133
+ instructionsFor: (client) => `Instrucciones — ${client}`,
134
+ instructionsState: (state) => {
135
+ switch (state.kind) {
136
+ case 'installed':
137
+ return `instalado ${state.version === null ? 'sin versión' : `v${state.version}`}`;
138
+ case 'installed-outdated':
139
+ return `desactualizado ${state.version === null ? 'sin versión' : `v${state.version}`}`;
140
+ case 'not-installed':
141
+ return 'no instalado';
142
+ case 'no-file':
143
+ return 'sin archivo de instrucciones';
144
+ case 'corrupt':
145
+ return 'corrupto (arreglalo a mano)';
146
+ }
147
+ },
148
+ cdpDirect: 'cdp-direct',
149
+ cdpDirectGranted: (minutes) => minutes === null
150
+ ? 'activado, con permiso (sin vencimiento)'
151
+ : `activado, con permiso (quedan ${minutes} min)`,
152
+ cdpDirectNoGrant: 'activado, sin permiso vigente (browser-link cdp allow)',
153
+ update: 'Actualizaciones',
154
+ updateAvailable: (latest) => `v${latest} disponible`,
155
+ upToDate: 'al día',
156
+ activity: 'Registro de actividad',
157
+ language: 'Idioma',
158
+ tool: (name) => `Tool ${name}`,
159
+ multiAgent: 'Modo multi-agente',
160
+ autoReelect: 'Re-elección automática al cerrar el primary',
161
+ instructionsAutoRefresh: 'Refrescar el bloque de instrucciones al arrancar el servidor',
162
+ cdpDirectEnabled: 'cdp-direct activado',
163
+ cdpDirectPort: 'Puerto de cdp-direct',
164
+ cdpDirectGrantTtl: 'Duración del permiso de cdp-direct',
165
+ minutes: (n) => `${n} min`,
166
+ never: 'no vence',
167
+ activityEnabled: 'Guardar el registro de actividad',
168
+ activityRecordPayloads: 'Guardar los payloads de las tools en el registro',
169
+ downloadsDir: 'Carpeta de descargas',
170
+ idleTtl: 'Desconexión por inactividad',
171
+ idleTtlHint: 'Se cambia desde el popup (Desconexión por inactividad) o con browser-link config set idle-ttl',
172
+ flowRecording: 'Grabación de flows',
173
+ flowRecordingHint: 'Se cambia desde el popup (grabación de flows) o con browser-link config set flow-recording',
174
+ popupDefault: 'lo elegido en el popup',
175
+ doctor: 'Ejecutar doctor',
176
+ updatesCheck: 'Buscar actualizaciones',
177
+ instructionsInstall: (client) => `Instalar el bloque de instrucciones — ${client}`,
178
+ instructionsUninstall: (client) => `Quitar el bloque de instrucciones — ${client}`,
179
+ register: (client) => `Registrar en ${client}`,
180
+ registered: 'registrado',
181
+ notRegistered: 'sin registrar',
182
+ stop: 'Detener el servidor / liberar el puerto',
183
+ stopDescription: 'Termina el servidor en marcha, por eso solo se hace desde una terminal.',
184
+ selfUpdate: 'Actualizar browser-link',
185
+ selfUpdateDescription: 'Reemplaza el servidor en marcha, por eso solo se hace desde una terminal.',
186
+ about: 'Acerca de',
187
+ aboutText: 'browser-link es un servidor MCP que deja a los agentes ver y actuar sobre las pestañas de Chrome que conectás.',
188
+ repo: 'Repositorio en GitHub',
189
+ extensionPath: 'Carpeta de la extensión',
190
+ extensionNotFound: 'no se encontró junto al servidor',
191
+ unknownAction: (id) => `Acción de administración desconocida: ${id}`,
192
+ notAnAction: (id) => `${id} es de solo lectura`,
193
+ invalidValue: (id) => `Valor inválido para ${id}`,
194
+ previewToolOn: (name) => `Habilitar la tool ${name} para todos los agentes`,
195
+ previewToolOff: (name) => `Deshabilitar la tool ${name} para todos los agentes`,
196
+ previewToggle: (label, on) => `${on ? 'Activar' : 'Desactivar'}: ${label}`,
197
+ previewSelect: (label, option) => `Cambiar ${label} a ${option}`,
198
+ previewInstall: (client) => `Escribir el bloque de instrucciones de browser-link en el archivo de ${client}`,
199
+ previewUninstall: (client) => `Quitar el bloque de instrucciones de browser-link del archivo de ${client}`,
200
+ previewRegister: (client) => `Registrar browser-link en la configuración MCP de ${client}`,
201
+ },
202
+ };
203
+ //# sourceMappingURL=i18n.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.js","sourceRoot":"","sources":["../../src/admin/i18n.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { type UpdateCheckResult } from '../commands/update-check.js';
2
+ import type { AdminHandlers } from '../bridge/ws-bridge.js';
3
+ /**
4
+ * The primary's admin handlers for the WS bridge. In process: the server
5
+ * rows describe this very process, the tab count and the extension version
6
+ * come from the live sessions. The update check never runs at boot: the
7
+ * first `admin.describe` starts one in the background (honouring the 6 h
8
+ * disk cache) and later states carry its result; the `updates.check` button
9
+ * forces a fresh one. `doctor` runs only on its button, because it is slow.
10
+ */
11
+ export interface PrimaryAdminOptions {
12
+ version: string;
13
+ tabsConnected: () => number;
14
+ connectedExtensionVersion: () => string | null;
15
+ /** Test seams; production leaves them unset. */
16
+ checkUpdates?: (opts: {
17
+ force?: boolean;
18
+ }) => Promise<UpdateCheckResult>;
19
+ doctor?: () => Promise<string[]>;
20
+ }
21
+ export declare function createPrimaryAdminHandlers(opts: PrimaryAdminOptions): AdminHandlers;
@@ -0,0 +1,56 @@
1
+ import { loadConfig } from '../config.js';
2
+ import { buildServerStatus } from '../commands/doctor.js';
3
+ import { checkForUpdates } from '../commands/update-check.js';
4
+ import { formatUpdate } from '../commands/updates.js';
5
+ import { buildVersionInfo } from '../lifecycle/version-endpoint.js';
6
+ import { readInstalledVersion } from '../version.js';
7
+ import { applyAdminAction } from './actions.js';
8
+ import { adminLanguage, buildAdminState } from './descriptor.js';
9
+ import { gatherAdminInput } from './sources.js';
10
+ async function runDoctorLines() {
11
+ const { runDoctor, formatDoctor } = await import('../commands/doctor.js');
12
+ return formatDoctor(await runDoctor(), adminLanguage(loadConfig())).split('\n');
13
+ }
14
+ export function createPrimaryAdminHandlers(opts) {
15
+ const check = opts.checkUpdates ?? checkForUpdates;
16
+ let update = null;
17
+ let backgroundCheck = null;
18
+ const input = () => gatherAdminInput({
19
+ server: () => Promise.resolve(buildServerStatus({ kind: 'version', info: buildVersionInfo(opts.version) }, readInstalledVersion())),
20
+ version: opts.version,
21
+ tabsConnected: opts.tabsConnected,
22
+ connectedExtensionVersion: opts.connectedExtensionVersion,
23
+ update: () => update,
24
+ });
25
+ const startBackgroundCheck = () => {
26
+ if (backgroundCheck)
27
+ return;
28
+ backgroundCheck = check({})
29
+ .then((r) => {
30
+ update = { latest: r.latest, isNewer: r.isNewer };
31
+ })
32
+ .catch(() => {
33
+ /* passive: an unreachable registry leaves "unknown" */
34
+ });
35
+ };
36
+ const checkUpdatesLines = async () => {
37
+ const r = await check({ force: true });
38
+ update = { latest: r.latest, isNewer: r.isNewer };
39
+ const info = r.latest === null
40
+ ? { current: r.current, latest: null, newer: null }
41
+ : { current: r.current, latest: r.latest, newer: r.isNewer };
42
+ return formatUpdate(info, adminLanguage(loadConfig())).split('\n');
43
+ };
44
+ return {
45
+ describe: async () => {
46
+ startBackgroundCheck();
47
+ return buildAdminState(await input());
48
+ },
49
+ action: (req) => applyAdminAction(req, {
50
+ input,
51
+ doctor: opts.doctor ?? runDoctorLines,
52
+ checkUpdates: checkUpdatesLines,
53
+ }),
54
+ };
55
+ }
56
+ //# sourceMappingURL=primary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"primary.js","sourceRoot":"","sources":["../../src/admin/primary.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import { type DoctorServerStatus } from '../commands/doctor.js';
2
+ import type { AdminInput } from './descriptor.js';
3
+ export interface AdminSourceOptions {
4
+ /** The running primary against the installed version. */
5
+ server: () => Promise<DoctorServerStatus>;
6
+ /** `null` when unknown (out of process). */
7
+ tabsConnected?: () => number | null;
8
+ /** Version a connected tab's extension reported; `null` when none did. */
9
+ connectedExtensionVersion?: () => string | null;
10
+ /** Version of the running server; default this module's `VERSION`. */
11
+ version?: string;
12
+ /** Last known update check; `null` when none ran yet. */
13
+ update?: () => {
14
+ latest: string | null;
15
+ isNewer: boolean;
16
+ } | null;
17
+ now?: () => number;
18
+ }
19
+ export declare function gatherAdminInput(opts: AdminSourceOptions): Promise<AdminInput>;
20
+ /** The console's view of the running primary: its `/version`, or "not
21
+ * running" when nothing answers. */
22
+ export declare function probeRunningServer(): Promise<DoctorServerStatus>;
@@ -0,0 +1,82 @@
1
+ import { loadConfig } from '../config.js';
2
+ import { IPC_PORT } from '../bridge/protocol.js';
3
+ import { detectInstructionsStatus } from '../manual/status.js';
4
+ import { INSTALLERS } from '../installers/index.js';
5
+ import { isGrantLive, loadGrant, remainingMs } from '../cdp/grant.js';
6
+ import { resolveDownloadsDir } from '../downloads.js';
7
+ import { getDataDir } from '../map/paths.js';
8
+ import { TOOL_CATALOGUE } from '../permissions.js';
9
+ import { resolveExtensionPath } from '../commands/extension.js';
10
+ import { buildServerStatus } from '../commands/doctor.js';
11
+ import { probeVersion } from '../lifecycle/election.js';
12
+ import { VERSION, readInstalledVersion } from '../version.js';
13
+ /**
14
+ * Gathers the facts `buildAdminState` needs from the real machine. Shared by
15
+ * the primary (in process: it knows its tabs and is the running server) and
16
+ * the console's status view (out of process: it asks the primary's
17
+ * `/version`, and the tab count stays unknown). Every source is a
18
+ * best-effort read: one that throws degrades to an empty or unknown value.
19
+ */
20
+ /** The WS bridge port; `ws-bridge.ts` owns it, repeated here (as `doctor`
21
+ * does) so the console does not load the bridge to read a number. */
22
+ const WS_BRIDGE_PORT = 17529;
23
+ function safe(read, fallback) {
24
+ try {
25
+ return read();
26
+ }
27
+ catch {
28
+ return fallback;
29
+ }
30
+ }
31
+ function detectClients() {
32
+ const out = [];
33
+ for (const installer of INSTALLERS) {
34
+ try {
35
+ const d = installer.detect();
36
+ out.push({
37
+ id: installer.id,
38
+ displayName: installer.displayName,
39
+ installed: d.installed,
40
+ registered: d.registered,
41
+ });
42
+ }
43
+ catch {
44
+ /* best effort: an unreadable client is left out */
45
+ }
46
+ }
47
+ return out;
48
+ }
49
+ export async function gatherAdminInput(opts) {
50
+ const config = safe(() => loadConfig(), {});
51
+ const grant = safe(() => loadGrant(), null);
52
+ const now = opts.now?.() ?? Date.now();
53
+ const server = await opts.server().catch(() => ({
54
+ running: null,
55
+ installed: readInstalledVersion(),
56
+ outdated: false,
57
+ }));
58
+ return {
59
+ config,
60
+ now,
61
+ version: opts.version ?? VERSION,
62
+ server,
63
+ tabsConnected: opts.tabsConnected?.() ?? null,
64
+ ports: { ws: WS_BRIDGE_PORT, ipc: IPC_PORT },
65
+ extension: {
66
+ path: safe(() => resolveExtensionPath(), null),
67
+ connectedVersion: opts.connectedExtensionVersion?.() ?? null,
68
+ },
69
+ instructions: safe(() => detectInstructionsStatus(), []),
70
+ clients: detectClients(),
71
+ cdpGrant: { live: isGrantLive(grant, now), remainingMs: remainingMs(grant, now) },
72
+ update: opts.update?.() ?? null,
73
+ toolNames: TOOL_CATALOGUE.map((t) => t.name),
74
+ downloadsDir: safe(() => resolveDownloadsDir(config, getDataDir()), ''),
75
+ };
76
+ }
77
+ /** The console's view of the running primary: its `/version`, or "not
78
+ * running" when nothing answers. */
79
+ export async function probeRunningServer() {
80
+ return buildServerStatus(await probeVersion(WS_BRIDGE_PORT), readInstalledVersion());
81
+ }
82
+ //# sourceMappingURL=sources.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sources.js","sourceRoot":"","sources":["../../src/admin/sources.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { WebSocket, WebSocketServer } from 'ws';
2
- import type { ActivityPurgeMessage, ActivityQueryMessage, FlowRecordedPayload, ServerToExtension, SettingsUpdateMessage, TabRegisterPayload, TabRegisteredPayload } from '../messages.js';
2
+ import type { ActivityPurgeMessage, AdminActionMessage, AdminActionResult, AdminDescribeMessage, AdminState, ActivityQueryMessage, ExtensionToServer, FlowRecordedPayload, ServerToExtension, SettingsUpdateMessage, TabRegisterPayload, TabRegisteredPayload } from '../messages.js';
3
3
  import { type BridgeEventLog } from './events.js';
4
4
  export declare const WS_HOST = "127.0.0.1";
5
5
  export declare const WS_PORT = 17529;
@@ -26,6 +26,19 @@ export interface WsBridgeOptions {
26
26
  /** Starts the popup "Repair connection" restart (see lifecycle/repair.ts).
27
27
  * Absent → `bridge.repair` is answered with `ok: false`. */
28
28
  onRepair?: () => boolean;
29
+ /** The admin panel (admin/descriptor.ts, admin/actions.ts), wired by the
30
+ * primary. Absent → `admin.*` is answered with `ok: false`. */
31
+ admin?: AdminHandlers;
32
+ }
33
+ /** What the primary hands the bridge to answer `admin.describe` and
34
+ * `admin.action`. Both may reject; the bridge turns that into `ok: false`. */
35
+ export interface AdminHandlers {
36
+ describe: () => Promise<AdminState>;
37
+ action: (req: {
38
+ id: string;
39
+ value?: string | number | boolean;
40
+ confirm?: boolean;
41
+ }) => Promise<AdminActionResult>;
29
42
  }
30
43
  /** The effective bind target and reported version. Exported so the
31
44
  * defaults are testable without binding the fixed port. */
@@ -47,6 +60,15 @@ export declare function buildTabRegisteredPayload(tabId: string, serverVersion:
47
60
  * `WebSocketServer` (same Chromium-only `verifyClient`), `GET /version`
48
61
  * answers the election probe and every other plain request is a 404.
49
62
  * Closing the returned `WebSocketServer` also closes the HTTP server. */
63
+ /**
64
+ * Whether a WebSocket handshake's `Origin` may open the bridge. A web page
65
+ * running inside Chrome passes the peer-process check (the owning process is
66
+ * Chromium) but always announces its own http(s) origin; only the extension's
67
+ * own context (`chrome-extension://<id>`) is allowed, and a missing `Origin`
68
+ * (non-browser clients) is left to the peer-process check. Pure; exported for
69
+ * tests. Since 0.42.0.
70
+ */
71
+ export declare function isAllowedHandshakeOrigin(origin: string | undefined): boolean;
50
72
  export declare function startWsBridge(tabs: Map<string, TabSession>, pendingRequests: Map<string, PendingRequest>, events: BridgeEventLog, options?: WsBridgeOptions): Promise<WebSocketServer>;
51
73
  /**
52
74
  * Build the `settings.update` a freshly-registered tab should receive, or
@@ -140,6 +162,21 @@ export declare function handleFlowRecordedMessage(payload: FlowRecordedPayload,
140
162
  * audit panel must not take the bridge connection down with it.
141
163
  */
142
164
  export declare function handleActivityQueryMessage(msg: ActivityQueryMessage): ServerToExtension;
165
+ /**
166
+ * Answer one `admin.describe` or `admin.action` frame. Exported so it is
167
+ * unit-testable directly, like `handleActivityQueryMessage`. Never rejects:
168
+ * a handler that throws, a malformed frame or a bridge without handlers
169
+ * comes back as `ok: false`. Only the typed fields of an action reach the
170
+ * handler.
171
+ */
172
+ export declare function handleAdminMessage(msg: AdminDescribeMessage | AdminActionMessage, admin?: AdminHandlers): Promise<ServerToExtension>;
173
+ /**
174
+ * The request/response frames the bridge answers without a tab scope:
175
+ * `activity.query` (synchronous, as before) and `admin.*` (asynchronous).
176
+ * `null` for every other kind. Exported so the dispatch is testable without
177
+ * binding the bridge port.
178
+ */
179
+ export declare function routeRequestMessage(msg: ExtensionToServer, admin?: AdminHandlers): ServerToExtension | Promise<ServerToExtension> | null;
143
180
  /**
144
181
  * Answer one `activity.purge` frame.
145
182
  *
@@ -84,6 +84,19 @@ export function buildTabRegisteredPayload(tabId, serverVersion, installedVersion
84
84
  * `WebSocketServer` (same Chromium-only `verifyClient`), `GET /version`
85
85
  * answers the election probe and every other plain request is a 404.
86
86
  * Closing the returned `WebSocketServer` also closes the HTTP server. */
87
+ /**
88
+ * Whether a WebSocket handshake's `Origin` may open the bridge. A web page
89
+ * running inside Chrome passes the peer-process check (the owning process is
90
+ * Chromium) but always announces its own http(s) origin; only the extension's
91
+ * own context (`chrome-extension://<id>`) is allowed, and a missing `Origin`
92
+ * (non-browser clients) is left to the peer-process check. Pure; exported for
93
+ * tests. Since 0.42.0.
94
+ */
95
+ export function isAllowedHandshakeOrigin(origin) {
96
+ if (origin === undefined || origin === '')
97
+ return true;
98
+ return origin.startsWith('chrome-extension://');
99
+ }
87
100
  export function startWsBridge(tabs, pendingRequests, events, options = {}) {
88
101
  const { host, port, version } = resolveBridgeOptions(options);
89
102
  return new Promise((resolve, reject) => {
@@ -95,6 +108,13 @@ export function startWsBridge(tabs, pendingRequests, events, options = {}) {
95
108
  // Refuse the upgrade unless the OS confirms the peer is a Chromium-based
96
109
  // browser. The check happens before any application bytes are exchanged.
97
110
  verifyClient: (info, cb) => {
111
+ // ws fills info.origin from the Origin header (undefined when absent).
112
+ const origin = info.origin;
113
+ if (!isAllowedHandshakeOrigin(origin)) {
114
+ log(`Rejected WS handshake from origin ${origin}: only the extension may connect.`);
115
+ cb(false, 403, 'Origin not allowed');
116
+ return;
117
+ }
98
118
  const remoteAddress = normaliseLoopback(info.req.socket.remoteAddress ?? '');
99
119
  const remotePort = info.req.socket.remotePort;
100
120
  if (!remoteAddress || remotePort == null) {
@@ -274,6 +294,19 @@ export function startWsBridge(tabs, pendingRequests, events, options = {}) {
274
294
  }, options.onRepair);
275
295
  return;
276
296
  }
297
+ if (msg.kind === 'admin.describe' || msg.kind === 'admin.action') {
298
+ // Same boundary as activity.query below: not tab-scoped, any
299
+ // authenticated extension connection may ask. The action side is a
300
+ // closed allowlist with a confirm step for every disk write
301
+ // (admin/actions.ts), so the socket can only reach the writes the
302
+ // console already offers. Async (doctor, update check): the reply
303
+ // goes out only while the socket is still open.
304
+ void handleAdminMessage(msg, options.admin).then((reply) => {
305
+ if (ws.readyState === WebSocket.OPEN)
306
+ send(ws, reply);
307
+ });
308
+ return;
309
+ }
277
310
  if (msg.kind === 'activity.query') {
278
311
  // Read-only and deliberately NOT scoped to `assignedTabId`, unlike
279
312
  // `flow.recorded` above. The Activity window's whole job is showing
@@ -506,6 +539,87 @@ export function handleActivityQueryMessage(msg) {
506
539
  };
507
540
  }
508
541
  }
542
+ function errorMessage(error) {
543
+ return error instanceof Error ? error.message : String(error);
544
+ }
545
+ const ADMIN_UNAVAILABLE = 'admin panel unavailable';
546
+ /**
547
+ * Answer one `admin.describe` or `admin.action` frame. Exported so it is
548
+ * unit-testable directly, like `handleActivityQueryMessage`. Never rejects:
549
+ * a handler that throws, a malformed frame or a bridge without handlers
550
+ * comes back as `ok: false`. Only the typed fields of an action reach the
551
+ * handler.
552
+ */
553
+ export async function handleAdminMessage(msg, admin) {
554
+ if (msg.kind === 'admin.describe') {
555
+ if (!admin) {
556
+ return {
557
+ kind: 'admin.state',
558
+ request_id: msg.request_id,
559
+ ok: false,
560
+ error: ADMIN_UNAVAILABLE,
561
+ };
562
+ }
563
+ try {
564
+ return {
565
+ kind: 'admin.state',
566
+ request_id: msg.request_id,
567
+ ok: true,
568
+ state: await admin.describe(),
569
+ };
570
+ }
571
+ catch (error) {
572
+ return {
573
+ kind: 'admin.state',
574
+ request_id: msg.request_id,
575
+ ok: false,
576
+ error: errorMessage(error),
577
+ };
578
+ }
579
+ }
580
+ const fail = (error) => ({
581
+ kind: 'admin.action.result',
582
+ request_id: msg.request_id,
583
+ result: { ok: false, error },
584
+ });
585
+ if (!admin)
586
+ return fail(ADMIN_UNAVAILABLE);
587
+ const raw = msg.id;
588
+ if (typeof raw !== 'string')
589
+ return fail('admin.action: id must be a string');
590
+ const value = msg.value;
591
+ const req = {
592
+ id: raw,
593
+ ...(typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean'
594
+ ? { value }
595
+ : {}),
596
+ ...(msg.confirm === true ? { confirm: true } : {}),
597
+ };
598
+ try {
599
+ return {
600
+ kind: 'admin.action.result',
601
+ request_id: msg.request_id,
602
+ result: await admin.action(req),
603
+ };
604
+ }
605
+ catch (error) {
606
+ return fail(errorMessage(error));
607
+ }
608
+ }
609
+ /**
610
+ * The request/response frames the bridge answers without a tab scope:
611
+ * `activity.query` (synchronous, as before) and `admin.*` (asynchronous).
612
+ * `null` for every other kind. Exported so the dispatch is testable without
613
+ * binding the bridge port.
614
+ */
615
+ export function routeRequestMessage(msg, admin) {
616
+ if (msg.kind === 'activity.query')
617
+ return handleActivityQueryMessage(msg);
618
+ if (msg.kind === 'admin.describe' || msg.kind === 'admin.action') {
619
+ return handleAdminMessage(msg, admin);
620
+ }
621
+ return null;
622
+ }
509
623
  /** camelCase store shape → snake_case wire shape. One mapper so the two can
510
624
  * never drift into reporting different numbers under the same name. */
511
625
  function toStatsPayload(s) {