browser-debugger-cli 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/bdg/SKILL.md +2 -1
- package/dist/cdp/methodTarget.d.ts +92 -0
- package/dist/cdp/methodTarget.js +159 -0
- package/dist/cdp/protocol.d.ts +16 -1
- package/dist/cdp/protocol.js +21 -0
- package/dist/cdp/schema.d.ts +55 -1
- package/dist/cdp/schema.js +134 -25
- package/dist/cdp/types.d.ts +3 -1
- package/dist/commands/cdp.d.ts +38 -1
- package/dist/commands/cdp.js +200 -133
- package/dist/commands/cleanup.js +18 -4
- package/dist/commands/dom/formInteraction.js +8 -4
- package/dist/commands/dom/helpers/index.d.ts +4 -4
- package/dist/commands/dom/helpers/index.js +3 -3
- package/dist/commands/dom/helpers/query.d.ts +2 -2
- package/dist/commands/dom/helpers/query.js +2 -2
- package/dist/commands/dom/helpers/screenshot.d.ts +21 -26
- package/dist/commands/dom/helpers/screenshot.js +50 -668
- package/dist/commands/dom/screenshot.js +56 -36
- package/dist/commands/optionBehaviors.js +18 -8
- package/dist/commands/shared/CommandRunner.d.ts +5 -0
- package/dist/commands/shared/CommandRunner.js +18 -3
- package/dist/commands/shared/interrupt.d.ts +40 -0
- package/dist/commands/shared/interrupt.js +73 -0
- package/dist/commands/shared/optionTypes.d.ts +2 -0
- package/dist/commands/shared/startHelpers.d.ts +26 -3
- package/dist/commands/shared/startHelpers.js +145 -23
- package/dist/commands/types.d.ts +5 -0
- package/dist/connection/cdp.js +1 -16
- package/dist/connection/chromeIdentity.d.ts +24 -5
- package/dist/connection/chromeIdentity.js +53 -22
- package/dist/connection/launcher.d.ts +34 -1
- package/dist/connection/launcher.js +98 -10
- package/dist/connection/typed-cdp.d.ts +3 -2
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/daemon/SessionController.d.ts +10 -5
- package/dist/daemon/SessionController.js +15 -8
- package/dist/daemon/ipcServer.js +1 -1
- package/dist/daemon/launcher.d.ts +5 -0
- package/dist/daemon/launcher.js +8 -1
- package/dist/daemon/session/Session.d.ts +5 -1
- package/dist/daemon/session/Session.js +9 -8
- package/dist/daemon/session/TelemetryStore.d.ts +5 -0
- package/dist/daemon/session/TelemetryStore.js +4 -0
- package/dist/daemon/session/captureGate.d.ts +59 -0
- package/dist/daemon/session/captureGate.js +96 -0
- package/dist/daemon/session/chromeConnection.d.ts +16 -1
- package/dist/daemon/session/chromeConnection.js +34 -4
- package/dist/daemon/session/collectors.d.ts +15 -0
- package/dist/daemon/session/collectors.js +39 -2
- package/dist/daemon/session/commandRegistry.d.ts +14 -1
- package/dist/daemon/session/commandRegistry.js +46 -11
- package/dist/daemon/session/downloads.d.ts +32 -0
- package/dist/daemon/session/downloads.js +96 -0
- package/dist/daemon/session/interactions.d.ts +3 -2
- package/dist/daemon/session/interactions.js +7 -2
- package/dist/daemon/session/plugins.js +6 -0
- package/dist/daemon.js +12843 -11482
- package/dist/errors/CommandError.d.ts +2 -0
- package/dist/errors/issues.d.ts +1 -1
- package/dist/errors/messages.d.ts +58 -0
- package/dist/errors/messages.js +112 -0
- package/dist/index.js +999 -1020
- package/dist/ipc/client.d.ts +14 -1
- package/dist/ipc/client.js +21 -4
- package/dist/ipc/protocol/commands.d.ts +32 -2
- package/dist/ipc/protocol/commands.js +1 -0
- package/dist/ipc/protocol/domTypes.d.ts +24 -1
- package/dist/ipc/session/queries.d.ts +3 -0
- package/dist/ipc/session/types.d.ts +5 -0
- package/dist/ipc/transport/IPCError.d.ts +9 -0
- package/dist/ipc/transport/IPCError.js +12 -0
- package/dist/ipc/transport/errors.d.ts +2 -1
- package/dist/ipc/transport/errors.js +4 -1
- package/dist/ipc/transport/index.d.ts +4 -2
- package/dist/ipc/transport/index.js +13 -3
- package/dist/runtime/dom/actionEffects.d.ts +48 -9
- package/dist/runtime/dom/actionEffects.js +269 -34
- package/dist/runtime/dom/actionEffectsScripts.d.ts +45 -0
- package/dist/runtime/dom/actionEffectsScripts.js +101 -2
- package/dist/runtime/dom/captureArea.d.ts +35 -0
- package/dist/runtime/dom/captureArea.js +203 -0
- package/dist/runtime/dom/elementInfo.d.ts +10 -8
- package/dist/runtime/dom/elementInfo.js +8 -6
- package/dist/runtime/dom/formDiscovery.d.ts +1 -1
- package/dist/runtime/page/bdgWorld.d.ts +9 -0
- package/dist/runtime/page/bdgWorld.js +11 -0
- package/dist/runtime/page/captureEmulation.d.ts +119 -0
- package/dist/runtime/page/captureEmulation.js +189 -0
- package/dist/runtime/page/captureScroll.d.ts +24 -0
- package/dist/runtime/page/captureScroll.js +124 -0
- package/dist/runtime/page/screenshot.d.ts +41 -0
- package/dist/runtime/page/screenshot.js +394 -0
- package/dist/session/paths.d.ts +14 -0
- package/dist/session/paths.js +25 -0
- package/dist/telemetry/downloads.d.ts +127 -0
- package/dist/telemetry/downloads.js +265 -0
- package/dist/telemetry/har/builder.js +22 -7
- package/dist/telemetry/har/sanitize.d.ts +7 -3
- package/dist/telemetry/har/sanitize.js +52 -6
- package/dist/telemetry/har/sanitizeBody.d.ts +47 -7
- package/dist/telemetry/har/sanitizeBody.js +429 -56
- package/dist/telemetry/har/types.d.ts +2 -0
- package/dist/telemetry/network.d.ts +4 -4
- package/dist/telemetry/network.js +38 -4
- package/dist/telemetry/networkRetention.d.ts +35 -14
- package/dist/telemetry/networkRetention.js +62 -26
- package/dist/types.d.ts +9 -14
- package/dist/ui/OutputBuilder.d.ts +3 -2
- package/dist/ui/OutputBuilder.js +4 -3
- package/dist/ui/formatters/cdp.d.ts +32 -9
- package/dist/ui/formatters/cdp.js +77 -6
- package/dist/ui/formatters/details.js +7 -15
- package/dist/ui/formatters/preview.d.ts +2 -0
- package/dist/ui/formatters/preview.js +7 -1
- package/dist/ui/formatters/status.js +6 -1
- package/dist/ui/formatting.d.ts +7 -0
- package/dist/ui/formatting.js +13 -0
- package/dist/ui/logging/logger.d.ts +1 -1
- package/dist/ui/messages/chrome.d.ts +13 -0
- package/dist/ui/messages/chrome.js +26 -0
- package/dist/ui/messages/commands.d.ts +71 -3
- package/dist/ui/messages/commands.js +98 -3
- package/dist/ui/messages/networkMessages.d.ts +24 -5
- package/dist/ui/messages/networkMessages.js +31 -8
- package/dist/utils/async.d.ts +3 -2
- package/dist/utils/async.js +16 -3
- package/dist/utils/http.d.ts +11 -4
- package/dist/utils/http.js +5 -3
- package/package.json +18 -4
- /package/dist/{commands/dom → runtime/page}/screenshotResize.d.ts +0 -0
- /package/dist/{commands/dom → runtime/page}/screenshotResize.js +0 -0
|
@@ -1,38 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* What the network collector keeps of a long session: the newest finished
|
|
3
|
-
* requests up to a cap, and the newest response bodies up to a
|
|
3
|
+
* requests up to a cap, and the newest request and response bodies up to a
|
|
4
|
+
* total size.
|
|
4
5
|
*/
|
|
5
6
|
import type { NetworkRequest } from '../types.js';
|
|
6
7
|
/** Counts of what the session let go at its limits */
|
|
7
8
|
export interface NetworkEvictions {
|
|
8
9
|
/** Finished requests dropped at the request cap, oldest first */
|
|
9
10
|
requestsDropped: number;
|
|
10
|
-
/**
|
|
11
|
+
/** Request and response bodies replaced by a placeholder at the total body budget, oldest first */
|
|
11
12
|
bodiesEvicted: number;
|
|
12
13
|
}
|
|
13
14
|
/** Limits of {@link RequestRetention} */
|
|
14
15
|
export interface RetentionLimits {
|
|
15
16
|
/** Finished requests kept at most */
|
|
16
17
|
maxRequests: number;
|
|
17
|
-
/** Total size of the stored response bodies (bytes) */
|
|
18
|
+
/** Total size of the stored request and response bodies (bytes) */
|
|
18
19
|
maxTotalBodyBytes: number;
|
|
19
20
|
}
|
|
20
21
|
/**
|
|
21
22
|
* Keeps finished requests, oldest first, dropping the oldest past the cap
|
|
22
23
|
* (requests in flight are never in the list, so never dropped), and tracks
|
|
23
|
-
* the size of their stored
|
|
24
|
-
*
|
|
24
|
+
* the size of their stored request and response bodies under one budget,
|
|
25
|
+
* replacing the oldest bodies past it with a placeholder that says why.
|
|
25
26
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* A request body is counted when its request finishes, a response body when
|
|
28
|
+
* it is fetched. Stored bodies are kept in a Set, which iterates in insertion
|
|
29
|
+
* order: its first entry is the oldest body. Each part is also indexed by
|
|
30
|
+
* request, so a dropped request's bodies are removed in O(1). Dropping from
|
|
31
|
+
* the front of the list is `Array.shift`, which V8 does without copying for
|
|
32
|
+
* arrays of this size.
|
|
30
33
|
*/
|
|
31
34
|
export declare class RequestRetention {
|
|
32
35
|
private readonly requests;
|
|
33
36
|
private readonly limits;
|
|
34
37
|
private readonly evictions;
|
|
35
|
-
private readonly
|
|
38
|
+
private readonly storedBodies;
|
|
39
|
+
private readonly bodiesByPart;
|
|
36
40
|
private storedBodyBytes;
|
|
37
41
|
/**
|
|
38
42
|
* @param requests - Finished requests, oldest first (updated in place)
|
|
@@ -41,12 +45,19 @@ export declare class RequestRetention {
|
|
|
41
45
|
*/
|
|
42
46
|
constructor(requests: NetworkRequest[], limits: RetentionLimits, evictions: NetworkEvictions);
|
|
43
47
|
/**
|
|
44
|
-
* Add a finished request,
|
|
48
|
+
* Add a finished request, drop the oldest request past the cap, then count
|
|
49
|
+
* the new request's body against the budget.
|
|
45
50
|
*
|
|
46
51
|
* @param request - Finished request
|
|
47
52
|
* @returns The dropped request, if one was
|
|
48
53
|
*/
|
|
49
54
|
add(request: NetworkRequest): NetworkRequest | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Drop the oldest request when the list is over the cap, with its bodies.
|
|
57
|
+
*
|
|
58
|
+
* @returns The dropped request, if one was
|
|
59
|
+
*/
|
|
60
|
+
private dropOldestPastCap;
|
|
50
61
|
/**
|
|
51
62
|
* Store a fetched response body on a kept request, then evict the oldest
|
|
52
63
|
* bodies until the total fits the budget (a body larger than the whole
|
|
@@ -57,17 +68,27 @@ export declare class RequestRetention {
|
|
|
57
68
|
* @param base64Encoded - Whether `body` is base64
|
|
58
69
|
*/
|
|
59
70
|
storeBody(request: NetworkRequest, body: string, base64Encoded: boolean): void;
|
|
71
|
+
/**
|
|
72
|
+
* Count a stored body (replacing an earlier count of the same part), then
|
|
73
|
+
* evict the oldest bodies while the total is over the budget.
|
|
74
|
+
*
|
|
75
|
+
* @param request - Request the body belongs to
|
|
76
|
+
* @param part - Which of its bodies
|
|
77
|
+
* @param size - Body size (bytes)
|
|
78
|
+
*/
|
|
79
|
+
private countBody;
|
|
60
80
|
/** Evict the oldest stored bodies while their total is over the budget. */
|
|
61
81
|
private enforceBodyBudget;
|
|
62
82
|
/**
|
|
63
|
-
* Stop counting a request's stored
|
|
83
|
+
* Stop counting one of a request's stored bodies.
|
|
64
84
|
*
|
|
65
85
|
* @param request - Request whose body is let go
|
|
86
|
+
* @param part - Which of its bodies
|
|
66
87
|
*/
|
|
67
88
|
private forgetBody;
|
|
68
89
|
}
|
|
69
90
|
/**
|
|
70
|
-
* Placeholder stored instead of a
|
|
91
|
+
* Placeholder stored instead of a body that was not fetched or not kept.
|
|
71
92
|
*
|
|
72
93
|
* @param reason - Why the body was skipped
|
|
73
94
|
* @returns Placeholder text shown by `bdg details`
|
|
@@ -76,7 +97,7 @@ export declare function skippedBodyPlaceholder(reason: string): string;
|
|
|
76
97
|
/**
|
|
77
98
|
* Extract the reason from a skipped-body placeholder.
|
|
78
99
|
*
|
|
79
|
-
* @param body - Stored response body
|
|
100
|
+
* @param body - Stored request or response body
|
|
80
101
|
* @returns Reason if `body` is a placeholder, otherwise undefined
|
|
81
102
|
*/
|
|
82
103
|
export declare function skippedBodyReason(body: string | undefined): string | undefined;
|
|
@@ -1,24 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* What the network collector keeps of a long session: the newest finished
|
|
3
|
-
* requests up to a cap, and the newest response bodies up to a
|
|
3
|
+
* requests up to a cap, and the newest request and response bodies up to a
|
|
4
|
+
* total size.
|
|
4
5
|
*/
|
|
5
6
|
import { bodyEvictedReason } from '../ui/messages/networkMessages.js';
|
|
6
7
|
/**
|
|
7
8
|
* Keeps finished requests, oldest first, dropping the oldest past the cap
|
|
8
9
|
* (requests in flight are never in the list, so never dropped), and tracks
|
|
9
|
-
* the size of their stored
|
|
10
|
-
*
|
|
10
|
+
* the size of their stored request and response bodies under one budget,
|
|
11
|
+
* replacing the oldest bodies past it with a placeholder that says why.
|
|
11
12
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* A request body is counted when its request finishes, a response body when
|
|
14
|
+
* it is fetched. Stored bodies are kept in a Set, which iterates in insertion
|
|
15
|
+
* order: its first entry is the oldest body. Each part is also indexed by
|
|
16
|
+
* request, so a dropped request's bodies are removed in O(1). Dropping from
|
|
17
|
+
* the front of the list is `Array.shift`, which V8 does without copying for
|
|
18
|
+
* arrays of this size.
|
|
16
19
|
*/
|
|
17
20
|
export class RequestRetention {
|
|
18
21
|
requests;
|
|
19
22
|
limits;
|
|
20
23
|
evictions;
|
|
21
|
-
|
|
24
|
+
storedBodies = new Set();
|
|
25
|
+
bodiesByPart = {
|
|
26
|
+
requestBody: new Map(),
|
|
27
|
+
responseBody: new Map(),
|
|
28
|
+
};
|
|
22
29
|
storedBodyBytes = 0;
|
|
23
30
|
/**
|
|
24
31
|
* @param requests - Finished requests, oldest first (updated in place)
|
|
@@ -31,20 +38,34 @@ export class RequestRetention {
|
|
|
31
38
|
this.evictions = evictions;
|
|
32
39
|
}
|
|
33
40
|
/**
|
|
34
|
-
* Add a finished request,
|
|
41
|
+
* Add a finished request, drop the oldest request past the cap, then count
|
|
42
|
+
* the new request's body against the budget.
|
|
35
43
|
*
|
|
36
44
|
* @param request - Finished request
|
|
37
45
|
* @returns The dropped request, if one was
|
|
38
46
|
*/
|
|
39
47
|
add(request) {
|
|
40
48
|
this.requests.push(request);
|
|
49
|
+
const dropped = this.dropOldestPastCap();
|
|
50
|
+
if (request.requestBody) {
|
|
51
|
+
this.countBody(request, 'requestBody', Buffer.byteLength(request.requestBody));
|
|
52
|
+
}
|
|
53
|
+
return dropped;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Drop the oldest request when the list is over the cap, with its bodies.
|
|
57
|
+
*
|
|
58
|
+
* @returns The dropped request, if one was
|
|
59
|
+
*/
|
|
60
|
+
dropOldestPastCap() {
|
|
41
61
|
if (this.requests.length <= this.limits.maxRequests)
|
|
42
62
|
return undefined;
|
|
43
63
|
const dropped = this.requests.shift();
|
|
44
64
|
if (!dropped)
|
|
45
65
|
return undefined;
|
|
46
66
|
this.evictions.requestsDropped++;
|
|
47
|
-
this.forgetBody(dropped);
|
|
67
|
+
this.forgetBody(dropped, 'requestBody');
|
|
68
|
+
this.forgetBody(dropped, 'responseBody');
|
|
48
69
|
return dropped;
|
|
49
70
|
}
|
|
50
71
|
/**
|
|
@@ -57,47 +78,62 @@ export class RequestRetention {
|
|
|
57
78
|
* @param base64Encoded - Whether `body` is base64
|
|
58
79
|
*/
|
|
59
80
|
storeBody(request, body, base64Encoded) {
|
|
60
|
-
this.forgetBody(request);
|
|
61
81
|
request.responseBody = body;
|
|
62
82
|
if (base64Encoded)
|
|
63
83
|
request.responseBodyBase64 = true;
|
|
64
84
|
if (body) {
|
|
65
85
|
request.decodedBodyLength = Buffer.byteLength(body, base64Encoded ? 'base64' : 'utf-8');
|
|
66
86
|
}
|
|
67
|
-
|
|
68
|
-
|
|
87
|
+
this.countBody(request, 'responseBody', Buffer.byteLength(body));
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Count a stored body (replacing an earlier count of the same part), then
|
|
91
|
+
* evict the oldest bodies while the total is over the budget.
|
|
92
|
+
*
|
|
93
|
+
* @param request - Request the body belongs to
|
|
94
|
+
* @param part - Which of its bodies
|
|
95
|
+
* @param size - Body size (bytes)
|
|
96
|
+
*/
|
|
97
|
+
countBody(request, part, size) {
|
|
98
|
+
this.forgetBody(request, part);
|
|
99
|
+
const stored = { request, part, size };
|
|
100
|
+
this.storedBodies.add(stored);
|
|
101
|
+
this.bodiesByPart[part].set(request, stored);
|
|
69
102
|
this.storedBodyBytes += size;
|
|
70
103
|
this.enforceBodyBudget();
|
|
71
104
|
}
|
|
72
105
|
/** Evict the oldest stored bodies while their total is over the budget. */
|
|
73
106
|
enforceBodyBudget() {
|
|
74
107
|
while (this.storedBodyBytes > this.limits.maxTotalBodyBytes) {
|
|
75
|
-
const oldest = this.
|
|
108
|
+
const oldest = this.storedBodies.values().next();
|
|
76
109
|
if (oldest.done)
|
|
77
110
|
return;
|
|
78
|
-
const request = oldest.value;
|
|
79
|
-
this.forgetBody(request);
|
|
80
|
-
request
|
|
81
|
-
|
|
111
|
+
const { request, part } = oldest.value;
|
|
112
|
+
this.forgetBody(request, part);
|
|
113
|
+
request[part] = skippedBodyPlaceholder(bodyEvictedReason(this.limits.maxTotalBodyBytes));
|
|
114
|
+
if (part === 'responseBody')
|
|
115
|
+
delete request.responseBodyBase64;
|
|
82
116
|
this.evictions.bodiesEvicted++;
|
|
83
117
|
}
|
|
84
118
|
}
|
|
85
119
|
/**
|
|
86
|
-
* Stop counting a request's stored
|
|
120
|
+
* Stop counting one of a request's stored bodies.
|
|
87
121
|
*
|
|
88
122
|
* @param request - Request whose body is let go
|
|
123
|
+
* @param part - Which of its bodies
|
|
89
124
|
*/
|
|
90
|
-
forgetBody(request) {
|
|
91
|
-
const
|
|
92
|
-
if (
|
|
125
|
+
forgetBody(request, part) {
|
|
126
|
+
const stored = this.bodiesByPart[part].get(request);
|
|
127
|
+
if (!stored)
|
|
93
128
|
return;
|
|
94
|
-
this.
|
|
95
|
-
this.
|
|
129
|
+
this.bodiesByPart[part].delete(request);
|
|
130
|
+
this.storedBodies.delete(stored);
|
|
131
|
+
this.storedBodyBytes -= stored.size;
|
|
96
132
|
}
|
|
97
133
|
}
|
|
98
134
|
const SKIPPED_BODY_PATTERN = /^\[SKIPPED: (.*)\]$/s;
|
|
99
135
|
/**
|
|
100
|
-
* Placeholder stored instead of a
|
|
136
|
+
* Placeholder stored instead of a body that was not fetched or not kept.
|
|
101
137
|
*
|
|
102
138
|
* @param reason - Why the body was skipped
|
|
103
139
|
* @returns Placeholder text shown by `bdg details`
|
|
@@ -108,7 +144,7 @@ export function skippedBodyPlaceholder(reason) {
|
|
|
108
144
|
/**
|
|
109
145
|
* Extract the reason from a skipped-body placeholder.
|
|
110
146
|
*
|
|
111
|
-
* @param body - Stored response body
|
|
147
|
+
* @param body - Stored request or response body
|
|
112
148
|
* @returns Reason if `body` is a placeholder, otherwise undefined
|
|
113
149
|
*/
|
|
114
150
|
export function skippedBodyReason(body) {
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Protocol } from './connection/typed-cdp.js';
|
|
2
|
+
import type { DownloadInfo } from './ipc/protocol/domTypes.js';
|
|
2
3
|
/**
|
|
3
4
|
* Standard response envelope for all bdg command JSON output.
|
|
4
5
|
*
|
|
@@ -40,6 +41,8 @@ export interface BdgResponse<T = unknown> {
|
|
|
40
41
|
exitCode?: number;
|
|
41
42
|
/** Actionable suggestion for error recovery */
|
|
42
43
|
suggestion?: string;
|
|
44
|
+
/** How the command ran despite something unusual (e.g. a CDP method bdg's protocol lacks) */
|
|
45
|
+
warning?: string;
|
|
43
46
|
}
|
|
44
47
|
/**
|
|
45
48
|
* Re-export connection types for backward compatibility.
|
|
@@ -150,6 +153,8 @@ export interface NetworkRequest {
|
|
|
150
153
|
fromCache?: boolean;
|
|
151
154
|
/** Why the response body was not captured (`details`; `responseBody` is then absent) */
|
|
152
155
|
bodyNotCaptured?: string;
|
|
156
|
+
/** Why the request body was not kept (`details`; `requestBody` is then absent) */
|
|
157
|
+
requestBodyNotCaptured?: string;
|
|
153
158
|
/** Messages and lifecycle of a WebSocket connection (`resourceType` is `WebSocket`) */
|
|
154
159
|
webSocket?: {
|
|
155
160
|
frames: WebSocketFrame[];
|
|
@@ -230,6 +235,8 @@ export interface BdgOutput {
|
|
|
230
235
|
currentNavigationId?: number;
|
|
231
236
|
/** When the page's renderer crashed (epoch ms), while it is not loaded again */
|
|
232
237
|
pageCrashedAt?: number;
|
|
238
|
+
/** Downloads that began during the session, oldest first (live previews) */
|
|
239
|
+
downloads?: DownloadInfo[];
|
|
233
240
|
/** Counts of all captured items matching the request (e.g. `peek --type`), when `data` holds only the most recent ones */
|
|
234
241
|
totals?: {
|
|
235
242
|
network: number;
|
|
@@ -528,8 +535,8 @@ export interface ScreenshotResult {
|
|
|
528
535
|
/** `--padding` given: CSS px of page added around the captured area */
|
|
529
536
|
padding?: number;
|
|
530
537
|
};
|
|
531
|
-
/** Capture mode used */
|
|
532
|
-
captureMode?: 'full_page' | 'viewport';
|
|
538
|
+
/** Capture mode used: the whole page, the viewport, or one element */
|
|
539
|
+
captureMode?: 'full_page' | 'viewport' | 'element';
|
|
533
540
|
/** Whether the image was auto-resized to fit token budget */
|
|
534
541
|
resized?: boolean;
|
|
535
542
|
/** Original width before resize (pixels) */
|
|
@@ -560,18 +567,6 @@ export interface DomGetOptions {
|
|
|
560
567
|
all?: boolean;
|
|
561
568
|
nth?: number;
|
|
562
569
|
}
|
|
563
|
-
/**
|
|
564
|
-
* Options for screenshot operation.
|
|
565
|
-
*/
|
|
566
|
-
export interface ScreenshotOptions {
|
|
567
|
-
format?: 'png' | 'jpeg';
|
|
568
|
-
quality?: number;
|
|
569
|
-
fullPage?: boolean;
|
|
570
|
-
/** Disable auto-resize to 1568px max edge */
|
|
571
|
-
noResize?: boolean;
|
|
572
|
-
/** Scroll element into view before capture */
|
|
573
|
-
scroll?: string;
|
|
574
|
-
}
|
|
575
570
|
/**
|
|
576
571
|
* Field validation state from HTML5 or custom validation.
|
|
577
572
|
*/
|
|
@@ -6,9 +6,10 @@ import type { BdgResponse } from '../types.js';
|
|
|
6
6
|
* Build a success response envelope.
|
|
7
7
|
*
|
|
8
8
|
* @param data - Response payload
|
|
9
|
-
* @
|
|
9
|
+
* @param warning - Warning about how the command ran, if any
|
|
10
|
+
* @returns `{ version, success: true, data }`, plus `warning` when given
|
|
10
11
|
*/
|
|
11
|
-
export declare function buildSuccessResponse<T>(data: T): BdgResponse<T>;
|
|
12
|
+
export declare function buildSuccessResponse<T>(data: T, warning?: string): BdgResponse<T>;
|
|
12
13
|
/**
|
|
13
14
|
* Serialize a `--json` response envelope for stdout.
|
|
14
15
|
*
|
package/dist/ui/OutputBuilder.js
CHANGED
|
@@ -6,10 +6,11 @@ import { VERSION } from '../utils/version.js';
|
|
|
6
6
|
* Build a success response envelope.
|
|
7
7
|
*
|
|
8
8
|
* @param data - Response payload
|
|
9
|
-
* @
|
|
9
|
+
* @param warning - Warning about how the command ran, if any
|
|
10
|
+
* @returns `{ version, success: true, data }`, plus `warning` when given
|
|
10
11
|
*/
|
|
11
|
-
export function buildSuccessResponse(data) {
|
|
12
|
-
return { version: VERSION, success: true, data };
|
|
12
|
+
export function buildSuccessResponse(data, warning) {
|
|
13
|
+
return { version: VERSION, success: true, data, ...(warning && { warning }) };
|
|
13
14
|
}
|
|
14
15
|
/**
|
|
15
16
|
* Serialize a `--json` response envelope for stdout.
|
|
@@ -67,6 +67,15 @@ interface CdpField {
|
|
|
67
67
|
description?: string | undefined;
|
|
68
68
|
items?: string | undefined;
|
|
69
69
|
}
|
|
70
|
+
/** A parameter (or type property) in `--describe`: `enum` also holds a `$ref` type's values */
|
|
71
|
+
interface CdpParameter extends CdpField {
|
|
72
|
+
required: boolean;
|
|
73
|
+
enum?: string[] | undefined;
|
|
74
|
+
ref?: string | undefined;
|
|
75
|
+
refType?: string | undefined;
|
|
76
|
+
experimental?: boolean | undefined;
|
|
77
|
+
deprecated?: boolean | undefined;
|
|
78
|
+
}
|
|
70
79
|
/** `bdg cdp <Domain.method> --describe` result */
|
|
71
80
|
export interface CdpMethodDescription extends ProtocolEntry {
|
|
72
81
|
type: 'method';
|
|
@@ -74,19 +83,31 @@ export interface CdpMethodDescription extends ProtocolEntry {
|
|
|
74
83
|
domain: string;
|
|
75
84
|
method: string;
|
|
76
85
|
note?: string | undefined;
|
|
77
|
-
parameters:
|
|
78
|
-
required: boolean;
|
|
79
|
-
enum?: string[] | undefined;
|
|
80
|
-
deprecated?: boolean | undefined;
|
|
81
|
-
})[];
|
|
86
|
+
parameters: CdpParameter[];
|
|
82
87
|
returns: (CdpField & {
|
|
83
88
|
optional: boolean;
|
|
84
89
|
})[];
|
|
90
|
+
redirect?: {
|
|
91
|
+
method: string;
|
|
92
|
+
resolved: boolean;
|
|
93
|
+
parameters: CdpParameter[];
|
|
94
|
+
} | undefined;
|
|
85
95
|
example?: {
|
|
86
96
|
command: string;
|
|
87
97
|
params?: Record<string, unknown> | undefined;
|
|
88
98
|
} | undefined;
|
|
89
99
|
}
|
|
100
|
+
/** `bdg cdp <Domain.Type> --describe` result */
|
|
101
|
+
export interface CdpTypeDescription extends ProtocolEntry {
|
|
102
|
+
type: 'type';
|
|
103
|
+
name: string;
|
|
104
|
+
domain: string;
|
|
105
|
+
id: string;
|
|
106
|
+
baseType: string;
|
|
107
|
+
enum?: string[] | undefined;
|
|
108
|
+
items?: string | undefined;
|
|
109
|
+
properties?: CdpParameter[] | undefined;
|
|
110
|
+
}
|
|
90
111
|
/** `bdg cdp <Domain.method>` result */
|
|
91
112
|
export interface CdpExecuteData {
|
|
92
113
|
method: string;
|
|
@@ -114,12 +135,14 @@ export declare function formatCdpDomains(data: CdpDomainListData): string;
|
|
|
114
135
|
*/
|
|
115
136
|
export declare function formatCdpDomainMethods(data: CdpDomainMethodsData): string;
|
|
116
137
|
/**
|
|
117
|
-
* Format `bdg cdp <Domain.method> --describe
|
|
138
|
+
* Format `bdg cdp <Domain.method> --describe`, `bdg cdp <Domain> --describe`
|
|
139
|
+
* or `bdg cdp <Domain.Type> --describe`.
|
|
118
140
|
*
|
|
119
|
-
* @param data - Method or
|
|
120
|
-
* @returns Description, parameters (`?` = optional
|
|
141
|
+
* @param data - Method, domain or type description
|
|
142
|
+
* @returns Description, parameters (`?` = optional, `$ref` enums inline),
|
|
143
|
+
* the redirect target's parameters, returns, note and example
|
|
121
144
|
*/
|
|
122
|
-
export declare function formatCdpDescription(data: CdpMethodDescription | CdpDomainDescription): string;
|
|
145
|
+
export declare function formatCdpDescription(data: CdpMethodDescription | CdpDomainDescription | CdpTypeDescription): string;
|
|
123
146
|
/**
|
|
124
147
|
* Whether a CDP method returned nothing (null, undefined or an empty object).
|
|
125
148
|
*
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* lists, method schemas and method results (`--json` prints the data as is).
|
|
4
4
|
*/
|
|
5
5
|
import { joinLines, pluralize } from '../formatting.js';
|
|
6
|
+
import { cdpRedirectTitle, cdpUnresolvedRedirectLine } from '../messages/commands.js';
|
|
6
7
|
/**
|
|
7
8
|
* First sentence (or line) of a protocol description.
|
|
8
9
|
*
|
|
@@ -43,6 +44,24 @@ function columns(rows) {
|
|
|
43
44
|
function typeText(field) {
|
|
44
45
|
return field.items ? `${field.type}<${field.items}>` : field.type;
|
|
45
46
|
}
|
|
47
|
+
/** Enum values listed in text before the rest are counted */
|
|
48
|
+
const MAX_ENUM_VALUES = 8;
|
|
49
|
+
/**
|
|
50
|
+
* What a field's type stands for, in parentheses: enum values (the first
|
|
51
|
+
* {@link MAX_ENUM_VALUES}), or the base type of a referenced non-object type.
|
|
52
|
+
*
|
|
53
|
+
* @param field - Field with its inline or referenced enum and referenced base type
|
|
54
|
+
* @returns e.g. " (Strict|Lax|None)", " (number)", or an empty string
|
|
55
|
+
*/
|
|
56
|
+
function expansionText(field) {
|
|
57
|
+
const values = field.enum;
|
|
58
|
+
if (!values || values.length === 0) {
|
|
59
|
+
return field.refType && field.refType !== 'object' ? ` (${field.refType})` : '';
|
|
60
|
+
}
|
|
61
|
+
const shown = values.slice(0, MAX_ENUM_VALUES);
|
|
62
|
+
const more = values.length - shown.length;
|
|
63
|
+
return ` (${[...shown, ...(more > 0 ? [`… ${more} more`] : [])].join('|')})`;
|
|
64
|
+
}
|
|
46
65
|
/**
|
|
47
66
|
* Format `bdg cdp --search <query>`.
|
|
48
67
|
*
|
|
@@ -89,22 +108,74 @@ function fieldSection(title, fields) {
|
|
|
89
108
|
return [
|
|
90
109
|
`${title}:`,
|
|
91
110
|
...columns(fields.map((f) => [
|
|
92
|
-
`${f.name}${f.optional ? '?' : ''}: ${typeText(f)}`,
|
|
93
|
-
(f.description ?? '').replace(/\s*\n\s*/g, ' ')
|
|
111
|
+
`${f.name}${f.optional ? '?' : ''}: ${typeText(f)}${expansionText(f)}`,
|
|
112
|
+
`${(f.description ?? '').replace(/\s*\n\s*/g, ' ')}${tags(f)}`,
|
|
94
113
|
])),
|
|
95
114
|
];
|
|
96
115
|
}
|
|
97
116
|
/**
|
|
98
|
-
*
|
|
117
|
+
* Parameters as fields with `?` for optional ones.
|
|
118
|
+
*
|
|
119
|
+
* @param parameters - Parameters or type properties
|
|
120
|
+
* @returns Fields for {@link fieldSection}
|
|
121
|
+
*/
|
|
122
|
+
function parameterFields(parameters) {
|
|
123
|
+
return parameters.map((p) => ({ ...p, optional: !p.required }));
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* How to describe the first object type a parameter refers to (enums and
|
|
127
|
+
* other types are already shown inline).
|
|
128
|
+
*
|
|
129
|
+
* @param parameters - Parameters, the redirect target's included
|
|
130
|
+
* @returns Hint line, or undefined when no parameter refers to such a type
|
|
131
|
+
*/
|
|
132
|
+
function typeHint(parameters) {
|
|
133
|
+
const ref = parameters.find((p) => p.refType === 'object')?.ref;
|
|
134
|
+
return ref && `Describe a type: bdg cdp ${ref} --describe`;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Lines naming the method a redirected one runs, with its parameters, or
|
|
138
|
+
* saying the protocol lacks it.
|
|
139
|
+
*
|
|
140
|
+
* @param redirect - Redirect target, if any
|
|
141
|
+
* @returns Title and parameter lines, or nothing without a redirect
|
|
142
|
+
*/
|
|
143
|
+
function redirectSection(redirect) {
|
|
144
|
+
if (!redirect)
|
|
145
|
+
return [];
|
|
146
|
+
if (!redirect.resolved)
|
|
147
|
+
return [cdpUnresolvedRedirectLine(redirect.method)];
|
|
148
|
+
const title = cdpRedirectTitle(redirect.method);
|
|
149
|
+
return redirect.parameters.length === 0
|
|
150
|
+
? [title]
|
|
151
|
+
: fieldSection(`${title}, with these parameters`, parameterFields(redirect.parameters));
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Format `bdg cdp <Domain.Type> --describe`.
|
|
155
|
+
*
|
|
156
|
+
* @param data - Type description
|
|
157
|
+
* @returns Base type, description and values or properties
|
|
158
|
+
*/
|
|
159
|
+
function formatCdpType(data) {
|
|
160
|
+
const properties = data.properties ?? [];
|
|
161
|
+
return joinLines(`${data.name}: ${typeText({ name: data.id, type: data.baseType, items: data.items })}${tags(data)}`, data.description, data.enum && `Values: ${data.enum.join(', ')}`, ...fieldSection('Properties', parameterFields(properties)), typeHint(properties));
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Format `bdg cdp <Domain.method> --describe`, `bdg cdp <Domain> --describe`
|
|
165
|
+
* or `bdg cdp <Domain.Type> --describe`.
|
|
99
166
|
*
|
|
100
|
-
* @param data - Method or
|
|
101
|
-
* @returns Description, parameters (`?` = optional
|
|
167
|
+
* @param data - Method, domain or type description
|
|
168
|
+
* @returns Description, parameters (`?` = optional, `$ref` enums inline),
|
|
169
|
+
* the redirect target's parameters, returns, note and example
|
|
102
170
|
*/
|
|
103
171
|
export function formatCdpDescription(data) {
|
|
172
|
+
if (data.type === 'type')
|
|
173
|
+
return formatCdpType(data);
|
|
104
174
|
if (data.type === 'domain') {
|
|
105
175
|
return joinLines(`${data.domain}: ${pluralize(data.commands, 'method')}, ${pluralize(data.events, 'event')}${tags(data)}`, data.description, data.note, data.nextStep);
|
|
106
176
|
}
|
|
107
|
-
|
|
177
|
+
const redirected = data.redirect?.parameters ?? [];
|
|
178
|
+
return joinLines(`${data.name}${tags(data)}`, data.description, ...fieldSection('Parameters', parameterFields(data.parameters)), ...redirectSection(data.redirect), ...fieldSection('Returns', data.returns), data.note && `Note: ${data.note}`, typeHint([...data.parameters, ...redirected]), data.example && `Example: ${data.example.command}`);
|
|
108
179
|
}
|
|
109
180
|
/**
|
|
110
181
|
* Whether a CDP method returned nothing (null, undefined or an empty object).
|
|
@@ -2,7 +2,7 @@ import { skippedBodyReason } from '../../telemetry/networkRetention.js';
|
|
|
2
2
|
import { formatFramePosition, formatTimestamp } from './console/shared.js';
|
|
3
3
|
import { headerValueLines } from './networkHeaders.js';
|
|
4
4
|
import { formatRequestStatus } from './requestStatus.js';
|
|
5
|
-
import { OutputFormatter } from '../formatting.js';
|
|
5
|
+
import { OutputFormatter, formatBytes } from '../formatting.js';
|
|
6
6
|
import { localProxyNote } from '../messages/networkMessages.js';
|
|
7
7
|
import { sessionCommand } from '../messages/sessionCommand.js';
|
|
8
8
|
import { truncateByLength } from '../../utils/strings.js';
|
|
@@ -85,19 +85,6 @@ function addWebSocketMessages(fmt, webSocket) {
|
|
|
85
85
|
}
|
|
86
86
|
/** Characters of a text body shown in human output (`--json` has all of it) */
|
|
87
87
|
const BODY_PREVIEW_LENGTH = 20000;
|
|
88
|
-
/**
|
|
89
|
-
* Format a byte count for humans.
|
|
90
|
-
*
|
|
91
|
-
* @param bytes - Byte count
|
|
92
|
-
* @returns e.g. "512 B", "12.3 KB"
|
|
93
|
-
*/
|
|
94
|
-
function formatBytes(bytes) {
|
|
95
|
-
if (bytes < 1024)
|
|
96
|
-
return `${bytes} B`;
|
|
97
|
-
return bytes < 1024 * 1024
|
|
98
|
-
? `${(bytes / 1024).toFixed(1)} KB`
|
|
99
|
-
: `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
100
|
-
}
|
|
101
88
|
/**
|
|
102
89
|
* Whether an IP address is a loopback address (`127.0.0.0/8`, `::1`).
|
|
103
90
|
*
|
|
@@ -229,7 +216,12 @@ export function formatNetworkDetails(request) {
|
|
|
229
216
|
fmt.blank();
|
|
230
217
|
if (request.requestHeaders)
|
|
231
218
|
addHeaders(fmt, 'Request Headers:', request.requestHeaders);
|
|
232
|
-
if (request.
|
|
219
|
+
if (request.requestBodyNotCaptured) {
|
|
220
|
+
fmt.text('Request Body:').separator('━', 70);
|
|
221
|
+
fmt.text(`(not captured: ${request.requestBodyNotCaptured})`);
|
|
222
|
+
fmt.blank();
|
|
223
|
+
}
|
|
224
|
+
else if (request.requestBody) {
|
|
233
225
|
fmt.text('Request Body:').separator('━', 70);
|
|
234
226
|
fmt.text(request.requestBody);
|
|
235
227
|
fmt.blank();
|
|
@@ -57,6 +57,8 @@ export interface PreviewJsonData {
|
|
|
57
57
|
totals?: BdgOutput['totals'];
|
|
58
58
|
/** When the page's renderer crashed (epoch ms), while it is not loaded again */
|
|
59
59
|
pageCrashedAt?: number;
|
|
60
|
+
/** Downloads that began during the session, oldest first */
|
|
61
|
+
downloads?: BdgOutput['downloads'];
|
|
60
62
|
network?: BdgOutput['data']['network'];
|
|
61
63
|
console?: BdgOutput['data']['console'];
|
|
62
64
|
}
|
|
@@ -4,7 +4,7 @@ import { capMessageText, formatTimestamp } from './console/shared.js';
|
|
|
4
4
|
import { capForDisplay } from './longValues.js';
|
|
5
5
|
import { failureReason, formatRequestStatus, getRequestState, } from './requestStatus.js';
|
|
6
6
|
import { OutputFormatter, truncateUrl, truncateText } from '../formatting.js';
|
|
7
|
-
import { moreCharsNote, withPageCrashedNote } from '../messages/commands.js';
|
|
7
|
+
import { downloadsSummary, moreCharsNote, withPageCrashedNote } from '../messages/commands.js';
|
|
8
8
|
import { consoleDroppedNote } from '../messages/consoleMessages.js';
|
|
9
9
|
import { networkEvictedNote } from '../messages/networkMessages.js';
|
|
10
10
|
import { PREVIEW_EMPTY_STATES, PREVIEW_HEADERS, compactTipsMessage, verboseCommandsMessage, } from '../messages/preview.js';
|
|
@@ -76,6 +76,7 @@ export function buildPreviewJsonData(output, options) {
|
|
|
76
76
|
...(output.partial !== undefined && { partial: output.partial }),
|
|
77
77
|
...(output.totals && { totals: output.totals }),
|
|
78
78
|
...(output.pageCrashedAt !== undefined && { pageCrashedAt: output.pageCrashedAt }),
|
|
79
|
+
...(output.downloads && { downloads: output.downloads }),
|
|
79
80
|
...(pick('network') && output.data.network && { network: last(output.data.network) }),
|
|
80
81
|
...(pick('console') &&
|
|
81
82
|
output.data.console && {
|
|
@@ -194,6 +195,8 @@ function formatPreviewCompact(output, options) {
|
|
|
194
195
|
fmt.blank();
|
|
195
196
|
}
|
|
196
197
|
}
|
|
198
|
+
if (output.downloads?.length)
|
|
199
|
+
fmt.text(`Downloads: ${downloadsSummary(output.downloads)}`).blank();
|
|
197
200
|
if (!options.follow) {
|
|
198
201
|
fmt.tip(compactTipsMessage());
|
|
199
202
|
}
|
|
@@ -296,6 +299,9 @@ function formatPreviewVerbose(output, options) {
|
|
|
296
299
|
fmt.blank();
|
|
297
300
|
}
|
|
298
301
|
}
|
|
302
|
+
if (output.downloads?.length) {
|
|
303
|
+
fmt.keyValue('Downloads', downloadsSummary(output.downloads), 18).blank();
|
|
304
|
+
}
|
|
299
305
|
if (!options.follow) {
|
|
300
306
|
fmt.tip(verboseCommandsMessage());
|
|
301
307
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describeRunningChrome } from '../../session/chrome.js';
|
|
2
2
|
import { calculateDuration, formatTimeAgo } from '../../session/statusData.js';
|
|
3
3
|
import { OutputFormatter } from '../formatting.js';
|
|
4
|
-
import { colorSchemeLabel, sessionActiveLine } from '../messages/commands.js';
|
|
4
|
+
import { colorSchemeLabel, downloadsSummary, sessionActiveLine } from '../messages/commands.js';
|
|
5
5
|
import { networkEvictedNote } from '../messages/networkMessages.js';
|
|
6
6
|
import { lastSessionEndText } from '../messages/session.js';
|
|
7
7
|
import { noActiveSessionMessage, sessionCommand } from '../messages/sessionCommand.js';
|
|
@@ -62,6 +62,11 @@ export function formatSessionStatus(metadata, pid, activity, pageState, verbose
|
|
|
62
62
|
if (activity.lastConsoleMessageAt) {
|
|
63
63
|
fmt.keyValue(' Last Message', formatTimeAgo(activity.lastConsoleMessageAt), 18);
|
|
64
64
|
}
|
|
65
|
+
const downloadRows = [
|
|
66
|
+
...(activity.downloads?.length ? [downloadsSummary(activity.downloads)] : []),
|
|
67
|
+
...(activity.downloadsWarning ? [`⚠ ${activity.downloadsWarning}`] : []),
|
|
68
|
+
];
|
|
69
|
+
downloadRows.forEach((row, index) => index === 0 ? fmt.keyValue('Downloads', row, 18) : fmt.text(`${' '.repeat(18)}${row}`));
|
|
65
70
|
}
|
|
66
71
|
fmt.blank().text('Collectors').separator('━', 50);
|
|
67
72
|
const activeTelemetry = metadata.activeTelemetry ?? ['network', 'console', 'dom'];
|