rea-agents 1.6.0 → 2.0.0
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/README.md +157 -66
- package/bridge/ghidra/ReaGhidraBridge.java +2075 -0
- package/bridge/hopper_bridge.py +13 -3
- package/dist/application/AbortablePromise.js +25 -0
- package/dist/application/AnalysisProvider.js +3 -0
- package/dist/application/AnalysisProviderEvaluation.js +60 -0
- package/dist/application/AnalysisProviderRegistry.js +228 -0
- package/dist/application/AnalysisSnapshotCache.js +41 -21
- package/dist/application/AnalysisSnapshotFiles.js +5 -6
- package/dist/application/BinarySession.js +218 -101
- package/dist/application/CapabilityInventory.js +18 -0
- package/dist/application/CompositeProvider.js +20 -8
- package/dist/application/CrossVersionInvestigation.js +10 -6
- package/dist/application/DirectAnalysis.js +49 -35
- package/dist/application/Doctor.js +110 -8
- package/dist/application/ElectronBoundaryAnalysis.js +96 -0
- package/dist/application/ElectronBoundaryGraph.js +9 -0
- package/dist/application/ElectronBoundaryGraphContext.js +62 -0
- package/dist/application/ElectronBoundaryGraphIpc.js +242 -0
- package/dist/application/ElectronBoundaryGraphNative.js +86 -0
- package/dist/application/ElectronBoundaryGraphWindows.js +199 -0
- package/dist/application/EnhancedTools.js +39 -14
- package/dist/application/InvestigationProviders.js +41 -0
- package/dist/application/JavaScriptApplicationEvidence.js +26 -0
- package/dist/application/JavaScriptApplicationEvidenceGraph.js +89 -0
- package/dist/application/JavaScriptApplicationService.js +74 -0
- package/dist/application/JavaScriptApplicationWorkflowEvidence.js +29 -0
- package/dist/application/JavaScriptApplicationWorkflowService.js +90 -0
- package/dist/application/JavaScriptArtifactAnalysis.js +305 -0
- package/dist/application/JavaScriptArtifactAnalysisTypes.js +1 -0
- package/dist/application/JavaScriptArtifactFiles.js +231 -0
- package/dist/application/JavaScriptArtifactGraphAccumulator.js +54 -0
- package/dist/application/JavaScriptArtifactGraphBuilder.js +144 -0
- package/dist/application/JavaScriptArtifactGraphContext.js +205 -0
- package/dist/application/JavaScriptArtifactGraphDocuments.js +108 -0
- package/dist/application/JavaScriptArtifactGraphEvidence.js +99 -0
- package/dist/application/JavaScriptArtifactGraphFindings.js +271 -0
- package/dist/application/JavaScriptArtifactGraphStructure.js +409 -0
- package/dist/application/JavaScriptArtifactReconstruction.js +77 -0
- package/dist/application/JavaScriptArtifactReconstructionInput.js +18 -0
- package/dist/application/JavaScriptReplayPlanning.js +201 -0
- package/dist/application/JavaScriptReplayService.js +266 -0
- package/dist/application/JavaScriptRuntimeReconciliationEvidence.js +25 -0
- package/dist/application/JavaScriptRuntimeReconciliationService.js +27 -0
- package/dist/application/MacHopper.js +20 -4
- package/dist/application/ManagedApplicationGraphService.js +55 -0
- package/dist/application/ManagedMemberComparisonService.js +122 -0
- package/dist/application/ManagedNativeVerificationService.js +39 -0
- package/dist/application/ManagedReconstructionService.js +58 -0
- package/dist/application/ManagedRuntimeCorrelationService.js +103 -0
- package/dist/application/PermissionAuthority.js +1 -1
- package/dist/application/ProcessCaptureLifecycle.js +1 -1
- package/dist/application/ProcessHarness.js +1 -1
- package/dist/application/ProcessSampling.js +16 -12
- package/dist/application/ProjectPermissionStore.js +1 -0
- package/dist/application/SessionProviderRouter.js +219 -0
- package/dist/application/Setup.js +84 -60
- package/dist/application/SetupClients.js +1 -1
- package/dist/application/SetupPlan.js +9 -2
- package/dist/application/SupportedClients.js +40 -16
- package/dist/application/Upgrade.js +1 -1
- package/dist/application/runtime.js +11 -6
- package/dist/artifacts/AsarArtifactReader.js +23 -3
- package/dist/browser/CdpBrowserProvider.js +4 -2
- package/dist/browser/CdpCaptureEvents.js +49 -0
- package/dist/browser/CdpElectronInspection.js +106 -162
- package/dist/browser/CdpElectronProvider.js +9 -3
- package/dist/browser/CdpElectronScriptEvents.js +69 -0
- package/dist/browser/CdpElectronScripts.js +94 -0
- package/dist/browser/CdpElectronWorkers.js +56 -0
- package/dist/browser/CdpEndpoint.js +14 -1
- package/dist/browser/CdpPageCapture.js +8 -4
- package/dist/catalogIdentity.js +6 -42
- package/dist/cli.js +446 -34
- package/dist/cliApplicationCommands.js +62 -0
- package/dist/cliBrowserAdvancedCommands.js +5 -4
- package/dist/cliBrowserCommands.js +5 -4
- package/dist/cliCommandNames.js +56 -0
- package/dist/cliElectronCommands.js +106 -3
- package/dist/cliEvidenceCommands.js +4 -3
- package/dist/cliInvestigationCommands.js +2 -1
- package/dist/cliJsonInput.js +54 -0
- package/dist/cliPolicyCommands.js +2 -1
- package/dist/cliProcessCommands.js +3 -2
- package/dist/config.js +101 -2
- package/dist/contracts/applicationToolContracts.js +91 -0
- package/dist/contracts/artifactToolContracts.js +5 -9
- package/dist/contracts/browserToolContracts.js +14 -62
- package/dist/contracts/electronToolContracts.js +45 -19
- package/dist/contracts/enhancedInputs.js +2 -2
- package/dist/contracts/errorSchemas.js +1 -0
- package/dist/contracts/investigationExamples.js +0 -6
- package/dist/contracts/javascriptApplicationWorkflowExamples.js +46 -0
- package/dist/contracts/javascriptRuntimeReconciliationExample.js +208 -0
- package/dist/contracts/managedToolContracts.js +153 -0
- package/dist/contracts/managedWorkflowExamples.js +346 -0
- package/dist/contracts/managedWorkflowToolContracts.js +203 -0
- package/dist/contracts/nativeToolContracts.js +2 -6
- package/dist/contracts/promptContracts.js +18 -2
- package/dist/contracts/providerSelection.js +28 -0
- package/dist/contracts/sessionLifecycleInputs.js +2 -0
- package/dist/contracts/toolContractExamples.js +10 -25
- package/dist/contracts/toolContracts.js +69 -65
- package/dist/contracts/toolEffects.js +160 -0
- package/dist/contracts/toolOutputSchemas.js +152 -42
- package/dist/doctorRuntime.js +47 -0
- package/dist/domain/analysisProfile.js +78 -0
- package/dist/domain/analysisSnapshot.js +92 -46
- package/dist/domain/artifactComparison.js +3 -4
- package/dist/domain/artifactGraph.js +1 -0
- package/dist/domain/binaryTarget.js +4 -29
- package/dist/domain/browserObservation.js +3 -0
- package/dist/domain/bundleComparison.js +4 -4
- package/dist/domain/electronObservation.js +13 -0
- package/dist/domain/electronStaticAnalysis.js +9 -0
- package/dist/domain/electronStaticAnalysisBrowser.js +194 -0
- package/dist/domain/electronStaticAnalysisIpc.js +171 -0
- package/dist/domain/electronStaticAnalysisNative.js +164 -0
- package/dist/domain/electronStaticAnalysisTypes.js +1 -0
- package/dist/domain/electronStaticAnalysisValues.js +103 -0
- package/dist/domain/errors.js +148 -7
- package/dist/domain/evidence.js +44 -5
- package/dist/domain/evidenceBundle.js +9 -7
- package/dist/domain/functionComparison.js +9 -3
- package/dist/domain/functionComparisonNormalization.js +37 -0
- package/dist/domain/functionComparisonSchemas.js +3 -8
- package/dist/domain/functionDossierEvidence.js +4 -1
- package/dist/domain/hopperValues.js +66 -9
- package/dist/domain/inputIssueProjection.js +52 -0
- package/dist/domain/investigationWorkspace.js +2 -1
- package/dist/domain/javascriptApplicationAnalysis.js +155 -0
- package/dist/domain/javascriptApplicationChangeGraph.js +231 -0
- package/dist/domain/javascriptApplicationEvidenceSchemas.js +379 -0
- package/dist/domain/javascriptApplicationGraph.js +282 -0
- package/dist/domain/javascriptApplicationGraphSchemas.js +139 -0
- package/dist/domain/javascriptApplicationVersionComparison.js +116 -0
- package/dist/domain/javascriptApplicationVersionComparisonSchemas.js +139 -0
- package/dist/domain/javascriptApplicationVersionItems.js +221 -0
- package/dist/domain/javascriptApplicationVersionKeys.js +170 -0
- package/dist/domain/javascriptAstFingerprint.js +143 -0
- package/dist/domain/javascriptFeatureSeed.js +140 -0
- package/dist/domain/javascriptFeatureTrace.js +232 -0
- package/dist/domain/javascriptFeatureTraceSchemas.js +136 -0
- package/dist/domain/javascriptFeatureTraversal.js +88 -0
- package/dist/domain/javascriptNativeHandoff.js +85 -0
- package/dist/domain/javascriptReplay.js +361 -0
- package/dist/domain/javascriptRuntimeLoadState.js +106 -0
- package/dist/domain/javascriptRuntimeReconciliation.js +42 -0
- package/dist/domain/javascriptRuntimeReconciliationGraph.js +169 -0
- package/dist/domain/javascriptRuntimeReconciliationMatching.js +247 -0
- package/dist/domain/javascriptRuntimeReconciliationParsing.js +160 -0
- package/dist/domain/javascriptRuntimeReconciliationResult.js +119 -0
- package/dist/domain/javascriptRuntimeReconciliationRuntime.js +316 -0
- package/dist/domain/javascriptRuntimeReconciliationSchemas.js +245 -0
- package/dist/domain/javascriptRuntimeStaticCandidates.js +191 -0
- package/dist/domain/javascriptStaticAnalysis.js +346 -0
- package/dist/domain/javascriptStaticAnalysisFindings.js +39 -0
- package/dist/domain/javascriptStaticAnalysisHelpers.js +274 -0
- package/dist/domain/javascriptStaticAnalysisState.js +23 -0
- package/dist/domain/javascriptStaticAnalysisTypes.js +1 -0
- package/dist/domain/managedApplicationGraph.js +556 -0
- package/dist/domain/managedArtifact.js +444 -0
- package/dist/domain/managedMemberComparison.js +565 -0
- package/dist/domain/managedNativeVerification.js +408 -0
- package/dist/domain/managedReconstruction.js +183 -0
- package/dist/domain/managedRuntimeCorrelation.js +175 -0
- package/dist/domain/reconstructionVerificationSchemas.js +1 -3
- package/dist/dotnet/ManagedArtifactInspector.js +318 -0
- package/dist/dotnet/ManagedMemberInspector.js +887 -0
- package/dist/dotnet/ManagedMetadataHeaps.js +131 -0
- package/dist/dotnet/ManagedMetadataInventory.js +368 -0
- package/dist/dotnet/ManagedMetadataLayout.js +322 -0
- package/dist/dotnet/ManagedNativeBoundaryInspector.js +391 -0
- package/dist/dotnet/ManagedPeReader.js +157 -0
- package/dist/dotnet/ManagedReaderFailure.js +10 -0
- package/dist/dotnet/ManagedStaticProvider.js +203 -0
- package/dist/generatedPackageMetadata.js +2 -2
- package/dist/ghidra/GhidraAnalysisProfile.js +37 -0
- package/dist/ghidra/GhidraClient.js +400 -0
- package/dist/ghidra/GhidraClientTypes.js +1 -0
- package/dist/ghidra/GhidraDefaults.js +20 -0
- package/dist/ghidra/GhidraDiagnostics.js +43 -0
- package/dist/ghidra/GhidraDoctor.js +59 -0
- package/dist/ghidra/GhidraFunctionValues.js +264 -0
- package/dist/ghidra/GhidraInstallation.js +239 -0
- package/dist/ghidra/GhidraInventoryValues.js +217 -0
- package/dist/ghidra/GhidraLauncher.js +139 -0
- package/dist/ghidra/GhidraProvider.js +366 -0
- package/dist/ghidra/GhidraRequestQueue.js +98 -0
- package/dist/ghidra/GhidraResponseBuffer.js +31 -0
- package/dist/ghidra/GhidraResponseRouter.js +24 -0
- package/dist/ghidra/GhidraSessionError.js +39 -0
- package/dist/ghidra/GhidraSessionValues.js +62 -0
- package/dist/ghidra/GhidraSocketConnection.js +46 -0
- package/dist/ghidra/protocol.js +69 -0
- package/dist/hopper/BridgeLauncher.js +19 -54
- package/dist/hopper/HopperAnalysisProfile.js +106 -0
- package/dist/hopper/HopperClient.js +154 -162
- package/dist/hopper/HopperProvider.js +99 -7
- package/dist/hopper/HopperSessionValues.js +27 -0
- package/dist/hopper/HopperSocketConnection.js +31 -0
- package/dist/main.js +14 -0
- package/dist/native/NativeMacOSProvider.js +3 -1
- package/dist/process/PendingOperations.js +59 -0
- package/dist/process/PrivateRuntimeRoot.js +33 -0
- package/dist/process/ProviderDeadline.js +95 -0
- package/dist/process/ProviderProcess.js +275 -0
- package/dist/replay/JavaScriptReplayWorker.js +286 -0
- package/dist/replay/LinuxJavaScriptReplayRunner.js +434 -0
- package/dist/replay/LinuxRuntimeClosure.js +46 -0
- package/dist/replay/LinuxSeccompPolicy.js +40 -0
- package/dist/replay/ReplayWorkerProtocol.js +55 -0
- package/dist/replay/SystemJavaScriptReplayHost.js +182 -0
- package/dist/server/createServer.js +53 -0
- package/dist/server/promptCompletion.js +9 -3
- package/dist/server/registerApplicationTools.js +115 -0
- package/dist/server/registerArtifactComparisonTool.js +16 -9
- package/dist/server/registerBrowserTools.js +33 -8
- package/dist/server/registerBundleComparisonTool.js +17 -20
- package/dist/server/registerElectronTools.js +45 -3
- package/dist/server/registerEnhancedTools.js +67 -19
- package/dist/server/registerEvidenceTools.js +5 -1
- package/dist/server/registerFunctionComparisonTool.js +14 -7
- package/dist/server/registerInvestigationTools.js +21 -17
- package/dist/server/registerManagedTools.js +6 -0
- package/dist/server/registerManagedWorkflowTools.js +277 -0
- package/dist/server/registerOfficialTools.js +11 -8
- package/dist/server/registerProcessComparisonTool.js +25 -47
- package/dist/server/registerSessionStatusTool.js +7 -7
- package/dist/server/registerSessionTools.js +57 -87
- package/dist/server/sessionEvidence.js +14 -19
- package/dist/server/toolInputValidation.js +77 -0
- package/dist/server/toolRegistrationOptions.js +79 -2
- package/dist/server/toolResult.js +25 -17
- package/package.json +17 -7
- package/scripts/rea.mjs +36 -2
- package/skills/rea-analysis/SKILL.md +59 -3
- /package/dist/{application → process}/ProcessOwnership.js +0 -0
|
@@ -1,78 +1,126 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import canonicalize from "canonicalize";
|
|
3
3
|
import { BrowserObservationError } from "../domain/errors.js";
|
|
4
|
-
import { createWebTextArtifact } from "../domain/webContentArtifact.js";
|
|
5
4
|
import { CdpCaptureCompleteness } from "./CdpCaptureCompleteness.js";
|
|
6
|
-
import {
|
|
5
|
+
import { delayWithCancellation, numberValue, recordValue, recordsValue, requiredRecord, stringValue, } from "./CdpCaptureValues.js";
|
|
6
|
+
import { ingestElectronScriptEvent, } from "./CdpElectronScriptEvents.js";
|
|
7
|
+
import { captureElectronScripts } from "./CdpElectronScripts.js";
|
|
8
|
+
import { captureElectronWorkers } from "./CdpElectronWorkers.js";
|
|
7
9
|
import { authorizedElectronFile, canonicalElectronRoots, } from "./ElectronFileScope.js";
|
|
8
10
|
/** Passively inspect one root-confined Electron file page. */
|
|
9
11
|
export const inspectCdpElectronPage = async (context) => {
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
const state = {
|
|
13
|
+
roots: await canonicalElectronRoots(context.input.allowed_file_roots),
|
|
14
|
+
completeness: new CdpCaptureCompleteness(),
|
|
15
|
+
scripts: [],
|
|
16
|
+
executionContextFrames: new Map(),
|
|
17
|
+
limitations: [
|
|
18
|
+
"Only canonical file paths contained by an approved root are retained.",
|
|
19
|
+
"REA does not evaluate renderer JavaScript, invoke Electron APIs, navigate, click, or close the page.",
|
|
20
|
+
"Script contents require separate source-capture approval and remain byte bounded.",
|
|
21
|
+
],
|
|
22
|
+
mainFrameId: undefined,
|
|
23
|
+
navigationDuringCapture: false,
|
|
24
|
+
};
|
|
15
25
|
const removeListener = context.connection.onEvent((event) => {
|
|
16
26
|
if (event.sessionId !== context.sessionId)
|
|
17
27
|
return;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
28
|
+
ingestElectronScriptEvent({
|
|
29
|
+
event,
|
|
30
|
+
scripts: state.scripts,
|
|
31
|
+
executionContextFrames: state.executionContextFrames,
|
|
32
|
+
completeness: state.completeness,
|
|
33
|
+
});
|
|
34
|
+
if (state.mainFrameId !== undefined &&
|
|
35
|
+
navigatedFrameId(event) === state.mainFrameId)
|
|
36
|
+
state.navigationDuringCapture = true;
|
|
21
37
|
});
|
|
22
|
-
const startedAt = new Date().toISOString();
|
|
23
38
|
try {
|
|
24
|
-
await
|
|
25
|
-
await context.connection.send("Page.enable", {}, context.sessionId, context.signal);
|
|
26
|
-
const before = await context.connection.send("Page.getFrameTree", {}, context.sessionId, context.signal);
|
|
27
|
-
const frames = await captureFrames(before, roots, context.input.limits.max_frames, completeness);
|
|
28
|
-
const main = frames[0];
|
|
29
|
-
if (main === undefined)
|
|
30
|
-
throw new BrowserObservationError("inspect_web_page", "target_not_allowed");
|
|
31
|
-
mainFrameId = main.frame_id;
|
|
32
|
-
await context.connection.send("Debugger.enable", { maxScriptsCacheSize: 4 * 1_024 * 1_024 }, context.sessionId, context.signal);
|
|
33
|
-
await delayWithCancellation(context.input.observation_ms, "inspect_electron_page", context.signal);
|
|
34
|
-
await report(context.progress, 2, "Capturing Electron structure");
|
|
35
|
-
const resources = await captureResources(context, roots, completeness);
|
|
36
|
-
const dom = await captureDom(context, roots, completeness);
|
|
37
|
-
const normalizedScripts = await captureScripts(context, roots, scripts, completeness);
|
|
38
|
-
const after = await context.connection.send("Page.getFrameTree", {}, context.sessionId, context.signal);
|
|
39
|
-
const afterMain = await mainFilePath(after, roots);
|
|
40
|
-
if (afterMain === undefined)
|
|
41
|
-
throw new BrowserObservationError("inspect_web_page", "target_not_allowed");
|
|
42
|
-
if (navigationDuringCapture || afterMain !== main.file_path)
|
|
43
|
-
throw new BrowserObservationError("inspect_web_page", "target_changed");
|
|
44
|
-
await report(context.progress, 3, "Electron inspection complete");
|
|
45
|
-
return {
|
|
46
|
-
schema_version: 1,
|
|
47
|
-
browser: context.discovery.version,
|
|
48
|
-
target: {
|
|
49
|
-
target_id: context.target.id,
|
|
50
|
-
type: context.target.type,
|
|
51
|
-
title: context.target.title.slice(0, 16_384),
|
|
52
|
-
file_path: main.file_path,
|
|
53
|
-
attached: context.target.attached,
|
|
54
|
-
},
|
|
55
|
-
capture_window: {
|
|
56
|
-
started_at: startedAt,
|
|
57
|
-
ended_at: new Date().toISOString(),
|
|
58
|
-
observation_ms: context.input.observation_ms,
|
|
59
|
-
},
|
|
60
|
-
completeness: completeness.snapshot(),
|
|
61
|
-
frames,
|
|
62
|
-
dom,
|
|
63
|
-
scripts: normalizedScripts,
|
|
64
|
-
resources,
|
|
65
|
-
limitations: [
|
|
66
|
-
"Only canonical file paths contained by an approved root are retained.",
|
|
67
|
-
"REA does not evaluate renderer JavaScript, invoke Electron APIs, navigate, click, or close the page.",
|
|
68
|
-
"Script contents require separate source-capture approval and remain byte bounded.",
|
|
69
|
-
],
|
|
70
|
-
};
|
|
39
|
+
return await runElectronInspection(context, state, new Date().toISOString());
|
|
71
40
|
}
|
|
72
41
|
finally {
|
|
73
42
|
removeListener();
|
|
74
43
|
}
|
|
75
44
|
};
|
|
45
|
+
const runElectronInspection = async (context, state, startedAt) => {
|
|
46
|
+
const frames = await authorizeInitialFrames(context, state);
|
|
47
|
+
const main = frames[0];
|
|
48
|
+
state.mainFrameId = main.frame_id;
|
|
49
|
+
await enableElectronCapture(context);
|
|
50
|
+
await delayWithCancellation(context.input.observation_ms, "inspect_electron_page", context.signal);
|
|
51
|
+
const captured = await captureElectronContent(context, state, frames);
|
|
52
|
+
await assertStableMainFrame(context, state, main.file_path);
|
|
53
|
+
await report(context.progress, 3, "Electron inspection complete");
|
|
54
|
+
return {
|
|
55
|
+
schema_version: 1,
|
|
56
|
+
browser: context.discovery.version,
|
|
57
|
+
target: {
|
|
58
|
+
target_id: context.target.id,
|
|
59
|
+
type: context.target.type,
|
|
60
|
+
title: context.target.title.slice(0, 16_384),
|
|
61
|
+
file_path: main.file_path,
|
|
62
|
+
attached: context.target.attached,
|
|
63
|
+
},
|
|
64
|
+
capture_window: {
|
|
65
|
+
started_at: startedAt,
|
|
66
|
+
ended_at: new Date().toISOString(),
|
|
67
|
+
observation_ms: context.input.observation_ms,
|
|
68
|
+
},
|
|
69
|
+
completeness: state.completeness.snapshot(),
|
|
70
|
+
frames,
|
|
71
|
+
...captured,
|
|
72
|
+
limitations: state.limitations,
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
const authorizeInitialFrames = async (context, state) => {
|
|
76
|
+
await report(context.progress, 1, "Authorizing Electron file frames");
|
|
77
|
+
await context.connection.send("Page.enable", {}, context.sessionId, context.signal);
|
|
78
|
+
const result = await context.connection.send("Page.getFrameTree", {}, context.sessionId, context.signal);
|
|
79
|
+
const frames = await captureFrames(result, state.roots, context.input.limits.max_frames, state.completeness);
|
|
80
|
+
if (frames[0] === undefined)
|
|
81
|
+
throw new BrowserObservationError("inspect_web_page", "target_not_allowed");
|
|
82
|
+
return frames;
|
|
83
|
+
};
|
|
84
|
+
const enableElectronCapture = async (context) => {
|
|
85
|
+
await context.connection.send("Runtime.enable", {}, context.sessionId, context.signal);
|
|
86
|
+
await context.connection.send("Debugger.enable", { maxScriptsCacheSize: 4 * 1_024 * 1_024 }, context.sessionId, context.signal);
|
|
87
|
+
};
|
|
88
|
+
const captureElectronContent = async (context, state, frames) => {
|
|
89
|
+
await report(context.progress, 2, "Capturing Electron structure");
|
|
90
|
+
const frameIds = new Set(frames.map((frame) => frame.frame_id));
|
|
91
|
+
const resources = await captureResources(context, state.roots, state.completeness);
|
|
92
|
+
const dom = await captureDom(context, state.roots, state.completeness);
|
|
93
|
+
const scripts = await captureElectronScripts({
|
|
94
|
+
connection: context.connection,
|
|
95
|
+
sessionId: context.sessionId,
|
|
96
|
+
...(context.signal === undefined ? {} : { signal: context.signal }),
|
|
97
|
+
request: context.input,
|
|
98
|
+
roots: state.roots,
|
|
99
|
+
scripts: state.scripts,
|
|
100
|
+
executionContextFrames: state.executionContextFrames,
|
|
101
|
+
frameIds,
|
|
102
|
+
completeness: state.completeness,
|
|
103
|
+
});
|
|
104
|
+
const workers = await captureElectronWorkers({
|
|
105
|
+
connection: context.connection,
|
|
106
|
+
...(context.signal === undefined ? {} : { signal: context.signal }),
|
|
107
|
+
target: context.target,
|
|
108
|
+
request: context.input,
|
|
109
|
+
roots: state.roots,
|
|
110
|
+
frameIds,
|
|
111
|
+
completeness: state.completeness,
|
|
112
|
+
limitations: state.limitations,
|
|
113
|
+
});
|
|
114
|
+
return { dom, scripts, resources, workers };
|
|
115
|
+
};
|
|
116
|
+
const assertStableMainFrame = async (context, state, expectedPath) => {
|
|
117
|
+
const result = await context.connection.send("Page.getFrameTree", {}, context.sessionId, context.signal);
|
|
118
|
+
const afterPath = await mainFilePath(result, state.roots);
|
|
119
|
+
if (afterPath === undefined)
|
|
120
|
+
throw new BrowserObservationError("inspect_web_page", "target_not_allowed");
|
|
121
|
+
if (state.navigationDuringCapture || afterPath !== expectedPath)
|
|
122
|
+
throw new BrowserObservationError("inspect_web_page", "target_changed");
|
|
123
|
+
};
|
|
76
124
|
const navigatedFrameId = (event) => {
|
|
77
125
|
const params = recordValue(event.params);
|
|
78
126
|
if (params === undefined)
|
|
@@ -83,33 +131,6 @@ const navigatedFrameId = (event) => {
|
|
|
83
131
|
return stringValue(params.frameId);
|
|
84
132
|
return undefined;
|
|
85
133
|
};
|
|
86
|
-
const ingestScript = (event, scripts, completeness) => {
|
|
87
|
-
if (event.method !== "Debugger.scriptParsed")
|
|
88
|
-
return;
|
|
89
|
-
const value = recordValue(event.params);
|
|
90
|
-
const scriptId = stringValue(value?.scriptId);
|
|
91
|
-
const rawUrl = stringValue(value?.url);
|
|
92
|
-
if (scriptId === undefined || scriptId.length > 256) {
|
|
93
|
-
completeness.exclude("scripts", "invalid_protocol_value");
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
if (rawUrl === undefined || rawUrl === "") {
|
|
97
|
-
completeness.exclude("scripts", "unattributed_origin");
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
if (scripts.length >= 2_000) {
|
|
101
|
-
completeness.drop("scripts");
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
scripts.push({
|
|
105
|
-
scriptId,
|
|
106
|
-
rawUrl,
|
|
107
|
-
hash: (stringValue(value?.hash) ?? "").slice(0, 512),
|
|
108
|
-
length: nonnegativeInteger(value?.length),
|
|
109
|
-
isModule: value?.isModule === true,
|
|
110
|
-
language: boundedText(value?.scriptLanguage, 100),
|
|
111
|
-
});
|
|
112
|
-
};
|
|
113
134
|
const captureFrames = async (result, roots, maximum, completeness) => {
|
|
114
135
|
const frameTree = recordValue(requiredRecord(result).frameTree);
|
|
115
136
|
if (frameTree === undefined)
|
|
@@ -227,82 +248,6 @@ const captureDom = async (context, roots, completeness) => {
|
|
|
227
248
|
completeness.truncate("dom");
|
|
228
249
|
return { total_nodes: total, nodes };
|
|
229
250
|
};
|
|
230
|
-
const captureScripts = async (context, roots, scripts, completeness) => {
|
|
231
|
-
let total = 0;
|
|
232
|
-
let sourceBytes = 0;
|
|
233
|
-
const items = [];
|
|
234
|
-
const seen = new Set();
|
|
235
|
-
for (const script of scripts) {
|
|
236
|
-
const path = await authorizedElectronFile(script.rawUrl, roots);
|
|
237
|
-
if (path === undefined) {
|
|
238
|
-
completeness.exclude("scripts", "out_of_target_scope");
|
|
239
|
-
continue;
|
|
240
|
-
}
|
|
241
|
-
const identity = {
|
|
242
|
-
file_path: path,
|
|
243
|
-
cdp_hash: script.hash,
|
|
244
|
-
length: script.length,
|
|
245
|
-
is_module: script.isModule,
|
|
246
|
-
language: script.language,
|
|
247
|
-
};
|
|
248
|
-
const scriptKey = `electron_script_${digest(identity)}`;
|
|
249
|
-
if (seen.has(scriptKey))
|
|
250
|
-
continue;
|
|
251
|
-
seen.add(scriptKey);
|
|
252
|
-
total += 1;
|
|
253
|
-
if (items.length >= context.input.limits.max_scripts) {
|
|
254
|
-
completeness.drop("scripts");
|
|
255
|
-
continue;
|
|
256
|
-
}
|
|
257
|
-
let source = {
|
|
258
|
-
included: false,
|
|
259
|
-
reason: "source capture was not approved",
|
|
260
|
-
};
|
|
261
|
-
if (context.input.include_script_sources) {
|
|
262
|
-
if (script.length > context.input.limits.max_script_source_bytes ||
|
|
263
|
-
sourceBytes + script.length >
|
|
264
|
-
context.input.limits.max_total_script_source_bytes) {
|
|
265
|
-
source = {
|
|
266
|
-
included: false,
|
|
267
|
-
reason: "script source byte limit reached",
|
|
268
|
-
};
|
|
269
|
-
completeness.truncate("script_sources");
|
|
270
|
-
}
|
|
271
|
-
else {
|
|
272
|
-
const result = requiredRecord(await context.connection.send("Debugger.getScriptSource", { scriptId: script.scriptId }, context.sessionId, context.signal));
|
|
273
|
-
const text = stringValue(result.scriptSource) ?? "";
|
|
274
|
-
const bytes = Buffer.byteLength(text);
|
|
275
|
-
if (bytes > context.input.limits.max_script_source_bytes ||
|
|
276
|
-
sourceBytes + bytes >
|
|
277
|
-
context.input.limits.max_total_script_source_bytes) {
|
|
278
|
-
source = {
|
|
279
|
-
included: false,
|
|
280
|
-
reason: "script source byte limit reached",
|
|
281
|
-
};
|
|
282
|
-
completeness.truncate("script_sources");
|
|
283
|
-
}
|
|
284
|
-
else {
|
|
285
|
-
source = {
|
|
286
|
-
included: true,
|
|
287
|
-
artifact: createWebTextArtifact(text, "text/javascript"),
|
|
288
|
-
};
|
|
289
|
-
sourceBytes += bytes;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
items.push({
|
|
294
|
-
script_key: scriptKey,
|
|
295
|
-
...identity,
|
|
296
|
-
source,
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
if (!context.input.include_script_sources)
|
|
300
|
-
completeness.exclude("script_sources", "not_approved", total);
|
|
301
|
-
return {
|
|
302
|
-
total,
|
|
303
|
-
items: items.sort((left, right) => left.script_key.localeCompare(right.script_key)),
|
|
304
|
-
};
|
|
305
|
-
};
|
|
306
251
|
const mainFilePath = async (result, roots) => {
|
|
307
252
|
const frameTree = recordValue(requiredRecord(result).frameTree);
|
|
308
253
|
const frame = recordValue(frameTree?.frame);
|
|
@@ -314,7 +259,6 @@ const integer = (value, fallback) => {
|
|
|
314
259
|
const number = numberValue(value);
|
|
315
260
|
return number === undefined ? fallback : Math.trunc(number);
|
|
316
261
|
};
|
|
317
|
-
const nonnegativeInteger = (value) => Math.max(0, integer(value, 0));
|
|
318
262
|
const digest = (value) => {
|
|
319
263
|
const encoded = canonicalize(value);
|
|
320
264
|
if (encoded === undefined)
|
|
@@ -5,11 +5,13 @@ import { discoverCdpEndpoint, hasCdpTargetWebSocket, } from "./CdpEndpoint.js";
|
|
|
5
5
|
import { closeCdpTargetSession, openCdpTargetSession, } from "./CdpTargetSession.js";
|
|
6
6
|
import { inspectCdpElectronPage } from "./CdpElectronInspection.js";
|
|
7
7
|
import { authorizedElectronFile, canonicalElectronRoots, } from "./ElectronFileScope.js";
|
|
8
|
-
|
|
8
|
+
/** Public identity committed by passive Electron observations. */
|
|
9
|
+
export const CDP_ELECTRON_PROVIDER_IDENTITY = Object.freeze({
|
|
9
10
|
id: "rea-cdp-electron",
|
|
10
11
|
name: "REA Electron file-page CDP observation provider",
|
|
11
12
|
version: "1",
|
|
12
|
-
};
|
|
13
|
+
});
|
|
14
|
+
const IDENTITY = CDP_ELECTRON_PROVIDER_IDENTITY;
|
|
13
15
|
/** Passive Electron provider with canonical file-root confinement. */
|
|
14
16
|
export class CdpElectronProvider {
|
|
15
17
|
identity() {
|
|
@@ -106,7 +108,11 @@ export class CdpElectronProvider {
|
|
|
106
108
|
}
|
|
107
109
|
finally {
|
|
108
110
|
if (targetSession !== undefined)
|
|
109
|
-
await closeCdpTargetSession(targetSession, [
|
|
111
|
+
await closeCdpTargetSession(targetSession, [
|
|
112
|
+
"Debugger",
|
|
113
|
+
"Runtime",
|
|
114
|
+
"Page",
|
|
115
|
+
]);
|
|
110
116
|
}
|
|
111
117
|
}
|
|
112
118
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { CdpCaptureCompleteness } from "./CdpCaptureCompleteness.js";
|
|
2
|
+
import { boundedText, numberValue, recordValue, stringValue, } from "./CdpCaptureValues.js";
|
|
3
|
+
/** Retain only bounded script/frame metadata from passive Electron events. */
|
|
4
|
+
export const ingestElectronScriptEvent = (input) => {
|
|
5
|
+
if (input.event.method === "Runtime.executionContextsCleared") {
|
|
6
|
+
input.executionContextFrames.clear();
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if (input.event.method === "Runtime.executionContextDestroyed") {
|
|
10
|
+
const parameters = recordValue(input.event.params);
|
|
11
|
+
const key = executionContextKey(parameters?.executionContextId);
|
|
12
|
+
if (key !== null)
|
|
13
|
+
input.executionContextFrames.delete(key);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (input.event.method === "Runtime.executionContextCreated") {
|
|
17
|
+
retainExecutionContext(input.event, input.executionContextFrames, input.completeness);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (input.event.method !== "Debugger.scriptParsed")
|
|
21
|
+
return;
|
|
22
|
+
const value = recordValue(input.event.params);
|
|
23
|
+
const scriptId = stringValue(value?.scriptId);
|
|
24
|
+
const rawUrl = stringValue(value?.url);
|
|
25
|
+
if (scriptId === undefined || scriptId.length > 256) {
|
|
26
|
+
input.completeness.exclude("scripts", "invalid_protocol_value");
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (rawUrl === undefined || rawUrl === "") {
|
|
30
|
+
input.completeness.exclude("scripts", "unattributed_origin");
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (input.scripts.length >= 2_000) {
|
|
34
|
+
input.completeness.drop("scripts");
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
input.scripts.push({
|
|
38
|
+
scriptId,
|
|
39
|
+
rawUrl,
|
|
40
|
+
hash: (stringValue(value?.hash) ?? "").slice(0, 512),
|
|
41
|
+
length: nonnegativeInteger(value?.length),
|
|
42
|
+
isModule: value?.isModule === true,
|
|
43
|
+
language: boundedText(value?.scriptLanguage, 100),
|
|
44
|
+
executionContextKey: executionContextKey(value?.executionContextId),
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
const retainExecutionContext = (event, frames, completeness) => {
|
|
48
|
+
const parameters = recordValue(event.params);
|
|
49
|
+
const runtimeContext = recordValue(parameters?.context);
|
|
50
|
+
const contextKey = executionContextKey(runtimeContext?.id);
|
|
51
|
+
const frameId = stringValue(recordValue(runtimeContext?.auxData)?.frameId);
|
|
52
|
+
if (contextKey === null || frameId === undefined || frameId.length > 256)
|
|
53
|
+
return;
|
|
54
|
+
if (frames.size >= 2_000 && !frames.has(contextKey)) {
|
|
55
|
+
completeness.truncate("scripts");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
frames.set(contextKey, frameId);
|
|
59
|
+
};
|
|
60
|
+
const nonnegativeInteger = (value) => {
|
|
61
|
+
const number = numberValue(value);
|
|
62
|
+
return number === undefined ? 0 : Math.max(0, Math.trunc(number));
|
|
63
|
+
};
|
|
64
|
+
const executionContextKey = (value) => {
|
|
65
|
+
const identifier = numberValue(value);
|
|
66
|
+
return identifier !== undefined && Number.isSafeInteger(identifier)
|
|
67
|
+
? String(identifier)
|
|
68
|
+
: null;
|
|
69
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import canonicalize from "canonicalize";
|
|
3
|
+
import { createWebTextArtifact } from "../domain/webContentArtifact.js";
|
|
4
|
+
import { CdpCaptureCompleteness } from "./CdpCaptureCompleteness.js";
|
|
5
|
+
import { requiredRecord, stringValue } from "./CdpCaptureValues.js";
|
|
6
|
+
import { authorizedElectronFile } from "./ElectronFileScope.js";
|
|
7
|
+
/** Normalize root-confined script metadata and optionally approved source. */
|
|
8
|
+
export const captureElectronScripts = async (input) => {
|
|
9
|
+
let total = 0;
|
|
10
|
+
let sourceBytes = 0;
|
|
11
|
+
const items = [];
|
|
12
|
+
const seen = new Set();
|
|
13
|
+
for (const script of input.scripts) {
|
|
14
|
+
const path = await authorizedElectronFile(script.rawUrl, input.roots);
|
|
15
|
+
if (path === undefined) {
|
|
16
|
+
input.completeness.exclude("scripts", "out_of_target_scope");
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const mappedFrame = script.executionContextKey === null
|
|
20
|
+
? undefined
|
|
21
|
+
: input.executionContextFrames.get(script.executionContextKey);
|
|
22
|
+
const identity = {
|
|
23
|
+
frame_id: mappedFrame !== undefined && input.frameIds.has(mappedFrame)
|
|
24
|
+
? mappedFrame
|
|
25
|
+
: null,
|
|
26
|
+
file_path: path,
|
|
27
|
+
cdp_hash: script.hash,
|
|
28
|
+
length: script.length,
|
|
29
|
+
is_module: script.isModule,
|
|
30
|
+
language: script.language,
|
|
31
|
+
};
|
|
32
|
+
const scriptKey = `electron_script_${digest(identity)}`;
|
|
33
|
+
if (seen.has(scriptKey))
|
|
34
|
+
continue;
|
|
35
|
+
seen.add(scriptKey);
|
|
36
|
+
total += 1;
|
|
37
|
+
if (items.length >= input.request.limits.max_scripts) {
|
|
38
|
+
input.completeness.drop("scripts");
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const capturedSource = await captureScriptSource(input, script, sourceBytes);
|
|
42
|
+
sourceBytes += capturedSource.bytes;
|
|
43
|
+
items.push({
|
|
44
|
+
script_key: scriptKey,
|
|
45
|
+
...identity,
|
|
46
|
+
source: capturedSource.source,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
if (!input.request.include_script_sources)
|
|
50
|
+
input.completeness.exclude("script_sources", "not_approved", total);
|
|
51
|
+
return {
|
|
52
|
+
total,
|
|
53
|
+
items: items.sort((left, right) => left.script_key.localeCompare(right.script_key)),
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
const captureScriptSource = async (input, script, sourceBytes) => {
|
|
57
|
+
if (!input.request.include_script_sources)
|
|
58
|
+
return {
|
|
59
|
+
source: {
|
|
60
|
+
included: false,
|
|
61
|
+
reason: "source capture was not approved",
|
|
62
|
+
},
|
|
63
|
+
bytes: 0,
|
|
64
|
+
};
|
|
65
|
+
if (sourceLimitReached(input.request.limits, script.length, sourceBytes))
|
|
66
|
+
return sourceLimitResult(input.completeness);
|
|
67
|
+
const result = requiredRecord(await input.connection.send("Debugger.getScriptSource", { scriptId: script.scriptId }, input.sessionId, input.signal));
|
|
68
|
+
const text = stringValue(result.scriptSource) ?? "";
|
|
69
|
+
const bytes = Buffer.byteLength(text);
|
|
70
|
+
if (sourceLimitReached(input.request.limits, bytes, sourceBytes))
|
|
71
|
+
return sourceLimitResult(input.completeness);
|
|
72
|
+
return {
|
|
73
|
+
source: {
|
|
74
|
+
included: true,
|
|
75
|
+
artifact: createWebTextArtifact(text, "text/javascript"),
|
|
76
|
+
},
|
|
77
|
+
bytes,
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
const sourceLimitReached = (limits, bytes, previousBytes) => bytes > limits.max_script_source_bytes ||
|
|
81
|
+
previousBytes + bytes > limits.max_total_script_source_bytes;
|
|
82
|
+
const sourceLimitResult = (completeness) => {
|
|
83
|
+
completeness.truncate("script_sources");
|
|
84
|
+
return {
|
|
85
|
+
source: { included: false, reason: "script source byte limit reached" },
|
|
86
|
+
bytes: 0,
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
const digest = (value) => {
|
|
90
|
+
const encoded = canonicalize(value);
|
|
91
|
+
if (encoded === undefined)
|
|
92
|
+
throw new TypeError("Expected canonical JSON");
|
|
93
|
+
return createHash("sha256").update(encoded).digest("hex");
|
|
94
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { CdpCaptureCompleteness } from "./CdpCaptureCompleteness.js";
|
|
2
|
+
import { boundedText, recordValue, recordsValue, stringValue, } from "./CdpCaptureValues.js";
|
|
3
|
+
import { optionalCdpCommand } from "./CdpOptionalCommand.js";
|
|
4
|
+
import { authorizedElectronFile } from "./ElectronFileScope.js";
|
|
5
|
+
/** Inventory root-confined worker targets without attaching to them. */
|
|
6
|
+
export const captureElectronWorkers = async (input) => {
|
|
7
|
+
const result = await optionalCdpCommand({
|
|
8
|
+
connection: input.connection,
|
|
9
|
+
sessionId: undefined,
|
|
10
|
+
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
11
|
+
}, "Target.getTargets", {}, input.limitations);
|
|
12
|
+
if (result === undefined) {
|
|
13
|
+
input.completeness.unavailable("workers");
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
const workers = [];
|
|
17
|
+
for (const target of recordsValue(recordValue(result)?.targetInfos)) {
|
|
18
|
+
const type = stringValue(target.type) ?? "";
|
|
19
|
+
if (!type.includes("worker"))
|
|
20
|
+
continue;
|
|
21
|
+
const openerTargetId = boundedTargetField(target.openerId);
|
|
22
|
+
const parentFrameId = boundedTargetField(target.parentFrameId);
|
|
23
|
+
if (openerTargetId !== input.target.id &&
|
|
24
|
+
(parentFrameId === null || !input.frameIds.has(parentFrameId))) {
|
|
25
|
+
input.completeness.exclude("workers", "out_of_target_scope");
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const path = await authorizedElectronFile(stringValue(target.url) ?? "", input.roots);
|
|
29
|
+
if (path === undefined) {
|
|
30
|
+
input.completeness.exclude("workers", "out_of_target_scope");
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (workers.length >= input.request.limits.max_workers) {
|
|
34
|
+
input.completeness.truncate("workers");
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const targetId = boundedTargetField(target.targetId);
|
|
38
|
+
if (targetId === null) {
|
|
39
|
+
input.completeness.exclude("workers", "invalid_protocol_value");
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
workers.push({
|
|
43
|
+
target_id: targetId,
|
|
44
|
+
type: type.slice(0, 100),
|
|
45
|
+
file_path: path,
|
|
46
|
+
attached: target.attached === true,
|
|
47
|
+
opener_target_id: openerTargetId,
|
|
48
|
+
parent_frame_id: parentFrameId,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return workers.sort((left, right) => left.target_id.localeCompare(right.target_id));
|
|
52
|
+
};
|
|
53
|
+
const boundedTargetField = (value) => {
|
|
54
|
+
const field = boundedText(value, 256);
|
|
55
|
+
return field === null || field === "" ? null : field;
|
|
56
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { request } from "node:http";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { AnalysisCancelledError, AnalysisError, AnalysisTimeoutError, BrowserObservationError, } from "../domain/errors.js";
|
|
4
|
+
import { sanitizeBrowserUrl } from "../domain/browserObservation.js";
|
|
4
5
|
const HTTP_TIMEOUT_MS = 5_000;
|
|
5
6
|
const MAX_VERSION_BYTES = 64 * 1_024;
|
|
6
7
|
const MAX_TARGET_LIST_BYTES = 2 * 1_024 * 1_024;
|
|
@@ -39,13 +40,25 @@ export const discoverCdpEndpoint = async (endpoint, operation, signal) => {
|
|
|
39
40
|
targets: targets.map((target) => ({
|
|
40
41
|
id: target.id,
|
|
41
42
|
type: target.type,
|
|
42
|
-
title: target.title,
|
|
43
|
+
title: sanitizeTargetTitle(target.title),
|
|
43
44
|
url: target.url,
|
|
44
45
|
attached: target.attached,
|
|
45
46
|
...targetWebSocket(endpoint, target, operation),
|
|
46
47
|
})),
|
|
47
48
|
};
|
|
48
49
|
};
|
|
50
|
+
const sanitizeTargetTitle = (value) => {
|
|
51
|
+
let url;
|
|
52
|
+
try {
|
|
53
|
+
url = new URL(value);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
return url.protocol === "http:" || url.protocol === "https:"
|
|
59
|
+
? sanitizeBrowserUrl(value).url.slice(0, 16_384)
|
|
60
|
+
: value;
|
|
61
|
+
};
|
|
49
62
|
/** Select a browser attachment socket or a direct socket for one page target. */
|
|
50
63
|
export const cdpTargetWebSocket = (discovery, target, operation) => {
|
|
51
64
|
const webSocket = availableCdpTargetWebSocket(discovery, target);
|
|
@@ -5,7 +5,7 @@ import { CdpCaptureEvents } from "./CdpCaptureEvents.js";
|
|
|
5
5
|
import { captureStorage } from "./CdpCaptureStorage.js";
|
|
6
6
|
import { optionalCdpCommand } from "./CdpOptionalCommand.js";
|
|
7
7
|
import { captureAccessibility, captureDom, captureFrames, captureResources, mainFrameUrl, } from "./CdpCaptureDocuments.js";
|
|
8
|
-
import { allowedSanitizedUrl, delayWithCancellation, isHttpUrl, recordValue, recordsValue, requiredRecord, sourceExcluded, sourceResult, stringValue, } from "./CdpCaptureValues.js";
|
|
8
|
+
import { allowedSanitizedUrl, boundedText, delayWithCancellation, isHttpUrl, recordValue, recordsValue, requiredRecord, sourceExcluded, sourceResult, stringValue, } from "./CdpCaptureValues.js";
|
|
9
9
|
/** Capture and normalize one attached page without evaluating page JavaScript. */
|
|
10
10
|
export const capturePage = async (context) => {
|
|
11
11
|
const allowedOrigins = new Set(context.input.allowed_origins);
|
|
@@ -59,7 +59,7 @@ const captureAuthorizedPage = async (state) => {
|
|
|
59
59
|
const accessibility = await accessibilityForFrames(state, frames);
|
|
60
60
|
if (accessibility.total > accessibility.nodes.length)
|
|
61
61
|
state.events.completeness.truncate("accessibility");
|
|
62
|
-
const scripts = await captureScripts(context, state.events, resourceCapture.items, resources);
|
|
62
|
+
const scripts = await captureScripts(context, state.events, resourceCapture.items, resources, new Set(frames.map((frame) => frame.frame_id)));
|
|
63
63
|
const workerCapture = await captureWorkers(state, new Set(frames.map((frame) => frame.frame_id)));
|
|
64
64
|
if (workerCapture.total > workerCapture.items.length)
|
|
65
65
|
state.events.completeness.truncate("workers");
|
|
@@ -220,7 +220,7 @@ const accessibilityForFrames = async (state, frames) => {
|
|
|
220
220
|
events.completeness.truncate("accessibility");
|
|
221
221
|
return capture;
|
|
222
222
|
};
|
|
223
|
-
const captureScripts = async (context, events, rawResources, resources) => {
|
|
223
|
+
const captureScripts = async (context, events, rawResources, resources, frameIds) => {
|
|
224
224
|
let totalSourceBytes = 0;
|
|
225
225
|
const drafts = [];
|
|
226
226
|
for (const script of events.scripts.values()) {
|
|
@@ -252,6 +252,7 @@ const captureScripts = async (context, events, rawResources, resources) => {
|
|
|
252
252
|
}
|
|
253
253
|
}
|
|
254
254
|
const inventoryScript = {
|
|
255
|
+
frame_id: events.frameForScript(script, frameIds),
|
|
255
256
|
url: script.url,
|
|
256
257
|
cdp_hash: script.hash,
|
|
257
258
|
length: script.length,
|
|
@@ -276,7 +277,8 @@ const captureScripts = async (context, events, rawResources, resources) => {
|
|
|
276
277
|
base: stableWebScriptKey(draft.item),
|
|
277
278
|
}));
|
|
278
279
|
keyed.sort((left, right) => left.base.localeCompare(right.base) ||
|
|
279
|
-
sourceDigest(left.item.source).localeCompare(sourceDigest(right.item.source))
|
|
280
|
+
sourceDigest(left.item.source).localeCompare(sourceDigest(right.item.source)) ||
|
|
281
|
+
(left.item.frame_id ?? "").localeCompare(right.item.frame_id ?? ""));
|
|
280
282
|
const totals = new Map();
|
|
281
283
|
for (const { base } of keyed)
|
|
282
284
|
totals.set(base, (totals.get(base) ?? 0) + 1);
|
|
@@ -349,6 +351,8 @@ const captureWorkers = async (state, frameIds) => {
|
|
|
349
351
|
url: url.url,
|
|
350
352
|
origin: url.origin,
|
|
351
353
|
attached: target.attached === true,
|
|
354
|
+
opener_target_id: boundedText(target.openerId, 256) ?? null,
|
|
355
|
+
parent_frame_id: boundedText(target.parentFrameId, 256) ?? null,
|
|
352
356
|
});
|
|
353
357
|
}
|
|
354
358
|
return {
|