rechrome 1.7.0 → 1.8.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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/rech.js +42 -6
  3. package/rech.ts +42 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rechrome",
3
- "version": "1.7.0",
3
+ "version": "1.8.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/snomiao/rechrome.git"
package/rech.js CHANGED
@@ -118,7 +118,8 @@ export function parseUrl(raw: string) {
118
118
  }
119
119
 
120
120
  export async function getOrCreateUrl(): Promise<string> {
121
- if (process.env[ENV_KEY]) return process.env[ENV_KEY];
121
+ // Treat a URL without a bearer key as missing — it cannot authenticate
122
+ try { if (process.env[ENV_KEY] && new URL(process.env[ENV_KEY]!).username) return process.env[ENV_KEY]!; } catch {}
122
123
  const key = randomBytes(9).toString("base64url"); // 12 chars
123
124
  const url = `http://${key}@127.0.0.1:${DEFAULT_PORT}`;
124
125
  const newLine = `${ENV_KEY}=${url}`;
@@ -338,14 +339,38 @@ async function callServe(
338
339
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
339
340
  body: JSON.stringify({ args, identity, env }),
340
341
  signal: AbortSignal.timeout(70_000),
341
- }).catch((e) => { console.error(`[rech] ${e.message}`); process.exit(1); });
342
- if (res.status === 401) { console.error("Unauthorized: bad key"); process.exit(1); }
342
+ }).catch(async (e) => {
343
+ console.error(`[rech] ${e.message}`);
344
+ const dnsResult = await import("dns/promises").then(m => m.lookup(host)).catch(() => null);
345
+ if (!dnsResult) {
346
+ console.error(`[rech] rech-client\n -x: DNS failed -> ${host}[unknown] -> rech-server[unknown]`);
347
+ } else {
348
+ const tcpOk = await new Promise<boolean>(resolve => {
349
+ import("net").then(({ createConnection }) => {
350
+ const s = createConnection({ host, port: Number(port), timeout: 3000 });
351
+ s.on("connect", () => { s.destroy(); resolve(true); });
352
+ s.on("error", () => resolve(false));
353
+ s.on("timeout", () => { s.destroy(); resolve(false); });
354
+ });
355
+ });
356
+ if (tcpOk) {
357
+ console.error(`[rech] rech-client -> ${host}:${port}\n -x: connection refused -> rech-server[unknown]`);
358
+ } else {
359
+ console.error(`[rech] rech-client -> ${host}(${dnsResult.address})\n -x: port ${port} unreachable -> rech-server[unknown]`);
360
+ }
361
+ }
362
+ process.exit(1);
363
+ });
364
+ if (res.status === 401) {
365
+ console.error(`[rech] rech-client -> rech-server[ok]\n -x: token rejected -> playwright[unknown]`);
366
+ process.exit(1);
367
+ }
343
368
  return res.json();
344
369
  }
345
370
 
346
371
  async function run(url: string, args: string[]) {
347
- const { host, port, protocol } = parseUrl(url);
348
- const effectiveProfile = parseUrl(url).profileDirectory || process.env.PLAYWRIGHT_MCP_PROFILE_DIRECTORY;
372
+ const { host, port, protocol, extensionId, extensionToken, profileDirectory, userDataDir } = parseUrl(url);
373
+ const effectiveProfile = profileDirectory || process.env.PLAYWRIGHT_MCP_PROFILE_DIRECTORY;
349
374
  const displayProfile = effectiveProfile ? await resolveProfileEmail(effectiveProfile) : undefined;
350
375
  const identity = await getClientIdentity();
351
376
  const profileSuffix = displayProfile ? ` profile:${displayProfile}` : "";
@@ -353,11 +378,21 @@ async function run(url: string, args: string[]) {
353
378
  `[rech] connecting to ${host}:${port} (identity: ${identity.gitUrl || `${identity.hostname}:${identity.cwd}`}${profileSuffix})`,
354
379
  );
355
380
 
381
+ const resolvedEnv = await getClientEnv({ extensionId, extensionToken, profileDirectory, userDataDir });
356
382
  const { status, stdout, stderr, files, existingSession } = await callServe(url, args);
357
383
 
358
384
  if (existingSession)
359
385
  console.error(`[rech] session already has open tabs — listing existing tabs instead of opening a new window`);
360
- if (stderr) process.stderr.write(stderr);
386
+ if (stderr) {
387
+ if (stderr.includes('Extension connection timeout')) {
388
+ const hasToken = !!resolvedEnv["PLAYWRIGHT_MCP_EXTENSION_TOKEN"];
389
+ const last = hasToken
390
+ ? ` -x: extension token rejected -> extension[unknown]`
391
+ : ` -> extension[not installed] (run: rech setup)`;
392
+ console.error(`[rech] rech-client -> rech-server[ok] -> playwright[ok]\n${last}`);
393
+ }
394
+ process.stderr.write(stderr);
395
+ }
361
396
  if (stdout) process.stdout.write(stdout);
362
397
 
363
398
  if (files?.length) {
@@ -611,6 +646,7 @@ async function setup(opts: { profile?: string } = {}): Promise<void> {
611
646
  if (saveChoice === "2") mkdirSync(join(process.cwd(), ".rechrome"), { recursive: true });
612
647
  const existing = await file(globalEnvPath).text().catch(() => "");
613
648
  const rechUrl = new URL(url);
649
+ if (!rechUrl.username) rechUrl.username = randomBytes(9).toString("base64url");
614
650
  rechUrl.searchParams.set("extension_id", extId);
615
651
  rechUrl.searchParams.set("token", token);
616
652
  rechUrl.searchParams.set("profile", profileEmail);
package/rech.ts CHANGED
@@ -118,7 +118,8 @@ export function parseUrl(raw: string) {
118
118
  }
119
119
 
120
120
  export async function getOrCreateUrl(): Promise<string> {
121
- if (process.env[ENV_KEY]) return process.env[ENV_KEY];
121
+ // Treat a URL without a bearer key as missing — it cannot authenticate
122
+ try { if (process.env[ENV_KEY] && new URL(process.env[ENV_KEY]!).username) return process.env[ENV_KEY]!; } catch {}
122
123
  const key = randomBytes(9).toString("base64url"); // 12 chars
123
124
  const url = `http://${key}@127.0.0.1:${DEFAULT_PORT}`;
124
125
  const newLine = `${ENV_KEY}=${url}`;
@@ -338,14 +339,38 @@ async function callServe(
338
339
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
339
340
  body: JSON.stringify({ args, identity, env }),
340
341
  signal: AbortSignal.timeout(70_000),
341
- }).catch((e) => { console.error(`[rech] ${e.message}`); process.exit(1); });
342
- if (res.status === 401) { console.error("Unauthorized: bad key"); process.exit(1); }
342
+ }).catch(async (e) => {
343
+ console.error(`[rech] ${e.message}`);
344
+ const dnsResult = await import("dns/promises").then(m => m.lookup(host)).catch(() => null);
345
+ if (!dnsResult) {
346
+ console.error(`[rech] rech-client\n -x: DNS failed -> ${host}[unknown] -> rech-server[unknown]`);
347
+ } else {
348
+ const tcpOk = await new Promise<boolean>(resolve => {
349
+ import("net").then(({ createConnection }) => {
350
+ const s = createConnection({ host, port: Number(port), timeout: 3000 });
351
+ s.on("connect", () => { s.destroy(); resolve(true); });
352
+ s.on("error", () => resolve(false));
353
+ s.on("timeout", () => { s.destroy(); resolve(false); });
354
+ });
355
+ });
356
+ if (tcpOk) {
357
+ console.error(`[rech] rech-client -> ${host}:${port}\n -x: connection refused -> rech-server[unknown]`);
358
+ } else {
359
+ console.error(`[rech] rech-client -> ${host}(${dnsResult.address})\n -x: port ${port} unreachable -> rech-server[unknown]`);
360
+ }
361
+ }
362
+ process.exit(1);
363
+ });
364
+ if (res.status === 401) {
365
+ console.error(`[rech] rech-client -> rech-server[ok]\n -x: token rejected -> playwright[unknown]`);
366
+ process.exit(1);
367
+ }
343
368
  return res.json();
344
369
  }
345
370
 
346
371
  async function run(url: string, args: string[]) {
347
- const { host, port, protocol } = parseUrl(url);
348
- const effectiveProfile = parseUrl(url).profileDirectory || process.env.PLAYWRIGHT_MCP_PROFILE_DIRECTORY;
372
+ const { host, port, protocol, extensionId, extensionToken, profileDirectory, userDataDir } = parseUrl(url);
373
+ const effectiveProfile = profileDirectory || process.env.PLAYWRIGHT_MCP_PROFILE_DIRECTORY;
349
374
  const displayProfile = effectiveProfile ? await resolveProfileEmail(effectiveProfile) : undefined;
350
375
  const identity = await getClientIdentity();
351
376
  const profileSuffix = displayProfile ? ` profile:${displayProfile}` : "";
@@ -353,11 +378,21 @@ async function run(url: string, args: string[]) {
353
378
  `[rech] connecting to ${host}:${port} (identity: ${identity.gitUrl || `${identity.hostname}:${identity.cwd}`}${profileSuffix})`,
354
379
  );
355
380
 
381
+ const resolvedEnv = await getClientEnv({ extensionId, extensionToken, profileDirectory, userDataDir });
356
382
  const { status, stdout, stderr, files, existingSession } = await callServe(url, args);
357
383
 
358
384
  if (existingSession)
359
385
  console.error(`[rech] session already has open tabs — listing existing tabs instead of opening a new window`);
360
- if (stderr) process.stderr.write(stderr);
386
+ if (stderr) {
387
+ if (stderr.includes('Extension connection timeout')) {
388
+ const hasToken = !!resolvedEnv["PLAYWRIGHT_MCP_EXTENSION_TOKEN"];
389
+ const last = hasToken
390
+ ? ` -x: extension token rejected -> extension[unknown]`
391
+ : ` -> extension[not installed] (run: rech setup)`;
392
+ console.error(`[rech] rech-client -> rech-server[ok] -> playwright[ok]\n${last}`);
393
+ }
394
+ process.stderr.write(stderr);
395
+ }
361
396
  if (stdout) process.stdout.write(stdout);
362
397
 
363
398
  if (files?.length) {
@@ -611,6 +646,7 @@ async function setup(opts: { profile?: string } = {}): Promise<void> {
611
646
  if (saveChoice === "2") mkdirSync(join(process.cwd(), ".rechrome"), { recursive: true });
612
647
  const existing = await file(globalEnvPath).text().catch(() => "");
613
648
  const rechUrl = new URL(url);
649
+ if (!rechUrl.username) rechUrl.username = randomBytes(9).toString("base64url");
614
650
  rechUrl.searchParams.set("extension_id", extId);
615
651
  rechUrl.searchParams.set("token", token);
616
652
  rechUrl.searchParams.set("profile", profileEmail);