@hasna/browser 0.0.4 → 0.0.6
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 +18447 -198
- 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 +804 -278
- package/dist/lib/ai-task.d.ts +23 -0
- package/dist/lib/ai-task.d.ts.map +1 -0
- package/dist/lib/auth.d.ts +28 -0
- package/dist/lib/auth.d.ts.map +1 -0
- package/dist/lib/coordination.d.ts +12 -0
- package/dist/lib/coordination.d.ts.map +1 -0
- package/dist/lib/cron-manager.d.ts +43 -0
- package/dist/lib/cron-manager.d.ts.map +1 -0
- package/dist/lib/extractor.d.ts.map +1 -1
- package/dist/lib/integrations.test.d.ts +2 -0
- package/dist/lib/integrations.test.d.ts.map +1 -0
- package/dist/lib/page-memory.d.ts +14 -0
- package/dist/lib/page-memory.d.ts.map +1 -0
- package/dist/lib/ref-cache.d.ts +9 -0
- package/dist/lib/ref-cache.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/skills-runner.d.ts +14 -0
- package/dist/lib/skills-runner.d.ts.map +1 -0
- package/dist/lib/snapshot.d.ts +1 -0
- package/dist/lib/snapshot.d.ts.map +1 -1
- package/dist/lib/task-queue.d.ts +21 -0
- package/dist/lib/task-queue.d.ts.map +1 -0
- package/dist/lib/url-watcher.d.ts +33 -0
- package/dist/lib/url-watcher.d.ts.map +1 -0
- package/dist/mcp/index.js +29279 -10967
- package/dist/server/index.js +565 -86
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +5 -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
|
|
@@ -7051,15 +7085,414 @@ async function connectLightpanda(port) {
|
|
|
7051
7085
|
}
|
|
7052
7086
|
}
|
|
7053
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
|
+
|
|
7054
7487
|
// src/engines/selector.ts
|
|
7055
7488
|
init_types();
|
|
7056
7489
|
var ENGINE_MAP = {
|
|
7057
|
-
["scrape" /* SCRAPE */]: "
|
|
7058
|
-
["extract_links" /* EXTRACT_LINKS */]: "
|
|
7059
|
-
["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",
|
|
7060
7495
|
["form_fill" /* FORM_FILL */]: "playwright",
|
|
7061
|
-
["spa_navigate" /* SPA_NAVIGATE */]: "playwright",
|
|
7062
|
-
["screenshot" /* SCREENSHOT */]: "playwright",
|
|
7063
7496
|
["auth_flow" /* AUTH_FLOW */]: "playwright",
|
|
7064
7497
|
["multi_tab" /* MULTI_TAB */]: "playwright",
|
|
7065
7498
|
["record_replay" /* RECORD_REPLAY */]: "playwright",
|
|
@@ -7073,6 +7506,14 @@ function selectEngine(useCase, explicit) {
|
|
|
7073
7506
|
if (explicit && explicit !== "auto")
|
|
7074
7507
|
return explicit;
|
|
7075
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
|
+
}
|
|
7076
7517
|
if (preferred === "lightpanda" && !isLightpandaAvailable()) {
|
|
7077
7518
|
return "playwright";
|
|
7078
7519
|
}
|
|
@@ -7192,28 +7633,8 @@ function startHAR(page) {
|
|
|
7192
7633
|
};
|
|
7193
7634
|
}
|
|
7194
7635
|
|
|
7195
|
-
// src/db/console-log.ts
|
|
7196
|
-
init_schema();
|
|
7197
|
-
import { randomUUID as randomUUID3 } from "crypto";
|
|
7198
|
-
function logConsoleMessage(data) {
|
|
7199
|
-
const db = getDatabase();
|
|
7200
|
-
const id = randomUUID3();
|
|
7201
|
-
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);
|
|
7202
|
-
return getConsoleMessage(id);
|
|
7203
|
-
}
|
|
7204
|
-
function getConsoleMessage(id) {
|
|
7205
|
-
const db = getDatabase();
|
|
7206
|
-
return db.query("SELECT * FROM console_log WHERE id = ?").get(id) ?? null;
|
|
7207
|
-
}
|
|
7208
|
-
function getConsoleLog(sessionId, level) {
|
|
7209
|
-
const db = getDatabase();
|
|
7210
|
-
if (level) {
|
|
7211
|
-
return db.query("SELECT * FROM console_log WHERE session_id = ? AND level = ? ORDER BY timestamp ASC").all(sessionId, level);
|
|
7212
|
-
}
|
|
7213
|
-
return db.query("SELECT * FROM console_log WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
|
|
7214
|
-
}
|
|
7215
|
-
|
|
7216
7636
|
// src/lib/console.ts
|
|
7637
|
+
init_console_log();
|
|
7217
7638
|
function enableConsoleCapture(page, sessionId) {
|
|
7218
7639
|
const onConsole = (msg) => {
|
|
7219
7640
|
const levelMap = {
|
|
@@ -7348,12 +7769,30 @@ function setupDialogHandler(page, sessionId) {
|
|
|
7348
7769
|
|
|
7349
7770
|
// src/lib/session.ts
|
|
7350
7771
|
var handles = new Map;
|
|
7772
|
+
function createBunProxy(view) {
|
|
7773
|
+
return view;
|
|
7774
|
+
}
|
|
7351
7775
|
async function createSession2(opts = {}) {
|
|
7352
7776
|
const engine = opts.engine === "auto" || !opts.engine ? selectEngine(opts.useCase ?? "spa_navigate" /* SPA_NAVIGATE */, opts.engine) : opts.engine;
|
|
7353
7777
|
const resolvedEngine = engine === "auto" ? "playwright" : engine;
|
|
7354
|
-
let browser;
|
|
7778
|
+
let browser = null;
|
|
7779
|
+
let bunView = null;
|
|
7355
7780
|
let page;
|
|
7356
|
-
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") {
|
|
7357
7796
|
browser = await connectLightpanda();
|
|
7358
7797
|
const context = await browser.newContext({ viewport: opts.viewport ?? { width: 1280, height: 720 } });
|
|
7359
7798
|
page = await context.newPage();
|
|
@@ -7363,12 +7802,9 @@ async function createSession2(opts = {}) {
|
|
|
7363
7802
|
viewport: opts.viewport,
|
|
7364
7803
|
userAgent: opts.userAgent
|
|
7365
7804
|
});
|
|
7366
|
-
page = await getPage(browser, {
|
|
7367
|
-
viewport: opts.viewport,
|
|
7368
|
-
userAgent: opts.userAgent
|
|
7369
|
-
});
|
|
7805
|
+
page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
|
|
7370
7806
|
}
|
|
7371
|
-
|
|
7807
|
+
const sessionName = opts.name ?? (opts.startUrl ? (() => {
|
|
7372
7808
|
try {
|
|
7373
7809
|
return new URL(opts.startUrl).hostname;
|
|
7374
7810
|
} catch {
|
|
@@ -7376,35 +7812,57 @@ async function createSession2(opts = {}) {
|
|
|
7376
7812
|
}
|
|
7377
7813
|
})() : undefined);
|
|
7378
7814
|
const session = createSession({
|
|
7379
|
-
engine: resolvedEngine,
|
|
7815
|
+
engine: bunView ? "bun" : browser ? resolvedEngine : resolvedEngine,
|
|
7380
7816
|
projectId: opts.projectId,
|
|
7381
7817
|
agentId: opts.agentId,
|
|
7382
7818
|
startUrl: opts.startUrl,
|
|
7383
7819
|
name: sessionName
|
|
7384
7820
|
});
|
|
7385
|
-
if (opts.stealth) {
|
|
7821
|
+
if (opts.stealth && !bunView) {
|
|
7386
7822
|
try {
|
|
7387
7823
|
await applyStealthPatches(page);
|
|
7388
7824
|
} catch {}
|
|
7389
7825
|
}
|
|
7390
7826
|
const cleanups = [];
|
|
7391
|
-
if (
|
|
7392
|
-
|
|
7393
|
-
|
|
7394
|
-
|
|
7395
|
-
|
|
7396
|
-
|
|
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
|
+
}
|
|
7397
7838
|
try {
|
|
7398
|
-
cleanups.push(
|
|
7839
|
+
cleanups.push(setupDialogHandler(page, session.id));
|
|
7399
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
|
+
}
|
|
7400
7857
|
}
|
|
7401
|
-
|
|
7402
|
-
cleanups.push(setupDialogHandler(page, session.id));
|
|
7403
|
-
} catch {}
|
|
7404
|
-
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 } });
|
|
7405
7859
|
if (opts.startUrl) {
|
|
7406
7860
|
try {
|
|
7407
|
-
|
|
7861
|
+
if (bunView) {
|
|
7862
|
+
await bunView.goto(opts.startUrl);
|
|
7863
|
+
} else {
|
|
7864
|
+
await page.goto(opts.startUrl, { waitUntil: "domcontentloaded" });
|
|
7865
|
+
}
|
|
7408
7866
|
} catch {}
|
|
7409
7867
|
}
|
|
7410
7868
|
return { session, page };
|
|
@@ -7414,7 +7872,11 @@ function getSessionPage(sessionId) {
|
|
|
7414
7872
|
if (!handle)
|
|
7415
7873
|
throw new SessionNotFoundError(sessionId);
|
|
7416
7874
|
try {
|
|
7417
|
-
handle.
|
|
7875
|
+
if (handle.bunView) {
|
|
7876
|
+
handle.bunView.url();
|
|
7877
|
+
} else {
|
|
7878
|
+
handle.page.url();
|
|
7879
|
+
}
|
|
7418
7880
|
} catch {
|
|
7419
7881
|
handles.delete(sessionId);
|
|
7420
7882
|
throw new SessionNotFoundError(sessionId);
|
|
@@ -7429,12 +7891,19 @@ async function closeSession2(sessionId) {
|
|
|
7429
7891
|
cleanup();
|
|
7430
7892
|
} catch {}
|
|
7431
7893
|
}
|
|
7432
|
-
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
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
|
+
}
|
|
7438
7907
|
handles.delete(sessionId);
|
|
7439
7908
|
}
|
|
7440
7909
|
return closeSession(sessionId);
|
|
@@ -7576,9 +8045,9 @@ async function extract(page, opts = {}) {
|
|
|
7576
8045
|
// src/lib/screenshot.ts
|
|
7577
8046
|
init_types();
|
|
7578
8047
|
var import_sharp = __toESM(require_lib(), 1);
|
|
7579
|
-
import { join as
|
|
7580
|
-
import { mkdirSync as
|
|
7581
|
-
import { homedir as
|
|
8048
|
+
import { join as join3 } from "path";
|
|
8049
|
+
import { mkdirSync as mkdirSync3 } from "fs";
|
|
8050
|
+
import { homedir as homedir3 } from "os";
|
|
7582
8051
|
|
|
7583
8052
|
// src/db/gallery.ts
|
|
7584
8053
|
init_schema();
|
|
@@ -7708,13 +8177,13 @@ function getGalleryStats(projectId) {
|
|
|
7708
8177
|
|
|
7709
8178
|
// src/lib/screenshot.ts
|
|
7710
8179
|
function getDataDir2() {
|
|
7711
|
-
return process.env["BROWSER_DATA_DIR"] ??
|
|
8180
|
+
return process.env["BROWSER_DATA_DIR"] ?? join3(homedir3(), ".browser");
|
|
7712
8181
|
}
|
|
7713
8182
|
function getScreenshotDir(projectId) {
|
|
7714
|
-
const base =
|
|
8183
|
+
const base = join3(getDataDir2(), "screenshots");
|
|
7715
8184
|
const date = new Date().toISOString().split("T")[0];
|
|
7716
|
-
const dir = projectId ?
|
|
7717
|
-
|
|
8185
|
+
const dir = projectId ? join3(base, projectId, date) : join3(base, date);
|
|
8186
|
+
mkdirSync3(dir, { recursive: true });
|
|
7718
8187
|
return dir;
|
|
7719
8188
|
}
|
|
7720
8189
|
async function compressBuffer(raw, format, quality, maxWidth) {
|
|
@@ -7729,7 +8198,7 @@ async function compressBuffer(raw, format, quality, maxWidth) {
|
|
|
7729
8198
|
}
|
|
7730
8199
|
}
|
|
7731
8200
|
async function generateThumbnail(raw, dir, stem) {
|
|
7732
|
-
const thumbPath =
|
|
8201
|
+
const thumbPath = join3(dir, `${stem}.thumb.webp`);
|
|
7733
8202
|
const thumbBuffer = await import_sharp.default(raw).resize({ width: 200, withoutEnlargement: true }).webp({ quality: 70, effort: 3 }).toBuffer();
|
|
7734
8203
|
await Bun.write(thumbPath, thumbBuffer);
|
|
7735
8204
|
return { path: thumbPath, base64: thumbBuffer.toString("base64") };
|
|
@@ -7748,11 +8217,20 @@ async function takeScreenshot(page, opts) {
|
|
|
7748
8217
|
type: "png"
|
|
7749
8218
|
};
|
|
7750
8219
|
let rawBuffer;
|
|
8220
|
+
const isBunView = typeof page.getNativeView === "function";
|
|
7751
8221
|
if (opts?.selector) {
|
|
7752
|
-
|
|
7753
|
-
|
|
7754
|
-
|
|
7755
|
-
|
|
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);
|
|
7756
8234
|
} else {
|
|
7757
8235
|
rawBuffer = await page.screenshot(rawOpts);
|
|
7758
8236
|
}
|
|
@@ -7777,7 +8255,7 @@ async function takeScreenshot(page, opts) {
|
|
|
7777
8255
|
const compressedSizeBytes = finalBuffer.length;
|
|
7778
8256
|
const compressionRatio = originalSizeBytes > 0 ? compressedSizeBytes / originalSizeBytes : 1;
|
|
7779
8257
|
const ext = format;
|
|
7780
|
-
const screenshotPath = opts?.path ??
|
|
8258
|
+
const screenshotPath = opts?.path ?? join3(dir, `${stem}.${ext}`);
|
|
7781
8259
|
await Bun.write(screenshotPath, finalBuffer);
|
|
7782
8260
|
let thumbnailPath;
|
|
7783
8261
|
let thumbnailBase64;
|
|
@@ -8176,19 +8654,20 @@ function listProjects() {
|
|
|
8176
8654
|
}
|
|
8177
8655
|
|
|
8178
8656
|
// src/server/index.ts
|
|
8657
|
+
init_console_log();
|
|
8179
8658
|
init_recordings();
|
|
8180
8659
|
|
|
8181
8660
|
// src/lib/downloads.ts
|
|
8182
|
-
import { join as
|
|
8183
|
-
import { mkdirSync as
|
|
8184
|
-
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";
|
|
8185
8664
|
function getDataDir3() {
|
|
8186
|
-
return process.env["BROWSER_DATA_DIR"] ??
|
|
8665
|
+
return process.env["BROWSER_DATA_DIR"] ?? join4(homedir4(), ".browser");
|
|
8187
8666
|
}
|
|
8188
8667
|
function getDownloadsDir(sessionId) {
|
|
8189
|
-
const base =
|
|
8190
|
-
const dir = sessionId ?
|
|
8191
|
-
|
|
8668
|
+
const base = join4(getDataDir3(), "downloads");
|
|
8669
|
+
const dir = sessionId ? join4(base, sessionId) : base;
|
|
8670
|
+
mkdirSync4(dir, { recursive: true });
|
|
8192
8671
|
return dir;
|
|
8193
8672
|
}
|
|
8194
8673
|
function metaPath(filePath) {
|
|
@@ -8204,7 +8683,7 @@ function listDownloads(sessionId) {
|
|
|
8204
8683
|
for (const entry of entries) {
|
|
8205
8684
|
if (entry.endsWith(".meta.json"))
|
|
8206
8685
|
continue;
|
|
8207
|
-
const full =
|
|
8686
|
+
const full = join4(d, entry);
|
|
8208
8687
|
const stat = statSync(full);
|
|
8209
8688
|
if (stat.isDirectory()) {
|
|
8210
8689
|
scanDir(full);
|
|
@@ -8265,9 +8744,9 @@ function cleanStaleDownloads(olderThanDays = 7) {
|
|
|
8265
8744
|
|
|
8266
8745
|
// src/lib/gallery-diff.ts
|
|
8267
8746
|
var import_sharp2 = __toESM(require_lib(), 1);
|
|
8268
|
-
import { join as
|
|
8269
|
-
import { mkdirSync as
|
|
8270
|
-
import { homedir as
|
|
8747
|
+
import { join as join5 } from "path";
|
|
8748
|
+
import { mkdirSync as mkdirSync5 } from "fs";
|
|
8749
|
+
import { homedir as homedir5 } from "os";
|
|
8271
8750
|
async function diffImages(path1, path2) {
|
|
8272
8751
|
const img1 = import_sharp2.default(path1);
|
|
8273
8752
|
const img2 = import_sharp2.default(path2);
|
|
@@ -8298,10 +8777,10 @@ async function diffImages(path1, path2) {
|
|
|
8298
8777
|
diffBuffer[i + 2] = Math.round(raw1[i + 2] * 0.4);
|
|
8299
8778
|
}
|
|
8300
8779
|
}
|
|
8301
|
-
const dataDir = process.env["BROWSER_DATA_DIR"] ??
|
|
8302
|
-
const diffDir =
|
|
8303
|
-
|
|
8304
|
-
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`);
|
|
8305
8784
|
const diffImageBuffer = await import_sharp2.default(diffBuffer, { raw: { width: w, height: h, channels } }).webp({ quality: 85 }).toBuffer();
|
|
8306
8785
|
await Bun.write(diffPath, diffImageBuffer);
|
|
8307
8786
|
return {
|
|
@@ -8588,13 +9067,13 @@ var server = Bun.serve({
|
|
|
8588
9067
|
const id = path.split("/")[3];
|
|
8589
9068
|
return ok({ deleted: deleteDownload(id) });
|
|
8590
9069
|
}
|
|
8591
|
-
const dashboardDist =
|
|
9070
|
+
const dashboardDist = join6(import.meta.dir, "../../dashboard/dist");
|
|
8592
9071
|
if (existsSync3(dashboardDist)) {
|
|
8593
|
-
const filePath = path === "/" ?
|
|
9072
|
+
const filePath = path === "/" ? join6(dashboardDist, "index.html") : join6(dashboardDist, path);
|
|
8594
9073
|
if (existsSync3(filePath)) {
|
|
8595
9074
|
return new Response(Bun.file(filePath), { headers: CORS_HEADERS });
|
|
8596
9075
|
}
|
|
8597
|
-
return new Response(Bun.file(
|
|
9076
|
+
return new Response(Bun.file(join6(dashboardDist, "index.html")), { headers: CORS_HEADERS });
|
|
8598
9077
|
}
|
|
8599
9078
|
if (path === "/" || path === "") {
|
|
8600
9079
|
return new Response("@hasna/browser REST API running. Dashboard not built.", {
|