@hydra-acp/browser 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +258 -0
- package/dist/config.js +101 -0
- package/dist/config.js.map +1 -0
- package/dist/hydra/client.js +61 -0
- package/dist/hydra/client.js.map +1 -0
- package/dist/hydra/ws.js +178 -0
- package/dist/hydra/ws.js.map +1 -0
- package/dist/index.js +99 -0
- package/dist/index.js.map +1 -0
- package/dist/server/auth.js +104 -0
- package/dist/server/auth.js.map +1 -0
- package/dist/server/http.js +128 -0
- package/dist/server/http.js.map +1 -0
- package/dist/server/routes-agents.js +14 -0
- package/dist/server/routes-agents.js.map +1 -0
- package/dist/server/routes-files.js +170 -0
- package/dist/server/routes-files.js.map +1 -0
- package/dist/server/routes-root.js +67 -0
- package/dist/server/routes-root.js.map +1 -0
- package/dist/server/routes-sessions.js +104 -0
- package/dist/server/routes-sessions.js.map +1 -0
- package/dist/server/title-cache.js +59 -0
- package/dist/server/title-cache.js.map +1 -0
- package/dist/server/ws-bridge.js +261 -0
- package/dist/server/ws-bridge.js.map +1 -0
- package/dist/ui/index.html +2151 -0
- package/dist/util/csrf.js +57 -0
- package/dist/util/csrf.js.map +1 -0
- package/dist/util/log.js +46 -0
- package/dist/util/log.js.map +1 -0
- package/dist/util/paths.js +24 -0
- package/dist/util/paths.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { HydraRestError } from "../hydra/client.js";
|
|
2
|
+
import { UpstreamConnection, runInitialize } from "../hydra/ws.js";
|
|
3
|
+
import { logger } from "../util/log.js";
|
|
4
|
+
const log = logger("routes-sessions");
|
|
5
|
+
export function registerSessionRoutes(app, ctx) {
|
|
6
|
+
app.get("/api/health", { config: { skipAuth: true } }, async (_request, reply) => {
|
|
7
|
+
try {
|
|
8
|
+
const upstream = await ctx.rest.health();
|
|
9
|
+
reply.send({ status: "ok", upstream });
|
|
10
|
+
}
|
|
11
|
+
catch (err) {
|
|
12
|
+
reply
|
|
13
|
+
.code(502)
|
|
14
|
+
.send({ status: "degraded", error: err.message });
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
app.get("/api/sessions", async (request, reply) => {
|
|
18
|
+
const query = request.query;
|
|
19
|
+
const all = query?.all === "true" || query?.all === "1";
|
|
20
|
+
try {
|
|
21
|
+
const result = await ctx.rest.listSessions({ cwd: query?.cwd, all });
|
|
22
|
+
reply.send(result);
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
const status = err instanceof HydraRestError ? err.status : 502;
|
|
26
|
+
reply.code(status).send({ error: err.message });
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
app.post("/api/kill", async (request, reply) => {
|
|
30
|
+
const body = (request.body ?? {});
|
|
31
|
+
if (!body.sessionId) {
|
|
32
|
+
reply.code(400).send({ error: "sessionId required" });
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
await ctx.rest.deleteSession(body.sessionId);
|
|
37
|
+
reply.code(204).send();
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
const status = err instanceof HydraRestError ? err.status : 502;
|
|
41
|
+
reply.code(status).send({ error: err.message });
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
app.post("/api/sessions", async (request, reply) => {
|
|
45
|
+
const body = (request.body ?? {});
|
|
46
|
+
if (!body.cwd || typeof body.cwd !== "string") {
|
|
47
|
+
reply.code(400).send({ error: "cwd required" });
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const result = await createSession(ctx, body);
|
|
52
|
+
reply.code(201).send(result);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
log.warn(`session creation failed: ${err.message}`);
|
|
56
|
+
reply.code(502).send({ error: err.message });
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
// Open a transient WSS to hydra, initialize, session/new (with name in
|
|
61
|
+
// _meta), optionally session/prompt, close. Returns the new sessionId.
|
|
62
|
+
async function createSession(ctx, body) {
|
|
63
|
+
const conn = new UpstreamConnection({
|
|
64
|
+
daemonWsUrl: ctx.config.hydraWsUrl,
|
|
65
|
+
token: ctx.config.hydraToken,
|
|
66
|
+
clientName: "hydra-acp-browser-session",
|
|
67
|
+
});
|
|
68
|
+
const opened = new Promise((resolveOpen, rejectOpen) => {
|
|
69
|
+
conn.once("open", () => resolveOpen());
|
|
70
|
+
conn.once("error", (err) => rejectOpen(err));
|
|
71
|
+
conn.once("close", () => rejectOpen(new Error("upstream closed")));
|
|
72
|
+
});
|
|
73
|
+
conn.start();
|
|
74
|
+
await opened;
|
|
75
|
+
try {
|
|
76
|
+
await runInitialize(conn);
|
|
77
|
+
const newParams = {
|
|
78
|
+
cwd: body.cwd,
|
|
79
|
+
mcpServers: [],
|
|
80
|
+
};
|
|
81
|
+
if (body.agentId) {
|
|
82
|
+
newParams.agentId = body.agentId;
|
|
83
|
+
}
|
|
84
|
+
if (body.name) {
|
|
85
|
+
newParams._meta = { "hydra-acp": { name: body.name } };
|
|
86
|
+
}
|
|
87
|
+
const newResult = (await conn.request("session/new", newParams));
|
|
88
|
+
if (body.prompt && body.prompt.trim().length > 0) {
|
|
89
|
+
await conn.request("session/prompt", {
|
|
90
|
+
sessionId: newResult.sessionId,
|
|
91
|
+
prompt: [{ type: "text", text: body.prompt }],
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
sessionId: newResult.sessionId,
|
|
96
|
+
agentId: body.agentId,
|
|
97
|
+
cwd: body.cwd,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
conn.stop();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=routes-sessions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes-sessions.js","sourceRoot":"","sources":["../../src/server/routes-sessions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Per-session title fallback cache. Hydra only stores the title that
|
|
2
|
+
// was passed at session/new (typically the editor's frame name, or
|
|
3
|
+
// nothing for sessions spawned through the browser). When a tab sends
|
|
4
|
+
// its first session/prompt frame, we seed an entry here from the prompt
|
|
5
|
+
// text — the routes-sessions handler folds this into /api/sessions
|
|
6
|
+
// responses for any session whose hydra-side title is empty, so the
|
|
7
|
+
// list view and chat topbar show something more informative than the
|
|
8
|
+
// raw sessionId. Mirrors what acp-hydra-slack does in the slack thread
|
|
9
|
+
// header.
|
|
10
|
+
//
|
|
11
|
+
// Lives in process memory only. A daemon restart loses it; the next
|
|
12
|
+
// prompt re-seeds.
|
|
13
|
+
const seededTitles = new Map();
|
|
14
|
+
export function seedSessionTitle(sessionId, text) {
|
|
15
|
+
if (seededTitles.has(sessionId)) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const seed = firstLine(text, 100);
|
|
19
|
+
if (!seed) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
seededTitles.set(sessionId, seed);
|
|
23
|
+
}
|
|
24
|
+
export function getSeededTitle(sessionId) {
|
|
25
|
+
return seededTitles.get(sessionId);
|
|
26
|
+
}
|
|
27
|
+
// First non-empty line of `text`, truncated to `max` chars with a
|
|
28
|
+
// trailing ellipsis if needed. Returns undefined if `text` is all
|
|
29
|
+
// whitespace.
|
|
30
|
+
function firstLine(text, max) {
|
|
31
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
32
|
+
const line = raw.trim();
|
|
33
|
+
if (!line) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
return line.length > max ? `${line.slice(0, max)}…` : line;
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
// Pull text out of an ACP session/prompt request's `prompt` field, which
|
|
41
|
+
// is an array of content blocks like `{type: "text", text: "..."}`.
|
|
42
|
+
// Concatenates all text blocks; ignores image/other blocks.
|
|
43
|
+
export function extractPromptText(params) {
|
|
44
|
+
if (!params || typeof params !== "object") {
|
|
45
|
+
return "";
|
|
46
|
+
}
|
|
47
|
+
const blocks = params.prompt;
|
|
48
|
+
if (!Array.isArray(blocks)) {
|
|
49
|
+
return typeof blocks === "string" ? blocks : "";
|
|
50
|
+
}
|
|
51
|
+
let out = "";
|
|
52
|
+
for (const b of blocks) {
|
|
53
|
+
if (b && typeof b === "object" && typeof b.text === "string") {
|
|
54
|
+
out += b.text;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=title-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"title-cache.js","sourceRoot":"","sources":["../../src/server/title-cache.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,mEAAmE;AACnE,sEAAsE;AACtE,wEAAwE;AACxE,mEAAmE;AACnE,oEAAoE;AACpE,qEAAqE;AACrE,uEAAuE;AACvE,UAAU;AACV,EAAE;AACF,oEAAoE;AACpE,mBAAmB;AAEnB,MAAM,YAAY,GAAG,IAAI,GAAG,EAAkB,CAAC;AAE/C,MAAM,UAAU,gBAAgB,CAAC,SAAiB,EAAE,IAAY;IAC9D,IAAI,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;QAChC,OAAO;IACT,CAAC;IACD,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAClC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO;IACT,CAAC;IACD,YAAY,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,SAAiB;IAC9C,OAAO,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;AACrC,CAAC;AAED,kEAAkE;AAClE,kEAAkE;AAClE,cAAc;AACd,SAAS,SAAS,CAAC,IAAY,EAAE,GAAW;IAC1C,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QACxB,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,SAAS;QACX,CAAC;QACD,OAAO,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7D,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,yEAAyE;AACzE,oEAAoE;AACpE,4DAA4D;AAC5D,MAAM,UAAU,iBAAiB,CAAC,MAAe;IAC/C,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC1C,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,MAAM,GAAI,MAA+B,CAAC,MAAM,CAAC;IACvD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3B,OAAO,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IAClD,CAAC;IACD,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,IAAI,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAQ,CAAwB,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACrF,GAAG,IAAK,CAAsB,CAAC,IAAI,CAAC;QACtC,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { WebSocket, WebSocketServer } from "ws";
|
|
2
|
+
import { logger } from "../util/log.js";
|
|
3
|
+
import { UpstreamConnection, isNotification, isRequest, isResponse, runInitialize, } from "../hydra/ws.js";
|
|
4
|
+
import { COOKIE_NAME, constantTimeKeyMatch, parseCookies, } from "./auth.js";
|
|
5
|
+
import { checkStateChanging } from "../util/csrf.js";
|
|
6
|
+
const log = logger("ws-bridge");
|
|
7
|
+
const ALLOWED_BROWSER_REQUEST_METHODS = new Set([
|
|
8
|
+
"session/prompt",
|
|
9
|
+
// session/cancel is kept here for backward compat with older browser
|
|
10
|
+
// builds that frame it as a request. New builds send the notification
|
|
11
|
+
// form (see ALLOWED_BROWSER_NOTIFICATION_METHODS) per the ACP spec.
|
|
12
|
+
"session/cancel",
|
|
13
|
+
"session/set_mode",
|
|
14
|
+
"session/set_model",
|
|
15
|
+
]);
|
|
16
|
+
const ALLOWED_BROWSER_NOTIFICATION_METHODS = new Set([
|
|
17
|
+
"session/cancel",
|
|
18
|
+
]);
|
|
19
|
+
const SHORT_CIRCUIT_AGENT_REQUEST_METHODS = new Set([
|
|
20
|
+
"fs/read_text_file",
|
|
21
|
+
"fs/write_text_file",
|
|
22
|
+
]);
|
|
23
|
+
export function attachWsBridge(httpServer, ctx) {
|
|
24
|
+
const wss = new WebSocketServer({ noServer: true });
|
|
25
|
+
httpServer.on("upgrade", (request, socket, head) => {
|
|
26
|
+
const url = new URL(request.url ?? "/", "http://placeholder");
|
|
27
|
+
if (url.pathname !== "/ws") {
|
|
28
|
+
socket.destroy();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const ip = (request.socket.remoteAddress ?? "unknown").toString();
|
|
32
|
+
if (ctx.rateLimiter.isBlocked(ip)) {
|
|
33
|
+
socket.write("HTTP/1.1 429 Too Many Requests\r\n\r\n");
|
|
34
|
+
socket.destroy();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const csrf = checkStateChanging(ctx.security, request.headers);
|
|
38
|
+
if (!csrf.ok) {
|
|
39
|
+
socket.write(`HTTP/1.1 ${csrf.status} ${csrf.reason}\r\n\r\n`);
|
|
40
|
+
socket.destroy();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const cookies = parseCookies(request.headers.cookie);
|
|
44
|
+
const provided = cookies.get(COOKIE_NAME);
|
|
45
|
+
if (!provided || !constantTimeKeyMatch(provided, ctx.authkey)) {
|
|
46
|
+
ctx.rateLimiter.recordFailure(ip);
|
|
47
|
+
socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
|
|
48
|
+
socket.destroy();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
ctx.rateLimiter.recordSuccess(ip);
|
|
52
|
+
const sessionId = url.searchParams.get("session");
|
|
53
|
+
const load = url.searchParams.get("load") === "true";
|
|
54
|
+
if (!sessionId) {
|
|
55
|
+
socket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
|
|
56
|
+
socket.destroy();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
wss.handleUpgrade(request, socket, head, (browserWs) => {
|
|
60
|
+
handleConnection(browserWs, request, ctx, sessionId, load);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
return wss;
|
|
64
|
+
}
|
|
65
|
+
function handleConnection(browserWs, _req, ctx, sessionId, load) {
|
|
66
|
+
log.info(`bridge open session=${sessionId} load=${load}`);
|
|
67
|
+
const upstream = new UpstreamConnection({
|
|
68
|
+
daemonWsUrl: ctx.config.hydraWsUrl,
|
|
69
|
+
token: ctx.config.hydraToken,
|
|
70
|
+
});
|
|
71
|
+
// Track ids of outstanding upstream→browser requests so we can validate
|
|
72
|
+
// browser-supplied responses (and reject responses for unknown ids,
|
|
73
|
+
// which would otherwise be a path for a compromised tab to spoof
|
|
74
|
+
// permission outcomes).
|
|
75
|
+
const outstandingFromUpstream = new Set();
|
|
76
|
+
let upstreamReady = false;
|
|
77
|
+
// While the upstream handshake is running we can't yet forward browser
|
|
78
|
+
// frames. Buffer them and flush once attach completes.
|
|
79
|
+
const browserBuffer = [];
|
|
80
|
+
upstream.on("open", () => {
|
|
81
|
+
void doHandshake().catch((err) => {
|
|
82
|
+
log.warn(`handshake failed for ${sessionId}: ${err.message}`);
|
|
83
|
+
sendBrowserError("handshake_failed", err.message);
|
|
84
|
+
cleanup();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
upstream.on("notification", (n) => {
|
|
88
|
+
sendBrowserFrame(n);
|
|
89
|
+
});
|
|
90
|
+
upstream.on("request", (r) => {
|
|
91
|
+
if (SHORT_CIRCUIT_AGENT_REQUEST_METHODS.has(r.method)) {
|
|
92
|
+
// We advertised fs/* off in initialize; if an agent still asks,
|
|
93
|
+
// refuse rather than expose the user's filesystem to it.
|
|
94
|
+
upstream.replyError(r.id, -32601, `method not supported: ${r.method}`);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
outstandingFromUpstream.add(String(r.id));
|
|
98
|
+
sendBrowserFrame(r);
|
|
99
|
+
});
|
|
100
|
+
upstream.on("response", (r) => {
|
|
101
|
+
sendBrowserFrame(r);
|
|
102
|
+
});
|
|
103
|
+
upstream.on("close", ({ code, reason }) => {
|
|
104
|
+
log.info(`upstream closed session=${sessionId} code=${code} reason=${reason}`);
|
|
105
|
+
try {
|
|
106
|
+
browserWs.close();
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
void 0;
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
upstream.on("error", (err) => {
|
|
113
|
+
log.warn(`upstream error session=${sessionId}: ${err.message}`);
|
|
114
|
+
});
|
|
115
|
+
browserWs.on("message", (data, isBinary) => {
|
|
116
|
+
if (isBinary) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
let parsed;
|
|
120
|
+
try {
|
|
121
|
+
parsed = JSON.parse(data.toString("utf8"));
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
log.warn(`browser parse error: ${err.message}`);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
handleBrowserFrame(parsed);
|
|
128
|
+
});
|
|
129
|
+
browserWs.on("close", () => {
|
|
130
|
+
log.info(`browser closed session=${sessionId}`);
|
|
131
|
+
cleanup();
|
|
132
|
+
});
|
|
133
|
+
browserWs.on("error", (err) => {
|
|
134
|
+
log.warn(`browser error session=${sessionId}: ${err.message}`);
|
|
135
|
+
});
|
|
136
|
+
upstream.start();
|
|
137
|
+
function handleBrowserFrame(msg) {
|
|
138
|
+
if (!upstreamReady) {
|
|
139
|
+
browserBuffer.push(msg);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
forwardBrowserFrame(msg);
|
|
143
|
+
}
|
|
144
|
+
function forwardBrowserFrame(msg) {
|
|
145
|
+
if (isRequest(msg)) {
|
|
146
|
+
if (!ALLOWED_BROWSER_REQUEST_METHODS.has(msg.method)) {
|
|
147
|
+
log.warn(`reject browser request method=${msg.method}`);
|
|
148
|
+
sendBrowserResponseError(msg.id, -32601, `method not allowed: ${msg.method}`);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
// Coerce sessionId in params to the URL-bound session so a
|
|
152
|
+
// compromised tab can't redirect prompts at sessions it doesn't
|
|
153
|
+
// hold the WS for.
|
|
154
|
+
const params = msg.params && typeof msg.params === "object"
|
|
155
|
+
? { ...msg.params, sessionId }
|
|
156
|
+
: { sessionId };
|
|
157
|
+
upstream.sendRaw({
|
|
158
|
+
jsonrpc: "2.0",
|
|
159
|
+
id: msg.id,
|
|
160
|
+
method: msg.method,
|
|
161
|
+
params,
|
|
162
|
+
});
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (isResponse(msg)) {
|
|
166
|
+
const idStr = String(msg.id);
|
|
167
|
+
if (!outstandingFromUpstream.has(idStr)) {
|
|
168
|
+
log.warn(`reject browser response for unknown id=${idStr}`);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
outstandingFromUpstream.delete(idStr);
|
|
172
|
+
upstream.sendRaw(msg);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (isNotification(msg)) {
|
|
176
|
+
if (!ALLOWED_BROWSER_NOTIFICATION_METHODS.has(msg.method)) {
|
|
177
|
+
log.debug(`ignore browser notification method=${msg.method}`);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
// Same sessionId coercion as the request branch: a compromised tab
|
|
181
|
+
// shouldn't be able to send notifications targeting other sessions.
|
|
182
|
+
const params = msg.params && typeof msg.params === "object"
|
|
183
|
+
? { ...msg.params, sessionId }
|
|
184
|
+
: { sessionId };
|
|
185
|
+
upstream.sendRaw({
|
|
186
|
+
jsonrpc: "2.0",
|
|
187
|
+
method: msg.method,
|
|
188
|
+
params,
|
|
189
|
+
});
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function sendBrowserFrame(msg) {
|
|
194
|
+
if (browserWs.readyState !== WebSocket.OPEN) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
browserWs.send(JSON.stringify(msg));
|
|
198
|
+
}
|
|
199
|
+
function sendBrowserError(code, message) {
|
|
200
|
+
sendBrowserFrame({
|
|
201
|
+
jsonrpc: "2.0",
|
|
202
|
+
method: "bridge/error",
|
|
203
|
+
params: { code, message },
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
function sendBrowserResponseError(id, code, message) {
|
|
207
|
+
const resp = {
|
|
208
|
+
jsonrpc: "2.0",
|
|
209
|
+
id,
|
|
210
|
+
error: { code, message },
|
|
211
|
+
};
|
|
212
|
+
sendBrowserFrame(resp);
|
|
213
|
+
}
|
|
214
|
+
async function doHandshake() {
|
|
215
|
+
await runInitialize(upstream);
|
|
216
|
+
if (load) {
|
|
217
|
+
try {
|
|
218
|
+
await upstream.request("session/load", { sessionId });
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
log.warn(`session/load failed for ${sessionId}: ${err.message} — will still attempt attach`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
await upstream.request("session/attach", {
|
|
225
|
+
sessionId,
|
|
226
|
+
historyPolicy: "full",
|
|
227
|
+
clientInfo: {
|
|
228
|
+
name: upstream.clientName,
|
|
229
|
+
version: upstream.clientVersion,
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
sendBrowserFrame({
|
|
233
|
+
jsonrpc: "2.0",
|
|
234
|
+
method: "bridge/ready",
|
|
235
|
+
params: { sessionId },
|
|
236
|
+
});
|
|
237
|
+
upstreamReady = true;
|
|
238
|
+
while (browserBuffer.length > 0) {
|
|
239
|
+
const next = browserBuffer.shift();
|
|
240
|
+
forwardBrowserFrame(next);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function cleanup() {
|
|
244
|
+
upstream.stop();
|
|
245
|
+
if (browserWs.readyState === WebSocket.OPEN) {
|
|
246
|
+
try {
|
|
247
|
+
browserWs.close();
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
void 0;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// Exported for tests.
|
|
256
|
+
export const _internal = {
|
|
257
|
+
ALLOWED_BROWSER_REQUEST_METHODS,
|
|
258
|
+
ALLOWED_BROWSER_NOTIFICATION_METHODS,
|
|
259
|
+
SHORT_CIRCUIT_AGENT_REQUEST_METHODS,
|
|
260
|
+
};
|
|
261
|
+
//# sourceMappingURL=ws-bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws-bridge.js","sourceRoot":"","sources":["../../src/server/ws-bridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,IAAI,CAAC;AAEhD,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,SAAS,EACT,UAAU,EACV,aAAa,GAKd,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,YAAY,GACb,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAGrD,MAAM,GAAG,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;AAEhC,MAAM,+BAA+B,GAAG,IAAI,GAAG,CAAS;IACtD,gBAAgB;IAChB,qEAAqE;IACrE,sEAAsE;IACtE,oEAAoE;IACpE,gBAAgB;IAChB,kBAAkB;IAClB,mBAAmB;CACpB,CAAC,CAAC;AAEH,MAAM,oCAAoC,GAAG,IAAI,GAAG,CAAS;IAC3D,gBAAgB;CACjB,CAAC,CAAC;AAEH,MAAM,mCAAmC,GAAG,IAAI,GAAG,CAAS;IAC1D,mBAAmB;IACnB,oBAAoB;CACrB,CAAC,CAAC;AAEH,MAAM,UAAU,cAAc,CAC5B,UAAsB,EACtB,GAAkB;IAElB,MAAM,GAAG,GAAG,IAAI,eAAe,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAEpD,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE;QACjD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,IAAI,GAAG,EAAE,oBAAoB,CAAC,CAAC;QAC9D,IAAI,GAAG,CAAC,QAAQ,KAAK,KAAK,EAAE,CAAC;YAC3B,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO;QACT,CAAC;QACD,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,aAAa,IAAI,SAAS,CAAC,CAAC,QAAQ,EAAE,CAAC;QAClE,IAAI,GAAG,CAAC,WAAW,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC;YAClC,MAAM,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;YACvD,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO;QACT,CAAC;QACD,MAAM,IAAI,GAAG,kBAAkB,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;QAC/D,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;YACb,MAAM,CAAC,KAAK,CAAC,YAAY,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,UAAU,CAAC,CAAC;YAC/D,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO;QACT,CAAC;QACD,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACrD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QAC1C,IAAI,CAAC,QAAQ,IAAI,CAAC,oBAAoB,CAAC,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YAC9D,GAAG,CAAC,WAAW,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;YAClC,MAAM,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;YAClD,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO;QACT,CAAC;QACD,GAAG,CAAC,WAAW,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;QAClC,MAAM,SAAS,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAClD,MAAM,IAAI,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,MAAM,CAAC;QACrD,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;YACjD,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO;QACT,CAAC;QACD,GAAG,CAAC,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,SAAS,EAAE,EAAE;YACrD,gBAAgB,CAAC,SAAS,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;QAC7D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,gBAAgB,CACvB,SAAoB,EACpB,IAAqB,EACrB,GAAkB,EAClB,SAAiB,EACjB,IAAa;IAEb,GAAG,CAAC,IAAI,CAAC,uBAAuB,SAAS,SAAS,IAAI,EAAE,CAAC,CAAC;IAE1D,MAAM,QAAQ,GAAG,IAAI,kBAAkB,CAAC;QACtC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,UAAU;QAClC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,UAAU;KAC7B,CAAC,CAAC;IAEH,wEAAwE;IACxE,oEAAoE;IACpE,iEAAiE;IACjE,wBAAwB;IACxB,MAAM,uBAAuB,GAAG,IAAI,GAAG,EAAU,CAAC;IAElD,IAAI,aAAa,GAAG,KAAK,CAAC;IAC1B,uEAAuE;IACvE,uDAAuD;IACvD,MAAM,aAAa,GAAqB,EAAE,CAAC;IAE3C,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;QACvB,KAAK,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;YACxC,GAAG,CAAC,IAAI,CACN,wBAAwB,SAAS,KAAM,GAAa,CAAC,OAAO,EAAE,CAC/D,CAAC;YACF,gBAAgB,CAAC,kBAAkB,EAAG,GAAa,CAAC,OAAO,CAAC,CAAC;YAC7D,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,EAAE,CAAC,cAAc,EAAE,CAAC,CAAC,EAAE,EAAE;QAChC,gBAAgB,CAAC,CAAC,CAAC,CAAC;IACtB,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE;QAC3B,IAAI,mCAAmC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;YACtD,gEAAgE;YAChE,yDAAyD;YACzD,QAAQ,CAAC,UAAU,CACjB,CAAC,CAAC,EAAE,EACJ,CAAC,KAAK,EACN,yBAAyB,CAAC,CAAC,MAAM,EAAE,CACpC,CAAC;YACF,OAAO;QACT,CAAC;QACD,uBAAuB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC1C,gBAAgB,CAAC,CAAC,CAAC,CAAC;IACtB,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE;QAC5B,gBAAgB,CAAC,CAAC,CAAC,CAAC;IACtB,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE;QACxC,GAAG,CAAC,IAAI,CAAC,2BAA2B,SAAS,SAAS,IAAI,WAAW,MAAM,EAAE,CAAC,CAAC;QAC/E,IAAI,CAAC;YACH,SAAS,CAAC,KAAK,EAAE,CAAC;QACpB,CAAC;QAAC,MAAM,CAAC;YACP,KAAK,CAAC,CAAC;QACT,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;QAC3B,GAAG,CAAC,IAAI,CAAC,0BAA0B,SAAS,KAAK,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE;QACzC,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO;QACT,CAAC;QACD,IAAI,MAAsB,CAAC;QAC3B,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAmB,CAAC;QAC/D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,IAAI,CAAC,wBAAyB,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;YAC3D,OAAO;QACT,CAAC;QACD,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;QACzB,GAAG,CAAC,IAAI,CAAC,0BAA0B,SAAS,EAAE,CAAC,CAAC;QAChD,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;QAC5B,GAAG,CAAC,IAAI,CAAC,yBAAyB,SAAS,KAAK,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,KAAK,EAAE,CAAC;IAEjB,SAAS,kBAAkB,CAAC,GAAmB;QAC7C,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACxB,OAAO;QACT,CAAC;QACD,mBAAmB,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAED,SAAS,mBAAmB,CAAC,GAAmB;QAC9C,IAAI,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;YACnB,IAAI,CAAC,+BAA+B,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;gBACrD,GAAG,CAAC,IAAI,CAAC,iCAAiC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;gBACxD,wBAAwB,CACtB,GAAG,CAAC,EAAE,EACN,CAAC,KAAK,EACN,uBAAuB,GAAG,CAAC,MAAM,EAAE,CACpC,CAAC;gBACF,OAAO;YACT,CAAC;YACD,2DAA2D;YAC3D,gEAAgE;YAChE,mBAAmB;YACnB,MAAM,MAAM,GACV,GAAG,CAAC,MAAM,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ;gBAC1C,CAAC,CAAC,EAAE,GAAI,GAAG,CAAC,MAAkC,EAAE,SAAS,EAAE;gBAC3D,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC;YACpB,QAAQ,CAAC,OAAO,CAAC;gBACf,OAAO,EAAE,KAAK;gBACd,EAAE,EAAE,GAAG,CAAC,EAAE;gBACV,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,MAAM;aACP,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QACD,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC7B,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;gBACxC,GAAG,CAAC,IAAI,CAAC,0CAA0C,KAAK,EAAE,CAAC,CAAC;gBAC5D,OAAO;YACT,CAAC;YACD,uBAAuB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACtC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACtB,OAAO;QACT,CAAC;QACD,IAAI,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,IAAI,CAAC,oCAAoC,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC1D,GAAG,CAAC,KAAK,CAAC,sCAAsC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;gBAC9D,OAAO;YACT,CAAC;YACD,mEAAmE;YACnE,oEAAoE;YACpE,MAAM,MAAM,GACV,GAAG,CAAC,MAAM,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ;gBAC1C,CAAC,CAAC,EAAE,GAAI,GAAG,CAAC,MAAkC,EAAE,SAAS,EAAE;gBAC3D,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC;YACpB,QAAQ,CAAC,OAAO,CAAC;gBACf,OAAO,EAAE,KAAK;gBACd,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,MAAM;aACP,CAAC,CAAC;YACH,OAAO;QACT,CAAC;IACH,CAAC;IAED,SAAS,gBAAgB,CAAC,GAAmB;QAC3C,IAAI,SAAS,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;YAC5C,OAAO;QACT,CAAC;QACD,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IACtC,CAAC;IAED,SAAS,gBAAgB,CAAC,IAAY,EAAE,OAAe;QACrD,gBAAgB,CAAC;YACf,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,cAAc;YACtB,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;SAC1B,CAAC,CAAC;IACL,CAAC;IAED,SAAS,wBAAwB,CAC/B,EAAa,EACb,IAAY,EACZ,OAAe;QAEf,MAAM,IAAI,GAAoB;YAC5B,OAAO,EAAE,KAAK;YACd,EAAE;YACF,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;SACzB,CAAC;QACF,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IAED,KAAK,UAAU,WAAW;QACxB,MAAM,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC9B,IAAI,IAAI,EAAE,CAAC;YACT,IAAI,CAAC;gBACH,MAAM,QAAQ,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;YACxD,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,GAAG,CAAC,IAAI,CACN,2BAA2B,SAAS,KAAM,GAAa,CAAC,OAAO,8BAA8B,CAC9F,CAAC;YACJ,CAAC;QACH,CAAC;QACD,MAAM,QAAQ,CAAC,OAAO,CAAC,gBAAgB,EAAE;YACvC,SAAS;YACT,aAAa,EAAE,MAAM;YACrB,UAAU,EAAE;gBACV,IAAI,EAAE,QAAQ,CAAC,UAAU;gBACzB,OAAO,EAAE,QAAQ,CAAC,aAAa;aAChC;SACF,CAAC,CAAC;QACH,gBAAgB,CAAC;YACf,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,cAAc;YACtB,MAAM,EAAE,EAAE,SAAS,EAAE;SACtB,CAAC,CAAC;QACH,aAAa,GAAG,IAAI,CAAC;QACrB,OAAO,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChC,MAAM,IAAI,GAAG,aAAa,CAAC,KAAK,EAAG,CAAC;YACpC,mBAAmB,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;IAED,SAAS,OAAO;QACd,QAAQ,CAAC,IAAI,EAAE,CAAC;QAChB,IAAI,SAAS,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;YAC5C,IAAI,CAAC;gBACH,SAAS,CAAC,KAAK,EAAE,CAAC;YACpB,CAAC;YAAC,MAAM,CAAC;gBACP,KAAK,CAAC,CAAC;YACT,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED,sBAAsB;AACtB,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,+BAA+B;IAC/B,oCAAoC;IACpC,mCAAmC;CACpC,CAAC"}
|