dirpush 0.1.4 → 0.1.5

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/browser.ts +31 -4
package/package.json CHANGED
@@ -37,7 +37,7 @@
37
37
  "url": "https://github.com/baronunread/dirpush.git"
38
38
  },
39
39
  "homepage": "https://dirpush.com",
40
- "version": "0.1.4",
40
+ "version": "0.1.5",
41
41
  "keywords": [
42
42
  "directories",
43
43
  "startup",
package/src/browser.ts CHANGED
@@ -78,8 +78,22 @@ export async function stopBrowser() {
78
78
  return { stopped: true };
79
79
  }
80
80
 
81
+ /**
82
+ * The CDP port of the agents' Chrome: the one `browser start` runs on BROWSER_PORT, else the one `dirpush ab` launched
83
+ * through agent-browser (its port is in the profile's DevToolsActivePort file). Only a live port counts.
84
+ */
85
+ async function agentsChromePort() {
86
+ const live = async (port: number) => fetch(`http://127.0.0.1:${port}/json/version`, { signal: AbortSignal.timeout(1500) }).then((r) => r.ok, () => false);
87
+ if (await live(BROWSER_PORT)) return BROWSER_PORT;
88
+ const file = Bun.file(join(PROFILE, "DevToolsActivePort"));
89
+ const port = (await file.exists()) ? Number((await file.text()).split("\n")[0]) : NaN;
90
+ if (port && (await live(port))) return port;
91
+ throw new Error("the agents' browser is not open: open the page with 'dirpush ab open <url>' first");
92
+ }
93
+ let cdpPort = BROWSER_PORT;
94
+
81
95
  function ab(args: string[], stdin?: string) {
82
- const p = Bun.spawnSync(["agent-browser", "--session", SECRETS_SESSION, "--cdp", String(BROWSER_PORT), ...args], { stdin: stdin === undefined ? "ignore" : Buffer.from(stdin), stdout: "pipe", stderr: "pipe", timeout: 60_000 });
96
+ const p = Bun.spawnSync(["agent-browser", "--session", SECRETS_SESSION, "--cdp", String(cdpPort), ...args], { stdin: stdin === undefined ? "ignore" : Buffer.from(stdin), stdout: "pipe", stderr: "pipe", timeout: 60_000 });
83
97
  const out = p.stdout.toString().trim();
84
98
  if (p.exitCode !== 0) throw new Error(`agent-browser ${args[0]} failed: ${(p.stderr.toString() || out).trim().slice(0, 300)}`);
85
99
  return out;
@@ -88,8 +102,9 @@ function ab(args: string[], stdin?: string) {
88
102
  /** The open tab on `site`'s host (the one the agent is working in); the most recently active one if several. */
89
103
  async function pageOn(site: string) {
90
104
  const host = new URL(site.includes("://") ? site : `https://${site}`).hostname.replace(/^www\./, "");
91
- if (!(await browserStatus()).running) throw new Error("the browser is not running: launch browser start");
92
- const page = (await json<Target[]>("/json/list")).find((t) => t.type === "page" && /^https?:/.test(t.url) && new URL(t.url).hostname.replace(/^www\./, "").endsWith(host));
105
+ cdpPort = await agentsChromePort();
106
+ const list = (await (await fetch(`http://127.0.0.1:${cdpPort}/json/list`, { signal: AbortSignal.timeout(1500) })).json()) as Target[];
107
+ const page = list.find((t) => t.type === "page" && /^https?:/.test(t.url) && new URL(t.url).hostname.replace(/^www\./, "").endsWith(host));
93
108
  if (!page) throw new Error(`no open tab on ${host}; open the login/sign-up page first`);
94
109
  ab(["tab", page.id]);
95
110
  return page;
@@ -97,6 +112,7 @@ async function pageOn(site: string) {
97
112
 
98
113
  /** Fills a saved login (agent-browser vault) into the open tab on that site. The password never leaves the vault. */
99
114
  export async function vaultLogin(name: string) {
115
+ cdpPort = await agentsChromePort();
100
116
  const meta = JSON.parse(ab(["auth", "show", name, "--json"])) as { data?: { profile?: { url?: string } } };
101
117
  const url = meta.data?.profile?.url;
102
118
  if (!url) throw new Error(`vault entry ${name} has no login URL`);
@@ -105,6 +121,17 @@ export async function vaultLogin(name: string) {
105
121
  return { vault: name, page: page.url.slice(0, 200), filled: true };
106
122
  }
107
123
 
124
+ /** 24 random characters with every class sites ask for (upper, lower, digit, symbol), symbols that forms accept. */
125
+ export function strongPassword() {
126
+ const sets = ["ABCDEFGHJKLMNPQRSTUVWXYZ", "abcdefghijkmnopqrstuvwxyz", "23456789", "!@#%^*-_=+?"];
127
+ const all = sets.join("");
128
+ const pick = (set: string, b: number) => set[b % set.length]!;
129
+ const bytes = randomBytes(32);
130
+ const chars = [...sets.map((set, i) => pick(set, bytes[i]!)), ...Array.from({ length: 20 }, (_, i) => pick(all, bytes[i + 4]!))];
131
+ for (let i = chars.length - 1; i > 0; i--) { const j = bytes[(i + 8) % 32]! % (i + 1); [chars[i], chars[j]] = [chars[j]!, chars[i]!]; }
132
+ return chars.join("");
133
+ }
134
+
108
135
  /**
109
136
  * New account password: generated here, typed into every given field of the open tab on `url`'s site, checked,
110
137
  * then saved to the vault as `name`. Returns no secret.
@@ -112,7 +139,7 @@ export async function vaultLogin(name: string) {
112
139
  export async function newPassword(name: string, url: string, username: string, selectors: string[]) {
113
140
  if (!selectors.length) throw new Error("pass the password field selector(s), e.g. 'input[type=password]'");
114
141
  const page = await pageOn(url);
115
- const pw = randomBytes(18).toString("base64");
142
+ const pw = strongPassword();
116
143
  for (const s of selectors) ab(["fill", s, pw]);
117
144
  // A fill can "succeed" on a selector that matched nothing; never save a password the site didn't get.
118
145
  const lengths = JSON.parse(ab(["eval", `JSON.stringify(${JSON.stringify(selectors)}.map((s) => document.querySelector(s)?.value.length ?? -1))`]));