browserscale-ts 1.8.0 → 1.10.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/dist/client.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { Transport } from "@connectrpc/connect";
2
2
  import type { Locator } from "./locator.ts";
3
- import type { DOMResult, DragResult, ElementResult, EvaluateResult, InterceptedRequest, InterceptedResponse, InspectResult, NavigateResult, NetworkCaptureOptions, PageInfo, ScreenshotResult, ReadCanvasResult, SelectOptionResult, WaitResult, IceServer, StreamAnswer, ReactionInfo } from "./types.ts";
3
+ import type { DOMResult, DragResult, ElementResult, EvaluateResult, InterceptedRequest, InterceptedResponse, InspectResult, NavigateResult, NetworkBody, NetworkBodyRange, NetworkCaptureOptions, PageInfo, ScreenshotResult, ReadCanvasResult, SelectOptionResult, SessionUsage, WaitResult, IceServer, StreamAnswer, ReactionInfo } from "./types.ts";
4
4
  import type { ClickOpts, FillOpts, GetDOMOpts, GetObservationOpts, LoadHTMLOpts, NavigateOpts, ReactionOpts, ScreenshotOpts, ReadCanvasOpts, SelectOpts, WaitOpts } from "./options.ts";
5
5
  import type { HeaderModification, RequestPattern } from "./network.ts";
6
6
  import { NetworkCapture, type NetworkExchangeHandler } from "./network-capture.ts";
@@ -25,7 +25,7 @@ export declare class CloudBrowser {
25
25
  private readonly _transport;
26
26
  private readonly _fingerprint;
27
27
  private readonly _stopFn?;
28
- constructor(transport: Transport, sessionId: string, apiKey: string, fingerprint: string, stopFn?: () => Promise<void>);
28
+ constructor(transport: Transport, sessionId: string, apiKey: string, fingerprint: string, stopFn?: () => Promise<SessionUsage | undefined>);
29
29
  /** Returns the unique server-assigned id for this browser session. */
30
30
  getSessionId(): string;
31
31
  /** Returns the API key used to rent this session. */
@@ -40,13 +40,21 @@ export declare class CloudBrowser {
40
40
  * {@link createWebSocketBrowser} the rental stays untouched; only the
41
41
  * transport is closed.
42
42
  *
43
+ * @returns SessionUsage what the session consumed over its whole life, read
44
+ * as it was torn down - no {@link CloudBrowser.getUsage} call is needed
45
+ * before stopping. Undefined when the server could not report it, and for
46
+ * a handle that owns no rental.
47
+ *
43
48
  * Rejects only when the stop API or the transport close fails. The session is
44
49
  * released either way; retrying a stop is safe.
45
50
  *
46
51
  * @example
47
- * await browser.stopBrowser();
52
+ * const usage = await browser.stopBrowser();
53
+ * if (usage) {
54
+ * console.log(`ran ${usage.wallTime}s, ${usage.cpuTime}s CPU, peak ${usage.peakMemory} bytes`);
55
+ * }
48
56
  */
49
- stopBrowser(): Promise<void>;
57
+ stopBrowser(): Promise<SessionUsage | undefined>;
50
58
  /**
51
59
  * Changes the runtime proxy for this session.
52
60
  *
@@ -86,6 +94,27 @@ export declare class CloudBrowser {
86
94
  * for (const p of pages) console.log(p.url, p.title);
87
95
  */
88
96
  getPages(): Promise<PageInfo[]>;
97
+ /**
98
+ * Reports what this session's browser has consumed so far.
99
+ *
100
+ * Everything but minMemory and averageMemory only ever grows, so polling and
101
+ * diffing two readings gives the cost of what ran in between. The final
102
+ * figures need no call of their own: {@link CloudBrowser.stopBrowser} resolves
103
+ * with them.
104
+ *
105
+ * @returns SessionUsage as of now
106
+ *
107
+ * Rejects only on a transport failure - a dead session, a broken connection.
108
+ * This call has no semantic failure of its own, so there are no error codes to
109
+ * branch on.
110
+ *
111
+ * @example
112
+ * const before = await browser.getUsage();
113
+ * await browser.navigate("https://example.com");
114
+ * const after = await browser.getUsage();
115
+ * console.log(`navigation cost ${after.cpuTime - before.cpuTime}s of CPU`);
116
+ */
117
+ getUsage(): Promise<SessionUsage>;
89
118
  /**
90
119
  * Navigates the page to url.
91
120
  *
@@ -817,6 +846,55 @@ export declare class CloudBrowser {
817
846
  * is a no-op rather than a failure, so there are no error codes to branch on.
818
847
  */
819
848
  stopNetworkCapture(): Promise<boolean>;
849
+ /**
850
+ * Reads a whole captured body: an exchange's `requestBodyId` or
851
+ * `responseBodyId`.
852
+ *
853
+ * Bodies are stored by the browser, not sent with the exchange, so reading
854
+ * one is a separate call. They stay readable after the capture stops, until
855
+ * the session ends.
856
+ *
857
+ * The body is fetched in ranges and assembled in memory. For very large
858
+ * bodies, use {@link CloudBrowser.readNetworkBodyRange} to process them piece
859
+ * by piece.
860
+ *
861
+ * @param bodyId - `requestBodyId` or `responseBodyId` from a NetworkExchange
862
+ *
863
+ * @returns the body and whether the kept body is shorter than the original
864
+ *
865
+ * @throws not_found - no body with this id was kept in this session
866
+ * @throws evicted - the body was dropped to stay within the session's storage
867
+ * quota; read bodies sooner, or narrow bodyPatterns
868
+ * @throws unavailable - the body could not be stored or read back
869
+ *
870
+ * @example
871
+ * const capture = await browser.captureNetwork(
872
+ * { patterns: ["*\/api/*"], bodies: "text" },
873
+ * (ex) => {
874
+ * if (!ex.responseBodyId) return;
875
+ * void browser.readNetworkBody(ex.responseBodyId).then(({ data }) =>
876
+ * console.log(ex.url, new TextDecoder().decode(data)),
877
+ * );
878
+ * },
879
+ * );
880
+ */
881
+ readNetworkBody(bodyId: string): Promise<NetworkBody>;
882
+ /**
883
+ * Reads up to `length` bytes of a captured body starting at `offset`.
884
+ *
885
+ * A single call returns at most 2 MiB; omit `length` to read that much. Loop
886
+ * on `offset + data.length` until it reaches `totalSize` to stream a large
887
+ * body.
888
+ *
889
+ * @param bodyId - `requestBodyId` or `responseBodyId` from a NetworkExchange
890
+ * @param offset - first byte to read (default 0)
891
+ * @param length - bytes to read (default and maximum 2 MiB)
892
+ *
893
+ * @returns NetworkBodyRange with the bytes and the body's total size
894
+ *
895
+ * @inheritDoc CloudBrowser.readNetworkBody
896
+ */
897
+ readNetworkBodyRange(bodyId: string, offset?: number, length?: number): Promise<NetworkBodyRange>;
820
898
  /**
821
899
  * Subscribes to the session's capture without arming one, for reading a
822
900
  * capture that {@link CloudBrowser.startNetworkCapture} armed elsewhere.
package/dist/client.js CHANGED
@@ -2,12 +2,12 @@ import { createClient } from "@connectrpc/connect";
2
2
  import { create } from "@bufbuild/protobuf";
3
3
  import { Browser,
4
4
  // request / response schemas
5
- SetProxyRequestSchema, GetPagesRequestSchema, NavigateRequestSchema, LoadHTMLRequestSchema, EvaluateRequestSchema, WaitForAnyParamsSchema, WaitConditionSchema, ClickRequestSchema, FillRequestSchema, MoveToRequestSchema, ScrollToRequestSchema, DragRequestSchema, SelectOptionRequestSchema, GetDOMRequestSchema, GetDOMHashRequestSchema, GetObservationRequestSchema, ScreenshotRequestSchema, ReadCanvasRequestSchema, SetBlockListRequestSchema, SetStaticPathsRequestSchema, WaitForAnyRequestRequestSchema, WaitForAnyResponseRequestSchema, ModifyRequestRequestSchema, GetCookiesRequestSchema, SetCookiesRequestSchema, ClearCookiesRequestSchema, GetStorageRequestSchema, SetStorageRequestSchema, ClearStorageRequestSchema, GetAuthSessionRequestSchema, SetAuthSessionRequestSchema, InspectAtPositionRequestSchema, HighlightNodeRequestSchema, InsertTextRequestSchema, TypeRequestSchema, PressKeyRequestSchema, ReleaseKeyRequestSchema, GetSelectionRequestSchema, SolveCaptchaRequestSchema, GetStreamConfigRequestSchema, StartStreamRequestSchema, StopStreamRequestSchema, AddReactionRequestSchema, RemoveReactionRequestSchema, ListReactionsRequestSchema, StartNetworkCaptureRequestSchema, StopNetworkCaptureRequestSchema, StartDomMirrorRequestSchema, StopDomMirrorRequestSchema, GetDomChildrenRequestSchema, ReleaseDomSubtreeRequestSchema, RevealDomNodeRequestSchema, GetDomRevisionRequestSchema, StreamDomEventsRequestSchema, StreamNetworkExchangesRequestSchema, RunScriptRequestSchema, StartScriptRequestSchema, StopScriptsRequestSchema, ListScriptRunsRequestSchema, StreamScriptEventsRequestSchema, } from "./gen/wrc_pb.js";
5
+ SetProxyRequestSchema, GetPagesRequestSchema, GetUsageRequestSchema, NavigateRequestSchema, LoadHTMLRequestSchema, EvaluateRequestSchema, WaitForAnyParamsSchema, WaitConditionSchema, ClickRequestSchema, FillRequestSchema, MoveToRequestSchema, ScrollToRequestSchema, DragRequestSchema, SelectOptionRequestSchema, GetDOMRequestSchema, GetDOMHashRequestSchema, GetObservationRequestSchema, ScreenshotRequestSchema, ReadCanvasRequestSchema, SetBlockListRequestSchema, SetStaticPathsRequestSchema, WaitForAnyRequestRequestSchema, WaitForAnyResponseRequestSchema, ModifyRequestRequestSchema, GetCookiesRequestSchema, SetCookiesRequestSchema, ClearCookiesRequestSchema, GetStorageRequestSchema, SetStorageRequestSchema, ClearStorageRequestSchema, GetAuthSessionRequestSchema, SetAuthSessionRequestSchema, InspectAtPositionRequestSchema, HighlightNodeRequestSchema, InsertTextRequestSchema, TypeRequestSchema, PressKeyRequestSchema, ReleaseKeyRequestSchema, GetSelectionRequestSchema, SolveCaptchaRequestSchema, GetStreamConfigRequestSchema, StartStreamRequestSchema, StopStreamRequestSchema, AddReactionRequestSchema, RemoveReactionRequestSchema, ListReactionsRequestSchema, StartNetworkCaptureRequestSchema, StopNetworkCaptureRequestSchema, StartDomMirrorRequestSchema, StopDomMirrorRequestSchema, GetDomChildrenRequestSchema, ReleaseDomSubtreeRequestSchema, RevealDomNodeRequestSchema, GetDomRevisionRequestSchema, StreamDomEventsRequestSchema, StreamNetworkExchangesRequestSchema, GetNetworkBodyRequestSchema, RunScriptRequestSchema, StartScriptRequestSchema, StopScriptsRequestSchema, ListScriptRunsRequestSchema, StreamScriptEventsRequestSchema, } from "./gen/wrc_pb.js";
6
6
  import { BrowserScaleError, CommandError, throwCommandError } from "./errors.js";
7
7
  import { NetworkCapture } from "./network-capture.js";
8
8
  import { ScriptFollow, ScriptRun, scriptLogEntryFromProto, } from "./scripts.js";
9
9
  import { DomMirror, } from "./dom-mirror.js";
10
- import { authSessionFromProto, authSessionToProto, cookieParamFromProto, cookieParamsToProto, elementFields, headerModsToProto, headersToProto, interceptedRequestFromProto, interceptedResponseFromProto, pageInfoFromProto, rectFromProto, splitRequestPatterns, storageEntriesToProto, storageEntryFromProto, unwrapClick, unwrapDrag, unwrapFill, unwrapMove, unwrapScroll, unwrapSelect, unwrapWait, } from "./internal/convert.js";
10
+ import { authSessionFromProto, authSessionToProto, cookieParamFromProto, cookieParamsToProto, elementFields, headerModsToProto, headersToProto, interceptedRequestFromProto, interceptedResponseFromProto, pageInfoFromProto, rectFromProto, sessionUsageFromProto, splitRequestPatterns, storageEntriesToProto, storageEntryFromProto, unwrapClick, unwrapDrag, unwrapFill, unwrapMove, unwrapScroll, unwrapSelect, unwrapWait, } from "./internal/convert.js";
11
11
  /**
12
12
  * CloudBrowser is the SDK-side handle for an active browserscale browser session.
13
13
  *
@@ -49,16 +49,25 @@ export class CloudBrowser {
49
49
  * {@link createWebSocketBrowser} the rental stays untouched; only the
50
50
  * transport is closed.
51
51
  *
52
+ * @returns SessionUsage what the session consumed over its whole life, read
53
+ * as it was torn down - no {@link CloudBrowser.getUsage} call is needed
54
+ * before stopping. Undefined when the server could not report it, and for
55
+ * a handle that owns no rental.
56
+ *
52
57
  * Rejects only when the stop API or the transport close fails. The session is
53
58
  * released either way; retrying a stop is safe.
54
59
  *
55
60
  * @example
56
- * await browser.stopBrowser();
61
+ * const usage = await browser.stopBrowser();
62
+ * if (usage) {
63
+ * console.log(`ran ${usage.wallTime}s, ${usage.cpuTime}s CPU, peak ${usage.peakMemory} bytes`);
64
+ * }
57
65
  */
58
66
  async stopBrowser() {
59
67
  if (this._stopFn) {
60
- await this._stopFn();
68
+ return await this._stopFn();
61
69
  }
70
+ return undefined;
62
71
  }
63
72
  // ──────────────────────────────────────────────────────────────────
64
73
  // Context-level
@@ -124,6 +133,37 @@ export class CloudBrowser {
124
133
  throwCommandError("getPages", resp.error);
125
134
  return resp.pages.map(pageInfoFromProto);
126
135
  }
136
+ /**
137
+ * Reports what this session's browser has consumed so far.
138
+ *
139
+ * Everything but minMemory and averageMemory only ever grows, so polling and
140
+ * diffing two readings gives the cost of what ran in between. The final
141
+ * figures need no call of their own: {@link CloudBrowser.stopBrowser} resolves
142
+ * with them.
143
+ *
144
+ * @returns SessionUsage as of now
145
+ *
146
+ * Rejects only on a transport failure - a dead session, a broken connection.
147
+ * This call has no semantic failure of its own, so there are no error codes to
148
+ * branch on.
149
+ *
150
+ * @example
151
+ * const before = await browser.getUsage();
152
+ * await browser.navigate("https://example.com");
153
+ * const after = await browser.getUsage();
154
+ * console.log(`navigation cost ${after.cpuTime - before.cpuTime}s of CPU`);
155
+ */
156
+ async getUsage() {
157
+ const resp = await this.client.getUsage(create(GetUsageRequestSchema, {
158
+ sessionId: this.sessionId,
159
+ apiKey: this.apiKey,
160
+ }));
161
+ throwCommandError("getUsage", resp.error);
162
+ if (!resp.usage) {
163
+ throw new BrowserScaleError("getUsage: the server sent no usage");
164
+ }
165
+ return sessionUsageFromProto(resp.usage);
166
+ }
127
167
  // ──────────────────────────────────────────────────────────────────
128
168
  // Page navigation / content
129
169
  // ──────────────────────────────────────────────────────────────────
@@ -1236,6 +1276,94 @@ export class CloudBrowser {
1236
1276
  throwCommandError("stopNetworkCapture", resp.error);
1237
1277
  return resp.stopped;
1238
1278
  }
1279
+ /**
1280
+ * Reads a whole captured body: an exchange's `requestBodyId` or
1281
+ * `responseBodyId`.
1282
+ *
1283
+ * Bodies are stored by the browser, not sent with the exchange, so reading
1284
+ * one is a separate call. They stay readable after the capture stops, until
1285
+ * the session ends.
1286
+ *
1287
+ * The body is fetched in ranges and assembled in memory. For very large
1288
+ * bodies, use {@link CloudBrowser.readNetworkBodyRange} to process them piece
1289
+ * by piece.
1290
+ *
1291
+ * @param bodyId - `requestBodyId` or `responseBodyId` from a NetworkExchange
1292
+ *
1293
+ * @returns the body and whether the kept body is shorter than the original
1294
+ *
1295
+ * @throws not_found - no body with this id was kept in this session
1296
+ * @throws evicted - the body was dropped to stay within the session's storage
1297
+ * quota; read bodies sooner, or narrow bodyPatterns
1298
+ * @throws unavailable - the body could not be stored or read back
1299
+ *
1300
+ * @example
1301
+ * const capture = await browser.captureNetwork(
1302
+ * { patterns: ["*\/api/*"], bodies: "text" },
1303
+ * (ex) => {
1304
+ * if (!ex.responseBodyId) return;
1305
+ * void browser.readNetworkBody(ex.responseBodyId).then(({ data }) =>
1306
+ * console.log(ex.url, new TextDecoder().decode(data)),
1307
+ * );
1308
+ * },
1309
+ * );
1310
+ */
1311
+ async readNetworkBody(bodyId) {
1312
+ const parts = [];
1313
+ let offset = 0;
1314
+ for (;;) {
1315
+ const r = await this.readNetworkBodyRange(bodyId, offset);
1316
+ parts.push(r.data);
1317
+ offset += r.data.length;
1318
+ if (r.data.length === 0 || offset >= r.totalSize) {
1319
+ if (parts.length === 1)
1320
+ return { data: r.data, truncated: r.truncated };
1321
+ const data = new Uint8Array(offset);
1322
+ let at = 0;
1323
+ for (const p of parts) {
1324
+ data.set(p, at);
1325
+ at += p.length;
1326
+ }
1327
+ return { data, truncated: r.truncated };
1328
+ }
1329
+ }
1330
+ }
1331
+ /**
1332
+ * Reads up to `length` bytes of a captured body starting at `offset`.
1333
+ *
1334
+ * A single call returns at most 2 MiB; omit `length` to read that much. Loop
1335
+ * on `offset + data.length` until it reaches `totalSize` to stream a large
1336
+ * body.
1337
+ *
1338
+ * @param bodyId - `requestBodyId` or `responseBodyId` from a NetworkExchange
1339
+ * @param offset - first byte to read (default 0)
1340
+ * @param length - bytes to read (default and maximum 2 MiB)
1341
+ *
1342
+ * @returns NetworkBodyRange with the bytes and the body's total size
1343
+ *
1344
+ * @inheritDoc CloudBrowser.readNetworkBody
1345
+ */
1346
+ async readNetworkBodyRange(bodyId, offset, length) {
1347
+ if (!bodyId) {
1348
+ throw new BrowserScaleError("browserscale.readNetworkBody: bodyId must not be empty");
1349
+ }
1350
+ const req = create(GetNetworkBodyRequestSchema, {
1351
+ sessionId: this.sessionId,
1352
+ apiKey: this.apiKey,
1353
+ bodyId,
1354
+ });
1355
+ if (offset && offset > 0)
1356
+ req.offset = BigInt(Math.floor(offset));
1357
+ if (length && length > 0)
1358
+ req.length = BigInt(Math.floor(length));
1359
+ const resp = await this.client.getNetworkBody(req);
1360
+ throwCommandError("getNetworkBody", resp.error);
1361
+ return {
1362
+ data: resp.data,
1363
+ totalSize: Number(resp.totalSize),
1364
+ truncated: resp.truncated,
1365
+ };
1366
+ }
1239
1367
  /**
1240
1368
  * Subscribes to the session's capture without arming one, for reading a
1241
1369
  * capture that {@link CloudBrowser.startNetworkCapture} armed elsewhere.
@@ -1453,6 +1453,113 @@ export type GetPagesResponse = Message<"browserscale.v1.GetPagesResponse"> & {
1453
1453
  * Use `create(GetPagesResponseSchema)` to create a new message.
1454
1454
  */
1455
1455
  export declare const GetPagesResponseSchema: GenMessage<GetPagesResponse>;
1456
+ /**
1457
+ * What a session's browser has consumed since the session started: the CPU
1458
+ * time and memory of every process that rendered its pages — main frames,
1459
+ * cross-site iframes and the workers they host — including processes that have
1460
+ * since exited. Work done on the session's behalf in processes it shares with
1461
+ * other sessions is not included. The stop endpoint returns the same object, in
1462
+ * its JSON form, as the session's final usage.
1463
+ *
1464
+ * @generated from message browserscale.v1.SessionUsage
1465
+ */
1466
+ export type SessionUsage = Message<"browserscale.v1.SessionUsage"> & {
1467
+ /**
1468
+ * Real time since the session's browser was created, in seconds.
1469
+ *
1470
+ * @generated from field: double wall_time = 1;
1471
+ */
1472
+ wallTime: number;
1473
+ /**
1474
+ * CPU time in seconds, user plus kernel. Only time a thread actually ran on a
1475
+ * core counts; waiting and idling do not.
1476
+ *
1477
+ * @generated from field: double cpu_time = 2;
1478
+ */
1479
+ cpuTime: number;
1480
+ /**
1481
+ * The least memory the session held once its browser was ready, in bytes.
1482
+ * Sampled about once a second.
1483
+ *
1484
+ * @generated from field: double min_memory = 3;
1485
+ */
1486
+ minMemory: number;
1487
+ /**
1488
+ * The memory held, averaged over wall_time, in bytes; average_memory *
1489
+ * wall_time is the memory-time used. Sampled about once a second, so short
1490
+ * spikes count toward the peak but barely toward the average.
1491
+ *
1492
+ * @generated from field: double average_memory = 4;
1493
+ */
1494
+ averageMemory: number;
1495
+ /**
1496
+ * The most memory held at any one moment, in bytes.
1497
+ *
1498
+ * @generated from field: double peak_memory = 5;
1499
+ */
1500
+ peakMemory: number;
1501
+ /**
1502
+ * Renderer processes that hosted at least one of the session's frames: one
1503
+ * per site its pages and cross-site iframes needed.
1504
+ *
1505
+ * @generated from field: int32 renderers_used = 6;
1506
+ */
1507
+ renderersUsed: number;
1508
+ /**
1509
+ * Child frames created in the session's pages, whether or not they got a
1510
+ * process of their own.
1511
+ *
1512
+ * @generated from field: int32 frames_created = 7;
1513
+ */
1514
+ framesCreated: number;
1515
+ };
1516
+ /**
1517
+ * Describes the message browserscale.v1.SessionUsage.
1518
+ * Use `create(SessionUsageSchema)` to create a new message.
1519
+ */
1520
+ export declare const SessionUsageSchema: GenMessage<SessionUsage>;
1521
+ /**
1522
+ * @generated from message browserscale.v1.GetUsageRequest
1523
+ */
1524
+ export type GetUsageRequest = Message<"browserscale.v1.GetUsageRequest"> & {
1525
+ /**
1526
+ * @generated from field: string session_id = 1;
1527
+ */
1528
+ sessionId: string;
1529
+ /**
1530
+ * @generated from field: string api_key = 2;
1531
+ */
1532
+ apiKey: string;
1533
+ };
1534
+ /**
1535
+ * Describes the message browserscale.v1.GetUsageRequest.
1536
+ * Use `create(GetUsageRequestSchema)` to create a new message.
1537
+ */
1538
+ export declare const GetUsageRequestSchema: GenMessage<GetUsageRequest>;
1539
+ /**
1540
+ * @generated from message browserscale.v1.GetUsageResponse
1541
+ */
1542
+ export type GetUsageResponse = Message<"browserscale.v1.GetUsageResponse"> & {
1543
+ /**
1544
+ * @generated from field: browserscale.v1.SessionUsage usage = 1;
1545
+ */
1546
+ usage?: SessionUsage;
1547
+ /**
1548
+ * Always true today: this command has no semantic failure of its own.
1549
+ *
1550
+ * @generated from field: bool success = 2;
1551
+ */
1552
+ success: boolean;
1553
+ /**
1554
+ * @generated from field: optional browserscale.v1.CommandError error = 3;
1555
+ */
1556
+ error?: CommandError;
1557
+ };
1558
+ /**
1559
+ * Describes the message browserscale.v1.GetUsageResponse.
1560
+ * Use `create(GetUsageResponseSchema)` to create a new message.
1561
+ */
1562
+ export declare const GetUsageResponseSchema: GenMessage<GetUsageResponse>;
1456
1563
  /**
1457
1564
  * @generated from message browserscale.v1.NavigateRequest
1458
1565
  */
@@ -2255,14 +2362,15 @@ export type StartNetworkCaptureRequest = Message<"browserscale.v1.StartNetworkCa
2255
2362
  */
2256
2363
  patterns: string[];
2257
2364
  /**
2258
- * Response body capture: "none" (default), "text" or "all".
2365
+ * Response body capture: "none" (default), "text" or "all". Request bodies
2366
+ * are kept whenever a request has one.
2259
2367
  *
2260
2368
  * @generated from field: string bodies = 4;
2261
2369
  */
2262
2370
  bodies: string;
2263
2371
  /**
2264
- * Narrows body capture further; empty applies `bodies` to every captured
2265
- * exchange.
2372
+ * Narrows response body capture further; empty applies `bodies` to every
2373
+ * captured exchange.
2266
2374
  *
2267
2375
  * @generated from field: repeated string body_patterns = 5;
2268
2376
  */
@@ -2433,13 +2541,22 @@ export type NetworkExchange = Message<"browserscale.v1.NetworkExchange"> & {
2433
2541
  */
2434
2542
  requestHeadersAreWire: boolean;
2435
2543
  /**
2436
- * Inline request body, capped by the browser. File and streamed uploads are
2437
- * not included; request_body_truncated is set when anything was left out.
2544
+ * Bodies never travel with the exchange. Pass the id to GetNetworkBody to
2545
+ * read the bytes; empty when the request had no body.
2438
2546
  *
2439
- * @generated from field: bytes request_body = 12;
2547
+ * @generated from field: string request_body_id = 28;
2440
2548
  */
2441
- requestBody: Uint8Array;
2549
+ requestBodyId: string;
2442
2550
  /**
2551
+ * Bytes kept for the request body.
2552
+ *
2553
+ * @generated from field: int64 request_body_size = 29;
2554
+ */
2555
+ requestBodySize: bigint;
2556
+ /**
2557
+ * True when part of the request body is missing: it hit the per-body cap, or
2558
+ * it was a file or streamed upload, which are not kept.
2559
+ *
2443
2560
  * @generated from field: bool request_body_truncated = 13;
2444
2561
  */
2445
2562
  requestBodyTruncated: boolean;
@@ -2486,21 +2603,25 @@ export type NetworkExchange = Message<"browserscale.v1.NetworkExchange"> & {
2486
2603
  */
2487
2604
  responseHeadersAreWire: boolean;
2488
2605
  /**
2489
- * Populated only when body capture was requested and applied. Bodies cross
2490
- * the browser boundary as text, so binary content (bodies="all" on an image)
2491
- * does not arrive intact.
2606
+ * Pass to GetNetworkBody to read the response body. Empty when body capture
2607
+ * did not apply to this exchange.
2492
2608
  *
2493
- * @generated from field: bytes response_body = 23;
2609
+ * @generated from field: string response_body_id = 30;
2494
2610
  */
2495
- responseBody: Uint8Array;
2611
+ responseBodyId: string;
2496
2612
  /**
2497
- * @generated from field: bool response_body_truncated = 24;
2613
+ * Bytes kept for the response body, after content decoding.
2614
+ *
2615
+ * @generated from field: int64 response_body_size = 31;
2498
2616
  */
2499
- responseBodyTruncated: boolean;
2617
+ responseBodySize: bigint;
2500
2618
  /**
2501
- * @generated from field: bool response_body_captured = 25;
2619
+ * True when the kept body is shorter than the one the page received: it hit
2620
+ * the per-body cap, the load ended early, or the host's disk fell behind.
2621
+ *
2622
+ * @generated from field: bool response_body_truncated = 24;
2502
2623
  */
2503
- responseBodyCaptured: boolean;
2624
+ responseBodyTruncated: boolean;
2504
2625
  /**
2505
2626
  * @generated from field: int64 encoded_data_length = 26;
2506
2627
  */
@@ -2517,6 +2638,84 @@ export type NetworkExchange = Message<"browserscale.v1.NetworkExchange"> & {
2517
2638
  * Use `create(NetworkExchangeSchema)` to create a new message.
2518
2639
  */
2519
2640
  export declare const NetworkExchangeSchema: GenMessage<NetworkExchange>;
2641
+ /**
2642
+ * @generated from message browserscale.v1.GetNetworkBodyRequest
2643
+ */
2644
+ export type GetNetworkBodyRequest = Message<"browserscale.v1.GetNetworkBodyRequest"> & {
2645
+ /**
2646
+ * @generated from field: string session_id = 1;
2647
+ */
2648
+ sessionId: string;
2649
+ /**
2650
+ * @generated from field: string api_key = 2;
2651
+ */
2652
+ apiKey: string;
2653
+ /**
2654
+ * request_body_id or response_body_id of an exchange.
2655
+ *
2656
+ * @generated from field: string body_id = 3;
2657
+ */
2658
+ bodyId: string;
2659
+ /**
2660
+ * First byte to read; 0 when unset.
2661
+ *
2662
+ * @generated from field: optional int64 offset = 4;
2663
+ */
2664
+ offset?: bigint;
2665
+ /**
2666
+ * Bytes to read. Unset or larger than the per-call limit (2 MiB) reads up to
2667
+ * that limit; larger bodies are read in ranges.
2668
+ *
2669
+ * @generated from field: optional int64 length = 5;
2670
+ */
2671
+ length?: bigint;
2672
+ };
2673
+ /**
2674
+ * Describes the message browserscale.v1.GetNetworkBodyRequest.
2675
+ * Use `create(GetNetworkBodyRequestSchema)` to create a new message.
2676
+ */
2677
+ export declare const GetNetworkBodyRequestSchema: GenMessage<GetNetworkBodyRequest>;
2678
+ /**
2679
+ * @generated from message browserscale.v1.GetNetworkBodyResponse
2680
+ */
2681
+ export type GetNetworkBodyResponse = Message<"browserscale.v1.GetNetworkBodyResponse"> & {
2682
+ /**
2683
+ * The bytes read; empty past the end of the body.
2684
+ *
2685
+ * @generated from field: bytes data = 1;
2686
+ */
2687
+ data: Uint8Array;
2688
+ /**
2689
+ * Bytes kept for this body in total.
2690
+ *
2691
+ * @generated from field: int64 total_size = 2;
2692
+ */
2693
+ totalSize: bigint;
2694
+ /**
2695
+ * Same as the exchange's truncated flag for this body.
2696
+ *
2697
+ * @generated from field: bool truncated = 3;
2698
+ */
2699
+ truncated: boolean;
2700
+ /**
2701
+ * False iff the body cannot be read; then error carries the code:
2702
+ * "not_found" — no body with this id was kept in this session.
2703
+ * "evicted" — the body was dropped to stay within the session's quota.
2704
+ * "unavailable" — the body could not be stored or read back.
2705
+ *
2706
+ * @generated from field: bool success = 4;
2707
+ */
2708
+ success: boolean;
2709
+ /**
2710
+ * @generated from field: optional browserscale.v1.CommandError error = 5;
2711
+ */
2712
+ error?: CommandError;
2713
+ };
2714
+ /**
2715
+ * Describes the message browserscale.v1.GetNetworkBodyResponse.
2716
+ * Use `create(GetNetworkBodyResponseSchema)` to create a new message.
2717
+ */
2718
+ export declare const GetNetworkBodyResponseSchema: GenMessage<GetNetworkBodyResponse>;
2520
2719
  /**
2521
2720
  * @generated from message browserscale.v1.GetCookiesRequest
2522
2721
  */
@@ -4641,6 +4840,17 @@ export declare const Browser: GenService<{
4641
4840
  input: typeof GetPagesRequestSchema;
4642
4841
  output: typeof GetPagesResponseSchema;
4643
4842
  };
4843
+ /**
4844
+ * What the session has consumed so far. The final figures need no call of
4845
+ * their own: the stop endpoint returns them.
4846
+ *
4847
+ * @generated from rpc browserscale.v1.Browser.GetUsage
4848
+ */
4849
+ getUsage: {
4850
+ methodKind: "unary";
4851
+ input: typeof GetUsageRequestSchema;
4852
+ output: typeof GetUsageResponseSchema;
4853
+ };
4644
4854
  /**
4645
4855
  * Page navigation / content
4646
4856
  *
@@ -4803,7 +5013,8 @@ export declare const Browser: GenService<{
4803
5013
  * Network capture — every matching request in the session, reported as it
4804
5014
  * completes. Start/Stop are session state; the stream is a separate
4805
5015
  * subscription, so a capture survives a reader reconnect and two readers
4806
- * can watch one capture.
5016
+ * can watch one capture. Exchanges only name their bodies; GetNetworkBody
5017
+ * reads them.
4807
5018
  *
4808
5019
  * @generated from rpc browserscale.v1.Browser.StartNetworkCapture
4809
5020
  */
@@ -4828,6 +5039,14 @@ export declare const Browser: GenService<{
4828
5039
  input: typeof StreamNetworkExchangesRequestSchema;
4829
5040
  output: typeof NetworkExchangeEventSchema;
4830
5041
  };
5042
+ /**
5043
+ * @generated from rpc browserscale.v1.Browser.GetNetworkBody
5044
+ */
5045
+ getNetworkBody: {
5046
+ methodKind: "unary";
5047
+ input: typeof GetNetworkBodyRequestSchema;
5048
+ output: typeof GetNetworkBodyResponseSchema;
5049
+ };
4831
5050
  /**
4832
5051
  * Cookies
4833
5052
  *