browser-commander 0.14.0 → 0.16.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/CHANGELOG.md +12 -0
- package/README.md +96 -2
- package/package.json +3 -2
- package/src/browser/browser-cookie-cache.js +278 -0
- package/src/browser/browser-cookie-credentials.js +148 -0
- package/src/browser/browser-cookie-crypto.js +128 -0
- package/src/browser/browser-cookie-database.js +35 -0
- package/src/browser/browser-cookies.js +343 -0
- package/src/browser/browser-profiles.js +280 -0
- package/src/browser/launch-options.js +68 -10
- package/src/browser/launcher.js +13 -3
- package/src/browser/real-browser.js +15 -4
- package/src/core/constants.js +1 -0
- package/src/exports.js +4 -0
- package/tests/fixtures/browser-cookie-cache-worker.mjs +21 -0
- package/tests/unit/browser/browser-cookies.test.js +628 -0
- package/tests/unit/browser/launch-options.test.js +55 -0
- package/tests/unit/browser/real-browser.test.js +19 -0
- package/tests/unit/core/constants.test.js +1 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { access, readdir, readFile } from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
export const SUPPORTED_COOKIE_BROWSERS = [
|
|
6
|
+
'chrome',
|
|
7
|
+
'edge',
|
|
8
|
+
'brave',
|
|
9
|
+
'chromium',
|
|
10
|
+
'firefox',
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
function platformPath(platform) {
|
|
14
|
+
return platform === 'win32' ? path.win32 : path;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function normalizeCookieBrowser(browser) {
|
|
18
|
+
const normalized = browser === 'msedge' ? 'edge' : browser;
|
|
19
|
+
if (!SUPPORTED_COOKIE_BROWSERS.includes(normalized)) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
`Unsupported browser: ${browser}. Expected one of ${SUPPORTED_COOKIE_BROWSERS.join(', ')}`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
return normalized;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function browserProfileRoot(
|
|
28
|
+
browser,
|
|
29
|
+
{
|
|
30
|
+
platform = process.platform,
|
|
31
|
+
homeDir = os.homedir(),
|
|
32
|
+
environment = process.env,
|
|
33
|
+
} = {}
|
|
34
|
+
) {
|
|
35
|
+
const normalizedBrowser = normalizeCookieBrowser(browser);
|
|
36
|
+
const pathApi = platformPath(platform);
|
|
37
|
+
if (platform === 'darwin') {
|
|
38
|
+
const support = pathApi.join(homeDir, 'Library', 'Application Support');
|
|
39
|
+
const roots = {
|
|
40
|
+
brave: pathApi.join(support, 'BraveSoftware', 'Brave-Browser'),
|
|
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];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function pathExists(filePath) {
|
|
78
|
+
try {
|
|
79
|
+
await access(filePath);
|
|
80
|
+
return true;
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function readJson(filePath) {
|
|
87
|
+
try {
|
|
88
|
+
return JSON.parse(await readFile(filePath, 'utf8'));
|
|
89
|
+
} catch {
|
|
90
|
+
return {};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function chromiumCookiePaths(profilePath, pathApi = path) {
|
|
95
|
+
return [
|
|
96
|
+
pathApi.join(profilePath, 'Network', 'Cookies'),
|
|
97
|
+
pathApi.join(profilePath, 'Cookies'),
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function findCookieDatabase(browser, profilePath, platform) {
|
|
102
|
+
const pathApi = platformPath(platform ?? process.platform);
|
|
103
|
+
if (normalizeCookieBrowser(browser) === 'firefox') {
|
|
104
|
+
const candidate = pathApi.join(profilePath, 'cookies.sqlite');
|
|
105
|
+
return (await pathExists(candidate)) ? candidate : null;
|
|
106
|
+
}
|
|
107
|
+
for (const candidate of chromiumCookiePaths(profilePath, pathApi)) {
|
|
108
|
+
if (await pathExists(candidate)) {
|
|
109
|
+
return candidate;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function listChromiumProfiles(browser, root, platform) {
|
|
116
|
+
if (!(await pathExists(root))) {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
const pathApi = platformPath(platform);
|
|
120
|
+
const localState = await readJson(pathApi.join(root, 'Local State'));
|
|
121
|
+
const infoCache = localState.profile?.info_cache ?? {};
|
|
122
|
+
const names = new Set(Object.keys(infoCache));
|
|
123
|
+
try {
|
|
124
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
125
|
+
for (const entry of entries) {
|
|
126
|
+
if (
|
|
127
|
+
entry.isDirectory() &&
|
|
128
|
+
(entry.name === 'Default' || entry.name.startsWith('Profile '))
|
|
129
|
+
) {
|
|
130
|
+
names.add(entry.name);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
} catch {
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const profiles = [];
|
|
138
|
+
for (const name of names) {
|
|
139
|
+
const profilePath = pathApi.join(root, name);
|
|
140
|
+
if (!(await findCookieDatabase(browser, profilePath, platform))) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
profiles.push({
|
|
144
|
+
browser,
|
|
145
|
+
name,
|
|
146
|
+
displayName: infoCache[name]?.name ?? name,
|
|
147
|
+
path: profilePath,
|
|
148
|
+
isDefault:
|
|
149
|
+
name === (localState.profile?.last_used ?? 'Default') ||
|
|
150
|
+
(names.size === 1 && name === 'Default'),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return profiles.sort(
|
|
154
|
+
(left, right) =>
|
|
155
|
+
Number(right.isDefault) - Number(left.isDefault) ||
|
|
156
|
+
left.name.localeCompare(right.name)
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function parseIni(text) {
|
|
161
|
+
const sections = [];
|
|
162
|
+
let current;
|
|
163
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
164
|
+
const line = rawLine.trim();
|
|
165
|
+
const sectionMatch = /^\[([^\]]+)]$/.exec(line);
|
|
166
|
+
if (sectionMatch) {
|
|
167
|
+
current = { section: sectionMatch[1] };
|
|
168
|
+
sections.push(current);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const separator = line.indexOf('=');
|
|
172
|
+
if (current && separator > 0 && !line.startsWith(';')) {
|
|
173
|
+
current[line.slice(0, separator)] = line.slice(separator + 1);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return sections;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function listFirefoxProfiles(root, platform) {
|
|
180
|
+
if (!(await pathExists(root))) {
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
const pathApi = platformPath(platform);
|
|
184
|
+
let sections = [];
|
|
185
|
+
try {
|
|
186
|
+
sections = parseIni(
|
|
187
|
+
await readFile(pathApi.join(root, 'profiles.ini'), 'utf8')
|
|
188
|
+
);
|
|
189
|
+
} catch {
|
|
190
|
+
const profilesRoot = pathApi.join(root, 'Profiles');
|
|
191
|
+
try {
|
|
192
|
+
const entries = await readdir(profilesRoot, { withFileTypes: true });
|
|
193
|
+
sections = entries
|
|
194
|
+
.filter((entry) => entry.isDirectory())
|
|
195
|
+
.map((entry) => ({
|
|
196
|
+
section: 'Profile',
|
|
197
|
+
Name: entry.name,
|
|
198
|
+
Path: entry.name,
|
|
199
|
+
ProfilesRoot: profilesRoot,
|
|
200
|
+
}));
|
|
201
|
+
} catch {
|
|
202
|
+
return [];
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const profiles = [];
|
|
207
|
+
for (const section of sections.filter(({ section }) =>
|
|
208
|
+
section.startsWith('Profile')
|
|
209
|
+
)) {
|
|
210
|
+
if (!section.Path) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const relativeRoot = section.ProfilesRoot ?? root;
|
|
214
|
+
const profilePath =
|
|
215
|
+
section.IsRelative === '0'
|
|
216
|
+
? section.Path
|
|
217
|
+
: pathApi.resolve(relativeRoot, section.Path);
|
|
218
|
+
if (!(await findCookieDatabase('firefox', profilePath, platform))) {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
const displayName = section.Name ?? pathApi.basename(profilePath);
|
|
222
|
+
profiles.push({
|
|
223
|
+
browser: 'firefox',
|
|
224
|
+
name: displayName,
|
|
225
|
+
displayName,
|
|
226
|
+
path: profilePath,
|
|
227
|
+
isDefault: section.Default === '1',
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
return profiles.sort(
|
|
231
|
+
(left, right) =>
|
|
232
|
+
Number(right.isDefault) - Number(left.isDefault) ||
|
|
233
|
+
left.name.localeCompare(right.name)
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Discover cookie-bearing profiles from installed browsers. */
|
|
238
|
+
export async function listBrowserProfiles({
|
|
239
|
+
browser,
|
|
240
|
+
platform = process.platform,
|
|
241
|
+
homeDir = os.homedir(),
|
|
242
|
+
environment = process.env,
|
|
243
|
+
} = {}) {
|
|
244
|
+
const browsers = browser
|
|
245
|
+
? [normalizeCookieBrowser(browser)]
|
|
246
|
+
: SUPPORTED_COOKIE_BROWSERS;
|
|
247
|
+
const profiles = [];
|
|
248
|
+
for (const candidate of browsers) {
|
|
249
|
+
const root = browserProfileRoot(candidate, {
|
|
250
|
+
platform,
|
|
251
|
+
homeDir,
|
|
252
|
+
environment,
|
|
253
|
+
});
|
|
254
|
+
profiles.push(
|
|
255
|
+
...(candidate === 'firefox'
|
|
256
|
+
? await listFirefoxProfiles(root, platform)
|
|
257
|
+
: await listChromiumProfiles(candidate, root, platform))
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
return profiles;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export async function resolveBrowserProfile(options) {
|
|
264
|
+
const browser = normalizeCookieBrowser(options.browser);
|
|
265
|
+
const profiles = await listBrowserProfiles({ ...options, browser });
|
|
266
|
+
const requested = options.profile;
|
|
267
|
+
const selected = requested
|
|
268
|
+
? profiles.find(
|
|
269
|
+
({ name, displayName, path: profilePath }) =>
|
|
270
|
+
requested === name ||
|
|
271
|
+
requested === displayName ||
|
|
272
|
+
requested === platformPath(options.platform).basename(profilePath)
|
|
273
|
+
)
|
|
274
|
+
: (profiles.find(({ isDefault }) => isDefault) ?? profiles[0]);
|
|
275
|
+
if (!selected) {
|
|
276
|
+
const detail = requested ? ` profile "${requested}"` : ' profile';
|
|
277
|
+
throw new Error(`Could not find a cookie database for ${browser}${detail}`);
|
|
278
|
+
}
|
|
279
|
+
return selected;
|
|
280
|
+
}
|
|
@@ -1,3 +1,47 @@
|
|
|
1
|
+
import { CHROME_ARGS } from '../core/constants.js';
|
|
2
|
+
|
|
3
|
+
function stringArray(value, name) {
|
|
4
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {
|
|
5
|
+
throw new TypeError(`${name} must be an array of strings`);
|
|
6
|
+
}
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Resolve Browser Commander defaults, compatibility args, and extra args. */
|
|
11
|
+
export function resolveChromeArgs({
|
|
12
|
+
args = [],
|
|
13
|
+
extraArgs = [],
|
|
14
|
+
ignoreDefaultArgs = [],
|
|
15
|
+
} = {}) {
|
|
16
|
+
stringArray(args, 'args');
|
|
17
|
+
stringArray(extraArgs, 'extraArgs');
|
|
18
|
+
if (
|
|
19
|
+
typeof ignoreDefaultArgs !== 'boolean' &&
|
|
20
|
+
!Array.isArray(ignoreDefaultArgs)
|
|
21
|
+
) {
|
|
22
|
+
throw new TypeError('ignoreDefaultArgs must be a boolean or string array');
|
|
23
|
+
}
|
|
24
|
+
if (Array.isArray(ignoreDefaultArgs)) {
|
|
25
|
+
stringArray(ignoreDefaultArgs, 'ignoreDefaultArgs');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const normalizedIgnoreDefaultArgs =
|
|
29
|
+
ignoreDefaultArgs === false ? [] : ignoreDefaultArgs;
|
|
30
|
+
const ignored = new Set(
|
|
31
|
+
normalizedIgnoreDefaultArgs === true
|
|
32
|
+
? CHROME_ARGS
|
|
33
|
+
: normalizedIgnoreDefaultArgs
|
|
34
|
+
);
|
|
35
|
+
return {
|
|
36
|
+
args: [
|
|
37
|
+
...CHROME_ARGS.filter((argument) => !ignored.has(argument)),
|
|
38
|
+
...args,
|
|
39
|
+
...extraArgs,
|
|
40
|
+
],
|
|
41
|
+
ignoreDefaultArgs: normalizedIgnoreDefaultArgs,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
1
45
|
function setBrowserSelectionOptions(options, { channel, executablePath }) {
|
|
2
46
|
if (channel !== undefined) {
|
|
3
47
|
options.channel = channel;
|
|
@@ -15,14 +59,21 @@ export function buildPlaywrightLaunchOptions({
|
|
|
15
59
|
colorScheme,
|
|
16
60
|
channel,
|
|
17
61
|
executablePath,
|
|
62
|
+
ignoreDefaultArgs = [],
|
|
18
63
|
}) {
|
|
64
|
+
const normalizedIgnoreDefaultArgs =
|
|
65
|
+
ignoreDefaultArgs === false ? [] : ignoreDefaultArgs;
|
|
66
|
+
const engineIgnoreDefaultArgs =
|
|
67
|
+
normalizedIgnoreDefaultArgs === true
|
|
68
|
+
? true
|
|
69
|
+
: [...new Set(['--enable-automation', ...normalizedIgnoreDefaultArgs])];
|
|
19
70
|
const options = {
|
|
20
71
|
headless,
|
|
21
72
|
slowMo,
|
|
22
73
|
chromiumSandbox: true,
|
|
23
74
|
viewport: null,
|
|
24
75
|
args: chromeArgs,
|
|
25
|
-
ignoreDefaultArgs:
|
|
76
|
+
ignoreDefaultArgs: engineIgnoreDefaultArgs,
|
|
26
77
|
};
|
|
27
78
|
|
|
28
79
|
if (colorScheme !== undefined) {
|
|
@@ -38,14 +89,21 @@ export function buildPuppeteerLaunchOptions({
|
|
|
38
89
|
userDataDir,
|
|
39
90
|
channel,
|
|
40
91
|
executablePath,
|
|
92
|
+
ignoreDefaultArgs = [],
|
|
41
93
|
}) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
94
|
+
const normalizedIgnoreDefaultArgs =
|
|
95
|
+
ignoreDefaultArgs === false ? [] : ignoreDefaultArgs;
|
|
96
|
+
const options = {
|
|
97
|
+
headless,
|
|
98
|
+
defaultViewport: null,
|
|
99
|
+
args: ['--start-maximized', ...chromeArgs],
|
|
100
|
+
userDataDir,
|
|
101
|
+
};
|
|
102
|
+
if (
|
|
103
|
+
normalizedIgnoreDefaultArgs === true ||
|
|
104
|
+
normalizedIgnoreDefaultArgs.length > 0
|
|
105
|
+
) {
|
|
106
|
+
options.ignoreDefaultArgs = normalizedIgnoreDefaultArgs;
|
|
107
|
+
}
|
|
108
|
+
return setBrowserSelectionOptions(options, { channel, executablePath });
|
|
51
109
|
}
|
package/src/browser/launcher.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import os from 'os';
|
|
3
|
-
import { CHROME_ARGS } from '../core/constants.js';
|
|
4
3
|
import { disableTranslateInPreferences } from '../core/preferences.js';
|
|
5
4
|
import { emulateMedia } from './media.js';
|
|
6
5
|
import {
|
|
7
6
|
buildPlaywrightLaunchOptions,
|
|
8
7
|
buildPuppeteerLaunchOptions,
|
|
8
|
+
resolveChromeArgs,
|
|
9
9
|
} from './launch-options.js';
|
|
10
10
|
import {
|
|
11
11
|
loadStorageState,
|
|
@@ -22,6 +22,8 @@ import {
|
|
|
22
22
|
* @param {number} options.slowMo - Slow down operations by ms (default: 150 for Playwright, 0 for Puppeteer)
|
|
23
23
|
* @param {boolean} options.verbose - Enable verbose logging (default: false)
|
|
24
24
|
* @param {string[]} options.args - Custom Chrome arguments to append to the default CHROME_ARGS
|
|
25
|
+
* @param {string[]} [options.extraArgs] - Additional Chrome arguments appended after legacy args
|
|
26
|
+
* @param {boolean|string[]} [options.ignoreDefaultArgs] - Browser Commander defaults to omit, or true for all
|
|
25
27
|
* @param {string|null} [options.colorScheme] - Emulate color scheme: 'light', 'dark', 'no-preference', or null to reset
|
|
26
28
|
* @param {string} [options.channel] - Installed browser channel, such as 'chrome', 'chrome-beta', or 'msedge'
|
|
27
29
|
* @param {string} [options.executablePath] - Explicit path to a Chrome or Chromium executable
|
|
@@ -36,14 +38,20 @@ export async function launchBrowser(options = {}) {
|
|
|
36
38
|
slowMo = engine === 'playwright' ? 150 : 0,
|
|
37
39
|
verbose = false,
|
|
38
40
|
args = [],
|
|
41
|
+
extraArgs = [],
|
|
42
|
+
ignoreDefaultArgs = [],
|
|
39
43
|
colorScheme,
|
|
40
44
|
channel,
|
|
41
45
|
executablePath,
|
|
42
46
|
storageState,
|
|
43
47
|
} = options;
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
const resolvedChromeArgs = resolveChromeArgs({
|
|
50
|
+
args,
|
|
51
|
+
extraArgs,
|
|
52
|
+
ignoreDefaultArgs,
|
|
53
|
+
});
|
|
54
|
+
const chromeArgs = resolvedChromeArgs.args;
|
|
47
55
|
|
|
48
56
|
if (!['playwright', 'puppeteer'].includes(engine)) {
|
|
49
57
|
throw new Error(
|
|
@@ -77,6 +85,7 @@ export async function launchBrowser(options = {}) {
|
|
|
77
85
|
colorScheme,
|
|
78
86
|
channel,
|
|
79
87
|
executablePath,
|
|
88
|
+
ignoreDefaultArgs: resolvedChromeArgs.ignoreDefaultArgs,
|
|
80
89
|
});
|
|
81
90
|
browser = await chromium.launchPersistentContext(
|
|
82
91
|
userDataDir,
|
|
@@ -96,6 +105,7 @@ export async function launchBrowser(options = {}) {
|
|
|
96
105
|
userDataDir,
|
|
97
106
|
channel,
|
|
98
107
|
executablePath,
|
|
108
|
+
ignoreDefaultArgs: resolvedChromeArgs.ignoreDefaultArgs,
|
|
99
109
|
})
|
|
100
110
|
);
|
|
101
111
|
const pages = await browser.pages();
|
|
@@ -5,6 +5,7 @@ import os from 'node:os';
|
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
|
|
7
7
|
import { connectBrowser } from './connector.js';
|
|
8
|
+
import { resolveChromeArgs } from './launch-options.js';
|
|
8
9
|
|
|
9
10
|
const MANAGED_ARGUMENTS = [
|
|
10
11
|
'--remote-debugging-address',
|
|
@@ -240,6 +241,8 @@ export function buildRealBrowserArgs({
|
|
|
240
241
|
remoteDebuggingPort = 0,
|
|
241
242
|
headless = false,
|
|
242
243
|
args = [],
|
|
244
|
+
extraArgs = [],
|
|
245
|
+
ignoreDefaultArgs = [],
|
|
243
246
|
}) {
|
|
244
247
|
if (
|
|
245
248
|
!Number.isInteger(remoteDebuggingPort) ||
|
|
@@ -250,7 +253,8 @@ export function buildRealBrowserArgs({
|
|
|
250
253
|
'remoteDebuggingPort must be an integer from 0 to 65535'
|
|
251
254
|
);
|
|
252
255
|
}
|
|
253
|
-
const
|
|
256
|
+
const customArgs = [...args, ...extraArgs];
|
|
257
|
+
const conflictingArgument = customArgs.find((argument) =>
|
|
254
258
|
MANAGED_ARGUMENTS.some(
|
|
255
259
|
(managed) => argument === managed || argument.startsWith(`${managed}=`)
|
|
256
260
|
)
|
|
@@ -261,14 +265,15 @@ export function buildRealBrowserArgs({
|
|
|
261
265
|
);
|
|
262
266
|
}
|
|
263
267
|
|
|
268
|
+
const defaultArgs = resolveChromeArgs({ ignoreDefaultArgs }).args;
|
|
269
|
+
|
|
264
270
|
return [
|
|
265
271
|
'--remote-debugging-address=127.0.0.1',
|
|
266
272
|
`--remote-debugging-port=${remoteDebuggingPort}`,
|
|
267
273
|
`--user-data-dir=${userDataDir}`,
|
|
268
|
-
|
|
269
|
-
'--no-default-browser-check',
|
|
274
|
+
...defaultArgs,
|
|
270
275
|
...(headless ? ['--headless=new'] : []),
|
|
271
|
-
...
|
|
276
|
+
...customArgs,
|
|
272
277
|
];
|
|
273
278
|
}
|
|
274
279
|
|
|
@@ -353,6 +358,8 @@ export async function waitForCdpEndpoint({
|
|
|
353
358
|
* @param {number} [options.remoteDebuggingPort=0] - Loopback CDP port; zero lets Chrome choose
|
|
354
359
|
* @param {boolean} [options.headless=false] - Run the installed browser headlessly
|
|
355
360
|
* @param {string[]} [options.args] - Additional browser arguments
|
|
361
|
+
* @param {string[]} [options.extraArgs] - Additional browser arguments appended after legacy args
|
|
362
|
+
* @param {boolean|string[]} [options.ignoreDefaultArgs] - Browser Commander defaults to omit, or true for all
|
|
356
363
|
* @param {number} [options.startupTimeout=30000] - CDP readiness timeout in milliseconds
|
|
357
364
|
* @param {Object[]} [options.seedCookies] - Cookies to seed after connecting
|
|
358
365
|
* @param {boolean} [options.verbose=false] - Show browser and connection logs
|
|
@@ -384,6 +391,8 @@ export async function launchAndConnectRealBrowserWithDependencies(
|
|
|
384
391
|
remoteDebuggingPort = 0,
|
|
385
392
|
headless = false,
|
|
386
393
|
args = [],
|
|
394
|
+
extraArgs = [],
|
|
395
|
+
ignoreDefaultArgs = [],
|
|
387
396
|
startupTimeout = 30_000,
|
|
388
397
|
verbose = false,
|
|
389
398
|
cdpEndpoint,
|
|
@@ -409,6 +418,8 @@ export async function launchAndConnectRealBrowserWithDependencies(
|
|
|
409
418
|
remoteDebuggingPort,
|
|
410
419
|
headless,
|
|
411
420
|
args,
|
|
421
|
+
extraArgs,
|
|
422
|
+
ignoreDefaultArgs,
|
|
412
423
|
});
|
|
413
424
|
const spawnBrowser = dependencies.spawnBrowser ?? spawn;
|
|
414
425
|
const browserProcess = spawnBrowser(resolvedExecutablePath, browserArgs, {
|
package/src/core/constants.js
CHANGED
package/src/exports.js
CHANGED
|
@@ -43,6 +43,10 @@ export {
|
|
|
43
43
|
|
|
44
44
|
// Re-export browser management
|
|
45
45
|
export { connectBrowser } from './browser/connector.js';
|
|
46
|
+
export {
|
|
47
|
+
listBrowserProfiles,
|
|
48
|
+
readBrowserCookies,
|
|
49
|
+
} from './browser/browser-cookies.js';
|
|
46
50
|
export {
|
|
47
51
|
launchAndConnectRealBrowser,
|
|
48
52
|
launchRealBrowser,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { appendFile } from 'node:fs/promises';
|
|
2
|
+
|
|
3
|
+
import { getCachedCredential } from '../../src/browser/browser-cookie-cache.js';
|
|
4
|
+
|
|
5
|
+
const [, , cacheDir, credentialReads] = process.argv;
|
|
6
|
+
|
|
7
|
+
await getCachedCredential({
|
|
8
|
+
cache: { enabled: true, dir: cacheDir, ttlSeconds: 60 },
|
|
9
|
+
identity: 'chrome:linux:safe-storage',
|
|
10
|
+
refresh: false,
|
|
11
|
+
metadata: {
|
|
12
|
+
browser: 'chrome',
|
|
13
|
+
platform: 'linux',
|
|
14
|
+
source: 'safe-storage',
|
|
15
|
+
},
|
|
16
|
+
create: async () => {
|
|
17
|
+
await appendFile(credentialReads, `${process.pid}\n`, 'utf8');
|
|
18
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
19
|
+
return Buffer.alloc(16, 7);
|
|
20
|
+
},
|
|
21
|
+
});
|