browserscale-ts 1.4.0 → 1.7.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.js CHANGED
@@ -9,6 +9,11 @@ export { Locator, css, js, node, at, AllFrames } from "./locator.js";
9
9
  export { DefaultWaitTimeoutMs, DefaultVisible, DefaultSteadyMs, } from "./defaults.js";
10
10
  // Errors — base class plus the typed semantic-failure subclasses
11
11
  export { BrowserScaleError, ClickError, FillError, DragError, ScrollError, MoveError, SelectOptionError, WaitError, } from "./errors.js";
12
+ // Scripts (automation running inside the browser process)
13
+ export { ScriptRun, ScriptFollow, } from "./scripts.js";
14
+ // Network capture (traffic log)
15
+ export { NetworkCapture } from "./network-capture.js";
16
+ export { DomMirror, } from "./dom-mirror.js";
12
17
  // Rent / stop ──────────────────────────────────────────────────────────
13
18
  let apiEndpoint = "https://api.browserscale.cloud";
14
19
  /**
@@ -106,6 +111,62 @@ export function connectSession(grpcUrl, apiKey, sessionId) {
106
111
  export async function stopBrowser(apiKey, sessionId) {
107
112
  await callStopApi(apiKey, sessionId);
108
113
  }
114
+ /**
115
+ * Reports the sessions an API key currently holds.
116
+ *
117
+ * Use it to recover session ids the process lost — after a restart, or from a
118
+ * different machine entirely. Without it a rental is only reachable through the
119
+ * handle that created it, so a crash between rent and stop leaves a paid session
120
+ * running with nothing able to name it.
121
+ *
122
+ * Each entry carries the `grpcUrl` it is driven from, so a listed session can be
123
+ * handed straight to {@link connectSession}. Only live sessions are listed; a
124
+ * stopped one is gone, not reported as ended.
125
+ *
126
+ * @param apiKey - API key whose sessions to list
127
+ *
128
+ * @returns the running sessions, oldest first; empty when the key holds none
129
+ *
130
+ * @throws UNKNOWN_ERROR - the list API rejected the request
131
+ *
132
+ * @example
133
+ * const browsers = await listBrowsers(apiKey);
134
+ * for (const b of browsers) console.log(b.sessionId, b.countryCode);
135
+ *
136
+ * // and to drive one of them
137
+ * const browser = connectSession(browsers[0].grpcUrl, apiKey, browsers[0].sessionId);
138
+ */
139
+ export async function listBrowsers(apiKey) {
140
+ const resp = await fetch(`${apiEndpoint}/sessions`, {
141
+ method: "POST",
142
+ headers: { "Content-Type": "application/json" },
143
+ body: JSON.stringify({ apiKey }),
144
+ });
145
+ // Named explicitly, because this is the one endpoint a caller can reach on a
146
+ // deployment that does not have it: listing came after rent and stop. Letting
147
+ // it fall through would report a JSON parse failure against an error page,
148
+ // which says nothing about the actual problem.
149
+ if (resp.status === 404) {
150
+ throw new Error(`the API at ${apiEndpoint} does not support listing sessions`);
151
+ }
152
+ const data = await resp.json();
153
+ if (!resp.ok || !data.success) {
154
+ throw new Error(`list failed: ${data.error ?? resp.statusText}`);
155
+ }
156
+ const sessions = (data.sessions ?? []);
157
+ return sessions.map((s) => ({
158
+ sessionId: s.sessionId,
159
+ grpcUrl: s.grpcUrl ?? "",
160
+ startTime: s.startTime ?? 0,
161
+ rentDuration: s.rentDuration ?? 0,
162
+ remainingSeconds: s.remainingSeconds,
163
+ countryCode: s.countryCode ?? "",
164
+ timezone: s.timezone ?? "",
165
+ proxyHost: s.proxyHost ?? "",
166
+ publicIp: s.publicIp ?? "",
167
+ gpuIndex: s.gpuIndex,
168
+ }));
169
+ }
109
170
  // Maps the rent response's gRPC URL to a transport base URL. The scheme
110
171
  // signals transport security: grpcs:// dials TLS, grpc:// (or no scheme,
111
172
  // for older servers) dials plaintext.
@@ -1,7 +1,8 @@
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 HeaderModification as ProtoHeaderModification, type CookieParam as ProtoCookieParam, type StorageOriginEntry as ProtoStorageOriginEntry } from "../gen/wrc_pb.ts";
2
- import type { DragResult, ElementResult, FrameInfo, Header, InterceptedRequest, InterceptedResponse, 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 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";
3
3
  import type { CookieParam } from "../cookies.ts";
4
4
  import type { StorageOriginEntry } from "../storage.ts";
5
+ import type { AuthSession } from "../auth-session.ts";
5
6
  import type { HeaderModification, RequestPattern } from "../network.ts";
6
7
  import { type Locator } from "../locator.ts";
7
8
  /**
@@ -38,11 +39,19 @@ export declare function pageInfoFromProto(p: ProtoPageInfo): PageInfo;
38
39
  export declare function headerFromProto(h: ProtoHeader): Header;
39
40
  export declare function headersToProto(headers: Header[] | undefined): ProtoHeader[];
40
41
  export declare function interceptedRequestFromProto(r: ProtoInterceptedRequest | undefined): InterceptedRequest | null;
42
+ /**
43
+ * The int64/uint64 fields arrive as bigint. Byte counts stay far below
44
+ * Number.MAX_SAFE_INTEGER, so they are narrowed to number to keep the SDK
45
+ * surface free of bigint.
46
+ */
47
+ export declare function networkExchangeFromProto(e: ProtoNetworkExchange): NetworkExchange;
41
48
  export declare function interceptedResponseFromProto(r: ProtoInterceptedResponse | undefined): InterceptedResponse | null;
42
49
  export declare function cookieParamFromProto(c: ProtoCookieParam): CookieParam;
43
50
  export declare function cookieParamsToProto(cookies: CookieParam[]): ProtoCookieParam[];
44
51
  export declare function storageEntryFromProto(e: ProtoStorageOriginEntry): StorageOriginEntry;
45
52
  export declare function storageEntriesToProto(storage: StorageOriginEntry[]): ProtoStorageOriginEntry[];
53
+ export declare function authSessionFromProto(s: ProtoAuthSession): AuthSession;
54
+ export declare function authSessionToProto(s: AuthSession): ProtoAuthSession;
46
55
  /**
47
56
  * splitRequestPatterns turns a RequestPattern[] into the proto's parallel
48
57
  * URL + abort-flag slices. When no pattern has abort set, the aborts
@@ -1,7 +1,7 @@
1
1
  // Internal proto ↔ TS helpers used by client.ts. Anything in here is
2
2
  // implementation detail and is NOT exported from the package entrypoints.
3
3
  import { create } from "@bufbuild/protobuf";
4
- import { HeaderModificationSchema, CookiePartitionKeySchema, CookieParamSchema, HeaderSchema, StorageItemSchema, StorageOriginEntrySchema, } from "../gen/wrc_pb.js";
4
+ import { HeaderModificationSchema, CookiePartitionKeySchema, CookieParamSchema, HeaderSchema, StorageItemSchema, StorageOriginEntrySchema, AuthSessionSchema, DbscSessionSchema, } from "../gen/wrc_pb.js";
5
5
  import { ClickError, DragError, FillError, MoveError, ScrollError, SelectOptionError, WaitError, } from "../errors.js";
6
6
  import { pickFrame } from "../locator.js";
7
7
  export function elementFields(target, optsInFrame) {
@@ -257,6 +257,45 @@ export function interceptedRequestFromProto(r) {
257
257
  resourceType: r.resourceType,
258
258
  };
259
259
  }
260
+ // ──────────────────────────────────────────────────────────────────────
261
+ // Network capture
262
+ // ──────────────────────────────────────────────────────────────────────
263
+ /**
264
+ * The int64/uint64 fields arrive as bigint. Byte counts stay far below
265
+ * Number.MAX_SAFE_INTEGER, so they are narrowed to number to keep the SDK
266
+ * surface free of bigint.
267
+ */
268
+ export function networkExchangeFromProto(e) {
269
+ return {
270
+ requestId: e.requestId,
271
+ chainId: e.chainId,
272
+ redirectIndex: e.redirectIndex,
273
+ frameId: e.frameId,
274
+ isOopif: e.isOopif,
275
+ resourceType: e.resourceType,
276
+ method: e.method,
277
+ url: e.url,
278
+ initiatorUrl: e.initiatorUrl,
279
+ requestHeaders: e.requestHeaders.map(headerFromProto),
280
+ requestHeadersAreWire: e.requestHeadersAreWire,
281
+ requestBody: e.requestBody,
282
+ requestBodyTruncated: e.requestBodyTruncated,
283
+ hasResponse: e.hasResponse,
284
+ statusCode: e.statusCode,
285
+ statusText: e.statusText,
286
+ mimeType: e.mimeType,
287
+ protocol: e.protocol,
288
+ remoteAddress: e.remoteAddress,
289
+ servedFrom: e.servedFrom,
290
+ responseHeaders: e.responseHeaders.map(headerFromProto),
291
+ responseHeadersAreWire: e.responseHeadersAreWire,
292
+ responseBody: e.responseBody,
293
+ responseBodyTruncated: e.responseBodyTruncated,
294
+ responseBodyCaptured: e.responseBodyCaptured,
295
+ encodedDataLength: Number(e.encodedDataLength),
296
+ error: e.error,
297
+ };
298
+ }
260
299
  export function interceptedResponseFromProto(r) {
261
300
  if (!r)
262
301
  return null;
@@ -346,6 +385,50 @@ export function storageEntriesToProto(storage) {
346
385
  });
347
386
  }
348
387
  // ──────────────────────────────────────────────────────────────────────
388
+ // Auth / DBSC
389
+ // ──────────────────────────────────────────────────────────────────────
390
+ export function authSessionFromProto(s) {
391
+ const out = {};
392
+ if (s.gaiaId !== undefined)
393
+ out.gaiaId = s.gaiaId;
394
+ if (s.email !== undefined)
395
+ out.email = s.email;
396
+ if (s.refreshToken !== undefined)
397
+ out.refreshToken = s.refreshToken;
398
+ if (s.wrappedBindingKey !== undefined)
399
+ out.wrappedBindingKey = s.wrappedBindingKey;
400
+ if (s.signinScopedDeviceId !== undefined)
401
+ out.signinScopedDeviceId = s.signinScopedDeviceId;
402
+ if (s.syncConsent !== undefined)
403
+ out.syncConsent = s.syncConsent;
404
+ if (s.dbscSessions.length > 0) {
405
+ out.dbscSessions = s.dbscSessions.map(d => ({ site: d.site, session: d.session }));
406
+ }
407
+ return out;
408
+ }
409
+ export function authSessionToProto(s) {
410
+ const msg = create(AuthSessionSchema);
411
+ if (s.gaiaId !== undefined)
412
+ msg.gaiaId = s.gaiaId;
413
+ if (s.email !== undefined)
414
+ msg.email = s.email;
415
+ if (s.refreshToken !== undefined)
416
+ msg.refreshToken = s.refreshToken;
417
+ if (s.wrappedBindingKey !== undefined)
418
+ msg.wrappedBindingKey = s.wrappedBindingKey;
419
+ if (s.signinScopedDeviceId !== undefined)
420
+ msg.signinScopedDeviceId = s.signinScopedDeviceId;
421
+ if (s.syncConsent !== undefined)
422
+ msg.syncConsent = s.syncConsent;
423
+ msg.dbscSessions = (s.dbscSessions ?? []).map(d => {
424
+ const item = create(DbscSessionSchema);
425
+ item.site = d.site;
426
+ item.session = d.session;
427
+ return item;
428
+ });
429
+ return msg;
430
+ }
431
+ // ──────────────────────────────────────────────────────────────────────
349
432
  // Network — patterns + header modifications
350
433
  // ──────────────────────────────────────────────────────────────────────
351
434
  /**
@@ -0,0 +1,84 @@
1
+ import type { NetworkExchangeEvent } from "./gen/wrc_pb.ts";
2
+ import type { NetworkExchange } from "./types.ts";
3
+ /**
4
+ * NetworkExchangeHandler is called once per completed exchange.
5
+ *
6
+ * Calls are sequential and in the order the browser finished the requests, so
7
+ * the hops of a redirect chain arrive in order.
8
+ *
9
+ * Blocking here stalls the capture: awaiting something slow means the server
10
+ * keeps buffering, and once its per-reader bound is reached it drops the oldest
11
+ * entries, which {@link NetworkCapture.dropped} reports. Hand slow work
12
+ * (uploads, IndexedDB) to a queue of your own instead of awaiting it here.
13
+ */
14
+ export type NetworkExchangeHandler = (exchange: NetworkExchange) => void;
15
+ /**
16
+ * NetworkCapture is a running capture, returned by
17
+ * {@link CloudBrowser.captureNetwork}. Exchanges are delivered to the handler
18
+ * passed there; this handle only exists to stop the capture and to report how
19
+ * it went.
20
+ */
21
+ export declare class NetworkCapture {
22
+ private readonly abort;
23
+ private readonly disarm;
24
+ private readonly finished;
25
+ private armed;
26
+ private stopped;
27
+ private failure;
28
+ private droppedCount;
29
+ /** @internal Constructed by CloudBrowser; not part of the public API. */
30
+ constructor(init: {
31
+ stream: AsyncIterable<NetworkExchangeEvent>;
32
+ onExchange: NetworkExchangeHandler;
33
+ disarm: () => Promise<unknown>;
34
+ abort: AbortController;
35
+ });
36
+ /**
37
+ * @internal Marks the capture as armed by this handle, so stop() disarms it
38
+ * server-side. Views that only attached to someone else's capture stay
39
+ * unarmed and merely detach.
40
+ */
41
+ arm(): void;
42
+ /**
43
+ * Reads the stream and calls the handler for each exchange. It never rejects:
44
+ * a failure is recorded for {@link NetworkCapture.error} instead, so nothing
45
+ * surfaces as an unhandled rejection.
46
+ */
47
+ private pump;
48
+ /**
49
+ * Why the capture ended: null while it is still running, and after a clean
50
+ * stop or the session ending normally.
51
+ */
52
+ get error(): Error | null;
53
+ /**
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.
57
+ */
58
+ get dropped(): number;
59
+ /**
60
+ * Resolves once the capture ends — {@link NetworkCapture.stop}, a dead
61
+ * session or a transport failure.
62
+ *
63
+ * Use it to capture for as long as the session lives. It is not needed when
64
+ * you drive the browser yourself and call stop when done.
65
+ *
66
+ * @throws the transport failure that ended the capture, if any
67
+ */
68
+ wait(): Promise<void>;
69
+ /**
70
+ * Ends the capture. Idempotent, and safe to call from a `finally`.
71
+ *
72
+ * Once it resolves the handler is no longer running, so data it collected is
73
+ * complete. Views from {@link CloudBrowser.streamNetworkExchanges} only
74
+ * detach — they never disarm a capture other readers may share.
75
+ *
76
+ * To stop from inside the handler, call
77
+ * {@link CloudBrowser.stopNetworkCapture} instead: stop awaits the reader,
78
+ * which cannot finish while the handler it called is still running.
79
+ *
80
+ * @throws UNKNOWN_ERROR - the capture could not be disarmed; the local reader
81
+ * is shut down regardless
82
+ */
83
+ stop(): Promise<void>;
84
+ }
@@ -0,0 +1,107 @@
1
+ import { networkExchangeFromProto } from "./internal/convert.js";
2
+ /**
3
+ * NetworkCapture is a running capture, returned by
4
+ * {@link CloudBrowser.captureNetwork}. Exchanges are delivered to the handler
5
+ * passed there; this handle only exists to stop the capture and to report how
6
+ * it went.
7
+ */
8
+ export class NetworkCapture {
9
+ /** @internal Constructed by CloudBrowser; not part of the public API. */
10
+ constructor(init) {
11
+ this.armed = false;
12
+ this.stopped = false;
13
+ this.failure = null;
14
+ this.droppedCount = 0;
15
+ this.abort = init.abort;
16
+ this.disarm = init.disarm;
17
+ this.finished = this.pump(init.stream, init.onExchange);
18
+ }
19
+ /**
20
+ * @internal Marks the capture as armed by this handle, so stop() disarms it
21
+ * server-side. Views that only attached to someone else's capture stay
22
+ * unarmed and merely detach.
23
+ */
24
+ arm() {
25
+ this.armed = true;
26
+ }
27
+ /**
28
+ * Reads the stream and calls the handler for each exchange. It never rejects:
29
+ * a failure is recorded for {@link NetworkCapture.error} instead, so nothing
30
+ * surfaces as an unhandled rejection.
31
+ */
32
+ async pump(stream, onExchange) {
33
+ try {
34
+ for await (const event of stream) {
35
+ if (event.dropped > 0n) {
36
+ this.droppedCount = Number(event.dropped);
37
+ }
38
+ if (!event.exchange)
39
+ continue;
40
+ onExchange(networkExchangeFromProto(event.exchange));
41
+ }
42
+ }
43
+ catch (err) {
44
+ // Cancelling is how stop() ends the stream, so the resulting error is
45
+ // expected rather than a failure worth reporting.
46
+ if (!this.stopped) {
47
+ this.failure = err instanceof Error ? err : new Error(String(err));
48
+ }
49
+ }
50
+ }
51
+ /**
52
+ * Why the capture ended: null while it is still running, and after a clean
53
+ * stop or the session ending normally.
54
+ */
55
+ get error() {
56
+ return this.failure;
57
+ }
58
+ /**
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.
62
+ */
63
+ get dropped() {
64
+ return this.droppedCount;
65
+ }
66
+ /**
67
+ * Resolves once the capture ends — {@link NetworkCapture.stop}, a dead
68
+ * session or a transport failure.
69
+ *
70
+ * Use it to capture for as long as the session lives. It is not needed when
71
+ * you drive the browser yourself and call stop when done.
72
+ *
73
+ * @throws the transport failure that ended the capture, if any
74
+ */
75
+ async wait() {
76
+ await this.finished;
77
+ if (this.failure)
78
+ throw this.failure;
79
+ }
80
+ /**
81
+ * Ends the capture. Idempotent, and safe to call from a `finally`.
82
+ *
83
+ * Once it resolves the handler is no longer running, so data it collected is
84
+ * complete. Views from {@link CloudBrowser.streamNetworkExchanges} only
85
+ * detach — they never disarm a capture other readers may share.
86
+ *
87
+ * To stop from inside the handler, call
88
+ * {@link CloudBrowser.stopNetworkCapture} instead: stop awaits the reader,
89
+ * which cannot finish while the handler it called is still running.
90
+ *
91
+ * @throws UNKNOWN_ERROR - the capture could not be disarmed; the local reader
92
+ * is shut down regardless
93
+ */
94
+ async stop() {
95
+ if (this.stopped)
96
+ return;
97
+ this.stopped = true;
98
+ try {
99
+ if (this.armed)
100
+ await this.disarm();
101
+ }
102
+ finally {
103
+ this.abort.abort();
104
+ await this.finished;
105
+ }
106
+ }
107
+ }
@@ -0,0 +1,205 @@
1
+ import type { ScriptEvent as ScriptEventMessage, ScriptLogEntry as ScriptLogEntryMessage } from "./gen/wrc_pb.ts";
2
+ /** One `console.*` call from a script. */
3
+ export interface ScriptLogEntry {
4
+ /** `"info"`, `"warning"` or `"error"`, from console.log / .warn / .error. */
5
+ level: string;
6
+ /** The logged arguments, already stringified the way console does it. */
7
+ message: string;
8
+ /** When the script printed the line, stamped in the browser. */
9
+ timestamp: Date;
10
+ }
11
+ /** The outcome of a blocking {@link CloudBrowser.runScript}. */
12
+ export interface ScriptResult {
13
+ /**
14
+ * False when the script failed to compile or threw; `result` then holds the
15
+ * message.
16
+ */
17
+ success: boolean;
18
+ /**
19
+ * The return value as JSON, or `"undefined"` when the script returned
20
+ * nothing. On failure it is the error message.
21
+ */
22
+ result: string;
23
+ /**
24
+ * Names the run. It arrives with the reply, so it is only useful after the
25
+ * fact — to match up log lines a separate follower already saw.
26
+ */
27
+ runId: string;
28
+ /** Everything the script printed, in order. */
29
+ log: ScriptLogEntry[];
30
+ /**
31
+ * True when the script printed more than the reply holds, in which case `log`
32
+ * is the tail of the output rather than all of it.
33
+ */
34
+ truncated: boolean;
35
+ }
36
+ /** How a run ended. */
37
+ export interface ScriptFinished {
38
+ /**
39
+ * False when the script failed to compile or threw; `result` then holds the
40
+ * message.
41
+ */
42
+ success: boolean;
43
+ /** The return value as JSON, or the error message. */
44
+ result: string;
45
+ /**
46
+ * True when the run was cancelled, or the session went away under it, rather
47
+ * than the script returning on its own.
48
+ */
49
+ stopped: boolean;
50
+ }
51
+ /** One run still in flight, as {@link CloudBrowser.listScriptRuns} reports it. */
52
+ export interface ScriptRunInfo {
53
+ runId: string;
54
+ /** How long the run has been going, in milliseconds. */
55
+ runningMs: number;
56
+ }
57
+ /**
58
+ * One item on a session's script event stream. Exactly one of `log` and
59
+ * `finished` is set.
60
+ */
61
+ export interface ScriptEvent {
62
+ /** The run that produced this event. */
63
+ runId: string;
64
+ /** A console line the script printed. */
65
+ log?: ScriptLogEntry;
66
+ /** The end of the run. No further event for that run follows. */
67
+ finished?: ScriptFinished;
68
+ }
69
+ /**
70
+ * ScriptEventHandler is called once per script event.
71
+ *
72
+ * Calls are sequential and in the order the browser produced them, so a run's
73
+ * last log line always arrives before its `finished`.
74
+ *
75
+ * Blocking here stalls delivery: the server buffers a bounded number of events
76
+ * per reader and then drops its oldest, which {@link ScriptRun.dropped}
77
+ * reports. Hand slow work to a queue of your own instead of awaiting it here.
78
+ */
79
+ export type ScriptEventHandler = (event: ScriptEvent) => void;
80
+ /** @internal Converts one log entry off the wire. */
81
+ export declare function scriptLogEntryFromProto(e: ScriptLogEntryMessage): ScriptLogEntry;
82
+ /**
83
+ * @internal Converts one stream item, or returns null for an event carrying
84
+ * neither variant — which a newer server could send and an older client should
85
+ * ignore rather than report as an empty log line.
86
+ */
87
+ export declare function scriptEventFromProto(event: ScriptEventMessage): ScriptEvent | null;
88
+ /**
89
+ * ScriptRun is a script running in the background, returned by
90
+ * {@link CloudBrowser.startScript}. Its output is delivered to the handler
91
+ * passed there; this handle exists to await the outcome and to cancel the run.
92
+ */
93
+ export declare class ScriptRun {
94
+ private readonly abort;
95
+ private readonly cancel;
96
+ private readonly finished;
97
+ private readonly id;
98
+ private ended;
99
+ private readonly endedPromise;
100
+ private detached;
101
+ private failure;
102
+ private droppedCount;
103
+ private result;
104
+ /** @internal Constructed by CloudBrowser; not part of the public API. */
105
+ constructor(init: {
106
+ runId: string;
107
+ stream: AsyncIterable<ScriptEventMessage>;
108
+ onEvent: ScriptEventHandler;
109
+ cancel: (runId: string) => Promise<unknown>;
110
+ abort: AbortController;
111
+ });
112
+ /**
113
+ * The id the browser gave this run. Pass it to
114
+ * {@link CloudBrowser.stopScripts} to cancel the run from elsewhere, or to
115
+ * {@link CloudBrowser.followScript} to watch it from another tab.
116
+ */
117
+ get runId(): string;
118
+ /**
119
+ * Reads the stream and calls the handler for each event belonging to this
120
+ * run. Filtering happens here rather than server-side because the
121
+ * subscription had to exist before the run id did.
122
+ *
123
+ * It never rejects: a failure is recorded for {@link ScriptRun.error}
124
+ * instead, so nothing surfaces as an unhandled rejection.
125
+ */
126
+ private pump;
127
+ /**
128
+ * Why the stream ended: null while it is still open, and after a clean stop
129
+ * or the session ending normally.
130
+ */
131
+ get error(): Error | null;
132
+ /**
133
+ * How many events the server discarded because this reader fell behind.
134
+ * Anything above zero means the log has holes: make the handler cheaper, or
135
+ * have the script print less.
136
+ */
137
+ get dropped(): number;
138
+ /**
139
+ * Resolves once the run ends, with how it ended.
140
+ *
141
+ * A script that threw is an outcome, not an error: it resolves with
142
+ * `success: false`. It rejects when the run's fate is unknown — the stream
143
+ * broke or the session died before the script finished.
144
+ *
145
+ * @returns how the script ended
146
+ *
147
+ * @throws UNKNOWN_ERROR - the outcome could not be observed
148
+ */
149
+ wait(): Promise<ScriptFinished>;
150
+ /**
151
+ * Cancels the run and detaches this reader. Idempotent, and safe to call from
152
+ * a `finally`.
153
+ *
154
+ * A script that is executing is interrupted; one parked on an await unwinds
155
+ * at its next operation in the page. Either way the handler sees a `finished`
156
+ * with `stopped` set, unless the local reader is torn down first.
157
+ *
158
+ * @throws UNKNOWN_ERROR - the run could not be cancelled server-side; the
159
+ * local reader is detached regardless
160
+ */
161
+ stop(): Promise<void>;
162
+ /**
163
+ * Stops reading this run's output without cancelling the run. The script
164
+ * keeps going with nobody watching, which is what makes a detached run
165
+ * outlive the page that started it.
166
+ */
167
+ detach(): Promise<void>;
168
+ }
169
+ /**
170
+ * ScriptFollow is a read-only view of script output, returned by
171
+ * {@link CloudBrowser.followScript}.
172
+ */
173
+ export declare class ScriptFollow {
174
+ private readonly abort;
175
+ private readonly finished;
176
+ private stopped;
177
+ private failure;
178
+ private droppedCount;
179
+ /** @internal Constructed by CloudBrowser; not part of the public API. */
180
+ constructor(init: {
181
+ stream: AsyncIterable<ScriptEventMessage>;
182
+ onEvent: ScriptEventHandler;
183
+ abort: AbortController;
184
+ });
185
+ private pump;
186
+ /**
187
+ * Why the subscription ended: null while it is still open and after a clean
188
+ * stop.
189
+ */
190
+ get error(): Error | null;
191
+ /** How many events the server discarded because this reader fell behind. */
192
+ get dropped(): number;
193
+ /**
194
+ * Resolves once the subscription ends — {@link ScriptFollow.stop}, a dead
195
+ * session or a transport failure.
196
+ *
197
+ * @throws the transport failure that ended the subscription, if any
198
+ */
199
+ wait(): Promise<void>;
200
+ /**
201
+ * Ends the subscription. Idempotent, and safe to call from a `finally`. It
202
+ * never cancels a run: other readers, and the script itself, are unaffected.
203
+ */
204
+ stop(): Promise<void>;
205
+ }