cookie-database.ts
73 lines · browse/src/cookie-database.ts at 3f79d81
1import { createRequire } from 'node:module';
3const require = createRequire(import.meta.url);
5function databaseError(error: unknown): Error & { code: string } {
6 const detail = error as { errcode?: number; errno?: number; code?: string } | null;
7 const codes: Record<number, string> = {
8 5: 'SQLITE_BUSY', 6: 'SQLITE_LOCKED', 8: 'SQLITE_READONLY', 10: 'SQLITE_IOERR',
9 11: 'SQLITE_CORRUPT', 14: 'SQLITE_CANTOPEN', 26: 'SQLITE_CORRUPT',
10 };
11 const number = detail?.errcode ?? detail?.errno;
12 const code = typeof number === 'number' ? codes[number & 255] ?? 'SQLITE_ERROR'
13 : Object.values(codes).includes(detail?.code ?? '') ? detail!.code! : 'SQLITE_ERROR';
14 const message = code === 'SQLITE_BUSY' || code === 'SQLITE_LOCKED'
15 ? 'Cookie database is locked. Close the source browser and retry.'
16 : 'Cookie database operation failed (' + code + ').';
17 return Object.assign(new Error(message), { code });
18}
20export function openCookieDatabase(dbPath: string): {
21 query(sql: string): { all(...bindings: any[]): unknown[] };
22 close(): void;
23} {
24 const isBun = typeof process.versions.bun === 'string';
25 let Database;
26 try {
27 const sqlite = require(isBun ? 'bun:sqlite' : 'node:sqlite');
28 Database = isBun ? sqlite.Database : sqlite.DatabaseSync;
29 if (typeof Database !== 'function') throw new Error();
30 } catch {
31 throw Object.assign(new Error(isBun
32 ? 'Cookie import requires a Bun runtime with SQLite support. Upgrade Bun and retry.'
33 : 'Cookie import requires Node.js 22.13 or newer with built-in SQLite enabled. Upgrade Node.js or enable SQLite and retry.'), { code: 'sqlite_unavailable' });
34 }
36 let database;
37 try {
38 database = new Database(dbPath, isBun ? { readonly: true, safeIntegers: true } : { readOnly: true });
39 } catch (error) {
40 throw databaseError(error);
41 }
43 return {
44 query(sql) {
45 let statement;
46 try {
47 statement = isBun ? database.query(sql) : database.prepare(sql);
48 if (!isBun) statement.setReadBigInts(true);
49 } catch (error) {
50 throw databaseError(error);
51 }
52 return {
53 all(...bindings) {
54 try {
55 return statement.all(...bindings).map((row: Record<string, unknown>) => Object.fromEntries(
56 Object.entries(row).map(([key, value]) => [key,
57 typeof value === 'bigint' && Number.isSafeInteger(Number(value)) ? Number(value) : value]),
58 ));
59 } catch (error) {
60 throw databaseError(error);
61 }
62 },
63 };
64 },
65 close() {
66 try {
67 database.close();
68 } catch (error) {
69 throw databaseError(error);
70 }
71 },
72 };
73}