@flrande/bak-extension 0.6.14 → 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 +406 -92
- package/dist/content.global.js +9 -3
- package/dist/manifest.json +1 -1
- package/dist/popup.global.js +11 -8
- package/dist/popup.html +13 -3
- package/package.json +2 -2
- package/public/popup.html +13 -3
- package/src/background.ts +537 -258
- package/src/content.ts +9 -3
- package/src/dynamic-data-tools.ts +2 -2
- package/src/network-debugger.ts +90 -12
- package/src/popup.ts +27 -9
- package/src/session-binding-storage.ts +7 -1
- package/src/session-binding.ts +41 -11
|
@@ -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 });
|
|
@@ -990,10 +1038,16 @@
|
|
|
990
1038
|
return typeof candidate.id === "string" && Array.isArray(candidate.tabIds) && (typeof candidate.windowId === "number" || candidate.windowId === null) && (typeof candidate.groupId === "number" || candidate.groupId === null) && (typeof candidate.activeTabId === "number" || candidate.activeTabId === null) && (typeof candidate.primaryTabId === "number" || candidate.primaryTabId === null);
|
|
991
1039
|
}
|
|
992
1040
|
function cloneSessionBindingRecord(state) {
|
|
993
|
-
|
|
1041
|
+
const cloned = {
|
|
994
1042
|
...state,
|
|
995
1043
|
tabIds: [...state.tabIds]
|
|
996
1044
|
};
|
|
1045
|
+
if (cloned.tabIds.length === 0) {
|
|
1046
|
+
cloned.groupId = null;
|
|
1047
|
+
cloned.activeTabId = null;
|
|
1048
|
+
cloned.primaryTabId = null;
|
|
1049
|
+
}
|
|
1050
|
+
return cloned;
|
|
997
1051
|
}
|
|
998
1052
|
function normalizeSessionBindingRecordMap(value) {
|
|
999
1053
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
@@ -1376,6 +1430,34 @@
|
|
|
1376
1430
|
};
|
|
1377
1431
|
}
|
|
1378
1432
|
const tabs = await this.readLooseTrackedTabs(remainingTabIds);
|
|
1433
|
+
if (tabs.length === 0) {
|
|
1434
|
+
const liveWindow = binding.windowId !== null ? await this.waitForWindow(binding.windowId, 300) : null;
|
|
1435
|
+
if (!liveWindow) {
|
|
1436
|
+
await this.storage.delete(binding.id);
|
|
1437
|
+
return {
|
|
1438
|
+
binding: null,
|
|
1439
|
+
closedTabId: resolvedTabId
|
|
1440
|
+
};
|
|
1441
|
+
}
|
|
1442
|
+
const nextState2 = {
|
|
1443
|
+
id: binding.id,
|
|
1444
|
+
label: binding.label,
|
|
1445
|
+
color: binding.color,
|
|
1446
|
+
windowId: liveWindow.id,
|
|
1447
|
+
groupId: null,
|
|
1448
|
+
tabIds: [],
|
|
1449
|
+
activeTabId: null,
|
|
1450
|
+
primaryTabId: null
|
|
1451
|
+
};
|
|
1452
|
+
await this.storage.save(nextState2);
|
|
1453
|
+
return {
|
|
1454
|
+
binding: {
|
|
1455
|
+
...nextState2,
|
|
1456
|
+
tabs: []
|
|
1457
|
+
},
|
|
1458
|
+
closedTabId: resolvedTabId
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1379
1461
|
const nextPrimaryTabId = binding.primaryTabId === resolvedTabId ? tabs[0]?.id ?? null : binding.primaryTabId;
|
|
1380
1462
|
const nextActiveTabId = binding.activeTabId === resolvedTabId ? tabs.find((candidate) => candidate.active)?.id ?? nextPrimaryTabId ?? tabs[0]?.id ?? null : binding.activeTabId;
|
|
1381
1463
|
const nextState = {
|
|
@@ -2041,38 +2123,82 @@
|
|
|
2041
2123
|
async function listSessionBindingStates() {
|
|
2042
2124
|
return Object.values(await loadSessionBindingStateMap());
|
|
2043
2125
|
}
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
tabCount: state.tabIds.length,
|
|
2054
|
-
activeTabId: state.activeTabId,
|
|
2055
|
-
activeTabTitle: activeTab?.title ?? null,
|
|
2056
|
-
activeTabUrl: activeTab?.url ?? null,
|
|
2057
|
-
windowId: state.windowId,
|
|
2058
|
-
groupId: state.groupId,
|
|
2059
|
-
detached,
|
|
2060
|
-
lastBindingUpdateAt: bindingUpdate?.at ?? null,
|
|
2061
|
-
lastBindingUpdateReason: bindingUpdate?.reason ?? null
|
|
2062
|
-
};
|
|
2126
|
+
function collectPopupSessionBindingTabIds(state) {
|
|
2127
|
+
return [
|
|
2128
|
+
...new Set(state.tabIds.concat([state.activeTabId, state.primaryTabId].filter((value) => typeof value === "number")))
|
|
2129
|
+
];
|
|
2130
|
+
}
|
|
2131
|
+
async function inspectPopupSessionBinding(state) {
|
|
2132
|
+
const trackedTabs = (await Promise.all(
|
|
2133
|
+
collectPopupSessionBindingTabIds(state).map(async (tabId) => {
|
|
2134
|
+
return await sessionBindingBrowser.getTab(tabId);
|
|
2063
2135
|
})
|
|
2064
|
-
);
|
|
2136
|
+
)).filter((tab) => tab !== null);
|
|
2137
|
+
const liveWindow = typeof state.windowId === "number" ? await sessionBindingBrowser.getWindow(state.windowId) : null;
|
|
2138
|
+
const activeTab = trackedTabs.find((tab) => tab.id === state.activeTabId) ?? trackedTabs.find((tab) => tab.active) ?? trackedTabs[0] ?? null;
|
|
2139
|
+
const status = trackedTabs.length > 0 ? "attached" : liveWindow ? "window-only" : "detached";
|
|
2140
|
+
const bindingUpdate = bindingUpdateMetadata.get(state.id);
|
|
2141
|
+
return {
|
|
2142
|
+
summary: {
|
|
2143
|
+
id: state.id,
|
|
2144
|
+
label: state.label,
|
|
2145
|
+
tabCount: trackedTabs.length,
|
|
2146
|
+
activeTabId: activeTab?.id ?? null,
|
|
2147
|
+
activeTabTitle: activeTab?.title ?? null,
|
|
2148
|
+
activeTabUrl: activeTab?.url ?? null,
|
|
2149
|
+
windowId: activeTab?.windowId ?? trackedTabs[0]?.windowId ?? liveWindow?.id ?? state.windowId,
|
|
2150
|
+
groupId: trackedTabs.length > 0 ? activeTab?.groupId ?? trackedTabs.find((tab) => tab.groupId !== null)?.groupId ?? state.groupId : null,
|
|
2151
|
+
status,
|
|
2152
|
+
lastBindingUpdateAt: bindingUpdate?.at ?? null,
|
|
2153
|
+
lastBindingUpdateReason: bindingUpdate?.reason ?? null
|
|
2154
|
+
},
|
|
2155
|
+
prune: status === "detached"
|
|
2156
|
+
};
|
|
2157
|
+
}
|
|
2158
|
+
async function summarizeSessionBindings() {
|
|
2159
|
+
const statusRank = {
|
|
2160
|
+
attached: 0,
|
|
2161
|
+
"window-only": 1,
|
|
2162
|
+
detached: 2
|
|
2163
|
+
};
|
|
2164
|
+
const items = await mutateSessionBindingStateMap(async (stateMap) => {
|
|
2165
|
+
const inspected = await Promise.all(
|
|
2166
|
+
Object.entries(stateMap).map(async ([bindingId, state]) => {
|
|
2167
|
+
return {
|
|
2168
|
+
bindingId,
|
|
2169
|
+
inspected: await inspectPopupSessionBinding(state)
|
|
2170
|
+
};
|
|
2171
|
+
})
|
|
2172
|
+
);
|
|
2173
|
+
for (const entry of inspected) {
|
|
2174
|
+
if (entry.inspected.prune) {
|
|
2175
|
+
delete stateMap[entry.bindingId];
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
return inspected.filter((entry) => !entry.inspected.prune).map((entry) => entry.inspected.summary).sort((left, right) => {
|
|
2179
|
+
const byStatus = statusRank[left.status] - statusRank[right.status];
|
|
2180
|
+
if (byStatus !== 0) {
|
|
2181
|
+
return byStatus;
|
|
2182
|
+
}
|
|
2183
|
+
const byUpdate = (right.lastBindingUpdateAt ?? 0) - (left.lastBindingUpdateAt ?? 0);
|
|
2184
|
+
if (byUpdate !== 0) {
|
|
2185
|
+
return byUpdate;
|
|
2186
|
+
}
|
|
2187
|
+
return left.label.localeCompare(right.label);
|
|
2188
|
+
});
|
|
2189
|
+
});
|
|
2065
2190
|
return {
|
|
2066
2191
|
count: items.length,
|
|
2067
|
-
attachedCount: items.filter((item) =>
|
|
2068
|
-
|
|
2192
|
+
attachedCount: items.filter((item) => item.status === "attached").length,
|
|
2193
|
+
windowOnlyCount: items.filter((item) => item.status === "window-only").length,
|
|
2194
|
+
detachedCount: items.filter((item) => item.status === "detached").length,
|
|
2069
2195
|
tabCount: items.reduce((sum, item) => sum + item.tabCount, 0),
|
|
2070
2196
|
items
|
|
2071
2197
|
};
|
|
2072
2198
|
}
|
|
2073
2199
|
async function buildPopupState() {
|
|
2074
2200
|
const config = await getConfig();
|
|
2075
|
-
const sessionBindings = await summarizeSessionBindings(
|
|
2201
|
+
const sessionBindings = await summarizeSessionBindings();
|
|
2076
2202
|
const reconnectRemainingMs = nextReconnectAt === null ? null : Math.max(0, nextReconnectAt - Date.now());
|
|
2077
2203
|
let connectionState;
|
|
2078
2204
|
if (!config.token) {
|
|
@@ -2429,7 +2555,18 @@
|
|
|
2429
2555
|
await new Promise((resolve) => setTimeout(resolve, 80));
|
|
2430
2556
|
}
|
|
2431
2557
|
try {
|
|
2432
|
-
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
|
+
};
|
|
2433
2570
|
} finally {
|
|
2434
2571
|
if (shouldSwitch && typeof activeTab?.id === "number") {
|
|
2435
2572
|
try {
|
|
@@ -2560,7 +2697,9 @@
|
|
|
2560
2697
|
contentType: typeof params.contentType === "string" ? params.contentType : void 0,
|
|
2561
2698
|
mode: params.mode === "json" ? "json" : "raw",
|
|
2562
2699
|
maxBytes: typeof params.maxBytes === "number" ? params.maxBytes : void 0,
|
|
2563
|
-
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"
|
|
2564
2703
|
}
|
|
2565
2704
|
],
|
|
2566
2705
|
func: async (payload) => {
|
|
@@ -2683,6 +2822,69 @@
|
|
|
2683
2822
|
}
|
|
2684
2823
|
return { resolver: "lexical", value: readLexical() };
|
|
2685
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
|
+
};
|
|
2686
2888
|
try {
|
|
2687
2889
|
const targetWindow = payload.scope === "main" ? window : payload.scope === "current" ? resolveFrameWindow(payload.framePath ?? []) : window;
|
|
2688
2890
|
if (payload.action === "eval") {
|
|
@@ -2703,9 +2905,48 @@
|
|
|
2703
2905
|
}
|
|
2704
2906
|
if (payload.action === "fetch") {
|
|
2705
2907
|
const headers = { ...payload.headers ?? {} };
|
|
2706
|
-
if (payload.contentType && !headers
|
|
2908
|
+
if (payload.contentType && !findHeaderName(headers, "Content-Type")) {
|
|
2707
2909
|
headers["Content-Type"] = payload.contentType;
|
|
2708
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
|
+
}
|
|
2709
2950
|
const controller = typeof AbortController === "function" ? new AbortController() : null;
|
|
2710
2951
|
const timeoutId = controller && typeof payload.timeoutMs === "number" && payload.timeoutMs > 0 ? window.setTimeout(() => controller.abort(), payload.timeoutMs) : null;
|
|
2711
2952
|
let response;
|
|
@@ -2732,32 +2973,43 @@
|
|
|
2732
2973
|
value: (() => {
|
|
2733
2974
|
const encoder = typeof TextEncoder === "function" ? new TextEncoder() : null;
|
|
2734
2975
|
const decoder = typeof TextDecoder === "function" ? new TextDecoder() : null;
|
|
2735
|
-
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;
|
|
2736
2977
|
const encodedBody = encoder ? encoder.encode(bodyText) : null;
|
|
2737
2978
|
const bodyBytes = encodedBody ? encodedBody.byteLength : bodyText.length;
|
|
2738
|
-
const truncated = bodyBytes > previewLimit;
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
code: "E_BODY_TOO_LARGE",
|
|
2742
|
-
message: "JSON response exceeds max-bytes",
|
|
2743
|
-
details: {
|
|
2744
|
-
bytes: bodyBytes,
|
|
2745
|
-
maxBytes: previewLimit
|
|
2746
|
-
}
|
|
2747
|
-
};
|
|
2748
|
-
}
|
|
2749
|
-
const previewText = encodedBody && decoder ? decoder.decode(encodedBody.subarray(0, Math.min(encodedBody.byteLength, previewLimit))) : truncated ? bodyText.slice(0, previewLimit) : bodyText;
|
|
2750
|
-
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 = {
|
|
2751
2982
|
url: response.url,
|
|
2752
2983
|
status: response.status,
|
|
2753
2984
|
ok: response.ok,
|
|
2754
2985
|
headers: headerMap,
|
|
2755
2986
|
contentType: response.headers.get("content-type") ?? void 0,
|
|
2756
|
-
bodyText: payload.mode === "json" ? void 0 : previewText,
|
|
2757
|
-
json: payload.mode === "json" && bodyText ? JSON.parse(bodyText) : void 0,
|
|
2758
2987
|
bytes: bodyBytes,
|
|
2759
|
-
truncated
|
|
2988
|
+
truncated,
|
|
2989
|
+
authApplied: authApplied.length > 0 ? authApplied : void 0,
|
|
2990
|
+
authSources: authSources.size > 0 ? [...authSources] : void 0
|
|
2760
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;
|
|
2761
3013
|
})()
|
|
2762
3014
|
};
|
|
2763
3015
|
}
|
|
@@ -2830,19 +3082,16 @@
|
|
|
2830
3082
|
}
|
|
2831
3083
|
return clone;
|
|
2832
3084
|
}
|
|
2833
|
-
function
|
|
2834
|
-
if (!
|
|
3085
|
+
function replayHeadersFromRequestHeaders(requestHeaders) {
|
|
3086
|
+
if (!requestHeaders) {
|
|
2835
3087
|
return void 0;
|
|
2836
3088
|
}
|
|
2837
3089
|
const headers = {};
|
|
2838
|
-
for (const [name, value] of Object.entries(
|
|
3090
|
+
for (const [name, value] of Object.entries(requestHeaders)) {
|
|
2839
3091
|
const normalizedName = name.toLowerCase();
|
|
2840
3092
|
if (REPLAY_FORBIDDEN_HEADER_NAMES.has(normalizedName) || normalizedName.startsWith("sec-")) {
|
|
2841
3093
|
continue;
|
|
2842
3094
|
}
|
|
2843
|
-
if (containsRedactionMarker(value)) {
|
|
2844
|
-
continue;
|
|
2845
|
-
}
|
|
2846
3095
|
headers[name] = value;
|
|
2847
3096
|
}
|
|
2848
3097
|
return Object.keys(headers).length > 0 ? headers : void 0;
|
|
@@ -2973,6 +3222,64 @@
|
|
|
2973
3222
|
}
|
|
2974
3223
|
return "unknown";
|
|
2975
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
|
+
}
|
|
2976
3283
|
async function collectPageInspection(tabId, params = {}) {
|
|
2977
3284
|
return await forwardContentRpc(tabId, "bak.internal.inspectState", params);
|
|
2978
3285
|
}
|
|
@@ -3094,7 +3401,9 @@
|
|
|
3094
3401
|
const domVisibleTimestamp = latestTimestampFromCandidates(visibleCandidates, now);
|
|
3095
3402
|
const latestNetworkTs = latestNetworkTimestamp(tabId);
|
|
3096
3403
|
const lastMutationAt = typeof inspection.lastMutationAt === "number" ? inspection.lastMutationAt : null;
|
|
3097
|
-
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;
|
|
3098
3407
|
return {
|
|
3099
3408
|
pageLoadedAt: typeof inspection.pageLoadedAt === "number" ? inspection.pageLoadedAt : null,
|
|
3100
3409
|
lastMutationAt,
|
|
@@ -3103,6 +3412,11 @@
|
|
|
3103
3412
|
latestPageDataTimestamp,
|
|
3104
3413
|
latestNetworkDataTimestamp,
|
|
3105
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)),
|
|
3106
3420
|
assessment: computeFreshnessAssessment({
|
|
3107
3421
|
latestInlineDataTimestamp,
|
|
3108
3422
|
latestPageDataTimestamp,
|
|
@@ -3535,9 +3849,11 @@
|
|
|
3535
3849
|
type: "bak.collectElements",
|
|
3536
3850
|
debugRichText: config.debugRichText
|
|
3537
3851
|
});
|
|
3538
|
-
const
|
|
3852
|
+
const screenshot = params.capture === false ? { captureStatus: "skipped" } : await captureAlignedTabScreenshot(tab);
|
|
3539
3853
|
return {
|
|
3540
|
-
|
|
3854
|
+
captureStatus: screenshot.captureStatus,
|
|
3855
|
+
captureError: screenshot.captureError,
|
|
3856
|
+
imageBase64: includeBase64 && typeof screenshot.imageData === "string" ? screenshot.imageData.replace(/^data:image\/png;base64,/, "") : void 0,
|
|
3541
3857
|
elements: elements.elements,
|
|
3542
3858
|
tabId: tab.id,
|
|
3543
3859
|
url: tab.url ?? ""
|
|
@@ -3661,13 +3977,7 @@
|
|
|
3661
3977
|
return await preserveHumanFocus(typeof target.tabId !== "number", async () => {
|
|
3662
3978
|
const tab = await withTab(target);
|
|
3663
3979
|
await ensureTabNetworkCapture(tab.id);
|
|
3664
|
-
return
|
|
3665
|
-
entries: searchNetworkEntries(
|
|
3666
|
-
tab.id,
|
|
3667
|
-
String(params.pattern ?? ""),
|
|
3668
|
-
typeof params.limit === "number" ? params.limit : 50
|
|
3669
|
-
)
|
|
3670
|
-
};
|
|
3980
|
+
return searchNetworkEntries(tab.id, String(params.pattern ?? ""), typeof params.limit === "number" ? params.limit : 50);
|
|
3671
3981
|
});
|
|
3672
3982
|
}
|
|
3673
3983
|
case "network.waitFor": {
|
|
@@ -3701,34 +4011,32 @@
|
|
|
3701
4011
|
return await preserveHumanFocus(typeof target.tabId !== "number", async () => {
|
|
3702
4012
|
const tab = await withTab(target);
|
|
3703
4013
|
await ensureTabNetworkCapture(tab.id);
|
|
3704
|
-
const
|
|
3705
|
-
if (!
|
|
4014
|
+
const replayable = getReplayableNetworkRequest(tab.id, String(params.id ?? ""));
|
|
4015
|
+
if (!replayable) {
|
|
3706
4016
|
throw toError("E_NOT_FOUND", `network entry not found: ${String(params.id ?? "")}`);
|
|
3707
4017
|
}
|
|
3708
|
-
if (
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
}
|
|
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
|
+
};
|
|
3718
4028
|
}
|
|
3719
4029
|
const replayed = await executePageWorld(tab.id, "fetch", {
|
|
3720
|
-
url: entry.url,
|
|
3721
|
-
method: entry.method,
|
|
3722
|
-
headers:
|
|
3723
|
-
body:
|
|
3724
|
-
contentType:
|
|
3725
|
-
const requestHeaders = entry.requestHeaders ?? {};
|
|
3726
|
-
const contentTypeHeader = Object.keys(requestHeaders).find((name) => name.toLowerCase() === "content-type");
|
|
3727
|
-
return contentTypeHeader ? requestHeaders[contentTypeHeader] : void 0;
|
|
3728
|
-
})(),
|
|
4030
|
+
url: replayable.entry.url,
|
|
4031
|
+
method: replayable.entry.method,
|
|
4032
|
+
headers: replayHeadersFromRequestHeaders(replayable.headers),
|
|
4033
|
+
body: replayable.body,
|
|
4034
|
+
contentType: replayable.contentType,
|
|
3729
4035
|
mode: params.mode,
|
|
3730
4036
|
timeoutMs: params.timeoutMs,
|
|
3731
4037
|
maxBytes: params.maxBytes,
|
|
4038
|
+
fullResponse: params.fullResponse === true,
|
|
4039
|
+
auth: params.auth,
|
|
3732
4040
|
scope: "current"
|
|
3733
4041
|
});
|
|
3734
4042
|
const frameResult = replayed.result ?? replayed.results?.find((candidate) => candidate.value || candidate.error);
|
|
@@ -3737,7 +4045,13 @@
|
|
|
3737
4045
|
}
|
|
3738
4046
|
const first = frameResult?.value;
|
|
3739
4047
|
if (!first) {
|
|
3740
|
-
|
|
4048
|
+
return {
|
|
4049
|
+
url: replayable.entry.url,
|
|
4050
|
+
status: 0,
|
|
4051
|
+
ok: false,
|
|
4052
|
+
headers: {},
|
|
4053
|
+
degradedReason: "network replay returned no response payload"
|
|
4054
|
+
};
|
|
3741
4055
|
}
|
|
3742
4056
|
return params.withSchema === "auto" && params.mode === "json" ? await enrichReplayWithSchema(tab.id, String(params.id ?? ""), first) : first;
|
|
3743
4057
|
});
|