@hasna/browser 0.4.5 → 0.4.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 +1 -1
- package/README.md +14 -1
- package/dist/cli/commands/tools.d.ts.map +1 -1
- package/dist/cli/index.js +1262 -773
- package/dist/db/gallery.d.ts.map +1 -1
- package/dist/engines/tui.d.ts +62 -22
- package/dist/engines/tui.d.ts.map +1 -1
- package/dist/index.js +345 -100
- package/dist/lib/actions.d.ts +2 -1
- package/dist/lib/actions.d.ts.map +1 -1
- package/dist/lib/auth.d.ts.map +1 -1
- package/dist/lib/session.d.ts +4 -0
- package/dist/lib/session.d.ts.map +1 -1
- package/dist/lib/stealth.d.ts.map +1 -1
- package/dist/mcp/actions.d.ts.map +1 -1
- package/dist/mcp/agents.d.ts +3 -0
- package/dist/mcp/agents.d.ts.map +1 -0
- package/dist/mcp/gallery.d.ts +3 -0
- package/dist/mcp/gallery.d.ts.map +1 -0
- package/dist/mcp/index.js +914 -526
- package/dist/mcp/integration.d.ts +3 -0
- package/dist/mcp/integration.d.ts.map +1 -0
- package/dist/mcp/meta-regression.test.d.ts +2 -0
- package/dist/mcp/meta-regression.test.d.ts.map +1 -0
- package/dist/mcp/meta.d.ts.map +1 -1
- package/dist/mcp/sessions.d.ts.map +1 -1
- package/dist/mcp/tui.d.ts.map +1 -1
- package/dist/server/index.js +460 -145
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -3,7 +3,6 @@ var __create = Object.create;
|
|
|
3
3
|
var __getProtoOf = Object.getPrototypeOf;
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
7
|
function __accessProp(key) {
|
|
9
8
|
return this[key];
|
|
@@ -30,23 +29,6 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
30
29
|
cache.set(mod, to);
|
|
31
30
|
return to;
|
|
32
31
|
};
|
|
33
|
-
var __toCommonJS = (from) => {
|
|
34
|
-
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
|
35
|
-
if (entry)
|
|
36
|
-
return entry;
|
|
37
|
-
entry = __defProp({}, "__esModule", { value: true });
|
|
38
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
39
|
-
for (var key of __getOwnPropNames(from))
|
|
40
|
-
if (!__hasOwnProp.call(entry, key))
|
|
41
|
-
__defProp(entry, key, {
|
|
42
|
-
get: __accessProp.bind(from, key),
|
|
43
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
__moduleCache.set(from, entry);
|
|
47
|
-
return entry;
|
|
48
|
-
};
|
|
49
|
-
var __moduleCache;
|
|
50
32
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
51
33
|
var __returnValue = (v) => v;
|
|
52
34
|
function __exportSetter(name, newValue) {
|
|
@@ -10834,6 +10816,7 @@ function watchPage(page, opts) {
|
|
|
10834
10816
|
const changes = [];
|
|
10835
10817
|
const intervalMs = opts?.intervalMs ?? 500;
|
|
10836
10818
|
const maxChanges = opts?.maxChanges ?? 50;
|
|
10819
|
+
const sessionId = opts?.sessionId;
|
|
10837
10820
|
const interval = setInterval(async () => {
|
|
10838
10821
|
if (changes.length >= maxChanges)
|
|
10839
10822
|
return;
|
|
@@ -10847,7 +10830,7 @@ function watchPage(page, opts) {
|
|
|
10847
10830
|
}
|
|
10848
10831
|
} catch {}
|
|
10849
10832
|
}, intervalMs);
|
|
10850
|
-
activeWatches.set(id, { interval, changes });
|
|
10833
|
+
activeWatches.set(id, { interval, changes, sessionId });
|
|
10851
10834
|
return {
|
|
10852
10835
|
id,
|
|
10853
10836
|
stop: () => {
|
|
@@ -10866,10 +10849,12 @@ function stopWatch(watchId) {
|
|
|
10866
10849
|
activeWatches.delete(watchId);
|
|
10867
10850
|
}
|
|
10868
10851
|
}
|
|
10869
|
-
function stopAllWatchesForSession(
|
|
10870
|
-
for (const [id, w] of activeWatches) {
|
|
10871
|
-
|
|
10872
|
-
|
|
10852
|
+
function stopAllWatchesForSession(sessionId) {
|
|
10853
|
+
for (const [id, w] of [...activeWatches]) {
|
|
10854
|
+
if (!sessionId || w.sessionId === sessionId) {
|
|
10855
|
+
clearInterval(w.interval);
|
|
10856
|
+
activeWatches.delete(id);
|
|
10857
|
+
}
|
|
10873
10858
|
}
|
|
10874
10859
|
}
|
|
10875
10860
|
async function clickRef(page, sessionId, ref, opts) {
|
|
@@ -14606,7 +14591,7 @@ var require_operation = __commonJS((exports, module) => {
|
|
|
14606
14591
|
// node_modules/@img/colour/color.cjs
|
|
14607
14592
|
var require_color = __commonJS((exports, module) => {
|
|
14608
14593
|
var __defProp3 = Object.defineProperty;
|
|
14609
|
-
var
|
|
14594
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
14610
14595
|
var __getOwnPropNames3 = Object.getOwnPropertyNames;
|
|
14611
14596
|
var __hasOwnProp3 = Object.prototype.hasOwnProperty;
|
|
14612
14597
|
var __export3 = (target, all) => {
|
|
@@ -14617,16 +14602,16 @@ var require_color = __commonJS((exports, module) => {
|
|
|
14617
14602
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
14618
14603
|
for (let key of __getOwnPropNames3(from))
|
|
14619
14604
|
if (!__hasOwnProp3.call(to, key) && key !== except)
|
|
14620
|
-
__defProp3(to, key, { get: () => from[key], enumerable: !(desc =
|
|
14605
|
+
__defProp3(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14621
14606
|
}
|
|
14622
14607
|
return to;
|
|
14623
14608
|
};
|
|
14624
|
-
var
|
|
14609
|
+
var __toCommonJS = (mod) => __copyProps(__defProp3({}, "__esModule", { value: true }), mod);
|
|
14625
14610
|
var index_exports = {};
|
|
14626
14611
|
__export3(index_exports, {
|
|
14627
14612
|
default: () => index_default
|
|
14628
14613
|
});
|
|
14629
|
-
module.exports =
|
|
14614
|
+
module.exports = __toCommonJS(index_exports);
|
|
14630
14615
|
var colors = {
|
|
14631
14616
|
aliceblue: [240, 248, 255],
|
|
14632
14617
|
antiquewhite: [250, 235, 215],
|
|
@@ -18232,6 +18217,207 @@ var THEMES = {
|
|
|
18232
18217
|
brightWhite: "#ffffff"
|
|
18233
18218
|
}
|
|
18234
18219
|
};
|
|
18220
|
+
async function configureDomRenderer(page, options) {
|
|
18221
|
+
await page.evaluate((opts) => {
|
|
18222
|
+
const runtimeKey = "__takumiTuiDomRenderer";
|
|
18223
|
+
const rootId = "takumi-tui-dom-root";
|
|
18224
|
+
const styleId = "takumi-tui-dom-style";
|
|
18225
|
+
const win = window;
|
|
18226
|
+
const ensureStyle = () => {
|
|
18227
|
+
let style = document.getElementById(styleId);
|
|
18228
|
+
if (!style) {
|
|
18229
|
+
style = document.createElement("style");
|
|
18230
|
+
style.id = styleId;
|
|
18231
|
+
document.head.appendChild(style);
|
|
18232
|
+
}
|
|
18233
|
+
style.textContent = `
|
|
18234
|
+
#${rootId} {
|
|
18235
|
+
position: absolute;
|
|
18236
|
+
inset: 0;
|
|
18237
|
+
overflow: hidden;
|
|
18238
|
+
display: flex;
|
|
18239
|
+
flex-direction: column;
|
|
18240
|
+
white-space: pre;
|
|
18241
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
|
18242
|
+
line-height: 1.2;
|
|
18243
|
+
background: var(--takumi-tui-bg, #1e1e1e);
|
|
18244
|
+
color: var(--takumi-tui-fg, #d4d4d4);
|
|
18245
|
+
z-index: 4;
|
|
18246
|
+
pointer-events: none;
|
|
18247
|
+
user-select: text;
|
|
18248
|
+
}
|
|
18249
|
+
#${rootId}[data-active="0"] {
|
|
18250
|
+
display: none;
|
|
18251
|
+
}
|
|
18252
|
+
#${rootId} .takumi-tui-dom-row {
|
|
18253
|
+
display: flex;
|
|
18254
|
+
min-height: 1.2em;
|
|
18255
|
+
}
|
|
18256
|
+
#${rootId} .takumi-tui-dom-cell {
|
|
18257
|
+
display: inline-flex;
|
|
18258
|
+
align-items: center;
|
|
18259
|
+
justify-content: center;
|
|
18260
|
+
min-width: 0.62em;
|
|
18261
|
+
height: 1.2em;
|
|
18262
|
+
}
|
|
18263
|
+
#${rootId} .takumi-tui-dom-cell[data-cursor="true"] {
|
|
18264
|
+
outline: 1px solid currentColor;
|
|
18265
|
+
outline-offset: -1px;
|
|
18266
|
+
}
|
|
18267
|
+
body[data-takumi-dom-render="1"] .xterm-rows,
|
|
18268
|
+
body[data-takumi-dom-render="1"] .xterm-text-layer,
|
|
18269
|
+
body[data-takumi-dom-render="1"] .xterm-cursor-layer,
|
|
18270
|
+
body[data-takumi-dom-render="1"] .xterm-selection-layer {
|
|
18271
|
+
opacity: 0 !important;
|
|
18272
|
+
}
|
|
18273
|
+
`;
|
|
18274
|
+
};
|
|
18275
|
+
const ensureRoot = () => {
|
|
18276
|
+
let root = document.getElementById(rootId);
|
|
18277
|
+
if (!root) {
|
|
18278
|
+
root = document.createElement("div");
|
|
18279
|
+
root.id = rootId;
|
|
18280
|
+
root.setAttribute("role", "grid");
|
|
18281
|
+
root.setAttribute("aria-label", "Terminal DOM renderer");
|
|
18282
|
+
const host = document.getElementById("terminal-container") ?? document.querySelector(".xterm") ?? document.body;
|
|
18283
|
+
if (getComputedStyle(host).position === "static") {
|
|
18284
|
+
host.style.position = "relative";
|
|
18285
|
+
}
|
|
18286
|
+
host.appendChild(root);
|
|
18287
|
+
}
|
|
18288
|
+
root.style.setProperty("--takumi-tui-bg", opts.theme === "light" ? "#ffffff" : "#1e1e1e");
|
|
18289
|
+
root.style.setProperty("--takumi-tui-fg", opts.theme === "light" ? "#1e1e1e" : "#d4d4d4");
|
|
18290
|
+
root.style.fontSize = `${opts.fontSize ?? 14}px`;
|
|
18291
|
+
root.dataset.active = opts.active ? "1" : "0";
|
|
18292
|
+
if (opts.active)
|
|
18293
|
+
root.removeAttribute("aria-hidden");
|
|
18294
|
+
else
|
|
18295
|
+
root.setAttribute("aria-hidden", "true");
|
|
18296
|
+
document.body.dataset.takumiDomRender = opts.active ? "1" : "0";
|
|
18297
|
+
return root;
|
|
18298
|
+
};
|
|
18299
|
+
const readCellChars = (line, col) => {
|
|
18300
|
+
try {
|
|
18301
|
+
const cell = typeof line?.getCell === "function" ? line.getCell(col) : null;
|
|
18302
|
+
const chars = typeof cell?.getChars === "function" ? cell.getChars() : "";
|
|
18303
|
+
if (chars)
|
|
18304
|
+
return chars;
|
|
18305
|
+
} catch {}
|
|
18306
|
+
try {
|
|
18307
|
+
const text = typeof line?.translateToString === "function" ? line.translateToString(false, col, col + 1) : "";
|
|
18308
|
+
if (text)
|
|
18309
|
+
return text;
|
|
18310
|
+
} catch {}
|
|
18311
|
+
return " ";
|
|
18312
|
+
};
|
|
18313
|
+
const buildState = (activeOnly) => {
|
|
18314
|
+
const term = win.term ?? win.terminal;
|
|
18315
|
+
if (!term?.buffer?.active) {
|
|
18316
|
+
return {
|
|
18317
|
+
text: "",
|
|
18318
|
+
rows: [],
|
|
18319
|
+
row_count: 0,
|
|
18320
|
+
cols: null,
|
|
18321
|
+
total_rows: 0,
|
|
18322
|
+
buffer_length: null,
|
|
18323
|
+
cursor_row: -1,
|
|
18324
|
+
cursor_col: -1,
|
|
18325
|
+
font_size: null,
|
|
18326
|
+
theme: opts.theme
|
|
18327
|
+
};
|
|
18328
|
+
}
|
|
18329
|
+
const buf = term.buffer.active;
|
|
18330
|
+
const rows = [];
|
|
18331
|
+
const root = ensureRoot();
|
|
18332
|
+
const fragment = document.createDocumentFragment();
|
|
18333
|
+
for (let row = 0;row < buf.length; row++) {
|
|
18334
|
+
const line = buf.getLine(row);
|
|
18335
|
+
if (!line)
|
|
18336
|
+
continue;
|
|
18337
|
+
const rowEl = document.createElement("div");
|
|
18338
|
+
rowEl.className = "takumi-tui-dom-row";
|
|
18339
|
+
rowEl.setAttribute("role", "row");
|
|
18340
|
+
rowEl.dataset.row = String(row);
|
|
18341
|
+
rowEl.setAttribute("aria-rowindex", String(row + 1));
|
|
18342
|
+
let rowText = "";
|
|
18343
|
+
for (let col = 0;col < term.cols; col++) {
|
|
18344
|
+
const char = readCellChars(line, col) || " ";
|
|
18345
|
+
rowText += char;
|
|
18346
|
+
const cellEl = document.createElement("span");
|
|
18347
|
+
cellEl.className = "takumi-tui-dom-cell";
|
|
18348
|
+
cellEl.setAttribute("role", "gridcell");
|
|
18349
|
+
cellEl.dataset.row = String(row);
|
|
18350
|
+
cellEl.dataset.col = String(col);
|
|
18351
|
+
cellEl.setAttribute("aria-colindex", String(col + 1));
|
|
18352
|
+
cellEl.textContent = char;
|
|
18353
|
+
if (buf.cursorY === row && buf.cursorX === col) {
|
|
18354
|
+
cellEl.dataset.cursor = "true";
|
|
18355
|
+
}
|
|
18356
|
+
rowEl.appendChild(cellEl);
|
|
18357
|
+
}
|
|
18358
|
+
rows.push(rowText.replace(/\s+$/g, ""));
|
|
18359
|
+
rowEl.setAttribute("aria-label", rows[rows.length - 1] || " ");
|
|
18360
|
+
fragment.appendChild(rowEl);
|
|
18361
|
+
}
|
|
18362
|
+
root.replaceChildren(fragment);
|
|
18363
|
+
root.setAttribute("aria-rowcount", String(rows.length));
|
|
18364
|
+
root.dataset.method = "dom";
|
|
18365
|
+
return {
|
|
18366
|
+
text: rows.join(`
|
|
18367
|
+
`).trimEnd(),
|
|
18368
|
+
rows,
|
|
18369
|
+
row_count: rows.length,
|
|
18370
|
+
cols: term.cols,
|
|
18371
|
+
total_rows: term.rows,
|
|
18372
|
+
buffer_length: buf.length,
|
|
18373
|
+
cursor_row: buf.cursorY,
|
|
18374
|
+
cursor_col: buf.cursorX,
|
|
18375
|
+
font_size: term.options?.fontSize ?? null,
|
|
18376
|
+
theme: term.options?.theme?.background === "#ffffff" ? "light" : "dark"
|
|
18377
|
+
};
|
|
18378
|
+
};
|
|
18379
|
+
ensureStyle();
|
|
18380
|
+
ensureRoot();
|
|
18381
|
+
if (!win[runtimeKey]) {
|
|
18382
|
+
win[runtimeKey] = {
|
|
18383
|
+
sync: () => buildState(false),
|
|
18384
|
+
activate: (active) => {
|
|
18385
|
+
const root = ensureRoot();
|
|
18386
|
+
root.dataset.active = active ? "1" : "0";
|
|
18387
|
+
if (active)
|
|
18388
|
+
root.removeAttribute("aria-hidden");
|
|
18389
|
+
else
|
|
18390
|
+
root.setAttribute("aria-hidden", "true");
|
|
18391
|
+
document.body.dataset.takumiDomRender = active ? "1" : "0";
|
|
18392
|
+
}
|
|
18393
|
+
};
|
|
18394
|
+
const intervalId = window.setInterval(() => {
|
|
18395
|
+
try {
|
|
18396
|
+
win[runtimeKey]?.sync?.();
|
|
18397
|
+
} catch {}
|
|
18398
|
+
}, 50);
|
|
18399
|
+
win[runtimeKey].intervalId = intervalId;
|
|
18400
|
+
}
|
|
18401
|
+
win[runtimeKey].activate(opts.active);
|
|
18402
|
+
win[runtimeKey].sync();
|
|
18403
|
+
}, options);
|
|
18404
|
+
}
|
|
18405
|
+
async function destroyDomRenderer(page) {
|
|
18406
|
+
await page.evaluate(() => {
|
|
18407
|
+
const runtimeKey = "__takumiTuiDomRenderer";
|
|
18408
|
+
const win = window;
|
|
18409
|
+
if (win[runtimeKey]?.intervalId) {
|
|
18410
|
+
clearInterval(win[runtimeKey].intervalId);
|
|
18411
|
+
}
|
|
18412
|
+
delete win[runtimeKey];
|
|
18413
|
+
document.getElementById("takumi-tui-dom-root")?.remove();
|
|
18414
|
+
document.getElementById("takumi-tui-dom-style")?.remove();
|
|
18415
|
+
delete document.body.dataset.takumiDomRender;
|
|
18416
|
+
}).catch(() => {});
|
|
18417
|
+
}
|
|
18418
|
+
function isDomMethod(method) {
|
|
18419
|
+
return method === "dom";
|
|
18420
|
+
}
|
|
18235
18421
|
function isTuiAvailable() {
|
|
18236
18422
|
try {
|
|
18237
18423
|
execSync2("which ttyd", { stdio: "ignore" });
|
|
@@ -18244,7 +18430,7 @@ async function findAvailablePort(startPort) {
|
|
|
18244
18430
|
let port = startPort;
|
|
18245
18431
|
for (let i = 0;i < 100; i++) {
|
|
18246
18432
|
try {
|
|
18247
|
-
|
|
18433
|
+
await fetch(`http://localhost:${port}`);
|
|
18248
18434
|
port++;
|
|
18249
18435
|
} catch {
|
|
18250
18436
|
return port;
|
|
@@ -18270,24 +18456,16 @@ async function launchTui(command, options = {}) {
|
|
|
18270
18456
|
}
|
|
18271
18457
|
const port = await findAvailablePort(nextPort);
|
|
18272
18458
|
nextPort = port + 1;
|
|
18273
|
-
const ttydProcess = spawn2("ttyd", ["--writable", "--port", String(port), "/bin/sh", "-c", command], {
|
|
18274
|
-
stdio: "ignore",
|
|
18275
|
-
detached: false
|
|
18276
|
-
});
|
|
18459
|
+
const ttydProcess = spawn2("ttyd", ["--writable", "--port", String(port), "/bin/sh", "-c", command], { stdio: "ignore", detached: false });
|
|
18277
18460
|
ttydProcess.on("error", (err) => {
|
|
18278
18461
|
console.error(`[tui] ttyd process error: ${err.message}`);
|
|
18279
18462
|
});
|
|
18280
18463
|
try {
|
|
18281
18464
|
await waitForTtyd(port);
|
|
18282
18465
|
const viewport = options.viewport ?? { width: 1280, height: 720 };
|
|
18283
|
-
const browser = await launchPlaywright({
|
|
18284
|
-
headless: options.headless ?? true,
|
|
18285
|
-
viewport
|
|
18286
|
-
});
|
|
18466
|
+
const browser = await launchPlaywright({ headless: options.headless ?? true, viewport });
|
|
18287
18467
|
const page = await getPage(browser, { viewport });
|
|
18288
|
-
await page.goto(`http://localhost:${port}`, {
|
|
18289
|
-
waitUntil: "domcontentloaded"
|
|
18290
|
-
});
|
|
18468
|
+
await page.goto(`http://localhost:${port}`, { waitUntil: "domcontentloaded" });
|
|
18291
18469
|
await page.waitForSelector(".xterm-screen", { timeout: 1e4 });
|
|
18292
18470
|
let resolvedTheme = "dark";
|
|
18293
18471
|
const requestedTheme = options.theme ?? "system";
|
|
@@ -18306,16 +18484,15 @@ async function launchTui(command, options = {}) {
|
|
|
18306
18484
|
const themeColors = THEMES[resolvedTheme];
|
|
18307
18485
|
await page.evaluate((theme) => {
|
|
18308
18486
|
const term = window.term ?? window.terminal;
|
|
18309
|
-
if (term?.options)
|
|
18487
|
+
if (term?.options)
|
|
18310
18488
|
term.options.theme = theme;
|
|
18311
|
-
}
|
|
18312
18489
|
document.body.style.backgroundColor = theme.background;
|
|
18313
18490
|
const container = document.getElementById("terminal-container");
|
|
18314
18491
|
if (container)
|
|
18315
18492
|
container.style.backgroundColor = theme.background;
|
|
18316
|
-
const
|
|
18317
|
-
if (
|
|
18318
|
-
|
|
18493
|
+
const vp = document.querySelector(".xterm-viewport");
|
|
18494
|
+
if (vp)
|
|
18495
|
+
vp.style.backgroundColor = theme.background;
|
|
18319
18496
|
}, themeColors);
|
|
18320
18497
|
if (options.fontSize) {
|
|
18321
18498
|
await page.evaluate((size) => {
|
|
@@ -18324,13 +18501,31 @@ async function launchTui(command, options = {}) {
|
|
|
18324
18501
|
term.options.fontSize = size;
|
|
18325
18502
|
}, options.fontSize);
|
|
18326
18503
|
}
|
|
18327
|
-
|
|
18504
|
+
const method = options.method ?? "buffer";
|
|
18505
|
+
await configureDomRenderer(page, {
|
|
18506
|
+
active: isDomMethod(method),
|
|
18507
|
+
theme: resolvedTheme,
|
|
18508
|
+
fontSize: options.fontSize
|
|
18509
|
+
});
|
|
18510
|
+
return {
|
|
18511
|
+
ttydProcess,
|
|
18512
|
+
port,
|
|
18513
|
+
browser,
|
|
18514
|
+
page,
|
|
18515
|
+
theme: resolvedTheme,
|
|
18516
|
+
method,
|
|
18517
|
+
lastHealthCheck: Date.now(),
|
|
18518
|
+
reconnectCount: 0
|
|
18519
|
+
};
|
|
18328
18520
|
} catch (err) {
|
|
18329
|
-
|
|
18521
|
+
try {
|
|
18522
|
+
ttydProcess.kill("SIGTERM");
|
|
18523
|
+
} catch {}
|
|
18330
18524
|
throw err;
|
|
18331
18525
|
}
|
|
18332
18526
|
}
|
|
18333
18527
|
async function closeTui(session) {
|
|
18528
|
+
await destroyDomRenderer(session.page);
|
|
18334
18529
|
try {
|
|
18335
18530
|
await session.page.close();
|
|
18336
18531
|
} catch {}
|
|
@@ -18340,6 +18535,9 @@ async function closeTui(session) {
|
|
|
18340
18535
|
try {
|
|
18341
18536
|
session.ttydProcess.kill("SIGTERM");
|
|
18342
18537
|
} catch {}
|
|
18538
|
+
try {
|
|
18539
|
+
session.ttydProcess.kill("SIGKILL");
|
|
18540
|
+
} catch {}
|
|
18343
18541
|
}
|
|
18344
18542
|
|
|
18345
18543
|
// src/engines/selector.ts
|
|
@@ -18551,19 +18749,13 @@ Object.defineProperty(navigator, 'plugins', {
|
|
|
18551
18749
|
{ name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '', length: 1 },
|
|
18552
18750
|
{ name: 'Native Client', filename: 'internal-nacl-plugin', description: '', length: 2 },
|
|
18553
18751
|
];
|
|
18554
|
-
// Mimic PluginArray interface
|
|
18555
|
-
const pluginArray =
|
|
18752
|
+
// Mimic PluginArray interface \u2014 guard against removed prototypes
|
|
18753
|
+
const pluginArray = {};
|
|
18556
18754
|
plugins.forEach((p, i) => {
|
|
18557
|
-
const plugin =
|
|
18558
|
-
Object.defineProperties(plugin, {
|
|
18559
|
-
name: { value: p.name, enumerable: true },
|
|
18560
|
-
filename: { value: p.filename, enumerable: true },
|
|
18561
|
-
description: { value: p.description, enumerable: true },
|
|
18562
|
-
length: { value: p.length, enumerable: true },
|
|
18563
|
-
});
|
|
18755
|
+
const plugin = { ...p, item: () => null };
|
|
18564
18756
|
pluginArray[i] = plugin;
|
|
18565
18757
|
});
|
|
18566
|
-
|
|
18758
|
+
pluginArray.length = plugins.length;
|
|
18567
18759
|
pluginArray.item = (i) => pluginArray[i] || null;
|
|
18568
18760
|
pluginArray.namedItem = (name) => plugins.find(p => p.name === name) ? pluginArray[plugins.findIndex(p => p.name === name)] : null;
|
|
18569
18761
|
pluginArray.refresh = () => {};
|
|
@@ -18618,14 +18810,16 @@ if (ttlInterval.unref)
|
|
|
18618
18810
|
var DB_PRUNE_INTERVAL_MS = 30 * 60000;
|
|
18619
18811
|
var DB_RETENTION_HOURS = 24;
|
|
18620
18812
|
var dbPruneInterval = setInterval(() => {
|
|
18621
|
-
|
|
18622
|
-
|
|
18623
|
-
|
|
18624
|
-
|
|
18625
|
-
|
|
18626
|
-
|
|
18627
|
-
|
|
18628
|
-
|
|
18813
|
+
(async () => {
|
|
18814
|
+
try {
|
|
18815
|
+
const { getDatabase: getDatabase2 } = await Promise.resolve().then(() => (init_schema(), exports_schema));
|
|
18816
|
+
const db = getDatabase2();
|
|
18817
|
+
const cutoff = new Date(Date.now() - DB_RETENTION_HOURS * 3600000).toISOString();
|
|
18818
|
+
db.prepare("DELETE FROM network_log WHERE session_id IN (SELECT id FROM sessions WHERE status != 'active') AND timestamp < ?").run(cutoff);
|
|
18819
|
+
db.prepare("DELETE FROM console_log WHERE session_id IN (SELECT id FROM sessions WHERE status != 'active') AND timestamp < ?").run(cutoff);
|
|
18820
|
+
db.prepare("DELETE FROM snapshots WHERE session_id IN (SELECT id FROM sessions WHERE status != 'active') AND timestamp < ?").run(cutoff);
|
|
18821
|
+
} catch {}
|
|
18822
|
+
})();
|
|
18629
18823
|
}, DB_PRUNE_INTERVAL_MS);
|
|
18630
18824
|
if (dbPruneInterval.unref)
|
|
18631
18825
|
dbPruneInterval.unref();
|
|
@@ -18661,7 +18855,7 @@ async function createSession2(opts = {}) {
|
|
|
18661
18855
|
try {
|
|
18662
18856
|
cleanups2.push(setupDialogHandler(page2, session2.id));
|
|
18663
18857
|
} catch {}
|
|
18664
|
-
handles.set(session2.id, { browser: cdpBrowser, bunView: null, tuiSession: null, page: page2, engine: "cdp", cleanups: cleanups2, tokenBudget: { total: 0, used: 0 }, lastActivity: Date.now(), autoGallery: opts.autoGallery ?? false });
|
|
18858
|
+
handles.set(session2.id, { browser: cdpBrowser, bunView: null, tuiSession: null, page: page2, engine: "cdp", cleanups: cleanups2, tokenBudget: { total: 0, used: 0 }, lastActivity: Date.now(), autoGallery: opts.autoGallery ?? false, startUrl: opts.startUrl ?? "" });
|
|
18665
18859
|
return { session: session2, page: page2 };
|
|
18666
18860
|
}
|
|
18667
18861
|
const engine = opts.engine === "auto" || !opts.engine ? selectEngine(opts.useCase ?? "spa_navigate" /* SPA_NAVIGATE */, opts.engine) : opts.engine;
|
|
@@ -18675,13 +18869,29 @@ async function createSession2(opts = {}) {
|
|
|
18675
18869
|
browser = await launchPlaywright({ headless: opts.headless ?? true, viewport: opts.viewport, userAgent: opts.userAgent });
|
|
18676
18870
|
page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
|
|
18677
18871
|
} else {
|
|
18678
|
-
|
|
18872
|
+
const testView = new BunWebViewSession({
|
|
18679
18873
|
width: opts.viewport?.width ?? 1280,
|
|
18680
18874
|
height: opts.viewport?.height ?? 720,
|
|
18681
18875
|
profile: opts.name ?? undefined
|
|
18682
18876
|
});
|
|
18683
|
-
|
|
18684
|
-
|
|
18877
|
+
let bunWorks = true;
|
|
18878
|
+
try {
|
|
18879
|
+
await testView.goto("data:text/html,<html></html>");
|
|
18880
|
+
} catch {
|
|
18881
|
+
bunWorks = false;
|
|
18882
|
+
try {
|
|
18883
|
+
await testView.close();
|
|
18884
|
+
} catch {}
|
|
18885
|
+
}
|
|
18886
|
+
if (!bunWorks) {
|
|
18887
|
+
console.warn("[browser] Bun.WebView exists but Chrome not available \u2014 falling back to playwright");
|
|
18888
|
+
browser = await launchPlaywright({ headless: opts.headless ?? true, viewport: opts.viewport, userAgent: opts.userAgent });
|
|
18889
|
+
page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
|
|
18890
|
+
} else {
|
|
18891
|
+
bunView = testView;
|
|
18892
|
+
if (opts.stealth) {}
|
|
18893
|
+
page = createBunProxy(bunView);
|
|
18894
|
+
}
|
|
18685
18895
|
}
|
|
18686
18896
|
} else if (resolvedEngine === "lightpanda") {
|
|
18687
18897
|
browser = await connectLightpanda();
|
|
@@ -18693,7 +18903,8 @@ async function createSession2(opts = {}) {
|
|
|
18693
18903
|
headless: opts.headless ?? true,
|
|
18694
18904
|
viewport: opts.viewport,
|
|
18695
18905
|
theme: opts.tuiTheme ?? "system",
|
|
18696
|
-
fontSize: opts.tuiFontSize
|
|
18906
|
+
fontSize: opts.tuiFontSize,
|
|
18907
|
+
method: opts.tuiMethod ?? "buffer"
|
|
18697
18908
|
});
|
|
18698
18909
|
browser = tuiSess.browser;
|
|
18699
18910
|
page = tuiSess.page;
|
|
@@ -18719,7 +18930,7 @@ async function createSession2(opts = {}) {
|
|
|
18719
18930
|
try {
|
|
18720
18931
|
cleanups2.push(setupDialogHandler(page, session2.id));
|
|
18721
18932
|
} catch {}
|
|
18722
|
-
handles.set(session2.id, { browser, bunView: null, tuiSession: tuiSess, page, engine: "tui", cleanups: cleanups2, tokenBudget: { total: 0, used: 0 }, lastActivity: Date.now(), autoGallery: opts.autoGallery ?? false });
|
|
18933
|
+
handles.set(session2.id, { browser, bunView: null, tuiSession: tuiSess, page, engine: "tui", cleanups: cleanups2, tokenBudget: { total: 0, used: 0 }, lastActivity: Date.now(), autoGallery: opts.autoGallery ?? false, startUrl: opts.startUrl ?? "bash" });
|
|
18723
18934
|
return { session: session2, page };
|
|
18724
18935
|
} else {
|
|
18725
18936
|
browser = await pool.acquire(opts.headless ?? true);
|
|
@@ -18791,7 +19002,7 @@ async function createSession2(opts = {}) {
|
|
|
18791
19002
|
} catch {}
|
|
18792
19003
|
}
|
|
18793
19004
|
}
|
|
18794
|
-
handles.set(session.id, { browser, bunView, tuiSession: null, page, engine: bunView ? "bun" : resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 }, lastActivity: Date.now(), autoGallery: opts.autoGallery ?? false });
|
|
19005
|
+
handles.set(session.id, { browser, bunView, tuiSession: null, page, engine: bunView ? "bun" : resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 }, lastActivity: Date.now(), autoGallery: opts.autoGallery ?? false, startUrl: opts.startUrl ?? "" });
|
|
18795
19006
|
if (opts.startUrl) {
|
|
18796
19007
|
try {
|
|
18797
19008
|
if (bunView) {
|
|
@@ -18822,38 +19033,43 @@ function getSessionPage(sessionId) {
|
|
|
18822
19033
|
}
|
|
18823
19034
|
async function closeSession2(sessionId) {
|
|
18824
19035
|
const handle = handles.get(sessionId);
|
|
18825
|
-
|
|
18826
|
-
|
|
18827
|
-
|
|
18828
|
-
|
|
18829
|
-
|
|
18830
|
-
|
|
18831
|
-
|
|
18832
|
-
|
|
18833
|
-
|
|
18834
|
-
|
|
18835
|
-
|
|
18836
|
-
|
|
18837
|
-
|
|
18838
|
-
|
|
18839
|
-
|
|
18840
|
-
|
|
19036
|
+
try {
|
|
19037
|
+
if (handle) {
|
|
19038
|
+
for (const cleanup of handle.cleanups) {
|
|
19039
|
+
try {
|
|
19040
|
+
cleanup();
|
|
19041
|
+
} catch {}
|
|
19042
|
+
}
|
|
19043
|
+
if (handle.bunView) {
|
|
19044
|
+
try {
|
|
19045
|
+
await handle.bunView.close();
|
|
19046
|
+
} catch {}
|
|
19047
|
+
} else if (handle.tuiSession) {} else {
|
|
19048
|
+
try {
|
|
19049
|
+
await handle.page.context().close();
|
|
19050
|
+
} catch {}
|
|
19051
|
+
try {
|
|
19052
|
+
if (handle.browser)
|
|
19053
|
+
pool.release(handle.browser);
|
|
19054
|
+
} catch {}
|
|
19055
|
+
}
|
|
18841
19056
|
}
|
|
19057
|
+
try {
|
|
19058
|
+
const { clearLastSnapshot: clearLastSnapshot2, clearSessionRefs: clearSessionRefs2 } = await Promise.resolve().then(() => (init_snapshot(), exports_snapshot));
|
|
19059
|
+
clearLastSnapshot2(sessionId);
|
|
19060
|
+
clearSessionRefs2(sessionId);
|
|
19061
|
+
} catch {}
|
|
19062
|
+
try {
|
|
19063
|
+
const { stopAllWatchesForSession: stopAllWatchesForSession2 } = await Promise.resolve().then(() => (init_actions(), exports_actions));
|
|
19064
|
+
stopAllWatchesForSession2(sessionId);
|
|
19065
|
+
} catch {}
|
|
19066
|
+
try {
|
|
19067
|
+
const { clearDialogs: clearDialogs2 } = await Promise.resolve().then(() => (init_dialogs(), exports_dialogs));
|
|
19068
|
+
clearDialogs2(sessionId);
|
|
19069
|
+
} catch {}
|
|
19070
|
+
} finally {
|
|
18842
19071
|
handles.delete(sessionId);
|
|
18843
19072
|
}
|
|
18844
|
-
try {
|
|
18845
|
-
const { clearLastSnapshot: clearLastSnapshot2, clearSessionRefs: clearSessionRefs2 } = await Promise.resolve().then(() => (init_snapshot(), exports_snapshot));
|
|
18846
|
-
clearLastSnapshot2(sessionId);
|
|
18847
|
-
clearSessionRefs2(sessionId);
|
|
18848
|
-
} catch {}
|
|
18849
|
-
try {
|
|
18850
|
-
const { stopAllWatchesForSession: stopAllWatchesForSession2 } = await Promise.resolve().then(() => (init_actions(), exports_actions));
|
|
18851
|
-
stopAllWatchesForSession2(sessionId);
|
|
18852
|
-
} catch {}
|
|
18853
|
-
try {
|
|
18854
|
-
const { clearDialogs: clearDialogs2 } = await Promise.resolve().then(() => (init_dialogs(), exports_dialogs));
|
|
18855
|
-
clearDialogs2(sessionId);
|
|
18856
|
-
} catch {}
|
|
18857
19073
|
return closeSession(sessionId);
|
|
18858
19074
|
}
|
|
18859
19075
|
function listSessions2(filter) {
|
|
@@ -18952,6 +19168,12 @@ import { mkdirSync as mkdirSync7 } from "fs";
|
|
|
18952
19168
|
// src/db/gallery.ts
|
|
18953
19169
|
init_schema();
|
|
18954
19170
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
19171
|
+
function validateDataPath(filePath) {
|
|
19172
|
+
if (filePath.includes("..")) {
|
|
19173
|
+
throw new Error(`File path must not contain '..': ${filePath}`);
|
|
19174
|
+
}
|
|
19175
|
+
return filePath;
|
|
19176
|
+
}
|
|
18955
19177
|
function deserialize(row) {
|
|
18956
19178
|
return {
|
|
18957
19179
|
id: row.id,
|
|
@@ -18976,6 +19198,9 @@ function deserialize(row) {
|
|
|
18976
19198
|
function createEntry(data) {
|
|
18977
19199
|
const db = getDatabase();
|
|
18978
19200
|
const id = randomUUID4();
|
|
19201
|
+
validateDataPath(data.path);
|
|
19202
|
+
if (data.thumbnail_path)
|
|
19203
|
+
validateDataPath(data.thumbnail_path);
|
|
18979
19204
|
db.prepare(`
|
|
18980
19205
|
INSERT INTO gallery_entries
|
|
18981
19206
|
(id, session_id, project_id, url, title, path, thumbnail_path, format,
|
|
@@ -19591,38 +19816,74 @@ async function diffImages(path1, path2) {
|
|
|
19591
19816
|
|
|
19592
19817
|
// src/server/index.ts
|
|
19593
19818
|
var PORT = parseInt(process.env["BROWSER_SERVER_PORT"] ?? "7030");
|
|
19819
|
+
var API_KEY = process.env["BROWSER_API_KEY"] ?? null;
|
|
19820
|
+
var ALLOWED_ORIGIN = process.env["BROWSER_ALLOWED_ORIGIN"] ?? (API_KEY ? null : "http://localhost:3000");
|
|
19594
19821
|
var startTime = Date.now();
|
|
19595
|
-
|
|
19596
|
-
|
|
19597
|
-
|
|
19598
|
-
|
|
19599
|
-
};
|
|
19822
|
+
function corsHeaders(origin) {
|
|
19823
|
+
const headers = {
|
|
19824
|
+
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
|
19825
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization"
|
|
19826
|
+
};
|
|
19827
|
+
if (origin) {
|
|
19828
|
+
if (!API_KEY && !origin.startsWith("http://localhost") && !origin.startsWith("http://127.0.0.1")) {
|
|
19829
|
+
headers["Access-Control-Allow-Origin"] = ALLOWED_ORIGIN ?? "http://localhost:3000";
|
|
19830
|
+
} else {
|
|
19831
|
+
headers["Access-Control-Allow-Origin"] = origin;
|
|
19832
|
+
}
|
|
19833
|
+
}
|
|
19834
|
+
return headers;
|
|
19835
|
+
}
|
|
19836
|
+
function authenticate(req) {
|
|
19837
|
+
if (!API_KEY)
|
|
19838
|
+
return null;
|
|
19839
|
+
const header = req.headers.get("Authorization") ?? "";
|
|
19840
|
+
const token = header.startsWith("Bearer ") ? header.slice(7) : "";
|
|
19841
|
+
if (token !== API_KEY) {
|
|
19842
|
+
return new Response(JSON.stringify({ error: "Unauthorized" }), {
|
|
19843
|
+
status: 401,
|
|
19844
|
+
headers: { "Content-Type": "application/json" }
|
|
19845
|
+
});
|
|
19846
|
+
}
|
|
19847
|
+
return null;
|
|
19848
|
+
}
|
|
19600
19849
|
var networkCleanup = new Map;
|
|
19601
19850
|
var consoleCleanup = new Map;
|
|
19602
19851
|
var harCaptures = new Map;
|
|
19603
|
-
function
|
|
19852
|
+
async function safeJson(req) {
|
|
19853
|
+
try {
|
|
19854
|
+
const contentType = req.headers.get("content-type") ?? "";
|
|
19855
|
+
if (!contentType.includes("application/json")) {
|
|
19856
|
+
return { error: badRequest("Content-Type must be application/json") };
|
|
19857
|
+
}
|
|
19858
|
+
const body = await req.json();
|
|
19859
|
+
return { body };
|
|
19860
|
+
} catch {
|
|
19861
|
+
return { error: badRequest("Invalid or missing JSON body") };
|
|
19862
|
+
}
|
|
19863
|
+
}
|
|
19864
|
+
function ok(data, status = 200, extraHeaders) {
|
|
19604
19865
|
return new Response(JSON.stringify(data), {
|
|
19605
19866
|
status,
|
|
19606
|
-
headers: { "Content-Type": "application/json", ...
|
|
19867
|
+
headers: { "Content-Type": "application/json", ...extraHeaders ?? {} }
|
|
19607
19868
|
});
|
|
19608
19869
|
}
|
|
19609
|
-
function notFound(msg) {
|
|
19870
|
+
function notFound(msg, extraHeaders) {
|
|
19610
19871
|
return new Response(JSON.stringify({ error: msg }), {
|
|
19611
19872
|
status: 404,
|
|
19612
|
-
headers: { "Content-Type": "application/json", ...
|
|
19873
|
+
headers: { "Content-Type": "application/json", ...extraHeaders ?? {} }
|
|
19613
19874
|
});
|
|
19614
19875
|
}
|
|
19615
|
-
function badRequest(msg) {
|
|
19876
|
+
function badRequest(msg, extraHeaders) {
|
|
19616
19877
|
return new Response(JSON.stringify({ error: msg }), {
|
|
19617
19878
|
status: 400,
|
|
19618
|
-
headers: { "Content-Type": "application/json", ...
|
|
19879
|
+
headers: { "Content-Type": "application/json", ...extraHeaders ?? {} }
|
|
19619
19880
|
});
|
|
19620
19881
|
}
|
|
19621
|
-
function serverError(e) {
|
|
19882
|
+
function serverError(e, extraHeaders) {
|
|
19622
19883
|
const msg = e instanceof Error ? e.message : String(e);
|
|
19623
19884
|
return new Response(JSON.stringify({ error: msg }), {
|
|
19624
19885
|
status: 500,
|
|
19625
|
-
headers: { "Content-Type": "application/json", ...
|
|
19886
|
+
headers: { "Content-Type": "application/json", ...extraHeaders ?? {} }
|
|
19626
19887
|
});
|
|
19627
19888
|
}
|
|
19628
19889
|
var server = Bun.serve({
|
|
@@ -19631,8 +19892,15 @@ var server = Bun.serve({
|
|
|
19631
19892
|
const url = new URL(req.url);
|
|
19632
19893
|
const path = url.pathname;
|
|
19633
19894
|
const method = req.method;
|
|
19895
|
+
const origin = req.headers.get("Origin") ?? undefined;
|
|
19896
|
+
const headers = corsHeaders(origin ?? null);
|
|
19634
19897
|
if (method === "OPTIONS") {
|
|
19635
|
-
return new Response(null, { status: 204, headers
|
|
19898
|
+
return new Response(null, { status: 204, headers });
|
|
19899
|
+
}
|
|
19900
|
+
if (!path.startsWith("/dashboard") && path !== "/health") {
|
|
19901
|
+
const authError = authenticate(req);
|
|
19902
|
+
if (authError)
|
|
19903
|
+
return authError;
|
|
19636
19904
|
}
|
|
19637
19905
|
try {
|
|
19638
19906
|
if (path === "/health" && method === "GET") {
|
|
@@ -19649,7 +19917,10 @@ var server = Bun.serve({
|
|
|
19649
19917
|
return ok({ sessions: listSessions2(status ? { status, projectId } : { projectId }) });
|
|
19650
19918
|
}
|
|
19651
19919
|
if (path === "/api/sessions" && method === "POST") {
|
|
19652
|
-
const
|
|
19920
|
+
const parsed = await safeJson(req);
|
|
19921
|
+
if ("error" in parsed)
|
|
19922
|
+
return parsed.error;
|
|
19923
|
+
const body = parsed.body;
|
|
19653
19924
|
const { session } = await createSession2({
|
|
19654
19925
|
engine: body.engine ?? "auto",
|
|
19655
19926
|
projectId: body.project_id,
|
|
@@ -19671,25 +19942,36 @@ var server = Bun.serve({
|
|
|
19671
19942
|
return ok({ session });
|
|
19672
19943
|
}
|
|
19673
19944
|
if (path === "/api/navigate" && method === "POST") {
|
|
19674
|
-
const
|
|
19945
|
+
const parsed = await safeJson(req);
|
|
19946
|
+
if ("error" in parsed)
|
|
19947
|
+
return parsed.error;
|
|
19948
|
+
const body = parsed.body;
|
|
19675
19949
|
if (!body.session_id || !body.url)
|
|
19676
|
-
return badRequest("session_id and url required");
|
|
19677
|
-
const
|
|
19678
|
-
|
|
19679
|
-
|
|
19950
|
+
return badRequest("session_id and url required", headers);
|
|
19951
|
+
const sessionId = body.session_id;
|
|
19952
|
+
const url2 = body.url;
|
|
19953
|
+
const page = getSessionPage(sessionId);
|
|
19954
|
+
await navigate(page, url2);
|
|
19955
|
+
return ok({ url: url2, title: await page.title(), current_url: page.url() });
|
|
19680
19956
|
}
|
|
19681
19957
|
if (path === "/api/extract" && method === "POST") {
|
|
19682
|
-
const
|
|
19958
|
+
const parsed = await safeJson(req);
|
|
19959
|
+
if ("error" in parsed)
|
|
19960
|
+
return parsed.error;
|
|
19961
|
+
const body = parsed.body;
|
|
19683
19962
|
if (!body.session_id)
|
|
19684
|
-
return badRequest("session_id required");
|
|
19963
|
+
return badRequest("session_id required", headers);
|
|
19685
19964
|
const page = getSessionPage(body.session_id);
|
|
19686
19965
|
const result = await extract(page, { format: body.format, selector: body.selector });
|
|
19687
19966
|
return ok(result);
|
|
19688
19967
|
}
|
|
19689
19968
|
if (path === "/api/screenshot" && method === "POST") {
|
|
19690
|
-
const
|
|
19969
|
+
const parsed = await safeJson(req);
|
|
19970
|
+
if ("error" in parsed)
|
|
19971
|
+
return parsed.error;
|
|
19972
|
+
const body = parsed.body;
|
|
19691
19973
|
if (!body.session_id)
|
|
19692
|
-
return badRequest("session_id required");
|
|
19974
|
+
return badRequest("session_id required", headers);
|
|
19693
19975
|
const page = getSessionPage(body.session_id);
|
|
19694
19976
|
const result = await takeScreenshot(page, { selector: body.selector, fullPage: body.full_page });
|
|
19695
19977
|
return ok(result);
|
|
@@ -19726,16 +20008,22 @@ var server = Bun.serve({
|
|
|
19726
20008
|
return ok({ metrics: await getPerformanceMetrics(page) });
|
|
19727
20009
|
}
|
|
19728
20010
|
if (path === "/api/har/start" && method === "POST") {
|
|
19729
|
-
const
|
|
20011
|
+
const parsed = await safeJson(req);
|
|
20012
|
+
if ("error" in parsed)
|
|
20013
|
+
return parsed.error;
|
|
20014
|
+
const body = parsed.body;
|
|
19730
20015
|
const page = getSessionPage(body.session_id);
|
|
19731
20016
|
harCaptures.set(body.session_id, startHAR(page));
|
|
19732
20017
|
return ok({ started: true });
|
|
19733
20018
|
}
|
|
19734
20019
|
if (path === "/api/har/stop" && method === "POST") {
|
|
19735
|
-
const
|
|
20020
|
+
const parsed = await safeJson(req);
|
|
20021
|
+
if ("error" in parsed)
|
|
20022
|
+
return parsed.error;
|
|
20023
|
+
const body = parsed.body;
|
|
19736
20024
|
const capture = harCaptures.get(body.session_id);
|
|
19737
20025
|
if (!capture)
|
|
19738
|
-
return notFound("No active HAR capture");
|
|
20026
|
+
return notFound("No active HAR capture", headers);
|
|
19739
20027
|
const har = capture.stop();
|
|
19740
20028
|
harCaptures.delete(body.session_id);
|
|
19741
20029
|
return ok({ har });
|
|
@@ -19744,8 +20032,11 @@ var server = Bun.serve({
|
|
|
19744
20032
|
return ok({ recordings: listRecordings(url.searchParams.get("project_id") ?? undefined) });
|
|
19745
20033
|
}
|
|
19746
20034
|
if (path.match(/^\/api\/recordings\/([^/]+)\/replay$/) && method === "POST") {
|
|
20035
|
+
const parsed = await safeJson(req);
|
|
20036
|
+
if ("error" in parsed)
|
|
20037
|
+
return parsed.error;
|
|
20038
|
+
const body = parsed.body;
|
|
19747
20039
|
const id = path.split("/")[3];
|
|
19748
|
-
const body = await req.json();
|
|
19749
20040
|
const page = getSessionPage(body.session_id);
|
|
19750
20041
|
const result = await replayRecording(id, page);
|
|
19751
20042
|
return ok(result);
|
|
@@ -19757,9 +20048,12 @@ var server = Bun.serve({
|
|
|
19757
20048
|
return ok({ deleted: id });
|
|
19758
20049
|
}
|
|
19759
20050
|
if (path === "/api/crawl" && method === "POST") {
|
|
19760
|
-
const
|
|
20051
|
+
const parsed = await safeJson(req);
|
|
20052
|
+
if ("error" in parsed)
|
|
20053
|
+
return parsed.error;
|
|
20054
|
+
const body = parsed.body;
|
|
19761
20055
|
if (!body.url)
|
|
19762
|
-
return badRequest("url required");
|
|
20056
|
+
return badRequest("url required", headers);
|
|
19763
20057
|
const result = await crawl(body.url, {
|
|
19764
20058
|
maxDepth: body.max_depth ?? 2,
|
|
19765
20059
|
maxPages: body.max_pages ?? 50,
|
|
@@ -19771,9 +20065,12 @@ var server = Bun.serve({
|
|
|
19771
20065
|
return ok({ agents: listAgents(url.searchParams.get("project_id") ?? undefined) });
|
|
19772
20066
|
}
|
|
19773
20067
|
if (path === "/api/agents" && method === "POST") {
|
|
19774
|
-
const
|
|
20068
|
+
const parsed = await safeJson(req);
|
|
20069
|
+
if ("error" in parsed)
|
|
20070
|
+
return parsed.error;
|
|
20071
|
+
const body = parsed.body;
|
|
19775
20072
|
if (!body.name)
|
|
19776
|
-
return badRequest("name required");
|
|
20073
|
+
return badRequest("name required", headers);
|
|
19777
20074
|
const agent = registerAgent2(body.name, { description: body.description, projectId: body.project_id, sessionId: body.session_id, workingDir: body.working_dir });
|
|
19778
20075
|
return ok({ agent }, 201);
|
|
19779
20076
|
}
|
|
@@ -19792,9 +20089,12 @@ var server = Bun.serve({
|
|
|
19792
20089
|
return ok({ projects: listProjects() });
|
|
19793
20090
|
}
|
|
19794
20091
|
if (path === "/api/projects" && method === "POST") {
|
|
19795
|
-
const
|
|
20092
|
+
const parsed = await safeJson(req);
|
|
20093
|
+
if ("error" in parsed)
|
|
20094
|
+
return parsed.error;
|
|
20095
|
+
const body = parsed.body;
|
|
19796
20096
|
if (!body.name || !body.path)
|
|
19797
|
-
return badRequest("name and path required");
|
|
20097
|
+
return badRequest("name and path required", headers);
|
|
19798
20098
|
const project = ensureProject(body.name, body.path, body.description);
|
|
19799
20099
|
return ok({ project }, 201);
|
|
19800
20100
|
}
|
|
@@ -19810,21 +20110,30 @@ var server = Bun.serve({
|
|
|
19810
20110
|
return ok(getGalleryStats(url.searchParams.get("project_id") ?? undefined));
|
|
19811
20111
|
}
|
|
19812
20112
|
if (path === "/api/gallery/diff" && method === "POST") {
|
|
19813
|
-
const
|
|
20113
|
+
const parsed = await safeJson(req);
|
|
20114
|
+
if ("error" in parsed)
|
|
20115
|
+
return parsed.error;
|
|
20116
|
+
const body = parsed.body;
|
|
19814
20117
|
const e1 = getEntry(body.id1);
|
|
19815
20118
|
const e2 = getEntry(body.id2);
|
|
19816
20119
|
if (!e1 || !e2)
|
|
19817
|
-
return notFound("Gallery entry not found");
|
|
20120
|
+
return notFound("Gallery entry not found", headers);
|
|
19818
20121
|
return ok(await diffImages(e1.path, e2.path));
|
|
19819
20122
|
}
|
|
19820
20123
|
if (path.match(/^\/api\/gallery\/([^/]+)\/tag$/) && method === "POST") {
|
|
20124
|
+
const parsed = await safeJson(req);
|
|
20125
|
+
if ("error" in parsed)
|
|
20126
|
+
return parsed.error;
|
|
20127
|
+
const body = parsed.body;
|
|
19821
20128
|
const id = path.split("/")[3];
|
|
19822
|
-
const body = await req.json();
|
|
19823
20129
|
return ok({ entry: tagEntry(id, body.tag) });
|
|
19824
20130
|
}
|
|
19825
20131
|
if (path.match(/^\/api\/gallery\/([^/]+)\/favorite$/) && method === "PUT") {
|
|
20132
|
+
const parsed = await safeJson(req);
|
|
20133
|
+
if ("error" in parsed)
|
|
20134
|
+
return parsed.error;
|
|
20135
|
+
const body = parsed.body;
|
|
19826
20136
|
const id = path.split("/")[3];
|
|
19827
|
-
const body = await req.json();
|
|
19828
20137
|
return ok({ entry: favoriteEntry(id, body.favorited) });
|
|
19829
20138
|
}
|
|
19830
20139
|
if (path.match(/^\/api\/gallery\/([^/]+)\/thumbnail$/) && method === "GET") {
|
|
@@ -19832,14 +20141,14 @@ var server = Bun.serve({
|
|
|
19832
20141
|
const entry = getEntry(id);
|
|
19833
20142
|
if (!entry?.thumbnail_path || !existsSync9(entry.thumbnail_path))
|
|
19834
20143
|
return notFound("Thumbnail not found");
|
|
19835
|
-
return new Response(Bun.file(entry.thumbnail_path), { headers: { ...
|
|
20144
|
+
return new Response(Bun.file(entry.thumbnail_path), { headers: { ...headers } });
|
|
19836
20145
|
}
|
|
19837
20146
|
if (path.match(/^\/api\/gallery\/([^/]+)\/image$/) && method === "GET") {
|
|
19838
20147
|
const id = path.split("/")[3];
|
|
19839
20148
|
const entry = getEntry(id);
|
|
19840
20149
|
if (!entry?.path || !existsSync9(entry.path))
|
|
19841
20150
|
return notFound("Image not found");
|
|
19842
|
-
return new Response(Bun.file(entry.path), { headers: { ...
|
|
20151
|
+
return new Response(Bun.file(entry.path), { headers: { ...headers } });
|
|
19843
20152
|
}
|
|
19844
20153
|
if (path.match(/^\/api\/gallery\/([^/]+)$/) && method === "DELETE") {
|
|
19845
20154
|
const id = path.split("/")[3];
|
|
@@ -19867,7 +20176,7 @@ var server = Bun.serve({
|
|
|
19867
20176
|
const file = getDownload(id);
|
|
19868
20177
|
if (!file || !existsSync9(file.path))
|
|
19869
20178
|
return notFound("Download not found");
|
|
19870
|
-
return new Response(Bun.file(file.path), { headers: { ...
|
|
20179
|
+
return new Response(Bun.file(file.path), { headers: { ...headers } });
|
|
19871
20180
|
}
|
|
19872
20181
|
if (path.match(/^\/api\/downloads\/([^/]+)$/) && method === "DELETE") {
|
|
19873
20182
|
const id = path.split("/")[3];
|
|
@@ -19875,15 +20184,21 @@ var server = Bun.serve({
|
|
|
19875
20184
|
}
|
|
19876
20185
|
const dashboardDist = join12(import.meta.dir, "../../dashboard/dist");
|
|
19877
20186
|
if (existsSync9(dashboardDist)) {
|
|
19878
|
-
const
|
|
20187
|
+
const cleanPath = path.replace(/^\//, "");
|
|
20188
|
+
if (cleanPath.includes("..") || cleanPath.startsWith("/"))
|
|
20189
|
+
return notFound("Not found", headers);
|
|
20190
|
+
const filePath = path === "/" ? join12(dashboardDist, "index.html") : join12(dashboardDist, cleanPath);
|
|
20191
|
+
const resolved = await Bun.file(filePath).arrayBuffer().then(() => join12(dashboardDist, cleanPath)) || "";
|
|
20192
|
+
if (!resolved.startsWith(dashboardDist))
|
|
20193
|
+
return notFound("Not found", headers);
|
|
19879
20194
|
if (existsSync9(filePath)) {
|
|
19880
|
-
return new Response(Bun.file(filePath), { headers
|
|
20195
|
+
return new Response(Bun.file(filePath), { headers });
|
|
19881
20196
|
}
|
|
19882
|
-
return new Response(Bun.file(join12(dashboardDist, "index.html")), { headers
|
|
20197
|
+
return new Response(Bun.file(join12(dashboardDist, "index.html")), { headers });
|
|
19883
20198
|
}
|
|
19884
20199
|
if (path === "/" || path === "") {
|
|
19885
20200
|
return new Response("@hasna/browser REST API running. Dashboard not built.", {
|
|
19886
|
-
headers: { "Content-Type": "text/plain", ...
|
|
20201
|
+
headers: { "Content-Type": "text/plain", ...headers }
|
|
19887
20202
|
});
|
|
19888
20203
|
}
|
|
19889
20204
|
return notFound(`Route not found: ${method} ${path}`);
|