@deadragdoll/tellymcp 0.0.13 → 0.0.15
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/.env.example.client +23 -39
- package/.env.example.gateway +52 -61
- package/CHANGELOG.md +57 -0
- package/README-ru.md +80 -6
- package/README.md +80 -6
- package/TOOLS.md +219 -10
- package/config/templates/env.both.template +19 -7
- package/config/templates/env.client.template +9 -19
- package/config/templates/env.gateway.template +19 -31
- package/dist/cli.js +252 -47
- package/dist/configureServer.js +966 -0
- package/dist/envMigration.js +316 -0
- package/dist/moleculer.config.js +1 -3
- package/dist/services/features/telegram-mcp/approval.service.js +1 -1
- package/dist/services/features/telegram-mcp/browser.service.js +18 -0
- package/dist/services/features/telegram-mcp/collaboration.service.js +2 -2
- package/dist/services/features/telegram-mcp/ensuredb.service.js +1 -1
- package/dist/services/features/telegram-mcp/file-content.service.js +94 -0
- package/dist/services/features/telegram-mcp/gateway-delivery.service.js +5 -1
- package/dist/services/features/telegram-mcp/gateway-socket.service.js +43 -11
- package/dist/services/features/telegram-mcp/gateway.service.js +1 -1
- package/dist/services/features/telegram-mcp/mcp-http.service.js +1 -0
- package/dist/services/features/telegram-mcp/mcp-server.service.js +20 -0
- package/dist/services/features/telegram-mcp/session-context.service.js +25 -1
- package/dist/services/features/telegram-mcp/src/app/bootstrap/runtime.js +79 -67
- package/dist/services/features/telegram-mcp/src/app/config/env.js +129 -38
- package/dist/services/features/telegram-mcp/src/app/config/environmentContract.js +66 -0
- package/dist/services/features/telegram-mcp/src/app/http.js +139 -99
- package/dist/services/features/telegram-mcp/src/app/oauthFacade.js +642 -0
- package/dist/services/features/telegram-mcp/src/app/webapp/assets.js +151 -170
- package/dist/services/features/telegram-mcp/src/app/webapp/auth.js +96 -99
- package/dist/services/features/telegram-mcp/src/entities/request/model/schema.js +88 -19
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserAttachActiveTabTool.js +28 -0
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserAttachTabTool.js +28 -0
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserDetachTabTool.js +28 -0
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserListAttachedInstancesTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserListTabsTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserService.js +412 -29
- package/dist/services/features/telegram-mcp/src/features/browser-attach/model/browserRecordingBundle.js +37 -3
- package/dist/services/features/telegram-mcp/src/features/browser-attach/model/firefoxAttachRegistry.js +7 -6
- package/dist/services/features/telegram-mcp/src/features/browser-attach/model/firefoxAttachServer.js +214 -37
- package/dist/services/features/telegram-mcp/src/features/browser-attach/model/types.js +186 -0
- package/dist/services/features/telegram-mcp/src/features/collaboration/model/collaborationService.js +2 -0
- package/dist/services/features/telegram-mcp/src/features/collaboration/model/gatewaySessionsService.js +5 -5
- package/dist/services/features/telegram-mcp/src/features/collaboration/model/sendPartnerFileService.js +6 -3
- package/dist/services/features/telegram-mcp/src/features/distributed-client/model/gatewayClientAccess.js +5 -2
- package/dist/services/features/telegram-mcp/src/features/distributed-client/model/gatewayCollaborationBackend.js +4 -4
- package/dist/services/features/telegram-mcp/src/features/distributed-gateway/model/gatewayHttpService.js +33 -36
- package/dist/services/features/telegram-mcp/src/features/distributed-gateway/model/remoteConsoleActionClient.js +4 -3
- package/dist/services/features/telegram-mcp/src/features/file-content/model/getFileListTool.js +33 -0
- package/dist/services/features/telegram-mcp/src/features/file-content/model/getFileService.js +327 -0
- package/dist/services/features/telegram-mcp/src/features/file-content/model/getFileTool.js +81 -0
- package/dist/services/features/telegram-mcp/src/features/file-content/model/temporaryFileLinkStore.js +307 -0
- package/dist/services/features/telegram-mcp/src/features/file-content/model/workspaceFilePolicy.js +115 -0
- package/dist/services/features/telegram-mcp/src/features/notify/model/notifyService.js +9 -5
- package/dist/services/features/telegram-mcp/src/features/session-context/model/getRuntimeDiagnosticsTool.js +30 -0
- package/dist/services/features/telegram-mcp/src/features/session-context/model/sessionContextService.js +169 -7
- package/dist/services/features/telegram-mcp/src/shared/integrations/memory/processLocalStateStore.js +260 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/object-storage/minioExchangeStore.js +1 -1
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transport.js +4 -1
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportConsoleRegistry.js +2 -2
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportFileHandoffActions.js +6 -3
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMessageFlow.js +1 -1
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectState.js +2 -2
- package/dist/services/features/telegram-mcp/src/shared/integrations/terminal/client.js +29 -6
- package/dist/services/features/telegram-mcp/src/shared/integrations/terminal/ptyRegistry.js +100 -2
- package/dist/services/features/telegram-mcp/src/shared/lib/bodyLimits.js +63 -0
- package/dist/services/features/telegram-mcp/src/shared/lib/gatewayAuth.js +13 -0
- package/dist/services/features/telegram-mcp/src/shared/lib/gatewayScope.js +5 -5
- package/dist/services/features/telegram-mcp/src/shared/lib/project-identity/projectIdentity.js +10 -0
- package/dist/services/features/telegram-mcp/src/shared/lib/time/localTimestamp.js +21 -0
- package/docs/CHAT_CONNECTOR.md +134 -0
- package/docs/STANDALONE-ru.md +41 -3
- package/docs/STANDALONE.md +41 -3
- package/package.json +5 -3
- package/packages/chrome-attach-extension/dist/background.js +572 -163
- package/packages/chrome-attach-extension/dist/manifest.json +2 -1
- package/packages/chrome-attach-extension/dist/options.js +15 -2
- package/packages/chrome-attach-extension/dist/recorder-content.js +14 -1
- package/packages/chrome-attach-extension/dist/recorder-page.js +34 -18
- package/packages/firefox-attach-extension/dist/background.js +413 -33
- package/packages/firefox-attach-extension/dist/manifest.json +0 -12
- package/packages/firefox-attach-extension/dist/options.js +14 -1
- package/packages/firefox-attach-extension/dist/recorder-content.js +14 -1
- package/packages/firefox-attach-extension/dist/recorder-page.js +34 -18
- package/scripts/postinstall.js +33 -1
|
@@ -15,18 +15,38 @@ const POPUP_COMMAND_RESULT_KEY = "attach_popup_command_result";
|
|
|
15
15
|
const CONTROL_PANEL_PORT_NAME = "telly-control-panel";
|
|
16
16
|
const RECONNECT_DELAY_MS = 3000;
|
|
17
17
|
const HEARTBEAT_INTERVAL_MS = 15000;
|
|
18
|
+
const KEEPALIVE_ALARM_NAME = "telly-keepalive";
|
|
19
|
+
const KEEPALIVE_ALARM_PERIOD_MINUTES = 0.5;
|
|
18
20
|
const MAX_CAPTURE_BYTES = 512 * 1024;
|
|
21
|
+
const MAX_BUFFERED_LOG_ENTRIES = 200;
|
|
19
22
|
|
|
20
23
|
let socket = null;
|
|
21
24
|
let reconnectTimer = null;
|
|
22
25
|
let heartbeatTimer = null;
|
|
23
26
|
let instanceId = null;
|
|
27
|
+
let attachedTabId = null;
|
|
24
28
|
let manualDisconnect = false;
|
|
25
29
|
const pendingManualRecordingRequests = new Map();
|
|
26
30
|
const controlPanelPorts = new Set();
|
|
27
31
|
|
|
28
32
|
const activeRecordingsById = new Map();
|
|
29
33
|
const activeRecordingIdByTabId = new Map();
|
|
34
|
+
const consoleMessagesByTabId = new Map();
|
|
35
|
+
const pageErrorsByTabId = new Map();
|
|
36
|
+
const networkFailuresByTabId = new Map();
|
|
37
|
+
|
|
38
|
+
function padNumber(value, length = 2) {
|
|
39
|
+
return String(value).padStart(length, "0");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function formatLocalTimestamp(date = new Date()) {
|
|
43
|
+
return `${date.getFullYear()}-${padNumber(date.getMonth() + 1)}-${padNumber(date.getDate())}T${padNumber(
|
|
44
|
+
date.getHours(),
|
|
45
|
+
)}:${padNumber(date.getMinutes())}:${padNumber(date.getSeconds())}.${padNumber(
|
|
46
|
+
date.getMilliseconds(),
|
|
47
|
+
3,
|
|
48
|
+
)}`;
|
|
49
|
+
}
|
|
30
50
|
|
|
31
51
|
function storageGet(defaults) {
|
|
32
52
|
return new Promise((resolve, reject) => {
|
|
@@ -106,6 +126,19 @@ function windowsUpdate(windowId, updateInfo) {
|
|
|
106
126
|
});
|
|
107
127
|
}
|
|
108
128
|
|
|
129
|
+
function tabsRemove(tabId) {
|
|
130
|
+
return new Promise((resolve, reject) => {
|
|
131
|
+
browser.tabs.remove(tabId, () => {
|
|
132
|
+
const error = browser.runtime?.lastError;
|
|
133
|
+
if (error) {
|
|
134
|
+
reject(new Error(error.message));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
resolve();
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
109
142
|
function cookiesGetAll(details) {
|
|
110
143
|
return new Promise((resolve, reject) => {
|
|
111
144
|
browser.cookies.getAll(details, (cookies) => {
|
|
@@ -225,6 +258,10 @@ async function setLocalInstanceId(value) {
|
|
|
225
258
|
}
|
|
226
259
|
|
|
227
260
|
async function setAttachedTab(tab) {
|
|
261
|
+
attachedTabId =
|
|
262
|
+
tab && Number.isInteger(tab.tab_id)
|
|
263
|
+
? Number(tab.tab_id)
|
|
264
|
+
: null;
|
|
228
265
|
await storageSet({
|
|
229
266
|
[ATTACHED_TAB_KEY]: tab,
|
|
230
267
|
});
|
|
@@ -236,6 +273,15 @@ async function setRecordingStatus(status) {
|
|
|
236
273
|
});
|
|
237
274
|
}
|
|
238
275
|
|
|
276
|
+
async function hydrateAttachedTabSelection() {
|
|
277
|
+
const stored = await storageGet({ [ATTACHED_TAB_KEY]: null });
|
|
278
|
+
const tab = stored[ATTACHED_TAB_KEY];
|
|
279
|
+
attachedTabId =
|
|
280
|
+
tab && Number.isInteger(tab.tab_id)
|
|
281
|
+
? Number(tab.tab_id)
|
|
282
|
+
: null;
|
|
283
|
+
}
|
|
284
|
+
|
|
239
285
|
function buildWebSocketUrl(settings) {
|
|
240
286
|
return `ws://${settings.host}:${settings.port}/browser-attach/ws`;
|
|
241
287
|
}
|
|
@@ -290,144 +336,421 @@ async function ensureTabIsActive(tabId) {
|
|
|
290
336
|
return await tabsGet(tabId);
|
|
291
337
|
}
|
|
292
338
|
|
|
293
|
-
function
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
return
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
339
|
+
function trimBufferedEntries(entries) {
|
|
340
|
+
if (entries.length <= MAX_BUFFERED_LOG_ENTRIES) {
|
|
341
|
+
return entries;
|
|
342
|
+
}
|
|
343
|
+
return entries.slice(entries.length - MAX_BUFFERED_LOG_ENTRIES);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function appendBufferedEntry(store, tabId, entry) {
|
|
347
|
+
if (!Number.isInteger(tabId) || tabId < 0 || !entry || typeof entry !== "object") {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
const existing = store.get(tabId) || [];
|
|
351
|
+
existing.push(entry);
|
|
352
|
+
store.set(tabId, trimBufferedEntries(existing));
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function extractErrorStack(args) {
|
|
356
|
+
if (!Array.isArray(args)) {
|
|
357
|
+
return "";
|
|
358
|
+
}
|
|
359
|
+
for (const arg of args) {
|
|
360
|
+
if (!arg || typeof arg !== "object") {
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
if (typeof arg.stack === "string" && arg.stack.trim()) {
|
|
364
|
+
return arg.stack;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return "";
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function extractConsoleLocation(event) {
|
|
371
|
+
if (!event || typeof event !== "object" || !Array.isArray(event.args)) {
|
|
372
|
+
return "";
|
|
373
|
+
}
|
|
374
|
+
for (const arg of event.args) {
|
|
375
|
+
if (!arg || typeof arg !== "object") {
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
if (typeof arg.filename === "string" && arg.filename.trim()) {
|
|
379
|
+
const line = Number.isFinite(arg.lineno) ? Number(arg.lineno) : 0;
|
|
380
|
+
const column = Number.isFinite(arg.colno) ? Number(arg.colno) : 0;
|
|
381
|
+
return `${arg.filename}:${line}:${column}`;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return "";
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function recordPageEventForBuffers(tabId, event) {
|
|
388
|
+
if (!Number.isInteger(tabId) || tabId < 0 || !event || typeof event !== "object") {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
if (event.kind !== "console_event") {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const timestamp =
|
|
396
|
+
typeof event.at === "string" && event.at.trim()
|
|
397
|
+
? event.at
|
|
398
|
+
: formatLocalTimestamp(new Date());
|
|
399
|
+
const text = typeof event.text === "string" ? event.text : "";
|
|
400
|
+
const location = extractConsoleLocation(event);
|
|
401
|
+
|
|
402
|
+
appendBufferedEntry(consoleMessagesByTabId, tabId, {
|
|
403
|
+
type:
|
|
404
|
+
typeof event.level === "string" && event.level.trim()
|
|
405
|
+
? event.level
|
|
406
|
+
: "log",
|
|
407
|
+
text,
|
|
408
|
+
...(location ? { location } : {}),
|
|
409
|
+
timestamp,
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
if (event.level === "error" || event.level === "assert") {
|
|
413
|
+
const stack = extractErrorStack(event.args);
|
|
414
|
+
appendBufferedEntry(pageErrorsByTabId, tabId, {
|
|
415
|
+
message: text || "Console error",
|
|
416
|
+
...(stack ? { stack } : {}),
|
|
417
|
+
timestamp,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function recordNetworkFailure(tabId, event) {
|
|
423
|
+
if (!Number.isInteger(tabId) || tabId < 0 || !event || typeof event !== "object") {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
appendBufferedEntry(networkFailuresByTabId, tabId, {
|
|
427
|
+
url: typeof event.url === "string" ? event.url : "",
|
|
428
|
+
method:
|
|
429
|
+
typeof event.method === "string" && event.method.trim()
|
|
430
|
+
? event.method
|
|
431
|
+
: "GET",
|
|
432
|
+
...(typeof event.status === "number" ? { status: event.status } : {}),
|
|
433
|
+
...(typeof event.error_text === "string" ? { error_text: event.error_text } : {}),
|
|
434
|
+
...(typeof event.resource_type === "string"
|
|
435
|
+
? { resource_type: event.resource_type }
|
|
436
|
+
: {}),
|
|
437
|
+
timestamp:
|
|
438
|
+
typeof event.timestamp === "string" && event.timestamp.trim()
|
|
439
|
+
? event.timestamp
|
|
440
|
+
: formatLocalTimestamp(new Date()),
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function getBufferedLogSnapshot(tabId, limit) {
|
|
445
|
+
const normalize = (value) =>
|
|
446
|
+
Number.isInteger(value) && value > 0 ? Number(value) : undefined;
|
|
447
|
+
const sliceEntries = (entries) => {
|
|
448
|
+
const normalizedLimit = normalize(limit);
|
|
449
|
+
return normalizedLimit ? entries.slice(-normalizedLimit) : entries;
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
const consoleMessages = consoleMessagesByTabId.get(tabId) || [];
|
|
453
|
+
const pageErrors = pageErrorsByTabId.get(tabId) || [];
|
|
454
|
+
const networkFailures = networkFailuresByTabId.get(tabId) || [];
|
|
455
|
+
|
|
456
|
+
return {
|
|
457
|
+
console_messages: sliceEntries(consoleMessages),
|
|
458
|
+
console_total: consoleMessages.length,
|
|
459
|
+
page_errors: sliceEntries(pageErrors),
|
|
460
|
+
page_error_total: pageErrors.length,
|
|
461
|
+
network_failures: sliceEntries(networkFailures),
|
|
462
|
+
network_failure_total: networkFailures.length,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function clearBufferedLogs(tabId) {
|
|
467
|
+
const consoleMessages = consoleMessagesByTabId.get(tabId) || [];
|
|
468
|
+
const pageErrors = pageErrorsByTabId.get(tabId) || [];
|
|
469
|
+
const networkFailures = networkFailuresByTabId.get(tabId) || [];
|
|
470
|
+
consoleMessagesByTabId.delete(tabId);
|
|
471
|
+
pageErrorsByTabId.delete(tabId);
|
|
472
|
+
networkFailuresByTabId.delete(tabId);
|
|
473
|
+
return {
|
|
474
|
+
console_messages_cleared: consoleMessages.length,
|
|
475
|
+
page_errors_cleared: pageErrors.length,
|
|
476
|
+
network_failures_cleared: networkFailures.length,
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async function executeTabActionInPage(action, payload) {
|
|
481
|
+
payload = payload || {};
|
|
482
|
+
const normalize = (value) => typeof value === "string" ? value.trim() : "";
|
|
483
|
+
const timeoutMs = Number.isInteger(payload.timeout_ms) ? payload.timeout_ms : 30000;
|
|
484
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
485
|
+
const byText = (text, exact) => {
|
|
486
|
+
const needle = normalize(text);
|
|
487
|
+
if (!needle) return null;
|
|
488
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT);
|
|
489
|
+
while (walker.nextNode()) {
|
|
490
|
+
const element = walker.currentNode;
|
|
491
|
+
const textContent = normalize(element.textContent || "");
|
|
492
|
+
if (!textContent) continue;
|
|
493
|
+
if ((exact && textContent === needle) || (!exact && textContent.includes(needle))) {
|
|
494
|
+
return element;
|
|
327
495
|
}
|
|
328
|
-
return document.body;
|
|
329
|
-
};
|
|
330
|
-
const target = resolveTarget();
|
|
331
|
-
if (action !== "screenshot" && action !== "inject_script" && !target) {
|
|
332
|
-
return { ok: false, error: "Target element was not found." };
|
|
333
496
|
}
|
|
334
|
-
|
|
335
|
-
|
|
497
|
+
return null;
|
|
498
|
+
};
|
|
499
|
+
const isVisible = (element) => {
|
|
500
|
+
if (!element) return false;
|
|
501
|
+
const computed = window.getComputedStyle(element);
|
|
502
|
+
return computed.display !== "none" && computed.visibility !== "hidden" && computed.opacity !== "0";
|
|
503
|
+
};
|
|
504
|
+
const resolveTarget = () => {
|
|
505
|
+
const aiTag = normalize(payload.ai_tag);
|
|
506
|
+
if (aiTag) {
|
|
507
|
+
return document.querySelector('[data-drive-tag="' + aiTag.replace(/"/g, '\\"') + '"], [ai-tag="' + aiTag.replace(/"/g, '\\"') + '"]');
|
|
336
508
|
}
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
return
|
|
340
|
-
};
|
|
341
|
-
if (action === "dom") {
|
|
342
|
-
const attributes = target
|
|
343
|
-
? Object.fromEntries(Array.from(target.attributes || []).map((attr) => [attr.name, attr.value]))
|
|
344
|
-
: {};
|
|
345
|
-
return {
|
|
346
|
-
ok: true,
|
|
347
|
-
result: {
|
|
348
|
-
found: Boolean(target),
|
|
349
|
-
outer_html: payload.include_html === false ? undefined : target.outerHTML,
|
|
350
|
-
text_content: payload.include_text === false ? undefined : (target.textContent || "").trim(),
|
|
351
|
-
visible: target ? toVisible(target) : false,
|
|
352
|
-
attributes,
|
|
353
|
-
url: location.href,
|
|
354
|
-
title: document.title,
|
|
355
|
-
},
|
|
356
|
-
};
|
|
509
|
+
const selector = normalize(payload.selector);
|
|
510
|
+
if (selector) {
|
|
511
|
+
return document.querySelector(selector);
|
|
357
512
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
return
|
|
361
|
-
ok: true,
|
|
362
|
-
result: {
|
|
363
|
-
url: location.href,
|
|
364
|
-
title: document.title,
|
|
365
|
-
},
|
|
366
|
-
};
|
|
513
|
+
const text = normalize(payload.text);
|
|
514
|
+
if (text) {
|
|
515
|
+
return byText(text, payload.exact === true);
|
|
367
516
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
517
|
+
return document.body;
|
|
518
|
+
};
|
|
519
|
+
const target = resolveTarget();
|
|
520
|
+
if (!target) {
|
|
521
|
+
return { ok: false, error: "Target element was not found." };
|
|
522
|
+
}
|
|
523
|
+
if (target && typeof target.scrollIntoView === "function") {
|
|
524
|
+
target.scrollIntoView({ block: "center", inline: "center" });
|
|
525
|
+
}
|
|
526
|
+
if (action === "dom") {
|
|
527
|
+
const attributes = target
|
|
528
|
+
? Object.fromEntries(Array.from(target.attributes || []).map((attr) => [attr.name, attr.value]))
|
|
529
|
+
: {};
|
|
530
|
+
return {
|
|
531
|
+
ok: true,
|
|
532
|
+
result: {
|
|
533
|
+
found: Boolean(target),
|
|
534
|
+
outer_html: payload.include_html === false ? undefined : target.outerHTML,
|
|
535
|
+
text_content: payload.include_text === false ? undefined : (target.textContent || "").trim(),
|
|
536
|
+
visible: target ? isVisible(target) : false,
|
|
537
|
+
attributes,
|
|
538
|
+
url: location.href,
|
|
539
|
+
title: document.title,
|
|
540
|
+
},
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
if (action === "computed_style") {
|
|
544
|
+
if (!target) {
|
|
373
545
|
return {
|
|
374
546
|
ok: true,
|
|
375
547
|
result: {
|
|
548
|
+
found: false,
|
|
376
549
|
url: location.href,
|
|
377
550
|
title: document.title,
|
|
378
551
|
},
|
|
379
552
|
};
|
|
380
553
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
url: location.href,
|
|
401
|
-
title: document.title,
|
|
554
|
+
const requestedProperties = Array.isArray(payload.properties) && payload.properties.length
|
|
555
|
+
? payload.properties.map((item) => String(item))
|
|
556
|
+
: ["display","position","visibility","opacity","color","background-color","font-size","z-index","overflow"];
|
|
557
|
+
const computed = window.getComputedStyle(target);
|
|
558
|
+
const rect = target.getBoundingClientRect();
|
|
559
|
+
const styles = Object.fromEntries(
|
|
560
|
+
requestedProperties.map((property) => [property, computed.getPropertyValue(property)]),
|
|
561
|
+
);
|
|
562
|
+
return {
|
|
563
|
+
ok: true,
|
|
564
|
+
result: {
|
|
565
|
+
found: true,
|
|
566
|
+
visible: isVisible(target),
|
|
567
|
+
styles,
|
|
568
|
+
box: {
|
|
569
|
+
x: rect.x,
|
|
570
|
+
y: rect.y,
|
|
571
|
+
width: rect.width,
|
|
572
|
+
height: rect.height,
|
|
402
573
|
},
|
|
403
|
-
|
|
574
|
+
url: location.href,
|
|
575
|
+
title: document.title,
|
|
576
|
+
},
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
if (action === "click") {
|
|
580
|
+
target.click();
|
|
581
|
+
return {
|
|
582
|
+
ok: true,
|
|
583
|
+
result: {
|
|
584
|
+
url: location.href,
|
|
585
|
+
title: document.title,
|
|
586
|
+
},
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
if (action === "wait_for") {
|
|
590
|
+
const waitState = normalize(payload.state) || "visible";
|
|
591
|
+
const startedAt = Date.now();
|
|
592
|
+
while (Date.now() - startedAt <= timeoutMs) {
|
|
593
|
+
const current = resolveTarget();
|
|
594
|
+
const visible = current ? isVisible(current) : false;
|
|
595
|
+
if (
|
|
596
|
+
(waitState === "attached" && current) ||
|
|
597
|
+
(waitState === "detached" && !current) ||
|
|
598
|
+
(waitState === "visible" && current && visible) ||
|
|
599
|
+
(waitState === "hidden" && (!current || !visible))
|
|
600
|
+
) {
|
|
601
|
+
return {
|
|
602
|
+
ok: true,
|
|
603
|
+
result: {
|
|
604
|
+
url: location.href,
|
|
605
|
+
title: document.title,
|
|
606
|
+
},
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
await sleep(100);
|
|
404
610
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
611
|
+
return {
|
|
612
|
+
ok: false,
|
|
613
|
+
error: "Timed out waiting for the requested element state.",
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
if (action === "wait_for_url") {
|
|
617
|
+
const exactUrl = normalize(payload.url);
|
|
618
|
+
const containsUrl = normalize(payload.url_contains);
|
|
619
|
+
const startedAt = Date.now();
|
|
620
|
+
while (Date.now() - startedAt <= timeoutMs) {
|
|
621
|
+
const currentUrl = String(location.href || "");
|
|
622
|
+
if ((exactUrl && currentUrl === exactUrl) || (containsUrl && currentUrl.includes(containsUrl))) {
|
|
623
|
+
return {
|
|
624
|
+
ok: true,
|
|
625
|
+
result: {
|
|
626
|
+
url: currentUrl,
|
|
627
|
+
title: document.title,
|
|
628
|
+
},
|
|
629
|
+
};
|
|
409
630
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
631
|
+
await sleep(100);
|
|
632
|
+
}
|
|
633
|
+
return {
|
|
634
|
+
ok: false,
|
|
635
|
+
error: "Timed out waiting for the requested URL.",
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
if (action === "reload") {
|
|
639
|
+
location.reload();
|
|
640
|
+
return {
|
|
641
|
+
ok: true,
|
|
642
|
+
result: {
|
|
643
|
+
url: location.href,
|
|
644
|
+
title: document.title,
|
|
645
|
+
},
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
if (action === "fill") {
|
|
649
|
+
target.focus();
|
|
650
|
+
target.value = payload.value || "";
|
|
651
|
+
target.dispatchEvent(new Event("input", { bubbles: true }));
|
|
652
|
+
target.dispatchEvent(new Event("change", { bubbles: true }));
|
|
653
|
+
return {
|
|
654
|
+
ok: true,
|
|
655
|
+
result: {
|
|
656
|
+
url: location.href,
|
|
657
|
+
title: document.title,
|
|
658
|
+
},
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
if (action === "press") {
|
|
662
|
+
const key = String(payload.key || "");
|
|
663
|
+
if (target && typeof target.focus === "function") {
|
|
664
|
+
target.focus();
|
|
422
665
|
}
|
|
423
|
-
|
|
424
|
-
|
|
666
|
+
const eventInit = { key, bubbles: true, cancelable: true };
|
|
667
|
+
const keyboardTarget = document.activeElement || target || document.body;
|
|
668
|
+
keyboardTarget.dispatchEvent(new KeyboardEvent("keydown", eventInit));
|
|
669
|
+
keyboardTarget.dispatchEvent(new KeyboardEvent("keypress", eventInit));
|
|
670
|
+
keyboardTarget.dispatchEvent(new KeyboardEvent("keyup", eventInit));
|
|
671
|
+
return {
|
|
672
|
+
ok: true,
|
|
673
|
+
result: {
|
|
674
|
+
url: location.href,
|
|
675
|
+
title: document.title,
|
|
676
|
+
},
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
return { ok: false, error: "Unsupported tab action." };
|
|
425
680
|
}
|
|
426
681
|
|
|
427
682
|
async function runTabAction(tabId, action, payload) {
|
|
428
683
|
const activeTab = await ensureTabIsActive(tabId);
|
|
429
684
|
|
|
685
|
+
if (action === "attach") {
|
|
686
|
+
const record = {
|
|
687
|
+
tab_id: activeTab.id,
|
|
688
|
+
window_id: activeTab.windowId,
|
|
689
|
+
active: activeTab.active === true,
|
|
690
|
+
title: activeTab.title || "",
|
|
691
|
+
url: activeTab.url || "",
|
|
692
|
+
status: activeTab.status || "",
|
|
693
|
+
};
|
|
694
|
+
await setAttachedTab(record);
|
|
695
|
+
try {
|
|
696
|
+
await injectRecorderContent(tabId);
|
|
697
|
+
} catch {
|
|
698
|
+
// ignore
|
|
699
|
+
}
|
|
700
|
+
return {
|
|
701
|
+
ok: true,
|
|
702
|
+
result: {
|
|
703
|
+
url: record.url,
|
|
704
|
+
title: record.title,
|
|
705
|
+
},
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
if (action === "detach") {
|
|
710
|
+
await setAttachedTab(null);
|
|
711
|
+
return {
|
|
712
|
+
ok: true,
|
|
713
|
+
result: {
|
|
714
|
+
url: activeTab.url || "",
|
|
715
|
+
title: activeTab.title || "",
|
|
716
|
+
},
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
if (action === "close") {
|
|
721
|
+
if (attachedTabId === tabId) {
|
|
722
|
+
await setAttachedTab(null);
|
|
723
|
+
}
|
|
724
|
+
await tabsRemove(tabId);
|
|
725
|
+
return {
|
|
726
|
+
ok: true,
|
|
727
|
+
result: {
|
|
728
|
+
url: activeTab.url || "",
|
|
729
|
+
title: activeTab.title || "",
|
|
730
|
+
},
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
if (action === "get_logs") {
|
|
735
|
+
return {
|
|
736
|
+
ok: true,
|
|
737
|
+
result: getBufferedLogSnapshot(
|
|
738
|
+
tabId,
|
|
739
|
+
Number.isInteger(payload?.limit) ? Number(payload.limit) : undefined,
|
|
740
|
+
),
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
if (action === "clear_logs") {
|
|
745
|
+
return {
|
|
746
|
+
ok: true,
|
|
747
|
+
result: clearBufferedLogs(tabId),
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
|
|
430
751
|
if (action === "screenshot") {
|
|
752
|
+
await new Promise((resolve) => setTimeout(resolve, 180));
|
|
753
|
+
const refreshedTab = await tabsGet(tabId).catch(() => activeTab);
|
|
431
754
|
const dataUrl = await captureVisibleTab(activeTab.windowId, {
|
|
432
755
|
format: "png",
|
|
433
756
|
});
|
|
@@ -435,8 +758,8 @@ async function runTabAction(tabId, action, payload) {
|
|
|
435
758
|
ok: true,
|
|
436
759
|
result: {
|
|
437
760
|
png_base64: String(dataUrl).replace(/^data:image\/png;base64,/, ""),
|
|
438
|
-
url:
|
|
439
|
-
title:
|
|
761
|
+
url: refreshedTab.url || "",
|
|
762
|
+
title: refreshedTab.title || "",
|
|
440
763
|
},
|
|
441
764
|
};
|
|
442
765
|
}
|
|
@@ -474,7 +797,8 @@ async function runTabAction(tabId, action, payload) {
|
|
|
474
797
|
|
|
475
798
|
const execution = await scriptingExecuteScript({
|
|
476
799
|
target: { tabId },
|
|
477
|
-
func:
|
|
800
|
+
func: executeTabActionInPage,
|
|
801
|
+
args: [action, payload || {}],
|
|
478
802
|
});
|
|
479
803
|
const result = execution[0]?.result;
|
|
480
804
|
|
|
@@ -495,6 +819,14 @@ function sendJson(payload) {
|
|
|
495
819
|
socket.send(JSON.stringify(payload));
|
|
496
820
|
}
|
|
497
821
|
|
|
822
|
+
function isSocketOpenOrConnecting() {
|
|
823
|
+
return Boolean(
|
|
824
|
+
socket &&
|
|
825
|
+
(socket.readyState === WebSocket.OPEN ||
|
|
826
|
+
socket.readyState === WebSocket.CONNECTING),
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
|
|
498
830
|
function clearTimers() {
|
|
499
831
|
if (reconnectTimer) {
|
|
500
832
|
clearTimeout(reconnectTimer);
|
|
@@ -534,7 +866,7 @@ function startHeartbeat() {
|
|
|
534
866
|
heartbeatTimer = setInterval(() => {
|
|
535
867
|
sendJson({
|
|
536
868
|
type: "heartbeat",
|
|
537
|
-
sent_at: new Date()
|
|
869
|
+
sent_at: formatLocalTimestamp(new Date()),
|
|
538
870
|
});
|
|
539
871
|
}, HEARTBEAT_INTERVAL_MS);
|
|
540
872
|
}
|
|
@@ -581,7 +913,7 @@ function sendRecordingEvent(recordingId, tabId, event) {
|
|
|
581
913
|
tab_id: tabId,
|
|
582
914
|
event: {
|
|
583
915
|
...event,
|
|
584
|
-
at: event.at || new Date()
|
|
916
|
+
at: event.at || formatLocalTimestamp(new Date()),
|
|
585
917
|
},
|
|
586
918
|
});
|
|
587
919
|
}
|
|
@@ -593,19 +925,31 @@ async function injectRecorderContent(tabId) {
|
|
|
593
925
|
});
|
|
594
926
|
}
|
|
595
927
|
|
|
928
|
+
function capturePageSnapshotInTab(recordingReason) {
|
|
929
|
+
const now = new Date();
|
|
930
|
+
const pad = (value, length = 2) => String(value).padStart(length, "0");
|
|
931
|
+
const timestamp = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}T${pad(
|
|
932
|
+
now.getHours(),
|
|
933
|
+
)}:${pad(now.getMinutes())}:${pad(now.getSeconds())}.${pad(
|
|
934
|
+
now.getMilliseconds(),
|
|
935
|
+
3,
|
|
936
|
+
)}`;
|
|
937
|
+
return {
|
|
938
|
+
kind: "page_snapshot",
|
|
939
|
+
source: "background",
|
|
940
|
+
reason: recordingReason,
|
|
941
|
+
at: timestamp,
|
|
942
|
+
url: location.href,
|
|
943
|
+
title: document.title,
|
|
944
|
+
ready_state: document.readyState,
|
|
945
|
+
html: document.documentElement ? document.documentElement.outerHTML : "",
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
|
|
596
949
|
async function captureTabSnapshot(tabId, reason) {
|
|
597
950
|
const execution = await scriptingExecuteScript({
|
|
598
951
|
target: { tabId },
|
|
599
|
-
func:
|
|
600
|
-
kind: "page_snapshot",
|
|
601
|
-
source: "background",
|
|
602
|
-
reason: recordingReason,
|
|
603
|
-
at: new Date().toISOString(),
|
|
604
|
-
url: location.href,
|
|
605
|
-
title: document.title,
|
|
606
|
-
ready_state: document.readyState,
|
|
607
|
-
html: document.documentElement ? document.documentElement.outerHTML : ""
|
|
608
|
-
}),
|
|
952
|
+
func: capturePageSnapshotInTab,
|
|
609
953
|
args: [reason],
|
|
610
954
|
});
|
|
611
955
|
const result = execution[0]?.result;
|
|
@@ -656,7 +1000,7 @@ async function startRecording(message) {
|
|
|
656
1000
|
tabId,
|
|
657
1001
|
tabTitle: browserTab.title || "",
|
|
658
1002
|
tabUrl: browserTab.url || "",
|
|
659
|
-
startedAt: new Date()
|
|
1003
|
+
startedAt: formatLocalTimestamp(new Date()),
|
|
660
1004
|
});
|
|
661
1005
|
activeRecordingIdByTabId.set(tabId, message.recording_id);
|
|
662
1006
|
|
|
@@ -730,6 +1074,11 @@ async function handleAttachTabSelectedCommand(tabId) {
|
|
|
730
1074
|
tab: record,
|
|
731
1075
|
});
|
|
732
1076
|
await setAttachedTab(record);
|
|
1077
|
+
try {
|
|
1078
|
+
await injectRecorderContent(tabId);
|
|
1079
|
+
} catch {
|
|
1080
|
+
// ignore recorder bootstrap errors for manual attach
|
|
1081
|
+
}
|
|
733
1082
|
|
|
734
1083
|
return {
|
|
735
1084
|
ok: true,
|
|
@@ -831,6 +1180,29 @@ async function sendManualRecordingRequest(type, payload = {}) {
|
|
|
831
1180
|
return result;
|
|
832
1181
|
}
|
|
833
1182
|
|
|
1183
|
+
async function handleTabActionMessage(message) {
|
|
1184
|
+
let result;
|
|
1185
|
+
try {
|
|
1186
|
+
result = await runTabAction(
|
|
1187
|
+
message.tab_id,
|
|
1188
|
+
message.action,
|
|
1189
|
+
message.payload || {},
|
|
1190
|
+
);
|
|
1191
|
+
} catch (error) {
|
|
1192
|
+
result = {
|
|
1193
|
+
ok: false,
|
|
1194
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
sendJson({
|
|
1198
|
+
type: "tab_action_result",
|
|
1199
|
+
request_id: message.request_id,
|
|
1200
|
+
ok: result.ok === true,
|
|
1201
|
+
...(result.result ? { result: result.result } : {}),
|
|
1202
|
+
...(result.error ? { error: result.error } : {}),
|
|
1203
|
+
});
|
|
1204
|
+
}
|
|
1205
|
+
|
|
834
1206
|
async function handleMessage(rawData) {
|
|
835
1207
|
const message =
|
|
836
1208
|
typeof rawData === "string"
|
|
@@ -865,18 +1237,7 @@ async function handleMessage(rawData) {
|
|
|
865
1237
|
return;
|
|
866
1238
|
}
|
|
867
1239
|
case "tab_action": {
|
|
868
|
-
|
|
869
|
-
message.tab_id,
|
|
870
|
-
message.action,
|
|
871
|
-
message.payload || {},
|
|
872
|
-
);
|
|
873
|
-
sendJson({
|
|
874
|
-
type: "tab_action_result",
|
|
875
|
-
request_id: message.request_id,
|
|
876
|
-
ok: result.ok === true,
|
|
877
|
-
...(result.result ? { result: result.result } : {}),
|
|
878
|
-
...(result.error ? { error: result.error } : {}),
|
|
879
|
-
});
|
|
1240
|
+
await handleTabActionMessage(message);
|
|
880
1241
|
return;
|
|
881
1242
|
}
|
|
882
1243
|
case "recording_start": {
|
|
@@ -933,6 +1294,9 @@ async function connect() {
|
|
|
933
1294
|
});
|
|
934
1295
|
return;
|
|
935
1296
|
}
|
|
1297
|
+
if (isSocketOpenOrConnecting()) {
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
936
1300
|
clearTimers();
|
|
937
1301
|
await setConnectionStatus({
|
|
938
1302
|
state: "connecting",
|
|
@@ -1015,7 +1379,7 @@ browser.storage.onChanged.addListener((changes, areaName) => {
|
|
|
1015
1379
|
[POPUP_COMMAND_RESULT_KEY]: {
|
|
1016
1380
|
command_id: command.command_id,
|
|
1017
1381
|
result,
|
|
1018
|
-
at: new Date()
|
|
1382
|
+
at: formatLocalTimestamp(new Date()),
|
|
1019
1383
|
},
|
|
1020
1384
|
}),
|
|
1021
1385
|
)
|
|
@@ -1027,7 +1391,7 @@ browser.storage.onChanged.addListener((changes, areaName) => {
|
|
|
1027
1391
|
ok: false,
|
|
1028
1392
|
error: error instanceof Error ? error.message : String(error),
|
|
1029
1393
|
},
|
|
1030
|
-
at: new Date()
|
|
1394
|
+
at: formatLocalTimestamp(new Date()),
|
|
1031
1395
|
},
|
|
1032
1396
|
}),
|
|
1033
1397
|
);
|
|
@@ -1095,34 +1459,41 @@ browser.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
|
|
|
1095
1459
|
});
|
|
1096
1460
|
|
|
1097
1461
|
const recording = getRecordingByTabId(tabId);
|
|
1098
|
-
|
|
1462
|
+
const shouldTrackTab = recording || attachedTabId === tabId;
|
|
1463
|
+
if (!shouldTrackTab) {
|
|
1099
1464
|
return;
|
|
1100
1465
|
}
|
|
1101
1466
|
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1467
|
+
if (recording) {
|
|
1468
|
+
sendRecordingEvent(recording.recordingId, tabId, {
|
|
1469
|
+
kind: "navigation",
|
|
1470
|
+
source: "browser",
|
|
1471
|
+
status: changeInfo.status || tab.status || "",
|
|
1472
|
+
url: tab.url || "",
|
|
1473
|
+
title: tab.title || "",
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1109
1476
|
|
|
1110
1477
|
if (changeInfo.status === "complete") {
|
|
1111
1478
|
try {
|
|
1112
1479
|
await injectRecorderContent(tabId);
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1480
|
+
if (recording) {
|
|
1481
|
+
const snapshot = await captureTabSnapshot(tabId, "tab-updated-complete");
|
|
1482
|
+
if (snapshot) {
|
|
1483
|
+
sendRecordingEvent(recording.recordingId, tabId, snapshot);
|
|
1484
|
+
}
|
|
1116
1485
|
}
|
|
1117
1486
|
} catch {
|
|
1118
1487
|
// ignore
|
|
1119
1488
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1489
|
+
if (recording) {
|
|
1490
|
+
await emitCookiesSnapshot(
|
|
1491
|
+
recording.recordingId,
|
|
1492
|
+
tabId,
|
|
1493
|
+
tab.url || "",
|
|
1494
|
+
tab.title || "",
|
|
1495
|
+
);
|
|
1496
|
+
}
|
|
1126
1497
|
}
|
|
1127
1498
|
});
|
|
1128
1499
|
|
|
@@ -1170,9 +1541,10 @@ async function handleRuntimeMessage(message, sender) {
|
|
|
1170
1541
|
if (!Number.isInteger(tabId)) {
|
|
1171
1542
|
return undefined;
|
|
1172
1543
|
}
|
|
1544
|
+
recordPageEventForBuffers(tabId, message.event || {});
|
|
1173
1545
|
const recording = getRecordingByTabId(tabId);
|
|
1174
1546
|
if (!recording) {
|
|
1175
|
-
return
|
|
1547
|
+
return { ok: true };
|
|
1176
1548
|
}
|
|
1177
1549
|
sendRecordingEvent(recording.recordingId, tabId, message.event || {});
|
|
1178
1550
|
return { ok: true };
|
|
@@ -1286,6 +1658,20 @@ browser.webRequest.onHeadersReceived.addListener(
|
|
|
1286
1658
|
|
|
1287
1659
|
browser.webRequest.onCompleted.addListener(
|
|
1288
1660
|
(details) => {
|
|
1661
|
+
if (
|
|
1662
|
+
Number.isInteger(details.tabId) &&
|
|
1663
|
+
details.tabId >= 0 &&
|
|
1664
|
+
typeof details.statusCode === "number" &&
|
|
1665
|
+
details.statusCode >= 400
|
|
1666
|
+
) {
|
|
1667
|
+
recordNetworkFailure(details.tabId, {
|
|
1668
|
+
url: details.url,
|
|
1669
|
+
method: details.method,
|
|
1670
|
+
status: details.statusCode,
|
|
1671
|
+
resource_type: details.type,
|
|
1672
|
+
timestamp: formatLocalTimestamp(new Date()),
|
|
1673
|
+
});
|
|
1674
|
+
}
|
|
1289
1675
|
const recording = getRecordingByTabId(details.tabId);
|
|
1290
1676
|
if (!recording) {
|
|
1291
1677
|
return;
|
|
@@ -1305,6 +1691,15 @@ browser.webRequest.onCompleted.addListener(
|
|
|
1305
1691
|
|
|
1306
1692
|
browser.webRequest.onErrorOccurred.addListener(
|
|
1307
1693
|
(details) => {
|
|
1694
|
+
if (Number.isInteger(details.tabId) && details.tabId >= 0) {
|
|
1695
|
+
recordNetworkFailure(details.tabId, {
|
|
1696
|
+
url: details.url,
|
|
1697
|
+
method: details.method,
|
|
1698
|
+
error_text: details.error,
|
|
1699
|
+
resource_type: details.type,
|
|
1700
|
+
timestamp: formatLocalTimestamp(new Date()),
|
|
1701
|
+
});
|
|
1702
|
+
}
|
|
1308
1703
|
const recording = getRecordingByTabId(details.tabId);
|
|
1309
1704
|
if (!recording) {
|
|
1310
1705
|
return;
|
|
@@ -1322,5 +1717,19 @@ browser.webRequest.onErrorOccurred.addListener(
|
|
|
1322
1717
|
{ urls: ["<all_urls>"] },
|
|
1323
1718
|
);
|
|
1324
1719
|
|
|
1720
|
+
browser.alarms.onAlarm.addListener((alarm) => {
|
|
1721
|
+
if (alarm.name !== KEEPALIVE_ALARM_NAME || manualDisconnect) {
|
|
1722
|
+
return;
|
|
1723
|
+
}
|
|
1724
|
+
if (!isSocketOpenOrConnecting()) {
|
|
1725
|
+
void connect();
|
|
1726
|
+
}
|
|
1727
|
+
});
|
|
1728
|
+
|
|
1729
|
+
browser.alarms.create(KEEPALIVE_ALARM_NAME, {
|
|
1730
|
+
periodInMinutes: KEEPALIVE_ALARM_PERIOD_MINUTES,
|
|
1731
|
+
});
|
|
1732
|
+
|
|
1733
|
+
void hydrateAttachedTabSelection();
|
|
1325
1734
|
void computeInstanceId();
|
|
1326
1735
|
void connect();
|