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,128 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createDecipheriv,
|
|
3
|
+
createHash,
|
|
4
|
+
pbkdf2Sync,
|
|
5
|
+
timingSafeEqual,
|
|
6
|
+
} from 'node:crypto';
|
|
7
|
+
import { TextDecoder } from 'node:util';
|
|
8
|
+
|
|
9
|
+
const CHROMIUM_CBC_IV = Buffer.alloc(16, 0x20);
|
|
10
|
+
const DOMAIN_HASH_BYTES = 32;
|
|
11
|
+
|
|
12
|
+
export function deriveChromiumCookieKey(password, platform) {
|
|
13
|
+
if (platform !== 'darwin' && platform !== 'linux') {
|
|
14
|
+
throw new Error(`CBC cookie keys are not used on ${platform}`);
|
|
15
|
+
}
|
|
16
|
+
return pbkdf2Sync(
|
|
17
|
+
password,
|
|
18
|
+
'saltysalt',
|
|
19
|
+
platform === 'darwin' ? 1003 : 1,
|
|
20
|
+
16,
|
|
21
|
+
'sha1'
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function removeDomainHash(plaintext, host, databaseVersion) {
|
|
26
|
+
if (databaseVersion < 24) {
|
|
27
|
+
return plaintext;
|
|
28
|
+
}
|
|
29
|
+
if (plaintext.length < DOMAIN_HASH_BYTES) {
|
|
30
|
+
throw new Error('decrypted cookie is missing its domain hash');
|
|
31
|
+
}
|
|
32
|
+
const expectedHash = createHash('sha256').update(host).digest();
|
|
33
|
+
const actualHash = plaintext.subarray(0, DOMAIN_HASH_BYTES);
|
|
34
|
+
if (!timingSafeEqual(actualHash, expectedHash)) {
|
|
35
|
+
throw new Error('decrypted cookie domain hash does not match its host');
|
|
36
|
+
}
|
|
37
|
+
return plaintext.subarray(DOMAIN_HASH_BYTES);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function decodeCookieValue(plaintext) {
|
|
41
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(plaintext);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Validate and decode plaintext returned by a Chromium platform decryptor. */
|
|
45
|
+
export function decodeChromiumCookiePlaintext({
|
|
46
|
+
plaintext,
|
|
47
|
+
host,
|
|
48
|
+
databaseVersion = 0,
|
|
49
|
+
}) {
|
|
50
|
+
return decodeCookieValue(
|
|
51
|
+
removeDomainHash(Buffer.from(plaintext), host, databaseVersion)
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function decryptCbcCookie(encryptedValue, key) {
|
|
56
|
+
const decipher = createDecipheriv('aes-128-cbc', key, CHROMIUM_CBC_IV);
|
|
57
|
+
return Buffer.concat([
|
|
58
|
+
decipher.update(encryptedValue.subarray(3)),
|
|
59
|
+
decipher.final(),
|
|
60
|
+
]);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function decryptGcmCookie(encryptedValue, key) {
|
|
64
|
+
const payload = encryptedValue.subarray(3);
|
|
65
|
+
if (payload.length < 12 + 16) {
|
|
66
|
+
throw new Error('encrypted AES-GCM cookie is truncated');
|
|
67
|
+
}
|
|
68
|
+
const nonce = payload.subarray(0, 12);
|
|
69
|
+
const authenticationTag = payload.subarray(-16);
|
|
70
|
+
const ciphertext = payload.subarray(12, -16);
|
|
71
|
+
const decipher = createDecipheriv('aes-256-gcm', key, nonce);
|
|
72
|
+
decipher.setAuthTag(authenticationTag);
|
|
73
|
+
return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Decrypt a Chromium cookie encrypted_value using an already recovered key. */
|
|
77
|
+
export function decryptChromiumCookie({
|
|
78
|
+
encryptedValue,
|
|
79
|
+
host,
|
|
80
|
+
databaseVersion = 0,
|
|
81
|
+
platform = process.platform,
|
|
82
|
+
key,
|
|
83
|
+
}) {
|
|
84
|
+
const encrypted = Buffer.from(encryptedValue);
|
|
85
|
+
const prefix = encrypted.subarray(0, 3).toString('ascii');
|
|
86
|
+
if (prefix === 'v20') {
|
|
87
|
+
throw new Error(
|
|
88
|
+
'Windows app-bound v20 cookies cannot be decrypted outside the browser; use a browser-supported export or a previously saved storage state'
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
if (prefix !== 'v10' && prefix !== 'v11') {
|
|
92
|
+
throw new Error(
|
|
93
|
+
'cookie does not use a supported Chromium encryption prefix'
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
if (!Buffer.isBuffer(key)) {
|
|
97
|
+
throw new TypeError('a recovered cookie encryption key is required');
|
|
98
|
+
}
|
|
99
|
+
const plaintext =
|
|
100
|
+
platform === 'win32'
|
|
101
|
+
? decryptGcmCookie(encrypted, key)
|
|
102
|
+
: decryptCbcCookie(encrypted, key);
|
|
103
|
+
return decodeChromiumCookiePlaintext({
|
|
104
|
+
plaintext,
|
|
105
|
+
host,
|
|
106
|
+
databaseVersion,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function chromiumSameSite(value) {
|
|
111
|
+
if (Number(value) === 2) {
|
|
112
|
+
return 'Strict';
|
|
113
|
+
}
|
|
114
|
+
if (Number(value) === 1) {
|
|
115
|
+
return 'Lax';
|
|
116
|
+
}
|
|
117
|
+
return 'None';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function firefoxSameSite(value) {
|
|
121
|
+
if (Number(value) === 2) {
|
|
122
|
+
return 'Strict';
|
|
123
|
+
}
|
|
124
|
+
if (Number(value) === 1) {
|
|
125
|
+
return 'Lax';
|
|
126
|
+
}
|
|
127
|
+
return 'None';
|
|
128
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import BetterSqlite3 from 'better-sqlite3';
|
|
2
|
+
|
|
3
|
+
let builtInSqlitePromise;
|
|
4
|
+
|
|
5
|
+
function loadBuiltInSqlite() {
|
|
6
|
+
builtInSqlitePromise ??= import('node:sqlite')
|
|
7
|
+
.then(({ DatabaseSync }) => DatabaseSync)
|
|
8
|
+
.catch(() => null);
|
|
9
|
+
return builtInSqlitePromise;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Open SQLite without requiring a native addon on Node versions that provide it. */
|
|
13
|
+
export async function openSqliteDatabase(
|
|
14
|
+
filename,
|
|
15
|
+
{ fileMustExist = false, readOnly = false } = {}
|
|
16
|
+
) {
|
|
17
|
+
const DatabaseSync = await loadBuiltInSqlite();
|
|
18
|
+
if (DatabaseSync) {
|
|
19
|
+
return new DatabaseSync(filename, { readOnly });
|
|
20
|
+
}
|
|
21
|
+
return new BetterSqlite3(filename, {
|
|
22
|
+
fileMustExist,
|
|
23
|
+
readonly: readOnly,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Configure a statement to preserve Chromium's 64-bit timestamp values. */
|
|
28
|
+
export function preserveIntegerPrecision(statement) {
|
|
29
|
+
if (typeof statement.setReadBigInts === 'function') {
|
|
30
|
+
statement.setReadBigInts(true);
|
|
31
|
+
} else {
|
|
32
|
+
statement.safeIntegers();
|
|
33
|
+
}
|
|
34
|
+
return statement;
|
|
35
|
+
}
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
clearBrowserCookieMemoryCache,
|
|
6
|
+
getCachedCredential,
|
|
7
|
+
normalizeCookieCache,
|
|
8
|
+
readCookieResultCache,
|
|
9
|
+
writeCookieResultCache,
|
|
10
|
+
} from './browser-cookie-cache.js';
|
|
11
|
+
import {
|
|
12
|
+
chromiumSameSite,
|
|
13
|
+
decodeChromiumCookiePlaintext,
|
|
14
|
+
decryptChromiumCookie,
|
|
15
|
+
deriveChromiumCookieKey,
|
|
16
|
+
firefoxSameSite,
|
|
17
|
+
} from './browser-cookie-crypto.js';
|
|
18
|
+
import {
|
|
19
|
+
decryptWindowsDpapi,
|
|
20
|
+
readSafeStoragePassword,
|
|
21
|
+
readWindowsEncryptionKey,
|
|
22
|
+
} from './browser-cookie-credentials.js';
|
|
23
|
+
import {
|
|
24
|
+
openSqliteDatabase,
|
|
25
|
+
preserveIntegerPrecision,
|
|
26
|
+
} from './browser-cookie-database.js';
|
|
27
|
+
import {
|
|
28
|
+
findCookieDatabase,
|
|
29
|
+
listBrowserProfiles,
|
|
30
|
+
normalizeCookieBrowser,
|
|
31
|
+
resolveBrowserProfile,
|
|
32
|
+
} from './browser-profiles.js';
|
|
33
|
+
|
|
34
|
+
const CHROME_EPOCH_OFFSET_SECONDS = 11_644_473_600n;
|
|
35
|
+
const MICROSECONDS_PER_SECOND = 1_000_000n;
|
|
36
|
+
|
|
37
|
+
function toNumber(value) {
|
|
38
|
+
return typeof value === 'bigint' ? Number(value) : Number(value ?? 0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function chromiumExpires(value) {
|
|
42
|
+
const microseconds = BigInt(value ?? 0);
|
|
43
|
+
if (microseconds === 0n) {
|
|
44
|
+
return -1;
|
|
45
|
+
}
|
|
46
|
+
return Number(
|
|
47
|
+
microseconds / MICROSECONDS_PER_SECOND - CHROME_EPOCH_OFFSET_SECONDS
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function firefoxExpires(value) {
|
|
52
|
+
const expires = toNumber(value);
|
|
53
|
+
return expires > 0 ? expires : -1;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function queryRows(database, query, domainFilter) {
|
|
57
|
+
const statement = preserveIntegerPrecision(database.prepare(query));
|
|
58
|
+
return domainFilter ? statement.all(`%${domainFilter}%`) : statement.all();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function readDatabaseVersion(database) {
|
|
62
|
+
try {
|
|
63
|
+
const row = database
|
|
64
|
+
.prepare("SELECT value FROM meta WHERE key = 'version'")
|
|
65
|
+
.get();
|
|
66
|
+
return Number(row?.value ?? 0);
|
|
67
|
+
} catch {
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function readFirefoxRows(database, domainFilter) {
|
|
73
|
+
return queryRows(
|
|
74
|
+
database,
|
|
75
|
+
`SELECT name, value, host, path, expiry, isSecure, isHttpOnly, sameSite
|
|
76
|
+
FROM moz_cookies
|
|
77
|
+
${domainFilter ? 'WHERE host LIKE ?' : ''}
|
|
78
|
+
ORDER BY host, name, path`,
|
|
79
|
+
domainFilter
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function readChromiumRows(database, domainFilter) {
|
|
84
|
+
return queryRows(
|
|
85
|
+
database,
|
|
86
|
+
`SELECT host_key, name, value, encrypted_value, path, expires_utc,
|
|
87
|
+
is_secure, is_httponly, samesite
|
|
88
|
+
FROM cookies
|
|
89
|
+
${domainFilter ? 'WHERE host_key LIKE ?' : ''}
|
|
90
|
+
ORDER BY host_key, name, path`,
|
|
91
|
+
domainFilter
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function mapFirefoxRows(rows) {
|
|
96
|
+
return rows.map((row) => ({
|
|
97
|
+
name: row.name,
|
|
98
|
+
value: row.value,
|
|
99
|
+
domain: row.host,
|
|
100
|
+
path: row.path || '/',
|
|
101
|
+
expires: firefoxExpires(row.expiry),
|
|
102
|
+
httpOnly: Boolean(row.isHttpOnly),
|
|
103
|
+
secure: Boolean(row.isSecure),
|
|
104
|
+
sameSite: firefoxSameSite(row.sameSite),
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function encryptionPrefix(encryptedValue) {
|
|
109
|
+
return Buffer.from(encryptedValue).subarray(0, 3).toString('ascii');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function chromiumKeyForPrefix(context, prefix) {
|
|
113
|
+
if (context.platform === 'linux' && prefix === 'v10') {
|
|
114
|
+
return deriveChromiumCookieKey('peanuts', 'linux');
|
|
115
|
+
}
|
|
116
|
+
if (context.platform === 'linux' || context.platform === 'darwin') {
|
|
117
|
+
const identity = `${context.browser}:${context.platform}:safe-storage`;
|
|
118
|
+
if (!context.credentialKeys.has(identity)) {
|
|
119
|
+
context.credentialKeys.set(
|
|
120
|
+
identity,
|
|
121
|
+
getCachedCredential({
|
|
122
|
+
cache: context.cache,
|
|
123
|
+
identity,
|
|
124
|
+
refresh: context.refresh,
|
|
125
|
+
now: context.now,
|
|
126
|
+
metadata: {
|
|
127
|
+
browser: context.browser,
|
|
128
|
+
platform: context.platform,
|
|
129
|
+
source: 'safe-storage',
|
|
130
|
+
},
|
|
131
|
+
create: async () =>
|
|
132
|
+
deriveChromiumCookieKey(
|
|
133
|
+
await context.readSafeStoragePassword({
|
|
134
|
+
browser: context.browser,
|
|
135
|
+
platform: context.platform,
|
|
136
|
+
environment: context.environment,
|
|
137
|
+
}),
|
|
138
|
+
context.platform
|
|
139
|
+
),
|
|
140
|
+
})
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
return context.credentialKeys.get(identity);
|
|
144
|
+
}
|
|
145
|
+
if (context.platform === 'win32') {
|
|
146
|
+
const identity = `${context.browser}:win32:legacy-aes-key`;
|
|
147
|
+
if (!context.credentialKeys.has(identity)) {
|
|
148
|
+
context.credentialKeys.set(
|
|
149
|
+
identity,
|
|
150
|
+
getCachedCredential({
|
|
151
|
+
cache: context.cache,
|
|
152
|
+
identity,
|
|
153
|
+
refresh: context.refresh,
|
|
154
|
+
now: context.now,
|
|
155
|
+
metadata: {
|
|
156
|
+
browser: context.browser,
|
|
157
|
+
platform: context.platform,
|
|
158
|
+
source: 'dpapi',
|
|
159
|
+
},
|
|
160
|
+
create: () =>
|
|
161
|
+
context.readWindowsEncryptionKey({
|
|
162
|
+
localStatePath: path.join(
|
|
163
|
+
path.dirname(context.profile.path),
|
|
164
|
+
'Local State'
|
|
165
|
+
),
|
|
166
|
+
environment: context.environment,
|
|
167
|
+
decryptDpapi: context.decryptWindowsDpapi,
|
|
168
|
+
}),
|
|
169
|
+
})
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
return context.credentialKeys.get(identity);
|
|
173
|
+
}
|
|
174
|
+
throw new Error(
|
|
175
|
+
`Chromium cookie decryption is unsupported on ${context.platform}`
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function decryptChromiumRow(row, databaseVersion, context) {
|
|
180
|
+
if (row.value) {
|
|
181
|
+
return row.value;
|
|
182
|
+
}
|
|
183
|
+
const encryptedValue = Buffer.from(row.encrypted_value);
|
|
184
|
+
if (encryptedValue.length === 0) {
|
|
185
|
+
return '';
|
|
186
|
+
}
|
|
187
|
+
const prefix = encryptionPrefix(encryptedValue);
|
|
188
|
+
if (context.platform === 'win32' && prefix !== 'v10' && prefix !== 'v11') {
|
|
189
|
+
if (prefix === 'v20') {
|
|
190
|
+
return decryptChromiumCookie({
|
|
191
|
+
encryptedValue,
|
|
192
|
+
host: row.host_key,
|
|
193
|
+
databaseVersion,
|
|
194
|
+
platform: context.platform,
|
|
195
|
+
key: Buffer.alloc(32),
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
const plaintext = await context.decryptWindowsDpapi(encryptedValue, {
|
|
199
|
+
environment: context.environment,
|
|
200
|
+
});
|
|
201
|
+
return decodeChromiumCookiePlaintext({
|
|
202
|
+
plaintext,
|
|
203
|
+
host: row.host_key,
|
|
204
|
+
databaseVersion,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
const key = await chromiumKeyForPrefix(context, prefix);
|
|
208
|
+
return decryptChromiumCookie({
|
|
209
|
+
encryptedValue,
|
|
210
|
+
host: row.host_key,
|
|
211
|
+
databaseVersion,
|
|
212
|
+
platform: context.platform,
|
|
213
|
+
key,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async function mapChromiumRows(rows, databaseVersion, context) {
|
|
218
|
+
const cookies = [];
|
|
219
|
+
for (const row of rows) {
|
|
220
|
+
try {
|
|
221
|
+
cookies.push({
|
|
222
|
+
name: row.name,
|
|
223
|
+
value: await decryptChromiumRow(row, databaseVersion, context),
|
|
224
|
+
domain: row.host_key,
|
|
225
|
+
path: row.path || '/',
|
|
226
|
+
expires: chromiumExpires(row.expires_utc),
|
|
227
|
+
httpOnly: Boolean(row.is_httponly),
|
|
228
|
+
secure: Boolean(row.is_secure),
|
|
229
|
+
sameSite: chromiumSameSite(row.samesite),
|
|
230
|
+
});
|
|
231
|
+
} catch (error) {
|
|
232
|
+
if (!context.ignoreDecryptionErrors) {
|
|
233
|
+
throw new Error(
|
|
234
|
+
`Could not decrypt cookie ${row.name} for ${row.host_key}: ${error.message}`
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return cookies;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function openCookieDatabase(cookiePath) {
|
|
243
|
+
try {
|
|
244
|
+
return await openSqliteDatabase(cookiePath, {
|
|
245
|
+
fileMustExist: true,
|
|
246
|
+
readOnly: true,
|
|
247
|
+
});
|
|
248
|
+
} catch (error) {
|
|
249
|
+
throw new Error(`Could not open browser cookie database: ${error.message}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Read cookies from an installed browser profile in Playwright/Puppeteer shape.
|
|
255
|
+
* Use dependency injection only for deterministic platform and credential tests.
|
|
256
|
+
*/
|
|
257
|
+
export async function readBrowserCookiesWithDependencies(
|
|
258
|
+
options,
|
|
259
|
+
dependencies = {}
|
|
260
|
+
) {
|
|
261
|
+
if (!options || typeof options !== 'object') {
|
|
262
|
+
throw new TypeError('readBrowserCookies requires an options object');
|
|
263
|
+
}
|
|
264
|
+
const browser = normalizeCookieBrowser(options.browser);
|
|
265
|
+
const platform = dependencies.platform ?? process.platform;
|
|
266
|
+
const homeDir = dependencies.homeDir ?? os.homedir();
|
|
267
|
+
const environment = dependencies.environment ?? process.env;
|
|
268
|
+
const profile = await resolveBrowserProfile({
|
|
269
|
+
browser,
|
|
270
|
+
profile: options.profile,
|
|
271
|
+
platform,
|
|
272
|
+
homeDir,
|
|
273
|
+
environment,
|
|
274
|
+
});
|
|
275
|
+
const cookiePath = await findCookieDatabase(browser, profile.path, platform);
|
|
276
|
+
if (!cookiePath) {
|
|
277
|
+
throw new Error(`No cookie database exists in ${profile.path}`);
|
|
278
|
+
}
|
|
279
|
+
const cache = normalizeCookieCache(
|
|
280
|
+
options.cache,
|
|
281
|
+
homeDir,
|
|
282
|
+
options.ttlMinutes
|
|
283
|
+
);
|
|
284
|
+
const identity = JSON.stringify({
|
|
285
|
+
browser,
|
|
286
|
+
profile: profile.path,
|
|
287
|
+
domainFilter: options.domainFilter ?? null,
|
|
288
|
+
ignoreDecryptionErrors: options.ignoreDecryptionErrors === true,
|
|
289
|
+
});
|
|
290
|
+
const now = dependencies.now ?? Date.now;
|
|
291
|
+
const cachedCookies = await readCookieResultCache({
|
|
292
|
+
cache,
|
|
293
|
+
identity,
|
|
294
|
+
refresh: options.refresh === true,
|
|
295
|
+
now,
|
|
296
|
+
});
|
|
297
|
+
if (cachedCookies) {
|
|
298
|
+
return cachedCookies;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const database = await openCookieDatabase(cookiePath);
|
|
302
|
+
let cookies;
|
|
303
|
+
try {
|
|
304
|
+
if (browser === 'firefox') {
|
|
305
|
+
cookies = mapFirefoxRows(readFirefoxRows(database, options.domainFilter));
|
|
306
|
+
} else {
|
|
307
|
+
const databaseVersion = readDatabaseVersion(database);
|
|
308
|
+
const rows = readChromiumRows(database, options.domainFilter);
|
|
309
|
+
cookies = await mapChromiumRows(rows, databaseVersion, {
|
|
310
|
+
browser,
|
|
311
|
+
cache,
|
|
312
|
+
credentialKeys: new Map(),
|
|
313
|
+
decryptWindowsDpapi:
|
|
314
|
+
dependencies.decryptWindowsDpapi ?? decryptWindowsDpapi,
|
|
315
|
+
environment,
|
|
316
|
+
ignoreDecryptionErrors: options.ignoreDecryptionErrors === true,
|
|
317
|
+
now,
|
|
318
|
+
platform,
|
|
319
|
+
profile,
|
|
320
|
+
readSafeStoragePassword:
|
|
321
|
+
dependencies.readSafeStoragePassword ?? readSafeStoragePassword,
|
|
322
|
+
readWindowsEncryptionKey:
|
|
323
|
+
dependencies.readWindowsEncryptionKey ?? readWindowsEncryptionKey,
|
|
324
|
+
refresh: options.refresh === true,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
} finally {
|
|
328
|
+
database.close();
|
|
329
|
+
}
|
|
330
|
+
await writeCookieResultCache({ cache, identity, cookies, now });
|
|
331
|
+
return cookies;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** Read cookies from an installed Chrome, Edge, Brave, Chromium, or Firefox. */
|
|
335
|
+
export function readBrowserCookies(options) {
|
|
336
|
+
return readBrowserCookiesWithDependencies(options);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export {
|
|
340
|
+
clearBrowserCookieMemoryCache,
|
|
341
|
+
decryptChromiumCookie,
|
|
342
|
+
listBrowserProfiles,
|
|
343
|
+
};
|