@flrande/bak-extension 0.6.15 → 0.6.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.bak-e2e-build-stamp +1 -1
- package/dist/background.global.js +304 -68
- package/dist/content.global.js +9 -3
- package/dist/manifest.json +1 -1
- package/package.json +2 -2
- package/src/background.ts +430 -209
- package/src/content.ts +9 -3
- package/src/dynamic-data-tools.ts +2 -2
- package/src/network-debugger.ts +90 -12
|
@@ -1 +1 @@
|
|
|
1
|
-
2026-03-
|
|
1
|
+
2026-03-15T05:31:19.065Z
|
|
@@ -302,7 +302,7 @@
|
|
|
302
302
|
detail: `Shared sample values: ${distinctOverlappingValues.join(", ")}`
|
|
303
303
|
});
|
|
304
304
|
}
|
|
305
|
-
const explicitReferenceHit = table.table.
|
|
305
|
+
const explicitReferenceHit = table.table.label.toLowerCase().includes(source.source.label.toLowerCase()) || (table.table.selector ?? "").toLowerCase().includes(source.source.label.toLowerCase()) || source.source.label.toLowerCase().includes(table.table.label.toLowerCase());
|
|
306
306
|
if (explicitReferenceHit) {
|
|
307
307
|
basis.push({
|
|
308
308
|
type: "explicitReference",
|
|
@@ -533,9 +533,6 @@
|
|
|
533
533
|
function shouldRedactHeader(name) {
|
|
534
534
|
return SENSITIVE_HEADER_PATTERNS.some((pattern) => pattern.test(name));
|
|
535
535
|
}
|
|
536
|
-
function containsRedactionMarker(raw) {
|
|
537
|
-
return typeof raw === "string" && raw.includes("[REDACTED");
|
|
538
|
-
}
|
|
539
536
|
function redactTransportText(raw) {
|
|
540
537
|
if (!raw) {
|
|
541
538
|
return "";
|
|
@@ -556,7 +553,7 @@
|
|
|
556
553
|
// package.json
|
|
557
554
|
var package_default = {
|
|
558
555
|
name: "@flrande/bak-extension",
|
|
559
|
-
version: "0.6.
|
|
556
|
+
version: "0.6.16",
|
|
560
557
|
type: "module",
|
|
561
558
|
scripts: {
|
|
562
559
|
build: "tsup src/background.ts src/content.ts src/popup.ts --format iife --out-dir dist --clean && node scripts/copy-assets.mjs",
|
|
@@ -664,6 +661,17 @@
|
|
|
664
661
|
const normalized = contentType.toLowerCase();
|
|
665
662
|
return normalized.startsWith("text/") || normalized.includes("json") || normalized.includes("javascript") || normalized.includes("xml") || normalized.includes("html") || normalized.includes("urlencoded") || normalized.includes("graphql");
|
|
666
663
|
}
|
|
664
|
+
function sanitizeEntry(entry) {
|
|
665
|
+
const { rawRequestHeaders, rawRequestBody, rawRequestBodyTruncated, ...publicEntry } = entry;
|
|
666
|
+
void rawRequestHeaders;
|
|
667
|
+
void rawRequestBody;
|
|
668
|
+
void rawRequestBodyTruncated;
|
|
669
|
+
return {
|
|
670
|
+
...publicEntry,
|
|
671
|
+
requestHeaders: typeof entry.requestHeaders === "object" && entry.requestHeaders !== null ? { ...entry.requestHeaders } : void 0,
|
|
672
|
+
responseHeaders: typeof entry.responseHeaders === "object" && entry.responseHeaders !== null ? { ...entry.responseHeaders } : void 0
|
|
673
|
+
};
|
|
674
|
+
}
|
|
667
675
|
function pushEntry(state, entry, requestId) {
|
|
668
676
|
state.entries.push(entry);
|
|
669
677
|
state.entriesById.set(entry.id, entry);
|
|
@@ -737,7 +745,8 @@
|
|
|
737
745
|
return;
|
|
738
746
|
}
|
|
739
747
|
const request = typeof params.request === "object" && params.request !== null ? params.request : {};
|
|
740
|
-
const
|
|
748
|
+
const rawHeaders = normalizeHeaders(request.headers);
|
|
749
|
+
const headers = redactHeaderMap(rawHeaders);
|
|
741
750
|
const truncatedRequest = truncateText(typeof request.postData === "string" ? request.postData : void 0, DEFAULT_BODY_BYTES);
|
|
742
751
|
const entry = {
|
|
743
752
|
id: `net_${tabId}_${requestId}`,
|
|
@@ -754,6 +763,9 @@
|
|
|
754
763
|
requestHeaders: headers,
|
|
755
764
|
requestBodyPreview: truncatedRequest.text ? redactTransportText(truncatedRequest.text) : void 0,
|
|
756
765
|
requestBodyTruncated: truncatedRequest.truncated,
|
|
766
|
+
rawRequestHeaders: rawHeaders,
|
|
767
|
+
rawRequestBody: typeof request.postData === "string" ? request.postData : void 0,
|
|
768
|
+
rawRequestBodyTruncated: false,
|
|
757
769
|
initiatorUrl: typeof params.initiator === "object" && params.initiator !== null && typeof params.initiator.url === "string" ? String(params.initiator.url) : void 0,
|
|
758
770
|
tabId,
|
|
759
771
|
source: "debugger"
|
|
@@ -857,12 +869,32 @@
|
|
|
857
869
|
function listNetworkEntries(tabId, filters = {}) {
|
|
858
870
|
const state = getState(tabId);
|
|
859
871
|
const limit = typeof filters.limit === "number" ? Math.max(1, Math.min(500, Math.floor(filters.limit))) : 50;
|
|
860
|
-
return state.entries.filter((entry) => entryMatchesFilters(entry, filters)).slice(-limit).reverse().map((entry) => (
|
|
872
|
+
return state.entries.filter((entry) => entryMatchesFilters(entry, filters)).slice(-limit).reverse().map((entry) => sanitizeEntry(entry));
|
|
861
873
|
}
|
|
862
874
|
function getNetworkEntry(tabId, id) {
|
|
863
875
|
const state = getState(tabId);
|
|
864
876
|
const entry = state.entriesById.get(id);
|
|
865
|
-
return entry ?
|
|
877
|
+
return entry ? sanitizeEntry(entry) : null;
|
|
878
|
+
}
|
|
879
|
+
function getReplayableNetworkRequest(tabId, id) {
|
|
880
|
+
const state = getState(tabId);
|
|
881
|
+
const entry = state.entriesById.get(id);
|
|
882
|
+
if (!entry) {
|
|
883
|
+
return null;
|
|
884
|
+
}
|
|
885
|
+
const publicEntry = sanitizeEntry(entry);
|
|
886
|
+
if (entry.rawRequestBodyTruncated === true) {
|
|
887
|
+
return {
|
|
888
|
+
entry: publicEntry,
|
|
889
|
+
degradedReason: "live replay unavailable because the captured request body was truncated in memory"
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
return {
|
|
893
|
+
entry: publicEntry,
|
|
894
|
+
headers: entry.rawRequestHeaders ? { ...entry.rawRequestHeaders } : void 0,
|
|
895
|
+
body: entry.rawRequestBody,
|
|
896
|
+
contentType: headerValue(entry.rawRequestHeaders, "content-type")
|
|
897
|
+
};
|
|
866
898
|
}
|
|
867
899
|
async function waitForNetworkEntry(tabId, filters = {}) {
|
|
868
900
|
const timeoutMs = typeof filters.timeoutMs === "number" ? Math.max(1, Math.floor(filters.timeoutMs)) : 5e3;
|
|
@@ -873,7 +905,7 @@
|
|
|
873
905
|
const nextState = getState(tabId);
|
|
874
906
|
const matched = nextState.entries.find((entry) => !seenIds.has(entry.id) && entryMatchesFilters(entry, filters));
|
|
875
907
|
if (matched) {
|
|
876
|
-
return
|
|
908
|
+
return sanitizeEntry(matched);
|
|
877
909
|
}
|
|
878
910
|
await new Promise((resolve) => setTimeout(resolve, 75));
|
|
879
911
|
}
|
|
@@ -883,14 +915,30 @@
|
|
|
883
915
|
};
|
|
884
916
|
}
|
|
885
917
|
function searchNetworkEntries(tabId, pattern, limit = 50) {
|
|
918
|
+
const state = getState(tabId);
|
|
886
919
|
const normalized = pattern.toLowerCase();
|
|
887
|
-
|
|
920
|
+
const matchedEntries = state.entries.filter((entry) => {
|
|
888
921
|
const headerText = JSON.stringify({
|
|
889
922
|
requestHeaders: entry.requestHeaders,
|
|
890
923
|
responseHeaders: entry.responseHeaders
|
|
891
924
|
}).toLowerCase();
|
|
892
925
|
return entry.url.toLowerCase().includes(normalized) || (entry.requestBodyPreview ?? "").toLowerCase().includes(normalized) || (entry.responseBodyPreview ?? "").toLowerCase().includes(normalized) || headerText.includes(normalized);
|
|
893
926
|
});
|
|
927
|
+
const scannedEntries = state.entries.filter((entry) => entryMatchesFilters(entry, {}));
|
|
928
|
+
const toCoverage = (entries, key, truncatedKey) => ({
|
|
929
|
+
full: entries.filter((entry) => typeof entry[key] === "string" && entry[truncatedKey] !== true).length,
|
|
930
|
+
partial: entries.filter((entry) => typeof entry[key] === "string" && entry[truncatedKey] === true).length,
|
|
931
|
+
none: entries.filter((entry) => typeof entry[key] !== "string").length
|
|
932
|
+
});
|
|
933
|
+
return {
|
|
934
|
+
entries: matchedEntries.slice(-Math.max(limit, 1)).reverse().map((entry) => sanitizeEntry(entry)),
|
|
935
|
+
scanned: scannedEntries.length,
|
|
936
|
+
matched: matchedEntries.length,
|
|
937
|
+
bodyCoverage: {
|
|
938
|
+
request: toCoverage(scannedEntries, "requestBodyPreview", "requestBodyTruncated"),
|
|
939
|
+
response: toCoverage(scannedEntries, "responseBodyPreview", "responseBodyTruncated")
|
|
940
|
+
}
|
|
941
|
+
};
|
|
894
942
|
}
|
|
895
943
|
function latestNetworkTimestamp(tabId) {
|
|
896
944
|
const entries = listNetworkEntries(tabId, { limit: MAX_ENTRIES });
|
|
@@ -2507,7 +2555,18 @@
|
|
|
2507
2555
|
await new Promise((resolve) => setTimeout(resolve, 80));
|
|
2508
2556
|
}
|
|
2509
2557
|
try {
|
|
2510
|
-
return
|
|
2558
|
+
return {
|
|
2559
|
+
captureStatus: "complete",
|
|
2560
|
+
imageData: await chrome.tabs.captureVisibleTab(tab.windowId, { format: "png" })
|
|
2561
|
+
};
|
|
2562
|
+
} catch (error) {
|
|
2563
|
+
return {
|
|
2564
|
+
captureStatus: "degraded",
|
|
2565
|
+
captureError: {
|
|
2566
|
+
code: "E_CAPTURE_FAILED",
|
|
2567
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2568
|
+
}
|
|
2569
|
+
};
|
|
2511
2570
|
} finally {
|
|
2512
2571
|
if (shouldSwitch && typeof activeTab?.id === "number") {
|
|
2513
2572
|
try {
|
|
@@ -2638,7 +2697,9 @@
|
|
|
2638
2697
|
contentType: typeof params.contentType === "string" ? params.contentType : void 0,
|
|
2639
2698
|
mode: params.mode === "json" ? "json" : "raw",
|
|
2640
2699
|
maxBytes: typeof params.maxBytes === "number" ? params.maxBytes : void 0,
|
|
2641
|
-
timeoutMs: typeof params.timeoutMs === "number" ? params.timeoutMs : void 0
|
|
2700
|
+
timeoutMs: typeof params.timeoutMs === "number" ? params.timeoutMs : void 0,
|
|
2701
|
+
fullResponse: params.fullResponse === true,
|
|
2702
|
+
auth: params.auth === "manual" || params.auth === "off" ? params.auth : "auto"
|
|
2642
2703
|
}
|
|
2643
2704
|
],
|
|
2644
2705
|
func: async (payload) => {
|
|
@@ -2761,6 +2822,69 @@
|
|
|
2761
2822
|
}
|
|
2762
2823
|
return { resolver: "lexical", value: readLexical() };
|
|
2763
2824
|
};
|
|
2825
|
+
const findHeaderName = (headers, name) => Object.keys(headers).find((key) => key.toLowerCase() === name.toLowerCase());
|
|
2826
|
+
const findCookieValue = (cookieString, name) => {
|
|
2827
|
+
const targetName = `${name}=`;
|
|
2828
|
+
for (const segment of cookieString.split(";")) {
|
|
2829
|
+
const trimmed = segment.trim();
|
|
2830
|
+
if (trimmed.toLowerCase().startsWith(targetName.toLowerCase())) {
|
|
2831
|
+
return trimmed.slice(targetName.length);
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
return void 0;
|
|
2835
|
+
};
|
|
2836
|
+
const buildJsonSummary = (value) => {
|
|
2837
|
+
const rowsCandidate = (() => {
|
|
2838
|
+
if (Array.isArray(value)) {
|
|
2839
|
+
return value;
|
|
2840
|
+
}
|
|
2841
|
+
if (typeof value !== "object" || value === null) {
|
|
2842
|
+
return null;
|
|
2843
|
+
}
|
|
2844
|
+
const record = value;
|
|
2845
|
+
for (const key of ["data", "rows", "results", "items"]) {
|
|
2846
|
+
if (Array.isArray(record[key])) {
|
|
2847
|
+
return record[key];
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
return null;
|
|
2851
|
+
})();
|
|
2852
|
+
if (Array.isArray(rowsCandidate) && rowsCandidate.length > 0) {
|
|
2853
|
+
const objectRows = rowsCandidate.filter((row) => typeof row === "object" && row !== null && !Array.isArray(row)).slice(0, 25);
|
|
2854
|
+
if (objectRows.length > 0) {
|
|
2855
|
+
const columns = [...new Set(objectRows.flatMap((row) => Object.keys(row)))].slice(0, 20);
|
|
2856
|
+
return {
|
|
2857
|
+
schema: {
|
|
2858
|
+
columns: columns.map((label, index) => ({
|
|
2859
|
+
key: `col_${index + 1}`,
|
|
2860
|
+
label
|
|
2861
|
+
}))
|
|
2862
|
+
},
|
|
2863
|
+
mappedRows: objectRows.map((row) => {
|
|
2864
|
+
const mapped = {};
|
|
2865
|
+
for (const column of columns) {
|
|
2866
|
+
mapped[column] = row[column];
|
|
2867
|
+
}
|
|
2868
|
+
return mapped;
|
|
2869
|
+
})
|
|
2870
|
+
};
|
|
2871
|
+
}
|
|
2872
|
+
}
|
|
2873
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
2874
|
+
const columns = Object.keys(value).slice(0, 20);
|
|
2875
|
+
if (columns.length > 0) {
|
|
2876
|
+
return {
|
|
2877
|
+
schema: {
|
|
2878
|
+
columns: columns.map((label, index) => ({
|
|
2879
|
+
key: `col_${index + 1}`,
|
|
2880
|
+
label
|
|
2881
|
+
}))
|
|
2882
|
+
}
|
|
2883
|
+
};
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
return {};
|
|
2887
|
+
};
|
|
2764
2888
|
try {
|
|
2765
2889
|
const targetWindow = payload.scope === "main" ? window : payload.scope === "current" ? resolveFrameWindow(payload.framePath ?? []) : window;
|
|
2766
2890
|
if (payload.action === "eval") {
|
|
@@ -2781,9 +2905,48 @@
|
|
|
2781
2905
|
}
|
|
2782
2906
|
if (payload.action === "fetch") {
|
|
2783
2907
|
const headers = { ...payload.headers ?? {} };
|
|
2784
|
-
if (payload.contentType && !headers
|
|
2908
|
+
if (payload.contentType && !findHeaderName(headers, "Content-Type")) {
|
|
2785
2909
|
headers["Content-Type"] = payload.contentType;
|
|
2786
2910
|
}
|
|
2911
|
+
const fullResponse = payload.fullResponse === true;
|
|
2912
|
+
const authApplied = [];
|
|
2913
|
+
const authSources = /* @__PURE__ */ new Set();
|
|
2914
|
+
const requestUrl = new URL(payload.url, targetWindow.location.href);
|
|
2915
|
+
const sameOrigin = requestUrl.origin === targetWindow.location.origin;
|
|
2916
|
+
const authMode = payload.auth === "manual" || payload.auth === "off" ? payload.auth : "auto";
|
|
2917
|
+
const maybeApplyHeader = (name, value, source) => {
|
|
2918
|
+
if (!value || findHeaderName(headers, name)) {
|
|
2919
|
+
return;
|
|
2920
|
+
}
|
|
2921
|
+
headers[name] = value;
|
|
2922
|
+
authApplied.push(name);
|
|
2923
|
+
authSources.add(source);
|
|
2924
|
+
};
|
|
2925
|
+
if (sameOrigin && authMode === "auto") {
|
|
2926
|
+
const xsrfCookie = findCookieValue(targetWindow.document.cookie ?? "", "XSRF-TOKEN");
|
|
2927
|
+
if (xsrfCookie) {
|
|
2928
|
+
maybeApplyHeader("X-XSRF-TOKEN", decodeURIComponent(xsrfCookie), "cookie:XSRF-TOKEN");
|
|
2929
|
+
}
|
|
2930
|
+
const metaTokens = [
|
|
2931
|
+
{
|
|
2932
|
+
selector: 'meta[name="xsrf-token"], meta[name="x-xsrf-token"]',
|
|
2933
|
+
header: "X-XSRF-TOKEN",
|
|
2934
|
+
source: "meta:xsrf-token"
|
|
2935
|
+
},
|
|
2936
|
+
{
|
|
2937
|
+
selector: 'meta[name="csrf-token"], meta[name="csrf_token"], meta[name="_csrf"]',
|
|
2938
|
+
header: "X-CSRF-TOKEN",
|
|
2939
|
+
source: "meta:csrf-token"
|
|
2940
|
+
}
|
|
2941
|
+
];
|
|
2942
|
+
for (const token of metaTokens) {
|
|
2943
|
+
const meta = targetWindow.document.querySelector(token.selector);
|
|
2944
|
+
const content = meta?.content?.trim();
|
|
2945
|
+
if (content) {
|
|
2946
|
+
maybeApplyHeader(token.header, content, token.source);
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2787
2950
|
const controller = typeof AbortController === "function" ? new AbortController() : null;
|
|
2788
2951
|
const timeoutId = controller && typeof payload.timeoutMs === "number" && payload.timeoutMs > 0 ? window.setTimeout(() => controller.abort(), payload.timeoutMs) : null;
|
|
2789
2952
|
let response;
|
|
@@ -2810,32 +2973,43 @@
|
|
|
2810
2973
|
value: (() => {
|
|
2811
2974
|
const encoder = typeof TextEncoder === "function" ? new TextEncoder() : null;
|
|
2812
2975
|
const decoder = typeof TextDecoder === "function" ? new TextDecoder() : null;
|
|
2813
|
-
const previewLimit = typeof payload.maxBytes === "number" && payload.maxBytes > 0 ? payload.maxBytes : 8192;
|
|
2976
|
+
const previewLimit = !fullResponse && typeof payload.maxBytes === "number" && payload.maxBytes > 0 ? payload.maxBytes : 8192;
|
|
2814
2977
|
const encodedBody = encoder ? encoder.encode(bodyText) : null;
|
|
2815
2978
|
const bodyBytes = encodedBody ? encodedBody.byteLength : bodyText.length;
|
|
2816
|
-
const truncated = bodyBytes > previewLimit;
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
code: "E_BODY_TOO_LARGE",
|
|
2820
|
-
message: "JSON response exceeds max-bytes",
|
|
2821
|
-
details: {
|
|
2822
|
-
bytes: bodyBytes,
|
|
2823
|
-
maxBytes: previewLimit
|
|
2824
|
-
}
|
|
2825
|
-
};
|
|
2826
|
-
}
|
|
2827
|
-
const previewText = encodedBody && decoder ? decoder.decode(encodedBody.subarray(0, Math.min(encodedBody.byteLength, previewLimit))) : truncated ? bodyText.slice(0, previewLimit) : bodyText;
|
|
2828
|
-
return {
|
|
2979
|
+
const truncated = !fullResponse && bodyBytes > previewLimit;
|
|
2980
|
+
const previewText = fullResponse ? bodyText : encodedBody && decoder ? decoder.decode(encodedBody.subarray(0, Math.min(encodedBody.byteLength, previewLimit))) : truncated ? bodyText.slice(0, previewLimit) : bodyText;
|
|
2981
|
+
const result = {
|
|
2829
2982
|
url: response.url,
|
|
2830
2983
|
status: response.status,
|
|
2831
2984
|
ok: response.ok,
|
|
2832
2985
|
headers: headerMap,
|
|
2833
2986
|
contentType: response.headers.get("content-type") ?? void 0,
|
|
2834
|
-
bodyText: payload.mode === "json" ? void 0 : previewText,
|
|
2835
|
-
json: payload.mode === "json" && bodyText ? JSON.parse(bodyText) : void 0,
|
|
2836
2987
|
bytes: bodyBytes,
|
|
2837
|
-
truncated
|
|
2988
|
+
truncated,
|
|
2989
|
+
authApplied: authApplied.length > 0 ? authApplied : void 0,
|
|
2990
|
+
authSources: authSources.size > 0 ? [...authSources] : void 0
|
|
2838
2991
|
};
|
|
2992
|
+
if (payload.mode === "json") {
|
|
2993
|
+
const parsedJson = bodyText ? JSON.parse(bodyText) : void 0;
|
|
2994
|
+
const summary = buildJsonSummary(parsedJson);
|
|
2995
|
+
if (fullResponse || !truncated) {
|
|
2996
|
+
result.json = parsedJson;
|
|
2997
|
+
} else {
|
|
2998
|
+
result.degradedReason = "response body exceeded max-bytes and was summarized";
|
|
2999
|
+
}
|
|
3000
|
+
if (summary.schema) {
|
|
3001
|
+
result.schema = summary.schema;
|
|
3002
|
+
}
|
|
3003
|
+
if (summary.mappedRows) {
|
|
3004
|
+
result.mappedRows = summary.mappedRows;
|
|
3005
|
+
}
|
|
3006
|
+
} else {
|
|
3007
|
+
result.bodyText = previewText;
|
|
3008
|
+
if (truncated) {
|
|
3009
|
+
result.degradedReason = "response body exceeded max-bytes and was truncated";
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
3012
|
+
return result;
|
|
2839
3013
|
})()
|
|
2840
3014
|
};
|
|
2841
3015
|
}
|
|
@@ -2908,19 +3082,16 @@
|
|
|
2908
3082
|
}
|
|
2909
3083
|
return clone;
|
|
2910
3084
|
}
|
|
2911
|
-
function
|
|
2912
|
-
if (!
|
|
3085
|
+
function replayHeadersFromRequestHeaders(requestHeaders) {
|
|
3086
|
+
if (!requestHeaders) {
|
|
2913
3087
|
return void 0;
|
|
2914
3088
|
}
|
|
2915
3089
|
const headers = {};
|
|
2916
|
-
for (const [name, value] of Object.entries(
|
|
3090
|
+
for (const [name, value] of Object.entries(requestHeaders)) {
|
|
2917
3091
|
const normalizedName = name.toLowerCase();
|
|
2918
3092
|
if (REPLAY_FORBIDDEN_HEADER_NAMES.has(normalizedName) || normalizedName.startsWith("sec-")) {
|
|
2919
3093
|
continue;
|
|
2920
3094
|
}
|
|
2921
|
-
if (containsRedactionMarker(value)) {
|
|
2922
|
-
continue;
|
|
2923
|
-
}
|
|
2924
3095
|
headers[name] = value;
|
|
2925
3096
|
}
|
|
2926
3097
|
return Object.keys(headers).length > 0 ? headers : void 0;
|
|
@@ -3051,6 +3222,64 @@
|
|
|
3051
3222
|
}
|
|
3052
3223
|
return "unknown";
|
|
3053
3224
|
}
|
|
3225
|
+
function freshnessCategoryPriority(category) {
|
|
3226
|
+
switch (category) {
|
|
3227
|
+
case "data":
|
|
3228
|
+
return 0;
|
|
3229
|
+
case "unknown":
|
|
3230
|
+
return 1;
|
|
3231
|
+
case "event":
|
|
3232
|
+
return 2;
|
|
3233
|
+
case "contract":
|
|
3234
|
+
return 3;
|
|
3235
|
+
default:
|
|
3236
|
+
return 4;
|
|
3237
|
+
}
|
|
3238
|
+
}
|
|
3239
|
+
function freshnessSourcePriority(source) {
|
|
3240
|
+
switch (source) {
|
|
3241
|
+
case "network":
|
|
3242
|
+
return 0;
|
|
3243
|
+
case "page-data":
|
|
3244
|
+
return 1;
|
|
3245
|
+
case "visible":
|
|
3246
|
+
return 2;
|
|
3247
|
+
case "inline":
|
|
3248
|
+
return 3;
|
|
3249
|
+
default:
|
|
3250
|
+
return 4;
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
function rankFreshnessEvidence(candidates, now = Date.now()) {
|
|
3254
|
+
return candidates.slice().sort((left, right) => {
|
|
3255
|
+
const byCategory = freshnessCategoryPriority(left.category) - freshnessCategoryPriority(right.category);
|
|
3256
|
+
if (byCategory !== 0) {
|
|
3257
|
+
return byCategory;
|
|
3258
|
+
}
|
|
3259
|
+
const bySource = freshnessSourcePriority(left.source) - freshnessSourcePriority(right.source);
|
|
3260
|
+
if (bySource !== 0) {
|
|
3261
|
+
return bySource;
|
|
3262
|
+
}
|
|
3263
|
+
const leftTimestamp = parseTimestampCandidate(left.value, now) ?? Number.NEGATIVE_INFINITY;
|
|
3264
|
+
const rightTimestamp = parseTimestampCandidate(right.value, now) ?? Number.NEGATIVE_INFINITY;
|
|
3265
|
+
if (leftTimestamp !== rightTimestamp) {
|
|
3266
|
+
return rightTimestamp - leftTimestamp;
|
|
3267
|
+
}
|
|
3268
|
+
return left.value.localeCompare(right.value);
|
|
3269
|
+
});
|
|
3270
|
+
}
|
|
3271
|
+
function deriveFreshnessConfidence(primary) {
|
|
3272
|
+
if (!primary) {
|
|
3273
|
+
return "low";
|
|
3274
|
+
}
|
|
3275
|
+
if (primary.category === "data" && (primary.source === "network" || primary.source === "page-data")) {
|
|
3276
|
+
return "high";
|
|
3277
|
+
}
|
|
3278
|
+
if (primary.category === "data") {
|
|
3279
|
+
return "medium";
|
|
3280
|
+
}
|
|
3281
|
+
return "low";
|
|
3282
|
+
}
|
|
3054
3283
|
async function collectPageInspection(tabId, params = {}) {
|
|
3055
3284
|
return await forwardContentRpc(tabId, "bak.internal.inspectState", params);
|
|
3056
3285
|
}
|
|
@@ -3172,7 +3401,9 @@
|
|
|
3172
3401
|
const domVisibleTimestamp = latestTimestampFromCandidates(visibleCandidates, now);
|
|
3173
3402
|
const latestNetworkTs = latestNetworkTimestamp(tabId);
|
|
3174
3403
|
const lastMutationAt = typeof inspection.lastMutationAt === "number" ? inspection.lastMutationAt : null;
|
|
3175
|
-
const allCandidates = [...visibleCandidates, ...inlineCandidates, ...pageDataCandidates, ...networkCandidates];
|
|
3404
|
+
const allCandidates = rankFreshnessEvidence([...visibleCandidates, ...inlineCandidates, ...pageDataCandidates, ...networkCandidates], now);
|
|
3405
|
+
const primaryEvidence = allCandidates.find((candidate) => parseTimestampCandidate(candidate.value, now) !== null) ?? null;
|
|
3406
|
+
const primaryTimestamp = primaryEvidence ? parseTimestampCandidate(primaryEvidence.value, now) : null;
|
|
3176
3407
|
return {
|
|
3177
3408
|
pageLoadedAt: typeof inspection.pageLoadedAt === "number" ? inspection.pageLoadedAt : null,
|
|
3178
3409
|
lastMutationAt,
|
|
@@ -3181,6 +3412,11 @@
|
|
|
3181
3412
|
latestPageDataTimestamp,
|
|
3182
3413
|
latestNetworkDataTimestamp,
|
|
3183
3414
|
domVisibleTimestamp,
|
|
3415
|
+
primaryTimestamp,
|
|
3416
|
+
primaryCategory: primaryEvidence?.category ?? null,
|
|
3417
|
+
primarySource: primaryEvidence?.source ?? null,
|
|
3418
|
+
confidence: deriveFreshnessConfidence(primaryEvidence),
|
|
3419
|
+
suppressedEvidenceCount: Math.max(0, allCandidates.length - (primaryEvidence ? 1 : 0)),
|
|
3184
3420
|
assessment: computeFreshnessAssessment({
|
|
3185
3421
|
latestInlineDataTimestamp,
|
|
3186
3422
|
latestPageDataTimestamp,
|
|
@@ -3613,9 +3849,11 @@
|
|
|
3613
3849
|
type: "bak.collectElements",
|
|
3614
3850
|
debugRichText: config.debugRichText
|
|
3615
3851
|
});
|
|
3616
|
-
const
|
|
3852
|
+
const screenshot = params.capture === false ? { captureStatus: "skipped" } : await captureAlignedTabScreenshot(tab);
|
|
3617
3853
|
return {
|
|
3618
|
-
|
|
3854
|
+
captureStatus: screenshot.captureStatus,
|
|
3855
|
+
captureError: screenshot.captureError,
|
|
3856
|
+
imageBase64: includeBase64 && typeof screenshot.imageData === "string" ? screenshot.imageData.replace(/^data:image\/png;base64,/, "") : void 0,
|
|
3619
3857
|
elements: elements.elements,
|
|
3620
3858
|
tabId: tab.id,
|
|
3621
3859
|
url: tab.url ?? ""
|
|
@@ -3739,13 +3977,7 @@
|
|
|
3739
3977
|
return await preserveHumanFocus(typeof target.tabId !== "number", async () => {
|
|
3740
3978
|
const tab = await withTab(target);
|
|
3741
3979
|
await ensureTabNetworkCapture(tab.id);
|
|
3742
|
-
return
|
|
3743
|
-
entries: searchNetworkEntries(
|
|
3744
|
-
tab.id,
|
|
3745
|
-
String(params.pattern ?? ""),
|
|
3746
|
-
typeof params.limit === "number" ? params.limit : 50
|
|
3747
|
-
)
|
|
3748
|
-
};
|
|
3980
|
+
return searchNetworkEntries(tab.id, String(params.pattern ?? ""), typeof params.limit === "number" ? params.limit : 50);
|
|
3749
3981
|
});
|
|
3750
3982
|
}
|
|
3751
3983
|
case "network.waitFor": {
|
|
@@ -3779,34 +4011,32 @@
|
|
|
3779
4011
|
return await preserveHumanFocus(typeof target.tabId !== "number", async () => {
|
|
3780
4012
|
const tab = await withTab(target);
|
|
3781
4013
|
await ensureTabNetworkCapture(tab.id);
|
|
3782
|
-
const
|
|
3783
|
-
if (!
|
|
4014
|
+
const replayable = getReplayableNetworkRequest(tab.id, String(params.id ?? ""));
|
|
4015
|
+
if (!replayable) {
|
|
3784
4016
|
throw toError("E_NOT_FOUND", `network entry not found: ${String(params.id ?? "")}`);
|
|
3785
4017
|
}
|
|
3786
|
-
if (
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
}
|
|
4018
|
+
if (replayable.degradedReason) {
|
|
4019
|
+
return {
|
|
4020
|
+
url: replayable.entry.url,
|
|
4021
|
+
status: 0,
|
|
4022
|
+
ok: false,
|
|
4023
|
+
headers: {},
|
|
4024
|
+
bytes: replayable.entry.requestBytes,
|
|
4025
|
+
truncated: true,
|
|
4026
|
+
degradedReason: replayable.degradedReason
|
|
4027
|
+
};
|
|
3796
4028
|
}
|
|
3797
4029
|
const replayed = await executePageWorld(tab.id, "fetch", {
|
|
3798
|
-
url: entry.url,
|
|
3799
|
-
method: entry.method,
|
|
3800
|
-
headers:
|
|
3801
|
-
body:
|
|
3802
|
-
contentType:
|
|
3803
|
-
const requestHeaders = entry.requestHeaders ?? {};
|
|
3804
|
-
const contentTypeHeader = Object.keys(requestHeaders).find((name) => name.toLowerCase() === "content-type");
|
|
3805
|
-
return contentTypeHeader ? requestHeaders[contentTypeHeader] : void 0;
|
|
3806
|
-
})(),
|
|
4030
|
+
url: replayable.entry.url,
|
|
4031
|
+
method: replayable.entry.method,
|
|
4032
|
+
headers: replayHeadersFromRequestHeaders(replayable.headers),
|
|
4033
|
+
body: replayable.body,
|
|
4034
|
+
contentType: replayable.contentType,
|
|
3807
4035
|
mode: params.mode,
|
|
3808
4036
|
timeoutMs: params.timeoutMs,
|
|
3809
4037
|
maxBytes: params.maxBytes,
|
|
4038
|
+
fullResponse: params.fullResponse === true,
|
|
4039
|
+
auth: params.auth,
|
|
3810
4040
|
scope: "current"
|
|
3811
4041
|
});
|
|
3812
4042
|
const frameResult = replayed.result ?? replayed.results?.find((candidate) => candidate.value || candidate.error);
|
|
@@ -3815,7 +4045,13 @@
|
|
|
3815
4045
|
}
|
|
3816
4046
|
const first = frameResult?.value;
|
|
3817
4047
|
if (!first) {
|
|
3818
|
-
|
|
4048
|
+
return {
|
|
4049
|
+
url: replayable.entry.url,
|
|
4050
|
+
status: 0,
|
|
4051
|
+
ok: false,
|
|
4052
|
+
headers: {},
|
|
4053
|
+
degradedReason: "network replay returned no response payload"
|
|
4054
|
+
};
|
|
3819
4055
|
}
|
|
3820
4056
|
return params.withSchema === "auto" && params.mode === "json" ? await enrichReplayWithSchema(tab.id, String(params.id ?? ""), first) : first;
|
|
3821
4057
|
});
|
package/dist/content.global.js
CHANGED
|
@@ -2001,7 +2001,7 @@
|
|
|
2001
2001
|
for (const [index, table] of htmlTables.entries()) {
|
|
2002
2002
|
const handle = {
|
|
2003
2003
|
id: buildTableId(table.closest(".dataTables_wrapper") ? "dataTables" : "html", index),
|
|
2004
|
-
|
|
2004
|
+
label: (table.getAttribute("aria-label") || table.getAttribute("data-testid") || table.id || `table-${index + 1}`).trim(),
|
|
2005
2005
|
kind: table.closest(".dataTables_wrapper") ? "dataTables" : "html",
|
|
2006
2006
|
selector: table.id ? `#${table.id}` : void 0,
|
|
2007
2007
|
rowCount: table.querySelectorAll("tbody tr").length || table.querySelectorAll("tr").length,
|
|
@@ -2014,7 +2014,7 @@
|
|
|
2014
2014
|
const kind = grid.className.includes("ag-") ? "ag-grid" : "aria-grid";
|
|
2015
2015
|
const handle = {
|
|
2016
2016
|
id: buildTableId(kind, index),
|
|
2017
|
-
|
|
2017
|
+
label: (grid.getAttribute("aria-label") || grid.getAttribute("data-testid") || grid.id || `grid-${index + 1}`).trim(),
|
|
2018
2018
|
kind,
|
|
2019
2019
|
selector: grid.id ? `#${grid.id}` : void 0,
|
|
2020
2020
|
rowCount: gridRowNodes(grid).length,
|
|
@@ -2026,7 +2026,13 @@
|
|
|
2026
2026
|
}
|
|
2027
2027
|
function resolveTable(handleId) {
|
|
2028
2028
|
const tables = describeTables();
|
|
2029
|
-
const
|
|
2029
|
+
const normalizedHandleId = handleId.trim().toLowerCase();
|
|
2030
|
+
const handle = tables.find((candidate) => {
|
|
2031
|
+
if (candidate.id === handleId) {
|
|
2032
|
+
return true;
|
|
2033
|
+
}
|
|
2034
|
+
return typeof candidate.label === "string" && candidate.label.trim().toLowerCase() === normalizedHandleId;
|
|
2035
|
+
});
|
|
2030
2036
|
if (!handle) {
|
|
2031
2037
|
return null;
|
|
2032
2038
|
}
|
package/dist/manifest.json
CHANGED
package/package.json
CHANGED