browser-commander 0.26.2 → 0.27.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/.jscpd-baseline.json +24 -23
- package/CHANGELOG.md +12 -0
- package/README.md +2 -0
- package/package.json +5 -3
- package/src/bindings.js +19 -0
- package/src/browser/attach/snapshot-launch.js +8 -6
- package/src/browser/attach/snapshot.js +39 -2
- package/src/browser/browser-cookie-database.js +71 -2
- package/src/browser/browser-cookie-session.js +183 -0
- package/src/browser/browser-cookies.js +29 -0
- package/src/browser/browser-sources.js +13 -0
- package/src/browser/browser-sources.json +13 -0
- package/src/browser/debugging-port.js +4 -2
- package/src/browser/default-browser.js +33 -9
- package/src/browser/launch-diagnostics.js +102 -0
- package/src/browser/launcher.js +28 -8
- package/src/browser/migration/sqlite-snapshot.js +5 -27
- package/src/browser/navigation.js +173 -98
- package/src/browser/real-browser.js +135 -68
- package/src/browser/session-cookies.js +78 -0
- package/src/browser/session-persistence.js +72 -0
- package/src/browser/storage-state.js +32 -6
- package/src/browser/system-browser.js +2 -2
- package/src/core/dialog-manager.js +7 -0
- package/src/core/managed-navigation.js +101 -0
- package/src/core/navigation-manager.js +62 -78
- package/src/core/navigation-operation.js +25 -0
- package/src/core/navigation-readiness.js +40 -16
- package/src/core/network-tracker.js +39 -92
- package/src/core/page-session.js +3 -2
- package/src/core/page-trigger-manager.js +15 -12
- package/src/core/readiness.js +67 -13
- package/src/core/subscriptions.js +20 -0
- package/src/core/webdriver-page.js +38 -0
- package/src/elements/locators.js +35 -5
- package/src/elements/reusable.js +92 -0
- package/src/elements/visibility.js +6 -11
- package/src/exports.js +14 -0
- package/src/factory.js +4 -4
- package/src/high-level/universal-logic.js +57 -21
- package/src/interactions/click.js +3 -0
- package/src/interactions/scroll.js +8 -2
- package/src/utilities/subprocess.js +15 -4
- package/tests/e2e/navigation-budget.e2e.test.js +132 -0
- package/tests/e2e/reusable-session.e2e.test.js +231 -0
- package/tests/unit/browser/attach/snapshot-launch.test.js +2 -2
- package/tests/unit/browser/attach/snapshot.test.js +33 -0
- package/tests/unit/browser/catalogue-launch.test.js +50 -2
- package/tests/unit/browser/default-browser.test.js +18 -0
- package/tests/unit/browser/launch-diagnostics.test.js +52 -0
- package/tests/unit/browser/real-browser.test.js +1 -1
- package/tests/unit/browser/session-workflows.test.js +206 -0
- package/tests/unit/browser/storage-state.test.js +7 -2
- package/tests/unit/core/navigation-budget.test.js +120 -0
- package/tests/unit/core/navigation-manager.test.js +23 -1
- package/tests/unit/core/webdriver-page.test.js +33 -0
- package/tests/unit/elements/reusable-helpers.test.js +83 -0
- package/tests/unit/traces/links.test.js +2 -2
package/.jscpd-baseline.json
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
3
|
"fingerprints": {
|
|
4
|
+
"00504cdec3a262ed": 1,
|
|
4
5
|
"00d381dd611ae8ac": 1,
|
|
5
6
|
"029a62ef1f48e856": 1,
|
|
7
|
+
"02fc9852e9ba4c49": 1,
|
|
6
8
|
"038170e72e0af1f5": 1,
|
|
7
9
|
"0413f601c8911a92": 1,
|
|
8
10
|
"04b28d01d0bb47dc": 1,
|
|
@@ -27,9 +29,7 @@
|
|
|
27
29
|
"14beedf656e57fba": 1,
|
|
28
30
|
"150c8efe188ac225": 1,
|
|
29
31
|
"16340a16ed52849e": 1,
|
|
30
|
-
"16e58ea67d8ef4e8": 1,
|
|
31
32
|
"17c26c0db2d35ad1": 1,
|
|
32
|
-
"193b46c0e7f72053": 1,
|
|
33
33
|
"19ff70fc80fad524": 1,
|
|
34
34
|
"1a9f0f0f68ed445c": 1,
|
|
35
35
|
"1ad4dc7e19a77f68": 1,
|
|
@@ -42,10 +42,10 @@
|
|
|
42
42
|
"22755b35701c5bb6": 1,
|
|
43
43
|
"2296c06fa00da254": 1,
|
|
44
44
|
"229b903386b17538": 1,
|
|
45
|
+
"22ab9a1ca6316d24": 1,
|
|
45
46
|
"22cbd954358d25f8": 1,
|
|
46
47
|
"230bfe57cfda164d": 1,
|
|
47
48
|
"2357ae472525f6bf": 1,
|
|
48
|
-
"2394170c8a2a0e9a": 1,
|
|
49
49
|
"23d2486c0e269b77": 1,
|
|
50
50
|
"266893ea7a511fe4": 1,
|
|
51
51
|
"266b9cb95f783cdf": 1,
|
|
@@ -66,7 +66,6 @@
|
|
|
66
66
|
"35cf2769645f3234": 1,
|
|
67
67
|
"37bd4114d0089f49": 1,
|
|
68
68
|
"39cf8134a7fdaa31": 1,
|
|
69
|
-
"3a9f1672ecc1d55b": 1,
|
|
70
69
|
"3adca52a3f04837c": 1,
|
|
71
70
|
"3c1017def7b3216c": 1,
|
|
72
71
|
"3c5cb4b62d3739e3": 1,
|
|
@@ -75,15 +74,17 @@
|
|
|
75
74
|
"406fed8e5b39aafd": 1,
|
|
76
75
|
"40aa2953f783819c": 1,
|
|
77
76
|
"418db56376cc23bd": 1,
|
|
77
|
+
"41ba3b00db6d29f5": 1,
|
|
78
78
|
"421bb16fafde32a7": 1,
|
|
79
79
|
"441570d51d6f70a4": 1,
|
|
80
80
|
"457c71f32f53833d": 1,
|
|
81
81
|
"4715a17d690865ee": 1,
|
|
82
|
+
"47dc143be0bd8be5": 1,
|
|
82
83
|
"4b0d3cc5c27227e7": 1,
|
|
83
84
|
"4c3b94caa28045d0": 1,
|
|
85
|
+
"4d1ff99bb19f5685": 1,
|
|
84
86
|
"4f3c541b3f3e3f23": 1,
|
|
85
87
|
"4f93b074bc2ac9e8": 1,
|
|
86
|
-
"5085765e1f103f3e": 1,
|
|
87
88
|
"5184c90c9f8406c3": 1,
|
|
88
89
|
"520a28ee2ced56c2": 1,
|
|
89
90
|
"53519ec61a6ba319": 1,
|
|
@@ -97,13 +98,16 @@
|
|
|
97
98
|
"57aa3b487de27c90": 1,
|
|
98
99
|
"57c410b183671eec": 1,
|
|
99
100
|
"5825f31d55b82fae": 1,
|
|
101
|
+
"589649758d30b0b4": 1,
|
|
100
102
|
"58c08b8242c875ec": 1,
|
|
101
|
-
"
|
|
103
|
+
"591377945ae26575": 1,
|
|
104
|
+
"59b7172dfb646586": 1,
|
|
102
105
|
"5a09e2677334ba05": 1,
|
|
103
106
|
"5b5d3f792532356c": 1,
|
|
104
107
|
"5ba2f3218754f04c": 1,
|
|
105
108
|
"60052da86e7bb241": 1,
|
|
106
109
|
"608e1dd27138ec6f": 1,
|
|
110
|
+
"60a3149247760d9b": 1,
|
|
107
111
|
"60d0c58a01fc8746": 1,
|
|
108
112
|
"61ae5da6cc003836": 1,
|
|
109
113
|
"61bcadcd8dc9f63e": 1,
|
|
@@ -112,6 +116,7 @@
|
|
|
112
116
|
"6479c3604c153ec4": 1,
|
|
113
117
|
"66fb97eebd7ff0dc": 1,
|
|
114
118
|
"67a95e428b8fa31c": 1,
|
|
119
|
+
"67c309590c5d69a1": 1,
|
|
115
120
|
"6880d620e0e67229": 1,
|
|
116
121
|
"69d50e6f6a5f2ee9": 1,
|
|
117
122
|
"6a7ed9ad3fc00a39": 1,
|
|
@@ -142,35 +147,28 @@
|
|
|
142
147
|
"79e705499ddaf44b": 1,
|
|
143
148
|
"7e6bad380644f9b0": 1,
|
|
144
149
|
"80cc77491cde09e5": 1,
|
|
145
|
-
"80f564fe45f6b315": 1,
|
|
146
|
-
"81a56c10bfd42e39": 1,
|
|
147
150
|
"837ba752ecdaf052": 1,
|
|
148
151
|
"86646647aab19e39": 1,
|
|
149
152
|
"8678919f1350f154": 1,
|
|
150
153
|
"86aae81b1aa0d21b": 1,
|
|
151
154
|
"87484a2744e09748": 1,
|
|
152
|
-
"8b013ac20e436d76": 1,
|
|
153
155
|
"8c2b1408886c1db1": 1,
|
|
154
156
|
"8e55579a412bc058": 1,
|
|
155
157
|
"8ec18cdbd4a96518": 1,
|
|
156
158
|
"901317bc4d7d30d0": 1,
|
|
157
159
|
"91049efadc65169b": 1,
|
|
158
160
|
"9134185980de8a17": 1,
|
|
159
|
-
"916833bafca4fa63": 1,
|
|
160
161
|
"91a538e4a0c28f25": 1,
|
|
161
|
-
"92ddafa7e65be7eb": 1,
|
|
162
162
|
"94821a4d1b745dfd": 1,
|
|
163
163
|
"961e3cc731bbc9fe": 1,
|
|
164
164
|
"97c5a112fdde7be8": 1,
|
|
165
165
|
"990d87f21678fa96": 1,
|
|
166
|
-
"9a72b1c2f22156ca": 1,
|
|
167
166
|
"9b6ef4df04562546": 1,
|
|
168
167
|
"9e049e3eefca1869": 1,
|
|
169
|
-
"
|
|
168
|
+
"9fdc43960bb59bcf": 1,
|
|
170
169
|
"9fe73b5b918293fe": 1,
|
|
171
170
|
"a0183465782b0b15": 1,
|
|
172
171
|
"a11079d40b28716c": 1,
|
|
173
|
-
"a133391f90bd1a28": 1,
|
|
174
172
|
"a1e3a3a13b27dba4": 1,
|
|
175
173
|
"a1ef39a15701c2b5": 1,
|
|
176
174
|
"a2a00d268fb2cee4": 1,
|
|
@@ -179,17 +177,21 @@
|
|
|
179
177
|
"a5298242b5439858": 1,
|
|
180
178
|
"a6ad7ab6c05656dc": 1,
|
|
181
179
|
"a6ffab1aacd00af6": 1,
|
|
180
|
+
"a71a4b720d466e32": 1,
|
|
181
|
+
"a71fab6edf38c846": 1,
|
|
182
182
|
"a78d76908162699e": 1,
|
|
183
183
|
"a9bd4ffef6da5faf": 1,
|
|
184
|
+
"ac3fe4ef966e0209": 1,
|
|
184
185
|
"acc95d694eb2f581": 1,
|
|
185
186
|
"ad4cfa76daa1b6fc": 1,
|
|
186
187
|
"ad50b48938af8aef": 1,
|
|
188
|
+
"adc4fadd2bfd220c": 1,
|
|
187
189
|
"add45ff3644fbeeb": 1,
|
|
188
190
|
"aee1e5c65f783848": 1,
|
|
189
191
|
"aee45e8b58c2d87f": 1,
|
|
190
192
|
"af702b20ebd45ef2": 1,
|
|
191
|
-
"af9d6a2f4eba146e": 1,
|
|
192
193
|
"b010686af23efa90": 1,
|
|
194
|
+
"b141049dde51b3e8": 1,
|
|
193
195
|
"b35ff46c365dcd18": 1,
|
|
194
196
|
"b41581af204af40e": 1,
|
|
195
197
|
"b71fa5250da28739": 1,
|
|
@@ -224,24 +226,23 @@
|
|
|
224
226
|
"cebff7d1cc87f646": 1,
|
|
225
227
|
"cfc928aa393146b6": 1,
|
|
226
228
|
"cfd68135b744c27b": 1,
|
|
227
|
-
"cfdc0cca50233fcb": 1,
|
|
228
229
|
"d016035b3bf3340c": 1,
|
|
229
230
|
"d097f41a6f43e634": 1,
|
|
230
|
-
"d0da8714c4b7de30": 1,
|
|
231
231
|
"d17201496391f6cc": 1,
|
|
232
232
|
"d17e8db5a2bab966": 1,
|
|
233
|
-
"d1a4a8e6a523b752": 1,
|
|
234
|
-
"d1e5c2961267399a": 1,
|
|
235
233
|
"d2ed3d02dd6a2d8c": 1,
|
|
236
234
|
"d6157a86fea41907": 1,
|
|
237
235
|
"d6fa9884eee815f0": 1,
|
|
238
236
|
"d86bafb915488255": 1,
|
|
239
237
|
"da2c10a66acdb52f": 1,
|
|
240
238
|
"dabb9fc09638e178": 1,
|
|
239
|
+
"dcc616702e5b0cb4": 1,
|
|
241
240
|
"de0bf2317c53d427": 1,
|
|
242
241
|
"de1b481bfe8c3e58": 1,
|
|
243
242
|
"df2dc922f40485e7": 1,
|
|
244
243
|
"df3b85f6947db144": 1,
|
|
244
|
+
"e071c1594e47dd0f": 1,
|
|
245
|
+
"e18fb1b06eac2f6a": 1,
|
|
245
246
|
"e2ef7800deb0f464": 1,
|
|
246
247
|
"e3111b845acb7a0e": 1,
|
|
247
248
|
"e59733bafda785e9": 1,
|
|
@@ -249,14 +250,14 @@
|
|
|
249
250
|
"e67726a913675120": 1,
|
|
250
251
|
"e686288f2e9b8a6d": 1,
|
|
251
252
|
"e7d7bb7e163d2327": 1,
|
|
253
|
+
"e902a25d9132eda9": 1,
|
|
252
254
|
"ea1195048ded896c": 1,
|
|
253
255
|
"ea88711c5deb1fe5": 1,
|
|
254
256
|
"eb9a70c2a21094ff": 1,
|
|
255
257
|
"ebe03c5f8c2f5147": 1,
|
|
256
258
|
"ee19c8b9b332ee98": 1,
|
|
257
|
-
"
|
|
259
|
+
"ef6b9112e659147b": 1,
|
|
258
260
|
"f07f979f8c2706e8": 1,
|
|
259
|
-
"f186f73c118b8e8a": 1,
|
|
260
261
|
"f38f72095a67c95e": 1,
|
|
261
262
|
"f40ba84763547f41": 1,
|
|
262
263
|
"f46509ed693574df": 1,
|
|
@@ -264,15 +265,15 @@
|
|
|
264
265
|
"f4bfa7e503443cd1": 1,
|
|
265
266
|
"f6678d6748e9c902": 1,
|
|
266
267
|
"f67a07a611f39cb2": 1,
|
|
268
|
+
"f762b61eef0af530": 1,
|
|
267
269
|
"f7b1f9d699bcd580": 1,
|
|
268
270
|
"f81e07af5d3a1c3c": 1,
|
|
269
271
|
"f904844f89cab699": 1,
|
|
270
272
|
"f911821aa7dda47f": 1,
|
|
271
273
|
"f97bf515bfd95829": 1,
|
|
272
274
|
"fae844842fba705b": 1,
|
|
273
|
-
"
|
|
275
|
+
"fb4a079a529a6285": 1,
|
|
274
276
|
"fc0d7bde0033411d": 1,
|
|
275
|
-
"fc6ed7d760c98f51": 1,
|
|
276
277
|
"fccfd8b004c12dd4": 1,
|
|
277
278
|
"fe859df9f97c75d4": 1
|
|
278
279
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.27.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- fab33b3: Honor per-call navigation policies, deadlines and cancellation; expose bounded redacted launch diagnostics. Add reusable indexed controls, ordered selectors/text, removable subscriptions and non-clearing flags. Add cookie-only browser reads, runtime cookies, session discovery and dedicated-profile persistence. Make SQLite optional, align links-notation 0.23, and document Bun CDP compatibility.
|
|
8
|
+
|
|
9
|
+
## 0.26.3
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 796ebd8: Accept dedicated macOS application profiles under `~/Library/Application Support` by separating Safari import discovery roots from protected browser data paths. Safari, Technology Preview and Chrome default profiles remain protected.
|
|
14
|
+
|
|
3
15
|
## 0.26.2
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1063,3 +1063,5 @@ See [src/ARCHITECTURE.md](src/ARCHITECTURE.md) for detailed architecture documen
|
|
|
1063
1063
|
## License
|
|
1064
1064
|
|
|
1065
1065
|
[UNLICENSE](../LICENSE)
|
|
1066
|
+
|
|
1067
|
+
See [navigation budgets, launch diagnostics, reusable helpers and sessions](../docs/navigation-launch-and-sessions.md) for the shared API contracts and engine limitations.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "browser-commander",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "Universal browser automation library that supports Playwright, Puppeteer, and Selenium with a unified API",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -80,11 +80,10 @@
|
|
|
80
80
|
}
|
|
81
81
|
},
|
|
82
82
|
"dependencies": {
|
|
83
|
-
"better-sqlite3": "^13.0.3",
|
|
84
83
|
"bplist-parser": "^0.5.0",
|
|
85
84
|
"command-stream": "^1.4.0",
|
|
86
85
|
"csv-parse": "^7.0.3",
|
|
87
|
-
"links-notation": "^0.
|
|
86
|
+
"links-notation": "^0.23.0",
|
|
88
87
|
"log-lazy": "^1.0.4",
|
|
89
88
|
"plist": "^5.0.0",
|
|
90
89
|
"test-anywhere": "^0.9.1"
|
|
@@ -108,5 +107,8 @@
|
|
|
108
107
|
"better-sqlite3@13.0.3": true,
|
|
109
108
|
"node-pty": false,
|
|
110
109
|
"puppeteer": false
|
|
110
|
+
},
|
|
111
|
+
"optionalDependencies": {
|
|
112
|
+
"better-sqlite3": "^13.0.3"
|
|
111
113
|
}
|
|
112
114
|
}
|
package/src/bindings.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* This module provides helper functions for binding page, engine, and log to library functions.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { findFirst, hasText, isChecked, check } from './elements/reusable.js';
|
|
7
|
+
import { setCookies, clearCookies } from './browser/session-cookies.js';
|
|
6
8
|
import { wait, evaluate, safeEvaluate } from './utilities/wait.js';
|
|
7
9
|
import { getUrl, unfocusAddressBar } from './utilities/url.js';
|
|
8
10
|
import {
|
|
@@ -52,6 +54,8 @@ import {
|
|
|
52
54
|
fillTextArea,
|
|
53
55
|
} from './interactions/fill.js';
|
|
54
56
|
import {
|
|
57
|
+
readFlag,
|
|
58
|
+
uninstallClickListener,
|
|
55
59
|
waitForUrlCondition,
|
|
56
60
|
installClickListener,
|
|
57
61
|
checkAndClearFlag,
|
|
@@ -116,6 +120,7 @@ export function createBoundFunctions(options = {}) {
|
|
|
116
120
|
page,
|
|
117
121
|
waitForUrlStabilization: waitForUrlStabilizationBound,
|
|
118
122
|
navigationManager,
|
|
123
|
+
networkTracker,
|
|
119
124
|
});
|
|
120
125
|
const setContentBound = (opts) =>
|
|
121
126
|
setContent({
|
|
@@ -352,6 +357,20 @@ export function createBoundFunctions(options = {}) {
|
|
|
352
357
|
installClickListener: installClickListenerBound,
|
|
353
358
|
checkAndClearFlag: checkAndClearFlagBound,
|
|
354
359
|
findToggleButton: findToggleButtonBound,
|
|
360
|
+
findFirst: (opts) => findFirst({ ...opts, page, engine }),
|
|
361
|
+
hasText: (opts) => hasText({ ...opts, page, engine }),
|
|
362
|
+
isChecked: (opts) => isChecked({ ...opts, page, engine }),
|
|
363
|
+
check: (opts) => check({ ...opts, page, engine, wait: waitBound }),
|
|
364
|
+
readFlag: (opts) => readFlag({ ...opts, evaluate: evaluateBound }),
|
|
365
|
+
uninstallClickListener: (opts) =>
|
|
366
|
+
uninstallClickListener({ ...opts, evaluate: evaluateBound }),
|
|
367
|
+
setCookies: (options) =>
|
|
368
|
+
setCookies({
|
|
369
|
+
page,
|
|
370
|
+
engine,
|
|
371
|
+
cookies: Array.isArray(options) ? options : options.cookies,
|
|
372
|
+
}),
|
|
373
|
+
clearCookies: (opts = {}) => clearCookies({ ...opts, page, engine }),
|
|
355
374
|
|
|
356
375
|
// PDF generation
|
|
357
376
|
pdf: pdfBound,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { findBrowserSource } from '../browser-sources.js';
|
|
1
2
|
import { resolveRestrictions } from '../restrictions.js';
|
|
2
3
|
import { describeAttachDifferences } from './differences.js';
|
|
3
4
|
import { snapshotUserDataDir } from './snapshot.js';
|
|
@@ -19,13 +20,13 @@ const KEYSTORE_BYPASS_SWITCHES = ['--use-mock-keychain', '--password-store='];
|
|
|
19
20
|
* @returns {string}
|
|
20
21
|
*/
|
|
21
22
|
export function snapshotBrowserForChannel(channel) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
const source = findBrowserSource(channel);
|
|
24
|
+
if (!source || source.family !== 'chromium') {
|
|
25
|
+
throw new TypeError(
|
|
26
|
+
`Snapshot channel must identify a Chromium browser: ${channel}`
|
|
27
|
+
);
|
|
27
28
|
}
|
|
28
|
-
return
|
|
29
|
+
return source.id;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
/**
|
|
@@ -122,6 +123,7 @@ export async function prepareSnapshotLaunch({
|
|
|
122
123
|
browser,
|
|
123
124
|
profile,
|
|
124
125
|
userDataDir: attach.userDataDir,
|
|
126
|
+
...(attach.include ? { include: attach.include } : {}),
|
|
125
127
|
});
|
|
126
128
|
const warnings = [
|
|
127
129
|
...(report.warnings ?? []),
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
readdir,
|
|
6
6
|
readFile,
|
|
7
7
|
stat,
|
|
8
|
+
lstat,
|
|
8
9
|
writeFile,
|
|
9
10
|
} from 'node:fs/promises';
|
|
10
11
|
import os from 'node:os';
|
|
@@ -279,6 +280,7 @@ export async function snapshotUserDataDir({
|
|
|
279
280
|
profile = 'Default',
|
|
280
281
|
userDataDir,
|
|
281
282
|
to,
|
|
283
|
+
include,
|
|
282
284
|
platform = process.platform,
|
|
283
285
|
homeDir = os.homedir(),
|
|
284
286
|
environment = process.env,
|
|
@@ -293,6 +295,16 @@ export async function snapshotUserDataDir({
|
|
|
293
295
|
);
|
|
294
296
|
}
|
|
295
297
|
assertProfileName(profile);
|
|
298
|
+
if (
|
|
299
|
+
include !== undefined &&
|
|
300
|
+
(!Array.isArray(include) ||
|
|
301
|
+
include.length !== 1 ||
|
|
302
|
+
include[0] !== 'cookies')
|
|
303
|
+
) {
|
|
304
|
+
throw new TypeError(
|
|
305
|
+
'snapshot include must be ["cookies"] or omitted for the whole profile'
|
|
306
|
+
);
|
|
307
|
+
}
|
|
296
308
|
const sourceRoot =
|
|
297
309
|
userDataDir ??
|
|
298
310
|
browserProfileRoot(normalizedBrowser, { platform, homeDir, environment });
|
|
@@ -310,6 +322,7 @@ export async function snapshotUserDataDir({
|
|
|
310
322
|
sourceRoot,
|
|
311
323
|
profile,
|
|
312
324
|
target,
|
|
325
|
+
include,
|
|
313
326
|
source: { browser: normalizedBrowser, profile, userDataDir: sourceRoot },
|
|
314
327
|
});
|
|
315
328
|
} catch (error) {
|
|
@@ -320,7 +333,7 @@ export async function snapshotUserDataDir({
|
|
|
320
333
|
}
|
|
321
334
|
}
|
|
322
335
|
|
|
323
|
-
async function copyProfile({ sourceRoot, profile, target, source }) {
|
|
336
|
+
async function copyProfile({ sourceRoot, profile, target, source, include }) {
|
|
324
337
|
const copier = new SnapshotCopier(sourceRoot, target);
|
|
325
338
|
const warnings = [];
|
|
326
339
|
const localState = await stat(path.join(sourceRoot, LOCAL_STATE_FILE)).catch(
|
|
@@ -336,7 +349,31 @@ async function copyProfile({ sourceRoot, profile, target, source }) {
|
|
|
336
349
|
'The source has no Local State; on Windows it holds the key cookies and passwords are encrypted with, so they cannot be decrypted in the copy.',
|
|
337
350
|
});
|
|
338
351
|
}
|
|
339
|
-
|
|
352
|
+
if (include) {
|
|
353
|
+
for (const relative of [
|
|
354
|
+
`${profile}/Cookies`,
|
|
355
|
+
`${profile}/Network/Cookies`,
|
|
356
|
+
]) {
|
|
357
|
+
const parent = await lstat(
|
|
358
|
+
path.dirname(path.join(sourceRoot, relative))
|
|
359
|
+
).catch(() => null);
|
|
360
|
+
if (parent?.isSymbolicLink()) {
|
|
361
|
+
copier.skipped.push({ item: relative, reason: 'symlink' });
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
const entry = await lstat(path.join(sourceRoot, relative)).catch(
|
|
365
|
+
() => null
|
|
366
|
+
);
|
|
367
|
+
if (entry?.isFile()) {
|
|
368
|
+
await mkdir(path.dirname(path.join(target, relative)), {
|
|
369
|
+
recursive: true,
|
|
370
|
+
});
|
|
371
|
+
await copier.copyFile(relative);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
} else {
|
|
375
|
+
await copier.copyDirectory(profile);
|
|
376
|
+
}
|
|
340
377
|
await prepareUserDataDir(target, { profileDirectory: profile });
|
|
341
378
|
await markExitedCleanly(path.join(target, profile));
|
|
342
379
|
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import BetterSqlite3 from 'better-sqlite3';
|
|
2
|
-
|
|
3
1
|
let builtInSqlitePromise;
|
|
4
2
|
|
|
5
3
|
function loadBuiltInSqlite() {
|
|
@@ -18,6 +16,7 @@ export async function openSqliteDatabase(
|
|
|
18
16
|
if (DatabaseSync) {
|
|
19
17
|
return new DatabaseSync(filename, { readOnly });
|
|
20
18
|
}
|
|
19
|
+
const BetterSqlite3 = await loadOptionalSqlite();
|
|
21
20
|
return new BetterSqlite3(filename, {
|
|
22
21
|
fileMustExist,
|
|
23
22
|
readonly: readOnly,
|
|
@@ -33,3 +32,73 @@ export function preserveIntegerPrecision(statement) {
|
|
|
33
32
|
}
|
|
34
33
|
return statement;
|
|
35
34
|
}
|
|
35
|
+
|
|
36
|
+
/** Lazy native fallback for runtimes without built-in SQLite. */
|
|
37
|
+
export async function loadOptionalSqlite() {
|
|
38
|
+
try {
|
|
39
|
+
return (await import('better-sqlite3')).default;
|
|
40
|
+
} catch (cause) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
'This SQLite operation needs optional better-sqlite3 on this runtime. Install it or use Node >=22.16 for built-in SQLite backups.',
|
|
43
|
+
{ cause }
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function backupProgress() {
|
|
49
|
+
let remaining = Infinity;
|
|
50
|
+
let lastProgress = Date.now();
|
|
51
|
+
return ({ remainingPages }) => {
|
|
52
|
+
if (remainingPages < remaining) {
|
|
53
|
+
lastProgress = Date.now();
|
|
54
|
+
} else if (Date.now() - lastProgress >= 1000) {
|
|
55
|
+
throw new Error('SQLite backup made no progress');
|
|
56
|
+
}
|
|
57
|
+
remaining = remainingPages;
|
|
58
|
+
return 256;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Make an online backup, including committed WAL data. */
|
|
63
|
+
export async function backupSqliteDatabase(sourcePath, snapshotPath) {
|
|
64
|
+
let native;
|
|
65
|
+
try {
|
|
66
|
+
native = await loadOptionalSqlite();
|
|
67
|
+
} catch {
|
|
68
|
+
/* built-in fallback */
|
|
69
|
+
}
|
|
70
|
+
let sqlite;
|
|
71
|
+
try {
|
|
72
|
+
sqlite = await import('node:sqlite');
|
|
73
|
+
} catch {
|
|
74
|
+
/* native fallback */
|
|
75
|
+
}
|
|
76
|
+
if (!native && sqlite?.backup) {
|
|
77
|
+
const source = new sqlite.DatabaseSync(sourcePath, {
|
|
78
|
+
readOnly: true,
|
|
79
|
+
timeout: 1000,
|
|
80
|
+
});
|
|
81
|
+
try {
|
|
82
|
+
source.prepare('PRAGMA schema_version').get();
|
|
83
|
+
await sqlite.backup(source, snapshotPath, {
|
|
84
|
+
rate: 256,
|
|
85
|
+
progress: backupProgress(),
|
|
86
|
+
});
|
|
87
|
+
} finally {
|
|
88
|
+
source.close();
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const BetterSqlite3 = native ?? (await loadOptionalSqlite());
|
|
93
|
+
const source = new BetterSqlite3(sourcePath, {
|
|
94
|
+
readonly: true,
|
|
95
|
+
fileMustExist: true,
|
|
96
|
+
timeout: 1000,
|
|
97
|
+
});
|
|
98
|
+
try {
|
|
99
|
+
source.pragma('schema_version');
|
|
100
|
+
await source.backup(snapshotPath, { progress: backupProgress() });
|
|
101
|
+
} finally {
|
|
102
|
+
source.close();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { rm } from 'node:fs/promises';
|
|
3
|
+
import { browserFamily } from './browser-sources.js';
|
|
4
|
+
import { matchesDomains } from './migration/domains.js';
|
|
5
|
+
import {
|
|
6
|
+
resolveBrowserProfile,
|
|
7
|
+
resolveSourceBrowser,
|
|
8
|
+
} from './browser-profiles.js';
|
|
9
|
+
import { saveStorageState } from './storage-state.js';
|
|
10
|
+
import { setCookies } from './session-cookies.js';
|
|
11
|
+
|
|
12
|
+
/** Decrypt through the source browser in a disposable cookie-only snapshot. */
|
|
13
|
+
export async function readBrowserCookieSession(options, dependencies = {}) {
|
|
14
|
+
const browser = await resolveSourceBrowser(options.browser, dependencies);
|
|
15
|
+
if (browserFamily(browser) !== 'chromium') {
|
|
16
|
+
throw new TypeError(
|
|
17
|
+
'Browser-backed cookie reading requires a Chromium snapshot; use via: database for Firefox or Safari'
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
const profile = options.profileDir
|
|
21
|
+
? { path: options.profileDir }
|
|
22
|
+
: await resolveBrowserProfile({
|
|
23
|
+
...dependencies,
|
|
24
|
+
browser,
|
|
25
|
+
profile: options.profile,
|
|
26
|
+
});
|
|
27
|
+
const launch =
|
|
28
|
+
dependencies.launch ??
|
|
29
|
+
(await import('./real-browser.js')).launchRealBrowser;
|
|
30
|
+
const result = await launch({
|
|
31
|
+
...options.launchOptions,
|
|
32
|
+
channel: browser,
|
|
33
|
+
engine: options.engine ?? 'playwright',
|
|
34
|
+
attach: {
|
|
35
|
+
mode: 'snapshot',
|
|
36
|
+
browser,
|
|
37
|
+
profile: path.basename(profile.path),
|
|
38
|
+
userDataDir: path.dirname(profile.path),
|
|
39
|
+
include: ['cookies'],
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
try {
|
|
43
|
+
const state = await saveStorageState(result.page);
|
|
44
|
+
return state.cookies.filter((cookie) =>
|
|
45
|
+
matchesDomains(
|
|
46
|
+
cookie.domain,
|
|
47
|
+
options.domains ?? (options.domainFilter ? [options.domainFilter] : [])
|
|
48
|
+
)
|
|
49
|
+
);
|
|
50
|
+
} finally {
|
|
51
|
+
await result.close();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function readDatabaseSession(source, options, dependencies) {
|
|
56
|
+
const read =
|
|
57
|
+
dependencies.readCookies ??
|
|
58
|
+
(await import('./browser-cookies.js')).readBrowserCookies;
|
|
59
|
+
const cookies = (
|
|
60
|
+
await read({
|
|
61
|
+
browser: source.browser,
|
|
62
|
+
profileDir: source.path,
|
|
63
|
+
via: 'database',
|
|
64
|
+
cache: false,
|
|
65
|
+
})
|
|
66
|
+
).filter((cookie) => matchesDomains(cookie.domain, options.domains));
|
|
67
|
+
let loggedIn = null;
|
|
68
|
+
if (options.isLoggedIn) {
|
|
69
|
+
const launch =
|
|
70
|
+
dependencies.launch ?? (await import('./launcher.js')).launchBrowser;
|
|
71
|
+
const engine = source.engine ?? options.engine ?? 'playwright';
|
|
72
|
+
const result = await launch({
|
|
73
|
+
...options.launchOptions,
|
|
74
|
+
...source.launchOptions,
|
|
75
|
+
engine,
|
|
76
|
+
launch: 'engine',
|
|
77
|
+
userDataDir: undefined,
|
|
78
|
+
attach: undefined,
|
|
79
|
+
persistSessionCookies: false,
|
|
80
|
+
});
|
|
81
|
+
try {
|
|
82
|
+
await setCookies({ page: result.page, engine, cookies });
|
|
83
|
+
loggedIn = Boolean(
|
|
84
|
+
await options.isLoggedIn({ ...result, source, cookies })
|
|
85
|
+
);
|
|
86
|
+
} finally {
|
|
87
|
+
await result.close();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return { ...source, cookies, loggedIn };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Discover candidate profiles and optionally validate their live sign-in state. */
|
|
94
|
+
export async function findSiteSessions(options = {}, dependencies = {}) {
|
|
95
|
+
if (!Array.isArray(options.domains) || !options.domains.length) {
|
|
96
|
+
throw new TypeError('findSiteSessions requires domains');
|
|
97
|
+
}
|
|
98
|
+
const list =
|
|
99
|
+
dependencies.listSources ??
|
|
100
|
+
(await import('./browser-cookies.js')).listCookieSources;
|
|
101
|
+
const sources = [
|
|
102
|
+
...(options.sources ?? (await list({ domains: options.domains }))),
|
|
103
|
+
...(options.profiles ?? []),
|
|
104
|
+
];
|
|
105
|
+
const sessions = [];
|
|
106
|
+
for (const source of sources) {
|
|
107
|
+
let result;
|
|
108
|
+
let snapshot;
|
|
109
|
+
try {
|
|
110
|
+
if (browserFamily(source.browser) !== 'chromium') {
|
|
111
|
+
sessions.push(await readDatabaseSession(source, options, dependencies));
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const attach = {
|
|
115
|
+
mode: 'snapshot',
|
|
116
|
+
browser: source.browser,
|
|
117
|
+
profile: path.basename(source.path),
|
|
118
|
+
userDataDir: path.dirname(source.path),
|
|
119
|
+
include: ['cookies'],
|
|
120
|
+
};
|
|
121
|
+
if (source.launch === 'engine') {
|
|
122
|
+
const copy =
|
|
123
|
+
dependencies.snapshot ??
|
|
124
|
+
(await import('./attach/snapshot.js')).snapshotUserDataDir;
|
|
125
|
+
snapshot = await copy(attach);
|
|
126
|
+
const launch =
|
|
127
|
+
dependencies.launch ?? (await import('./launcher.js')).launchBrowser;
|
|
128
|
+
result = await launch({
|
|
129
|
+
...options.launchOptions,
|
|
130
|
+
...source.launchOptions,
|
|
131
|
+
engine: source.engine ?? options.engine ?? 'playwright',
|
|
132
|
+
launch: 'engine',
|
|
133
|
+
userDataDir: snapshot.target,
|
|
134
|
+
args: [
|
|
135
|
+
...(options.launchOptions?.args ?? []),
|
|
136
|
+
...(source.launchOptions?.args ?? []),
|
|
137
|
+
`--profile-directory=${attach.profile}`,
|
|
138
|
+
],
|
|
139
|
+
});
|
|
140
|
+
} else {
|
|
141
|
+
const launch =
|
|
142
|
+
dependencies.launch ??
|
|
143
|
+
(await import('./real-browser.js')).launchRealBrowser;
|
|
144
|
+
result = await launch({
|
|
145
|
+
...options.launchOptions,
|
|
146
|
+
engine: source.engine ?? options.engine ?? 'playwright',
|
|
147
|
+
channel: source.browser,
|
|
148
|
+
attach,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
const state = await saveStorageState(result.page);
|
|
152
|
+
const cookies = state.cookies.filter((cookie) =>
|
|
153
|
+
matchesDomains(cookie.domain, options.domains)
|
|
154
|
+
);
|
|
155
|
+
const loggedIn = options.isLoggedIn
|
|
156
|
+
? Boolean(await options.isLoggedIn({ ...result, source, cookies }))
|
|
157
|
+
: null;
|
|
158
|
+
sessions.push({ ...source, cookies, loggedIn });
|
|
159
|
+
} catch (error) {
|
|
160
|
+
sessions.push({
|
|
161
|
+
...source,
|
|
162
|
+
cookies: [],
|
|
163
|
+
loggedIn: null,
|
|
164
|
+
error: error.message,
|
|
165
|
+
});
|
|
166
|
+
} finally {
|
|
167
|
+
try {
|
|
168
|
+
await result?.close();
|
|
169
|
+
} catch (error) {
|
|
170
|
+
sessions.at(-1).error = error.message;
|
|
171
|
+
} finally {
|
|
172
|
+
if (snapshot) {
|
|
173
|
+
try {
|
|
174
|
+
await rm(snapshot.target, { recursive: true, force: true });
|
|
175
|
+
} catch (error) {
|
|
176
|
+
sessions.at(-1).error = error.message;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return sessions;
|
|
183
|
+
}
|