rechrome 1.10.2 → 1.11.0
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.
- package/package.json +1 -1
- package/rech.js +70 -11
- package/rech.ts +70 -11
- package/serve.js +1 -1
- package/serve.ts +1 -1
package/package.json
CHANGED
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(
|
|
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:
|
|
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();
|
|
@@ -486,6 +486,17 @@ async function runOxmgr(args: string[]): Promise<number> {
|
|
|
486
486
|
}
|
|
487
487
|
|
|
488
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
|
+
|
|
489
500
|
const home = process.env.HOME!;
|
|
490
501
|
const bunBin = Bun.which("bun") ?? process.execPath;
|
|
491
502
|
const rechScript = import.meta.filename;
|
|
@@ -543,6 +554,13 @@ async function setup(opts: { profile?: string } = {}): Promise<void> {
|
|
|
543
554
|
|
|
544
555
|
// [1/4] Daemon
|
|
545
556
|
console.log("\n[1/4] Setting up serve daemon...");
|
|
557
|
+
|
|
558
|
+
// Bind address (persists to ~/.env.local as RECH_HOST).
|
|
559
|
+
// Read the persisted value from ~/.env.local directly — process.env may be shadowed by nearer .env files.
|
|
560
|
+
const globalEnvRaw = await file(globalEnvFile).text().catch(() => "");
|
|
561
|
+
const persistedBindMatch = globalEnvRaw.match(/^\s*RECH_HOST\s*=\s*(.*?)\s*$/m);
|
|
562
|
+
const persistedBind = persistedBindMatch?.[1].replace(/^["']|["']$/g, "") || "127.0.0.1";
|
|
563
|
+
|
|
546
564
|
// Clear stale hostname-based URL so we always use 127.0.0.1 locally
|
|
547
565
|
if (process.env[ENV_KEY]) {
|
|
548
566
|
try {
|
|
@@ -560,14 +578,35 @@ async function setup(opts: { profile?: string } = {}): Promise<void> {
|
|
|
560
578
|
headers: { Authorization: `Bearer ${serveKey}` },
|
|
561
579
|
signal: AbortSignal.timeout(2000),
|
|
562
580
|
}).catch(() => null) : null;
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
581
|
+
// The daemon's *live* bind (from /ping) is authoritative — persisted RECH_HOST may diverge if the user edited it manually.
|
|
582
|
+
const liveBind = authPing?.ok
|
|
583
|
+
? await authPing.clone().json().then((b: { bind?: string }) => b?.bind).catch(() => undefined)
|
|
584
|
+
: undefined;
|
|
585
|
+
// Pre-patch daemons return plain "ok" with no bind info — we can't trust persisted/env values to match their live bind, so force reinstall to be safe.
|
|
586
|
+
const liveBindUnknown = !!authPing?.ok && !liveBind;
|
|
587
|
+
const currentBind = liveBind || persistedBind;
|
|
588
|
+
|
|
589
|
+
// Non-TTY honors explicit process.env.RECH_HOST (shell or merged env stack) — matches the documented `RECH_HOST=0.0.0.0 rech setup` flow.
|
|
590
|
+
let desiredBind = process.env.RECH_HOST || currentBind;
|
|
591
|
+
if (isTTY) {
|
|
592
|
+
console.log(`\n Bind address (current: ${currentBind}):`);
|
|
593
|
+
console.log(` 1. 127.0.0.1 (localhost only)`);
|
|
594
|
+
console.log(` 2. 0.0.0.0 (all interfaces — HTTP plaintext, trust your network)`);
|
|
595
|
+
const defaultBindChoice = currentBind === "0.0.0.0" ? "2" : "1";
|
|
596
|
+
const bindAns = (await ask(` Choice [${defaultBindChoice}]: `, defaultBindChoice)).trim();
|
|
597
|
+
desiredBind = bindAns === "2" || bindAns === "0.0.0.0" ? "0.0.0.0" : "127.0.0.1";
|
|
598
|
+
}
|
|
599
|
+
const bindChanged = desiredBind !== currentBind;
|
|
600
|
+
const persistedChanged = desiredBind !== persistedBind;
|
|
601
|
+
if (persistedChanged) {
|
|
602
|
+
const lines = globalEnvRaw.trimEnd().split("\n").filter(l => !/^\s*RECH_HOST\s*=/.test(l));
|
|
603
|
+
await Bun.write(globalEnvFile, [...lines, `RECH_HOST=${desiredBind}`, ""].join("\n"));
|
|
604
|
+
console.log(` Saved RECH_HOST=${desiredBind} to ~/.env.local`);
|
|
605
|
+
}
|
|
606
|
+
// Always align process.env with the desired bind — a nearer .env.local may have shadowed it.
|
|
607
|
+
process.env.RECH_HOST = desiredBind;
|
|
608
|
+
|
|
609
|
+
const waitForServe = async () => {
|
|
571
610
|
process.stdout.write(" Starting");
|
|
572
611
|
let ping = null;
|
|
573
612
|
for (let i = 0; i < 15; i++) {
|
|
@@ -583,6 +622,26 @@ async function setup(opts: { profile?: string } = {}): Promise<void> {
|
|
|
583
622
|
process.exit(1);
|
|
584
623
|
}
|
|
585
624
|
console.log(` Serve running at ${protocol}://${host}:${port}`);
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
if (anonPing && authPing?.ok && !bindChanged && !liveBindUnknown) {
|
|
628
|
+
console.log(` Already running at ${protocol}://${host}:${port} — skipping reinstall`);
|
|
629
|
+
} else if (anonPing && authPing?.ok && liveBindUnknown) {
|
|
630
|
+
console.log(` Pre-patch daemon detected (no live bind info) — reinstalling to verify bind`);
|
|
631
|
+
await daemonInstall(url);
|
|
632
|
+
await waitForServe();
|
|
633
|
+
} else if (anonPing && bindChanged) {
|
|
634
|
+
console.log(` Bind changed (${currentBind} → ${desiredBind}) — reinstalling`);
|
|
635
|
+
await daemonInstall(url);
|
|
636
|
+
await waitForServe();
|
|
637
|
+
} else if (anonPing && !authPing?.ok) {
|
|
638
|
+
console.log(` Server running but key mismatch — reinstalling with new key`);
|
|
639
|
+
await daemonInstall(url);
|
|
640
|
+
await waitForServe();
|
|
641
|
+
} else {
|
|
642
|
+
await daemonInstall(url);
|
|
643
|
+
console.log(` Registered daemon: ${OXMGR_PROCESS_NAME}`);
|
|
644
|
+
await waitForServe();
|
|
586
645
|
}
|
|
587
646
|
|
|
588
647
|
const cache = await readChromeProfileCache();
|
|
@@ -682,7 +741,7 @@ async function setup(opts: { profile?: string } = {}): Promise<void> {
|
|
|
682
741
|
|
|
683
742
|
// Build RECHROME_URL and show it before asking where to save
|
|
684
743
|
const rechUrl = new URL(url);
|
|
685
|
-
if (!rechUrl.username) rechUrl.username = randomBytes(
|
|
744
|
+
if (!rechUrl.username) rechUrl.username = randomBytes(12).toString("base64url");
|
|
686
745
|
rechUrl.searchParams.set("extension_id", extId);
|
|
687
746
|
rechUrl.searchParams.set("token", token);
|
|
688
747
|
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(
|
|
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:
|
|
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();
|
|
@@ -486,6 +486,17 @@ async function runOxmgr(args: string[]): Promise<number> {
|
|
|
486
486
|
}
|
|
487
487
|
|
|
488
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
|
+
|
|
489
500
|
const home = process.env.HOME!;
|
|
490
501
|
const bunBin = Bun.which("bun") ?? process.execPath;
|
|
491
502
|
const rechScript = import.meta.filename;
|
|
@@ -543,6 +554,13 @@ async function setup(opts: { profile?: string } = {}): Promise<void> {
|
|
|
543
554
|
|
|
544
555
|
// [1/4] Daemon
|
|
545
556
|
console.log("\n[1/4] Setting up serve daemon...");
|
|
557
|
+
|
|
558
|
+
// Bind address (persists to ~/.env.local as RECH_HOST).
|
|
559
|
+
// Read the persisted value from ~/.env.local directly — process.env may be shadowed by nearer .env files.
|
|
560
|
+
const globalEnvRaw = await file(globalEnvFile).text().catch(() => "");
|
|
561
|
+
const persistedBindMatch = globalEnvRaw.match(/^\s*RECH_HOST\s*=\s*(.*?)\s*$/m);
|
|
562
|
+
const persistedBind = persistedBindMatch?.[1].replace(/^["']|["']$/g, "") || "127.0.0.1";
|
|
563
|
+
|
|
546
564
|
// Clear stale hostname-based URL so we always use 127.0.0.1 locally
|
|
547
565
|
if (process.env[ENV_KEY]) {
|
|
548
566
|
try {
|
|
@@ -560,14 +578,35 @@ async function setup(opts: { profile?: string } = {}): Promise<void> {
|
|
|
560
578
|
headers: { Authorization: `Bearer ${serveKey}` },
|
|
561
579
|
signal: AbortSignal.timeout(2000),
|
|
562
580
|
}).catch(() => null) : null;
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
581
|
+
// The daemon's *live* bind (from /ping) is authoritative — persisted RECH_HOST may diverge if the user edited it manually.
|
|
582
|
+
const liveBind = authPing?.ok
|
|
583
|
+
? await authPing.clone().json().then((b: { bind?: string }) => b?.bind).catch(() => undefined)
|
|
584
|
+
: undefined;
|
|
585
|
+
// Pre-patch daemons return plain "ok" with no bind info — we can't trust persisted/env values to match their live bind, so force reinstall to be safe.
|
|
586
|
+
const liveBindUnknown = !!authPing?.ok && !liveBind;
|
|
587
|
+
const currentBind = liveBind || persistedBind;
|
|
588
|
+
|
|
589
|
+
// Non-TTY honors explicit process.env.RECH_HOST (shell or merged env stack) — matches the documented `RECH_HOST=0.0.0.0 rech setup` flow.
|
|
590
|
+
let desiredBind = process.env.RECH_HOST || currentBind;
|
|
591
|
+
if (isTTY) {
|
|
592
|
+
console.log(`\n Bind address (current: ${currentBind}):`);
|
|
593
|
+
console.log(` 1. 127.0.0.1 (localhost only)`);
|
|
594
|
+
console.log(` 2. 0.0.0.0 (all interfaces — HTTP plaintext, trust your network)`);
|
|
595
|
+
const defaultBindChoice = currentBind === "0.0.0.0" ? "2" : "1";
|
|
596
|
+
const bindAns = (await ask(` Choice [${defaultBindChoice}]: `, defaultBindChoice)).trim();
|
|
597
|
+
desiredBind = bindAns === "2" || bindAns === "0.0.0.0" ? "0.0.0.0" : "127.0.0.1";
|
|
598
|
+
}
|
|
599
|
+
const bindChanged = desiredBind !== currentBind;
|
|
600
|
+
const persistedChanged = desiredBind !== persistedBind;
|
|
601
|
+
if (persistedChanged) {
|
|
602
|
+
const lines = globalEnvRaw.trimEnd().split("\n").filter(l => !/^\s*RECH_HOST\s*=/.test(l));
|
|
603
|
+
await Bun.write(globalEnvFile, [...lines, `RECH_HOST=${desiredBind}`, ""].join("\n"));
|
|
604
|
+
console.log(` Saved RECH_HOST=${desiredBind} to ~/.env.local`);
|
|
605
|
+
}
|
|
606
|
+
// Always align process.env with the desired bind — a nearer .env.local may have shadowed it.
|
|
607
|
+
process.env.RECH_HOST = desiredBind;
|
|
608
|
+
|
|
609
|
+
const waitForServe = async () => {
|
|
571
610
|
process.stdout.write(" Starting");
|
|
572
611
|
let ping = null;
|
|
573
612
|
for (let i = 0; i < 15; i++) {
|
|
@@ -583,6 +622,26 @@ async function setup(opts: { profile?: string } = {}): Promise<void> {
|
|
|
583
622
|
process.exit(1);
|
|
584
623
|
}
|
|
585
624
|
console.log(` Serve running at ${protocol}://${host}:${port}`);
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
if (anonPing && authPing?.ok && !bindChanged && !liveBindUnknown) {
|
|
628
|
+
console.log(` Already running at ${protocol}://${host}:${port} — skipping reinstall`);
|
|
629
|
+
} else if (anonPing && authPing?.ok && liveBindUnknown) {
|
|
630
|
+
console.log(` Pre-patch daemon detected (no live bind info) — reinstalling to verify bind`);
|
|
631
|
+
await daemonInstall(url);
|
|
632
|
+
await waitForServe();
|
|
633
|
+
} else if (anonPing && bindChanged) {
|
|
634
|
+
console.log(` Bind changed (${currentBind} → ${desiredBind}) — reinstalling`);
|
|
635
|
+
await daemonInstall(url);
|
|
636
|
+
await waitForServe();
|
|
637
|
+
} else if (anonPing && !authPing?.ok) {
|
|
638
|
+
console.log(` Server running but key mismatch — reinstalling with new key`);
|
|
639
|
+
await daemonInstall(url);
|
|
640
|
+
await waitForServe();
|
|
641
|
+
} else {
|
|
642
|
+
await daemonInstall(url);
|
|
643
|
+
console.log(` Registered daemon: ${OXMGR_PROCESS_NAME}`);
|
|
644
|
+
await waitForServe();
|
|
586
645
|
}
|
|
587
646
|
|
|
588
647
|
const cache = await readChromeProfileCache();
|
|
@@ -682,7 +741,7 @@ async function setup(opts: { profile?: string } = {}): Promise<void> {
|
|
|
682
741
|
|
|
683
742
|
// Build RECHROME_URL and show it before asking where to save
|
|
684
743
|
const rechUrl = new URL(url);
|
|
685
|
-
if (!rechUrl.username) rechUrl.username = randomBytes(
|
|
744
|
+
if (!rechUrl.username) rechUrl.username = randomBytes(12).toString("base64url");
|
|
686
745
|
rechUrl.searchParams.set("extension_id", extId);
|
|
687
746
|
rechUrl.searchParams.set("token", token);
|
|
688
747
|
rechUrl.searchParams.set("profile", profileEmail);
|
package/serve.js
CHANGED
|
@@ -110,7 +110,7 @@ export async function serve() {
|
|
|
110
110
|
if (reqUrl.pathname === "/ping") {
|
|
111
111
|
const denied = authCheck(req, key);
|
|
112
112
|
if (denied) return denied;
|
|
113
|
-
return
|
|
113
|
+
return Response.json({ ok: true, bind: listenHost });
|
|
114
114
|
}
|
|
115
115
|
if (reqUrl.pathname !== "/run") return new Response("rech server\n");
|
|
116
116
|
const denied = authCheck(req, key);
|
package/serve.ts
CHANGED
|
@@ -110,7 +110,7 @@ export async function serve() {
|
|
|
110
110
|
if (reqUrl.pathname === "/ping") {
|
|
111
111
|
const denied = authCheck(req, key);
|
|
112
112
|
if (denied) return denied;
|
|
113
|
-
return
|
|
113
|
+
return Response.json({ ok: true, bind: listenHost });
|
|
114
114
|
}
|
|
115
115
|
if (reqUrl.pathname !== "/run") return new Response("rech server\n");
|
|
116
116
|
const denied = authCheck(req, key);
|