@flrande/bak-extension 0.6.16 → 0.6.17
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 +604 -54
- package/dist/content.global.js +356 -20
- package/dist/manifest.json +1 -1
- package/package.json +2 -2
- package/src/background.ts +445 -74
- package/src/content.ts +257 -24
- package/src/dynamic-data-tools.ts +139 -5
- package/src/network-debugger.ts +19 -47
- package/src/network-tools.ts +184 -0
|
@@ -360,8 +360,8 @@
|
|
|
360
360
|
if (source.source.type === "networkResponse") {
|
|
361
361
|
const requestId = source.source.sourceId.replace(/^networkResponse:/, "");
|
|
362
362
|
pushRecommendation({
|
|
363
|
-
title: `
|
|
364
|
-
command: `bak network
|
|
363
|
+
title: `Clone ${requestId} into a reusable fetch template`,
|
|
364
|
+
command: `bak network clone ${requestId}`,
|
|
365
365
|
note: `Recent response mapped to ${mapping.tableId} with ${mapping.confidence} confidence.`
|
|
366
366
|
});
|
|
367
367
|
continue;
|
|
@@ -381,6 +381,96 @@
|
|
|
381
381
|
}
|
|
382
382
|
return recommendations.slice(0, 6);
|
|
383
383
|
}
|
|
384
|
+
function confidenceRank(confidence) {
|
|
385
|
+
switch (confidence) {
|
|
386
|
+
case "high":
|
|
387
|
+
return 0;
|
|
388
|
+
case "medium":
|
|
389
|
+
return 1;
|
|
390
|
+
case "low":
|
|
391
|
+
return 2;
|
|
392
|
+
default:
|
|
393
|
+
return 3;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
function isSameOrigin(pageUrl, requestUrl) {
|
|
397
|
+
if (!pageUrl) {
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
try {
|
|
401
|
+
const page = new URL(pageUrl);
|
|
402
|
+
const request = new URL(requestUrl, page);
|
|
403
|
+
return page.origin === request.origin;
|
|
404
|
+
} catch {
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
function selectPrimaryEndpoint(recentNetwork, mappings, pageUrl) {
|
|
409
|
+
const mapped = mappings.filter((mapping) => mapping.sourceId.startsWith("networkResponse:")).map((mapping) => ({
|
|
410
|
+
mapping,
|
|
411
|
+
entry: recentNetwork.find((entry) => entry.id === mapping.sourceId.replace(/^networkResponse:/, ""))
|
|
412
|
+
})).filter((candidate) => candidate.entry !== void 0).sort((left, right) => {
|
|
413
|
+
return confidenceRank(left.mapping.confidence) - confidenceRank(right.mapping.confidence) || right.entry.ts - left.entry.ts || left.entry.id.localeCompare(right.entry.id);
|
|
414
|
+
})[0];
|
|
415
|
+
if (mapped) {
|
|
416
|
+
return {
|
|
417
|
+
requestId: mapped.entry.id,
|
|
418
|
+
url: mapped.entry.url,
|
|
419
|
+
method: mapped.entry.method,
|
|
420
|
+
status: mapped.entry.status,
|
|
421
|
+
kind: mapped.entry.kind,
|
|
422
|
+
resourceType: mapped.entry.resourceType,
|
|
423
|
+
contentType: mapped.entry.contentType,
|
|
424
|
+
sameOrigin: isSameOrigin(pageUrl, mapped.entry.url),
|
|
425
|
+
matchedTableId: mapped.mapping.tableId,
|
|
426
|
+
matchedSourceId: mapped.mapping.sourceId,
|
|
427
|
+
reason: `Mapped to ${mapped.mapping.tableId} with ${mapped.mapping.confidence} confidence`
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
const fallback = recentNetwork.filter((entry) => (entry.kind === "fetch" || entry.kind === "xhr") && entry.status >= 200 && entry.status < 400).sort((left, right) => right.ts - left.ts)[0];
|
|
431
|
+
if (!fallback) {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
return {
|
|
435
|
+
requestId: fallback.id,
|
|
436
|
+
url: fallback.url,
|
|
437
|
+
method: fallback.method,
|
|
438
|
+
status: fallback.status,
|
|
439
|
+
kind: fallback.kind,
|
|
440
|
+
resourceType: fallback.resourceType,
|
|
441
|
+
contentType: fallback.contentType,
|
|
442
|
+
sameOrigin: isSameOrigin(pageUrl, fallback.url),
|
|
443
|
+
reason: `Latest successful ${fallback.kind.toUpperCase()} request observed on the page`
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
function summarizeAvailableModes(modeGroups) {
|
|
447
|
+
return [...new Set(modeGroups.flatMap((group) => group.options.map((option) => option.label)))];
|
|
448
|
+
}
|
|
449
|
+
function selectCurrentMode(modeGroups) {
|
|
450
|
+
for (const group of modeGroups) {
|
|
451
|
+
const selected = group.options.find((option) => option.selected);
|
|
452
|
+
if (selected) {
|
|
453
|
+
return {
|
|
454
|
+
controlType: group.controlType,
|
|
455
|
+
label: selected.label,
|
|
456
|
+
value: selected.value,
|
|
457
|
+
groupLabel: group.label
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
function deriveLatestArchiveDate(dateControls) {
|
|
464
|
+
const candidates = dateControls.flatMap((control) => [
|
|
465
|
+
control.value,
|
|
466
|
+
control.min,
|
|
467
|
+
control.max,
|
|
468
|
+
control.dataMaxDate,
|
|
469
|
+
...Array.isArray(control.options) ? control.options : []
|
|
470
|
+
]);
|
|
471
|
+
const dated = candidates.filter((value) => typeof value === "string").map((value) => ({ value, parsed: Date.parse(value) })).filter((item) => Number.isFinite(item.parsed)).sort((left, right) => right.parsed - left.parsed);
|
|
472
|
+
return dated[0]?.value ?? null;
|
|
473
|
+
}
|
|
384
474
|
function buildSourceMappingReport(input) {
|
|
385
475
|
const now = typeof input.now === "number" ? input.now : Date.now();
|
|
386
476
|
const windowAnalyses = buildWindowSources(input.windowSources);
|
|
@@ -388,14 +478,15 @@
|
|
|
388
478
|
const networkAnalyses = buildNetworkAnalyses(input.recentNetwork);
|
|
389
479
|
const sourceAnalyses = [...windowAnalyses, ...inlineAnalyses, ...networkAnalyses];
|
|
390
480
|
const sourceMappings = input.tables.flatMap((table) => sourceAnalyses.map((source) => scoreSourceMapping(table, source, now))).filter((mapping) => mapping !== null).sort((left, right) => {
|
|
391
|
-
const
|
|
392
|
-
return
|
|
481
|
+
const confidenceRank2 = { high: 0, medium: 1, low: 2 };
|
|
482
|
+
return confidenceRank2[left.confidence] - confidenceRank2[right.confidence] || left.tableId.localeCompare(right.tableId) || left.sourceId.localeCompare(right.sourceId);
|
|
393
483
|
});
|
|
394
484
|
return {
|
|
395
485
|
dataSources: sourceAnalyses.map((analysis) => analysis.source),
|
|
396
486
|
sourceMappings,
|
|
397
487
|
recommendedNextActions: buildRecommendedNextActions(input.tables, sourceMappings, sourceAnalyses),
|
|
398
|
-
sourceAnalyses
|
|
488
|
+
sourceAnalyses,
|
|
489
|
+
primaryEndpoint: selectPrimaryEndpoint(input.recentNetwork, sourceMappings, input.pageUrl)
|
|
399
490
|
};
|
|
400
491
|
}
|
|
401
492
|
function mapObjectRowToSchema(row, schema) {
|
|
@@ -473,12 +564,17 @@
|
|
|
473
564
|
windowSources: input.pageDataCandidates,
|
|
474
565
|
inlineJsonSources: input.inlineJsonSources,
|
|
475
566
|
recentNetwork: input.recentNetwork,
|
|
567
|
+
pageUrl: input.pageUrl,
|
|
476
568
|
now: input.now
|
|
477
569
|
});
|
|
478
570
|
return {
|
|
479
571
|
dataSources: report.dataSources,
|
|
480
572
|
sourceMappings: report.sourceMappings,
|
|
481
|
-
recommendedNextActions: report.recommendedNextActions
|
|
573
|
+
recommendedNextActions: report.recommendedNextActions,
|
|
574
|
+
availableModes: summarizeAvailableModes(input.modeGroups),
|
|
575
|
+
currentMode: selectCurrentMode(input.modeGroups),
|
|
576
|
+
latestArchiveDate: deriveLatestArchiveDate(input.dateControls),
|
|
577
|
+
primaryEndpoint: report.primaryEndpoint
|
|
482
578
|
};
|
|
483
579
|
}
|
|
484
580
|
function buildPageDataProbe(name, resolver, sample) {
|
|
@@ -550,10 +646,159 @@
|
|
|
550
646
|
return Object.keys(result).length > 0 ? result : void 0;
|
|
551
647
|
}
|
|
552
648
|
|
|
649
|
+
// src/network-tools.ts
|
|
650
|
+
var ROW_CANDIDATE_KEYS2 = ["data", "rows", "results", "items", "records", "entries"];
|
|
651
|
+
var SUMMARY_TEXT_LIMIT = 96;
|
|
652
|
+
function truncateSummaryText(value, limit = SUMMARY_TEXT_LIMIT) {
|
|
653
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
654
|
+
if (normalized.length <= limit) {
|
|
655
|
+
return normalized;
|
|
656
|
+
}
|
|
657
|
+
return `${normalized.slice(0, Math.max(1, limit - 1)).trimEnd()}...`;
|
|
658
|
+
}
|
|
659
|
+
function safeParseUrl(urlText) {
|
|
660
|
+
try {
|
|
661
|
+
return new URL(urlText);
|
|
662
|
+
} catch {
|
|
663
|
+
try {
|
|
664
|
+
return new URL(urlText, "http://127.0.0.1");
|
|
665
|
+
} catch {
|
|
666
|
+
return null;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
function looksLikeFormBody(value) {
|
|
671
|
+
return value.includes("=") && (value.includes("&") || !value.trim().startsWith("{"));
|
|
672
|
+
}
|
|
673
|
+
function summarizeSearchParams(params) {
|
|
674
|
+
const entries = [];
|
|
675
|
+
params.forEach((value, key) => {
|
|
676
|
+
entries.push([key, value]);
|
|
677
|
+
});
|
|
678
|
+
if (entries.length === 0) {
|
|
679
|
+
return void 0;
|
|
680
|
+
}
|
|
681
|
+
const preview = entries.slice(0, 4).map(([key, value]) => `${key}=${truncateSummaryText(value, 32)}`);
|
|
682
|
+
return entries.length > 4 ? `${preview.join(", ")} ...` : preview.join(", ");
|
|
683
|
+
}
|
|
684
|
+
function summarizeJsonValue(value) {
|
|
685
|
+
if (Array.isArray(value)) {
|
|
686
|
+
return `json array(${value.length})`;
|
|
687
|
+
}
|
|
688
|
+
if (!value || typeof value !== "object") {
|
|
689
|
+
return `json ${truncateSummaryText(String(value), 40)}`;
|
|
690
|
+
}
|
|
691
|
+
const record = value;
|
|
692
|
+
for (const key of ROW_CANDIDATE_KEYS2) {
|
|
693
|
+
if (Array.isArray(record[key])) {
|
|
694
|
+
return `json rows(${record[key].length}) via ${key}`;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
const keys = Object.keys(record).slice(0, 5);
|
|
698
|
+
return keys.length > 0 ? `json keys: ${keys.join(", ")}` : "json object";
|
|
699
|
+
}
|
|
700
|
+
function headerValue(headers, name) {
|
|
701
|
+
if (!headers) {
|
|
702
|
+
return void 0;
|
|
703
|
+
}
|
|
704
|
+
const normalizedName = name.toLowerCase();
|
|
705
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
706
|
+
if (key.toLowerCase() === normalizedName) {
|
|
707
|
+
return value;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return void 0;
|
|
711
|
+
}
|
|
712
|
+
function summarizeNetworkPayload(payload, contentType, truncated = false) {
|
|
713
|
+
if (typeof payload !== "string") {
|
|
714
|
+
return void 0;
|
|
715
|
+
}
|
|
716
|
+
const trimmed = payload.trim();
|
|
717
|
+
if (!trimmed) {
|
|
718
|
+
return void 0;
|
|
719
|
+
}
|
|
720
|
+
const normalizedContentType = contentType?.toLowerCase() ?? "";
|
|
721
|
+
let summary;
|
|
722
|
+
if (normalizedContentType.includes("json") || trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
723
|
+
try {
|
|
724
|
+
summary = summarizeJsonValue(JSON.parse(trimmed));
|
|
725
|
+
} catch {
|
|
726
|
+
summary = `json text: ${truncateSummaryText(trimmed)}`;
|
|
727
|
+
}
|
|
728
|
+
} else if (normalizedContentType.includes("x-www-form-urlencoded") || looksLikeFormBody(trimmed)) {
|
|
729
|
+
try {
|
|
730
|
+
const params = new URLSearchParams(trimmed);
|
|
731
|
+
const preview = summarizeSearchParams(params);
|
|
732
|
+
summary = preview ? `form: ${preview}` : "form body";
|
|
733
|
+
} catch {
|
|
734
|
+
summary = `form text: ${truncateSummaryText(trimmed)}`;
|
|
735
|
+
}
|
|
736
|
+
} else {
|
|
737
|
+
summary = `text: ${truncateSummaryText(trimmed)}`;
|
|
738
|
+
}
|
|
739
|
+
return truncated ? `${summary} (truncated)` : summary;
|
|
740
|
+
}
|
|
741
|
+
function buildNetworkEntryDerivedFields(entry) {
|
|
742
|
+
const parsedUrl = safeParseUrl(entry.url);
|
|
743
|
+
const preview = {
|
|
744
|
+
query: parsedUrl ? summarizeSearchParams(parsedUrl.searchParams) : void 0,
|
|
745
|
+
request: summarizeNetworkPayload(
|
|
746
|
+
entry.requestBodyPreview,
|
|
747
|
+
headerValue(entry.requestHeaders, "content-type"),
|
|
748
|
+
entry.requestBodyTruncated === true
|
|
749
|
+
),
|
|
750
|
+
response: summarizeNetworkPayload(entry.responseBodyPreview, entry.contentType, entry.responseBodyTruncated === true)
|
|
751
|
+
};
|
|
752
|
+
return {
|
|
753
|
+
hostname: parsedUrl?.hostname,
|
|
754
|
+
pathname: parsedUrl?.pathname,
|
|
755
|
+
preview: preview.query || preview.request || preview.response ? preview : void 0
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
function clampNetworkListLimit(limit, fallback = 50) {
|
|
759
|
+
return typeof limit === "number" ? Math.max(1, Math.min(500, Math.floor(limit))) : fallback;
|
|
760
|
+
}
|
|
761
|
+
function networkEntryMatchesFilters(entry, filters) {
|
|
762
|
+
const urlIncludes = typeof filters.urlIncludes === "string" ? filters.urlIncludes : "";
|
|
763
|
+
const method = typeof filters.method === "string" ? filters.method.toUpperCase() : "";
|
|
764
|
+
const status = typeof filters.status === "number" ? filters.status : void 0;
|
|
765
|
+
const domain = typeof filters.domain === "string" ? filters.domain.trim().toLowerCase() : "";
|
|
766
|
+
const resourceType = typeof filters.resourceType === "string" ? filters.resourceType.trim().toLowerCase() : "";
|
|
767
|
+
const kind = typeof filters.kind === "string" ? filters.kind : void 0;
|
|
768
|
+
const sinceTs = typeof filters.sinceTs === "number" ? filters.sinceTs : void 0;
|
|
769
|
+
if (typeof sinceTs === "number" && entry.ts < sinceTs) {
|
|
770
|
+
return false;
|
|
771
|
+
}
|
|
772
|
+
if (urlIncludes && !entry.url.includes(urlIncludes)) {
|
|
773
|
+
return false;
|
|
774
|
+
}
|
|
775
|
+
if (method && entry.method.toUpperCase() !== method) {
|
|
776
|
+
return false;
|
|
777
|
+
}
|
|
778
|
+
if (typeof status === "number" && entry.status !== status) {
|
|
779
|
+
return false;
|
|
780
|
+
}
|
|
781
|
+
if (domain) {
|
|
782
|
+
const hostname = (entry.hostname ?? safeParseUrl(entry.url)?.hostname ?? "").toLowerCase();
|
|
783
|
+
if (!hostname || !hostname.includes(domain) && hostname !== domain) {
|
|
784
|
+
return false;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
if (resourceType) {
|
|
788
|
+
if ((entry.resourceType ?? "").toLowerCase() !== resourceType) {
|
|
789
|
+
return false;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
if (kind && entry.kind !== kind) {
|
|
793
|
+
return false;
|
|
794
|
+
}
|
|
795
|
+
return true;
|
|
796
|
+
}
|
|
797
|
+
|
|
553
798
|
// package.json
|
|
554
799
|
var package_default = {
|
|
555
800
|
name: "@flrande/bak-extension",
|
|
556
|
-
version: "0.6.
|
|
801
|
+
version: "0.6.17",
|
|
557
802
|
type: "module",
|
|
558
803
|
scripts: {
|
|
559
804
|
build: "tsup src/background.ts src/content.ts src/popup.ts --format iife --out-dir dist --clean && node scripts/copy-assets.mjs",
|
|
@@ -642,18 +887,6 @@
|
|
|
642
887
|
}
|
|
643
888
|
return Object.keys(result).length > 0 ? result : void 0;
|
|
644
889
|
}
|
|
645
|
-
function headerValue(headers, name) {
|
|
646
|
-
if (!headers) {
|
|
647
|
-
return void 0;
|
|
648
|
-
}
|
|
649
|
-
const lower = name.toLowerCase();
|
|
650
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
651
|
-
if (key.toLowerCase() === lower) {
|
|
652
|
-
return value;
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
return void 0;
|
|
656
|
-
}
|
|
657
890
|
function isTextualContentType(contentType) {
|
|
658
891
|
if (!contentType) {
|
|
659
892
|
return true;
|
|
@@ -669,7 +902,8 @@
|
|
|
669
902
|
return {
|
|
670
903
|
...publicEntry,
|
|
671
904
|
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
|
|
905
|
+
responseHeaders: typeof entry.responseHeaders === "object" && entry.responseHeaders !== null ? { ...entry.responseHeaders } : void 0,
|
|
906
|
+
...buildNetworkEntryDerivedFields(entry)
|
|
673
907
|
};
|
|
674
908
|
}
|
|
675
909
|
function pushEntry(state, entry, requestId) {
|
|
@@ -851,25 +1085,11 @@
|
|
|
851
1085
|
state.requestIdToEntryId.clear();
|
|
852
1086
|
state.lastTouchedAt = Date.now();
|
|
853
1087
|
}
|
|
854
|
-
function entryMatchesFilters(entry, filters) {
|
|
855
|
-
const urlIncludes = typeof filters.urlIncludes === "string" ? filters.urlIncludes : "";
|
|
856
|
-
const method = typeof filters.method === "string" ? filters.method.toUpperCase() : "";
|
|
857
|
-
const status = typeof filters.status === "number" ? filters.status : void 0;
|
|
858
|
-
if (urlIncludes && !entry.url.includes(urlIncludes)) {
|
|
859
|
-
return false;
|
|
860
|
-
}
|
|
861
|
-
if (method && entry.method.toUpperCase() !== method) {
|
|
862
|
-
return false;
|
|
863
|
-
}
|
|
864
|
-
if (typeof status === "number" && entry.status !== status) {
|
|
865
|
-
return false;
|
|
866
|
-
}
|
|
867
|
-
return true;
|
|
868
|
-
}
|
|
869
1088
|
function listNetworkEntries(tabId, filters = {}) {
|
|
870
1089
|
const state = getState(tabId);
|
|
871
|
-
const limit =
|
|
872
|
-
|
|
1090
|
+
const limit = clampNetworkListLimit(filters.limit, 50);
|
|
1091
|
+
const ordered = state.entries.filter((entry) => networkEntryMatchesFilters(entry, filters)).slice(-limit).map((entry) => sanitizeEntry(entry));
|
|
1092
|
+
return filters.tail === true ? ordered : ordered.reverse();
|
|
873
1093
|
}
|
|
874
1094
|
function getNetworkEntry(tabId, id) {
|
|
875
1095
|
const state = getState(tabId);
|
|
@@ -886,6 +1106,7 @@
|
|
|
886
1106
|
if (entry.rawRequestBodyTruncated === true) {
|
|
887
1107
|
return {
|
|
888
1108
|
entry: publicEntry,
|
|
1109
|
+
bodyTruncated: true,
|
|
889
1110
|
degradedReason: "live replay unavailable because the captured request body was truncated in memory"
|
|
890
1111
|
};
|
|
891
1112
|
}
|
|
@@ -893,17 +1114,18 @@
|
|
|
893
1114
|
entry: publicEntry,
|
|
894
1115
|
headers: entry.rawRequestHeaders ? { ...entry.rawRequestHeaders } : void 0,
|
|
895
1116
|
body: entry.rawRequestBody,
|
|
896
|
-
contentType: headerValue(entry.rawRequestHeaders, "content-type")
|
|
1117
|
+
contentType: headerValue(entry.rawRequestHeaders, "content-type"),
|
|
1118
|
+
bodyTruncated: false
|
|
897
1119
|
};
|
|
898
1120
|
}
|
|
899
1121
|
async function waitForNetworkEntry(tabId, filters = {}) {
|
|
900
1122
|
const timeoutMs = typeof filters.timeoutMs === "number" ? Math.max(1, Math.floor(filters.timeoutMs)) : 5e3;
|
|
901
1123
|
const deadline = Date.now() + timeoutMs;
|
|
902
1124
|
const state = getState(tabId);
|
|
903
|
-
const seenIds = new Set(state.entries.filter((entry) =>
|
|
1125
|
+
const seenIds = new Set(state.entries.filter((entry) => networkEntryMatchesFilters(entry, filters)).map((entry) => entry.id));
|
|
904
1126
|
while (Date.now() < deadline) {
|
|
905
1127
|
const nextState = getState(tabId);
|
|
906
|
-
const matched = nextState.entries.find((entry) => !seenIds.has(entry.id) &&
|
|
1128
|
+
const matched = nextState.entries.find((entry) => !seenIds.has(entry.id) && networkEntryMatchesFilters(entry, filters));
|
|
907
1129
|
if (matched) {
|
|
908
1130
|
return sanitizeEntry(matched);
|
|
909
1131
|
}
|
|
@@ -924,7 +1146,7 @@
|
|
|
924
1146
|
}).toLowerCase();
|
|
925
1147
|
return entry.url.toLowerCase().includes(normalized) || (entry.requestBodyPreview ?? "").toLowerCase().includes(normalized) || (entry.responseBodyPreview ?? "").toLowerCase().includes(normalized) || headerText.includes(normalized);
|
|
926
1148
|
});
|
|
927
|
-
const scannedEntries = state.entries.filter((entry) =>
|
|
1149
|
+
const scannedEntries = state.entries.filter((entry) => networkEntryMatchesFilters(entry, {}));
|
|
928
1150
|
const toCoverage = (entries, key, truncatedKey) => ({
|
|
929
1151
|
full: entries.filter((entry) => typeof entry[key] === "string" && entry[truncatedKey] !== true).length,
|
|
930
1152
|
partial: entries.filter((entry) => typeof entry[key] === "string" && entry[truncatedKey] === true).length,
|
|
@@ -1979,6 +2201,11 @@
|
|
|
1979
2201
|
"referer",
|
|
1980
2202
|
"set-cookie"
|
|
1981
2203
|
]);
|
|
2204
|
+
var CLONE_FORBIDDEN_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
2205
|
+
...REPLAY_FORBIDDEN_HEADER_NAMES,
|
|
2206
|
+
"x-csrf-token",
|
|
2207
|
+
"x-xsrf-token"
|
|
2208
|
+
]);
|
|
1982
2209
|
var ws = null;
|
|
1983
2210
|
var reconnectTimer = null;
|
|
1984
2211
|
var nextReconnectInMs = null;
|
|
@@ -2123,6 +2350,12 @@
|
|
|
2123
2350
|
async function listSessionBindingStates() {
|
|
2124
2351
|
return Object.values(await loadSessionBindingStateMap());
|
|
2125
2352
|
}
|
|
2353
|
+
function quotePowerShellArg(value) {
|
|
2354
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
2355
|
+
}
|
|
2356
|
+
function renderPowerShellCommand(argv) {
|
|
2357
|
+
return ["bak", ...argv].map((part) => quotePowerShellArg(part)).join(" ");
|
|
2358
|
+
}
|
|
2126
2359
|
function collectPopupSessionBindingTabIds(state) {
|
|
2127
2360
|
return [
|
|
2128
2361
|
...new Set(state.tabIds.concat([state.activeTabId, state.primaryTabId].filter((value) => typeof value === "number")))
|
|
@@ -2885,6 +3118,54 @@
|
|
|
2885
3118
|
}
|
|
2886
3119
|
return {};
|
|
2887
3120
|
};
|
|
3121
|
+
const utf8ByteLength2 = (value) => {
|
|
3122
|
+
if (typeof TextEncoder === "function") {
|
|
3123
|
+
return new TextEncoder().encode(value).byteLength;
|
|
3124
|
+
}
|
|
3125
|
+
return value.length;
|
|
3126
|
+
};
|
|
3127
|
+
const truncateUtf8Text = (value, limit) => {
|
|
3128
|
+
if (limit <= 0) {
|
|
3129
|
+
return "";
|
|
3130
|
+
}
|
|
3131
|
+
if (typeof TextEncoder !== "function" || typeof TextDecoder !== "function") {
|
|
3132
|
+
return value.slice(0, limit);
|
|
3133
|
+
}
|
|
3134
|
+
const encoded = new TextEncoder().encode(value);
|
|
3135
|
+
if (encoded.byteLength <= limit) {
|
|
3136
|
+
return value;
|
|
3137
|
+
}
|
|
3138
|
+
return new TextDecoder().decode(encoded.subarray(0, limit));
|
|
3139
|
+
};
|
|
3140
|
+
const buildRetryHints = (requestUrl, baseUrl) => {
|
|
3141
|
+
const hints = [];
|
|
3142
|
+
let parsed = null;
|
|
3143
|
+
try {
|
|
3144
|
+
parsed = new URL(requestUrl, baseUrl);
|
|
3145
|
+
} catch {
|
|
3146
|
+
parsed = null;
|
|
3147
|
+
}
|
|
3148
|
+
const keys = (() => {
|
|
3149
|
+
if (!parsed) {
|
|
3150
|
+
return [];
|
|
3151
|
+
}
|
|
3152
|
+
const collected = [];
|
|
3153
|
+
parsed.searchParams.forEach((_value, key) => {
|
|
3154
|
+
collected.push(key.toLowerCase());
|
|
3155
|
+
});
|
|
3156
|
+
return [...new Set(collected)];
|
|
3157
|
+
})();
|
|
3158
|
+
if (keys.some((key) => key.includes("limit"))) {
|
|
3159
|
+
hints.push("reduce the limit parameter and retry");
|
|
3160
|
+
}
|
|
3161
|
+
if (keys.some((key) => /(from|to|start|end|date|time|timestamp)/i.test(key))) {
|
|
3162
|
+
hints.push("narrow the requested time window and retry");
|
|
3163
|
+
}
|
|
3164
|
+
if (keys.some((key) => key.includes("page")) && keys.some((key) => key.includes("limit"))) {
|
|
3165
|
+
hints.push("retry with smaller paginated windows");
|
|
3166
|
+
}
|
|
3167
|
+
return hints;
|
|
3168
|
+
};
|
|
2888
3169
|
try {
|
|
2889
3170
|
const targetWindow = payload.scope === "main" ? window : payload.scope === "current" ? resolveFrameWindow(payload.framePath ?? []) : window;
|
|
2890
3171
|
if (payload.action === "eval") {
|
|
@@ -2947,22 +3228,113 @@
|
|
|
2947
3228
|
}
|
|
2948
3229
|
}
|
|
2949
3230
|
}
|
|
3231
|
+
const requestHints = buildRetryHints(payload.url, targetWindow.location.href);
|
|
3232
|
+
const diagnosticsBase = {
|
|
3233
|
+
requestSent: false,
|
|
3234
|
+
responseStarted: false,
|
|
3235
|
+
status: void 0,
|
|
3236
|
+
headersReceived: {},
|
|
3237
|
+
bodyBytesRead: 0,
|
|
3238
|
+
partialBodyPreview: "",
|
|
3239
|
+
timing: {
|
|
3240
|
+
startedAt: Date.now()
|
|
3241
|
+
}
|
|
3242
|
+
};
|
|
3243
|
+
const previewLimit = !fullResponse && typeof payload.maxBytes === "number" && payload.maxBytes > 0 ? payload.maxBytes : 8192;
|
|
3244
|
+
let previewBytes = 0;
|
|
3245
|
+
const appendPreviewText = (value) => {
|
|
3246
|
+
if (!value || previewBytes >= previewLimit) {
|
|
3247
|
+
return;
|
|
3248
|
+
}
|
|
3249
|
+
const remaining = previewLimit - previewBytes;
|
|
3250
|
+
const next = truncateUtf8Text(value, remaining);
|
|
3251
|
+
diagnosticsBase.partialBodyPreview += next;
|
|
3252
|
+
previewBytes += utf8ByteLength2(next);
|
|
3253
|
+
};
|
|
2950
3254
|
const controller = typeof AbortController === "function" ? new AbortController() : null;
|
|
2951
3255
|
const timeoutId = controller && typeof payload.timeoutMs === "number" && payload.timeoutMs > 0 ? window.setTimeout(() => controller.abort(), payload.timeoutMs) : null;
|
|
2952
3256
|
let response;
|
|
3257
|
+
let bodyText = "";
|
|
2953
3258
|
try {
|
|
3259
|
+
diagnosticsBase.requestSent = true;
|
|
3260
|
+
diagnosticsBase.timing.requestSentAt = Date.now();
|
|
2954
3261
|
response = await targetWindow.fetch(payload.url, {
|
|
2955
3262
|
method: payload.method || "GET",
|
|
2956
3263
|
headers,
|
|
2957
3264
|
body: typeof payload.body === "string" ? payload.body : void 0,
|
|
2958
3265
|
signal: controller ? controller.signal : void 0
|
|
2959
3266
|
});
|
|
3267
|
+
diagnosticsBase.responseStarted = true;
|
|
3268
|
+
diagnosticsBase.timing.responseStartedAt = Date.now();
|
|
3269
|
+
diagnosticsBase.status = response.status;
|
|
3270
|
+
response.headers.forEach((value, key) => {
|
|
3271
|
+
diagnosticsBase.headersReceived[key] = value;
|
|
3272
|
+
});
|
|
3273
|
+
const reader = response.body?.getReader?.();
|
|
3274
|
+
if (reader) {
|
|
3275
|
+
const decoder = typeof TextDecoder === "function" ? new TextDecoder() : null;
|
|
3276
|
+
while (true) {
|
|
3277
|
+
const chunk = await reader.read();
|
|
3278
|
+
if (chunk.done) {
|
|
3279
|
+
break;
|
|
3280
|
+
}
|
|
3281
|
+
const value = chunk.value ?? new Uint8Array();
|
|
3282
|
+
diagnosticsBase.bodyBytesRead += value.byteLength;
|
|
3283
|
+
if (decoder) {
|
|
3284
|
+
const decoded = decoder.decode(value, { stream: true });
|
|
3285
|
+
bodyText += decoded;
|
|
3286
|
+
appendPreviewText(decoded);
|
|
3287
|
+
} else {
|
|
3288
|
+
const fallback = String.fromCharCode(...value);
|
|
3289
|
+
bodyText += fallback;
|
|
3290
|
+
appendPreviewText(fallback);
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
if (decoder) {
|
|
3294
|
+
const flushed = decoder.decode();
|
|
3295
|
+
if (flushed) {
|
|
3296
|
+
bodyText += flushed;
|
|
3297
|
+
appendPreviewText(flushed);
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
} else {
|
|
3301
|
+
bodyText = await response.text();
|
|
3302
|
+
diagnosticsBase.bodyBytesRead = utf8ByteLength2(bodyText);
|
|
3303
|
+
appendPreviewText(bodyText);
|
|
3304
|
+
}
|
|
3305
|
+
diagnosticsBase.timing.completedAt = Date.now();
|
|
3306
|
+
} catch (error) {
|
|
3307
|
+
const abortLike = error instanceof DOMException && error.name === "AbortError" || error instanceof Error && /abort|timeout/i.test(error.message);
|
|
3308
|
+
const where = diagnosticsBase.requestSent !== true ? "dispatch" : diagnosticsBase.responseStarted !== true ? "ttfb" : "body";
|
|
3309
|
+
const diagnostics = {
|
|
3310
|
+
kind: abortLike ? "timeout" : "network",
|
|
3311
|
+
retryable: true,
|
|
3312
|
+
where,
|
|
3313
|
+
timing: {
|
|
3314
|
+
...diagnosticsBase.timing,
|
|
3315
|
+
...abortLike ? { timeoutAt: Date.now() } : {}
|
|
3316
|
+
},
|
|
3317
|
+
requestSent: diagnosticsBase.requestSent,
|
|
3318
|
+
responseStarted: diagnosticsBase.responseStarted,
|
|
3319
|
+
status: diagnosticsBase.status,
|
|
3320
|
+
headersReceived: Object.keys(diagnosticsBase.headersReceived).length > 0 ? diagnosticsBase.headersReceived : void 0,
|
|
3321
|
+
bodyBytesRead: diagnosticsBase.bodyBytesRead,
|
|
3322
|
+
partialBodyPreview: diagnosticsBase.partialBodyPreview || void 0,
|
|
3323
|
+
hints: requestHints
|
|
3324
|
+
};
|
|
3325
|
+
throw {
|
|
3326
|
+
code: abortLike ? "E_TIMEOUT" : "E_EXECUTION",
|
|
3327
|
+
message: abortLike ? `page.fetch timeout during ${where}` : error instanceof Error ? error.message : String(error),
|
|
3328
|
+
details: {
|
|
3329
|
+
...diagnostics,
|
|
3330
|
+
diagnostics
|
|
3331
|
+
}
|
|
3332
|
+
};
|
|
2960
3333
|
} finally {
|
|
2961
3334
|
if (timeoutId !== null) {
|
|
2962
3335
|
window.clearTimeout(timeoutId);
|
|
2963
3336
|
}
|
|
2964
3337
|
}
|
|
2965
|
-
const bodyText = await response.text();
|
|
2966
3338
|
const headerMap = {};
|
|
2967
3339
|
response.headers.forEach((value, key) => {
|
|
2968
3340
|
headerMap[key] = value;
|
|
@@ -2971,13 +3343,9 @@
|
|
|
2971
3343
|
url: targetWindow.location.href,
|
|
2972
3344
|
framePath: payload.scope === "current" ? payload.framePath ?? [] : [],
|
|
2973
3345
|
value: (() => {
|
|
2974
|
-
const
|
|
2975
|
-
const decoder = typeof TextDecoder === "function" ? new TextDecoder() : null;
|
|
2976
|
-
const previewLimit = !fullResponse && typeof payload.maxBytes === "number" && payload.maxBytes > 0 ? payload.maxBytes : 8192;
|
|
2977
|
-
const encodedBody = encoder ? encoder.encode(bodyText) : null;
|
|
2978
|
-
const bodyBytes = encodedBody ? encodedBody.byteLength : bodyText.length;
|
|
3346
|
+
const bodyBytes = diagnosticsBase.bodyBytesRead || utf8ByteLength2(bodyText);
|
|
2979
3347
|
const truncated = !fullResponse && bodyBytes > previewLimit;
|
|
2980
|
-
const previewText = fullResponse ? bodyText :
|
|
3348
|
+
const previewText = fullResponse ? bodyText : truncated ? truncateUtf8Text(bodyText, previewLimit) : bodyText;
|
|
2981
3349
|
const result = {
|
|
2982
3350
|
url: response.url,
|
|
2983
3351
|
status: response.status,
|
|
@@ -2987,10 +3355,57 @@
|
|
|
2987
3355
|
bytes: bodyBytes,
|
|
2988
3356
|
truncated,
|
|
2989
3357
|
authApplied: authApplied.length > 0 ? authApplied : void 0,
|
|
2990
|
-
authSources: authSources.size > 0 ? [...authSources] : void 0
|
|
3358
|
+
authSources: authSources.size > 0 ? [...authSources] : void 0,
|
|
3359
|
+
diagnostics: {
|
|
3360
|
+
kind: "success",
|
|
3361
|
+
retryable: false,
|
|
3362
|
+
where: "complete",
|
|
3363
|
+
timing: diagnosticsBase.timing,
|
|
3364
|
+
requestSent: diagnosticsBase.requestSent,
|
|
3365
|
+
responseStarted: diagnosticsBase.responseStarted,
|
|
3366
|
+
status: response.status,
|
|
3367
|
+
headersReceived: headerMap,
|
|
3368
|
+
bodyBytesRead: bodyBytes,
|
|
3369
|
+
partialBodyPreview: diagnosticsBase.partialBodyPreview || void 0,
|
|
3370
|
+
hints: requestHints
|
|
3371
|
+
}
|
|
2991
3372
|
};
|
|
2992
3373
|
if (payload.mode === "json") {
|
|
2993
|
-
|
|
3374
|
+
let parsedJson;
|
|
3375
|
+
try {
|
|
3376
|
+
parsedJson = bodyText ? JSON.parse(bodyText) : void 0;
|
|
3377
|
+
} catch (error) {
|
|
3378
|
+
throw {
|
|
3379
|
+
code: "E_EXECUTION",
|
|
3380
|
+
message: error instanceof Error ? error.message : String(error),
|
|
3381
|
+
details: {
|
|
3382
|
+
kind: "execution",
|
|
3383
|
+
retryable: false,
|
|
3384
|
+
where: "complete",
|
|
3385
|
+
timing: diagnosticsBase.timing,
|
|
3386
|
+
requestSent: diagnosticsBase.requestSent,
|
|
3387
|
+
responseStarted: diagnosticsBase.responseStarted,
|
|
3388
|
+
status: response.status,
|
|
3389
|
+
headersReceived: headerMap,
|
|
3390
|
+
bodyBytesRead: bodyBytes,
|
|
3391
|
+
partialBodyPreview: diagnosticsBase.partialBodyPreview || void 0,
|
|
3392
|
+
hints: requestHints,
|
|
3393
|
+
diagnostics: {
|
|
3394
|
+
kind: "execution",
|
|
3395
|
+
retryable: false,
|
|
3396
|
+
where: "complete",
|
|
3397
|
+
timing: diagnosticsBase.timing,
|
|
3398
|
+
requestSent: diagnosticsBase.requestSent,
|
|
3399
|
+
responseStarted: diagnosticsBase.responseStarted,
|
|
3400
|
+
status: response.status,
|
|
3401
|
+
headersReceived: headerMap,
|
|
3402
|
+
bodyBytesRead: bodyBytes,
|
|
3403
|
+
partialBodyPreview: diagnosticsBase.partialBodyPreview || void 0,
|
|
3404
|
+
hints: requestHints
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
};
|
|
3408
|
+
}
|
|
2994
3409
|
const summary = buildJsonSummary(parsedJson);
|
|
2995
3410
|
if (fullResponse || !truncated) {
|
|
2996
3411
|
result.json = parsedJson;
|
|
@@ -3073,12 +3488,26 @@
|
|
|
3073
3488
|
delete clone.requestHeaders;
|
|
3074
3489
|
delete clone.requestBodyPreview;
|
|
3075
3490
|
delete clone.requestBodyTruncated;
|
|
3491
|
+
if (clone.preview) {
|
|
3492
|
+
clone.preview = { ...clone.preview };
|
|
3493
|
+
delete clone.preview.request;
|
|
3494
|
+
if (!clone.preview.query && !clone.preview.request && !clone.preview.response) {
|
|
3495
|
+
delete clone.preview;
|
|
3496
|
+
}
|
|
3497
|
+
}
|
|
3076
3498
|
}
|
|
3077
3499
|
if (!sections.has("response")) {
|
|
3078
3500
|
delete clone.responseHeaders;
|
|
3079
3501
|
delete clone.responseBodyPreview;
|
|
3080
3502
|
delete clone.responseBodyTruncated;
|
|
3081
3503
|
delete clone.binary;
|
|
3504
|
+
if (clone.preview) {
|
|
3505
|
+
clone.preview = { ...clone.preview };
|
|
3506
|
+
delete clone.preview.response;
|
|
3507
|
+
if (!clone.preview.query && !clone.preview.request && !clone.preview.response) {
|
|
3508
|
+
delete clone.preview;
|
|
3509
|
+
}
|
|
3510
|
+
}
|
|
3082
3511
|
}
|
|
3083
3512
|
return clone;
|
|
3084
3513
|
}
|
|
@@ -3096,6 +3525,99 @@
|
|
|
3096
3525
|
}
|
|
3097
3526
|
return Object.keys(headers).length > 0 ? headers : void 0;
|
|
3098
3527
|
}
|
|
3528
|
+
function cloneHeadersFromRequestHeaders(requestHeaders) {
|
|
3529
|
+
if (!requestHeaders) {
|
|
3530
|
+
return { omitted: [] };
|
|
3531
|
+
}
|
|
3532
|
+
const headers = {};
|
|
3533
|
+
const omitted = /* @__PURE__ */ new Set();
|
|
3534
|
+
for (const [name, value] of Object.entries(requestHeaders)) {
|
|
3535
|
+
const normalizedName = name.toLowerCase();
|
|
3536
|
+
if (CLONE_FORBIDDEN_HEADER_NAMES.has(normalizedName) || normalizedName.startsWith("sec-")) {
|
|
3537
|
+
omitted.add(normalizedName);
|
|
3538
|
+
continue;
|
|
3539
|
+
}
|
|
3540
|
+
headers[name] = value;
|
|
3541
|
+
}
|
|
3542
|
+
return {
|
|
3543
|
+
headers: Object.keys(headers).length > 0 ? headers : void 0,
|
|
3544
|
+
omitted: [...omitted].sort()
|
|
3545
|
+
};
|
|
3546
|
+
}
|
|
3547
|
+
function isSameOriginWithTab(tabUrl, requestUrl) {
|
|
3548
|
+
try {
|
|
3549
|
+
if (!tabUrl) {
|
|
3550
|
+
return false;
|
|
3551
|
+
}
|
|
3552
|
+
return new URL(requestUrl).origin === new URL(tabUrl).origin;
|
|
3553
|
+
} catch {
|
|
3554
|
+
return false;
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
function buildNetworkCloneResult(requestId, tabUrl, replayable) {
|
|
3558
|
+
const sameOrigin = isSameOriginWithTab(tabUrl, replayable.entry.url);
|
|
3559
|
+
const cloneable = sameOrigin && replayable.bodyTruncated !== true;
|
|
3560
|
+
const notes = [];
|
|
3561
|
+
const sanitizedHeaders = cloneHeadersFromRequestHeaders(replayable.headers);
|
|
3562
|
+
if (sanitizedHeaders.omitted.length > 0) {
|
|
3563
|
+
notes.push(`omitted sensitive headers: ${sanitizedHeaders.omitted.join(", ")}`);
|
|
3564
|
+
}
|
|
3565
|
+
if (!sameOrigin) {
|
|
3566
|
+
notes.push("preferred page.fetch template was skipped because the captured request is not same-origin with the current page");
|
|
3567
|
+
}
|
|
3568
|
+
if (replayable.bodyTruncated) {
|
|
3569
|
+
notes.push("captured request body was truncated, so bak cannot emit a reliable page.fetch clone");
|
|
3570
|
+
}
|
|
3571
|
+
if (!sameOrigin || replayable.bodyTruncated) {
|
|
3572
|
+
notes.push("falling back to network.replay preserves the captured request shape more safely");
|
|
3573
|
+
} else {
|
|
3574
|
+
notes.push("page.fetch clone keeps session cookies and auto-applies same-origin auth helpers");
|
|
3575
|
+
}
|
|
3576
|
+
const pageFetch = sameOrigin && replayable.bodyTruncated !== true ? {
|
|
3577
|
+
url: replayable.entry.url,
|
|
3578
|
+
method: replayable.entry.method,
|
|
3579
|
+
headers: sanitizedHeaders.headers,
|
|
3580
|
+
body: replayable.body,
|
|
3581
|
+
contentType: replayable.contentType,
|
|
3582
|
+
mode: (replayable.entry.contentType ?? replayable.contentType)?.toLowerCase().includes("json") ? "json" : "raw",
|
|
3583
|
+
auth: "auto"
|
|
3584
|
+
} : void 0;
|
|
3585
|
+
const preferredArgv = pageFetch ? [
|
|
3586
|
+
"page",
|
|
3587
|
+
"fetch",
|
|
3588
|
+
"--url",
|
|
3589
|
+
pageFetch.url,
|
|
3590
|
+
"--method",
|
|
3591
|
+
pageFetch.method,
|
|
3592
|
+
"--auth",
|
|
3593
|
+
pageFetch.auth,
|
|
3594
|
+
"--mode",
|
|
3595
|
+
pageFetch.mode ?? "raw",
|
|
3596
|
+
...pageFetch.contentType ? ["--content-type", pageFetch.contentType] : [],
|
|
3597
|
+
...Object.entries(pageFetch.headers ?? {}).flatMap(([name, value]) => ["--header", `${name}: ${value}`]),
|
|
3598
|
+
...typeof pageFetch.body === "string" ? ["--body", pageFetch.body] : []
|
|
3599
|
+
] : ["network", "replay", "--request-id", requestId, "--auth", "auto"];
|
|
3600
|
+
return {
|
|
3601
|
+
request: {
|
|
3602
|
+
id: replayable.entry.id,
|
|
3603
|
+
url: replayable.entry.url,
|
|
3604
|
+
method: replayable.entry.method,
|
|
3605
|
+
kind: replayable.entry.kind,
|
|
3606
|
+
contentType: replayable.contentType,
|
|
3607
|
+
sameOrigin,
|
|
3608
|
+
bodyPresent: typeof replayable.body === "string" && replayable.body.length > 0,
|
|
3609
|
+
bodyTruncated: replayable.bodyTruncated
|
|
3610
|
+
},
|
|
3611
|
+
cloneable,
|
|
3612
|
+
preferredCommand: {
|
|
3613
|
+
tool: pageFetch ? "page.fetch" : "network.replay",
|
|
3614
|
+
argv: preferredArgv,
|
|
3615
|
+
powershell: renderPowerShellCommand(preferredArgv)
|
|
3616
|
+
},
|
|
3617
|
+
pageFetch,
|
|
3618
|
+
notes
|
|
3619
|
+
};
|
|
3620
|
+
}
|
|
3099
3621
|
function collectTimestampMatchesFromText(text, source, patterns) {
|
|
3100
3622
|
const regexes = (patterns ?? [
|
|
3101
3623
|
String.raw`\b20\d{2}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?\b`,
|
|
@@ -3516,6 +4038,7 @@
|
|
|
3516
4038
|
const pageDataCandidates = await probePageDataCandidatesForTab(tabId, inspection);
|
|
3517
4039
|
const recentNetwork = listNetworkEntries(tabId, { limit: 25 });
|
|
3518
4040
|
const pageDataReport = buildInspectPageDataResult({
|
|
4041
|
+
pageUrl: inspection.url,
|
|
3519
4042
|
suspiciousGlobals: inspection.suspiciousGlobals ?? [],
|
|
3520
4043
|
tables: inspection.tables ?? [],
|
|
3521
4044
|
visibleTimestamps: inspection.visibleTimestamps ?? [],
|
|
@@ -3523,7 +4046,9 @@
|
|
|
3523
4046
|
pageDataCandidates,
|
|
3524
4047
|
recentNetwork,
|
|
3525
4048
|
tableAnalyses: tables,
|
|
3526
|
-
inlineJsonSources: Array.isArray(inspection.inlineJsonSources) ? inspection.inlineJsonSources : []
|
|
4049
|
+
inlineJsonSources: Array.isArray(inspection.inlineJsonSources) ? inspection.inlineJsonSources : [],
|
|
4050
|
+
modeGroups: Array.isArray(inspection.modeGroups) ? inspection.modeGroups : [],
|
|
4051
|
+
dateControls: Array.isArray(inspection.dateControls) ? inspection.dateControls : []
|
|
3527
4052
|
});
|
|
3528
4053
|
const matched = selectReplaySchemaMatch(response.json, tables, {
|
|
3529
4054
|
preferredSourceId: `networkResponse:${requestId}`,
|
|
@@ -3943,7 +4468,12 @@
|
|
|
3943
4468
|
limit: typeof params.limit === "number" ? params.limit : void 0,
|
|
3944
4469
|
urlIncludes: typeof params.urlIncludes === "string" ? params.urlIncludes : void 0,
|
|
3945
4470
|
status: typeof params.status === "number" ? params.status : void 0,
|
|
3946
|
-
method: typeof params.method === "string" ? params.method : void 0
|
|
4471
|
+
method: typeof params.method === "string" ? params.method : void 0,
|
|
4472
|
+
domain: typeof params.domain === "string" ? params.domain : void 0,
|
|
4473
|
+
resourceType: typeof params.resourceType === "string" ? params.resourceType : void 0,
|
|
4474
|
+
kind: typeof params.kind === "string" ? params.kind : void 0,
|
|
4475
|
+
sinceTs: typeof params.sinceTs === "number" ? params.sinceTs : void 0,
|
|
4476
|
+
tail: params.tail === true
|
|
3947
4477
|
})
|
|
3948
4478
|
};
|
|
3949
4479
|
} catch {
|
|
@@ -3980,6 +4510,17 @@
|
|
|
3980
4510
|
return searchNetworkEntries(tab.id, String(params.pattern ?? ""), typeof params.limit === "number" ? params.limit : 50);
|
|
3981
4511
|
});
|
|
3982
4512
|
}
|
|
4513
|
+
case "network.clone": {
|
|
4514
|
+
return await preserveHumanFocus(typeof target.tabId !== "number", async () => {
|
|
4515
|
+
const tab = await withTab(target);
|
|
4516
|
+
await ensureTabNetworkCapture(tab.id);
|
|
4517
|
+
const replayable = getReplayableNetworkRequest(tab.id, String(params.id ?? ""));
|
|
4518
|
+
if (!replayable) {
|
|
4519
|
+
throw toError("E_NOT_FOUND", `network entry not found: ${String(params.id ?? "")}`);
|
|
4520
|
+
}
|
|
4521
|
+
return buildNetworkCloneResult(String(params.id ?? ""), tab.url, replayable);
|
|
4522
|
+
});
|
|
4523
|
+
}
|
|
3983
4524
|
case "network.waitFor": {
|
|
3984
4525
|
return await preserveHumanFocus(typeof target.tabId !== "number", async () => {
|
|
3985
4526
|
const tab = await withTab(target);
|
|
@@ -4130,9 +4671,10 @@
|
|
|
4130
4671
|
await ensureNetworkDebugger(tab.id).catch(() => void 0);
|
|
4131
4672
|
const inspection = await collectPageInspection(tab.id, params);
|
|
4132
4673
|
const pageDataCandidates = await probePageDataCandidatesForTab(tab.id, inspection);
|
|
4133
|
-
const network = listNetworkEntries(tab.id, { limit:
|
|
4674
|
+
const network = listNetworkEntries(tab.id, { limit: 25, tail: true });
|
|
4134
4675
|
const tableAnalyses = await collectTableAnalyses(tab.id);
|
|
4135
4676
|
const enriched = buildInspectPageDataResult({
|
|
4677
|
+
pageUrl: inspection.url,
|
|
4136
4678
|
suspiciousGlobals: inspection.suspiciousGlobals ?? [],
|
|
4137
4679
|
tables: inspection.tables ?? [],
|
|
4138
4680
|
visibleTimestamps: inspection.visibleTimestamps ?? [],
|
|
@@ -4140,7 +4682,9 @@
|
|
|
4140
4682
|
pageDataCandidates,
|
|
4141
4683
|
recentNetwork: network,
|
|
4142
4684
|
tableAnalyses,
|
|
4143
|
-
inlineJsonSources: Array.isArray(inspection.inlineJsonSources) ? inspection.inlineJsonSources : []
|
|
4685
|
+
inlineJsonSources: Array.isArray(inspection.inlineJsonSources) ? inspection.inlineJsonSources : [],
|
|
4686
|
+
modeGroups: Array.isArray(inspection.modeGroups) ? inspection.modeGroups : [],
|
|
4687
|
+
dateControls: Array.isArray(inspection.dateControls) ? inspection.dateControls : []
|
|
4144
4688
|
});
|
|
4145
4689
|
const recommendedNextSteps = enriched.recommendedNextActions.map((action) => action.command);
|
|
4146
4690
|
return {
|
|
@@ -4150,6 +4694,12 @@
|
|
|
4150
4694
|
inlineTimestamps: inspection.inlineTimestamps ?? [],
|
|
4151
4695
|
pageDataCandidates,
|
|
4152
4696
|
recentNetwork: network,
|
|
4697
|
+
modeGroups: Array.isArray(inspection.modeGroups) ? inspection.modeGroups : [],
|
|
4698
|
+
availableModes: enriched.availableModes,
|
|
4699
|
+
currentMode: enriched.currentMode,
|
|
4700
|
+
dateControls: Array.isArray(inspection.dateControls) ? inspection.dateControls : [],
|
|
4701
|
+
latestArchiveDate: enriched.latestArchiveDate,
|
|
4702
|
+
primaryEndpoint: enriched.primaryEndpoint,
|
|
4153
4703
|
dataSources: enriched.dataSources,
|
|
4154
4704
|
sourceMappings: enriched.sourceMappings,
|
|
4155
4705
|
recommendedNextActions: enriched.recommendedNextActions,
|