surfsky 0.0.6 → 0.0.9

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
@@ -45,6 +45,20 @@ declare const regionalPool: z.ZodEnum<{
45
45
  western: "western";
46
46
  westeurope: "westeurope";
47
47
  }>;
48
+ declare const captchaType: z.ZodEnum<{
49
+ bls: "bls";
50
+ click: "click";
51
+ datadome: "datadome";
52
+ datadome_audio: "datadome_audio";
53
+ datadome_click: "datadome_click";
54
+ funcaptcha: "funcaptcha";
55
+ geetest: "geetest";
56
+ hcaptcha: "hcaptcha";
57
+ image: "image";
58
+ perimeterx: "perimeterx";
59
+ recaptcha: "recaptcha";
60
+ turnstile: "turnstile";
61
+ }>;
48
62
  type OS = z.infer<typeof os>;
49
63
  type Arch = z.infer<typeof arch>;
50
64
  type DeviceType = z.infer<typeof deviceType>;
@@ -52,6 +66,7 @@ type WaitUntil = z.infer<typeof waitUntil>;
52
66
  type ProxyType = z.infer<typeof proxyType>;
53
67
  type ProfileOrdering = z.infer<typeof profileOrdering>;
54
68
  type RegionalPool = z.infer<typeof regionalPool>;
69
+ type CaptchaType = z.infer<typeof captchaType>;
55
70
  type ExportFormat = "json" | "netscape";
56
71
  type MouseButton = "left" | "right" | "middle";
57
72
  type KeyModifier = "Alt" | "Control" | "Meta" | "Shift";
@@ -178,6 +193,27 @@ declare const browserSettings: z.ZodObject<{
178
193
  cache_key: z.ZodOptional<z.ZodString>;
179
194
  }, z.core.$strict>;
180
195
  type BrowserSettings = z.infer<typeof browserSettings>;
196
+ declare const antiCaptcha: z.ZodObject<{
197
+ enabled: z.ZodOptional<z.ZodBoolean>;
198
+ auto_captcha_types: z.ZodOptional<z.ZodArray<z.ZodEnum<{
199
+ bls: "bls";
200
+ click: "click";
201
+ datadome: "datadome";
202
+ datadome_audio: "datadome_audio";
203
+ datadome_click: "datadome_click";
204
+ funcaptcha: "funcaptcha";
205
+ geetest: "geetest";
206
+ hcaptcha: "hcaptcha";
207
+ image: "image";
208
+ perimeterx: "perimeterx";
209
+ recaptcha: "recaptcha";
210
+ turnstile: "turnstile";
211
+ }>>>;
212
+ gemini_api_key: z.ZodOptional<z.ZodString>;
213
+ disable_external_providers: z.ZodOptional<z.ZodBoolean>;
214
+ external_providers_initially_enabled: z.ZodOptional<z.ZodBoolean>;
215
+ }, z.core.$strict>;
216
+ type AntiCaptcha = z.infer<typeof antiCaptcha>;
181
217
  declare const shared: z.ZodObject<{
182
218
  tier: z.ZodLiteral<"shared">;
183
219
  country: z.ZodOptional<z.ZodString>;
@@ -371,6 +407,26 @@ declare const oneTimeStartRequest: z.ZodObject<{
371
407
  cache_enabled: z.ZodOptional<z.ZodBoolean>;
372
408
  cache_key: z.ZodOptional<z.ZodString>;
373
409
  }, z.core.$strict>>;
410
+ anti_captcha: z.ZodOptional<z.ZodObject<{
411
+ enabled: z.ZodOptional<z.ZodBoolean>;
412
+ auto_captcha_types: z.ZodOptional<z.ZodArray<z.ZodEnum<{
413
+ bls: "bls";
414
+ click: "click";
415
+ datadome: "datadome";
416
+ datadome_audio: "datadome_audio";
417
+ datadome_click: "datadome_click";
418
+ funcaptcha: "funcaptcha";
419
+ geetest: "geetest";
420
+ hcaptcha: "hcaptcha";
421
+ image: "image";
422
+ perimeterx: "perimeterx";
423
+ recaptcha: "recaptcha";
424
+ turnstile: "turnstile";
425
+ }>>>;
426
+ gemini_api_key: z.ZodOptional<z.ZodString>;
427
+ disable_external_providers: z.ZodOptional<z.ZodBoolean>;
428
+ external_providers_initially_enabled: z.ZodOptional<z.ZodBoolean>;
429
+ }, z.core.$strict>>;
374
430
  enable_chromedriver: z.ZodOptional<z.ZodBoolean>;
375
431
  extensions: z.ZodOptional<z.ZodArray<z.ZodString>>;
376
432
  proxy_blacklist: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -390,6 +446,26 @@ declare const profileStartRequest: z.ZodObject<{
390
446
  cache_enabled: z.ZodOptional<z.ZodBoolean>;
391
447
  cache_key: z.ZodOptional<z.ZodString>;
392
448
  }, z.core.$strict>>;
449
+ anti_captcha: z.ZodOptional<z.ZodObject<{
450
+ enabled: z.ZodOptional<z.ZodBoolean>;
451
+ auto_captcha_types: z.ZodOptional<z.ZodArray<z.ZodEnum<{
452
+ bls: "bls";
453
+ click: "click";
454
+ datadome: "datadome";
455
+ datadome_audio: "datadome_audio";
456
+ datadome_click: "datadome_click";
457
+ funcaptcha: "funcaptcha";
458
+ geetest: "geetest";
459
+ hcaptcha: "hcaptcha";
460
+ image: "image";
461
+ perimeterx: "perimeterx";
462
+ recaptcha: "recaptcha";
463
+ turnstile: "turnstile";
464
+ }>>>;
465
+ gemini_api_key: z.ZodOptional<z.ZodString>;
466
+ disable_external_providers: z.ZodOptional<z.ZodBoolean>;
467
+ external_providers_initially_enabled: z.ZodOptional<z.ZodBoolean>;
468
+ }, z.core.$strict>>;
393
469
  enable_chromedriver: z.ZodOptional<z.ZodBoolean>;
394
470
  extensions: z.ZodOptional<z.ZodArray<z.ZodString>>;
395
471
  proxy_blacklist: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -1266,7 +1342,6 @@ declare class Deadline {
1266
1342
  readonly until: number;
1267
1343
  constructor(timeout: number | null | undefined, message: string);
1268
1344
  error(): BrowserTimeoutError;
1269
- check(): void;
1270
1345
  race<T>(promise: Promise<T>): Promise<T>;
1271
1346
  }
1272
1347
  declare class CapturedResponse {
@@ -1330,6 +1405,7 @@ declare class Page extends Actions {
1330
1405
  /** @internal */
1331
1406
  _send(method: string, params?: Record<string, unknown>): Promise<any>;
1332
1407
  /** @internal */
1408
+ _change(): Promise<void>;
1333
1409
  _waitReady(): Promise<void>;
1334
1410
  /** @internal */
1335
1411
  _setup(waiting: boolean): Promise<void>;
@@ -1428,6 +1504,7 @@ declare class Browser extends Page implements AsyncDisposable {
1428
1504
  /** @internal */ _pages: Map<string, Page>;
1429
1505
  /** @internal aborts on close: background sleeps end early */
1430
1506
  _signal: AbortSignal;
1507
+ _changed: PromiseWithResolvers<void>;
1431
1508
  constructor(session: Session, options?: BrowserOptions);
1432
1509
  get internalUuid(): string;
1433
1510
  get cdp(): CDPClient;
@@ -1466,6 +1543,8 @@ declare class Browser extends Page implements AsyncDisposable {
1466
1543
  /** @internal */
1467
1544
  _onAttached(event: Record<string, any>): void;
1468
1545
  /** @internal */
1546
+ _notify(): void;
1547
+ /** @internal */
1469
1548
  _onDetached(event: Record<string, any>): void;
1470
1549
  /** @internal */
1471
1550
  _drop(page: Page): void;
@@ -1628,6 +1707,7 @@ interface RequestOptions {
1628
1707
  interface SessionStartOptions extends SessionOptions {
1629
1708
  /** A saved profile to start; a one-time session when absent. */
1630
1709
  profileUuid?: string;
1710
+ signal?: AbortSignal;
1631
1711
  }
1632
1712
  interface BrowserStartOptions extends SessionStartOptions {
1633
1713
  /** CDP resource types to drop: `["image", "font", "media"]` saves proxy traffic. */
@@ -1674,7 +1754,7 @@ declare class Surfsky implements AsyncDisposable {
1674
1754
  /** An open `BrowserPool`. `close()` stops every browser in it. */
1675
1755
  browsers(options?: PoolOptions): Promise<BrowserPool>;
1676
1756
  /** `browsers()` and `pool.map()` in 1 call. */
1677
- map<I, R>(handler: PoolHandler<I, R>, items: Iterable<I>, options?: PoolOptions): Promise<PoolOutcome<I, R>[]>;
1757
+ map<I, R>(handler: PoolHandler<I, R>, items: Iterable<I>, options?: PoolOptions & LeaseOptions): Promise<PoolOutcome<I, R>[]>;
1678
1758
  /** Nothing to release with fetch; here for `await using` symmetry. */
1679
1759
  close(): Promise<void>;
1680
1760
  [Symbol.asyncDispose](): Promise<void>;
@@ -1704,6 +1784,9 @@ type PoolOutcome<I, R> = {
1704
1784
  index: number;
1705
1785
  error: unknown;
1706
1786
  };
1787
+ interface LeaseOptions {
1788
+ signal?: AbortSignal;
1789
+ }
1707
1790
  declare class BrowserPool implements AsyncDisposable {
1708
1791
  #private;
1709
1792
  readonly sessionOptions: SessionOptions;
@@ -1717,12 +1800,12 @@ declare class BrowserPool implements AsyncDisposable {
1717
1800
  close(): Promise<void>;
1718
1801
  [Symbol.asyncDispose](): Promise<void>;
1719
1802
  /** Run `fn` on a live browser, then hand the browser back. Waits while all are busy. */
1720
- lease<R>(fn: (browser: Browser) => Promise<R>): Promise<R>;
1803
+ lease<R>(fn: (browser: Browser) => Promise<R>, { signal }?: LeaseOptions): Promise<R>;
1721
1804
  /** Run every item through `handler` on a leased browser, `capacity` at a time. */
1722
- map<I, R>(handler: PoolHandler<I, R>, items: Iterable<I>): Promise<PoolOutcome<I, R>[]>;
1805
+ map<I, R>(handler: PoolHandler<I, R>, items: Iterable<I>, { signal }?: LeaseOptions): Promise<PoolOutcome<I, R>[]>;
1723
1806
  }
1724
1807
  //#endregion
1725
1808
  //#region src/version.d.ts
1726
1809
  declare const VERSION: string;
1727
1810
  //#endregion
1728
- export { APIConnectionError, APIError, APIErrorOptions, APITimeoutError, Account, Actions, type ActiveProfile, type Arch, AuthenticationError, BadRequestError, type BatchDeleteResult, Browser, type BrowserLimits, type BrowserOptions, BrowserPool, type BrowserSettings, type BrowserStartOptions, BrowserTimeoutError, CDPClient, type CDPClientOptions, CDPError, CapturedResponse, type ClickOptions, ConfigurationError, ConflictError, type Cookie, type CookieInput, type CreateWebSocket, type DblclickOptions, type DeviceModel, type DeviceModelsOptions, type DeviceType, type DialogHandler, type DomainRoute, type DragOptions, type EvaluateOptions, type EventHandler, type ExportFormat, type Extension, type ExtensionFile, Extensions, type Fingerprint, Fingerprints, ForbiddenError, type Geolocation, type GotoOptions, type HoverOptions, type Inspector, type InspectorPage, type KeyModifier, Keyboard, type ListPageOptions, type ListPageRequest, type Logger, type Loose, type ManagedSession, type MediaDevices, MonthlySessionLimitError, Mouse, type MouseButton, type MouseClickOptions, type Noise, NotFoundError, type OS, type OneTimeStartRequest, Page, PageClosedError, PaymentRequiredError, type PoolHandler, type PoolOptions, type PoolOutcome, type PremiumProxy, PremiumTrafficLimitError, type PressOptions, type Profile, type ProfileCreate, type ProfileCreateRequest, type ProfileOrdering, type ProfileRef, type ProfileStartRequest, type ProfileSummary, type ProfileUpdate, type ProfileUpdateRequest, Profiles, Proxies, type ProxyCity, type ProxyCountry, ProxyCycle, type ProxyFactory, type ProxyInput, type ProxyLike, type ProxyQuota, ProxyRandom, type ProxyRegion, type ProxySource, ProxyTemplate, type ProxyType, RESOURCE_TYPES, RateLimitError, type RegionalPool, type Renderer, type RequestOptions, type ScrapeOptions, type ScrapeRequest, type ScrapeResult, type Screen, type ScreenshotOptions, type ScrollBehavior, type ScrollOptions, type ScrollToOptions, ServerError, type Session, type SessionLimits, type SessionOptions, type SessionStartOptions, type SharedProxy, type SharedProxyQuota, SharedTrafficLimitError, type Spec, type StopAllResult, StopRun, type StorageOptions, Surfsky, SurfskyError, type SurfskyOptions, type TrafficStats, type TrafficVolume, VERSION, ValidationError, type WaitOptions, type WaitUntil, type WebSocketLike, type isRecord, type parseListPageRequest, type parseOneTimeStartRequest, type parseProfileCreateRequest, type parseProfileStartRequest, type parseProfileUpdateRequest, type parseProxyLike, type parseScrapeRequest };
1811
+ export { APIConnectionError, APIError, APIErrorOptions, APITimeoutError, Account, Actions, type ActiveProfile, type AntiCaptcha, type Arch, AuthenticationError, BadRequestError, type BatchDeleteResult, Browser, type BrowserLimits, type BrowserOptions, BrowserPool, type BrowserSettings, type BrowserStartOptions, BrowserTimeoutError, CDPClient, type CDPClientOptions, CDPError, type CaptchaType, CapturedResponse, type ClickOptions, ConfigurationError, ConflictError, type Cookie, type CookieInput, type CreateWebSocket, type DblclickOptions, type DeviceModel, type DeviceModelsOptions, type DeviceType, type DialogHandler, type DomainRoute, type DragOptions, type EvaluateOptions, type EventHandler, type ExportFormat, type Extension, type ExtensionFile, Extensions, type Fingerprint, Fingerprints, ForbiddenError, type Geolocation, type GotoOptions, type HoverOptions, type Inspector, type InspectorPage, type KeyModifier, Keyboard, type ListPageOptions, type ListPageRequest, type Logger, type Loose, type ManagedSession, type MediaDevices, MonthlySessionLimitError, Mouse, type MouseButton, type MouseClickOptions, type Noise, NotFoundError, type OS, type OneTimeStartRequest, Page, PageClosedError, PaymentRequiredError, type PoolHandler, type PoolOptions, type PoolOutcome, type PremiumProxy, PremiumTrafficLimitError, type PressOptions, type Profile, type ProfileCreate, type ProfileCreateRequest, type ProfileOrdering, type ProfileRef, type ProfileStartRequest, type ProfileSummary, type ProfileUpdate, type ProfileUpdateRequest, Profiles, Proxies, type ProxyCity, type ProxyCountry, ProxyCycle, type ProxyFactory, type ProxyInput, type ProxyLike, type ProxyQuota, ProxyRandom, type ProxyRegion, type ProxySource, ProxyTemplate, type ProxyType, RESOURCE_TYPES, RateLimitError, type RegionalPool, type Renderer, type RequestOptions, type ScrapeOptions, type ScrapeRequest, type ScrapeResult, type Screen, type ScreenshotOptions, type ScrollBehavior, type ScrollOptions, type ScrollToOptions, ServerError, type Session, type SessionLimits, type SessionOptions, type SessionStartOptions, type SharedProxy, type SharedProxyQuota, SharedTrafficLimitError, type Spec, type StopAllResult, StopRun, type StorageOptions, Surfsky, SurfskyError, type SurfskyOptions, type TrafficStats, type TrafficVolume, VERSION, ValidationError, type WaitOptions, type WaitUntil, type WebSocketLike, type isRecord, type parseListPageRequest, type parseOneTimeStartRequest, type parseProfileCreateRequest, type parseProfileStartRequest, type parseProfileUpdateRequest, type parseProxyLike, type parseScrapeRequest };
package/dist/index.js CHANGED
@@ -277,6 +277,20 @@ const regionalPool = z.enum([
277
277
  "oceania",
278
278
  "mena"
279
279
  ]);
280
+ const captchaType = z.enum([
281
+ "recaptcha",
282
+ "hcaptcha",
283
+ "turnstile",
284
+ "perimeterx",
285
+ "geetest",
286
+ "bls",
287
+ "datadome",
288
+ "datadome_audio",
289
+ "datadome_click",
290
+ "click",
291
+ "image",
292
+ "funcaptcha"
293
+ ]);
280
294
  const loose = () => z.string();
281
295
  function isRecord(value) {
282
296
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -329,6 +343,13 @@ const browserSettings = z.strictObject({
329
343
  cache_enabled: z.boolean().optional(),
330
344
  cache_key: z.string().optional()
331
345
  });
346
+ const antiCaptcha = z.strictObject({
347
+ enabled: z.boolean().optional(),
348
+ auto_captcha_types: z.array(captchaType).optional(),
349
+ gemini_api_key: z.string().optional(),
350
+ disable_external_providers: z.boolean().optional(),
351
+ external_providers_initially_enabled: z.boolean().optional()
352
+ });
332
353
  const shared = z.strictObject({
333
354
  tier: z.literal("shared"),
334
355
  country: z.string().min(1).optional()
@@ -383,6 +404,7 @@ const oneTimeStartRequest = z.strictObject({
383
404
  fingerprint: fingerprint.optional(),
384
405
  proxy: proxyLike.nullish(),
385
406
  browser_settings: browserSettings.optional(),
407
+ anti_captcha: antiCaptcha.optional(),
386
408
  enable_chromedriver: z.boolean().optional(),
387
409
  extensions: z.array(z.string()).max(5).optional(),
388
410
  proxy_blacklist: z.array(z.string()).optional(),
@@ -920,9 +942,6 @@ var Deadline = class {
920
942
  error() {
921
943
  return new BrowserTimeoutError(`${this.message} within ${this.timeout}ms`);
922
944
  }
923
- check() {
924
- if (Date.now() >= this.until) throw this.error();
925
- }
926
945
  race(promise) {
927
946
  if (this.timeout === null) return promise;
928
947
  return withTimeout(promise, Math.max(0, this.until - Date.now()), () => this.error());
@@ -1064,6 +1083,9 @@ var Page = class extends Actions {
1064
1083
  }
1065
1084
  }
1066
1085
  /** @internal */
1086
+ _change() {
1087
+ return Promise.race([this._browser._changed.promise, sleep(100)]);
1088
+ }
1067
1089
  async _waitReady() {
1068
1090
  await this._ready.wait();
1069
1091
  if (this._setupError !== void 0) throw this._setupError;
@@ -1113,8 +1135,7 @@ var Page = class extends Actions {
1113
1135
  const deadline = new Deadline(options.timeout ?? 3e4, `the page did not reach '${state}'`);
1114
1136
  while (!this._milestones.has(milestone)) {
1115
1137
  this._requireOpen();
1116
- deadline.check();
1117
- await sleep(100);
1138
+ await deadline.race(this._change());
1118
1139
  }
1119
1140
  }
1120
1141
  async goto(url, options = {}) {
@@ -1166,8 +1187,7 @@ var Page = class extends Actions {
1166
1187
  const deadline = new Deadline(timeout, "history did not move");
1167
1188
  while (true) {
1168
1189
  if ((await deadline.race(this.send("Page.getNavigationHistory"))).currentIndex === index) break;
1169
- deadline.check();
1170
- await sleep(100);
1190
+ await deadline.race(sleep(100));
1171
1191
  }
1172
1192
  return this.url();
1173
1193
  }
@@ -1192,8 +1212,7 @@ var Page = class extends Actions {
1192
1212
  const found = this._responses.find((response) => response.url.includes(fragment));
1193
1213
  if (found !== void 0) return found;
1194
1214
  this._requireOpen();
1195
- deadline.check();
1196
- await sleep(100);
1215
+ await deadline.race(this._change());
1197
1216
  }
1198
1217
  }
1199
1218
  /** @internal */
@@ -1221,6 +1240,7 @@ var Page = class extends Actions {
1221
1240
  this._browser.logger.debug(`no body for ${String(response.url)}: ${String(err)}`);
1222
1241
  }
1223
1242
  into.push(new CapturedResponse(typeof response.url === "string" ? response.url : "", typeof response.status === "number" ? response.status : 0, isRecord(response.headers) ? response.headers : {}, body));
1243
+ this._browser._notify();
1224
1244
  }
1225
1245
  localStorage() {
1226
1246
  return this.#storage(true);
@@ -1338,8 +1358,7 @@ var Page = class extends Actions {
1338
1358
  while (true) {
1339
1359
  const value = await deadline.race(this.evaluate(expression, rest));
1340
1360
  if (value) return value;
1341
- deadline.check();
1342
- await sleep(100);
1361
+ await deadline.race(sleep(100));
1343
1362
  }
1344
1363
  }
1345
1364
  async innerText(selector) {
@@ -1413,10 +1432,7 @@ var Page = class extends Actions {
1413
1432
  const visible = options.visible ?? true;
1414
1433
  const state = visible ? "visible" : "in the document";
1415
1434
  const deadline = new Deadline(options.timeout ?? 3e4, `'${selector}' was not ${state}`);
1416
- while (!await deadline.race(this.#matches(selector, visible))) {
1417
- deadline.check();
1418
- await sleep(100);
1419
- }
1435
+ while (!await deadline.race(this.#matches(selector, visible))) await deadline.race(sleep(100));
1420
1436
  }
1421
1437
  async #matches(selector, visible) {
1422
1438
  let box;
@@ -1472,8 +1488,7 @@ var Page = class extends Actions {
1472
1488
  while (true) {
1473
1489
  const url = await deadline.race(this.url());
1474
1490
  if (url.includes(fragment)) return url;
1475
- deadline.check();
1476
- await sleep(100);
1491
+ await deadline.race(sleep(100));
1477
1492
  }
1478
1493
  }
1479
1494
  /** The first match has a bounding box. */
@@ -1551,6 +1566,7 @@ var Page = class extends Actions {
1551
1566
  this._loaderId = loaderId;
1552
1567
  this._milestones = /* @__PURE__ */ new Set([name]);
1553
1568
  }
1569
+ this._browser._notify();
1554
1570
  this._waiter?.observe(name, loaderId);
1555
1571
  }
1556
1572
  /** @internal */
@@ -1663,6 +1679,7 @@ var Browser = class extends Page {
1663
1679
  #client;
1664
1680
  #abort = new AbortController();
1665
1681
  #pending = /* @__PURE__ */ new Set();
1682
+ _changed = Promise.withResolvers();
1666
1683
  #closing;
1667
1684
  #retired = false;
1668
1685
  #useCount = 0;
@@ -1744,8 +1761,7 @@ var Browser = class extends Page {
1744
1761
  const last = opened[opened.length - 1];
1745
1762
  if (last !== void 0) return last;
1746
1763
  this._requireOpen();
1747
- deadline.check();
1748
- await sleep(100);
1764
+ await deadline.race(this._change());
1749
1765
  }
1750
1766
  }
1751
1767
  async connect() {
@@ -1817,8 +1833,7 @@ var Browser = class extends Page {
1817
1833
  let page = this.#pageFor(targetId);
1818
1834
  while (page === void 0) {
1819
1835
  this._requireOpen();
1820
- deadline.check();
1821
- await sleep(100);
1836
+ await deadline.race(this._change());
1822
1837
  page = this.#pageFor(targetId);
1823
1838
  }
1824
1839
  await deadline.race(page._waitReady());
@@ -1848,8 +1863,14 @@ var Browser = class extends Page {
1848
1863
  page._frameId = page._targetId;
1849
1864
  page._ready = new Flag();
1850
1865
  this._pages.set(sessionId, page);
1866
+ this._notify();
1851
1867
  this._spawn(page._setup(waiting));
1852
1868
  }
1869
+ /** @internal */
1870
+ _notify() {
1871
+ this._changed.resolve();
1872
+ this._changed = Promise.withResolvers();
1873
+ }
1853
1874
  async #letGo(sessionId, waiting) {
1854
1875
  const timeout = this.commandTimeout;
1855
1876
  const detach = async () => {
@@ -1867,6 +1888,7 @@ var Browser = class extends Page {
1867
1888
  _drop(page) {
1868
1889
  this._pages.delete(page._sessionId);
1869
1890
  page._closed = true;
1891
+ this._notify();
1870
1892
  page._waiter?.fail("page closed");
1871
1893
  if (page === this) {
1872
1894
  this.logger.warn(`page target gone for ${this.internalUuid}`);
@@ -1885,6 +1907,7 @@ var Browser = class extends Page {
1885
1907
  page._closed = true;
1886
1908
  page._waiter?.fail("CDP connection closed");
1887
1909
  }
1910
+ this._notify();
1888
1911
  }
1889
1912
  /** @internal track a background task; its failure is logged at debug level */
1890
1913
  _spawn(task) {
@@ -2230,7 +2253,6 @@ var BrowserPool = class {
2230
2253
  #idle = [];
2231
2254
  #owned = 0;
2232
2255
  #planFull = false;
2233
- #planFullError;
2234
2256
  #waiters = [];
2235
2257
  #capacity = 0;
2236
2258
  #slots;
@@ -2263,7 +2285,6 @@ var BrowserPool = class {
2263
2285
  this.#capacity = Math.max(1, total);
2264
2286
  this.#slots = new Semaphore(this.#capacity);
2265
2287
  this.#planFull = false;
2266
- this.#planFullError = void 0;
2267
2288
  this.#owned = 0;
2268
2289
  return this;
2269
2290
  }
@@ -2279,14 +2300,17 @@ var BrowserPool = class {
2279
2300
  return this.close();
2280
2301
  }
2281
2302
  /** Run `fn` on a live browser, then hand the browser back. Waits while all are busy. */
2282
- async lease(fn) {
2303
+ async lease(fn, { signal } = {}) {
2304
+ signal?.throwIfAborted();
2283
2305
  const slots = this.#requireOpen();
2284
2306
  await slots.acquire();
2285
2307
  try {
2286
2308
  if (this.#slots !== slots) throw new Error("the pool was closed while waiting for a slot");
2309
+ signal?.throwIfAborted();
2287
2310
  const browser = await this.#acquire();
2288
2311
  browser._lease();
2289
2312
  try {
2313
+ signal?.throwIfAborted();
2290
2314
  return await fn(browser);
2291
2315
  } finally {
2292
2316
  await this.#release(browser);
@@ -2317,7 +2341,6 @@ var BrowserPool = class {
2317
2341
  const idle = this.#idle.pop();
2318
2342
  if (idle !== void 0) return idle;
2319
2343
  if (this.#planFull) {
2320
- if (this.#owned === 0) throw this.#planFullError ?? new RateLimitError("parallel browser limit reached", { statusCode: 429 });
2321
2344
  await this.#wait();
2322
2345
  continue;
2323
2346
  }
@@ -2326,15 +2349,10 @@ var BrowserPool = class {
2326
2349
  try {
2327
2350
  browser = await this.#startBrowser();
2328
2351
  } catch (err) {
2329
- const full = planIsFull(err);
2330
2352
  this.#owned -= 1;
2331
- if (full) {
2332
- this.#planFull = true;
2333
- this.#planFullError = err;
2334
- }
2335
- const nothingLeft = this.#owned === 0;
2353
+ this.#planFull = planIsFull(err) && this.#owned > 0;
2336
2354
  this.#notifyAll();
2337
- if (!full || nothingLeft) throw err;
2355
+ if (!this.#planFull) throw err;
2338
2356
  this.#client.logger.info("plan is full, waiting for a browser of ours");
2339
2357
  continue;
2340
2358
  }
@@ -2358,20 +2376,20 @@ var BrowserPool = class {
2358
2376
  this.#notifyAll();
2359
2377
  }
2360
2378
  /** Run every item through `handler` on a leased browser, `capacity` at a time. */
2361
- async map(handler, items) {
2379
+ async map(handler, items, { signal } = {}) {
2362
2380
  const work = items[Symbol.iterator]();
2363
2381
  const outcomes = [];
2364
2382
  let index = 0;
2365
2383
  let stopped = false;
2366
2384
  const worker = async () => {
2367
- while (!stopped) {
2385
+ while (!stopped && !signal?.aborted) {
2368
2386
  const next = work.next();
2369
2387
  if (next.done) return;
2370
2388
  const item = next.value;
2371
2389
  const at = index;
2372
2390
  index += 1;
2373
2391
  try {
2374
- const value = await this.lease((browser) => handler(browser, item));
2392
+ const value = await this.lease((browser) => handler(browser, item), { signal });
2375
2393
  outcomes.push({
2376
2394
  ok: true,
2377
2395
  item,
@@ -2678,7 +2696,7 @@ var Proxies = class {
2678
2696
  };
2679
2697
  //#endregion
2680
2698
  //#region src/version.ts
2681
- const VERSION = "0.0.6";
2699
+ const VERSION = "0.0.9";
2682
2700
  //#endregion
2683
2701
  //#region src/client.ts
2684
2702
  function userAgent() {
@@ -2777,13 +2795,18 @@ var Surfsky = class Surfsky {
2777
2795
  }
2778
2796
  /** Start a session; `stop()` it (or `await using`) so it does not keep billing. */
2779
2797
  async session(options = {}) {
2780
- const { profileUuid, ...rest } = options;
2798
+ const { profileUuid, signal, ...rest } = options;
2799
+ signal?.throwIfAborted();
2781
2800
  const session = profileUuid === void 0 ? await this.profiles.startOneTime(rest) : await this.profiles.start(profileUuid, rest);
2782
2801
  let stopping;
2783
2802
  const stop = () => {
2784
2803
  stopping ??= stopSession(this, session.internal_uuid);
2785
2804
  return stopping;
2786
2805
  };
2806
+ if (signal?.aborted) {
2807
+ await stop();
2808
+ throw signal.reason;
2809
+ }
2787
2810
  return {
2788
2811
  ...session,
2789
2812
  stop,
@@ -2792,11 +2815,12 @@ var Surfsky = class Surfsky {
2792
2815
  }
2793
2816
  /** Start a session and connect a `Browser` to it. `close()` stops the session. */
2794
2817
  async browser(options = {}) {
2795
- const { profileUuid, blockResources, blockUrls, connectTimeout, commandTimeout, createWebSocket, ...sessionOptions } = options;
2818
+ const { profileUuid, signal, blockResources, blockUrls, connectTimeout, commandTimeout, createWebSocket, ...sessionOptions } = options;
2796
2819
  const blocked = normalizeBlocked(blockResources);
2797
2820
  const urls = normalizeUrls(blockUrls);
2798
2821
  const session = await this.session({
2799
2822
  profileUuid,
2823
+ signal,
2800
2824
  ...sessionOptions
2801
2825
  });
2802
2826
  const browser = new Browser(session, {
@@ -2808,7 +2832,19 @@ var Surfsky = class Surfsky {
2808
2832
  logger: this.logger,
2809
2833
  onClose: () => session.stop()
2810
2834
  });
2811
- await browser.connect();
2835
+ const onAbort = () => void browser.close();
2836
+ signal?.addEventListener("abort", onAbort, { once: true });
2837
+ try {
2838
+ await browser.connect();
2839
+ } catch (err) {
2840
+ throw signal?.aborted ? signal.reason : err;
2841
+ } finally {
2842
+ signal?.removeEventListener("abort", onAbort);
2843
+ }
2844
+ if (signal?.aborted) {
2845
+ await browser.close();
2846
+ throw signal.reason;
2847
+ }
2812
2848
  return browser;
2813
2849
  }
2814
2850
  /** An open `BrowserPool`. `close()` stops every browser in it. */
@@ -2817,9 +2853,10 @@ var Surfsky = class Surfsky {
2817
2853
  }
2818
2854
  /** `browsers()` and `pool.map()` in 1 call. */
2819
2855
  async map(handler, items, options = {}) {
2820
- const pool = await this.browsers(options);
2856
+ const { signal, ...poolOptions } = options;
2857
+ const pool = await this.browsers(poolOptions);
2821
2858
  try {
2822
- return await pool.map(handler, items);
2859
+ return await pool.map(handler, items, { signal });
2823
2860
  } finally {
2824
2861
  await pool.close();
2825
2862
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "surfsky",
3
- "version": "0.0.6",
3
+ "version": "0.0.9",
4
4
  "description": "TypeScript SDK for the Surfsky antidetect browser cloud.",
5
5
  "keywords": [
6
6
  "surfsky",