@btraut/browser-bridge 0.3.0 → 0.4.2
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/CHANGELOG.md +42 -0
- package/README.md +72 -31
- package/dist/api.js +1671 -1384
- package/dist/api.js.map +4 -4
- package/dist/index.js +228 -2
- package/dist/index.js.map +4 -4
- package/extension/dist/background.js +509 -2
- package/extension/dist/background.js.map +4 -4
- package/extension/dist/content.js +71 -0
- package/extension/dist/content.js.map +2 -2
- package/extension/manifest.json +1 -1
- package/package.json +1 -1
- package/skills/browser-bridge/SKILL.md +12 -0
- package/skills/browser-bridge/skill.json +1 -1
package/dist/api.js
CHANGED
|
@@ -138,7 +138,8 @@ var SessionRegistry = class {
|
|
|
138
138
|
id,
|
|
139
139
|
state: "INIT" /* INIT */,
|
|
140
140
|
createdAt: now,
|
|
141
|
-
updatedAt: now
|
|
141
|
+
updatedAt: now,
|
|
142
|
+
lastAccessedAt: now
|
|
142
143
|
};
|
|
143
144
|
this.sessions.set(id, session);
|
|
144
145
|
return session;
|
|
@@ -157,8 +158,23 @@ var SessionRegistry = class {
|
|
|
157
158
|
`Session ${sessionId} does not exist.`
|
|
158
159
|
);
|
|
159
160
|
}
|
|
161
|
+
session.lastAccessedAt = /* @__PURE__ */ new Date();
|
|
160
162
|
return session;
|
|
161
163
|
}
|
|
164
|
+
cleanupIdleSessions(ttlMs, now = /* @__PURE__ */ new Date()) {
|
|
165
|
+
if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
|
|
166
|
+
return 0;
|
|
167
|
+
}
|
|
168
|
+
let removed = 0;
|
|
169
|
+
for (const [id, session] of this.sessions.entries()) {
|
|
170
|
+
const idleMs = now.getTime() - session.lastAccessedAt.getTime();
|
|
171
|
+
if (idleMs > ttlMs) {
|
|
172
|
+
this.sessions.delete(id);
|
|
173
|
+
removed += 1;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return removed;
|
|
177
|
+
}
|
|
162
178
|
apply(sessionId, event) {
|
|
163
179
|
const session = this.require(sessionId);
|
|
164
180
|
if (session.state === "CLOSED" /* CLOSED */ && event !== "CLOSE") {
|
|
@@ -467,7 +483,18 @@ var createSessionRouter = (registry, options = {}) => {
|
|
|
467
483
|
return router;
|
|
468
484
|
};
|
|
469
485
|
|
|
470
|
-
// packages/core/src/inspect.ts
|
|
486
|
+
// packages/core/src/inspect/errors.ts
|
|
487
|
+
var InspectError = class extends Error {
|
|
488
|
+
constructor(code, message, options = {}) {
|
|
489
|
+
super(message);
|
|
490
|
+
this.name = "InspectError";
|
|
491
|
+
this.code = code;
|
|
492
|
+
this.retryable = options.retryable ?? false;
|
|
493
|
+
this.details = options.details;
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
// packages/core/src/inspect/service.ts
|
|
471
498
|
var import_crypto3 = require("crypto");
|
|
472
499
|
var import_promises2 = require("node:fs/promises");
|
|
473
500
|
var import_node_path2 = __toESM(require("node:path"));
|
|
@@ -506,6 +533,7 @@ var ExtensionBridge = class {
|
|
|
506
533
|
this.pending = /* @__PURE__ */ new Map();
|
|
507
534
|
this.connected = false;
|
|
508
535
|
this.tabs = [];
|
|
536
|
+
this.badMessageLogsRemaining = 3;
|
|
509
537
|
this.heartbeatInterval = null;
|
|
510
538
|
this.awaitingHeartbeat = false;
|
|
511
539
|
this.debuggerListeners = /* @__PURE__ */ new Set();
|
|
@@ -636,7 +664,11 @@ var ExtensionBridge = class {
|
|
|
636
664
|
if (this.socket && this.socket.readyState === import_ws.WebSocket.OPEN) {
|
|
637
665
|
try {
|
|
638
666
|
this.socket.terminate();
|
|
639
|
-
} catch {
|
|
667
|
+
} catch (error) {
|
|
668
|
+
console.debug(
|
|
669
|
+
"Extension socket terminate failed; falling back to close().",
|
|
670
|
+
error
|
|
671
|
+
);
|
|
640
672
|
this.socket.close();
|
|
641
673
|
}
|
|
642
674
|
}
|
|
@@ -688,7 +720,11 @@ var ExtensionBridge = class {
|
|
|
688
720
|
let message = null;
|
|
689
721
|
try {
|
|
690
722
|
message = JSON.parse(text);
|
|
691
|
-
} catch {
|
|
723
|
+
} catch (error) {
|
|
724
|
+
if (this.badMessageLogsRemaining > 0) {
|
|
725
|
+
this.badMessageLogsRemaining -= 1;
|
|
726
|
+
console.debug("Failed to parse extension message.", error);
|
|
727
|
+
}
|
|
692
728
|
return;
|
|
693
729
|
}
|
|
694
730
|
if (!message || typeof message !== "object") {
|
|
@@ -920,227 +956,7 @@ var DriveController = class {
|
|
|
920
956
|
}
|
|
921
957
|
};
|
|
922
958
|
|
|
923
|
-
// packages/core/src/
|
|
924
|
-
var PAGE_STATE_SCRIPT = [
|
|
925
|
-
"(() => {",
|
|
926
|
-
" const escape = (value) => {",
|
|
927
|
-
" if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {",
|
|
928
|
-
" return CSS.escape(value);",
|
|
929
|
-
" }",
|
|
930
|
-
` return String(value).replace(/["'\\\\]/g, '\\\\$&');`,
|
|
931
|
-
" };",
|
|
932
|
-
" const truncate = (value, max) => {",
|
|
933
|
-
" const text = String(value ?? '');",
|
|
934
|
-
" return text.length > max ? text.slice(0, max) : text;",
|
|
935
|
-
" };",
|
|
936
|
-
" const selectorFor = (element) => {",
|
|
937
|
-
" if (element.id) {",
|
|
938
|
-
" return `#${escape(element.id)}`;",
|
|
939
|
-
" }",
|
|
940
|
-
" const name = element.getAttribute('name');",
|
|
941
|
-
" if (name) {",
|
|
942
|
-
' return `${element.tagName.toLowerCase()}[name="${escape(name)}"]`;',
|
|
943
|
-
" }",
|
|
944
|
-
" const parts = [];",
|
|
945
|
-
" let node = element;",
|
|
946
|
-
" while (node && node.nodeType === 1 && parts.length < 4) {",
|
|
947
|
-
" let part = node.tagName.toLowerCase();",
|
|
948
|
-
" const parent = node.parentElement;",
|
|
949
|
-
" if (parent) {",
|
|
950
|
-
" const siblings = Array.from(parent.children).filter(",
|
|
951
|
-
" (child) => child.tagName === node.tagName",
|
|
952
|
-
" );",
|
|
953
|
-
" if (siblings.length > 1) {",
|
|
954
|
-
" part += `:nth-of-type(${siblings.indexOf(node) + 1})`;",
|
|
955
|
-
" }",
|
|
956
|
-
" }",
|
|
957
|
-
" parts.unshift(part);",
|
|
958
|
-
" node = parent;",
|
|
959
|
-
" }",
|
|
960
|
-
" return parts.join('>');",
|
|
961
|
-
" };",
|
|
962
|
-
" const readStorage = (storage, limit) => {",
|
|
963
|
-
" try {",
|
|
964
|
-
" return Object.keys(storage)",
|
|
965
|
-
" .slice(0, limit)",
|
|
966
|
-
" .map((key) => ({",
|
|
967
|
-
" key,",
|
|
968
|
-
" value: truncate(storage.getItem(key), 500),",
|
|
969
|
-
" }));",
|
|
970
|
-
" } catch {",
|
|
971
|
-
" return [];",
|
|
972
|
-
" }",
|
|
973
|
-
" };",
|
|
974
|
-
" const forms = Array.from(document.querySelectorAll('form')).map((form) => {",
|
|
975
|
-
" const fields = Array.from(form.elements)",
|
|
976
|
-
" .filter((element) => element && element.tagName)",
|
|
977
|
-
" .map((element) => {",
|
|
978
|
-
" const tag = element.tagName.toLowerCase();",
|
|
979
|
-
" const type = 'type' in element && element.type ? element.type : tag;",
|
|
980
|
-
" const name = element.name || element.getAttribute('name') || element.id || '';",
|
|
981
|
-
" let value = '';",
|
|
982
|
-
" let options;",
|
|
983
|
-
" if (tag === 'select') {",
|
|
984
|
-
" const select = element;",
|
|
985
|
-
" value = select.value ?? '';",
|
|
986
|
-
" options = Array.from(select.options).map((option) => option.text);",
|
|
987
|
-
" } else if (tag === 'input' && element.type === 'password') {",
|
|
988
|
-
" value = '[redacted]';",
|
|
989
|
-
" } else if (tag === 'input' || tag === 'textarea') {",
|
|
990
|
-
" value = element.value ?? '';",
|
|
991
|
-
" } else if (element.isContentEditable) {",
|
|
992
|
-
" value = element.textContent ?? '';",
|
|
993
|
-
" } else if ('value' in element) {",
|
|
994
|
-
" value = element.value ?? '';",
|
|
995
|
-
" }",
|
|
996
|
-
" return {",
|
|
997
|
-
" name,",
|
|
998
|
-
" type,",
|
|
999
|
-
" value: truncate(value, 500),",
|
|
1000
|
-
" ...(options ? { options } : {}),",
|
|
1001
|
-
" };",
|
|
1002
|
-
" });",
|
|
1003
|
-
" return {",
|
|
1004
|
-
" selector: selectorFor(form),",
|
|
1005
|
-
" action: form.getAttribute('action') || undefined,",
|
|
1006
|
-
" method: form.getAttribute('method') || undefined,",
|
|
1007
|
-
" fields,",
|
|
1008
|
-
" };",
|
|
1009
|
-
" });",
|
|
1010
|
-
" const localStorage = readStorage(window.localStorage, 100);",
|
|
1011
|
-
" const sessionStorage = readStorage(window.sessionStorage, 100);",
|
|
1012
|
-
" const cookies = (document.cookie ? document.cookie.split(';') : [])",
|
|
1013
|
-
" .map((entry) => entry.trim())",
|
|
1014
|
-
" .filter((entry) => entry.length > 0)",
|
|
1015
|
-
" .slice(0, 50)",
|
|
1016
|
-
" .map((entry) => {",
|
|
1017
|
-
" const [key, ...rest] = entry.split('=');",
|
|
1018
|
-
" return { key, value: truncate(rest.join('='), 500) };",
|
|
1019
|
-
" });",
|
|
1020
|
-
" return { forms, localStorage, sessionStorage, cookies };",
|
|
1021
|
-
"})()"
|
|
1022
|
-
].join("\n");
|
|
1023
|
-
|
|
1024
|
-
// packages/core/src/target-matching.ts
|
|
1025
|
-
var normalizeText = (value) => (value ?? "").trim().toLowerCase();
|
|
1026
|
-
var normalizeUrl = (value) => {
|
|
1027
|
-
const normalized = normalizeText(value);
|
|
1028
|
-
if (!normalized) {
|
|
1029
|
-
return "";
|
|
1030
|
-
}
|
|
1031
|
-
const hashIndex = normalized.indexOf("#");
|
|
1032
|
-
const withoutHash = hashIndex >= 0 ? normalized.slice(0, hashIndex) : normalized;
|
|
1033
|
-
return withoutHash.endsWith("/") && withoutHash.length > 1 ? withoutHash.slice(0, -1) : withoutHash;
|
|
1034
|
-
};
|
|
1035
|
-
var scoreUrlMatch = (candidateUrl, hintUrl) => {
|
|
1036
|
-
if (!candidateUrl || !hintUrl) {
|
|
1037
|
-
return 0;
|
|
1038
|
-
}
|
|
1039
|
-
if (candidateUrl === hintUrl) {
|
|
1040
|
-
return 100;
|
|
1041
|
-
}
|
|
1042
|
-
if (candidateUrl.includes(hintUrl) || hintUrl.includes(candidateUrl)) {
|
|
1043
|
-
return 60;
|
|
1044
|
-
}
|
|
1045
|
-
return 0;
|
|
1046
|
-
};
|
|
1047
|
-
var scoreTitleMatch = (candidateTitle, hintTitle) => {
|
|
1048
|
-
if (!candidateTitle || !hintTitle) {
|
|
1049
|
-
return 0;
|
|
1050
|
-
}
|
|
1051
|
-
if (candidateTitle === hintTitle) {
|
|
1052
|
-
return 80;
|
|
1053
|
-
}
|
|
1054
|
-
if (candidateTitle.includes(hintTitle) || hintTitle.includes(candidateTitle)) {
|
|
1055
|
-
return 40;
|
|
1056
|
-
}
|
|
1057
|
-
return 0;
|
|
1058
|
-
};
|
|
1059
|
-
var scoreRecency = (lastSeenAt, now) => {
|
|
1060
|
-
if (!lastSeenAt) {
|
|
1061
|
-
return 0;
|
|
1062
|
-
}
|
|
1063
|
-
const ageMinutes = Math.max(0, (now - lastSeenAt) / 6e4);
|
|
1064
|
-
return Math.max(0, 30 - ageMinutes);
|
|
1065
|
-
};
|
|
1066
|
-
var scoreHintRecency = (lastSeenAt, hintLastActiveAt) => {
|
|
1067
|
-
if (!lastSeenAt || !hintLastActiveAt) {
|
|
1068
|
-
return 0;
|
|
1069
|
-
}
|
|
1070
|
-
const deltaMinutes = Math.abs(lastSeenAt - hintLastActiveAt) / 6e4;
|
|
1071
|
-
return Math.max(0, 20 - deltaMinutes);
|
|
1072
|
-
};
|
|
1073
|
-
var isBlankUrl = (url) => url.length === 0 || url === "about:blank";
|
|
1074
|
-
var rankTargetCandidates = (candidates, hint, now = Date.now()) => {
|
|
1075
|
-
const normalizedHintUrl = normalizeUrl(hint?.url);
|
|
1076
|
-
const normalizedHintTitle = normalizeText(hint?.title);
|
|
1077
|
-
const hintLastActiveAt = hint?.lastActiveAt ? Date.parse(hint.lastActiveAt) : void 0;
|
|
1078
|
-
const ranked = candidates.map((candidate) => {
|
|
1079
|
-
const reasons = [];
|
|
1080
|
-
const normalizedUrl = normalizeUrl(candidate.url);
|
|
1081
|
-
const normalizedTitle = normalizeText(candidate.title);
|
|
1082
|
-
let score = 0;
|
|
1083
|
-
const urlScore = scoreUrlMatch(normalizedUrl, normalizedHintUrl);
|
|
1084
|
-
if (urlScore > 0) {
|
|
1085
|
-
score += urlScore;
|
|
1086
|
-
reasons.push(urlScore >= 100 ? "url:exact" : "url:partial");
|
|
1087
|
-
}
|
|
1088
|
-
const titleScore = scoreTitleMatch(normalizedTitle, normalizedHintTitle);
|
|
1089
|
-
if (titleScore > 0) {
|
|
1090
|
-
score += titleScore;
|
|
1091
|
-
reasons.push(titleScore >= 80 ? "title:exact" : "title:partial");
|
|
1092
|
-
}
|
|
1093
|
-
const recencyScore = scoreRecency(candidate.lastSeenAt, now);
|
|
1094
|
-
if (recencyScore > 0) {
|
|
1095
|
-
score += recencyScore;
|
|
1096
|
-
reasons.push("recency");
|
|
1097
|
-
}
|
|
1098
|
-
const hintRecencyScore = scoreHintRecency(
|
|
1099
|
-
candidate.lastSeenAt,
|
|
1100
|
-
Number.isFinite(hintLastActiveAt) ? hintLastActiveAt : void 0
|
|
1101
|
-
);
|
|
1102
|
-
if (hintRecencyScore > 0) {
|
|
1103
|
-
score += hintRecencyScore;
|
|
1104
|
-
reasons.push("hint-recency");
|
|
1105
|
-
}
|
|
1106
|
-
if (isBlankUrl(normalizedUrl)) {
|
|
1107
|
-
score -= 50;
|
|
1108
|
-
reasons.push("blank-url");
|
|
1109
|
-
}
|
|
1110
|
-
return { candidate, score, reasons };
|
|
1111
|
-
});
|
|
1112
|
-
return ranked.sort((a, b) => {
|
|
1113
|
-
if (a.score !== b.score) {
|
|
1114
|
-
return b.score - a.score;
|
|
1115
|
-
}
|
|
1116
|
-
const aSeen = a.candidate.lastSeenAt ?? 0;
|
|
1117
|
-
const bSeen = b.candidate.lastSeenAt ?? 0;
|
|
1118
|
-
if (aSeen !== bSeen) {
|
|
1119
|
-
return bSeen - aSeen;
|
|
1120
|
-
}
|
|
1121
|
-
return a.candidate.url.localeCompare(b.candidate.url);
|
|
1122
|
-
});
|
|
1123
|
-
};
|
|
1124
|
-
var pickBestTarget = (candidates, hint, now = Date.now()) => {
|
|
1125
|
-
const ranked = rankTargetCandidates(candidates, hint, now);
|
|
1126
|
-
return ranked.length > 0 ? ranked[0] : null;
|
|
1127
|
-
};
|
|
1128
|
-
|
|
1129
|
-
// packages/core/src/inspect.ts
|
|
1130
|
-
var InspectError = class extends Error {
|
|
1131
|
-
constructor(code, message, options = {}) {
|
|
1132
|
-
super(message);
|
|
1133
|
-
this.name = "InspectError";
|
|
1134
|
-
this.code = code;
|
|
1135
|
-
this.retryable = options.retryable ?? false;
|
|
1136
|
-
this.details = options.details;
|
|
1137
|
-
}
|
|
1138
|
-
};
|
|
1139
|
-
var DEFAULT_MAX_SNAPSHOTS_PER_SESSION = 20;
|
|
1140
|
-
var DEFAULT_MAX_SNAPSHOT_HISTORY = 100;
|
|
1141
|
-
var SNAPSHOT_REF_ATTRIBUTE = "data-bv-ref";
|
|
1142
|
-
var MAX_REF_ASSIGNMENTS = 500;
|
|
1143
|
-
var MAX_REF_WARNINGS = 5;
|
|
959
|
+
// packages/core/src/inspect/ax-snapshot.ts
|
|
1144
960
|
var INTERACTIVE_AX_ROLES = /* @__PURE__ */ new Set([
|
|
1145
961
|
"button",
|
|
1146
962
|
"link",
|
|
@@ -1167,24 +983,1080 @@ var LABEL_AX_ROLES = /* @__PURE__ */ new Set([
|
|
|
1167
983
|
"spinbutton",
|
|
1168
984
|
"slider"
|
|
1169
985
|
]);
|
|
1170
|
-
var
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
this.maxSnapshotHistory = options.maxSnapshotHistory ?? DEFAULT_MAX_SNAPSHOT_HISTORY;
|
|
986
|
+
var getAxNodes = (snapshot) => {
|
|
987
|
+
const nodes = Array.isArray(snapshot) ? snapshot : snapshot?.nodes;
|
|
988
|
+
return Array.isArray(nodes) ? nodes : [];
|
|
989
|
+
};
|
|
990
|
+
var replaceAxNodes = (snapshot, nodes) => {
|
|
991
|
+
if (Array.isArray(snapshot)) {
|
|
992
|
+
return nodes;
|
|
1178
993
|
}
|
|
1179
|
-
|
|
1180
|
-
|
|
994
|
+
if (snapshot && typeof snapshot === "object") {
|
|
995
|
+
snapshot.nodes = nodes;
|
|
1181
996
|
}
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
997
|
+
return snapshot;
|
|
998
|
+
};
|
|
999
|
+
var getAxRole = (node) => {
|
|
1000
|
+
const role = typeof node.role === "string" ? node.role : node.role?.value ?? "";
|
|
1001
|
+
return typeof role === "string" ? role.toLowerCase() : "";
|
|
1002
|
+
};
|
|
1003
|
+
var getAxName = (node) => {
|
|
1004
|
+
const name = typeof node.name === "string" ? node.name : node.name?.value ?? "";
|
|
1005
|
+
return typeof name === "string" ? name : "";
|
|
1006
|
+
};
|
|
1007
|
+
var hasAxValue = (node) => {
|
|
1008
|
+
if (!Array.isArray(node.properties)) {
|
|
1009
|
+
return false;
|
|
1010
|
+
}
|
|
1011
|
+
for (const prop of node.properties) {
|
|
1012
|
+
if (!prop || typeof prop !== "object") {
|
|
1013
|
+
continue;
|
|
1014
|
+
}
|
|
1015
|
+
const value = prop.value?.value;
|
|
1016
|
+
if (value === void 0 || value === null) {
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
if (typeof value === "string" && value.trim().length === 0) {
|
|
1020
|
+
continue;
|
|
1021
|
+
}
|
|
1022
|
+
return true;
|
|
1023
|
+
}
|
|
1024
|
+
return false;
|
|
1025
|
+
};
|
|
1026
|
+
var normalizeQuery = (value) => value.trim().toLowerCase();
|
|
1027
|
+
var matchesTextValue = (value, query) => {
|
|
1028
|
+
if (!query) {
|
|
1029
|
+
return false;
|
|
1030
|
+
}
|
|
1031
|
+
return value.toLowerCase().includes(query);
|
|
1032
|
+
};
|
|
1033
|
+
var matchesAxText = (node, query) => {
|
|
1034
|
+
if (!query) {
|
|
1035
|
+
return false;
|
|
1036
|
+
}
|
|
1037
|
+
const candidates = [getAxName(node)];
|
|
1038
|
+
if (Array.isArray(node.properties)) {
|
|
1039
|
+
for (const prop of node.properties) {
|
|
1040
|
+
if (!prop || typeof prop !== "object") {
|
|
1041
|
+
continue;
|
|
1042
|
+
}
|
|
1043
|
+
const value = prop.value?.value;
|
|
1044
|
+
if (value === void 0 || value === null) {
|
|
1045
|
+
continue;
|
|
1046
|
+
}
|
|
1047
|
+
if (typeof value === "string") {
|
|
1048
|
+
candidates.push(value);
|
|
1049
|
+
} else if (typeof value === "number" || typeof value === "boolean") {
|
|
1050
|
+
candidates.push(String(value));
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
return candidates.some((text) => matchesTextValue(text, query));
|
|
1055
|
+
};
|
|
1056
|
+
var isInteractiveAxNode = (node) => {
|
|
1057
|
+
const role = getAxRole(node);
|
|
1058
|
+
return Boolean(role && INTERACTIVE_AX_ROLES.has(role));
|
|
1059
|
+
};
|
|
1060
|
+
var filterAxSnapshot = (snapshot, predicate) => {
|
|
1061
|
+
const nodes = getAxNodes(snapshot);
|
|
1062
|
+
if (nodes.length === 0) {
|
|
1063
|
+
return snapshot;
|
|
1064
|
+
}
|
|
1065
|
+
const keepIds = /* @__PURE__ */ new Set();
|
|
1066
|
+
const filtered = nodes.filter((node) => {
|
|
1067
|
+
if (!node || typeof node !== "object") {
|
|
1068
|
+
return false;
|
|
1069
|
+
}
|
|
1070
|
+
const keep = predicate(node);
|
|
1071
|
+
if (keep && typeof node.nodeId === "string") {
|
|
1072
|
+
keepIds.add(node.nodeId);
|
|
1073
|
+
}
|
|
1074
|
+
return keep;
|
|
1075
|
+
});
|
|
1076
|
+
for (const node of filtered) {
|
|
1077
|
+
if (Array.isArray(node.childIds)) {
|
|
1078
|
+
node.childIds = node.childIds.filter((id) => keepIds.has(id));
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
return replaceAxNodes(snapshot, filtered);
|
|
1082
|
+
};
|
|
1083
|
+
var collectKeptDescendants = (nodeId, nodeById, keepIds, visited = /* @__PURE__ */ new Set()) => {
|
|
1084
|
+
if (visited.has(nodeId)) {
|
|
1085
|
+
return [];
|
|
1086
|
+
}
|
|
1087
|
+
visited.add(nodeId);
|
|
1088
|
+
if (keepIds.has(nodeId)) {
|
|
1089
|
+
return [nodeId];
|
|
1090
|
+
}
|
|
1091
|
+
const node = nodeById.get(nodeId);
|
|
1092
|
+
if (!node || !Array.isArray(node.childIds)) {
|
|
1093
|
+
return [];
|
|
1094
|
+
}
|
|
1095
|
+
const output = [];
|
|
1096
|
+
for (const childId of node.childIds) {
|
|
1097
|
+
output.push(...collectKeptDescendants(childId, nodeById, keepIds, visited));
|
|
1098
|
+
}
|
|
1099
|
+
return output;
|
|
1100
|
+
};
|
|
1101
|
+
var shouldKeepCompactNode = (node) => {
|
|
1102
|
+
if (node.ignored) {
|
|
1103
|
+
return false;
|
|
1104
|
+
}
|
|
1105
|
+
const role = getAxRole(node);
|
|
1106
|
+
if (role && INTERACTIVE_AX_ROLES.has(role)) {
|
|
1107
|
+
return true;
|
|
1108
|
+
}
|
|
1109
|
+
const name = getAxName(node);
|
|
1110
|
+
const hasName = name.trim().length > 0;
|
|
1111
|
+
const hasValue = hasAxValue(node);
|
|
1112
|
+
if (hasName || hasValue) {
|
|
1113
|
+
return true;
|
|
1114
|
+
}
|
|
1115
|
+
const hasChildren = Array.isArray(node.childIds) && node.childIds.length > 0;
|
|
1116
|
+
if (!role || DECORATIVE_AX_ROLES.has(role)) {
|
|
1117
|
+
return false;
|
|
1118
|
+
}
|
|
1119
|
+
return hasChildren;
|
|
1120
|
+
};
|
|
1121
|
+
var compactAxSnapshot = (snapshot) => {
|
|
1122
|
+
const nodes = getAxNodes(snapshot);
|
|
1123
|
+
if (nodes.length === 0) {
|
|
1124
|
+
return snapshot;
|
|
1125
|
+
}
|
|
1126
|
+
const nodeById = /* @__PURE__ */ new Map();
|
|
1127
|
+
nodes.forEach((node) => {
|
|
1128
|
+
if (node && typeof node.nodeId === "string") {
|
|
1129
|
+
nodeById.set(node.nodeId, node);
|
|
1130
|
+
}
|
|
1131
|
+
});
|
|
1132
|
+
const keepIds = /* @__PURE__ */ new Set();
|
|
1133
|
+
for (const node of nodes) {
|
|
1134
|
+
if (!node || typeof node !== "object" || typeof node.nodeId !== "string") {
|
|
1135
|
+
continue;
|
|
1136
|
+
}
|
|
1137
|
+
if (shouldKeepCompactNode(node)) {
|
|
1138
|
+
keepIds.add(node.nodeId);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
const filtered = nodes.filter(
|
|
1142
|
+
(node) => node && typeof node.nodeId === "string" && keepIds.has(node.nodeId)
|
|
1143
|
+
);
|
|
1144
|
+
for (const node of filtered) {
|
|
1145
|
+
if (!Array.isArray(node.childIds) || typeof node.nodeId !== "string") {
|
|
1146
|
+
continue;
|
|
1147
|
+
}
|
|
1148
|
+
const nextChildIds = [];
|
|
1149
|
+
for (const childId of node.childIds) {
|
|
1150
|
+
nextChildIds.push(...collectKeptDescendants(childId, nodeById, keepIds));
|
|
1151
|
+
}
|
|
1152
|
+
node.childIds = Array.from(new Set(nextChildIds));
|
|
1153
|
+
}
|
|
1154
|
+
return replaceAxNodes(snapshot, filtered);
|
|
1155
|
+
};
|
|
1156
|
+
var applyAxSnapshotFilters = (snapshot, options) => {
|
|
1157
|
+
let filtered = snapshot;
|
|
1158
|
+
if (options.compact) {
|
|
1159
|
+
filtered = compactAxSnapshot(filtered);
|
|
1160
|
+
}
|
|
1161
|
+
if (options.interactiveOnly) {
|
|
1162
|
+
filtered = filterAxSnapshot(filtered, (node) => isInteractiveAxNode(node));
|
|
1163
|
+
}
|
|
1164
|
+
return filtered;
|
|
1165
|
+
};
|
|
1166
|
+
var truncateAxSnapshot = (snapshot, maxNodes) => {
|
|
1167
|
+
const nodes = getAxNodes(snapshot);
|
|
1168
|
+
if (!Number.isFinite(maxNodes) || maxNodes <= 0) {
|
|
1169
|
+
return { snapshot, truncated: false };
|
|
1170
|
+
}
|
|
1171
|
+
if (nodes.length === 0 || nodes.length <= maxNodes) {
|
|
1172
|
+
return { snapshot, truncated: false };
|
|
1173
|
+
}
|
|
1174
|
+
const nodeById = /* @__PURE__ */ new Map();
|
|
1175
|
+
const parentCount = /* @__PURE__ */ new Map();
|
|
1176
|
+
for (const node of nodes) {
|
|
1177
|
+
if (!node || typeof node !== "object" || typeof node.nodeId !== "string") {
|
|
1178
|
+
continue;
|
|
1179
|
+
}
|
|
1180
|
+
nodeById.set(node.nodeId, node);
|
|
1181
|
+
parentCount.set(node.nodeId, parentCount.get(node.nodeId) ?? 0);
|
|
1182
|
+
}
|
|
1183
|
+
if (nodeById.size === 0) {
|
|
1184
|
+
const sliced = nodes.slice(0, maxNodes);
|
|
1185
|
+
for (const node of sliced) {
|
|
1186
|
+
if (node && typeof node === "object" && Array.isArray(node.childIds)) {
|
|
1187
|
+
node.childIds = [];
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
return {
|
|
1191
|
+
snapshot: replaceAxNodes(snapshot, sliced),
|
|
1192
|
+
truncated: true
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
for (const node of nodes) {
|
|
1196
|
+
if (!node || typeof node !== "object" || !Array.isArray(node.childIds)) {
|
|
1197
|
+
continue;
|
|
1198
|
+
}
|
|
1199
|
+
for (const childId of node.childIds) {
|
|
1200
|
+
if (typeof childId !== "string") {
|
|
1201
|
+
continue;
|
|
1202
|
+
}
|
|
1203
|
+
parentCount.set(childId, (parentCount.get(childId) ?? 0) + 1);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
let roots = Array.from(nodeById.keys()).filter(
|
|
1207
|
+
(id) => (parentCount.get(id) ?? 0) === 0
|
|
1208
|
+
);
|
|
1209
|
+
if (roots.length === 0) {
|
|
1210
|
+
const first = nodes.find(
|
|
1211
|
+
(node) => node && typeof node.nodeId === "string"
|
|
1212
|
+
)?.nodeId;
|
|
1213
|
+
if (first) {
|
|
1214
|
+
roots = [first];
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
const kept = /* @__PURE__ */ new Set();
|
|
1218
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1219
|
+
const queue = [...roots];
|
|
1220
|
+
while (queue.length > 0 && kept.size < maxNodes) {
|
|
1221
|
+
const id = queue.shift();
|
|
1222
|
+
if (!id || visited.has(id)) {
|
|
1223
|
+
continue;
|
|
1224
|
+
}
|
|
1225
|
+
visited.add(id);
|
|
1226
|
+
const node = nodeById.get(id);
|
|
1227
|
+
if (!node) {
|
|
1228
|
+
continue;
|
|
1229
|
+
}
|
|
1230
|
+
kept.add(id);
|
|
1231
|
+
if (Array.isArray(node.childIds)) {
|
|
1232
|
+
for (const childId of node.childIds) {
|
|
1233
|
+
if (typeof childId === "string" && !visited.has(childId)) {
|
|
1234
|
+
queue.push(childId);
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
if (kept.size === 0) {
|
|
1240
|
+
const fallback = [];
|
|
1241
|
+
for (const node of nodes) {
|
|
1242
|
+
if (fallback.length >= maxNodes) {
|
|
1243
|
+
break;
|
|
1244
|
+
}
|
|
1245
|
+
if (node && typeof node.nodeId === "string") {
|
|
1246
|
+
fallback.push(node.nodeId);
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
fallback.forEach((id) => kept.add(id));
|
|
1250
|
+
}
|
|
1251
|
+
const filtered = nodes.filter(
|
|
1252
|
+
(node) => node && typeof node.nodeId === "string" && kept.has(node.nodeId)
|
|
1253
|
+
);
|
|
1254
|
+
for (const node of filtered) {
|
|
1255
|
+
if (Array.isArray(node.childIds)) {
|
|
1256
|
+
node.childIds = node.childIds.filter(
|
|
1257
|
+
(id) => typeof id === "string" && kept.has(id)
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
return {
|
|
1262
|
+
snapshot: replaceAxNodes(snapshot, filtered),
|
|
1263
|
+
truncated: true
|
|
1264
|
+
};
|
|
1265
|
+
};
|
|
1266
|
+
|
|
1267
|
+
// packages/core/src/inspect/console.ts
|
|
1268
|
+
var toSourceLocation = (input) => {
|
|
1269
|
+
const url = typeof input.url === "string" && input.url.length > 0 ? input.url : void 0;
|
|
1270
|
+
const line = typeof input.lineNumber === "number" && Number.isFinite(input.lineNumber) ? Math.max(1, Math.floor(input.lineNumber) + 1) : void 0;
|
|
1271
|
+
const column = typeof input.columnNumber === "number" && Number.isFinite(input.columnNumber) ? Math.max(1, Math.floor(input.columnNumber) + 1) : void 0;
|
|
1272
|
+
if (!url && !line && !column) {
|
|
1273
|
+
return void 0;
|
|
1274
|
+
}
|
|
1275
|
+
return {
|
|
1276
|
+
...url ? { url } : {},
|
|
1277
|
+
...line ? { line } : {},
|
|
1278
|
+
...column ? { column } : {}
|
|
1279
|
+
};
|
|
1280
|
+
};
|
|
1281
|
+
var toStackFrames = (stackTrace) => {
|
|
1282
|
+
const frames = [];
|
|
1283
|
+
const collect = (trace) => {
|
|
1284
|
+
if (!trace || typeof trace !== "object") {
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
const callFrames = trace.callFrames;
|
|
1288
|
+
if (Array.isArray(callFrames)) {
|
|
1289
|
+
for (const frame of callFrames) {
|
|
1290
|
+
if (!frame || typeof frame !== "object") {
|
|
1291
|
+
continue;
|
|
1292
|
+
}
|
|
1293
|
+
const functionName = typeof frame.functionName === "string" ? String(frame.functionName) : void 0;
|
|
1294
|
+
const url = typeof frame.url === "string" ? String(frame.url) : void 0;
|
|
1295
|
+
const lineNumber = frame.lineNumber;
|
|
1296
|
+
const columnNumber = frame.columnNumber;
|
|
1297
|
+
const loc = toSourceLocation({ url, lineNumber, columnNumber });
|
|
1298
|
+
frames.push({
|
|
1299
|
+
...functionName ? { functionName } : {},
|
|
1300
|
+
...loc?.url ? { url: loc.url } : {},
|
|
1301
|
+
...loc?.line ? { line: loc.line } : {},
|
|
1302
|
+
...loc?.column ? { column: loc.column } : {}
|
|
1303
|
+
});
|
|
1304
|
+
if (frames.length >= 50) {
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
const parent = trace.parent;
|
|
1310
|
+
if (frames.length < 50 && parent) {
|
|
1311
|
+
collect(parent);
|
|
1312
|
+
}
|
|
1313
|
+
};
|
|
1314
|
+
collect(stackTrace);
|
|
1315
|
+
return frames.length > 0 ? frames : void 0;
|
|
1316
|
+
};
|
|
1317
|
+
var toRemoteObjectSummary = (obj) => {
|
|
1318
|
+
if (!obj || typeof obj !== "object") {
|
|
1319
|
+
return void 0;
|
|
1320
|
+
}
|
|
1321
|
+
const raw = obj;
|
|
1322
|
+
const type = typeof raw.type === "string" ? raw.type : void 0;
|
|
1323
|
+
const subtype = typeof raw.subtype === "string" ? raw.subtype : void 0;
|
|
1324
|
+
const description = typeof raw.description === "string" ? raw.description : void 0;
|
|
1325
|
+
const unserializableValue = typeof raw.unserializableValue === "string" ? raw.unserializableValue : void 0;
|
|
1326
|
+
const out = {};
|
|
1327
|
+
if (type) out.type = type;
|
|
1328
|
+
if (subtype) out.subtype = subtype;
|
|
1329
|
+
if (description) out.description = description;
|
|
1330
|
+
if (raw.value !== void 0) out.value = raw.value;
|
|
1331
|
+
if (unserializableValue) out.unserializableValue = unserializableValue;
|
|
1332
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
1333
|
+
};
|
|
1334
|
+
var stringifyRemoteObject = (value) => {
|
|
1335
|
+
if (!value || typeof value !== "object") {
|
|
1336
|
+
return String(value ?? "");
|
|
1337
|
+
}
|
|
1338
|
+
const obj = value;
|
|
1339
|
+
if (obj.unserializableValue) {
|
|
1340
|
+
return obj.unserializableValue;
|
|
1341
|
+
}
|
|
1342
|
+
if (obj.value !== void 0) {
|
|
1343
|
+
try {
|
|
1344
|
+
return typeof obj.value === "string" ? obj.value : JSON.stringify(obj.value);
|
|
1345
|
+
} catch {
|
|
1346
|
+
return String(obj.value);
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
if (obj.description) {
|
|
1350
|
+
return obj.description;
|
|
1351
|
+
}
|
|
1352
|
+
return obj.type ?? "";
|
|
1353
|
+
};
|
|
1354
|
+
var toConsoleEntry = (event) => {
|
|
1355
|
+
const params = event.params ?? {};
|
|
1356
|
+
switch (event.method) {
|
|
1357
|
+
case "Runtime.consoleAPICalled": {
|
|
1358
|
+
const rawArgs = Array.isArray(params.args) ? params.args : [];
|
|
1359
|
+
const text = rawArgs.map((arg) => stringifyRemoteObject(arg)).join(" ");
|
|
1360
|
+
const level = String(params.type ?? "log");
|
|
1361
|
+
const stack = toStackFrames(
|
|
1362
|
+
params.stackTrace
|
|
1363
|
+
);
|
|
1364
|
+
const args = rawArgs.map((arg) => toRemoteObjectSummary(arg)).filter((entry) => Boolean(entry));
|
|
1365
|
+
return {
|
|
1366
|
+
level,
|
|
1367
|
+
text,
|
|
1368
|
+
timestamp: event.timestamp,
|
|
1369
|
+
...stack && stack.length > 0 ? { stack } : {},
|
|
1370
|
+
...args.length > 0 ? { args } : {}
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
case "Runtime.exceptionThrown": {
|
|
1374
|
+
const details = params.exceptionDetails;
|
|
1375
|
+
const exception = toRemoteObjectSummary(details?.exception);
|
|
1376
|
+
const stack = toStackFrames(details?.stackTrace);
|
|
1377
|
+
const source = toSourceLocation({
|
|
1378
|
+
url: details?.url,
|
|
1379
|
+
lineNumber: details?.lineNumber,
|
|
1380
|
+
columnNumber: details?.columnNumber
|
|
1381
|
+
}) ?? // If the top frame exists, treat it as the source.
|
|
1382
|
+
(stack && stack.length > 0 ? {
|
|
1383
|
+
url: stack[0].url,
|
|
1384
|
+
line: stack[0].line,
|
|
1385
|
+
column: stack[0].column
|
|
1386
|
+
} : void 0);
|
|
1387
|
+
const baseText = typeof details?.text === "string" && details.text.trim().length > 0 ? details.text : "Uncaught exception";
|
|
1388
|
+
const exceptionDesc = typeof exception?.description === "string" && exception.description.trim().length > 0 ? exception.description : void 0;
|
|
1389
|
+
const text = baseText === "Uncaught" && exceptionDesc ? `Uncaught: ${exceptionDesc}` : baseText;
|
|
1390
|
+
return {
|
|
1391
|
+
level: "error",
|
|
1392
|
+
text,
|
|
1393
|
+
timestamp: event.timestamp,
|
|
1394
|
+
...source ? { source } : {},
|
|
1395
|
+
...stack && stack.length > 0 ? { stack } : {},
|
|
1396
|
+
...exception ? { exception } : {}
|
|
1397
|
+
};
|
|
1398
|
+
}
|
|
1399
|
+
case "Log.entryAdded": {
|
|
1400
|
+
const entry = params.entry;
|
|
1401
|
+
if (!entry) {
|
|
1402
|
+
return null;
|
|
1403
|
+
}
|
|
1404
|
+
const stack = toStackFrames(entry.stackTrace);
|
|
1405
|
+
const source = toSourceLocation({
|
|
1406
|
+
url: entry.url,
|
|
1407
|
+
lineNumber: entry.lineNumber,
|
|
1408
|
+
columnNumber: void 0
|
|
1409
|
+
});
|
|
1410
|
+
return {
|
|
1411
|
+
level: entry.level ?? "log",
|
|
1412
|
+
text: entry.text ?? "",
|
|
1413
|
+
timestamp: event.timestamp,
|
|
1414
|
+
...source ? { source } : {},
|
|
1415
|
+
...stack && stack.length > 0 ? { stack } : {}
|
|
1416
|
+
};
|
|
1417
|
+
}
|
|
1418
|
+
default:
|
|
1419
|
+
return null;
|
|
1420
|
+
}
|
|
1421
|
+
};
|
|
1422
|
+
|
|
1423
|
+
// packages/core/src/inspect/har.ts
|
|
1424
|
+
var buildHar = (events, title) => {
|
|
1425
|
+
const requests = /* @__PURE__ */ new Map();
|
|
1426
|
+
const toTimestamp = (event, fallback) => {
|
|
1427
|
+
const raw = event.params?.wallTime;
|
|
1428
|
+
if (typeof raw === "number") {
|
|
1429
|
+
return raw * 1e3;
|
|
1430
|
+
}
|
|
1431
|
+
const ts = event.params?.timestamp;
|
|
1432
|
+
if (typeof ts === "number") {
|
|
1433
|
+
return ts * 1e3;
|
|
1434
|
+
}
|
|
1435
|
+
const parsed = Date.parse(event.timestamp);
|
|
1436
|
+
if (Number.isFinite(parsed)) {
|
|
1437
|
+
return parsed;
|
|
1438
|
+
}
|
|
1439
|
+
return fallback ?? Date.now();
|
|
1440
|
+
};
|
|
1441
|
+
for (const event of events) {
|
|
1442
|
+
const params = event.params ?? {};
|
|
1443
|
+
switch (event.method) {
|
|
1444
|
+
case "Network.requestWillBeSent": {
|
|
1445
|
+
const requestId = String(params.requestId);
|
|
1446
|
+
if (!requestId) {
|
|
1447
|
+
break;
|
|
1448
|
+
}
|
|
1449
|
+
const request = params.request;
|
|
1450
|
+
const record = {
|
|
1451
|
+
id: requestId,
|
|
1452
|
+
url: request?.url,
|
|
1453
|
+
method: request?.method,
|
|
1454
|
+
requestHeaders: request?.headers ?? {},
|
|
1455
|
+
startTime: toTimestamp(event)
|
|
1456
|
+
};
|
|
1457
|
+
requests.set(requestId, record);
|
|
1458
|
+
break;
|
|
1459
|
+
}
|
|
1460
|
+
case "Network.responseReceived": {
|
|
1461
|
+
const requestId = String(params.requestId);
|
|
1462
|
+
if (!requestId) {
|
|
1463
|
+
break;
|
|
1464
|
+
}
|
|
1465
|
+
const response = params.response;
|
|
1466
|
+
const record = requests.get(requestId) ?? { id: requestId };
|
|
1467
|
+
record.status = response?.status;
|
|
1468
|
+
record.statusText = response?.statusText;
|
|
1469
|
+
record.mimeType = response?.mimeType;
|
|
1470
|
+
record.responseHeaders = response?.headers ?? {};
|
|
1471
|
+
record.protocol = response?.protocol;
|
|
1472
|
+
record.startTime = record.startTime ?? toTimestamp(event);
|
|
1473
|
+
requests.set(requestId, record);
|
|
1474
|
+
break;
|
|
1475
|
+
}
|
|
1476
|
+
case "Network.loadingFinished": {
|
|
1477
|
+
const requestId = String(params.requestId);
|
|
1478
|
+
if (!requestId) {
|
|
1479
|
+
break;
|
|
1480
|
+
}
|
|
1481
|
+
const record = requests.get(requestId) ?? { id: requestId };
|
|
1482
|
+
record.encodedDataLength = params.encodedDataLength;
|
|
1483
|
+
record.endTime = toTimestamp(event, record.startTime);
|
|
1484
|
+
requests.set(requestId, record);
|
|
1485
|
+
break;
|
|
1486
|
+
}
|
|
1487
|
+
case "Network.loadingFailed": {
|
|
1488
|
+
const requestId = String(params.requestId);
|
|
1489
|
+
if (!requestId) {
|
|
1490
|
+
break;
|
|
1491
|
+
}
|
|
1492
|
+
const record = requests.get(requestId) ?? { id: requestId };
|
|
1493
|
+
record.endTime = toTimestamp(event, record.startTime);
|
|
1494
|
+
requests.set(requestId, record);
|
|
1495
|
+
break;
|
|
1496
|
+
}
|
|
1497
|
+
default:
|
|
1498
|
+
break;
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
const entries = Array.from(requests.values()).map((record) => {
|
|
1502
|
+
const started = record.startTime ?? Date.now();
|
|
1503
|
+
const ended = record.endTime ?? started;
|
|
1504
|
+
const time = Math.max(0, ended - started);
|
|
1505
|
+
const url = record.url ?? "";
|
|
1506
|
+
const queryString = [];
|
|
1507
|
+
try {
|
|
1508
|
+
const parsed = new URL(url);
|
|
1509
|
+
parsed.searchParams.forEach((value, name) => {
|
|
1510
|
+
queryString.push({ name, value });
|
|
1511
|
+
});
|
|
1512
|
+
} catch {
|
|
1513
|
+
}
|
|
1514
|
+
return {
|
|
1515
|
+
pageref: "page_0",
|
|
1516
|
+
startedDateTime: new Date(started).toISOString(),
|
|
1517
|
+
time,
|
|
1518
|
+
request: {
|
|
1519
|
+
method: record.method ?? "GET",
|
|
1520
|
+
url,
|
|
1521
|
+
httpVersion: record.protocol ?? "HTTP/1.1",
|
|
1522
|
+
cookies: [],
|
|
1523
|
+
headers: [],
|
|
1524
|
+
queryString,
|
|
1525
|
+
headersSize: -1,
|
|
1526
|
+
bodySize: -1
|
|
1527
|
+
},
|
|
1528
|
+
response: {
|
|
1529
|
+
status: record.status ?? 0,
|
|
1530
|
+
statusText: record.statusText ?? "",
|
|
1531
|
+
httpVersion: record.protocol ?? "HTTP/1.1",
|
|
1532
|
+
cookies: [],
|
|
1533
|
+
headers: [],
|
|
1534
|
+
redirectURL: "",
|
|
1535
|
+
headersSize: -1,
|
|
1536
|
+
bodySize: record.encodedDataLength ?? 0,
|
|
1537
|
+
content: {
|
|
1538
|
+
size: record.encodedDataLength ?? 0,
|
|
1539
|
+
mimeType: record.mimeType ?? ""
|
|
1540
|
+
}
|
|
1541
|
+
},
|
|
1542
|
+
cache: {},
|
|
1543
|
+
timings: {
|
|
1544
|
+
send: 0,
|
|
1545
|
+
wait: time,
|
|
1546
|
+
receive: 0
|
|
1547
|
+
}
|
|
1548
|
+
};
|
|
1549
|
+
});
|
|
1550
|
+
const startedDateTime = entries.length ? entries[0].startedDateTime ?? (/* @__PURE__ */ new Date()).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
|
|
1551
|
+
return {
|
|
1552
|
+
log: {
|
|
1553
|
+
version: "1.2",
|
|
1554
|
+
creator: {
|
|
1555
|
+
name: "browser-bridge",
|
|
1556
|
+
version: "0.0.0"
|
|
1557
|
+
},
|
|
1558
|
+
pages: [
|
|
1559
|
+
{
|
|
1560
|
+
id: "page_0",
|
|
1561
|
+
title: title ?? "page",
|
|
1562
|
+
startedDateTime,
|
|
1563
|
+
pageTimings: {
|
|
1564
|
+
onContentLoad: -1,
|
|
1565
|
+
onLoad: -1
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
],
|
|
1569
|
+
entries
|
|
1570
|
+
}
|
|
1571
|
+
};
|
|
1572
|
+
};
|
|
1573
|
+
|
|
1574
|
+
// packages/core/src/inspect/html-snapshot.ts
|
|
1575
|
+
var captureHtml = async (tabId, options) => {
|
|
1576
|
+
await options.debuggerCommand(tabId, "Runtime.enable", {});
|
|
1577
|
+
const expression = options.selector ? `(() => { try { const el = document.querySelector(${JSON.stringify(
|
|
1578
|
+
options.selector
|
|
1579
|
+
)}); return el ? el.outerHTML : ""; } catch { return ""; } })()` : "document.documentElement ? document.documentElement.outerHTML : ''";
|
|
1580
|
+
const result = await options.debuggerCommand(tabId, "Runtime.evaluate", {
|
|
1581
|
+
expression,
|
|
1582
|
+
returnByValue: true,
|
|
1583
|
+
awaitPromise: true
|
|
1584
|
+
});
|
|
1585
|
+
if (result && typeof result === "object" && "exceptionDetails" in result) {
|
|
1586
|
+
return options.onEvaluationFailed();
|
|
1587
|
+
}
|
|
1588
|
+
return String(
|
|
1589
|
+
result?.result?.value ?? ""
|
|
1590
|
+
);
|
|
1591
|
+
};
|
|
1592
|
+
var collectHtmlEntries = (html) => {
|
|
1593
|
+
const entries = /* @__PURE__ */ new Map();
|
|
1594
|
+
const tagPattern = /<([a-zA-Z0-9-]+)([^>]*)>/g;
|
|
1595
|
+
let match;
|
|
1596
|
+
let index = 0;
|
|
1597
|
+
while ((match = tagPattern.exec(html)) && entries.size < 1e3) {
|
|
1598
|
+
const tag = match[1].toLowerCase();
|
|
1599
|
+
const attrs = match[2] ?? "";
|
|
1600
|
+
const idMatch = /\bid=["']([^"']+)["']/.exec(attrs);
|
|
1601
|
+
const classMatch = /\bclass=["']([^"']+)["']/.exec(attrs);
|
|
1602
|
+
const id = idMatch?.[1];
|
|
1603
|
+
const className = classMatch?.[1]?.split(/\s+/)[0];
|
|
1604
|
+
let key = tag;
|
|
1605
|
+
if (id) {
|
|
1606
|
+
key = `${tag}#${id}`;
|
|
1607
|
+
} else if (className) {
|
|
1608
|
+
key = `${tag}.${className}`;
|
|
1609
|
+
} else {
|
|
1610
|
+
key = `${tag}:nth-${index}`;
|
|
1611
|
+
}
|
|
1612
|
+
entries.set(key, attrs.trim());
|
|
1613
|
+
index += 1;
|
|
1614
|
+
}
|
|
1615
|
+
return entries;
|
|
1616
|
+
};
|
|
1617
|
+
|
|
1618
|
+
// packages/core/src/inspect/snapshot-history.ts
|
|
1619
|
+
var SnapshotHistory = class {
|
|
1620
|
+
constructor(options) {
|
|
1621
|
+
this.history = [];
|
|
1622
|
+
this.maxSnapshotsPerSession = Math.max(0, options.maxSnapshotsPerSession);
|
|
1623
|
+
this.maxSnapshotHistory = Math.max(0, options.maxSnapshotHistory);
|
|
1624
|
+
}
|
|
1625
|
+
record(sessionId, snapshot) {
|
|
1626
|
+
const entries = this.collectSnapshotEntries(snapshot);
|
|
1627
|
+
if (!entries) {
|
|
1628
|
+
return;
|
|
1629
|
+
}
|
|
1630
|
+
this.history.push({
|
|
1631
|
+
sessionId,
|
|
1632
|
+
format: snapshot.format,
|
|
1633
|
+
entries,
|
|
1634
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1635
|
+
});
|
|
1636
|
+
let count = 0;
|
|
1637
|
+
for (const record of this.history) {
|
|
1638
|
+
if (record.sessionId === sessionId) {
|
|
1639
|
+
count += 1;
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
while (count > this.maxSnapshotsPerSession) {
|
|
1643
|
+
const index = this.history.findIndex(
|
|
1644
|
+
(record) => record.sessionId === sessionId
|
|
1645
|
+
);
|
|
1646
|
+
if (index === -1) {
|
|
1647
|
+
break;
|
|
1648
|
+
}
|
|
1649
|
+
this.history.splice(index, 1);
|
|
1650
|
+
count -= 1;
|
|
1651
|
+
}
|
|
1652
|
+
while (this.history.length > this.maxSnapshotHistory) {
|
|
1653
|
+
this.history.shift();
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
diff(sessionId) {
|
|
1657
|
+
const snapshots = this.history.filter(
|
|
1658
|
+
(record) => record.sessionId === sessionId
|
|
1659
|
+
);
|
|
1660
|
+
if (snapshots.length < 2) {
|
|
1661
|
+
return {
|
|
1662
|
+
added: [],
|
|
1663
|
+
removed: [],
|
|
1664
|
+
changed: [],
|
|
1665
|
+
summary: "Not enough snapshots to diff."
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
const previous = snapshots[snapshots.length - 2];
|
|
1669
|
+
const current = snapshots[snapshots.length - 1];
|
|
1670
|
+
const added = [];
|
|
1671
|
+
const removed = [];
|
|
1672
|
+
const changed = [];
|
|
1673
|
+
for (const [key, value] of current.entries.entries()) {
|
|
1674
|
+
if (!previous.entries.has(key)) {
|
|
1675
|
+
added.push(key);
|
|
1676
|
+
} else if (previous.entries.get(key) !== value) {
|
|
1677
|
+
changed.push(key);
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
for (const key of previous.entries.keys()) {
|
|
1681
|
+
if (!current.entries.has(key)) {
|
|
1682
|
+
removed.push(key);
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
return {
|
|
1686
|
+
added,
|
|
1687
|
+
removed,
|
|
1688
|
+
changed,
|
|
1689
|
+
summary: `Added ${added.length}, removed ${removed.length}, changed ${changed.length}.`
|
|
1690
|
+
};
|
|
1691
|
+
}
|
|
1692
|
+
collectSnapshotEntries(snapshot) {
|
|
1693
|
+
if (snapshot.format === "html" && typeof snapshot.snapshot === "string") {
|
|
1694
|
+
return collectHtmlEntries(snapshot.snapshot);
|
|
1695
|
+
}
|
|
1696
|
+
if (snapshot.format === "ax") {
|
|
1697
|
+
return this.collectAxEntries(snapshot.snapshot);
|
|
1698
|
+
}
|
|
1699
|
+
return null;
|
|
1700
|
+
}
|
|
1701
|
+
collectAxEntries(snapshot) {
|
|
1702
|
+
const entries = /* @__PURE__ */ new Map();
|
|
1703
|
+
const nodes = getAxNodes(snapshot);
|
|
1704
|
+
if (nodes.length === 0) {
|
|
1705
|
+
return entries;
|
|
1706
|
+
}
|
|
1707
|
+
nodes.forEach((node, index) => {
|
|
1708
|
+
if (!node || typeof node !== "object") {
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
const record = node;
|
|
1712
|
+
const role = typeof record.role === "string" ? record.role : record.role?.value ?? "node";
|
|
1713
|
+
const name = typeof record.name === "string" ? record.name : record.name?.value ?? "";
|
|
1714
|
+
const nodeId = record.nodeId ?? (record.backendDOMNodeId !== void 0 ? String(record.backendDOMNodeId) : void 0);
|
|
1715
|
+
const key = nodeId ? `node-${nodeId}` : `${role}:${name}:${index}`;
|
|
1716
|
+
entries.set(key, `${role}:${name}`);
|
|
1717
|
+
});
|
|
1718
|
+
return entries;
|
|
1719
|
+
}
|
|
1720
|
+
};
|
|
1721
|
+
|
|
1722
|
+
// packages/core/src/inspect/snapshot-refs.ts
|
|
1723
|
+
var SNAPSHOT_REF_ATTRIBUTE = "data-bv-ref";
|
|
1724
|
+
var MAX_REF_ASSIGNMENTS = 500;
|
|
1725
|
+
var MAX_REF_WARNINGS = 5;
|
|
1726
|
+
var isInspectError = (error) => Boolean(
|
|
1727
|
+
error && typeof error === "object" && "name" in error && error.name === "InspectError" && "message" in error && typeof error.message === "string"
|
|
1728
|
+
);
|
|
1729
|
+
var assignRefsToAxSnapshot = (snapshot) => {
|
|
1730
|
+
const nodes = getAxNodes(snapshot);
|
|
1731
|
+
const refs = /* @__PURE__ */ new Map();
|
|
1732
|
+
let index = 1;
|
|
1733
|
+
for (const node of nodes) {
|
|
1734
|
+
if (!node || typeof node !== "object") {
|
|
1735
|
+
continue;
|
|
1736
|
+
}
|
|
1737
|
+
if (node.ignored) {
|
|
1738
|
+
continue;
|
|
1739
|
+
}
|
|
1740
|
+
const backendId = node.backendDOMNodeId;
|
|
1741
|
+
if (typeof backendId !== "number") {
|
|
1742
|
+
continue;
|
|
1743
|
+
}
|
|
1744
|
+
const ref = `@e${index}`;
|
|
1745
|
+
index += 1;
|
|
1746
|
+
node.ref = ref;
|
|
1747
|
+
refs.set(backendId, ref);
|
|
1748
|
+
}
|
|
1749
|
+
return refs;
|
|
1750
|
+
};
|
|
1751
|
+
var clearSnapshotRefs = async (tabId, debuggerCommand) => {
|
|
1752
|
+
await debuggerCommand(tabId, "Runtime.evaluate", {
|
|
1753
|
+
expression: `document.querySelectorAll('[${SNAPSHOT_REF_ATTRIBUTE}]').forEach((el) => el.removeAttribute('${SNAPSHOT_REF_ATTRIBUTE}'))`,
|
|
1754
|
+
returnByValue: true,
|
|
1755
|
+
awaitPromise: true
|
|
1756
|
+
});
|
|
1757
|
+
};
|
|
1758
|
+
var applySnapshotRefs = async (tabId, refs, debuggerCommand) => {
|
|
1759
|
+
const warnings = [];
|
|
1760
|
+
await debuggerCommand(tabId, "DOM.enable", {});
|
|
1761
|
+
await debuggerCommand(tabId, "Runtime.enable", {});
|
|
1762
|
+
try {
|
|
1763
|
+
await clearSnapshotRefs(tabId, debuggerCommand);
|
|
1764
|
+
} catch {
|
|
1765
|
+
warnings.push("Failed to clear prior snapshot refs.");
|
|
1766
|
+
}
|
|
1767
|
+
if (refs.size === 0) {
|
|
1768
|
+
return warnings;
|
|
1769
|
+
}
|
|
1770
|
+
let applied = 0;
|
|
1771
|
+
for (const [backendNodeId, ref] of refs) {
|
|
1772
|
+
if (applied >= MAX_REF_ASSIGNMENTS) {
|
|
1773
|
+
warnings.push(
|
|
1774
|
+
`Snapshot refs truncated at ${MAX_REF_ASSIGNMENTS} elements.`
|
|
1775
|
+
);
|
|
1776
|
+
break;
|
|
1777
|
+
}
|
|
1778
|
+
try {
|
|
1779
|
+
const described = await debuggerCommand(tabId, "DOM.describeNode", {
|
|
1780
|
+
backendNodeId
|
|
1781
|
+
});
|
|
1782
|
+
const node = described.node;
|
|
1783
|
+
if (!node || node.nodeType !== 1 || typeof node.nodeId !== "number") {
|
|
1784
|
+
if (warnings.length < MAX_REF_WARNINGS) {
|
|
1785
|
+
warnings.push(`Ref ${ref} could not be applied to a DOM element.`);
|
|
1786
|
+
}
|
|
1787
|
+
continue;
|
|
1788
|
+
}
|
|
1789
|
+
await debuggerCommand(tabId, "DOM.setAttributeValue", {
|
|
1790
|
+
nodeId: node.nodeId,
|
|
1791
|
+
name: SNAPSHOT_REF_ATTRIBUTE,
|
|
1792
|
+
value: ref
|
|
1793
|
+
});
|
|
1794
|
+
applied += 1;
|
|
1795
|
+
} catch {
|
|
1796
|
+
if (warnings.length < MAX_REF_WARNINGS) {
|
|
1797
|
+
warnings.push(`Ref ${ref} could not be applied.`);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
return warnings;
|
|
1802
|
+
};
|
|
1803
|
+
var resolveNodeIdForSelector = async (tabId, selector, debuggerCommand) => {
|
|
1804
|
+
await debuggerCommand(tabId, "DOM.enable", {});
|
|
1805
|
+
const document = await debuggerCommand(tabId, "DOM.getDocument", {
|
|
1806
|
+
depth: 1
|
|
1807
|
+
});
|
|
1808
|
+
const rootNodeId = document.root?.nodeId;
|
|
1809
|
+
if (typeof rootNodeId !== "number") {
|
|
1810
|
+
return { warnings: ["Failed to resolve DOM root for selector."] };
|
|
1811
|
+
}
|
|
1812
|
+
try {
|
|
1813
|
+
const result = await debuggerCommand(tabId, "DOM.querySelector", {
|
|
1814
|
+
nodeId: rootNodeId,
|
|
1815
|
+
selector
|
|
1816
|
+
});
|
|
1817
|
+
const nodeId = result.nodeId;
|
|
1818
|
+
if (!nodeId) {
|
|
1819
|
+
return { warnings: [`Selector not found: ${selector}`] };
|
|
1820
|
+
}
|
|
1821
|
+
return { nodeId };
|
|
1822
|
+
} catch (error) {
|
|
1823
|
+
if (isInspectError(error)) {
|
|
1824
|
+
return { warnings: [error.message] };
|
|
1825
|
+
}
|
|
1826
|
+
return { warnings: ["Selector query failed."] };
|
|
1827
|
+
}
|
|
1828
|
+
};
|
|
1829
|
+
|
|
1830
|
+
// packages/core/src/page-state-script.ts
|
|
1831
|
+
var PAGE_STATE_SCRIPT = [
|
|
1832
|
+
"(() => {",
|
|
1833
|
+
" const escape = (value) => {",
|
|
1834
|
+
" if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {",
|
|
1835
|
+
" return CSS.escape(value);",
|
|
1836
|
+
" }",
|
|
1837
|
+
` return String(value).replace(/["'\\\\]/g, '\\\\$&');`,
|
|
1838
|
+
" };",
|
|
1839
|
+
" const truncate = (value, max) => {",
|
|
1840
|
+
" const text = String(value ?? '');",
|
|
1841
|
+
" return text.length > max ? text.slice(0, max) : text;",
|
|
1842
|
+
" };",
|
|
1843
|
+
" const selectorFor = (element) => {",
|
|
1844
|
+
" if (element.id) {",
|
|
1845
|
+
" return `#${escape(element.id)}`;",
|
|
1846
|
+
" }",
|
|
1847
|
+
" const name = element.getAttribute('name');",
|
|
1848
|
+
" if (name) {",
|
|
1849
|
+
' return `${element.tagName.toLowerCase()}[name="${escape(name)}"]`;',
|
|
1850
|
+
" }",
|
|
1851
|
+
" const parts = [];",
|
|
1852
|
+
" let node = element;",
|
|
1853
|
+
" while (node && node.nodeType === 1 && parts.length < 4) {",
|
|
1854
|
+
" let part = node.tagName.toLowerCase();",
|
|
1855
|
+
" const parent = node.parentElement;",
|
|
1856
|
+
" if (parent) {",
|
|
1857
|
+
" const siblings = Array.from(parent.children).filter(",
|
|
1858
|
+
" (child) => child.tagName === node.tagName",
|
|
1859
|
+
" );",
|
|
1860
|
+
" if (siblings.length > 1) {",
|
|
1861
|
+
" part += `:nth-of-type(${siblings.indexOf(node) + 1})`;",
|
|
1862
|
+
" }",
|
|
1863
|
+
" }",
|
|
1864
|
+
" parts.unshift(part);",
|
|
1865
|
+
" node = parent;",
|
|
1866
|
+
" }",
|
|
1867
|
+
" return parts.join('>');",
|
|
1868
|
+
" };",
|
|
1869
|
+
" const readStorage = (storage, limit) => {",
|
|
1870
|
+
" try {",
|
|
1871
|
+
" return Object.keys(storage)",
|
|
1872
|
+
" .slice(0, limit)",
|
|
1873
|
+
" .map((key) => ({",
|
|
1874
|
+
" key,",
|
|
1875
|
+
" value: truncate(storage.getItem(key), 500),",
|
|
1876
|
+
" }));",
|
|
1877
|
+
" } catch {",
|
|
1878
|
+
" return [];",
|
|
1879
|
+
" }",
|
|
1880
|
+
" };",
|
|
1881
|
+
" const forms = Array.from(document.querySelectorAll('form')).map((form) => {",
|
|
1882
|
+
" const fields = Array.from(form.elements)",
|
|
1883
|
+
" .filter((element) => element && element.tagName)",
|
|
1884
|
+
" .map((element) => {",
|
|
1885
|
+
" const tag = element.tagName.toLowerCase();",
|
|
1886
|
+
" const type = 'type' in element && element.type ? element.type : tag;",
|
|
1887
|
+
" const name = element.name || element.getAttribute('name') || element.id || '';",
|
|
1888
|
+
" let value = '';",
|
|
1889
|
+
" let options;",
|
|
1890
|
+
" if (tag === 'select') {",
|
|
1891
|
+
" const select = element;",
|
|
1892
|
+
" value = select.value ?? '';",
|
|
1893
|
+
" options = Array.from(select.options).map((option) => option.text);",
|
|
1894
|
+
" } else if (tag === 'input' && element.type === 'password') {",
|
|
1895
|
+
" value = '[redacted]';",
|
|
1896
|
+
" } else if (tag === 'input' || tag === 'textarea') {",
|
|
1897
|
+
" value = element.value ?? '';",
|
|
1898
|
+
" } else if (element.isContentEditable) {",
|
|
1899
|
+
" value = element.textContent ?? '';",
|
|
1900
|
+
" } else if ('value' in element) {",
|
|
1901
|
+
" value = element.value ?? '';",
|
|
1902
|
+
" }",
|
|
1903
|
+
" return {",
|
|
1904
|
+
" name,",
|
|
1905
|
+
" type,",
|
|
1906
|
+
" value: truncate(value, 500),",
|
|
1907
|
+
" ...(options ? { options } : {}),",
|
|
1908
|
+
" };",
|
|
1909
|
+
" });",
|
|
1910
|
+
" return {",
|
|
1911
|
+
" selector: selectorFor(form),",
|
|
1912
|
+
" action: form.getAttribute('action') || undefined,",
|
|
1913
|
+
" method: form.getAttribute('method') || undefined,",
|
|
1914
|
+
" fields,",
|
|
1915
|
+
" };",
|
|
1916
|
+
" });",
|
|
1917
|
+
" const localStorage = readStorage(window.localStorage, 100);",
|
|
1918
|
+
" const sessionStorage = readStorage(window.sessionStorage, 100);",
|
|
1919
|
+
" const cookies = (document.cookie ? document.cookie.split(';') : [])",
|
|
1920
|
+
" .map((entry) => entry.trim())",
|
|
1921
|
+
" .filter((entry) => entry.length > 0)",
|
|
1922
|
+
" .slice(0, 50)",
|
|
1923
|
+
" .map((entry) => {",
|
|
1924
|
+
" const [key, ...rest] = entry.split('=');",
|
|
1925
|
+
" return { key, value: truncate(rest.join('='), 500) };",
|
|
1926
|
+
" });",
|
|
1927
|
+
" return { forms, localStorage, sessionStorage, cookies };",
|
|
1928
|
+
"})()"
|
|
1929
|
+
].join("\n");
|
|
1930
|
+
|
|
1931
|
+
// packages/core/src/target-matching.ts
|
|
1932
|
+
var normalizeText = (value) => (value ?? "").trim().toLowerCase();
|
|
1933
|
+
var normalizeUrl = (value) => {
|
|
1934
|
+
const normalized = normalizeText(value);
|
|
1935
|
+
if (!normalized) {
|
|
1936
|
+
return "";
|
|
1937
|
+
}
|
|
1938
|
+
const hashIndex = normalized.indexOf("#");
|
|
1939
|
+
const withoutHash = hashIndex >= 0 ? normalized.slice(0, hashIndex) : normalized;
|
|
1940
|
+
return withoutHash.endsWith("/") && withoutHash.length > 1 ? withoutHash.slice(0, -1) : withoutHash;
|
|
1941
|
+
};
|
|
1942
|
+
var scoreUrlMatch = (candidateUrl, hintUrl) => {
|
|
1943
|
+
if (!candidateUrl || !hintUrl) {
|
|
1944
|
+
return 0;
|
|
1945
|
+
}
|
|
1946
|
+
if (candidateUrl === hintUrl) {
|
|
1947
|
+
return 100;
|
|
1948
|
+
}
|
|
1949
|
+
if (candidateUrl.includes(hintUrl) || hintUrl.includes(candidateUrl)) {
|
|
1950
|
+
return 60;
|
|
1951
|
+
}
|
|
1952
|
+
return 0;
|
|
1953
|
+
};
|
|
1954
|
+
var scoreTitleMatch = (candidateTitle, hintTitle) => {
|
|
1955
|
+
if (!candidateTitle || !hintTitle) {
|
|
1956
|
+
return 0;
|
|
1957
|
+
}
|
|
1958
|
+
if (candidateTitle === hintTitle) {
|
|
1959
|
+
return 80;
|
|
1960
|
+
}
|
|
1961
|
+
if (candidateTitle.includes(hintTitle) || hintTitle.includes(candidateTitle)) {
|
|
1962
|
+
return 40;
|
|
1963
|
+
}
|
|
1964
|
+
return 0;
|
|
1965
|
+
};
|
|
1966
|
+
var scoreRecency = (lastSeenAt, now) => {
|
|
1967
|
+
if (!lastSeenAt) {
|
|
1968
|
+
return 0;
|
|
1969
|
+
}
|
|
1970
|
+
const ageMinutes = Math.max(0, (now - lastSeenAt) / 6e4);
|
|
1971
|
+
return Math.max(0, 30 - ageMinutes);
|
|
1972
|
+
};
|
|
1973
|
+
var scoreHintRecency = (lastSeenAt, hintLastActiveAt) => {
|
|
1974
|
+
if (!lastSeenAt || !hintLastActiveAt) {
|
|
1975
|
+
return 0;
|
|
1976
|
+
}
|
|
1977
|
+
const deltaMinutes = Math.abs(lastSeenAt - hintLastActiveAt) / 6e4;
|
|
1978
|
+
return Math.max(0, 20 - deltaMinutes);
|
|
1979
|
+
};
|
|
1980
|
+
var isBlankUrl = (url) => url.length === 0 || url === "about:blank";
|
|
1981
|
+
var rankTargetCandidates = (candidates, hint, now = Date.now()) => {
|
|
1982
|
+
const normalizedHintUrl = normalizeUrl(hint?.url);
|
|
1983
|
+
const normalizedHintTitle = normalizeText(hint?.title);
|
|
1984
|
+
const hintLastActiveAt = hint?.lastActiveAt ? Date.parse(hint.lastActiveAt) : void 0;
|
|
1985
|
+
const ranked = candidates.map((candidate) => {
|
|
1986
|
+
const reasons = [];
|
|
1987
|
+
const normalizedUrl = normalizeUrl(candidate.url);
|
|
1988
|
+
const normalizedTitle = normalizeText(candidate.title);
|
|
1989
|
+
let score = 0;
|
|
1990
|
+
const urlScore = scoreUrlMatch(normalizedUrl, normalizedHintUrl);
|
|
1991
|
+
if (urlScore > 0) {
|
|
1992
|
+
score += urlScore;
|
|
1993
|
+
reasons.push(urlScore >= 100 ? "url:exact" : "url:partial");
|
|
1994
|
+
}
|
|
1995
|
+
const titleScore = scoreTitleMatch(normalizedTitle, normalizedHintTitle);
|
|
1996
|
+
if (titleScore > 0) {
|
|
1997
|
+
score += titleScore;
|
|
1998
|
+
reasons.push(titleScore >= 80 ? "title:exact" : "title:partial");
|
|
1999
|
+
}
|
|
2000
|
+
const recencyScore = scoreRecency(candidate.lastSeenAt, now);
|
|
2001
|
+
if (recencyScore > 0) {
|
|
2002
|
+
score += recencyScore;
|
|
2003
|
+
reasons.push("recency");
|
|
2004
|
+
}
|
|
2005
|
+
const hintRecencyScore = scoreHintRecency(
|
|
2006
|
+
candidate.lastSeenAt,
|
|
2007
|
+
Number.isFinite(hintLastActiveAt) ? hintLastActiveAt : void 0
|
|
2008
|
+
);
|
|
2009
|
+
if (hintRecencyScore > 0) {
|
|
2010
|
+
score += hintRecencyScore;
|
|
2011
|
+
reasons.push("hint-recency");
|
|
2012
|
+
}
|
|
2013
|
+
if (isBlankUrl(normalizedUrl)) {
|
|
2014
|
+
score -= 50;
|
|
2015
|
+
reasons.push("blank-url");
|
|
2016
|
+
}
|
|
2017
|
+
return { candidate, score, reasons };
|
|
2018
|
+
});
|
|
2019
|
+
return ranked.sort((a, b) => {
|
|
2020
|
+
if (a.score !== b.score) {
|
|
2021
|
+
return b.score - a.score;
|
|
2022
|
+
}
|
|
2023
|
+
const aSeen = a.candidate.lastSeenAt ?? 0;
|
|
2024
|
+
const bSeen = b.candidate.lastSeenAt ?? 0;
|
|
2025
|
+
if (aSeen !== bSeen) {
|
|
2026
|
+
return bSeen - aSeen;
|
|
2027
|
+
}
|
|
2028
|
+
return a.candidate.url.localeCompare(b.candidate.url);
|
|
2029
|
+
});
|
|
2030
|
+
};
|
|
2031
|
+
var pickBestTarget = (candidates, hint, now = Date.now()) => {
|
|
2032
|
+
const ranked = rankTargetCandidates(candidates, hint, now);
|
|
2033
|
+
return ranked.length > 0 ? ranked[0] : null;
|
|
2034
|
+
};
|
|
2035
|
+
|
|
2036
|
+
// packages/core/src/inspect/service.ts
|
|
2037
|
+
var DEFAULT_MAX_SNAPSHOTS_PER_SESSION = 20;
|
|
2038
|
+
var DEFAULT_MAX_SNAPSHOT_HISTORY = 100;
|
|
2039
|
+
var InspectService = class {
|
|
2040
|
+
constructor(options) {
|
|
2041
|
+
this.registry = options.registry;
|
|
2042
|
+
this.debugger = options.debuggerBridge;
|
|
2043
|
+
this.extensionBridge = options.extensionBridge;
|
|
2044
|
+
const maxSnapshotsPerSession = options.maxSnapshotsPerSession ?? DEFAULT_MAX_SNAPSHOTS_PER_SESSION;
|
|
2045
|
+
const maxSnapshotHistory = options.maxSnapshotHistory ?? DEFAULT_MAX_SNAPSHOT_HISTORY;
|
|
2046
|
+
this.snapshots = new SnapshotHistory({
|
|
2047
|
+
maxSnapshotsPerSession,
|
|
2048
|
+
maxSnapshotHistory
|
|
2049
|
+
});
|
|
2050
|
+
}
|
|
2051
|
+
isConnected() {
|
|
2052
|
+
return this.debugger?.hasAttachments() ?? false;
|
|
2053
|
+
}
|
|
2054
|
+
getLastError() {
|
|
2055
|
+
if (!this.lastError || !this.lastErrorAt) {
|
|
2056
|
+
const debuggerError = this.debugger?.getLastError();
|
|
2057
|
+
if (!debuggerError) {
|
|
2058
|
+
return void 0;
|
|
2059
|
+
}
|
|
1188
2060
|
return {
|
|
1189
2061
|
error: new InspectError(
|
|
1190
2062
|
"INSPECT_UNAVAILABLE",
|
|
@@ -1225,9 +2097,22 @@ var InspectService = class {
|
|
|
1225
2097
|
async domSnapshot(input) {
|
|
1226
2098
|
this.requireSession(input.sessionId);
|
|
1227
2099
|
const selection = await this.resolveTab(input.targetHint);
|
|
2100
|
+
const debuggerCommand = this.debuggerCommand.bind(this);
|
|
1228
2101
|
const work = async () => {
|
|
1229
2102
|
if (input.format === "html") {
|
|
1230
|
-
const html = await
|
|
2103
|
+
const html = await captureHtml(selection.tabId, {
|
|
2104
|
+
selector: input.selector,
|
|
2105
|
+
debuggerCommand,
|
|
2106
|
+
onEvaluationFailed: () => {
|
|
2107
|
+
const error = new InspectError(
|
|
2108
|
+
"EVALUATION_FAILED",
|
|
2109
|
+
"Failed to evaluate HTML snapshot.",
|
|
2110
|
+
{ retryable: false }
|
|
2111
|
+
);
|
|
2112
|
+
this.recordError(error);
|
|
2113
|
+
throw error;
|
|
2114
|
+
}
|
|
2115
|
+
});
|
|
1231
2116
|
const warnings = [...selection.warnings ?? []];
|
|
1232
2117
|
if (input.interactive) {
|
|
1233
2118
|
warnings.push(
|
|
@@ -1254,17 +2139,19 @@ var InspectService = class {
|
|
|
1254
2139
|
const selectorWarnings = [];
|
|
1255
2140
|
let result2;
|
|
1256
2141
|
if (input.selector) {
|
|
1257
|
-
const resolved = await
|
|
2142
|
+
const resolved = await resolveNodeIdForSelector(
|
|
1258
2143
|
selection.tabId,
|
|
1259
|
-
input.selector
|
|
2144
|
+
input.selector,
|
|
2145
|
+
debuggerCommand
|
|
1260
2146
|
);
|
|
1261
2147
|
selectorWarnings.push(...resolved.warnings ?? []);
|
|
1262
2148
|
if (!resolved.nodeId) {
|
|
1263
2149
|
let refWarnings2 = [];
|
|
1264
2150
|
try {
|
|
1265
|
-
refWarnings2 = await
|
|
2151
|
+
refWarnings2 = await applySnapshotRefs(
|
|
1266
2152
|
selection.tabId,
|
|
1267
|
-
/* @__PURE__ */ new Map()
|
|
2153
|
+
/* @__PURE__ */ new Map(),
|
|
2154
|
+
debuggerCommand
|
|
1268
2155
|
);
|
|
1269
2156
|
} catch {
|
|
1270
2157
|
refWarnings2 = ["Failed to clear prior snapshot refs."];
|
|
@@ -1292,17 +2179,14 @@ var InspectService = class {
|
|
|
1292
2179
|
{}
|
|
1293
2180
|
);
|
|
1294
2181
|
}
|
|
1295
|
-
let snapshot = input.interactive || input.compact ?
|
|
2182
|
+
let snapshot = input.interactive || input.compact ? applyAxSnapshotFilters(result2, {
|
|
1296
2183
|
interactiveOnly: input.interactive,
|
|
1297
2184
|
compact: input.compact
|
|
1298
2185
|
}) : result2;
|
|
1299
2186
|
let truncated = false;
|
|
1300
2187
|
const truncationWarnings = [];
|
|
1301
2188
|
if (input.maxNodes !== void 0) {
|
|
1302
|
-
const truncatedResult =
|
|
1303
|
-
snapshot,
|
|
1304
|
-
input.maxNodes
|
|
1305
|
-
);
|
|
2189
|
+
const truncatedResult = truncateAxSnapshot(snapshot, input.maxNodes);
|
|
1306
2190
|
snapshot = truncatedResult.snapshot;
|
|
1307
2191
|
truncated = truncatedResult.truncated;
|
|
1308
2192
|
if (truncated) {
|
|
@@ -1311,10 +2195,11 @@ var InspectService = class {
|
|
|
1311
2195
|
);
|
|
1312
2196
|
}
|
|
1313
2197
|
}
|
|
1314
|
-
const refMap =
|
|
1315
|
-
const refWarnings = await
|
|
2198
|
+
const refMap = assignRefsToAxSnapshot(snapshot);
|
|
2199
|
+
const refWarnings = await applySnapshotRefs(
|
|
1316
2200
|
selection.tabId,
|
|
1317
|
-
refMap
|
|
2201
|
+
refMap,
|
|
2202
|
+
debuggerCommand
|
|
1318
2203
|
);
|
|
1319
2204
|
const warnings = [
|
|
1320
2205
|
...selection.warnings ?? [],
|
|
@@ -1338,7 +2223,19 @@ var InspectService = class {
|
|
|
1338
2223
|
if (!fallbackCodes.includes(error.code)) {
|
|
1339
2224
|
throw error;
|
|
1340
2225
|
}
|
|
1341
|
-
const html = await
|
|
2226
|
+
const html = await captureHtml(selection.tabId, {
|
|
2227
|
+
selector: input.selector,
|
|
2228
|
+
debuggerCommand,
|
|
2229
|
+
onEvaluationFailed: () => {
|
|
2230
|
+
const error2 = new InspectError(
|
|
2231
|
+
"EVALUATION_FAILED",
|
|
2232
|
+
"Failed to evaluate HTML snapshot.",
|
|
2233
|
+
{ retryable: false }
|
|
2234
|
+
);
|
|
2235
|
+
this.recordError(error2);
|
|
2236
|
+
throw error2;
|
|
2237
|
+
}
|
|
2238
|
+
});
|
|
1342
2239
|
const warnings = [
|
|
1343
2240
|
...selection.warnings ?? [],
|
|
1344
2241
|
"AX snapshot failed; returned HTML instead.",
|
|
@@ -1358,52 +2255,19 @@ var InspectService = class {
|
|
|
1358
2255
|
};
|
|
1359
2256
|
if (input.consistency === "quiesce") {
|
|
1360
2257
|
const result2 = await driveMutex.runExclusive(work);
|
|
1361
|
-
this.
|
|
2258
|
+
this.snapshots.record(input.sessionId, result2);
|
|
1362
2259
|
this.markInspectConnected(input.sessionId);
|
|
1363
2260
|
return result2;
|
|
1364
2261
|
}
|
|
1365
2262
|
const result = await work();
|
|
1366
|
-
this.
|
|
2263
|
+
this.snapshots.record(input.sessionId, result);
|
|
1367
2264
|
this.markInspectConnected(input.sessionId);
|
|
1368
2265
|
return result;
|
|
1369
2266
|
}
|
|
1370
2267
|
domDiff(input) {
|
|
1371
2268
|
this.requireSession(input.sessionId);
|
|
1372
2269
|
this.markInspectConnected(input.sessionId);
|
|
1373
|
-
|
|
1374
|
-
(record) => record.sessionId === input.sessionId
|
|
1375
|
-
);
|
|
1376
|
-
if (snapshots.length < 2) {
|
|
1377
|
-
return {
|
|
1378
|
-
added: [],
|
|
1379
|
-
removed: [],
|
|
1380
|
-
changed: [],
|
|
1381
|
-
summary: "Not enough snapshots to diff."
|
|
1382
|
-
};
|
|
1383
|
-
}
|
|
1384
|
-
const previous = snapshots[snapshots.length - 2];
|
|
1385
|
-
const current = snapshots[snapshots.length - 1];
|
|
1386
|
-
const added = [];
|
|
1387
|
-
const removed = [];
|
|
1388
|
-
const changed = [];
|
|
1389
|
-
for (const [key, value] of current.entries.entries()) {
|
|
1390
|
-
if (!previous.entries.has(key)) {
|
|
1391
|
-
added.push(key);
|
|
1392
|
-
} else if (previous.entries.get(key) !== value) {
|
|
1393
|
-
changed.push(key);
|
|
1394
|
-
}
|
|
1395
|
-
}
|
|
1396
|
-
for (const key of previous.entries.keys()) {
|
|
1397
|
-
if (!current.entries.has(key)) {
|
|
1398
|
-
removed.push(key);
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
|
-
return {
|
|
1402
|
-
added,
|
|
1403
|
-
removed,
|
|
1404
|
-
changed,
|
|
1405
|
-
summary: `Added ${added.length}, removed ${removed.length}, changed ${changed.length}.`
|
|
1406
|
-
};
|
|
2270
|
+
return this.snapshots.diff(input.sessionId);
|
|
1407
2271
|
}
|
|
1408
2272
|
async find(input) {
|
|
1409
2273
|
const snapshot = await this.domSnapshot({
|
|
@@ -1420,12 +2284,12 @@ var InspectService = class {
|
|
|
1420
2284
|
...warnings.length > 0 ? { warnings } : {}
|
|
1421
2285
|
};
|
|
1422
2286
|
}
|
|
1423
|
-
const nodes =
|
|
2287
|
+
const nodes = getAxNodes(snapshot.snapshot);
|
|
1424
2288
|
const matches = [];
|
|
1425
|
-
const nameQuery = typeof input.name === "string" ?
|
|
1426
|
-
const textQuery = typeof input.text === "string" ?
|
|
1427
|
-
const labelQuery = typeof input.label === "string" ?
|
|
1428
|
-
const roleQuery = typeof input.role === "string" ?
|
|
2289
|
+
const nameQuery = typeof input.name === "string" ? normalizeQuery(input.name) : "";
|
|
2290
|
+
const textQuery = typeof input.text === "string" ? normalizeQuery(input.text) : "";
|
|
2291
|
+
const labelQuery = typeof input.label === "string" ? normalizeQuery(input.label) : "";
|
|
2292
|
+
const roleQuery = typeof input.role === "string" ? normalizeQuery(input.role) : "";
|
|
1429
2293
|
for (const node of nodes) {
|
|
1430
2294
|
if (!node || typeof node !== "object") {
|
|
1431
2295
|
continue;
|
|
@@ -1433,24 +2297,24 @@ var InspectService = class {
|
|
|
1433
2297
|
if (typeof node.ref !== "string" || node.ref.length === 0) {
|
|
1434
2298
|
continue;
|
|
1435
2299
|
}
|
|
1436
|
-
const role =
|
|
1437
|
-
const name =
|
|
2300
|
+
const role = getAxRole(node);
|
|
2301
|
+
const name = getAxName(node);
|
|
1438
2302
|
if (input.kind === "role") {
|
|
1439
2303
|
if (!role || role !== roleQuery) {
|
|
1440
2304
|
continue;
|
|
1441
2305
|
}
|
|
1442
|
-
if (nameQuery && !
|
|
2306
|
+
if (nameQuery && !matchesTextValue(name, nameQuery)) {
|
|
1443
2307
|
continue;
|
|
1444
2308
|
}
|
|
1445
2309
|
} else if (input.kind === "text") {
|
|
1446
|
-
if (!textQuery || !
|
|
2310
|
+
if (!textQuery || !matchesAxText(node, textQuery)) {
|
|
1447
2311
|
continue;
|
|
1448
2312
|
}
|
|
1449
2313
|
} else if (input.kind === "label") {
|
|
1450
2314
|
if (!labelQuery || !LABEL_AX_ROLES.has(role)) {
|
|
1451
2315
|
continue;
|
|
1452
2316
|
}
|
|
1453
|
-
if (!
|
|
2317
|
+
if (!matchesTextValue(name, labelQuery)) {
|
|
1454
2318
|
continue;
|
|
1455
2319
|
}
|
|
1456
2320
|
}
|
|
@@ -1458,835 +2322,440 @@ var InspectService = class {
|
|
|
1458
2322
|
ref: node.ref,
|
|
1459
2323
|
...role ? { role } : {},
|
|
1460
2324
|
...name ? { name } : {}
|
|
1461
|
-
});
|
|
1462
|
-
}
|
|
1463
|
-
return {
|
|
1464
|
-
matches,
|
|
1465
|
-
...warnings.length > 0 ? { warnings } : {}
|
|
1466
|
-
};
|
|
1467
|
-
}
|
|
1468
|
-
async consoleList(input) {
|
|
1469
|
-
this.requireSession(input.sessionId);
|
|
1470
|
-
const selection = await this.resolveTab(input.targetHint);
|
|
1471
|
-
await this.enableConsole(selection.tabId);
|
|
1472
|
-
const events = this.ensureDebugger().getConsoleEvents(selection.tabId);
|
|
1473
|
-
const entries = events.map((event) => this.toConsoleEntry(event)).filter((entry) => entry !== null);
|
|
1474
|
-
const result = {
|
|
1475
|
-
entries,
|
|
1476
|
-
warnings: selection.warnings
|
|
1477
|
-
};
|
|
1478
|
-
this.markInspectConnected(input.sessionId);
|
|
1479
|
-
return result;
|
|
1480
|
-
}
|
|
1481
|
-
async networkHar(input) {
|
|
1482
|
-
this.requireSession(input.sessionId);
|
|
1483
|
-
const selection = await this.resolveTab(input.targetHint);
|
|
1484
|
-
await this.enableNetwork(selection.tabId);
|
|
1485
|
-
const events = this.ensureDebugger().getNetworkEvents(selection.tabId);
|
|
1486
|
-
const har = this.buildHar(events, selection.tab.title);
|
|
1487
|
-
try {
|
|
1488
|
-
const rootDir = await ensureArtifactRootDir(input.sessionId);
|
|
1489
|
-
const artifactId = (0, import_crypto3.randomUUID)();
|
|
1490
|
-
const filePath = import_node_path2.default.join(rootDir, `har-${artifactId}.json`);
|
|
1491
|
-
await (0, import_promises2.writeFile)(filePath, JSON.stringify(har, null, 2), "utf-8");
|
|
1492
|
-
const result = {
|
|
1493
|
-
artifact_id: artifactId,
|
|
1494
|
-
path: filePath,
|
|
1495
|
-
mime: "application/json"
|
|
1496
|
-
};
|
|
1497
|
-
this.markInspectConnected(input.sessionId);
|
|
1498
|
-
return result;
|
|
1499
|
-
} catch {
|
|
1500
|
-
const error = new InspectError(
|
|
1501
|
-
"ARTIFACT_IO_ERROR",
|
|
1502
|
-
"Failed to write HAR file."
|
|
1503
|
-
);
|
|
1504
|
-
this.recordError(error);
|
|
1505
|
-
throw error;
|
|
1506
|
-
}
|
|
1507
|
-
}
|
|
1508
|
-
async evaluate(input) {
|
|
1509
|
-
this.requireSession(input.sessionId);
|
|
1510
|
-
const selection = await this.resolveTab(input.targetHint);
|
|
1511
|
-
const expression = input.expression ?? "undefined";
|
|
1512
|
-
await this.debuggerCommand(selection.tabId, "Runtime.enable", {});
|
|
1513
|
-
const result = await this.debuggerCommand(
|
|
1514
|
-
selection.tabId,
|
|
1515
|
-
"Runtime.evaluate",
|
|
1516
|
-
{
|
|
1517
|
-
expression,
|
|
1518
|
-
returnByValue: true,
|
|
1519
|
-
awaitPromise: true
|
|
1520
|
-
}
|
|
1521
|
-
);
|
|
1522
|
-
if (result && typeof result === "object" && "exceptionDetails" in result) {
|
|
1523
|
-
const output2 = {
|
|
1524
|
-
exception: result.exceptionDetails,
|
|
1525
|
-
warnings: selection.warnings
|
|
1526
|
-
};
|
|
1527
|
-
this.markInspectConnected(input.sessionId);
|
|
1528
|
-
return output2;
|
|
1529
|
-
}
|
|
1530
|
-
const output = {
|
|
1531
|
-
value: result?.result?.value,
|
|
1532
|
-
warnings: selection.warnings
|
|
1533
|
-
};
|
|
1534
|
-
this.markInspectConnected(input.sessionId);
|
|
1535
|
-
return output;
|
|
1536
|
-
}
|
|
1537
|
-
async extractContent(input) {
|
|
1538
|
-
this.requireSession(input.sessionId);
|
|
1539
|
-
const selection = await this.resolveTab(input.targetHint);
|
|
1540
|
-
const html = await this.captureHtml(selection.tabId);
|
|
1541
|
-
const url = selection.tab.url ?? "about:blank";
|
|
1542
|
-
let article = null;
|
|
1543
|
-
try {
|
|
1544
|
-
const dom = new import_jsdom.JSDOM(html, { url });
|
|
1545
|
-
const reader = new import_readability.Readability(dom.window.document);
|
|
1546
|
-
article = reader.parse();
|
|
1547
|
-
} catch {
|
|
1548
|
-
const err = new InspectError(
|
|
1549
|
-
"EVALUATION_FAILED",
|
|
1550
|
-
"Failed to parse page content.",
|
|
1551
|
-
{ retryable: false }
|
|
1552
|
-
);
|
|
1553
|
-
this.recordError(err);
|
|
1554
|
-
throw err;
|
|
1555
|
-
}
|
|
1556
|
-
if (!article) {
|
|
1557
|
-
const err = new InspectError(
|
|
1558
|
-
"NOT_SUPPORTED",
|
|
1559
|
-
"Readability could not extract content.",
|
|
1560
|
-
{ retryable: false }
|
|
1561
|
-
);
|
|
1562
|
-
this.recordError(err);
|
|
1563
|
-
throw err;
|
|
1564
|
-
}
|
|
1565
|
-
let content = "";
|
|
1566
|
-
if (input.format === "article_json") {
|
|
1567
|
-
content = JSON.stringify(article, null, 2);
|
|
1568
|
-
} else if (input.format === "text") {
|
|
1569
|
-
content = article.textContent ?? "";
|
|
1570
|
-
} else {
|
|
1571
|
-
const turndown = new import_turndown.default();
|
|
1572
|
-
content = turndown.turndown(article.content ?? "");
|
|
1573
|
-
}
|
|
1574
|
-
const warnings = selection.warnings ?? [];
|
|
1575
|
-
const includeMetadata = input.includeMetadata ?? true;
|
|
1576
|
-
const output = {
|
|
1577
|
-
content,
|
|
1578
|
-
...includeMetadata ? {
|
|
1579
|
-
title: article.title ?? void 0,
|
|
1580
|
-
byline: article.byline ?? void 0,
|
|
1581
|
-
excerpt: article.excerpt ?? void 0,
|
|
1582
|
-
siteName: article.siteName ?? void 0
|
|
1583
|
-
} : {},
|
|
1584
|
-
...warnings.length > 0 ? { warnings } : {}
|
|
1585
|
-
};
|
|
1586
|
-
this.markInspectConnected(input.sessionId);
|
|
1587
|
-
return output;
|
|
1588
|
-
}
|
|
1589
|
-
async pageState(input) {
|
|
1590
|
-
this.requireSession(input.sessionId);
|
|
1591
|
-
const selection = await this.resolveTab(input.targetHint);
|
|
1592
|
-
await this.debuggerCommand(selection.tabId, "Runtime.enable", {});
|
|
1593
|
-
const expression = PAGE_STATE_SCRIPT;
|
|
1594
|
-
const result = await this.debuggerCommand(
|
|
1595
|
-
selection.tabId,
|
|
1596
|
-
"Runtime.evaluate",
|
|
1597
|
-
{
|
|
1598
|
-
expression,
|
|
1599
|
-
returnByValue: true,
|
|
1600
|
-
awaitPromise: true
|
|
1601
|
-
}
|
|
1602
|
-
);
|
|
1603
|
-
if (result && typeof result === "object" && "exceptionDetails" in result) {
|
|
1604
|
-
const error = new InspectError(
|
|
1605
|
-
"EVALUATION_FAILED",
|
|
1606
|
-
"Failed to capture page state.",
|
|
1607
|
-
{ retryable: false }
|
|
1608
|
-
);
|
|
1609
|
-
this.recordError(error);
|
|
1610
|
-
throw error;
|
|
2325
|
+
});
|
|
1611
2326
|
}
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
const warnings = [
|
|
1615
|
-
...Array.isArray(raw.warnings) ? raw.warnings : [],
|
|
1616
|
-
...selection.warnings ?? []
|
|
1617
|
-
];
|
|
1618
|
-
const output = {
|
|
1619
|
-
forms: Array.isArray(raw.forms) ? raw.forms : [],
|
|
1620
|
-
localStorage: Array.isArray(raw.localStorage) ? raw.localStorage : [],
|
|
1621
|
-
sessionStorage: Array.isArray(raw.sessionStorage) ? raw.sessionStorage : [],
|
|
1622
|
-
cookies: Array.isArray(raw.cookies) ? raw.cookies : [],
|
|
2327
|
+
return {
|
|
2328
|
+
matches,
|
|
1623
2329
|
...warnings.length > 0 ? { warnings } : {}
|
|
1624
2330
|
};
|
|
1625
|
-
this.markInspectConnected(input.sessionId);
|
|
1626
|
-
return output;
|
|
1627
2331
|
}
|
|
1628
|
-
async
|
|
2332
|
+
async consoleList(input) {
|
|
1629
2333
|
this.requireSession(input.sessionId);
|
|
1630
2334
|
const selection = await this.resolveTab(input.targetHint);
|
|
1631
|
-
await this.
|
|
1632
|
-
const
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
name: metric.name,
|
|
1639
|
-
value: metric.value
|
|
1640
|
-
})) : [];
|
|
1641
|
-
const output = { metrics, warnings: selection.warnings };
|
|
2335
|
+
await this.enableConsole(selection.tabId);
|
|
2336
|
+
const events = this.ensureDebugger().getConsoleEvents(selection.tabId);
|
|
2337
|
+
const entries = events.map((event) => toConsoleEntry(event)).filter((entry) => entry !== null);
|
|
2338
|
+
const result = {
|
|
2339
|
+
entries,
|
|
2340
|
+
warnings: selection.warnings
|
|
2341
|
+
};
|
|
1642
2342
|
this.markInspectConnected(input.sessionId);
|
|
1643
|
-
return
|
|
2343
|
+
return result;
|
|
1644
2344
|
}
|
|
1645
|
-
async
|
|
2345
|
+
async networkHar(input) {
|
|
1646
2346
|
this.requireSession(input.sessionId);
|
|
1647
2347
|
const selection = await this.resolveTab(input.targetHint);
|
|
1648
|
-
await this.
|
|
1649
|
-
const
|
|
1650
|
-
|
|
1651
|
-
format,
|
|
1652
|
-
fromSurface: true
|
|
1653
|
-
};
|
|
1654
|
-
if (format !== "png" && typeof input.quality === "number") {
|
|
1655
|
-
captureParams = { ...captureParams, quality: input.quality };
|
|
1656
|
-
}
|
|
1657
|
-
if (input.target === "full") {
|
|
1658
|
-
const layout = await this.debuggerCommand(
|
|
1659
|
-
selection.tabId,
|
|
1660
|
-
"Page.getLayoutMetrics",
|
|
1661
|
-
{}
|
|
1662
|
-
);
|
|
1663
|
-
const contentSize = layout?.contentSize;
|
|
1664
|
-
if (contentSize) {
|
|
1665
|
-
captureParams = {
|
|
1666
|
-
...captureParams,
|
|
1667
|
-
clip: {
|
|
1668
|
-
x: 0,
|
|
1669
|
-
y: 0,
|
|
1670
|
-
width: contentSize.width,
|
|
1671
|
-
height: contentSize.height,
|
|
1672
|
-
scale: 1
|
|
1673
|
-
}
|
|
1674
|
-
};
|
|
1675
|
-
} else {
|
|
1676
|
-
captureParams = { ...captureParams, captureBeyondViewport: true };
|
|
1677
|
-
}
|
|
1678
|
-
}
|
|
1679
|
-
const result = await this.debuggerCommand(
|
|
1680
|
-
selection.tabId,
|
|
1681
|
-
"Page.captureScreenshot",
|
|
1682
|
-
captureParams
|
|
1683
|
-
);
|
|
1684
|
-
const data = result.data;
|
|
1685
|
-
if (!data) {
|
|
1686
|
-
const error = new InspectError(
|
|
1687
|
-
"INSPECT_UNAVAILABLE",
|
|
1688
|
-
"Failed to capture screenshot.",
|
|
1689
|
-
{ retryable: false }
|
|
1690
|
-
);
|
|
1691
|
-
this.recordError(error);
|
|
1692
|
-
throw error;
|
|
1693
|
-
}
|
|
2348
|
+
await this.enableNetwork(selection.tabId);
|
|
2349
|
+
const events = this.ensureDebugger().getNetworkEvents(selection.tabId);
|
|
2350
|
+
const har = buildHar(events, selection.tab.title);
|
|
1694
2351
|
try {
|
|
1695
2352
|
const rootDir = await ensureArtifactRootDir(input.sessionId);
|
|
1696
2353
|
const artifactId = (0, import_crypto3.randomUUID)();
|
|
1697
|
-
const
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
`screenshot-${artifactId}.${extension}`
|
|
1701
|
-
);
|
|
1702
|
-
await (0, import_promises2.writeFile)(filePath, Buffer.from(data, "base64"));
|
|
1703
|
-
const mime = format === "jpeg" ? "image/jpeg" : `image/${format}`;
|
|
1704
|
-
const output = {
|
|
2354
|
+
const filePath = import_node_path2.default.join(rootDir, `har-${artifactId}.json`);
|
|
2355
|
+
await (0, import_promises2.writeFile)(filePath, JSON.stringify(har, null, 2), "utf-8");
|
|
2356
|
+
const result = {
|
|
1705
2357
|
artifact_id: artifactId,
|
|
1706
2358
|
path: filePath,
|
|
1707
|
-
mime
|
|
2359
|
+
mime: "application/json"
|
|
1708
2360
|
};
|
|
1709
2361
|
this.markInspectConnected(input.sessionId);
|
|
1710
|
-
return
|
|
2362
|
+
return result;
|
|
1711
2363
|
} catch {
|
|
1712
2364
|
const error = new InspectError(
|
|
1713
2365
|
"ARTIFACT_IO_ERROR",
|
|
1714
|
-
"Failed to write
|
|
1715
|
-
);
|
|
1716
|
-
this.recordError(error);
|
|
1717
|
-
throw error;
|
|
1718
|
-
}
|
|
1719
|
-
}
|
|
1720
|
-
ensureDebugger() {
|
|
1721
|
-
if (!this.debugger) {
|
|
1722
|
-
const error = this.buildUnavailableError();
|
|
1723
|
-
this.recordError(error);
|
|
1724
|
-
throw error;
|
|
1725
|
-
}
|
|
1726
|
-
return this.debugger;
|
|
1727
|
-
}
|
|
1728
|
-
async resolveTab(hint) {
|
|
1729
|
-
if (!this.extensionBridge || !this.extensionBridge.isConnected()) {
|
|
1730
|
-
const error = new InspectError(
|
|
1731
|
-
"EXTENSION_DISCONNECTED",
|
|
1732
|
-
"Extension is not connected.",
|
|
1733
|
-
{ retryable: true }
|
|
1734
|
-
);
|
|
1735
|
-
this.recordError(error);
|
|
1736
|
-
throw error;
|
|
1737
|
-
}
|
|
1738
|
-
const tabs = this.extensionBridge.getStatus().tabs ?? [];
|
|
1739
|
-
if (!Array.isArray(tabs) || tabs.length === 0) {
|
|
1740
|
-
const error = new InspectError(
|
|
1741
|
-
"TAB_NOT_FOUND",
|
|
1742
|
-
"No tabs available to inspect."
|
|
1743
|
-
);
|
|
1744
|
-
this.recordError(error);
|
|
1745
|
-
throw error;
|
|
1746
|
-
}
|
|
1747
|
-
const candidates = tabs.map((tab2) => ({
|
|
1748
|
-
id: String(tab2.tab_id),
|
|
1749
|
-
url: tab2.url ?? "",
|
|
1750
|
-
title: tab2.title,
|
|
1751
|
-
lastSeenAt: tab2.last_active_at ? Date.parse(tab2.last_active_at) : void 0
|
|
1752
|
-
}));
|
|
1753
|
-
const ranked = pickBestTarget(candidates, hint);
|
|
1754
|
-
if (!ranked) {
|
|
1755
|
-
const error = new InspectError("TAB_NOT_FOUND", "No matching tab found.");
|
|
1756
|
-
this.recordError(error);
|
|
1757
|
-
throw error;
|
|
1758
|
-
}
|
|
1759
|
-
const tabId = Number(ranked.candidate.id);
|
|
1760
|
-
if (!Number.isFinite(tabId)) {
|
|
1761
|
-
const error = new InspectError(
|
|
1762
|
-
"TAB_NOT_FOUND",
|
|
1763
|
-
"Resolved tab id is invalid."
|
|
2366
|
+
"Failed to write HAR file."
|
|
1764
2367
|
);
|
|
1765
2368
|
this.recordError(error);
|
|
1766
2369
|
throw error;
|
|
1767
2370
|
}
|
|
1768
|
-
const tab = tabs.find((entry) => entry.tab_id === tabId) ?? tabs[0];
|
|
1769
|
-
const warnings = [];
|
|
1770
|
-
if (!hint) {
|
|
1771
|
-
warnings.push("No target hint provided; using the most recent tab.");
|
|
1772
|
-
} else if (ranked.score < 20) {
|
|
1773
|
-
warnings.push("Weak target match; using best available tab.");
|
|
1774
|
-
}
|
|
1775
|
-
return {
|
|
1776
|
-
tabId,
|
|
1777
|
-
tab,
|
|
1778
|
-
warnings: warnings.length > 0 ? warnings : void 0
|
|
1779
|
-
};
|
|
1780
|
-
}
|
|
1781
|
-
async enableConsole(tabId) {
|
|
1782
|
-
await this.debuggerCommand(tabId, "Runtime.enable", {});
|
|
1783
|
-
await this.debuggerCommand(tabId, "Log.enable", {});
|
|
1784
|
-
}
|
|
1785
|
-
async enableNetwork(tabId) {
|
|
1786
|
-
await this.debuggerCommand(tabId, "Network.enable", {});
|
|
1787
|
-
}
|
|
1788
|
-
async enableAccessibility(tabId) {
|
|
1789
|
-
await this.debuggerCommand(tabId, "Accessibility.enable", {});
|
|
1790
|
-
}
|
|
1791
|
-
recordSnapshot(sessionId, snapshot) {
|
|
1792
|
-
const entries = this.collectSnapshotEntries(snapshot);
|
|
1793
|
-
if (!entries) {
|
|
1794
|
-
return;
|
|
1795
|
-
}
|
|
1796
|
-
this.snapshotHistory.push({
|
|
1797
|
-
sessionId,
|
|
1798
|
-
format: snapshot.format,
|
|
1799
|
-
entries,
|
|
1800
|
-
capturedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1801
|
-
});
|
|
1802
|
-
let count = 0;
|
|
1803
|
-
for (const record of this.snapshotHistory) {
|
|
1804
|
-
if (record.sessionId === sessionId) {
|
|
1805
|
-
count += 1;
|
|
1806
|
-
}
|
|
1807
|
-
}
|
|
1808
|
-
while (count > this.maxSnapshotsPerSession) {
|
|
1809
|
-
const index = this.snapshotHistory.findIndex(
|
|
1810
|
-
(record) => record.sessionId === sessionId
|
|
1811
|
-
);
|
|
1812
|
-
if (index === -1) {
|
|
1813
|
-
break;
|
|
1814
|
-
}
|
|
1815
|
-
this.snapshotHistory.splice(index, 1);
|
|
1816
|
-
count -= 1;
|
|
1817
|
-
}
|
|
1818
|
-
while (this.snapshotHistory.length > this.maxSnapshotHistory) {
|
|
1819
|
-
this.snapshotHistory.shift();
|
|
1820
|
-
}
|
|
1821
|
-
}
|
|
1822
|
-
collectSnapshotEntries(snapshot) {
|
|
1823
|
-
if (snapshot.format === "html" && typeof snapshot.snapshot === "string") {
|
|
1824
|
-
return this.collectHtmlEntries(snapshot.snapshot);
|
|
1825
|
-
}
|
|
1826
|
-
if (snapshot.format === "ax") {
|
|
1827
|
-
return this.collectAxEntries(snapshot.snapshot);
|
|
1828
|
-
}
|
|
1829
|
-
return null;
|
|
1830
|
-
}
|
|
1831
|
-
getAxNodes(snapshot) {
|
|
1832
|
-
const nodes = Array.isArray(snapshot) ? snapshot : snapshot?.nodes;
|
|
1833
|
-
return Array.isArray(nodes) ? nodes : [];
|
|
1834
|
-
}
|
|
1835
|
-
applyAxSnapshotFilters(snapshot, options) {
|
|
1836
|
-
let filtered = snapshot;
|
|
1837
|
-
if (options.compact) {
|
|
1838
|
-
filtered = this.compactAxSnapshot(filtered);
|
|
1839
|
-
}
|
|
1840
|
-
if (options.interactiveOnly) {
|
|
1841
|
-
filtered = this.filterAxSnapshot(
|
|
1842
|
-
filtered,
|
|
1843
|
-
(node) => this.isInteractiveAxNode(node)
|
|
1844
|
-
);
|
|
1845
|
-
}
|
|
1846
|
-
return filtered;
|
|
1847
2371
|
}
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
}
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
continue;
|
|
1861
|
-
}
|
|
1862
|
-
nodeById.set(node.nodeId, node);
|
|
1863
|
-
parentCount.set(node.nodeId, parentCount.get(node.nodeId) ?? 0);
|
|
1864
|
-
}
|
|
1865
|
-
if (nodeById.size === 0) {
|
|
1866
|
-
const sliced = nodes.slice(0, maxNodes);
|
|
1867
|
-
for (const node of sliced) {
|
|
1868
|
-
if (node && typeof node === "object" && Array.isArray(node.childIds)) {
|
|
1869
|
-
node.childIds = [];
|
|
1870
|
-
}
|
|
1871
|
-
}
|
|
1872
|
-
return {
|
|
1873
|
-
snapshot: this.replaceAxNodes(snapshot, sliced),
|
|
1874
|
-
truncated: true
|
|
1875
|
-
};
|
|
1876
|
-
}
|
|
1877
|
-
for (const node of nodes) {
|
|
1878
|
-
if (!node || typeof node !== "object" || !Array.isArray(node.childIds)) {
|
|
1879
|
-
continue;
|
|
1880
|
-
}
|
|
1881
|
-
for (const childId of node.childIds) {
|
|
1882
|
-
if (typeof childId !== "string") {
|
|
1883
|
-
continue;
|
|
1884
|
-
}
|
|
1885
|
-
parentCount.set(childId, (parentCount.get(childId) ?? 0) + 1);
|
|
1886
|
-
}
|
|
1887
|
-
}
|
|
1888
|
-
let roots = Array.from(nodeById.keys()).filter(
|
|
1889
|
-
(id) => (parentCount.get(id) ?? 0) === 0
|
|
1890
|
-
);
|
|
1891
|
-
if (roots.length === 0) {
|
|
1892
|
-
const first = nodes.find(
|
|
1893
|
-
(node) => node && typeof node.nodeId === "string"
|
|
1894
|
-
)?.nodeId;
|
|
1895
|
-
if (first) {
|
|
1896
|
-
roots = [first];
|
|
1897
|
-
}
|
|
1898
|
-
}
|
|
1899
|
-
const kept = /* @__PURE__ */ new Set();
|
|
1900
|
-
const visited = /* @__PURE__ */ new Set();
|
|
1901
|
-
const queue = [...roots];
|
|
1902
|
-
while (queue.length > 0 && kept.size < maxNodes) {
|
|
1903
|
-
const id = queue.shift();
|
|
1904
|
-
if (!id || visited.has(id)) {
|
|
1905
|
-
continue;
|
|
1906
|
-
}
|
|
1907
|
-
visited.add(id);
|
|
1908
|
-
const node = nodeById.get(id);
|
|
1909
|
-
if (!node) {
|
|
1910
|
-
continue;
|
|
1911
|
-
}
|
|
1912
|
-
kept.add(id);
|
|
1913
|
-
if (Array.isArray(node.childIds)) {
|
|
1914
|
-
for (const childId of node.childIds) {
|
|
1915
|
-
if (typeof childId === "string" && !visited.has(childId)) {
|
|
1916
|
-
queue.push(childId);
|
|
1917
|
-
}
|
|
1918
|
-
}
|
|
1919
|
-
}
|
|
1920
|
-
}
|
|
1921
|
-
if (kept.size === 0) {
|
|
1922
|
-
const fallback = [];
|
|
1923
|
-
for (const node of nodes) {
|
|
1924
|
-
if (fallback.length >= maxNodes) {
|
|
1925
|
-
break;
|
|
1926
|
-
}
|
|
1927
|
-
if (node && typeof node.nodeId === "string") {
|
|
1928
|
-
fallback.push(node.nodeId);
|
|
1929
|
-
}
|
|
2372
|
+
async evaluate(input) {
|
|
2373
|
+
this.requireSession(input.sessionId);
|
|
2374
|
+
const selection = await this.resolveTab(input.targetHint);
|
|
2375
|
+
const expression = input.expression ?? "undefined";
|
|
2376
|
+
await this.debuggerCommand(selection.tabId, "Runtime.enable", {});
|
|
2377
|
+
const result = await this.debuggerCommand(
|
|
2378
|
+
selection.tabId,
|
|
2379
|
+
"Runtime.evaluate",
|
|
2380
|
+
{
|
|
2381
|
+
expression,
|
|
2382
|
+
returnByValue: true,
|
|
2383
|
+
awaitPromise: true
|
|
1930
2384
|
}
|
|
1931
|
-
fallback.forEach((id) => kept.add(id));
|
|
1932
|
-
}
|
|
1933
|
-
const filtered = nodes.filter(
|
|
1934
|
-
(node) => node && typeof node.nodeId === "string" && kept.has(node.nodeId)
|
|
1935
2385
|
);
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
2386
|
+
if (result && typeof result === "object" && "exceptionDetails" in result) {
|
|
2387
|
+
const output2 = {
|
|
2388
|
+
exception: result.exceptionDetails,
|
|
2389
|
+
warnings: selection.warnings
|
|
2390
|
+
};
|
|
2391
|
+
this.markInspectConnected(input.sessionId);
|
|
2392
|
+
return output2;
|
|
1942
2393
|
}
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
2394
|
+
const output = {
|
|
2395
|
+
value: result?.result?.value,
|
|
2396
|
+
warnings: selection.warnings
|
|
1946
2397
|
};
|
|
2398
|
+
this.markInspectConnected(input.sessionId);
|
|
2399
|
+
return output;
|
|
1947
2400
|
}
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
2401
|
+
async extractContent(input) {
|
|
2402
|
+
this.requireSession(input.sessionId);
|
|
2403
|
+
const selection = await this.resolveTab(input.targetHint);
|
|
2404
|
+
const debuggerCommand = this.debuggerCommand.bind(this);
|
|
2405
|
+
const html = await captureHtml(selection.tabId, {
|
|
2406
|
+
debuggerCommand,
|
|
2407
|
+
onEvaluationFailed: () => {
|
|
2408
|
+
const error = new InspectError(
|
|
2409
|
+
"EVALUATION_FAILED",
|
|
2410
|
+
"Failed to evaluate HTML snapshot.",
|
|
2411
|
+
{ retryable: false }
|
|
2412
|
+
);
|
|
2413
|
+
this.recordError(error);
|
|
2414
|
+
throw error;
|
|
1961
2415
|
}
|
|
1962
|
-
return keep;
|
|
1963
2416
|
});
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
}
|
|
2417
|
+
const url = selection.tab.url ?? "about:blank";
|
|
2418
|
+
let article = null;
|
|
2419
|
+
try {
|
|
2420
|
+
const dom = new import_jsdom.JSDOM(html, { url });
|
|
2421
|
+
const reader = new import_readability.Readability(dom.window.document);
|
|
2422
|
+
article = reader.parse();
|
|
2423
|
+
} catch {
|
|
2424
|
+
const err = new InspectError(
|
|
2425
|
+
"EVALUATION_FAILED",
|
|
2426
|
+
"Failed to parse page content.",
|
|
2427
|
+
{ retryable: false }
|
|
2428
|
+
);
|
|
2429
|
+
this.recordError(err);
|
|
2430
|
+
throw err;
|
|
1968
2431
|
}
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
2432
|
+
if (!article) {
|
|
2433
|
+
const err = new InspectError(
|
|
2434
|
+
"NOT_SUPPORTED",
|
|
2435
|
+
"Readability could not extract content.",
|
|
2436
|
+
{ retryable: false }
|
|
2437
|
+
);
|
|
2438
|
+
this.recordError(err);
|
|
2439
|
+
throw err;
|
|
1974
2440
|
}
|
|
1975
|
-
|
|
1976
|
-
|
|
2441
|
+
let content = "";
|
|
2442
|
+
if (input.format === "article_json") {
|
|
2443
|
+
content = JSON.stringify(article, null, 2);
|
|
2444
|
+
} else if (input.format === "text") {
|
|
2445
|
+
content = article.textContent ?? "";
|
|
2446
|
+
} else {
|
|
2447
|
+
const turndown = new import_turndown.default();
|
|
2448
|
+
content = turndown.turndown(article.content ?? "");
|
|
1977
2449
|
}
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
2450
|
+
const warnings = selection.warnings ?? [];
|
|
2451
|
+
const includeMetadata = input.includeMetadata ?? true;
|
|
2452
|
+
const output = {
|
|
2453
|
+
content,
|
|
2454
|
+
...includeMetadata ? {
|
|
2455
|
+
title: article.title ?? void 0,
|
|
2456
|
+
byline: article.byline ?? void 0,
|
|
2457
|
+
excerpt: article.excerpt ?? void 0,
|
|
2458
|
+
siteName: article.siteName ?? void 0
|
|
2459
|
+
} : {},
|
|
2460
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
2461
|
+
};
|
|
2462
|
+
this.markInspectConnected(input.sessionId);
|
|
2463
|
+
return output;
|
|
1983
2464
|
}
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
const
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
for (const node of nodes) {
|
|
1997
|
-
if (!node || typeof node !== "object" || typeof node.nodeId !== "string") {
|
|
1998
|
-
continue;
|
|
1999
|
-
}
|
|
2000
|
-
if (this.shouldKeepCompactNode(node)) {
|
|
2001
|
-
keepIds.add(node.nodeId);
|
|
2465
|
+
async pageState(input) {
|
|
2466
|
+
this.requireSession(input.sessionId);
|
|
2467
|
+
const selection = await this.resolveTab(input.targetHint);
|
|
2468
|
+
await this.debuggerCommand(selection.tabId, "Runtime.enable", {});
|
|
2469
|
+
const expression = PAGE_STATE_SCRIPT;
|
|
2470
|
+
const result = await this.debuggerCommand(
|
|
2471
|
+
selection.tabId,
|
|
2472
|
+
"Runtime.evaluate",
|
|
2473
|
+
{
|
|
2474
|
+
expression,
|
|
2475
|
+
returnByValue: true,
|
|
2476
|
+
awaitPromise: true
|
|
2002
2477
|
}
|
|
2003
|
-
}
|
|
2004
|
-
const filtered = nodes.filter(
|
|
2005
|
-
(node) => node && typeof node.nodeId === "string" && keepIds.has(node.nodeId)
|
|
2006
2478
|
);
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
for (const childId of node.childIds) {
|
|
2013
|
-
nextChildIds.push(
|
|
2014
|
-
...this.collectKeptDescendants(childId, nodeById, keepIds)
|
|
2015
|
-
);
|
|
2016
|
-
}
|
|
2017
|
-
node.childIds = Array.from(new Set(nextChildIds));
|
|
2018
|
-
}
|
|
2019
|
-
return this.replaceAxNodes(snapshot, filtered);
|
|
2020
|
-
}
|
|
2021
|
-
collectKeptDescendants(nodeId, nodeById, keepIds, visited = /* @__PURE__ */ new Set()) {
|
|
2022
|
-
if (visited.has(nodeId)) {
|
|
2023
|
-
return [];
|
|
2024
|
-
}
|
|
2025
|
-
visited.add(nodeId);
|
|
2026
|
-
if (keepIds.has(nodeId)) {
|
|
2027
|
-
return [nodeId];
|
|
2028
|
-
}
|
|
2029
|
-
const node = nodeById.get(nodeId);
|
|
2030
|
-
if (!node || !Array.isArray(node.childIds)) {
|
|
2031
|
-
return [];
|
|
2032
|
-
}
|
|
2033
|
-
const output = [];
|
|
2034
|
-
for (const childId of node.childIds) {
|
|
2035
|
-
output.push(
|
|
2036
|
-
...this.collectKeptDescendants(childId, nodeById, keepIds, visited)
|
|
2479
|
+
if (result && typeof result === "object" && "exceptionDetails" in result) {
|
|
2480
|
+
const error = new InspectError(
|
|
2481
|
+
"EVALUATION_FAILED",
|
|
2482
|
+
"Failed to capture page state.",
|
|
2483
|
+
{ retryable: false }
|
|
2037
2484
|
);
|
|
2485
|
+
this.recordError(error);
|
|
2486
|
+
throw error;
|
|
2038
2487
|
}
|
|
2488
|
+
const value = result?.result?.value;
|
|
2489
|
+
const raw = value && typeof value === "object" ? value : {};
|
|
2490
|
+
const warnings = [
|
|
2491
|
+
...Array.isArray(raw.warnings) ? raw.warnings : [],
|
|
2492
|
+
...selection.warnings ?? []
|
|
2493
|
+
];
|
|
2494
|
+
const output = {
|
|
2495
|
+
forms: Array.isArray(raw.forms) ? raw.forms : [],
|
|
2496
|
+
localStorage: Array.isArray(raw.localStorage) ? raw.localStorage : [],
|
|
2497
|
+
sessionStorage: Array.isArray(raw.sessionStorage) ? raw.sessionStorage : [],
|
|
2498
|
+
cookies: Array.isArray(raw.cookies) ? raw.cookies : [],
|
|
2499
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
2500
|
+
};
|
|
2501
|
+
this.markInspectConnected(input.sessionId);
|
|
2039
2502
|
return output;
|
|
2040
2503
|
}
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
}
|
|
2045
|
-
const
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
const
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
}
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
return false;
|
|
2058
|
-
}
|
|
2059
|
-
return hasChildren;
|
|
2060
|
-
}
|
|
2061
|
-
getAxRole(node) {
|
|
2062
|
-
const role = typeof node.role === "string" ? node.role : node.role?.value ?? "";
|
|
2063
|
-
return typeof role === "string" ? role.toLowerCase() : "";
|
|
2064
|
-
}
|
|
2065
|
-
getAxName(node) {
|
|
2066
|
-
const name = typeof node.name === "string" ? node.name : node.name?.value ?? "";
|
|
2067
|
-
return typeof name === "string" ? name : "";
|
|
2068
|
-
}
|
|
2069
|
-
hasAxValue(node) {
|
|
2070
|
-
if (!Array.isArray(node.properties)) {
|
|
2071
|
-
return false;
|
|
2072
|
-
}
|
|
2073
|
-
for (const prop of node.properties) {
|
|
2074
|
-
if (!prop || typeof prop !== "object") {
|
|
2075
|
-
continue;
|
|
2076
|
-
}
|
|
2077
|
-
const value = prop.value?.value;
|
|
2078
|
-
if (value === void 0 || value === null) {
|
|
2079
|
-
continue;
|
|
2080
|
-
}
|
|
2081
|
-
if (typeof value === "string" && value.trim().length === 0) {
|
|
2082
|
-
continue;
|
|
2083
|
-
}
|
|
2084
|
-
return true;
|
|
2085
|
-
}
|
|
2086
|
-
return false;
|
|
2087
|
-
}
|
|
2088
|
-
normalizeQuery(value) {
|
|
2089
|
-
return value.trim().toLowerCase();
|
|
2090
|
-
}
|
|
2091
|
-
matchesTextValue(value, query) {
|
|
2092
|
-
if (!query) {
|
|
2093
|
-
return false;
|
|
2094
|
-
}
|
|
2095
|
-
return value.toLowerCase().includes(query);
|
|
2096
|
-
}
|
|
2097
|
-
matchesAxText(node, query) {
|
|
2098
|
-
if (!query) {
|
|
2099
|
-
return false;
|
|
2100
|
-
}
|
|
2101
|
-
const candidates = [this.getAxName(node)];
|
|
2102
|
-
if (Array.isArray(node.properties)) {
|
|
2103
|
-
for (const prop of node.properties) {
|
|
2104
|
-
if (!prop || typeof prop !== "object") {
|
|
2105
|
-
continue;
|
|
2106
|
-
}
|
|
2107
|
-
const value = prop.value?.value;
|
|
2108
|
-
if (value === void 0 || value === null) {
|
|
2109
|
-
continue;
|
|
2110
|
-
}
|
|
2111
|
-
if (typeof value === "string") {
|
|
2112
|
-
candidates.push(value);
|
|
2113
|
-
} else if (typeof value === "number" || typeof value === "boolean") {
|
|
2114
|
-
candidates.push(String(value));
|
|
2115
|
-
}
|
|
2116
|
-
}
|
|
2117
|
-
}
|
|
2118
|
-
return candidates.some((text) => this.matchesTextValue(text, query));
|
|
2119
|
-
}
|
|
2120
|
-
collectHtmlEntries(html) {
|
|
2121
|
-
const entries = /* @__PURE__ */ new Map();
|
|
2122
|
-
const tagPattern = /<([a-zA-Z0-9-]+)([^>]*)>/g;
|
|
2123
|
-
let match;
|
|
2124
|
-
let index = 0;
|
|
2125
|
-
while ((match = tagPattern.exec(html)) && entries.size < 1e3) {
|
|
2126
|
-
const tag = match[1].toLowerCase();
|
|
2127
|
-
const attrs = match[2] ?? "";
|
|
2128
|
-
const idMatch = /\bid=["']([^"']+)["']/.exec(attrs);
|
|
2129
|
-
const classMatch = /\bclass=["']([^"']+)["']/.exec(attrs);
|
|
2130
|
-
const id = idMatch?.[1];
|
|
2131
|
-
const className = classMatch?.[1]?.split(/\s+/)[0];
|
|
2132
|
-
let key = tag;
|
|
2133
|
-
if (id) {
|
|
2134
|
-
key = `${tag}#${id}`;
|
|
2135
|
-
} else if (className) {
|
|
2136
|
-
key = `${tag}.${className}`;
|
|
2137
|
-
} else {
|
|
2138
|
-
key = `${tag}:nth-${index}`;
|
|
2139
|
-
}
|
|
2140
|
-
entries.set(key, attrs.trim());
|
|
2141
|
-
index += 1;
|
|
2142
|
-
}
|
|
2143
|
-
return entries;
|
|
2144
|
-
}
|
|
2145
|
-
collectAxEntries(snapshot) {
|
|
2146
|
-
const entries = /* @__PURE__ */ new Map();
|
|
2147
|
-
const nodes = this.getAxNodes(snapshot);
|
|
2148
|
-
if (nodes.length === 0) {
|
|
2149
|
-
return entries;
|
|
2150
|
-
}
|
|
2151
|
-
nodes.forEach((node, index) => {
|
|
2152
|
-
if (!node || typeof node !== "object") {
|
|
2153
|
-
return;
|
|
2154
|
-
}
|
|
2155
|
-
const record = node;
|
|
2156
|
-
const role = typeof record.role === "string" ? record.role : record.role?.value ?? "node";
|
|
2157
|
-
const name = typeof record.name === "string" ? record.name : record.name?.value ?? "";
|
|
2158
|
-
const nodeId = record.nodeId ?? (record.backendDOMNodeId !== void 0 ? String(record.backendDOMNodeId) : void 0);
|
|
2159
|
-
const key = nodeId ? `node-${nodeId}` : `${role}:${name}:${index}`;
|
|
2160
|
-
entries.set(key, `${role}:${name}`);
|
|
2161
|
-
});
|
|
2162
|
-
return entries;
|
|
2504
|
+
async performanceMetrics(input) {
|
|
2505
|
+
this.requireSession(input.sessionId);
|
|
2506
|
+
const selection = await this.resolveTab(input.targetHint);
|
|
2507
|
+
await this.debuggerCommand(selection.tabId, "Performance.enable", {});
|
|
2508
|
+
const result = await this.debuggerCommand(
|
|
2509
|
+
selection.tabId,
|
|
2510
|
+
"Performance.getMetrics",
|
|
2511
|
+
{}
|
|
2512
|
+
);
|
|
2513
|
+
const metrics = Array.isArray(result?.metrics) ? result.metrics.map((metric) => ({
|
|
2514
|
+
name: metric.name,
|
|
2515
|
+
value: metric.value
|
|
2516
|
+
})) : [];
|
|
2517
|
+
const output = { metrics, warnings: selection.warnings };
|
|
2518
|
+
this.markInspectConnected(input.sessionId);
|
|
2519
|
+
return output;
|
|
2163
2520
|
}
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
const
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2521
|
+
async screenshot(input) {
|
|
2522
|
+
this.requireSession(input.sessionId);
|
|
2523
|
+
const selection = await this.resolveTab(input.targetHint);
|
|
2524
|
+
const format = input.format ?? "png";
|
|
2525
|
+
const writeArtifact = async (data2) => {
|
|
2526
|
+
try {
|
|
2527
|
+
const rootDir = await ensureArtifactRootDir(input.sessionId);
|
|
2528
|
+
const artifactId = (0, import_crypto3.randomUUID)();
|
|
2529
|
+
const extension = format === "jpeg" ? "jpg" : format;
|
|
2530
|
+
const filePath = import_node_path2.default.join(
|
|
2531
|
+
rootDir,
|
|
2532
|
+
`screenshot-${artifactId}.${extension}`
|
|
2533
|
+
);
|
|
2534
|
+
await (0, import_promises2.writeFile)(filePath, Buffer.from(data2, "base64"));
|
|
2535
|
+
const mime = format === "jpeg" ? "image/jpeg" : `image/${format}`;
|
|
2536
|
+
const output = {
|
|
2537
|
+
artifact_id: artifactId,
|
|
2538
|
+
path: filePath,
|
|
2539
|
+
mime
|
|
2540
|
+
};
|
|
2541
|
+
this.markInspectConnected(input.sessionId);
|
|
2542
|
+
return output;
|
|
2543
|
+
} catch {
|
|
2544
|
+
const error = new InspectError(
|
|
2545
|
+
"ARTIFACT_IO_ERROR",
|
|
2546
|
+
"Failed to write screenshot file."
|
|
2547
|
+
);
|
|
2548
|
+
this.recordError(error);
|
|
2549
|
+
throw error;
|
|
2171
2550
|
}
|
|
2172
|
-
|
|
2173
|
-
|
|
2551
|
+
};
|
|
2552
|
+
if (input.selector) {
|
|
2553
|
+
if (!this.extensionBridge?.request) {
|
|
2554
|
+
const error = new InspectError(
|
|
2555
|
+
"NOT_SUPPORTED",
|
|
2556
|
+
"Element screenshots require an extension that supports drive.screenshot."
|
|
2557
|
+
);
|
|
2558
|
+
this.recordError(error);
|
|
2559
|
+
throw error;
|
|
2174
2560
|
}
|
|
2175
|
-
const
|
|
2176
|
-
|
|
2177
|
-
|
|
2561
|
+
const response = await this.extensionBridge.request(
|
|
2562
|
+
"drive.screenshot",
|
|
2563
|
+
{
|
|
2564
|
+
tab_id: selection.tabId,
|
|
2565
|
+
mode: "element",
|
|
2566
|
+
selector: input.selector,
|
|
2567
|
+
format,
|
|
2568
|
+
...typeof input.quality === "number" ? { quality: input.quality } : {}
|
|
2569
|
+
},
|
|
2570
|
+
12e4
|
|
2571
|
+
);
|
|
2572
|
+
if (response.status === "error") {
|
|
2573
|
+
const error = new InspectError(
|
|
2574
|
+
response.error?.code ?? "INSPECT_UNAVAILABLE",
|
|
2575
|
+
response.error?.message ?? "Failed to capture element screenshot.",
|
|
2576
|
+
{
|
|
2577
|
+
retryable: response.error?.retryable ?? false,
|
|
2578
|
+
...response.error?.details ? { details: response.error.details } : {}
|
|
2579
|
+
}
|
|
2580
|
+
);
|
|
2581
|
+
this.recordError(error);
|
|
2582
|
+
throw error;
|
|
2178
2583
|
}
|
|
2179
|
-
const
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
return refs;
|
|
2185
|
-
}
|
|
2186
|
-
async applySnapshotRefs(tabId, refs) {
|
|
2187
|
-
const warnings = [];
|
|
2188
|
-
await this.debuggerCommand(tabId, "DOM.enable", {});
|
|
2189
|
-
await this.debuggerCommand(tabId, "Runtime.enable", {});
|
|
2190
|
-
try {
|
|
2191
|
-
await this.clearSnapshotRefs(tabId);
|
|
2192
|
-
} catch {
|
|
2193
|
-
warnings.push("Failed to clear prior snapshot refs.");
|
|
2194
|
-
}
|
|
2195
|
-
if (refs.size === 0) {
|
|
2196
|
-
return warnings;
|
|
2197
|
-
}
|
|
2198
|
-
let applied = 0;
|
|
2199
|
-
for (const [backendNodeId, ref] of refs) {
|
|
2200
|
-
if (applied >= MAX_REF_ASSIGNMENTS) {
|
|
2201
|
-
warnings.push(
|
|
2202
|
-
`Snapshot refs truncated at ${MAX_REF_ASSIGNMENTS} elements.`
|
|
2584
|
+
const result2 = response.result;
|
|
2585
|
+
if (!result2?.data_base64 || typeof result2.data_base64 !== "string") {
|
|
2586
|
+
const error = new InspectError(
|
|
2587
|
+
"INSPECT_UNAVAILABLE",
|
|
2588
|
+
"Failed to capture element screenshot."
|
|
2203
2589
|
);
|
|
2204
|
-
|
|
2590
|
+
this.recordError(error);
|
|
2591
|
+
throw error;
|
|
2205
2592
|
}
|
|
2593
|
+
return await writeArtifact(result2.data_base64);
|
|
2594
|
+
}
|
|
2595
|
+
if (input.target === "full" && this.extensionBridge?.request) {
|
|
2206
2596
|
try {
|
|
2207
|
-
const
|
|
2208
|
-
|
|
2209
|
-
"DOM.describeNode",
|
|
2597
|
+
const response = await this.extensionBridge.request(
|
|
2598
|
+
"drive.screenshot",
|
|
2210
2599
|
{
|
|
2211
|
-
|
|
2212
|
-
|
|
2600
|
+
tab_id: selection.tabId,
|
|
2601
|
+
mode: "full_page",
|
|
2602
|
+
format,
|
|
2603
|
+
...typeof input.quality === "number" ? { quality: input.quality } : {}
|
|
2604
|
+
},
|
|
2605
|
+
12e4
|
|
2213
2606
|
);
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2607
|
+
if (response.status === "error") {
|
|
2608
|
+
const error = new InspectError(
|
|
2609
|
+
response.error?.code ?? "INSPECT_UNAVAILABLE",
|
|
2610
|
+
response.error?.message ?? "Failed to capture full page screenshot.",
|
|
2611
|
+
{
|
|
2612
|
+
retryable: response.error?.retryable ?? false,
|
|
2613
|
+
...response.error?.details ? { details: response.error.details } : {}
|
|
2614
|
+
}
|
|
2615
|
+
);
|
|
2616
|
+
this.recordError(error);
|
|
2617
|
+
throw error;
|
|
2220
2618
|
}
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2619
|
+
const result2 = response.result;
|
|
2620
|
+
if (!result2?.data_base64 || typeof result2.data_base64 !== "string") {
|
|
2621
|
+
const error = new InspectError(
|
|
2622
|
+
"INSPECT_UNAVAILABLE",
|
|
2623
|
+
"Failed to capture full page screenshot."
|
|
2624
|
+
);
|
|
2625
|
+
this.recordError(error);
|
|
2626
|
+
throw error;
|
|
2627
|
+
}
|
|
2628
|
+
return await writeArtifact(result2.data_base64);
|
|
2629
|
+
} catch (error) {
|
|
2630
|
+
if (error instanceof InspectError) {
|
|
2631
|
+
const code = String(error.code);
|
|
2632
|
+
if (![
|
|
2633
|
+
"NOT_SUPPORTED",
|
|
2634
|
+
"NOT_IMPLEMENTED",
|
|
2635
|
+
"INSPECT_UNAVAILABLE"
|
|
2636
|
+
].includes(code)) {
|
|
2637
|
+
throw error;
|
|
2638
|
+
}
|
|
2230
2639
|
}
|
|
2231
2640
|
}
|
|
2232
2641
|
}
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
});
|
|
2241
|
-
}
|
|
2242
|
-
async resolveNodeIdForSelector(tabId, selector) {
|
|
2243
|
-
await this.debuggerCommand(tabId, "DOM.enable", {});
|
|
2244
|
-
const document = await this.debuggerCommand(tabId, "DOM.getDocument", {
|
|
2245
|
-
depth: 1
|
|
2246
|
-
});
|
|
2247
|
-
const rootNodeId = document.root?.nodeId;
|
|
2248
|
-
if (typeof rootNodeId !== "number") {
|
|
2249
|
-
return { warnings: ["Failed to resolve DOM root for selector."] };
|
|
2642
|
+
await this.debuggerCommand(selection.tabId, "Page.enable", {});
|
|
2643
|
+
let captureParams = {
|
|
2644
|
+
format,
|
|
2645
|
+
fromSurface: true
|
|
2646
|
+
};
|
|
2647
|
+
if (format !== "png" && typeof input.quality === "number") {
|
|
2648
|
+
captureParams = { ...captureParams, quality: input.quality };
|
|
2250
2649
|
}
|
|
2251
|
-
|
|
2252
|
-
const
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2650
|
+
if (input.target === "full") {
|
|
2651
|
+
const layout = await this.debuggerCommand(
|
|
2652
|
+
selection.tabId,
|
|
2653
|
+
"Page.getLayoutMetrics",
|
|
2654
|
+
{}
|
|
2655
|
+
);
|
|
2656
|
+
const contentSize = layout?.contentSize;
|
|
2657
|
+
if (contentSize) {
|
|
2658
|
+
captureParams = {
|
|
2659
|
+
...captureParams,
|
|
2660
|
+
clip: {
|
|
2661
|
+
x: 0,
|
|
2662
|
+
y: 0,
|
|
2663
|
+
width: contentSize.width,
|
|
2664
|
+
height: contentSize.height,
|
|
2665
|
+
scale: 1
|
|
2666
|
+
}
|
|
2667
|
+
};
|
|
2668
|
+
} else {
|
|
2669
|
+
captureParams = { ...captureParams, captureBeyondViewport: true };
|
|
2264
2670
|
}
|
|
2265
|
-
return { warnings: ["Selector query failed."] };
|
|
2266
2671
|
}
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
expression,
|
|
2275
|
-
returnByValue: true,
|
|
2276
|
-
awaitPromise: true
|
|
2277
|
-
});
|
|
2278
|
-
if (result && typeof result === "object" && "exceptionDetails" in result) {
|
|
2672
|
+
const result = await this.debuggerCommand(
|
|
2673
|
+
selection.tabId,
|
|
2674
|
+
"Page.captureScreenshot",
|
|
2675
|
+
captureParams
|
|
2676
|
+
);
|
|
2677
|
+
const data = result.data;
|
|
2678
|
+
if (!data) {
|
|
2279
2679
|
const error = new InspectError(
|
|
2280
|
-
"
|
|
2281
|
-
"Failed to
|
|
2680
|
+
"INSPECT_UNAVAILABLE",
|
|
2681
|
+
"Failed to capture screenshot.",
|
|
2282
2682
|
{ retryable: false }
|
|
2283
2683
|
);
|
|
2284
2684
|
this.recordError(error);
|
|
2285
2685
|
throw error;
|
|
2286
2686
|
}
|
|
2287
|
-
return
|
|
2288
|
-
|
|
2289
|
-
|
|
2687
|
+
return await writeArtifact(data);
|
|
2688
|
+
}
|
|
2689
|
+
ensureDebugger() {
|
|
2690
|
+
if (!this.debugger) {
|
|
2691
|
+
const error = this.buildUnavailableError();
|
|
2692
|
+
this.recordError(error);
|
|
2693
|
+
throw error;
|
|
2694
|
+
}
|
|
2695
|
+
return this.debugger;
|
|
2696
|
+
}
|
|
2697
|
+
async resolveTab(hint) {
|
|
2698
|
+
if (!this.extensionBridge || !this.extensionBridge.isConnected()) {
|
|
2699
|
+
const error = new InspectError(
|
|
2700
|
+
"EXTENSION_DISCONNECTED",
|
|
2701
|
+
"Extension is not connected.",
|
|
2702
|
+
{ retryable: true }
|
|
2703
|
+
);
|
|
2704
|
+
this.recordError(error);
|
|
2705
|
+
throw error;
|
|
2706
|
+
}
|
|
2707
|
+
const tabs = this.extensionBridge.getStatus().tabs ?? [];
|
|
2708
|
+
if (!Array.isArray(tabs) || tabs.length === 0) {
|
|
2709
|
+
const error = new InspectError(
|
|
2710
|
+
"TAB_NOT_FOUND",
|
|
2711
|
+
"No tabs available to inspect."
|
|
2712
|
+
);
|
|
2713
|
+
this.recordError(error);
|
|
2714
|
+
throw error;
|
|
2715
|
+
}
|
|
2716
|
+
const candidates = tabs.map((tab2) => ({
|
|
2717
|
+
id: String(tab2.tab_id),
|
|
2718
|
+
url: tab2.url ?? "",
|
|
2719
|
+
title: tab2.title,
|
|
2720
|
+
lastSeenAt: tab2.last_active_at ? Date.parse(tab2.last_active_at) : void 0
|
|
2721
|
+
}));
|
|
2722
|
+
const ranked = pickBestTarget(candidates, hint);
|
|
2723
|
+
if (!ranked) {
|
|
2724
|
+
const error = new InspectError("TAB_NOT_FOUND", "No matching tab found.");
|
|
2725
|
+
this.recordError(error);
|
|
2726
|
+
throw error;
|
|
2727
|
+
}
|
|
2728
|
+
const tabId = Number(ranked.candidate.id);
|
|
2729
|
+
if (!Number.isFinite(tabId)) {
|
|
2730
|
+
const error = new InspectError(
|
|
2731
|
+
"TAB_NOT_FOUND",
|
|
2732
|
+
"Resolved tab id is invalid."
|
|
2733
|
+
);
|
|
2734
|
+
this.recordError(error);
|
|
2735
|
+
throw error;
|
|
2736
|
+
}
|
|
2737
|
+
const tab = tabs.find((entry) => entry.tab_id === tabId) ?? tabs[0];
|
|
2738
|
+
const warnings = [];
|
|
2739
|
+
if (!hint) {
|
|
2740
|
+
warnings.push("No target hint provided; using the most recent tab.");
|
|
2741
|
+
} else if (ranked.score < 20) {
|
|
2742
|
+
warnings.push("Weak target match; using best available tab.");
|
|
2743
|
+
}
|
|
2744
|
+
return {
|
|
2745
|
+
tabId,
|
|
2746
|
+
tab,
|
|
2747
|
+
warnings: warnings.length > 0 ? warnings : void 0
|
|
2748
|
+
};
|
|
2749
|
+
}
|
|
2750
|
+
async enableConsole(tabId) {
|
|
2751
|
+
await this.debuggerCommand(tabId, "Runtime.enable", {});
|
|
2752
|
+
await this.debuggerCommand(tabId, "Log.enable", {});
|
|
2753
|
+
}
|
|
2754
|
+
async enableNetwork(tabId) {
|
|
2755
|
+
await this.debuggerCommand(tabId, "Network.enable", {});
|
|
2756
|
+
}
|
|
2757
|
+
async enableAccessibility(tabId) {
|
|
2758
|
+
await this.debuggerCommand(tabId, "Accessibility.enable", {});
|
|
2290
2759
|
}
|
|
2291
2760
|
async debuggerCommand(tabId, method, params, timeoutMs) {
|
|
2292
2761
|
const debuggerBridge = this.ensureDebugger();
|
|
@@ -2323,319 +2792,6 @@ var InspectService = class {
|
|
|
2323
2792
|
details: error.details
|
|
2324
2793
|
});
|
|
2325
2794
|
}
|
|
2326
|
-
toConsoleEntry(event) {
|
|
2327
|
-
const params = event.params ?? {};
|
|
2328
|
-
switch (event.method) {
|
|
2329
|
-
case "Runtime.consoleAPICalled": {
|
|
2330
|
-
const rawArgs = Array.isArray(params.args) ? params.args : [];
|
|
2331
|
-
const text = rawArgs.map((arg) => this.stringifyRemoteObject(arg)).join(" ");
|
|
2332
|
-
const level = String(params.type ?? "log");
|
|
2333
|
-
const stack = this.toStackFrames(
|
|
2334
|
-
params.stackTrace
|
|
2335
|
-
);
|
|
2336
|
-
const args = rawArgs.map((arg) => this.toRemoteObjectSummary(arg)).filter(
|
|
2337
|
-
(entry) => Boolean(entry)
|
|
2338
|
-
);
|
|
2339
|
-
return {
|
|
2340
|
-
level,
|
|
2341
|
-
text,
|
|
2342
|
-
timestamp: event.timestamp,
|
|
2343
|
-
...stack && stack.length > 0 ? { stack } : {},
|
|
2344
|
-
...args.length > 0 ? { args } : {}
|
|
2345
|
-
};
|
|
2346
|
-
}
|
|
2347
|
-
case "Runtime.exceptionThrown": {
|
|
2348
|
-
const details = params.exceptionDetails;
|
|
2349
|
-
const exception = this.toRemoteObjectSummary(details?.exception);
|
|
2350
|
-
const stack = this.toStackFrames(details?.stackTrace);
|
|
2351
|
-
const source = this.toSourceLocation({
|
|
2352
|
-
url: details?.url,
|
|
2353
|
-
lineNumber: details?.lineNumber,
|
|
2354
|
-
columnNumber: details?.columnNumber
|
|
2355
|
-
}) ?? // If the top frame exists, treat it as the source.
|
|
2356
|
-
(stack && stack.length > 0 ? {
|
|
2357
|
-
url: stack[0].url,
|
|
2358
|
-
line: stack[0].line,
|
|
2359
|
-
column: stack[0].column
|
|
2360
|
-
} : void 0);
|
|
2361
|
-
const baseText = typeof details?.text === "string" && details.text.trim().length > 0 ? details.text : "Uncaught exception";
|
|
2362
|
-
const exceptionDesc = typeof exception?.description === "string" && exception.description.trim().length > 0 ? exception.description : void 0;
|
|
2363
|
-
const text = baseText === "Uncaught" && exceptionDesc ? `Uncaught: ${exceptionDesc}` : baseText;
|
|
2364
|
-
return {
|
|
2365
|
-
level: "error",
|
|
2366
|
-
text,
|
|
2367
|
-
timestamp: event.timestamp,
|
|
2368
|
-
...source ? { source } : {},
|
|
2369
|
-
...stack && stack.length > 0 ? { stack } : {},
|
|
2370
|
-
...exception ? { exception } : {}
|
|
2371
|
-
};
|
|
2372
|
-
}
|
|
2373
|
-
case "Log.entryAdded": {
|
|
2374
|
-
const entry = params.entry;
|
|
2375
|
-
if (!entry) {
|
|
2376
|
-
return null;
|
|
2377
|
-
}
|
|
2378
|
-
const stack = this.toStackFrames(entry.stackTrace);
|
|
2379
|
-
const source = this.toSourceLocation({
|
|
2380
|
-
url: entry.url,
|
|
2381
|
-
lineNumber: entry.lineNumber,
|
|
2382
|
-
columnNumber: void 0
|
|
2383
|
-
});
|
|
2384
|
-
return {
|
|
2385
|
-
level: entry.level ?? "log",
|
|
2386
|
-
text: entry.text ?? "",
|
|
2387
|
-
timestamp: event.timestamp,
|
|
2388
|
-
...source ? { source } : {},
|
|
2389
|
-
...stack && stack.length > 0 ? { stack } : {}
|
|
2390
|
-
};
|
|
2391
|
-
}
|
|
2392
|
-
default:
|
|
2393
|
-
return null;
|
|
2394
|
-
}
|
|
2395
|
-
}
|
|
2396
|
-
toSourceLocation(input) {
|
|
2397
|
-
const url = typeof input.url === "string" && input.url.length > 0 ? input.url : void 0;
|
|
2398
|
-
const line = typeof input.lineNumber === "number" && Number.isFinite(input.lineNumber) ? Math.max(1, Math.floor(input.lineNumber) + 1) : void 0;
|
|
2399
|
-
const column = typeof input.columnNumber === "number" && Number.isFinite(input.columnNumber) ? Math.max(1, Math.floor(input.columnNumber) + 1) : void 0;
|
|
2400
|
-
if (!url && !line && !column) {
|
|
2401
|
-
return void 0;
|
|
2402
|
-
}
|
|
2403
|
-
return {
|
|
2404
|
-
...url ? { url } : {},
|
|
2405
|
-
...line ? { line } : {},
|
|
2406
|
-
...column ? { column } : {}
|
|
2407
|
-
};
|
|
2408
|
-
}
|
|
2409
|
-
toStackFrames(stackTrace) {
|
|
2410
|
-
const frames = [];
|
|
2411
|
-
const collect = (trace) => {
|
|
2412
|
-
if (!trace || typeof trace !== "object") {
|
|
2413
|
-
return;
|
|
2414
|
-
}
|
|
2415
|
-
const callFrames = trace.callFrames;
|
|
2416
|
-
if (Array.isArray(callFrames)) {
|
|
2417
|
-
for (const frame of callFrames) {
|
|
2418
|
-
if (!frame || typeof frame !== "object") {
|
|
2419
|
-
continue;
|
|
2420
|
-
}
|
|
2421
|
-
const functionName = typeof frame.functionName === "string" ? String(frame.functionName) : void 0;
|
|
2422
|
-
const url = typeof frame.url === "string" ? String(frame.url) : void 0;
|
|
2423
|
-
const lineNumber = frame.lineNumber;
|
|
2424
|
-
const columnNumber = frame.columnNumber;
|
|
2425
|
-
const loc = this.toSourceLocation({ url, lineNumber, columnNumber });
|
|
2426
|
-
frames.push({
|
|
2427
|
-
...functionName ? { functionName } : {},
|
|
2428
|
-
...loc?.url ? { url: loc.url } : {},
|
|
2429
|
-
...loc?.line ? { line: loc.line } : {},
|
|
2430
|
-
...loc?.column ? { column: loc.column } : {}
|
|
2431
|
-
});
|
|
2432
|
-
if (frames.length >= 50) {
|
|
2433
|
-
return;
|
|
2434
|
-
}
|
|
2435
|
-
}
|
|
2436
|
-
}
|
|
2437
|
-
const parent = trace.parent;
|
|
2438
|
-
if (frames.length < 50 && parent) {
|
|
2439
|
-
collect(parent);
|
|
2440
|
-
}
|
|
2441
|
-
};
|
|
2442
|
-
collect(stackTrace);
|
|
2443
|
-
return frames.length > 0 ? frames : void 0;
|
|
2444
|
-
}
|
|
2445
|
-
toRemoteObjectSummary(obj) {
|
|
2446
|
-
if (!obj || typeof obj !== "object") {
|
|
2447
|
-
return void 0;
|
|
2448
|
-
}
|
|
2449
|
-
const raw = obj;
|
|
2450
|
-
const type = typeof raw.type === "string" ? raw.type : void 0;
|
|
2451
|
-
const subtype = typeof raw.subtype === "string" ? raw.subtype : void 0;
|
|
2452
|
-
const description = typeof raw.description === "string" ? raw.description : void 0;
|
|
2453
|
-
const unserializableValue = typeof raw.unserializableValue === "string" ? raw.unserializableValue : void 0;
|
|
2454
|
-
const out = {};
|
|
2455
|
-
if (type) out.type = type;
|
|
2456
|
-
if (subtype) out.subtype = subtype;
|
|
2457
|
-
if (description) out.description = description;
|
|
2458
|
-
if (raw.value !== void 0) out.value = raw.value;
|
|
2459
|
-
if (unserializableValue) out.unserializableValue = unserializableValue;
|
|
2460
|
-
return Object.keys(out).length > 0 ? out : void 0;
|
|
2461
|
-
}
|
|
2462
|
-
stringifyRemoteObject(value) {
|
|
2463
|
-
if (!value || typeof value !== "object") {
|
|
2464
|
-
return String(value ?? "");
|
|
2465
|
-
}
|
|
2466
|
-
const obj = value;
|
|
2467
|
-
if (obj.unserializableValue) {
|
|
2468
|
-
return obj.unserializableValue;
|
|
2469
|
-
}
|
|
2470
|
-
if (obj.value !== void 0) {
|
|
2471
|
-
try {
|
|
2472
|
-
return typeof obj.value === "string" ? obj.value : JSON.stringify(obj.value);
|
|
2473
|
-
} catch {
|
|
2474
|
-
return String(obj.value);
|
|
2475
|
-
}
|
|
2476
|
-
}
|
|
2477
|
-
if (obj.description) {
|
|
2478
|
-
return obj.description;
|
|
2479
|
-
}
|
|
2480
|
-
return obj.type ?? "";
|
|
2481
|
-
}
|
|
2482
|
-
buildHar(events, title) {
|
|
2483
|
-
const requests = /* @__PURE__ */ new Map();
|
|
2484
|
-
const toTimestamp = (event, fallback) => {
|
|
2485
|
-
const raw = event.params?.wallTime;
|
|
2486
|
-
if (typeof raw === "number") {
|
|
2487
|
-
return raw * 1e3;
|
|
2488
|
-
}
|
|
2489
|
-
const ts = event.params?.timestamp;
|
|
2490
|
-
if (typeof ts === "number") {
|
|
2491
|
-
return ts * 1e3;
|
|
2492
|
-
}
|
|
2493
|
-
const parsed = Date.parse(event.timestamp);
|
|
2494
|
-
if (Number.isFinite(parsed)) {
|
|
2495
|
-
return parsed;
|
|
2496
|
-
}
|
|
2497
|
-
return fallback ?? Date.now();
|
|
2498
|
-
};
|
|
2499
|
-
for (const event of events) {
|
|
2500
|
-
const params = event.params ?? {};
|
|
2501
|
-
switch (event.method) {
|
|
2502
|
-
case "Network.requestWillBeSent": {
|
|
2503
|
-
const requestId = String(
|
|
2504
|
-
params.requestId
|
|
2505
|
-
);
|
|
2506
|
-
if (!requestId) {
|
|
2507
|
-
break;
|
|
2508
|
-
}
|
|
2509
|
-
const request = params.request;
|
|
2510
|
-
const record = {
|
|
2511
|
-
id: requestId,
|
|
2512
|
-
url: request?.url,
|
|
2513
|
-
method: request?.method,
|
|
2514
|
-
requestHeaders: request?.headers ?? {},
|
|
2515
|
-
startTime: toTimestamp(event)
|
|
2516
|
-
};
|
|
2517
|
-
requests.set(requestId, record);
|
|
2518
|
-
break;
|
|
2519
|
-
}
|
|
2520
|
-
case "Network.responseReceived": {
|
|
2521
|
-
const requestId = String(
|
|
2522
|
-
params.requestId
|
|
2523
|
-
);
|
|
2524
|
-
if (!requestId) {
|
|
2525
|
-
break;
|
|
2526
|
-
}
|
|
2527
|
-
const response = params.response;
|
|
2528
|
-
const record = requests.get(requestId) ?? { id: requestId };
|
|
2529
|
-
record.status = response?.status;
|
|
2530
|
-
record.statusText = response?.statusText;
|
|
2531
|
-
record.mimeType = response?.mimeType;
|
|
2532
|
-
record.responseHeaders = response?.headers ?? {};
|
|
2533
|
-
record.protocol = response?.protocol;
|
|
2534
|
-
record.startTime = record.startTime ?? toTimestamp(event);
|
|
2535
|
-
requests.set(requestId, record);
|
|
2536
|
-
break;
|
|
2537
|
-
}
|
|
2538
|
-
case "Network.loadingFinished": {
|
|
2539
|
-
const requestId = String(
|
|
2540
|
-
params.requestId
|
|
2541
|
-
);
|
|
2542
|
-
if (!requestId) {
|
|
2543
|
-
break;
|
|
2544
|
-
}
|
|
2545
|
-
const record = requests.get(requestId) ?? { id: requestId };
|
|
2546
|
-
record.encodedDataLength = params.encodedDataLength;
|
|
2547
|
-
record.endTime = toTimestamp(event, record.startTime);
|
|
2548
|
-
requests.set(requestId, record);
|
|
2549
|
-
break;
|
|
2550
|
-
}
|
|
2551
|
-
case "Network.loadingFailed": {
|
|
2552
|
-
const requestId = String(
|
|
2553
|
-
params.requestId
|
|
2554
|
-
);
|
|
2555
|
-
if (!requestId) {
|
|
2556
|
-
break;
|
|
2557
|
-
}
|
|
2558
|
-
const record = requests.get(requestId) ?? { id: requestId };
|
|
2559
|
-
record.endTime = toTimestamp(event, record.startTime);
|
|
2560
|
-
requests.set(requestId, record);
|
|
2561
|
-
break;
|
|
2562
|
-
}
|
|
2563
|
-
default:
|
|
2564
|
-
break;
|
|
2565
|
-
}
|
|
2566
|
-
}
|
|
2567
|
-
const entries = Array.from(requests.values()).map((record) => {
|
|
2568
|
-
const started = record.startTime ?? Date.now();
|
|
2569
|
-
const ended = record.endTime ?? started;
|
|
2570
|
-
const time = Math.max(0, ended - started);
|
|
2571
|
-
const url = record.url ?? "";
|
|
2572
|
-
const queryString = [];
|
|
2573
|
-
try {
|
|
2574
|
-
const parsed = new URL(url);
|
|
2575
|
-
parsed.searchParams.forEach((value, name) => {
|
|
2576
|
-
queryString.push({ name, value });
|
|
2577
|
-
});
|
|
2578
|
-
} catch {
|
|
2579
|
-
}
|
|
2580
|
-
return {
|
|
2581
|
-
pageref: "page_0",
|
|
2582
|
-
startedDateTime: new Date(started).toISOString(),
|
|
2583
|
-
time,
|
|
2584
|
-
request: {
|
|
2585
|
-
method: record.method ?? "GET",
|
|
2586
|
-
url,
|
|
2587
|
-
httpVersion: record.protocol ?? "HTTP/1.1",
|
|
2588
|
-
cookies: [],
|
|
2589
|
-
headers: [],
|
|
2590
|
-
queryString,
|
|
2591
|
-
headersSize: -1,
|
|
2592
|
-
bodySize: -1
|
|
2593
|
-
},
|
|
2594
|
-
response: {
|
|
2595
|
-
status: record.status ?? 0,
|
|
2596
|
-
statusText: record.statusText ?? "",
|
|
2597
|
-
httpVersion: record.protocol ?? "HTTP/1.1",
|
|
2598
|
-
cookies: [],
|
|
2599
|
-
headers: [],
|
|
2600
|
-
redirectURL: "",
|
|
2601
|
-
headersSize: -1,
|
|
2602
|
-
bodySize: record.encodedDataLength ?? 0,
|
|
2603
|
-
content: {
|
|
2604
|
-
size: record.encodedDataLength ?? 0,
|
|
2605
|
-
mimeType: record.mimeType ?? ""
|
|
2606
|
-
}
|
|
2607
|
-
},
|
|
2608
|
-
cache: {},
|
|
2609
|
-
timings: {
|
|
2610
|
-
send: 0,
|
|
2611
|
-
wait: time,
|
|
2612
|
-
receive: 0
|
|
2613
|
-
}
|
|
2614
|
-
};
|
|
2615
|
-
});
|
|
2616
|
-
const startedDateTime = entries.length ? entries[0].startedDateTime : (/* @__PURE__ */ new Date()).toISOString();
|
|
2617
|
-
return {
|
|
2618
|
-
log: {
|
|
2619
|
-
version: "1.2",
|
|
2620
|
-
creator: {
|
|
2621
|
-
name: "browser-bridge",
|
|
2622
|
-
version: "0.0.0"
|
|
2623
|
-
},
|
|
2624
|
-
pages: [
|
|
2625
|
-
{
|
|
2626
|
-
id: "page_0",
|
|
2627
|
-
title: title ?? "page",
|
|
2628
|
-
startedDateTime,
|
|
2629
|
-
pageTimings: {
|
|
2630
|
-
onContentLoad: -1,
|
|
2631
|
-
onLoad: -1
|
|
2632
|
-
}
|
|
2633
|
-
}
|
|
2634
|
-
],
|
|
2635
|
-
entries
|
|
2636
|
-
}
|
|
2637
|
-
};
|
|
2638
|
-
}
|
|
2639
2795
|
markInspectConnected(sessionId) {
|
|
2640
2796
|
try {
|
|
2641
2797
|
const session = this.registry.require(sessionId);
|
|
@@ -2658,11 +2814,6 @@ var InspectService = class {
|
|
|
2658
2814
|
{ retryable: false }
|
|
2659
2815
|
);
|
|
2660
2816
|
}
|
|
2661
|
-
throwUnavailable() {
|
|
2662
|
-
const error = this.buildUnavailableError();
|
|
2663
|
-
this.recordError(error);
|
|
2664
|
-
throw error;
|
|
2665
|
-
}
|
|
2666
2817
|
requireSession(sessionId) {
|
|
2667
2818
|
try {
|
|
2668
2819
|
return this.registry.require(sessionId);
|
|
@@ -2797,6 +2948,11 @@ var DiagnosticReportSchema = import_zod2.z.object({
|
|
|
2797
2948
|
ok: import_zod2.z.boolean(),
|
|
2798
2949
|
session_id: import_zod2.z.string().optional(),
|
|
2799
2950
|
checks: import_zod2.z.array(DiagnosticCheckSchema).optional(),
|
|
2951
|
+
sessions: import_zod2.z.object({
|
|
2952
|
+
count: import_zod2.z.number().finite().optional(),
|
|
2953
|
+
max_age_ms: import_zod2.z.number().finite().optional(),
|
|
2954
|
+
max_idle_ms: import_zod2.z.number().finite().optional()
|
|
2955
|
+
}).optional(),
|
|
2800
2956
|
extension: import_zod2.z.object({
|
|
2801
2957
|
connected: import_zod2.z.boolean().optional(),
|
|
2802
2958
|
version: import_zod2.z.string().optional(),
|
|
@@ -3203,10 +3359,30 @@ var ArtifactsScreenshotInputSchema = import_zod2.z.object({
|
|
|
3203
3359
|
session_id: import_zod2.z.string().min(1),
|
|
3204
3360
|
target: import_zod2.z.enum(["viewport", "full"]).default("viewport"),
|
|
3205
3361
|
fullPage: import_zod2.z.boolean().default(false),
|
|
3362
|
+
selector: import_zod2.z.string().min(1).optional(),
|
|
3206
3363
|
format: import_zod2.z.enum(["png", "jpeg", "webp"]).default("png"),
|
|
3207
3364
|
quality: import_zod2.z.number().min(0).max(100).optional()
|
|
3208
3365
|
});
|
|
3209
3366
|
var ArtifactsScreenshotOutputSchema = ArtifactInfoSchema;
|
|
3367
|
+
var HealthCheckInputSchema = import_zod2.z.object({});
|
|
3368
|
+
var HealthCheckOutputSchema = import_zod2.z.object({
|
|
3369
|
+
started_at: import_zod2.z.string().min(1),
|
|
3370
|
+
uptime_ms: import_zod2.z.number().finite().nonnegative(),
|
|
3371
|
+
memory: import_zod2.z.object({
|
|
3372
|
+
rss: import_zod2.z.number().finite().nonnegative(),
|
|
3373
|
+
heapTotal: import_zod2.z.number().finite().nonnegative(),
|
|
3374
|
+
heapUsed: import_zod2.z.number().finite().nonnegative(),
|
|
3375
|
+
external: import_zod2.z.number().finite().nonnegative(),
|
|
3376
|
+
arrayBuffers: import_zod2.z.number().finite().nonnegative().optional()
|
|
3377
|
+
}).passthrough(),
|
|
3378
|
+
sessions: import_zod2.z.object({
|
|
3379
|
+
active: import_zod2.z.number().finite().nonnegative()
|
|
3380
|
+
}).passthrough(),
|
|
3381
|
+
extension: import_zod2.z.object({
|
|
3382
|
+
connected: import_zod2.z.boolean(),
|
|
3383
|
+
last_seen_at: import_zod2.z.string().min(1).optional()
|
|
3384
|
+
}).passthrough()
|
|
3385
|
+
}).passthrough();
|
|
3210
3386
|
var DiagnosticsDoctorInputSchema = import_zod2.z.object({
|
|
3211
3387
|
session_id: import_zod2.z.string().min(1).optional()
|
|
3212
3388
|
});
|
|
@@ -3262,6 +3438,7 @@ var registerArtifactsRoutes = (router, options = {}) => {
|
|
|
3262
3438
|
const result = await inspect.screenshot({
|
|
3263
3439
|
sessionId: input.session_id,
|
|
3264
3440
|
target,
|
|
3441
|
+
selector: input.selector,
|
|
3265
3442
|
format: input.format,
|
|
3266
3443
|
quality: input.quality,
|
|
3267
3444
|
targetHint: hint
|
|
@@ -3362,6 +3539,11 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
|
|
|
3362
3539
|
ok: checks.every((check) => check.ok),
|
|
3363
3540
|
session_id: sessionId,
|
|
3364
3541
|
checks,
|
|
3542
|
+
sessions: context.sessions ? {
|
|
3543
|
+
count: context.sessions.count,
|
|
3544
|
+
max_age_ms: context.sessions.maxAgeMs,
|
|
3545
|
+
max_idle_ms: context.sessions.maxIdleMs
|
|
3546
|
+
} : void 0,
|
|
3365
3547
|
extension: {
|
|
3366
3548
|
connected: extensionConnected,
|
|
3367
3549
|
last_seen_at: context.extension?.lastSeenAt
|
|
@@ -3393,7 +3575,44 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
|
|
|
3393
3575
|
};
|
|
3394
3576
|
|
|
3395
3577
|
// packages/core/src/routes/diagnostics.ts
|
|
3578
|
+
var PROCESS_STARTED_AT = new Date(
|
|
3579
|
+
Date.now() - Math.floor(process.uptime() * 1e3)
|
|
3580
|
+
).toISOString();
|
|
3396
3581
|
var registerDiagnosticsRoutes = (router, options = {}) => {
|
|
3582
|
+
router.post("/health_check", (req, res) => {
|
|
3583
|
+
const body = req.body ?? {};
|
|
3584
|
+
if (!isRecord(body)) {
|
|
3585
|
+
sendError(res, 400, {
|
|
3586
|
+
code: "INVALID_ARGUMENT",
|
|
3587
|
+
message: "Request body must be an object.",
|
|
3588
|
+
retryable: false
|
|
3589
|
+
});
|
|
3590
|
+
return;
|
|
3591
|
+
}
|
|
3592
|
+
const parsed = HealthCheckInputSchema.safeParse(body);
|
|
3593
|
+
if (!parsed.success) {
|
|
3594
|
+
const issue = parsed.error.issues[0];
|
|
3595
|
+
sendError(res, 400, {
|
|
3596
|
+
code: "INVALID_ARGUMENT",
|
|
3597
|
+
message: issue?.message ?? "Invalid health check request.",
|
|
3598
|
+
retryable: false,
|
|
3599
|
+
details: issue?.path.length ? { field: issue.path.map((part) => String(part)).join(".") } : void 0
|
|
3600
|
+
});
|
|
3601
|
+
return;
|
|
3602
|
+
}
|
|
3603
|
+
const sessionsActive = options.registry ? options.registry.list().length : 0;
|
|
3604
|
+
const extensionStatus = options.extensionBridge?.getStatus();
|
|
3605
|
+
sendResult(res, {
|
|
3606
|
+
started_at: PROCESS_STARTED_AT,
|
|
3607
|
+
uptime_ms: Math.floor(process.uptime() * 1e3),
|
|
3608
|
+
memory: process.memoryUsage(),
|
|
3609
|
+
sessions: { active: sessionsActive },
|
|
3610
|
+
extension: {
|
|
3611
|
+
connected: extensionStatus?.connected ?? false,
|
|
3612
|
+
...extensionStatus?.lastSeenAt ? { last_seen_at: extensionStatus.lastSeenAt } : {}
|
|
3613
|
+
}
|
|
3614
|
+
});
|
|
3615
|
+
});
|
|
3397
3616
|
router.post("/diagnostics/doctor", (req, res) => {
|
|
3398
3617
|
let sessionId;
|
|
3399
3618
|
if (req.body !== void 0) {
|
|
@@ -3426,6 +3645,26 @@ var registerDiagnosticsRoutes = (router, options = {}) => {
|
|
|
3426
3645
|
} catch {
|
|
3427
3646
|
}
|
|
3428
3647
|
}
|
|
3648
|
+
if (options.registry) {
|
|
3649
|
+
const now = Date.now();
|
|
3650
|
+
const sessions = options.registry.list();
|
|
3651
|
+
let maxAgeMs = 0;
|
|
3652
|
+
let maxIdleMs = 0;
|
|
3653
|
+
for (const session of sessions) {
|
|
3654
|
+
const ageMs = now - session.createdAt.getTime();
|
|
3655
|
+
const idleMs = now - session.lastAccessedAt.getTime();
|
|
3656
|
+
if (ageMs > maxAgeMs) {
|
|
3657
|
+
maxAgeMs = ageMs;
|
|
3658
|
+
}
|
|
3659
|
+
if (idleMs > maxIdleMs) {
|
|
3660
|
+
maxIdleMs = idleMs;
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
context.sessions = {
|
|
3664
|
+
count: sessions.length,
|
|
3665
|
+
...sessions.length > 0 ? { maxAgeMs, maxIdleMs } : {}
|
|
3666
|
+
};
|
|
3667
|
+
}
|
|
3429
3668
|
if (options.extensionBridge) {
|
|
3430
3669
|
const status = options.extensionBridge.getStatus();
|
|
3431
3670
|
context.extension = {
|
|
@@ -4250,6 +4489,29 @@ var resolveCorePort = (portOverride) => {
|
|
|
4250
4489
|
}
|
|
4251
4490
|
return 3210;
|
|
4252
4491
|
};
|
|
4492
|
+
var resolveSessionTtlMs = () => {
|
|
4493
|
+
const env = process.env.BROWSER_BRIDGE_SESSION_TTL_MS || process.env.BROWSER_VISION_SESSION_TTL_MS;
|
|
4494
|
+
if (env) {
|
|
4495
|
+
const parsed = Number(env);
|
|
4496
|
+
if (Number.isFinite(parsed) && parsed >= 0) {
|
|
4497
|
+
return parsed;
|
|
4498
|
+
}
|
|
4499
|
+
}
|
|
4500
|
+
return 60 * 60 * 1e3;
|
|
4501
|
+
};
|
|
4502
|
+
var resolveSessionCleanupIntervalMs = (ttlMs) => {
|
|
4503
|
+
const env = process.env.BROWSER_BRIDGE_SESSION_CLEANUP_INTERVAL_MS || process.env.BROWSER_VISION_SESSION_CLEANUP_INTERVAL_MS;
|
|
4504
|
+
if (env) {
|
|
4505
|
+
const parsed = Number(env);
|
|
4506
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
4507
|
+
return parsed;
|
|
4508
|
+
}
|
|
4509
|
+
}
|
|
4510
|
+
if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
|
|
4511
|
+
return 60 * 1e3;
|
|
4512
|
+
}
|
|
4513
|
+
return Math.min(60 * 1e3, Math.max(1e3, Math.floor(ttlMs / 2)));
|
|
4514
|
+
};
|
|
4253
4515
|
var startCoreServer = (options = {}) => {
|
|
4254
4516
|
const host = options.host ?? "127.0.0.1";
|
|
4255
4517
|
const port = resolveCorePort(options.port);
|
|
@@ -4262,6 +4524,19 @@ var startCoreServer = (options = {}) => {
|
|
|
4262
4524
|
server.listen(port, host, () => {
|
|
4263
4525
|
const address = server.address();
|
|
4264
4526
|
const resolvedPort = typeof address === "object" && address !== null ? address.port : port;
|
|
4527
|
+
const ttlMs = resolveSessionTtlMs();
|
|
4528
|
+
if (ttlMs > 0) {
|
|
4529
|
+
const intervalMs = resolveSessionCleanupIntervalMs(ttlMs);
|
|
4530
|
+
const timer = setInterval(() => {
|
|
4531
|
+
try {
|
|
4532
|
+
registry.cleanupIdleSessions(ttlMs);
|
|
4533
|
+
} catch (error) {
|
|
4534
|
+
console.warn("Session cleanup failed:", error);
|
|
4535
|
+
}
|
|
4536
|
+
}, intervalMs);
|
|
4537
|
+
timer.unref();
|
|
4538
|
+
server.on("close", () => clearInterval(timer));
|
|
4539
|
+
}
|
|
4265
4540
|
resolve({ app, registry, server, host, port: resolvedPort });
|
|
4266
4541
|
});
|
|
4267
4542
|
server.on("error", (error) => {
|
|
@@ -4702,6 +4977,16 @@ var TOOL_DEFINITIONS = [
|
|
|
4702
4977
|
corePath: "/artifacts/screenshot"
|
|
4703
4978
|
}
|
|
4704
4979
|
},
|
|
4980
|
+
{
|
|
4981
|
+
name: "health_check",
|
|
4982
|
+
config: {
|
|
4983
|
+
title: "Health Check",
|
|
4984
|
+
description: "Check server health including uptime, memory usage, active session count, and extension connection status.",
|
|
4985
|
+
inputSchema: HealthCheckInputSchema,
|
|
4986
|
+
outputSchema: envelope(HealthCheckOutputSchema),
|
|
4987
|
+
corePath: "/health_check"
|
|
4988
|
+
}
|
|
4989
|
+
},
|
|
4705
4990
|
{
|
|
4706
4991
|
name: "diagnostics.doctor",
|
|
4707
4992
|
config: {
|
|
@@ -4746,6 +5031,8 @@ var registerBrowserBridgeTools = (server, client) => {
|
|
|
4746
5031
|
// packages/mcp-adapter/src/server.ts
|
|
4747
5032
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
4748
5033
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
5034
|
+
var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
5035
|
+
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
4749
5036
|
var DEFAULT_SERVER_NAME = "browser-bridge";
|
|
4750
5037
|
var DEFAULT_SERVER_VERSION = "0.0.0";
|
|
4751
5038
|
var createMcpServer = (options = {}) => {
|