@crouton-kit/capture 1.3.7 → 1.3.9
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/bin/capture +32786 -9989
- package/package.json +9 -3
- package/src/capture.ts +86 -163
- package/src/cdp/a11y.ts +113 -14
- package/src/cdp/args.ts +300 -116
- package/src/cdp/bridge/protocol.ts +187 -1
- package/src/cdp/bridge/server.ts +211 -84
- package/src/cdp/bridge/spawn.ts +110 -1
- package/src/cdp/client.ts +13 -3
- package/src/cdp/commands/bridge-serve.ts +35 -4
- package/src/cdp/commands/cdp.ts +266 -69
- package/src/cdp/commands/gate-guard.ts +43 -0
- package/src/cdp/commands/lib.ts +339 -213
- package/src/cdp/commands/measure/census.ts +87 -0
- package/src/cdp/commands/measure/check.ts +109 -0
- package/src/cdp/commands/measure/diff.ts +120 -0
- package/src/cdp/commands/measure/explain.ts +133 -0
- package/src/cdp/commands/measure/index.ts +106 -0
- package/src/cdp/commands/measure/map-ax.ts +52 -0
- package/src/cdp/commands/measure/map-focus.ts +52 -0
- package/src/cdp/commands/measure/map-layers.ts +59 -0
- package/src/cdp/commands/measure/map-scroll.ts +94 -0
- package/src/cdp/commands/measure/snap.ts +416 -0
- package/src/cdp/commands/measure/sweep.ts +227 -0
- package/src/cdp/commands/motion/index.ts +61 -0
- package/src/cdp/commands/motion/jank.ts +132 -0
- package/src/cdp/commands/motion/mask.ts +92 -0
- package/src/cdp/commands/motion/rec.ts +534 -0
- package/src/cdp/commands/motion/response.ts +76 -0
- package/src/cdp/commands/motion/timeline.ts +152 -0
- package/src/cdp/commands/page/click.ts +194 -0
- package/src/cdp/commands/page/elements.ts +154 -0
- package/src/cdp/commands/page/exec.ts +317 -0
- package/src/cdp/commands/page/index.ts +72 -0
- package/src/cdp/commands/page/navigate.ts +330 -0
- package/src/cdp/commands/page/scroll.ts +107 -0
- package/src/cdp/commands/page/shot.ts +208 -0
- package/src/cdp/commands/page/type.ts +104 -0
- package/src/cdp/commands/tab/index.ts +59 -0
- package/src/cdp/commands/tab/list.ts +167 -0
- package/src/cdp/commands/tab/network.ts +162 -0
- package/src/cdp/commands/tab/open.ts +90 -0
- package/src/cdp/commands/tab/reset.ts +160 -0
- package/src/cdp/connection.ts +368 -37
- package/src/cdp/coordinates.ts +158 -0
- package/src/cdp/detect.ts +42 -13
- package/src/cdp/dispatch.ts +76 -26
- package/src/cdp/domains.ts +75 -0
- package/src/cdp/har-recorder.ts +648 -115
- package/src/cdp/leaf-grammar.ts +99 -0
- package/src/cdp/measure/census.ts +205 -0
- package/src/cdp/measure/check.ts +318 -0
- package/src/cdp/measure/collectors/animation.ts +385 -0
- package/src/cdp/measure/collectors/ax.ts +278 -0
- package/src/cdp/measure/collectors/focus.ts +1041 -0
- package/src/cdp/measure/collectors/forms.ts +905 -0
- package/src/cdp/measure/collectors/geometry.ts +1038 -0
- package/src/cdp/measure/collectors/hittest.ts +835 -0
- package/src/cdp/measure/collectors/layers.ts +697 -0
- package/src/cdp/measure/collectors/media.ts +522 -0
- package/src/cdp/measure/collectors/pixels.ts +1584 -0
- package/src/cdp/measure/collectors/queries.ts +464 -0
- package/src/cdp/measure/collectors/scroll.ts +599 -0
- package/src/cdp/measure/collectors/states.ts +992 -0
- package/src/cdp/measure/collectors/style-provenance.ts +643 -0
- package/src/cdp/measure/collectors/styles.ts +355 -0
- package/src/cdp/measure/collectors/text.ts +713 -0
- package/src/cdp/measure/diff.ts +470 -0
- package/src/cdp/measure/explain.ts +498 -0
- package/src/cdp/measure/map-ax.ts +633 -0
- package/src/cdp/measure/map-focus.ts +188 -0
- package/src/cdp/measure/map-layers.ts +177 -0
- package/src/cdp/measure/map-scroll.ts +317 -0
- package/src/cdp/measure/redaction.ts +51 -0
- package/src/cdp/measure/settle.ts +1051 -0
- package/src/cdp/measure/snapshot.ts +390 -0
- package/src/cdp/measure/sweep.ts +330 -0
- package/src/cdp/measure/types.ts +220 -0
- package/src/cdp/measure/v2/collectors.ts +19 -0
- package/src/cdp/measure/v2/orchestrator.ts +46 -0
- package/src/cdp/measure/v2/snap-selection.ts +33 -0
- package/src/cdp/measure/v2/target-lock.ts +15 -0
- package/src/cdp/measure/v2/target.ts +16 -0
- package/src/cdp/motion/jank.ts +433 -0
- package/src/cdp/motion/mask.ts +421 -0
- package/src/cdp/motion/recorder.ts +764 -0
- package/src/cdp/motion/response.ts +356 -0
- package/src/cdp/motion/timeline.ts +215 -0
- package/src/cdp/record.ts +5 -212
- package/src/cdp/recorder-bridge.ts +1894 -0
- package/src/cdp/recorder-client.ts +326 -0
- package/src/cdp/scope-lock.ts +73 -0
- package/src/cdp/screenshot.ts +199 -66
- package/src/cdp/source-map.ts +309 -0
- package/src/cdp/targets.ts +21 -1
- package/src/cdp/timing.ts +87 -0
- package/src/cdp/types.ts +87 -6
- package/src/cdp/viewport.ts +21 -0
- package/src/cdp.ts +12 -58
- package/src/command-help.ts +29 -0
- package/src/command-resolution.ts +22 -0
- package/src/command-walker.ts +35 -0
- package/src/contracts/a11y.ts +33 -0
- package/src/contracts/cursor.ts +35 -0
- package/src/contracts/index.ts +15 -0
- package/src/contracts/motion.ts +19 -0
- package/src/contracts/neutral.ts +56 -0
- package/src/contracts/primitives.ts +205 -0
- package/src/contracts/release.ts +17 -0
- package/src/contracts/results.ts +208 -0
- package/src/contracts/routes.ts +140 -0
- package/src/contracts/snapshot.ts +92 -0
- package/src/cursor/index.ts +13 -0
- package/src/cursor/service.ts +310 -0
- package/src/errors.ts +71 -0
- package/src/har-manager.ts +992 -41
- package/src/interact.ts +410 -98
- package/src/output/artifact-lifecycle.ts +371 -0
- package/src/output/artifact.ts +526 -0
- package/src/output/diff.ts +111 -0
- package/src/output/exact-raw.ts +114 -0
- package/src/output/render.ts +987 -0
- package/src/output/selector.ts +185 -0
- package/src/output/structured.ts +211 -0
- package/src/registry.ts +208 -0
- package/src/session/artifacts.ts +481 -0
- package/src/session/commands.ts +1237 -368
- package/src/session/coordinator.ts +251 -0
- package/src/session/har-filter.ts +57 -0
- package/src/session/log-tailer.ts +653 -0
- package/src/session-context.ts +302 -34
- package/src/vault/bundle.ts +32 -17
- package/src/cdp/commands/exec.ts +0 -105
- package/src/cdp/commands/har.ts +0 -128
- package/src/cdp/commands/tabs.ts +0 -183
- package/src/cdp/commands/traffic.ts +0 -121
- package/src/cdp/commands/ui.ts +0 -172
- package/src/cdp/execute.ts +0 -113
- package/src/cdp/har-output.ts +0 -47
- package/src/cdp/usage.ts +0 -77
- package/src/tab-session.ts +0 -91
package/src/cdp/args.ts
CHANGED
|
@@ -1,25 +1,208 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { invalidInput } from '../errors.js';
|
|
2
2
|
import { getActiveSession } from '../session-context.js';
|
|
3
|
+
import { type ParsedArgs } from './types.js';
|
|
4
|
+
import { assertParseableUrl, assertScrollDestination, assertSweepBounds, parsePositiveNumber, parseDoAction, type SweepAxisName } from './leaf-grammar.js';
|
|
3
5
|
|
|
4
|
-
// Every flag below is matched as `--flag` followed by a separate `next`
|
|
5
|
-
// token (space-separated). To accept the equally-common `--flag=value` form
|
|
6
|
-
// without duplicating every branch, split any `--flag=value` token into two
|
|
7
|
-
// tokens up front so the rest of the parser only ever has to handle one
|
|
8
|
-
// convention.
|
|
9
6
|
export function expandEqualsFlags(argv: string[]): string[] {
|
|
10
7
|
const out: string[] = [];
|
|
11
8
|
for (const arg of argv) {
|
|
12
9
|
if (arg.startsWith('--') && arg.includes('=')) {
|
|
13
10
|
const eq = arg.indexOf('=');
|
|
14
11
|
out.push(arg.slice(0, eq), arg.slice(eq + 1));
|
|
15
|
-
} else
|
|
16
|
-
out.push(arg);
|
|
17
|
-
}
|
|
12
|
+
} else out.push(arg);
|
|
18
13
|
}
|
|
19
14
|
return out;
|
|
20
15
|
}
|
|
21
16
|
|
|
22
|
-
|
|
17
|
+
const VALUE_FLAGS = new Set([
|
|
18
|
+
'--port', '--out', '--duration', '--settle', '--file', '--target', '--url', '--into', '--viewport', '--session',
|
|
19
|
+
'--filter', '--name', '--filter-url', '--filter-status', '--filter-method', '--limit', '--params', '--wait-event',
|
|
20
|
+
'--timeout', '--socket', '--settle-timeout', '--state', '--for', '--before', '--after', '--snap', '--set-file',
|
|
21
|
+
'--axis', '--from', '--to', '--viewport-height', '--rec-id', '--selector', '--do', '--element', '--prop', '--action', '--occurrence',
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
function valueFor(flag: string, next: string | undefined): string {
|
|
25
|
+
if (next === undefined || next.startsWith('--')) throw invalidInput(`${flag} requires a value.`);
|
|
26
|
+
return next;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function integer(token: string, flag: string, min: number, max: number, safe = false): number {
|
|
30
|
+
// Full unsigned decimal integer grammar (leading zeros allowed); no sign,
|
|
31
|
+
// exponent, decimal point, partial token, or non-finite value.
|
|
32
|
+
if (!/^\d+$/.test(token)) throw invalidInput(`Invalid ${flag}: ${token}.`);
|
|
33
|
+
const value = Number(token);
|
|
34
|
+
if (!Number.isInteger(value) || value < min || value > max || (safe && !Number.isSafeInteger(value))) {
|
|
35
|
+
throw invalidInput(`Invalid ${flag}: ${token}.`);
|
|
36
|
+
}
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function durationMs(token: string): number {
|
|
41
|
+
// Full unsigned decimal-seconds grammar (`1`, `1.`, `.5`, `00.5`); no sign,
|
|
42
|
+
// exponent, partial token, or non-finite value. Compare the decimal token
|
|
43
|
+
// before Number conversion so precision rounding cannot admit an overflow.
|
|
44
|
+
if (!/^(?:\d+\.?\d*|\.\d+)$/.test(token)) throw invalidInput(`Invalid --duration: ${token}.`);
|
|
45
|
+
const [whole = '', fraction = ''] = token.split('.');
|
|
46
|
+
const normalizedWhole = whole.replace(/^0+/, '') || '0';
|
|
47
|
+
const maxWhole = '2147483';
|
|
48
|
+
const wholeComparison = normalizedWhole.length - maxWhole.length || normalizedWhole.localeCompare(maxWhole);
|
|
49
|
+
const maxFraction = '647';
|
|
50
|
+
const fractionComparison = fraction.slice(0, maxFraction.length).padEnd(maxFraction.length, '0').localeCompare(maxFraction);
|
|
51
|
+
if (wholeComparison > 0 || (wholeComparison === 0 && (fractionComparison > 0 || (fractionComparison === 0 && /[1-9]/.test(fraction.slice(maxFraction.length)))))) {
|
|
52
|
+
throw invalidInput(`Invalid --duration: ${token}.`);
|
|
53
|
+
}
|
|
54
|
+
return Number(token) * 1000;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function validateKnownLeaf(command: string, positional: readonly string[]): void {
|
|
58
|
+
const leaf = positional[0];
|
|
59
|
+
if (leaf === undefined) return;
|
|
60
|
+
const branches: Record<string, readonly string[]> = {
|
|
61
|
+
session: ['start', 'stop', 'list', 'view', 'har', 'log'],
|
|
62
|
+
page: ['click', 'type', 'scroll', 'navigate', 'exec', 'shot', 'elements'],
|
|
63
|
+
tab: ['list', 'open', 'reset', 'network'],
|
|
64
|
+
measure: ['snap', 'check', 'diff', 'census', 'explain', 'sweep', 'map'],
|
|
65
|
+
motion: ['rec', 'mask', 'timeline', 'jank', 'response'],
|
|
66
|
+
lib: ['list', 'search', 'show', 'read'],
|
|
67
|
+
};
|
|
68
|
+
const known = branches[command];
|
|
69
|
+
if (known && !known.includes(leaf)) throw invalidInput(`Unknown ${command} leaf: ${leaf}.`, 'unknown_command');
|
|
70
|
+
if (command === 'measure' && leaf === 'map' && positional[1] !== undefined) {
|
|
71
|
+
const mapLeaf = positional[1];
|
|
72
|
+
if (!['focus', 'scroll', 'layers', 'ax'].includes(mapLeaf)) throw invalidInput(`Unknown measure map leaf: ${mapLeaf}.`, 'unknown_command');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function requireCount(values: readonly string[], min: number, max: number, command: string): void {
|
|
77
|
+
if (values.length < min || values.length > max) {
|
|
78
|
+
const expected = min === max ? `exactly ${min}` : `${min}..${max}`;
|
|
79
|
+
throw invalidInput(`${command} received ${values.length} positional argument(s); expected ${expected}.`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Pure branch/leaf validation. It must stay before session and environment resolution. */
|
|
84
|
+
export function validateCliInvocation(parsed: ParsedArgs): void {
|
|
85
|
+
validateKnownLeaf(parsed.command, parsed.positional);
|
|
86
|
+
if (parsed.help) return;
|
|
87
|
+
if (parsed.command === 'cdp') {
|
|
88
|
+
requireCount(parsed.positional, 0, 1, 'cdp');
|
|
89
|
+
if (parsed.positional.length === 0 && !parsed.waitEvent) throw invalidInput('cdp requires a method or --wait-event.');
|
|
90
|
+
if (parsed.params !== undefined) {
|
|
91
|
+
try {
|
|
92
|
+
const value = JSON.parse(parsed.params) as unknown;
|
|
93
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('not an object');
|
|
94
|
+
} catch {
|
|
95
|
+
throw invalidInput('--params must be a JSON object.');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (parsed.positional[0] === undefined) return;
|
|
101
|
+
|
|
102
|
+
const leaf = parsed.positional[0];
|
|
103
|
+
const values = parsed.positional.slice(1);
|
|
104
|
+
if (parsed.command === 'session') {
|
|
105
|
+
const ranges: Record<string, readonly [number, number]> = {
|
|
106
|
+
start: [0, 0], stop: [1, 1], list: [0, 0], view: [1, 1], har: [0, 1], log: [1, 1],
|
|
107
|
+
};
|
|
108
|
+
const [min, max] = ranges[leaf];
|
|
109
|
+
requireCount(values, min, max, `session ${leaf}`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (parsed.command === 'page') {
|
|
114
|
+
if (leaf === 'click' || leaf === 'type' || leaf === 'navigate') requireCount(values, 1, 1, `page ${leaf}`);
|
|
115
|
+
if (leaf === 'navigate') assertParseableUrl(values[0], 'URL');
|
|
116
|
+
if (leaf === 'scroll') {
|
|
117
|
+
requireCount(values, 1, 1, 'page scroll');
|
|
118
|
+
if (parsed.to === undefined) throw invalidInput('page scroll requires --to <top|bottom|px>.');
|
|
119
|
+
assertScrollDestination(parsed.to);
|
|
120
|
+
}
|
|
121
|
+
if (leaf === 'exec') requireCount(values, parsed.file === undefined ? 1 : 0, parsed.file === undefined ? 1 : 0, 'page exec');
|
|
122
|
+
if (leaf === 'shot' || leaf === 'elements') requireCount(values, 0, 0, `page ${leaf}`);
|
|
123
|
+
if (leaf === 'shot' && parsed.viewport !== undefined) {
|
|
124
|
+
const match = /^([1-9]\d*)x([1-9]\d*)$/.exec(parsed.viewport);
|
|
125
|
+
if (!match || !Number.isSafeInteger(Number(match[1])) || !Number.isSafeInteger(Number(match[2]))) {
|
|
126
|
+
throw invalidInput(`Invalid --viewport: ${parsed.viewport}.`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (parsed.command === 'tab') {
|
|
133
|
+
requireCount(values, leaf === 'list' ? 0 : 1, leaf === 'list' ? 0 : 1, `tab ${leaf}`);
|
|
134
|
+
if (leaf === 'network' && values[0] !== 'offline' && values[0] !== 'online') {
|
|
135
|
+
throw invalidInput('tab network requires offline or online.');
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (parsed.command === 'measure') {
|
|
141
|
+
if (leaf === 'snap' || leaf === 'check' || leaf === 'sweep') requireCount(values, 0, 1, `measure ${leaf}`);
|
|
142
|
+
if (leaf === 'diff' || leaf === 'census') requireCount(values, 0, 0, `measure ${leaf}`);
|
|
143
|
+
if (leaf === 'diff' && (!parsed.before || !parsed.after)) throw invalidInput('measure diff requires --before and --after.');
|
|
144
|
+
if (leaf === 'census' && !['color', 'font', 'spacing', 'radius', 'shadow', 'animation', 'geometry', 'media', 'queries'].includes(parsed.axis ?? '')) {
|
|
145
|
+
throw invalidInput('measure census requires a documented --axis value.');
|
|
146
|
+
}
|
|
147
|
+
if (leaf === 'explain') {
|
|
148
|
+
requireCount(values, 1, 1, 'measure explain');
|
|
149
|
+
if (!parsed.selector?.trim()) throw invalidInput('measure explain requires --selector.');
|
|
150
|
+
}
|
|
151
|
+
if (leaf === 'sweep') {
|
|
152
|
+
if (!['width', 'dpr', 'zoom', 'color-scheme', 'reduced-motion'].includes(parsed.axis ?? '')) {
|
|
153
|
+
throw invalidInput('measure sweep requires a documented --axis value.');
|
|
154
|
+
}
|
|
155
|
+
assertSweepBounds(parsed.axis as SweepAxisName, parsed.from, parsed.to);
|
|
156
|
+
if (parsed.viewportHeight !== undefined) parsePositiveNumber(parsed.viewportHeight, 1, '--viewport-height');
|
|
157
|
+
}
|
|
158
|
+
if (leaf === 'map') {
|
|
159
|
+
const mapLeaf = values[0];
|
|
160
|
+
if (mapLeaf === undefined) return;
|
|
161
|
+
const targets = values.slice(1);
|
|
162
|
+
requireCount(targets, mapLeaf === 'focus' || mapLeaf === 'ax' ? 1 : 0, 1, `measure map ${mapLeaf}`);
|
|
163
|
+
}
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (parsed.command === 'motion') {
|
|
168
|
+
if (leaf === 'mask' || leaf === 'jank' || leaf === 'response') requireCount(values, 1, 1, `motion ${leaf}`);
|
|
169
|
+
if (leaf === 'timeline') {
|
|
170
|
+
requireCount(values, 1, 1, 'motion timeline');
|
|
171
|
+
if (!parsed.element?.trim()) throw invalidInput('motion timeline requires --element.');
|
|
172
|
+
}
|
|
173
|
+
if (leaf === 'rec') {
|
|
174
|
+
if (parsed.start && parsed.stop) throw invalidInput('motion rec cannot combine --start and --stop.');
|
|
175
|
+
if (!parsed.stop && parsed.recId) throw invalidInput('motion rec accepts --rec-id only with --stop.');
|
|
176
|
+
if (parsed.start || parsed.stop) {
|
|
177
|
+
requireCount(values, 0, 0, 'motion rec lifecycle');
|
|
178
|
+
if (parsed.do || parsed.duration !== undefined || (parsed.stop && parsed.viewport !== undefined)) {
|
|
179
|
+
throw invalidInput('motion rec lifecycle flags cannot be combined with one-shot flags.');
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
requireCount(values, 1, 1, 'motion rec');
|
|
183
|
+
if (!parsed.do) throw invalidInput('motion rec one-shot requires --do.');
|
|
184
|
+
assertParseableUrl(values[0], 'recording URL');
|
|
185
|
+
parseDoAction(parsed.do);
|
|
186
|
+
}
|
|
187
|
+
if (parsed.viewport !== undefined) {
|
|
188
|
+
const match = /^([1-9]\d*)x([1-9]\d*)$/.exec(parsed.viewport);
|
|
189
|
+
if (!match || !Number.isSafeInteger(Number(match[1])) || !Number.isSafeInteger(Number(match[2]))) {
|
|
190
|
+
throw invalidInput(`Invalid --viewport: ${parsed.viewport}.`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (parsed.command === 'lib') {
|
|
198
|
+
if (leaf === 'list') requireCount(values, 0, 0, 'lib list');
|
|
199
|
+
if (leaf === 'search' || leaf === 'show') requireCount(values, 1, 1, `lib ${leaf}`);
|
|
200
|
+
if (leaf === 'read') requireCount(values, 1, Number.MAX_SAFE_INTEGER, 'lib read');
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Pure syntax/provenance parsing: no session or environment reads. */
|
|
205
|
+
export function parseCliSyntax(argv: string[]): ParsedArgs {
|
|
23
206
|
const command = argv[0] ?? '';
|
|
24
207
|
const rest = expandEqualsFlags(argv.slice(1));
|
|
25
208
|
const positional: string[] = [];
|
|
@@ -28,120 +211,121 @@ export function parseCliArgs(argv: string[]): ParsedArgs {
|
|
|
28
211
|
for (let i = 0; i < rest.length; i++) {
|
|
29
212
|
const arg = rest[i];
|
|
30
213
|
const next = rest[i + 1];
|
|
214
|
+
if (VALUE_FLAGS.has(arg)) valueFor(arg, next);
|
|
31
215
|
|
|
32
|
-
if (arg === '--port'
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
i++;
|
|
91
|
-
} else if (arg === '--filter-method' && next) {
|
|
92
|
-
parsed.filterMethod = next.toUpperCase();
|
|
93
|
-
i++;
|
|
94
|
-
} else if (arg === '--limit' && next) {
|
|
95
|
-
parsed.limit = parseInt(next, 10);
|
|
96
|
-
i++;
|
|
97
|
-
} else if (arg === '--browser') {
|
|
98
|
-
parsed.browser = true;
|
|
99
|
-
} else if (arg === '--params' && next) {
|
|
100
|
-
parsed.params = next;
|
|
101
|
-
i++;
|
|
102
|
-
} else if (arg === '--wait-event' && next) {
|
|
103
|
-
parsed.waitEvent = next;
|
|
104
|
-
i++;
|
|
105
|
-
} else if (arg === '--timeout' && next) {
|
|
106
|
-
parsed.timeoutMs = parseInt(next, 10);
|
|
107
|
-
i++;
|
|
108
|
-
} else if (arg === '--socket' && next) {
|
|
109
|
-
parsed.socket = next;
|
|
110
|
-
i++;
|
|
111
|
-
} else if (arg === '--help' || arg === '-h') {
|
|
112
|
-
parsed.help = true;
|
|
113
|
-
} else if (arg.startsWith('--')) {
|
|
114
|
-
console.error(`Unknown flag: ${arg}`);
|
|
115
|
-
process.exit(1);
|
|
116
|
-
} else {
|
|
117
|
-
positional.push(arg);
|
|
118
|
-
}
|
|
216
|
+
if (arg === '--port') { parsed.port = integer(valueFor(arg, next), '--port', 1, 65535); i++; }
|
|
217
|
+
else if (arg === '--out') { parsed.out = valueFor(arg, next); i++; }
|
|
218
|
+
else if (arg === '--json') parsed.json = true;
|
|
219
|
+
else if (arg === '--duration') { parsed.duration = durationMs(valueFor(arg, next)); i++; }
|
|
220
|
+
else if (arg === '--settle') { parsed.settle = integer(valueFor(arg, next), '--settle', 0, 2_147_483_647); i++; }
|
|
221
|
+
else if (arg === '--file') { parsed.file = valueFor(arg, next); i++; }
|
|
222
|
+
else if (arg === '--new') parsed.new = true;
|
|
223
|
+
else if (arg === '--target') { parsed.target = valueFor(arg, next); parsed.targetSource = 'flag'; i++; }
|
|
224
|
+
else if (arg === '--url') { const value = valueFor(arg, next); parsed.url = value; (parsed.urls ??= []).push(value); i++; }
|
|
225
|
+
else if (arg === '--into') { parsed.into = valueFor(arg, next); i++; }
|
|
226
|
+
else if (arg === '--no-screenshot') parsed.noScreenshot = true;
|
|
227
|
+
else if (arg === '--viewport') { const value = valueFor(arg, next); parsed.viewport = value; (parsed.viewports ??= []).push(value); i++; }
|
|
228
|
+
else if (arg === '--full-page') parsed.fullPage = true;
|
|
229
|
+
else if (arg === '--all') parsed.all = true;
|
|
230
|
+
else if (arg === '--session') { parsed.session = valueFor(arg, next); i++; }
|
|
231
|
+
else if (arg === '--hold') parsed.hold = true;
|
|
232
|
+
else if (arg === '--filter') { parsed.filter = valueFor(arg, next); i++; }
|
|
233
|
+
else if (arg === '--name') { parsed.name = valueFor(arg, next); i++; }
|
|
234
|
+
else if (arg === '--filter-url') { parsed.filterUrl = valueFor(arg, next); i++; }
|
|
235
|
+
else if (arg === '--filter-status') { parsed.filterStatus = valueFor(arg, next); i++; }
|
|
236
|
+
else if (arg === '--filter-method') { parsed.filterMethod = valueFor(arg, next).toUpperCase(); i++; }
|
|
237
|
+
else if (arg === '--limit') { parsed.limit = integer(valueFor(arg, next), '--limit', 1, Number.MAX_SAFE_INTEGER, true); i++; }
|
|
238
|
+
else if (arg === '--browser') parsed.browser = true;
|
|
239
|
+
else if (arg === '--params') { parsed.params = valueFor(arg, next); i++; }
|
|
240
|
+
else if (arg === '--wait-event') { parsed.waitEvent = valueFor(arg, next); i++; }
|
|
241
|
+
else if (arg === '--timeout') { parsed.timeoutMs = integer(valueFor(arg, next), '--timeout', 1, 2_147_483_647); i++; }
|
|
242
|
+
else if (arg === '--socket') { parsed.socket = valueFor(arg, next); i++; }
|
|
243
|
+
else if (arg === '--freeze-animations') parsed.freezeAnimations = true;
|
|
244
|
+
else if (arg === '--settle-timeout') { parsed.settleTimeout = integer(valueFor(arg, next), '--settle-timeout', 1, 2_147_483_647); i++; }
|
|
245
|
+
else if (arg === '--capture-unsettled') parsed.captureUnsettled = true;
|
|
246
|
+
else if (arg === '--pixels') parsed.pixels = true;
|
|
247
|
+
else if (arg === '--state') { (parsed.state ??= []).push(valueFor(arg, next)); i++; }
|
|
248
|
+
else if (arg === '--for') { parsed.for = valueFor(arg, next); i++; }
|
|
249
|
+
else if (arg === '--before') { parsed.before = valueFor(arg, next); i++; }
|
|
250
|
+
else if (arg === '--after') { parsed.after = valueFor(arg, next); i++; }
|
|
251
|
+
else if (arg === '--full') parsed.full = true;
|
|
252
|
+
else if (arg === '--gate') parsed.gate = true;
|
|
253
|
+
else if (arg === '--snap') { (parsed.snap ??= []).push(valueFor(arg, next)); i++; }
|
|
254
|
+
else if (arg === '--set-file') { parsed.setFile = valueFor(arg, next); i++; }
|
|
255
|
+
else if (arg === '--axis') { parsed.axis = valueFor(arg, next); i++; }
|
|
256
|
+
else if (arg === '--from') { parsed.from = valueFor(arg, next); i++; }
|
|
257
|
+
else if (arg === '--to') { parsed.to = valueFor(arg, next); i++; }
|
|
258
|
+
else if (arg === '--viewport-height') { parsed.viewportHeight = valueFor(arg, next); i++; }
|
|
259
|
+
else if (arg === '--rec-id') { parsed.recId = valueFor(arg, next); i++; }
|
|
260
|
+
else if (arg === '--selector') { parsed.selector = valueFor(arg, next); i++; }
|
|
261
|
+
else if (arg === '--size') parsed.size = true;
|
|
262
|
+
else if (arg === '--text') parsed.text = true;
|
|
263
|
+
else if (arg === '--form') parsed.form = true;
|
|
264
|
+
else if (arg === '--start') parsed.start = true;
|
|
265
|
+
else if (arg === '--stop') parsed.stop = true;
|
|
266
|
+
else if (arg === '--do') { parsed.do = valueFor(arg, next); i++; }
|
|
267
|
+
else if (arg === '--element') { parsed.element = valueFor(arg, next); i++; }
|
|
268
|
+
else if (arg === '--prop') { parsed.prop = valueFor(arg, next); i++; }
|
|
269
|
+
else if (arg === '--action') { parsed.action = valueFor(arg, next); i++; }
|
|
270
|
+
else if (arg === '--occurrence') { parsed.occurrence = integer(valueFor(arg, next), '--occurrence', 1, Number.MAX_SAFE_INTEGER, true); i++; }
|
|
271
|
+
else if (arg === '-h') parsed.help = true;
|
|
272
|
+
else if (arg.startsWith('--')) throw invalidInput(`Unknown flag: ${arg}.`, 'unknown_flag');
|
|
273
|
+
else positional.push(arg);
|
|
119
274
|
}
|
|
120
275
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
276
|
+
return parsed as ParsedArgs;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Decides whether an invocation needs CDP endpoint (port/target) resolution.
|
|
281
|
+
* Bare branches (`session`, `page`, ... with no leaf) and `measure map` with no
|
|
282
|
+
* sub-leaf only render help. `lib` never touches a browser. Of the `session`
|
|
283
|
+
* lifecycle leaves, only `start` connects to CDP — `stop`, `list`, `view`,
|
|
284
|
+
* `har`, and `log` finalize/read session state and must NOT parse `CDP_PORT`
|
|
285
|
+
* or read/clean the active-session index (a malformed ambient `CDP_PORT` must
|
|
286
|
+
* never block `session stop <id>` from finalizing).
|
|
287
|
+
*/
|
|
288
|
+
function needsEndpointResolution(parsed: ParsedArgs): boolean {
|
|
289
|
+
if (parsed.help || parsed.command === 'lib') return false;
|
|
290
|
+
const branchOnly = ['session', 'page', 'tab', 'measure', 'motion'].includes(parsed.command)
|
|
291
|
+
&& (parsed.positional.length === 0 || (parsed.command === 'measure' && parsed.positional.length === 1 && parsed.positional[0] === 'map'));
|
|
292
|
+
if (branchOnly) return false;
|
|
293
|
+
if (parsed.command === 'session' && parsed.positional[0] !== 'start') return false;
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
136
296
|
|
|
137
|
-
|
|
138
|
-
|
|
297
|
+
/**
|
|
298
|
+
* Hydrates endpoint/session defaults only after pure invocation validation
|
|
299
|
+
* succeeds. Endpoint precedence is `explicit flag > session endpoint > env`:
|
|
300
|
+
* the active session is hydrated FIRST, and the ambient `CDP_PORT` token is
|
|
301
|
+
* parsed only when neither an explicit `--port` nor a session port supplies
|
|
302
|
+
* one — so a malformed `CDP_PORT` can never reject a command whose session
|
|
303
|
+
* already holds the authoritative port.
|
|
304
|
+
*/
|
|
305
|
+
export function resolveCliContext(parsed: ParsedArgs): ParsedArgs {
|
|
306
|
+
if (!needsEndpointResolution(parsed)) return parsed;
|
|
307
|
+
const session = getActiveSession({ cleanStale: false });
|
|
139
308
|
if (session) {
|
|
140
309
|
if (!parsed.har && session.harId) parsed.har = session.harId;
|
|
141
310
|
// An explicit --url picks a different (parallel) tab than the session's
|
|
142
311
|
// own — don't let the session's targetId clobber that choice.
|
|
143
|
-
if (!parsed.target && !parsed.url && session.targetId) parsed.target = session.targetId;
|
|
312
|
+
if (!parsed.target && !parsed.url && session.targetId) { parsed.target = session.targetId; parsed.targetSource = 'session'; }
|
|
313
|
+
// A session target is inseparable from the endpoint that created it.
|
|
314
|
+
// Prefer that endpoint over ambient CDP_PORT, but retain an explicit
|
|
315
|
+
// --port override for deliberate multi-browser work.
|
|
316
|
+
if (parsed.port === undefined && session.port !== undefined && session.port !== null) parsed.port = session.port;
|
|
317
|
+
}
|
|
318
|
+
if (parsed.port === undefined && process.env.CDP_PORT !== undefined) {
|
|
319
|
+
parsed.port = integer(process.env.CDP_PORT, 'CDP_PORT', 1, 65535);
|
|
144
320
|
}
|
|
321
|
+
// Preserve historical stale-index cleanup once endpoint validation has
|
|
322
|
+
// succeeded; only the malformed-env throw path deliberately leaves it alone.
|
|
323
|
+
if (!session) getActiveSession();
|
|
324
|
+
if (!parsed.target && !parsed.url && process.env.CDP_TARGET) { parsed.target = process.env.CDP_TARGET; parsed.targetSource = 'env'; }
|
|
325
|
+
return parsed;
|
|
326
|
+
}
|
|
145
327
|
|
|
146
|
-
|
|
328
|
+
/** Public pure parser used by command-level tests and programmatic branch callers. */
|
|
329
|
+
export function parseCliArgs(argv: string[]): ParsedArgs {
|
|
330
|
+
return parseCliSyntax(argv);
|
|
147
331
|
}
|
|
@@ -17,7 +17,7 @@ export interface BridgeRequest {
|
|
|
17
17
|
* domains (`Browser.*`, `Target.*`).
|
|
18
18
|
*/
|
|
19
19
|
targetId?: string;
|
|
20
|
-
/** CDP event name to
|
|
20
|
+
/** CDP event name to observe after this request is armed. Observation is future-only and scoped to this request's actual flattened target session (`undefined` without `targetId`); one event broadcasts to all already-armed same-key requests. */
|
|
21
21
|
waitEvent?: string;
|
|
22
22
|
timeoutMs?: number;
|
|
23
23
|
}
|
|
@@ -36,3 +36,189 @@ export interface BridgeResponseErr {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
export type BridgeResponse = BridgeResponseOk | BridgeResponseErr;
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Recorder-mode protocol — the SAME NDJSON-over-unix-socket wire format as
|
|
42
|
+
// above, specialized for `capture motion rec` (see ../recorder-bridge.ts).
|
|
43
|
+
// The generic `BridgeRequest`/`BridgeResponse` are unchanged and still
|
|
44
|
+
// govern `capture cdp --browser` / `session start --hold`; a recorder-mode
|
|
45
|
+
// bridge process (spawned in recorder mode, see ../bridge/spawn.ts) speaks
|
|
46
|
+
// this union instead, one request per connection, one response, same as
|
|
47
|
+
// the plain bridge.
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The three-way clock baseline the design's "Recorder timing model" requires to convert
|
|
52
|
+
* screencast-frame and Tracing timestamps into the authoritative `performance.now()` domain.
|
|
53
|
+
* `performanceNowMs`/`wallClockMs` are read synchronously when the recorder arms (`rec-start`);
|
|
54
|
+
* `firstScreencastTimestampSec`/`firstTraceEventTsUs` cannot be — no frame or trace batch has
|
|
55
|
+
* necessarily arrived yet at that instant — so they are filled in opportunistically as the
|
|
56
|
+
* recorder's own screencast/tracing handlers observe the first occurrence of each, and stay
|
|
57
|
+
* `null` (with `baselinesPending: true`) until then. `rec-stop`'s response carries the same
|
|
58
|
+
* object read again at stop time — the flush path a caller uses to get the completed baselines
|
|
59
|
+
* for `markers.json` even when `rec-start`'s own response returned it still pending.
|
|
60
|
+
*/
|
|
61
|
+
export interface RecorderClockBaselines {
|
|
62
|
+
performanceNowMs: number;
|
|
63
|
+
wallClockMs: number;
|
|
64
|
+
/** First `Page.screencastFrame`'s `metadata.timestamp` (wall-clock seconds), or `null` if none had arrived yet at the moment this snapshot was taken. */
|
|
65
|
+
firstScreencastTimestampSec: number | null;
|
|
66
|
+
/** First `Tracing.dataCollected` batch's earliest event `ts` (trace-clock microseconds), or `null` if none had arrived yet at the moment this snapshot was taken. */
|
|
67
|
+
firstTraceEventTsUs: number | null;
|
|
68
|
+
/** `true` while either timestamp above is still `null`. */
|
|
69
|
+
baselinesPending: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Arms the recorder on its held tab connection: enables the motion-rec CDP
|
|
74
|
+
* domains, starts `Page.startScreencast` + `Tracing`, injects the
|
|
75
|
+
* Mutation/Resize/Performance observers, and captures the clock baseline.
|
|
76
|
+
* A recorder process only accepts one `rec-start` while `state==="idle"`.
|
|
77
|
+
*
|
|
78
|
+
* `nonce` (here and on every other recorder request shape) is the
|
|
79
|
+
* per-recording control-socket admission token: the recorder bridge process
|
|
80
|
+
* generates it at boot (64 lowercase hex chars, 256 bits) and hands it back
|
|
81
|
+
* to its starter over a private boot file; every request line must carry it
|
|
82
|
+
* verbatim, and the server constant-time-compares it BEFORE any handler
|
|
83
|
+
* runs. There is no unauthenticated request shape and no compatibility
|
|
84
|
+
* lane — a missing or mismatched nonce is answered `ok:false error:
|
|
85
|
+
* 'unauthorized'` with no side effects.
|
|
86
|
+
*/
|
|
87
|
+
export interface RecStartRequest {
|
|
88
|
+
reqId: number;
|
|
89
|
+
type: 'rec-start';
|
|
90
|
+
/** Per-recording control-socket admission token — see the doc comment above. */
|
|
91
|
+
nonce: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface RecStartResponseOk {
|
|
95
|
+
reqId: number;
|
|
96
|
+
ok: true;
|
|
97
|
+
type: 'rec-start';
|
|
98
|
+
/** Returned for the caller to persist into `markers.json` — the recorder itself does not write that file. */
|
|
99
|
+
markers: RecorderClockBaselines;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Stops screencast + tracing, flushes the injected observers, and tears
|
|
104
|
+
* down the recorder's subscriptions (not the socket — the caller closes
|
|
105
|
+
* the bridge process once it has read the response). Returns counts for
|
|
106
|
+
* the caller's `meta.json`; the recorder itself does not write that file.
|
|
107
|
+
*/
|
|
108
|
+
export interface RecStopRequest {
|
|
109
|
+
reqId: number;
|
|
110
|
+
type: 'rec-stop';
|
|
111
|
+
/** Per-recording control-socket admission token — see `RecStartRequest`. */
|
|
112
|
+
nonce: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface RecStopResponseOk {
|
|
116
|
+
reqId: number;
|
|
117
|
+
ok: true;
|
|
118
|
+
type: 'rec-stop';
|
|
119
|
+
frameCount: number;
|
|
120
|
+
eventCount: number;
|
|
121
|
+
durationMs: number;
|
|
122
|
+
/** The clock baselines re-read at stop time — the flush path for a `rec-start` response whose baselines were still pending. */
|
|
123
|
+
markers: RecorderClockBaselines;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* A CDP request routed through the recorder's held tab connection — the
|
|
128
|
+
* mechanism intervening session commands (`click`, `type`, `navigate`, ...)
|
|
129
|
+
* use during a composed recording. Setting `mark` brackets the dispatch
|
|
130
|
+
* with two performance.now() reads taken in the page's execution context
|
|
131
|
+
* (`../timing.ts`'s `withDocumentPerformanceNow`) and appends a labeled
|
|
132
|
+
* input-landmark record to `events.jsonl`, tying the action to the frame
|
|
133
|
+
* timeline — this is the "marked CDP request message" the protocol
|
|
134
|
+
* supports. Omit `mark` for a plain passthrough call (still observed
|
|
135
|
+
* natively by the recorder's own event subscriptions, just not logged as a
|
|
136
|
+
* distinct input landmark). Omit `method` for a wait-event-only request;
|
|
137
|
+
* the bridge arms a future-only wait on the recorder tab websocket's
|
|
138
|
+
* unscoped event envelope and returns the matched event in one response.
|
|
139
|
+
*/
|
|
140
|
+
export interface RecCdpDispatchRequest {
|
|
141
|
+
reqId: number;
|
|
142
|
+
type: 'cdp';
|
|
143
|
+
/** Per-recording control-socket admission token — see `RecStartRequest`. */
|
|
144
|
+
nonce: string;
|
|
145
|
+
method: string;
|
|
146
|
+
params?: Record<string, unknown>;
|
|
147
|
+
mark?: string;
|
|
148
|
+
waitEvent?: string;
|
|
149
|
+
timeoutMs?: number;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface RecCdpWaitEventRequest {
|
|
153
|
+
reqId: number;
|
|
154
|
+
type: 'cdp';
|
|
155
|
+
/** Per-recording control-socket admission token — see `RecStartRequest`. */
|
|
156
|
+
nonce: string;
|
|
157
|
+
method?: undefined;
|
|
158
|
+
waitEvent: string;
|
|
159
|
+
timeoutMs?: number;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export type RecCdpRequest = RecCdpDispatchRequest | RecCdpWaitEventRequest;
|
|
163
|
+
|
|
164
|
+
export interface RecCdpResponseOk {
|
|
165
|
+
reqId: number;
|
|
166
|
+
ok: true;
|
|
167
|
+
type: 'cdp';
|
|
168
|
+
result?: unknown;
|
|
169
|
+
event?: unknown;
|
|
170
|
+
/**
|
|
171
|
+
* Present iff this request armed a `waitEvent` AND carried a `method`. It
|
|
172
|
+
* reports the load-wait outcome SEPARATELY from the method result: a method
|
|
173
|
+
* dispatch that succeeds is always `ok:true` with its `result` preserved,
|
|
174
|
+
* regardless of whether the paired wait observed its event
|
|
175
|
+
* (`'observed'`, with `event` set) or the wait's own bounded timer elapsed
|
|
176
|
+
* first (`'bounded-timeout'`, no `event`). A wait-event-ONLY request (no
|
|
177
|
+
* `method`) whose wait times out is still `ok:false` — that path has no
|
|
178
|
+
* method result to preserve, so its timeout is a genuine failure.
|
|
179
|
+
*/
|
|
180
|
+
waitOutcome?: 'observed' | 'bounded-timeout';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* The action-return HAR flush/health barrier. A recorder-routed page action
|
|
185
|
+
* (`click`, `type`, `scroll`, `exec`, `navigate`) sends this after its settle
|
|
186
|
+
* window and before claiming success, so every HAR entry/body/append the
|
|
187
|
+
* recorder's streaming collector had already completed by then is durably in
|
|
188
|
+
* the owning session's live HAR first. Deliberately NON-attributing: the
|
|
189
|
+
* response reports store health only — `ok:true` means "all work completed at
|
|
190
|
+
* request time has appended and no fatal store error is latched"; it never
|
|
191
|
+
* says which entries belong to which action and carries no counts (the
|
|
192
|
+
* collector cannot truthfully attribute concurrent traffic to one action).
|
|
193
|
+
* Once the terminal `rec-stop` latch is claimed, HAR finalization belongs to
|
|
194
|
+
* that one owner and a later `har-flush` is answered with a deterministic
|
|
195
|
+
* `ok:false` rejection instead of sharing or re-driving the drain.
|
|
196
|
+
*/
|
|
197
|
+
export interface RecHarFlushRequest {
|
|
198
|
+
reqId: number;
|
|
199
|
+
type: 'har-flush';
|
|
200
|
+
/** Per-recording control-socket admission token — see `RecStartRequest`. */
|
|
201
|
+
nonce: string;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Health-only: success carries no result fields beyond the envelope — see `RecHarFlushRequest`. */
|
|
205
|
+
export interface RecHarFlushResponseOk {
|
|
206
|
+
reqId: number;
|
|
207
|
+
ok: true;
|
|
208
|
+
type: 'har-flush';
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export type RecorderRequest = RecStartRequest | RecStopRequest | RecCdpRequest | RecHarFlushRequest;
|
|
212
|
+
|
|
213
|
+
export interface RecorderResponseErr {
|
|
214
|
+
reqId: number;
|
|
215
|
+
ok: false;
|
|
216
|
+
type: RecorderRequest['type'];
|
|
217
|
+
error: string;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export type RecStartResponse = RecStartResponseOk | RecorderResponseErr;
|
|
221
|
+
export type RecStopResponse = RecStopResponseOk | RecorderResponseErr;
|
|
222
|
+
export type RecCdpResponse = RecCdpResponseOk | RecorderResponseErr;
|
|
223
|
+
export type RecHarFlushResponse = RecHarFlushResponseOk | RecorderResponseErr;
|
|
224
|
+
export type RecorderResponse = RecStartResponse | RecStopResponse | RecCdpResponse | RecHarFlushResponse;
|