@flrande/bak-extension 0.6.15 → 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 +898 -112
- package/dist/content.global.js +365 -23
- package/dist/manifest.json +1 -1
- package/package.json +2 -2
- package/src/background.ts +865 -273
- package/src/content.ts +266 -27
- package/src/dynamic-data-tools.ts +141 -7
- package/src/network-debugger.ts +106 -56
- package/src/network-tools.ts +184 -0
|
@@ -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",
|
|
@@ -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) {
|
|
@@ -533,9 +629,6 @@
|
|
|
533
629
|
function shouldRedactHeader(name) {
|
|
534
630
|
return SENSITIVE_HEADER_PATTERNS.some((pattern) => pattern.test(name));
|
|
535
631
|
}
|
|
536
|
-
function containsRedactionMarker(raw) {
|
|
537
|
-
return typeof raw === "string" && raw.includes("[REDACTED");
|
|
538
|
-
}
|
|
539
632
|
function redactTransportText(raw) {
|
|
540
633
|
if (!raw) {
|
|
541
634
|
return "";
|
|
@@ -553,10 +646,159 @@
|
|
|
553
646
|
return Object.keys(result).length > 0 ? result : void 0;
|
|
554
647
|
}
|
|
555
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
|
+
|
|
556
798
|
// package.json
|
|
557
799
|
var package_default = {
|
|
558
800
|
name: "@flrande/bak-extension",
|
|
559
|
-
version: "0.6.
|
|
801
|
+
version: "0.6.17",
|
|
560
802
|
type: "module",
|
|
561
803
|
scripts: {
|
|
562
804
|
build: "tsup src/background.ts src/content.ts src/popup.ts --format iife --out-dir dist --clean && node scripts/copy-assets.mjs",
|
|
@@ -645,18 +887,6 @@
|
|
|
645
887
|
}
|
|
646
888
|
return Object.keys(result).length > 0 ? result : void 0;
|
|
647
889
|
}
|
|
648
|
-
function headerValue(headers, name) {
|
|
649
|
-
if (!headers) {
|
|
650
|
-
return void 0;
|
|
651
|
-
}
|
|
652
|
-
const lower = name.toLowerCase();
|
|
653
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
654
|
-
if (key.toLowerCase() === lower) {
|
|
655
|
-
return value;
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
return void 0;
|
|
659
|
-
}
|
|
660
890
|
function isTextualContentType(contentType) {
|
|
661
891
|
if (!contentType) {
|
|
662
892
|
return true;
|
|
@@ -664,6 +894,18 @@
|
|
|
664
894
|
const normalized = contentType.toLowerCase();
|
|
665
895
|
return normalized.startsWith("text/") || normalized.includes("json") || normalized.includes("javascript") || normalized.includes("xml") || normalized.includes("html") || normalized.includes("urlencoded") || normalized.includes("graphql");
|
|
666
896
|
}
|
|
897
|
+
function sanitizeEntry(entry) {
|
|
898
|
+
const { rawRequestHeaders, rawRequestBody, rawRequestBodyTruncated, ...publicEntry } = entry;
|
|
899
|
+
void rawRequestHeaders;
|
|
900
|
+
void rawRequestBody;
|
|
901
|
+
void rawRequestBodyTruncated;
|
|
902
|
+
return {
|
|
903
|
+
...publicEntry,
|
|
904
|
+
requestHeaders: typeof entry.requestHeaders === "object" && entry.requestHeaders !== null ? { ...entry.requestHeaders } : void 0,
|
|
905
|
+
responseHeaders: typeof entry.responseHeaders === "object" && entry.responseHeaders !== null ? { ...entry.responseHeaders } : void 0,
|
|
906
|
+
...buildNetworkEntryDerivedFields(entry)
|
|
907
|
+
};
|
|
908
|
+
}
|
|
667
909
|
function pushEntry(state, entry, requestId) {
|
|
668
910
|
state.entries.push(entry);
|
|
669
911
|
state.entriesById.set(entry.id, entry);
|
|
@@ -737,7 +979,8 @@
|
|
|
737
979
|
return;
|
|
738
980
|
}
|
|
739
981
|
const request = typeof params.request === "object" && params.request !== null ? params.request : {};
|
|
740
|
-
const
|
|
982
|
+
const rawHeaders = normalizeHeaders(request.headers);
|
|
983
|
+
const headers = redactHeaderMap(rawHeaders);
|
|
741
984
|
const truncatedRequest = truncateText(typeof request.postData === "string" ? request.postData : void 0, DEFAULT_BODY_BYTES);
|
|
742
985
|
const entry = {
|
|
743
986
|
id: `net_${tabId}_${requestId}`,
|
|
@@ -754,6 +997,9 @@
|
|
|
754
997
|
requestHeaders: headers,
|
|
755
998
|
requestBodyPreview: truncatedRequest.text ? redactTransportText(truncatedRequest.text) : void 0,
|
|
756
999
|
requestBodyTruncated: truncatedRequest.truncated,
|
|
1000
|
+
rawRequestHeaders: rawHeaders,
|
|
1001
|
+
rawRequestBody: typeof request.postData === "string" ? request.postData : void 0,
|
|
1002
|
+
rawRequestBodyTruncated: false,
|
|
757
1003
|
initiatorUrl: typeof params.initiator === "object" && params.initiator !== null && typeof params.initiator.url === "string" ? String(params.initiator.url) : void 0,
|
|
758
1004
|
tabId,
|
|
759
1005
|
source: "debugger"
|
|
@@ -839,41 +1085,49 @@
|
|
|
839
1085
|
state.requestIdToEntryId.clear();
|
|
840
1086
|
state.lastTouchedAt = Date.now();
|
|
841
1087
|
}
|
|
842
|
-
function entryMatchesFilters(entry, filters) {
|
|
843
|
-
const urlIncludes = typeof filters.urlIncludes === "string" ? filters.urlIncludes : "";
|
|
844
|
-
const method = typeof filters.method === "string" ? filters.method.toUpperCase() : "";
|
|
845
|
-
const status = typeof filters.status === "number" ? filters.status : void 0;
|
|
846
|
-
if (urlIncludes && !entry.url.includes(urlIncludes)) {
|
|
847
|
-
return false;
|
|
848
|
-
}
|
|
849
|
-
if (method && entry.method.toUpperCase() !== method) {
|
|
850
|
-
return false;
|
|
851
|
-
}
|
|
852
|
-
if (typeof status === "number" && entry.status !== status) {
|
|
853
|
-
return false;
|
|
854
|
-
}
|
|
855
|
-
return true;
|
|
856
|
-
}
|
|
857
1088
|
function listNetworkEntries(tabId, filters = {}) {
|
|
858
1089
|
const state = getState(tabId);
|
|
859
|
-
const limit =
|
|
860
|
-
|
|
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();
|
|
861
1093
|
}
|
|
862
1094
|
function getNetworkEntry(tabId, id) {
|
|
863
1095
|
const state = getState(tabId);
|
|
864
1096
|
const entry = state.entriesById.get(id);
|
|
865
|
-
return entry ?
|
|
1097
|
+
return entry ? sanitizeEntry(entry) : null;
|
|
1098
|
+
}
|
|
1099
|
+
function getReplayableNetworkRequest(tabId, id) {
|
|
1100
|
+
const state = getState(tabId);
|
|
1101
|
+
const entry = state.entriesById.get(id);
|
|
1102
|
+
if (!entry) {
|
|
1103
|
+
return null;
|
|
1104
|
+
}
|
|
1105
|
+
const publicEntry = sanitizeEntry(entry);
|
|
1106
|
+
if (entry.rawRequestBodyTruncated === true) {
|
|
1107
|
+
return {
|
|
1108
|
+
entry: publicEntry,
|
|
1109
|
+
bodyTruncated: true,
|
|
1110
|
+
degradedReason: "live replay unavailable because the captured request body was truncated in memory"
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
return {
|
|
1114
|
+
entry: publicEntry,
|
|
1115
|
+
headers: entry.rawRequestHeaders ? { ...entry.rawRequestHeaders } : void 0,
|
|
1116
|
+
body: entry.rawRequestBody,
|
|
1117
|
+
contentType: headerValue(entry.rawRequestHeaders, "content-type"),
|
|
1118
|
+
bodyTruncated: false
|
|
1119
|
+
};
|
|
866
1120
|
}
|
|
867
1121
|
async function waitForNetworkEntry(tabId, filters = {}) {
|
|
868
1122
|
const timeoutMs = typeof filters.timeoutMs === "number" ? Math.max(1, Math.floor(filters.timeoutMs)) : 5e3;
|
|
869
1123
|
const deadline = Date.now() + timeoutMs;
|
|
870
1124
|
const state = getState(tabId);
|
|
871
|
-
const seenIds = new Set(state.entries.filter((entry) =>
|
|
1125
|
+
const seenIds = new Set(state.entries.filter((entry) => networkEntryMatchesFilters(entry, filters)).map((entry) => entry.id));
|
|
872
1126
|
while (Date.now() < deadline) {
|
|
873
1127
|
const nextState = getState(tabId);
|
|
874
|
-
const matched = nextState.entries.find((entry) => !seenIds.has(entry.id) &&
|
|
1128
|
+
const matched = nextState.entries.find((entry) => !seenIds.has(entry.id) && networkEntryMatchesFilters(entry, filters));
|
|
875
1129
|
if (matched) {
|
|
876
|
-
return
|
|
1130
|
+
return sanitizeEntry(matched);
|
|
877
1131
|
}
|
|
878
1132
|
await new Promise((resolve) => setTimeout(resolve, 75));
|
|
879
1133
|
}
|
|
@@ -883,14 +1137,30 @@
|
|
|
883
1137
|
};
|
|
884
1138
|
}
|
|
885
1139
|
function searchNetworkEntries(tabId, pattern, limit = 50) {
|
|
1140
|
+
const state = getState(tabId);
|
|
886
1141
|
const normalized = pattern.toLowerCase();
|
|
887
|
-
|
|
1142
|
+
const matchedEntries = state.entries.filter((entry) => {
|
|
888
1143
|
const headerText = JSON.stringify({
|
|
889
1144
|
requestHeaders: entry.requestHeaders,
|
|
890
1145
|
responseHeaders: entry.responseHeaders
|
|
891
1146
|
}).toLowerCase();
|
|
892
1147
|
return entry.url.toLowerCase().includes(normalized) || (entry.requestBodyPreview ?? "").toLowerCase().includes(normalized) || (entry.responseBodyPreview ?? "").toLowerCase().includes(normalized) || headerText.includes(normalized);
|
|
893
1148
|
});
|
|
1149
|
+
const scannedEntries = state.entries.filter((entry) => networkEntryMatchesFilters(entry, {}));
|
|
1150
|
+
const toCoverage = (entries, key, truncatedKey) => ({
|
|
1151
|
+
full: entries.filter((entry) => typeof entry[key] === "string" && entry[truncatedKey] !== true).length,
|
|
1152
|
+
partial: entries.filter((entry) => typeof entry[key] === "string" && entry[truncatedKey] === true).length,
|
|
1153
|
+
none: entries.filter((entry) => typeof entry[key] !== "string").length
|
|
1154
|
+
});
|
|
1155
|
+
return {
|
|
1156
|
+
entries: matchedEntries.slice(-Math.max(limit, 1)).reverse().map((entry) => sanitizeEntry(entry)),
|
|
1157
|
+
scanned: scannedEntries.length,
|
|
1158
|
+
matched: matchedEntries.length,
|
|
1159
|
+
bodyCoverage: {
|
|
1160
|
+
request: toCoverage(scannedEntries, "requestBodyPreview", "requestBodyTruncated"),
|
|
1161
|
+
response: toCoverage(scannedEntries, "responseBodyPreview", "responseBodyTruncated")
|
|
1162
|
+
}
|
|
1163
|
+
};
|
|
894
1164
|
}
|
|
895
1165
|
function latestNetworkTimestamp(tabId) {
|
|
896
1166
|
const entries = listNetworkEntries(tabId, { limit: MAX_ENTRIES });
|
|
@@ -1931,6 +2201,11 @@
|
|
|
1931
2201
|
"referer",
|
|
1932
2202
|
"set-cookie"
|
|
1933
2203
|
]);
|
|
2204
|
+
var CLONE_FORBIDDEN_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
2205
|
+
...REPLAY_FORBIDDEN_HEADER_NAMES,
|
|
2206
|
+
"x-csrf-token",
|
|
2207
|
+
"x-xsrf-token"
|
|
2208
|
+
]);
|
|
1934
2209
|
var ws = null;
|
|
1935
2210
|
var reconnectTimer = null;
|
|
1936
2211
|
var nextReconnectInMs = null;
|
|
@@ -2075,6 +2350,12 @@
|
|
|
2075
2350
|
async function listSessionBindingStates() {
|
|
2076
2351
|
return Object.values(await loadSessionBindingStateMap());
|
|
2077
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
|
+
}
|
|
2078
2359
|
function collectPopupSessionBindingTabIds(state) {
|
|
2079
2360
|
return [
|
|
2080
2361
|
...new Set(state.tabIds.concat([state.activeTabId, state.primaryTabId].filter((value) => typeof value === "number")))
|
|
@@ -2507,7 +2788,18 @@
|
|
|
2507
2788
|
await new Promise((resolve) => setTimeout(resolve, 80));
|
|
2508
2789
|
}
|
|
2509
2790
|
try {
|
|
2510
|
-
return
|
|
2791
|
+
return {
|
|
2792
|
+
captureStatus: "complete",
|
|
2793
|
+
imageData: await chrome.tabs.captureVisibleTab(tab.windowId, { format: "png" })
|
|
2794
|
+
};
|
|
2795
|
+
} catch (error) {
|
|
2796
|
+
return {
|
|
2797
|
+
captureStatus: "degraded",
|
|
2798
|
+
captureError: {
|
|
2799
|
+
code: "E_CAPTURE_FAILED",
|
|
2800
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2801
|
+
}
|
|
2802
|
+
};
|
|
2511
2803
|
} finally {
|
|
2512
2804
|
if (shouldSwitch && typeof activeTab?.id === "number") {
|
|
2513
2805
|
try {
|
|
@@ -2638,7 +2930,9 @@
|
|
|
2638
2930
|
contentType: typeof params.contentType === "string" ? params.contentType : void 0,
|
|
2639
2931
|
mode: params.mode === "json" ? "json" : "raw",
|
|
2640
2932
|
maxBytes: typeof params.maxBytes === "number" ? params.maxBytes : void 0,
|
|
2641
|
-
timeoutMs: typeof params.timeoutMs === "number" ? params.timeoutMs : void 0
|
|
2933
|
+
timeoutMs: typeof params.timeoutMs === "number" ? params.timeoutMs : void 0,
|
|
2934
|
+
fullResponse: params.fullResponse === true,
|
|
2935
|
+
auth: params.auth === "manual" || params.auth === "off" ? params.auth : "auto"
|
|
2642
2936
|
}
|
|
2643
2937
|
],
|
|
2644
2938
|
func: async (payload) => {
|
|
@@ -2761,6 +3055,117 @@
|
|
|
2761
3055
|
}
|
|
2762
3056
|
return { resolver: "lexical", value: readLexical() };
|
|
2763
3057
|
};
|
|
3058
|
+
const findHeaderName = (headers, name) => Object.keys(headers).find((key) => key.toLowerCase() === name.toLowerCase());
|
|
3059
|
+
const findCookieValue = (cookieString, name) => {
|
|
3060
|
+
const targetName = `${name}=`;
|
|
3061
|
+
for (const segment of cookieString.split(";")) {
|
|
3062
|
+
const trimmed = segment.trim();
|
|
3063
|
+
if (trimmed.toLowerCase().startsWith(targetName.toLowerCase())) {
|
|
3064
|
+
return trimmed.slice(targetName.length);
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
return void 0;
|
|
3068
|
+
};
|
|
3069
|
+
const buildJsonSummary = (value) => {
|
|
3070
|
+
const rowsCandidate = (() => {
|
|
3071
|
+
if (Array.isArray(value)) {
|
|
3072
|
+
return value;
|
|
3073
|
+
}
|
|
3074
|
+
if (typeof value !== "object" || value === null) {
|
|
3075
|
+
return null;
|
|
3076
|
+
}
|
|
3077
|
+
const record = value;
|
|
3078
|
+
for (const key of ["data", "rows", "results", "items"]) {
|
|
3079
|
+
if (Array.isArray(record[key])) {
|
|
3080
|
+
return record[key];
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
return null;
|
|
3084
|
+
})();
|
|
3085
|
+
if (Array.isArray(rowsCandidate) && rowsCandidate.length > 0) {
|
|
3086
|
+
const objectRows = rowsCandidate.filter((row) => typeof row === "object" && row !== null && !Array.isArray(row)).slice(0, 25);
|
|
3087
|
+
if (objectRows.length > 0) {
|
|
3088
|
+
const columns = [...new Set(objectRows.flatMap((row) => Object.keys(row)))].slice(0, 20);
|
|
3089
|
+
return {
|
|
3090
|
+
schema: {
|
|
3091
|
+
columns: columns.map((label, index) => ({
|
|
3092
|
+
key: `col_${index + 1}`,
|
|
3093
|
+
label
|
|
3094
|
+
}))
|
|
3095
|
+
},
|
|
3096
|
+
mappedRows: objectRows.map((row) => {
|
|
3097
|
+
const mapped = {};
|
|
3098
|
+
for (const column of columns) {
|
|
3099
|
+
mapped[column] = row[column];
|
|
3100
|
+
}
|
|
3101
|
+
return mapped;
|
|
3102
|
+
})
|
|
3103
|
+
};
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
3107
|
+
const columns = Object.keys(value).slice(0, 20);
|
|
3108
|
+
if (columns.length > 0) {
|
|
3109
|
+
return {
|
|
3110
|
+
schema: {
|
|
3111
|
+
columns: columns.map((label, index) => ({
|
|
3112
|
+
key: `col_${index + 1}`,
|
|
3113
|
+
label
|
|
3114
|
+
}))
|
|
3115
|
+
}
|
|
3116
|
+
};
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
return {};
|
|
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
|
+
};
|
|
2764
3169
|
try {
|
|
2765
3170
|
const targetWindow = payload.scope === "main" ? window : payload.scope === "current" ? resolveFrameWindow(payload.framePath ?? []) : window;
|
|
2766
3171
|
if (payload.action === "eval") {
|
|
@@ -2781,25 +3186,155 @@
|
|
|
2781
3186
|
}
|
|
2782
3187
|
if (payload.action === "fetch") {
|
|
2783
3188
|
const headers = { ...payload.headers ?? {} };
|
|
2784
|
-
if (payload.contentType && !headers
|
|
3189
|
+
if (payload.contentType && !findHeaderName(headers, "Content-Type")) {
|
|
2785
3190
|
headers["Content-Type"] = payload.contentType;
|
|
2786
3191
|
}
|
|
3192
|
+
const fullResponse = payload.fullResponse === true;
|
|
3193
|
+
const authApplied = [];
|
|
3194
|
+
const authSources = /* @__PURE__ */ new Set();
|
|
3195
|
+
const requestUrl = new URL(payload.url, targetWindow.location.href);
|
|
3196
|
+
const sameOrigin = requestUrl.origin === targetWindow.location.origin;
|
|
3197
|
+
const authMode = payload.auth === "manual" || payload.auth === "off" ? payload.auth : "auto";
|
|
3198
|
+
const maybeApplyHeader = (name, value, source) => {
|
|
3199
|
+
if (!value || findHeaderName(headers, name)) {
|
|
3200
|
+
return;
|
|
3201
|
+
}
|
|
3202
|
+
headers[name] = value;
|
|
3203
|
+
authApplied.push(name);
|
|
3204
|
+
authSources.add(source);
|
|
3205
|
+
};
|
|
3206
|
+
if (sameOrigin && authMode === "auto") {
|
|
3207
|
+
const xsrfCookie = findCookieValue(targetWindow.document.cookie ?? "", "XSRF-TOKEN");
|
|
3208
|
+
if (xsrfCookie) {
|
|
3209
|
+
maybeApplyHeader("X-XSRF-TOKEN", decodeURIComponent(xsrfCookie), "cookie:XSRF-TOKEN");
|
|
3210
|
+
}
|
|
3211
|
+
const metaTokens = [
|
|
3212
|
+
{
|
|
3213
|
+
selector: 'meta[name="xsrf-token"], meta[name="x-xsrf-token"]',
|
|
3214
|
+
header: "X-XSRF-TOKEN",
|
|
3215
|
+
source: "meta:xsrf-token"
|
|
3216
|
+
},
|
|
3217
|
+
{
|
|
3218
|
+
selector: 'meta[name="csrf-token"], meta[name="csrf_token"], meta[name="_csrf"]',
|
|
3219
|
+
header: "X-CSRF-TOKEN",
|
|
3220
|
+
source: "meta:csrf-token"
|
|
3221
|
+
}
|
|
3222
|
+
];
|
|
3223
|
+
for (const token of metaTokens) {
|
|
3224
|
+
const meta = targetWindow.document.querySelector(token.selector);
|
|
3225
|
+
const content = meta?.content?.trim();
|
|
3226
|
+
if (content) {
|
|
3227
|
+
maybeApplyHeader(token.header, content, token.source);
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
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
|
+
};
|
|
2787
3254
|
const controller = typeof AbortController === "function" ? new AbortController() : null;
|
|
2788
3255
|
const timeoutId = controller && typeof payload.timeoutMs === "number" && payload.timeoutMs > 0 ? window.setTimeout(() => controller.abort(), payload.timeoutMs) : null;
|
|
2789
3256
|
let response;
|
|
3257
|
+
let bodyText = "";
|
|
2790
3258
|
try {
|
|
3259
|
+
diagnosticsBase.requestSent = true;
|
|
3260
|
+
diagnosticsBase.timing.requestSentAt = Date.now();
|
|
2791
3261
|
response = await targetWindow.fetch(payload.url, {
|
|
2792
3262
|
method: payload.method || "GET",
|
|
2793
3263
|
headers,
|
|
2794
3264
|
body: typeof payload.body === "string" ? payload.body : void 0,
|
|
2795
3265
|
signal: controller ? controller.signal : void 0
|
|
2796
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
|
+
};
|
|
2797
3333
|
} finally {
|
|
2798
3334
|
if (timeoutId !== null) {
|
|
2799
3335
|
window.clearTimeout(timeoutId);
|
|
2800
3336
|
}
|
|
2801
3337
|
}
|
|
2802
|
-
const bodyText = await response.text();
|
|
2803
3338
|
const headerMap = {};
|
|
2804
3339
|
response.headers.forEach((value, key) => {
|
|
2805
3340
|
headerMap[key] = value;
|
|
@@ -2808,34 +3343,88 @@
|
|
|
2808
3343
|
url: targetWindow.location.href,
|
|
2809
3344
|
framePath: payload.scope === "current" ? payload.framePath ?? [] : [],
|
|
2810
3345
|
value: (() => {
|
|
2811
|
-
const
|
|
2812
|
-
const
|
|
2813
|
-
const
|
|
2814
|
-
const
|
|
2815
|
-
const bodyBytes = encodedBody ? encodedBody.byteLength : bodyText.length;
|
|
2816
|
-
const truncated = bodyBytes > previewLimit;
|
|
2817
|
-
if (payload.mode === "json" && truncated) {
|
|
2818
|
-
throw {
|
|
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 {
|
|
3346
|
+
const bodyBytes = diagnosticsBase.bodyBytesRead || utf8ByteLength2(bodyText);
|
|
3347
|
+
const truncated = !fullResponse && bodyBytes > previewLimit;
|
|
3348
|
+
const previewText = fullResponse ? bodyText : truncated ? truncateUtf8Text(bodyText, previewLimit) : bodyText;
|
|
3349
|
+
const result = {
|
|
2829
3350
|
url: response.url,
|
|
2830
3351
|
status: response.status,
|
|
2831
3352
|
ok: response.ok,
|
|
2832
3353
|
headers: headerMap,
|
|
2833
3354
|
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
3355
|
bytes: bodyBytes,
|
|
2837
|
-
truncated
|
|
3356
|
+
truncated,
|
|
3357
|
+
authApplied: authApplied.length > 0 ? authApplied : 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
|
+
}
|
|
2838
3372
|
};
|
|
3373
|
+
if (payload.mode === "json") {
|
|
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
|
+
}
|
|
3409
|
+
const summary = buildJsonSummary(parsedJson);
|
|
3410
|
+
if (fullResponse || !truncated) {
|
|
3411
|
+
result.json = parsedJson;
|
|
3412
|
+
} else {
|
|
3413
|
+
result.degradedReason = "response body exceeded max-bytes and was summarized";
|
|
3414
|
+
}
|
|
3415
|
+
if (summary.schema) {
|
|
3416
|
+
result.schema = summary.schema;
|
|
3417
|
+
}
|
|
3418
|
+
if (summary.mappedRows) {
|
|
3419
|
+
result.mappedRows = summary.mappedRows;
|
|
3420
|
+
}
|
|
3421
|
+
} else {
|
|
3422
|
+
result.bodyText = previewText;
|
|
3423
|
+
if (truncated) {
|
|
3424
|
+
result.degradedReason = "response body exceeded max-bytes and was truncated";
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
return result;
|
|
2839
3428
|
})()
|
|
2840
3429
|
};
|
|
2841
3430
|
}
|
|
@@ -2899,31 +3488,135 @@
|
|
|
2899
3488
|
delete clone.requestHeaders;
|
|
2900
3489
|
delete clone.requestBodyPreview;
|
|
2901
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
|
+
}
|
|
2902
3498
|
}
|
|
2903
3499
|
if (!sections.has("response")) {
|
|
2904
3500
|
delete clone.responseHeaders;
|
|
2905
3501
|
delete clone.responseBodyPreview;
|
|
2906
3502
|
delete clone.responseBodyTruncated;
|
|
2907
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
|
+
}
|
|
2908
3511
|
}
|
|
2909
3512
|
return clone;
|
|
2910
3513
|
}
|
|
2911
|
-
function
|
|
2912
|
-
if (!
|
|
3514
|
+
function replayHeadersFromRequestHeaders(requestHeaders) {
|
|
3515
|
+
if (!requestHeaders) {
|
|
2913
3516
|
return void 0;
|
|
2914
3517
|
}
|
|
2915
3518
|
const headers = {};
|
|
2916
|
-
for (const [name, value] of Object.entries(
|
|
3519
|
+
for (const [name, value] of Object.entries(requestHeaders)) {
|
|
2917
3520
|
const normalizedName = name.toLowerCase();
|
|
2918
3521
|
if (REPLAY_FORBIDDEN_HEADER_NAMES.has(normalizedName) || normalizedName.startsWith("sec-")) {
|
|
2919
3522
|
continue;
|
|
2920
3523
|
}
|
|
2921
|
-
|
|
3524
|
+
headers[name] = value;
|
|
3525
|
+
}
|
|
3526
|
+
return Object.keys(headers).length > 0 ? headers : void 0;
|
|
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);
|
|
2922
3538
|
continue;
|
|
2923
3539
|
}
|
|
2924
3540
|
headers[name] = value;
|
|
2925
3541
|
}
|
|
2926
|
-
return
|
|
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
|
+
};
|
|
2927
3620
|
}
|
|
2928
3621
|
function collectTimestampMatchesFromText(text, source, patterns) {
|
|
2929
3622
|
const regexes = (patterns ?? [
|
|
@@ -3051,6 +3744,64 @@
|
|
|
3051
3744
|
}
|
|
3052
3745
|
return "unknown";
|
|
3053
3746
|
}
|
|
3747
|
+
function freshnessCategoryPriority(category) {
|
|
3748
|
+
switch (category) {
|
|
3749
|
+
case "data":
|
|
3750
|
+
return 0;
|
|
3751
|
+
case "unknown":
|
|
3752
|
+
return 1;
|
|
3753
|
+
case "event":
|
|
3754
|
+
return 2;
|
|
3755
|
+
case "contract":
|
|
3756
|
+
return 3;
|
|
3757
|
+
default:
|
|
3758
|
+
return 4;
|
|
3759
|
+
}
|
|
3760
|
+
}
|
|
3761
|
+
function freshnessSourcePriority(source) {
|
|
3762
|
+
switch (source) {
|
|
3763
|
+
case "network":
|
|
3764
|
+
return 0;
|
|
3765
|
+
case "page-data":
|
|
3766
|
+
return 1;
|
|
3767
|
+
case "visible":
|
|
3768
|
+
return 2;
|
|
3769
|
+
case "inline":
|
|
3770
|
+
return 3;
|
|
3771
|
+
default:
|
|
3772
|
+
return 4;
|
|
3773
|
+
}
|
|
3774
|
+
}
|
|
3775
|
+
function rankFreshnessEvidence(candidates, now = Date.now()) {
|
|
3776
|
+
return candidates.slice().sort((left, right) => {
|
|
3777
|
+
const byCategory = freshnessCategoryPriority(left.category) - freshnessCategoryPriority(right.category);
|
|
3778
|
+
if (byCategory !== 0) {
|
|
3779
|
+
return byCategory;
|
|
3780
|
+
}
|
|
3781
|
+
const bySource = freshnessSourcePriority(left.source) - freshnessSourcePriority(right.source);
|
|
3782
|
+
if (bySource !== 0) {
|
|
3783
|
+
return bySource;
|
|
3784
|
+
}
|
|
3785
|
+
const leftTimestamp = parseTimestampCandidate(left.value, now) ?? Number.NEGATIVE_INFINITY;
|
|
3786
|
+
const rightTimestamp = parseTimestampCandidate(right.value, now) ?? Number.NEGATIVE_INFINITY;
|
|
3787
|
+
if (leftTimestamp !== rightTimestamp) {
|
|
3788
|
+
return rightTimestamp - leftTimestamp;
|
|
3789
|
+
}
|
|
3790
|
+
return left.value.localeCompare(right.value);
|
|
3791
|
+
});
|
|
3792
|
+
}
|
|
3793
|
+
function deriveFreshnessConfidence(primary) {
|
|
3794
|
+
if (!primary) {
|
|
3795
|
+
return "low";
|
|
3796
|
+
}
|
|
3797
|
+
if (primary.category === "data" && (primary.source === "network" || primary.source === "page-data")) {
|
|
3798
|
+
return "high";
|
|
3799
|
+
}
|
|
3800
|
+
if (primary.category === "data") {
|
|
3801
|
+
return "medium";
|
|
3802
|
+
}
|
|
3803
|
+
return "low";
|
|
3804
|
+
}
|
|
3054
3805
|
async function collectPageInspection(tabId, params = {}) {
|
|
3055
3806
|
return await forwardContentRpc(tabId, "bak.internal.inspectState", params);
|
|
3056
3807
|
}
|
|
@@ -3172,7 +3923,9 @@
|
|
|
3172
3923
|
const domVisibleTimestamp = latestTimestampFromCandidates(visibleCandidates, now);
|
|
3173
3924
|
const latestNetworkTs = latestNetworkTimestamp(tabId);
|
|
3174
3925
|
const lastMutationAt = typeof inspection.lastMutationAt === "number" ? inspection.lastMutationAt : null;
|
|
3175
|
-
const allCandidates = [...visibleCandidates, ...inlineCandidates, ...pageDataCandidates, ...networkCandidates];
|
|
3926
|
+
const allCandidates = rankFreshnessEvidence([...visibleCandidates, ...inlineCandidates, ...pageDataCandidates, ...networkCandidates], now);
|
|
3927
|
+
const primaryEvidence = allCandidates.find((candidate) => parseTimestampCandidate(candidate.value, now) !== null) ?? null;
|
|
3928
|
+
const primaryTimestamp = primaryEvidence ? parseTimestampCandidate(primaryEvidence.value, now) : null;
|
|
3176
3929
|
return {
|
|
3177
3930
|
pageLoadedAt: typeof inspection.pageLoadedAt === "number" ? inspection.pageLoadedAt : null,
|
|
3178
3931
|
lastMutationAt,
|
|
@@ -3181,6 +3934,11 @@
|
|
|
3181
3934
|
latestPageDataTimestamp,
|
|
3182
3935
|
latestNetworkDataTimestamp,
|
|
3183
3936
|
domVisibleTimestamp,
|
|
3937
|
+
primaryTimestamp,
|
|
3938
|
+
primaryCategory: primaryEvidence?.category ?? null,
|
|
3939
|
+
primarySource: primaryEvidence?.source ?? null,
|
|
3940
|
+
confidence: deriveFreshnessConfidence(primaryEvidence),
|
|
3941
|
+
suppressedEvidenceCount: Math.max(0, allCandidates.length - (primaryEvidence ? 1 : 0)),
|
|
3184
3942
|
assessment: computeFreshnessAssessment({
|
|
3185
3943
|
latestInlineDataTimestamp,
|
|
3186
3944
|
latestPageDataTimestamp,
|
|
@@ -3280,6 +4038,7 @@
|
|
|
3280
4038
|
const pageDataCandidates = await probePageDataCandidatesForTab(tabId, inspection);
|
|
3281
4039
|
const recentNetwork = listNetworkEntries(tabId, { limit: 25 });
|
|
3282
4040
|
const pageDataReport = buildInspectPageDataResult({
|
|
4041
|
+
pageUrl: inspection.url,
|
|
3283
4042
|
suspiciousGlobals: inspection.suspiciousGlobals ?? [],
|
|
3284
4043
|
tables: inspection.tables ?? [],
|
|
3285
4044
|
visibleTimestamps: inspection.visibleTimestamps ?? [],
|
|
@@ -3287,7 +4046,9 @@
|
|
|
3287
4046
|
pageDataCandidates,
|
|
3288
4047
|
recentNetwork,
|
|
3289
4048
|
tableAnalyses: tables,
|
|
3290
|
-
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 : []
|
|
3291
4052
|
});
|
|
3292
4053
|
const matched = selectReplaySchemaMatch(response.json, tables, {
|
|
3293
4054
|
preferredSourceId: `networkResponse:${requestId}`,
|
|
@@ -3613,9 +4374,11 @@
|
|
|
3613
4374
|
type: "bak.collectElements",
|
|
3614
4375
|
debugRichText: config.debugRichText
|
|
3615
4376
|
});
|
|
3616
|
-
const
|
|
4377
|
+
const screenshot = params.capture === false ? { captureStatus: "skipped" } : await captureAlignedTabScreenshot(tab);
|
|
3617
4378
|
return {
|
|
3618
|
-
|
|
4379
|
+
captureStatus: screenshot.captureStatus,
|
|
4380
|
+
captureError: screenshot.captureError,
|
|
4381
|
+
imageBase64: includeBase64 && typeof screenshot.imageData === "string" ? screenshot.imageData.replace(/^data:image\/png;base64,/, "") : void 0,
|
|
3619
4382
|
elements: elements.elements,
|
|
3620
4383
|
tabId: tab.id,
|
|
3621
4384
|
url: tab.url ?? ""
|
|
@@ -3705,7 +4468,12 @@
|
|
|
3705
4468
|
limit: typeof params.limit === "number" ? params.limit : void 0,
|
|
3706
4469
|
urlIncludes: typeof params.urlIncludes === "string" ? params.urlIncludes : void 0,
|
|
3707
4470
|
status: typeof params.status === "number" ? params.status : void 0,
|
|
3708
|
-
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
|
|
3709
4477
|
})
|
|
3710
4478
|
};
|
|
3711
4479
|
} catch {
|
|
@@ -3739,13 +4507,18 @@
|
|
|
3739
4507
|
return await preserveHumanFocus(typeof target.tabId !== "number", async () => {
|
|
3740
4508
|
const tab = await withTab(target);
|
|
3741
4509
|
await ensureTabNetworkCapture(tab.id);
|
|
3742
|
-
return
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
4510
|
+
return searchNetworkEntries(tab.id, String(params.pattern ?? ""), typeof params.limit === "number" ? params.limit : 50);
|
|
4511
|
+
});
|
|
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);
|
|
3749
4522
|
});
|
|
3750
4523
|
}
|
|
3751
4524
|
case "network.waitFor": {
|
|
@@ -3779,34 +4552,32 @@
|
|
|
3779
4552
|
return await preserveHumanFocus(typeof target.tabId !== "number", async () => {
|
|
3780
4553
|
const tab = await withTab(target);
|
|
3781
4554
|
await ensureTabNetworkCapture(tab.id);
|
|
3782
|
-
const
|
|
3783
|
-
if (!
|
|
4555
|
+
const replayable = getReplayableNetworkRequest(tab.id, String(params.id ?? ""));
|
|
4556
|
+
if (!replayable) {
|
|
3784
4557
|
throw toError("E_NOT_FOUND", `network entry not found: ${String(params.id ?? "")}`);
|
|
3785
4558
|
}
|
|
3786
|
-
if (
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
}
|
|
4559
|
+
if (replayable.degradedReason) {
|
|
4560
|
+
return {
|
|
4561
|
+
url: replayable.entry.url,
|
|
4562
|
+
status: 0,
|
|
4563
|
+
ok: false,
|
|
4564
|
+
headers: {},
|
|
4565
|
+
bytes: replayable.entry.requestBytes,
|
|
4566
|
+
truncated: true,
|
|
4567
|
+
degradedReason: replayable.degradedReason
|
|
4568
|
+
};
|
|
3796
4569
|
}
|
|
3797
4570
|
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
|
-
})(),
|
|
4571
|
+
url: replayable.entry.url,
|
|
4572
|
+
method: replayable.entry.method,
|
|
4573
|
+
headers: replayHeadersFromRequestHeaders(replayable.headers),
|
|
4574
|
+
body: replayable.body,
|
|
4575
|
+
contentType: replayable.contentType,
|
|
3807
4576
|
mode: params.mode,
|
|
3808
4577
|
timeoutMs: params.timeoutMs,
|
|
3809
4578
|
maxBytes: params.maxBytes,
|
|
4579
|
+
fullResponse: params.fullResponse === true,
|
|
4580
|
+
auth: params.auth,
|
|
3810
4581
|
scope: "current"
|
|
3811
4582
|
});
|
|
3812
4583
|
const frameResult = replayed.result ?? replayed.results?.find((candidate) => candidate.value || candidate.error);
|
|
@@ -3815,7 +4586,13 @@
|
|
|
3815
4586
|
}
|
|
3816
4587
|
const first = frameResult?.value;
|
|
3817
4588
|
if (!first) {
|
|
3818
|
-
|
|
4589
|
+
return {
|
|
4590
|
+
url: replayable.entry.url,
|
|
4591
|
+
status: 0,
|
|
4592
|
+
ok: false,
|
|
4593
|
+
headers: {},
|
|
4594
|
+
degradedReason: "network replay returned no response payload"
|
|
4595
|
+
};
|
|
3819
4596
|
}
|
|
3820
4597
|
return params.withSchema === "auto" && params.mode === "json" ? await enrichReplayWithSchema(tab.id, String(params.id ?? ""), first) : first;
|
|
3821
4598
|
});
|
|
@@ -3894,9 +4671,10 @@
|
|
|
3894
4671
|
await ensureNetworkDebugger(tab.id).catch(() => void 0);
|
|
3895
4672
|
const inspection = await collectPageInspection(tab.id, params);
|
|
3896
4673
|
const pageDataCandidates = await probePageDataCandidatesForTab(tab.id, inspection);
|
|
3897
|
-
const network = listNetworkEntries(tab.id, { limit:
|
|
4674
|
+
const network = listNetworkEntries(tab.id, { limit: 25, tail: true });
|
|
3898
4675
|
const tableAnalyses = await collectTableAnalyses(tab.id);
|
|
3899
4676
|
const enriched = buildInspectPageDataResult({
|
|
4677
|
+
pageUrl: inspection.url,
|
|
3900
4678
|
suspiciousGlobals: inspection.suspiciousGlobals ?? [],
|
|
3901
4679
|
tables: inspection.tables ?? [],
|
|
3902
4680
|
visibleTimestamps: inspection.visibleTimestamps ?? [],
|
|
@@ -3904,7 +4682,9 @@
|
|
|
3904
4682
|
pageDataCandidates,
|
|
3905
4683
|
recentNetwork: network,
|
|
3906
4684
|
tableAnalyses,
|
|
3907
|
-
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 : []
|
|
3908
4688
|
});
|
|
3909
4689
|
const recommendedNextSteps = enriched.recommendedNextActions.map((action) => action.command);
|
|
3910
4690
|
return {
|
|
@@ -3914,6 +4694,12 @@
|
|
|
3914
4694
|
inlineTimestamps: inspection.inlineTimestamps ?? [],
|
|
3915
4695
|
pageDataCandidates,
|
|
3916
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,
|
|
3917
4703
|
dataSources: enriched.dataSources,
|
|
3918
4704
|
sourceMappings: enriched.sourceMappings,
|
|
3919
4705
|
recommendedNextActions: enriched.recommendedNextActions,
|