surfsky 0.0.6 → 0.0.8
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 +11 -4
- package/dist/index.js +56 -41
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1266,7 +1266,6 @@ declare class Deadline {
|
|
|
1266
1266
|
readonly until: number;
|
|
1267
1267
|
constructor(timeout: number | null | undefined, message: string);
|
|
1268
1268
|
error(): BrowserTimeoutError;
|
|
1269
|
-
check(): void;
|
|
1270
1269
|
race<T>(promise: Promise<T>): Promise<T>;
|
|
1271
1270
|
}
|
|
1272
1271
|
declare class CapturedResponse {
|
|
@@ -1330,6 +1329,7 @@ declare class Page extends Actions {
|
|
|
1330
1329
|
/** @internal */
|
|
1331
1330
|
_send(method: string, params?: Record<string, unknown>): Promise<any>;
|
|
1332
1331
|
/** @internal */
|
|
1332
|
+
_change(): Promise<void>;
|
|
1333
1333
|
_waitReady(): Promise<void>;
|
|
1334
1334
|
/** @internal */
|
|
1335
1335
|
_setup(waiting: boolean): Promise<void>;
|
|
@@ -1428,6 +1428,7 @@ declare class Browser extends Page implements AsyncDisposable {
|
|
|
1428
1428
|
/** @internal */ _pages: Map<string, Page>;
|
|
1429
1429
|
/** @internal aborts on close: background sleeps end early */
|
|
1430
1430
|
_signal: AbortSignal;
|
|
1431
|
+
_changed: PromiseWithResolvers<void>;
|
|
1431
1432
|
constructor(session: Session, options?: BrowserOptions);
|
|
1432
1433
|
get internalUuid(): string;
|
|
1433
1434
|
get cdp(): CDPClient;
|
|
@@ -1466,6 +1467,8 @@ declare class Browser extends Page implements AsyncDisposable {
|
|
|
1466
1467
|
/** @internal */
|
|
1467
1468
|
_onAttached(event: Record<string, any>): void;
|
|
1468
1469
|
/** @internal */
|
|
1470
|
+
_notify(): void;
|
|
1471
|
+
/** @internal */
|
|
1469
1472
|
_onDetached(event: Record<string, any>): void;
|
|
1470
1473
|
/** @internal */
|
|
1471
1474
|
_drop(page: Page): void;
|
|
@@ -1628,6 +1631,7 @@ interface RequestOptions {
|
|
|
1628
1631
|
interface SessionStartOptions extends SessionOptions {
|
|
1629
1632
|
/** A saved profile to start; a one-time session when absent. */
|
|
1630
1633
|
profileUuid?: string;
|
|
1634
|
+
signal?: AbortSignal;
|
|
1631
1635
|
}
|
|
1632
1636
|
interface BrowserStartOptions extends SessionStartOptions {
|
|
1633
1637
|
/** CDP resource types to drop: `["image", "font", "media"]` saves proxy traffic. */
|
|
@@ -1674,7 +1678,7 @@ declare class Surfsky implements AsyncDisposable {
|
|
|
1674
1678
|
/** An open `BrowserPool`. `close()` stops every browser in it. */
|
|
1675
1679
|
browsers(options?: PoolOptions): Promise<BrowserPool>;
|
|
1676
1680
|
/** `browsers()` and `pool.map()` in 1 call. */
|
|
1677
|
-
map<I, R>(handler: PoolHandler<I, R>, items: Iterable<I>, options?: PoolOptions): Promise<PoolOutcome<I, R>[]>;
|
|
1681
|
+
map<I, R>(handler: PoolHandler<I, R>, items: Iterable<I>, options?: PoolOptions & LeaseOptions): Promise<PoolOutcome<I, R>[]>;
|
|
1678
1682
|
/** Nothing to release with fetch; here for `await using` symmetry. */
|
|
1679
1683
|
close(): Promise<void>;
|
|
1680
1684
|
[Symbol.asyncDispose](): Promise<void>;
|
|
@@ -1704,6 +1708,9 @@ type PoolOutcome<I, R> = {
|
|
|
1704
1708
|
index: number;
|
|
1705
1709
|
error: unknown;
|
|
1706
1710
|
};
|
|
1711
|
+
interface LeaseOptions {
|
|
1712
|
+
signal?: AbortSignal;
|
|
1713
|
+
}
|
|
1707
1714
|
declare class BrowserPool implements AsyncDisposable {
|
|
1708
1715
|
#private;
|
|
1709
1716
|
readonly sessionOptions: SessionOptions;
|
|
@@ -1717,9 +1724,9 @@ declare class BrowserPool implements AsyncDisposable {
|
|
|
1717
1724
|
close(): Promise<void>;
|
|
1718
1725
|
[Symbol.asyncDispose](): Promise<void>;
|
|
1719
1726
|
/** Run `fn` on a live browser, then hand the browser back. Waits while all are busy. */
|
|
1720
|
-
lease<R>(fn: (browser: Browser) => Promise<R
|
|
1727
|
+
lease<R>(fn: (browser: Browser) => Promise<R>, { signal }?: LeaseOptions): Promise<R>;
|
|
1721
1728
|
/** Run every item through `handler` on a leased browser, `capacity` at a time. */
|
|
1722
|
-
map<I, R>(handler: PoolHandler<I, R>, items: Iterable<I
|
|
1729
|
+
map<I, R>(handler: PoolHandler<I, R>, items: Iterable<I>, { signal }?: LeaseOptions): Promise<PoolOutcome<I, R>[]>;
|
|
1723
1730
|
}
|
|
1724
1731
|
//#endregion
|
|
1725
1732
|
//#region src/version.d.ts
|
package/dist/index.js
CHANGED
|
@@ -920,9 +920,6 @@ var Deadline = class {
|
|
|
920
920
|
error() {
|
|
921
921
|
return new BrowserTimeoutError(`${this.message} within ${this.timeout}ms`);
|
|
922
922
|
}
|
|
923
|
-
check() {
|
|
924
|
-
if (Date.now() >= this.until) throw this.error();
|
|
925
|
-
}
|
|
926
923
|
race(promise) {
|
|
927
924
|
if (this.timeout === null) return promise;
|
|
928
925
|
return withTimeout(promise, Math.max(0, this.until - Date.now()), () => this.error());
|
|
@@ -1064,6 +1061,9 @@ var Page = class extends Actions {
|
|
|
1064
1061
|
}
|
|
1065
1062
|
}
|
|
1066
1063
|
/** @internal */
|
|
1064
|
+
_change() {
|
|
1065
|
+
return Promise.race([this._browser._changed.promise, sleep(100)]);
|
|
1066
|
+
}
|
|
1067
1067
|
async _waitReady() {
|
|
1068
1068
|
await this._ready.wait();
|
|
1069
1069
|
if (this._setupError !== void 0) throw this._setupError;
|
|
@@ -1113,8 +1113,7 @@ var Page = class extends Actions {
|
|
|
1113
1113
|
const deadline = new Deadline(options.timeout ?? 3e4, `the page did not reach '${state}'`);
|
|
1114
1114
|
while (!this._milestones.has(milestone)) {
|
|
1115
1115
|
this._requireOpen();
|
|
1116
|
-
deadline.
|
|
1117
|
-
await sleep(100);
|
|
1116
|
+
await deadline.race(this._change());
|
|
1118
1117
|
}
|
|
1119
1118
|
}
|
|
1120
1119
|
async goto(url, options = {}) {
|
|
@@ -1166,8 +1165,7 @@ var Page = class extends Actions {
|
|
|
1166
1165
|
const deadline = new Deadline(timeout, "history did not move");
|
|
1167
1166
|
while (true) {
|
|
1168
1167
|
if ((await deadline.race(this.send("Page.getNavigationHistory"))).currentIndex === index) break;
|
|
1169
|
-
deadline.
|
|
1170
|
-
await sleep(100);
|
|
1168
|
+
await deadline.race(sleep(100));
|
|
1171
1169
|
}
|
|
1172
1170
|
return this.url();
|
|
1173
1171
|
}
|
|
@@ -1192,8 +1190,7 @@ var Page = class extends Actions {
|
|
|
1192
1190
|
const found = this._responses.find((response) => response.url.includes(fragment));
|
|
1193
1191
|
if (found !== void 0) return found;
|
|
1194
1192
|
this._requireOpen();
|
|
1195
|
-
deadline.
|
|
1196
|
-
await sleep(100);
|
|
1193
|
+
await deadline.race(this._change());
|
|
1197
1194
|
}
|
|
1198
1195
|
}
|
|
1199
1196
|
/** @internal */
|
|
@@ -1221,6 +1218,7 @@ var Page = class extends Actions {
|
|
|
1221
1218
|
this._browser.logger.debug(`no body for ${String(response.url)}: ${String(err)}`);
|
|
1222
1219
|
}
|
|
1223
1220
|
into.push(new CapturedResponse(typeof response.url === "string" ? response.url : "", typeof response.status === "number" ? response.status : 0, isRecord(response.headers) ? response.headers : {}, body));
|
|
1221
|
+
this._browser._notify();
|
|
1224
1222
|
}
|
|
1225
1223
|
localStorage() {
|
|
1226
1224
|
return this.#storage(true);
|
|
@@ -1338,8 +1336,7 @@ var Page = class extends Actions {
|
|
|
1338
1336
|
while (true) {
|
|
1339
1337
|
const value = await deadline.race(this.evaluate(expression, rest));
|
|
1340
1338
|
if (value) return value;
|
|
1341
|
-
deadline.
|
|
1342
|
-
await sleep(100);
|
|
1339
|
+
await deadline.race(sleep(100));
|
|
1343
1340
|
}
|
|
1344
1341
|
}
|
|
1345
1342
|
async innerText(selector) {
|
|
@@ -1413,10 +1410,7 @@ var Page = class extends Actions {
|
|
|
1413
1410
|
const visible = options.visible ?? true;
|
|
1414
1411
|
const state = visible ? "visible" : "in the document";
|
|
1415
1412
|
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
|
-
}
|
|
1413
|
+
while (!await deadline.race(this.#matches(selector, visible))) await deadline.race(sleep(100));
|
|
1420
1414
|
}
|
|
1421
1415
|
async #matches(selector, visible) {
|
|
1422
1416
|
let box;
|
|
@@ -1472,8 +1466,7 @@ var Page = class extends Actions {
|
|
|
1472
1466
|
while (true) {
|
|
1473
1467
|
const url = await deadline.race(this.url());
|
|
1474
1468
|
if (url.includes(fragment)) return url;
|
|
1475
|
-
deadline.
|
|
1476
|
-
await sleep(100);
|
|
1469
|
+
await deadline.race(sleep(100));
|
|
1477
1470
|
}
|
|
1478
1471
|
}
|
|
1479
1472
|
/** The first match has a bounding box. */
|
|
@@ -1551,6 +1544,7 @@ var Page = class extends Actions {
|
|
|
1551
1544
|
this._loaderId = loaderId;
|
|
1552
1545
|
this._milestones = /* @__PURE__ */ new Set([name]);
|
|
1553
1546
|
}
|
|
1547
|
+
this._browser._notify();
|
|
1554
1548
|
this._waiter?.observe(name, loaderId);
|
|
1555
1549
|
}
|
|
1556
1550
|
/** @internal */
|
|
@@ -1663,6 +1657,7 @@ var Browser = class extends Page {
|
|
|
1663
1657
|
#client;
|
|
1664
1658
|
#abort = new AbortController();
|
|
1665
1659
|
#pending = /* @__PURE__ */ new Set();
|
|
1660
|
+
_changed = Promise.withResolvers();
|
|
1666
1661
|
#closing;
|
|
1667
1662
|
#retired = false;
|
|
1668
1663
|
#useCount = 0;
|
|
@@ -1744,8 +1739,7 @@ var Browser = class extends Page {
|
|
|
1744
1739
|
const last = opened[opened.length - 1];
|
|
1745
1740
|
if (last !== void 0) return last;
|
|
1746
1741
|
this._requireOpen();
|
|
1747
|
-
deadline.
|
|
1748
|
-
await sleep(100);
|
|
1742
|
+
await deadline.race(this._change());
|
|
1749
1743
|
}
|
|
1750
1744
|
}
|
|
1751
1745
|
async connect() {
|
|
@@ -1817,8 +1811,7 @@ var Browser = class extends Page {
|
|
|
1817
1811
|
let page = this.#pageFor(targetId);
|
|
1818
1812
|
while (page === void 0) {
|
|
1819
1813
|
this._requireOpen();
|
|
1820
|
-
deadline.
|
|
1821
|
-
await sleep(100);
|
|
1814
|
+
await deadline.race(this._change());
|
|
1822
1815
|
page = this.#pageFor(targetId);
|
|
1823
1816
|
}
|
|
1824
1817
|
await deadline.race(page._waitReady());
|
|
@@ -1848,8 +1841,14 @@ var Browser = class extends Page {
|
|
|
1848
1841
|
page._frameId = page._targetId;
|
|
1849
1842
|
page._ready = new Flag();
|
|
1850
1843
|
this._pages.set(sessionId, page);
|
|
1844
|
+
this._notify();
|
|
1851
1845
|
this._spawn(page._setup(waiting));
|
|
1852
1846
|
}
|
|
1847
|
+
/** @internal */
|
|
1848
|
+
_notify() {
|
|
1849
|
+
this._changed.resolve();
|
|
1850
|
+
this._changed = Promise.withResolvers();
|
|
1851
|
+
}
|
|
1853
1852
|
async #letGo(sessionId, waiting) {
|
|
1854
1853
|
const timeout = this.commandTimeout;
|
|
1855
1854
|
const detach = async () => {
|
|
@@ -1867,6 +1866,7 @@ var Browser = class extends Page {
|
|
|
1867
1866
|
_drop(page) {
|
|
1868
1867
|
this._pages.delete(page._sessionId);
|
|
1869
1868
|
page._closed = true;
|
|
1869
|
+
this._notify();
|
|
1870
1870
|
page._waiter?.fail("page closed");
|
|
1871
1871
|
if (page === this) {
|
|
1872
1872
|
this.logger.warn(`page target gone for ${this.internalUuid}`);
|
|
@@ -1885,6 +1885,7 @@ var Browser = class extends Page {
|
|
|
1885
1885
|
page._closed = true;
|
|
1886
1886
|
page._waiter?.fail("CDP connection closed");
|
|
1887
1887
|
}
|
|
1888
|
+
this._notify();
|
|
1888
1889
|
}
|
|
1889
1890
|
/** @internal track a background task; its failure is logged at debug level */
|
|
1890
1891
|
_spawn(task) {
|
|
@@ -2230,7 +2231,6 @@ var BrowserPool = class {
|
|
|
2230
2231
|
#idle = [];
|
|
2231
2232
|
#owned = 0;
|
|
2232
2233
|
#planFull = false;
|
|
2233
|
-
#planFullError;
|
|
2234
2234
|
#waiters = [];
|
|
2235
2235
|
#capacity = 0;
|
|
2236
2236
|
#slots;
|
|
@@ -2263,7 +2263,6 @@ var BrowserPool = class {
|
|
|
2263
2263
|
this.#capacity = Math.max(1, total);
|
|
2264
2264
|
this.#slots = new Semaphore(this.#capacity);
|
|
2265
2265
|
this.#planFull = false;
|
|
2266
|
-
this.#planFullError = void 0;
|
|
2267
2266
|
this.#owned = 0;
|
|
2268
2267
|
return this;
|
|
2269
2268
|
}
|
|
@@ -2279,14 +2278,17 @@ var BrowserPool = class {
|
|
|
2279
2278
|
return this.close();
|
|
2280
2279
|
}
|
|
2281
2280
|
/** Run `fn` on a live browser, then hand the browser back. Waits while all are busy. */
|
|
2282
|
-
async lease(fn) {
|
|
2281
|
+
async lease(fn, { signal } = {}) {
|
|
2282
|
+
signal?.throwIfAborted();
|
|
2283
2283
|
const slots = this.#requireOpen();
|
|
2284
2284
|
await slots.acquire();
|
|
2285
2285
|
try {
|
|
2286
2286
|
if (this.#slots !== slots) throw new Error("the pool was closed while waiting for a slot");
|
|
2287
|
+
signal?.throwIfAborted();
|
|
2287
2288
|
const browser = await this.#acquire();
|
|
2288
2289
|
browser._lease();
|
|
2289
2290
|
try {
|
|
2291
|
+
signal?.throwIfAborted();
|
|
2290
2292
|
return await fn(browser);
|
|
2291
2293
|
} finally {
|
|
2292
2294
|
await this.#release(browser);
|
|
@@ -2317,7 +2319,6 @@ var BrowserPool = class {
|
|
|
2317
2319
|
const idle = this.#idle.pop();
|
|
2318
2320
|
if (idle !== void 0) return idle;
|
|
2319
2321
|
if (this.#planFull) {
|
|
2320
|
-
if (this.#owned === 0) throw this.#planFullError ?? new RateLimitError("parallel browser limit reached", { statusCode: 429 });
|
|
2321
2322
|
await this.#wait();
|
|
2322
2323
|
continue;
|
|
2323
2324
|
}
|
|
@@ -2326,15 +2327,10 @@ var BrowserPool = class {
|
|
|
2326
2327
|
try {
|
|
2327
2328
|
browser = await this.#startBrowser();
|
|
2328
2329
|
} catch (err) {
|
|
2329
|
-
const full = planIsFull(err);
|
|
2330
2330
|
this.#owned -= 1;
|
|
2331
|
-
|
|
2332
|
-
this.#planFull = true;
|
|
2333
|
-
this.#planFullError = err;
|
|
2334
|
-
}
|
|
2335
|
-
const nothingLeft = this.#owned === 0;
|
|
2331
|
+
this.#planFull = planIsFull(err) && this.#owned > 0;
|
|
2336
2332
|
this.#notifyAll();
|
|
2337
|
-
if (!
|
|
2333
|
+
if (!this.#planFull) throw err;
|
|
2338
2334
|
this.#client.logger.info("plan is full, waiting for a browser of ours");
|
|
2339
2335
|
continue;
|
|
2340
2336
|
}
|
|
@@ -2358,20 +2354,20 @@ var BrowserPool = class {
|
|
|
2358
2354
|
this.#notifyAll();
|
|
2359
2355
|
}
|
|
2360
2356
|
/** Run every item through `handler` on a leased browser, `capacity` at a time. */
|
|
2361
|
-
async map(handler, items) {
|
|
2357
|
+
async map(handler, items, { signal } = {}) {
|
|
2362
2358
|
const work = items[Symbol.iterator]();
|
|
2363
2359
|
const outcomes = [];
|
|
2364
2360
|
let index = 0;
|
|
2365
2361
|
let stopped = false;
|
|
2366
2362
|
const worker = async () => {
|
|
2367
|
-
while (!stopped) {
|
|
2363
|
+
while (!stopped && !signal?.aborted) {
|
|
2368
2364
|
const next = work.next();
|
|
2369
2365
|
if (next.done) return;
|
|
2370
2366
|
const item = next.value;
|
|
2371
2367
|
const at = index;
|
|
2372
2368
|
index += 1;
|
|
2373
2369
|
try {
|
|
2374
|
-
const value = await this.lease((browser) => handler(browser, item));
|
|
2370
|
+
const value = await this.lease((browser) => handler(browser, item), { signal });
|
|
2375
2371
|
outcomes.push({
|
|
2376
2372
|
ok: true,
|
|
2377
2373
|
item,
|
|
@@ -2678,7 +2674,7 @@ var Proxies = class {
|
|
|
2678
2674
|
};
|
|
2679
2675
|
//#endregion
|
|
2680
2676
|
//#region src/version.ts
|
|
2681
|
-
const VERSION = "0.0.
|
|
2677
|
+
const VERSION = "0.0.8";
|
|
2682
2678
|
//#endregion
|
|
2683
2679
|
//#region src/client.ts
|
|
2684
2680
|
function userAgent() {
|
|
@@ -2777,13 +2773,18 @@ var Surfsky = class Surfsky {
|
|
|
2777
2773
|
}
|
|
2778
2774
|
/** Start a session; `stop()` it (or `await using`) so it does not keep billing. */
|
|
2779
2775
|
async session(options = {}) {
|
|
2780
|
-
const { profileUuid, ...rest } = options;
|
|
2776
|
+
const { profileUuid, signal, ...rest } = options;
|
|
2777
|
+
signal?.throwIfAborted();
|
|
2781
2778
|
const session = profileUuid === void 0 ? await this.profiles.startOneTime(rest) : await this.profiles.start(profileUuid, rest);
|
|
2782
2779
|
let stopping;
|
|
2783
2780
|
const stop = () => {
|
|
2784
2781
|
stopping ??= stopSession(this, session.internal_uuid);
|
|
2785
2782
|
return stopping;
|
|
2786
2783
|
};
|
|
2784
|
+
if (signal?.aborted) {
|
|
2785
|
+
await stop();
|
|
2786
|
+
throw signal.reason;
|
|
2787
|
+
}
|
|
2787
2788
|
return {
|
|
2788
2789
|
...session,
|
|
2789
2790
|
stop,
|
|
@@ -2792,11 +2793,12 @@ var Surfsky = class Surfsky {
|
|
|
2792
2793
|
}
|
|
2793
2794
|
/** Start a session and connect a `Browser` to it. `close()` stops the session. */
|
|
2794
2795
|
async browser(options = {}) {
|
|
2795
|
-
const { profileUuid, blockResources, blockUrls, connectTimeout, commandTimeout, createWebSocket, ...sessionOptions } = options;
|
|
2796
|
+
const { profileUuid, signal, blockResources, blockUrls, connectTimeout, commandTimeout, createWebSocket, ...sessionOptions } = options;
|
|
2796
2797
|
const blocked = normalizeBlocked(blockResources);
|
|
2797
2798
|
const urls = normalizeUrls(blockUrls);
|
|
2798
2799
|
const session = await this.session({
|
|
2799
2800
|
profileUuid,
|
|
2801
|
+
signal,
|
|
2800
2802
|
...sessionOptions
|
|
2801
2803
|
});
|
|
2802
2804
|
const browser = new Browser(session, {
|
|
@@ -2808,7 +2810,19 @@ var Surfsky = class Surfsky {
|
|
|
2808
2810
|
logger: this.logger,
|
|
2809
2811
|
onClose: () => session.stop()
|
|
2810
2812
|
});
|
|
2811
|
-
|
|
2813
|
+
const onAbort = () => void browser.close();
|
|
2814
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2815
|
+
try {
|
|
2816
|
+
await browser.connect();
|
|
2817
|
+
} catch (err) {
|
|
2818
|
+
throw signal?.aborted ? signal.reason : err;
|
|
2819
|
+
} finally {
|
|
2820
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2821
|
+
}
|
|
2822
|
+
if (signal?.aborted) {
|
|
2823
|
+
await browser.close();
|
|
2824
|
+
throw signal.reason;
|
|
2825
|
+
}
|
|
2812
2826
|
return browser;
|
|
2813
2827
|
}
|
|
2814
2828
|
/** An open `BrowserPool`. `close()` stops every browser in it. */
|
|
@@ -2817,9 +2831,10 @@ var Surfsky = class Surfsky {
|
|
|
2817
2831
|
}
|
|
2818
2832
|
/** `browsers()` and `pool.map()` in 1 call. */
|
|
2819
2833
|
async map(handler, items, options = {}) {
|
|
2820
|
-
const
|
|
2834
|
+
const { signal, ...poolOptions } = options;
|
|
2835
|
+
const pool = await this.browsers(poolOptions);
|
|
2821
2836
|
try {
|
|
2822
|
-
return await pool.map(handler, items);
|
|
2837
|
+
return await pool.map(handler, items, { signal });
|
|
2823
2838
|
} finally {
|
|
2824
2839
|
await pool.close();
|
|
2825
2840
|
}
|