@elizaos/plugin-browser 2.0.0-beta.1 → 2.0.11-beta.7
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.
- package/LICENSE +21 -0
- package/README.md +106 -64
- package/dist/actions/browser-autofill-login.d.ts.map +1 -1
- package/dist/actions/browser-autofill-login.js.map +1 -1
- package/dist/actions/browser.d.ts +5 -6
- package/dist/actions/browser.d.ts.map +1 -1
- package/dist/actions/browser.js +54 -59
- package/dist/actions/browser.js.map +1 -1
- package/dist/actions/manage-browser-bridge.d.ts.map +1 -1
- package/dist/actions/manage-browser-bridge.js +10 -14
- package/dist/actions/manage-browser-bridge.js.map +1 -1
- package/dist/bridge-policy.d.ts +10 -0
- package/dist/bridge-policy.d.ts.map +1 -0
- package/dist/bridge-policy.js +37 -0
- package/dist/bridge-policy.js.map +1 -0
- package/dist/bridge-readiness.d.ts +16 -0
- package/dist/bridge-readiness.d.ts.map +1 -0
- package/dist/bridge-readiness.js +82 -0
- package/dist/bridge-readiness.js.map +1 -0
- package/dist/bridge-records.d.ts +9 -0
- package/dist/bridge-records.d.ts.map +1 -0
- package/dist/bridge-records.js +37 -0
- package/dist/bridge-records.js.map +1 -0
- package/dist/browser-capture-hooks.d.ts +9 -0
- package/dist/browser-capture-hooks.d.ts.map +1 -0
- package/dist/browser-capture-hooks.js +15 -0
- package/dist/browser-capture-hooks.js.map +1 -0
- package/dist/browser-service.d.ts +22 -4
- package/dist/browser-service.d.ts.map +1 -1
- package/dist/browser-service.js +63 -15
- package/dist/browser-service.js.map +1 -1
- package/dist/browser-workspace-hooks.d.ts +14 -0
- package/dist/browser-workspace-hooks.d.ts.map +1 -0
- package/dist/browser-workspace-hooks.js +15 -0
- package/dist/browser-workspace-hooks.js.map +1 -0
- package/dist/companion-auth.d.ts +34 -0
- package/dist/companion-auth.d.ts.map +1 -0
- package/dist/companion-auth.js +98 -0
- package/dist/companion-auth.js.map +1 -0
- package/dist/index.d.ts +9 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +46 -11
- package/dist/index.js.map +1 -1
- package/dist/message-adapter.d.ts +9 -0
- package/dist/message-adapter.d.ts.map +1 -0
- package/dist/message-adapter.js +104 -0
- package/dist/message-adapter.js.map +1 -0
- package/dist/packaging.d.ts.map +1 -1
- package/dist/packaging.js +2 -0
- package/dist/packaging.js.map +1 -1
- package/dist/password-manager-bridge.d.ts +50 -0
- package/dist/password-manager-bridge.d.ts.map +1 -0
- package/dist/password-manager-bridge.js +437 -0
- package/dist/password-manager-bridge.js.map +1 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +8 -4
- package/dist/plugin.js.map +1 -1
- package/dist/providers/workspace.d.ts +1 -1
- package/dist/providers/workspace.js.map +1 -1
- package/dist/routes/bridge.d.ts.map +1 -1
- package/dist/routes/bridge.js +63 -14
- package/dist/routes/bridge.js.map +1 -1
- package/dist/routes/workspace-setup.d.ts.map +1 -1
- package/dist/routes/workspace-setup.js +1 -1
- package/dist/routes/workspace-setup.js.map +1 -1
- package/dist/routes/workspace.d.ts +1 -2
- package/dist/routes/workspace.d.ts.map +1 -1
- package/dist/routes/workspace.js +63 -3
- package/dist/routes/workspace.js.map +1 -1
- package/dist/schema.d.ts +2 -2
- package/dist/schema.js.map +1 -1
- package/dist/service.d.ts +1 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js.map +1 -1
- package/dist/targets/bridge-target.d.ts +1 -1
- package/dist/targets/bridge-target.d.ts.map +1 -1
- package/dist/targets/bridge-target.js.map +1 -1
- package/dist/targets/stagehand-target.d.ts +3 -0
- package/dist/targets/stagehand-target.d.ts.map +1 -0
- package/dist/targets/stagehand-target.js +187 -0
- package/dist/targets/stagehand-target.js.map +1 -0
- package/dist/workspace/browser-capture.d.ts +1 -1
- package/dist/workspace/browser-capture.js.map +1 -1
- package/dist/workspace/browser-workspace-desktop.d.ts +1 -1
- package/dist/workspace/browser-workspace-desktop.d.ts.map +1 -1
- package/dist/workspace/browser-workspace-desktop.js +47 -25
- package/dist/workspace/browser-workspace-desktop.js.map +1 -1
- package/dist/workspace/browser-workspace-forms.d.ts.map +1 -1
- package/dist/workspace/browser-workspace-forms.js +1 -1
- package/dist/workspace/browser-workspace-forms.js.map +1 -1
- package/dist/workspace/browser-workspace-helpers.d.ts +7 -0
- package/dist/workspace/browser-workspace-helpers.d.ts.map +1 -1
- package/dist/workspace/browser-workspace-helpers.js +37 -0
- package/dist/workspace/browser-workspace-helpers.js.map +1 -1
- package/dist/workspace/browser-workspace-network.d.ts +1 -1
- package/dist/workspace/browser-workspace-network.d.ts.map +1 -1
- package/dist/workspace/browser-workspace-types.d.ts +15 -0
- package/dist/workspace/browser-workspace-types.d.ts.map +1 -1
- package/dist/workspace/browser-workspace-types.js.map +1 -1
- package/dist/workspace/browser-workspace-web.d.ts.map +1 -1
- package/dist/workspace/browser-workspace-web.js +15 -88
- package/dist/workspace/browser-workspace-web.js.map +1 -1
- package/dist/workspace/browser-workspace.d.ts +1 -1
- package/dist/workspace/browser-workspace.d.ts.map +1 -1
- package/dist/workspace/browser-workspace.js +9 -4
- package/dist/workspace/browser-workspace.js.map +1 -1
- package/package.json +28 -7
- package/dist/actions/browser-autofill-login.d.js +0 -1
- package/dist/actions/browser-autofill-login.d.js.map +0 -1
- package/dist/actions/browser.d.js +0 -1
- package/dist/actions/browser.d.js.map +0 -1
- package/dist/actions/manage-browser-bridge.d.js +0 -1
- package/dist/actions/manage-browser-bridge.d.js.map +0 -1
- package/dist/ambient-jsdom.d.js +0 -1
- package/dist/ambient-jsdom.d.js.map +0 -1
- package/dist/browser-service.d.js +0 -1
- package/dist/browser-service.d.js.map +0 -1
- package/dist/contracts.d.js +0 -1
- package/dist/contracts.d.js.map +0 -1
- package/dist/index.d.js +0 -21
- package/dist/index.d.js.map +0 -1
- package/dist/lifeops-session-contracts.d.js +0 -1
- package/dist/lifeops-session-contracts.d.js.map +0 -1
- package/dist/packaging.d.js +0 -1
- package/dist/packaging.d.js.map +0 -1
- package/dist/plugin.d.js +0 -1
- package/dist/plugin.d.js.map +0 -1
- package/dist/providers/workspace.d.js +0 -1
- package/dist/providers/workspace.d.js.map +0 -1
- package/dist/routes/bridge.d.js +0 -1
- package/dist/routes/bridge.d.js.map +0 -1
- package/dist/routes/workspace-account-gate.d.js +0 -1
- package/dist/routes/workspace-account-gate.d.js.map +0 -1
- package/dist/routes/workspace-setup.d.js +0 -1
- package/dist/routes/workspace-setup.d.js.map +0 -1
- package/dist/routes/workspace.d.js +0 -1
- package/dist/routes/workspace.d.js.map +0 -1
- package/dist/schema.d.js +0 -1
- package/dist/schema.d.js.map +0 -1
- package/dist/service.d.js +0 -1
- package/dist/service.d.js.map +0 -1
- package/dist/targets/bridge-target.d.js +0 -1
- package/dist/targets/bridge-target.d.js.map +0 -1
- package/dist/workspace/browser-capture.d.js +0 -1
- package/dist/workspace/browser-capture.d.js.map +0 -1
- package/dist/workspace/browser-workspace-desktop.d.js +0 -1
- package/dist/workspace/browser-workspace-desktop.d.js.map +0 -1
- package/dist/workspace/browser-workspace-elements.d.js +0 -1
- package/dist/workspace/browser-workspace-elements.d.js.map +0 -1
- package/dist/workspace/browser-workspace-forms.d.js +0 -1
- package/dist/workspace/browser-workspace-forms.d.js.map +0 -1
- package/dist/workspace/browser-workspace-helpers.d.js +0 -1
- package/dist/workspace/browser-workspace-helpers.d.js.map +0 -1
- package/dist/workspace/browser-workspace-jsdom.d.js +0 -1
- package/dist/workspace/browser-workspace-jsdom.d.js.map +0 -1
- package/dist/workspace/browser-workspace-network.d.js +0 -1
- package/dist/workspace/browser-workspace-network.d.js.map +0 -1
- package/dist/workspace/browser-workspace-snapshots.d.js +0 -1
- package/dist/workspace/browser-workspace-snapshots.d.js.map +0 -1
- package/dist/workspace/browser-workspace-state.d.js +0 -1
- package/dist/workspace/browser-workspace-state.d.js.map +0 -1
- package/dist/workspace/browser-workspace-types.d.js +0 -1
- package/dist/workspace/browser-workspace-types.d.js.map +0 -1
- package/dist/workspace/browser-workspace-web.d.js +0 -1
- package/dist/workspace/browser-workspace-web.d.js.map +0 -1
- package/dist/workspace/browser-workspace.d.js +0 -11
- package/dist/workspace/browser-workspace.d.js.map +0 -1
- package/dist/workspace/index.d.js +0 -3
- package/dist/workspace/index.d.js.map +0 -1
package/dist/routes/bridge.js
CHANGED
|
@@ -82,7 +82,9 @@ function cleanupRateLimitBuckets(windowMs) {
|
|
|
82
82
|
lastRateLimitCleanup = now;
|
|
83
83
|
const cutoff = now - windowMs;
|
|
84
84
|
for (const [key, entry] of rateLimitBuckets) {
|
|
85
|
-
entry.timestamps = entry.timestamps.filter(
|
|
85
|
+
entry.timestamps = entry.timestamps.filter(
|
|
86
|
+
(timestamp) => timestamp > cutoff
|
|
87
|
+
);
|
|
86
88
|
if (entry.timestamps.length === 0) rateLimitBuckets.delete(key);
|
|
87
89
|
}
|
|
88
90
|
}
|
|
@@ -158,7 +160,8 @@ function createBrowserBridgeTelemetrySpan(meta) {
|
|
|
158
160
|
durationMs: Math.max(0, Date.now() - startedAt)
|
|
159
161
|
};
|
|
160
162
|
if (typeof meta.timeoutMs === "number") event.timeoutMs = meta.timeoutMs;
|
|
161
|
-
if (typeof args?.statusCode === "number")
|
|
163
|
+
if (typeof args?.statusCode === "number")
|
|
164
|
+
event.statusCode = args.statusCode;
|
|
162
165
|
if (outcome === "failure") {
|
|
163
166
|
event.errorKind = sanitizeTelemetryToken(args?.errorKind) ?? inferTelemetryErrorKind(args?.error);
|
|
164
167
|
}
|
|
@@ -187,6 +190,13 @@ function routeJsonError(ctx, message, status, code) {
|
|
|
187
190
|
status
|
|
188
191
|
);
|
|
189
192
|
}
|
|
193
|
+
function isBrowserBridgeRouteBodyObject(value) {
|
|
194
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
195
|
+
}
|
|
196
|
+
function rejectMalformedBrowserBridgePayload(ctx) {
|
|
197
|
+
ctx.error(ctx.res, "request body must be a JSON object", 400);
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
190
200
|
function isStatusError(error) {
|
|
191
201
|
return error instanceof Error && "status" in error && typeof error.status === "number";
|
|
192
202
|
}
|
|
@@ -327,6 +337,9 @@ async function handleBrowserBridgeRoutes(ctx) {
|
|
|
327
337
|
res
|
|
328
338
|
);
|
|
329
339
|
if (!body) return true;
|
|
340
|
+
if (!isBrowserBridgeRouteBodyObject(body)) {
|
|
341
|
+
return rejectMalformedBrowserBridgePayload(ctx);
|
|
342
|
+
}
|
|
330
343
|
return runRoute(ctx, async (service) => {
|
|
331
344
|
json(res, {
|
|
332
345
|
settings: await service.updateBrowserSettings(
|
|
@@ -342,6 +355,9 @@ async function handleBrowserBridgeRoutes(ctx) {
|
|
|
342
355
|
res
|
|
343
356
|
);
|
|
344
357
|
if (!body) return true;
|
|
358
|
+
if (!isBrowserBridgeRouteBodyObject(body)) {
|
|
359
|
+
return rejectMalformedBrowserBridgePayload(ctx);
|
|
360
|
+
}
|
|
345
361
|
return runRoute(ctx, async (service) => {
|
|
346
362
|
json(
|
|
347
363
|
res,
|
|
@@ -367,6 +383,9 @@ async function handleBrowserBridgeRoutes(ctx) {
|
|
|
367
383
|
}
|
|
368
384
|
const body = await readJsonBody(req, res);
|
|
369
385
|
if (!body) return true;
|
|
386
|
+
if (!isBrowserBridgeRouteBodyObject(body)) {
|
|
387
|
+
return rejectMalformedBrowserBridgePayload(ctx);
|
|
388
|
+
}
|
|
370
389
|
return runRoute(ctx, async (service) => {
|
|
371
390
|
json(
|
|
372
391
|
res,
|
|
@@ -445,6 +464,9 @@ async function handleBrowserBridgeRoutes(ctx) {
|
|
|
445
464
|
}
|
|
446
465
|
const body = await readJsonBody(req, res);
|
|
447
466
|
if (!body) return true;
|
|
467
|
+
if (!isBrowserBridgeRouteBodyObject(body)) {
|
|
468
|
+
return rejectMalformedBrowserBridgePayload(ctx);
|
|
469
|
+
}
|
|
448
470
|
if (typeof body.target !== "string" || !BROWSER_BRIDGE_PACKAGE_PATH_TARGETS.includes(
|
|
449
471
|
body.target
|
|
450
472
|
)) {
|
|
@@ -469,13 +491,17 @@ async function handleBrowserBridgeRoutes(ctx) {
|
|
|
469
491
|
if (rateLimitRequest(ctx, "companions:sync")) {
|
|
470
492
|
return true;
|
|
471
493
|
}
|
|
472
|
-
const body = await readJsonBody(req, res);
|
|
473
|
-
if (!body) return true;
|
|
474
494
|
return runRoute(ctx, async (service) => {
|
|
475
495
|
const auth = getBrowserCompanionAuth(ctx);
|
|
476
496
|
if (!auth) {
|
|
477
497
|
return;
|
|
478
498
|
}
|
|
499
|
+
const body = await readJsonBody(req, res);
|
|
500
|
+
if (!body) return;
|
|
501
|
+
if (!isBrowserBridgeRouteBodyObject(body)) {
|
|
502
|
+
rejectMalformedBrowserBridgePayload(ctx);
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
479
505
|
json(
|
|
480
506
|
res,
|
|
481
507
|
await service.syncBrowserCompanion(
|
|
@@ -587,6 +613,9 @@ async function handleBrowserBridgeRoutes(ctx) {
|
|
|
587
613
|
if (method === "POST" && pathname === "/api/browser-bridge/sync") {
|
|
588
614
|
const body = await readJsonBody(req, res);
|
|
589
615
|
if (!body) return true;
|
|
616
|
+
if (!isBrowserBridgeRouteBodyObject(body)) {
|
|
617
|
+
return rejectMalformedBrowserBridgePayload(ctx);
|
|
618
|
+
}
|
|
590
619
|
return runRoute(ctx, async (service) => {
|
|
591
620
|
json(res, await service.syncBrowserState(body, ctx.state.adminEntityId));
|
|
592
621
|
});
|
|
@@ -597,6 +626,9 @@ async function handleBrowserBridgeRoutes(ctx) {
|
|
|
597
626
|
res
|
|
598
627
|
);
|
|
599
628
|
if (!body) return true;
|
|
629
|
+
if (!isBrowserBridgeRouteBodyObject(body)) {
|
|
630
|
+
return rejectMalformedBrowserBridgePayload(ctx);
|
|
631
|
+
}
|
|
600
632
|
return runRoute(ctx, async (service) => {
|
|
601
633
|
json(
|
|
602
634
|
res,
|
|
@@ -650,6 +682,9 @@ async function handleBrowserBridgeRoutes(ctx) {
|
|
|
650
682
|
res
|
|
651
683
|
);
|
|
652
684
|
if (!body) return true;
|
|
685
|
+
if (!isBrowserBridgeRouteBodyObject(body)) {
|
|
686
|
+
return rejectMalformedBrowserBridgePayload(ctx);
|
|
687
|
+
}
|
|
653
688
|
return runRoute(ctx, async (service) => {
|
|
654
689
|
json(res, {
|
|
655
690
|
session: await service.confirmBrowserSession(
|
|
@@ -677,6 +712,9 @@ async function handleBrowserBridgeRoutes(ctx) {
|
|
|
677
712
|
res
|
|
678
713
|
);
|
|
679
714
|
if (!body) return true;
|
|
715
|
+
if (!isBrowserBridgeRouteBodyObject(body)) {
|
|
716
|
+
return rejectMalformedBrowserBridgePayload(ctx);
|
|
717
|
+
}
|
|
680
718
|
return runRoute(ctx, async (service) => {
|
|
681
719
|
json(res, {
|
|
682
720
|
session: await service.updateBrowserSessionProgress(
|
|
@@ -704,6 +742,9 @@ async function handleBrowserBridgeRoutes(ctx) {
|
|
|
704
742
|
res
|
|
705
743
|
);
|
|
706
744
|
if (!body) return true;
|
|
745
|
+
if (!isBrowserBridgeRouteBodyObject(body)) {
|
|
746
|
+
return rejectMalformedBrowserBridgePayload(ctx);
|
|
747
|
+
}
|
|
707
748
|
return runRoute(ctx, async (service) => {
|
|
708
749
|
json(res, {
|
|
709
750
|
session: await service.completeBrowserSession(
|
|
@@ -729,16 +770,20 @@ async function handleBrowserBridgeRoutes(ctx) {
|
|
|
729
770
|
"browser session id"
|
|
730
771
|
);
|
|
731
772
|
if (!sessionId) return true;
|
|
732
|
-
const body = await readJsonBody(
|
|
733
|
-
req,
|
|
734
|
-
res
|
|
735
|
-
);
|
|
736
|
-
if (!body) return true;
|
|
737
773
|
return runRoute(ctx, async (service) => {
|
|
738
774
|
const auth = getBrowserCompanionAuth(ctx);
|
|
739
775
|
if (!auth) {
|
|
740
776
|
return;
|
|
741
777
|
}
|
|
778
|
+
const body = await readJsonBody(
|
|
779
|
+
req,
|
|
780
|
+
res
|
|
781
|
+
);
|
|
782
|
+
if (!body) return;
|
|
783
|
+
if (!isBrowserBridgeRouteBodyObject(body)) {
|
|
784
|
+
rejectMalformedBrowserBridgePayload(ctx);
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
742
787
|
json(res, {
|
|
743
788
|
session: await service.updateBrowserSessionProgressFromCompanion(
|
|
744
789
|
auth.companionId,
|
|
@@ -765,16 +810,20 @@ async function handleBrowserBridgeRoutes(ctx) {
|
|
|
765
810
|
"browser session id"
|
|
766
811
|
);
|
|
767
812
|
if (!sessionId) return true;
|
|
768
|
-
const body = await readJsonBody(
|
|
769
|
-
req,
|
|
770
|
-
res
|
|
771
|
-
);
|
|
772
|
-
if (!body) return true;
|
|
773
813
|
return runRoute(ctx, async (service) => {
|
|
774
814
|
const auth = getBrowserCompanionAuth(ctx);
|
|
775
815
|
if (!auth) {
|
|
776
816
|
return;
|
|
777
817
|
}
|
|
818
|
+
const body = await readJsonBody(
|
|
819
|
+
req,
|
|
820
|
+
res
|
|
821
|
+
);
|
|
822
|
+
if (!body) return;
|
|
823
|
+
if (!isBrowserBridgeRouteBodyObject(body)) {
|
|
824
|
+
rejectMalformedBrowserBridgePayload(ctx);
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
778
827
|
json(res, {
|
|
779
828
|
session: await service.completeBrowserSessionFromCompanion(
|
|
780
829
|
auth.companionId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/routes/bridge.ts"],"sourcesContent":["/**\n * Agent Browser Bridge HTTP routes.\n *\n * Mounted under `/api/browser-bridge/*` by the plugin-collector. Pair +\n * settings + companion-sync + tabs + current-page + packaging endpoints\n * are fully generic. Session endpoints (confirm/progress/complete) still\n * operate on the LifeOps-owned `life_browser_sessions` table and therefore\n * call into the LifeOps route service because that app owns workflow-scoped\n * browser sessions.\n *\n * Companion/auto-pair/sync authentication uses the\n * `X-Browser-Bridge-Companion-Id` header paired with a bearer pairing\n * token. The old `X-LifeOps-Browser-Companion-Id` and legacy\n * `x-eliza-browser-companion-id` aliases were deliberately removed.\n */\n\nimport fs from \"node:fs\";\nimport type http from \"node:http\";\nimport type { ReadJsonBodyOptions } from \"@elizaos/core\";\nimport { type AgentRuntime, logger, type UUID } from \"@elizaos/core\";\nimport type {\n CompleteLifeOpsBrowserSessionRequest,\n ConfirmLifeOpsBrowserSessionRequest,\n CreateLifeOpsBrowserSessionRequest,\n} from \"../lifeops-session-contracts.js\";\nimport {\n type BrowserBridgeCompanionAuthErrorCode,\n BROWSER_BRIDGE_PACKAGE_PATH_TARGETS,\n type CreateBrowserBridgeCompanionAutoPairRequest,\n type CreateBrowserBridgeCompanionPairingRequest,\n type SyncBrowserBridgeStateRequest,\n type UpdateBrowserBridgeSessionProgressRequest,\n type UpdateBrowserBridgeSettingsRequest,\n} from \"../contracts.js\";\nimport {\n buildBrowserBridgeCompanionPackage,\n getBrowserBridgeCompanionDownloadFile,\n getBrowserBridgeCompanionPackageStatus,\n openBrowserBridgeCompanionManager,\n openBrowserBridgeCompanionPackagePath,\n} from \"../packaging.js\";\nimport {\n BROWSER_BRIDGE_ROUTE_SERVICE_TYPE,\n type BrowserBridgeRouteService,\n} from \"../service.js\";\n\nexport interface BrowserBridgeRouteContext {\n req: http.IncomingMessage;\n res: http.ServerResponse;\n method: string;\n pathname: string;\n url: URL;\n state: {\n runtime: AgentRuntime | null;\n adminEntityId: UUID | null;\n };\n json: (res: http.ServerResponse, data: unknown, status?: number) => void;\n error: (res: http.ServerResponse, message: string, status?: number) => void;\n readJsonBody: <T extends object>(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n options?: ReadJsonBodyOptions,\n ) => Promise<T | null>;\n decodePathComponent: (\n raw: string,\n res: http.ServerResponse,\n label: string,\n ) => string | null;\n}\n\nfunction getService(\n ctx: BrowserBridgeRouteContext,\n): BrowserBridgeRouteService | null {\n if (!ctx.state.runtime) {\n ctx.error(ctx.res, \"Agent runtime is not available\", 503);\n return null;\n }\n const service = ctx.state.runtime.getService<BrowserBridgeRouteService>(\n BROWSER_BRIDGE_ROUTE_SERVICE_TYPE,\n );\n if (!service) {\n ctx.error(ctx.res, \"Browser Bridge service is not available\", 503);\n return null;\n }\n return service;\n}\n\nfunction getBrowserCompanionAuth(\n ctx: BrowserBridgeRouteContext,\n): { companionId: string; pairingToken: string } | null {\n const companionHeader = ctx.req.headers[\"x-browser-bridge-companion-id\"];\n const companionId =\n typeof companionHeader === \"string\" ? companionHeader.trim() : \"\";\n if (!companionId) {\n routeJsonError(\n ctx,\n \"Missing X-Browser-Bridge-Companion-Id header\",\n 401,\n \"browser_bridge_companion_auth_missing_id\",\n );\n return null;\n }\n const authHeader =\n typeof ctx.req.headers.authorization === \"string\"\n ? ctx.req.headers.authorization.trim()\n : \"\";\n const match = /^Bearer\\s+(.+)$/i.exec(authHeader);\n const pairingToken = match?.[1]?.trim() ?? \"\";\n if (!pairingToken) {\n routeJsonError(\n ctx,\n \"Missing browser companion bearer token\",\n 401,\n \"browser_bridge_companion_auth_missing_token\",\n );\n return null;\n }\n return {\n companionId,\n pairingToken,\n };\n}\n\nfunction browserAutoPairOriginAllowed(ctx: BrowserBridgeRouteContext): boolean {\n const originHeader =\n typeof ctx.req.headers.origin === \"string\"\n ? ctx.req.headers.origin.trim()\n : \"\";\n if (!originHeader) {\n return requestIsLoopback(ctx);\n }\n if (originHeader === ctx.url.origin) {\n return true;\n }\n return (\n originHeader.startsWith(\"chrome-extension://\") ||\n originHeader.startsWith(\"safari-web-extension://\")\n );\n}\n\nfunction requestIsLoopback(ctx: BrowserBridgeRouteContext): boolean {\n const remoteAddress = ctx.req.socket.remoteAddress?.trim().toLowerCase();\n return (\n remoteAddress === \"127.0.0.1\" ||\n remoteAddress === \"::1\" ||\n remoteAddress === \"0:0:0:0:0:0:0:1\" ||\n remoteAddress === \"::ffff:127.0.0.1\" ||\n remoteAddress === \"::ffff:0:127.0.0.1\"\n );\n}\n\nconst BROWSER_BRIDGE_RATE_LIMITS = {\n default: { maxRequests: 60, windowMs: 60_000 },\n} satisfies Record<string, RateLimitConfig>;\n\ninterface RateLimitConfig {\n maxRequests: number;\n windowMs: number;\n}\n\ninterface RateLimitEntry {\n timestamps: number[];\n}\n\nconst rateLimitBuckets = new Map<string, RateLimitEntry>();\nconst RATE_LIMIT_CLEANUP_INTERVAL_MS = 5 * 60 * 1_000;\nlet lastRateLimitCleanup = Date.now();\n\nfunction cleanupRateLimitBuckets(windowMs: number): void {\n const now = Date.now();\n if (now - lastRateLimitCleanup < RATE_LIMIT_CLEANUP_INTERVAL_MS) return;\n lastRateLimitCleanup = now;\n const cutoff = now - windowMs;\n for (const [key, entry] of rateLimitBuckets) {\n entry.timestamps = entry.timestamps.filter((timestamp) => timestamp > cutoff);\n if (entry.timestamps.length === 0) rateLimitBuckets.delete(key);\n }\n}\n\nfunction checkBrowserBridgeRateLimit(\n key: string,\n config: RateLimitConfig,\n): { allowed: boolean; retryAfterMs: number } {\n const now = Date.now();\n cleanupRateLimitBuckets(config.windowMs);\n let entry = rateLimitBuckets.get(key);\n if (!entry) {\n entry = { timestamps: [] };\n rateLimitBuckets.set(key, entry);\n }\n\n const cutoff = now - config.windowMs;\n entry.timestamps = entry.timestamps.filter((timestamp) => timestamp > cutoff);\n\n if (entry.timestamps.length >= config.maxRequests) {\n const oldestInWindow = entry.timestamps[0];\n const retryAfterMs =\n oldestInWindow === undefined ? 0 : oldestInWindow + config.windowMs - now;\n return { allowed: false, retryAfterMs: Math.max(retryAfterMs, 0) };\n }\n\n entry.timestamps.push(now);\n return { allowed: true, retryAfterMs: 0 };\n}\n\nfunction rateLimitRequest(\n ctx: BrowserBridgeRouteContext,\n operation: string,\n): boolean {\n const agentId = String(ctx.state.runtime?.agentId ?? \"unknown\");\n const companionHeader = ctx.req.headers[\"x-browser-bridge-companion-id\"];\n const companionId =\n typeof companionHeader === \"string\" ? companionHeader.trim() : \"anonymous\";\n const remoteAddress = ctx.req.socket.remoteAddress?.trim() ?? \"unknown\";\n const limitKey = `${agentId}:${operation}:${remoteAddress}:${companionId}`;\n const config = BROWSER_BRIDGE_RATE_LIMITS.default;\n const { allowed, retryAfterMs } = checkBrowserBridgeRateLimit(\n limitKey,\n config,\n );\n if (!allowed) {\n ctx.res.writeHead(429, {\n \"Content-Type\": \"application/json\",\n \"Retry-After\": String(Math.ceil(retryAfterMs / 1_000)),\n });\n ctx.res.end(JSON.stringify({ error: \"Rate limit exceeded\", retryAfterMs }));\n return true;\n }\n return false;\n}\n\nfunction routeOperation(ctx: BrowserBridgeRouteContext): string {\n return `${ctx.method.toUpperCase()} ${ctx.pathname}`;\n}\n\ninterface BrowserBridgeTelemetrySpan {\n success: (args?: { statusCode?: number }) => void;\n failure: (args?: {\n statusCode?: number;\n error?: unknown;\n errorKind?: string;\n }) => void;\n}\n\nfunction sanitizeTelemetryToken(value: string | undefined): string | undefined {\n if (!value) return undefined;\n const token = value.toLowerCase().replace(/[^a-z0-9_-]/g, \"_\");\n const normalized = token.replace(/_+/g, \"_\").replace(/^_+|_+$/g, \"\");\n return normalized ? normalized.slice(0, 64) : undefined;\n}\n\nfunction inferTelemetryErrorKind(error: unknown): string | undefined {\n if (error instanceof Error) {\n const message = error.message.toLowerCase();\n if (\n error.name === \"AbortError\" ||\n error.name === \"TimeoutError\" ||\n message.includes(\"timeout\") ||\n message.includes(\"timed out\")\n ) {\n return \"timeout\";\n }\n return sanitizeTelemetryToken(error.name);\n }\n return typeof error === \"string\" ? sanitizeTelemetryToken(error) : undefined;\n}\n\nfunction createBrowserBridgeTelemetrySpan(meta: {\n boundary: \"browser-bridge\";\n operation: string;\n timeoutMs?: number;\n}): BrowserBridgeTelemetrySpan {\n const startedAt = Date.now();\n let settled = false;\n\n const finalize = (\n outcome: \"success\" | \"failure\",\n args?: { statusCode?: number; error?: unknown; errorKind?: string },\n ): void => {\n if (settled) return;\n settled = true;\n const event: Record<string, unknown> = {\n schema: \"integration_boundary_v1\",\n boundary: meta.boundary,\n operation: meta.operation,\n outcome,\n durationMs: Math.max(0, Date.now() - startedAt),\n };\n if (typeof meta.timeoutMs === \"number\") event.timeoutMs = meta.timeoutMs;\n if (typeof args?.statusCode === \"number\") event.statusCode = args.statusCode;\n if (outcome === \"failure\") {\n event.errorKind =\n sanitizeTelemetryToken(args?.errorKind) ??\n inferTelemetryErrorKind(args?.error);\n }\n const line = `[integration] ${JSON.stringify(event)}`;\n if (outcome === \"success\") {\n logger.info(line);\n } else {\n logger.warn(line);\n }\n };\n\n return {\n success: (args) => finalize(\"success\", args),\n failure: (args) => finalize(\"failure\", args),\n };\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction routeJsonError(\n ctx: BrowserBridgeRouteContext,\n message: string,\n status: number,\n code?: BrowserBridgeCompanionAuthErrorCode | string | null,\n): void {\n ctx.json(\n ctx.res,\n {\n error: message,\n ...(code ? { code } : {}),\n },\n status,\n );\n}\n\nfunction isStatusError(\n error: unknown,\n): error is Error & { readonly status: number } {\n return (\n error instanceof Error &&\n \"status\" in error &&\n typeof error.status === \"number\"\n );\n}\n\nfunction statusErrorCode(error: unknown): string | null {\n if (\n error &&\n typeof error === \"object\" &&\n \"code\" in error &&\n typeof (error as { code?: unknown }).code === \"string\"\n ) {\n return (error as { code: string }).code;\n }\n return null;\n}\n\nfunction decodeMatchedPathComponent(\n ctx: BrowserBridgeRouteContext,\n match: RegExpMatchArray | null,\n index: number,\n res: http.ServerResponse,\n label: string,\n): string | null {\n const raw = match?.[index];\n return raw ? ctx.decodePathComponent(raw, res, label) : null;\n}\n\nasync function runRoute(\n ctx: BrowserBridgeRouteContext,\n fn: (service: BrowserBridgeRouteService) => Promise<void>,\n): Promise<boolean> {\n const operation = routeOperation(ctx);\n const span = createBrowserBridgeTelemetrySpan({\n boundary: \"browser-bridge\",\n operation,\n });\n const service = getService(ctx);\n if (!service) {\n logger.info(\n {\n boundary: \"browser-bridge\",\n operation,\n statusCode: 503,\n },\n \"[browser-bridge] Route rejected because agent runtime is unavailable\",\n );\n span.failure({\n statusCode: 503,\n errorKind: \"runtime_unavailable\",\n });\n return true;\n }\n try {\n await fn(service);\n span.success({\n statusCode: ctx.res.statusCode >= 400 ? ctx.res.statusCode : 200,\n });\n return true;\n } catch (error) {\n if (isStatusError(error)) {\n const logFn =\n error.status === 401\n ? logger.debug.bind(logger)\n : logger.warn.bind(logger);\n logFn(\n {\n boundary: \"browser-bridge\",\n operation,\n statusCode: error.status,\n },\n `[browser-bridge] Route failed: ${error.message}`,\n );\n span.failure({\n statusCode: error.status,\n error,\n errorKind:\n error.status === 401\n ? \"browser_bridge_auth_invalid\"\n : \"browser_bridge_service_error\",\n });\n routeJsonError(ctx, error.message, error.status, statusErrorCode(error));\n return true;\n }\n logger.error(\n {\n boundary: \"browser-bridge\",\n operation,\n },\n `[browser-bridge] Route crashed: ${errorMessage(error)}`,\n );\n span.failure({\n error,\n errorKind: \"unhandled_error\",\n });\n throw error;\n }\n}\n\nasync function runStatelessRoute(\n ctx: BrowserBridgeRouteContext,\n fn: () => Promise<void>,\n): Promise<boolean> {\n const operation = routeOperation(ctx);\n const span = createBrowserBridgeTelemetrySpan({\n boundary: \"browser-bridge\",\n operation,\n });\n try {\n await fn();\n span.success({\n statusCode: ctx.res.statusCode >= 400 ? ctx.res.statusCode : 200,\n });\n return true;\n } catch (error) {\n if (isStatusError(error)) {\n logger.warn(\n {\n boundary: \"browser-bridge\",\n operation,\n statusCode: error.status,\n },\n `[browser-bridge] Route failed: ${error.message}`,\n );\n span.failure({\n statusCode: error.status,\n error,\n errorKind: \"browser_bridge_service_error\",\n });\n ctx.error(ctx.res, error.message, error.status);\n return true;\n }\n logger.error(\n {\n boundary: \"browser-bridge\",\n operation,\n },\n `[browser-bridge] Route crashed: ${errorMessage(error)}`,\n );\n span.failure({\n error,\n errorKind: \"unhandled_error\",\n });\n throw error;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Dispatcher\n// ---------------------------------------------------------------------------\n\nexport async function handleBrowserBridgeRoutes(\n ctx: BrowserBridgeRouteContext,\n): Promise<boolean> {\n const { req, res, method, pathname, json, readJsonBody } = ctx;\n\n if (method === \"GET\" && pathname === \"/api/browser-bridge/sessions\") {\n return runRoute(ctx, async (service) => {\n json(res, {\n sessions: await service.listBrowserSessions(ctx.state.adminEntityId),\n });\n });\n }\n\n if (method === \"GET\" && pathname === \"/api/browser-bridge/settings\") {\n return runRoute(ctx, async (service) => {\n json(res, {\n settings: await service.getBrowserSettings(ctx.state.adminEntityId),\n });\n });\n }\n\n if (method === \"POST\" && pathname === \"/api/browser-bridge/settings\") {\n const body = await readJsonBody<UpdateBrowserBridgeSettingsRequest>(\n req,\n res,\n );\n if (!body) return true;\n return runRoute(ctx, async (service) => {\n json(res, {\n settings: await service.updateBrowserSettings(\n body,\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n\n if (method === \"POST\" && pathname === \"/api/browser-bridge/companions/pair\") {\n const body = await readJsonBody<CreateBrowserBridgeCompanionPairingRequest>(\n req,\n res,\n );\n if (!body) return true;\n return runRoute(ctx, async (service) => {\n json(\n res,\n await service.createBrowserCompanionPairing(\n body,\n ctx.state.adminEntityId,\n ),\n 201,\n );\n });\n }\n\n if (\n method === \"POST\" &&\n pathname === \"/api/browser-bridge/companions/auto-pair\"\n ) {\n if (rateLimitRequest(ctx, \"companions:auto-pair\")) {\n return true;\n }\n if (!browserAutoPairOriginAllowed(ctx)) {\n ctx.error(\n res,\n \"browser auto-pair must come from the agent app or a browser extension\",\n 403,\n );\n return true;\n }\n const body =\n await readJsonBody<CreateBrowserBridgeCompanionAutoPairRequest>(req, res);\n if (!body) return true;\n return runRoute(ctx, async (service) => {\n json(\n res,\n await service.autoPairBrowserCompanion(\n body,\n ctx.url.origin,\n ctx.state.adminEntityId,\n ),\n 201,\n );\n });\n }\n\n if (method === \"GET\" && pathname === \"/api/browser-bridge/companions\") {\n return runRoute(ctx, async (service) => {\n json(res, {\n companions: await service.listBrowserCompanions(\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n\n if (\n method === \"POST\" &&\n pathname === \"/api/browser-bridge/companions/revoke\"\n ) {\n if (rateLimitRequest(ctx, \"companions:revoke\")) {\n return true;\n }\n return runRoute(ctx, async (service) => {\n const auth = getBrowserCompanionAuth(ctx);\n if (!auth) {\n return;\n }\n json(\n res,\n await service.revokeBrowserCompanionFromCompanion(\n auth.companionId,\n auth.pairingToken,\n ctx.state.adminEntityId,\n ),\n );\n });\n }\n\n const browserCompanionRevokeMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/companions\\/([^/]+)\\/revoke$/,\n );\n if (method === \"POST\" && browserCompanionRevokeMatch) {\n const companionId = decodeMatchedPathComponent(\n ctx,\n browserCompanionRevokeMatch,\n 1,\n res,\n \"browser companion id\",\n );\n if (!companionId) return true;\n return runRoute(ctx, async (service) => {\n json(\n res,\n await service.revokeBrowserCompanion(\n companionId,\n ctx.state.adminEntityId,\n ),\n );\n });\n }\n\n if (method === \"GET\" && pathname === \"/api/browser-bridge/packages\") {\n return runStatelessRoute(ctx, async () => {\n json(res, { status: getBrowserBridgeCompanionPackageStatus() });\n });\n }\n\n if (\n method === \"POST\" &&\n pathname === \"/api/browser-bridge/packages/open-path\"\n ) {\n if (!requestIsLoopback(ctx)) {\n ctx.error(\n res,\n \"Local extension install helpers can only run on the same machine as the agent\",\n 403,\n );\n return true;\n }\n const body = await readJsonBody<{\n target?: string;\n revealOnly?: boolean;\n }>(req, res);\n if (!body) return true;\n if (\n typeof body.target !== \"string\" ||\n !BROWSER_BRIDGE_PACKAGE_PATH_TARGETS.includes(\n body.target as (typeof BROWSER_BRIDGE_PACKAGE_PATH_TARGETS)[number],\n )\n ) {\n ctx.error(\n res,\n `target must be one of: ${BROWSER_BRIDGE_PACKAGE_PATH_TARGETS.join(\", \")}`,\n 400,\n );\n return true;\n }\n const validatedTarget =\n body.target as (typeof BROWSER_BRIDGE_PACKAGE_PATH_TARGETS)[number];\n return runStatelessRoute(ctx, async () => {\n json(\n res,\n await openBrowserBridgeCompanionPackagePath(validatedTarget, {\n revealOnly: body.revealOnly === true,\n }),\n );\n });\n }\n\n if (method === \"POST\" && pathname === \"/api/browser-bridge/companions/sync\") {\n if (rateLimitRequest(ctx, \"companions:sync\")) {\n return true;\n }\n const body = await readJsonBody<SyncBrowserBridgeStateRequest>(req, res);\n if (!body) return true;\n return runRoute(ctx, async (service) => {\n const auth = getBrowserCompanionAuth(ctx);\n if (!auth) {\n return;\n }\n json(\n res,\n await service.syncBrowserCompanion(\n auth.companionId,\n auth.pairingToken,\n body,\n ctx.state.adminEntityId,\n ),\n );\n });\n }\n\n if (method === \"GET\" && pathname === \"/api/browser-bridge/tabs\") {\n return runRoute(ctx, async (service) => {\n json(res, {\n tabs: await service.listBrowserTabs(ctx.state.adminEntityId),\n });\n });\n }\n\n const browserPackageBuildMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/packages\\/([^/]+)\\/build$/,\n );\n if (method === \"POST\" && browserPackageBuildMatch) {\n const browser = decodeMatchedPathComponent(\n ctx,\n browserPackageBuildMatch,\n 1,\n res,\n \"browser package target\",\n );\n if (!browser) return true;\n if (browser !== \"chrome\" && browser !== \"safari\") {\n ctx.error(res, \"browser must be chrome or safari\", 400);\n return true;\n }\n return runStatelessRoute(ctx, async () => {\n json(res, {\n status: await buildBrowserBridgeCompanionPackage(browser),\n });\n });\n }\n\n const browserPackageOpenManagerMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/packages\\/([^/]+)\\/open-manager$/,\n );\n if (method === \"POST\" && browserPackageOpenManagerMatch) {\n if (!requestIsLoopback(ctx)) {\n ctx.error(\n res,\n \"Local extension install helpers can only run on the same machine as the agent\",\n 403,\n );\n return true;\n }\n const browser = decodeMatchedPathComponent(\n ctx,\n browserPackageOpenManagerMatch,\n 1,\n res,\n \"browser package target\",\n );\n if (!browser) return true;\n if (browser !== \"chrome\" && browser !== \"safari\") {\n ctx.error(res, \"browser must be chrome or safari\", 400);\n return true;\n }\n return runStatelessRoute(ctx, async () => {\n json(res, await openBrowserBridgeCompanionManager(browser));\n });\n }\n\n const browserPackageDownloadMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/packages\\/([^/]+)\\/download$/,\n );\n if (method === \"GET\" && browserPackageDownloadMatch) {\n const browser = decodeMatchedPathComponent(\n ctx,\n browserPackageDownloadMatch,\n 1,\n res,\n \"browser package target\",\n );\n if (!browser) return true;\n if (browser !== \"chrome\" && browser !== \"safari\") {\n ctx.error(res, \"browser must be chrome or safari\", 400);\n return true;\n }\n return runStatelessRoute(ctx, async () => {\n const artifact = getBrowserBridgeCompanionDownloadFile(browser);\n res.statusCode = 200;\n res.setHeader(\"Content-Type\", artifact.contentType);\n res.setHeader(\n \"Content-Disposition\",\n `attachment; filename=\"${artifact.filename}\"`,\n );\n await new Promise<void>((resolve, reject) => {\n const stream = fs.createReadStream(artifact.path);\n stream.on(\"error\", reject);\n res.on(\"error\", reject);\n stream.on(\"end\", resolve);\n stream.pipe(res);\n });\n });\n }\n\n if (method === \"GET\" && pathname === \"/api/browser-bridge/current-page\") {\n return runRoute(ctx, async (service) => {\n json(res, {\n page: await service.getCurrentBrowserPage(ctx.state.adminEntityId),\n });\n });\n }\n\n if (method === \"POST\" && pathname === \"/api/browser-bridge/sync\") {\n const body = await readJsonBody<SyncBrowserBridgeStateRequest>(req, res);\n if (!body) return true;\n return runRoute(ctx, async (service) => {\n json(res, await service.syncBrowserState(body, ctx.state.adminEntityId));\n });\n }\n\n if (method === \"POST\" && pathname === \"/api/browser-bridge/sessions\") {\n const body = await readJsonBody<CreateLifeOpsBrowserSessionRequest>(\n req,\n res,\n );\n if (!body) return true;\n return runRoute(ctx, async (service) => {\n json(\n res,\n {\n session: await service.createBrowserSession(\n body,\n ctx.state.adminEntityId,\n ),\n },\n 201,\n );\n });\n }\n\n const browserSessionMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/sessions\\/([^/]+)$/,\n );\n if (browserSessionMatch) {\n const sessionId = decodeMatchedPathComponent(\n ctx,\n browserSessionMatch,\n 1,\n res,\n \"browser session id\",\n );\n if (!sessionId) return true;\n if (method === \"GET\") {\n return runRoute(ctx, async (service) => {\n json(res, {\n session: await service.getBrowserSession(\n sessionId,\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n }\n\n const browserConfirmMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/sessions\\/([^/]+)\\/confirm$/,\n );\n if (method === \"POST\" && browserConfirmMatch) {\n const sessionId = decodeMatchedPathComponent(\n ctx,\n browserConfirmMatch,\n 1,\n res,\n \"browser session id\",\n );\n if (!sessionId) return true;\n const body = await readJsonBody<ConfirmLifeOpsBrowserSessionRequest>(\n req,\n res,\n );\n if (!body) return true;\n return runRoute(ctx, async (service) => {\n json(res, {\n session: await service.confirmBrowserSession(\n sessionId,\n body,\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n\n const browserProgressMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/sessions\\/([^/]+)\\/progress$/,\n );\n if (method === \"POST\" && browserProgressMatch) {\n const sessionId = decodeMatchedPathComponent(\n ctx,\n browserProgressMatch,\n 1,\n res,\n \"browser session id\",\n );\n if (!sessionId) return true;\n const body = await readJsonBody<UpdateBrowserBridgeSessionProgressRequest>(\n req,\n res,\n );\n if (!body) return true;\n return runRoute(ctx, async (service) => {\n json(res, {\n session: await service.updateBrowserSessionProgress(\n sessionId,\n body,\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n\n const browserCompleteMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/sessions\\/([^/]+)\\/complete$/,\n );\n if (method === \"POST\" && browserCompleteMatch) {\n const sessionId = decodeMatchedPathComponent(\n ctx,\n browserCompleteMatch,\n 1,\n res,\n \"browser session id\",\n );\n if (!sessionId) return true;\n const body = await readJsonBody<CompleteLifeOpsBrowserSessionRequest>(\n req,\n res,\n );\n if (!body) return true;\n return runRoute(ctx, async (service) => {\n json(res, {\n session: await service.completeBrowserSession(\n sessionId,\n body,\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n\n const browserCompanionProgressMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/companions\\/sessions\\/([^/]+)\\/progress$/,\n );\n if (method === \"POST\" && browserCompanionProgressMatch) {\n if (rateLimitRequest(ctx, \"companions:session-progress\")) {\n return true;\n }\n const sessionId = decodeMatchedPathComponent(\n ctx,\n browserCompanionProgressMatch,\n 1,\n res,\n \"browser session id\",\n );\n if (!sessionId) return true;\n const body = await readJsonBody<UpdateBrowserBridgeSessionProgressRequest>(\n req,\n res,\n );\n if (!body) return true;\n return runRoute(ctx, async (service) => {\n const auth = getBrowserCompanionAuth(ctx);\n if (!auth) {\n return;\n }\n json(res, {\n session: await service.updateBrowserSessionProgressFromCompanion(\n auth.companionId,\n auth.pairingToken,\n sessionId,\n body,\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n\n const browserCompanionCompleteMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/companions\\/sessions\\/([^/]+)\\/complete$/,\n );\n if (method === \"POST\" && browserCompanionCompleteMatch) {\n if (rateLimitRequest(ctx, \"companions:session-complete\")) {\n return true;\n }\n const sessionId = decodeMatchedPathComponent(\n ctx,\n browserCompanionCompleteMatch,\n 1,\n res,\n \"browser session id\",\n );\n if (!sessionId) return true;\n const body = await readJsonBody<CompleteLifeOpsBrowserSessionRequest>(\n req,\n res,\n );\n if (!body) return true;\n return runRoute(ctx, async (service) => {\n const auth = getBrowserCompanionAuth(ctx);\n if (!auth) {\n return;\n }\n json(res, {\n session: await service.completeBrowserSessionFromCompanion(\n auth.companionId,\n auth.pairingToken,\n sessionId,\n body,\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n\n return false;\n}\n\nexport { rateLimitRequest };\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/routes/bridge.ts"],"sourcesContent":["/**\n * Agent Browser Bridge HTTP routes.\n *\n * Mounted under `/api/browser-bridge/*` by the plugin-collector. Pair +\n * settings + companion-sync + tabs + current-page + packaging endpoints\n * are fully generic. Session endpoints (confirm/progress/complete) still\n * operate on the LifeOps-owned `life_browser_sessions` table and therefore\n * call into the LifeOps route service because that app owns workflow-scoped\n * browser sessions.\n *\n * Companion/auto-pair/sync authentication uses the\n * `X-Browser-Bridge-Companion-Id` header paired with a bearer pairing\n * token. The old `X-LifeOps-Browser-Companion-Id` and legacy\n * `x-eliza-browser-companion-id` aliases were deliberately removed.\n */\n\nimport fs from \"node:fs\";\nimport type http from \"node:http\";\nimport type { ReadJsonBodyOptions } from \"@elizaos/core\";\nimport { type AgentRuntime, logger, type UUID } from \"@elizaos/core\";\nimport {\n BROWSER_BRIDGE_PACKAGE_PATH_TARGETS,\n type BrowserBridgeCompanionAuthErrorCode,\n type CreateBrowserBridgeCompanionAutoPairRequest,\n type CreateBrowserBridgeCompanionPairingRequest,\n type SyncBrowserBridgeStateRequest,\n type UpdateBrowserBridgeSessionProgressRequest,\n type UpdateBrowserBridgeSettingsRequest,\n} from \"../contracts.js\";\nimport type {\n CompleteLifeOpsBrowserSessionRequest,\n ConfirmLifeOpsBrowserSessionRequest,\n CreateLifeOpsBrowserSessionRequest,\n} from \"../lifeops-session-contracts.js\";\nimport {\n buildBrowserBridgeCompanionPackage,\n getBrowserBridgeCompanionDownloadFile,\n getBrowserBridgeCompanionPackageStatus,\n openBrowserBridgeCompanionManager,\n openBrowserBridgeCompanionPackagePath,\n} from \"../packaging.js\";\nimport {\n BROWSER_BRIDGE_ROUTE_SERVICE_TYPE,\n type BrowserBridgeRouteService,\n} from \"../service.js\";\n\nexport interface BrowserBridgeRouteContext {\n req: http.IncomingMessage;\n res: http.ServerResponse;\n method: string;\n pathname: string;\n url: URL;\n state: {\n runtime: AgentRuntime | null;\n adminEntityId: UUID | null;\n };\n json: (res: http.ServerResponse, data: unknown, status?: number) => void;\n error: (res: http.ServerResponse, message: string, status?: number) => void;\n readJsonBody: <T extends object>(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n options?: ReadJsonBodyOptions,\n ) => Promise<T | null>;\n decodePathComponent: (\n raw: string,\n res: http.ServerResponse,\n label: string,\n ) => string | null;\n}\n\nfunction getService(\n ctx: BrowserBridgeRouteContext,\n): BrowserBridgeRouteService | null {\n if (!ctx.state.runtime) {\n ctx.error(ctx.res, \"Agent runtime is not available\", 503);\n return null;\n }\n const service = ctx.state.runtime.getService<BrowserBridgeRouteService>(\n BROWSER_BRIDGE_ROUTE_SERVICE_TYPE,\n );\n if (!service) {\n ctx.error(ctx.res, \"Browser Bridge service is not available\", 503);\n return null;\n }\n return service;\n}\n\nfunction getBrowserCompanionAuth(\n ctx: BrowserBridgeRouteContext,\n): { companionId: string; pairingToken: string } | null {\n const companionHeader = ctx.req.headers[\"x-browser-bridge-companion-id\"];\n const companionId =\n typeof companionHeader === \"string\" ? companionHeader.trim() : \"\";\n if (!companionId) {\n routeJsonError(\n ctx,\n \"Missing X-Browser-Bridge-Companion-Id header\",\n 401,\n \"browser_bridge_companion_auth_missing_id\",\n );\n return null;\n }\n const authHeader =\n typeof ctx.req.headers.authorization === \"string\"\n ? ctx.req.headers.authorization.trim()\n : \"\";\n const match = /^Bearer\\s+(.+)$/i.exec(authHeader);\n const pairingToken = match?.[1]?.trim() ?? \"\";\n if (!pairingToken) {\n routeJsonError(\n ctx,\n \"Missing browser companion bearer token\",\n 401,\n \"browser_bridge_companion_auth_missing_token\",\n );\n return null;\n }\n return {\n companionId,\n pairingToken,\n };\n}\n\nfunction browserAutoPairOriginAllowed(ctx: BrowserBridgeRouteContext): boolean {\n const originHeader =\n typeof ctx.req.headers.origin === \"string\"\n ? ctx.req.headers.origin.trim()\n : \"\";\n if (!originHeader) {\n return requestIsLoopback(ctx);\n }\n if (originHeader === ctx.url.origin) {\n return true;\n }\n return (\n originHeader.startsWith(\"chrome-extension://\") ||\n originHeader.startsWith(\"safari-web-extension://\")\n );\n}\n\nfunction requestIsLoopback(ctx: BrowserBridgeRouteContext): boolean {\n const remoteAddress = ctx.req.socket.remoteAddress?.trim().toLowerCase();\n return (\n remoteAddress === \"127.0.0.1\" ||\n remoteAddress === \"::1\" ||\n remoteAddress === \"0:0:0:0:0:0:0:1\" ||\n remoteAddress === \"::ffff:127.0.0.1\" ||\n remoteAddress === \"::ffff:0:127.0.0.1\"\n );\n}\n\nconst BROWSER_BRIDGE_RATE_LIMITS = {\n default: { maxRequests: 60, windowMs: 60_000 },\n} satisfies Record<string, RateLimitConfig>;\n\ninterface RateLimitConfig {\n maxRequests: number;\n windowMs: number;\n}\n\ninterface RateLimitEntry {\n timestamps: number[];\n}\n\nconst rateLimitBuckets = new Map<string, RateLimitEntry>();\nconst RATE_LIMIT_CLEANUP_INTERVAL_MS = 5 * 60 * 1_000;\nlet lastRateLimitCleanup = Date.now();\n\nfunction cleanupRateLimitBuckets(windowMs: number): void {\n const now = Date.now();\n if (now - lastRateLimitCleanup < RATE_LIMIT_CLEANUP_INTERVAL_MS) return;\n lastRateLimitCleanup = now;\n const cutoff = now - windowMs;\n for (const [key, entry] of rateLimitBuckets) {\n entry.timestamps = entry.timestamps.filter(\n (timestamp) => timestamp > cutoff,\n );\n if (entry.timestamps.length === 0) rateLimitBuckets.delete(key);\n }\n}\n\nfunction checkBrowserBridgeRateLimit(\n key: string,\n config: RateLimitConfig,\n): { allowed: boolean; retryAfterMs: number } {\n const now = Date.now();\n cleanupRateLimitBuckets(config.windowMs);\n let entry = rateLimitBuckets.get(key);\n if (!entry) {\n entry = { timestamps: [] };\n rateLimitBuckets.set(key, entry);\n }\n\n const cutoff = now - config.windowMs;\n entry.timestamps = entry.timestamps.filter((timestamp) => timestamp > cutoff);\n\n if (entry.timestamps.length >= config.maxRequests) {\n const oldestInWindow = entry.timestamps[0];\n const retryAfterMs =\n oldestInWindow === undefined ? 0 : oldestInWindow + config.windowMs - now;\n return { allowed: false, retryAfterMs: Math.max(retryAfterMs, 0) };\n }\n\n entry.timestamps.push(now);\n return { allowed: true, retryAfterMs: 0 };\n}\n\nfunction rateLimitRequest(\n ctx: BrowserBridgeRouteContext,\n operation: string,\n): boolean {\n const agentId = String(ctx.state.runtime?.agentId ?? \"unknown\");\n const companionHeader = ctx.req.headers[\"x-browser-bridge-companion-id\"];\n const companionId =\n typeof companionHeader === \"string\" ? companionHeader.trim() : \"anonymous\";\n const remoteAddress = ctx.req.socket.remoteAddress?.trim() ?? \"unknown\";\n const limitKey = `${agentId}:${operation}:${remoteAddress}:${companionId}`;\n const config = BROWSER_BRIDGE_RATE_LIMITS.default;\n const { allowed, retryAfterMs } = checkBrowserBridgeRateLimit(\n limitKey,\n config,\n );\n if (!allowed) {\n ctx.res.writeHead(429, {\n \"Content-Type\": \"application/json\",\n \"Retry-After\": String(Math.ceil(retryAfterMs / 1_000)),\n });\n ctx.res.end(JSON.stringify({ error: \"Rate limit exceeded\", retryAfterMs }));\n return true;\n }\n return false;\n}\n\nfunction routeOperation(ctx: BrowserBridgeRouteContext): string {\n return `${ctx.method.toUpperCase()} ${ctx.pathname}`;\n}\n\ninterface BrowserBridgeTelemetrySpan {\n success: (args?: { statusCode?: number }) => void;\n failure: (args?: {\n statusCode?: number;\n error?: unknown;\n errorKind?: string;\n }) => void;\n}\n\nfunction sanitizeTelemetryToken(value: string | undefined): string | undefined {\n if (!value) return undefined;\n const token = value.toLowerCase().replace(/[^a-z0-9_-]/g, \"_\");\n const normalized = token.replace(/_+/g, \"_\").replace(/^_+|_+$/g, \"\");\n return normalized ? normalized.slice(0, 64) : undefined;\n}\n\nfunction inferTelemetryErrorKind(error: unknown): string | undefined {\n if (error instanceof Error) {\n const message = error.message.toLowerCase();\n if (\n error.name === \"AbortError\" ||\n error.name === \"TimeoutError\" ||\n message.includes(\"timeout\") ||\n message.includes(\"timed out\")\n ) {\n return \"timeout\";\n }\n return sanitizeTelemetryToken(error.name);\n }\n return typeof error === \"string\" ? sanitizeTelemetryToken(error) : undefined;\n}\n\nfunction createBrowserBridgeTelemetrySpan(meta: {\n boundary: \"browser-bridge\";\n operation: string;\n timeoutMs?: number;\n}): BrowserBridgeTelemetrySpan {\n const startedAt = Date.now();\n let settled = false;\n\n const finalize = (\n outcome: \"success\" | \"failure\",\n args?: { statusCode?: number; error?: unknown; errorKind?: string },\n ): void => {\n if (settled) return;\n settled = true;\n const event: Record<string, unknown> = {\n schema: \"integration_boundary_v1\",\n boundary: meta.boundary,\n operation: meta.operation,\n outcome,\n durationMs: Math.max(0, Date.now() - startedAt),\n };\n if (typeof meta.timeoutMs === \"number\") event.timeoutMs = meta.timeoutMs;\n if (typeof args?.statusCode === \"number\")\n event.statusCode = args.statusCode;\n if (outcome === \"failure\") {\n event.errorKind =\n sanitizeTelemetryToken(args?.errorKind) ??\n inferTelemetryErrorKind(args?.error);\n }\n const line = `[integration] ${JSON.stringify(event)}`;\n if (outcome === \"success\") {\n logger.info(line);\n } else {\n logger.warn(line);\n }\n };\n\n return {\n success: (args) => finalize(\"success\", args),\n failure: (args) => finalize(\"failure\", args),\n };\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction routeJsonError(\n ctx: BrowserBridgeRouteContext,\n message: string,\n status: number,\n code?: BrowserBridgeCompanionAuthErrorCode | string | null,\n): void {\n ctx.json(\n ctx.res,\n {\n error: message,\n ...(code ? { code } : {}),\n },\n status,\n );\n}\n\nfunction isBrowserBridgeRouteBodyObject(\n value: unknown,\n): value is Record<string, unknown> {\n return Boolean(value && typeof value === \"object\" && !Array.isArray(value));\n}\n\nfunction rejectMalformedBrowserBridgePayload(\n ctx: BrowserBridgeRouteContext,\n): true {\n ctx.error(ctx.res, \"request body must be a JSON object\", 400);\n return true;\n}\n\nfunction isStatusError(\n error: unknown,\n): error is Error & { readonly status: number } {\n return (\n error instanceof Error &&\n \"status\" in error &&\n typeof error.status === \"number\"\n );\n}\n\nfunction statusErrorCode(error: unknown): string | null {\n if (\n error &&\n typeof error === \"object\" &&\n \"code\" in error &&\n typeof (error as { code?: unknown }).code === \"string\"\n ) {\n return (error as { code: string }).code;\n }\n return null;\n}\n\nfunction decodeMatchedPathComponent(\n ctx: BrowserBridgeRouteContext,\n match: RegExpMatchArray | null,\n index: number,\n res: http.ServerResponse,\n label: string,\n): string | null {\n const raw = match?.[index];\n return raw ? ctx.decodePathComponent(raw, res, label) : null;\n}\n\nasync function runRoute(\n ctx: BrowserBridgeRouteContext,\n fn: (service: BrowserBridgeRouteService) => Promise<void>,\n): Promise<boolean> {\n const operation = routeOperation(ctx);\n const span = createBrowserBridgeTelemetrySpan({\n boundary: \"browser-bridge\",\n operation,\n });\n const service = getService(ctx);\n if (!service) {\n logger.info(\n {\n boundary: \"browser-bridge\",\n operation,\n statusCode: 503,\n },\n \"[browser-bridge] Route rejected because agent runtime is unavailable\",\n );\n span.failure({\n statusCode: 503,\n errorKind: \"runtime_unavailable\",\n });\n return true;\n }\n try {\n await fn(service);\n span.success({\n statusCode: ctx.res.statusCode >= 400 ? ctx.res.statusCode : 200,\n });\n return true;\n } catch (error) {\n if (isStatusError(error)) {\n const logFn =\n error.status === 401\n ? logger.debug.bind(logger)\n : logger.warn.bind(logger);\n logFn(\n {\n boundary: \"browser-bridge\",\n operation,\n statusCode: error.status,\n },\n `[browser-bridge] Route failed: ${error.message}`,\n );\n span.failure({\n statusCode: error.status,\n error,\n errorKind:\n error.status === 401\n ? \"browser_bridge_auth_invalid\"\n : \"browser_bridge_service_error\",\n });\n routeJsonError(ctx, error.message, error.status, statusErrorCode(error));\n return true;\n }\n logger.error(\n {\n boundary: \"browser-bridge\",\n operation,\n },\n `[browser-bridge] Route crashed: ${errorMessage(error)}`,\n );\n span.failure({\n error,\n errorKind: \"unhandled_error\",\n });\n throw error;\n }\n}\n\nasync function runStatelessRoute(\n ctx: BrowserBridgeRouteContext,\n fn: () => Promise<void>,\n): Promise<boolean> {\n const operation = routeOperation(ctx);\n const span = createBrowserBridgeTelemetrySpan({\n boundary: \"browser-bridge\",\n operation,\n });\n try {\n await fn();\n span.success({\n statusCode: ctx.res.statusCode >= 400 ? ctx.res.statusCode : 200,\n });\n return true;\n } catch (error) {\n if (isStatusError(error)) {\n logger.warn(\n {\n boundary: \"browser-bridge\",\n operation,\n statusCode: error.status,\n },\n `[browser-bridge] Route failed: ${error.message}`,\n );\n span.failure({\n statusCode: error.status,\n error,\n errorKind: \"browser_bridge_service_error\",\n });\n ctx.error(ctx.res, error.message, error.status);\n return true;\n }\n logger.error(\n {\n boundary: \"browser-bridge\",\n operation,\n },\n `[browser-bridge] Route crashed: ${errorMessage(error)}`,\n );\n span.failure({\n error,\n errorKind: \"unhandled_error\",\n });\n throw error;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Dispatcher\n// ---------------------------------------------------------------------------\n\nexport async function handleBrowserBridgeRoutes(\n ctx: BrowserBridgeRouteContext,\n): Promise<boolean> {\n const { req, res, method, pathname, json, readJsonBody } = ctx;\n\n if (method === \"GET\" && pathname === \"/api/browser-bridge/sessions\") {\n return runRoute(ctx, async (service) => {\n json(res, {\n sessions: await service.listBrowserSessions(ctx.state.adminEntityId),\n });\n });\n }\n\n if (method === \"GET\" && pathname === \"/api/browser-bridge/settings\") {\n return runRoute(ctx, async (service) => {\n json(res, {\n settings: await service.getBrowserSettings(ctx.state.adminEntityId),\n });\n });\n }\n\n if (method === \"POST\" && pathname === \"/api/browser-bridge/settings\") {\n const body = await readJsonBody<UpdateBrowserBridgeSettingsRequest>(\n req,\n res,\n );\n if (!body) return true;\n if (!isBrowserBridgeRouteBodyObject(body)) {\n return rejectMalformedBrowserBridgePayload(ctx);\n }\n return runRoute(ctx, async (service) => {\n json(res, {\n settings: await service.updateBrowserSettings(\n body,\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n\n if (method === \"POST\" && pathname === \"/api/browser-bridge/companions/pair\") {\n const body = await readJsonBody<CreateBrowserBridgeCompanionPairingRequest>(\n req,\n res,\n );\n if (!body) return true;\n if (!isBrowserBridgeRouteBodyObject(body)) {\n return rejectMalformedBrowserBridgePayload(ctx);\n }\n return runRoute(ctx, async (service) => {\n json(\n res,\n await service.createBrowserCompanionPairing(\n body,\n ctx.state.adminEntityId,\n ),\n 201,\n );\n });\n }\n\n if (\n method === \"POST\" &&\n pathname === \"/api/browser-bridge/companions/auto-pair\"\n ) {\n if (rateLimitRequest(ctx, \"companions:auto-pair\")) {\n return true;\n }\n if (!browserAutoPairOriginAllowed(ctx)) {\n ctx.error(\n res,\n \"browser auto-pair must come from the agent app or a browser extension\",\n 403,\n );\n return true;\n }\n const body =\n await readJsonBody<CreateBrowserBridgeCompanionAutoPairRequest>(req, res);\n if (!body) return true;\n if (!isBrowserBridgeRouteBodyObject(body)) {\n return rejectMalformedBrowserBridgePayload(ctx);\n }\n return runRoute(ctx, async (service) => {\n json(\n res,\n await service.autoPairBrowserCompanion(\n body,\n ctx.url.origin,\n ctx.state.adminEntityId,\n ),\n 201,\n );\n });\n }\n\n if (method === \"GET\" && pathname === \"/api/browser-bridge/companions\") {\n return runRoute(ctx, async (service) => {\n json(res, {\n companions: await service.listBrowserCompanions(\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n\n if (\n method === \"POST\" &&\n pathname === \"/api/browser-bridge/companions/revoke\"\n ) {\n if (rateLimitRequest(ctx, \"companions:revoke\")) {\n return true;\n }\n return runRoute(ctx, async (service) => {\n const auth = getBrowserCompanionAuth(ctx);\n if (!auth) {\n return;\n }\n json(\n res,\n await service.revokeBrowserCompanionFromCompanion(\n auth.companionId,\n auth.pairingToken,\n ctx.state.adminEntityId,\n ),\n );\n });\n }\n\n const browserCompanionRevokeMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/companions\\/([^/]+)\\/revoke$/,\n );\n if (method === \"POST\" && browserCompanionRevokeMatch) {\n const companionId = decodeMatchedPathComponent(\n ctx,\n browserCompanionRevokeMatch,\n 1,\n res,\n \"browser companion id\",\n );\n if (!companionId) return true;\n return runRoute(ctx, async (service) => {\n json(\n res,\n await service.revokeBrowserCompanion(\n companionId,\n ctx.state.adminEntityId,\n ),\n );\n });\n }\n\n if (method === \"GET\" && pathname === \"/api/browser-bridge/packages\") {\n return runStatelessRoute(ctx, async () => {\n json(res, { status: getBrowserBridgeCompanionPackageStatus() });\n });\n }\n\n if (\n method === \"POST\" &&\n pathname === \"/api/browser-bridge/packages/open-path\"\n ) {\n if (!requestIsLoopback(ctx)) {\n ctx.error(\n res,\n \"Local extension install helpers can only run on the same machine as the agent\",\n 403,\n );\n return true;\n }\n const body = await readJsonBody<{\n target?: string;\n revealOnly?: boolean;\n }>(req, res);\n if (!body) return true;\n if (!isBrowserBridgeRouteBodyObject(body)) {\n return rejectMalformedBrowserBridgePayload(ctx);\n }\n if (\n typeof body.target !== \"string\" ||\n !BROWSER_BRIDGE_PACKAGE_PATH_TARGETS.includes(\n body.target as (typeof BROWSER_BRIDGE_PACKAGE_PATH_TARGETS)[number],\n )\n ) {\n ctx.error(\n res,\n `target must be one of: ${BROWSER_BRIDGE_PACKAGE_PATH_TARGETS.join(\", \")}`,\n 400,\n );\n return true;\n }\n const validatedTarget =\n body.target as (typeof BROWSER_BRIDGE_PACKAGE_PATH_TARGETS)[number];\n return runStatelessRoute(ctx, async () => {\n json(\n res,\n await openBrowserBridgeCompanionPackagePath(validatedTarget, {\n revealOnly: body.revealOnly === true,\n }),\n );\n });\n }\n\n if (method === \"POST\" && pathname === \"/api/browser-bridge/companions/sync\") {\n if (rateLimitRequest(ctx, \"companions:sync\")) {\n return true;\n }\n return runRoute(ctx, async (service) => {\n const auth = getBrowserCompanionAuth(ctx);\n if (!auth) {\n return;\n }\n const body = await readJsonBody<SyncBrowserBridgeStateRequest>(req, res);\n if (!body) return;\n if (!isBrowserBridgeRouteBodyObject(body)) {\n rejectMalformedBrowserBridgePayload(ctx);\n return;\n }\n json(\n res,\n await service.syncBrowserCompanion(\n auth.companionId,\n auth.pairingToken,\n body,\n ctx.state.adminEntityId,\n ),\n );\n });\n }\n\n if (method === \"GET\" && pathname === \"/api/browser-bridge/tabs\") {\n return runRoute(ctx, async (service) => {\n json(res, {\n tabs: await service.listBrowserTabs(ctx.state.adminEntityId),\n });\n });\n }\n\n const browserPackageBuildMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/packages\\/([^/]+)\\/build$/,\n );\n if (method === \"POST\" && browserPackageBuildMatch) {\n const browser = decodeMatchedPathComponent(\n ctx,\n browserPackageBuildMatch,\n 1,\n res,\n \"browser package target\",\n );\n if (!browser) return true;\n if (browser !== \"chrome\" && browser !== \"safari\") {\n ctx.error(res, \"browser must be chrome or safari\", 400);\n return true;\n }\n return runStatelessRoute(ctx, async () => {\n json(res, {\n status: await buildBrowserBridgeCompanionPackage(browser),\n });\n });\n }\n\n const browserPackageOpenManagerMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/packages\\/([^/]+)\\/open-manager$/,\n );\n if (method === \"POST\" && browserPackageOpenManagerMatch) {\n if (!requestIsLoopback(ctx)) {\n ctx.error(\n res,\n \"Local extension install helpers can only run on the same machine as the agent\",\n 403,\n );\n return true;\n }\n const browser = decodeMatchedPathComponent(\n ctx,\n browserPackageOpenManagerMatch,\n 1,\n res,\n \"browser package target\",\n );\n if (!browser) return true;\n if (browser !== \"chrome\" && browser !== \"safari\") {\n ctx.error(res, \"browser must be chrome or safari\", 400);\n return true;\n }\n return runStatelessRoute(ctx, async () => {\n json(res, await openBrowserBridgeCompanionManager(browser));\n });\n }\n\n const browserPackageDownloadMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/packages\\/([^/]+)\\/download$/,\n );\n if (method === \"GET\" && browserPackageDownloadMatch) {\n const browser = decodeMatchedPathComponent(\n ctx,\n browserPackageDownloadMatch,\n 1,\n res,\n \"browser package target\",\n );\n if (!browser) return true;\n if (browser !== \"chrome\" && browser !== \"safari\") {\n ctx.error(res, \"browser must be chrome or safari\", 400);\n return true;\n }\n return runStatelessRoute(ctx, async () => {\n const artifact = getBrowserBridgeCompanionDownloadFile(browser);\n res.statusCode = 200;\n res.setHeader(\"Content-Type\", artifact.contentType);\n res.setHeader(\n \"Content-Disposition\",\n `attachment; filename=\"${artifact.filename}\"`,\n );\n await new Promise<void>((resolve, reject) => {\n const stream = fs.createReadStream(artifact.path);\n stream.on(\"error\", reject);\n res.on(\"error\", reject);\n stream.on(\"end\", resolve);\n stream.pipe(res);\n });\n });\n }\n\n if (method === \"GET\" && pathname === \"/api/browser-bridge/current-page\") {\n return runRoute(ctx, async (service) => {\n json(res, {\n page: await service.getCurrentBrowserPage(ctx.state.adminEntityId),\n });\n });\n }\n\n if (method === \"POST\" && pathname === \"/api/browser-bridge/sync\") {\n const body = await readJsonBody<SyncBrowserBridgeStateRequest>(req, res);\n if (!body) return true;\n if (!isBrowserBridgeRouteBodyObject(body)) {\n return rejectMalformedBrowserBridgePayload(ctx);\n }\n return runRoute(ctx, async (service) => {\n json(res, await service.syncBrowserState(body, ctx.state.adminEntityId));\n });\n }\n\n if (method === \"POST\" && pathname === \"/api/browser-bridge/sessions\") {\n const body = await readJsonBody<CreateLifeOpsBrowserSessionRequest>(\n req,\n res,\n );\n if (!body) return true;\n if (!isBrowserBridgeRouteBodyObject(body)) {\n return rejectMalformedBrowserBridgePayload(ctx);\n }\n return runRoute(ctx, async (service) => {\n json(\n res,\n {\n session: await service.createBrowserSession(\n body,\n ctx.state.adminEntityId,\n ),\n },\n 201,\n );\n });\n }\n\n const browserSessionMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/sessions\\/([^/]+)$/,\n );\n if (browserSessionMatch) {\n const sessionId = decodeMatchedPathComponent(\n ctx,\n browserSessionMatch,\n 1,\n res,\n \"browser session id\",\n );\n if (!sessionId) return true;\n if (method === \"GET\") {\n return runRoute(ctx, async (service) => {\n json(res, {\n session: await service.getBrowserSession(\n sessionId,\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n }\n\n const browserConfirmMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/sessions\\/([^/]+)\\/confirm$/,\n );\n if (method === \"POST\" && browserConfirmMatch) {\n const sessionId = decodeMatchedPathComponent(\n ctx,\n browserConfirmMatch,\n 1,\n res,\n \"browser session id\",\n );\n if (!sessionId) return true;\n const body = await readJsonBody<ConfirmLifeOpsBrowserSessionRequest>(\n req,\n res,\n );\n if (!body) return true;\n if (!isBrowserBridgeRouteBodyObject(body)) {\n return rejectMalformedBrowserBridgePayload(ctx);\n }\n return runRoute(ctx, async (service) => {\n json(res, {\n session: await service.confirmBrowserSession(\n sessionId,\n body,\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n\n const browserProgressMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/sessions\\/([^/]+)\\/progress$/,\n );\n if (method === \"POST\" && browserProgressMatch) {\n const sessionId = decodeMatchedPathComponent(\n ctx,\n browserProgressMatch,\n 1,\n res,\n \"browser session id\",\n );\n if (!sessionId) return true;\n const body = await readJsonBody<UpdateBrowserBridgeSessionProgressRequest>(\n req,\n res,\n );\n if (!body) return true;\n if (!isBrowserBridgeRouteBodyObject(body)) {\n return rejectMalformedBrowserBridgePayload(ctx);\n }\n return runRoute(ctx, async (service) => {\n json(res, {\n session: await service.updateBrowserSessionProgress(\n sessionId,\n body,\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n\n const browserCompleteMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/sessions\\/([^/]+)\\/complete$/,\n );\n if (method === \"POST\" && browserCompleteMatch) {\n const sessionId = decodeMatchedPathComponent(\n ctx,\n browserCompleteMatch,\n 1,\n res,\n \"browser session id\",\n );\n if (!sessionId) return true;\n const body = await readJsonBody<CompleteLifeOpsBrowserSessionRequest>(\n req,\n res,\n );\n if (!body) return true;\n if (!isBrowserBridgeRouteBodyObject(body)) {\n return rejectMalformedBrowserBridgePayload(ctx);\n }\n return runRoute(ctx, async (service) => {\n json(res, {\n session: await service.completeBrowserSession(\n sessionId,\n body,\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n\n const browserCompanionProgressMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/companions\\/sessions\\/([^/]+)\\/progress$/,\n );\n if (method === \"POST\" && browserCompanionProgressMatch) {\n if (rateLimitRequest(ctx, \"companions:session-progress\")) {\n return true;\n }\n const sessionId = decodeMatchedPathComponent(\n ctx,\n browserCompanionProgressMatch,\n 1,\n res,\n \"browser session id\",\n );\n if (!sessionId) return true;\n return runRoute(ctx, async (service) => {\n const auth = getBrowserCompanionAuth(ctx);\n if (!auth) {\n return;\n }\n const body = await readJsonBody<UpdateBrowserBridgeSessionProgressRequest>(\n req,\n res,\n );\n if (!body) return;\n if (!isBrowserBridgeRouteBodyObject(body)) {\n rejectMalformedBrowserBridgePayload(ctx);\n return;\n }\n json(res, {\n session: await service.updateBrowserSessionProgressFromCompanion(\n auth.companionId,\n auth.pairingToken,\n sessionId,\n body,\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n\n const browserCompanionCompleteMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/companions\\/sessions\\/([^/]+)\\/complete$/,\n );\n if (method === \"POST\" && browserCompanionCompleteMatch) {\n if (rateLimitRequest(ctx, \"companions:session-complete\")) {\n return true;\n }\n const sessionId = decodeMatchedPathComponent(\n ctx,\n browserCompanionCompleteMatch,\n 1,\n res,\n \"browser session id\",\n );\n if (!sessionId) return true;\n return runRoute(ctx, async (service) => {\n const auth = getBrowserCompanionAuth(ctx);\n if (!auth) {\n return;\n }\n const body = await readJsonBody<CompleteLifeOpsBrowserSessionRequest>(\n req,\n res,\n );\n if (!body) return;\n if (!isBrowserBridgeRouteBodyObject(body)) {\n rejectMalformedBrowserBridgePayload(ctx);\n return;\n }\n json(res, {\n session: await service.completeBrowserSessionFromCompanion(\n auth.companionId,\n auth.pairingToken,\n sessionId,\n body,\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n\n return false;\n}\n\nexport { rateLimitRequest };\n"],"mappings":"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","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace-setup.d.ts","sourceRoot":"","sources":["../../src/routes/workspace-setup.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"workspace-setup.d.ts","sourceRoot":"","sources":["../../src/routes/workspace-setup.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAqC,KAAK,EAAE,MAAM,eAAe,CAAC;AAsE9E,eAAO,MAAM,sBAAsB,EAAE,KAAK,EASvC,CAAC"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { TLSSocket } from "node:tls";
|
|
1
2
|
import {
|
|
2
3
|
readJsonBody as httpReadJsonBody,
|
|
3
4
|
sendJson as httpSendJson,
|
|
4
5
|
sendJsonError as httpSendJsonError
|
|
5
6
|
} from "@elizaos/core";
|
|
6
|
-
import { TLSSocket } from "node:tls";
|
|
7
7
|
import {
|
|
8
8
|
BROWSER_WORKSPACE_ROUTE_PATHS,
|
|
9
9
|
handleBrowserWorkspaceRoutes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/routes/workspace-setup.ts"],"sourcesContent":["/**\n * Browser workspace HTTP routes — Plugin route registration.\n *\n * Mounts the legacy `/api/browser-workspace/*` paths through `Plugin.routes`\n * with `rawPath: true` so they keep their absolute paths under the runtime\n * route registry (no `/<pluginName>/` prefix).\n */\n\nimport type http from \"node:http\";\nimport {
|
|
1
|
+
{"version":3,"sources":["../../src/routes/workspace-setup.ts"],"sourcesContent":["/**\n * Browser workspace HTTP routes — Plugin route registration.\n *\n * Mounts the legacy `/api/browser-workspace/*` paths through `Plugin.routes`\n * with `rawPath: true` so they keep their absolute paths under the runtime\n * route registry (no `/<pluginName>/` prefix).\n */\n\nimport type http from \"node:http\";\nimport { TLSSocket } from \"node:tls\";\nimport type { IAgentRuntime, LegacyRouteHandler, Route } from \"@elizaos/core\";\nimport {\n readJsonBody as httpReadJsonBody,\n sendJson as httpSendJson,\n sendJsonError as httpSendJsonError,\n} from \"@elizaos/core\";\nimport {\n BROWSER_WORKSPACE_ROUTE_PATHS,\n handleBrowserWorkspaceRoutes,\n} from \"./workspace.js\";\n\nfunction json(res: http.ServerResponse, data: unknown, status = 200): void {\n httpSendJson(res, data, status);\n}\n\nfunction error(res: http.ServerResponse, message: string, status = 400): void {\n httpSendJsonError(res, message, status);\n}\n\nfunction firstHeaderValue(value: string | string[] | undefined): string | null {\n if (Array.isArray(value)) {\n return firstHeaderValue(value[0]);\n }\n if (typeof value !== \"string\") {\n return null;\n }\n const normalized = value.split(\",\")[0]?.trim();\n return normalized ? normalized : null;\n}\n\nfunction requestBaseUrl(req: http.IncomingMessage): string {\n const headers = req.headers ?? {};\n const protocol =\n firstHeaderValue(headers[\"x-forwarded-proto\"]) ??\n (req.socket instanceof TLSSocket && req.socket.encrypted\n ? \"https\"\n : \"http\");\n const host =\n firstHeaderValue(headers[\"x-forwarded-host\"]) ??\n firstHeaderValue(headers.host) ??\n \"localhost\";\n return `${protocol}://${host}`;\n}\n\nfunction browserWorkspaceRouteHandler(): LegacyRouteHandler {\n return async (\n req: unknown,\n res: unknown,\n runtime: unknown,\n ): Promise<void> => {\n const httpReq = req as http.IncomingMessage;\n const httpRes = res as http.ServerResponse;\n const method = (httpReq.method ?? \"GET\").toUpperCase();\n const url = new URL(httpReq.url ?? \"/\", requestBaseUrl(httpReq));\n await handleBrowserWorkspaceRoutes({\n req: httpReq,\n res: httpRes,\n method,\n pathname: url.pathname,\n url,\n state: {\n runtime: (runtime as IAgentRuntime) ?? null,\n },\n readJsonBody: httpReadJsonBody,\n json,\n error,\n });\n };\n}\n\nexport const browserWorkspaceRoutes: Route[] =\n BROWSER_WORKSPACE_ROUTE_PATHS.map(\n (r) =>\n ({\n type: r.type as Route[\"type\"],\n path: r.path,\n rawPath: true as const,\n handler: browserWorkspaceRouteHandler(),\n }) as Route,\n );\n"],"mappings":"AASA,SAAS,iBAAiB;AAE1B;AAAA,EACE,gBAAgB;AAAA,EAChB,YAAY;AAAA,EACZ,iBAAiB;AAAA,OACZ;AACP;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAEP,SAAS,KAAK,KAA0B,MAAe,SAAS,KAAW;AACzE,eAAa,KAAK,MAAM,MAAM;AAChC;AAEA,SAAS,MAAM,KAA0B,SAAiB,SAAS,KAAW;AAC5E,oBAAkB,KAAK,SAAS,MAAM;AACxC;AAEA,SAAS,iBAAiB,OAAqD;AAC7E,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,iBAAiB,MAAM,CAAC,CAAC;AAAA,EAClC;AACA,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO;AAAA,EACT;AACA,QAAM,aAAa,MAAM,MAAM,GAAG,EAAE,CAAC,GAAG,KAAK;AAC7C,SAAO,aAAa,aAAa;AACnC;AAEA,SAAS,eAAe,KAAmC;AACzD,QAAM,UAAU,IAAI,WAAW,CAAC;AAChC,QAAM,WACJ,iBAAiB,QAAQ,mBAAmB,CAAC,MAC5C,IAAI,kBAAkB,aAAa,IAAI,OAAO,YAC3C,UACA;AACN,QAAM,OACJ,iBAAiB,QAAQ,kBAAkB,CAAC,KAC5C,iBAAiB,QAAQ,IAAI,KAC7B;AACF,SAAO,GAAG,QAAQ,MAAM,IAAI;AAC9B;AAEA,SAAS,+BAAmD;AAC1D,SAAO,OACL,KACA,KACA,YACkB;AAClB,UAAM,UAAU;AAChB,UAAM,UAAU;AAChB,UAAM,UAAU,QAAQ,UAAU,OAAO,YAAY;AACrD,UAAM,MAAM,IAAI,IAAI,QAAQ,OAAO,KAAK,eAAe,OAAO,CAAC;AAC/D,UAAM,6BAA6B;AAAA,MACjC,KAAK;AAAA,MACL,KAAK;AAAA,MACL;AAAA,MACA,UAAU,IAAI;AAAA,MACd;AAAA,MACA,OAAO;AAAA,QACL,SAAU,WAA6B;AAAA,MACzC;AAAA,MACA,cAAc;AAAA,MACd;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAEO,MAAM,yBACX,8BAA8B;AAAA,EAC5B,CAAC,OACE;AAAA,IACC,MAAM,EAAE;AAAA,IACR,MAAM,EAAE;AAAA,IACR,SAAS;AAAA,IACT,SAAS,6BAA6B;AAAA,EACxC;AACJ;","names":[]}
|
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
* legacy `/api/browser-workspace/*` paths are preserved. Implementation
|
|
6
6
|
* lives in `@elizaos/plugin-browser/workspace`; this is the HTTP edge.
|
|
7
7
|
*/
|
|
8
|
-
import type { RouteRequestContext } from "@elizaos/core";
|
|
9
|
-
import type { IAgentRuntime } from "@elizaos/core";
|
|
8
|
+
import type { IAgentRuntime, RouteRequestContext } from "@elizaos/core";
|
|
10
9
|
export interface BrowserWorkspaceRouteContext extends RouteRequestContext {
|
|
11
10
|
url?: URL;
|
|
12
11
|
state?: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../src/routes/workspace.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../src/routes/workspace.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAyDxE,MAAM,WAAW,4BAA6B,SAAQ,mBAAmB;IACvE,GAAG,CAAC,EAAE,GAAG,CAAC;IACV,KAAK,CAAC,EAAE;QACN,OAAO,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;KAChC,CAAC;CACH;AA0FD,wBAAsB,4BAA4B,CAChD,GAAG,EAAE,4BAA4B,GAChC,OAAO,CAAC,OAAO,CAAC,CAqMlB;AAED,eAAO,MAAM,6BAA6B,EAAE,KAAK,CAAC;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAYA,CAAC"}
|
package/dist/routes/workspace.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { requestBrowserWorkspace } from "../workspace/browser-workspace-desktop.js";
|
|
2
|
+
import { assertBrowserWorkspaceUserScriptAllowed } from "../workspace/browser-workspace-helpers.js";
|
|
1
3
|
import {
|
|
2
4
|
closeBrowserWorkspaceTab,
|
|
3
5
|
evaluateBrowserWorkspaceTab,
|
|
@@ -5,6 +7,7 @@ import {
|
|
|
5
7
|
getBrowserWorkspaceSnapshot,
|
|
6
8
|
getBrowserWorkspaceUnavailableMessage,
|
|
7
9
|
hideBrowserWorkspaceTab,
|
|
10
|
+
isBrowserWorkspaceBridgeConfigured,
|
|
8
11
|
listBrowserWorkspaceTabs,
|
|
9
12
|
navigateBrowserWorkspaceTab,
|
|
10
13
|
openBrowserWorkspaceTab,
|
|
@@ -40,6 +43,33 @@ function connectorReferenceFromSearchParams(url) {
|
|
|
40
43
|
partition: url?.searchParams.get("partition")
|
|
41
44
|
};
|
|
42
45
|
}
|
|
46
|
+
function buildBrowserWorkspaceEventsBridgePath(url) {
|
|
47
|
+
const params = new URLSearchParams();
|
|
48
|
+
for (const key of ["after", "limit", "tabId", "type"]) {
|
|
49
|
+
const value = url?.searchParams.get(key)?.trim();
|
|
50
|
+
if (value) {
|
|
51
|
+
params.set(key, value);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const query = params.toString();
|
|
55
|
+
return query ? `/events?${query}` : "/events";
|
|
56
|
+
}
|
|
57
|
+
function isBrowserWorkspaceRouteBodyObject(value) {
|
|
58
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
59
|
+
}
|
|
60
|
+
function rejectMalformedBrowserWorkspacePayload(ctx) {
|
|
61
|
+
ctx.json(ctx.res, { error: "request body must be a JSON object" }, 400);
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
function decodeBrowserWorkspaceTabId(raw) {
|
|
65
|
+
if (typeof raw !== "string") return null;
|
|
66
|
+
try {
|
|
67
|
+
const decoded = decodeURIComponent(raw).trim();
|
|
68
|
+
return decoded ? decoded : null;
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
43
73
|
async function assertBrowserWorkspaceTabConnectorAccountGate(ctx, tabId, reference, operation) {
|
|
44
74
|
const tabs = await listBrowserWorkspaceTabs();
|
|
45
75
|
const tab = tabs.find((entry) => entry.id === tabId) ?? null;
|
|
@@ -53,7 +83,7 @@ async function assertBrowserWorkspaceTabConnectorAccountGate(ctx, tabId, referen
|
|
|
53
83
|
}
|
|
54
84
|
async function handleBrowserWorkspaceRoutes(ctx) {
|
|
55
85
|
const { req, res, method, pathname, readJsonBody, json } = ctx;
|
|
56
|
-
if (pathname !== "/api/browser-workspace" && pathname !== "/api/browser-workspace/command" && pathname !== "/api/browser-workspace/tabs" && !pathname.startsWith("/api/browser-workspace/tabs/")) {
|
|
86
|
+
if (pathname !== "/api/browser-workspace" && pathname !== "/api/browser-workspace/command" && pathname !== "/api/browser-workspace/events" && pathname !== "/api/browser-workspace/tabs" && !pathname.startsWith("/api/browser-workspace/tabs/")) {
|
|
57
87
|
return false;
|
|
58
88
|
}
|
|
59
89
|
try {
|
|
@@ -61,8 +91,23 @@ async function handleBrowserWorkspaceRoutes(ctx) {
|
|
|
61
91
|
json(res, await getBrowserWorkspaceSnapshot());
|
|
62
92
|
return true;
|
|
63
93
|
}
|
|
94
|
+
if (pathname === "/api/browser-workspace/events" && method === "GET") {
|
|
95
|
+
if (!isBrowserWorkspaceBridgeConfigured()) {
|
|
96
|
+
throw new Error(getBrowserWorkspaceUnavailableMessage());
|
|
97
|
+
}
|
|
98
|
+
json(
|
|
99
|
+
res,
|
|
100
|
+
await requestBrowserWorkspace(
|
|
101
|
+
buildBrowserWorkspaceEventsBridgePath(ctx.url)
|
|
102
|
+
)
|
|
103
|
+
);
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
64
106
|
if (pathname === "/api/browser-workspace/command" && method === "POST") {
|
|
65
107
|
const body = await readJsonBody(req, res) ?? null;
|
|
108
|
+
if (!isBrowserWorkspaceRouteBodyObject(body)) {
|
|
109
|
+
return rejectMalformedBrowserWorkspacePayload(ctx);
|
|
110
|
+
}
|
|
66
111
|
if (!body?.subaction) {
|
|
67
112
|
json(res, { error: "subaction is required" }, 400);
|
|
68
113
|
return true;
|
|
@@ -80,7 +125,10 @@ async function handleBrowserWorkspaceRoutes(ctx) {
|
|
|
80
125
|
return true;
|
|
81
126
|
}
|
|
82
127
|
if (pathname === "/api/browser-workspace/tabs" && method === "POST") {
|
|
83
|
-
const body = await readJsonBody(req, res) ??
|
|
128
|
+
const body = await readJsonBody(req, res) ?? null;
|
|
129
|
+
if (!isBrowserWorkspaceRouteBodyObject(body)) {
|
|
130
|
+
return rejectMalformedBrowserWorkspacePayload(ctx);
|
|
131
|
+
}
|
|
84
132
|
const connectorGate = await assertBrowserWorkspaceConnectorAccountGate({
|
|
85
133
|
runtime: ctx.state?.runtime ?? null,
|
|
86
134
|
connectorProvider: body.connectorProvider,
|
|
@@ -102,7 +150,11 @@ async function handleBrowserWorkspaceRoutes(ctx) {
|
|
|
102
150
|
if (!match) {
|
|
103
151
|
return false;
|
|
104
152
|
}
|
|
105
|
-
const tabId =
|
|
153
|
+
const tabId = decodeBrowserWorkspaceTabId(match[1]);
|
|
154
|
+
if (!tabId) {
|
|
155
|
+
json(res, { error: "valid tab id is required" }, 400);
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
106
158
|
const action = match[2] ?? null;
|
|
107
159
|
if (!action && method === "DELETE") {
|
|
108
160
|
await assertBrowserWorkspaceTabConnectorAccountGate(
|
|
@@ -151,6 +203,9 @@ async function handleBrowserWorkspaceRoutes(ctx) {
|
|
|
151
203
|
}
|
|
152
204
|
if (action === "navigate" && method === "POST") {
|
|
153
205
|
const body = await readJsonBody(req, res);
|
|
206
|
+
if (!isBrowserWorkspaceRouteBodyObject(body)) {
|
|
207
|
+
return rejectMalformedBrowserWorkspacePayload(ctx);
|
|
208
|
+
}
|
|
154
209
|
if (!body?.url?.trim()) {
|
|
155
210
|
json(res, { error: "url is required" }, 400);
|
|
156
211
|
return true;
|
|
@@ -171,6 +226,9 @@ async function handleBrowserWorkspaceRoutes(ctx) {
|
|
|
171
226
|
}
|
|
172
227
|
if (action === "eval" && method === "POST") {
|
|
173
228
|
const body = await readJsonBody(req, res);
|
|
229
|
+
if (!isBrowserWorkspaceRouteBodyObject(body)) {
|
|
230
|
+
return rejectMalformedBrowserWorkspacePayload(ctx);
|
|
231
|
+
}
|
|
174
232
|
if (!body?.script?.trim()) {
|
|
175
233
|
json(res, { error: "script is required" }, 400);
|
|
176
234
|
return true;
|
|
@@ -181,6 +239,7 @@ async function handleBrowserWorkspaceRoutes(ctx) {
|
|
|
181
239
|
body,
|
|
182
240
|
"evaluate browser workspace tab"
|
|
183
241
|
);
|
|
242
|
+
assertBrowserWorkspaceUserScriptAllowed(body.script, "eval", "desktop");
|
|
184
243
|
json(res, {
|
|
185
244
|
result: await evaluateBrowserWorkspaceTab({
|
|
186
245
|
id: tabId,
|
|
@@ -200,6 +259,7 @@ async function handleBrowserWorkspaceRoutes(ctx) {
|
|
|
200
259
|
const BROWSER_WORKSPACE_ROUTE_PATHS = [
|
|
201
260
|
{ type: "GET", path: "/api/browser-workspace" },
|
|
202
261
|
{ type: "POST", path: "/api/browser-workspace/command" },
|
|
262
|
+
{ type: "GET", path: "/api/browser-workspace/events" },
|
|
203
263
|
{ type: "GET", path: "/api/browser-workspace/tabs" },
|
|
204
264
|
{ type: "POST", path: "/api/browser-workspace/tabs" },
|
|
205
265
|
{ type: "DELETE", path: "/api/browser-workspace/tabs/:tabId" },
|