@elizaos/plugin-browser 2.0.0-alpha.9 → 2.0.3-beta.2
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 +98 -83
- package/auto-enable.ts +24 -0
- package/dist/actions/browser-autofill-login.d.ts +43 -0
- package/dist/actions/browser-autofill-login.d.ts.map +1 -0
- package/dist/actions/browser-autofill-login.js +278 -0
- package/dist/actions/browser-autofill-login.js.map +1 -0
- package/dist/actions/browser.d.ts +11 -0
- package/dist/actions/browser.d.ts.map +1 -0
- package/dist/actions/browser.js +412 -0
- package/dist/actions/browser.js.map +1 -0
- package/dist/actions/manage-browser-bridge.d.ts +34 -0
- package/dist/actions/manage-browser-bridge.d.ts.map +1 -0
- package/dist/actions/manage-browser-bridge.js +572 -0
- package/dist/actions/manage-browser-bridge.js.map +1 -0
- 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 +103 -0
- package/dist/browser-service.d.ts.map +1 -0
- package/dist/browser-service.js +186 -0
- package/dist/browser-service.js.map +1 -0
- 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/contracts.d.ts +284 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +56 -0
- package/dist/contracts.js.map +1 -0
- package/dist/index.d.ts +30 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +76 -90
- package/dist/index.js.map +1 -1
- package/dist/lifeops-session-contracts.d.ts +46 -0
- package/dist/lifeops-session-contracts.d.ts.map +1 -0
- package/dist/lifeops-session-contracts.js +1 -0
- package/dist/lifeops-session-contracts.js.map +1 -0
- 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 +27 -0
- package/dist/packaging.d.ts.map +1 -0
- package/dist/packaging.js +571 -0
- package/dist/packaging.js.map +1 -0
- 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 +10 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +168 -0
- package/dist/plugin.js.map +1 -0
- package/dist/providers/workspace.d.ts +13 -0
- package/dist/providers/workspace.d.ts.map +1 -0
- package/dist/providers/workspace.js +64 -0
- package/dist/providers/workspace.js.map +1 -0
- package/dist/routes/bridge.d.ts +37 -0
- package/dist/routes/bridge.d.ts.map +1 -0
- package/dist/routes/bridge.js +844 -0
- package/dist/routes/bridge.js.map +1 -0
- package/dist/routes/workspace-account-gate.d.ts +29 -0
- package/dist/routes/workspace-account-gate.d.ts.map +1 -0
- package/dist/routes/workspace-account-gate.js +147 -0
- package/dist/routes/workspace-account-gate.js.map +1 -0
- package/dist/routes/workspace-setup.d.ts +10 -0
- package/dist/routes/workspace-setup.d.ts.map +1 -0
- package/dist/routes/workspace-setup.js +65 -0
- package/dist/routes/workspace-setup.js.map +1 -0
- package/dist/routes/workspace.d.ts +20 -0
- package/dist/routes/workspace.d.ts.map +1 -0
- package/dist/routes/workspace.js +276 -0
- package/dist/routes/workspace.js.map +1 -0
- package/dist/schema.d.ts +2326 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +133 -0
- package/dist/schema.js.map +1 -0
- package/dist/service.d.ts +30 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +5 -0
- package/dist/service.js.map +1 -0
- package/dist/targets/bridge-target.d.ts +31 -0
- package/dist/targets/bridge-target.d.ts.map +1 -0
- package/dist/targets/bridge-target.js +98 -0
- package/dist/targets/bridge-target.js.map +1 -0
- 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 +41 -0
- package/dist/workspace/browser-capture.d.ts.map +1 -0
- package/dist/workspace/browser-capture.js +159 -0
- package/dist/workspace/browser-capture.js.map +1 -0
- package/dist/workspace/browser-workspace-desktop.d.ts +19 -0
- package/dist/workspace/browser-workspace-desktop.d.ts.map +1 -0
- package/dist/workspace/browser-workspace-desktop.js +1578 -0
- package/dist/workspace/browser-workspace-desktop.js.map +1 -0
- package/dist/workspace/browser-workspace-elements.d.ts +42 -0
- package/dist/workspace/browser-workspace-elements.d.ts.map +1 -0
- package/dist/workspace/browser-workspace-elements.js +547 -0
- package/dist/workspace/browser-workspace-elements.js.map +1 -0
- package/dist/workspace/browser-workspace-forms.d.ts +19 -0
- package/dist/workspace/browser-workspace-forms.d.ts.map +1 -0
- package/dist/workspace/browser-workspace-forms.js +277 -0
- package/dist/workspace/browser-workspace-forms.js.map +1 -0
- package/dist/workspace/browser-workspace-helpers.d.ts +32 -0
- package/dist/workspace/browser-workspace-helpers.d.ts.map +1 -0
- package/dist/workspace/browser-workspace-helpers.js +232 -0
- package/dist/workspace/browser-workspace-helpers.js.map +1 -0
- package/dist/workspace/browser-workspace-jsdom.d.ts +16 -0
- package/dist/workspace/browser-workspace-jsdom.d.ts.map +1 -0
- package/dist/workspace/browser-workspace-jsdom.js +233 -0
- package/dist/workspace/browser-workspace-jsdom.js.map +1 -0
- package/dist/workspace/browser-workspace-network.d.ts +7 -0
- package/dist/workspace/browser-workspace-network.d.ts.map +1 -0
- package/dist/workspace/browser-workspace-network.js +145 -0
- package/dist/workspace/browser-workspace-network.js.map +1 -0
- package/dist/workspace/browser-workspace-snapshots.d.ts +14 -0
- package/dist/workspace/browser-workspace-snapshots.d.ts.map +1 -0
- package/dist/workspace/browser-workspace-snapshots.js +144 -0
- package/dist/workspace/browser-workspace-snapshots.js.map +1 -0
- package/dist/workspace/browser-workspace-state.d.ts +24 -0
- package/dist/workspace/browser-workspace-state.d.ts.map +1 -0
- package/dist/workspace/browser-workspace-state.js +155 -0
- package/dist/workspace/browser-workspace-state.js.map +1 -0
- package/dist/workspace/browser-workspace-types.d.ts +345 -0
- package/dist/workspace/browser-workspace-types.d.ts.map +1 -0
- package/dist/workspace/browser-workspace-types.js +11 -0
- package/dist/workspace/browser-workspace-types.js.map +1 -0
- package/dist/workspace/browser-workspace-web.d.ts +8 -0
- package/dist/workspace/browser-workspace-web.d.ts.map +1 -0
- package/dist/workspace/browser-workspace-web.js +1342 -0
- package/dist/workspace/browser-workspace-web.js.map +1 -0
- package/dist/workspace/browser-workspace.d.ts +39 -0
- package/dist/workspace/browser-workspace.d.ts.map +1 -0
- package/dist/workspace/browser-workspace.js +958 -0
- package/dist/workspace/browser-workspace.js.map +1 -0
- package/dist/workspace/index.d.ts +26 -0
- package/dist/workspace/index.d.ts.map +1 -0
- package/dist/workspace/index.js +3 -0
- package/dist/workspace/index.js.map +1 -0
- package/dist/workspace.d.ts +2 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +2 -0
- package/dist/workspace.js.map +1 -0
- package/package.json +71 -110
- package/dist/actions/click.d.ts +0 -3
- package/dist/actions/click.d.ts.map +0 -1
- package/dist/actions/click.js +0 -158
- package/dist/actions/click.js.map +0 -1
- package/dist/actions/extract.d.ts +0 -3
- package/dist/actions/extract.d.ts.map +0 -1
- package/dist/actions/extract.js +0 -168
- package/dist/actions/extract.js.map +0 -1
- package/dist/actions/index.d.ts +0 -7
- package/dist/actions/index.d.ts.map +0 -1
- package/dist/actions/index.js +0 -7
- package/dist/actions/index.js.map +0 -1
- package/dist/actions/navigate.d.ts +0 -3
- package/dist/actions/navigate.d.ts.map +0 -1
- package/dist/actions/navigate.js +0 -187
- package/dist/actions/navigate.js.map +0 -1
- package/dist/actions/screenshot.d.ts +0 -3
- package/dist/actions/screenshot.d.ts.map +0 -1
- package/dist/actions/screenshot.js +0 -167
- package/dist/actions/screenshot.js.map +0 -1
- package/dist/actions/select.d.ts +0 -3
- package/dist/actions/select.d.ts.map +0 -1
- package/dist/actions/select.js +0 -167
- package/dist/actions/select.js.map +0 -1
- package/dist/actions/type.d.ts +0 -3
- package/dist/actions/type.d.ts.map +0 -1
- package/dist/actions/type.js +0 -167
- package/dist/actions/type.js.map +0 -1
- package/dist/cli/index.d.ts +0 -8
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -13
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/register.d.ts +0 -20
- package/dist/cli/register.d.ts.map +0 -1
- package/dist/cli/register.js +0 -403
- package/dist/cli/register.js.map +0 -1
- package/dist/providerRelevance.d.ts +0 -4
- package/dist/providerRelevance.d.ts.map +0 -1
- package/dist/providerRelevance.js +0 -33
- package/dist/providerRelevance.js.map +0 -1
- package/dist/providers/browser-state.d.ts +0 -3
- package/dist/providers/browser-state.d.ts.map +0 -1
- package/dist/providers/browser-state.js +0 -72
- package/dist/providers/browser-state.js.map +0 -1
- package/dist/providers/index.d.ts +0 -2
- package/dist/providers/index.d.ts.map +0 -1
- package/dist/providers/index.js +0 -2
- package/dist/providers/index.js.map +0 -1
- package/dist/services/browser-service.d.ts +0 -32
- package/dist/services/browser-service.d.ts.map +0 -1
- package/dist/services/browser-service.js +0 -213
- package/dist/services/browser-service.js.map +0 -1
- package/dist/services/index.d.ts +0 -4
- package/dist/services/index.d.ts.map +0 -1
- package/dist/services/index.js +0 -4
- package/dist/services/index.js.map +0 -1
- package/dist/services/process-manager.d.ts +0 -24
- package/dist/services/process-manager.d.ts.map +0 -1
- package/dist/services/process-manager.js +0 -270
- package/dist/services/process-manager.js.map +0 -1
- package/dist/services/websocket-client.d.ts +0 -35
- package/dist/services/websocket-client.d.ts.map +0 -1
- package/dist/services/websocket-client.js +0 -221
- package/dist/services/websocket-client.js.map +0 -1
- package/dist/types.d.ts +0 -101
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/dist/utils/captcha.d.ts +0 -33
- package/dist/utils/captcha.d.ts.map +0 -1
- package/dist/utils/captcha.js +0 -219
- package/dist/utils/captcha.js.map +0 -1
- package/dist/utils/errors.d.ts +0 -37
- package/dist/utils/errors.d.ts.map +0 -1
- package/dist/utils/errors.js +0 -81
- package/dist/utils/errors.js.map +0 -1
- package/dist/utils/index.d.ts +0 -5
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -5
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/retry.d.ts +0 -26
- package/dist/utils/retry.d.ts.map +0 -1
- package/dist/utils/retry.js +0 -55
- package/dist/utils/retry.js.map +0 -1
- package/dist/utils/security.d.ts +0 -27
- package/dist/utils/security.d.ts.map +0 -1
- package/dist/utils/security.js +0 -139
- package/dist/utils/security.js.map +0 -1
- package/dist/utils/url.d.ts +0 -12
- package/dist/utils/url.d.ts.map +0 -1
- package/dist/utils/url.js +0 -39
- package/dist/utils/url.js.map +0 -1
|
@@ -0,0 +1,844 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { logger } from "@elizaos/core";
|
|
3
|
+
import {
|
|
4
|
+
BROWSER_BRIDGE_PACKAGE_PATH_TARGETS
|
|
5
|
+
} from "../contracts.js";
|
|
6
|
+
import {
|
|
7
|
+
buildBrowserBridgeCompanionPackage,
|
|
8
|
+
getBrowserBridgeCompanionDownloadFile,
|
|
9
|
+
getBrowserBridgeCompanionPackageStatus,
|
|
10
|
+
openBrowserBridgeCompanionManager,
|
|
11
|
+
openBrowserBridgeCompanionPackagePath
|
|
12
|
+
} from "../packaging.js";
|
|
13
|
+
import {
|
|
14
|
+
BROWSER_BRIDGE_ROUTE_SERVICE_TYPE
|
|
15
|
+
} from "../service.js";
|
|
16
|
+
function getService(ctx) {
|
|
17
|
+
if (!ctx.state.runtime) {
|
|
18
|
+
ctx.error(ctx.res, "Agent runtime is not available", 503);
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const service = ctx.state.runtime.getService(
|
|
22
|
+
BROWSER_BRIDGE_ROUTE_SERVICE_TYPE
|
|
23
|
+
);
|
|
24
|
+
if (!service) {
|
|
25
|
+
ctx.error(ctx.res, "Browser Bridge service is not available", 503);
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
return service;
|
|
29
|
+
}
|
|
30
|
+
function getBrowserCompanionAuth(ctx) {
|
|
31
|
+
const companionHeader = ctx.req.headers["x-browser-bridge-companion-id"];
|
|
32
|
+
const companionId = typeof companionHeader === "string" ? companionHeader.trim() : "";
|
|
33
|
+
if (!companionId) {
|
|
34
|
+
routeJsonError(
|
|
35
|
+
ctx,
|
|
36
|
+
"Missing X-Browser-Bridge-Companion-Id header",
|
|
37
|
+
401,
|
|
38
|
+
"browser_bridge_companion_auth_missing_id"
|
|
39
|
+
);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const authHeader = typeof ctx.req.headers.authorization === "string" ? ctx.req.headers.authorization.trim() : "";
|
|
43
|
+
const match = /^Bearer\s+(.+)$/i.exec(authHeader);
|
|
44
|
+
const pairingToken = match?.[1]?.trim() ?? "";
|
|
45
|
+
if (!pairingToken) {
|
|
46
|
+
routeJsonError(
|
|
47
|
+
ctx,
|
|
48
|
+
"Missing browser companion bearer token",
|
|
49
|
+
401,
|
|
50
|
+
"browser_bridge_companion_auth_missing_token"
|
|
51
|
+
);
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
companionId,
|
|
56
|
+
pairingToken
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function browserAutoPairOriginAllowed(ctx) {
|
|
60
|
+
const originHeader = typeof ctx.req.headers.origin === "string" ? ctx.req.headers.origin.trim() : "";
|
|
61
|
+
if (!originHeader) {
|
|
62
|
+
return requestIsLoopback(ctx);
|
|
63
|
+
}
|
|
64
|
+
if (originHeader === ctx.url.origin) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
return originHeader.startsWith("chrome-extension://") || originHeader.startsWith("safari-web-extension://");
|
|
68
|
+
}
|
|
69
|
+
function requestIsLoopback(ctx) {
|
|
70
|
+
const remoteAddress = ctx.req.socket.remoteAddress?.trim().toLowerCase();
|
|
71
|
+
return remoteAddress === "127.0.0.1" || remoteAddress === "::1" || remoteAddress === "0:0:0:0:0:0:0:1" || remoteAddress === "::ffff:127.0.0.1" || remoteAddress === "::ffff:0:127.0.0.1";
|
|
72
|
+
}
|
|
73
|
+
const BROWSER_BRIDGE_RATE_LIMITS = {
|
|
74
|
+
default: { maxRequests: 60, windowMs: 6e4 }
|
|
75
|
+
};
|
|
76
|
+
const rateLimitBuckets = /* @__PURE__ */ new Map();
|
|
77
|
+
const RATE_LIMIT_CLEANUP_INTERVAL_MS = 5 * 60 * 1e3;
|
|
78
|
+
let lastRateLimitCleanup = Date.now();
|
|
79
|
+
function cleanupRateLimitBuckets(windowMs) {
|
|
80
|
+
const now = Date.now();
|
|
81
|
+
if (now - lastRateLimitCleanup < RATE_LIMIT_CLEANUP_INTERVAL_MS) return;
|
|
82
|
+
lastRateLimitCleanup = now;
|
|
83
|
+
const cutoff = now - windowMs;
|
|
84
|
+
for (const [key, entry] of rateLimitBuckets) {
|
|
85
|
+
entry.timestamps = entry.timestamps.filter(
|
|
86
|
+
(timestamp) => timestamp > cutoff
|
|
87
|
+
);
|
|
88
|
+
if (entry.timestamps.length === 0) rateLimitBuckets.delete(key);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function checkBrowserBridgeRateLimit(key, config) {
|
|
92
|
+
const now = Date.now();
|
|
93
|
+
cleanupRateLimitBuckets(config.windowMs);
|
|
94
|
+
let entry = rateLimitBuckets.get(key);
|
|
95
|
+
if (!entry) {
|
|
96
|
+
entry = { timestamps: [] };
|
|
97
|
+
rateLimitBuckets.set(key, entry);
|
|
98
|
+
}
|
|
99
|
+
const cutoff = now - config.windowMs;
|
|
100
|
+
entry.timestamps = entry.timestamps.filter((timestamp) => timestamp > cutoff);
|
|
101
|
+
if (entry.timestamps.length >= config.maxRequests) {
|
|
102
|
+
const oldestInWindow = entry.timestamps[0];
|
|
103
|
+
const retryAfterMs = oldestInWindow === void 0 ? 0 : oldestInWindow + config.windowMs - now;
|
|
104
|
+
return { allowed: false, retryAfterMs: Math.max(retryAfterMs, 0) };
|
|
105
|
+
}
|
|
106
|
+
entry.timestamps.push(now);
|
|
107
|
+
return { allowed: true, retryAfterMs: 0 };
|
|
108
|
+
}
|
|
109
|
+
function rateLimitRequest(ctx, operation) {
|
|
110
|
+
const agentId = String(ctx.state.runtime?.agentId ?? "unknown");
|
|
111
|
+
const companionHeader = ctx.req.headers["x-browser-bridge-companion-id"];
|
|
112
|
+
const companionId = typeof companionHeader === "string" ? companionHeader.trim() : "anonymous";
|
|
113
|
+
const remoteAddress = ctx.req.socket.remoteAddress?.trim() ?? "unknown";
|
|
114
|
+
const limitKey = `${agentId}:${operation}:${remoteAddress}:${companionId}`;
|
|
115
|
+
const config = BROWSER_BRIDGE_RATE_LIMITS.default;
|
|
116
|
+
const { allowed, retryAfterMs } = checkBrowserBridgeRateLimit(
|
|
117
|
+
limitKey,
|
|
118
|
+
config
|
|
119
|
+
);
|
|
120
|
+
if (!allowed) {
|
|
121
|
+
ctx.res.writeHead(429, {
|
|
122
|
+
"Content-Type": "application/json",
|
|
123
|
+
"Retry-After": String(Math.ceil(retryAfterMs / 1e3))
|
|
124
|
+
});
|
|
125
|
+
ctx.res.end(JSON.stringify({ error: "Rate limit exceeded", retryAfterMs }));
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
function routeOperation(ctx) {
|
|
131
|
+
return `${ctx.method.toUpperCase()} ${ctx.pathname}`;
|
|
132
|
+
}
|
|
133
|
+
function sanitizeTelemetryToken(value) {
|
|
134
|
+
if (!value) return void 0;
|
|
135
|
+
const token = value.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
|
|
136
|
+
const normalized = token.replace(/_+/g, "_").replace(/^_+|_+$/g, "");
|
|
137
|
+
return normalized ? normalized.slice(0, 64) : void 0;
|
|
138
|
+
}
|
|
139
|
+
function inferTelemetryErrorKind(error) {
|
|
140
|
+
if (error instanceof Error) {
|
|
141
|
+
const message = error.message.toLowerCase();
|
|
142
|
+
if (error.name === "AbortError" || error.name === "TimeoutError" || message.includes("timeout") || message.includes("timed out")) {
|
|
143
|
+
return "timeout";
|
|
144
|
+
}
|
|
145
|
+
return sanitizeTelemetryToken(error.name);
|
|
146
|
+
}
|
|
147
|
+
return typeof error === "string" ? sanitizeTelemetryToken(error) : void 0;
|
|
148
|
+
}
|
|
149
|
+
function createBrowserBridgeTelemetrySpan(meta) {
|
|
150
|
+
const startedAt = Date.now();
|
|
151
|
+
let settled = false;
|
|
152
|
+
const finalize = (outcome, args) => {
|
|
153
|
+
if (settled) return;
|
|
154
|
+
settled = true;
|
|
155
|
+
const event = {
|
|
156
|
+
schema: "integration_boundary_v1",
|
|
157
|
+
boundary: meta.boundary,
|
|
158
|
+
operation: meta.operation,
|
|
159
|
+
outcome,
|
|
160
|
+
durationMs: Math.max(0, Date.now() - startedAt)
|
|
161
|
+
};
|
|
162
|
+
if (typeof meta.timeoutMs === "number") event.timeoutMs = meta.timeoutMs;
|
|
163
|
+
if (typeof args?.statusCode === "number")
|
|
164
|
+
event.statusCode = args.statusCode;
|
|
165
|
+
if (outcome === "failure") {
|
|
166
|
+
event.errorKind = sanitizeTelemetryToken(args?.errorKind) ?? inferTelemetryErrorKind(args?.error);
|
|
167
|
+
}
|
|
168
|
+
const line = `[integration] ${JSON.stringify(event)}`;
|
|
169
|
+
if (outcome === "success") {
|
|
170
|
+
logger.info(line);
|
|
171
|
+
} else {
|
|
172
|
+
logger.warn(line);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
return {
|
|
176
|
+
success: (args) => finalize("success", args),
|
|
177
|
+
failure: (args) => finalize("failure", args)
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function errorMessage(error) {
|
|
181
|
+
return error instanceof Error ? error.message : String(error);
|
|
182
|
+
}
|
|
183
|
+
function routeJsonError(ctx, message, status, code) {
|
|
184
|
+
ctx.json(
|
|
185
|
+
ctx.res,
|
|
186
|
+
{
|
|
187
|
+
error: message,
|
|
188
|
+
...code ? { code } : {}
|
|
189
|
+
},
|
|
190
|
+
status
|
|
191
|
+
);
|
|
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
|
+
}
|
|
200
|
+
function isStatusError(error) {
|
|
201
|
+
return error instanceof Error && "status" in error && typeof error.status === "number";
|
|
202
|
+
}
|
|
203
|
+
function statusErrorCode(error) {
|
|
204
|
+
if (error && typeof error === "object" && "code" in error && typeof error.code === "string") {
|
|
205
|
+
return error.code;
|
|
206
|
+
}
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
function decodeMatchedPathComponent(ctx, match, index, res, label) {
|
|
210
|
+
const raw = match?.[index];
|
|
211
|
+
return raw ? ctx.decodePathComponent(raw, res, label) : null;
|
|
212
|
+
}
|
|
213
|
+
async function runRoute(ctx, fn) {
|
|
214
|
+
const operation = routeOperation(ctx);
|
|
215
|
+
const span = createBrowserBridgeTelemetrySpan({
|
|
216
|
+
boundary: "browser-bridge",
|
|
217
|
+
operation
|
|
218
|
+
});
|
|
219
|
+
const service = getService(ctx);
|
|
220
|
+
if (!service) {
|
|
221
|
+
logger.info(
|
|
222
|
+
{
|
|
223
|
+
boundary: "browser-bridge",
|
|
224
|
+
operation,
|
|
225
|
+
statusCode: 503
|
|
226
|
+
},
|
|
227
|
+
"[browser-bridge] Route rejected because agent runtime is unavailable"
|
|
228
|
+
);
|
|
229
|
+
span.failure({
|
|
230
|
+
statusCode: 503,
|
|
231
|
+
errorKind: "runtime_unavailable"
|
|
232
|
+
});
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
await fn(service);
|
|
237
|
+
span.success({
|
|
238
|
+
statusCode: ctx.res.statusCode >= 400 ? ctx.res.statusCode : 200
|
|
239
|
+
});
|
|
240
|
+
return true;
|
|
241
|
+
} catch (error) {
|
|
242
|
+
if (isStatusError(error)) {
|
|
243
|
+
const logFn = error.status === 401 ? logger.debug.bind(logger) : logger.warn.bind(logger);
|
|
244
|
+
logFn(
|
|
245
|
+
{
|
|
246
|
+
boundary: "browser-bridge",
|
|
247
|
+
operation,
|
|
248
|
+
statusCode: error.status
|
|
249
|
+
},
|
|
250
|
+
`[browser-bridge] Route failed: ${error.message}`
|
|
251
|
+
);
|
|
252
|
+
span.failure({
|
|
253
|
+
statusCode: error.status,
|
|
254
|
+
error,
|
|
255
|
+
errorKind: error.status === 401 ? "browser_bridge_auth_invalid" : "browser_bridge_service_error"
|
|
256
|
+
});
|
|
257
|
+
routeJsonError(ctx, error.message, error.status, statusErrorCode(error));
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
logger.error(
|
|
261
|
+
{
|
|
262
|
+
boundary: "browser-bridge",
|
|
263
|
+
operation
|
|
264
|
+
},
|
|
265
|
+
`[browser-bridge] Route crashed: ${errorMessage(error)}`
|
|
266
|
+
);
|
|
267
|
+
span.failure({
|
|
268
|
+
error,
|
|
269
|
+
errorKind: "unhandled_error"
|
|
270
|
+
});
|
|
271
|
+
throw error;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
async function runStatelessRoute(ctx, fn) {
|
|
275
|
+
const operation = routeOperation(ctx);
|
|
276
|
+
const span = createBrowserBridgeTelemetrySpan({
|
|
277
|
+
boundary: "browser-bridge",
|
|
278
|
+
operation
|
|
279
|
+
});
|
|
280
|
+
try {
|
|
281
|
+
await fn();
|
|
282
|
+
span.success({
|
|
283
|
+
statusCode: ctx.res.statusCode >= 400 ? ctx.res.statusCode : 200
|
|
284
|
+
});
|
|
285
|
+
return true;
|
|
286
|
+
} catch (error) {
|
|
287
|
+
if (isStatusError(error)) {
|
|
288
|
+
logger.warn(
|
|
289
|
+
{
|
|
290
|
+
boundary: "browser-bridge",
|
|
291
|
+
operation,
|
|
292
|
+
statusCode: error.status
|
|
293
|
+
},
|
|
294
|
+
`[browser-bridge] Route failed: ${error.message}`
|
|
295
|
+
);
|
|
296
|
+
span.failure({
|
|
297
|
+
statusCode: error.status,
|
|
298
|
+
error,
|
|
299
|
+
errorKind: "browser_bridge_service_error"
|
|
300
|
+
});
|
|
301
|
+
ctx.error(ctx.res, error.message, error.status);
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
logger.error(
|
|
305
|
+
{
|
|
306
|
+
boundary: "browser-bridge",
|
|
307
|
+
operation
|
|
308
|
+
},
|
|
309
|
+
`[browser-bridge] Route crashed: ${errorMessage(error)}`
|
|
310
|
+
);
|
|
311
|
+
span.failure({
|
|
312
|
+
error,
|
|
313
|
+
errorKind: "unhandled_error"
|
|
314
|
+
});
|
|
315
|
+
throw error;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
async function handleBrowserBridgeRoutes(ctx) {
|
|
319
|
+
const { req, res, method, pathname, json, readJsonBody } = ctx;
|
|
320
|
+
if (method === "GET" && pathname === "/api/browser-bridge/sessions") {
|
|
321
|
+
return runRoute(ctx, async (service) => {
|
|
322
|
+
json(res, {
|
|
323
|
+
sessions: await service.listBrowserSessions(ctx.state.adminEntityId)
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
if (method === "GET" && pathname === "/api/browser-bridge/settings") {
|
|
328
|
+
return runRoute(ctx, async (service) => {
|
|
329
|
+
json(res, {
|
|
330
|
+
settings: await service.getBrowserSettings(ctx.state.adminEntityId)
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
if (method === "POST" && pathname === "/api/browser-bridge/settings") {
|
|
335
|
+
const body = await readJsonBody(
|
|
336
|
+
req,
|
|
337
|
+
res
|
|
338
|
+
);
|
|
339
|
+
if (!body) return true;
|
|
340
|
+
if (!isBrowserBridgeRouteBodyObject(body)) {
|
|
341
|
+
return rejectMalformedBrowserBridgePayload(ctx);
|
|
342
|
+
}
|
|
343
|
+
return runRoute(ctx, async (service) => {
|
|
344
|
+
json(res, {
|
|
345
|
+
settings: await service.updateBrowserSettings(
|
|
346
|
+
body,
|
|
347
|
+
ctx.state.adminEntityId
|
|
348
|
+
)
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
if (method === "POST" && pathname === "/api/browser-bridge/companions/pair") {
|
|
353
|
+
const body = await readJsonBody(
|
|
354
|
+
req,
|
|
355
|
+
res
|
|
356
|
+
);
|
|
357
|
+
if (!body) return true;
|
|
358
|
+
if (!isBrowserBridgeRouteBodyObject(body)) {
|
|
359
|
+
return rejectMalformedBrowserBridgePayload(ctx);
|
|
360
|
+
}
|
|
361
|
+
return runRoute(ctx, async (service) => {
|
|
362
|
+
json(
|
|
363
|
+
res,
|
|
364
|
+
await service.createBrowserCompanionPairing(
|
|
365
|
+
body,
|
|
366
|
+
ctx.state.adminEntityId
|
|
367
|
+
),
|
|
368
|
+
201
|
|
369
|
+
);
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
if (method === "POST" && pathname === "/api/browser-bridge/companions/auto-pair") {
|
|
373
|
+
if (rateLimitRequest(ctx, "companions:auto-pair")) {
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
if (!browserAutoPairOriginAllowed(ctx)) {
|
|
377
|
+
ctx.error(
|
|
378
|
+
res,
|
|
379
|
+
"browser auto-pair must come from the agent app or a browser extension",
|
|
380
|
+
403
|
|
381
|
+
);
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
const body = await readJsonBody(req, res);
|
|
385
|
+
if (!body) return true;
|
|
386
|
+
if (!isBrowserBridgeRouteBodyObject(body)) {
|
|
387
|
+
return rejectMalformedBrowserBridgePayload(ctx);
|
|
388
|
+
}
|
|
389
|
+
return runRoute(ctx, async (service) => {
|
|
390
|
+
json(
|
|
391
|
+
res,
|
|
392
|
+
await service.autoPairBrowserCompanion(
|
|
393
|
+
body,
|
|
394
|
+
ctx.url.origin,
|
|
395
|
+
ctx.state.adminEntityId
|
|
396
|
+
),
|
|
397
|
+
201
|
|
398
|
+
);
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
if (method === "GET" && pathname === "/api/browser-bridge/companions") {
|
|
402
|
+
return runRoute(ctx, async (service) => {
|
|
403
|
+
json(res, {
|
|
404
|
+
companions: await service.listBrowserCompanions(
|
|
405
|
+
ctx.state.adminEntityId
|
|
406
|
+
)
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
if (method === "POST" && pathname === "/api/browser-bridge/companions/revoke") {
|
|
411
|
+
if (rateLimitRequest(ctx, "companions:revoke")) {
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
return runRoute(ctx, async (service) => {
|
|
415
|
+
const auth = getBrowserCompanionAuth(ctx);
|
|
416
|
+
if (!auth) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
json(
|
|
420
|
+
res,
|
|
421
|
+
await service.revokeBrowserCompanionFromCompanion(
|
|
422
|
+
auth.companionId,
|
|
423
|
+
auth.pairingToken,
|
|
424
|
+
ctx.state.adminEntityId
|
|
425
|
+
)
|
|
426
|
+
);
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
const browserCompanionRevokeMatch = pathname.match(
|
|
430
|
+
/^\/api\/browser-bridge\/companions\/([^/]+)\/revoke$/
|
|
431
|
+
);
|
|
432
|
+
if (method === "POST" && browserCompanionRevokeMatch) {
|
|
433
|
+
const companionId = decodeMatchedPathComponent(
|
|
434
|
+
ctx,
|
|
435
|
+
browserCompanionRevokeMatch,
|
|
436
|
+
1,
|
|
437
|
+
res,
|
|
438
|
+
"browser companion id"
|
|
439
|
+
);
|
|
440
|
+
if (!companionId) return true;
|
|
441
|
+
return runRoute(ctx, async (service) => {
|
|
442
|
+
json(
|
|
443
|
+
res,
|
|
444
|
+
await service.revokeBrowserCompanion(
|
|
445
|
+
companionId,
|
|
446
|
+
ctx.state.adminEntityId
|
|
447
|
+
)
|
|
448
|
+
);
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
if (method === "GET" && pathname === "/api/browser-bridge/packages") {
|
|
452
|
+
return runStatelessRoute(ctx, async () => {
|
|
453
|
+
json(res, { status: getBrowserBridgeCompanionPackageStatus() });
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
if (method === "POST" && pathname === "/api/browser-bridge/packages/open-path") {
|
|
457
|
+
if (!requestIsLoopback(ctx)) {
|
|
458
|
+
ctx.error(
|
|
459
|
+
res,
|
|
460
|
+
"Local extension install helpers can only run on the same machine as the agent",
|
|
461
|
+
403
|
|
462
|
+
);
|
|
463
|
+
return true;
|
|
464
|
+
}
|
|
465
|
+
const body = await readJsonBody(req, res);
|
|
466
|
+
if (!body) return true;
|
|
467
|
+
if (!isBrowserBridgeRouteBodyObject(body)) {
|
|
468
|
+
return rejectMalformedBrowserBridgePayload(ctx);
|
|
469
|
+
}
|
|
470
|
+
if (typeof body.target !== "string" || !BROWSER_BRIDGE_PACKAGE_PATH_TARGETS.includes(
|
|
471
|
+
body.target
|
|
472
|
+
)) {
|
|
473
|
+
ctx.error(
|
|
474
|
+
res,
|
|
475
|
+
`target must be one of: ${BROWSER_BRIDGE_PACKAGE_PATH_TARGETS.join(", ")}`,
|
|
476
|
+
400
|
|
477
|
+
);
|
|
478
|
+
return true;
|
|
479
|
+
}
|
|
480
|
+
const validatedTarget = body.target;
|
|
481
|
+
return runStatelessRoute(ctx, async () => {
|
|
482
|
+
json(
|
|
483
|
+
res,
|
|
484
|
+
await openBrowserBridgeCompanionPackagePath(validatedTarget, {
|
|
485
|
+
revealOnly: body.revealOnly === true
|
|
486
|
+
})
|
|
487
|
+
);
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
if (method === "POST" && pathname === "/api/browser-bridge/companions/sync") {
|
|
491
|
+
if (rateLimitRequest(ctx, "companions:sync")) {
|
|
492
|
+
return true;
|
|
493
|
+
}
|
|
494
|
+
return runRoute(ctx, async (service) => {
|
|
495
|
+
const auth = getBrowserCompanionAuth(ctx);
|
|
496
|
+
if (!auth) {
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
const body = await readJsonBody(req, res);
|
|
500
|
+
if (!body) return;
|
|
501
|
+
if (!isBrowserBridgeRouteBodyObject(body)) {
|
|
502
|
+
rejectMalformedBrowserBridgePayload(ctx);
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
json(
|
|
506
|
+
res,
|
|
507
|
+
await service.syncBrowserCompanion(
|
|
508
|
+
auth.companionId,
|
|
509
|
+
auth.pairingToken,
|
|
510
|
+
body,
|
|
511
|
+
ctx.state.adminEntityId
|
|
512
|
+
)
|
|
513
|
+
);
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
if (method === "GET" && pathname === "/api/browser-bridge/tabs") {
|
|
517
|
+
return runRoute(ctx, async (service) => {
|
|
518
|
+
json(res, {
|
|
519
|
+
tabs: await service.listBrowserTabs(ctx.state.adminEntityId)
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
const browserPackageBuildMatch = pathname.match(
|
|
524
|
+
/^\/api\/browser-bridge\/packages\/([^/]+)\/build$/
|
|
525
|
+
);
|
|
526
|
+
if (method === "POST" && browserPackageBuildMatch) {
|
|
527
|
+
const browser = decodeMatchedPathComponent(
|
|
528
|
+
ctx,
|
|
529
|
+
browserPackageBuildMatch,
|
|
530
|
+
1,
|
|
531
|
+
res,
|
|
532
|
+
"browser package target"
|
|
533
|
+
);
|
|
534
|
+
if (!browser) return true;
|
|
535
|
+
if (browser !== "chrome" && browser !== "safari") {
|
|
536
|
+
ctx.error(res, "browser must be chrome or safari", 400);
|
|
537
|
+
return true;
|
|
538
|
+
}
|
|
539
|
+
return runStatelessRoute(ctx, async () => {
|
|
540
|
+
json(res, {
|
|
541
|
+
status: await buildBrowserBridgeCompanionPackage(browser)
|
|
542
|
+
});
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
const browserPackageOpenManagerMatch = pathname.match(
|
|
546
|
+
/^\/api\/browser-bridge\/packages\/([^/]+)\/open-manager$/
|
|
547
|
+
);
|
|
548
|
+
if (method === "POST" && browserPackageOpenManagerMatch) {
|
|
549
|
+
if (!requestIsLoopback(ctx)) {
|
|
550
|
+
ctx.error(
|
|
551
|
+
res,
|
|
552
|
+
"Local extension install helpers can only run on the same machine as the agent",
|
|
553
|
+
403
|
|
554
|
+
);
|
|
555
|
+
return true;
|
|
556
|
+
}
|
|
557
|
+
const browser = decodeMatchedPathComponent(
|
|
558
|
+
ctx,
|
|
559
|
+
browserPackageOpenManagerMatch,
|
|
560
|
+
1,
|
|
561
|
+
res,
|
|
562
|
+
"browser package target"
|
|
563
|
+
);
|
|
564
|
+
if (!browser) return true;
|
|
565
|
+
if (browser !== "chrome" && browser !== "safari") {
|
|
566
|
+
ctx.error(res, "browser must be chrome or safari", 400);
|
|
567
|
+
return true;
|
|
568
|
+
}
|
|
569
|
+
return runStatelessRoute(ctx, async () => {
|
|
570
|
+
json(res, await openBrowserBridgeCompanionManager(browser));
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
const browserPackageDownloadMatch = pathname.match(
|
|
574
|
+
/^\/api\/browser-bridge\/packages\/([^/]+)\/download$/
|
|
575
|
+
);
|
|
576
|
+
if (method === "GET" && browserPackageDownloadMatch) {
|
|
577
|
+
const browser = decodeMatchedPathComponent(
|
|
578
|
+
ctx,
|
|
579
|
+
browserPackageDownloadMatch,
|
|
580
|
+
1,
|
|
581
|
+
res,
|
|
582
|
+
"browser package target"
|
|
583
|
+
);
|
|
584
|
+
if (!browser) return true;
|
|
585
|
+
if (browser !== "chrome" && browser !== "safari") {
|
|
586
|
+
ctx.error(res, "browser must be chrome or safari", 400);
|
|
587
|
+
return true;
|
|
588
|
+
}
|
|
589
|
+
return runStatelessRoute(ctx, async () => {
|
|
590
|
+
const artifact = getBrowserBridgeCompanionDownloadFile(browser);
|
|
591
|
+
res.statusCode = 200;
|
|
592
|
+
res.setHeader("Content-Type", artifact.contentType);
|
|
593
|
+
res.setHeader(
|
|
594
|
+
"Content-Disposition",
|
|
595
|
+
`attachment; filename="${artifact.filename}"`
|
|
596
|
+
);
|
|
597
|
+
await new Promise((resolve, reject) => {
|
|
598
|
+
const stream = fs.createReadStream(artifact.path);
|
|
599
|
+
stream.on("error", reject);
|
|
600
|
+
res.on("error", reject);
|
|
601
|
+
stream.on("end", resolve);
|
|
602
|
+
stream.pipe(res);
|
|
603
|
+
});
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
if (method === "GET" && pathname === "/api/browser-bridge/current-page") {
|
|
607
|
+
return runRoute(ctx, async (service) => {
|
|
608
|
+
json(res, {
|
|
609
|
+
page: await service.getCurrentBrowserPage(ctx.state.adminEntityId)
|
|
610
|
+
});
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
if (method === "POST" && pathname === "/api/browser-bridge/sync") {
|
|
614
|
+
const body = await readJsonBody(req, res);
|
|
615
|
+
if (!body) return true;
|
|
616
|
+
if (!isBrowserBridgeRouteBodyObject(body)) {
|
|
617
|
+
return rejectMalformedBrowserBridgePayload(ctx);
|
|
618
|
+
}
|
|
619
|
+
return runRoute(ctx, async (service) => {
|
|
620
|
+
json(res, await service.syncBrowserState(body, ctx.state.adminEntityId));
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
if (method === "POST" && pathname === "/api/browser-bridge/sessions") {
|
|
624
|
+
const body = await readJsonBody(
|
|
625
|
+
req,
|
|
626
|
+
res
|
|
627
|
+
);
|
|
628
|
+
if (!body) return true;
|
|
629
|
+
if (!isBrowserBridgeRouteBodyObject(body)) {
|
|
630
|
+
return rejectMalformedBrowserBridgePayload(ctx);
|
|
631
|
+
}
|
|
632
|
+
return runRoute(ctx, async (service) => {
|
|
633
|
+
json(
|
|
634
|
+
res,
|
|
635
|
+
{
|
|
636
|
+
session: await service.createBrowserSession(
|
|
637
|
+
body,
|
|
638
|
+
ctx.state.adminEntityId
|
|
639
|
+
)
|
|
640
|
+
},
|
|
641
|
+
201
|
|
642
|
+
);
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
const browserSessionMatch = pathname.match(
|
|
646
|
+
/^\/api\/browser-bridge\/sessions\/([^/]+)$/
|
|
647
|
+
);
|
|
648
|
+
if (browserSessionMatch) {
|
|
649
|
+
const sessionId = decodeMatchedPathComponent(
|
|
650
|
+
ctx,
|
|
651
|
+
browserSessionMatch,
|
|
652
|
+
1,
|
|
653
|
+
res,
|
|
654
|
+
"browser session id"
|
|
655
|
+
);
|
|
656
|
+
if (!sessionId) return true;
|
|
657
|
+
if (method === "GET") {
|
|
658
|
+
return runRoute(ctx, async (service) => {
|
|
659
|
+
json(res, {
|
|
660
|
+
session: await service.getBrowserSession(
|
|
661
|
+
sessionId,
|
|
662
|
+
ctx.state.adminEntityId
|
|
663
|
+
)
|
|
664
|
+
});
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
const browserConfirmMatch = pathname.match(
|
|
669
|
+
/^\/api\/browser-bridge\/sessions\/([^/]+)\/confirm$/
|
|
670
|
+
);
|
|
671
|
+
if (method === "POST" && browserConfirmMatch) {
|
|
672
|
+
const sessionId = decodeMatchedPathComponent(
|
|
673
|
+
ctx,
|
|
674
|
+
browserConfirmMatch,
|
|
675
|
+
1,
|
|
676
|
+
res,
|
|
677
|
+
"browser session id"
|
|
678
|
+
);
|
|
679
|
+
if (!sessionId) return true;
|
|
680
|
+
const body = await readJsonBody(
|
|
681
|
+
req,
|
|
682
|
+
res
|
|
683
|
+
);
|
|
684
|
+
if (!body) return true;
|
|
685
|
+
if (!isBrowserBridgeRouteBodyObject(body)) {
|
|
686
|
+
return rejectMalformedBrowserBridgePayload(ctx);
|
|
687
|
+
}
|
|
688
|
+
return runRoute(ctx, async (service) => {
|
|
689
|
+
json(res, {
|
|
690
|
+
session: await service.confirmBrowserSession(
|
|
691
|
+
sessionId,
|
|
692
|
+
body,
|
|
693
|
+
ctx.state.adminEntityId
|
|
694
|
+
)
|
|
695
|
+
});
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
const browserProgressMatch = pathname.match(
|
|
699
|
+
/^\/api\/browser-bridge\/sessions\/([^/]+)\/progress$/
|
|
700
|
+
);
|
|
701
|
+
if (method === "POST" && browserProgressMatch) {
|
|
702
|
+
const sessionId = decodeMatchedPathComponent(
|
|
703
|
+
ctx,
|
|
704
|
+
browserProgressMatch,
|
|
705
|
+
1,
|
|
706
|
+
res,
|
|
707
|
+
"browser session id"
|
|
708
|
+
);
|
|
709
|
+
if (!sessionId) return true;
|
|
710
|
+
const body = await readJsonBody(
|
|
711
|
+
req,
|
|
712
|
+
res
|
|
713
|
+
);
|
|
714
|
+
if (!body) return true;
|
|
715
|
+
if (!isBrowserBridgeRouteBodyObject(body)) {
|
|
716
|
+
return rejectMalformedBrowserBridgePayload(ctx);
|
|
717
|
+
}
|
|
718
|
+
return runRoute(ctx, async (service) => {
|
|
719
|
+
json(res, {
|
|
720
|
+
session: await service.updateBrowserSessionProgress(
|
|
721
|
+
sessionId,
|
|
722
|
+
body,
|
|
723
|
+
ctx.state.adminEntityId
|
|
724
|
+
)
|
|
725
|
+
});
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
const browserCompleteMatch = pathname.match(
|
|
729
|
+
/^\/api\/browser-bridge\/sessions\/([^/]+)\/complete$/
|
|
730
|
+
);
|
|
731
|
+
if (method === "POST" && browserCompleteMatch) {
|
|
732
|
+
const sessionId = decodeMatchedPathComponent(
|
|
733
|
+
ctx,
|
|
734
|
+
browserCompleteMatch,
|
|
735
|
+
1,
|
|
736
|
+
res,
|
|
737
|
+
"browser session id"
|
|
738
|
+
);
|
|
739
|
+
if (!sessionId) return true;
|
|
740
|
+
const body = await readJsonBody(
|
|
741
|
+
req,
|
|
742
|
+
res
|
|
743
|
+
);
|
|
744
|
+
if (!body) return true;
|
|
745
|
+
if (!isBrowserBridgeRouteBodyObject(body)) {
|
|
746
|
+
return rejectMalformedBrowserBridgePayload(ctx);
|
|
747
|
+
}
|
|
748
|
+
return runRoute(ctx, async (service) => {
|
|
749
|
+
json(res, {
|
|
750
|
+
session: await service.completeBrowserSession(
|
|
751
|
+
sessionId,
|
|
752
|
+
body,
|
|
753
|
+
ctx.state.adminEntityId
|
|
754
|
+
)
|
|
755
|
+
});
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
const browserCompanionProgressMatch = pathname.match(
|
|
759
|
+
/^\/api\/browser-bridge\/companions\/sessions\/([^/]+)\/progress$/
|
|
760
|
+
);
|
|
761
|
+
if (method === "POST" && browserCompanionProgressMatch) {
|
|
762
|
+
if (rateLimitRequest(ctx, "companions:session-progress")) {
|
|
763
|
+
return true;
|
|
764
|
+
}
|
|
765
|
+
const sessionId = decodeMatchedPathComponent(
|
|
766
|
+
ctx,
|
|
767
|
+
browserCompanionProgressMatch,
|
|
768
|
+
1,
|
|
769
|
+
res,
|
|
770
|
+
"browser session id"
|
|
771
|
+
);
|
|
772
|
+
if (!sessionId) return true;
|
|
773
|
+
return runRoute(ctx, async (service) => {
|
|
774
|
+
const auth = getBrowserCompanionAuth(ctx);
|
|
775
|
+
if (!auth) {
|
|
776
|
+
return;
|
|
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
|
+
}
|
|
787
|
+
json(res, {
|
|
788
|
+
session: await service.updateBrowserSessionProgressFromCompanion(
|
|
789
|
+
auth.companionId,
|
|
790
|
+
auth.pairingToken,
|
|
791
|
+
sessionId,
|
|
792
|
+
body,
|
|
793
|
+
ctx.state.adminEntityId
|
|
794
|
+
)
|
|
795
|
+
});
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
const browserCompanionCompleteMatch = pathname.match(
|
|
799
|
+
/^\/api\/browser-bridge\/companions\/sessions\/([^/]+)\/complete$/
|
|
800
|
+
);
|
|
801
|
+
if (method === "POST" && browserCompanionCompleteMatch) {
|
|
802
|
+
if (rateLimitRequest(ctx, "companions:session-complete")) {
|
|
803
|
+
return true;
|
|
804
|
+
}
|
|
805
|
+
const sessionId = decodeMatchedPathComponent(
|
|
806
|
+
ctx,
|
|
807
|
+
browserCompanionCompleteMatch,
|
|
808
|
+
1,
|
|
809
|
+
res,
|
|
810
|
+
"browser session id"
|
|
811
|
+
);
|
|
812
|
+
if (!sessionId) return true;
|
|
813
|
+
return runRoute(ctx, async (service) => {
|
|
814
|
+
const auth = getBrowserCompanionAuth(ctx);
|
|
815
|
+
if (!auth) {
|
|
816
|
+
return;
|
|
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
|
+
}
|
|
827
|
+
json(res, {
|
|
828
|
+
session: await service.completeBrowserSessionFromCompanion(
|
|
829
|
+
auth.companionId,
|
|
830
|
+
auth.pairingToken,
|
|
831
|
+
sessionId,
|
|
832
|
+
body,
|
|
833
|
+
ctx.state.adminEntityId
|
|
834
|
+
)
|
|
835
|
+
});
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
return false;
|
|
839
|
+
}
|
|
840
|
+
export {
|
|
841
|
+
handleBrowserBridgeRoutes,
|
|
842
|
+
rateLimitRequest
|
|
843
|
+
};
|
|
844
|
+
//# sourceMappingURL=bridge.js.map
|