feat(doctor): auto-discover extension token from Chrome LevelDB

Scan Chrome/Edge/Chromium localStorage LevelDB files to extract the
Playwright MCP Bridge auth-token directly from the extension's storage.
Uses a fast 'strings | grep' shell pipeline (~200ms) on macOS/Linux
with a pure-Node fallback for Windows.

The discovered token is now shown in 'opencli doctor' output and takes
priority as the recommended token when using '--fix'.
This commit is contained in:
jackwener committed 2026-03-16 13:38:48 +08:00
1 parent c77c8a8e3a
commit 8e84145ccc
1 file changed
+23 -40
+23 -40
View File
@@ -1,6 +1,7 @@
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { execSync } from 'node:child_process';
import { createInterface } from 'node:readline/promises';
import { stdin as input, stdout as output } from 'node:process';
import type { IPage } from './types.js';
@@ -282,47 +283,29 @@ export function discoverExtensionToken(): string | null {
function extractTokenViaStrings(dir: string, tokenRe: RegExp): string | null {
try {
// Step 1: Find files containing the extension ID
const { execSync } = require('node:child_process') as typeof import('node:child_process');
const grepResult = execSync(
`grep -rl '${PLAYWRIGHT_EXTENSION_ID}' "${dir}" 2>/dev/null | head -10`,
{ encoding: 'utf-8', timeout: 5000 },
).trim();
if (!grepResult) return null;
// Single shell pipeline: for each LevelDB file, extract strings, find lines
// after the extension ID, and filter for base64url token pattern.
//
// LevelDB `strings` output for the extension's auth-token entry:
// auth-token ← key name
// 4,mmlmfjhmonkocbjadbfplnigmagldckm.7 ← LevelDB internal key
// hqI86ncsD1QpcVcj-k9CyzTF-ieCQd_4KreZ_wy1WHA ← token value
//
// We get the line immediately after any EXTENSION_ID mention and check
// if it looks like a base64url token (40-50 chars, [A-Za-z0-9_-]).
const shellDir = dir.replace(/'/g, "'\\''");
const cmd = `for f in '${shellDir}'/*.ldb '${shellDir}'/*.log; do ` +
`[ -f "$f" ] && strings "$f" 2>/dev/null | ` +
`grep -A1 '${PLAYWRIGHT_EXTENSION_ID}' | ` +
`grep -v '${PLAYWRIGHT_EXTENSION_ID}' | ` +
`grep -E '^[A-Za-z0-9_-]{40,50}$' | head -1; ` +
`done 2>/dev/null`;
const result = execSync(cmd, { encoding: 'utf-8', timeout: 10000 }).trim();
const candidateFiles = grepResult.split('\n').filter(Boolean);
for (const file of candidateFiles) {
// Step 2: Extract strings containing "auth-token" or the extension ID pattern
try {
const stringsOutput = execSync(
`strings "${file}" 2>/dev/null`,
{ encoding: 'utf-8', timeout: 5000, maxBuffer: 4 * 1024 * 1024 },
);
// Look for the token pattern near the extension ID + auth-token key
// LevelDB stores: "EXTENSION_ID.N\0...\0auth-token\0R\n...TOKEN_VALUE"
const lines = stringsOutput.split('\n');
let foundExtKey = false;
for (const line of lines) {
if (line.includes(PLAYWRIGHT_EXTENSION_ID)) foundExtKey = true;
if (foundExtKey && line.includes('auth-token')) {
// The token should appear in a nearby line
foundExtKey = false; // reset
continue;
}
if (foundExtKey || line.includes('auth-token')) {
const m = line.match(tokenRe);
if (m && validateBase64urlToken(m[1])) return m[1];
}
}
// Also try: look for token value after "EXTENSION_ID.N" pattern
const extKeyMatch = stringsOutput.match(
new RegExp(`${PLAYWRIGHT_EXTENSION_ID}\\.\\d[\\s\\S]{0,100}?${tokenRe.source}`),
);
if (extKeyMatch?.[1] && validateBase64urlToken(extKeyMatch[1])) return extKeyMatch[1];
} catch { continue; }
// Take the first non-empty line
for (const line of result.split('\n')) {
const token = line.trim();
if (token && validateBase64urlToken(token)) return token;
}
} catch {}
return null;