opensoma 0.8.0 → 0.9.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.
@@ -1,220 +0,0 @@
1
- import { Database } from 'bun:sqlite'
2
- import { afterEach, describe, expect, it } from 'bun:test'
3
- import { execSync } from 'node:child_process'
4
- import { createCipheriv, pbkdf2Sync } from 'node:crypto'
5
- import { mkdirSync, rmSync, writeFileSync } from 'node:fs'
6
- import { mkdtemp } from 'node:fs/promises'
7
- import { tmpdir } from 'node:os'
8
- import { dirname, join } from 'node:path'
9
-
10
- import { BROWSERS, TokenExtractor } from './token-extractor'
11
-
12
- const CHROMIUM_IV = Buffer.alloc(16, 0x20)
13
- const CHROMIUM_SALT = 'saltysalt'
14
-
15
- let createdDirs: string[] = []
16
-
17
- afterEach(() => {
18
- for (const dir of createdDirs) {
19
- rmSync(dir, { recursive: true, force: true })
20
- }
21
- createdDirs = []
22
- })
23
-
24
- describe('TokenExtractor', () => {
25
- it('finds cookie databases for every supported browser on macOS', async () => {
26
- const home = await makeTempDir()
27
-
28
- for (const browser of BROWSERS) {
29
- createCookieFile(join(home, 'Library', 'Application Support', browser.macPath, 'Default', 'Cookies'))
30
- }
31
-
32
- const extractor = new TokenExtractor('darwin', home)
33
- const paths = extractor.findCookieDatabases()
34
-
35
- expect(paths).toHaveLength(BROWSERS.length)
36
- for (const browser of BROWSERS) {
37
- expect(paths).toContainEqual(join(home, 'Library', 'Application Support', browser.macPath, 'Default', 'Cookies'))
38
- }
39
- })
40
-
41
- it('finds cookie databases for every supported browser on Linux', async () => {
42
- const home = await makeTempDir()
43
-
44
- for (const browser of BROWSERS) {
45
- createCookieFile(join(home, '.config', browser.linuxPath, 'Default', 'Cookies'))
46
- }
47
-
48
- const extractor = new TokenExtractor('linux', home)
49
- const paths = extractor.findCookieDatabases()
50
-
51
- expect(paths).toHaveLength(BROWSERS.length)
52
- })
53
-
54
- it('returns null when no cookie databases exist', async () => {
55
- const extractor = new TokenExtractor('linux', await makeTempDir())
56
- expect(await extractor.extract()).toBeNull()
57
- })
58
-
59
- it('extracts a plaintext cookie value', async () => {
60
- const home = await makeTempDir()
61
- const dbPath = join(home, '.config', 'google-chrome', 'Default', 'Cookies')
62
- createCookieDbWithPlaintext(dbPath, 'my-session-id')
63
-
64
- const extractor = new TokenExtractor('linux', home)
65
- expect(await extractor.extract()).toEqual({ sessionCookie: 'my-session-id' })
66
- })
67
-
68
- it('finds cookie databases across numbered browser profiles', async () => {
69
- const home = await makeTempDir()
70
- createCookieFile(join(home, '.config', 'google-chrome', 'Default', 'Cookies'))
71
- createCookieFile(join(home, '.config', 'google-chrome', 'Profile 1', 'Cookies'))
72
- createCookieFile(join(home, '.config', 'google-chrome', 'Profile 2', 'Cookies'))
73
-
74
- const extractor = new TokenExtractor('linux', home)
75
-
76
- expect(extractor.findCookieDatabases()).toEqual([
77
- join(home, '.config', 'google-chrome', 'Default', 'Cookies'),
78
- join(home, '.config', 'google-chrome', 'Profile 1', 'Cookies'),
79
- join(home, '.config', 'google-chrome', 'Profile 2', 'Cookies'),
80
- ])
81
- })
82
-
83
- it('keeps the newest unique cookie across profiles when collecting candidates', async () => {
84
- const home = await makeTempDir()
85
- createCookieDbWithPlaintext(join(home, '.config', 'google-chrome', 'Default', 'Cookies'), 'stale-session', 10)
86
- createCookieDbWithPlaintext(join(home, '.config', 'google-chrome', 'Profile 1', 'Cookies'), 'valid-session', 20)
87
- createCookieDbWithPlaintext(join(home, '.config', 'google-chrome', 'Profile 2', 'Cookies'), 'stale-session', 30)
88
-
89
- const extractor = new TokenExtractor('linux', home)
90
-
91
- await expect(extractor.extractCandidates()).resolves.toEqual([
92
- {
93
- browser: 'Chrome',
94
- lastAccessUtc: 30,
95
- profile: 'Profile 2',
96
- sessionCookie: 'stale-session',
97
- },
98
- {
99
- browser: 'Chrome',
100
- lastAccessUtc: 20,
101
- profile: 'Profile 1',
102
- sessionCookie: 'valid-session',
103
- },
104
- ])
105
- })
106
-
107
- it('extracts a plaintext cookie from the opensoma.dev host', async () => {
108
- const home = await makeTempDir()
109
- const dbPath = join(home, '.config', 'google-chrome', 'Default', 'Cookies')
110
- createCookieDbWithPlaintext(dbPath, 'opensoma-dev-session', 0, 'opensoma.dev')
111
-
112
- const extractor = new TokenExtractor('linux', home)
113
- expect(await extractor.extract()).toEqual({ sessionCookie: 'opensoma-dev-session' })
114
- })
115
-
116
- it('decrypts an encrypted cookie from the opensoma.dev host on Linux', async () => {
117
- const home = await makeTempDir()
118
- const dbPath = join(home, '.config', 'google-chrome', 'Default', 'Cookies')
119
- const encrypted = encryptLinuxCookie('opensoma-dev-encrypted')
120
- createCookieDbWithEncrypted(dbPath, encrypted, 'opensoma.dev')
121
-
122
- const extractor = new TokenExtractor('linux', home)
123
- expect(await extractor.extract()).toEqual({ sessionCookie: 'opensoma-dev-encrypted' })
124
- })
125
-
126
- it('decrypts an encrypted cookie on Linux', async () => {
127
- const home = await makeTempDir()
128
- const dbPath = join(home, '.config', 'google-chrome', 'Default', 'Cookies')
129
- const encrypted = encryptLinuxCookie('decrypted-session')
130
- createCookieDbWithEncrypted(dbPath, encrypted)
131
-
132
- const extractor = new TokenExtractor('linux', home)
133
- expect(await extractor.extract()).toEqual({ sessionCookie: 'decrypted-session' })
134
- })
135
-
136
- it('extracts a plaintext cookie when running under Node.js (compiled ESM)', async () => {
137
- execSync('bun run build', { cwd: join(import.meta.dir, '..'), stdio: 'pipe' })
138
- const home = await makeTempDir()
139
- const dbPath = join(home, '.config', 'google-chrome', 'Default', 'Cookies')
140
- createCookieDbWithPlaintext(dbPath, 'node-test-session')
141
-
142
- const result = runNodeExtractor(home)
143
-
144
- expect(JSON.parse(result.trim())).toEqual({ sessionCookie: 'node-test-session' })
145
- })
146
-
147
- it('extracts an encrypted cookie when running under Node.js (compiled ESM)', async () => {
148
- execSync('bun run build', { cwd: join(import.meta.dir, '..'), stdio: 'pipe' })
149
- const home = await makeTempDir()
150
- const dbPath = join(home, '.config', 'google-chrome', 'Default', 'Cookies')
151
- createCookieDbWithEncrypted(dbPath, encryptLinuxCookie('node-encrypted-session'))
152
-
153
- const result = runNodeExtractor(home)
154
-
155
- expect(JSON.parse(result.trim())).toEqual({ sessionCookie: 'node-encrypted-session' })
156
- })
157
- })
158
-
159
- async function makeTempDir(): Promise<string> {
160
- const dir = await mkdtemp(join(tmpdir(), 'opensoma-token-extractor-'))
161
- createdDirs.push(dir)
162
- return dir
163
- }
164
-
165
- function createCookieFile(filePath: string): void {
166
- mkdirSync(dirname(filePath), { recursive: true })
167
- writeFileSync(filePath, '')
168
- }
169
-
170
- function createCookieDbWithPlaintext(
171
- filePath: string,
172
- value: string,
173
- lastAccessUtc = 0,
174
- hostKey = 'swmaestro.ai',
175
- ): void {
176
- mkdirSync(dirname(filePath), { recursive: true })
177
- const db = new Database(filePath)
178
- db.run(
179
- 'CREATE TABLE cookies (host_key TEXT, name TEXT, value TEXT, encrypted_value BLOB, creation_utc INTEGER, expires_utc INTEGER, is_httponly INTEGER, has_expires INTEGER, is_persistent INTEGER, priority INTEGER, samesite INTEGER, source_scheme INTEGER, is_secure INTEGER, path TEXT, last_access_utc INTEGER, last_update_utc INTEGER, source_port INTEGER, source_type INTEGER)',
180
- )
181
- db.run(
182
- "INSERT INTO cookies (host_key, name, value, encrypted_value, last_access_utc) VALUES (?, 'JSESSIONID', ?, '', ?)",
183
- [hostKey, value, lastAccessUtc],
184
- )
185
- db.close()
186
- }
187
-
188
- function createCookieDbWithEncrypted(filePath: string, encrypted: Buffer, hostKey = 'swmaestro.ai'): void {
189
- mkdirSync(dirname(filePath), { recursive: true })
190
- const db = new Database(filePath)
191
- db.run(
192
- 'CREATE TABLE cookies (host_key TEXT, name TEXT, value TEXT, encrypted_value BLOB, creation_utc INTEGER, expires_utc INTEGER, is_httponly INTEGER, has_expires INTEGER, is_persistent INTEGER, priority INTEGER, samesite INTEGER, source_scheme INTEGER, is_secure INTEGER, path TEXT, last_access_utc INTEGER, last_update_utc INTEGER, source_port INTEGER, source_type INTEGER)',
193
- )
194
- db.run("INSERT INTO cookies (host_key, name, value, encrypted_value) VALUES (?, 'JSESSIONID', '', ?)", [
195
- hostKey,
196
- encrypted,
197
- ])
198
- db.close()
199
- }
200
-
201
- function runNodeExtractor(home: string): string {
202
- const projectRoot = join(import.meta.dir, '..')
203
- const scriptPath = join(home, 'test-node-extract.mjs')
204
- writeFileSync(
205
- scriptPath,
206
- [
207
- `import { TokenExtractor } from ${JSON.stringify(join(projectRoot, 'dist', 'src', 'token-extractor.js'))};`,
208
- `const ext = new TokenExtractor('linux', ${JSON.stringify(home)});`,
209
- `const result = await ext.extract();`,
210
- `console.log(JSON.stringify(result));`,
211
- ].join('\n'),
212
- )
213
- return execSync(`node ${scriptPath}`, { encoding: 'utf-8', timeout: 15_000 })
214
- }
215
-
216
- function encryptLinuxCookie(value: string): Buffer {
217
- const key = pbkdf2Sync('peanuts', CHROMIUM_SALT, 1, 16, 'sha1')
218
- const cipher = createCipheriv('aes-128-cbc', key, CHROMIUM_IV)
219
- return Buffer.concat([Buffer.from('v10'), cipher.update(value, 'utf8'), cipher.final()])
220
- }
@@ -1,392 +0,0 @@
1
- import { execSync } from 'node:child_process'
2
- import { createDecipheriv, pbkdf2Sync } from 'node:crypto'
3
- import { copyFileSync, existsSync, mkdtempSync, readdirSync, rmSync } from 'node:fs'
4
- import { createRequire } from 'node:module'
5
- import { homedir, tmpdir } from 'node:os'
6
- import { basename, dirname, join } from 'node:path'
7
-
8
- const require = createRequire(import.meta.url)
9
-
10
- const COOKIE_QUERY =
11
- "SELECT encrypted_value, last_access_utc, value FROM cookies WHERE (host_key LIKE '%swmaestro.ai' OR host_key LIKE '%opensoma.dev') AND name = 'JSESSIONID' ORDER BY last_access_utc DESC LIMIT 1"
12
- const CHROMIUM_SALT = 'saltysalt'
13
- const CHROMIUM_IV = Buffer.alloc(16, 0x20)
14
- const PROFILE_DIR_PATTERN = /^Profile\s+\d+$/
15
-
16
- type BrowserConfig = {
17
- name: string
18
- macPath: string
19
- linuxPath: string
20
- keychainService: string
21
- keychainAccount: string
22
- }
23
-
24
- type CookieRow = {
25
- encrypted_value: ArrayBuffer | Uint8Array | Buffer | null
26
- last_access_utc?: number | bigint | null
27
- value: ArrayBuffer | Uint8Array | Buffer | string | null
28
- }
29
-
30
- export interface ExtractedSessionCandidate {
31
- browser: string
32
- lastAccessUtc: number
33
- profile: string
34
- sessionCookie: string
35
- }
36
-
37
- export const BROWSERS: BrowserConfig[] = [
38
- {
39
- name: 'Chrome',
40
- macPath: 'Google Chrome',
41
- linuxPath: 'google-chrome',
42
- keychainService: 'Chrome Safe Storage',
43
- keychainAccount: 'Chrome',
44
- },
45
- {
46
- name: 'Edge',
47
- macPath: 'Microsoft Edge',
48
- linuxPath: 'microsoft-edge',
49
- keychainService: 'Microsoft Edge Safe Storage',
50
- keychainAccount: 'Microsoft Edge',
51
- },
52
- {
53
- name: 'Brave',
54
- macPath: 'BraveSoftware/Brave-Browser',
55
- linuxPath: 'BraveSoftware/Brave-Browser',
56
- keychainService: 'Brave Safe Storage',
57
- keychainAccount: 'Brave',
58
- },
59
- {
60
- name: 'Arc',
61
- macPath: join('Arc', 'User Data'),
62
- linuxPath: join('Arc', 'User Data'),
63
- keychainService: 'Arc Safe Storage',
64
- keychainAccount: 'Arc',
65
- },
66
- {
67
- name: 'Vivaldi',
68
- macPath: 'Vivaldi',
69
- linuxPath: 'Vivaldi',
70
- keychainService: 'Vivaldi Safe Storage',
71
- keychainAccount: 'Vivaldi',
72
- },
73
- {
74
- name: 'Chromium',
75
- macPath: 'Chromium',
76
- linuxPath: 'Chromium',
77
- keychainService: 'Chromium Safe Storage',
78
- keychainAccount: 'Chromium',
79
- },
80
- ]
81
-
82
- function queryCookieDb(dbPath: string): CookieRow | undefined {
83
- if (typeof globalThis.Bun !== 'undefined') {
84
- const { Database } = require('bun:sqlite')
85
- const db = new Database(dbPath, { readonly: true })
86
- try {
87
- const row = db.query(COOKIE_QUERY).get() as CookieRow | undefined
88
- return row ?? undefined
89
- } finally {
90
- db.close()
91
- }
92
- }
93
-
94
- try {
95
- const Database = require('better-sqlite3') as new (
96
- path: string,
97
- options?: { readonly?: boolean },
98
- ) => {
99
- close: () => void
100
- prepare: (query: string) => { get: () => CookieRow | undefined }
101
- }
102
- const db = new Database(dbPath, { readonly: true })
103
- try {
104
- return db.prepare(COOKIE_QUERY).get() ?? undefined
105
- } finally {
106
- db.close()
107
- }
108
- } catch {
109
- const { DatabaseSync } = require('node:sqlite') as {
110
- DatabaseSync: new (
111
- path: string,
112
- options?: { readonly?: boolean },
113
- ) => {
114
- close: () => void
115
- prepare: (query: string) => { get: () => CookieRow | undefined }
116
- }
117
- }
118
- const db = new DatabaseSync(dbPath, { readonly: true })
119
- try {
120
- return db.prepare(COOKIE_QUERY).get() ?? undefined
121
- } finally {
122
- db.close()
123
- }
124
- }
125
- }
126
-
127
- export type TokenExtractorOptions = {
128
- platform?: NodeJS.Platform
129
- homeDirectory?: string
130
- debug?: boolean
131
- }
132
-
133
- export class TokenExtractor {
134
- private readonly platform: NodeJS.Platform
135
- private readonly homeDirectory: string
136
- private readonly debugEnabled: boolean
137
-
138
- constructor(options?: TokenExtractorOptions)
139
- constructor(platform?: NodeJS.Platform, homeDirectory?: string, debug?: boolean)
140
- constructor(platformOrOptions?: NodeJS.Platform | TokenExtractorOptions, homeDirectory?: string, debug?: boolean) {
141
- if (typeof platformOrOptions === 'object' && platformOrOptions !== null) {
142
- this.platform = platformOrOptions.platform ?? process.platform
143
- this.homeDirectory = platformOrOptions.homeDirectory ?? homedir()
144
- this.debugEnabled = platformOrOptions.debug ?? false
145
- } else {
146
- this.platform = platformOrOptions ?? process.platform
147
- this.homeDirectory = homeDirectory ?? homedir()
148
- this.debugEnabled = debug ?? false
149
- }
150
- }
151
-
152
- private log(...args: unknown[]): void {
153
- if (!this.debugEnabled) return
154
- const message = args.map((a) => (typeof a === 'string' ? a : JSON.stringify(a))).join(' ')
155
- process.stderr.write(`[extract] ${message}\n`)
156
- }
157
-
158
- async extract(): Promise<{ sessionCookie: string } | null> {
159
- const candidates = await this.extractCandidates()
160
- const firstCandidate = candidates[0]
161
-
162
- if (!firstCandidate) {
163
- return null
164
- }
165
-
166
- return { sessionCookie: firstCandidate.sessionCookie }
167
- }
168
-
169
- async extractCandidates(): Promise<ExtractedSessionCandidate[]> {
170
- const candidates = new Map<string, ExtractedSessionCandidate>()
171
- const cookieDatabases = this.findCookieDatabases()
172
- this.log(`Found ${cookieDatabases.length} cookie database(s)`)
173
-
174
- for (const databasePath of cookieDatabases) {
175
- const browser = this.getBrowserByPath(databasePath)
176
- if (!browser) {
177
- this.log(`Skipping ${databasePath} (no matching browser config)`)
178
- continue
179
- }
180
-
181
- const profile = basename(dirname(databasePath))
182
- this.log(`Processing ${browser.name} / ${profile}`)
183
-
184
- const tempDirectory = mkdtempSync(join(tmpdir(), 'opensoma-cookie-db-'))
185
- const tempDatabasePath = join(tempDirectory, 'Cookies')
186
-
187
- try {
188
- copySqliteDatabase(databasePath, tempDatabasePath)
189
- this.log(` Copied DB to ${tempDatabasePath}`)
190
-
191
- const row = queryCookieDb(tempDatabasePath)
192
- if (!row) {
193
- this.log(' No JSESSIONID cookie found')
194
- continue
195
- }
196
-
197
- const plaintextValue = normalizeCookieText(row.value)
198
- if (plaintextValue) {
199
- this.log(` Found plaintext cookie (${plaintextValue.length} chars)`)
200
- this.addCandidate(candidates, {
201
- browser: browser.name,
202
- lastAccessUtc: this.normalizeLastAccessUtc(row.last_access_utc),
203
- profile,
204
- sessionCookie: plaintextValue,
205
- })
206
- continue
207
- }
208
-
209
- const encryptedValue = normalizeCookieBytes(row.encrypted_value)
210
- if (!encryptedValue || encryptedValue.length === 0) {
211
- this.log(' No plaintext or encrypted value')
212
- continue
213
- }
214
-
215
- this.log(` Decrypting encrypted cookie (${encryptedValue.length} bytes)...`)
216
- const decryptedValue = await this.decryptCookie(encryptedValue, browser.name)
217
- if (decryptedValue) {
218
- this.log(` Decrypted successfully (${decryptedValue.length} chars)`)
219
- this.addCandidate(candidates, {
220
- browser: browser.name,
221
- lastAccessUtc: this.normalizeLastAccessUtc(row.last_access_utc),
222
- profile,
223
- sessionCookie: decryptedValue,
224
- })
225
- } else {
226
- this.log(' Decryption returned empty value')
227
- }
228
- } catch (error) {
229
- this.log(` Error: ${error instanceof Error ? error.message : String(error)}`)
230
- } finally {
231
- rmSync(tempDirectory, { recursive: true, force: true })
232
- }
233
- }
234
-
235
- this.log(`Total unique candidates: ${candidates.size}`)
236
- return [...candidates.values()].sort((left, right) => right.lastAccessUtc - left.lastAccessUtc)
237
- }
238
-
239
- findCookieDatabases(): string[] {
240
- const results = BROWSERS.flatMap((browser) => this.findBrowserCookieDatabases(browser))
241
- if (results.length === 0) {
242
- this.log('No cookie databases found. Searched browsers:', BROWSERS.map((b) => b.name).join(', '))
243
- }
244
- return results
245
- }
246
-
247
- private async decryptCookie(encryptedValue: Buffer, browserName: string): Promise<string> {
248
- if (encryptedValue.length === 0) {
249
- return ''
250
- }
251
-
252
- if (this.platform === 'linux') {
253
- this.log(' Using Linux PBKDF2 decryption')
254
- return this.decryptChromiumValue(encryptedValue, pbkdf2Sync('peanuts', CHROMIUM_SALT, 1, 16, 'sha1'))
255
- }
256
-
257
- if (this.platform === 'darwin') {
258
- this.log(` Fetching macOS Keychain key for ${browserName}...`)
259
- const key = await this.getMacOSEncryptionKey(browserName)
260
- this.log(' Keychain key obtained')
261
- return this.decryptChromiumValue(encryptedValue, key)
262
- }
263
-
264
- this.log(` Unsupported platform for decryption: ${this.platform}`)
265
- return ''
266
- }
267
-
268
- private async getMacOSEncryptionKey(browserName: string): Promise<Buffer> {
269
- const browser = BROWSERS.find((entry) => entry.name === browserName)
270
- if (!browser) {
271
- throw new Error(`Unsupported browser: ${browserName}`)
272
- }
273
-
274
- const command = `security find-generic-password -s ${JSON.stringify(browser.keychainService)} -a ${JSON.stringify(browser.keychainAccount)} -w`
275
- this.log(` Keychain command: ${command}`)
276
-
277
- const password = execSync(command, { encoding: 'utf8' }).trimEnd()
278
- return pbkdf2Sync(password, CHROMIUM_SALT, 1003, 16, 'sha1')
279
- }
280
-
281
- private decryptChromiumValue(encryptedValue: Buffer, key: Buffer): string {
282
- const encryptedPayload =
283
- encryptedValue.subarray(0, 3).toString('utf8') === 'v10' ? encryptedValue.subarray(3) : encryptedValue
284
- const decipher = createDecipheriv('aes-128-cbc', key, CHROMIUM_IV)
285
- decipher.setAutoPadding(true)
286
- const decrypted = Buffer.concat([decipher.update(encryptedPayload), decipher.final()])
287
-
288
- // Chromium v130+ prepends a 32-byte integrity hash before the actual cookie value
289
- if (decrypted.length > 32) {
290
- const hasNonPrintablePrefix = decrypted.subarray(0, 32).some((b) => b < 0x20 || b > 0x7e)
291
- if (hasNonPrintablePrefix) {
292
- return decrypted.subarray(32).toString('utf8')
293
- }
294
- }
295
-
296
- return decrypted.toString('utf8')
297
- }
298
-
299
- private getBrowserByPath(databasePath: string): BrowserConfig | undefined {
300
- return BROWSERS.find(
301
- (browser) => databasePath.includes(`${browser.macPath}/`) || databasePath.includes(`${browser.linuxPath}/`),
302
- )
303
- }
304
-
305
- private addCandidate(candidates: Map<string, ExtractedSessionCandidate>, candidate: ExtractedSessionCandidate): void {
306
- const existing = candidates.get(candidate.sessionCookie)
307
- if (!existing || existing.lastAccessUtc < candidate.lastAccessUtc) {
308
- candidates.set(candidate.sessionCookie, candidate)
309
- }
310
- }
311
-
312
- private findBrowserCookieDatabases(browser: BrowserConfig): string[] {
313
- const browserRoot = this.getBrowserRoot(browser)
314
- if (!browserRoot || !existsSync(browserRoot)) {
315
- this.log(`${browser.name}: not found at ${browserRoot ?? '(unsupported platform)'}`)
316
- return []
317
- }
318
-
319
- const profiles = readdirSync(browserRoot, { withFileTypes: true })
320
- .filter((entry) => entry.isDirectory() && this.isSupportedProfileDirectory(entry.name))
321
- .sort((left, right) => left.name.localeCompare(right.name))
322
-
323
- const databases = profiles
324
- .map((entry) => join(browserRoot, entry.name, 'Cookies'))
325
- .filter((databasePath) => existsSync(databasePath))
326
-
327
- this.log(`${browser.name}: ${profiles.length} profile(s), ${databases.length} cookie DB(s)`)
328
- return databases
329
- }
330
-
331
- private getBrowserRoot(browser: BrowserConfig): string | null {
332
- if (this.platform === 'darwin') {
333
- return join(this.homeDirectory, 'Library', 'Application Support', browser.macPath)
334
- }
335
-
336
- if (this.platform === 'linux') {
337
- return join(this.homeDirectory, '.config', browser.linuxPath)
338
- }
339
-
340
- return null
341
- }
342
-
343
- private isSupportedProfileDirectory(profileName: string): boolean {
344
- return profileName === 'Default' || PROFILE_DIR_PATTERN.test(profileName)
345
- }
346
-
347
- private normalizeLastAccessUtc(lastAccessUtc: number | bigint | null | undefined): number {
348
- if (typeof lastAccessUtc === 'bigint') {
349
- return Number(lastAccessUtc)
350
- }
351
-
352
- return typeof lastAccessUtc === 'number' ? lastAccessUtc : 0
353
- }
354
- }
355
-
356
- function copySqliteDatabase(sourcePath: string, targetPath: string): void {
357
- copyFileSync(sourcePath, targetPath)
358
-
359
- for (const suffix of ['-wal', '-shm']) {
360
- const sidecarSourcePath = `${sourcePath}${suffix}`
361
- if (!existsSync(sidecarSourcePath)) {
362
- continue
363
- }
364
-
365
- copyFileSync(sidecarSourcePath, `${targetPath}${suffix}`)
366
- }
367
- }
368
-
369
- function normalizeCookieBytes(value: ArrayBuffer | Uint8Array | Buffer | null): Buffer | null {
370
- if (!value) {
371
- return null
372
- }
373
-
374
- if (Buffer.isBuffer(value)) {
375
- return value
376
- }
377
-
378
- if (value instanceof Uint8Array) {
379
- return Buffer.from(value)
380
- }
381
-
382
- return Buffer.from(value)
383
- }
384
-
385
- function normalizeCookieText(value: ArrayBuffer | Uint8Array | Buffer | string | null): string {
386
- if (typeof value === 'string') {
387
- return value.trim()
388
- }
389
-
390
- const bytes = normalizeCookieBytes(value)
391
- return bytes ? bytes.toString('utf8').trim() : ''
392
- }