onebrowser 0.4.0 → 0.6.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/src/session.js CHANGED
@@ -1,46 +1,40 @@
1
- /**
2
- * A CDP command this often while connected keeps a provider's idle timeout from ending a session that is in use
3
- * (also while a page waits long for an answer). OneBrowser requires idleTimeoutSec to be at least twice as long.
4
- */
1
+ /** A CDP command this often keeps a provider's idle timeout from ending a session in use; idleTimeoutSec must be at least twice as long */
5
2
  export const KEEP_ALIVE_MS = 15_000;
6
3
 
7
4
  /** Playwright errors carry a multi-line call log; the first line says what failed */
8
5
  const firstLine = (e) => String(e.message).split('\n')[0];
9
6
 
10
7
  /**
11
- * One remote browser session, as OneBrowser.openSession() opened it: created by its provider and recorded in the
12
- * ledger. Use it over CDP: through cdpUrl with any CDP client, or through connect() / newPage() with Playwright.
13
- * It ends at the latest maxDurationMs after it was opened: this process closes it then (a client using it sees
14
- * its connection drop). close() releases it; one never closed (its process was killed) is ended by its provider
15
- * idleTimeoutSec after its client left.
16
- *
17
- * id, provider, egress the provider's session id, its name, and the egress the session uses
18
- * cdpUrl CDP WebSocket URL (it may carry the API key: never log it)
19
- * openedAt, closedAt ms: when it was asked for (its provider created it right after), when it was
20
- * closed; closedAt is null while open
8
+ * A remote browser session (Account.openSession), used over CDP: cdpUrl with any client (it may carry the API
9
+ * key: never log it), or connect() / newPage() with Playwright. This process closes it maxDurationMs after it was
10
+ * opened; one whose process died is ended by its provider idleTimeoutSec (at least its minIdleTimeoutSec) after
11
+ * its client left.
12
+ * account: the Account it is billed to; openedAt: when it was asked for; closedAt: when its provider released
13
+ * it, null until then.
21
14
  */
22
15
  export class Session {
23
16
  #release;
24
- #browser = null; // the connection in progress or made: a promise of Playwright's Browser
17
+ #closing = null;
18
+ #browser = null; // a promise of Playwright's Browser
25
19
  #keepAlive = null;
26
20
  #deadline;
27
21
 
28
- /** release(session): records the session closed and releases it at its provider (OneBrowser) */
29
- constructor({ provider, remote, openedAt, maxDurationMs, release }) {
30
- this.provider = provider;
22
+ /** release(session) → closedAt: releases it at its provider and records it closed (Account) */
23
+ constructor({ account, remote, openedAt, maxDurationMs, release }) {
24
+ this.account = account;
31
25
  this.id = remote.id;
32
26
  this.egress = remote.egress;
33
27
  this.cdpUrl = remote.cdpUrl;
34
28
  this.openedAt = openedAt;
35
29
  this.closedAt = null;
36
30
  this.#release = release;
37
- this.#deadline = setTimeout(() => this.close().catch(() => {}), openedAt + maxDurationMs - Date.now()); // a release that fails here leaves the session to the provider's idle timeout
31
+ this.#deadline = setTimeout(() => this.close().catch(() => {}), openedAt + maxDurationMs - Date.now());
38
32
  this.#deadline.unref();
39
33
  }
40
34
 
41
- /** Playwright's Browser over this session's CDP connection, made once; the keep-alive runs while it is connected */
35
+ /** Playwright's Browser over this session's CDP connection, made once; a keep-alive runs while it is connected */
42
36
  connect() {
43
- if (this.closedAt) return Promise.reject(new Error(`${this.provider} session ${this.id} is closed`));
37
+ if (this.#closing) return Promise.reject(new Error(`${this.account.id} session ${this.id} is closed`));
44
38
  this.#browser ??= this.#connect();
45
39
  return this.#browser;
46
40
  }
@@ -51,11 +45,11 @@ export class Session {
51
45
  try {
52
46
  browser = await chromium.connectOverCDP(this.cdpUrl, { timeout: 60_000 });
53
47
  } catch (e) {
54
- throw new Error(`failed to connect to the ${this.provider} browser: ${firstLine(e)}`);
48
+ throw new Error(`failed to connect to the ${this.account.provider} browser: ${firstLine(e)}`);
55
49
  }
56
50
  try {
57
51
  const cdp = await browser.newBrowserCDPSession();
58
- if (this.closedAt) throw new Error(`${this.provider} session ${this.id} is closed`); // closed while connecting
52
+ if (this.#closing) throw new Error(`${this.account.id} session ${this.id} is closed`);
59
53
  this.#keepAlive = setInterval(() => cdp.send('Browser.getVersion').catch(() => {}), KEEP_ALIVE_MS);
60
54
  this.#keepAlive.unref();
61
55
  } catch (e) {
@@ -65,10 +59,7 @@ export class Session {
65
59
  return browser;
66
60
  }
67
61
 
68
- /**
69
- * A new page in the browser's default context. Always a new one: the page a remote browser starts with is not
70
- * always one that can be navigated.
71
- */
62
+ /** A new page in the default context: the page a remote browser starts with cannot always be navigated */
72
63
  async newPage() {
73
64
  const browser = await this.connect();
74
65
  const context = browser.contexts()[0] ?? (await browser.newContext());
@@ -76,22 +67,20 @@ export class Session {
76
67
  }
77
68
 
78
69
  /**
79
- * Disconnects and releases the session at its provider; only the first call does. The ledger records it closed
80
- * in any case. Throws when the provider could not release it: the provider then ends it by itself,
81
- * idleTimeoutSec after the disconnect.
70
+ * Disconnects and releases the session; every call answers as the first. Throws when its provider could not
71
+ * release it: the ledger then counts it as lasting as long as it can, as its provider ends it by itself.
82
72
  */
83
- async close() {
84
- if (this.closedAt) return;
85
- this.closedAt = Date.now();
73
+ close() {
74
+ return (this.#closing ??= this.#close());
75
+ }
76
+
77
+ async #close() {
86
78
  clearTimeout(this.#deadline);
87
79
  clearInterval(this.#keepAlive);
88
- const browser = this.#browser;
89
- this.#browser = null;
90
- await browser?.then((b) => b.close()).catch(() => {});
91
- await this.#release(this);
80
+ await this.#browser?.then((b) => b.close()).catch(() => {});
81
+ this.closedAt = await this.#release(this);
92
82
  }
93
83
 
94
- /** close(): `await using session = await browsers.openSession(…)` releases it at the end of the block */
95
84
  [Symbol.asyncDispose]() {
96
85
  return this.close();
97
86
  }