@crawlee/browser-pool 4.0.0-beta.98 → 4.0.0-rc.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.
@@ -1,5 +1,6 @@
1
1
  import { serviceLocator } from '@crawlee/core';
2
2
  import { BrowserPool } from './browser-pool.js';
3
+ import { BROWSER_CONTROLLER_EVENTS, BROWSER_POOL_EVENTS } from './events.js';
3
4
  import { RemoteBrowserProvider } from './remote-browser-provider.js';
4
5
  /**
5
6
  * Owns the lifecycle of remote browser sessions for a single {@link RemoteBrowserPool}: endpoint
@@ -7,18 +8,18 @@ import { RemoteBrowserProvider } from './remote-browser-provider.js';
7
8
  * {@link RemoteConnection} so it can be injected into a plugin.
8
9
  */
9
10
  class RemoteSessionRegistry {
10
- endpoint;
11
- onRelease;
12
- log;
13
- sessions = new Map();
14
- nextToken = 0;
11
+ #sessions = new Map();
12
+ #nextToken = 0;
13
+ #endpoint;
14
+ #onRelease;
15
+ #log;
15
16
  constructor(endpoint, onRelease, log) {
16
- this.endpoint = endpoint;
17
- this.onRelease = onRelease;
18
- this.log = log;
17
+ this.#endpoint = endpoint;
18
+ this.#onRelease = onRelease;
19
+ this.#log = log;
19
20
  }
20
21
  async resolve(options) {
21
- const resolved = typeof this.endpoint === 'function' ? await this.endpoint(options) : this.endpoint;
22
+ const resolved = typeof this.#endpoint === 'function' ? await this.#endpoint(options) : this.#endpoint;
22
23
  let result;
23
24
  if (typeof resolved === 'string') {
24
25
  if (!resolved)
@@ -31,30 +32,30 @@ class RemoteSessionRegistry {
31
32
  else {
32
33
  result = resolved;
33
34
  }
34
- const token = this.nextToken++;
35
- this.sessions.set(token, { url: result.url, context: result.context, released: false });
35
+ const token = this.#nextToken++;
36
+ this.#sessions.set(token, { url: result.url, context: result.context, released: false });
36
37
  return { url: result.url, token };
37
38
  }
38
39
  async release(token) {
39
- const session = this.sessions.get(token);
40
+ const session = this.#sessions.get(token);
40
41
  // Release at most once per session — guards a close()/teardown race (the `released` flag is set
41
42
  // synchronously before the awaited onRelease, so releaseAll() can't double-fire an in-flight release).
42
43
  if (!session || session.released)
43
44
  return;
44
45
  session.released = true;
45
46
  try {
46
- await this.onRelease?.({ endpoint: session.url, context: session.context });
47
+ await this.#onRelease?.({ endpoint: session.url, context: session.context });
47
48
  }
48
49
  catch (err) {
49
- this.log.warning('Remote browser release() failed.', { error: err?.message });
50
+ this.#log.warning('Remote browser release() failed.', { error: err?.message });
50
51
  }
51
52
  finally {
52
- this.sessions.delete(token);
53
+ this.#sessions.delete(token);
53
54
  }
54
55
  }
55
56
  /** Releases every session that is still open. Called on pool teardown so no remote session leaks. */
56
57
  async releaseAll() {
57
- await Promise.all([...this.sessions.keys()].map(async (token) => this.release(token)));
58
+ await Promise.all([...this.#sessions.keys()].map(async (token) => this.release(token)));
58
59
  }
59
60
  }
60
61
  /**
@@ -92,16 +93,16 @@ export class RemoteBrowserPool {
92
93
  /** The wrapped pool that performs the remote connections and serves pages. */
93
94
  browserPool;
94
95
  /** The wrapped pool viewed through the {@link IBrowserPool} contract (the bare type widens pages to `never`). */
95
- pool;
96
- registry;
97
- slotPollIntervalMillis;
98
- log;
96
+ #pool;
97
+ #registry;
98
+ #slotPollIntervalMillis;
99
+ #log;
99
100
  /** Shared by all `newPage` callers waiting for a free slot, so they don't each register their own listeners. */
100
- _capacityChange;
101
+ #capacityChange;
101
102
  constructor(options) {
102
103
  const { browserPlugins, endpoint, release, maxOpenBrowsers, connection = {}, browserPoolOptions = {}, slotPollIntervalMillis = 500, } = options;
103
- this.log = serviceLocator.getLogger().child({ prefix: 'RemoteBrowserPool' });
104
- this.slotPollIntervalMillis = slotPollIntervalMillis;
104
+ this.#log = serviceLocator.getLogger().child({ prefix: 'RemoteBrowserPool' });
105
+ this.#slotPollIntervalMillis = slotPollIntervalMillis;
105
106
  // A RemoteBrowserProvider carries its own endpoint, release, and maxOpenBrowsers.
106
107
  const provider = endpoint instanceof RemoteBrowserProvider ? endpoint : undefined;
107
108
  const resolvedEndpoint = provider
@@ -111,20 +112,20 @@ export class RemoteBrowserPool {
111
112
  ? ({ context }) => provider.release(context)
112
113
  : release;
113
114
  const resolvedMax = maxOpenBrowsers ?? provider?.maxOpenBrowsers;
114
- this.registry = new RemoteSessionRegistry(resolvedEndpoint, resolvedRelease, this.log);
115
+ this.#registry = new RemoteSessionRegistry(resolvedEndpoint, resolvedRelease, this.#log);
115
116
  // Wire every plugin for remote connection.
116
117
  for (const plugin of browserPlugins) {
117
- plugin.useRemoteConnection(this.registry, connection);
118
+ plugin.useRemoteConnection(this.#registry, connection);
118
119
  }
119
120
  this.browserPool = new BrowserPool({ ...browserPoolOptions, browserPlugins });
120
- this.pool = this.browserPool;
121
+ this.#pool = this.browserPool;
121
122
  // Release a browser's remote session once it closes. The registry dedupes (close() schedules a delayed
122
123
  // kill(), so BROWSER_CLOSED can fire twice), and destroy()'s releaseAll() backstops any that never close.
123
- this.browserPool.on("browserLaunched" /* BROWSER_POOL_EVENTS.BROWSER_LAUNCHED */, (controller) => {
124
- controller.once("browserClosed" /* BROWSER_CONTROLLER_EVENTS.BROWSER_CLOSED */, () => {
125
- const token = controller.launchContext._remoteToken;
124
+ this.browserPool.on(BROWSER_POOL_EVENTS.BROWSER_LAUNCHED, (controller) => {
125
+ controller.once(BROWSER_CONTROLLER_EVENTS.BROWSER_CLOSED, () => {
126
+ const token = controller.launchContext.remoteToken;
126
127
  if (token !== undefined)
127
- void this.registry.release(token);
128
+ void this.#registry.release(token);
128
129
  });
129
130
  });
130
131
  if (resolvedMax !== undefined) {
@@ -143,28 +144,28 @@ export class RemoteBrowserPool {
143
144
  * allows it (either a new browser slot is free, or an active browser still has page capacity).
144
145
  */
145
146
  async newPage(options) {
146
- await this._waitForFreeSlot();
147
- return this.pool.newPage(options);
147
+ await this.waitForFreeSlot();
148
+ return this.#pool.newPage(options);
148
149
  }
149
150
  async closePage(page, options) {
150
- return this.pool.closePage(page, options);
151
+ return this.#pool.closePage(page, options);
151
152
  }
152
153
  async extractPageState(page) {
153
- return this.pool.extractPageState(page);
154
+ return this.#pool.extractPageState(page);
154
155
  }
155
156
  async injectPageState(page, state) {
156
- return this.pool.injectPageState(page, state);
157
+ return this.#pool.injectPageState(page, state);
157
158
  }
158
159
  /** Closes all browsers, releases any still-open remote sessions, and tears down the wrapped pool. */
159
160
  async destroy() {
160
161
  await this.browserPool.destroy();
161
162
  // Backstop: release any sessions whose browser never emitted a close (e.g. dropped on teardown).
162
- await this.registry.releaseAll();
163
+ await this.#registry.releaseAll();
163
164
  }
164
165
  /** Resolves once the wrapped pool can serve another page without exceeding `maxOpenBrowsers`. */
165
- async _waitForFreeSlot() {
166
+ async waitForFreeSlot() {
166
167
  while (!this.browserPool.hasFreeBrowserSlot() && !this.browserPool.hasActiveBrowserWithFreeCapacity()) {
167
- await this._nextCapacityChange();
168
+ await this.nextCapacityChange();
168
169
  }
169
170
  }
170
171
  /**
@@ -172,20 +173,20 @@ export class RemoteBrowserPool {
172
173
  * concurrently-waiting `newPage` calls share a single promise (and a single pair of event listeners)
173
174
  * per tick, so a fleet of saturated callers doesn't fan out into N listener pairs on the pool.
174
175
  */
175
- _nextCapacityChange() {
176
- this._capacityChange ??= new Promise((resolve) => {
176
+ nextCapacityChange() {
177
+ this.#capacityChange ??= new Promise((resolve) => {
177
178
  const done = () => {
178
179
  clearTimeout(timer);
179
- this.browserPool.off("browserRetired" /* BROWSER_POOL_EVENTS.BROWSER_RETIRED */, done);
180
- this.browserPool.off("pageClosed" /* BROWSER_POOL_EVENTS.PAGE_CLOSED */, done);
181
- this._capacityChange = undefined;
180
+ this.browserPool.off(BROWSER_POOL_EVENTS.BROWSER_RETIRED, done);
181
+ this.browserPool.off(BROWSER_POOL_EVENTS.PAGE_CLOSED, done);
182
+ this.#capacityChange = undefined;
182
183
  resolve();
183
184
  };
184
- const timer = setTimeout(done, this.slotPollIntervalMillis);
185
+ const timer = setTimeout(done, this.#slotPollIntervalMillis);
185
186
  timer.unref?.();
186
- this.browserPool.once("browserRetired" /* BROWSER_POOL_EVENTS.BROWSER_RETIRED */, done);
187
- this.browserPool.once("pageClosed" /* BROWSER_POOL_EVENTS.PAGE_CLOSED */, done);
187
+ this.browserPool.once(BROWSER_POOL_EVENTS.BROWSER_RETIRED, done);
188
+ this.browserPool.once(BROWSER_POOL_EVENTS.PAGE_CLOSED, done);
188
189
  });
189
- return this._capacityChange;
190
+ return this.#capacityChange;
190
191
  }
191
192
  }