rechrome 1.10.1 → 1.10.3

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 (5) hide show
  1. package/package.json +1 -1
  2. package/rech.js +20 -4
  3. package/rech.ts +20 -4
  4. package/serve.js +20 -13
  5. package/serve.ts +20 -13
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rechrome",
3
- "version": "1.10.1",
3
+ "version": "1.10.3",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/snomiao/rechrome.git"
package/rech.js CHANGED
@@ -120,7 +120,7 @@ export function parseUrl(raw: string) {
120
120
  export async function getOrCreateUrl(): Promise<string> {
121
121
  // Treat a URL without a bearer key as missing — it cannot authenticate
122
122
  try { if (process.env[ENV_KEY] && new URL(process.env[ENV_KEY]!).username) return process.env[ENV_KEY]!; } catch {}
123
- const key = randomBytes(9).toString("base64url"); // 12 chars
123
+ const key = randomBytes(12).toString("base64url"); // 16 chars
124
124
  const url = `http://${key}@127.0.0.1:${DEFAULT_PORT}`;
125
125
  const newLine = `${ENV_KEY}=${url}`;
126
126
  // Write to ~/.env.local so it's not shadowed by project .env.local
@@ -362,7 +362,7 @@ async function callServe(
362
362
  process.exit(1);
363
363
  });
364
364
  if (res.status === 401) {
365
- console.error(`[rech] rech-client -> rech-server[ok]\n -x: token rejected (used: ${key.slice(0, 6)}...) -> playwright[unknown]`);
365
+ console.error(`[rech] rech-client -> rech-server[ok]\n -x: bearer key rejected (used: ${key.slice(0, 4)}...) -> playwright[unknown]`);
366
366
  process.exit(1);
367
367
  }
368
368
  return res.json();
@@ -381,6 +381,11 @@ async function run(url: string, args: string[]) {
381
381
  const resolvedEnv = await getClientEnv({ extensionId, extensionToken, profileDirectory, userDataDir });
382
382
  const { status, stdout, stderr, files, existingSession } = await callServe(url, args);
383
383
 
384
+ const isOpenWithUrl = args[0] === "open" && args.length > 1;
385
+ if (existingSession && isOpenWithUrl) {
386
+ return run(url, ["goto", ...args.slice(1)]);
387
+ }
388
+
384
389
  if (existingSession)
385
390
  console.error(`[rech] session already has open tabs — listing existing tabs instead of opening a new window`);
386
391
  if (stderr) {
@@ -406,7 +411,7 @@ async function run(url: string, args: string[]) {
406
411
  });
407
412
  if (!fileRes.ok) continue;
408
413
  const dest = join(dlDir, basename(name));
409
- await Bun.write(dest, fileRes);
414
+ await Bun.write(dest, await fileRes.arrayBuffer());
410
415
  console.error(`[rech] downloaded: ${dest}`);
411
416
  }
412
417
  }
@@ -481,6 +486,17 @@ async function runOxmgr(args: string[]): Promise<number> {
481
486
  }
482
487
 
483
488
  async function daemonInstall(serveUrl: string): Promise<void> {
489
+ // Persist the URL to ~/.env.local before starting the daemon. The daemon's
490
+ // loadEnv() walks CWD→root reading .env.local files and unconditionally
491
+ // overwrites process.env.RECHROME_URL from whichever file it finds first.
492
+ // Without this write, oxmgr's --env RECHROME_URL=... gets clobbered by a
493
+ // stale ~/.env.local entry — the daemon then listens on a different bearer
494
+ // key than the one daemonInstall was called with, and every client request
495
+ // is rejected with "bearer key rejected".
496
+ const envRaw = await file(globalEnvFile).text().catch(() => "");
497
+ const filtered = envRaw.trimEnd().split("\n").filter(l => !l.startsWith(`${ENV_KEY}=`));
498
+ await Bun.write(globalEnvFile, [...filtered, `${ENV_KEY}=${serveUrl}`, ""].join("\n"));
499
+
484
500
  const home = process.env.HOME!;
485
501
  const bunBin = Bun.which("bun") ?? process.execPath;
486
502
  const rechScript = import.meta.filename;
@@ -677,7 +693,7 @@ async function setup(opts: { profile?: string } = {}): Promise<void> {
677
693
 
678
694
  // Build RECHROME_URL and show it before asking where to save
679
695
  const rechUrl = new URL(url);
680
- if (!rechUrl.username) rechUrl.username = randomBytes(9).toString("base64url");
696
+ if (!rechUrl.username) rechUrl.username = randomBytes(12).toString("base64url");
681
697
  rechUrl.searchParams.set("extension_id", extId);
682
698
  rechUrl.searchParams.set("token", token);
683
699
  rechUrl.searchParams.set("profile", profileEmail);
package/rech.ts CHANGED
@@ -120,7 +120,7 @@ export function parseUrl(raw: string) {
120
120
  export async function getOrCreateUrl(): Promise<string> {
121
121
  // Treat a URL without a bearer key as missing — it cannot authenticate
122
122
  try { if (process.env[ENV_KEY] && new URL(process.env[ENV_KEY]!).username) return process.env[ENV_KEY]!; } catch {}
123
- const key = randomBytes(9).toString("base64url"); // 12 chars
123
+ const key = randomBytes(12).toString("base64url"); // 16 chars
124
124
  const url = `http://${key}@127.0.0.1:${DEFAULT_PORT}`;
125
125
  const newLine = `${ENV_KEY}=${url}`;
126
126
  // Write to ~/.env.local so it's not shadowed by project .env.local
@@ -362,7 +362,7 @@ async function callServe(
362
362
  process.exit(1);
363
363
  });
364
364
  if (res.status === 401) {
365
- console.error(`[rech] rech-client -> rech-server[ok]\n -x: token rejected (used: ${key.slice(0, 6)}...) -> playwright[unknown]`);
365
+ console.error(`[rech] rech-client -> rech-server[ok]\n -x: bearer key rejected (used: ${key.slice(0, 4)}...) -> playwright[unknown]`);
366
366
  process.exit(1);
367
367
  }
368
368
  return res.json();
@@ -381,6 +381,11 @@ async function run(url: string, args: string[]) {
381
381
  const resolvedEnv = await getClientEnv({ extensionId, extensionToken, profileDirectory, userDataDir });
382
382
  const { status, stdout, stderr, files, existingSession } = await callServe(url, args);
383
383
 
384
+ const isOpenWithUrl = args[0] === "open" && args.length > 1;
385
+ if (existingSession && isOpenWithUrl) {
386
+ return run(url, ["goto", ...args.slice(1)]);
387
+ }
388
+
384
389
  if (existingSession)
385
390
  console.error(`[rech] session already has open tabs — listing existing tabs instead of opening a new window`);
386
391
  if (stderr) {
@@ -406,7 +411,7 @@ async function run(url: string, args: string[]) {
406
411
  });
407
412
  if (!fileRes.ok) continue;
408
413
  const dest = join(dlDir, basename(name));
409
- await Bun.write(dest, fileRes);
414
+ await Bun.write(dest, await fileRes.arrayBuffer());
410
415
  console.error(`[rech] downloaded: ${dest}`);
411
416
  }
412
417
  }
@@ -481,6 +486,17 @@ async function runOxmgr(args: string[]): Promise<number> {
481
486
  }
482
487
 
483
488
  async function daemonInstall(serveUrl: string): Promise<void> {
489
+ // Persist the URL to ~/.env.local before starting the daemon. The daemon's
490
+ // loadEnv() walks CWD→root reading .env.local files and unconditionally
491
+ // overwrites process.env.RECHROME_URL from whichever file it finds first.
492
+ // Without this write, oxmgr's --env RECHROME_URL=... gets clobbered by a
493
+ // stale ~/.env.local entry — the daemon then listens on a different bearer
494
+ // key than the one daemonInstall was called with, and every client request
495
+ // is rejected with "bearer key rejected".
496
+ const envRaw = await file(globalEnvFile).text().catch(() => "");
497
+ const filtered = envRaw.trimEnd().split("\n").filter(l => !l.startsWith(`${ENV_KEY}=`));
498
+ await Bun.write(globalEnvFile, [...filtered, `${ENV_KEY}=${serveUrl}`, ""].join("\n"));
499
+
484
500
  const home = process.env.HOME!;
485
501
  const bunBin = Bun.which("bun") ?? process.execPath;
486
502
  const rechScript = import.meta.filename;
@@ -677,7 +693,7 @@ async function setup(opts: { profile?: string } = {}): Promise<void> {
677
693
 
678
694
  // Build RECHROME_URL and show it before asking where to save
679
695
  const rechUrl = new URL(url);
680
- if (!rechUrl.username) rechUrl.username = randomBytes(9).toString("base64url");
696
+ if (!rechUrl.username) rechUrl.username = randomBytes(12).toString("base64url");
681
697
  rechUrl.searchParams.set("extension_id", extId);
682
698
  rechUrl.searchParams.set("token", token);
683
699
  rechUrl.searchParams.set("profile", profileEmail);
package/serve.js CHANGED
@@ -173,8 +173,8 @@ export async function serve() {
173
173
 
174
174
  // For open commands, default to about:blank to avoid leaving connect.html visible
175
175
  const isOpenCmd = filteredArgs[0] === "open";
176
- if (isOpenCmd && filteredArgs.length === 1)
177
- filteredArgs.push("about:blank");
176
+ const isOpenNoUrl = isOpenCmd && filteredArgs.length === 1;
177
+ if (isOpenNoUrl) filteredArgs.push("about:blank");
178
178
 
179
179
  // bare `rech open` with no URL: warn if session already has tabs
180
180
  if (isOpenCmd && filteredArgs.length === 1) {
@@ -186,19 +186,26 @@ export async function serve() {
186
186
  stderr: "pipe",
187
187
  env: { PATH: process.env.PATH, HOME: process.env.HOME },
188
188
  });
189
- const [listStatus, listOut] = await Promise.all([
190
- listProc.exited,
191
- new Response(listProc.stdout).text(),
189
+ const [listStatus, listOut] = await Promise.race([
190
+ Promise.all([listProc.exited, new Response(listProc.stdout).text()]),
191
+ new Promise<[number, string]>((resolve) =>
192
+ setTimeout(() => { listProc.kill(); resolve([1, ""]); }, 5000)
193
+ ),
192
194
  ]);
193
195
  if (listStatus === 0 && listOut.trim()) {
194
- log(`session ${namespacedSession} already has tabs, returning tab-list hint`);
195
- return Response.json({
196
- status: 0,
197
- stdout: listOut,
198
- stderr: `[rech] session "${namespacedSession}" already has open tabs:\n`,
199
- files: [],
200
- existingSession: true,
201
- });
196
+ if (isOpenNoUrl) {
197
+ log(`session ${namespacedSession} already has tabs, returning tab-list hint`);
198
+ return Response.json({
199
+ status: 0,
200
+ stdout: listOut,
201
+ stderr: `[rech] session "${namespacedSession}" already has open tabs:\n`,
202
+ files: [],
203
+ existingSession: true,
204
+ });
205
+ }
206
+ // URL specified: navigate to it instead of returning tab-list
207
+ log(`session ${namespacedSession} already has tabs, converting open to goto`);
208
+ filteredArgs[0] = "goto";
202
209
  }
203
210
  } catch (e) {
204
211
  log(`tab-list check failed: ${e}`);
package/serve.ts CHANGED
@@ -173,8 +173,8 @@ export async function serve() {
173
173
 
174
174
  // For open commands, default to about:blank to avoid leaving connect.html visible
175
175
  const isOpenCmd = filteredArgs[0] === "open";
176
- if (isOpenCmd && filteredArgs.length === 1)
177
- filteredArgs.push("about:blank");
176
+ const isOpenNoUrl = isOpenCmd && filteredArgs.length === 1;
177
+ if (isOpenNoUrl) filteredArgs.push("about:blank");
178
178
 
179
179
  // bare `rech open` with no URL: warn if session already has tabs
180
180
  if (isOpenCmd && filteredArgs.length === 1) {
@@ -186,19 +186,26 @@ export async function serve() {
186
186
  stderr: "pipe",
187
187
  env: { PATH: process.env.PATH, HOME: process.env.HOME },
188
188
  });
189
- const [listStatus, listOut] = await Promise.all([
190
- listProc.exited,
191
- new Response(listProc.stdout).text(),
189
+ const [listStatus, listOut] = await Promise.race([
190
+ Promise.all([listProc.exited, new Response(listProc.stdout).text()]),
191
+ new Promise<[number, string]>((resolve) =>
192
+ setTimeout(() => { listProc.kill(); resolve([1, ""]); }, 5000)
193
+ ),
192
194
  ]);
193
195
  if (listStatus === 0 && listOut.trim()) {
194
- log(`session ${namespacedSession} already has tabs, returning tab-list hint`);
195
- return Response.json({
196
- status: 0,
197
- stdout: listOut,
198
- stderr: `[rech] session "${namespacedSession}" already has open tabs:\n`,
199
- files: [],
200
- existingSession: true,
201
- });
196
+ if (isOpenNoUrl) {
197
+ log(`session ${namespacedSession} already has tabs, returning tab-list hint`);
198
+ return Response.json({
199
+ status: 0,
200
+ stdout: listOut,
201
+ stderr: `[rech] session "${namespacedSession}" already has open tabs:\n`,
202
+ files: [],
203
+ existingSession: true,
204
+ });
205
+ }
206
+ // URL specified: navigate to it instead of returning tab-list
207
+ log(`session ${namespacedSession} already has tabs, converting open to goto`);
208
+ filteredArgs[0] = "goto";
202
209
  }
203
210
  } catch (e) {
204
211
  log(`tab-list check failed: ${e}`);