browser-commander 0.21.2 → 0.23.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.
@@ -0,0 +1,429 @@
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
+ "id": "safari",
400
+ "family": "safari",
401
+ "aliases": ["apple-safari"],
402
+ "singleProfile": true,
403
+ "roots": {
404
+ "darwin": [
405
+ "{home}/Library/Containers/com.apple.Safari/Data/Library",
406
+ "{home}/Library"
407
+ ]
408
+ },
409
+ "default": {
410
+ "darwin": ["com.apple.safari"]
411
+ }
412
+ },
413
+ {
414
+ "id": "safari-technology-preview",
415
+ "family": "safari",
416
+ "aliases": ["safari-tp", "safari-preview"],
417
+ "singleProfile": true,
418
+ "roots": {
419
+ "darwin": [
420
+ "{home}/Library/Containers/com.apple.SafariTechnologyPreview/Data/Library",
421
+ "{home}/Library/Safari Technology Preview"
422
+ ]
423
+ },
424
+ "default": {
425
+ "darwin": ["com.apple.safaritechnologypreview"]
426
+ }
427
+ }
428
+ ]
429
+ }
@@ -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,