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/README.md +43 -33
- package/package.json +2 -2
- package/src/account.js +139 -0
- package/src/cli.js +29 -24
- package/src/cost.js +8 -6
- package/src/credentials.js +17 -12
- package/src/index.js +54 -186
- package/src/ledger.js +74 -63
- package/src/options.js +30 -0
- package/src/providers/anchor.js +1 -0
- package/src/providers/index.js +22 -25
- package/src/session.js +27 -38
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
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* its
|
|
15
|
-
*
|
|
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
|
-
#
|
|
17
|
+
#closing = null;
|
|
18
|
+
#browser = null; // a promise of Playwright's Browser
|
|
25
19
|
#keepAlive = null;
|
|
26
20
|
#deadline;
|
|
27
21
|
|
|
28
|
-
/** release(session)
|
|
29
|
-
constructor({
|
|
30
|
-
this.
|
|
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());
|
|
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;
|
|
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
|
|
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
|
|
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
|
|
80
|
-
*
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
73
|
+
close() {
|
|
74
|
+
return (this.#closing ??= this.#close());
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async #close() {
|
|
86
78
|
clearTimeout(this.#deadline);
|
|
87
79
|
clearInterval(this.#keepAlive);
|
|
88
|
-
|
|
89
|
-
this
|
|
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
|
}
|