@deadragdoll/tellymcp 0.0.13 → 0.0.14
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 +5 -1
- package/.env.example.gateway +16 -1
- package/CHANGELOG.md +30 -0
- package/README-ru.md +14 -0
- package/README.md +14 -0
- package/TOOLS.md +182 -10
- package/config/templates/env.both.template +15 -2
- package/config/templates/env.client.template +5 -2
- package/config/templates/env.gateway.template +15 -2
- package/dist/cli.js +28 -1
- package/dist/services/features/telegram-mcp/browser.service.js +18 -0
- 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/mcp-http.service.js +1 -0
- package/dist/services/features/telegram-mcp/mcp-server.service.js +18 -0
- package/dist/services/features/telegram-mcp/src/app/bootstrap/runtime.js +9 -1
- package/dist/services/features/telegram-mcp/src/app/config/env.js +119 -2
- 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 +62 -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 +410 -27
- 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/sendPartnerFileService.js +6 -3
- package/dist/services/features/telegram-mcp/src/features/distributed-client/model/gatewayClientAccess.js +4 -1
- package/dist/services/features/telegram-mcp/src/features/distributed-gateway/model/gatewayHttpService.js +33 -35
- 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 +5 -1
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transport.js +3 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportFileHandoffActions.js +6 -3
- 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/time/localTimestamp.js +21 -0
- package/docs/CHAT_CONNECTOR.md +134 -0
- package/docs/STANDALONE-ru.md +13 -0
- package/docs/STANDALONE.md +13 -0
- 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
|
@@ -13,16 +13,34 @@ const POPUP_COMMAND_RESULT_KEY = "attach_popup_command_result";
|
|
|
13
13
|
const RECONNECT_DELAY_MS = 3000;
|
|
14
14
|
const HEARTBEAT_INTERVAL_MS = 15000;
|
|
15
15
|
const MAX_CAPTURE_BYTES = 512 * 1024;
|
|
16
|
+
const MAX_BUFFERED_LOG_ENTRIES = 200;
|
|
16
17
|
|
|
17
18
|
let socket = null;
|
|
18
19
|
let reconnectTimer = null;
|
|
19
20
|
let heartbeatTimer = null;
|
|
20
21
|
let instanceId = null;
|
|
22
|
+
let attachedTabId = null;
|
|
21
23
|
let manualDisconnect = false;
|
|
22
24
|
const pendingManualRecordingRequests = new Map();
|
|
23
25
|
|
|
24
26
|
const activeRecordingsById = new Map();
|
|
25
27
|
const activeRecordingIdByTabId = new Map();
|
|
28
|
+
const consoleMessagesByTabId = new Map();
|
|
29
|
+
const pageErrorsByTabId = new Map();
|
|
30
|
+
const networkFailuresByTabId = new Map();
|
|
31
|
+
|
|
32
|
+
function padNumber(value, length = 2) {
|
|
33
|
+
return String(value).padStart(length, "0");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function formatLocalTimestamp(date = new Date()) {
|
|
37
|
+
return `${date.getFullYear()}-${padNumber(date.getMonth() + 1)}-${padNumber(date.getDate())}T${padNumber(
|
|
38
|
+
date.getHours(),
|
|
39
|
+
)}:${padNumber(date.getMinutes())}:${padNumber(date.getSeconds())}.${padNumber(
|
|
40
|
+
date.getMilliseconds(),
|
|
41
|
+
3,
|
|
42
|
+
)}`;
|
|
43
|
+
}
|
|
26
44
|
|
|
27
45
|
async function getSettings() {
|
|
28
46
|
return browser.storage.local.get({
|
|
@@ -44,6 +62,10 @@ async function setLocalInstanceId(value) {
|
|
|
44
62
|
}
|
|
45
63
|
|
|
46
64
|
async function setAttachedTab(tab) {
|
|
65
|
+
attachedTabId =
|
|
66
|
+
tab && Number.isInteger(tab.tab_id)
|
|
67
|
+
? Number(tab.tab_id)
|
|
68
|
+
: null;
|
|
47
69
|
await browser.storage.local.set({
|
|
48
70
|
[ATTACHED_TAB_KEY]: tab,
|
|
49
71
|
});
|
|
@@ -55,6 +77,15 @@ async function setRecordingStatus(status) {
|
|
|
55
77
|
});
|
|
56
78
|
}
|
|
57
79
|
|
|
80
|
+
async function hydrateAttachedTabSelection() {
|
|
81
|
+
const stored = await browser.storage.local.get({ [ATTACHED_TAB_KEY]: null });
|
|
82
|
+
const tab = stored[ATTACHED_TAB_KEY];
|
|
83
|
+
attachedTabId =
|
|
84
|
+
tab && Number.isInteger(tab.tab_id)
|
|
85
|
+
? Number(tab.tab_id)
|
|
86
|
+
: null;
|
|
87
|
+
}
|
|
88
|
+
|
|
58
89
|
function buildWebSocketUrl(settings) {
|
|
59
90
|
return `ws://${settings.host}:${settings.port}/browser-attach/ws`;
|
|
60
91
|
}
|
|
@@ -109,14 +140,157 @@ async function ensureTabIsActive(tabId) {
|
|
|
109
140
|
return await browser.tabs.get(tabId);
|
|
110
141
|
}
|
|
111
142
|
|
|
143
|
+
function trimBufferedEntries(entries) {
|
|
144
|
+
if (entries.length <= MAX_BUFFERED_LOG_ENTRIES) {
|
|
145
|
+
return entries;
|
|
146
|
+
}
|
|
147
|
+
return entries.slice(entries.length - MAX_BUFFERED_LOG_ENTRIES);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function appendBufferedEntry(store, tabId, entry) {
|
|
151
|
+
if (!Number.isInteger(tabId) || tabId < 0 || !entry || typeof entry !== "object") {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const existing = store.get(tabId) || [];
|
|
155
|
+
existing.push(entry);
|
|
156
|
+
store.set(tabId, trimBufferedEntries(existing));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function extractErrorStack(args) {
|
|
160
|
+
if (!Array.isArray(args)) {
|
|
161
|
+
return "";
|
|
162
|
+
}
|
|
163
|
+
for (const arg of args) {
|
|
164
|
+
if (!arg || typeof arg !== "object") {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (typeof arg.stack === "string" && arg.stack.trim()) {
|
|
168
|
+
return arg.stack;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return "";
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function extractConsoleLocation(event) {
|
|
175
|
+
if (!event || typeof event !== "object" || !Array.isArray(event.args)) {
|
|
176
|
+
return "";
|
|
177
|
+
}
|
|
178
|
+
for (const arg of event.args) {
|
|
179
|
+
if (!arg || typeof arg !== "object") {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (typeof arg.filename === "string" && arg.filename.trim()) {
|
|
183
|
+
const line = Number.isFinite(arg.lineno) ? Number(arg.lineno) : 0;
|
|
184
|
+
const column = Number.isFinite(arg.colno) ? Number(arg.colno) : 0;
|
|
185
|
+
return `${arg.filename}:${line}:${column}`;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return "";
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function recordPageEventForBuffers(tabId, event) {
|
|
192
|
+
if (!Number.isInteger(tabId) || tabId < 0 || !event || typeof event !== "object") {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (event.kind !== "console_event") {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const timestamp =
|
|
200
|
+
typeof event.at === "string" && event.at.trim()
|
|
201
|
+
? event.at
|
|
202
|
+
: formatLocalTimestamp(new Date());
|
|
203
|
+
const text = typeof event.text === "string" ? event.text : "";
|
|
204
|
+
const location = extractConsoleLocation(event);
|
|
205
|
+
|
|
206
|
+
appendBufferedEntry(consoleMessagesByTabId, tabId, {
|
|
207
|
+
type:
|
|
208
|
+
typeof event.level === "string" && event.level.trim()
|
|
209
|
+
? event.level
|
|
210
|
+
: "log",
|
|
211
|
+
text,
|
|
212
|
+
...(location ? { location } : {}),
|
|
213
|
+
timestamp,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
if (event.level === "error" || event.level === "assert") {
|
|
217
|
+
const stack = extractErrorStack(event.args);
|
|
218
|
+
appendBufferedEntry(pageErrorsByTabId, tabId, {
|
|
219
|
+
message: text || "Console error",
|
|
220
|
+
...(stack ? { stack } : {}),
|
|
221
|
+
timestamp,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function recordNetworkFailure(tabId, event) {
|
|
227
|
+
if (!Number.isInteger(tabId) || tabId < 0 || !event || typeof event !== "object") {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
appendBufferedEntry(networkFailuresByTabId, tabId, {
|
|
231
|
+
url: typeof event.url === "string" ? event.url : "",
|
|
232
|
+
method:
|
|
233
|
+
typeof event.method === "string" && event.method.trim()
|
|
234
|
+
? event.method
|
|
235
|
+
: "GET",
|
|
236
|
+
...(typeof event.status === "number" ? { status: event.status } : {}),
|
|
237
|
+
...(typeof event.error_text === "string" ? { error_text: event.error_text } : {}),
|
|
238
|
+
...(typeof event.resource_type === "string"
|
|
239
|
+
? { resource_type: event.resource_type }
|
|
240
|
+
: {}),
|
|
241
|
+
timestamp:
|
|
242
|
+
typeof event.timestamp === "string" && event.timestamp.trim()
|
|
243
|
+
? event.timestamp
|
|
244
|
+
: formatLocalTimestamp(new Date()),
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function getBufferedLogSnapshot(tabId, limit) {
|
|
249
|
+
const normalize = (value) =>
|
|
250
|
+
Number.isInteger(value) && value > 0 ? Number(value) : undefined;
|
|
251
|
+
const sliceEntries = (entries) => {
|
|
252
|
+
const normalizedLimit = normalize(limit);
|
|
253
|
+
return normalizedLimit ? entries.slice(-normalizedLimit) : entries;
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const consoleMessages = consoleMessagesByTabId.get(tabId) || [];
|
|
257
|
+
const pageErrors = pageErrorsByTabId.get(tabId) || [];
|
|
258
|
+
const networkFailures = networkFailuresByTabId.get(tabId) || [];
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
console_messages: sliceEntries(consoleMessages),
|
|
262
|
+
console_total: consoleMessages.length,
|
|
263
|
+
page_errors: sliceEntries(pageErrors),
|
|
264
|
+
page_error_total: pageErrors.length,
|
|
265
|
+
network_failures: sliceEntries(networkFailures),
|
|
266
|
+
network_failure_total: networkFailures.length,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function clearBufferedLogs(tabId) {
|
|
271
|
+
const consoleMessages = consoleMessagesByTabId.get(tabId) || [];
|
|
272
|
+
const pageErrors = pageErrorsByTabId.get(tabId) || [];
|
|
273
|
+
const networkFailures = networkFailuresByTabId.get(tabId) || [];
|
|
274
|
+
consoleMessagesByTabId.delete(tabId);
|
|
275
|
+
pageErrorsByTabId.delete(tabId);
|
|
276
|
+
networkFailuresByTabId.delete(tabId);
|
|
277
|
+
return {
|
|
278
|
+
console_messages_cleared: consoleMessages.length,
|
|
279
|
+
page_errors_cleared: pageErrors.length,
|
|
280
|
+
network_failures_cleared: networkFailures.length,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
112
284
|
function buildTabActionCode(action, payload) {
|
|
113
285
|
const serializedAction = JSON.stringify(action);
|
|
114
286
|
const serializedPayload = JSON.stringify(payload || {});
|
|
115
287
|
|
|
116
|
-
return `(() => {
|
|
288
|
+
return `(async () => {
|
|
117
289
|
const action = ${serializedAction};
|
|
118
290
|
const payload = ${serializedPayload};
|
|
119
291
|
const normalize = (value) => typeof value === "string" ? value.trim() : "";
|
|
292
|
+
const timeoutMs = Number.isInteger(payload.timeout_ms) ? payload.timeout_ms : 30000;
|
|
293
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
120
294
|
const byText = (text, exact) => {
|
|
121
295
|
const needle = normalize(text);
|
|
122
296
|
if (!needle) return null;
|
|
@@ -131,6 +305,11 @@ function buildTabActionCode(action, payload) {
|
|
|
131
305
|
}
|
|
132
306
|
return null;
|
|
133
307
|
};
|
|
308
|
+
const isVisible = (element) => {
|
|
309
|
+
if (!element) return false;
|
|
310
|
+
const computed = window.getComputedStyle(element);
|
|
311
|
+
return computed.display !== "none" && computed.visibility !== "hidden" && computed.opacity !== "0";
|
|
312
|
+
};
|
|
134
313
|
const resolveTarget = () => {
|
|
135
314
|
const aiTag = normalize(payload.ai_tag);
|
|
136
315
|
if (aiTag) {
|
|
@@ -153,10 +332,6 @@ function buildTabActionCode(action, payload) {
|
|
|
153
332
|
if (target && typeof target.scrollIntoView === "function") {
|
|
154
333
|
target.scrollIntoView({ block: "center", inline: "center" });
|
|
155
334
|
}
|
|
156
|
-
const toVisible = (element) => {
|
|
157
|
-
const computed = window.getComputedStyle(element);
|
|
158
|
-
return computed.display !== "none" && computed.visibility !== "hidden" && computed.opacity !== "0";
|
|
159
|
-
};
|
|
160
335
|
if (action === "dom") {
|
|
161
336
|
const attributes = target
|
|
162
337
|
? Object.fromEntries(Array.from(target.attributes || []).map((attr) => [attr.name, attr.value]))
|
|
@@ -167,13 +342,49 @@ function buildTabActionCode(action, payload) {
|
|
|
167
342
|
found: Boolean(target),
|
|
168
343
|
outer_html: payload.include_html === false ? undefined : target.outerHTML,
|
|
169
344
|
text_content: payload.include_text === false ? undefined : (target.textContent || "").trim(),
|
|
170
|
-
visible: target ?
|
|
345
|
+
visible: target ? isVisible(target) : false,
|
|
171
346
|
attributes,
|
|
172
347
|
url: location.href,
|
|
173
348
|
title: document.title,
|
|
174
349
|
},
|
|
175
350
|
};
|
|
176
351
|
}
|
|
352
|
+
if (action === "computed_style") {
|
|
353
|
+
if (!target) {
|
|
354
|
+
return {
|
|
355
|
+
ok: true,
|
|
356
|
+
result: {
|
|
357
|
+
found: false,
|
|
358
|
+
url: location.href,
|
|
359
|
+
title: document.title,
|
|
360
|
+
},
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
const requestedProperties = Array.isArray(payload.properties) && payload.properties.length
|
|
364
|
+
? payload.properties.map((item) => String(item))
|
|
365
|
+
: ["display","position","visibility","opacity","color","background-color","font-size","z-index","overflow"];
|
|
366
|
+
const computed = window.getComputedStyle(target);
|
|
367
|
+
const rect = target.getBoundingClientRect();
|
|
368
|
+
const styles = Object.fromEntries(
|
|
369
|
+
requestedProperties.map((property) => [property, computed.getPropertyValue(property)]),
|
|
370
|
+
);
|
|
371
|
+
return {
|
|
372
|
+
ok: true,
|
|
373
|
+
result: {
|
|
374
|
+
found: true,
|
|
375
|
+
visible: isVisible(target),
|
|
376
|
+
styles,
|
|
377
|
+
box: {
|
|
378
|
+
x: rect.x,
|
|
379
|
+
y: rect.y,
|
|
380
|
+
width: rect.width,
|
|
381
|
+
height: rect.height,
|
|
382
|
+
},
|
|
383
|
+
url: location.href,
|
|
384
|
+
title: document.title,
|
|
385
|
+
},
|
|
386
|
+
};
|
|
387
|
+
}
|
|
177
388
|
if (action === "click") {
|
|
178
389
|
target.click();
|
|
179
390
|
return {
|
|
@@ -184,6 +395,65 @@ function buildTabActionCode(action, payload) {
|
|
|
184
395
|
},
|
|
185
396
|
};
|
|
186
397
|
}
|
|
398
|
+
if (action === "wait_for") {
|
|
399
|
+
const waitState = normalize(payload.state) || "visible";
|
|
400
|
+
const startedAt = Date.now();
|
|
401
|
+
while (Date.now() - startedAt <= timeoutMs) {
|
|
402
|
+
const current = resolveTarget();
|
|
403
|
+
const visible = current ? isVisible(current) : false;
|
|
404
|
+
if (
|
|
405
|
+
(waitState === "attached" && current) ||
|
|
406
|
+
(waitState === "detached" && !current) ||
|
|
407
|
+
(waitState === "visible" && current && visible) ||
|
|
408
|
+
(waitState === "hidden" && (!current || !visible))
|
|
409
|
+
) {
|
|
410
|
+
return {
|
|
411
|
+
ok: true,
|
|
412
|
+
result: {
|
|
413
|
+
url: location.href,
|
|
414
|
+
title: document.title,
|
|
415
|
+
},
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
await sleep(100);
|
|
419
|
+
}
|
|
420
|
+
return {
|
|
421
|
+
ok: false,
|
|
422
|
+
error: "Timed out waiting for the requested element state.",
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
if (action === "wait_for_url") {
|
|
426
|
+
const exactUrl = normalize(payload.url);
|
|
427
|
+
const containsUrl = normalize(payload.url_contains);
|
|
428
|
+
const startedAt = Date.now();
|
|
429
|
+
while (Date.now() - startedAt <= timeoutMs) {
|
|
430
|
+
const currentUrl = String(location.href || "");
|
|
431
|
+
if ((exactUrl && currentUrl === exactUrl) || (containsUrl && currentUrl.includes(containsUrl))) {
|
|
432
|
+
return {
|
|
433
|
+
ok: true,
|
|
434
|
+
result: {
|
|
435
|
+
url: currentUrl,
|
|
436
|
+
title: document.title,
|
|
437
|
+
},
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
await sleep(100);
|
|
441
|
+
}
|
|
442
|
+
return {
|
|
443
|
+
ok: false,
|
|
444
|
+
error: "Timed out waiting for the requested URL.",
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
if (action === "reload") {
|
|
448
|
+
location.reload();
|
|
449
|
+
return {
|
|
450
|
+
ok: true,
|
|
451
|
+
result: {
|
|
452
|
+
url: location.href,
|
|
453
|
+
title: document.title,
|
|
454
|
+
},
|
|
455
|
+
};
|
|
456
|
+
}
|
|
187
457
|
if (action === "fill") {
|
|
188
458
|
target.focus();
|
|
189
459
|
target.value = payload.value || "";
|
|
@@ -246,16 +516,84 @@ function buildTabActionCode(action, payload) {
|
|
|
246
516
|
async function runTabAction(tabId, action, payload) {
|
|
247
517
|
const activeTab = await ensureTabIsActive(tabId);
|
|
248
518
|
|
|
519
|
+
if (action === "attach") {
|
|
520
|
+
const record = {
|
|
521
|
+
tab_id: activeTab.id,
|
|
522
|
+
window_id: activeTab.windowId,
|
|
523
|
+
active: activeTab.active === true,
|
|
524
|
+
title: activeTab.title || "",
|
|
525
|
+
url: activeTab.url || "",
|
|
526
|
+
status: activeTab.status || "",
|
|
527
|
+
};
|
|
528
|
+
await setAttachedTab(record);
|
|
529
|
+
try {
|
|
530
|
+
await injectRecorderContent(tabId);
|
|
531
|
+
} catch {
|
|
532
|
+
// ignore
|
|
533
|
+
}
|
|
534
|
+
return {
|
|
535
|
+
ok: true,
|
|
536
|
+
result: {
|
|
537
|
+
url: record.url,
|
|
538
|
+
title: record.title,
|
|
539
|
+
},
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if (action === "detach") {
|
|
544
|
+
await setAttachedTab(null);
|
|
545
|
+
return {
|
|
546
|
+
ok: true,
|
|
547
|
+
result: {
|
|
548
|
+
url: activeTab.url || "",
|
|
549
|
+
title: activeTab.title || "",
|
|
550
|
+
},
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
if (action === "close") {
|
|
555
|
+
if (attachedTabId === tabId) {
|
|
556
|
+
await setAttachedTab(null);
|
|
557
|
+
}
|
|
558
|
+
await browser.tabs.remove(tabId);
|
|
559
|
+
return {
|
|
560
|
+
ok: true,
|
|
561
|
+
result: {
|
|
562
|
+
url: activeTab.url || "",
|
|
563
|
+
title: activeTab.title || "",
|
|
564
|
+
},
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (action === "get_logs") {
|
|
569
|
+
return {
|
|
570
|
+
ok: true,
|
|
571
|
+
result: getBufferedLogSnapshot(
|
|
572
|
+
tabId,
|
|
573
|
+
Number.isInteger(payload?.limit) ? Number(payload.limit) : undefined,
|
|
574
|
+
),
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (action === "clear_logs") {
|
|
579
|
+
return {
|
|
580
|
+
ok: true,
|
|
581
|
+
result: clearBufferedLogs(tabId),
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
|
|
249
585
|
if (action === "screenshot") {
|
|
250
|
-
|
|
586
|
+
await new Promise((resolve) => setTimeout(resolve, 180));
|
|
587
|
+
const refreshedTab = await browser.tabs.get(tabId).catch(() => activeTab);
|
|
588
|
+
const dataUrl = await browser.tabs.captureTab(refreshedTab.id, {
|
|
251
589
|
format: "png",
|
|
252
590
|
});
|
|
253
591
|
return {
|
|
254
592
|
ok: true,
|
|
255
593
|
result: {
|
|
256
594
|
png_base64: String(dataUrl).replace(/^data:image\/png;base64,/, ""),
|
|
257
|
-
url:
|
|
258
|
-
title:
|
|
595
|
+
url: refreshedTab.url || "",
|
|
596
|
+
title: refreshedTab.title || "",
|
|
259
597
|
},
|
|
260
598
|
};
|
|
261
599
|
}
|
|
@@ -320,7 +658,7 @@ function startHeartbeat() {
|
|
|
320
658
|
heartbeatTimer = setInterval(() => {
|
|
321
659
|
sendJson({
|
|
322
660
|
type: "heartbeat",
|
|
323
|
-
sent_at: new Date()
|
|
661
|
+
sent_at: formatLocalTimestamp(new Date()),
|
|
324
662
|
});
|
|
325
663
|
}, HEARTBEAT_INTERVAL_MS);
|
|
326
664
|
}
|
|
@@ -391,7 +729,7 @@ function sendRecordingEvent(recordingId, tabId, event) {
|
|
|
391
729
|
tab_id: tabId,
|
|
392
730
|
event: {
|
|
393
731
|
...event,
|
|
394
|
-
at: event.at || new Date()
|
|
732
|
+
at: event.at || formatLocalTimestamp(new Date()),
|
|
395
733
|
},
|
|
396
734
|
});
|
|
397
735
|
}
|
|
@@ -409,7 +747,7 @@ async function captureTabSnapshot(tabId, reason) {
|
|
|
409
747
|
kind: "page_snapshot",
|
|
410
748
|
source: "background",
|
|
411
749
|
reason: ${JSON.stringify(reason)},
|
|
412
|
-
at: new Date()
|
|
750
|
+
at: formatLocalTimestamp(new Date()),
|
|
413
751
|
url: location.href,
|
|
414
752
|
title: document.title,
|
|
415
753
|
ready_state: document.readyState,
|
|
@@ -463,7 +801,7 @@ async function startRecording(message) {
|
|
|
463
801
|
tabId,
|
|
464
802
|
tabTitle: browserTab.title || "",
|
|
465
803
|
tabUrl: browserTab.url || "",
|
|
466
|
-
startedAt: new Date()
|
|
804
|
+
startedAt: formatLocalTimestamp(new Date()),
|
|
467
805
|
});
|
|
468
806
|
activeRecordingIdByTabId.set(tabId, message.recording_id);
|
|
469
807
|
|
|
@@ -583,6 +921,11 @@ async function handlePopupCommand(command) {
|
|
|
583
921
|
tab: record,
|
|
584
922
|
});
|
|
585
923
|
await setAttachedTab(record);
|
|
924
|
+
try {
|
|
925
|
+
await injectRecorderContent(tabId);
|
|
926
|
+
} catch {
|
|
927
|
+
// ignore recorder bootstrap errors for manual attach
|
|
928
|
+
}
|
|
586
929
|
|
|
587
930
|
return {
|
|
588
931
|
ok: true,
|
|
@@ -827,7 +1170,7 @@ browser.storage.onChanged.addListener((changes, areaName) => {
|
|
|
827
1170
|
[POPUP_COMMAND_RESULT_KEY]: {
|
|
828
1171
|
command_id: command.command_id,
|
|
829
1172
|
result,
|
|
830
|
-
at: new Date()
|
|
1173
|
+
at: formatLocalTimestamp(new Date()),
|
|
831
1174
|
},
|
|
832
1175
|
}),
|
|
833
1176
|
)
|
|
@@ -839,7 +1182,7 @@ browser.storage.onChanged.addListener((changes, areaName) => {
|
|
|
839
1182
|
ok: false,
|
|
840
1183
|
error: error instanceof Error ? error.message : String(error),
|
|
841
1184
|
},
|
|
842
|
-
at: new Date()
|
|
1185
|
+
at: formatLocalTimestamp(new Date()),
|
|
843
1186
|
},
|
|
844
1187
|
}),
|
|
845
1188
|
);
|
|
@@ -889,34 +1232,41 @@ browser.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
|
|
|
889
1232
|
});
|
|
890
1233
|
|
|
891
1234
|
const recording = getRecordingByTabId(tabId);
|
|
892
|
-
|
|
1235
|
+
const shouldTrackTab = recording || attachedTabId === tabId;
|
|
1236
|
+
if (!shouldTrackTab) {
|
|
893
1237
|
return;
|
|
894
1238
|
}
|
|
895
1239
|
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
1240
|
+
if (recording) {
|
|
1241
|
+
sendRecordingEvent(recording.recordingId, tabId, {
|
|
1242
|
+
kind: "navigation",
|
|
1243
|
+
source: "browser",
|
|
1244
|
+
status: changeInfo.status || tab.status || "",
|
|
1245
|
+
url: tab.url || "",
|
|
1246
|
+
title: tab.title || "",
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
903
1249
|
|
|
904
1250
|
if (changeInfo.status === "complete") {
|
|
905
1251
|
try {
|
|
906
1252
|
await injectRecorderContent(tabId);
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1253
|
+
if (recording) {
|
|
1254
|
+
const snapshot = await captureTabSnapshot(tabId, "tab-updated-complete");
|
|
1255
|
+
if (snapshot) {
|
|
1256
|
+
sendRecordingEvent(recording.recordingId, tabId, snapshot);
|
|
1257
|
+
}
|
|
910
1258
|
}
|
|
911
1259
|
} catch {
|
|
912
1260
|
// ignore
|
|
913
1261
|
}
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
1262
|
+
if (recording) {
|
|
1263
|
+
await emitCookiesSnapshot(
|
|
1264
|
+
recording.recordingId,
|
|
1265
|
+
tabId,
|
|
1266
|
+
tab.url || "",
|
|
1267
|
+
tab.title || "",
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
920
1270
|
}
|
|
921
1271
|
});
|
|
922
1272
|
|
|
@@ -967,6 +1317,11 @@ async function handleRuntimeMessage(message, sender) {
|
|
|
967
1317
|
tab: record,
|
|
968
1318
|
});
|
|
969
1319
|
await setAttachedTab(record);
|
|
1320
|
+
try {
|
|
1321
|
+
await injectRecorderContent(tabId);
|
|
1322
|
+
} catch {
|
|
1323
|
+
// ignore recorder bootstrap errors for manual attach
|
|
1324
|
+
}
|
|
970
1325
|
|
|
971
1326
|
return {
|
|
972
1327
|
ok: true,
|
|
@@ -1027,9 +1382,10 @@ async function handleRuntimeMessage(message, sender) {
|
|
|
1027
1382
|
if (!Number.isInteger(tabId)) {
|
|
1028
1383
|
return undefined;
|
|
1029
1384
|
}
|
|
1385
|
+
recordPageEventForBuffers(tabId, message.event || {});
|
|
1030
1386
|
const recording = getRecordingByTabId(tabId);
|
|
1031
1387
|
if (!recording) {
|
|
1032
|
-
return
|
|
1388
|
+
return { ok: true };
|
|
1033
1389
|
}
|
|
1034
1390
|
sendRecordingEvent(recording.recordingId, tabId, message.event || {});
|
|
1035
1391
|
return { ok: true };
|
|
@@ -1202,6 +1558,20 @@ browser.webRequest.onHeadersReceived.addListener(
|
|
|
1202
1558
|
|
|
1203
1559
|
browser.webRequest.onCompleted.addListener(
|
|
1204
1560
|
(details) => {
|
|
1561
|
+
if (
|
|
1562
|
+
Number.isInteger(details.tabId) &&
|
|
1563
|
+
details.tabId >= 0 &&
|
|
1564
|
+
typeof details.statusCode === "number" &&
|
|
1565
|
+
details.statusCode >= 400
|
|
1566
|
+
) {
|
|
1567
|
+
recordNetworkFailure(details.tabId, {
|
|
1568
|
+
url: details.url,
|
|
1569
|
+
method: details.method,
|
|
1570
|
+
status: details.statusCode,
|
|
1571
|
+
resource_type: details.type,
|
|
1572
|
+
timestamp: formatLocalTimestamp(new Date()),
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1205
1575
|
const recording = getRecordingByTabId(details.tabId);
|
|
1206
1576
|
if (!recording) {
|
|
1207
1577
|
return;
|
|
@@ -1221,6 +1591,15 @@ browser.webRequest.onCompleted.addListener(
|
|
|
1221
1591
|
|
|
1222
1592
|
browser.webRequest.onErrorOccurred.addListener(
|
|
1223
1593
|
(details) => {
|
|
1594
|
+
if (Number.isInteger(details.tabId) && details.tabId >= 0) {
|
|
1595
|
+
recordNetworkFailure(details.tabId, {
|
|
1596
|
+
url: details.url,
|
|
1597
|
+
method: details.method,
|
|
1598
|
+
error_text: details.error,
|
|
1599
|
+
resource_type: details.type,
|
|
1600
|
+
timestamp: formatLocalTimestamp(new Date()),
|
|
1601
|
+
});
|
|
1602
|
+
}
|
|
1224
1603
|
const recording = getRecordingByTabId(details.tabId);
|
|
1225
1604
|
if (!recording) {
|
|
1226
1605
|
return;
|
|
@@ -1238,5 +1617,6 @@ browser.webRequest.onErrorOccurred.addListener(
|
|
|
1238
1617
|
{ urls: ["<all_urls>"] },
|
|
1239
1618
|
);
|
|
1240
1619
|
|
|
1620
|
+
void hydrateAttachedTabSelection();
|
|
1241
1621
|
void computeInstanceId();
|
|
1242
1622
|
void connect();
|
|
@@ -33,18 +33,6 @@
|
|
|
33
33
|
"page": "options.html",
|
|
34
34
|
"browser_style": true
|
|
35
35
|
},
|
|
36
|
-
"content_scripts": [
|
|
37
|
-
{
|
|
38
|
-
"matches": [
|
|
39
|
-
"<all_urls>"
|
|
40
|
-
],
|
|
41
|
-
"js": [
|
|
42
|
-
"recorder-content.js"
|
|
43
|
-
],
|
|
44
|
-
"run_at": "document_start",
|
|
45
|
-
"all_frames": true
|
|
46
|
-
}
|
|
47
|
-
],
|
|
48
36
|
"web_accessible_resources": [
|
|
49
37
|
"recorder-page.js"
|
|
50
38
|
],
|
|
@@ -18,6 +18,19 @@ const ATTACHED_TAB_KEY = "attach_selected_tab";
|
|
|
18
18
|
const POPUP_COMMAND_KEY = "attach_popup_command";
|
|
19
19
|
const POPUP_COMMAND_RESULT_KEY = "attach_popup_command_result";
|
|
20
20
|
|
|
21
|
+
function padNumber(value, length = 2) {
|
|
22
|
+
return String(value).padStart(length, "0");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function formatLocalTimestamp(date = new Date()) {
|
|
26
|
+
return `${date.getFullYear()}-${padNumber(date.getMonth() + 1)}-${padNumber(date.getDate())}T${padNumber(
|
|
27
|
+
date.getHours(),
|
|
28
|
+
)}:${padNumber(date.getMinutes())}:${padNumber(date.getSeconds())}.${padNumber(
|
|
29
|
+
date.getMilliseconds(),
|
|
30
|
+
3,
|
|
31
|
+
)}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
21
34
|
function storageGet(defaults) {
|
|
22
35
|
return browser.storage.local.get(defaults);
|
|
23
36
|
}
|
|
@@ -412,7 +425,7 @@ async function sendPopupCommand(type, payload = {}, timeoutMs = 15000) {
|
|
|
412
425
|
command_id: commandId,
|
|
413
426
|
type,
|
|
414
427
|
...payload,
|
|
415
|
-
at: new Date()
|
|
428
|
+
at: formatLocalTimestamp(new Date()),
|
|
416
429
|
},
|
|
417
430
|
});
|
|
418
431
|
try {
|