browser-commander 0.21.2 → 0.22.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 +5 -6
- package/CHANGELOG.md +32 -0
- package/package.json +1 -1
- package/src/browser/browser-cookie-credentials.js +15 -24
- package/src/browser/browser-cookies.js +219 -11
- package/src/browser/browser-profiles.js +145 -79
- package/src/browser/browser-sources.js +184 -0
- package/src/browser/browser-sources.json +399 -0
- package/src/browser/default-browser.js +176 -0
- package/src/browser/migration/cookies.js +3 -0
- package/src/browser/migration/index.js +41 -14
- package/src/cli/args.js +10 -0
- package/src/cli/commands.js +27 -0
- package/src/cli/high-level-methods.js +28 -2
- package/src/cli/modules.js +6 -0
- package/src/exports.js +4 -0
- package/tests/helpers/cli-fakes.js +21 -0
- package/tests/helpers/migration-fixtures.js +30 -1
- package/tests/unit/browser/browser-cookies-profiledir.test.js +36 -0
- package/tests/unit/browser/browser-cookies.test.js +2 -11
- package/tests/unit/browser/browser-profiles-sources.test.js +150 -0
- package/tests/unit/browser/browser-sources.test.js +154 -0
- package/tests/unit/browser/cookie-sources.test.js +146 -0
- package/tests/unit/browser/default-browser.test.js +121 -0
- package/tests/unit/browser/migration/index.test.js +62 -1
- package/tests/unit/cli/main.test.js +32 -5
- package/tests/unit/scripts/browser-support.test.js +56 -0
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
{
|
|
2
|
+
"comment": [
|
|
3
|
+
"Catalogue of installed browsers Browser Commander can import from (issue",
|
|
4
|
+
"#114). Each entry says where a browser keeps its profiles on every",
|
|
5
|
+
"platform, how its Chromium Safe Storage key is named, and how the OS",
|
|
6
|
+
"records it as the default browser, so profile discovery, cookie",
|
|
7
|
+
"decryption and `browser: 'default'` resolution are all data-driven and",
|
|
8
|
+
"identical across JavaScript, Python and Rust.",
|
|
9
|
+
"",
|
|
10
|
+
"Root templates use these placeholders, expanded per platform:",
|
|
11
|
+
" {home} the user home directory",
|
|
12
|
+
" {appSupport} {home}/Library/Application Support (darwin only)",
|
|
13
|
+
" {localAppData} %LOCALAPPDATA% (win32 only)",
|
|
14
|
+
" {appData} %APPDATA% / roaming (win32 only)",
|
|
15
|
+
" {config} {home}/.config (linux only)",
|
|
16
|
+
"A browser with \"singleProfile\": true keeps its data in the root itself",
|
|
17
|
+
"(one profile named \"Default\"); otherwise the root holds Chromium profile",
|
|
18
|
+
"subdirectories (Default, Profile 1, ...) or a Firefox profiles.ini.",
|
|
19
|
+
"",
|
|
20
|
+
"This file is shared, not translated: Python and Rust ship byte-identical",
|
|
21
|
+
"copies, checked by scripts/check-shared-fingerprint-assets.sh."
|
|
22
|
+
],
|
|
23
|
+
"browsers": [
|
|
24
|
+
{
|
|
25
|
+
"id": "chrome",
|
|
26
|
+
"family": "chromium",
|
|
27
|
+
"aliases": ["google-chrome", "google chrome"],
|
|
28
|
+
"roots": {
|
|
29
|
+
"darwin": ["{appSupport}/Google/Chrome"],
|
|
30
|
+
"win32": ["{localAppData}/Google/Chrome/User Data"],
|
|
31
|
+
"linux": ["{config}/google-chrome"]
|
|
32
|
+
},
|
|
33
|
+
"safeStorage": {
|
|
34
|
+
"service": "Chrome Safe Storage",
|
|
35
|
+
"application": "chrome",
|
|
36
|
+
"folder": "Chrome Keys"
|
|
37
|
+
},
|
|
38
|
+
"default": {
|
|
39
|
+
"darwin": ["com.google.chrome"],
|
|
40
|
+
"linux": ["google-chrome.desktop", "google-chrome-stable.desktop"],
|
|
41
|
+
"win32": ["ChromeHTML"]
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": "chrome-beta",
|
|
46
|
+
"family": "chromium",
|
|
47
|
+
"aliases": ["google-chrome-beta"],
|
|
48
|
+
"roots": {
|
|
49
|
+
"darwin": ["{appSupport}/Google/Chrome Beta"],
|
|
50
|
+
"win32": ["{localAppData}/Google/Chrome Beta/User Data"],
|
|
51
|
+
"linux": ["{config}/google-chrome-beta"]
|
|
52
|
+
},
|
|
53
|
+
"safeStorage": {
|
|
54
|
+
"service": "Chrome Safe Storage",
|
|
55
|
+
"application": "google-chrome-beta",
|
|
56
|
+
"folder": "Chrome Keys"
|
|
57
|
+
},
|
|
58
|
+
"default": {
|
|
59
|
+
"darwin": ["com.google.chrome.beta"],
|
|
60
|
+
"linux": ["google-chrome-beta.desktop"],
|
|
61
|
+
"win32": ["ChromeBHTML"]
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"id": "chrome-dev",
|
|
66
|
+
"family": "chromium",
|
|
67
|
+
"aliases": ["google-chrome-dev", "google-chrome-unstable"],
|
|
68
|
+
"roots": {
|
|
69
|
+
"darwin": ["{appSupport}/Google/Chrome Dev"],
|
|
70
|
+
"win32": ["{localAppData}/Google/Chrome Dev/User Data"],
|
|
71
|
+
"linux": ["{config}/google-chrome-unstable"]
|
|
72
|
+
},
|
|
73
|
+
"safeStorage": {
|
|
74
|
+
"service": "Chrome Safe Storage",
|
|
75
|
+
"application": "google-chrome-unstable",
|
|
76
|
+
"folder": "Chrome Keys"
|
|
77
|
+
},
|
|
78
|
+
"default": {
|
|
79
|
+
"darwin": ["com.google.chrome.dev"],
|
|
80
|
+
"linux": ["google-chrome-unstable.desktop"],
|
|
81
|
+
"win32": ["ChromeDHTML"]
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"id": "chrome-canary",
|
|
86
|
+
"family": "chromium",
|
|
87
|
+
"aliases": ["google-chrome-canary"],
|
|
88
|
+
"roots": {
|
|
89
|
+
"darwin": ["{appSupport}/Google/Chrome Canary"],
|
|
90
|
+
"win32": ["{localAppData}/Google/Chrome SxS/User Data"]
|
|
91
|
+
},
|
|
92
|
+
"safeStorage": {
|
|
93
|
+
"service": "Chrome Safe Storage",
|
|
94
|
+
"application": "chrome",
|
|
95
|
+
"folder": "Chrome Keys"
|
|
96
|
+
},
|
|
97
|
+
"default": {
|
|
98
|
+
"darwin": ["com.google.chrome.canary"],
|
|
99
|
+
"win32": ["ChromeSSHTM"]
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"id": "chromium",
|
|
104
|
+
"family": "chromium",
|
|
105
|
+
"aliases": [],
|
|
106
|
+
"roots": {
|
|
107
|
+
"darwin": ["{appSupport}/Chromium"],
|
|
108
|
+
"win32": ["{localAppData}/Chromium/User Data"],
|
|
109
|
+
"linux": ["{config}/chromium"]
|
|
110
|
+
},
|
|
111
|
+
"safeStorage": {
|
|
112
|
+
"service": "Chromium Safe Storage",
|
|
113
|
+
"application": "chromium",
|
|
114
|
+
"folder": "Chromium Keys"
|
|
115
|
+
},
|
|
116
|
+
"default": {
|
|
117
|
+
"darwin": ["org.chromium.chromium"],
|
|
118
|
+
"linux": ["chromium.desktop", "chromium-browser.desktop"]
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"id": "edge",
|
|
123
|
+
"family": "chromium",
|
|
124
|
+
"aliases": ["msedge", "microsoft-edge"],
|
|
125
|
+
"roots": {
|
|
126
|
+
"darwin": ["{appSupport}/Microsoft Edge"],
|
|
127
|
+
"win32": ["{localAppData}/Microsoft/Edge/User Data"],
|
|
128
|
+
"linux": ["{config}/microsoft-edge"]
|
|
129
|
+
},
|
|
130
|
+
"safeStorage": {
|
|
131
|
+
"service": "Microsoft Edge Safe Storage",
|
|
132
|
+
"application": "microsoft-edge",
|
|
133
|
+
"folder": "Microsoft Edge Keys"
|
|
134
|
+
},
|
|
135
|
+
"default": {
|
|
136
|
+
"darwin": ["com.microsoft.edgemac"],
|
|
137
|
+
"linux": ["microsoft-edge.desktop"],
|
|
138
|
+
"win32": ["MSEdgeHTM"]
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"id": "edge-beta",
|
|
143
|
+
"family": "chromium",
|
|
144
|
+
"aliases": ["microsoft-edge-beta"],
|
|
145
|
+
"roots": {
|
|
146
|
+
"darwin": ["{appSupport}/Microsoft Edge Beta"],
|
|
147
|
+
"win32": ["{localAppData}/Microsoft/Edge Beta/User Data"],
|
|
148
|
+
"linux": ["{config}/microsoft-edge-beta"]
|
|
149
|
+
},
|
|
150
|
+
"safeStorage": {
|
|
151
|
+
"service": "Microsoft Edge Safe Storage",
|
|
152
|
+
"application": "microsoft-edge-beta",
|
|
153
|
+
"folder": "Microsoft Edge Keys"
|
|
154
|
+
},
|
|
155
|
+
"default": {
|
|
156
|
+
"darwin": ["com.microsoft.edgemac.beta"],
|
|
157
|
+
"linux": ["microsoft-edge-beta.desktop"]
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"id": "edge-dev",
|
|
162
|
+
"family": "chromium",
|
|
163
|
+
"aliases": ["microsoft-edge-dev"],
|
|
164
|
+
"roots": {
|
|
165
|
+
"darwin": ["{appSupport}/Microsoft Edge Dev"],
|
|
166
|
+
"win32": ["{localAppData}/Microsoft/Edge Dev/User Data"],
|
|
167
|
+
"linux": ["{config}/microsoft-edge-dev"]
|
|
168
|
+
},
|
|
169
|
+
"safeStorage": {
|
|
170
|
+
"service": "Microsoft Edge Safe Storage",
|
|
171
|
+
"application": "microsoft-edge-dev",
|
|
172
|
+
"folder": "Microsoft Edge Keys"
|
|
173
|
+
},
|
|
174
|
+
"default": {
|
|
175
|
+
"darwin": ["com.microsoft.edgemac.dev"],
|
|
176
|
+
"linux": ["microsoft-edge-dev.desktop"]
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"id": "brave",
|
|
181
|
+
"family": "chromium",
|
|
182
|
+
"aliases": ["brave-browser"],
|
|
183
|
+
"roots": {
|
|
184
|
+
"darwin": ["{appSupport}/BraveSoftware/Brave-Browser"],
|
|
185
|
+
"win32": ["{localAppData}/BraveSoftware/Brave-Browser/User Data"],
|
|
186
|
+
"linux": ["{config}/BraveSoftware/Brave-Browser"]
|
|
187
|
+
},
|
|
188
|
+
"safeStorage": {
|
|
189
|
+
"service": "Brave Safe Storage",
|
|
190
|
+
"application": "brave",
|
|
191
|
+
"folder": "Brave Keys"
|
|
192
|
+
},
|
|
193
|
+
"default": {
|
|
194
|
+
"darwin": ["com.brave.browser"],
|
|
195
|
+
"linux": ["brave-browser.desktop", "brave.desktop"],
|
|
196
|
+
"win32": ["BraveHTML"]
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"id": "vivaldi",
|
|
201
|
+
"family": "chromium",
|
|
202
|
+
"aliases": [],
|
|
203
|
+
"roots": {
|
|
204
|
+
"darwin": ["{appSupport}/Vivaldi"],
|
|
205
|
+
"win32": ["{localAppData}/Vivaldi/User Data"],
|
|
206
|
+
"linux": ["{config}/vivaldi"]
|
|
207
|
+
},
|
|
208
|
+
"safeStorage": {
|
|
209
|
+
"service": "Vivaldi Safe Storage",
|
|
210
|
+
"application": "vivaldi",
|
|
211
|
+
"folder": "Vivaldi Keys"
|
|
212
|
+
},
|
|
213
|
+
"default": {
|
|
214
|
+
"darwin": ["com.vivaldi.vivaldi"],
|
|
215
|
+
"linux": ["vivaldi.desktop", "vivaldi-stable.desktop"],
|
|
216
|
+
"win32": ["VivaldiHTM"]
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"id": "opera",
|
|
221
|
+
"family": "chromium",
|
|
222
|
+
"aliases": [],
|
|
223
|
+
"singleProfile": true,
|
|
224
|
+
"roots": {
|
|
225
|
+
"darwin": ["{appSupport}/com.operasoftware.Opera"],
|
|
226
|
+
"win32": ["{appData}/Opera Software/Opera Stable"],
|
|
227
|
+
"linux": ["{config}/opera"]
|
|
228
|
+
},
|
|
229
|
+
"safeStorage": {
|
|
230
|
+
"service": "Opera Safe Storage",
|
|
231
|
+
"application": "opera",
|
|
232
|
+
"folder": "Opera Keys"
|
|
233
|
+
},
|
|
234
|
+
"default": {
|
|
235
|
+
"darwin": ["com.operasoftware.opera"],
|
|
236
|
+
"linux": ["opera.desktop", "opera_opera.desktop"],
|
|
237
|
+
"win32": ["OperaStable"]
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
"id": "opera-gx",
|
|
242
|
+
"family": "chromium",
|
|
243
|
+
"aliases": ["operagx"],
|
|
244
|
+
"singleProfile": true,
|
|
245
|
+
"roots": {
|
|
246
|
+
"darwin": ["{appSupport}/com.operasoftware.OperaGX"],
|
|
247
|
+
"win32": ["{appData}/Opera Software/Opera GX Stable"],
|
|
248
|
+
"linux": ["{config}/opera-gx"]
|
|
249
|
+
},
|
|
250
|
+
"safeStorage": {
|
|
251
|
+
"service": "Opera Safe Storage",
|
|
252
|
+
"application": "opera-gx",
|
|
253
|
+
"folder": "Opera Keys"
|
|
254
|
+
},
|
|
255
|
+
"default": {
|
|
256
|
+
"darwin": ["com.operasoftware.operagx"],
|
|
257
|
+
"win32": ["OperaGXStable"]
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
"id": "yandex",
|
|
262
|
+
"family": "chromium",
|
|
263
|
+
"aliases": ["yandex-browser", "yandexbrowser"],
|
|
264
|
+
"roots": {
|
|
265
|
+
"darwin": ["{appSupport}/Yandex/YandexBrowser"],
|
|
266
|
+
"win32": ["{localAppData}/Yandex/YandexBrowser/User Data"],
|
|
267
|
+
"linux": ["{config}/yandex-browser"]
|
|
268
|
+
},
|
|
269
|
+
"safeStorage": {
|
|
270
|
+
"service": "Yandex Safe Storage",
|
|
271
|
+
"application": "yandex-browser",
|
|
272
|
+
"folder": "Yandex Keys"
|
|
273
|
+
},
|
|
274
|
+
"default": {
|
|
275
|
+
"darwin": ["ru.yandex.desktop.yandex-browser"],
|
|
276
|
+
"linux": ["yandex-browser.desktop"],
|
|
277
|
+
"win32": ["YandexHTML"]
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
"id": "arc",
|
|
282
|
+
"family": "chromium",
|
|
283
|
+
"aliases": [],
|
|
284
|
+
"roots": {
|
|
285
|
+
"darwin": ["{appSupport}/Arc/User Data"],
|
|
286
|
+
"win32": [
|
|
287
|
+
"{localAppData}/Packages/TheBrowserCompany.Arc_ttt1ap7aakyb4/LocalCache/Local/Arc/User Data"
|
|
288
|
+
]
|
|
289
|
+
},
|
|
290
|
+
"safeStorage": {
|
|
291
|
+
"service": "Arc Safe Storage",
|
|
292
|
+
"application": "arc",
|
|
293
|
+
"folder": "Arc Keys"
|
|
294
|
+
},
|
|
295
|
+
"default": {
|
|
296
|
+
"darwin": ["company.thebrowser.browser"],
|
|
297
|
+
"win32": ["ArcHTML"]
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
"id": "firefox",
|
|
302
|
+
"family": "firefox",
|
|
303
|
+
"aliases": ["mozilla-firefox"],
|
|
304
|
+
"roots": {
|
|
305
|
+
"darwin": ["{appSupport}/Firefox"],
|
|
306
|
+
"win32": ["{appData}/Mozilla/Firefox"],
|
|
307
|
+
"linux": ["{home}/.mozilla/firefox"]
|
|
308
|
+
},
|
|
309
|
+
"default": {
|
|
310
|
+
"darwin": ["org.mozilla.firefox"],
|
|
311
|
+
"linux": ["firefox.desktop", "firefox-esr.desktop"],
|
|
312
|
+
"win32": ["FirefoxHTML", "FirefoxURL"]
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
"id": "firefox-developer",
|
|
317
|
+
"family": "firefox",
|
|
318
|
+
"aliases": ["firefox-dev", "firefox-developer-edition"],
|
|
319
|
+
"roots": {
|
|
320
|
+
"darwin": ["{appSupport}/Firefox"],
|
|
321
|
+
"win32": ["{appData}/Mozilla/Firefox"],
|
|
322
|
+
"linux": ["{home}/.mozilla/firefox"]
|
|
323
|
+
},
|
|
324
|
+
"default": {
|
|
325
|
+
"darwin": ["org.mozilla.firefoxdeveloperedition"]
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
"id": "firefox-nightly",
|
|
330
|
+
"family": "firefox",
|
|
331
|
+
"aliases": ["nightly"],
|
|
332
|
+
"roots": {
|
|
333
|
+
"darwin": ["{appSupport}/Firefox"],
|
|
334
|
+
"win32": ["{appData}/Mozilla/Firefox"],
|
|
335
|
+
"linux": ["{home}/.mozilla/firefox"]
|
|
336
|
+
},
|
|
337
|
+
"default": {
|
|
338
|
+
"darwin": ["org.mozilla.nightly"],
|
|
339
|
+
"linux": ["firefox-nightly.desktop"]
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
"id": "librewolf",
|
|
344
|
+
"family": "firefox",
|
|
345
|
+
"aliases": [],
|
|
346
|
+
"roots": {
|
|
347
|
+
"darwin": ["{appSupport}/LibreWolf"],
|
|
348
|
+
"win32": ["{appData}/librewolf"],
|
|
349
|
+
"linux": ["{home}/.librewolf"]
|
|
350
|
+
},
|
|
351
|
+
"default": {
|
|
352
|
+
"darwin": ["io.gitlab.librewolf-community"],
|
|
353
|
+
"linux": ["librewolf.desktop"]
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
"id": "waterfox",
|
|
358
|
+
"family": "firefox",
|
|
359
|
+
"aliases": [],
|
|
360
|
+
"roots": {
|
|
361
|
+
"darwin": ["{appSupport}/Waterfox"],
|
|
362
|
+
"win32": ["{appData}/Waterfox"],
|
|
363
|
+
"linux": ["{home}/.waterfox"]
|
|
364
|
+
},
|
|
365
|
+
"default": {
|
|
366
|
+
"darwin": ["net.waterfox.waterfox"],
|
|
367
|
+
"linux": ["waterfox.desktop"]
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
"id": "zen",
|
|
372
|
+
"family": "firefox",
|
|
373
|
+
"aliases": ["zen-browser"],
|
|
374
|
+
"roots": {
|
|
375
|
+
"darwin": ["{appSupport}/zen"],
|
|
376
|
+
"win32": ["{appData}/zen"],
|
|
377
|
+
"linux": ["{home}/.zen"]
|
|
378
|
+
},
|
|
379
|
+
"default": {
|
|
380
|
+
"darwin": ["app.zen-browser.zen"],
|
|
381
|
+
"linux": ["zen.desktop", "zen-browser.desktop"]
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
"id": "floorp",
|
|
386
|
+
"family": "firefox",
|
|
387
|
+
"aliases": [],
|
|
388
|
+
"roots": {
|
|
389
|
+
"darwin": ["{appSupport}/Floorp"],
|
|
390
|
+
"win32": ["{appData}/Floorp"],
|
|
391
|
+
"linux": ["{home}/.floorp"]
|
|
392
|
+
},
|
|
393
|
+
"default": {
|
|
394
|
+
"darwin": ["org.floorp.Floorp"],
|
|
395
|
+
"linux": ["floorp.desktop"]
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
]
|
|
399
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the operating-system default web browser to a canonical browser id
|
|
3
|
+
* from the shared catalogue (#114), so `browser: 'default'` imports from
|
|
4
|
+
* whichever browser a person actually uses.
|
|
5
|
+
*
|
|
6
|
+
* Each platform records the default differently:
|
|
7
|
+
*
|
|
8
|
+
* - macOS keeps it in the LaunchServices database as the `https` URL-scheme
|
|
9
|
+
* handler's bundle id (`defaults read
|
|
10
|
+
* com.apple.LaunchServices/com.apple.launchservices.secure LSHandlers`).
|
|
11
|
+
* - Linux reports it through `xdg-settings get default-web-browser` (a
|
|
12
|
+
* `.desktop` file name), falling back to the `x-scheme-handler/https`
|
|
13
|
+
* association from `xdg-mime`.
|
|
14
|
+
* - Windows stores the `https` UserChoice ProgId under
|
|
15
|
+
* `HKCU\...\UrlAssociations\https\UserChoice`.
|
|
16
|
+
*
|
|
17
|
+
* Every lookup runs through an injected command runner so the resolver is
|
|
18
|
+
* deterministic in tests, and the raw identifier is matched against the
|
|
19
|
+
* `default` identifiers each browser declares in `browser-sources.json`.
|
|
20
|
+
*/
|
|
21
|
+
import { runCommand as runSubprocess } from '../utilities/subprocess.js';
|
|
22
|
+
import { BROWSER_SOURCES } from './browser-sources.js';
|
|
23
|
+
|
|
24
|
+
async function defaultRunCommand(command, args, environment) {
|
|
25
|
+
const { stdout } = await runSubprocess(command, args, {
|
|
26
|
+
env: environment,
|
|
27
|
+
check: false,
|
|
28
|
+
});
|
|
29
|
+
return stdout;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Match a raw OS identifier (bundle id, `.desktop` name or ProgId) to a
|
|
34
|
+
* canonical browser id. Comparison is case-insensitive because the registry
|
|
35
|
+
* and the operating systems disagree on casing.
|
|
36
|
+
*/
|
|
37
|
+
function browserForIdentifier(identifier, platform) {
|
|
38
|
+
if (!identifier) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const needle = identifier.trim().toLowerCase();
|
|
42
|
+
if (!needle) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
for (const source of BROWSER_SOURCES) {
|
|
46
|
+
const identifiers = source.default?.[platform] ?? [];
|
|
47
|
+
if (identifiers.some((candidate) => candidate.toLowerCase() === needle)) {
|
|
48
|
+
return source.id;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function parseMacLaunchServicesHandler(output) {
|
|
55
|
+
// `defaults read` prints NeXTSTEP-style plist text; each handler is a brace
|
|
56
|
+
// block that may carry an LSHandlerURLScheme and an LSHandlerRoleAll bundle
|
|
57
|
+
// id. Find the block that handles https and read its bundle id.
|
|
58
|
+
for (const block of output.split('}')) {
|
|
59
|
+
if (!/LSHandlerURLScheme\s*=\s*"?https"?\s*;/.test(block)) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const match = /LSHandlerRoleAll\s*=\s*"?([^";]+)"?\s*;/.exec(block);
|
|
63
|
+
if (match) {
|
|
64
|
+
return match[1].trim();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function resolveDarwinDefault(runCommand, environment) {
|
|
71
|
+
const output = await runCommand(
|
|
72
|
+
'defaults',
|
|
73
|
+
[
|
|
74
|
+
'read',
|
|
75
|
+
'com.apple.LaunchServices/com.apple.launchservices.secure',
|
|
76
|
+
'LSHandlers',
|
|
77
|
+
],
|
|
78
|
+
environment
|
|
79
|
+
);
|
|
80
|
+
return browserForIdentifier(parseMacLaunchServicesHandler(output), 'darwin');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function resolveLinuxDefault(runCommand, environment) {
|
|
84
|
+
let desktop = '';
|
|
85
|
+
try {
|
|
86
|
+
desktop = (
|
|
87
|
+
await runCommand(
|
|
88
|
+
'xdg-settings',
|
|
89
|
+
['get', 'default-web-browser'],
|
|
90
|
+
environment
|
|
91
|
+
)
|
|
92
|
+
).trim();
|
|
93
|
+
} catch {
|
|
94
|
+
// xdg-settings may be absent; fall through to the xdg-mime query.
|
|
95
|
+
}
|
|
96
|
+
let resolved = browserForIdentifier(desktop, 'linux');
|
|
97
|
+
if (resolved) {
|
|
98
|
+
return resolved;
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
const fallback = (
|
|
102
|
+
await runCommand(
|
|
103
|
+
'xdg-mime',
|
|
104
|
+
['query', 'default', 'x-scheme-handler/https'],
|
|
105
|
+
environment
|
|
106
|
+
)
|
|
107
|
+
).trim();
|
|
108
|
+
resolved = browserForIdentifier(fallback, 'linux');
|
|
109
|
+
} catch {
|
|
110
|
+
resolved = null;
|
|
111
|
+
}
|
|
112
|
+
return resolved;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function parseWindowsProgId(output) {
|
|
116
|
+
// `reg query` prints ` ProgId REG_SZ FirefoxHTML`; take the last
|
|
117
|
+
// whitespace-separated token of the ProgId line.
|
|
118
|
+
for (const line of output.split(/\r?\n/)) {
|
|
119
|
+
if (/\bProgId\b/i.test(line)) {
|
|
120
|
+
const tokens = line.trim().split(/\s+/);
|
|
121
|
+
return tokens[tokens.length - 1];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function resolveWindowsDefault(runCommand, environment) {
|
|
128
|
+
let output;
|
|
129
|
+
try {
|
|
130
|
+
output = await runCommand(
|
|
131
|
+
'reg',
|
|
132
|
+
[
|
|
133
|
+
'query',
|
|
134
|
+
'HKCU\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\https\\UserChoice',
|
|
135
|
+
'/v',
|
|
136
|
+
'ProgId',
|
|
137
|
+
],
|
|
138
|
+
environment
|
|
139
|
+
);
|
|
140
|
+
} catch {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
return browserForIdentifier(parseWindowsProgId(output), 'win32');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Resolve the system default browser to a canonical catalogue id.
|
|
148
|
+
*
|
|
149
|
+
* @param {Object} [options]
|
|
150
|
+
* @param {string} [options.platform=process.platform]
|
|
151
|
+
* @param {Object<string,string>} [options.environment=process.env]
|
|
152
|
+
* @param {Function} [options.runCommand] - `(command, args, env) => stdout`
|
|
153
|
+
* @returns {Promise<string|null>} The browser id, or null when it cannot be resolved
|
|
154
|
+
*/
|
|
155
|
+
export async function resolveDefaultBrowser({
|
|
156
|
+
platform = process.platform,
|
|
157
|
+
environment = process.env,
|
|
158
|
+
runCommand = defaultRunCommand,
|
|
159
|
+
} = {}) {
|
|
160
|
+
if (platform === 'darwin') {
|
|
161
|
+
return await resolveDarwinDefault(runCommand, environment);
|
|
162
|
+
}
|
|
163
|
+
if (platform === 'linux') {
|
|
164
|
+
return await resolveLinuxDefault(runCommand, environment);
|
|
165
|
+
}
|
|
166
|
+
if (platform === 'win32') {
|
|
167
|
+
return await resolveWindowsDefault(runCommand, environment);
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export {
|
|
173
|
+
browserForIdentifier,
|
|
174
|
+
parseMacLaunchServicesHandler,
|
|
175
|
+
parseWindowsProgId,
|
|
176
|
+
};
|
|
@@ -116,6 +116,9 @@ export async function migrateCookies({
|
|
|
116
116
|
const batch = await readCookies({
|
|
117
117
|
browser,
|
|
118
118
|
profile,
|
|
119
|
+
// Read from the exact directory the migration resolved (which honours a
|
|
120
|
+
// custom `userDataDir`) instead of re-resolving the default profile.
|
|
121
|
+
profileDir: sourceProfileDir,
|
|
119
122
|
domainFilter: domainFilter ?? undefined,
|
|
120
123
|
ignoreDecryptionErrors: true,
|
|
121
124
|
...readerOptions,
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
|
|
4
|
+
import { resolveImportSource } from '../browser-cookies.js';
|
|
5
|
+
import { browserFamily, isSingleProfileBrowser } from '../browser-sources.js';
|
|
4
6
|
import {
|
|
5
7
|
browserProfileRoot,
|
|
6
|
-
normalizeCookieBrowser,
|
|
7
8
|
resolveBrowserProfile,
|
|
8
9
|
} from '../browser-profiles.js';
|
|
9
10
|
import { migrateBookmarks } from './bookmarks.js';
|
|
@@ -48,7 +49,17 @@ export const ALL_DATA_CLASSES = Object.freeze([
|
|
|
48
49
|
'extensions',
|
|
49
50
|
]);
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
// Classification is driven by the shared catalogue so every Chromium variant
|
|
53
|
+
// (vivaldi, opera, arc, the chrome/edge channels, …) and every Firefox fork
|
|
54
|
+
// (librewolf, waterfox, zen, floorp, the firefox channels, …) is handled the
|
|
55
|
+
// same way as its canonical engine, not just the original four browsers.
|
|
56
|
+
function isChromiumBrowser(browser) {
|
|
57
|
+
return browserFamily(browser) === 'chromium';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isFirefoxBrowser(browser) {
|
|
61
|
+
return browserFamily(browser) === 'firefox';
|
|
62
|
+
}
|
|
52
63
|
|
|
53
64
|
function emptyMigrated() {
|
|
54
65
|
return {
|
|
@@ -69,21 +80,23 @@ async function resolveSourceProfileDir({
|
|
|
69
80
|
homeDir,
|
|
70
81
|
environment,
|
|
71
82
|
}) {
|
|
83
|
+
// A Chromium profile lives in a named subdirectory of the user data dir,
|
|
84
|
+
// except in single-profile browsers (Opera) that keep it in the root; for
|
|
85
|
+
// Firefox the userDataDir already points at the profile.
|
|
86
|
+
const nestsProfiles =
|
|
87
|
+
isChromiumBrowser(browser) && !isSingleProfileBrowser(browser);
|
|
72
88
|
if (userDataDir) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return path.join(userDataDir, profile ?? 'Default');
|
|
77
|
-
}
|
|
78
|
-
return userDataDir;
|
|
89
|
+
return nestsProfiles
|
|
90
|
+
? path.join(userDataDir, profile ?? 'Default')
|
|
91
|
+
: userDataDir;
|
|
79
92
|
}
|
|
80
|
-
if (
|
|
93
|
+
if (isChromiumBrowser(browser)) {
|
|
81
94
|
const root = browserProfileRoot(browser, {
|
|
82
95
|
platform,
|
|
83
96
|
homeDir,
|
|
84
97
|
environment,
|
|
85
98
|
});
|
|
86
|
-
return path.join(root, profile ?? 'Default');
|
|
99
|
+
return nestsProfiles ? path.join(root, profile ?? 'Default') : root;
|
|
87
100
|
}
|
|
88
101
|
const resolved = await resolveBrowserProfile({
|
|
89
102
|
browser,
|
|
@@ -120,7 +133,7 @@ async function resolvePasswordKeys({
|
|
|
120
133
|
platform,
|
|
121
134
|
environment,
|
|
122
135
|
});
|
|
123
|
-
const resolveSourceKey =
|
|
136
|
+
const resolveSourceKey = isChromiumBrowser(browser)
|
|
124
137
|
? createSourceKeyResolver({
|
|
125
138
|
browser,
|
|
126
139
|
platform,
|
|
@@ -156,6 +169,7 @@ export async function migrateProfile({
|
|
|
156
169
|
keys,
|
|
157
170
|
homeDir = os.homedir(),
|
|
158
171
|
environment = process.env,
|
|
172
|
+
runCommand,
|
|
159
173
|
}) {
|
|
160
174
|
if (!from?.browser) {
|
|
161
175
|
throw new TypeError('migrateProfile requires from.browser');
|
|
@@ -163,9 +177,19 @@ export async function migrateProfile({
|
|
|
163
177
|
if (!to) {
|
|
164
178
|
throw new TypeError('migrateProfile requires a target directory (to)');
|
|
165
179
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const
|
|
180
|
+
// An explicit userDataDir names the source, so only an installed-browser
|
|
181
|
+
// import is steered towards the profile holding the requested domains.
|
|
182
|
+
const source = await resolveImportSource({
|
|
183
|
+
browser: from.browser,
|
|
184
|
+
domains: from.userDataDir ? undefined : domains,
|
|
185
|
+
platform,
|
|
186
|
+
homeDir,
|
|
187
|
+
environment,
|
|
188
|
+
runCommand,
|
|
189
|
+
});
|
|
190
|
+
const { browser } = source;
|
|
191
|
+
const profile = from.profile ?? source.profile ?? 'Default';
|
|
192
|
+
const isFirefox = isFirefoxBrowser(browser);
|
|
169
193
|
const sourceProfileDir = await resolveSourceProfileDir({
|
|
170
194
|
browser,
|
|
171
195
|
profile,
|
|
@@ -183,6 +207,9 @@ export async function migrateProfile({
|
|
|
183
207
|
warnings: [],
|
|
184
208
|
cookies: [],
|
|
185
209
|
};
|
|
210
|
+
if (source.warning) {
|
|
211
|
+
report.warnings.push(source.warning);
|
|
212
|
+
}
|
|
186
213
|
|
|
187
214
|
if (selected.has('cookies')) {
|
|
188
215
|
if (isFirefox) {
|