@hasna/browser 0.0.2 → 0.0.4
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 +1576 -289
- package/dist/db/sessions.d.ts.map +1 -1
- package/dist/index.js +418 -157
- package/dist/lib/actions-ref.test.d.ts +2 -0
- package/dist/lib/actions-ref.test.d.ts.map +1 -0
- package/dist/lib/actions.d.ts +12 -0
- package/dist/lib/actions.d.ts.map +1 -1
- package/dist/lib/annotate.d.ts +18 -0
- package/dist/lib/annotate.d.ts.map +1 -0
- package/dist/lib/annotate.test.d.ts +2 -0
- package/dist/lib/annotate.test.d.ts.map +1 -0
- package/dist/lib/dialogs.d.ts +15 -0
- package/dist/lib/dialogs.d.ts.map +1 -0
- package/dist/lib/profiles.d.ts +23 -0
- package/dist/lib/profiles.d.ts.map +1 -0
- 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-v3.test.d.ts +2 -0
- package/dist/lib/session-v3.test.d.ts.map +1 -0
- package/dist/lib/session.d.ts +5 -0
- package/dist/lib/session.d.ts.map +1 -1
- package/dist/lib/snapshot-diff.test.d.ts +2 -0
- package/dist/lib/snapshot-diff.test.d.ts.map +1 -0
- package/dist/lib/snapshot.d.ts +33 -0
- package/dist/lib/snapshot.d.ts.map +1 -0
- package/dist/lib/snapshot.test.d.ts +2 -0
- package/dist/lib/snapshot.test.d.ts.map +1 -0
- package/dist/lib/stealth.d.ts +5 -0
- package/dist/lib/stealth.d.ts.map +1 -0
- package/dist/lib/stealth.test.d.ts +2 -0
- package/dist/lib/stealth.test.d.ts.map +1 -0
- package/dist/lib/tabs.d.ts +18 -0
- package/dist/lib/tabs.d.ts.map +1 -0
- package/dist/mcp/index.js +1591 -312
- package/dist/mcp/v4.test.d.ts +2 -0
- package/dist/mcp/v4.test.d.ts.map +1 -0
- package/dist/server/index.js +340 -173
- package/dist/types/index.d.ts +35 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v4.test.d.ts","sourceRoot":"","sources":["../../src/mcp/v4.test.ts"],"names":[],"mappings":""}
|
package/dist/server/index.js
CHANGED
|
@@ -6881,7 +6881,14 @@ import { randomUUID } from "crypto";
|
|
|
6881
6881
|
function createSession(data) {
|
|
6882
6882
|
const db = getDatabase();
|
|
6883
6883
|
const id = randomUUID();
|
|
6884
|
-
|
|
6884
|
+
let name = data.name ?? null;
|
|
6885
|
+
if (name) {
|
|
6886
|
+
const existing = db.query("SELECT id FROM sessions WHERE name = ?").get(name);
|
|
6887
|
+
if (existing) {
|
|
6888
|
+
name = `${name}-${id.slice(0, 6)}`;
|
|
6889
|
+
}
|
|
6890
|
+
}
|
|
6891
|
+
db.prepare("INSERT INTO sessions (id, engine, project_id, agent_id, start_url, name) VALUES (?, ?, ?, ?, ?, ?)").run(id, data.engine, data.projectId ?? null, data.agentId ?? null, data.startUrl ?? null, name);
|
|
6885
6892
|
return getSession(id);
|
|
6886
6893
|
}
|
|
6887
6894
|
function getSession(id) {
|
|
@@ -7072,6 +7079,273 @@ function selectEngine(useCase, explicit) {
|
|
|
7072
7079
|
return preferred;
|
|
7073
7080
|
}
|
|
7074
7081
|
|
|
7082
|
+
// src/db/network-log.ts
|
|
7083
|
+
init_schema();
|
|
7084
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
7085
|
+
function logRequest(data) {
|
|
7086
|
+
const db = getDatabase();
|
|
7087
|
+
const id = randomUUID2();
|
|
7088
|
+
db.prepare(`INSERT INTO network_log (id, session_id, method, url, status_code, request_headers,
|
|
7089
|
+
response_headers, request_body, body_size, duration_ms, resource_type)
|
|
7090
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, data.session_id, data.method, data.url, data.status_code ?? null, data.request_headers ?? null, data.response_headers ?? null, data.request_body ?? null, data.body_size ?? null, data.duration_ms ?? null, data.resource_type ?? null);
|
|
7091
|
+
return getNetworkRequest(id);
|
|
7092
|
+
}
|
|
7093
|
+
function getNetworkRequest(id) {
|
|
7094
|
+
const db = getDatabase();
|
|
7095
|
+
return db.query("SELECT * FROM network_log WHERE id = ?").get(id) ?? null;
|
|
7096
|
+
}
|
|
7097
|
+
function getNetworkLog(sessionId) {
|
|
7098
|
+
const db = getDatabase();
|
|
7099
|
+
return db.query("SELECT * FROM network_log WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
|
|
7100
|
+
}
|
|
7101
|
+
function clearNetworkLog(sessionId) {
|
|
7102
|
+
const db = getDatabase();
|
|
7103
|
+
db.prepare("DELETE FROM network_log WHERE session_id = ?").run(sessionId);
|
|
7104
|
+
}
|
|
7105
|
+
|
|
7106
|
+
// src/lib/network.ts
|
|
7107
|
+
function enableNetworkLogging(page, sessionId) {
|
|
7108
|
+
const requestStart = new Map;
|
|
7109
|
+
const onRequest = (req) => {
|
|
7110
|
+
requestStart.set(req.url(), Date.now());
|
|
7111
|
+
};
|
|
7112
|
+
const onResponse = (res) => {
|
|
7113
|
+
const start = requestStart.get(res.url()) ?? Date.now();
|
|
7114
|
+
const duration = Date.now() - start;
|
|
7115
|
+
const req = res.request();
|
|
7116
|
+
try {
|
|
7117
|
+
logRequest({
|
|
7118
|
+
session_id: sessionId,
|
|
7119
|
+
method: req.method(),
|
|
7120
|
+
url: res.url(),
|
|
7121
|
+
status_code: res.status(),
|
|
7122
|
+
request_headers: JSON.stringify(req.headers()),
|
|
7123
|
+
response_headers: JSON.stringify(res.headers()),
|
|
7124
|
+
body_size: res.headers()["content-length"] != null ? parseInt(res.headers()["content-length"]) : undefined,
|
|
7125
|
+
duration_ms: duration,
|
|
7126
|
+
resource_type: req.resourceType()
|
|
7127
|
+
});
|
|
7128
|
+
} catch {}
|
|
7129
|
+
};
|
|
7130
|
+
page.on("request", onRequest);
|
|
7131
|
+
page.on("response", onResponse);
|
|
7132
|
+
return () => {
|
|
7133
|
+
page.off("request", onRequest);
|
|
7134
|
+
page.off("response", onResponse);
|
|
7135
|
+
};
|
|
7136
|
+
}
|
|
7137
|
+
function startHAR(page) {
|
|
7138
|
+
const entries = [];
|
|
7139
|
+
const requestStart = new Map;
|
|
7140
|
+
const onRequest = (req) => {
|
|
7141
|
+
requestStart.set(req.url() + req.method(), {
|
|
7142
|
+
time: Date.now(),
|
|
7143
|
+
method: req.method(),
|
|
7144
|
+
headers: req.headers(),
|
|
7145
|
+
postData: req.postData() ?? undefined
|
|
7146
|
+
});
|
|
7147
|
+
};
|
|
7148
|
+
const onResponse = async (res) => {
|
|
7149
|
+
const key = res.url() + res.request().method();
|
|
7150
|
+
const start = requestStart.get(key);
|
|
7151
|
+
if (!start)
|
|
7152
|
+
return;
|
|
7153
|
+
const duration = Date.now() - start.time;
|
|
7154
|
+
const entry = {
|
|
7155
|
+
startedDateTime: new Date(start.time).toISOString(),
|
|
7156
|
+
time: duration,
|
|
7157
|
+
request: {
|
|
7158
|
+
method: start.method,
|
|
7159
|
+
url: res.url(),
|
|
7160
|
+
headers: Object.entries(start.headers).map(([name, value]) => ({ name, value })),
|
|
7161
|
+
postData: start.postData ? { text: start.postData } : undefined
|
|
7162
|
+
},
|
|
7163
|
+
response: {
|
|
7164
|
+
status: res.status(),
|
|
7165
|
+
statusText: res.statusText(),
|
|
7166
|
+
headers: Object.entries(res.headers()).map(([name, value]) => ({ name, value })),
|
|
7167
|
+
content: {
|
|
7168
|
+
size: parseInt(res.headers()["content-length"] ?? "0") || 0,
|
|
7169
|
+
mimeType: res.headers()["content-type"] ?? "application/octet-stream"
|
|
7170
|
+
}
|
|
7171
|
+
},
|
|
7172
|
+
timings: { send: 0, wait: duration, receive: 0 }
|
|
7173
|
+
};
|
|
7174
|
+
entries.push(entry);
|
|
7175
|
+
requestStart.delete(key);
|
|
7176
|
+
};
|
|
7177
|
+
page.on("request", onRequest);
|
|
7178
|
+
page.on("response", onResponse);
|
|
7179
|
+
return {
|
|
7180
|
+
entries,
|
|
7181
|
+
stop: () => {
|
|
7182
|
+
page.off("request", onRequest);
|
|
7183
|
+
page.off("response", onResponse);
|
|
7184
|
+
return {
|
|
7185
|
+
log: {
|
|
7186
|
+
version: "1.2",
|
|
7187
|
+
creator: { name: "@hasna/browser", version: "0.0.1" },
|
|
7188
|
+
entries
|
|
7189
|
+
}
|
|
7190
|
+
};
|
|
7191
|
+
}
|
|
7192
|
+
};
|
|
7193
|
+
}
|
|
7194
|
+
|
|
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
|
+
// src/lib/console.ts
|
|
7217
|
+
function enableConsoleCapture(page, sessionId) {
|
|
7218
|
+
const onConsole = (msg) => {
|
|
7219
|
+
const levelMap = {
|
|
7220
|
+
log: "log",
|
|
7221
|
+
warn: "warn",
|
|
7222
|
+
error: "error",
|
|
7223
|
+
debug: "debug",
|
|
7224
|
+
info: "info",
|
|
7225
|
+
warning: "warn"
|
|
7226
|
+
};
|
|
7227
|
+
const level = levelMap[msg.type()] ?? "log";
|
|
7228
|
+
const location = msg.location();
|
|
7229
|
+
try {
|
|
7230
|
+
logConsoleMessage({
|
|
7231
|
+
session_id: sessionId,
|
|
7232
|
+
level,
|
|
7233
|
+
message: msg.text(),
|
|
7234
|
+
source: location.url || undefined,
|
|
7235
|
+
line_number: location.lineNumber || undefined
|
|
7236
|
+
});
|
|
7237
|
+
} catch {}
|
|
7238
|
+
};
|
|
7239
|
+
page.on("console", onConsole);
|
|
7240
|
+
return () => page.off("console", onConsole);
|
|
7241
|
+
}
|
|
7242
|
+
|
|
7243
|
+
// src/lib/stealth.ts
|
|
7244
|
+
var REALISTIC_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36";
|
|
7245
|
+
var STEALTH_SCRIPT = `
|
|
7246
|
+
// \u2500\u2500 1. Remove navigator.webdriver flag \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
7247
|
+
Object.defineProperty(navigator, 'webdriver', {
|
|
7248
|
+
get: () => false,
|
|
7249
|
+
configurable: true,
|
|
7250
|
+
});
|
|
7251
|
+
|
|
7252
|
+
// \u2500\u2500 2. Override navigator.plugins to show typical Chrome plugins \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
7253
|
+
Object.defineProperty(navigator, 'plugins', {
|
|
7254
|
+
get: () => {
|
|
7255
|
+
const plugins = [
|
|
7256
|
+
{ name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format', length: 1 },
|
|
7257
|
+
{ name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '', length: 1 },
|
|
7258
|
+
{ name: 'Native Client', filename: 'internal-nacl-plugin', description: '', length: 2 },
|
|
7259
|
+
];
|
|
7260
|
+
// Mimic PluginArray interface
|
|
7261
|
+
const pluginArray = Object.create(PluginArray.prototype);
|
|
7262
|
+
plugins.forEach((p, i) => {
|
|
7263
|
+
const plugin = Object.create(Plugin.prototype);
|
|
7264
|
+
Object.defineProperties(plugin, {
|
|
7265
|
+
name: { value: p.name, enumerable: true },
|
|
7266
|
+
filename: { value: p.filename, enumerable: true },
|
|
7267
|
+
description: { value: p.description, enumerable: true },
|
|
7268
|
+
length: { value: p.length, enumerable: true },
|
|
7269
|
+
});
|
|
7270
|
+
pluginArray[i] = plugin;
|
|
7271
|
+
});
|
|
7272
|
+
Object.defineProperty(pluginArray, 'length', { value: plugins.length });
|
|
7273
|
+
pluginArray.item = (i) => pluginArray[i] || null;
|
|
7274
|
+
pluginArray.namedItem = (name) => plugins.find(p => p.name === name) ? pluginArray[plugins.findIndex(p => p.name === name)] : null;
|
|
7275
|
+
pluginArray.refresh = () => {};
|
|
7276
|
+
return pluginArray;
|
|
7277
|
+
},
|
|
7278
|
+
configurable: true,
|
|
7279
|
+
});
|
|
7280
|
+
|
|
7281
|
+
// \u2500\u2500 3. Override navigator.languages \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
7282
|
+
Object.defineProperty(navigator, 'languages', {
|
|
7283
|
+
get: () => ['en-US', 'en'],
|
|
7284
|
+
configurable: true,
|
|
7285
|
+
});
|
|
7286
|
+
|
|
7287
|
+
// \u2500\u2500 4. Override chrome.runtime to appear like real Chrome \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
7288
|
+
if (!window.chrome) {
|
|
7289
|
+
window.chrome = {};
|
|
7290
|
+
}
|
|
7291
|
+
if (!window.chrome.runtime) {
|
|
7292
|
+
window.chrome.runtime = {
|
|
7293
|
+
connect: function() { return { onMessage: { addListener: function() {} }, postMessage: function() {} }; },
|
|
7294
|
+
sendMessage: function() {},
|
|
7295
|
+
onMessage: { addListener: function() {}, removeListener: function() {} },
|
|
7296
|
+
id: undefined,
|
|
7297
|
+
};
|
|
7298
|
+
}
|
|
7299
|
+
`;
|
|
7300
|
+
async function applyStealthPatches(page) {
|
|
7301
|
+
await page.context().addInitScript(STEALTH_SCRIPT);
|
|
7302
|
+
await page.context().setExtraHTTPHeaders({
|
|
7303
|
+
"User-Agent": REALISTIC_USER_AGENT
|
|
7304
|
+
});
|
|
7305
|
+
}
|
|
7306
|
+
|
|
7307
|
+
// src/lib/dialogs.ts
|
|
7308
|
+
var pendingDialogs = new Map;
|
|
7309
|
+
var AUTO_DISMISS_MS = 5000;
|
|
7310
|
+
function setupDialogHandler(page, sessionId) {
|
|
7311
|
+
const onDialog = (dialog) => {
|
|
7312
|
+
const info = {
|
|
7313
|
+
type: dialog.type(),
|
|
7314
|
+
message: dialog.message(),
|
|
7315
|
+
default_value: dialog.defaultValue(),
|
|
7316
|
+
timestamp: new Date().toISOString()
|
|
7317
|
+
};
|
|
7318
|
+
const autoTimer = setTimeout(() => {
|
|
7319
|
+
try {
|
|
7320
|
+
dialog.dismiss().catch(() => {});
|
|
7321
|
+
} catch {}
|
|
7322
|
+
const list = pendingDialogs.get(sessionId);
|
|
7323
|
+
if (list) {
|
|
7324
|
+
const idx = list.findIndex((p) => p.dialog === dialog);
|
|
7325
|
+
if (idx >= 0)
|
|
7326
|
+
list.splice(idx, 1);
|
|
7327
|
+
if (list.length === 0)
|
|
7328
|
+
pendingDialogs.delete(sessionId);
|
|
7329
|
+
}
|
|
7330
|
+
}, AUTO_DISMISS_MS);
|
|
7331
|
+
const pending = { dialog, info, autoTimer };
|
|
7332
|
+
if (!pendingDialogs.has(sessionId)) {
|
|
7333
|
+
pendingDialogs.set(sessionId, []);
|
|
7334
|
+
}
|
|
7335
|
+
pendingDialogs.get(sessionId).push(pending);
|
|
7336
|
+
};
|
|
7337
|
+
page.on("dialog", onDialog);
|
|
7338
|
+
return () => {
|
|
7339
|
+
page.off("dialog", onDialog);
|
|
7340
|
+
const list = pendingDialogs.get(sessionId);
|
|
7341
|
+
if (list) {
|
|
7342
|
+
for (const p of list)
|
|
7343
|
+
clearTimeout(p.autoTimer);
|
|
7344
|
+
pendingDialogs.delete(sessionId);
|
|
7345
|
+
}
|
|
7346
|
+
};
|
|
7347
|
+
}
|
|
7348
|
+
|
|
7075
7349
|
// src/lib/session.ts
|
|
7076
7350
|
var handles = new Map;
|
|
7077
7351
|
async function createSession2(opts = {}) {
|
|
@@ -7094,17 +7368,44 @@ async function createSession2(opts = {}) {
|
|
|
7094
7368
|
userAgent: opts.userAgent
|
|
7095
7369
|
});
|
|
7096
7370
|
}
|
|
7371
|
+
let sessionName = opts.name ?? (opts.startUrl ? (() => {
|
|
7372
|
+
try {
|
|
7373
|
+
return new URL(opts.startUrl).hostname;
|
|
7374
|
+
} catch {
|
|
7375
|
+
return;
|
|
7376
|
+
}
|
|
7377
|
+
})() : undefined);
|
|
7097
7378
|
const session = createSession({
|
|
7098
7379
|
engine: resolvedEngine,
|
|
7099
7380
|
projectId: opts.projectId,
|
|
7100
7381
|
agentId: opts.agentId,
|
|
7101
|
-
startUrl: opts.startUrl
|
|
7382
|
+
startUrl: opts.startUrl,
|
|
7383
|
+
name: sessionName
|
|
7102
7384
|
});
|
|
7103
|
-
|
|
7385
|
+
if (opts.stealth) {
|
|
7386
|
+
try {
|
|
7387
|
+
await applyStealthPatches(page);
|
|
7388
|
+
} catch {}
|
|
7389
|
+
}
|
|
7390
|
+
const cleanups = [];
|
|
7391
|
+
if (opts.captureNetwork !== false) {
|
|
7392
|
+
try {
|
|
7393
|
+
cleanups.push(enableNetworkLogging(page, session.id));
|
|
7394
|
+
} catch {}
|
|
7395
|
+
}
|
|
7396
|
+
if (opts.captureConsole !== false) {
|
|
7397
|
+
try {
|
|
7398
|
+
cleanups.push(enableConsoleCapture(page, session.id));
|
|
7399
|
+
} catch {}
|
|
7400
|
+
}
|
|
7401
|
+
try {
|
|
7402
|
+
cleanups.push(setupDialogHandler(page, session.id));
|
|
7403
|
+
} catch {}
|
|
7404
|
+
handles.set(session.id, { browser, page, engine: resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 } });
|
|
7104
7405
|
if (opts.startUrl) {
|
|
7105
7406
|
try {
|
|
7106
7407
|
await page.goto(opts.startUrl, { waitUntil: "domcontentloaded" });
|
|
7107
|
-
} catch
|
|
7408
|
+
} catch {}
|
|
7108
7409
|
}
|
|
7109
7410
|
return { session, page };
|
|
7110
7411
|
}
|
|
@@ -7112,11 +7413,22 @@ function getSessionPage(sessionId) {
|
|
|
7112
7413
|
const handle = handles.get(sessionId);
|
|
7113
7414
|
if (!handle)
|
|
7114
7415
|
throw new SessionNotFoundError(sessionId);
|
|
7416
|
+
try {
|
|
7417
|
+
handle.page.url();
|
|
7418
|
+
} catch {
|
|
7419
|
+
handles.delete(sessionId);
|
|
7420
|
+
throw new SessionNotFoundError(sessionId);
|
|
7421
|
+
}
|
|
7115
7422
|
return handle.page;
|
|
7116
7423
|
}
|
|
7117
7424
|
async function closeSession2(sessionId) {
|
|
7118
7425
|
const handle = handles.get(sessionId);
|
|
7119
7426
|
if (handle) {
|
|
7427
|
+
for (const cleanup of handle.cleanups) {
|
|
7428
|
+
try {
|
|
7429
|
+
cleanup();
|
|
7430
|
+
} catch {}
|
|
7431
|
+
}
|
|
7120
7432
|
try {
|
|
7121
7433
|
await handle.page.context().close();
|
|
7122
7434
|
} catch {}
|
|
@@ -7133,6 +7445,12 @@ function listSessions2(filter) {
|
|
|
7133
7445
|
|
|
7134
7446
|
// src/lib/actions.ts
|
|
7135
7447
|
init_types();
|
|
7448
|
+
|
|
7449
|
+
// src/lib/snapshot.ts
|
|
7450
|
+
var lastSnapshots = new Map;
|
|
7451
|
+
var sessionRefMaps = new Map;
|
|
7452
|
+
|
|
7453
|
+
// src/lib/actions.ts
|
|
7136
7454
|
async function click(page, selector, opts) {
|
|
7137
7455
|
try {
|
|
7138
7456
|
await page.click(selector, {
|
|
@@ -7264,7 +7582,7 @@ import { homedir as homedir2 } from "os";
|
|
|
7264
7582
|
|
|
7265
7583
|
// src/db/gallery.ts
|
|
7266
7584
|
init_schema();
|
|
7267
|
-
import { randomUUID as
|
|
7585
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
7268
7586
|
function deserialize(row) {
|
|
7269
7587
|
return {
|
|
7270
7588
|
id: row.id,
|
|
@@ -7288,7 +7606,7 @@ function deserialize(row) {
|
|
|
7288
7606
|
}
|
|
7289
7607
|
function createEntry(data) {
|
|
7290
7608
|
const db = getDatabase();
|
|
7291
|
-
const id =
|
|
7609
|
+
const id = randomUUID4();
|
|
7292
7610
|
db.prepare(`
|
|
7293
7611
|
INSERT INTO gallery_entries
|
|
7294
7612
|
(id, session_id, project_id, url, title, path, thumbnail_path, format,
|
|
@@ -7440,11 +7758,20 @@ async function takeScreenshot(page, opts) {
|
|
|
7440
7758
|
}
|
|
7441
7759
|
const originalSizeBytes = rawBuffer.length;
|
|
7442
7760
|
let finalBuffer;
|
|
7443
|
-
|
|
7444
|
-
|
|
7445
|
-
|
|
7446
|
-
|
|
7447
|
-
|
|
7761
|
+
let compressed = true;
|
|
7762
|
+
let fallback = false;
|
|
7763
|
+
try {
|
|
7764
|
+
if (compress && format !== "png") {
|
|
7765
|
+
finalBuffer = await compressBuffer(rawBuffer, format, quality ?? 82, maxWidth);
|
|
7766
|
+
} else if (compress && format === "png") {
|
|
7767
|
+
finalBuffer = await compressBuffer(rawBuffer, "png", quality ?? 9, maxWidth);
|
|
7768
|
+
} else {
|
|
7769
|
+
finalBuffer = rawBuffer;
|
|
7770
|
+
compressed = false;
|
|
7771
|
+
}
|
|
7772
|
+
} catch (sharpErr) {
|
|
7773
|
+
fallback = true;
|
|
7774
|
+
compressed = false;
|
|
7448
7775
|
finalBuffer = rawBuffer;
|
|
7449
7776
|
}
|
|
7450
7777
|
const compressedSizeBytes = finalBuffer.length;
|
|
@@ -7472,7 +7799,8 @@ async function takeScreenshot(page, opts) {
|
|
|
7472
7799
|
compressed_size_bytes: compressedSizeBytes,
|
|
7473
7800
|
compression_ratio: compressionRatio,
|
|
7474
7801
|
thumbnail_path: thumbnailPath,
|
|
7475
|
-
thumbnail_base64: thumbnailBase64
|
|
7802
|
+
thumbnail_base64: thumbnailBase64,
|
|
7803
|
+
...fallback ? { fallback: true, compressed: false } : {}
|
|
7476
7804
|
};
|
|
7477
7805
|
if (opts?.track !== false) {
|
|
7478
7806
|
try {
|
|
@@ -7508,119 +7836,6 @@ async function takeScreenshot(page, opts) {
|
|
|
7508
7836
|
}
|
|
7509
7837
|
}
|
|
7510
7838
|
|
|
7511
|
-
// src/db/network-log.ts
|
|
7512
|
-
init_schema();
|
|
7513
|
-
import { randomUUID as randomUUID3 } from "crypto";
|
|
7514
|
-
function logRequest(data) {
|
|
7515
|
-
const db = getDatabase();
|
|
7516
|
-
const id = randomUUID3();
|
|
7517
|
-
db.prepare(`INSERT INTO network_log (id, session_id, method, url, status_code, request_headers,
|
|
7518
|
-
response_headers, request_body, body_size, duration_ms, resource_type)
|
|
7519
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, data.session_id, data.method, data.url, data.status_code ?? null, data.request_headers ?? null, data.response_headers ?? null, data.request_body ?? null, data.body_size ?? null, data.duration_ms ?? null, data.resource_type ?? null);
|
|
7520
|
-
return getNetworkRequest(id);
|
|
7521
|
-
}
|
|
7522
|
-
function getNetworkRequest(id) {
|
|
7523
|
-
const db = getDatabase();
|
|
7524
|
-
return db.query("SELECT * FROM network_log WHERE id = ?").get(id) ?? null;
|
|
7525
|
-
}
|
|
7526
|
-
function getNetworkLog(sessionId) {
|
|
7527
|
-
const db = getDatabase();
|
|
7528
|
-
return db.query("SELECT * FROM network_log WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
|
|
7529
|
-
}
|
|
7530
|
-
function clearNetworkLog(sessionId) {
|
|
7531
|
-
const db = getDatabase();
|
|
7532
|
-
db.prepare("DELETE FROM network_log WHERE session_id = ?").run(sessionId);
|
|
7533
|
-
}
|
|
7534
|
-
|
|
7535
|
-
// src/lib/network.ts
|
|
7536
|
-
function enableNetworkLogging(page, sessionId) {
|
|
7537
|
-
const requestStart = new Map;
|
|
7538
|
-
const onRequest = (req) => {
|
|
7539
|
-
requestStart.set(req.url(), Date.now());
|
|
7540
|
-
};
|
|
7541
|
-
const onResponse = (res) => {
|
|
7542
|
-
const start = requestStart.get(res.url()) ?? Date.now();
|
|
7543
|
-
const duration = Date.now() - start;
|
|
7544
|
-
const req = res.request();
|
|
7545
|
-
try {
|
|
7546
|
-
logRequest({
|
|
7547
|
-
session_id: sessionId,
|
|
7548
|
-
method: req.method(),
|
|
7549
|
-
url: res.url(),
|
|
7550
|
-
status_code: res.status(),
|
|
7551
|
-
request_headers: JSON.stringify(req.headers()),
|
|
7552
|
-
response_headers: JSON.stringify(res.headers()),
|
|
7553
|
-
body_size: res.headers()["content-length"] != null ? parseInt(res.headers()["content-length"]) : undefined,
|
|
7554
|
-
duration_ms: duration,
|
|
7555
|
-
resource_type: req.resourceType()
|
|
7556
|
-
});
|
|
7557
|
-
} catch {}
|
|
7558
|
-
};
|
|
7559
|
-
page.on("request", onRequest);
|
|
7560
|
-
page.on("response", onResponse);
|
|
7561
|
-
return () => {
|
|
7562
|
-
page.off("request", onRequest);
|
|
7563
|
-
page.off("response", onResponse);
|
|
7564
|
-
};
|
|
7565
|
-
}
|
|
7566
|
-
function startHAR(page) {
|
|
7567
|
-
const entries = [];
|
|
7568
|
-
const requestStart = new Map;
|
|
7569
|
-
const onRequest = (req) => {
|
|
7570
|
-
requestStart.set(req.url() + req.method(), {
|
|
7571
|
-
time: Date.now(),
|
|
7572
|
-
method: req.method(),
|
|
7573
|
-
headers: req.headers(),
|
|
7574
|
-
postData: req.postData() ?? undefined
|
|
7575
|
-
});
|
|
7576
|
-
};
|
|
7577
|
-
const onResponse = async (res) => {
|
|
7578
|
-
const key = res.url() + res.request().method();
|
|
7579
|
-
const start = requestStart.get(key);
|
|
7580
|
-
if (!start)
|
|
7581
|
-
return;
|
|
7582
|
-
const duration = Date.now() - start.time;
|
|
7583
|
-
const entry = {
|
|
7584
|
-
startedDateTime: new Date(start.time).toISOString(),
|
|
7585
|
-
time: duration,
|
|
7586
|
-
request: {
|
|
7587
|
-
method: start.method,
|
|
7588
|
-
url: res.url(),
|
|
7589
|
-
headers: Object.entries(start.headers).map(([name, value]) => ({ name, value })),
|
|
7590
|
-
postData: start.postData ? { text: start.postData } : undefined
|
|
7591
|
-
},
|
|
7592
|
-
response: {
|
|
7593
|
-
status: res.status(),
|
|
7594
|
-
statusText: res.statusText(),
|
|
7595
|
-
headers: Object.entries(res.headers()).map(([name, value]) => ({ name, value })),
|
|
7596
|
-
content: {
|
|
7597
|
-
size: parseInt(res.headers()["content-length"] ?? "0") || 0,
|
|
7598
|
-
mimeType: res.headers()["content-type"] ?? "application/octet-stream"
|
|
7599
|
-
}
|
|
7600
|
-
},
|
|
7601
|
-
timings: { send: 0, wait: duration, receive: 0 }
|
|
7602
|
-
};
|
|
7603
|
-
entries.push(entry);
|
|
7604
|
-
requestStart.delete(key);
|
|
7605
|
-
};
|
|
7606
|
-
page.on("request", onRequest);
|
|
7607
|
-
page.on("response", onResponse);
|
|
7608
|
-
return {
|
|
7609
|
-
entries,
|
|
7610
|
-
stop: () => {
|
|
7611
|
-
page.off("request", onRequest);
|
|
7612
|
-
page.off("response", onResponse);
|
|
7613
|
-
return {
|
|
7614
|
-
log: {
|
|
7615
|
-
version: "1.2",
|
|
7616
|
-
creator: { name: "@hasna/browser", version: "0.0.1" },
|
|
7617
|
-
entries
|
|
7618
|
-
}
|
|
7619
|
-
};
|
|
7620
|
-
}
|
|
7621
|
-
};
|
|
7622
|
-
}
|
|
7623
|
-
|
|
7624
7839
|
// src/engines/cdp.ts
|
|
7625
7840
|
init_types();
|
|
7626
7841
|
|
|
@@ -7768,54 +7983,6 @@ async function getPerformanceMetrics(page) {
|
|
|
7768
7983
|
};
|
|
7769
7984
|
}
|
|
7770
7985
|
|
|
7771
|
-
// src/db/console-log.ts
|
|
7772
|
-
init_schema();
|
|
7773
|
-
import { randomUUID as randomUUID4 } from "crypto";
|
|
7774
|
-
function logConsoleMessage(data) {
|
|
7775
|
-
const db = getDatabase();
|
|
7776
|
-
const id = randomUUID4();
|
|
7777
|
-
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);
|
|
7778
|
-
return getConsoleMessage(id);
|
|
7779
|
-
}
|
|
7780
|
-
function getConsoleMessage(id) {
|
|
7781
|
-
const db = getDatabase();
|
|
7782
|
-
return db.query("SELECT * FROM console_log WHERE id = ?").get(id) ?? null;
|
|
7783
|
-
}
|
|
7784
|
-
function getConsoleLog(sessionId, level) {
|
|
7785
|
-
const db = getDatabase();
|
|
7786
|
-
if (level) {
|
|
7787
|
-
return db.query("SELECT * FROM console_log WHERE session_id = ? AND level = ? ORDER BY timestamp ASC").all(sessionId, level);
|
|
7788
|
-
}
|
|
7789
|
-
return db.query("SELECT * FROM console_log WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
|
|
7790
|
-
}
|
|
7791
|
-
|
|
7792
|
-
// src/lib/console.ts
|
|
7793
|
-
function enableConsoleCapture(page, sessionId) {
|
|
7794
|
-
const onConsole = (msg) => {
|
|
7795
|
-
const levelMap = {
|
|
7796
|
-
log: "log",
|
|
7797
|
-
warn: "warn",
|
|
7798
|
-
error: "error",
|
|
7799
|
-
debug: "debug",
|
|
7800
|
-
info: "info",
|
|
7801
|
-
warning: "warn"
|
|
7802
|
-
};
|
|
7803
|
-
const level = levelMap[msg.type()] ?? "log";
|
|
7804
|
-
const location = msg.location();
|
|
7805
|
-
try {
|
|
7806
|
-
logConsoleMessage({
|
|
7807
|
-
session_id: sessionId,
|
|
7808
|
-
level,
|
|
7809
|
-
message: msg.text(),
|
|
7810
|
-
source: location.url || undefined,
|
|
7811
|
-
line_number: location.lineNumber || undefined
|
|
7812
|
-
});
|
|
7813
|
-
} catch {}
|
|
7814
|
-
};
|
|
7815
|
-
page.on("console", onConsole);
|
|
7816
|
-
return () => page.off("console", onConsole);
|
|
7817
|
-
}
|
|
7818
|
-
|
|
7819
7986
|
// src/lib/crawler.ts
|
|
7820
7987
|
init_types();
|
|
7821
7988
|
|
package/dist/types/index.d.ts
CHANGED
|
@@ -56,12 +56,16 @@ export interface SessionOptions {
|
|
|
56
56
|
projectId?: string;
|
|
57
57
|
agentId?: string;
|
|
58
58
|
startUrl?: string;
|
|
59
|
+
name?: string;
|
|
59
60
|
headless?: boolean;
|
|
60
61
|
viewport?: {
|
|
61
62
|
width: number;
|
|
62
63
|
height: number;
|
|
63
64
|
};
|
|
64
65
|
userAgent?: string;
|
|
66
|
+
captureNetwork?: boolean;
|
|
67
|
+
captureConsole?: boolean;
|
|
68
|
+
stealth?: boolean;
|
|
65
69
|
}
|
|
66
70
|
export interface Snapshot {
|
|
67
71
|
id: string;
|
|
@@ -344,6 +348,37 @@ export interface PDFResult {
|
|
|
344
348
|
size_bytes: number;
|
|
345
349
|
page_count?: number;
|
|
346
350
|
}
|
|
351
|
+
export interface RefInfo {
|
|
352
|
+
role: string;
|
|
353
|
+
name: string;
|
|
354
|
+
description?: string;
|
|
355
|
+
visible: boolean;
|
|
356
|
+
enabled: boolean;
|
|
357
|
+
value?: string;
|
|
358
|
+
checked?: boolean;
|
|
359
|
+
}
|
|
360
|
+
export interface SnapshotResult {
|
|
361
|
+
tree: string;
|
|
362
|
+
refs: Record<string, RefInfo>;
|
|
363
|
+
interactive_count: number;
|
|
364
|
+
}
|
|
365
|
+
export interface SnapshotDiff {
|
|
366
|
+
added: Array<{
|
|
367
|
+
ref: string;
|
|
368
|
+
info: RefInfo;
|
|
369
|
+
}>;
|
|
370
|
+
removed: Array<{
|
|
371
|
+
ref: string;
|
|
372
|
+
info: RefInfo;
|
|
373
|
+
}>;
|
|
374
|
+
modified: Array<{
|
|
375
|
+
ref: string;
|
|
376
|
+
before: RefInfo;
|
|
377
|
+
after: RefInfo;
|
|
378
|
+
}>;
|
|
379
|
+
url_changed: boolean;
|
|
380
|
+
title_changed: boolean;
|
|
381
|
+
}
|
|
347
382
|
export interface BrowserConfig {
|
|
348
383
|
default_engine: BrowserEngine;
|
|
349
384
|
headless: boolean;
|