π File detail
utils/claudeInChrome/common.ts
π― Use case
This file lives under βutils/β, which covers cross-cutting helpers (shell, tempfiles, settings, messages, process input, β¦). On the API surface it exposes CLAUDE_IN_CHROME_MCP_SERVER_NAME, CHROMIUM_BROWSERS, BROWSER_DETECTION_ORDER, getAllBrowserDataPaths, and getAllNativeMessagingHostsDirs (and more) β mainly functions, hooks, or classes. Dependencies touch Node filesystem, Node OS/process metadata, and Node path helpers. It composes internal code from services, debug, errors, execFileNoThrow, and platform (relative imports).
Generated from folder role, exports, dependency roots, and inline comments β not hand-reviewed for every path.
π§ Inline summary
import { readdirSync } from 'fs' import { stat } from 'fs/promises' import { homedir, platform, tmpdir, userInfo } from 'os' import { join } from 'path' import { normalizeNameForMCP } from '../../services/mcp/normalization.js'
π€ Exports (heuristic)
CLAUDE_IN_CHROME_MCP_SERVER_NAMECHROMIUM_BROWSERSBROWSER_DETECTION_ORDERgetAllBrowserDataPathsgetAllNativeMessagingHostsDirsgetAllWindowsRegistryKeysdetectAvailableBrowserisClaudeInChromeMCPServertrackClaudeInChromeTabIdisTrackedClaudeInChromeTabIdopenInChromegetSocketDirgetSecureSocketPathgetAllSocketPaths
π External import roots
Package roots from from "β¦" (relative paths omitted).
fsospath
π₯οΈ Source preview
import { readdirSync } from 'fs'
import { stat } from 'fs/promises'
import { homedir, platform, tmpdir, userInfo } from 'os'
import { join } from 'path'
import { normalizeNameForMCP } from '../../services/mcp/normalization.js'
import { logForDebugging } from '../debug.js'
import { isFsInaccessible } from '../errors.js'
import { execFileNoThrow } from '../execFileNoThrow.js'
import { getPlatform } from '../platform.js'
import { which } from '../which.js'
export const CLAUDE_IN_CHROME_MCP_SERVER_NAME = 'claude-in-chrome'
// Re-export ChromiumBrowser type for setup.ts
export type { ChromiumBrowser } from './setupPortable.js'
// Import for local use
import type { ChromiumBrowser } from './setupPortable.js'
type BrowserConfig = {
name: string
macos: {
appName: string
dataPath: string[]
nativeMessagingPath: string[]
}
linux: {
binaries: string[]
dataPath: string[]
nativeMessagingPath: string[]
}
windows: {
dataPath: string[]
registryKey: string
useRoaming?: boolean // Opera uses Roaming instead of Local
}
}
export const CHROMIUM_BROWSERS: Record<ChromiumBrowser, BrowserConfig> = {
chrome: {
name: 'Google Chrome',
macos: {
appName: 'Google Chrome',
dataPath: ['Library', 'Application Support', 'Google', 'Chrome'],
nativeMessagingPath: [
'Library',
'Application Support',
'Google',
'Chrome',
'NativeMessagingHosts',
],
},
linux: {
binaries: ['google-chrome', 'google-chrome-stable'],
dataPath: ['.config', 'google-chrome'],
nativeMessagingPath: ['.config', 'google-chrome', 'NativeMessagingHosts'],
},
windows: {
dataPath: ['Google', 'Chrome', 'User Data'],
registryKey: 'HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts',
},
},
brave: {
name: 'Brave',
macos: {
appName: 'Brave Browser',
dataPath: [
'Library',
'Application Support',
'BraveSoftware',
'Brave-Browser',
],
nativeMessagingPath: [
'Library',
'Application Support',
'BraveSoftware',
'Brave-Browser',
'NativeMessagingHosts',
],
},
linux: {
binaries: ['brave-browser', 'brave'],
dataPath: ['.config', 'BraveSoftware', 'Brave-Browser'],
nativeMessagingPath: [
'.config',
'BraveSoftware',
'Brave-Browser',
'NativeMessagingHosts',
],
},
windows: {
dataPath: ['BraveSoftware', 'Brave-Browser', 'User Data'],
registryKey:
'HKCU\\Software\\BraveSoftware\\Brave-Browser\\NativeMessagingHosts',
},
},
arc: {
name: 'Arc',
macos: {
appName: 'Arc',
dataPath: ['Library', 'Application Support', 'Arc', 'User Data'],
nativeMessagingPath: [
'Library',
'Application Support',
'Arc',
'User Data',
'NativeMessagingHosts',
],
},
linux: {
// Arc is not available on Linux
binaries: [],
dataPath: [],
nativeMessagingPath: [],
},
windows: {
// Arc Windows is Chromium-based
dataPath: ['Arc', 'User Data'],
registryKey: 'HKCU\\Software\\ArcBrowser\\Arc\\NativeMessagingHosts',
},
},
chromium: {
name: 'Chromium',
macos: {
appName: 'Chromium',
dataPath: ['Library', 'Application Support', 'Chromium'],
nativeMessagingPath: [
'Library',
'Application Support',
'Chromium',
'NativeMessagingHosts',
],
},
linux: {
binaries: ['chromium', 'chromium-browser'],
dataPath: ['.config', 'chromium'],
nativeMessagingPath: ['.config', 'chromium', 'NativeMessagingHosts'],
},
windows: {
dataPath: ['Chromium', 'User Data'],
registryKey: 'HKCU\\Software\\Chromium\\NativeMessagingHosts',
},
},
edge: {
name: 'Microsoft Edge',
macos: {
appName: 'Microsoft Edge',
dataPath: ['Library', 'Application Support', 'Microsoft Edge'],
nativeMessagingPath: [
'Library',
'Application Support',
'Microsoft Edge',
'NativeMessagingHosts',
],
},
linux: {
binaries: ['microsoft-edge', 'microsoft-edge-stable'],
dataPath: ['.config', 'microsoft-edge'],
nativeMessagingPath: [
'.config',
'microsoft-edge',
'NativeMessagingHosts',
],
},
windows: {
dataPath: ['Microsoft', 'Edge', 'User Data'],
registryKey: 'HKCU\\Software\\Microsoft\\Edge\\NativeMessagingHosts',
},
},
vivaldi: {
name: 'Vivaldi',
macos: {
appName: 'Vivaldi',
dataPath: ['Library', 'Application Support', 'Vivaldi'],
nativeMessagingPath: [
'Library',
'Application Support',
'Vivaldi',
'NativeMessagingHosts',
],
},
linux: {
binaries: ['vivaldi', 'vivaldi-stable'],
dataPath: ['.config', 'vivaldi'],
nativeMessagingPath: ['.config', 'vivaldi', 'NativeMessagingHosts'],
},
windows: {
dataPath: ['Vivaldi', 'User Data'],
registryKey: 'HKCU\\Software\\Vivaldi\\NativeMessagingHosts',
},
},
opera: {
name: 'Opera',
macos: {
appName: 'Opera',
dataPath: ['Library', 'Application Support', 'com.operasoftware.Opera'],
nativeMessagingPath: [
'Library',
'Application Support',
'com.operasoftware.Opera',
'NativeMessagingHosts',
],
},
linux: {
binaries: ['opera'],
dataPath: ['.config', 'opera'],
nativeMessagingPath: ['.config', 'opera', 'NativeMessagingHosts'],
},
windows: {
dataPath: ['Opera Software', 'Opera Stable'],
registryKey:
'HKCU\\Software\\Opera Software\\Opera Stable\\NativeMessagingHosts',
useRoaming: true, // Opera uses Roaming AppData, not Local
},
},
}
// Priority order for browser detection (most common first)
export const BROWSER_DETECTION_ORDER: ChromiumBrowser[] = [
'chrome',
'brave',
'arc',
'edge',
'chromium',
'vivaldi',
'opera',
]
/**
* Get all browser data paths to check for extension installation
*/
export function getAllBrowserDataPaths(): {
browser: ChromiumBrowser
path: string
}[] {
const platform = getPlatform()
const home = homedir()
const paths: { browser: ChromiumBrowser; path: string }[] = []
for (const browserId of BROWSER_DETECTION_ORDER) {
const config = CHROMIUM_BROWSERS[browserId]
let dataPath: string[] | undefined
switch (platform) {
case 'macos':
dataPath = config.macos.dataPath
break
case 'linux':
case 'wsl':
dataPath = config.linux.dataPath
break
case 'windows': {
if (config.windows.dataPath.length > 0) {
const appDataBase = config.windows.useRoaming
? join(home, 'AppData', 'Roaming')
: join(home, 'AppData', 'Local')
paths.push({
browser: browserId,
path: join(appDataBase, ...config.windows.dataPath),
})
}
continue
}
}
if (dataPath && dataPath.length > 0) {
paths.push({
browser: browserId,
path: join(home, ...dataPath),
})
}
}
return paths
}
/**
* Get native messaging host directories for all supported browsers
*/
export function getAllNativeMessagingHostsDirs(): {
browser: ChromiumBrowser
path: string
}[] {
const platform = getPlatform()
const home = homedir()
const paths: { browser: ChromiumBrowser; path: string }[] = []
for (const browserId of BROWSER_DETECTION_ORDER) {
const config = CHROMIUM_BROWSERS[browserId]
switch (platform) {
case 'macos':
if (config.macos.nativeMessagingPath.length > 0) {
paths.push({
browser: browserId,
path: join(home, ...config.macos.nativeMessagingPath),
})
}
break
case 'linux':
case 'wsl':
if (config.linux.nativeMessagingPath.length > 0) {
paths.push({
browser: browserId,
path: join(home, ...config.linux.nativeMessagingPath),
})
}
break
case 'windows':
// Windows uses registry, not file paths for native messaging
// We'll use a common location for the manifest file
break
}
}
return paths
}
/**
* Get Windows registry keys for all supported browsers
*/
export function getAllWindowsRegistryKeys(): {
browser: ChromiumBrowser
key: string
}[] {
const keys: { browser: ChromiumBrowser; key: string }[] = []
for (const browserId of BROWSER_DETECTION_ORDER) {
const config = CHROMIUM_BROWSERS[browserId]
if (config.windows.registryKey) {
keys.push({
browser: browserId,
key: config.windows.registryKey,
})
}
}
return keys
}
/**
* Detect which browser to use for opening URLs
* Returns the first available browser, or null if none found
*/
export async function detectAvailableBrowser(): Promise<ChromiumBrowser | null> {
const platform = getPlatform()
for (const browserId of BROWSER_DETECTION_ORDER) {
const config = CHROMIUM_BROWSERS[browserId]
switch (platform) {
case 'macos': {
// Check if the .app bundle (a directory) exists
const appPath = `/Applications/${config.macos.appName}.app`
try {
const stats = await stat(appPath)
if (stats.isDirectory()) {
logForDebugging(
`[Claude in Chrome] Detected browser: ${config.name}`,
)
return browserId
}
} catch (e) {
if (!isFsInaccessible(e)) throw e
// App not found, continue checking
}
break
}
case 'wsl':
case 'linux': {
// Check if any binary exists
for (const binary of config.linux.binaries) {
if (await which(binary).catch(() => null)) {
logForDebugging(
`[Claude in Chrome] Detected browser: ${config.name}`,
)
return browserId
}
}
break
}
case 'windows': {
// Check if data path exists (indicates browser is installed)
const home = homedir()
if (config.windows.dataPath.length > 0) {
const appDataBase = config.windows.useRoaming
? join(home, 'AppData', 'Roaming')
: join(home, 'AppData', 'Local')
const dataPath = join(appDataBase, ...config.windows.dataPath)
try {
const stats = await stat(dataPath)
if (stats.isDirectory()) {
logForDebugging(
`[Claude in Chrome] Detected browser: ${config.name}`,
)
return browserId
}
} catch (e) {
if (!isFsInaccessible(e)) throw e
// Browser not found, continue checking
}
}
break
}
}
}
return null
}
export function isClaudeInChromeMCPServer(name: string): boolean {
return normalizeNameForMCP(name) === CLAUDE_IN_CHROME_MCP_SERVER_NAME
}
const MAX_TRACKED_TABS = 200
const trackedTabIds = new Set<number>()
export function trackClaudeInChromeTabId(tabId: number): void {
if (trackedTabIds.size >= MAX_TRACKED_TABS && !trackedTabIds.has(tabId)) {
trackedTabIds.clear()
}
trackedTabIds.add(tabId)
}
export function isTrackedClaudeInChromeTabId(tabId: number): boolean {
return trackedTabIds.has(tabId)
}
export async function openInChrome(url: string): Promise<boolean> {
const currentPlatform = getPlatform()
// Detect the best available browser
const browser = await detectAvailableBrowser()
if (!browser) {
logForDebugging('[Claude in Chrome] No compatible browser found')
return false
}
const config = CHROMIUM_BROWSERS[browser]
switch (currentPlatform) {
case 'macos': {
const { code } = await execFileNoThrow('open', [
'-a',
config.macos.appName,
url,
])
return code === 0
}
case 'windows': {
// Use rundll32 to avoid cmd.exe metacharacter issues with URLs containing & | > <
const { code } = await execFileNoThrow('rundll32', ['url,OpenURL', url])
return code === 0
}
case 'wsl':
case 'linux': {
for (const binary of config.linux.binaries) {
const { code } = await execFileNoThrow(binary, [url])
if (code === 0) {
return true
}
}
return false
}
default:
return false
}
}
/**
* Get the socket directory path (Unix only)
*/
export function getSocketDir(): string {
return `/tmp/claude-mcp-browser-bridge-${getUsername()}`
}
/**
* Get the socket path (Unix) or pipe name (Windows)
*/
export function getSecureSocketPath(): string {
if (platform() === 'win32') {
return `\\\\.\\pipe\\${getSocketName()}`
}
return join(getSocketDir(), `${process.pid}.sock`)
}
/**
* Get all socket paths including PID-based sockets in the directory
* and legacy fallback paths
*/
export function getAllSocketPaths(): string[] {
// Windows uses named pipes, not Unix sockets
if (platform() === 'win32') {
return [`\\\\.\\pipe\\${getSocketName()}`]
}
const paths: string[] = []
const socketDir = getSocketDir()
// Scan for *.sock files in the socket directory
try {
// eslint-disable-next-line custom-rules/no-sync-fs -- ClaudeForChromeContext.getSocketPaths (external @ant/claude-for-chrome-mcp) requires a sync () => string[] callback
const files = readdirSync(socketDir)
for (const file of files) {
if (file.endsWith('.sock')) {
paths.push(join(socketDir, file))
}
}
} catch {
// Directory may not exist yet
}
// Legacy fallback paths
const legacyName = `claude-mcp-browser-bridge-${getUsername()}`
const legacyTmpdir = join(tmpdir(), legacyName)
const legacyTmp = `/tmp/${legacyName}`
if (!paths.includes(legacyTmpdir)) {
paths.push(legacyTmpdir)
}
if (legacyTmpdir !== legacyTmp && !paths.includes(legacyTmp)) {
paths.push(legacyTmp)
}
return paths
}
function getSocketName(): string {
// NOTE: This must match the one used in the Claude in Chrome MCP
return `claude-mcp-browser-bridge-${getUsername()}`
}
function getUsername(): string {
try {
return userInfo().username || 'default'
} catch {
return process.env.USER || process.env.USERNAME || 'default'
}
}