@hasna/browser 0.0.3 → 0.0.5
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/dist/cli/index.js +906 -159
- package/dist/db/sessions.d.ts.map +1 -1
- package/dist/engines/bun-webview.d.ts +147 -0
- package/dist/engines/bun-webview.d.ts.map +1 -0
- package/dist/engines/bun-webview.test.d.ts +2 -0
- package/dist/engines/bun-webview.test.d.ts.map +1 -0
- package/dist/engines/selector.d.ts +2 -2
- package/dist/engines/selector.d.ts.map +1 -1
- package/dist/index.js +835 -285
- package/dist/lib/annotate.d.ts.map +1 -1
- package/dist/lib/extractor.d.ts.map +1 -1
- package/dist/lib/screenshot-v4.test.d.ts +2 -0
- package/dist/lib/screenshot-v4.test.d.ts.map +1 -0
- package/dist/lib/screenshot.d.ts.map +1 -1
- package/dist/lib/session.d.ts +3 -0
- package/dist/lib/session.d.ts.map +1 -1
- package/dist/lib/snapshot.d.ts +1 -0
- package/dist/lib/snapshot.d.ts.map +1 -1
- package/dist/mcp/index.js +896 -151
- package/dist/mcp/v4.test.d.ts +2 -0
- package/dist/mcp/v4.test.d.ts.map +1 -0
- package/dist/server/index.js +596 -93
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -273,6 +273,40 @@ function runMigrations(db) {
|
|
|
273
273
|
var _db = null, _dbPath = null;
|
|
274
274
|
var init_schema = () => {};
|
|
275
275
|
|
|
276
|
+
// src/db/console-log.ts
|
|
277
|
+
var exports_console_log = {};
|
|
278
|
+
__export(exports_console_log, {
|
|
279
|
+
logConsoleMessage: () => logConsoleMessage,
|
|
280
|
+
getConsoleMessage: () => getConsoleMessage,
|
|
281
|
+
getConsoleLog: () => getConsoleLog,
|
|
282
|
+
clearConsoleLog: () => clearConsoleLog
|
|
283
|
+
});
|
|
284
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
285
|
+
function logConsoleMessage(data) {
|
|
286
|
+
const db = getDatabase();
|
|
287
|
+
const id = randomUUID3();
|
|
288
|
+
db.prepare("INSERT INTO console_log (id, session_id, level, message, source, line_number) VALUES (?, ?, ?, ?, ?, ?)").run(id, data.session_id, data.level, data.message, data.source ?? null, data.line_number ?? null);
|
|
289
|
+
return getConsoleMessage(id);
|
|
290
|
+
}
|
|
291
|
+
function getConsoleMessage(id) {
|
|
292
|
+
const db = getDatabase();
|
|
293
|
+
return db.query("SELECT * FROM console_log WHERE id = ?").get(id) ?? null;
|
|
294
|
+
}
|
|
295
|
+
function getConsoleLog(sessionId, level) {
|
|
296
|
+
const db = getDatabase();
|
|
297
|
+
if (level) {
|
|
298
|
+
return db.query("SELECT * FROM console_log WHERE session_id = ? AND level = ? ORDER BY timestamp ASC").all(sessionId, level);
|
|
299
|
+
}
|
|
300
|
+
return db.query("SELECT * FROM console_log WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
|
|
301
|
+
}
|
|
302
|
+
function clearConsoleLog(sessionId) {
|
|
303
|
+
const db = getDatabase();
|
|
304
|
+
db.prepare("DELETE FROM console_log WHERE session_id = ?").run(sessionId);
|
|
305
|
+
}
|
|
306
|
+
var init_console_log = __esm(() => {
|
|
307
|
+
init_schema();
|
|
308
|
+
});
|
|
309
|
+
|
|
276
310
|
// node_modules/sharp/lib/is.js
|
|
277
311
|
var require_is = __commonJS((exports, module) => {
|
|
278
312
|
/*!
|
|
@@ -6867,7 +6901,7 @@ var init_snapshots = __esm(() => {
|
|
|
6867
6901
|
});
|
|
6868
6902
|
|
|
6869
6903
|
// src/server/index.ts
|
|
6870
|
-
import { join as
|
|
6904
|
+
import { join as join6 } from "path";
|
|
6871
6905
|
import { existsSync as existsSync3 } from "fs";
|
|
6872
6906
|
|
|
6873
6907
|
// src/lib/session.ts
|
|
@@ -6881,7 +6915,14 @@ import { randomUUID } from "crypto";
|
|
|
6881
6915
|
function createSession(data) {
|
|
6882
6916
|
const db = getDatabase();
|
|
6883
6917
|
const id = randomUUID();
|
|
6884
|
-
|
|
6918
|
+
let name = data.name ?? null;
|
|
6919
|
+
if (name) {
|
|
6920
|
+
const existing = db.query("SELECT id FROM sessions WHERE name = ?").get(name);
|
|
6921
|
+
if (existing) {
|
|
6922
|
+
name = `${name}-${id.slice(0, 6)}`;
|
|
6923
|
+
}
|
|
6924
|
+
}
|
|
6925
|
+
db.prepare("INSERT INTO sessions (id, engine, project_id, agent_id, start_url, name) VALUES (?, ?, ?, ?, ?, ?)").run(id, data.engine, data.projectId ?? null, data.agentId ?? null, data.startUrl ?? null, name);
|
|
6885
6926
|
return getSession(id);
|
|
6886
6927
|
}
|
|
6887
6928
|
function getSession(id) {
|
|
@@ -7044,15 +7085,414 @@ async function connectLightpanda(port) {
|
|
|
7044
7085
|
}
|
|
7045
7086
|
}
|
|
7046
7087
|
|
|
7088
|
+
// src/engines/bun-webview.ts
|
|
7089
|
+
import { join as join2 } from "path";
|
|
7090
|
+
import { mkdirSync as mkdirSync2 } from "fs";
|
|
7091
|
+
import { homedir as homedir2 } from "os";
|
|
7092
|
+
function isBunWebViewAvailable() {
|
|
7093
|
+
return typeof globalThis.Bun !== "undefined" && typeof globalThis.Bun.WebView !== "undefined";
|
|
7094
|
+
}
|
|
7095
|
+
function getProfileDir(profileName) {
|
|
7096
|
+
const base = process.env["BROWSER_DATA_DIR"] ?? join2(homedir2(), ".browser");
|
|
7097
|
+
const dir = join2(base, "profiles", profileName);
|
|
7098
|
+
mkdirSync2(dir, { recursive: true });
|
|
7099
|
+
return dir;
|
|
7100
|
+
}
|
|
7101
|
+
|
|
7102
|
+
class BunWebViewSession {
|
|
7103
|
+
view;
|
|
7104
|
+
_sessionId;
|
|
7105
|
+
_eventListeners = new Map;
|
|
7106
|
+
constructor(opts = {}) {
|
|
7107
|
+
if (!isBunWebViewAvailable()) {
|
|
7108
|
+
throw new Error("Bun.WebView is not available. Install Bun canary: bun upgrade --canary");
|
|
7109
|
+
}
|
|
7110
|
+
const BunWebView = globalThis.Bun.WebView;
|
|
7111
|
+
const constructorOpts = {
|
|
7112
|
+
width: opts.width ?? 1280,
|
|
7113
|
+
height: opts.height ?? 720
|
|
7114
|
+
};
|
|
7115
|
+
if (opts.profile) {
|
|
7116
|
+
constructorOpts.dataStore = { directory: getProfileDir(opts.profile) };
|
|
7117
|
+
} else {
|
|
7118
|
+
constructorOpts.dataStore = "ephemeral";
|
|
7119
|
+
}
|
|
7120
|
+
if (opts.onConsole) {
|
|
7121
|
+
constructorOpts.console = opts.onConsole;
|
|
7122
|
+
}
|
|
7123
|
+
this.view = new BunWebView(constructorOpts);
|
|
7124
|
+
this.view.onNavigated = (url) => {
|
|
7125
|
+
this._emit("navigated", url);
|
|
7126
|
+
};
|
|
7127
|
+
this.view.onNavigationFailed = (error) => {
|
|
7128
|
+
this._emit("navigationfailed", error);
|
|
7129
|
+
};
|
|
7130
|
+
}
|
|
7131
|
+
async goto(url, opts) {
|
|
7132
|
+
await this.view.navigate(url);
|
|
7133
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
7134
|
+
}
|
|
7135
|
+
async goBack() {
|
|
7136
|
+
await this.view.goBack();
|
|
7137
|
+
}
|
|
7138
|
+
async goForward() {
|
|
7139
|
+
await this.view.goForward();
|
|
7140
|
+
}
|
|
7141
|
+
async reload() {
|
|
7142
|
+
await this.view.reload();
|
|
7143
|
+
}
|
|
7144
|
+
async evaluate(fnOrExpr, ...args) {
|
|
7145
|
+
let expr;
|
|
7146
|
+
if (typeof fnOrExpr === "function") {
|
|
7147
|
+
const serializedArgs = args.map((a) => JSON.stringify(a)).join(", ");
|
|
7148
|
+
expr = `(${fnOrExpr.toString()})(${serializedArgs})`;
|
|
7149
|
+
} else {
|
|
7150
|
+
expr = fnOrExpr;
|
|
7151
|
+
}
|
|
7152
|
+
return this.view.evaluate(expr);
|
|
7153
|
+
}
|
|
7154
|
+
async screenshot(opts) {
|
|
7155
|
+
const uint8 = await this.view.screenshot();
|
|
7156
|
+
return Buffer.from(uint8);
|
|
7157
|
+
}
|
|
7158
|
+
async click(selector, opts) {
|
|
7159
|
+
await this.view.click(selector, opts ? { button: opts.button } : undefined);
|
|
7160
|
+
}
|
|
7161
|
+
async type(selector, text, opts) {
|
|
7162
|
+
try {
|
|
7163
|
+
await this.view.click(selector);
|
|
7164
|
+
} catch {}
|
|
7165
|
+
await this.view.type(text);
|
|
7166
|
+
}
|
|
7167
|
+
async fill(selector, value) {
|
|
7168
|
+
await this.view.evaluate(`
|
|
7169
|
+
(() => {
|
|
7170
|
+
const el = document.querySelector(${JSON.stringify(selector)});
|
|
7171
|
+
if (el) { el.value = ''; el.dispatchEvent(new Event('input')); }
|
|
7172
|
+
})()
|
|
7173
|
+
`);
|
|
7174
|
+
await this.type(selector, value);
|
|
7175
|
+
}
|
|
7176
|
+
async press(key, opts) {
|
|
7177
|
+
await this.view.press(key, opts);
|
|
7178
|
+
}
|
|
7179
|
+
async scroll(direction, amount) {
|
|
7180
|
+
const dx = direction === "left" ? -amount : direction === "right" ? amount : 0;
|
|
7181
|
+
const dy = direction === "up" ? -amount : direction === "down" ? amount : 0;
|
|
7182
|
+
await this.view.scroll(dx, dy);
|
|
7183
|
+
}
|
|
7184
|
+
async scrollIntoView(selector) {
|
|
7185
|
+
await this.view.scrollTo(selector);
|
|
7186
|
+
}
|
|
7187
|
+
async hover(selector) {
|
|
7188
|
+
try {
|
|
7189
|
+
await this.view.scrollTo(selector);
|
|
7190
|
+
} catch {}
|
|
7191
|
+
}
|
|
7192
|
+
async resize(width, height) {
|
|
7193
|
+
await this.view.resize(width, height);
|
|
7194
|
+
}
|
|
7195
|
+
async $(selector) {
|
|
7196
|
+
const exists = await this.view.evaluate(`!!document.querySelector(${JSON.stringify(selector)})`);
|
|
7197
|
+
if (!exists)
|
|
7198
|
+
return null;
|
|
7199
|
+
return {
|
|
7200
|
+
textContent: async () => this.view.evaluate(`document.querySelector(${JSON.stringify(selector)})?.textContent ?? null`)
|
|
7201
|
+
};
|
|
7202
|
+
}
|
|
7203
|
+
async $$(selector) {
|
|
7204
|
+
const count = await this.view.evaluate(`document.querySelectorAll(${JSON.stringify(selector)}).length`);
|
|
7205
|
+
return Array.from({ length: count }, (_, i) => ({
|
|
7206
|
+
textContent: async () => this.view.evaluate(`document.querySelectorAll(${JSON.stringify(selector)})[${i}]?.textContent ?? null`)
|
|
7207
|
+
}));
|
|
7208
|
+
}
|
|
7209
|
+
async inputValue(selector) {
|
|
7210
|
+
return this.view.evaluate(`document.querySelector(${JSON.stringify(selector)})?.value ?? ''`);
|
|
7211
|
+
}
|
|
7212
|
+
async isChecked(selector) {
|
|
7213
|
+
return this.view.evaluate(`!!(document.querySelector(${JSON.stringify(selector)})?.checked)`);
|
|
7214
|
+
}
|
|
7215
|
+
async isVisible(selector) {
|
|
7216
|
+
return this.view.evaluate(`
|
|
7217
|
+
(() => {
|
|
7218
|
+
const el = document.querySelector(${JSON.stringify(selector)});
|
|
7219
|
+
if (!el) return false;
|
|
7220
|
+
const style = window.getComputedStyle(el);
|
|
7221
|
+
return style.display !== 'none' && style.visibility !== 'hidden' && el.offsetWidth > 0;
|
|
7222
|
+
})()
|
|
7223
|
+
`);
|
|
7224
|
+
}
|
|
7225
|
+
async isEnabled(selector) {
|
|
7226
|
+
return this.view.evaluate(`!(document.querySelector(${JSON.stringify(selector)})?.disabled)`);
|
|
7227
|
+
}
|
|
7228
|
+
async selectOption(selector, value) {
|
|
7229
|
+
await this.view.evaluate(`
|
|
7230
|
+
(() => {
|
|
7231
|
+
const el = document.querySelector(${JSON.stringify(selector)});
|
|
7232
|
+
if (el) {
|
|
7233
|
+
el.value = ${JSON.stringify(value)};
|
|
7234
|
+
el.dispatchEvent(new Event('change'));
|
|
7235
|
+
}
|
|
7236
|
+
})()
|
|
7237
|
+
`);
|
|
7238
|
+
return [value];
|
|
7239
|
+
}
|
|
7240
|
+
async check(selector) {
|
|
7241
|
+
await this.view.evaluate(`
|
|
7242
|
+
(() => {
|
|
7243
|
+
const el = document.querySelector(${JSON.stringify(selector)});
|
|
7244
|
+
if (el && !el.checked) { el.checked = true; el.dispatchEvent(new Event('change')); }
|
|
7245
|
+
})()
|
|
7246
|
+
`);
|
|
7247
|
+
}
|
|
7248
|
+
async uncheck(selector) {
|
|
7249
|
+
await this.view.evaluate(`
|
|
7250
|
+
(() => {
|
|
7251
|
+
const el = document.querySelector(${JSON.stringify(selector)});
|
|
7252
|
+
if (el && el.checked) { el.checked = false; el.dispatchEvent(new Event('change')); }
|
|
7253
|
+
})()
|
|
7254
|
+
`);
|
|
7255
|
+
}
|
|
7256
|
+
async setInputFiles(selector, files) {
|
|
7257
|
+
throw new Error("File upload not supported in Bun.WebView engine. Use engine: 'playwright' instead.");
|
|
7258
|
+
}
|
|
7259
|
+
getByRole(role, opts) {
|
|
7260
|
+
const name = opts?.name?.toString() ?? "";
|
|
7261
|
+
const selector = name ? `[role="${role}"][aria-label*="${name}"], ${role}[aria-label*="${name}"]` : `[role="${role}"], ${role}`;
|
|
7262
|
+
return {
|
|
7263
|
+
click: (clickOpts) => this.click(selector, clickOpts),
|
|
7264
|
+
fill: (value) => this.fill(selector, value),
|
|
7265
|
+
check: () => this.check(selector),
|
|
7266
|
+
uncheck: () => this.uncheck(selector),
|
|
7267
|
+
isVisible: () => this.isVisible(selector),
|
|
7268
|
+
textContent: () => this.view.evaluate(`document.querySelector(${JSON.stringify(selector)})?.textContent ?? null`),
|
|
7269
|
+
inputValue: () => this.inputValue(selector),
|
|
7270
|
+
first: () => ({
|
|
7271
|
+
click: (clickOpts) => this.click(selector, clickOpts),
|
|
7272
|
+
fill: (value) => this.fill(selector, value),
|
|
7273
|
+
textContent: () => this.view.evaluate(`document.querySelector(${JSON.stringify(selector)})?.textContent ?? null`),
|
|
7274
|
+
isVisible: () => this.isVisible(selector),
|
|
7275
|
+
hover: () => this.hover(selector),
|
|
7276
|
+
boundingBox: async () => null,
|
|
7277
|
+
scrollIntoViewIfNeeded: () => this.scrollIntoView(selector),
|
|
7278
|
+
evaluate: (fn) => this.view.evaluate(`(${fn.toString()})(document.querySelector(${JSON.stringify(selector)}))`),
|
|
7279
|
+
waitFor: (opts2) => {
|
|
7280
|
+
return new Promise((resolve, reject) => {
|
|
7281
|
+
const timeout = opts2?.timeout ?? 1e4;
|
|
7282
|
+
const start = Date.now();
|
|
7283
|
+
const check = async () => {
|
|
7284
|
+
const visible = await this.isVisible(selector);
|
|
7285
|
+
if (visible)
|
|
7286
|
+
return resolve();
|
|
7287
|
+
if (Date.now() - start > timeout)
|
|
7288
|
+
return reject(new Error(`Timeout waiting for ${selector}`));
|
|
7289
|
+
setTimeout(check, 100);
|
|
7290
|
+
};
|
|
7291
|
+
check();
|
|
7292
|
+
});
|
|
7293
|
+
}
|
|
7294
|
+
}),
|
|
7295
|
+
count: async () => {
|
|
7296
|
+
const count = await this.view.evaluate(`document.querySelectorAll(${JSON.stringify(selector)}).length`);
|
|
7297
|
+
return count;
|
|
7298
|
+
},
|
|
7299
|
+
nth: (n) => ({
|
|
7300
|
+
click: (clickOpts) => this.click(selector, clickOpts),
|
|
7301
|
+
textContent: () => this.view.evaluate(`document.querySelectorAll(${JSON.stringify(selector)})[${n}]?.textContent ?? null`),
|
|
7302
|
+
isVisible: () => this.isVisible(selector)
|
|
7303
|
+
})
|
|
7304
|
+
};
|
|
7305
|
+
}
|
|
7306
|
+
getByText(text, opts) {
|
|
7307
|
+
const selector = opts?.exact ? `*:is(button, a, span, div, p, h1, h2, h3, h4, label)` : "*";
|
|
7308
|
+
return {
|
|
7309
|
+
first: () => ({
|
|
7310
|
+
click: async (clickOpts) => {
|
|
7311
|
+
await this.view.evaluate(`
|
|
7312
|
+
(() => {
|
|
7313
|
+
const text = ${JSON.stringify(text)};
|
|
7314
|
+
const all = document.querySelectorAll('*');
|
|
7315
|
+
for (const el of all) {
|
|
7316
|
+
if (el.children.length === 0 && el.textContent?.trim() === text) {
|
|
7317
|
+
el.click(); return;
|
|
7318
|
+
}
|
|
7319
|
+
}
|
|
7320
|
+
for (const el of all) {
|
|
7321
|
+
if (el.textContent?.includes(text)) { el.click(); return; }
|
|
7322
|
+
}
|
|
7323
|
+
})()
|
|
7324
|
+
`);
|
|
7325
|
+
},
|
|
7326
|
+
waitFor: (waitOpts) => {
|
|
7327
|
+
const timeout = waitOpts?.timeout ?? 1e4;
|
|
7328
|
+
return new Promise((resolve, reject) => {
|
|
7329
|
+
const start = Date.now();
|
|
7330
|
+
const check = async () => {
|
|
7331
|
+
const found = await this.view.evaluate(`document.body?.textContent?.includes(${JSON.stringify(text)})`);
|
|
7332
|
+
if (found)
|
|
7333
|
+
return resolve();
|
|
7334
|
+
if (Date.now() - start > timeout)
|
|
7335
|
+
return reject(new Error(`Timeout: text "${text}" not found`));
|
|
7336
|
+
setTimeout(check, 100);
|
|
7337
|
+
};
|
|
7338
|
+
check();
|
|
7339
|
+
});
|
|
7340
|
+
}
|
|
7341
|
+
})
|
|
7342
|
+
};
|
|
7343
|
+
}
|
|
7344
|
+
locator(selector) {
|
|
7345
|
+
return {
|
|
7346
|
+
click: (opts) => this.click(selector, opts),
|
|
7347
|
+
fill: (value) => this.fill(selector, value),
|
|
7348
|
+
scrollIntoViewIfNeeded: () => this.scrollIntoView(selector),
|
|
7349
|
+
first: () => this.getByRole("*").first(),
|
|
7350
|
+
evaluate: (fn) => this.view.evaluate(`(${fn.toString()})(document.querySelector(${JSON.stringify(selector)}))`),
|
|
7351
|
+
waitFor: (opts) => {
|
|
7352
|
+
const timeout = opts?.timeout ?? 1e4;
|
|
7353
|
+
return new Promise((resolve, reject) => {
|
|
7354
|
+
const start = Date.now();
|
|
7355
|
+
const check = async () => {
|
|
7356
|
+
const exists = await this.view.evaluate(`!!document.querySelector(${JSON.stringify(selector)})`);
|
|
7357
|
+
if (exists)
|
|
7358
|
+
return resolve();
|
|
7359
|
+
if (Date.now() - start > timeout)
|
|
7360
|
+
return reject(new Error(`Timeout: ${selector}`));
|
|
7361
|
+
setTimeout(check, 100);
|
|
7362
|
+
};
|
|
7363
|
+
check();
|
|
7364
|
+
});
|
|
7365
|
+
}
|
|
7366
|
+
};
|
|
7367
|
+
}
|
|
7368
|
+
url() {
|
|
7369
|
+
return this.view.url;
|
|
7370
|
+
}
|
|
7371
|
+
async title() {
|
|
7372
|
+
return this.view.title || await this.evaluate("document.title");
|
|
7373
|
+
}
|
|
7374
|
+
viewportSize() {
|
|
7375
|
+
return { width: 1280, height: 720 };
|
|
7376
|
+
}
|
|
7377
|
+
async waitForLoadState(state, opts) {
|
|
7378
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
7379
|
+
}
|
|
7380
|
+
async waitForURL(pattern, opts) {
|
|
7381
|
+
const timeout = opts?.timeout ?? 30000;
|
|
7382
|
+
const start = Date.now();
|
|
7383
|
+
while (Date.now() - start < timeout) {
|
|
7384
|
+
const url = this.view.url;
|
|
7385
|
+
const matches = pattern instanceof RegExp ? pattern.test(url) : url.includes(pattern);
|
|
7386
|
+
if (matches)
|
|
7387
|
+
return;
|
|
7388
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
7389
|
+
}
|
|
7390
|
+
throw new Error(`Timeout waiting for URL to match ${pattern}`);
|
|
7391
|
+
}
|
|
7392
|
+
async waitForSelector(selector, opts) {
|
|
7393
|
+
const timeout = opts?.timeout ?? 1e4;
|
|
7394
|
+
const start = Date.now();
|
|
7395
|
+
while (Date.now() - start < timeout) {
|
|
7396
|
+
const exists = await this.view.evaluate(`!!document.querySelector(${JSON.stringify(selector)})`);
|
|
7397
|
+
if (exists)
|
|
7398
|
+
return;
|
|
7399
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
7400
|
+
}
|
|
7401
|
+
throw new Error(`Timeout waiting for ${selector}`);
|
|
7402
|
+
}
|
|
7403
|
+
async setContent(html) {
|
|
7404
|
+
await this.view.navigate(`data:text/html,${encodeURIComponent(html)}`);
|
|
7405
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
7406
|
+
}
|
|
7407
|
+
async content() {
|
|
7408
|
+
return this.view.evaluate("document.documentElement.outerHTML");
|
|
7409
|
+
}
|
|
7410
|
+
async addInitScript(script) {
|
|
7411
|
+
const expr = typeof script === "function" ? `(${script.toString()})()` : script;
|
|
7412
|
+
await this.view.evaluate(expr);
|
|
7413
|
+
}
|
|
7414
|
+
keyboard = {
|
|
7415
|
+
press: (key) => this.view.press(key)
|
|
7416
|
+
};
|
|
7417
|
+
context() {
|
|
7418
|
+
return {
|
|
7419
|
+
close: async () => {
|
|
7420
|
+
await this.close();
|
|
7421
|
+
},
|
|
7422
|
+
newPage: async () => {
|
|
7423
|
+
throw new Error("Multi-tab not supported in Bun.WebView. Use engine: 'playwright'");
|
|
7424
|
+
},
|
|
7425
|
+
cookies: async () => [],
|
|
7426
|
+
addCookies: async (_) => {},
|
|
7427
|
+
clearCookies: async () => {},
|
|
7428
|
+
newCDPSession: async () => {
|
|
7429
|
+
throw new Error("CDP session via context not available in Bun.WebView. Use view.cdp() when shipped.");
|
|
7430
|
+
},
|
|
7431
|
+
route: async (_pattern, _handler) => {
|
|
7432
|
+
throw new Error("Network interception not supported in Bun.WebView. Use engine: 'cdp' or 'playwright'.");
|
|
7433
|
+
},
|
|
7434
|
+
unrouteAll: async () => {},
|
|
7435
|
+
pages: () => [],
|
|
7436
|
+
addInitScript: async (script) => {
|
|
7437
|
+
await this.addInitScript(script);
|
|
7438
|
+
}
|
|
7439
|
+
};
|
|
7440
|
+
}
|
|
7441
|
+
on(event, handler) {
|
|
7442
|
+
if (!this._eventListeners.has(event))
|
|
7443
|
+
this._eventListeners.set(event, []);
|
|
7444
|
+
this._eventListeners.get(event).push(handler);
|
|
7445
|
+
return this;
|
|
7446
|
+
}
|
|
7447
|
+
off(event, handler) {
|
|
7448
|
+
const listeners = this._eventListeners.get(event) ?? [];
|
|
7449
|
+
this._eventListeners.set(event, listeners.filter((l) => l !== handler));
|
|
7450
|
+
return this;
|
|
7451
|
+
}
|
|
7452
|
+
_emit(event, ...args) {
|
|
7453
|
+
for (const handler of this._eventListeners.get(event) ?? []) {
|
|
7454
|
+
try {
|
|
7455
|
+
handler(...args);
|
|
7456
|
+
} catch {}
|
|
7457
|
+
}
|
|
7458
|
+
}
|
|
7459
|
+
async pdf(_opts) {
|
|
7460
|
+
throw new Error("PDF generation not supported in Bun.WebView. Use engine: 'playwright'.");
|
|
7461
|
+
}
|
|
7462
|
+
coverage = {
|
|
7463
|
+
startJSCoverage: async () => {},
|
|
7464
|
+
stopJSCoverage: async () => [],
|
|
7465
|
+
startCSSCoverage: async () => {},
|
|
7466
|
+
stopCSSCoverage: async () => []
|
|
7467
|
+
};
|
|
7468
|
+
setSessionId(id) {
|
|
7469
|
+
this._sessionId = id;
|
|
7470
|
+
}
|
|
7471
|
+
getSessionId() {
|
|
7472
|
+
return this._sessionId;
|
|
7473
|
+
}
|
|
7474
|
+
getNativeView() {
|
|
7475
|
+
return this.view;
|
|
7476
|
+
}
|
|
7477
|
+
async close() {
|
|
7478
|
+
try {
|
|
7479
|
+
await this.view.close();
|
|
7480
|
+
} catch {}
|
|
7481
|
+
}
|
|
7482
|
+
[Symbol.asyncDispose]() {
|
|
7483
|
+
return this.close();
|
|
7484
|
+
}
|
|
7485
|
+
}
|
|
7486
|
+
|
|
7047
7487
|
// src/engines/selector.ts
|
|
7048
7488
|
init_types();
|
|
7049
7489
|
var ENGINE_MAP = {
|
|
7050
|
-
["scrape" /* SCRAPE */]: "
|
|
7051
|
-
["extract_links" /* EXTRACT_LINKS */]: "
|
|
7052
|
-
["status_check" /* STATUS_CHECK */]: "
|
|
7490
|
+
["scrape" /* SCRAPE */]: "bun",
|
|
7491
|
+
["extract_links" /* EXTRACT_LINKS */]: "bun",
|
|
7492
|
+
["status_check" /* STATUS_CHECK */]: "bun",
|
|
7493
|
+
["screenshot" /* SCREENSHOT */]: "bun",
|
|
7494
|
+
["spa_navigate" /* SPA_NAVIGATE */]: "bun",
|
|
7053
7495
|
["form_fill" /* FORM_FILL */]: "playwright",
|
|
7054
|
-
["spa_navigate" /* SPA_NAVIGATE */]: "playwright",
|
|
7055
|
-
["screenshot" /* SCREENSHOT */]: "playwright",
|
|
7056
7496
|
["auth_flow" /* AUTH_FLOW */]: "playwright",
|
|
7057
7497
|
["multi_tab" /* MULTI_TAB */]: "playwright",
|
|
7058
7498
|
["record_replay" /* RECORD_REPLAY */]: "playwright",
|
|
@@ -7066,6 +7506,14 @@ function selectEngine(useCase, explicit) {
|
|
|
7066
7506
|
if (explicit && explicit !== "auto")
|
|
7067
7507
|
return explicit;
|
|
7068
7508
|
const preferred = ENGINE_MAP[useCase];
|
|
7509
|
+
if (preferred === "bun") {
|
|
7510
|
+
if (isBunWebViewAvailable())
|
|
7511
|
+
return "bun";
|
|
7512
|
+
if (useCase === "scrape" /* SCRAPE */ || useCase === "extract_links" /* EXTRACT_LINKS */ || useCase === "status_check" /* STATUS_CHECK */) {
|
|
7513
|
+
return isLightpandaAvailable() ? "lightpanda" : "playwright";
|
|
7514
|
+
}
|
|
7515
|
+
return "playwright";
|
|
7516
|
+
}
|
|
7069
7517
|
if (preferred === "lightpanda" && !isLightpandaAvailable()) {
|
|
7070
7518
|
return "playwright";
|
|
7071
7519
|
}
|
|
@@ -7185,28 +7633,8 @@ function startHAR(page) {
|
|
|
7185
7633
|
};
|
|
7186
7634
|
}
|
|
7187
7635
|
|
|
7188
|
-
// src/db/console-log.ts
|
|
7189
|
-
init_schema();
|
|
7190
|
-
import { randomUUID as randomUUID3 } from "crypto";
|
|
7191
|
-
function logConsoleMessage(data) {
|
|
7192
|
-
const db = getDatabase();
|
|
7193
|
-
const id = randomUUID3();
|
|
7194
|
-
db.prepare("INSERT INTO console_log (id, session_id, level, message, source, line_number) VALUES (?, ?, ?, ?, ?, ?)").run(id, data.session_id, data.level, data.message, data.source ?? null, data.line_number ?? null);
|
|
7195
|
-
return getConsoleMessage(id);
|
|
7196
|
-
}
|
|
7197
|
-
function getConsoleMessage(id) {
|
|
7198
|
-
const db = getDatabase();
|
|
7199
|
-
return db.query("SELECT * FROM console_log WHERE id = ?").get(id) ?? null;
|
|
7200
|
-
}
|
|
7201
|
-
function getConsoleLog(sessionId, level) {
|
|
7202
|
-
const db = getDatabase();
|
|
7203
|
-
if (level) {
|
|
7204
|
-
return db.query("SELECT * FROM console_log WHERE session_id = ? AND level = ? ORDER BY timestamp ASC").all(sessionId, level);
|
|
7205
|
-
}
|
|
7206
|
-
return db.query("SELECT * FROM console_log WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
|
|
7207
|
-
}
|
|
7208
|
-
|
|
7209
7636
|
// src/lib/console.ts
|
|
7637
|
+
init_console_log();
|
|
7210
7638
|
function enableConsoleCapture(page, sessionId) {
|
|
7211
7639
|
const onConsole = (msg) => {
|
|
7212
7640
|
const levelMap = {
|
|
@@ -7341,12 +7769,30 @@ function setupDialogHandler(page, sessionId) {
|
|
|
7341
7769
|
|
|
7342
7770
|
// src/lib/session.ts
|
|
7343
7771
|
var handles = new Map;
|
|
7772
|
+
function createBunProxy(view) {
|
|
7773
|
+
return view;
|
|
7774
|
+
}
|
|
7344
7775
|
async function createSession2(opts = {}) {
|
|
7345
7776
|
const engine = opts.engine === "auto" || !opts.engine ? selectEngine(opts.useCase ?? "spa_navigate" /* SPA_NAVIGATE */, opts.engine) : opts.engine;
|
|
7346
7777
|
const resolvedEngine = engine === "auto" ? "playwright" : engine;
|
|
7347
|
-
let browser;
|
|
7778
|
+
let browser = null;
|
|
7779
|
+
let bunView = null;
|
|
7348
7780
|
let page;
|
|
7349
|
-
if (resolvedEngine === "
|
|
7781
|
+
if (resolvedEngine === "bun") {
|
|
7782
|
+
if (!isBunWebViewAvailable()) {
|
|
7783
|
+
console.warn("[browser] Bun.WebView requested but not available \u2014 falling back to playwright. Run: bun upgrade --canary");
|
|
7784
|
+
browser = await launchPlaywright({ headless: opts.headless ?? true, viewport: opts.viewport, userAgent: opts.userAgent });
|
|
7785
|
+
page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
|
|
7786
|
+
} else {
|
|
7787
|
+
bunView = new BunWebViewSession({
|
|
7788
|
+
width: opts.viewport?.width ?? 1280,
|
|
7789
|
+
height: opts.viewport?.height ?? 720,
|
|
7790
|
+
profile: opts.name ?? undefined
|
|
7791
|
+
});
|
|
7792
|
+
if (opts.stealth) {}
|
|
7793
|
+
page = createBunProxy(bunView);
|
|
7794
|
+
}
|
|
7795
|
+
} else if (resolvedEngine === "lightpanda") {
|
|
7350
7796
|
browser = await connectLightpanda();
|
|
7351
7797
|
const context = await browser.newContext({ viewport: opts.viewport ?? { width: 1280, height: 720 } });
|
|
7352
7798
|
page = await context.newPage();
|
|
@@ -7356,41 +7802,67 @@ async function createSession2(opts = {}) {
|
|
|
7356
7802
|
viewport: opts.viewport,
|
|
7357
7803
|
userAgent: opts.userAgent
|
|
7358
7804
|
});
|
|
7359
|
-
page = await getPage(browser, {
|
|
7360
|
-
viewport: opts.viewport,
|
|
7361
|
-
userAgent: opts.userAgent
|
|
7362
|
-
});
|
|
7805
|
+
page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
|
|
7363
7806
|
}
|
|
7807
|
+
const sessionName = opts.name ?? (opts.startUrl ? (() => {
|
|
7808
|
+
try {
|
|
7809
|
+
return new URL(opts.startUrl).hostname;
|
|
7810
|
+
} catch {
|
|
7811
|
+
return;
|
|
7812
|
+
}
|
|
7813
|
+
})() : undefined);
|
|
7364
7814
|
const session = createSession({
|
|
7365
|
-
engine: resolvedEngine,
|
|
7815
|
+
engine: bunView ? "bun" : browser ? resolvedEngine : resolvedEngine,
|
|
7366
7816
|
projectId: opts.projectId,
|
|
7367
7817
|
agentId: opts.agentId,
|
|
7368
7818
|
startUrl: opts.startUrl,
|
|
7369
|
-
name:
|
|
7819
|
+
name: sessionName
|
|
7370
7820
|
});
|
|
7371
|
-
if (opts.stealth) {
|
|
7821
|
+
if (opts.stealth && !bunView) {
|
|
7372
7822
|
try {
|
|
7373
7823
|
await applyStealthPatches(page);
|
|
7374
7824
|
} catch {}
|
|
7375
7825
|
}
|
|
7376
7826
|
const cleanups = [];
|
|
7377
|
-
if (
|
|
7378
|
-
|
|
7379
|
-
|
|
7380
|
-
|
|
7381
|
-
|
|
7382
|
-
|
|
7827
|
+
if (!bunView) {
|
|
7828
|
+
if (opts.captureNetwork !== false) {
|
|
7829
|
+
try {
|
|
7830
|
+
cleanups.push(enableNetworkLogging(page, session.id));
|
|
7831
|
+
} catch {}
|
|
7832
|
+
}
|
|
7833
|
+
if (opts.captureConsole !== false) {
|
|
7834
|
+
try {
|
|
7835
|
+
cleanups.push(enableConsoleCapture(page, session.id));
|
|
7836
|
+
} catch {}
|
|
7837
|
+
}
|
|
7383
7838
|
try {
|
|
7384
|
-
cleanups.push(
|
|
7839
|
+
cleanups.push(setupDialogHandler(page, session.id));
|
|
7385
7840
|
} catch {}
|
|
7841
|
+
} else {
|
|
7842
|
+
if (opts.captureConsole !== false) {
|
|
7843
|
+
try {
|
|
7844
|
+
const { logConsoleMessage: logConsoleMessage2 } = await Promise.resolve().then(() => (init_console_log(), exports_console_log));
|
|
7845
|
+
await bunView.addInitScript(`
|
|
7846
|
+
(() => {
|
|
7847
|
+
const orig = { log: console.log, warn: console.warn, error: console.error, debug: console.debug, info: console.info };
|
|
7848
|
+
['log','warn','error','debug','info'].forEach(level => {
|
|
7849
|
+
console[level] = (...args) => {
|
|
7850
|
+
orig[level](...args);
|
|
7851
|
+
};
|
|
7852
|
+
});
|
|
7853
|
+
})()
|
|
7854
|
+
`);
|
|
7855
|
+
} catch {}
|
|
7856
|
+
}
|
|
7386
7857
|
}
|
|
7387
|
-
|
|
7388
|
-
cleanups.push(setupDialogHandler(page, session.id));
|
|
7389
|
-
} catch {}
|
|
7390
|
-
handles.set(session.id, { browser, page, engine: resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 } });
|
|
7858
|
+
handles.set(session.id, { browser, bunView, page, engine: bunView ? "bun" : resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 } });
|
|
7391
7859
|
if (opts.startUrl) {
|
|
7392
7860
|
try {
|
|
7393
|
-
|
|
7861
|
+
if (bunView) {
|
|
7862
|
+
await bunView.goto(opts.startUrl);
|
|
7863
|
+
} else {
|
|
7864
|
+
await page.goto(opts.startUrl, { waitUntil: "domcontentloaded" });
|
|
7865
|
+
}
|
|
7394
7866
|
} catch {}
|
|
7395
7867
|
}
|
|
7396
7868
|
return { session, page };
|
|
@@ -7400,7 +7872,11 @@ function getSessionPage(sessionId) {
|
|
|
7400
7872
|
if (!handle)
|
|
7401
7873
|
throw new SessionNotFoundError(sessionId);
|
|
7402
7874
|
try {
|
|
7403
|
-
handle.
|
|
7875
|
+
if (handle.bunView) {
|
|
7876
|
+
handle.bunView.url();
|
|
7877
|
+
} else {
|
|
7878
|
+
handle.page.url();
|
|
7879
|
+
}
|
|
7404
7880
|
} catch {
|
|
7405
7881
|
handles.delete(sessionId);
|
|
7406
7882
|
throw new SessionNotFoundError(sessionId);
|
|
@@ -7415,12 +7891,19 @@ async function closeSession2(sessionId) {
|
|
|
7415
7891
|
cleanup();
|
|
7416
7892
|
} catch {}
|
|
7417
7893
|
}
|
|
7418
|
-
|
|
7419
|
-
|
|
7420
|
-
|
|
7421
|
-
|
|
7422
|
-
|
|
7423
|
-
|
|
7894
|
+
if (handle.bunView) {
|
|
7895
|
+
try {
|
|
7896
|
+
await handle.bunView.close();
|
|
7897
|
+
} catch {}
|
|
7898
|
+
} else {
|
|
7899
|
+
try {
|
|
7900
|
+
await handle.page.context().close();
|
|
7901
|
+
} catch {}
|
|
7902
|
+
try {
|
|
7903
|
+
if (handle.browser)
|
|
7904
|
+
await closeBrowser(handle.browser);
|
|
7905
|
+
} catch {}
|
|
7906
|
+
}
|
|
7424
7907
|
handles.delete(sessionId);
|
|
7425
7908
|
}
|
|
7426
7909
|
return closeSession(sessionId);
|
|
@@ -7562,9 +8045,9 @@ async function extract(page, opts = {}) {
|
|
|
7562
8045
|
// src/lib/screenshot.ts
|
|
7563
8046
|
init_types();
|
|
7564
8047
|
var import_sharp = __toESM(require_lib(), 1);
|
|
7565
|
-
import { join as
|
|
7566
|
-
import { mkdirSync as
|
|
7567
|
-
import { homedir as
|
|
8048
|
+
import { join as join3 } from "path";
|
|
8049
|
+
import { mkdirSync as mkdirSync3 } from "fs";
|
|
8050
|
+
import { homedir as homedir3 } from "os";
|
|
7568
8051
|
|
|
7569
8052
|
// src/db/gallery.ts
|
|
7570
8053
|
init_schema();
|
|
@@ -7694,13 +8177,13 @@ function getGalleryStats(projectId) {
|
|
|
7694
8177
|
|
|
7695
8178
|
// src/lib/screenshot.ts
|
|
7696
8179
|
function getDataDir2() {
|
|
7697
|
-
return process.env["BROWSER_DATA_DIR"] ??
|
|
8180
|
+
return process.env["BROWSER_DATA_DIR"] ?? join3(homedir3(), ".browser");
|
|
7698
8181
|
}
|
|
7699
8182
|
function getScreenshotDir(projectId) {
|
|
7700
|
-
const base =
|
|
8183
|
+
const base = join3(getDataDir2(), "screenshots");
|
|
7701
8184
|
const date = new Date().toISOString().split("T")[0];
|
|
7702
|
-
const dir = projectId ?
|
|
7703
|
-
|
|
8185
|
+
const dir = projectId ? join3(base, projectId, date) : join3(base, date);
|
|
8186
|
+
mkdirSync3(dir, { recursive: true });
|
|
7704
8187
|
return dir;
|
|
7705
8188
|
}
|
|
7706
8189
|
async function compressBuffer(raw, format, quality, maxWidth) {
|
|
@@ -7715,7 +8198,7 @@ async function compressBuffer(raw, format, quality, maxWidth) {
|
|
|
7715
8198
|
}
|
|
7716
8199
|
}
|
|
7717
8200
|
async function generateThumbnail(raw, dir, stem) {
|
|
7718
|
-
const thumbPath =
|
|
8201
|
+
const thumbPath = join3(dir, `${stem}.thumb.webp`);
|
|
7719
8202
|
const thumbBuffer = await import_sharp.default(raw).resize({ width: 200, withoutEnlargement: true }).webp({ quality: 70, effort: 3 }).toBuffer();
|
|
7720
8203
|
await Bun.write(thumbPath, thumbBuffer);
|
|
7721
8204
|
return { path: thumbPath, base64: thumbBuffer.toString("base64") };
|
|
@@ -7734,27 +8217,45 @@ async function takeScreenshot(page, opts) {
|
|
|
7734
8217
|
type: "png"
|
|
7735
8218
|
};
|
|
7736
8219
|
let rawBuffer;
|
|
8220
|
+
const isBunView = typeof page.getNativeView === "function";
|
|
7737
8221
|
if (opts?.selector) {
|
|
7738
|
-
|
|
7739
|
-
|
|
7740
|
-
|
|
7741
|
-
|
|
8222
|
+
if (isBunView) {
|
|
8223
|
+
const uint8 = await page.screenshot();
|
|
8224
|
+
rawBuffer = Buffer.from(uint8 instanceof Uint8Array ? uint8 : await uint8);
|
|
8225
|
+
} else {
|
|
8226
|
+
const el = await page.$(opts.selector);
|
|
8227
|
+
if (!el)
|
|
8228
|
+
throw new BrowserError(`Element not found: ${opts.selector}`, "ELEMENT_NOT_FOUND");
|
|
8229
|
+
rawBuffer = await el.screenshot(rawOpts);
|
|
8230
|
+
}
|
|
8231
|
+
} else if (isBunView) {
|
|
8232
|
+
const uint8 = await page.screenshot();
|
|
8233
|
+
rawBuffer = Buffer.from(uint8 instanceof Uint8Array ? uint8 : await uint8);
|
|
7742
8234
|
} else {
|
|
7743
8235
|
rawBuffer = await page.screenshot(rawOpts);
|
|
7744
8236
|
}
|
|
7745
8237
|
const originalSizeBytes = rawBuffer.length;
|
|
7746
8238
|
let finalBuffer;
|
|
7747
|
-
|
|
7748
|
-
|
|
7749
|
-
|
|
7750
|
-
|
|
7751
|
-
|
|
8239
|
+
let compressed = true;
|
|
8240
|
+
let fallback = false;
|
|
8241
|
+
try {
|
|
8242
|
+
if (compress && format !== "png") {
|
|
8243
|
+
finalBuffer = await compressBuffer(rawBuffer, format, quality ?? 82, maxWidth);
|
|
8244
|
+
} else if (compress && format === "png") {
|
|
8245
|
+
finalBuffer = await compressBuffer(rawBuffer, "png", quality ?? 9, maxWidth);
|
|
8246
|
+
} else {
|
|
8247
|
+
finalBuffer = rawBuffer;
|
|
8248
|
+
compressed = false;
|
|
8249
|
+
}
|
|
8250
|
+
} catch (sharpErr) {
|
|
8251
|
+
fallback = true;
|
|
8252
|
+
compressed = false;
|
|
7752
8253
|
finalBuffer = rawBuffer;
|
|
7753
8254
|
}
|
|
7754
8255
|
const compressedSizeBytes = finalBuffer.length;
|
|
7755
8256
|
const compressionRatio = originalSizeBytes > 0 ? compressedSizeBytes / originalSizeBytes : 1;
|
|
7756
8257
|
const ext = format;
|
|
7757
|
-
const screenshotPath = opts?.path ??
|
|
8258
|
+
const screenshotPath = opts?.path ?? join3(dir, `${stem}.${ext}`);
|
|
7758
8259
|
await Bun.write(screenshotPath, finalBuffer);
|
|
7759
8260
|
let thumbnailPath;
|
|
7760
8261
|
let thumbnailBase64;
|
|
@@ -7776,7 +8277,8 @@ async function takeScreenshot(page, opts) {
|
|
|
7776
8277
|
compressed_size_bytes: compressedSizeBytes,
|
|
7777
8278
|
compression_ratio: compressionRatio,
|
|
7778
8279
|
thumbnail_path: thumbnailPath,
|
|
7779
|
-
thumbnail_base64: thumbnailBase64
|
|
8280
|
+
thumbnail_base64: thumbnailBase64,
|
|
8281
|
+
...fallback ? { fallback: true, compressed: false } : {}
|
|
7780
8282
|
};
|
|
7781
8283
|
if (opts?.track !== false) {
|
|
7782
8284
|
try {
|
|
@@ -8152,19 +8654,20 @@ function listProjects() {
|
|
|
8152
8654
|
}
|
|
8153
8655
|
|
|
8154
8656
|
// src/server/index.ts
|
|
8657
|
+
init_console_log();
|
|
8155
8658
|
init_recordings();
|
|
8156
8659
|
|
|
8157
8660
|
// src/lib/downloads.ts
|
|
8158
|
-
import { join as
|
|
8159
|
-
import { mkdirSync as
|
|
8160
|
-
import { homedir as
|
|
8661
|
+
import { join as join4, basename, extname } from "path";
|
|
8662
|
+
import { mkdirSync as mkdirSync4, existsSync, readdirSync, statSync, unlinkSync, copyFileSync, writeFileSync, readFileSync } from "fs";
|
|
8663
|
+
import { homedir as homedir4 } from "os";
|
|
8161
8664
|
function getDataDir3() {
|
|
8162
|
-
return process.env["BROWSER_DATA_DIR"] ??
|
|
8665
|
+
return process.env["BROWSER_DATA_DIR"] ?? join4(homedir4(), ".browser");
|
|
8163
8666
|
}
|
|
8164
8667
|
function getDownloadsDir(sessionId) {
|
|
8165
|
-
const base =
|
|
8166
|
-
const dir = sessionId ?
|
|
8167
|
-
|
|
8668
|
+
const base = join4(getDataDir3(), "downloads");
|
|
8669
|
+
const dir = sessionId ? join4(base, sessionId) : base;
|
|
8670
|
+
mkdirSync4(dir, { recursive: true });
|
|
8168
8671
|
return dir;
|
|
8169
8672
|
}
|
|
8170
8673
|
function metaPath(filePath) {
|
|
@@ -8180,7 +8683,7 @@ function listDownloads(sessionId) {
|
|
|
8180
8683
|
for (const entry of entries) {
|
|
8181
8684
|
if (entry.endsWith(".meta.json"))
|
|
8182
8685
|
continue;
|
|
8183
|
-
const full =
|
|
8686
|
+
const full = join4(d, entry);
|
|
8184
8687
|
const stat = statSync(full);
|
|
8185
8688
|
if (stat.isDirectory()) {
|
|
8186
8689
|
scanDir(full);
|
|
@@ -8241,9 +8744,9 @@ function cleanStaleDownloads(olderThanDays = 7) {
|
|
|
8241
8744
|
|
|
8242
8745
|
// src/lib/gallery-diff.ts
|
|
8243
8746
|
var import_sharp2 = __toESM(require_lib(), 1);
|
|
8244
|
-
import { join as
|
|
8245
|
-
import { mkdirSync as
|
|
8246
|
-
import { homedir as
|
|
8747
|
+
import { join as join5 } from "path";
|
|
8748
|
+
import { mkdirSync as mkdirSync5 } from "fs";
|
|
8749
|
+
import { homedir as homedir5 } from "os";
|
|
8247
8750
|
async function diffImages(path1, path2) {
|
|
8248
8751
|
const img1 = import_sharp2.default(path1);
|
|
8249
8752
|
const img2 = import_sharp2.default(path2);
|
|
@@ -8274,10 +8777,10 @@ async function diffImages(path1, path2) {
|
|
|
8274
8777
|
diffBuffer[i + 2] = Math.round(raw1[i + 2] * 0.4);
|
|
8275
8778
|
}
|
|
8276
8779
|
}
|
|
8277
|
-
const dataDir = process.env["BROWSER_DATA_DIR"] ??
|
|
8278
|
-
const diffDir =
|
|
8279
|
-
|
|
8280
|
-
const diffPath =
|
|
8780
|
+
const dataDir = process.env["BROWSER_DATA_DIR"] ?? join5(homedir5(), ".browser");
|
|
8781
|
+
const diffDir = join5(dataDir, "diffs");
|
|
8782
|
+
mkdirSync5(diffDir, { recursive: true });
|
|
8783
|
+
const diffPath = join5(diffDir, `diff-${Date.now()}.webp`);
|
|
8281
8784
|
const diffImageBuffer = await import_sharp2.default(diffBuffer, { raw: { width: w, height: h, channels } }).webp({ quality: 85 }).toBuffer();
|
|
8282
8785
|
await Bun.write(diffPath, diffImageBuffer);
|
|
8283
8786
|
return {
|
|
@@ -8564,13 +9067,13 @@ var server = Bun.serve({
|
|
|
8564
9067
|
const id = path.split("/")[3];
|
|
8565
9068
|
return ok({ deleted: deleteDownload(id) });
|
|
8566
9069
|
}
|
|
8567
|
-
const dashboardDist =
|
|
9070
|
+
const dashboardDist = join6(import.meta.dir, "../../dashboard/dist");
|
|
8568
9071
|
if (existsSync3(dashboardDist)) {
|
|
8569
|
-
const filePath = path === "/" ?
|
|
9072
|
+
const filePath = path === "/" ? join6(dashboardDist, "index.html") : join6(dashboardDist, path);
|
|
8570
9073
|
if (existsSync3(filePath)) {
|
|
8571
9074
|
return new Response(Bun.file(filePath), { headers: CORS_HEADERS });
|
|
8572
9075
|
}
|
|
8573
|
-
return new Response(Bun.file(
|
|
9076
|
+
return new Response(Bun.file(join6(dashboardDist, "index.html")), { headers: CORS_HEADERS });
|
|
8574
9077
|
}
|
|
8575
9078
|
if (path === "/" || path === "") {
|
|
8576
9079
|
return new Response("@hasna/browser REST API running. Dashboard not built.", {
|