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/index.d.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import { CloudBrowser } from "./client.ts";
2
2
  import { BrowserConfig } from "./config.ts";
3
- import type { BrowserInfo } from "./types.ts";
3
+ import type { BrowserInfo, SessionUsage } from "./types.ts";
4
4
  export { CloudBrowser } from "./client.ts";
5
5
  export { BrowserConfig } from "./config.ts";
6
6
  export { Locator, css, js, node, at, AllFrames } from "./locator.ts";
7
7
  export { DefaultWaitTimeoutMs, DefaultVisible, DefaultSteadyMs, } from "./defaults.ts";
8
8
  export { BrowserScaleError, CommandError, ClickError, FillError, DragError, ScrollError, MoveError, SelectOptionError, WaitError, } from "./errors.ts";
9
- export type { Rect, FrameInfo, PageInfo, Header, InterceptedRequest, InterceptedResponse, NetworkExchange, NetworkResourceType, NetworkServedFrom, NetworkBodies, NetworkCaptureOptions, WaitResult, WaitConditionStatus, OccluderInfo, ElementRef, NavigateResult, EvaluateResult, ElementResult, DragResult, SelectOptionResult, ScreenshotResult, ReadCanvasResult, DOMResult, InspectResult, RentResponse, BrowserInfo, IceServer, StreamAnswer, ReactionInfo, } from "./types.ts";
9
+ export type { Rect, FrameInfo, PageInfo, Header, InterceptedRequest, InterceptedResponse, NetworkExchange, NetworkResourceType, NetworkServedFrom, NetworkBodies, NetworkBody, NetworkBodyRange, NetworkCaptureOptions, WaitResult, WaitConditionStatus, OccluderInfo, ElementRef, NavigateResult, EvaluateResult, ElementResult, DragResult, SelectOptionResult, ScreenshotResult, ReadCanvasResult, DOMResult, InspectResult, RentResponse, BrowserInfo, SessionUsage, IceServer, StreamAnswer, ReactionInfo, } from "./types.ts";
10
10
  export type { Button, ClickAction, ClickOpts, FillOpts, ReactionOpts, SelectOpts, WaitOpts, WaitUntil, NavigateOpts, LoadHTMLOpts, GetDOMOpts, GetObservationOpts, ScreenshotOpts, ReadCanvasOpts, } from "./options.ts";
11
11
  export { type RequestPattern, type HeaderModification, type HeaderModificationAction, } from "./network.ts";
12
12
  export { ScriptRun, ScriptFollow, type ScriptEvent, type ScriptEventHandler, type ScriptFinished, type ScriptLogEntry, type ScriptResult, type ScriptRunInfo, } from "./scripts.ts";
@@ -88,13 +88,16 @@ export declare function connectSession(grpcUrl: string, apiKey: string, sessionI
88
88
  * @param apiKey - API key the session was rented with
89
89
  * @param sessionId - id of the session to release
90
90
  *
91
+ * @returns SessionUsage what the session consumed over its whole life;
92
+ * undefined when the server could not report it
93
+ *
91
94
  * Rejects when the stop API refuses the request. Stopping a session that is
92
95
  * already gone is a no-op rather than a failure.
93
96
  *
94
97
  * @example
95
- * await stopBrowser(apiKey, sessionId);
98
+ * const usage = await stopBrowser(apiKey, sessionId);
96
99
  */
97
- export declare function stopBrowser(apiKey: string, sessionId: string): Promise<void>;
100
+ export declare function stopBrowser(apiKey: string, sessionId: string): Promise<SessionUsage | undefined>;
98
101
  /**
99
102
  * Reports the sessions an API key currently holds.
100
103
  *
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { createGrpcTransport } from "@connectrpc/connect-node";
2
2
  import { CloudBrowser } from "./client.js";
3
+ import { sessionUsageFromJson } from "./internal/convert.js";
3
4
  // Public API ──────────────────────────────────────────────────────────
4
5
  export { CloudBrowser } from "./client.js";
5
6
  export { BrowserConfig } from "./config.js";
@@ -61,9 +62,7 @@ export async function rentBrowser(config) {
61
62
  const transport = createGrpcTransport({
62
63
  baseUrl: grpcBaseUrl(rentResp.grpcUrl),
63
64
  });
64
- return new CloudBrowser(transport, rentResp.sessionId, config.apiKey, rentResp.fingerprint, async () => {
65
- await callStopApi(config.apiKey, rentResp.sessionId);
66
- });
65
+ return new CloudBrowser(transport, rentResp.sessionId, config.apiKey, rentResp.fingerprint, () => callStopApi(config.apiKey, rentResp.sessionId));
67
66
  }
68
67
  /**
69
68
  * Attaches to an already-rented session over a fresh gRPC connection.
@@ -90,9 +89,7 @@ export async function rentBrowser(config) {
90
89
  */
91
90
  export function connectSession(grpcUrl, apiKey, sessionId) {
92
91
  const transport = createGrpcTransport({ baseUrl: grpcBaseUrl(grpcUrl) });
93
- return new CloudBrowser(transport, sessionId, apiKey, "", async () => {
94
- await callStopApi(apiKey, sessionId);
95
- });
92
+ return new CloudBrowser(transport, sessionId, apiKey, "", () => callStopApi(apiKey, sessionId));
96
93
  }
97
94
  /**
98
95
  * Releases a session without needing a {@link CloudBrowser} handle.
@@ -104,14 +101,17 @@ export function connectSession(grpcUrl, apiKey, sessionId) {
104
101
  * @param apiKey - API key the session was rented with
105
102
  * @param sessionId - id of the session to release
106
103
  *
104
+ * @returns SessionUsage what the session consumed over its whole life;
105
+ * undefined when the server could not report it
106
+ *
107
107
  * Rejects when the stop API refuses the request. Stopping a session that is
108
108
  * already gone is a no-op rather than a failure.
109
109
  *
110
110
  * @example
111
- * await stopBrowser(apiKey, sessionId);
111
+ * const usage = await stopBrowser(apiKey, sessionId);
112
112
  */
113
113
  export async function stopBrowser(apiKey, sessionId) {
114
- await callStopApi(apiKey, sessionId);
114
+ return await callStopApi(apiKey, sessionId);
115
115
  }
116
116
  /**
117
117
  * Reports the sessions an API key currently holds.
@@ -224,4 +224,5 @@ async function callStopApi(apiKey, sessionId) {
224
224
  if (!resp.ok || !data.success) {
225
225
  throw new Error(`stop failed: ${data.error ?? resp.statusText}`);
226
226
  }
227
+ return sessionUsageFromJson(data.usage);
227
228
  }
@@ -1,5 +1,5 @@
1
- import { type Rect as ProtoRect, type ClickResult as ProtoClickResult, type FillResult as ProtoFillResult, type MoveResult as ProtoMoveResult, type ScrollResult as ProtoScrollResult, type DragResult as ProtoDragResult, type SelectOptionResult as ProtoSelectOptionResult, type WaitResult as ProtoWaitResult, type FrameInfo as ProtoFrameInfo, type PageInfo as ProtoPageInfo, type Header as ProtoHeader, type InterceptedRequest as ProtoInterceptedRequest, type InterceptedResponse as ProtoInterceptedResponse, type NetworkExchange as ProtoNetworkExchange, type HeaderModification as ProtoHeaderModification, type CookieParam as ProtoCookieParam, type StorageOriginEntry as ProtoStorageOriginEntry, type AuthSession as ProtoAuthSession } from "../gen/wrc_pb.ts";
2
- import type { DragResult, ElementResult, FrameInfo, Header, InterceptedRequest, InterceptedResponse, NetworkExchange, PageInfo, Rect, SelectOptionResult, WaitResult } from "../types.ts";
1
+ import { type Rect as ProtoRect, type ClickResult as ProtoClickResult, type FillResult as ProtoFillResult, type MoveResult as ProtoMoveResult, type ScrollResult as ProtoScrollResult, type DragResult as ProtoDragResult, type SelectOptionResult as ProtoSelectOptionResult, type WaitResult as ProtoWaitResult, type FrameInfo as ProtoFrameInfo, type PageInfo as ProtoPageInfo, type SessionUsage as ProtoSessionUsage, type Header as ProtoHeader, type InterceptedRequest as ProtoInterceptedRequest, type InterceptedResponse as ProtoInterceptedResponse, type NetworkExchange as ProtoNetworkExchange, type HeaderModification as ProtoHeaderModification, type CookieParam as ProtoCookieParam, type StorageOriginEntry as ProtoStorageOriginEntry, type AuthSession as ProtoAuthSession } from "../gen/wrc_pb.ts";
2
+ import type { DragResult, ElementResult, FrameInfo, Header, InterceptedRequest, InterceptedResponse, NetworkExchange, PageInfo, Rect, SelectOptionResult, SessionUsage, WaitResult } from "../types.ts";
3
3
  import type { CookieParam } from "../cookies.ts";
4
4
  import type { StorageOriginEntry } from "../storage.ts";
5
5
  import type { AuthSession } from "../auth-session.ts";
@@ -36,6 +36,8 @@ export declare function unwrapSelect(r: ProtoSelectOptionResult): SelectOptionRe
36
36
  export declare function unwrapWait(r: ProtoWaitResult): WaitResult;
37
37
  export declare function frameInfoFromProto(f: ProtoFrameInfo): FrameInfo;
38
38
  export declare function pageInfoFromProto(p: ProtoPageInfo): PageInfo;
39
+ export declare function sessionUsageFromProto(u: ProtoSessionUsage): SessionUsage;
40
+ export declare function sessionUsageFromJson(v: unknown): SessionUsage | undefined;
39
41
  export declare function headerFromProto(h: ProtoHeader): Header;
40
42
  export declare function headersToProto(headers: Header[] | undefined): ProtoHeader[];
41
43
  export declare function interceptedRequestFromProto(r: ProtoInterceptedRequest | undefined): InterceptedRequest | null;
@@ -228,6 +228,33 @@ export function pageInfoFromProto(p) {
228
228
  : {},
229
229
  };
230
230
  }
231
+ export function sessionUsageFromProto(u) {
232
+ return {
233
+ wallTime: u.wallTime,
234
+ cpuTime: u.cpuTime,
235
+ minMemory: u.minMemory,
236
+ averageMemory: u.averageMemory,
237
+ peakMemory: u.peakMemory,
238
+ renderersUsed: u.renderersUsed,
239
+ framesCreated: u.framesCreated,
240
+ };
241
+ }
242
+ // The stop endpoint answers in JSON, with the same field names as SessionUsage.
243
+ export function sessionUsageFromJson(v) {
244
+ if (!v || typeof v !== "object")
245
+ return undefined;
246
+ const u = v;
247
+ const num = (x) => (typeof x === "number" ? x : 0);
248
+ return {
249
+ wallTime: num(u.wallTime),
250
+ cpuTime: num(u.cpuTime),
251
+ minMemory: num(u.minMemory),
252
+ averageMemory: num(u.averageMemory),
253
+ peakMemory: num(u.peakMemory),
254
+ renderersUsed: num(u.renderersUsed),
255
+ framesCreated: num(u.framesCreated),
256
+ };
257
+ }
231
258
  // ──────────────────────────────────────────────────────────────────────
232
259
  // Headers
233
260
  // ──────────────────────────────────────────────────────────────────────
@@ -279,7 +306,8 @@ export function networkExchangeFromProto(e) {
279
306
  initiatorUrl: e.initiatorUrl,
280
307
  requestHeaders: e.requestHeaders.map(headerFromProto),
281
308
  requestHeadersAreWire: e.requestHeadersAreWire,
282
- requestBody: e.requestBody,
309
+ requestBodyId: e.requestBodyId,
310
+ requestBodySize: Number(e.requestBodySize),
283
311
  requestBodyTruncated: e.requestBodyTruncated,
284
312
  hasResponse: e.hasResponse,
285
313
  statusCode: e.statusCode,
@@ -290,9 +318,9 @@ export function networkExchangeFromProto(e) {
290
318
  servedFrom: e.servedFrom,
291
319
  responseHeaders: e.responseHeaders.map(headerFromProto),
292
320
  responseHeadersAreWire: e.responseHeadersAreWire,
293
- responseBody: e.responseBody,
321
+ responseBodyId: e.responseBodyId,
322
+ responseBodySize: Number(e.responseBodySize),
294
323
  responseBodyTruncated: e.responseBodyTruncated,
295
- responseBodyCaptured: e.responseBodyCaptured,
296
324
  encodedDataLength: Number(e.encodedDataLength),
297
325
  error: e.error,
298
326
  };
@@ -52,8 +52,8 @@ export declare class NetworkCapture {
52
52
  get error(): Error | null;
53
53
  /**
54
54
  * How many exchanges the server discarded because this reader fell behind.
55
- * Anything above zero means the log has holes: make the handler cheaper,
56
- * narrow `patterns`, or stop capturing bodies.
55
+ * Anything above zero means the log has holes: make the handler cheaper or
56
+ * narrow `patterns`.
57
57
  */
58
58
  get dropped(): number;
59
59
  /**
@@ -57,8 +57,8 @@ export class NetworkCapture {
57
57
  }
58
58
  /**
59
59
  * How many exchanges the server discarded because this reader fell behind.
60
- * Anything above zero means the log has holes: make the handler cheaper,
61
- * narrow `patterns`, or stop capturing bodies.
60
+ * Anything above zero means the log has holes: make the handler cheaper or
61
+ * narrow `patterns`.
62
62
  */
63
63
  get dropped() {
64
64
  return this.droppedCount;
package/dist/types.d.ts CHANGED
@@ -26,6 +26,46 @@ export interface PageInfo {
26
26
  viewport: Rect;
27
27
  frameTree: FrameInfo;
28
28
  }
29
+ /**
30
+ * SessionUsage is what a session's browser has consumed since the session
31
+ * started: the CPU time and memory of every process that rendered its pages —
32
+ * main frames, cross-site iframes and the workers they host — including
33
+ * processes that have since exited. Work done on the session's behalf in
34
+ * processes it shares with other sessions is not included.
35
+ *
36
+ * {@link CloudBrowser.getUsage} reports it while the session runs, and
37
+ * {@link CloudBrowser.stopBrowser} resolves with the final figures, so there is
38
+ * no need to read it right before stopping.
39
+ */
40
+ export interface SessionUsage {
41
+ /** Real time since the session's browser was created, in seconds. */
42
+ wallTime: number;
43
+ /**
44
+ * User plus kernel CPU time in seconds. Only time a thread actually ran on a
45
+ * core counts; waiting and idling do not.
46
+ */
47
+ cpuTime: number;
48
+ /**
49
+ * The least memory the session held once its browser was ready, in bytes.
50
+ * Sampled about once a second.
51
+ */
52
+ minMemory: number;
53
+ /**
54
+ * The memory held, averaged over wallTime, in bytes; averageMemory * wallTime
55
+ * is the memory-time used. Sampled about once a second, so short spikes count
56
+ * toward the peak but barely toward the average.
57
+ */
58
+ averageMemory: number;
59
+ /** The most memory held at any one moment, in bytes. */
60
+ peakMemory: number;
61
+ /**
62
+ * Renderer processes that hosted at least one of the session's frames: one
63
+ * per site its pages and cross-site iframes needed.
64
+ */
65
+ renderersUsed: number;
66
+ /** Child frames created in the session's pages, whether or not they got a process of their own. */
67
+ framesCreated: number;
68
+ }
29
69
  /** Header is a single HTTP header (name/value pair) on an intercepted request or response. */
30
70
  export interface Header {
31
71
  name: string;
@@ -102,10 +142,16 @@ export interface NetworkExchange {
102
142
  */
103
143
  requestHeadersAreWire: boolean;
104
144
  /**
105
- * Inline body only. File and streamed uploads set requestBodyTruncated
106
- * instead of appearing here.
145
+ * Names the request body; read it with {@link CloudBrowser.readNetworkBody}.
146
+ * Empty when the request had no body. Bodies never travel with the exchange.
147
+ */
148
+ requestBodyId: string;
149
+ /** Bytes kept for the request body. */
150
+ requestBodySize: number;
151
+ /**
152
+ * Part of the request body is missing: it hit the per-body cap, or it was a
153
+ * file or streamed upload, which are not kept.
107
154
  */
108
- requestBody: Uint8Array;
109
155
  requestBodyTruncated: boolean;
110
156
  /** False when the request failed before any response arrived; see error. */
111
157
  hasResponse: boolean;
@@ -119,26 +165,49 @@ export interface NetworkExchange {
119
165
  responseHeaders: Header[];
120
166
  responseHeadersAreWire: boolean;
121
167
  /**
122
- * Populated only when body capture was requested for this URL and applied;
123
- * check responseBodyCaptured to tell an empty body from an uncaptured one.
124
- * Binary content does not survive the browser boundary intact — see
168
+ * Names the response body; read it with {@link CloudBrowser.readNetworkBody}.
169
+ * Empty when body capture did not apply to this exchange — see
125
170
  * {@link NetworkCaptureOptions.bodies}.
126
171
  */
127
- responseBody: Uint8Array;
172
+ responseBodyId: string;
173
+ /** Bytes kept for the response body, after content decoding. */
174
+ responseBodySize: number;
175
+ /**
176
+ * The kept body is shorter than the one the page received: it hit the
177
+ * per-body cap or the load ended early.
178
+ */
128
179
  responseBodyTruncated: boolean;
129
- responseBodyCaptured: boolean;
130
180
  /** Bytes on the wire, not body size; 0 for a response served from cache. */
131
181
  encodedDataLength: number;
132
182
  /** Net error name (e.g. "net::ERR_ABORTED"), empty on success. */
133
183
  error: string;
134
184
  }
135
- /** How much of a response body a network capture keeps. */
185
+ /** One range of a captured body, from {@link CloudBrowser.readNetworkBodyRange}. */
186
+ export interface NetworkBodyRange {
187
+ /** The bytes read; empty past the end of the body. */
188
+ data: Uint8Array;
189
+ /** Bytes kept for the body as a whole. */
190
+ totalSize: number;
191
+ /** Matches the exchange's truncated flag for this body. */
192
+ truncated: boolean;
193
+ }
194
+ /** A whole captured body, from {@link CloudBrowser.readNetworkBody}. */
195
+ export interface NetworkBody {
196
+ data: Uint8Array;
197
+ /** The kept body is shorter than the original; see the exchange's flag. */
198
+ truncated: boolean;
199
+ }
200
+ /**
201
+ * Which response bodies a network capture keeps. Kept bodies are not part of
202
+ * the exchange; read them with {@link CloudBrowser.readNetworkBody}.
203
+ */
136
204
  export type NetworkBodies = "none" | "text" | "all";
137
205
  /**
138
206
  * Configures {@link CloudBrowser.captureNetwork}.
139
207
  *
140
- * There is deliberately no byte-cap option: buffer sizes bound memory on a
141
- * machine shared with other sessions, so the server owns them.
208
+ * There is deliberately no byte-cap option: kept bodies are stored on a
209
+ * machine shared with other sessions, so the server owns the quota. When a
210
+ * session's bodies exceed it, the oldest are dropped first.
142
211
  */
143
212
  export interface NetworkCaptureOptions {
144
213
  /**
@@ -149,9 +218,8 @@ export interface NetworkCaptureOptions {
149
218
  patterns?: string[];
150
219
  /**
151
220
  * Response-body capture. `"text"` keeps bodies whose MIME type is textual,
152
- * `"all"` keeps every body — but binary payloads (images, fonts, video) do
153
- * not cross the browser boundary intact, so prefer `"text"` unless you know
154
- * the bodies are textual. Defaults to `"none"`, headers and status only.
221
+ * `"all"` keeps every body, binary included. Defaults to `"none"`, headers
222
+ * and status only. Request bodies are kept whenever a request has one.
155
223
  */
156
224
  bodies?: NetworkBodies;
157
225
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "browserscale-ts",
3
- "version": "1.8.0",
3
+ "version": "1.10.0",
4
4
  "description": "Official SDK for browserscale — undetected stealth browsers in the cloud. Rent real Chromium sessions with unique fingerprints, built-in proxies, captcha solving, human-like input and live video — scale web scraping and automation without running a single browser yourself.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",