@vforsh/argus-watcher 0.5.3 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/cdp/ffmpeg.d.ts +22 -6
- package/dist/cdp/ffmpeg.d.ts.map +1 -1
- package/dist/cdp/ffmpeg.js +90 -30
- package/dist/cdp/ffmpeg.js.map +1 -1
- package/dist/cdp/navigation.d.ts +82 -0
- package/dist/cdp/navigation.d.ts.map +1 -0
- package/dist/cdp/navigation.js +186 -0
- package/dist/cdp/navigation.js.map +1 -0
- package/dist/cdp/protocol.d.ts +24 -0
- package/dist/cdp/protocol.d.ts.map +1 -1
- package/dist/cdp/recording.d.ts +3 -1
- package/dist/cdp/recording.d.ts.map +1 -1
- package/dist/cdp/recording.js +237 -132
- package/dist/cdp/recording.js.map +1 -1
- package/dist/cdp/recordingSession.d.ts +97 -0
- package/dist/cdp/recordingSession.d.ts.map +1 -0
- package/dist/cdp/recordingSession.js +157 -0
- package/dist/cdp/recordingSession.js.map +1 -0
- package/dist/cdp/scenarioBridge.d.ts +2 -0
- package/dist/cdp/scenarioBridge.d.ts.map +1 -1
- package/dist/cdp/scenarioBridge.js +49 -3
- package/dist/cdp/scenarioBridge.js.map +1 -1
- package/dist/cdp/screenshot.d.ts.map +1 -1
- package/dist/cdp/screenshot.js +3 -2
- package/dist/cdp/screenshot.js.map +1 -1
- package/dist/cdp/visualCapture.d.ts +20 -0
- package/dist/cdp/visualCapture.d.ts.map +1 -1
- package/dist/cdp/visualCapture.js +53 -42
- package/dist/cdp/visualCapture.js.map +1 -1
- package/dist/cdp/watcher.d.ts.map +1 -1
- package/dist/cdp/watcher.js +55 -1
- package/dist/cdp/watcher.js.map +1 -1
- package/dist/http/endpoints.d.ts +1 -1
- package/dist/http/endpoints.d.ts.map +1 -1
- package/dist/http/endpoints.js +3 -0
- package/dist/http/endpoints.js.map +1 -1
- package/dist/http/routes/getStatus.js +1 -0
- package/dist/http/routes/getStatus.js.map +1 -1
- package/dist/http/routes/index.d.ts.map +1 -1
- package/dist/http/routes/index.js +4 -0
- package/dist/http/routes/index.js.map +1 -1
- package/dist/http/routes/postDomClick.d.ts.map +1 -1
- package/dist/http/routes/postDomClick.js +15 -9
- package/dist/http/routes/postDomClick.js.map +1 -1
- package/dist/http/routes/postDomKeydown.d.ts.map +1 -1
- package/dist/http/routes/postDomKeydown.js +4 -2
- package/dist/http/routes/postDomKeydown.js.map +1 -1
- package/dist/http/routes/postEval.d.ts.map +1 -1
- package/dist/http/routes/postEval.js +1 -0
- package/dist/http/routes/postEval.js.map +1 -1
- package/dist/http/routes/postNavigate.d.ts +12 -0
- package/dist/http/routes/postNavigate.d.ts.map +1 -0
- package/dist/http/routes/postNavigate.js +51 -0
- package/dist/http/routes/postNavigate.js.map +1 -0
- package/dist/http/routes/postNavigateHistory.d.ts +9 -0
- package/dist/http/routes/postNavigateHistory.d.ts.map +1 -0
- package/dist/http/routes/postNavigateHistory.js +35 -0
- package/dist/http/routes/postNavigateHistory.js.map +1 -0
- package/dist/http/routes/postRecord.d.ts.map +1 -1
- package/dist/http/routes/postRecord.js +9 -0
- package/dist/http/routes/postRecord.js.map +1 -1
- package/dist/internal.d.ts +1 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +1 -0
- package/dist/internal.js.map +1 -1
- package/dist/sources/extension-session-events.d.ts.map +1 -1
- package/dist/sources/extension-session-events.js +21 -0
- package/dist/sources/extension-session-events.js.map +1 -1
- package/package.json +1 -1
package/dist/cdp/ffmpeg.d.ts
CHANGED
|
@@ -4,16 +4,19 @@ import type { RecordFormat } from '@vforsh/argus-core';
|
|
|
4
4
|
import type { VisualCaptureClip, VisualCaptureViewport } from './visualCapture.js';
|
|
5
5
|
/**
|
|
6
6
|
* The ffmpeg side of screen recording: spawning the encoder, building its arguments, and reading
|
|
7
|
-
* the
|
|
7
|
+
* the frame headers that size its filter graph.
|
|
8
8
|
*
|
|
9
9
|
* Split out of `recording.ts` because none of it touches CDP — the recorder pipes frames into a
|
|
10
10
|
* child process, and that process's argument construction, stderr ring, and ENOENT phrasing are a
|
|
11
11
|
* separate concern from screencast frame pacing.
|
|
12
12
|
*/
|
|
13
|
-
/**
|
|
14
|
-
export type
|
|
13
|
+
/** Wire format of the screencast frames piped into ffmpeg. */
|
|
14
|
+
export type FrameCodec = 'png' | 'jpeg';
|
|
15
|
+
/** Dimensions and encoding read from a screencast frame. */
|
|
16
|
+
export type FrameInfo = {
|
|
15
17
|
width: number;
|
|
16
18
|
height: number;
|
|
19
|
+
codec: FrameCodec;
|
|
17
20
|
};
|
|
18
21
|
type FfmpegChild = ChildProcessByStdio<Writable, null, Readable>;
|
|
19
22
|
/** A running ffmpeg encoder and a promise for its exit. */
|
|
@@ -22,16 +25,29 @@ export type FfmpegProcess = {
|
|
|
22
25
|
/** Resolves on a clean exit; rejects with the tail of stderr otherwise. */
|
|
23
26
|
completion: Promise<void>;
|
|
24
27
|
};
|
|
25
|
-
/** Spawn ffmpeg to encode a
|
|
28
|
+
/** Spawn ffmpeg to encode a screencast frame stream, resolving once the child has actually spawned. */
|
|
26
29
|
export declare const startFfmpeg: (options: {
|
|
27
30
|
absolutePath: string;
|
|
28
31
|
fps: number;
|
|
29
32
|
format: RecordFormat;
|
|
30
33
|
clip: VisualCaptureClip | undefined;
|
|
31
34
|
viewport: VisualCaptureViewport | undefined;
|
|
32
|
-
|
|
35
|
+
frame: FrameInfo;
|
|
33
36
|
}) => Promise<FfmpegProcess>;
|
|
34
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Read the encoding and pixel dimensions of a screencast frame.
|
|
39
|
+
*
|
|
40
|
+
* The codec is *detected*, never assumed from the `Page.startScreencast` request: ffmpeg is told
|
|
41
|
+
* up front which decoder sits on the pipe, and if that disagrees with the bytes it gets, every
|
|
42
|
+
* frame fails to decode and the encode dies with an empty file. Chrome is not the only producer
|
|
43
|
+
* here — test fakes and future capture paths feed this too — so the frames are the authority.
|
|
44
|
+
*
|
|
45
|
+
* The size matters because a CSS-pixel crop rectangle has to be translated into encoder
|
|
46
|
+
* coordinates, and the screencast reports no device pixel ratio worth trusting across DPR changes.
|
|
47
|
+
*
|
|
48
|
+
* @throws {Error} When the buffer is not a recognizable PNG or JPEG.
|
|
49
|
+
*/
|
|
50
|
+
export declare const readFrameInfo: (buffer: Buffer) => FrameInfo;
|
|
35
51
|
export declare const formatFfmpegError: (error: unknown) => Error;
|
|
36
52
|
export {};
|
|
37
53
|
//# sourceMappingURL=ffmpeg.d.ts.map
|
package/dist/cdp/ffmpeg.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ffmpeg.d.ts","sourceRoot":"","sources":["../../src/cdp/ffmpeg.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AACpE,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAElF;;;;;;;GAOG;AAEH,
|
|
1
|
+
{"version":3,"file":"ffmpeg.d.ts","sourceRoot":"","sources":["../../src/cdp/ffmpeg.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AACpE,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAElF;;;;;;;GAOG;AAEH,8DAA8D;AAC9D,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,CAAA;AAEvC,4DAA4D;AAC5D,MAAM,MAAM,SAAS,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,CAAA;AAE5E,KAAK,WAAW,GAAG,mBAAmB,CAAC,QAAQ,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAA;AAEhE,2DAA2D;AAC3D,MAAM,MAAM,aAAa,GAAG;IAC3B,KAAK,EAAE,WAAW,CAAA;IAClB,2EAA2E;IAC3E,UAAU,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;CACzB,CAAA;AAED,uGAAuG;AACvG,eAAO,MAAM,WAAW,GAAU,SAAS;IAC1C,YAAY,EAAE,MAAM,CAAA;IACpB,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,YAAY,CAAA;IACpB,IAAI,EAAE,iBAAiB,GAAG,SAAS,CAAA;IACnC,QAAQ,EAAE,qBAAqB,GAAG,SAAS,CAAA;IAC3C,KAAK,EAAE,SAAS,CAAA;CAChB,KAAG,OAAO,CAAC,aAAa,CA8CxB,CAAA;AAqDD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,aAAa,GAAI,QAAQ,MAAM,KAAG,SAW9C,CAAA;AAsDD,eAAO,MAAM,iBAAiB,GAAI,OAAO,OAAO,KAAG,KAMlD,CAAA"}
|
package/dist/cdp/ffmpeg.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
/** Spawn ffmpeg to encode a
|
|
2
|
+
/** Spawn ffmpeg to encode a screencast frame stream, resolving once the child has actually spawned. */
|
|
3
3
|
export const startFfmpeg = async (options) => {
|
|
4
4
|
const ffmpeg = process.env.ARGUS_FFMPEG?.trim() || 'ffmpeg';
|
|
5
|
-
const filter = buildVideoFilter(options);
|
|
6
5
|
const args = [
|
|
7
6
|
'-hide_banner',
|
|
8
7
|
'-loglevel',
|
|
@@ -12,12 +11,12 @@ export const startFfmpeg = async (options) => {
|
|
|
12
11
|
'-framerate',
|
|
13
12
|
String(options.fps),
|
|
14
13
|
'-vcodec',
|
|
15
|
-
'png',
|
|
14
|
+
options.frame.codec === 'jpeg' ? 'mjpeg' : 'png',
|
|
16
15
|
'-i',
|
|
17
16
|
'pipe:0',
|
|
18
17
|
'-an',
|
|
19
18
|
'-vf',
|
|
20
|
-
|
|
19
|
+
buildVideoFilter(options),
|
|
21
20
|
...buildEncoderArgs(options.format),
|
|
22
21
|
'-y',
|
|
23
22
|
options.absolutePath,
|
|
@@ -46,43 +45,104 @@ export const startFfmpeg = async (options) => {
|
|
|
46
45
|
return { child, completion };
|
|
47
46
|
};
|
|
48
47
|
const buildVideoFilter = (options) => {
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
const geometry = options.clip ? buildCropFilter(options.clip, options.viewport, options.frame) : 'scale=trunc(iw/2)*2:trunc(ih/2)*2';
|
|
49
|
+
if (options.format !== 'gif') {
|
|
50
|
+
return `${geometry},format=yuv420p`;
|
|
51
51
|
}
|
|
52
|
-
|
|
52
|
+
/**
|
|
53
|
+
* One-pass palette generation, because the frames arrive on a pipe.
|
|
54
|
+
*
|
|
55
|
+
* The usual high-quality GIF recipe runs `palettegen` over the whole file and only then
|
|
56
|
+
* `paletteuse`, which needs two passes over a seekable input. A live screencast has neither,
|
|
57
|
+
* so this builds a palette per frame (`stats_mode=single`) and tells `paletteuse` to expect a
|
|
58
|
+
* new one each time (`new=1`). Slightly larger files than a global palette, no second pass,
|
|
59
|
+
* and no banding on the gradients that make up most app UI.
|
|
60
|
+
*/
|
|
61
|
+
return `${geometry},split[gifsrc][gifpal];[gifpal]palettegen=stats_mode=single[pal];[gifsrc][pal]paletteuse=new=1:dither=sierra2_4a`;
|
|
62
|
+
};
|
|
63
|
+
const buildCropFilter = (clip, viewport, frameSize) => {
|
|
64
|
+
if (!viewport) {
|
|
53
65
|
throw new Error('Unable to compute viewport size for recording crop');
|
|
54
66
|
}
|
|
55
|
-
const ratioX =
|
|
56
|
-
const ratioY =
|
|
57
|
-
const x = clampEven(
|
|
58
|
-
const y = clampEven(
|
|
59
|
-
const width = clampEven(
|
|
60
|
-
const height = clampEven(
|
|
61
|
-
return `crop=${width}:${height}:${x}:${y}
|
|
67
|
+
const ratioX = frameSize.width / viewport.width;
|
|
68
|
+
const ratioY = frameSize.height / viewport.height;
|
|
69
|
+
const x = clampEven(clip.x * ratioX, 0, frameSize.width - 2);
|
|
70
|
+
const y = clampEven(clip.y * ratioY, 0, frameSize.height - 2);
|
|
71
|
+
const width = clampEven(clip.width * ratioX, 2, frameSize.width - x);
|
|
72
|
+
const height = clampEven(clip.height * ratioY, 2, frameSize.height - y);
|
|
73
|
+
return `crop=${width}:${height}:${x}:${y}`;
|
|
62
74
|
};
|
|
63
75
|
const buildEncoderArgs = (format) => {
|
|
76
|
+
if (format === 'gif') {
|
|
77
|
+
return ['-loop', '0', '-f', 'gif'];
|
|
78
|
+
}
|
|
64
79
|
if (format === 'webm') {
|
|
65
|
-
|
|
80
|
+
// VP9 at constant quality: the old flat 1M VP8 bitrate smeared any 2x-DPR canvas.
|
|
81
|
+
return ['-c:v', 'libvpx-vp9', '-deadline', 'realtime', '-cpu-used', '5', '-crf', '32', '-b:v', '0', '-row-mt', '1', '-f', 'webm'];
|
|
66
82
|
}
|
|
67
83
|
// yuv420p + H.264 keeps files playable in Finder, QuickTime, iOS Photos, Slack, and browsers.
|
|
68
84
|
return ['-c:v', 'libx264', '-preset', 'veryfast', '-crf', '23', '-movflags', '+faststart', '-f', 'mp4'];
|
|
69
85
|
};
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Read the encoding and pixel dimensions of a screencast frame.
|
|
88
|
+
*
|
|
89
|
+
* The codec is *detected*, never assumed from the `Page.startScreencast` request: ffmpeg is told
|
|
90
|
+
* up front which decoder sits on the pipe, and if that disagrees with the bytes it gets, every
|
|
91
|
+
* frame fails to decode and the encode dies with an empty file. Chrome is not the only producer
|
|
92
|
+
* here — test fakes and future capture paths feed this too — so the frames are the authority.
|
|
93
|
+
*
|
|
94
|
+
* The size matters because a CSS-pixel crop rectangle has to be translated into encoder
|
|
95
|
+
* coordinates, and the screencast reports no device pixel ratio worth trusting across DPR changes.
|
|
96
|
+
*
|
|
97
|
+
* @throws {Error} When the buffer is not a recognizable PNG or JPEG.
|
|
98
|
+
*/
|
|
99
|
+
export const readFrameInfo = (buffer) => {
|
|
100
|
+
if (isPng(buffer)) {
|
|
101
|
+
return { width: buffer.readUInt32BE(16), height: buffer.readUInt32BE(20), codec: 'png' };
|
|
102
|
+
}
|
|
103
|
+
const jpegSize = readJpegSize(buffer);
|
|
104
|
+
if (jpegSize) {
|
|
105
|
+
return { ...jpegSize, codec: 'jpeg' };
|
|
106
|
+
}
|
|
107
|
+
throw new Error('Expected a PNG or JPEG screencast frame');
|
|
108
|
+
};
|
|
109
|
+
const isPng = (buffer) => buffer.length >= 24 &&
|
|
110
|
+
buffer[0] === 0x89 &&
|
|
111
|
+
buffer[1] === 0x50 &&
|
|
112
|
+
buffer[2] === 0x4e &&
|
|
113
|
+
buffer[3] === 0x47 &&
|
|
114
|
+
buffer[4] === 0x0d &&
|
|
115
|
+
buffer[5] === 0x0a &&
|
|
116
|
+
buffer[6] === 0x1a &&
|
|
117
|
+
buffer[7] === 0x0a;
|
|
118
|
+
/** Frame-header markers (SOF0-SOF15) that carry dimensions, minus the four that are not frame headers. */
|
|
119
|
+
const JPEG_NON_SOF_MARKERS = new Set([0xc4, 0xc8, 0xcc]);
|
|
120
|
+
const readJpegSize = (buffer) => {
|
|
121
|
+
if (buffer.length < 4 || buffer[0] !== 0xff || buffer[1] !== 0xd8) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
let offset = 2;
|
|
125
|
+
while (offset + 3 < buffer.length) {
|
|
126
|
+
if (buffer[offset] !== 0xff) {
|
|
127
|
+
offset += 1;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const marker = buffer[offset + 1];
|
|
131
|
+
// Padding fill bytes and the standalone markers carry no length field.
|
|
132
|
+
if (marker === 0xff || marker === 0x01 || (marker >= 0xd0 && marker <= 0xd9)) {
|
|
133
|
+
offset += 2;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const segmentLength = buffer.readUInt16BE(offset + 2);
|
|
137
|
+
if (marker >= 0xc0 && marker <= 0xcf && !JPEG_NON_SOF_MARKERS.has(marker)) {
|
|
138
|
+
if (offset + 9 > buffer.length) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
return { width: buffer.readUInt16BE(offset + 7), height: buffer.readUInt16BE(offset + 5) };
|
|
142
|
+
}
|
|
143
|
+
offset += 2 + segmentLength;
|
|
81
144
|
}
|
|
82
|
-
return
|
|
83
|
-
width: buffer.readUInt32BE(16),
|
|
84
|
-
height: buffer.readUInt32BE(20),
|
|
85
|
-
};
|
|
145
|
+
return null;
|
|
86
146
|
};
|
|
87
147
|
const clampEven = (value, min, max) => {
|
|
88
148
|
const clamped = Math.min(max, Math.max(min, Math.floor(value)));
|
package/dist/cdp/ffmpeg.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ffmpeg.js","sourceRoot":"","sources":["../../src/cdp/ffmpeg.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAA4B,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"ffmpeg.js","sourceRoot":"","sources":["../../src/cdp/ffmpeg.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { type LogEpoch, type NavigationDirection, type NavigationSummary, type NavigationWait, type NavigationWaitOptions } from '@vforsh/argus-core';
|
|
2
|
+
import type { CdpSessionHandle } from './connection.js';
|
|
3
|
+
/**
|
|
4
|
+
* Waiting for a navigation to finish, for every command that causes one.
|
|
5
|
+
*
|
|
6
|
+
* The hard part is not sending `Page.navigate` — it is knowing when the answer is safe to
|
|
7
|
+
* send back. A load event on its own proves nothing: it may belong to the document that was
|
|
8
|
+
* already there when the command was issued. So a wait only counts a `domContentEventFired`
|
|
9
|
+
* or `loadEventFired` that arrives *after* the top-frame `Page.frameNavigated`, and — when
|
|
10
|
+
* Chrome told us which loader it started — for that loader.
|
|
11
|
+
*
|
|
12
|
+
* Handlers are attached before the navigating command is sent, never after, so a fast
|
|
13
|
+
* same-origin load cannot slip through the gap between the send and the subscribe.
|
|
14
|
+
*/
|
|
15
|
+
/** A primed navigation wait. Always `dispose()` it, including on the failure path. */
|
|
16
|
+
export type NavigationWaiter = {
|
|
17
|
+
/**
|
|
18
|
+
* Wait for the requested phase, resolving to the URL navigated to, or `null` when nothing
|
|
19
|
+
* navigated (which is what `wait: 'none'` always reports).
|
|
20
|
+
*
|
|
21
|
+
* @param expectedLoaderId Loader id from the navigating command, when it reported one. A load
|
|
22
|
+
* event for a different loader (a redirect chain's earlier hop, a concurrent reload) is ignored.
|
|
23
|
+
* @throws A `navigation_timeout` coded error when the phase does not arrive in time.
|
|
24
|
+
*/
|
|
25
|
+
settle: (expectedLoaderId?: string | null) => Promise<string | null>;
|
|
26
|
+
dispose: () => void;
|
|
27
|
+
};
|
|
28
|
+
/** Subscribe to the navigation lifecycle before issuing the command that triggers it. */
|
|
29
|
+
export declare const armNavigationWaiter: (session: CdpSessionHandle, options: {
|
|
30
|
+
wait: NavigationWait;
|
|
31
|
+
timeoutMs: number;
|
|
32
|
+
}) => NavigationWaiter;
|
|
33
|
+
/** Result of a completed `Page.navigate`. */
|
|
34
|
+
export type NavigatePageResult = {
|
|
35
|
+
url: string;
|
|
36
|
+
loaderId: string | null;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Navigate the page and wait for the requested phase.
|
|
40
|
+
*
|
|
41
|
+
* @throws A `navigation_failed` coded error when Chrome refuses the URL (bad host, blocked
|
|
42
|
+
* scheme); `navigation_timeout` when the load does not complete in time. Both are the
|
|
43
|
+
* request's fault or the network's, so they are reported as such rather than as CDP errors.
|
|
44
|
+
*/
|
|
45
|
+
export declare const navigatePage: (session: CdpSessionHandle, options: {
|
|
46
|
+
url: string;
|
|
47
|
+
wait: NavigationWait;
|
|
48
|
+
timeoutMs: number;
|
|
49
|
+
}) => Promise<NavigatePageResult>;
|
|
50
|
+
/** Result of a completed history navigation. */
|
|
51
|
+
export type NavigateHistoryResult = {
|
|
52
|
+
url: string;
|
|
53
|
+
index: number;
|
|
54
|
+
length: number;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Move through the session history and wait for the requested phase.
|
|
58
|
+
*
|
|
59
|
+
* @throws A `no_history` coded error when the requested step falls outside the history, which
|
|
60
|
+
* is what "already at the first entry" looks like — not a transport failure.
|
|
61
|
+
*/
|
|
62
|
+
export declare const navigateHistory: (session: CdpSessionHandle, options: {
|
|
63
|
+
direction: NavigationDirection;
|
|
64
|
+
steps: number;
|
|
65
|
+
wait: NavigationWait;
|
|
66
|
+
timeoutMs: number;
|
|
67
|
+
}) => Promise<NavigateHistoryResult>;
|
|
68
|
+
/**
|
|
69
|
+
* Run an interaction that may navigate, and report whether it did.
|
|
70
|
+
*
|
|
71
|
+
* A click on a plain button navigates nowhere, and that is the common case — so a timeout here
|
|
72
|
+
* is a `navigated: false` summary, not an error. Only a **top-frame** navigation counts: an
|
|
73
|
+
* iframe that navigates internally reports `navigated: false`.
|
|
74
|
+
*
|
|
75
|
+
* @param beginEpoch Opens a log epoch just before the interaction, so the caller can read the
|
|
76
|
+
* new page's console output without racing it. Only called when a wait was requested.
|
|
77
|
+
*/
|
|
78
|
+
export declare const withNavigationWait: <T>(session: CdpSessionHandle, options: NavigationWaitOptions, beginEpoch: () => LogEpoch, act: () => Promise<T>) => Promise<{
|
|
79
|
+
result: T;
|
|
80
|
+
navigation?: NavigationSummary;
|
|
81
|
+
}>;
|
|
82
|
+
//# sourceMappingURL=navigation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../src/cdp/navigation.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,KAAK,QAAQ,EACb,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,MAAM,oBAAoB,CAAA;AAC3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAGvD;;;;;;;;;;;GAWG;AAEH,sFAAsF;AACtF,MAAM,MAAM,gBAAgB,GAAG;IAC9B;;;;;;;OAOG;IACH,MAAM,EAAE,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACpE,OAAO,EAAE,MAAM,IAAI,CAAA;CACnB,CAAA;AAED,yFAAyF;AACzF,eAAO,MAAM,mBAAmB,GAAI,SAAS,gBAAgB,EAAE,SAAS;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,KAAG,gBA2FrH,CAAA;AAED,6CAA6C;AAC7C,MAAM,MAAM,kBAAkB,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAA;AAEzE;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,GACxB,SAAS,gBAAgB,EACzB,SAAS;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,cAAc,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,KAC/D,OAAO,CAAC,kBAAkB,CAc5B,CAAA;AAED,gDAAgD;AAChD,MAAM,MAAM,qBAAqB,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAElF;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAC3B,SAAS,gBAAgB,EACzB,SAAS;IAAE,SAAS,EAAE,mBAAmB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,cAAc,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,KACjG,OAAO,CAAC,qBAAqB,CAuB/B,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,GAAU,CAAC,EACzC,SAAS,gBAAgB,EACzB,SAAS,qBAAqB,EAC9B,YAAY,MAAM,QAAQ,EAC1B,KAAK,MAAM,OAAO,CAAC,CAAC,CAAC,KACnB,OAAO,CAAC;IAAE,MAAM,EAAE,CAAC,CAAC;IAAC,UAAU,CAAC,EAAE,iBAAiB,CAAA;CAAE,CAiBvD,CAAA"}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { DEFAULT_INTERACTION_NAV_TIMEOUT_MS, } from '@vforsh/argus-core';
|
|
2
|
+
import { codedError, hasErrorCode } from '../errors.js';
|
|
3
|
+
/** Subscribe to the navigation lifecycle before issuing the command that triggers it. */
|
|
4
|
+
export const armNavigationWaiter = (session, options) => {
|
|
5
|
+
let topFrameId = null;
|
|
6
|
+
let navigatedUrl = null;
|
|
7
|
+
let navigatedLoaderId = null;
|
|
8
|
+
let domFired = false;
|
|
9
|
+
let loadFired = false;
|
|
10
|
+
// Set when the navigation completed but no dom/load event will ever follow it: a hash change
|
|
11
|
+
// or pushState (same document), and a back/forward-cache restore (the document is resumed,
|
|
12
|
+
// not parsed). Waiting for `load` in either case would burn the whole timeout on a page that
|
|
13
|
+
// is already there.
|
|
14
|
+
let settledWithoutLoadUrl = null;
|
|
15
|
+
let notify = null;
|
|
16
|
+
// The top frame's id, so a same-document navigation inside an iframe cannot settle the wait.
|
|
17
|
+
// Fire-and-forget: a detached session fails the navigating command with its own error.
|
|
18
|
+
void session
|
|
19
|
+
.sendAndWait('Page.getFrameTree')
|
|
20
|
+
.then((result) => {
|
|
21
|
+
topFrameId = result.frameTree?.frame.id ?? null;
|
|
22
|
+
})
|
|
23
|
+
.catch(() => { });
|
|
24
|
+
const isSettled = (expectedLoaderId) => {
|
|
25
|
+
if (settledWithoutLoadUrl != null)
|
|
26
|
+
return true;
|
|
27
|
+
if (navigatedUrl == null)
|
|
28
|
+
return false;
|
|
29
|
+
if (expectedLoaderId != null && navigatedLoaderId != null && navigatedLoaderId !== expectedLoaderId)
|
|
30
|
+
return false;
|
|
31
|
+
return options.wait === 'domcontentloaded' ? domFired || loadFired : loadFired;
|
|
32
|
+
};
|
|
33
|
+
const removers = [
|
|
34
|
+
// A child target's events carry a sessionId; only the top frame's do not.
|
|
35
|
+
session.onEvent('Page.frameNavigated', (params, meta) => {
|
|
36
|
+
const frame = params.frame;
|
|
37
|
+
if (meta.sessionId || !frame || frame.parentId)
|
|
38
|
+
return;
|
|
39
|
+
topFrameId = frame.id;
|
|
40
|
+
navigatedUrl = frame.url ?? '';
|
|
41
|
+
navigatedLoaderId = frame.loaderId ?? null;
|
|
42
|
+
domFired = false;
|
|
43
|
+
loadFired = false;
|
|
44
|
+
settledWithoutLoadUrl = params.type === 'BackForwardCacheRestore' ? navigatedUrl : null;
|
|
45
|
+
notify?.();
|
|
46
|
+
}),
|
|
47
|
+
session.onEvent('Page.domContentEventFired', (_params, meta) => {
|
|
48
|
+
if (meta.sessionId)
|
|
49
|
+
return;
|
|
50
|
+
domFired = true;
|
|
51
|
+
notify?.();
|
|
52
|
+
}),
|
|
53
|
+
session.onEvent('Page.loadEventFired', (_params, meta) => {
|
|
54
|
+
if (meta.sessionId)
|
|
55
|
+
return;
|
|
56
|
+
loadFired = true;
|
|
57
|
+
notify?.();
|
|
58
|
+
}),
|
|
59
|
+
session.onEvent('Page.navigatedWithinDocument', (params, meta) => {
|
|
60
|
+
if (meta.sessionId || !params.url)
|
|
61
|
+
return;
|
|
62
|
+
if (topFrameId != null && params.frameId !== topFrameId)
|
|
63
|
+
return;
|
|
64
|
+
settledWithoutLoadUrl = params.url;
|
|
65
|
+
notify?.();
|
|
66
|
+
}),
|
|
67
|
+
];
|
|
68
|
+
let timer = null;
|
|
69
|
+
const dispose = () => {
|
|
70
|
+
for (const remove of removers)
|
|
71
|
+
remove();
|
|
72
|
+
if (timer)
|
|
73
|
+
clearTimeout(timer);
|
|
74
|
+
timer = null;
|
|
75
|
+
notify = null;
|
|
76
|
+
};
|
|
77
|
+
const settle = async (expectedLoaderId = null) => {
|
|
78
|
+
if (options.wait === 'none') {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
if (!isSettled(expectedLoaderId)) {
|
|
82
|
+
await new Promise((resolve, reject) => {
|
|
83
|
+
notify = () => {
|
|
84
|
+
if (!isSettled(expectedLoaderId))
|
|
85
|
+
return;
|
|
86
|
+
notify = null;
|
|
87
|
+
resolve();
|
|
88
|
+
};
|
|
89
|
+
timer = setTimeout(() => {
|
|
90
|
+
notify = null;
|
|
91
|
+
reject(codedError('navigation_timeout', `Navigation did not reach "${options.wait}" within ${options.timeoutMs}ms.`));
|
|
92
|
+
}, options.timeoutMs);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return settledWithoutLoadUrl ?? navigatedUrl;
|
|
96
|
+
};
|
|
97
|
+
return { settle, dispose };
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Navigate the page and wait for the requested phase.
|
|
101
|
+
*
|
|
102
|
+
* @throws A `navigation_failed` coded error when Chrome refuses the URL (bad host, blocked
|
|
103
|
+
* scheme); `navigation_timeout` when the load does not complete in time. Both are the
|
|
104
|
+
* request's fault or the network's, so they are reported as such rather than as CDP errors.
|
|
105
|
+
*/
|
|
106
|
+
export const navigatePage = async (session, options) => {
|
|
107
|
+
const waiter = armNavigationWaiter(session, options);
|
|
108
|
+
try {
|
|
109
|
+
const result = await session.sendAndWait('Page.navigate', { url: options.url });
|
|
110
|
+
if (result.errorText) {
|
|
111
|
+
throw codedError('navigation_failed', `Navigation to ${options.url} failed: ${result.errorText}`);
|
|
112
|
+
}
|
|
113
|
+
const loaderId = result.loaderId ?? null;
|
|
114
|
+
const settledUrl = await waiter.settle(loaderId);
|
|
115
|
+
return { url: settledUrl ?? options.url, loaderId };
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
waiter.dispose();
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Move through the session history and wait for the requested phase.
|
|
123
|
+
*
|
|
124
|
+
* @throws A `no_history` coded error when the requested step falls outside the history, which
|
|
125
|
+
* is what "already at the first entry" looks like — not a transport failure.
|
|
126
|
+
*/
|
|
127
|
+
export const navigateHistory = async (session, options) => {
|
|
128
|
+
const history = await session.sendAndWait('Page.getNavigationHistory');
|
|
129
|
+
const entries = history.entries ?? [];
|
|
130
|
+
const currentIndex = history.currentIndex ?? 0;
|
|
131
|
+
const targetIndex = currentIndex + (options.direction === 'back' ? -options.steps : options.steps);
|
|
132
|
+
const entry = entries[targetIndex];
|
|
133
|
+
if (!entry) {
|
|
134
|
+
const step = options.steps === 1 ? '' : ` ${options.steps} entries`;
|
|
135
|
+
throw codedError('no_history', `Cannot go ${options.direction}${step}: at entry ${currentIndex + 1} of ${entries.length} in the session history.`);
|
|
136
|
+
}
|
|
137
|
+
const waiter = armNavigationWaiter(session, options);
|
|
138
|
+
try {
|
|
139
|
+
await session.sendAndWait('Page.navigateToHistoryEntry', { entryId: entry.id });
|
|
140
|
+
const settledUrl = await waiter.settle();
|
|
141
|
+
return { url: settledUrl ?? entry.url, index: targetIndex, length: entries.length };
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
waiter.dispose();
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Run an interaction that may navigate, and report whether it did.
|
|
149
|
+
*
|
|
150
|
+
* A click on a plain button navigates nowhere, and that is the common case — so a timeout here
|
|
151
|
+
* is a `navigated: false` summary, not an error. Only a **top-frame** navigation counts: an
|
|
152
|
+
* iframe that navigates internally reports `navigated: false`.
|
|
153
|
+
*
|
|
154
|
+
* @param beginEpoch Opens a log epoch just before the interaction, so the caller can read the
|
|
155
|
+
* new page's console output without racing it. Only called when a wait was requested.
|
|
156
|
+
*/
|
|
157
|
+
export const withNavigationWait = async (session, options, beginEpoch, act) => {
|
|
158
|
+
if (!options.waitNav) {
|
|
159
|
+
return { result: await act() };
|
|
160
|
+
}
|
|
161
|
+
const epoch = beginEpoch();
|
|
162
|
+
const waiter = armNavigationWaiter(session, {
|
|
163
|
+
wait: options.waitNav,
|
|
164
|
+
timeoutMs: options.navTimeoutMs ?? DEFAULT_INTERACTION_NAV_TIMEOUT_MS,
|
|
165
|
+
});
|
|
166
|
+
try {
|
|
167
|
+
const result = await act();
|
|
168
|
+
return { result, navigation: await summarizeNavigation(waiter, epoch) };
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
waiter.dispose();
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
/** Settle a `--wait-nav` waiter, turning its timeout into a "nothing navigated" summary. */
|
|
175
|
+
const summarizeNavigation = async (waiter, epoch) => {
|
|
176
|
+
try {
|
|
177
|
+
const url = await waiter.settle();
|
|
178
|
+
return { navigated: url != null, url, epoch };
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
if (!hasErrorCode(error, 'navigation_timeout'))
|
|
182
|
+
throw error;
|
|
183
|
+
return { navigated: false, url: null, epoch };
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
//# sourceMappingURL=navigation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"navigation.js","sourceRoot":"","sources":["../../src/cdp/navigation.ts"],"names":[],"mappings":"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"}
|
package/dist/cdp/protocol.d.ts
CHANGED
|
@@ -64,6 +64,15 @@ export type CdpFrame = {
|
|
|
64
64
|
parentId?: string;
|
|
65
65
|
url?: string;
|
|
66
66
|
name?: string;
|
|
67
|
+
/** Identifies the navigation that produced this frame state. Used to match load events to a navigation. */
|
|
68
|
+
loaderId?: string;
|
|
69
|
+
};
|
|
70
|
+
/** One entry in the `Page.getNavigationHistory` response. */
|
|
71
|
+
export type CdpNavigationHistoryEntry = {
|
|
72
|
+
id: number;
|
|
73
|
+
url: string;
|
|
74
|
+
title: string;
|
|
75
|
+
transitionType?: string;
|
|
67
76
|
};
|
|
68
77
|
/** A node in the `Page.getFrameTree` response. */
|
|
69
78
|
export type CdpFrameTreeNode = {
|
|
@@ -369,6 +378,10 @@ export type CdpCommandMap = {
|
|
|
369
378
|
cssVisualViewport?: CdpVisualViewport;
|
|
370
379
|
visualViewport?: CdpVisualViewport;
|
|
371
380
|
}>;
|
|
381
|
+
'Page.getNavigationHistory': Command<NoParams, {
|
|
382
|
+
currentIndex?: number;
|
|
383
|
+
entries?: CdpNavigationHistoryEntry[];
|
|
384
|
+
}>;
|
|
372
385
|
'Page.handleJavaScriptDialog': Command<{
|
|
373
386
|
accept: boolean;
|
|
374
387
|
promptText?: string;
|
|
@@ -381,6 +394,9 @@ export type CdpCommandMap = {
|
|
|
381
394
|
loaderId?: string;
|
|
382
395
|
errorText?: string;
|
|
383
396
|
}>;
|
|
397
|
+
'Page.navigateToHistoryEntry': Command<{
|
|
398
|
+
entryId: number;
|
|
399
|
+
}, unknown>;
|
|
384
400
|
'Page.reload': Command<{
|
|
385
401
|
ignoreCache?: boolean;
|
|
386
402
|
scriptToEvaluateOnLoad?: string;
|
|
@@ -547,11 +563,19 @@ export type CdpEventMap = {
|
|
|
547
563
|
};
|
|
548
564
|
'Page.frameNavigated': {
|
|
549
565
|
frame?: CdpFrame;
|
|
566
|
+
type?: 'Navigation' | 'BackForwardCacheRestore';
|
|
550
567
|
};
|
|
551
568
|
'Page.javascriptDialogClosed': {
|
|
552
569
|
result?: boolean;
|
|
553
570
|
userInput?: string;
|
|
554
571
|
};
|
|
572
|
+
'Page.loadEventFired': {
|
|
573
|
+
timestamp?: number;
|
|
574
|
+
};
|
|
575
|
+
'Page.navigatedWithinDocument': {
|
|
576
|
+
frameId?: string;
|
|
577
|
+
url?: string;
|
|
578
|
+
};
|
|
555
579
|
'Page.javascriptDialogOpening': {
|
|
556
580
|
url?: string;
|
|
557
581
|
message?: string;
|