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.
- package/.jscpd-baseline.json +5 -6
- package/CHANGELOG.md +43 -0
- package/package.json +1 -1
- package/src/browser/browser-cookie-credentials.js +15 -24
- package/src/browser/browser-cookies.js +251 -11
- package/src/browser/browser-profiles.js +163 -79
- package/src/browser/browser-sources.js +184 -0
- package/src/browser/browser-sources.json +429 -0
- package/src/browser/default-browser.js +176 -0
- package/src/browser/migration/cookies.js +3 -0
- package/src/browser/migration/index.js +74 -14
- package/src/browser/safari-cookies.js +173 -0
- 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/browser/safari-cookies.test.js +246 -0
- package/tests/unit/cli/main.test.js +32 -5
- package/tests/unit/scripts/browser-support.test.js +56 -0
|
@@ -2,28 +2,75 @@ import { access, readdir, readFile } from 'node:fs/promises';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
import {
|
|
6
|
+
BROWSER_IDS,
|
|
7
|
+
browserFamily,
|
|
8
|
+
isSingleProfileBrowser,
|
|
9
|
+
normalizeBrowserId,
|
|
10
|
+
resolveBrowserRoots,
|
|
11
|
+
} from './browser-sources.js';
|
|
12
|
+
import { resolveDefaultBrowser } from './default-browser.js';
|
|
13
|
+
import { findSafariCookieFile } from './safari-cookies.js';
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Keywords that select the operating-system default browser instead of a named
|
|
17
|
+
* one, so `browser: 'default'` (or `'auto'`) imports from whatever a person
|
|
18
|
+
* actually uses. Importing stays opt-in: callers pass this explicitly.
|
|
19
|
+
*/
|
|
20
|
+
const DEFAULT_BROWSER_KEYWORDS = new Set(['default', 'auto']);
|
|
21
|
+
|
|
22
|
+
/** Whether `browser` asks for the system default rather than a named browser. */
|
|
23
|
+
export function isDefaultBrowserKeyword(browser) {
|
|
24
|
+
return (
|
|
25
|
+
typeof browser === 'string' &&
|
|
26
|
+
DEFAULT_BROWSER_KEYWORDS.has(browser.trim().toLowerCase())
|
|
27
|
+
);
|
|
15
28
|
}
|
|
16
29
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Resolve a requested browser to a canonical catalogue id, expanding the
|
|
32
|
+
* `default`/`auto` keywords to the system default browser. Named browsers are
|
|
33
|
+
* normalized through the catalogue as before.
|
|
34
|
+
*/
|
|
35
|
+
export async function resolveSourceBrowser(
|
|
36
|
+
browser,
|
|
37
|
+
{ platform = process.platform, environment = process.env, runCommand } = {}
|
|
38
|
+
) {
|
|
39
|
+
if (!isDefaultBrowserKeyword(browser)) {
|
|
40
|
+
return normalizeCookieBrowser(browser);
|
|
41
|
+
}
|
|
42
|
+
const resolved = await resolveDefaultBrowser({
|
|
43
|
+
platform,
|
|
44
|
+
environment,
|
|
45
|
+
runCommand,
|
|
46
|
+
});
|
|
47
|
+
if (!resolved) {
|
|
20
48
|
throw new Error(
|
|
21
|
-
|
|
49
|
+
'Could not determine the system default browser; pass an explicit browser instead of "default".'
|
|
22
50
|
);
|
|
23
51
|
}
|
|
24
|
-
return
|
|
52
|
+
return resolved;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Every browser profile discovery can read from. Driven by the shared
|
|
57
|
+
* `browser-sources.json` catalogue, so adding a browser there adds it here.
|
|
58
|
+
*/
|
|
59
|
+
export const SUPPORTED_COOKIE_BROWSERS = BROWSER_IDS;
|
|
60
|
+
|
|
61
|
+
function platformPath(platform) {
|
|
62
|
+
return platform === 'win32' ? path.win32 : path;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Resolve a browser name (id or alias) to its canonical id. */
|
|
66
|
+
export function normalizeCookieBrowser(browser) {
|
|
67
|
+
return normalizeBrowserId(browser);
|
|
25
68
|
}
|
|
26
69
|
|
|
70
|
+
/**
|
|
71
|
+
* The primary profile root a browser uses on a platform, or `undefined` when
|
|
72
|
+
* the browser does not run there.
|
|
73
|
+
*/
|
|
27
74
|
export function browserProfileRoot(
|
|
28
75
|
browser,
|
|
29
76
|
{
|
|
@@ -32,46 +79,12 @@ export function browserProfileRoot(
|
|
|
32
79
|
environment = process.env,
|
|
33
80
|
} = {}
|
|
34
81
|
) {
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
chrome: pathApi.join(support, 'Google', 'Chrome'),
|
|
42
|
-
chromium: pathApi.join(support, 'Chromium'),
|
|
43
|
-
edge: pathApi.join(support, 'Microsoft Edge'),
|
|
44
|
-
firefox: pathApi.join(support, 'Firefox'),
|
|
45
|
-
};
|
|
46
|
-
return roots[normalizedBrowser];
|
|
47
|
-
}
|
|
48
|
-
if (platform === 'win32') {
|
|
49
|
-
const localAppData =
|
|
50
|
-
environment.LOCALAPPDATA ?? pathApi.join(homeDir, 'AppData', 'Local');
|
|
51
|
-
const roamingAppData =
|
|
52
|
-
environment.APPDATA ?? pathApi.join(homeDir, 'AppData', 'Roaming');
|
|
53
|
-
const roots = {
|
|
54
|
-
brave: pathApi.join(
|
|
55
|
-
localAppData,
|
|
56
|
-
'BraveSoftware',
|
|
57
|
-
'Brave-Browser',
|
|
58
|
-
'User Data'
|
|
59
|
-
),
|
|
60
|
-
chrome: pathApi.join(localAppData, 'Google', 'Chrome', 'User Data'),
|
|
61
|
-
chromium: pathApi.join(localAppData, 'Chromium', 'User Data'),
|
|
62
|
-
edge: pathApi.join(localAppData, 'Microsoft', 'Edge', 'User Data'),
|
|
63
|
-
firefox: pathApi.join(roamingAppData, 'Mozilla', 'Firefox'),
|
|
64
|
-
};
|
|
65
|
-
return roots[normalizedBrowser];
|
|
66
|
-
}
|
|
67
|
-
const roots = {
|
|
68
|
-
brave: pathApi.join(homeDir, '.config', 'BraveSoftware', 'Brave-Browser'),
|
|
69
|
-
chrome: pathApi.join(homeDir, '.config', 'google-chrome'),
|
|
70
|
-
chromium: pathApi.join(homeDir, '.config', 'chromium'),
|
|
71
|
-
edge: pathApi.join(homeDir, '.config', 'microsoft-edge'),
|
|
72
|
-
firefox: pathApi.join(homeDir, '.mozilla', 'firefox'),
|
|
73
|
-
};
|
|
74
|
-
return roots[normalizedBrowser];
|
|
82
|
+
const [root] = resolveBrowserRoots(browser, {
|
|
83
|
+
platform,
|
|
84
|
+
homeDir,
|
|
85
|
+
environment,
|
|
86
|
+
});
|
|
87
|
+
return root;
|
|
75
88
|
}
|
|
76
89
|
|
|
77
90
|
async function pathExists(filePath) {
|
|
@@ -100,7 +113,10 @@ function chromiumCookiePaths(profilePath, pathApi = path) {
|
|
|
100
113
|
|
|
101
114
|
export async function findCookieDatabase(browser, profilePath, platform) {
|
|
102
115
|
const pathApi = platformPath(platform ?? process.platform);
|
|
103
|
-
if (
|
|
116
|
+
if (browserFamily(browser) === 'safari') {
|
|
117
|
+
return findSafariCookieFile(profilePath);
|
|
118
|
+
}
|
|
119
|
+
if (browserFamily(browser) === 'firefox') {
|
|
104
120
|
const candidate = pathApi.join(profilePath, 'cookies.sqlite');
|
|
105
121
|
return (await pathExists(candidate)) ? candidate : null;
|
|
106
122
|
}
|
|
@@ -112,11 +128,38 @@ export async function findCookieDatabase(browser, profilePath, platform) {
|
|
|
112
128
|
return null;
|
|
113
129
|
}
|
|
114
130
|
|
|
131
|
+
/** Default profile first, then by name. */
|
|
132
|
+
function sortProfiles(profiles) {
|
|
133
|
+
return profiles.sort(
|
|
134
|
+
(left, right) =>
|
|
135
|
+
Number(right.isDefault) - Number(left.isDefault) ||
|
|
136
|
+
left.name.localeCompare(right.name)
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
115
140
|
async function listChromiumProfiles(browser, root, platform) {
|
|
116
141
|
if (!(await pathExists(root))) {
|
|
117
142
|
return [];
|
|
118
143
|
}
|
|
119
144
|
const pathApi = platformPath(platform);
|
|
145
|
+
|
|
146
|
+
// Opera-style browsers keep one profile in the root itself rather than in
|
|
147
|
+
// Default/Profile N subdirectories.
|
|
148
|
+
if (isSingleProfileBrowser(browser)) {
|
|
149
|
+
if (!(await findCookieDatabase(browser, root, platform))) {
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
return [
|
|
153
|
+
{
|
|
154
|
+
browser,
|
|
155
|
+
name: 'Default',
|
|
156
|
+
displayName: 'Default',
|
|
157
|
+
path: root,
|
|
158
|
+
isDefault: true,
|
|
159
|
+
},
|
|
160
|
+
];
|
|
161
|
+
}
|
|
162
|
+
|
|
120
163
|
const localState = await readJson(pathApi.join(root, 'Local State'));
|
|
121
164
|
const infoCache = localState.profile?.info_cache ?? {};
|
|
122
165
|
const names = new Set(Object.keys(infoCache));
|
|
@@ -150,11 +193,7 @@ async function listChromiumProfiles(browser, root, platform) {
|
|
|
150
193
|
(names.size === 1 && name === 'Default'),
|
|
151
194
|
});
|
|
152
195
|
}
|
|
153
|
-
return profiles
|
|
154
|
-
(left, right) =>
|
|
155
|
-
Number(right.isDefault) - Number(left.isDefault) ||
|
|
156
|
-
left.name.localeCompare(right.name)
|
|
157
|
-
);
|
|
196
|
+
return sortProfiles(profiles);
|
|
158
197
|
}
|
|
159
198
|
|
|
160
199
|
function parseIni(text) {
|
|
@@ -176,7 +215,7 @@ function parseIni(text) {
|
|
|
176
215
|
return sections;
|
|
177
216
|
}
|
|
178
217
|
|
|
179
|
-
async function listFirefoxProfiles(root, platform) {
|
|
218
|
+
async function listFirefoxProfiles(browser, root, platform) {
|
|
180
219
|
if (!(await pathExists(root))) {
|
|
181
220
|
return [];
|
|
182
221
|
}
|
|
@@ -215,23 +254,51 @@ async function listFirefoxProfiles(root, platform) {
|
|
|
215
254
|
section.IsRelative === '0'
|
|
216
255
|
? section.Path
|
|
217
256
|
: pathApi.resolve(relativeRoot, section.Path);
|
|
218
|
-
if (!(await findCookieDatabase(
|
|
257
|
+
if (!(await findCookieDatabase(browser, profilePath, platform))) {
|
|
219
258
|
continue;
|
|
220
259
|
}
|
|
221
260
|
const displayName = section.Name ?? pathApi.basename(profilePath);
|
|
222
261
|
profiles.push({
|
|
223
|
-
browser
|
|
262
|
+
browser,
|
|
224
263
|
name: displayName,
|
|
225
264
|
displayName,
|
|
226
265
|
path: profilePath,
|
|
227
266
|
isDefault: section.Default === '1',
|
|
228
267
|
});
|
|
229
268
|
}
|
|
230
|
-
return profiles
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
269
|
+
return sortProfiles(profiles);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function listProfilesForBrowser(browser, platform, homeDir, environment) {
|
|
273
|
+
const roots = resolveBrowserRoots(browser, {
|
|
274
|
+
platform,
|
|
275
|
+
homeDir,
|
|
276
|
+
environment,
|
|
277
|
+
});
|
|
278
|
+
const family = browserFamily(browser);
|
|
279
|
+
const profiles = [];
|
|
280
|
+
for (const root of roots) {
|
|
281
|
+
if (family === 'safari') {
|
|
282
|
+
if (await findSafariCookieFile(root)) {
|
|
283
|
+
return [
|
|
284
|
+
{
|
|
285
|
+
browser,
|
|
286
|
+
name: 'Default',
|
|
287
|
+
displayName: 'Default',
|
|
288
|
+
path: root,
|
|
289
|
+
isDefault: true,
|
|
290
|
+
},
|
|
291
|
+
];
|
|
292
|
+
}
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
profiles.push(
|
|
296
|
+
...(family === 'firefox'
|
|
297
|
+
? await listFirefoxProfiles(browser, root, platform)
|
|
298
|
+
: await listChromiumProfiles(browser, root, platform))
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
return profiles;
|
|
235
302
|
}
|
|
236
303
|
|
|
237
304
|
/** Discover cookie-bearing profiles from installed browsers. */
|
|
@@ -240,28 +307,45 @@ export async function listBrowserProfiles({
|
|
|
240
307
|
platform = process.platform,
|
|
241
308
|
homeDir = os.homedir(),
|
|
242
309
|
environment = process.env,
|
|
310
|
+
runCommand,
|
|
243
311
|
} = {}) {
|
|
244
312
|
const browsers = browser
|
|
245
|
-
? [
|
|
246
|
-
|
|
313
|
+
? [
|
|
314
|
+
await resolveSourceBrowser(browser, {
|
|
315
|
+
platform,
|
|
316
|
+
environment,
|
|
317
|
+
runCommand,
|
|
318
|
+
}),
|
|
319
|
+
]
|
|
320
|
+
: BROWSER_IDS;
|
|
247
321
|
const profiles = [];
|
|
322
|
+
// Several Firefox channels (firefox, firefox-developer, firefox-nightly)
|
|
323
|
+
// share one profile root, so a catalogue-wide scan would otherwise report
|
|
324
|
+
// the same profile under each id. Keep the first (canonical) browser.
|
|
325
|
+
const seen = new Set();
|
|
248
326
|
for (const candidate of browsers) {
|
|
249
|
-
const
|
|
327
|
+
for (const profile of await listProfilesForBrowser(
|
|
328
|
+
candidate,
|
|
250
329
|
platform,
|
|
251
330
|
homeDir,
|
|
252
|
-
environment
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
331
|
+
environment
|
|
332
|
+
)) {
|
|
333
|
+
if (seen.has(profile.path)) {
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
seen.add(profile.path);
|
|
337
|
+
profiles.push(profile);
|
|
338
|
+
}
|
|
259
339
|
}
|
|
260
340
|
return profiles;
|
|
261
341
|
}
|
|
262
342
|
|
|
263
343
|
export async function resolveBrowserProfile(options) {
|
|
264
|
-
const browser =
|
|
344
|
+
const browser = await resolveSourceBrowser(options.browser, {
|
|
345
|
+
platform: options.platform,
|
|
346
|
+
environment: options.environment,
|
|
347
|
+
runCommand: options.runCommand,
|
|
348
|
+
});
|
|
265
349
|
const profiles = await listBrowserProfiles({ ...options, browser });
|
|
266
350
|
const requested = options.profile;
|
|
267
351
|
const selected = requested
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catalogue of installed browsers Browser Commander can import from (#114).
|
|
3
|
+
*
|
|
4
|
+
* The data lives in `browser-sources.json`, a shared asset Python and Rust
|
|
5
|
+
* duplicate byte-for-byte (checked by
|
|
6
|
+
* `scripts/check-shared-fingerprint-assets.sh`). This module turns that data
|
|
7
|
+
* into the lookups the rest of the browser code needs: canonical ids with
|
|
8
|
+
* their aliases, the per-platform profile roots, the Chromium Safe Storage
|
|
9
|
+
* identity, and the operating-system identifiers that mark a browser as the
|
|
10
|
+
* system default.
|
|
11
|
+
*
|
|
12
|
+
* Keeping it data-driven is what lets a single JSON edit add Opera, Vivaldi,
|
|
13
|
+
* Arc, a Firefox fork, or a Chrome channel to all three implementations at
|
|
14
|
+
* once, rather than touching hand-written per-platform maps in each language.
|
|
15
|
+
*/
|
|
16
|
+
import { readFileSync } from 'node:fs';
|
|
17
|
+
import os from 'node:os';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
|
|
20
|
+
const registry = JSON.parse(
|
|
21
|
+
readFileSync(new URL('./browser-sources.json', import.meta.url), 'utf8')
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Every known browser, in catalogue order.
|
|
26
|
+
*
|
|
27
|
+
* @type {ReadonlyArray<object>}
|
|
28
|
+
*/
|
|
29
|
+
export const BROWSER_SOURCES = Object.freeze(
|
|
30
|
+
registry.browsers.map((browser) => Object.freeze(browser))
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
/** Canonical ids, in catalogue order. */
|
|
34
|
+
export const BROWSER_IDS = Object.freeze(
|
|
35
|
+
BROWSER_SOURCES.map((browser) => browser.id)
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const BY_NAME = new Map();
|
|
39
|
+
for (const browser of BROWSER_SOURCES) {
|
|
40
|
+
BY_NAME.set(browser.id, browser);
|
|
41
|
+
for (const alias of browser.aliases ?? []) {
|
|
42
|
+
BY_NAME.set(alias, browser);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function platformPath(platform) {
|
|
47
|
+
return platform === 'win32' ? path.win32 : path;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Resolve a name (canonical id or alias) to its catalogue entry.
|
|
52
|
+
*
|
|
53
|
+
* @param {string} name
|
|
54
|
+
* @returns {object|undefined}
|
|
55
|
+
*/
|
|
56
|
+
export function findBrowserSource(name) {
|
|
57
|
+
if (typeof name !== 'string') {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
return BY_NAME.get(name) ?? BY_NAME.get(name.toLowerCase());
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Resolve a name to its canonical id, throwing a readable error otherwise.
|
|
65
|
+
*
|
|
66
|
+
* @param {string} name
|
|
67
|
+
* @returns {string}
|
|
68
|
+
*/
|
|
69
|
+
export function normalizeBrowserId(name) {
|
|
70
|
+
return normalizeBrowserSource(name).id;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** The family ('chromium' or 'firefox') of a browser name. */
|
|
74
|
+
export function browserFamily(name) {
|
|
75
|
+
return normalizeBrowserSource(name).family;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function normalizeBrowserSource(name) {
|
|
79
|
+
const source = findBrowserSource(name);
|
|
80
|
+
if (!source) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`Unsupported browser: ${name}. Expected one of ${BROWSER_IDS.join(', ')}`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return source;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function templateVariables(platform, homeDir, environment) {
|
|
89
|
+
const pathApi = platformPath(platform);
|
|
90
|
+
if (platform === 'darwin') {
|
|
91
|
+
return {
|
|
92
|
+
home: homeDir,
|
|
93
|
+
appSupport: pathApi.join(homeDir, 'Library', 'Application Support'),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
if (platform === 'win32') {
|
|
97
|
+
return {
|
|
98
|
+
home: homeDir,
|
|
99
|
+
localAppData:
|
|
100
|
+
environment.LOCALAPPDATA ?? pathApi.join(homeDir, 'AppData', 'Local'),
|
|
101
|
+
appData:
|
|
102
|
+
environment.APPDATA ?? pathApi.join(homeDir, 'AppData', 'Roaming'),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
home: homeDir,
|
|
107
|
+
config: environment.XDG_CONFIG_HOME ?? pathApi.join(homeDir, '.config'),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function expandTemplate(template, variables, pathApi) {
|
|
112
|
+
const match = /^\{(\w+)\}(.*)$/.exec(template);
|
|
113
|
+
if (!match) {
|
|
114
|
+
return template;
|
|
115
|
+
}
|
|
116
|
+
const base = variables[match[1]];
|
|
117
|
+
if (base === undefined) {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
const rest = match[2].split('/').filter(Boolean);
|
|
121
|
+
return pathApi.join(base, ...rest);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The absolute profile roots a browser uses on a platform. Returns an empty
|
|
126
|
+
* array when the browser does not run on that platform (for example Chrome
|
|
127
|
+
* Canary on Linux).
|
|
128
|
+
*
|
|
129
|
+
* @param {string} name
|
|
130
|
+
* @param {Object} [options]
|
|
131
|
+
* @param {string} [options.platform=process.platform]
|
|
132
|
+
* @param {string} [options.homeDir=os.homedir()]
|
|
133
|
+
* @param {Object} [options.environment=process.env]
|
|
134
|
+
* @returns {string[]}
|
|
135
|
+
*/
|
|
136
|
+
export function resolveBrowserRoots(
|
|
137
|
+
name,
|
|
138
|
+
{
|
|
139
|
+
platform = process.platform,
|
|
140
|
+
homeDir = os.homedir(),
|
|
141
|
+
environment = process.env,
|
|
142
|
+
} = {}
|
|
143
|
+
) {
|
|
144
|
+
const source = normalizeBrowserSource(name);
|
|
145
|
+
const templates = source.roots?.[platform] ?? [];
|
|
146
|
+
const pathApi = platformPath(platform);
|
|
147
|
+
const variables = templateVariables(platform, homeDir, environment);
|
|
148
|
+
const roots = [];
|
|
149
|
+
for (const template of templates) {
|
|
150
|
+
const resolved = expandTemplate(template, variables, pathApi);
|
|
151
|
+
if (resolved !== undefined) {
|
|
152
|
+
roots.push(resolved);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return roots;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** True when a browser stores one profile in the root itself (Opera-style). */
|
|
159
|
+
export function isSingleProfileBrowser(name) {
|
|
160
|
+
return normalizeBrowserSource(name).singleProfile === true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The Chromium Safe Storage identity for a browser, or undefined for a
|
|
165
|
+
* Firefox-family browser (which does not use OSCrypt).
|
|
166
|
+
*
|
|
167
|
+
* @param {string} name
|
|
168
|
+
* @returns {{service: string, application: string, folder: string}|undefined}
|
|
169
|
+
*/
|
|
170
|
+
export function safeStorageIdentity(name) {
|
|
171
|
+
return normalizeBrowserSource(name).safeStorage;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* The operating-system identifiers that mark a browser as the system default:
|
|
176
|
+
* macOS bundle ids, Linux `.desktop` file names, or Windows ProgIds.
|
|
177
|
+
*
|
|
178
|
+
* @param {string} name
|
|
179
|
+
* @param {string} platform
|
|
180
|
+
* @returns {string[]}
|
|
181
|
+
*/
|
|
182
|
+
export function defaultBrowserIdentifiers(name, platform) {
|
|
183
|
+
return normalizeBrowserSource(name).default?.[platform] ?? [];
|
|
184
|
+
}
|