rechrome 1.38.0 → 1.38.1
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/rechrome.js +46 -3
- package/rechrome.ts +46 -3
package/package.json
CHANGED
package/rechrome.js
CHANGED
|
@@ -1493,6 +1493,43 @@ export function chooseShareListener(profile: string, listeners: Listener[]): str
|
|
|
1493
1493
|
].filter(Boolean).join("\n"), "none", scoped);
|
|
1494
1494
|
}
|
|
1495
1495
|
|
|
1496
|
+
/**
|
|
1497
|
+
* Why a saved link to one of this machine's listeners stopped working, with the command that
|
|
1498
|
+
* replaces it; null when it still matches its listener or points at another machine.
|
|
1499
|
+
* A removed listener's port goes to the next one created (`freeListenerPort`), so a stale
|
|
1500
|
+
* link can reach a different listener: its path is not served there and its key is unknown.
|
|
1501
|
+
*/
|
|
1502
|
+
export function staleLinkHint(url: string, listeners: Listener[], source?: string): string | null {
|
|
1503
|
+
let parsed: ReturnType<typeof parseUrl>;
|
|
1504
|
+
try { parsed = parseUrl(url); } catch { return null; }
|
|
1505
|
+
const loopback = (host: string) => isLoopback(host) || host === "localhost";
|
|
1506
|
+
const urlHost = parsed.host.replace(/^\[|\]$/g, "");
|
|
1507
|
+
const at = listeners.find(l => l.port === parsed.port && (l.host === urlHost || l.host === "0.0.0.0" || loopback(l.host) && loopback(urlHost)));
|
|
1508
|
+
const owner = listeners.find(l => l.key === parsed.key);
|
|
1509
|
+
if (!at && !owner && !loopback(urlHost)) return null; // another machine's daemon: only its owner knows
|
|
1510
|
+
if (owner && owner === at && normalizePrefix(owner.prefix) === parsed.prefix) return null;
|
|
1511
|
+
const profile = parsed.profileDirectory;
|
|
1512
|
+
let fix = profile ? `rech share ${JSON.stringify(profile)} --save` : "rech share --save";
|
|
1513
|
+
if (profile) {
|
|
1514
|
+
try {
|
|
1515
|
+
const name = owner && owner.profiles !== "*" ? owner.name : chooseShareListener(profile, listeners);
|
|
1516
|
+
fix = `rech share ${JSON.stringify(profile)} --listener ${name}${owner?.publicUrl || listeners.find(l => l.name === name)?.publicUrl ? "" : " --local"} --save`;
|
|
1517
|
+
} catch { /* no single listener shares it: the default creates a fresh link */ }
|
|
1518
|
+
}
|
|
1519
|
+
const lines = [owner
|
|
1520
|
+
? `this link's listener "${owner.name}" moved: it now serves ${listenerAddress(owner)}${normalizePrefix(owner.prefix)}, not ${parsed.host}:${parsed.port}${parsed.prefix}.`
|
|
1521
|
+
: `this link's key matches no listener on this machine: its listener was removed or its key rotated.${at ? ` ${listenerAddress(at)} belongs to listener "${at.name}" (${normalizePrefix(at.prefix)}), which this link is not for.` : ""}`];
|
|
1522
|
+
if (source && source !== "environment") lines.push(`The stale RECHROME_URL is saved in ${source}.`);
|
|
1523
|
+
lines.push(`Replace it with a current link: ${fix}`);
|
|
1524
|
+
return lines.join("\n ");
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
/** staleLinkHint against this machine's listeners.json; null when it can't be read. */
|
|
1528
|
+
async function localStaleLinkHint(url: string): Promise<string | null> {
|
|
1529
|
+
const config = await readListeners().catch(() => null);
|
|
1530
|
+
return config ? staleLinkHint(url, config.listeners, url === process.env[ENV_KEY] ? rechromeUrlSource : undefined) : null;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1496
1533
|
/** A loopback port no listener uses and nothing else is bound to, from the management port + 2. */
|
|
1497
1534
|
function freeListenerPort(listeners: Listener[]): number {
|
|
1498
1535
|
const used = new Set(listeners.map(l => l.port));
|
|
@@ -1918,8 +1955,9 @@ async function callServe(
|
|
|
1918
1955
|
process.exit(1);
|
|
1919
1956
|
});
|
|
1920
1957
|
if (res.status === 401) {
|
|
1921
|
-
|
|
1922
|
-
|
|
1958
|
+
const stale = await localStaleLinkHint(url);
|
|
1959
|
+
if (throwOnFailure) throw new Error(stale ? `The daemon rejected this link's key: ${stale}` : "The daemon rejected its connection key. Run setup again to refresh it.");
|
|
1960
|
+
console.error(`[rech] rech-client -> rech-server[ok]\n -x: bearer key rejected (used: ${key.slice(0, 4)}...) -> playwright[unknown]${stale ? `\n ${stale}` : ""}`);
|
|
1923
1961
|
process.exit(1);
|
|
1924
1962
|
}
|
|
1925
1963
|
const text = await res.text();
|
|
@@ -1930,6 +1968,8 @@ async function callServe(
|
|
|
1930
1968
|
let detail = `HTTP ${res.status} from ${serviceUrl(url, "run")} is not a rechrome daemon response: ${text.slice(0, 200).trim()}`;
|
|
1931
1969
|
if (res.status === 502 || res.status === 503 || res.status === 504)
|
|
1932
1970
|
detail += `\n The proxy is up but nothing answers behind it: this link's listener was likely removed or moved. Ask its owner for a fresh link (\`rech share\`), then \`rechrome connect <link>\`.`;
|
|
1971
|
+
const stale = await localStaleLinkHint(url);
|
|
1972
|
+
if (stale) detail += `\n ${stale}`;
|
|
1933
1973
|
if (throwOnFailure) throw new Error(detail);
|
|
1934
1974
|
console.error(`[rech] rech-client -> ${serviceUrl(url, "run")}\n -x: ${detail}`);
|
|
1935
1975
|
process.exit(1);
|
|
@@ -4135,7 +4175,8 @@ async function status(): Promise<void> {
|
|
|
4135
4175
|
// Show the URL this client connects to; through a proxy, the daemon's bind is on another port.
|
|
4136
4176
|
const details = [pingBody?.listener && `listener ${pingBody.listener}`, pingBody?.bind && `bind ${pingBody.bind}`].filter(Boolean).join(", ");
|
|
4137
4177
|
// Any HTTP answer is not the daemon: a proxy's 404/502 means the link's listener is gone or moved.
|
|
4138
|
-
|
|
4178
|
+
// A 200 that isn't the /ping JSON is another listener's catch-all: its port was reused.
|
|
4179
|
+
const answered = (pingResponse?.ok && !!pingBody) || pingResponse?.status === 401 || (pingResponse?.status === 403 && !!pingBody?.error);
|
|
4139
4180
|
console.log(`serve: ${!ping ? `not reachable at ${serviceUrl(url)}`
|
|
4140
4181
|
: answered ? `running ${serviceUrl(url)}${details ? ` (${details})` : ""}`
|
|
4141
4182
|
: `✗ ${serviceUrl(url)} answers HTTP ${pingResponse?.status ?? ping.status}, not a rechrome daemon — this link's listener was likely removed or moved; ask its host for a fresh link (\`rech share <profile>\`), then \`rech connect '<url>'\``}`);
|
|
@@ -4147,6 +4188,8 @@ async function status(): Promise<void> {
|
|
|
4147
4188
|
if (pingResponse?.status === 403 && pingBody?.error) console.log(`auth: ✗ ${pingBody.error}`);
|
|
4148
4189
|
if (pingResponse?.status === 401)
|
|
4149
4190
|
console.log(`auth: ✗ key rejected — ask the host for a fresh URL (\`rech share <profile>\`), then \`rech connect '<url>'\``);
|
|
4191
|
+
const stale = ping && !pingBody ? await localStaleLinkHint(url) : null;
|
|
4192
|
+
if (stale) console.log(`stale: ✗ ${stale}`);
|
|
4150
4193
|
// daemonManager().id — there is no PM_BIN constant. Referencing one threw a
|
|
4151
4194
|
// ReferenceError that took down the whole of `rech status`, so the one command
|
|
4152
4195
|
// that reports "the relay is wedged" died exactly when the relay was wedged,
|
package/rechrome.ts
CHANGED
|
@@ -1493,6 +1493,43 @@ export function chooseShareListener(profile: string, listeners: Listener[]): str
|
|
|
1493
1493
|
].filter(Boolean).join("\n"), "none", scoped);
|
|
1494
1494
|
}
|
|
1495
1495
|
|
|
1496
|
+
/**
|
|
1497
|
+
* Why a saved link to one of this machine's listeners stopped working, with the command that
|
|
1498
|
+
* replaces it; null when it still matches its listener or points at another machine.
|
|
1499
|
+
* A removed listener's port goes to the next one created (`freeListenerPort`), so a stale
|
|
1500
|
+
* link can reach a different listener: its path is not served there and its key is unknown.
|
|
1501
|
+
*/
|
|
1502
|
+
export function staleLinkHint(url: string, listeners: Listener[], source?: string): string | null {
|
|
1503
|
+
let parsed: ReturnType<typeof parseUrl>;
|
|
1504
|
+
try { parsed = parseUrl(url); } catch { return null; }
|
|
1505
|
+
const loopback = (host: string) => isLoopback(host) || host === "localhost";
|
|
1506
|
+
const urlHost = parsed.host.replace(/^\[|\]$/g, "");
|
|
1507
|
+
const at = listeners.find(l => l.port === parsed.port && (l.host === urlHost || l.host === "0.0.0.0" || loopback(l.host) && loopback(urlHost)));
|
|
1508
|
+
const owner = listeners.find(l => l.key === parsed.key);
|
|
1509
|
+
if (!at && !owner && !loopback(urlHost)) return null; // another machine's daemon: only its owner knows
|
|
1510
|
+
if (owner && owner === at && normalizePrefix(owner.prefix) === parsed.prefix) return null;
|
|
1511
|
+
const profile = parsed.profileDirectory;
|
|
1512
|
+
let fix = profile ? `rech share ${JSON.stringify(profile)} --save` : "rech share --save";
|
|
1513
|
+
if (profile) {
|
|
1514
|
+
try {
|
|
1515
|
+
const name = owner && owner.profiles !== "*" ? owner.name : chooseShareListener(profile, listeners);
|
|
1516
|
+
fix = `rech share ${JSON.stringify(profile)} --listener ${name}${owner?.publicUrl || listeners.find(l => l.name === name)?.publicUrl ? "" : " --local"} --save`;
|
|
1517
|
+
} catch { /* no single listener shares it: the default creates a fresh link */ }
|
|
1518
|
+
}
|
|
1519
|
+
const lines = [owner
|
|
1520
|
+
? `this link's listener "${owner.name}" moved: it now serves ${listenerAddress(owner)}${normalizePrefix(owner.prefix)}, not ${parsed.host}:${parsed.port}${parsed.prefix}.`
|
|
1521
|
+
: `this link's key matches no listener on this machine: its listener was removed or its key rotated.${at ? ` ${listenerAddress(at)} belongs to listener "${at.name}" (${normalizePrefix(at.prefix)}), which this link is not for.` : ""}`];
|
|
1522
|
+
if (source && source !== "environment") lines.push(`The stale RECHROME_URL is saved in ${source}.`);
|
|
1523
|
+
lines.push(`Replace it with a current link: ${fix}`);
|
|
1524
|
+
return lines.join("\n ");
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
/** staleLinkHint against this machine's listeners.json; null when it can't be read. */
|
|
1528
|
+
async function localStaleLinkHint(url: string): Promise<string | null> {
|
|
1529
|
+
const config = await readListeners().catch(() => null);
|
|
1530
|
+
return config ? staleLinkHint(url, config.listeners, url === process.env[ENV_KEY] ? rechromeUrlSource : undefined) : null;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1496
1533
|
/** A loopback port no listener uses and nothing else is bound to, from the management port + 2. */
|
|
1497
1534
|
function freeListenerPort(listeners: Listener[]): number {
|
|
1498
1535
|
const used = new Set(listeners.map(l => l.port));
|
|
@@ -1918,8 +1955,9 @@ async function callServe(
|
|
|
1918
1955
|
process.exit(1);
|
|
1919
1956
|
});
|
|
1920
1957
|
if (res.status === 401) {
|
|
1921
|
-
|
|
1922
|
-
|
|
1958
|
+
const stale = await localStaleLinkHint(url);
|
|
1959
|
+
if (throwOnFailure) throw new Error(stale ? `The daemon rejected this link's key: ${stale}` : "The daemon rejected its connection key. Run setup again to refresh it.");
|
|
1960
|
+
console.error(`[rech] rech-client -> rech-server[ok]\n -x: bearer key rejected (used: ${key.slice(0, 4)}...) -> playwright[unknown]${stale ? `\n ${stale}` : ""}`);
|
|
1923
1961
|
process.exit(1);
|
|
1924
1962
|
}
|
|
1925
1963
|
const text = await res.text();
|
|
@@ -1930,6 +1968,8 @@ async function callServe(
|
|
|
1930
1968
|
let detail = `HTTP ${res.status} from ${serviceUrl(url, "run")} is not a rechrome daemon response: ${text.slice(0, 200).trim()}`;
|
|
1931
1969
|
if (res.status === 502 || res.status === 503 || res.status === 504)
|
|
1932
1970
|
detail += `\n The proxy is up but nothing answers behind it: this link's listener was likely removed or moved. Ask its owner for a fresh link (\`rech share\`), then \`rechrome connect <link>\`.`;
|
|
1971
|
+
const stale = await localStaleLinkHint(url);
|
|
1972
|
+
if (stale) detail += `\n ${stale}`;
|
|
1933
1973
|
if (throwOnFailure) throw new Error(detail);
|
|
1934
1974
|
console.error(`[rech] rech-client -> ${serviceUrl(url, "run")}\n -x: ${detail}`);
|
|
1935
1975
|
process.exit(1);
|
|
@@ -4135,7 +4175,8 @@ async function status(): Promise<void> {
|
|
|
4135
4175
|
// Show the URL this client connects to; through a proxy, the daemon's bind is on another port.
|
|
4136
4176
|
const details = [pingBody?.listener && `listener ${pingBody.listener}`, pingBody?.bind && `bind ${pingBody.bind}`].filter(Boolean).join(", ");
|
|
4137
4177
|
// Any HTTP answer is not the daemon: a proxy's 404/502 means the link's listener is gone or moved.
|
|
4138
|
-
|
|
4178
|
+
// A 200 that isn't the /ping JSON is another listener's catch-all: its port was reused.
|
|
4179
|
+
const answered = (pingResponse?.ok && !!pingBody) || pingResponse?.status === 401 || (pingResponse?.status === 403 && !!pingBody?.error);
|
|
4139
4180
|
console.log(`serve: ${!ping ? `not reachable at ${serviceUrl(url)}`
|
|
4140
4181
|
: answered ? `running ${serviceUrl(url)}${details ? ` (${details})` : ""}`
|
|
4141
4182
|
: `✗ ${serviceUrl(url)} answers HTTP ${pingResponse?.status ?? ping.status}, not a rechrome daemon — this link's listener was likely removed or moved; ask its host for a fresh link (\`rech share <profile>\`), then \`rech connect '<url>'\``}`);
|
|
@@ -4147,6 +4188,8 @@ async function status(): Promise<void> {
|
|
|
4147
4188
|
if (pingResponse?.status === 403 && pingBody?.error) console.log(`auth: ✗ ${pingBody.error}`);
|
|
4148
4189
|
if (pingResponse?.status === 401)
|
|
4149
4190
|
console.log(`auth: ✗ key rejected — ask the host for a fresh URL (\`rech share <profile>\`), then \`rech connect '<url>'\``);
|
|
4191
|
+
const stale = ping && !pingBody ? await localStaleLinkHint(url) : null;
|
|
4192
|
+
if (stale) console.log(`stale: ✗ ${stale}`);
|
|
4150
4193
|
// daemonManager().id — there is no PM_BIN constant. Referencing one threw a
|
|
4151
4194
|
// ReferenceError that took down the whole of `rech status`, so the one command
|
|
4152
4195
|
// that reports "the relay is wedged" died exactly when the relay was wedged,
|