@saptools/cf-inspector 0.3.1 → 0.3.3
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 +30 -13
- package/dist/cli.js +276 -50
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +14 -5
- package/dist/index.js +136 -39
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DebuggerHandle } from '@saptools/cf-debugger';
|
|
2
2
|
|
|
3
|
-
type CfInspectorErrorCode = "INVALID_ARGUMENT" | "INVALID_BREAKPOINT" | "INVALID_REMOTE_ROOT" | "INVALID_EXPRESSION" | "BREAKPOINT_DID_NOT_BIND" | "INSPECTOR_DISCOVERY_FAILED" | "INSPECTOR_CONNECTION_FAILED" | "CDP_REQUEST_FAILED" | "BREAKPOINT_NOT_HIT" | "EVALUATION_FAILED" | "MISSING_TARGET" | "ABORTED";
|
|
3
|
+
type CfInspectorErrorCode = "INVALID_ARGUMENT" | "INVALID_BREAKPOINT" | "INVALID_REMOTE_ROOT" | "INVALID_EXPRESSION" | "BREAKPOINT_DID_NOT_BIND" | "INSPECTOR_DISCOVERY_FAILED" | "INSPECTOR_CONNECTION_FAILED" | "CDP_REQUEST_FAILED" | "BREAKPOINT_NOT_HIT" | "UNRELATED_PAUSE" | "UNRELATED_PAUSE_TIMEOUT" | "EVALUATION_FAILED" | "MISSING_TARGET" | "ABORTED";
|
|
4
4
|
declare class CfInspectorError extends Error {
|
|
5
5
|
readonly code: CfInspectorErrorCode;
|
|
6
6
|
readonly detail?: string;
|
|
@@ -54,6 +54,7 @@ interface PauseEvent {
|
|
|
54
54
|
readonly reason: string;
|
|
55
55
|
readonly hitBreakpoints: readonly string[];
|
|
56
56
|
readonly callFrames: readonly CallFrameInfo[];
|
|
57
|
+
readonly receivedAtMs?: number;
|
|
57
58
|
}
|
|
58
59
|
interface VariableSnapshot {
|
|
59
60
|
readonly name: string;
|
|
@@ -78,14 +79,16 @@ interface CapturedExpression {
|
|
|
78
79
|
readonly type?: string;
|
|
79
80
|
readonly error?: string;
|
|
80
81
|
}
|
|
81
|
-
interface
|
|
82
|
+
interface SnapshotCaptureResult {
|
|
82
83
|
readonly reason: string;
|
|
83
84
|
readonly hitBreakpoints: readonly string[];
|
|
84
85
|
readonly capturedAt: string;
|
|
85
|
-
readonly captureDurationMs: number;
|
|
86
86
|
readonly topFrame?: FrameSnapshot;
|
|
87
87
|
readonly captures: readonly CapturedExpression[];
|
|
88
88
|
}
|
|
89
|
+
interface SnapshotResult extends SnapshotCaptureResult {
|
|
90
|
+
readonly pausedDurationMs: number | null;
|
|
91
|
+
}
|
|
89
92
|
interface ScriptInfo {
|
|
90
93
|
readonly scriptId: string;
|
|
91
94
|
readonly url: string;
|
|
@@ -170,12 +173,16 @@ interface InspectorVersion {
|
|
|
170
173
|
interface PauseWaitGate {
|
|
171
174
|
active: boolean;
|
|
172
175
|
}
|
|
176
|
+
interface DebuggerState {
|
|
177
|
+
lastResumedAtMs?: number;
|
|
178
|
+
}
|
|
173
179
|
interface InspectorSession {
|
|
174
180
|
readonly client: CdpClient;
|
|
175
181
|
readonly target: InspectorTarget;
|
|
176
182
|
readonly scripts: ReadonlyMap<string, ScriptInfo>;
|
|
177
183
|
readonly pauseBuffer: PauseEvent[];
|
|
178
184
|
readonly pauseWaitGate: PauseWaitGate;
|
|
185
|
+
readonly debuggerState: DebuggerState;
|
|
179
186
|
dispose(): Promise<void>;
|
|
180
187
|
}
|
|
181
188
|
interface CdpEvalResult {
|
|
@@ -204,6 +211,8 @@ declare function removeBreakpoint(session: InspectorSession, breakpointId: strin
|
|
|
204
211
|
interface WaitForPauseOptions {
|
|
205
212
|
readonly timeoutMs: number;
|
|
206
213
|
readonly breakpointIds?: readonly string[];
|
|
214
|
+
readonly unmatchedPausePolicy?: "wait-for-resume" | "fail";
|
|
215
|
+
readonly onUnmatchedPause?: (pause: PauseEvent) => void;
|
|
207
216
|
}
|
|
208
217
|
declare function waitForPause(session: InspectorSession, options: WaitForPauseOptions): Promise<PauseEvent>;
|
|
209
218
|
declare function resume(session: InspectorSession): Promise<void>;
|
|
@@ -236,7 +245,7 @@ declare function getProperties(session: InspectorSession, objectId: string): Pro
|
|
|
236
245
|
interface CaptureSnapshotOptions {
|
|
237
246
|
readonly captures?: readonly string[];
|
|
238
247
|
}
|
|
239
|
-
declare function captureSnapshot(session: InspectorSession, pause: PauseEvent, options?: CaptureSnapshotOptions): Promise<
|
|
248
|
+
declare function captureSnapshot(session: InspectorSession, pause: PauseEvent, options?: CaptureSnapshotOptions): Promise<SnapshotCaptureResult>;
|
|
240
249
|
|
|
241
250
|
interface LogpointEvent {
|
|
242
251
|
readonly ts: string;
|
|
@@ -296,4 +305,4 @@ interface OpenedTunnel {
|
|
|
296
305
|
}
|
|
297
306
|
declare function openCfTunnel(target: TunnelTarget): Promise<OpenedTunnel>;
|
|
298
307
|
|
|
299
|
-
export { type BreakpointHandle, type BreakpointLocation, type CallFrameInfo, type CaptureSnapshotOptions, type CapturedExpression, CfInspectorError, type CfInspectorErrorCode, type FrameSnapshot, type InspectorConnectOptions, type InspectorSession, type InspectorTarget, type LogpointEvent, type LogpointStreamOptions, type LogpointStreamResult, type OpenedTunnel, type PauseEvent, type RemoteRootSetting, type ResolvedLocation, type ScopeInfo, type ScopeSnapshot, type ScriptInfo, type SetBreakpointInput, type SnapshotResult, type TunnelTarget, type VariableSnapshot, type WaitForPauseOptions, buildBreakpointUrlRegex, buildLogpointCondition, captureSnapshot, connectInspector, discoverInspectorTargets, evaluateGlobal, evaluateOnFrame, fetchInspectorVersion, getProperties, listScripts, openCfTunnel, parseBreakpointSpec, parseRemoteRoot, removeBreakpoint, resume, setBreakpoint, streamLogpoint, validateExpression, waitForPause };
|
|
308
|
+
export { type BreakpointHandle, type BreakpointLocation, type CallFrameInfo, type CaptureSnapshotOptions, type CapturedExpression, CfInspectorError, type CfInspectorErrorCode, type DebuggerState, type FrameSnapshot, type InspectorConnectOptions, type InspectorSession, type InspectorTarget, type LogpointEvent, type LogpointStreamOptions, type LogpointStreamResult, type OpenedTunnel, type PauseEvent, type RemoteRootSetting, type ResolvedLocation, type ScopeInfo, type ScopeSnapshot, type ScriptInfo, type SetBreakpointInput, type SnapshotCaptureResult, type SnapshotResult, type TunnelTarget, type VariableSnapshot, type WaitForPauseOptions, buildBreakpointUrlRegex, buildLogpointCondition, captureSnapshot, connectInspector, discoverInspectorTargets, evaluateGlobal, evaluateOnFrame, fetchInspectorVersion, getProperties, listScripts, openCfTunnel, parseBreakpointSpec, parseRemoteRoot, removeBreakpoint, resume, setBreakpoint, streamLogpoint, validateExpression, waitForPause };
|
package/dist/index.js
CHANGED
|
@@ -215,6 +215,15 @@ function stripTrailingSlash(value) {
|
|
|
215
215
|
}
|
|
216
216
|
return value;
|
|
217
217
|
}
|
|
218
|
+
function normalizeRegexRootPattern(pattern) {
|
|
219
|
+
const withoutStartAnchor = pattern.startsWith("^") ? pattern.slice(1) : pattern;
|
|
220
|
+
const withoutEndAnchor = withoutStartAnchor.endsWith("$") && !isEscaped(withoutStartAnchor, withoutStartAnchor.length - 1) ? withoutStartAnchor.slice(0, -1) : withoutStartAnchor;
|
|
221
|
+
return stripTrailingSlash(withoutEndAnchor);
|
|
222
|
+
}
|
|
223
|
+
function buildFileUrlRegex(rootPattern, tail) {
|
|
224
|
+
const separator = rootPattern.endsWith("/") ? "" : "/";
|
|
225
|
+
return `^file://${rootPattern}${separator}${tail}$`;
|
|
226
|
+
}
|
|
218
227
|
function escapeRegExp(value) {
|
|
219
228
|
return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
220
229
|
}
|
|
@@ -241,16 +250,18 @@ function buildBreakpointUrlRegex(input) {
|
|
|
241
250
|
}
|
|
242
251
|
case "literal": {
|
|
243
252
|
const escapedRoot = escapeRegExp(input.remoteRoot.value);
|
|
244
|
-
return
|
|
253
|
+
return buildFileUrlRegex(escapedRoot, tail);
|
|
245
254
|
}
|
|
246
255
|
case "regex": {
|
|
247
|
-
|
|
256
|
+
const rootPattern = normalizeRegexRootPattern(input.remoteRoot.pattern);
|
|
257
|
+
return buildFileUrlRegex(rootPattern, tail);
|
|
248
258
|
}
|
|
249
259
|
}
|
|
250
260
|
}
|
|
251
261
|
|
|
252
262
|
// src/inspector.ts
|
|
253
263
|
import { request } from "http";
|
|
264
|
+
import { performance } from "perf_hooks";
|
|
254
265
|
|
|
255
266
|
// src/cdp.ts
|
|
256
267
|
init_types();
|
|
@@ -606,21 +617,21 @@ async function connectInspector(options) {
|
|
|
606
617
|
const PAUSE_BUFFER_LIMIT = 32;
|
|
607
618
|
const pauseBuffer = [];
|
|
608
619
|
const pauseWaitGate = { active: false };
|
|
620
|
+
const debuggerState = {};
|
|
609
621
|
client.on("Debugger.paused", (raw) => {
|
|
610
622
|
if (pauseWaitGate.active) {
|
|
611
623
|
return;
|
|
612
624
|
}
|
|
613
625
|
const params = raw;
|
|
614
|
-
const event =
|
|
615
|
-
reason: asString(params.reason),
|
|
616
|
-
hitBreakpoints: Array.isArray(params.hitBreakpoints) ? params.hitBreakpoints.filter((id) => typeof id === "string") : [],
|
|
617
|
-
callFrames: toCallFrames(params.callFrames)
|
|
618
|
-
};
|
|
626
|
+
const event = toPauseEvent(params, performance.now());
|
|
619
627
|
if (pauseBuffer.length >= PAUSE_BUFFER_LIMIT) {
|
|
620
628
|
pauseBuffer.shift();
|
|
621
629
|
}
|
|
622
630
|
pauseBuffer.push(event);
|
|
623
631
|
});
|
|
632
|
+
client.on("Debugger.resumed", () => {
|
|
633
|
+
debuggerState.lastResumedAtMs = performance.now();
|
|
634
|
+
});
|
|
624
635
|
await client.send("Runtime.enable");
|
|
625
636
|
await client.send("Debugger.enable");
|
|
626
637
|
return {
|
|
@@ -629,6 +640,7 @@ async function connectInspector(options) {
|
|
|
629
640
|
scripts,
|
|
630
641
|
pauseBuffer,
|
|
631
642
|
pauseWaitGate,
|
|
643
|
+
debuggerState,
|
|
632
644
|
dispose: async () => {
|
|
633
645
|
try {
|
|
634
646
|
await client.send("Debugger.disable");
|
|
@@ -746,36 +758,121 @@ function pauseMatches(pause, breakpointIds) {
|
|
|
746
758
|
}
|
|
747
759
|
return pause.hitBreakpoints.some((id) => breakpointIds.includes(id));
|
|
748
760
|
}
|
|
761
|
+
function toPauseEvent(params, receivedAtMs) {
|
|
762
|
+
return {
|
|
763
|
+
reason: asString(params.reason),
|
|
764
|
+
hitBreakpoints: Array.isArray(params.hitBreakpoints) ? params.hitBreakpoints.filter((id) => typeof id === "string") : [],
|
|
765
|
+
callFrames: toCallFrames(params.callFrames),
|
|
766
|
+
receivedAtMs
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
function remainingUntil(deadlineMs) {
|
|
770
|
+
return Math.max(0, deadlineMs - performance.now());
|
|
771
|
+
}
|
|
772
|
+
function topFrameLocation(pause) {
|
|
773
|
+
const top = pause.callFrames[0];
|
|
774
|
+
if (top === void 0) {
|
|
775
|
+
return "(no call frame)";
|
|
776
|
+
}
|
|
777
|
+
const url = top.url !== void 0 && top.url.length > 0 ? top.url : "(unknown)";
|
|
778
|
+
return `${url}:${(top.lineNumber + 1).toString()}:${(top.columnNumber + 1).toString()}`;
|
|
779
|
+
}
|
|
780
|
+
function pauseDetail(pause) {
|
|
781
|
+
return JSON.stringify({
|
|
782
|
+
reason: pause.reason,
|
|
783
|
+
hitBreakpoints: pause.hitBreakpoints,
|
|
784
|
+
topFrame: topFrameLocation(pause)
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
function hasResumedSincePause(session, pause) {
|
|
788
|
+
const pauseAt = pause.receivedAtMs;
|
|
789
|
+
const resumedAt = session.debuggerState.lastResumedAtMs;
|
|
790
|
+
return pauseAt !== void 0 && resumedAt !== void 0 && resumedAt >= pauseAt;
|
|
791
|
+
}
|
|
792
|
+
function throwBreakpointTimeout(timeoutMs) {
|
|
793
|
+
throw new CfInspectorError(
|
|
794
|
+
"BREAKPOINT_NOT_HIT",
|
|
795
|
+
`Timed out waiting for matching Debugger.paused after ${timeoutMs.toString()}ms`
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
function throwUnrelatedPauseTimeout(pause, timeoutMs) {
|
|
799
|
+
throw new CfInspectorError(
|
|
800
|
+
"UNRELATED_PAUSE_TIMEOUT",
|
|
801
|
+
`Target stayed paused by another debugger event before this command's breakpoint could hit within ${timeoutMs.toString()}ms`,
|
|
802
|
+
pauseDetail(pause)
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
async function waitForUnmatchedPauseToResume(session, pause, deadlineMs, timeoutMs) {
|
|
806
|
+
if (hasResumedSincePause(session, pause)) {
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
const remainingMs = remainingUntil(deadlineMs);
|
|
810
|
+
if (remainingMs <= 0) {
|
|
811
|
+
throwUnrelatedPauseTimeout(pause, timeoutMs);
|
|
812
|
+
}
|
|
813
|
+
try {
|
|
814
|
+
await session.client.waitFor("Debugger.resumed", { timeoutMs: remainingMs });
|
|
815
|
+
session.debuggerState.lastResumedAtMs = performance.now();
|
|
816
|
+
} catch (err) {
|
|
817
|
+
if (err instanceof CfInspectorError && err.code === "BREAKPOINT_NOT_HIT") {
|
|
818
|
+
throwUnrelatedPauseTimeout(pause, timeoutMs);
|
|
819
|
+
}
|
|
820
|
+
throw err;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
async function handleUnmatchedPause(session, pause, options, deadlineMs) {
|
|
824
|
+
if (options.unmatchedPausePolicy === "fail") {
|
|
825
|
+
throw new CfInspectorError(
|
|
826
|
+
"UNRELATED_PAUSE",
|
|
827
|
+
"Target paused before this command's breakpoint was reached",
|
|
828
|
+
pauseDetail(pause)
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
if (hasResumedSincePause(session, pause)) {
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
options.onUnmatchedPause?.(pause);
|
|
835
|
+
await waitForUnmatchedPauseToResume(session, pause, deadlineMs, options.timeoutMs);
|
|
836
|
+
}
|
|
749
837
|
async function waitForPause(session, options) {
|
|
838
|
+
const deadlineMs = performance.now() + options.timeoutMs;
|
|
750
839
|
const buffer = session.pauseBuffer;
|
|
751
|
-
while (buffer.length > 0) {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
840
|
+
while (buffer.length > 0 || remainingUntil(deadlineMs) > 0) {
|
|
841
|
+
while (buffer.length > 0) {
|
|
842
|
+
const buffered = buffer.shift();
|
|
843
|
+
if (buffered === void 0) {
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
if (pauseMatches(buffered, options.breakpointIds)) {
|
|
847
|
+
return buffered;
|
|
848
|
+
}
|
|
849
|
+
await handleUnmatchedPause(session, buffered, options, deadlineMs);
|
|
755
850
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
851
|
+
const remainingMs = remainingUntil(deadlineMs);
|
|
852
|
+
if (remainingMs <= 0) {
|
|
853
|
+
throwBreakpointTimeout(options.timeoutMs);
|
|
854
|
+
}
|
|
855
|
+
session.pauseWaitGate.active = true;
|
|
856
|
+
let receivedAtMs;
|
|
857
|
+
let params;
|
|
858
|
+
try {
|
|
859
|
+
params = await session.client.waitFor("Debugger.paused", {
|
|
860
|
+
timeoutMs: remainingMs,
|
|
861
|
+
predicate: () => {
|
|
862
|
+
receivedAtMs = performance.now();
|
|
765
863
|
return true;
|
|
766
864
|
}
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
|
|
865
|
+
});
|
|
866
|
+
} finally {
|
|
867
|
+
session.pauseWaitGate.active = false;
|
|
868
|
+
}
|
|
869
|
+
const pause = toPauseEvent(params, receivedAtMs ?? performance.now());
|
|
870
|
+
if (pauseMatches(pause, options.breakpointIds)) {
|
|
871
|
+
return pause;
|
|
872
|
+
}
|
|
873
|
+
await handleUnmatchedPause(session, pause, options, deadlineMs);
|
|
773
874
|
}
|
|
774
|
-
|
|
775
|
-
reason: asString(params.reason),
|
|
776
|
-
hitBreakpoints: Array.isArray(params.hitBreakpoints) ? params.hitBreakpoints.filter((id) => typeof id === "string") : [],
|
|
777
|
-
callFrames: toCallFrames(params.callFrames)
|
|
778
|
-
};
|
|
875
|
+
throwBreakpointTimeout(options.timeoutMs);
|
|
779
876
|
}
|
|
780
877
|
async function resume(session) {
|
|
781
878
|
await session.client.send("Debugger.resume");
|
|
@@ -826,13 +923,12 @@ async function getProperties(session, objectId) {
|
|
|
826
923
|
}
|
|
827
924
|
|
|
828
925
|
// src/snapshot.ts
|
|
829
|
-
import { performance } from "perf_hooks";
|
|
830
926
|
var MAX_SCOPES = 3;
|
|
831
927
|
var MAX_SCOPE_VARIABLES = 20;
|
|
832
928
|
var MAX_CHILD_VARIABLES = 8;
|
|
833
929
|
var MAX_VARIABLE_DEPTH = 2;
|
|
834
930
|
var MAX_VALUE_LENGTH = 240;
|
|
835
|
-
var SENSITIVE_NAME_REGEX = /(pass(?:word)?|token|secret|api[_-]?key|authorization|cookie|session|private[_-]?key)/i;
|
|
931
|
+
var SENSITIVE_NAME_REGEX = /(pass(?:word)?|credentials?|creds?|token|secret|api[_-]?key|authorization|cookie|session|private[_-]?key)/i;
|
|
836
932
|
var PRIORITY_BY_TYPE = {
|
|
837
933
|
local: 0,
|
|
838
934
|
arguments: 1,
|
|
@@ -893,8 +989,11 @@ function formatPrimitive(value) {
|
|
|
893
989
|
}
|
|
894
990
|
return String(value);
|
|
895
991
|
}
|
|
992
|
+
function isSensitiveName(name) {
|
|
993
|
+
return SENSITIVE_NAME_REGEX.test(name);
|
|
994
|
+
}
|
|
896
995
|
function sanitizeValue(name, raw) {
|
|
897
|
-
if (
|
|
996
|
+
if (isSensitiveName(name)) {
|
|
898
997
|
return "[REDACTED]";
|
|
899
998
|
}
|
|
900
999
|
if (raw.length <= MAX_VALUE_LENGTH) {
|
|
@@ -912,8 +1011,9 @@ async function captureProperties(session, objectId, limit, depth) {
|
|
|
912
1011
|
limited.map(async (prop) => {
|
|
913
1012
|
const name = typeof prop.name === "string" ? prop.name : "?";
|
|
914
1013
|
const described = describeProperty(prop);
|
|
1014
|
+
const sensitive = isSensitiveName(name);
|
|
915
1015
|
let children;
|
|
916
|
-
if (depth > 0 && described.objectId !== void 0 && isExpandable(described.type)) {
|
|
1016
|
+
if (!sensitive && depth > 0 && described.objectId !== void 0 && isExpandable(described.type)) {
|
|
917
1017
|
try {
|
|
918
1018
|
const nested = await captureProperties(
|
|
919
1019
|
session,
|
|
@@ -927,7 +1027,7 @@ async function captureProperties(session, objectId, limit, depth) {
|
|
|
927
1027
|
} catch {
|
|
928
1028
|
}
|
|
929
1029
|
}
|
|
930
|
-
const sanitizedValue = sanitizeValue(name, described.value);
|
|
1030
|
+
const sanitizedValue = sensitive ? "[REDACTED]" : sanitizeValue(name, described.value);
|
|
931
1031
|
const base = { name, value: sanitizedValue };
|
|
932
1032
|
const withType = described.type === void 0 ? base : { ...base, type: described.type };
|
|
933
1033
|
return children === void 0 ? withType : { ...withType, children };
|
|
@@ -985,7 +1085,6 @@ function evalResultToCaptured(expression, result) {
|
|
|
985
1085
|
return buildCaptured("undefined");
|
|
986
1086
|
}
|
|
987
1087
|
async function captureSnapshot(session, pause, options = {}) {
|
|
988
|
-
const startedAt = performance.now();
|
|
989
1088
|
const top = pause.callFrames[0];
|
|
990
1089
|
let topFrame;
|
|
991
1090
|
let captures = [];
|
|
@@ -1012,12 +1111,10 @@ async function captureSnapshot(session, pause, options = {}) {
|
|
|
1012
1111
|
);
|
|
1013
1112
|
}
|
|
1014
1113
|
}
|
|
1015
|
-
const captureDurationMs = Math.round((performance.now() - startedAt) * 1e3) / 1e3;
|
|
1016
1114
|
return {
|
|
1017
1115
|
reason: pause.reason,
|
|
1018
1116
|
hitBreakpoints: pause.hitBreakpoints,
|
|
1019
1117
|
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1020
|
-
captureDurationMs,
|
|
1021
1118
|
...topFrame === void 0 ? {} : { topFrame },
|
|
1022
1119
|
captures
|
|
1023
1120
|
};
|