chrome-tools 1.1.6 → 1.1.7

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.
Files changed (44) hide show
  1. package/dist/bin/cli.js +556 -1
  2. package/dist/src/core/browsers.d.ts +7 -3
  3. package/dist/src/core/browsers.js +186 -1
  4. package/dist/src/core/chrome.d.ts +6 -4
  5. package/dist/src/core/chrome.js +240 -1
  6. package/dist/src/core/crypto.d.ts +1 -1
  7. package/dist/src/core/crypto.js +113 -1
  8. package/dist/src/core/db.d.ts +4 -2
  9. package/dist/src/core/db.js +90 -1
  10. package/dist/src/core/keychain.js +191 -1
  11. package/dist/src/core/limit.d.ts +4 -0
  12. package/dist/src/core/limit.js +12 -0
  13. package/dist/src/core/platform/darwin.js +31 -1
  14. package/dist/src/core/platform/index.d.ts +4 -3
  15. package/dist/src/core/platform/index.js +69 -1
  16. package/dist/src/core/platform/linux.js +64 -1
  17. package/dist/src/core/platform/types.d.ts +3 -2
  18. package/dist/src/core/platform/types.js +1 -1
  19. package/dist/src/core/platform/win32.js +114 -1
  20. package/dist/src/core/sql.d.ts +2 -0
  21. package/dist/src/core/sql.js +4 -0
  22. package/dist/src/index.d.ts +1 -1
  23. package/dist/src/index.js +34 -1
  24. package/dist/src/modules/autofill.d.ts +7 -15
  25. package/dist/src/modules/autofill.js +170 -1
  26. package/dist/src/modules/bookmarks.d.ts +3 -5
  27. package/dist/src/modules/bookmarks.js +83 -1
  28. package/dist/src/modules/cookies.d.ts +3 -5
  29. package/dist/src/modules/cookies.js +157 -1
  30. package/dist/src/modules/downloads.d.ts +3 -5
  31. package/dist/src/modules/downloads.js +80 -1
  32. package/dist/src/modules/extensions.d.ts +3 -5
  33. package/dist/src/modules/extensions.js +71 -1
  34. package/dist/src/modules/history.d.ts +5 -10
  35. package/dist/src/modules/history.js +83 -1
  36. package/dist/src/modules/local-storage.d.ts +3 -5
  37. package/dist/src/modules/local-storage.js +455 -1
  38. package/dist/src/modules/passwords.d.ts +3 -5
  39. package/dist/src/modules/passwords.js +64 -1
  40. package/dist/src/modules/search-engines.d.ts +3 -5
  41. package/dist/src/modules/search-engines.js +38 -1
  42. package/dist/src/modules/top-sites.d.ts +3 -5
  43. package/dist/src/modules/top-sites.js +33 -1
  44. package/package.json +14 -12
@@ -1 +1,186 @@
1
- export const BROWSERS={chrome:{id:"chrome",name:"Google Chrome",aliases:["google-chrome","google chrome"],safeStorageLabel:"Chrome Safe Storage",linuxSecretApp:"chrome",dirs:{darwin:"Google/Chrome",win32:"Google/Chrome/User Data",linux:"google-chrome"}},"chrome-beta":{id:"chrome-beta",name:"Google Chrome Beta",safeStorageLabel:"Chrome Safe Storage",linuxSecretApp:"chrome",dirs:{darwin:"Google/Chrome Beta",win32:"Google/Chrome Beta/User Data",linux:"google-chrome-beta"}},"chrome-canary":{id:"chrome-canary",name:"Google Chrome Canary",aliases:["canary"],safeStorageLabel:"Chrome Safe Storage",linuxSecretApp:"chrome",dirs:{darwin:"Google/Chrome Canary",win32:"Google/Chrome SxS/User Data",linux:"google-chrome-unstable"}},chromium:{id:"chromium",name:"Chromium",safeStorageLabel:"Chromium Safe Storage",linuxSecretApp:"chromium",dirs:{darwin:"Chromium",win32:"Chromium/User Data",linux:"chromium"}},edge:{id:"edge",name:"Microsoft Edge",aliases:["msedge","microsoft-edge","microsoft edge"],safeStorageLabel:"Microsoft Edge Safe Storage",linuxSecretApp:"chromium",dirs:{darwin:"Microsoft Edge",win32:"Microsoft/Edge/User Data",linux:"microsoft-edge"}},brave:{id:"brave",name:"Brave",aliases:["brave-browser"],safeStorageLabel:"Brave Safe Storage",linuxSecretApp:"chrome",dirs:{darwin:"BraveSoftware/Brave-Browser",win32:"BraveSoftware/Brave-Browser/User Data",linux:"BraveSoftware/Brave-Browser"}},comet:{id:"comet",name:"Comet",aliases:["perplexity","perplexity-comet"],safeStorageLabel:"Comet Safe Storage",linuxSecretApp:"chromium",dirs:{darwin:"Comet",win32:"Perplexity/Comet/User Data",linux:"comet"}},vivaldi:{id:"vivaldi",name:"Vivaldi",safeStorageLabel:"Chrome Safe Storage",linuxSecretApp:"chrome",dirs:{darwin:"Vivaldi",win32:"Vivaldi/User Data",linux:"vivaldi"}},opera:{id:"opera",name:"Opera",safeStorageLabel:"Opera Safe Storage",linuxSecretApp:"chromium",dirs:{darwin:"com.operasoftware.Opera",win32:"Opera Software/Opera Stable",linux:"opera"}}};const e="chrome";export function listBrowsers(){return Object.values(BROWSERS)}export function resolveBrowser(e){if(!e)return null;const r=e.trim().toLowerCase();for(const e of listBrowsers()){if(e.id.toLowerCase()===r)return e;if(e.name.toLowerCase()===r)return e;if(e.aliases?.some(e=>e.toLowerCase()===r))return e}return null}let r=BROWSERS[e];export function getActiveBrowser(){return r}export function setActiveBrowser(o){if(!o||!o.trim())return r=BROWSERS[e],r;const a=resolveBrowser(o);if(!a){const e=listBrowsers().map(e=>e.id).join(", ");throw new Error(`Unknown browser "${o}". Supported: ${e}.`)}return r=a,r}
1
+ /**
2
+ * Chromium-browser registry — the single source of truth for what differs
3
+ * between Chromium-based browsers (Chrome, Edge, Brave, Comet, …).
4
+ *
5
+ * Every supported browser is a Chromium fork, so the *decryption* is identical
6
+ * per OS (AES-128-CBC + PBKDF2 on macOS/Linux, AES-256-GCM + DPAPI on Windows).
7
+ * Only two things vary by browser:
8
+ * 1. the default profile data directory, and
9
+ * 2. the OS-keystore "Safe Storage" label (macOS Keychain service name /
10
+ * Linux Secret Service application).
11
+ *
12
+ * "Browser" is therefore a small dimension orthogonal to "platform": the
13
+ * platform backends (darwin/win32/linux) receive a browser descriptor and stay
14
+ * otherwise unchanged.
15
+ */
16
+ /**
17
+ * Supported browsers. macOS paths/labels are verified; a few Windows/Linux
18
+ * paths for niche browsers (Comet, Opera) are best-effort and easy to correct —
19
+ * each is a single edit here, with no other code change required.
20
+ */
21
+ export const BROWSERS = {
22
+ chrome: {
23
+ id: "chrome",
24
+ name: "Google Chrome",
25
+ aliases: ["google-chrome", "google chrome"],
26
+ safeStorageLabel: "Chrome Safe Storage",
27
+ linuxSecretApp: "chrome",
28
+ dirs: {
29
+ darwin: "Google/Chrome",
30
+ win32: "Google/Chrome/User Data",
31
+ linux: "google-chrome",
32
+ },
33
+ },
34
+ "chrome-beta": {
35
+ id: "chrome-beta",
36
+ name: "Google Chrome Beta",
37
+ safeStorageLabel: "Chrome Safe Storage",
38
+ linuxSecretApp: "chrome",
39
+ dirs: {
40
+ darwin: "Google/Chrome Beta",
41
+ win32: "Google/Chrome Beta/User Data",
42
+ linux: "google-chrome-beta",
43
+ },
44
+ },
45
+ "chrome-canary": {
46
+ id: "chrome-canary",
47
+ name: "Google Chrome Canary",
48
+ aliases: ["canary"],
49
+ safeStorageLabel: "Chrome Safe Storage",
50
+ linuxSecretApp: "chrome",
51
+ dirs: {
52
+ darwin: "Google/Chrome Canary",
53
+ win32: "Google/Chrome SxS/User Data",
54
+ linux: "google-chrome-unstable",
55
+ },
56
+ },
57
+ chromium: {
58
+ id: "chromium",
59
+ name: "Chromium",
60
+ safeStorageLabel: "Chromium Safe Storage",
61
+ linuxSecretApp: "chromium",
62
+ dirs: {
63
+ darwin: "Chromium",
64
+ win32: "Chromium/User Data",
65
+ linux: "chromium",
66
+ },
67
+ },
68
+ edge: {
69
+ id: "edge",
70
+ name: "Microsoft Edge",
71
+ aliases: ["msedge", "microsoft-edge", "microsoft edge"],
72
+ safeStorageLabel: "Microsoft Edge Safe Storage",
73
+ linuxSecretApp: "chromium",
74
+ dirs: {
75
+ darwin: "Microsoft Edge",
76
+ win32: "Microsoft/Edge/User Data",
77
+ linux: "microsoft-edge",
78
+ },
79
+ },
80
+ brave: {
81
+ id: "brave",
82
+ name: "Brave",
83
+ aliases: ["brave-browser"],
84
+ safeStorageLabel: "Brave Safe Storage",
85
+ linuxSecretApp: "chrome",
86
+ dirs: {
87
+ darwin: "BraveSoftware/Brave-Browser",
88
+ win32: "BraveSoftware/Brave-Browser/User Data",
89
+ linux: "BraveSoftware/Brave-Browser",
90
+ },
91
+ },
92
+ comet: {
93
+ id: "comet",
94
+ name: "Comet",
95
+ aliases: ["perplexity", "perplexity-comet"],
96
+ safeStorageLabel: "Comet Safe Storage",
97
+ linuxSecretApp: "chromium",
98
+ // macOS dir verified (~/Library/Application Support/Comet); Win/Linux best-effort.
99
+ dirs: {
100
+ darwin: "Comet",
101
+ win32: "Perplexity/Comet/User Data",
102
+ linux: "comet",
103
+ },
104
+ },
105
+ vivaldi: {
106
+ id: "vivaldi",
107
+ name: "Vivaldi",
108
+ // Vivaldi reuses Chrome's Keychain item rather than minting its own.
109
+ safeStorageLabel: "Chrome Safe Storage",
110
+ linuxSecretApp: "chrome",
111
+ dirs: {
112
+ darwin: "Vivaldi",
113
+ win32: "Vivaldi/User Data",
114
+ linux: "vivaldi",
115
+ },
116
+ },
117
+ opera: {
118
+ id: "opera",
119
+ name: "Opera",
120
+ safeStorageLabel: "Opera Safe Storage",
121
+ linuxSecretApp: "chromium",
122
+ // Opera's product dir *is* the user-data root (no nested "User Data").
123
+ // Profiles are usually still under Default/; some layouts are flat at root —
124
+ // discoverProfiles handles both.
125
+ dirs: {
126
+ darwin: "com.operasoftware.Opera",
127
+ win32: "Opera Software/Opera Stable",
128
+ win32Base: "roaming",
129
+ linux: "opera",
130
+ },
131
+ },
132
+ };
133
+ const DEFAULT_BROWSER = "chrome";
134
+ /** All supported browsers, in a stable display order. */
135
+ export function listBrowsers() {
136
+ return Object.values(BROWSERS);
137
+ }
138
+ /**
139
+ * Resolves a browser by id, display name, or alias (case-insensitive).
140
+ * @returns The descriptor, or `null` if unrecognized.
141
+ */
142
+ export function resolveBrowser(idOrName) {
143
+ if (!idOrName)
144
+ return null;
145
+ const needle = idOrName.trim().toLowerCase();
146
+ for (const b of listBrowsers()) {
147
+ if (b.id.toLowerCase() === needle)
148
+ return b;
149
+ if (b.name.toLowerCase() === needle)
150
+ return b;
151
+ if (b.aliases?.some((a) => a.toLowerCase() === needle))
152
+ return b;
153
+ }
154
+ return null;
155
+ }
156
+ /** Resolves a browser or throws an actionable error without changing global state. */
157
+ export function requireBrowser(idOrName) {
158
+ const resolved = resolveBrowser(idOrName);
159
+ if (resolved)
160
+ return resolved;
161
+ throw new Error(`Unknown browser "${idOrName}". Supported: ${listBrowsers().map((b) => b.id).join(", ")}.`);
162
+ }
163
+ let _active = BROWSERS[DEFAULT_BROWSER];
164
+ /** The browser currently targeted by dir/key lookups. Defaults to Chrome. */
165
+ export function getActiveBrowser() {
166
+ return _active;
167
+ }
168
+ /**
169
+ * Sets the active browser for subsequent dir/key lookups.
170
+ *
171
+ * Caches in the platform backends are keyed by their real inputs (Keychain
172
+ * service label, chromeDir, Linux secret app), so switching browsers never
173
+ * returns a stale key — no cache flush is needed here.
174
+ *
175
+ * @param idOrName - Browser id/name/alias. Empty/undefined resets to Chrome.
176
+ * @returns The now-active descriptor.
177
+ * @throws If `idOrName` is non-empty but unrecognized.
178
+ */
179
+ export function setActiveBrowser(idOrName) {
180
+ if (!idOrName || !idOrName.trim()) {
181
+ _active = BROWSERS[DEFAULT_BROWSER];
182
+ return _active;
183
+ }
184
+ _active = requireBrowser(idOrName);
185
+ return _active;
186
+ }
@@ -37,7 +37,7 @@ export interface ResolveProfilesOptions {
37
37
  * Delegates to the active platform backend (macOS/Windows/Linux).
38
38
  * @returns The absolute path to Chrome's User Data.
39
39
  */
40
- export declare function getDefaultChromeDir(): string;
40
+ export declare function getDefaultChromeDir(browser?: string | BrowserDescriptor): string;
41
41
  export interface InstalledBrowser {
42
42
  /** Registry descriptor for the detected browser. */
43
43
  browser: BrowserDescriptor;
@@ -51,9 +51,6 @@ export interface InstalledBrowser {
51
51
  /**
52
52
  * Probes every supported browser for an on-disk profile directory.
53
53
  *
54
- * Temporarily switches the active browser to resolve each one's default dir,
55
- * then restores the previously-active browser before returning.
56
- *
57
54
  * @param options.includeMissing - When true, also returns browsers with no data
58
55
  * directory (installed: false). Defaults to false (installed browsers only).
59
56
  * @returns One entry per matching browser.
@@ -65,6 +62,11 @@ export declare function detectInstalledBrowsers({ includeMissing }?: {
65
62
  * Scans the Chrome User Data directory for profile subdirectories.
66
63
  * A directory is considered a profile if it contains at least one known data file.
67
64
  *
65
+ * Also handles "flat" layouts (e.g. some Opera installs, or when `chromeDir`
66
+ * already points at a single profile folder): if no child profiles are found
67
+ * but the user-data directory itself has profile indicators, it is returned
68
+ * as a single profile.
69
+ *
68
70
  * @param chromeDir - Chrome User Data path. Defaults to standard macOS location.
69
71
  * @returns Discovered profiles with name and absolute path.
70
72
  */
@@ -1 +1,240 @@
1
- import{existsSync as e,readdirSync as t,readFileSync as r,statSync as o}from"node:fs";import{join as i}from"node:path";import{getPlatformChromeDir as n}from"./platform/index.js";import{listBrowsers as a,getActiveBrowser as s,setActiveBrowser as c}from"./browsers.js";const f=["Login Data","History","Bookmarks","Cookies","Web Data","Local Storage","Preferences"];export function getDefaultChromeDir(){return n()}export function detectInstalledBrowsers({includeMissing:t=!1}={}){const r=s(),o=[];try{for(const r of a()){c(r.id);const i=getDefaultChromeDir(),n=Boolean(i)&&e(i);(n||t)&&o.push({browser:r,chromeDir:i,installed:n,profileCount:n?discoverProfiles(i).length:0})}}finally{c(r.id)}return o}export function discoverProfiles(r){const o=r||getDefaultChromeDir();if(!o||!e(o))return[];try{const r=t(o,{withFileTypes:!0}),n=[];for(const t of r){if(!t.isDirectory())continue;const r=i(o,t.name);f.some(t=>e(i(r,t)))&&n.push({name:t.name,path:r})}return n}catch{return[]}}export function getProfileMetadata(t){const o=t||getDefaultChromeDir(),n=i(o,"Local State");if(!e(n))return[];try{const e=r(n,"utf-8"),t=JSON.parse(e),o=t?.profile?.info_cache;return o&&"object"==typeof o?Object.entries(o).map(([e,t])=>{const r=t;return{directory:e,name:r.name||e,gaiaName:r.gaia_name||"",userName:r.user_name||"",email:r.user_name||"",avatarIcon:r.avatar_icon||"",isUsingDefaultName:r.is_using_default_name??!0,isEphemeral:r.is_ephemeral??!1,activeTime:r.active_time||0}}):[]}catch{return[]}}export function getProfileStates(t){const r=t||getDefaultChromeDir(),o=discoverProfiles(r),n=getProfileMetadata(r),a=new Map;for(const e of n)a.set(e.directory,e);return o.map(t=>{const r=a.get(t.name),o=t.path,n={};for(const t of f)n[t.replace(/\s+/g,"_").toLowerCase()]=e(i(o,t));return n.top_sites=e(i(o,"Top Sites")),n.extensions=e(i(o,"Extensions")),{directory:t.name,path:o,displayName:r?.name||t.name,gaiaName:r?.gaiaName||"",email:r?.email||"",avatarIcon:r?.avatarIcon||"",isEphemeral:r?.isEphemeral??!1,activeTime:r?.activeTime||0,dataFiles:n}})}export function getDirectorySize(r){if(!r||!e(r))return 0;let n,a=0;try{n=t(r,{withFileTypes:!0})}catch{return 0}for(const e of n){const t=i(r,e.name);try{if(e.isSymbolicLink())continue;e.isDirectory()?a+=getDirectorySize(t):e.isFile()&&(a+=o(t).size)}catch{}}return a}export function resolveProfilePath(t,r){const o=t||getDefaultChromeDir(),n=i(o,r);return e(n)?n:null}export function resolveTargetProfiles({profilePath:e,chromeDir:t,profile:r,browser:o}={}){o&&c(o);const i=e||t||getDefaultChromeDir(),n=discoverProfiles(i);if(r){const e=n.filter(e=>e.name===r);if(0===e.length)throw new Error(`Profile "${r}" not found in ${i}`);return e}return n}
1
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
2
+ import { basename, join } from "node:path";
3
+ import { getPlatformChromeDir } from "./platform/index.js";
4
+ import { listBrowsers, requireBrowser, } from "./browsers.js";
5
+ /** Known database/data files that indicate a valid Chrome profile directory. */
6
+ const PROFILE_INDICATORS = [
7
+ "Login Data",
8
+ "History",
9
+ "Bookmarks",
10
+ "Cookies",
11
+ "Web Data",
12
+ "Local Storage",
13
+ "Preferences",
14
+ ];
15
+ /** True when `dir` looks like a Chromium profile (has at least one indicator). */
16
+ function isProfileDirectory(dir) {
17
+ return PROFILE_INDICATORS.some((file) => existsSync(join(dir, file)));
18
+ }
19
+ /**
20
+ * Returns the default Chrome User Data directory for the current OS.
21
+ * Delegates to the active platform backend (macOS/Windows/Linux).
22
+ * @returns The absolute path to Chrome's User Data.
23
+ */
24
+ export function getDefaultChromeDir(browser) {
25
+ return getPlatformChromeDir(browser);
26
+ }
27
+ /**
28
+ * Probes every supported browser for an on-disk profile directory.
29
+ *
30
+ * @param options.includeMissing - When true, also returns browsers with no data
31
+ * directory (installed: false). Defaults to false (installed browsers only).
32
+ * @returns One entry per matching browser.
33
+ */
34
+ export function detectInstalledBrowsers({ includeMissing = false } = {}) {
35
+ const out = [];
36
+ for (const browser of listBrowsers()) {
37
+ const chromeDir = getDefaultChromeDir(browser);
38
+ const installed = existsSync(chromeDir);
39
+ if (!installed && !includeMissing)
40
+ continue;
41
+ out.push({
42
+ browser,
43
+ chromeDir,
44
+ installed,
45
+ profileCount: installed ? discoverProfiles(chromeDir).length : 0,
46
+ });
47
+ }
48
+ return out;
49
+ }
50
+ /**
51
+ * Scans the Chrome User Data directory for profile subdirectories.
52
+ * A directory is considered a profile if it contains at least one known data file.
53
+ *
54
+ * Also handles "flat" layouts (e.g. some Opera installs, or when `chromeDir`
55
+ * already points at a single profile folder): if no child profiles are found
56
+ * but the user-data directory itself has profile indicators, it is returned
57
+ * as a single profile.
58
+ *
59
+ * @param chromeDir - Chrome User Data path. Defaults to standard macOS location.
60
+ * @returns Discovered profiles with name and absolute path.
61
+ */
62
+ export function discoverProfiles(chromeDir) {
63
+ const dir = chromeDir || getDefaultChromeDir();
64
+ if (!dir || !existsSync(dir)) {
65
+ return [];
66
+ }
67
+ try {
68
+ const entries = readdirSync(dir, { withFileTypes: true });
69
+ const profiles = [];
70
+ for (const entry of entries) {
71
+ if (!entry.isDirectory())
72
+ continue;
73
+ const profilePath = join(dir, entry.name);
74
+ if (isProfileDirectory(profilePath)) {
75
+ profiles.push({ name: entry.name, path: profilePath });
76
+ }
77
+ }
78
+ // Flat / root-level profile: indicators live on the product dir itself
79
+ // (no Default/Profile N children), or the caller passed a profile path.
80
+ if (profiles.length === 0 && isProfileDirectory(dir)) {
81
+ const name = basename(dir) || "Default";
82
+ profiles.push({ name, path: dir });
83
+ }
84
+ return profiles;
85
+ }
86
+ catch {
87
+ return [];
88
+ }
89
+ }
90
+ /**
91
+ * Parses the Chrome "Local State" JSON file to extract profile metadata.
92
+ * Returns enriched profile info including display name, email, avatar, etc.
93
+ *
94
+ * @param chromeDir - Chrome User Data path.
95
+ * @returns Profile metadata objects.
96
+ */
97
+ export function getProfileMetadata(chromeDir) {
98
+ const dir = chromeDir || getDefaultChromeDir();
99
+ const localStatePath = join(dir, "Local State");
100
+ if (!existsSync(localStatePath)) {
101
+ return [];
102
+ }
103
+ try {
104
+ const raw = readFileSync(localStatePath, "utf-8");
105
+ const localState = JSON.parse(raw);
106
+ const infoCache = localState?.profile?.info_cache;
107
+ if (!infoCache || typeof infoCache !== "object") {
108
+ return [];
109
+ }
110
+ return Object.entries(infoCache).map(([dirName, infoAny]) => {
111
+ const info = infoAny;
112
+ return {
113
+ directory: dirName,
114
+ name: info.name || dirName,
115
+ gaiaName: info.gaia_name || "",
116
+ userName: info.user_name || "",
117
+ email: info.user_name || "",
118
+ avatarIcon: info.avatar_icon || "",
119
+ isUsingDefaultName: info.is_using_default_name ?? true,
120
+ isEphemeral: info.is_ephemeral ?? false,
121
+ activeTime: info.active_time || 0,
122
+ };
123
+ });
124
+ }
125
+ catch {
126
+ return [];
127
+ }
128
+ }
129
+ /**
130
+ * Combines filesystem profile discovery with Local State metadata
131
+ * to provide a comprehensive view of each profile's state.
132
+ *
133
+ * @param chromeDir - Chrome User Data path.
134
+ * @returns Enriched profile state objects.
135
+ */
136
+ export function getProfileStates(chromeDir) {
137
+ const dir = chromeDir || getDefaultChromeDir();
138
+ const discovered = discoverProfiles(dir);
139
+ const metadata = getProfileMetadata(dir);
140
+ const metaMap = new Map();
141
+ for (const m of metadata) {
142
+ metaMap.set(m.directory, m);
143
+ }
144
+ return discovered.map((profile) => {
145
+ const meta = metaMap.get(profile.name);
146
+ const profilePath = profile.path;
147
+ // Check which data files exist
148
+ const dataFiles = {};
149
+ for (const file of PROFILE_INDICATORS) {
150
+ dataFiles[file.replace(/\s+/g, "_").toLowerCase()] = existsSync(join(profilePath, file));
151
+ }
152
+ // Additional files
153
+ dataFiles.top_sites = existsSync(join(profilePath, "Top Sites"));
154
+ dataFiles.extensions = existsSync(join(profilePath, "Extensions"));
155
+ return {
156
+ directory: profile.name,
157
+ path: profilePath,
158
+ displayName: meta?.name || profile.name,
159
+ gaiaName: meta?.gaiaName || "",
160
+ email: meta?.email || "",
161
+ avatarIcon: meta?.avatarIcon || "",
162
+ isEphemeral: meta?.isEphemeral ?? false,
163
+ activeTime: meta?.activeTime || 0,
164
+ dataFiles,
165
+ };
166
+ });
167
+ }
168
+ /**
169
+ * Recursively computes the total size, in bytes, of a directory's contents.
170
+ * Symbolic links are not followed and unreadable entries are skipped so the
171
+ * walk never throws on a single permission error.
172
+ *
173
+ * @param dirPath - Absolute path to the directory to measure.
174
+ * @returns Total size of all contained files in bytes (0 if missing/unreadable).
175
+ */
176
+ export function getDirectorySize(dirPath) {
177
+ if (!dirPath || !existsSync(dirPath)) {
178
+ return 0;
179
+ }
180
+ let total = 0;
181
+ let entries;
182
+ try {
183
+ entries = readdirSync(dirPath, { withFileTypes: true });
184
+ }
185
+ catch {
186
+ return 0;
187
+ }
188
+ for (const entry of entries) {
189
+ const entryPath = join(dirPath, entry.name);
190
+ try {
191
+ if (entry.isSymbolicLink()) {
192
+ continue;
193
+ }
194
+ if (entry.isDirectory()) {
195
+ total += getDirectorySize(entryPath);
196
+ }
197
+ else if (entry.isFile()) {
198
+ total += statSync(entryPath).size;
199
+ }
200
+ }
201
+ catch {
202
+ // Skip entries we cannot stat (e.g. permission denied, race on deletion).
203
+ }
204
+ }
205
+ return total;
206
+ }
207
+ /**
208
+ * Resolves a profile name to its directory path.
209
+ *
210
+ * @param chromeDir - Chrome User Data path.
211
+ * @param profileName - Profile directory name (e.g., "Default", "Profile 1").
212
+ * @returns Absolute path to profile directory, or null if not found.
213
+ */
214
+ export function resolveProfilePath(chromeDir, profileName) {
215
+ const dir = chromeDir || getDefaultChromeDir();
216
+ const profilePath = join(dir, profileName);
217
+ return existsSync(profilePath) ? profilePath : null;
218
+ }
219
+ /**
220
+ * Helper to get the profiles to operate on based on options.
221
+ * If a specific profile is specified, returns only that one.
222
+ * Otherwise returns all discovered profiles.
223
+ *
224
+ * @param options - Resolution options.
225
+ * @returns Array of targeted profiles.
226
+ */
227
+ export function resolveTargetProfiles({ profilePath, chromeDir, profile, browser, } = {}) {
228
+ // Validate an explicit browser even when a custom path bypasses default-dir lookup.
229
+ const targetBrowser = browser ? requireBrowser(browser) : undefined;
230
+ const dir = profilePath || chromeDir || getDefaultChromeDir(targetBrowser);
231
+ const allProfiles = discoverProfiles(dir);
232
+ if (profile) {
233
+ const match = allProfiles.filter((p) => p.name === profile);
234
+ if (match.length === 0) {
235
+ throw new Error(`Profile "${profile}" not found in ${dir}`);
236
+ }
237
+ return match;
238
+ }
239
+ return allProfiles;
240
+ }
@@ -35,4 +35,4 @@ export declare function decryptValueBufferGcm(encryptedBlob: Buffer | Uint8Array
35
35
  * @param chromeTimestamp - Timestamp in microseconds.
36
36
  * @returns Human-readable date-time string, or "N/A".
37
37
  */
38
- export declare function chromeTimeToDate(chromeTimestamp: bigint | number | null | undefined): string;
38
+ export declare function chromeTimeToDate(chromeTimestamp: bigint | number | string | null | undefined): string;
@@ -1 +1,113 @@
1
- import{createDecipheriv as r}from"node:crypto";const t=Buffer.alloc(16," ");export function decryptValue(r,t){const e=decryptValueBuffer(r,t);return Buffer.isBuffer(e)?e.toString("utf-8"):e}export function decryptValueBuffer(e,n){if(!e||0===e.length)return Buffer.alloc(0);const f=Buffer.isBuffer(e)?e:Buffer.from(e),u=f.subarray(0,3).toString("utf-8");if("v10"!==u&&"v11"!==u)return"<unsupported encryption format>";const o=f.subarray(3);try{const e=r("aes-128-cbc",n,t);let f=e.update(o);return f=Buffer.concat([f,e.final()]),f}catch{return"<decryption failed>"}}export function decryptValueBufferGcm(t,e){if(!t||0===t.length)return Buffer.alloc(0);const n=Buffer.isBuffer(t)?t:Buffer.from(t),f=n.subarray(0,3).toString("utf-8");if("v10"!==f&&"v11"!==f)return"<unsupported encryption format>";if(n.length<31)return"<decryption failed>";const u=n.subarray(3,15),o=n.subarray(n.length-16),c=n.subarray(15,n.length-16);try{const t=r("aes-256-gcm",e,u);return t.setAuthTag(o),Buffer.concat([t.update(c),t.final()])}catch{return"<decryption failed>"}}export function chromeTimeToDate(r){if(!r||0===r||0n===r)return"N/A";try{const t=BigInt(r),e=Number((t-11644473600000000n)/1000n),n=new Date(e);return isNaN(n.getTime())?"N/A":n.toLocaleString()}catch{return"N/A"}}
1
+ import { createDecipheriv } from "node:crypto";
2
+ const AES_IV = Buffer.alloc(16, " "); // 16 spaces (0x20)
3
+ // Windows AES-256-GCM layout: "v10" prefix + 12-byte nonce + ciphertext + 16-byte tag.
4
+ const GCM_NONCE_LENGTH = 12;
5
+ const GCM_TAG_LENGTH = 16;
6
+ /**
7
+ * Decrypts a password/value buffer encrypted by Chrome on macOS or Linux.
8
+ * Chrome v10+ prefixes the encrypted blob with "v10" or "v11", followed by
9
+ * AES-128-CBC ciphertext. (Windows uses AES-256-GCM — see decryptValueBufferGcm.)
10
+ *
11
+ * @param encryptedBlob - The encrypted blob from the database.
12
+ * @param derivedKey - The derived 16-byte key.
13
+ * @returns The decrypted plaintext, or an error placeholder.
14
+ */
15
+ export function decryptValue(encryptedBlob, derivedKey) {
16
+ const decrypted = decryptValueBuffer(encryptedBlob, derivedKey);
17
+ return Buffer.isBuffer(decrypted) ? decrypted.toString("utf-8") : decrypted;
18
+ }
19
+ /**
20
+ * Decrypts a Chrome encrypted blob and returns the raw plaintext bytes.
21
+ *
22
+ * @param encryptedBlob - The encrypted blob from the database.
23
+ * @param derivedKey - The derived 16-byte key.
24
+ * @returns The decrypted plaintext bytes, or an error placeholder.
25
+ */
26
+ export function decryptValueBuffer(encryptedBlob, derivedKey) {
27
+ if (!encryptedBlob || encryptedBlob.length === 0) {
28
+ return Buffer.alloc(0);
29
+ }
30
+ const blob = Buffer.isBuffer(encryptedBlob)
31
+ ? encryptedBlob
32
+ : Buffer.from(encryptedBlob);
33
+ const prefix = blob.subarray(0, 3).toString("utf-8");
34
+ if (prefix !== "v10" && prefix !== "v11") {
35
+ return "<unsupported encryption format>";
36
+ }
37
+ const ciphertext = blob.subarray(3);
38
+ try {
39
+ const decipher = createDecipheriv("aes-128-cbc", derivedKey, AES_IV);
40
+ let decrypted = decipher.update(ciphertext);
41
+ decrypted = Buffer.concat([decrypted, decipher.final()]);
42
+ return decrypted;
43
+ }
44
+ catch {
45
+ return "<decryption failed>";
46
+ }
47
+ }
48
+ /**
49
+ * Decrypts a Windows Chrome `v10` blob using AES-256-GCM.
50
+ *
51
+ * Unlike the macOS/Linux CBC scheme, Windows blobs carry their own 12-byte
52
+ * nonce after the version prefix and a 16-byte GCM auth tag at the end. The
53
+ * `key` is the 32-byte AES key recovered from `Local State` via DPAPI.
54
+ *
55
+ * @param encryptedBlob - The encrypted blob from the database.
56
+ * @param key - The 32-byte AES-256 key.
57
+ * @returns The decrypted plaintext bytes, or a placeholder string.
58
+ */
59
+ export function decryptValueBufferGcm(encryptedBlob, key) {
60
+ if (!encryptedBlob || encryptedBlob.length === 0) {
61
+ return Buffer.alloc(0);
62
+ }
63
+ const blob = Buffer.isBuffer(encryptedBlob)
64
+ ? encryptedBlob
65
+ : Buffer.from(encryptedBlob);
66
+ const prefix = blob.subarray(0, 3).toString("utf-8");
67
+ if (prefix !== "v10" && prefix !== "v11") {
68
+ // Pre-v10 values are raw DPAPI blobs (no app-level key); not handled here.
69
+ return "<unsupported encryption format>";
70
+ }
71
+ if (blob.length < 3 + GCM_NONCE_LENGTH + GCM_TAG_LENGTH) {
72
+ return "<decryption failed>";
73
+ }
74
+ const nonce = blob.subarray(3, 3 + GCM_NONCE_LENGTH);
75
+ const tag = blob.subarray(blob.length - GCM_TAG_LENGTH);
76
+ const ciphertext = blob.subarray(3 + GCM_NONCE_LENGTH, blob.length - GCM_TAG_LENGTH);
77
+ try {
78
+ const decipher = createDecipheriv("aes-256-gcm", key, nonce);
79
+ decipher.setAuthTag(tag);
80
+ return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
81
+ }
82
+ catch {
83
+ return "<decryption failed>";
84
+ }
85
+ }
86
+ /**
87
+ * Converts a Chrome/Webkit timestamp (microseconds since 1601-01-01)
88
+ * to a readable local Date string.
89
+ *
90
+ * @param chromeTimestamp - Timestamp in microseconds.
91
+ * @returns Human-readable date-time string, or "N/A".
92
+ */
93
+ export function chromeTimeToDate(chromeTimestamp) {
94
+ if (chromeTimestamp === null || chromeTimestamp === undefined) {
95
+ return "N/A";
96
+ }
97
+ try {
98
+ const timestampBigInt = BigInt(chromeTimestamp);
99
+ if (timestampBigInt === 0n) {
100
+ return "N/A";
101
+ }
102
+ const unixMicroseconds = timestampBigInt - 11644473600000000n;
103
+ const ms = Number(unixMicroseconds / 1000n);
104
+ const date = new Date(ms);
105
+ if (isNaN(date.getTime())) {
106
+ return "N/A";
107
+ }
108
+ return date.toLocaleString();
109
+ }
110
+ catch {
111
+ return "N/A";
112
+ }
113
+ }
@@ -1,6 +1,8 @@
1
1
  /**
2
- * Copies a Chrome SQLite database to a temporary location, executes a query,
3
- * and cleans up. This avoids SQLite locking issues when Chrome is running.
2
+ * Creates a transactionally consistent snapshot of a Chrome SQLite database,
3
+ * executes a query against it, and cleans up. `VACUUM INTO` reads through
4
+ * SQLite itself, so live WAL contents are included without racing raw file
5
+ * copies while Chrome is writing.
4
6
  *
5
7
  * @param dbFilePath - Absolute path to the Chrome database file.
6
8
  * @param sql - SQL query to execute.