chrome-tools 1.1.7 → 1.1.8

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,5 +1,7 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
1
3
  import { join } from "node:path";
2
- import { resolveTargetProfiles } from "../core/chrome.js";
4
+ import { discoverProfiles, resolveTargetProfiles, } from "../core/chrome.js";
3
5
  import { deriveEncryptionKey, decryptValue } from "../core/platform/index.js";
4
6
  import { chromeTimeToDate } from "../core/crypto.js";
5
7
  import { queryDatabase } from "../core/db.js";
@@ -14,6 +16,44 @@ const PASSWORD_STORES = [
14
16
  { file: "Login Data", source: "device" },
15
17
  { file: "Login Data For Account", source: "account" },
16
18
  ];
19
+ /** Where a CDP launcher keeps the User Data directory it drives Chrome with. */
20
+ const AUTOMATION_DIR = join(homedir(), ".chrome_cdp_profile");
21
+ /** …and where it records a relocated one. */
22
+ const AUTOMATION_CONFIG = join(homedir(), ".config", "chrome-cdp-manager", "config.json");
23
+ /** `CHROME_AUTOMATION_DIR` values meaning "do not look there at all". */
24
+ const AUTOMATION_OFF = ["0", "off", "none", "false"];
25
+ /**
26
+ * The automation profile tree, on a machine that has one.
27
+ *
28
+ * Most machines do not: no CDP launcher is installed, the directory was never
29
+ * created, or it exists but holds no profile yet. All three answer undefined,
30
+ * which leaves the search exactly as it was before this existed — the everyday
31
+ * profile, alone. Nothing here is required for a plain Chrome install.
32
+ *
33
+ * `CHROME_AUTOMATION_DIR` names the tree explicitly, and `off` (or `0`, `none`,
34
+ * `false`) skips it even where one exists.
35
+ */
36
+ function automationChromeDir() {
37
+ const override = process.env.CHROME_AUTOMATION_DIR?.trim();
38
+ if (override && AUTOMATION_OFF.includes(override.toLowerCase()))
39
+ return undefined;
40
+ const dir = override || configuredAutomationDir() || AUTOMATION_DIR;
41
+ // Holding a profile is what makes it a tree worth reading — an absent or
42
+ // empty directory would otherwise add a root every lookup searches for
43
+ // nothing, and turn a missing `--profile` into a spurious refusal.
44
+ return discoverProfiles(dir).length > 0 ? dir : undefined;
45
+ }
46
+ /** The profile directory a CDP launcher was configured with, if it says. */
47
+ function configuredAutomationDir() {
48
+ try {
49
+ const stored = JSON.parse(readFileSync(AUTOMATION_CONFIG, "utf-8"));
50
+ return typeof stored.profileDir === "string" && stored.profileDir ? stored.profileDir : undefined;
51
+ }
52
+ catch {
53
+ // No launcher config, or an unreadable one: fall back to its default path.
54
+ return undefined;
55
+ }
56
+ }
17
57
  /**
18
58
  * Extracts and decrypts saved passwords from Chrome's password databases.
19
59
  * Reads both the device-local and account-synced stores, labelling each entry
@@ -24,41 +64,68 @@ const PASSWORD_STORES = [
24
64
  */
25
65
  export function getPasswords({ chromeDir, profilePath, profile, search, browser, decryptionKey, } = {}) {
26
66
  const userDataDir = profilePath || chromeDir;
27
- const profiles = resolveTargetProfiles({ chromeDir, profilePath, profile, browser });
28
- // Pass user-data dir so Windows reads Local State from the same tree as the profiles.
29
- let derivedKey = decryptionKey;
67
+ // The automation User Data directory first, then the everyday one. A Chrome
68
+ // driven over CDP keeps its own profile tree, so a login saved while signing
69
+ // in to that window is stored there and nowhere else — looking only at the
70
+ // everyday tree could not find it. Naming a directory or a browser answers
71
+ // "which tree?" outright, and is used as given.
72
+ const roots = [];
73
+ const automation = userDataDir || browser ? undefined : automationChromeDir();
74
+ if (automation)
75
+ roots.push(automation);
76
+ roots.push(userDataDir);
30
77
  const results = [];
31
78
  const term = search?.toLowerCase();
32
- for (const p of profiles) {
33
- for (const store of PASSWORD_STORES) {
34
- const dbPath = join(p.path, store.file);
35
- try {
36
- const rows = queryDatabase(dbPath, `SELECT origin_url, username_value, password_value, date_last_used
37
- FROM logins ORDER BY origin_url`);
38
- for (const row of rows) {
39
- const entry = {
40
- profile: p.name,
41
- source: store.source,
42
- url: row.origin_url,
43
- username: row.username_value,
44
- password: row.password_value?.length > 0
45
- ? decryptValue(row.password_value, derivedKey ??= deriveEncryptionKey(userDataDir, browser))
46
- : "",
47
- lastUsed: chromeTimeToDate(row.date_last_used || 0),
48
- };
49
- if (term) {
50
- if (!(entry.url && entry.url.toLowerCase().includes(term)) &&
51
- !(entry.username && entry.username.toLowerCase().includes(term))) {
52
- continue;
79
+ // A named profile missing from one tree is not a failure while another is
80
+ // still to come — but a profile in none of them is still an error, so the
81
+ // first refusal is kept and raised if no tree resolved at all.
82
+ let refusal;
83
+ let resolved = false;
84
+ for (const root of roots) {
85
+ let profiles;
86
+ try {
87
+ profiles = resolveTargetProfiles({ chromeDir: root, profile, browser });
88
+ }
89
+ catch (error) {
90
+ refusal ??= error;
91
+ continue;
92
+ }
93
+ resolved = true;
94
+ // Per root: on Windows the key comes from that tree's own Local State.
95
+ let derivedKey = decryptionKey;
96
+ for (const p of profiles) {
97
+ for (const store of PASSWORD_STORES) {
98
+ const dbPath = join(p.path, store.file);
99
+ try {
100
+ const rows = queryDatabase(dbPath, `SELECT origin_url, username_value, password_value, date_last_used
101
+ FROM logins ORDER BY origin_url`);
102
+ for (const row of rows) {
103
+ const entry = {
104
+ profile: p.name,
105
+ source: store.source,
106
+ url: row.origin_url,
107
+ username: row.username_value,
108
+ password: row.password_value?.length > 0
109
+ ? decryptValue(row.password_value, derivedKey ??= deriveEncryptionKey(root, browser))
110
+ : "",
111
+ lastUsed: chromeTimeToDate(row.date_last_used || 0),
112
+ };
113
+ if (term) {
114
+ if (!(entry.url && entry.url.toLowerCase().includes(term)) &&
115
+ !(entry.username && entry.username.toLowerCase().includes(term))) {
116
+ continue;
117
+ }
53
118
  }
119
+ results.push(entry);
54
120
  }
55
- results.push(entry);
56
121
  }
57
- }
58
- catch {
59
- // Skip missing or unreadable stores and continue with the others.
122
+ catch {
123
+ // Skip missing or unreadable stores and continue with the others.
124
+ }
60
125
  }
61
126
  }
62
127
  }
128
+ if (!resolved && refusal)
129
+ throw refusal;
63
130
  return results;
64
131
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-tools",
3
- "version": "1.1.7",
3
+ "version": "1.1.8",
4
4
  "description": "CLI & Node.js library for chrome",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",