@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/index.js
CHANGED
|
@@ -16,8 +16,249 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
16
16
|
return to;
|
|
17
17
|
};
|
|
18
18
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
19
|
+
var __export = (target, all) => {
|
|
20
|
+
for (var name in all)
|
|
21
|
+
__defProp(target, name, {
|
|
22
|
+
get: all[name],
|
|
23
|
+
enumerable: true,
|
|
24
|
+
configurable: true,
|
|
25
|
+
set: (newValue) => all[name] = () => newValue
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
19
29
|
var __require = import.meta.require;
|
|
20
30
|
|
|
31
|
+
// src/db/schema.ts
|
|
32
|
+
import { Database } from "bun:sqlite";
|
|
33
|
+
import { join } from "path";
|
|
34
|
+
import { mkdirSync } from "fs";
|
|
35
|
+
import { homedir } from "os";
|
|
36
|
+
function getDataDir() {
|
|
37
|
+
return process.env["BROWSER_DATA_DIR"] ?? join(homedir(), ".browser");
|
|
38
|
+
}
|
|
39
|
+
function getDatabase(path) {
|
|
40
|
+
const resolvedPath = path ?? process.env["BROWSER_DB_PATH"] ?? join(getDataDir(), "browser.db");
|
|
41
|
+
if (_db && _dbPath === resolvedPath)
|
|
42
|
+
return _db;
|
|
43
|
+
if (_db) {
|
|
44
|
+
try {
|
|
45
|
+
_db.close();
|
|
46
|
+
} catch {}
|
|
47
|
+
_db = null;
|
|
48
|
+
}
|
|
49
|
+
mkdirSync(join(resolvedPath, ".."), { recursive: true });
|
|
50
|
+
_db = new Database(resolvedPath);
|
|
51
|
+
_dbPath = resolvedPath;
|
|
52
|
+
_db.exec("PRAGMA journal_mode=WAL;");
|
|
53
|
+
_db.exec("PRAGMA foreign_keys=ON;");
|
|
54
|
+
runMigrations(_db);
|
|
55
|
+
return _db;
|
|
56
|
+
}
|
|
57
|
+
function resetDatabase() {
|
|
58
|
+
if (_db) {
|
|
59
|
+
try {
|
|
60
|
+
_db.close();
|
|
61
|
+
} catch {}
|
|
62
|
+
}
|
|
63
|
+
_db = null;
|
|
64
|
+
_dbPath = null;
|
|
65
|
+
}
|
|
66
|
+
function runMigrations(db) {
|
|
67
|
+
db.exec(`
|
|
68
|
+
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
69
|
+
version INTEGER PRIMARY KEY,
|
|
70
|
+
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
71
|
+
);
|
|
72
|
+
`);
|
|
73
|
+
const applied = new Set(db.query("SELECT version FROM schema_migrations").all().map((r) => r.version));
|
|
74
|
+
const migrations = [
|
|
75
|
+
{
|
|
76
|
+
version: 1,
|
|
77
|
+
sql: `
|
|
78
|
+
CREATE TABLE IF NOT EXISTS projects (
|
|
79
|
+
id TEXT PRIMARY KEY,
|
|
80
|
+
name TEXT NOT NULL UNIQUE,
|
|
81
|
+
path TEXT NOT NULL,
|
|
82
|
+
description TEXT,
|
|
83
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
CREATE TABLE IF NOT EXISTS agents (
|
|
87
|
+
id TEXT PRIMARY KEY,
|
|
88
|
+
name TEXT NOT NULL,
|
|
89
|
+
description TEXT,
|
|
90
|
+
session_id TEXT,
|
|
91
|
+
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
92
|
+
working_dir TEXT,
|
|
93
|
+
last_seen TEXT NOT NULL DEFAULT (datetime('now')),
|
|
94
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
CREATE TABLE IF NOT EXISTS heartbeats (
|
|
98
|
+
id TEXT PRIMARY KEY,
|
|
99
|
+
agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
|
|
100
|
+
session_id TEXT,
|
|
101
|
+
timestamp TEXT NOT NULL DEFAULT (datetime('now'))
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
105
|
+
id TEXT PRIMARY KEY,
|
|
106
|
+
engine TEXT NOT NULL,
|
|
107
|
+
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
108
|
+
agent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
|
|
109
|
+
start_url TEXT,
|
|
110
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
111
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
112
|
+
closed_at TEXT
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
CREATE TABLE IF NOT EXISTS snapshots (
|
|
116
|
+
id TEXT PRIMARY KEY,
|
|
117
|
+
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
118
|
+
url TEXT NOT NULL,
|
|
119
|
+
title TEXT,
|
|
120
|
+
html TEXT,
|
|
121
|
+
screenshot_path TEXT,
|
|
122
|
+
timestamp TEXT NOT NULL DEFAULT (datetime('now'))
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
CREATE TABLE IF NOT EXISTS network_log (
|
|
126
|
+
id TEXT PRIMARY KEY,
|
|
127
|
+
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
128
|
+
method TEXT NOT NULL,
|
|
129
|
+
url TEXT NOT NULL,
|
|
130
|
+
status_code INTEGER,
|
|
131
|
+
request_headers TEXT,
|
|
132
|
+
response_headers TEXT,
|
|
133
|
+
request_body TEXT,
|
|
134
|
+
body_size INTEGER,
|
|
135
|
+
duration_ms INTEGER,
|
|
136
|
+
resource_type TEXT,
|
|
137
|
+
timestamp TEXT NOT NULL DEFAULT (datetime('now'))
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
CREATE TABLE IF NOT EXISTS console_log (
|
|
141
|
+
id TEXT PRIMARY KEY,
|
|
142
|
+
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
143
|
+
level TEXT NOT NULL DEFAULT 'log',
|
|
144
|
+
message TEXT NOT NULL,
|
|
145
|
+
source TEXT,
|
|
146
|
+
line_number INTEGER,
|
|
147
|
+
timestamp TEXT NOT NULL DEFAULT (datetime('now'))
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
CREATE TABLE IF NOT EXISTS recordings (
|
|
151
|
+
id TEXT PRIMARY KEY,
|
|
152
|
+
name TEXT NOT NULL,
|
|
153
|
+
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
154
|
+
start_url TEXT,
|
|
155
|
+
steps TEXT NOT NULL DEFAULT '[]',
|
|
156
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
CREATE TABLE IF NOT EXISTS crawl_results (
|
|
160
|
+
id TEXT PRIMARY KEY,
|
|
161
|
+
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
162
|
+
start_url TEXT NOT NULL,
|
|
163
|
+
depth INTEGER NOT NULL DEFAULT 1,
|
|
164
|
+
pages TEXT NOT NULL DEFAULT '[]',
|
|
165
|
+
links TEXT NOT NULL DEFAULT '[]',
|
|
166
|
+
errors TEXT NOT NULL DEFAULT '[]',
|
|
167
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id);
|
|
171
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
|
|
172
|
+
CREATE INDEX IF NOT EXISTS idx_snapshots_session ON snapshots(session_id);
|
|
173
|
+
CREATE INDEX IF NOT EXISTS idx_network_log_session ON network_log(session_id);
|
|
174
|
+
CREATE INDEX IF NOT EXISTS idx_console_log_session ON console_log(session_id);
|
|
175
|
+
CREATE INDEX IF NOT EXISTS idx_agents_project ON agents(project_id);
|
|
176
|
+
CREATE INDEX IF NOT EXISTS idx_heartbeats_agent ON heartbeats(agent_id);
|
|
177
|
+
CREATE INDEX IF NOT EXISTS idx_recordings_project ON recordings(project_id);
|
|
178
|
+
CREATE INDEX IF NOT EXISTS idx_crawl_results_project ON crawl_results(project_id);
|
|
179
|
+
`
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
version: 2,
|
|
183
|
+
sql: `
|
|
184
|
+
-- Gallery entries
|
|
185
|
+
CREATE TABLE IF NOT EXISTS gallery_entries (
|
|
186
|
+
id TEXT PRIMARY KEY,
|
|
187
|
+
session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
|
|
188
|
+
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
189
|
+
url TEXT,
|
|
190
|
+
title TEXT,
|
|
191
|
+
path TEXT NOT NULL,
|
|
192
|
+
thumbnail_path TEXT,
|
|
193
|
+
format TEXT,
|
|
194
|
+
width INTEGER,
|
|
195
|
+
height INTEGER,
|
|
196
|
+
original_size_bytes INTEGER,
|
|
197
|
+
compressed_size_bytes INTEGER,
|
|
198
|
+
compression_ratio REAL,
|
|
199
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
200
|
+
notes TEXT,
|
|
201
|
+
is_favorite INTEGER NOT NULL DEFAULT 0,
|
|
202
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
-- Session name column (migration 2 adds it)
|
|
206
|
+
ALTER TABLE sessions ADD COLUMN name TEXT;
|
|
207
|
+
|
|
208
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_sessions_name ON sessions(name) WHERE name IS NOT NULL;
|
|
209
|
+
CREATE INDEX IF NOT EXISTS idx_gallery_session ON gallery_entries(session_id);
|
|
210
|
+
CREATE INDEX IF NOT EXISTS idx_gallery_project ON gallery_entries(project_id);
|
|
211
|
+
CREATE INDEX IF NOT EXISTS idx_gallery_favorite ON gallery_entries(is_favorite);
|
|
212
|
+
CREATE INDEX IF NOT EXISTS idx_gallery_created ON gallery_entries(created_at);
|
|
213
|
+
`
|
|
214
|
+
}
|
|
215
|
+
];
|
|
216
|
+
for (const m of migrations) {
|
|
217
|
+
if (!applied.has(m.version)) {
|
|
218
|
+
db.transaction(() => {
|
|
219
|
+
db.exec(m.sql);
|
|
220
|
+
db.prepare("INSERT INTO schema_migrations (version) VALUES (?)").run(m.version);
|
|
221
|
+
})();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
var _db = null, _dbPath = null;
|
|
226
|
+
var init_schema = () => {};
|
|
227
|
+
|
|
228
|
+
// src/db/console-log.ts
|
|
229
|
+
var exports_console_log = {};
|
|
230
|
+
__export(exports_console_log, {
|
|
231
|
+
logConsoleMessage: () => logConsoleMessage,
|
|
232
|
+
getConsoleMessage: () => getConsoleMessage,
|
|
233
|
+
getConsoleLog: () => getConsoleLog,
|
|
234
|
+
clearConsoleLog: () => clearConsoleLog
|
|
235
|
+
});
|
|
236
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
237
|
+
function logConsoleMessage(data) {
|
|
238
|
+
const db = getDatabase();
|
|
239
|
+
const id = randomUUID6();
|
|
240
|
+
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);
|
|
241
|
+
return getConsoleMessage(id);
|
|
242
|
+
}
|
|
243
|
+
function getConsoleMessage(id) {
|
|
244
|
+
const db = getDatabase();
|
|
245
|
+
return db.query("SELECT * FROM console_log WHERE id = ?").get(id) ?? null;
|
|
246
|
+
}
|
|
247
|
+
function getConsoleLog(sessionId, level) {
|
|
248
|
+
const db = getDatabase();
|
|
249
|
+
if (level) {
|
|
250
|
+
return db.query("SELECT * FROM console_log WHERE session_id = ? AND level = ? ORDER BY timestamp ASC").all(sessionId, level);
|
|
251
|
+
}
|
|
252
|
+
return db.query("SELECT * FROM console_log WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
|
|
253
|
+
}
|
|
254
|
+
function clearConsoleLog(sessionId) {
|
|
255
|
+
const db = getDatabase();
|
|
256
|
+
db.prepare("DELETE FROM console_log WHERE session_id = ?").run(sessionId);
|
|
257
|
+
}
|
|
258
|
+
var init_console_log = __esm(() => {
|
|
259
|
+
init_schema();
|
|
260
|
+
});
|
|
261
|
+
|
|
21
262
|
// node_modules/sharp/lib/is.js
|
|
22
263
|
var require_is = __commonJS((exports, module) => {
|
|
23
264
|
/*!
|
|
@@ -3680,7 +3921,7 @@ var require_color = __commonJS((exports, module) => {
|
|
|
3680
3921
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3681
3922
|
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
3682
3923
|
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
3683
|
-
var
|
|
3924
|
+
var __export2 = (target, all) => {
|
|
3684
3925
|
for (var name in all)
|
|
3685
3926
|
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
3686
3927
|
};
|
|
@@ -3694,7 +3935,7 @@ var require_color = __commonJS((exports, module) => {
|
|
|
3694
3935
|
};
|
|
3695
3936
|
var __toCommonJS = (mod) => __copyProps(__defProp2({}, "__esModule", { value: true }), mod);
|
|
3696
3937
|
var index_exports = {};
|
|
3697
|
-
|
|
3938
|
+
__export2(index_exports, {
|
|
3698
3939
|
default: () => index_default
|
|
3699
3940
|
});
|
|
3700
3941
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -6491,209 +6732,18 @@ class ProjectNotFoundError extends BrowserError {
|
|
|
6491
6732
|
this.name = "ProjectNotFoundError";
|
|
6492
6733
|
}
|
|
6493
6734
|
}
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
if (_db && _dbPath === resolvedPath)
|
|
6507
|
-
return _db;
|
|
6508
|
-
if (_db) {
|
|
6509
|
-
try {
|
|
6510
|
-
_db.close();
|
|
6511
|
-
} catch {}
|
|
6512
|
-
_db = null;
|
|
6513
|
-
}
|
|
6514
|
-
mkdirSync(join(resolvedPath, ".."), { recursive: true });
|
|
6515
|
-
_db = new Database(resolvedPath);
|
|
6516
|
-
_dbPath = resolvedPath;
|
|
6517
|
-
_db.exec("PRAGMA journal_mode=WAL;");
|
|
6518
|
-
_db.exec("PRAGMA foreign_keys=ON;");
|
|
6519
|
-
runMigrations(_db);
|
|
6520
|
-
return _db;
|
|
6521
|
-
}
|
|
6522
|
-
function resetDatabase() {
|
|
6523
|
-
if (_db) {
|
|
6524
|
-
try {
|
|
6525
|
-
_db.close();
|
|
6526
|
-
} catch {}
|
|
6527
|
-
}
|
|
6528
|
-
_db = null;
|
|
6529
|
-
_dbPath = null;
|
|
6530
|
-
}
|
|
6531
|
-
function runMigrations(db) {
|
|
6532
|
-
db.exec(`
|
|
6533
|
-
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
6534
|
-
version INTEGER PRIMARY KEY,
|
|
6535
|
-
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
6536
|
-
);
|
|
6537
|
-
`);
|
|
6538
|
-
const applied = new Set(db.query("SELECT version FROM schema_migrations").all().map((r) => r.version));
|
|
6539
|
-
const migrations = [
|
|
6540
|
-
{
|
|
6541
|
-
version: 1,
|
|
6542
|
-
sql: `
|
|
6543
|
-
CREATE TABLE IF NOT EXISTS projects (
|
|
6544
|
-
id TEXT PRIMARY KEY,
|
|
6545
|
-
name TEXT NOT NULL UNIQUE,
|
|
6546
|
-
path TEXT NOT NULL,
|
|
6547
|
-
description TEXT,
|
|
6548
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
6549
|
-
);
|
|
6550
|
-
|
|
6551
|
-
CREATE TABLE IF NOT EXISTS agents (
|
|
6552
|
-
id TEXT PRIMARY KEY,
|
|
6553
|
-
name TEXT NOT NULL,
|
|
6554
|
-
description TEXT,
|
|
6555
|
-
session_id TEXT,
|
|
6556
|
-
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
6557
|
-
working_dir TEXT,
|
|
6558
|
-
last_seen TEXT NOT NULL DEFAULT (datetime('now')),
|
|
6559
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
6560
|
-
);
|
|
6561
|
-
|
|
6562
|
-
CREATE TABLE IF NOT EXISTS heartbeats (
|
|
6563
|
-
id TEXT PRIMARY KEY,
|
|
6564
|
-
agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
|
|
6565
|
-
session_id TEXT,
|
|
6566
|
-
timestamp TEXT NOT NULL DEFAULT (datetime('now'))
|
|
6567
|
-
);
|
|
6568
|
-
|
|
6569
|
-
CREATE TABLE IF NOT EXISTS sessions (
|
|
6570
|
-
id TEXT PRIMARY KEY,
|
|
6571
|
-
engine TEXT NOT NULL,
|
|
6572
|
-
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
6573
|
-
agent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
|
|
6574
|
-
start_url TEXT,
|
|
6575
|
-
status TEXT NOT NULL DEFAULT 'active',
|
|
6576
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
6577
|
-
closed_at TEXT
|
|
6578
|
-
);
|
|
6579
|
-
|
|
6580
|
-
CREATE TABLE IF NOT EXISTS snapshots (
|
|
6581
|
-
id TEXT PRIMARY KEY,
|
|
6582
|
-
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
6583
|
-
url TEXT NOT NULL,
|
|
6584
|
-
title TEXT,
|
|
6585
|
-
html TEXT,
|
|
6586
|
-
screenshot_path TEXT,
|
|
6587
|
-
timestamp TEXT NOT NULL DEFAULT (datetime('now'))
|
|
6588
|
-
);
|
|
6589
|
-
|
|
6590
|
-
CREATE TABLE IF NOT EXISTS network_log (
|
|
6591
|
-
id TEXT PRIMARY KEY,
|
|
6592
|
-
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
6593
|
-
method TEXT NOT NULL,
|
|
6594
|
-
url TEXT NOT NULL,
|
|
6595
|
-
status_code INTEGER,
|
|
6596
|
-
request_headers TEXT,
|
|
6597
|
-
response_headers TEXT,
|
|
6598
|
-
request_body TEXT,
|
|
6599
|
-
body_size INTEGER,
|
|
6600
|
-
duration_ms INTEGER,
|
|
6601
|
-
resource_type TEXT,
|
|
6602
|
-
timestamp TEXT NOT NULL DEFAULT (datetime('now'))
|
|
6603
|
-
);
|
|
6604
|
-
|
|
6605
|
-
CREATE TABLE IF NOT EXISTS console_log (
|
|
6606
|
-
id TEXT PRIMARY KEY,
|
|
6607
|
-
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
6608
|
-
level TEXT NOT NULL DEFAULT 'log',
|
|
6609
|
-
message TEXT NOT NULL,
|
|
6610
|
-
source TEXT,
|
|
6611
|
-
line_number INTEGER,
|
|
6612
|
-
timestamp TEXT NOT NULL DEFAULT (datetime('now'))
|
|
6613
|
-
);
|
|
6614
|
-
|
|
6615
|
-
CREATE TABLE IF NOT EXISTS recordings (
|
|
6616
|
-
id TEXT PRIMARY KEY,
|
|
6617
|
-
name TEXT NOT NULL,
|
|
6618
|
-
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
6619
|
-
start_url TEXT,
|
|
6620
|
-
steps TEXT NOT NULL DEFAULT '[]',
|
|
6621
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
6622
|
-
);
|
|
6623
|
-
|
|
6624
|
-
CREATE TABLE IF NOT EXISTS crawl_results (
|
|
6625
|
-
id TEXT PRIMARY KEY,
|
|
6626
|
-
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
6627
|
-
start_url TEXT NOT NULL,
|
|
6628
|
-
depth INTEGER NOT NULL DEFAULT 1,
|
|
6629
|
-
pages TEXT NOT NULL DEFAULT '[]',
|
|
6630
|
-
links TEXT NOT NULL DEFAULT '[]',
|
|
6631
|
-
errors TEXT NOT NULL DEFAULT '[]',
|
|
6632
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
6633
|
-
);
|
|
6634
|
-
|
|
6635
|
-
CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id);
|
|
6636
|
-
CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
|
|
6637
|
-
CREATE INDEX IF NOT EXISTS idx_snapshots_session ON snapshots(session_id);
|
|
6638
|
-
CREATE INDEX IF NOT EXISTS idx_network_log_session ON network_log(session_id);
|
|
6639
|
-
CREATE INDEX IF NOT EXISTS idx_console_log_session ON console_log(session_id);
|
|
6640
|
-
CREATE INDEX IF NOT EXISTS idx_agents_project ON agents(project_id);
|
|
6641
|
-
CREATE INDEX IF NOT EXISTS idx_heartbeats_agent ON heartbeats(agent_id);
|
|
6642
|
-
CREATE INDEX IF NOT EXISTS idx_recordings_project ON recordings(project_id);
|
|
6643
|
-
CREATE INDEX IF NOT EXISTS idx_crawl_results_project ON crawl_results(project_id);
|
|
6644
|
-
`
|
|
6645
|
-
},
|
|
6646
|
-
{
|
|
6647
|
-
version: 2,
|
|
6648
|
-
sql: `
|
|
6649
|
-
-- Gallery entries
|
|
6650
|
-
CREATE TABLE IF NOT EXISTS gallery_entries (
|
|
6651
|
-
id TEXT PRIMARY KEY,
|
|
6652
|
-
session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
|
|
6653
|
-
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
6654
|
-
url TEXT,
|
|
6655
|
-
title TEXT,
|
|
6656
|
-
path TEXT NOT NULL,
|
|
6657
|
-
thumbnail_path TEXT,
|
|
6658
|
-
format TEXT,
|
|
6659
|
-
width INTEGER,
|
|
6660
|
-
height INTEGER,
|
|
6661
|
-
original_size_bytes INTEGER,
|
|
6662
|
-
compressed_size_bytes INTEGER,
|
|
6663
|
-
compression_ratio REAL,
|
|
6664
|
-
tags TEXT NOT NULL DEFAULT '[]',
|
|
6665
|
-
notes TEXT,
|
|
6666
|
-
is_favorite INTEGER NOT NULL DEFAULT 0,
|
|
6667
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
6668
|
-
);
|
|
6669
|
-
|
|
6670
|
-
-- Session name column (migration 2 adds it)
|
|
6671
|
-
ALTER TABLE sessions ADD COLUMN name TEXT;
|
|
6672
|
-
|
|
6673
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_sessions_name ON sessions(name) WHERE name IS NOT NULL;
|
|
6674
|
-
CREATE INDEX IF NOT EXISTS idx_gallery_session ON gallery_entries(session_id);
|
|
6675
|
-
CREATE INDEX IF NOT EXISTS idx_gallery_project ON gallery_entries(project_id);
|
|
6676
|
-
CREATE INDEX IF NOT EXISTS idx_gallery_favorite ON gallery_entries(is_favorite);
|
|
6677
|
-
CREATE INDEX IF NOT EXISTS idx_gallery_created ON gallery_entries(created_at);
|
|
6678
|
-
`
|
|
6679
|
-
}
|
|
6680
|
-
];
|
|
6681
|
-
for (const m of migrations) {
|
|
6682
|
-
if (!applied.has(m.version)) {
|
|
6683
|
-
db.transaction(() => {
|
|
6684
|
-
db.exec(m.sql);
|
|
6685
|
-
db.prepare("INSERT INTO schema_migrations (version) VALUES (?)").run(m.version);
|
|
6686
|
-
})();
|
|
6687
|
-
}
|
|
6688
|
-
}
|
|
6689
|
-
}
|
|
6690
|
-
// src/db/projects.ts
|
|
6691
|
-
import { randomUUID } from "crypto";
|
|
6692
|
-
function createProject(data) {
|
|
6693
|
-
const db = getDatabase();
|
|
6694
|
-
const id = randomUUID();
|
|
6695
|
-
db.prepare("INSERT INTO projects (id, name, path, description) VALUES (?, ?, ?, ?)").run(id, data.name, data.path, data.description ?? null);
|
|
6696
|
-
return getProject(id);
|
|
6735
|
+
|
|
6736
|
+
// src/index.ts
|
|
6737
|
+
init_schema();
|
|
6738
|
+
|
|
6739
|
+
// src/db/projects.ts
|
|
6740
|
+
init_schema();
|
|
6741
|
+
import { randomUUID } from "crypto";
|
|
6742
|
+
function createProject(data) {
|
|
6743
|
+
const db = getDatabase();
|
|
6744
|
+
const id = randomUUID();
|
|
6745
|
+
db.prepare("INSERT INTO projects (id, name, path, description) VALUES (?, ?, ?, ?)").run(id, data.name, data.path, data.description ?? null);
|
|
6746
|
+
return getProject(id);
|
|
6697
6747
|
}
|
|
6698
6748
|
function ensureProject(name, path, description) {
|
|
6699
6749
|
const db = getDatabase();
|
|
@@ -6744,6 +6794,7 @@ function deleteProject(id) {
|
|
|
6744
6794
|
db.prepare("DELETE FROM projects WHERE id = ?").run(id);
|
|
6745
6795
|
}
|
|
6746
6796
|
// src/db/agents.ts
|
|
6797
|
+
init_schema();
|
|
6747
6798
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
6748
6799
|
function registerAgent(name, opts = {}) {
|
|
6749
6800
|
const db = getDatabase();
|
|
@@ -6823,11 +6874,19 @@ function cleanStaleAgents(thresholdMs) {
|
|
|
6823
6874
|
return result.changes;
|
|
6824
6875
|
}
|
|
6825
6876
|
// src/db/sessions.ts
|
|
6877
|
+
init_schema();
|
|
6826
6878
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
6827
6879
|
function createSession(data) {
|
|
6828
6880
|
const db = getDatabase();
|
|
6829
6881
|
const id = randomUUID3();
|
|
6830
|
-
|
|
6882
|
+
let name = data.name ?? null;
|
|
6883
|
+
if (name) {
|
|
6884
|
+
const existing = db.query("SELECT id FROM sessions WHERE name = ?").get(name);
|
|
6885
|
+
if (existing) {
|
|
6886
|
+
name = `${name}-${id.slice(0, 6)}`;
|
|
6887
|
+
}
|
|
6888
|
+
}
|
|
6889
|
+
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);
|
|
6831
6890
|
return getSession(id);
|
|
6832
6891
|
}
|
|
6833
6892
|
function getSessionByName(name) {
|
|
@@ -6875,6 +6934,7 @@ function deleteSession(id) {
|
|
|
6875
6934
|
db.prepare("DELETE FROM sessions WHERE id = ?").run(id);
|
|
6876
6935
|
}
|
|
6877
6936
|
// src/db/snapshots.ts
|
|
6937
|
+
init_schema();
|
|
6878
6938
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
6879
6939
|
function createSnapshot(data) {
|
|
6880
6940
|
const db = getDatabase();
|
|
@@ -6899,6 +6959,7 @@ function deleteSnapshotsBySession(sessionId) {
|
|
|
6899
6959
|
db.prepare("DELETE FROM snapshots WHERE session_id = ?").run(sessionId);
|
|
6900
6960
|
}
|
|
6901
6961
|
// src/db/network-log.ts
|
|
6962
|
+
init_schema();
|
|
6902
6963
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
6903
6964
|
function logRequest(data) {
|
|
6904
6965
|
const db = getDatabase();
|
|
@@ -6924,30 +6985,12 @@ function deleteNetworkRequest(id) {
|
|
|
6924
6985
|
const db = getDatabase();
|
|
6925
6986
|
db.prepare("DELETE FROM network_log WHERE id = ?").run(id);
|
|
6926
6987
|
}
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
const id = randomUUID6();
|
|
6932
|
-
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);
|
|
6933
|
-
return getConsoleMessage(id);
|
|
6934
|
-
}
|
|
6935
|
-
function getConsoleMessage(id) {
|
|
6936
|
-
const db = getDatabase();
|
|
6937
|
-
return db.query("SELECT * FROM console_log WHERE id = ?").get(id) ?? null;
|
|
6938
|
-
}
|
|
6939
|
-
function getConsoleLog(sessionId, level) {
|
|
6940
|
-
const db = getDatabase();
|
|
6941
|
-
if (level) {
|
|
6942
|
-
return db.query("SELECT * FROM console_log WHERE session_id = ? AND level = ? ORDER BY timestamp ASC").all(sessionId, level);
|
|
6943
|
-
}
|
|
6944
|
-
return db.query("SELECT * FROM console_log WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
|
|
6945
|
-
}
|
|
6946
|
-
function clearConsoleLog(sessionId) {
|
|
6947
|
-
const db = getDatabase();
|
|
6948
|
-
db.prepare("DELETE FROM console_log WHERE session_id = ?").run(sessionId);
|
|
6949
|
-
}
|
|
6988
|
+
|
|
6989
|
+
// src/index.ts
|
|
6990
|
+
init_console_log();
|
|
6991
|
+
|
|
6950
6992
|
// src/db/recordings.ts
|
|
6993
|
+
init_schema();
|
|
6951
6994
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
6952
6995
|
function deserialize(row) {
|
|
6953
6996
|
return {
|
|
@@ -7002,6 +7045,7 @@ function deleteRecording(id) {
|
|
|
7002
7045
|
db.prepare("DELETE FROM recordings WHERE id = ?").run(id);
|
|
7003
7046
|
}
|
|
7004
7047
|
// src/db/crawl-results.ts
|
|
7048
|
+
init_schema();
|
|
7005
7049
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
7006
7050
|
function deserialize2(row) {
|
|
7007
7051
|
const pages = JSON.parse(row.pages);
|
|
@@ -7037,6 +7081,7 @@ function deleteCrawlResult(id) {
|
|
|
7037
7081
|
db.prepare("DELETE FROM crawl_results WHERE id = ?").run(id);
|
|
7038
7082
|
}
|
|
7039
7083
|
// src/db/heartbeats.ts
|
|
7084
|
+
init_schema();
|
|
7040
7085
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
7041
7086
|
function recordHeartbeat(agentId, sessionId) {
|
|
7042
7087
|
const db = getDatabase();
|
|
@@ -7387,14 +7432,413 @@ class LightpandaPage {
|
|
|
7387
7432
|
await this.page.context().close();
|
|
7388
7433
|
}
|
|
7389
7434
|
}
|
|
7435
|
+
// src/engines/bun-webview.ts
|
|
7436
|
+
import { join as join2 } from "path";
|
|
7437
|
+
import { mkdirSync as mkdirSync2 } from "fs";
|
|
7438
|
+
import { homedir as homedir2 } from "os";
|
|
7439
|
+
function isBunWebViewAvailable() {
|
|
7440
|
+
return typeof globalThis.Bun !== "undefined" && typeof globalThis.Bun.WebView !== "undefined";
|
|
7441
|
+
}
|
|
7442
|
+
function getProfileDir(profileName) {
|
|
7443
|
+
const base = process.env["BROWSER_DATA_DIR"] ?? join2(homedir2(), ".browser");
|
|
7444
|
+
const dir = join2(base, "profiles", profileName);
|
|
7445
|
+
mkdirSync2(dir, { recursive: true });
|
|
7446
|
+
return dir;
|
|
7447
|
+
}
|
|
7448
|
+
|
|
7449
|
+
class BunWebViewSession {
|
|
7450
|
+
view;
|
|
7451
|
+
_sessionId;
|
|
7452
|
+
_eventListeners = new Map;
|
|
7453
|
+
constructor(opts = {}) {
|
|
7454
|
+
if (!isBunWebViewAvailable()) {
|
|
7455
|
+
throw new Error("Bun.WebView is not available. Install Bun canary: bun upgrade --canary");
|
|
7456
|
+
}
|
|
7457
|
+
const BunWebView = globalThis.Bun.WebView;
|
|
7458
|
+
const constructorOpts = {
|
|
7459
|
+
width: opts.width ?? 1280,
|
|
7460
|
+
height: opts.height ?? 720
|
|
7461
|
+
};
|
|
7462
|
+
if (opts.profile) {
|
|
7463
|
+
constructorOpts.dataStore = { directory: getProfileDir(opts.profile) };
|
|
7464
|
+
} else {
|
|
7465
|
+
constructorOpts.dataStore = "ephemeral";
|
|
7466
|
+
}
|
|
7467
|
+
if (opts.onConsole) {
|
|
7468
|
+
constructorOpts.console = opts.onConsole;
|
|
7469
|
+
}
|
|
7470
|
+
this.view = new BunWebView(constructorOpts);
|
|
7471
|
+
this.view.onNavigated = (url) => {
|
|
7472
|
+
this._emit("navigated", url);
|
|
7473
|
+
};
|
|
7474
|
+
this.view.onNavigationFailed = (error) => {
|
|
7475
|
+
this._emit("navigationfailed", error);
|
|
7476
|
+
};
|
|
7477
|
+
}
|
|
7478
|
+
async goto(url, opts) {
|
|
7479
|
+
await this.view.navigate(url);
|
|
7480
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
7481
|
+
}
|
|
7482
|
+
async goBack() {
|
|
7483
|
+
await this.view.goBack();
|
|
7484
|
+
}
|
|
7485
|
+
async goForward() {
|
|
7486
|
+
await this.view.goForward();
|
|
7487
|
+
}
|
|
7488
|
+
async reload() {
|
|
7489
|
+
await this.view.reload();
|
|
7490
|
+
}
|
|
7491
|
+
async evaluate(fnOrExpr, ...args) {
|
|
7492
|
+
let expr;
|
|
7493
|
+
if (typeof fnOrExpr === "function") {
|
|
7494
|
+
const serializedArgs = args.map((a) => JSON.stringify(a)).join(", ");
|
|
7495
|
+
expr = `(${fnOrExpr.toString()})(${serializedArgs})`;
|
|
7496
|
+
} else {
|
|
7497
|
+
expr = fnOrExpr;
|
|
7498
|
+
}
|
|
7499
|
+
return this.view.evaluate(expr);
|
|
7500
|
+
}
|
|
7501
|
+
async screenshot(opts) {
|
|
7502
|
+
const uint8 = await this.view.screenshot();
|
|
7503
|
+
return Buffer.from(uint8);
|
|
7504
|
+
}
|
|
7505
|
+
async click(selector, opts) {
|
|
7506
|
+
await this.view.click(selector, opts ? { button: opts.button } : undefined);
|
|
7507
|
+
}
|
|
7508
|
+
async type(selector, text, opts) {
|
|
7509
|
+
try {
|
|
7510
|
+
await this.view.click(selector);
|
|
7511
|
+
} catch {}
|
|
7512
|
+
await this.view.type(text);
|
|
7513
|
+
}
|
|
7514
|
+
async fill(selector, value) {
|
|
7515
|
+
await this.view.evaluate(`
|
|
7516
|
+
(() => {
|
|
7517
|
+
const el = document.querySelector(${JSON.stringify(selector)});
|
|
7518
|
+
if (el) { el.value = ''; el.dispatchEvent(new Event('input')); }
|
|
7519
|
+
})()
|
|
7520
|
+
`);
|
|
7521
|
+
await this.type(selector, value);
|
|
7522
|
+
}
|
|
7523
|
+
async press(key, opts) {
|
|
7524
|
+
await this.view.press(key, opts);
|
|
7525
|
+
}
|
|
7526
|
+
async scroll(direction, amount) {
|
|
7527
|
+
const dx = direction === "left" ? -amount : direction === "right" ? amount : 0;
|
|
7528
|
+
const dy = direction === "up" ? -amount : direction === "down" ? amount : 0;
|
|
7529
|
+
await this.view.scroll(dx, dy);
|
|
7530
|
+
}
|
|
7531
|
+
async scrollIntoView(selector) {
|
|
7532
|
+
await this.view.scrollTo(selector);
|
|
7533
|
+
}
|
|
7534
|
+
async hover(selector) {
|
|
7535
|
+
try {
|
|
7536
|
+
await this.view.scrollTo(selector);
|
|
7537
|
+
} catch {}
|
|
7538
|
+
}
|
|
7539
|
+
async resize(width, height) {
|
|
7540
|
+
await this.view.resize(width, height);
|
|
7541
|
+
}
|
|
7542
|
+
async $(selector) {
|
|
7543
|
+
const exists = await this.view.evaluate(`!!document.querySelector(${JSON.stringify(selector)})`);
|
|
7544
|
+
if (!exists)
|
|
7545
|
+
return null;
|
|
7546
|
+
return {
|
|
7547
|
+
textContent: async () => this.view.evaluate(`document.querySelector(${JSON.stringify(selector)})?.textContent ?? null`)
|
|
7548
|
+
};
|
|
7549
|
+
}
|
|
7550
|
+
async $$(selector) {
|
|
7551
|
+
const count = await this.view.evaluate(`document.querySelectorAll(${JSON.stringify(selector)}).length`);
|
|
7552
|
+
return Array.from({ length: count }, (_, i) => ({
|
|
7553
|
+
textContent: async () => this.view.evaluate(`document.querySelectorAll(${JSON.stringify(selector)})[${i}]?.textContent ?? null`)
|
|
7554
|
+
}));
|
|
7555
|
+
}
|
|
7556
|
+
async inputValue(selector) {
|
|
7557
|
+
return this.view.evaluate(`document.querySelector(${JSON.stringify(selector)})?.value ?? ''`);
|
|
7558
|
+
}
|
|
7559
|
+
async isChecked(selector) {
|
|
7560
|
+
return this.view.evaluate(`!!(document.querySelector(${JSON.stringify(selector)})?.checked)`);
|
|
7561
|
+
}
|
|
7562
|
+
async isVisible(selector) {
|
|
7563
|
+
return this.view.evaluate(`
|
|
7564
|
+
(() => {
|
|
7565
|
+
const el = document.querySelector(${JSON.stringify(selector)});
|
|
7566
|
+
if (!el) return false;
|
|
7567
|
+
const style = window.getComputedStyle(el);
|
|
7568
|
+
return style.display !== 'none' && style.visibility !== 'hidden' && el.offsetWidth > 0;
|
|
7569
|
+
})()
|
|
7570
|
+
`);
|
|
7571
|
+
}
|
|
7572
|
+
async isEnabled(selector) {
|
|
7573
|
+
return this.view.evaluate(`!(document.querySelector(${JSON.stringify(selector)})?.disabled)`);
|
|
7574
|
+
}
|
|
7575
|
+
async selectOption(selector, value) {
|
|
7576
|
+
await this.view.evaluate(`
|
|
7577
|
+
(() => {
|
|
7578
|
+
const el = document.querySelector(${JSON.stringify(selector)});
|
|
7579
|
+
if (el) {
|
|
7580
|
+
el.value = ${JSON.stringify(value)};
|
|
7581
|
+
el.dispatchEvent(new Event('change'));
|
|
7582
|
+
}
|
|
7583
|
+
})()
|
|
7584
|
+
`);
|
|
7585
|
+
return [value];
|
|
7586
|
+
}
|
|
7587
|
+
async check(selector) {
|
|
7588
|
+
await this.view.evaluate(`
|
|
7589
|
+
(() => {
|
|
7590
|
+
const el = document.querySelector(${JSON.stringify(selector)});
|
|
7591
|
+
if (el && !el.checked) { el.checked = true; el.dispatchEvent(new Event('change')); }
|
|
7592
|
+
})()
|
|
7593
|
+
`);
|
|
7594
|
+
}
|
|
7595
|
+
async uncheck(selector) {
|
|
7596
|
+
await this.view.evaluate(`
|
|
7597
|
+
(() => {
|
|
7598
|
+
const el = document.querySelector(${JSON.stringify(selector)});
|
|
7599
|
+
if (el && el.checked) { el.checked = false; el.dispatchEvent(new Event('change')); }
|
|
7600
|
+
})()
|
|
7601
|
+
`);
|
|
7602
|
+
}
|
|
7603
|
+
async setInputFiles(selector, files) {
|
|
7604
|
+
throw new Error("File upload not supported in Bun.WebView engine. Use engine: 'playwright' instead.");
|
|
7605
|
+
}
|
|
7606
|
+
getByRole(role, opts) {
|
|
7607
|
+
const name = opts?.name?.toString() ?? "";
|
|
7608
|
+
const selector = name ? `[role="${role}"][aria-label*="${name}"], ${role}[aria-label*="${name}"]` : `[role="${role}"], ${role}`;
|
|
7609
|
+
return {
|
|
7610
|
+
click: (clickOpts) => this.click(selector, clickOpts),
|
|
7611
|
+
fill: (value) => this.fill(selector, value),
|
|
7612
|
+
check: () => this.check(selector),
|
|
7613
|
+
uncheck: () => this.uncheck(selector),
|
|
7614
|
+
isVisible: () => this.isVisible(selector),
|
|
7615
|
+
textContent: () => this.view.evaluate(`document.querySelector(${JSON.stringify(selector)})?.textContent ?? null`),
|
|
7616
|
+
inputValue: () => this.inputValue(selector),
|
|
7617
|
+
first: () => ({
|
|
7618
|
+
click: (clickOpts) => this.click(selector, clickOpts),
|
|
7619
|
+
fill: (value) => this.fill(selector, value),
|
|
7620
|
+
textContent: () => this.view.evaluate(`document.querySelector(${JSON.stringify(selector)})?.textContent ?? null`),
|
|
7621
|
+
isVisible: () => this.isVisible(selector),
|
|
7622
|
+
hover: () => this.hover(selector),
|
|
7623
|
+
boundingBox: async () => null,
|
|
7624
|
+
scrollIntoViewIfNeeded: () => this.scrollIntoView(selector),
|
|
7625
|
+
evaluate: (fn) => this.view.evaluate(`(${fn.toString()})(document.querySelector(${JSON.stringify(selector)}))`),
|
|
7626
|
+
waitFor: (opts2) => {
|
|
7627
|
+
return new Promise((resolve, reject) => {
|
|
7628
|
+
const timeout = opts2?.timeout ?? 1e4;
|
|
7629
|
+
const start = Date.now();
|
|
7630
|
+
const check = async () => {
|
|
7631
|
+
const visible = await this.isVisible(selector);
|
|
7632
|
+
if (visible)
|
|
7633
|
+
return resolve();
|
|
7634
|
+
if (Date.now() - start > timeout)
|
|
7635
|
+
return reject(new Error(`Timeout waiting for ${selector}`));
|
|
7636
|
+
setTimeout(check, 100);
|
|
7637
|
+
};
|
|
7638
|
+
check();
|
|
7639
|
+
});
|
|
7640
|
+
}
|
|
7641
|
+
}),
|
|
7642
|
+
count: async () => {
|
|
7643
|
+
const count = await this.view.evaluate(`document.querySelectorAll(${JSON.stringify(selector)}).length`);
|
|
7644
|
+
return count;
|
|
7645
|
+
},
|
|
7646
|
+
nth: (n) => ({
|
|
7647
|
+
click: (clickOpts) => this.click(selector, clickOpts),
|
|
7648
|
+
textContent: () => this.view.evaluate(`document.querySelectorAll(${JSON.stringify(selector)})[${n}]?.textContent ?? null`),
|
|
7649
|
+
isVisible: () => this.isVisible(selector)
|
|
7650
|
+
})
|
|
7651
|
+
};
|
|
7652
|
+
}
|
|
7653
|
+
getByText(text, opts) {
|
|
7654
|
+
const selector = opts?.exact ? `*:is(button, a, span, div, p, h1, h2, h3, h4, label)` : "*";
|
|
7655
|
+
return {
|
|
7656
|
+
first: () => ({
|
|
7657
|
+
click: async (clickOpts) => {
|
|
7658
|
+
await this.view.evaluate(`
|
|
7659
|
+
(() => {
|
|
7660
|
+
const text = ${JSON.stringify(text)};
|
|
7661
|
+
const all = document.querySelectorAll('*');
|
|
7662
|
+
for (const el of all) {
|
|
7663
|
+
if (el.children.length === 0 && el.textContent?.trim() === text) {
|
|
7664
|
+
el.click(); return;
|
|
7665
|
+
}
|
|
7666
|
+
}
|
|
7667
|
+
for (const el of all) {
|
|
7668
|
+
if (el.textContent?.includes(text)) { el.click(); return; }
|
|
7669
|
+
}
|
|
7670
|
+
})()
|
|
7671
|
+
`);
|
|
7672
|
+
},
|
|
7673
|
+
waitFor: (waitOpts) => {
|
|
7674
|
+
const timeout = waitOpts?.timeout ?? 1e4;
|
|
7675
|
+
return new Promise((resolve, reject) => {
|
|
7676
|
+
const start = Date.now();
|
|
7677
|
+
const check = async () => {
|
|
7678
|
+
const found = await this.view.evaluate(`document.body?.textContent?.includes(${JSON.stringify(text)})`);
|
|
7679
|
+
if (found)
|
|
7680
|
+
return resolve();
|
|
7681
|
+
if (Date.now() - start > timeout)
|
|
7682
|
+
return reject(new Error(`Timeout: text "${text}" not found`));
|
|
7683
|
+
setTimeout(check, 100);
|
|
7684
|
+
};
|
|
7685
|
+
check();
|
|
7686
|
+
});
|
|
7687
|
+
}
|
|
7688
|
+
})
|
|
7689
|
+
};
|
|
7690
|
+
}
|
|
7691
|
+
locator(selector) {
|
|
7692
|
+
return {
|
|
7693
|
+
click: (opts) => this.click(selector, opts),
|
|
7694
|
+
fill: (value) => this.fill(selector, value),
|
|
7695
|
+
scrollIntoViewIfNeeded: () => this.scrollIntoView(selector),
|
|
7696
|
+
first: () => this.getByRole("*").first(),
|
|
7697
|
+
evaluate: (fn) => this.view.evaluate(`(${fn.toString()})(document.querySelector(${JSON.stringify(selector)}))`),
|
|
7698
|
+
waitFor: (opts) => {
|
|
7699
|
+
const timeout = opts?.timeout ?? 1e4;
|
|
7700
|
+
return new Promise((resolve, reject) => {
|
|
7701
|
+
const start = Date.now();
|
|
7702
|
+
const check = async () => {
|
|
7703
|
+
const exists = await this.view.evaluate(`!!document.querySelector(${JSON.stringify(selector)})`);
|
|
7704
|
+
if (exists)
|
|
7705
|
+
return resolve();
|
|
7706
|
+
if (Date.now() - start > timeout)
|
|
7707
|
+
return reject(new Error(`Timeout: ${selector}`));
|
|
7708
|
+
setTimeout(check, 100);
|
|
7709
|
+
};
|
|
7710
|
+
check();
|
|
7711
|
+
});
|
|
7712
|
+
}
|
|
7713
|
+
};
|
|
7714
|
+
}
|
|
7715
|
+
url() {
|
|
7716
|
+
return this.view.url;
|
|
7717
|
+
}
|
|
7718
|
+
async title() {
|
|
7719
|
+
return this.view.title || await this.evaluate("document.title");
|
|
7720
|
+
}
|
|
7721
|
+
viewportSize() {
|
|
7722
|
+
return { width: 1280, height: 720 };
|
|
7723
|
+
}
|
|
7724
|
+
async waitForLoadState(state, opts) {
|
|
7725
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
7726
|
+
}
|
|
7727
|
+
async waitForURL(pattern, opts) {
|
|
7728
|
+
const timeout = opts?.timeout ?? 30000;
|
|
7729
|
+
const start = Date.now();
|
|
7730
|
+
while (Date.now() - start < timeout) {
|
|
7731
|
+
const url = this.view.url;
|
|
7732
|
+
const matches = pattern instanceof RegExp ? pattern.test(url) : url.includes(pattern);
|
|
7733
|
+
if (matches)
|
|
7734
|
+
return;
|
|
7735
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
7736
|
+
}
|
|
7737
|
+
throw new Error(`Timeout waiting for URL to match ${pattern}`);
|
|
7738
|
+
}
|
|
7739
|
+
async waitForSelector(selector, opts) {
|
|
7740
|
+
const timeout = opts?.timeout ?? 1e4;
|
|
7741
|
+
const start = Date.now();
|
|
7742
|
+
while (Date.now() - start < timeout) {
|
|
7743
|
+
const exists = await this.view.evaluate(`!!document.querySelector(${JSON.stringify(selector)})`);
|
|
7744
|
+
if (exists)
|
|
7745
|
+
return;
|
|
7746
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
7747
|
+
}
|
|
7748
|
+
throw new Error(`Timeout waiting for ${selector}`);
|
|
7749
|
+
}
|
|
7750
|
+
async setContent(html) {
|
|
7751
|
+
await this.view.navigate(`data:text/html,${encodeURIComponent(html)}`);
|
|
7752
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
7753
|
+
}
|
|
7754
|
+
async content() {
|
|
7755
|
+
return this.view.evaluate("document.documentElement.outerHTML");
|
|
7756
|
+
}
|
|
7757
|
+
async addInitScript(script) {
|
|
7758
|
+
const expr = typeof script === "function" ? `(${script.toString()})()` : script;
|
|
7759
|
+
await this.view.evaluate(expr);
|
|
7760
|
+
}
|
|
7761
|
+
keyboard = {
|
|
7762
|
+
press: (key) => this.view.press(key)
|
|
7763
|
+
};
|
|
7764
|
+
context() {
|
|
7765
|
+
return {
|
|
7766
|
+
close: async () => {
|
|
7767
|
+
await this.close();
|
|
7768
|
+
},
|
|
7769
|
+
newPage: async () => {
|
|
7770
|
+
throw new Error("Multi-tab not supported in Bun.WebView. Use engine: 'playwright'");
|
|
7771
|
+
},
|
|
7772
|
+
cookies: async () => [],
|
|
7773
|
+
addCookies: async (_) => {},
|
|
7774
|
+
clearCookies: async () => {},
|
|
7775
|
+
newCDPSession: async () => {
|
|
7776
|
+
throw new Error("CDP session via context not available in Bun.WebView. Use view.cdp() when shipped.");
|
|
7777
|
+
},
|
|
7778
|
+
route: async (_pattern, _handler) => {
|
|
7779
|
+
throw new Error("Network interception not supported in Bun.WebView. Use engine: 'cdp' or 'playwright'.");
|
|
7780
|
+
},
|
|
7781
|
+
unrouteAll: async () => {},
|
|
7782
|
+
pages: () => [],
|
|
7783
|
+
addInitScript: async (script) => {
|
|
7784
|
+
await this.addInitScript(script);
|
|
7785
|
+
}
|
|
7786
|
+
};
|
|
7787
|
+
}
|
|
7788
|
+
on(event, handler) {
|
|
7789
|
+
if (!this._eventListeners.has(event))
|
|
7790
|
+
this._eventListeners.set(event, []);
|
|
7791
|
+
this._eventListeners.get(event).push(handler);
|
|
7792
|
+
return this;
|
|
7793
|
+
}
|
|
7794
|
+
off(event, handler) {
|
|
7795
|
+
const listeners = this._eventListeners.get(event) ?? [];
|
|
7796
|
+
this._eventListeners.set(event, listeners.filter((l) => l !== handler));
|
|
7797
|
+
return this;
|
|
7798
|
+
}
|
|
7799
|
+
_emit(event, ...args) {
|
|
7800
|
+
for (const handler of this._eventListeners.get(event) ?? []) {
|
|
7801
|
+
try {
|
|
7802
|
+
handler(...args);
|
|
7803
|
+
} catch {}
|
|
7804
|
+
}
|
|
7805
|
+
}
|
|
7806
|
+
async pdf(_opts) {
|
|
7807
|
+
throw new Error("PDF generation not supported in Bun.WebView. Use engine: 'playwright'.");
|
|
7808
|
+
}
|
|
7809
|
+
coverage = {
|
|
7810
|
+
startJSCoverage: async () => {},
|
|
7811
|
+
stopJSCoverage: async () => [],
|
|
7812
|
+
startCSSCoverage: async () => {},
|
|
7813
|
+
stopCSSCoverage: async () => []
|
|
7814
|
+
};
|
|
7815
|
+
setSessionId(id) {
|
|
7816
|
+
this._sessionId = id;
|
|
7817
|
+
}
|
|
7818
|
+
getSessionId() {
|
|
7819
|
+
return this._sessionId;
|
|
7820
|
+
}
|
|
7821
|
+
getNativeView() {
|
|
7822
|
+
return this.view;
|
|
7823
|
+
}
|
|
7824
|
+
async close() {
|
|
7825
|
+
try {
|
|
7826
|
+
await this.view.close();
|
|
7827
|
+
} catch {}
|
|
7828
|
+
}
|
|
7829
|
+
[Symbol.asyncDispose]() {
|
|
7830
|
+
return this.close();
|
|
7831
|
+
}
|
|
7832
|
+
}
|
|
7833
|
+
|
|
7390
7834
|
// src/engines/selector.ts
|
|
7391
7835
|
var ENGINE_MAP = {
|
|
7392
|
-
["scrape" /* SCRAPE */]: "
|
|
7393
|
-
["extract_links" /* EXTRACT_LINKS */]: "
|
|
7394
|
-
["status_check" /* STATUS_CHECK */]: "
|
|
7836
|
+
["scrape" /* SCRAPE */]: "bun",
|
|
7837
|
+
["extract_links" /* EXTRACT_LINKS */]: "bun",
|
|
7838
|
+
["status_check" /* STATUS_CHECK */]: "bun",
|
|
7839
|
+
["screenshot" /* SCREENSHOT */]: "bun",
|
|
7840
|
+
["spa_navigate" /* SPA_NAVIGATE */]: "bun",
|
|
7395
7841
|
["form_fill" /* FORM_FILL */]: "playwright",
|
|
7396
|
-
["spa_navigate" /* SPA_NAVIGATE */]: "playwright",
|
|
7397
|
-
["screenshot" /* SCREENSHOT */]: "playwright",
|
|
7398
7842
|
["auth_flow" /* AUTH_FLOW */]: "playwright",
|
|
7399
7843
|
["multi_tab" /* MULTI_TAB */]: "playwright",
|
|
7400
7844
|
["record_replay" /* RECORD_REPLAY */]: "playwright",
|
|
@@ -7408,6 +7852,14 @@ function selectEngine(useCase, explicit) {
|
|
|
7408
7852
|
if (explicit && explicit !== "auto")
|
|
7409
7853
|
return explicit;
|
|
7410
7854
|
const preferred = ENGINE_MAP[useCase];
|
|
7855
|
+
if (preferred === "bun") {
|
|
7856
|
+
if (isBunWebViewAvailable())
|
|
7857
|
+
return "bun";
|
|
7858
|
+
if (useCase === "scrape" /* SCRAPE */ || useCase === "extract_links" /* EXTRACT_LINKS */ || useCase === "status_check" /* STATUS_CHECK */) {
|
|
7859
|
+
return isLightpandaAvailable() ? "lightpanda" : "playwright";
|
|
7860
|
+
}
|
|
7861
|
+
return "playwright";
|
|
7862
|
+
}
|
|
7411
7863
|
if (preferred === "lightpanda" && !isLightpandaAvailable()) {
|
|
7412
7864
|
return "playwright";
|
|
7413
7865
|
}
|
|
@@ -7416,6 +7868,8 @@ function selectEngine(useCase, explicit) {
|
|
|
7416
7868
|
function isEngineAvailable(engine) {
|
|
7417
7869
|
if (engine === "auto")
|
|
7418
7870
|
return true;
|
|
7871
|
+
if (engine === "bun")
|
|
7872
|
+
return isBunWebViewAvailable();
|
|
7419
7873
|
if (engine === "playwright")
|
|
7420
7874
|
return true;
|
|
7421
7875
|
if (engine === "cdp")
|
|
@@ -7559,6 +8013,7 @@ function startHAR(page) {
|
|
|
7559
8013
|
}
|
|
7560
8014
|
|
|
7561
8015
|
// src/lib/console.ts
|
|
8016
|
+
init_console_log();
|
|
7562
8017
|
function enableConsoleCapture(page, sessionId) {
|
|
7563
8018
|
const onConsole = (msg) => {
|
|
7564
8019
|
const levelMap = {
|
|
@@ -7708,12 +8163,30 @@ function setupDialogHandler(page, sessionId) {
|
|
|
7708
8163
|
|
|
7709
8164
|
// src/lib/session.ts
|
|
7710
8165
|
var handles = new Map;
|
|
8166
|
+
function createBunProxy(view) {
|
|
8167
|
+
return view;
|
|
8168
|
+
}
|
|
7711
8169
|
async function createSession2(opts = {}) {
|
|
7712
8170
|
const engine = opts.engine === "auto" || !opts.engine ? selectEngine(opts.useCase ?? "spa_navigate" /* SPA_NAVIGATE */, opts.engine) : opts.engine;
|
|
7713
8171
|
const resolvedEngine = engine === "auto" ? "playwright" : engine;
|
|
7714
|
-
let browser;
|
|
8172
|
+
let browser = null;
|
|
8173
|
+
let bunView = null;
|
|
7715
8174
|
let page;
|
|
7716
|
-
if (resolvedEngine === "
|
|
8175
|
+
if (resolvedEngine === "bun") {
|
|
8176
|
+
if (!isBunWebViewAvailable()) {
|
|
8177
|
+
console.warn("[browser] Bun.WebView requested but not available \u2014 falling back to playwright. Run: bun upgrade --canary");
|
|
8178
|
+
browser = await launchPlaywright({ headless: opts.headless ?? true, viewport: opts.viewport, userAgent: opts.userAgent });
|
|
8179
|
+
page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
|
|
8180
|
+
} else {
|
|
8181
|
+
bunView = new BunWebViewSession({
|
|
8182
|
+
width: opts.viewport?.width ?? 1280,
|
|
8183
|
+
height: opts.viewport?.height ?? 720,
|
|
8184
|
+
profile: opts.name ?? undefined
|
|
8185
|
+
});
|
|
8186
|
+
if (opts.stealth) {}
|
|
8187
|
+
page = createBunProxy(bunView);
|
|
8188
|
+
}
|
|
8189
|
+
} else if (resolvedEngine === "lightpanda") {
|
|
7717
8190
|
browser = await connectLightpanda();
|
|
7718
8191
|
const context = await browser.newContext({ viewport: opts.viewport ?? { width: 1280, height: 720 } });
|
|
7719
8192
|
page = await context.newPage();
|
|
@@ -7723,41 +8196,67 @@ async function createSession2(opts = {}) {
|
|
|
7723
8196
|
viewport: opts.viewport,
|
|
7724
8197
|
userAgent: opts.userAgent
|
|
7725
8198
|
});
|
|
7726
|
-
page = await getPage(browser, {
|
|
7727
|
-
viewport: opts.viewport,
|
|
7728
|
-
userAgent: opts.userAgent
|
|
7729
|
-
});
|
|
8199
|
+
page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
|
|
7730
8200
|
}
|
|
8201
|
+
const sessionName = opts.name ?? (opts.startUrl ? (() => {
|
|
8202
|
+
try {
|
|
8203
|
+
return new URL(opts.startUrl).hostname;
|
|
8204
|
+
} catch {
|
|
8205
|
+
return;
|
|
8206
|
+
}
|
|
8207
|
+
})() : undefined);
|
|
7731
8208
|
const session = createSession({
|
|
7732
|
-
engine: resolvedEngine,
|
|
8209
|
+
engine: bunView ? "bun" : browser ? resolvedEngine : resolvedEngine,
|
|
7733
8210
|
projectId: opts.projectId,
|
|
7734
8211
|
agentId: opts.agentId,
|
|
7735
8212
|
startUrl: opts.startUrl,
|
|
7736
|
-
name:
|
|
8213
|
+
name: sessionName
|
|
7737
8214
|
});
|
|
7738
|
-
if (opts.stealth) {
|
|
8215
|
+
if (opts.stealth && !bunView) {
|
|
7739
8216
|
try {
|
|
7740
8217
|
await applyStealthPatches(page);
|
|
7741
8218
|
} catch {}
|
|
7742
8219
|
}
|
|
7743
8220
|
const cleanups = [];
|
|
7744
|
-
if (
|
|
7745
|
-
|
|
7746
|
-
|
|
7747
|
-
|
|
7748
|
-
|
|
7749
|
-
|
|
8221
|
+
if (!bunView) {
|
|
8222
|
+
if (opts.captureNetwork !== false) {
|
|
8223
|
+
try {
|
|
8224
|
+
cleanups.push(enableNetworkLogging(page, session.id));
|
|
8225
|
+
} catch {}
|
|
8226
|
+
}
|
|
8227
|
+
if (opts.captureConsole !== false) {
|
|
8228
|
+
try {
|
|
8229
|
+
cleanups.push(enableConsoleCapture(page, session.id));
|
|
8230
|
+
} catch {}
|
|
8231
|
+
}
|
|
7750
8232
|
try {
|
|
7751
|
-
cleanups.push(
|
|
8233
|
+
cleanups.push(setupDialogHandler(page, session.id));
|
|
7752
8234
|
} catch {}
|
|
8235
|
+
} else {
|
|
8236
|
+
if (opts.captureConsole !== false) {
|
|
8237
|
+
try {
|
|
8238
|
+
const { logConsoleMessage: logConsoleMessage2 } = await Promise.resolve().then(() => (init_console_log(), exports_console_log));
|
|
8239
|
+
await bunView.addInitScript(`
|
|
8240
|
+
(() => {
|
|
8241
|
+
const orig = { log: console.log, warn: console.warn, error: console.error, debug: console.debug, info: console.info };
|
|
8242
|
+
['log','warn','error','debug','info'].forEach(level => {
|
|
8243
|
+
console[level] = (...args) => {
|
|
8244
|
+
orig[level](...args);
|
|
8245
|
+
};
|
|
8246
|
+
});
|
|
8247
|
+
})()
|
|
8248
|
+
`);
|
|
8249
|
+
} catch {}
|
|
8250
|
+
}
|
|
7753
8251
|
}
|
|
7754
|
-
|
|
7755
|
-
cleanups.push(setupDialogHandler(page, session.id));
|
|
7756
|
-
} catch {}
|
|
7757
|
-
handles.set(session.id, { browser, page, engine: resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 } });
|
|
8252
|
+
handles.set(session.id, { browser, bunView, page, engine: bunView ? "bun" : resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 } });
|
|
7758
8253
|
if (opts.startUrl) {
|
|
7759
8254
|
try {
|
|
7760
|
-
|
|
8255
|
+
if (bunView) {
|
|
8256
|
+
await bunView.goto(opts.startUrl);
|
|
8257
|
+
} else {
|
|
8258
|
+
await page.goto(opts.startUrl, { waitUntil: "domcontentloaded" });
|
|
8259
|
+
}
|
|
7761
8260
|
} catch {}
|
|
7762
8261
|
}
|
|
7763
8262
|
return { session, page };
|
|
@@ -7767,17 +8266,29 @@ function getSessionPage(sessionId) {
|
|
|
7767
8266
|
if (!handle)
|
|
7768
8267
|
throw new SessionNotFoundError(sessionId);
|
|
7769
8268
|
try {
|
|
7770
|
-
handle.
|
|
8269
|
+
if (handle.bunView) {
|
|
8270
|
+
handle.bunView.url();
|
|
8271
|
+
} else {
|
|
8272
|
+
handle.page.url();
|
|
8273
|
+
}
|
|
7771
8274
|
} catch {
|
|
7772
8275
|
handles.delete(sessionId);
|
|
7773
8276
|
throw new SessionNotFoundError(sessionId);
|
|
7774
8277
|
}
|
|
7775
8278
|
return handle.page;
|
|
7776
8279
|
}
|
|
8280
|
+
function getSessionBunView(sessionId) {
|
|
8281
|
+
return handles.get(sessionId)?.bunView ?? null;
|
|
8282
|
+
}
|
|
8283
|
+
function isBunSession(sessionId) {
|
|
8284
|
+
return handles.get(sessionId)?.engine === "bun";
|
|
8285
|
+
}
|
|
7777
8286
|
function getSessionBrowser(sessionId) {
|
|
7778
8287
|
const handle = handles.get(sessionId);
|
|
7779
8288
|
if (!handle)
|
|
7780
8289
|
throw new SessionNotFoundError(sessionId);
|
|
8290
|
+
if (!handle.browser)
|
|
8291
|
+
throw new BrowserError("This session uses Bun.WebView (no Playwright browser)", "NO_PLAYWRIGHT_BROWSER");
|
|
7781
8292
|
return handle.browser;
|
|
7782
8293
|
}
|
|
7783
8294
|
function getSessionEngine(sessionId) {
|
|
@@ -7803,12 +8314,19 @@ async function closeSession2(sessionId) {
|
|
|
7803
8314
|
cleanup();
|
|
7804
8315
|
} catch {}
|
|
7805
8316
|
}
|
|
7806
|
-
|
|
7807
|
-
|
|
7808
|
-
|
|
7809
|
-
|
|
7810
|
-
|
|
7811
|
-
|
|
8317
|
+
if (handle.bunView) {
|
|
8318
|
+
try {
|
|
8319
|
+
await handle.bunView.close();
|
|
8320
|
+
} catch {}
|
|
8321
|
+
} else {
|
|
8322
|
+
try {
|
|
8323
|
+
await handle.page.context().close();
|
|
8324
|
+
} catch {}
|
|
8325
|
+
try {
|
|
8326
|
+
if (handle.browser)
|
|
8327
|
+
await closeBrowser(handle.browser);
|
|
8328
|
+
} catch {}
|
|
8329
|
+
}
|
|
7812
8330
|
handles.delete(sessionId);
|
|
7813
8331
|
}
|
|
7814
8332
|
return closeSession(sessionId);
|
|
@@ -8190,9 +8708,19 @@ async function getLinks(page, baseUrl) {
|
|
|
8190
8708
|
}, baseUrl ?? page.url());
|
|
8191
8709
|
}
|
|
8192
8710
|
async function getTitle(page) {
|
|
8711
|
+
if (typeof page.getNativeView === "function") {
|
|
8712
|
+
const nativeView = page.getNativeView();
|
|
8713
|
+
const t = nativeView?.title;
|
|
8714
|
+
return typeof t === "string" && t ? t : "";
|
|
8715
|
+
}
|
|
8193
8716
|
return page.title();
|
|
8194
8717
|
}
|
|
8195
8718
|
async function getUrl(page) {
|
|
8719
|
+
if (typeof page.getNativeView === "function") {
|
|
8720
|
+
const nativeView = page.getNativeView();
|
|
8721
|
+
const u = nativeView?.url;
|
|
8722
|
+
return typeof u === "string" ? u : "";
|
|
8723
|
+
}
|
|
8196
8724
|
return page.url();
|
|
8197
8725
|
}
|
|
8198
8726
|
async function getMetaTags(page) {
|
|
@@ -8399,11 +8927,12 @@ async function startCoverage(page) {
|
|
|
8399
8927
|
}
|
|
8400
8928
|
// src/lib/screenshot.ts
|
|
8401
8929
|
var import_sharp = __toESM(require_lib(), 1);
|
|
8402
|
-
import { join as
|
|
8403
|
-
import { mkdirSync as
|
|
8404
|
-
import { homedir as
|
|
8930
|
+
import { join as join3 } from "path";
|
|
8931
|
+
import { mkdirSync as mkdirSync3 } from "fs";
|
|
8932
|
+
import { homedir as homedir3 } from "os";
|
|
8405
8933
|
|
|
8406
8934
|
// src/db/gallery.ts
|
|
8935
|
+
init_schema();
|
|
8407
8936
|
import { randomUUID as randomUUID10 } from "crypto";
|
|
8408
8937
|
function deserialize3(row) {
|
|
8409
8938
|
return {
|
|
@@ -8446,13 +8975,13 @@ function getEntry(id) {
|
|
|
8446
8975
|
|
|
8447
8976
|
// src/lib/screenshot.ts
|
|
8448
8977
|
function getDataDir2() {
|
|
8449
|
-
return process.env["BROWSER_DATA_DIR"] ??
|
|
8978
|
+
return process.env["BROWSER_DATA_DIR"] ?? join3(homedir3(), ".browser");
|
|
8450
8979
|
}
|
|
8451
8980
|
function getScreenshotDir(projectId) {
|
|
8452
|
-
const base =
|
|
8981
|
+
const base = join3(getDataDir2(), "screenshots");
|
|
8453
8982
|
const date = new Date().toISOString().split("T")[0];
|
|
8454
|
-
const dir = projectId ?
|
|
8455
|
-
|
|
8983
|
+
const dir = projectId ? join3(base, projectId, date) : join3(base, date);
|
|
8984
|
+
mkdirSync3(dir, { recursive: true });
|
|
8456
8985
|
return dir;
|
|
8457
8986
|
}
|
|
8458
8987
|
async function compressBuffer(raw, format, quality, maxWidth) {
|
|
@@ -8467,7 +8996,7 @@ async function compressBuffer(raw, format, quality, maxWidth) {
|
|
|
8467
8996
|
}
|
|
8468
8997
|
}
|
|
8469
8998
|
async function generateThumbnail(raw, dir, stem) {
|
|
8470
|
-
const thumbPath =
|
|
8999
|
+
const thumbPath = join3(dir, `${stem}.thumb.webp`);
|
|
8471
9000
|
const thumbBuffer = await import_sharp.default(raw).resize({ width: 200, withoutEnlargement: true }).webp({ quality: 70, effort: 3 }).toBuffer();
|
|
8472
9001
|
await Bun.write(thumbPath, thumbBuffer);
|
|
8473
9002
|
return { path: thumbPath, base64: thumbBuffer.toString("base64") };
|
|
@@ -8486,27 +9015,45 @@ async function takeScreenshot(page, opts) {
|
|
|
8486
9015
|
type: "png"
|
|
8487
9016
|
};
|
|
8488
9017
|
let rawBuffer;
|
|
9018
|
+
const isBunView = typeof page.getNativeView === "function";
|
|
8489
9019
|
if (opts?.selector) {
|
|
8490
|
-
|
|
8491
|
-
|
|
8492
|
-
|
|
8493
|
-
|
|
9020
|
+
if (isBunView) {
|
|
9021
|
+
const uint8 = await page.screenshot();
|
|
9022
|
+
rawBuffer = Buffer.from(uint8 instanceof Uint8Array ? uint8 : await uint8);
|
|
9023
|
+
} else {
|
|
9024
|
+
const el = await page.$(opts.selector);
|
|
9025
|
+
if (!el)
|
|
9026
|
+
throw new BrowserError(`Element not found: ${opts.selector}`, "ELEMENT_NOT_FOUND");
|
|
9027
|
+
rawBuffer = await el.screenshot(rawOpts);
|
|
9028
|
+
}
|
|
9029
|
+
} else if (isBunView) {
|
|
9030
|
+
const uint8 = await page.screenshot();
|
|
9031
|
+
rawBuffer = Buffer.from(uint8 instanceof Uint8Array ? uint8 : await uint8);
|
|
8494
9032
|
} else {
|
|
8495
9033
|
rawBuffer = await page.screenshot(rawOpts);
|
|
8496
9034
|
}
|
|
8497
9035
|
const originalSizeBytes = rawBuffer.length;
|
|
8498
9036
|
let finalBuffer;
|
|
8499
|
-
|
|
8500
|
-
|
|
8501
|
-
|
|
8502
|
-
|
|
8503
|
-
|
|
9037
|
+
let compressed = true;
|
|
9038
|
+
let fallback = false;
|
|
9039
|
+
try {
|
|
9040
|
+
if (compress && format !== "png") {
|
|
9041
|
+
finalBuffer = await compressBuffer(rawBuffer, format, quality ?? 82, maxWidth);
|
|
9042
|
+
} else if (compress && format === "png") {
|
|
9043
|
+
finalBuffer = await compressBuffer(rawBuffer, "png", quality ?? 9, maxWidth);
|
|
9044
|
+
} else {
|
|
9045
|
+
finalBuffer = rawBuffer;
|
|
9046
|
+
compressed = false;
|
|
9047
|
+
}
|
|
9048
|
+
} catch (sharpErr) {
|
|
9049
|
+
fallback = true;
|
|
9050
|
+
compressed = false;
|
|
8504
9051
|
finalBuffer = rawBuffer;
|
|
8505
9052
|
}
|
|
8506
9053
|
const compressedSizeBytes = finalBuffer.length;
|
|
8507
9054
|
const compressionRatio = originalSizeBytes > 0 ? compressedSizeBytes / originalSizeBytes : 1;
|
|
8508
9055
|
const ext = format;
|
|
8509
|
-
const screenshotPath = opts?.path ??
|
|
9056
|
+
const screenshotPath = opts?.path ?? join3(dir, `${stem}.${ext}`);
|
|
8510
9057
|
await Bun.write(screenshotPath, finalBuffer);
|
|
8511
9058
|
let thumbnailPath;
|
|
8512
9059
|
let thumbnailBase64;
|
|
@@ -8528,7 +9075,8 @@ async function takeScreenshot(page, opts) {
|
|
|
8528
9075
|
compressed_size_bytes: compressedSizeBytes,
|
|
8529
9076
|
compression_ratio: compressionRatio,
|
|
8530
9077
|
thumbnail_path: thumbnailPath,
|
|
8531
|
-
thumbnail_base64: thumbnailBase64
|
|
9078
|
+
thumbnail_base64: thumbnailBase64,
|
|
9079
|
+
...fallback ? { fallback: true, compressed: false } : {}
|
|
8532
9080
|
};
|
|
8533
9081
|
if (opts?.track !== false) {
|
|
8534
9082
|
try {
|
|
@@ -8565,12 +9113,12 @@ async function takeScreenshot(page, opts) {
|
|
|
8565
9113
|
}
|
|
8566
9114
|
async function generatePDF(page, opts) {
|
|
8567
9115
|
try {
|
|
8568
|
-
const base =
|
|
9116
|
+
const base = join3(getDataDir2(), "pdfs");
|
|
8569
9117
|
const date = new Date().toISOString().split("T")[0];
|
|
8570
|
-
const dir = opts?.projectId ?
|
|
8571
|
-
|
|
9118
|
+
const dir = opts?.projectId ? join3(base, opts.projectId, date) : join3(base, date);
|
|
9119
|
+
mkdirSync3(dir, { recursive: true });
|
|
8572
9120
|
const timestamp = Date.now();
|
|
8573
|
-
const pdfPath = opts?.path ??
|
|
9121
|
+
const pdfPath = opts?.path ?? join3(dir, `${timestamp}.pdf`);
|
|
8574
9122
|
const buffer = await page.pdf({
|
|
8575
9123
|
path: pdfPath,
|
|
8576
9124
|
format: opts?.format ?? "A4",
|
|
@@ -8942,6 +9490,7 @@ export {
|
|
|
8942
9490
|
launchLightpanda,
|
|
8943
9491
|
isLightpandaAvailable,
|
|
8944
9492
|
isEngineAvailable,
|
|
9493
|
+
isBunSession,
|
|
8945
9494
|
isAgentStale,
|
|
8946
9495
|
inferUseCase,
|
|
8947
9496
|
hoverRef,
|
|
@@ -8961,6 +9510,7 @@ export {
|
|
|
8961
9510
|
getSessionPage,
|
|
8962
9511
|
getSessionEngine,
|
|
8963
9512
|
getSessionByName2 as getSessionByName,
|
|
9513
|
+
getSessionBunView,
|
|
8964
9514
|
getSessionBrowser,
|
|
8965
9515
|
getSession,
|
|
8966
9516
|
getRecording,
|