source dump of claude code
at main 233 lines 6.8 kB view raw
1import { readdir } from 'fs/promises' 2import { homedir } from 'os' 3import { join } from 'path' 4import { isFsInaccessible } from '../errors.js' 5 6export const CHROME_EXTENSION_URL = 'https://claude.ai/chrome' 7 8// Production extension ID 9const PROD_EXTENSION_ID = 'fcoeoabgfenejglbffodgkkbkcdhcgfn' 10// Dev extension IDs (for internal use) 11const DEV_EXTENSION_ID = 'dihbgbndebgnbjfmelmegjepbnkhlgni' 12const ANT_EXTENSION_ID = 'dngcpimnedloihjnnfngkgjoidhnaolf' 13 14function getExtensionIds(): string[] { 15 return process.env.USER_TYPE === 'ant' 16 ? [PROD_EXTENSION_ID, DEV_EXTENSION_ID, ANT_EXTENSION_ID] 17 : [PROD_EXTENSION_ID] 18} 19 20// Must match ChromiumBrowser from common.ts 21export type ChromiumBrowser = 22 | 'chrome' 23 | 'brave' 24 | 'arc' 25 | 'chromium' 26 | 'edge' 27 | 'vivaldi' 28 | 'opera' 29 30export type BrowserPath = { 31 browser: ChromiumBrowser 32 path: string 33} 34 35type Logger = (message: string) => void 36 37// Browser detection order - must match BROWSER_DETECTION_ORDER from common.ts 38const BROWSER_DETECTION_ORDER: ChromiumBrowser[] = [ 39 'chrome', 40 'brave', 41 'arc', 42 'edge', 43 'chromium', 44 'vivaldi', 45 'opera', 46] 47 48type BrowserDataConfig = { 49 macos: string[] 50 linux: string[] 51 windows: { path: string[]; useRoaming?: boolean } 52} 53 54// Must match CHROMIUM_BROWSERS dataPath from common.ts 55const CHROMIUM_BROWSERS: Record<ChromiumBrowser, BrowserDataConfig> = { 56 chrome: { 57 macos: ['Library', 'Application Support', 'Google', 'Chrome'], 58 linux: ['.config', 'google-chrome'], 59 windows: { path: ['Google', 'Chrome', 'User Data'] }, 60 }, 61 brave: { 62 macos: ['Library', 'Application Support', 'BraveSoftware', 'Brave-Browser'], 63 linux: ['.config', 'BraveSoftware', 'Brave-Browser'], 64 windows: { path: ['BraveSoftware', 'Brave-Browser', 'User Data'] }, 65 }, 66 arc: { 67 macos: ['Library', 'Application Support', 'Arc', 'User Data'], 68 linux: [], 69 windows: { path: ['Arc', 'User Data'] }, 70 }, 71 chromium: { 72 macos: ['Library', 'Application Support', 'Chromium'], 73 linux: ['.config', 'chromium'], 74 windows: { path: ['Chromium', 'User Data'] }, 75 }, 76 edge: { 77 macos: ['Library', 'Application Support', 'Microsoft Edge'], 78 linux: ['.config', 'microsoft-edge'], 79 windows: { path: ['Microsoft', 'Edge', 'User Data'] }, 80 }, 81 vivaldi: { 82 macos: ['Library', 'Application Support', 'Vivaldi'], 83 linux: ['.config', 'vivaldi'], 84 windows: { path: ['Vivaldi', 'User Data'] }, 85 }, 86 opera: { 87 macos: ['Library', 'Application Support', 'com.operasoftware.Opera'], 88 linux: ['.config', 'opera'], 89 windows: { path: ['Opera Software', 'Opera Stable'], useRoaming: true }, 90 }, 91} 92 93/** 94 * Get all browser data paths to check for extension installation. 95 * Portable version that uses process.platform directly. 96 */ 97export function getAllBrowserDataPathsPortable(): BrowserPath[] { 98 const home = homedir() 99 const paths: BrowserPath[] = [] 100 101 for (const browserId of BROWSER_DETECTION_ORDER) { 102 const config = CHROMIUM_BROWSERS[browserId] 103 let dataPath: string[] | undefined 104 105 switch (process.platform) { 106 case 'darwin': 107 dataPath = config.macos 108 break 109 case 'linux': 110 dataPath = config.linux 111 break 112 case 'win32': { 113 if (config.windows.path.length > 0) { 114 const appDataBase = config.windows.useRoaming 115 ? join(home, 'AppData', 'Roaming') 116 : join(home, 'AppData', 'Local') 117 paths.push({ 118 browser: browserId, 119 path: join(appDataBase, ...config.windows.path), 120 }) 121 } 122 continue 123 } 124 } 125 126 if (dataPath && dataPath.length > 0) { 127 paths.push({ 128 browser: browserId, 129 path: join(home, ...dataPath), 130 }) 131 } 132 } 133 134 return paths 135} 136 137/** 138 * Detects if the Claude in Chrome extension is installed by checking the Extensions 139 * directory across all supported Chromium-based browsers and their profiles. 140 * 141 * This is a portable version that can be used by both TUI and VS Code extension. 142 * 143 * @param browserPaths - Array of browser data paths to check (from getAllBrowserDataPaths) 144 * @param log - Optional logging callback for debug messages 145 * @returns Object with isInstalled boolean and the browser where the extension was found 146 */ 147export async function detectExtensionInstallationPortable( 148 browserPaths: BrowserPath[], 149 log?: Logger, 150): Promise<{ 151 isInstalled: boolean 152 browser: ChromiumBrowser | null 153}> { 154 if (browserPaths.length === 0) { 155 log?.(`[Claude in Chrome] No browser paths to check`) 156 return { isInstalled: false, browser: null } 157 } 158 159 const extensionIds = getExtensionIds() 160 161 // Check each browser for the extension 162 for (const { browser, path: browserBasePath } of browserPaths) { 163 let browserProfileEntries = [] 164 165 try { 166 browserProfileEntries = await readdir(browserBasePath, { 167 withFileTypes: true, 168 }) 169 } catch (e) { 170 // Browser not installed or path doesn't exist, continue to next browser 171 if (isFsInaccessible(e)) continue 172 throw e 173 } 174 175 const profileDirs = browserProfileEntries 176 .filter(entry => entry.isDirectory()) 177 .filter( 178 entry => entry.name === 'Default' || entry.name.startsWith('Profile '), 179 ) 180 .map(entry => entry.name) 181 182 if (profileDirs.length > 0) { 183 log?.( 184 `[Claude in Chrome] Found ${browser} profiles: ${profileDirs.join(', ')}`, 185 ) 186 } 187 188 // Check each profile for any of the extension IDs 189 for (const profile of profileDirs) { 190 for (const extensionId of extensionIds) { 191 const extensionPath = join( 192 browserBasePath, 193 profile, 194 'Extensions', 195 extensionId, 196 ) 197 198 try { 199 await readdir(extensionPath) 200 log?.( 201 `[Claude in Chrome] Extension ${extensionId} found in ${browser} ${profile}`, 202 ) 203 return { isInstalled: true, browser } 204 } catch { 205 // Extension not found in this profile, continue checking 206 } 207 } 208 } 209 } 210 211 log?.(`[Claude in Chrome] Extension not found in any browser`) 212 return { isInstalled: false, browser: null } 213} 214 215/** 216 * Simple wrapper that returns just the boolean result 217 */ 218export async function isChromeExtensionInstalledPortable( 219 browserPaths: BrowserPath[], 220 log?: Logger, 221): Promise<boolean> { 222 const result = await detectExtensionInstallationPortable(browserPaths, log) 223 return result.isInstalled 224} 225 226/** 227 * Convenience function that gets browser paths automatically. 228 * Use this when you don't need to provide custom browser paths. 229 */ 230export function isChromeExtensionInstalled(log?: Logger): Promise<boolean> { 231 const browserPaths = getAllBrowserDataPathsPortable() 232 return isChromeExtensionInstalledPortable(browserPaths, log) 233}