Welcome back! In the previous chapter, Native Messaging Host, we built a "Translator" program that speaks Chrome's specific language.
However, if you tried to run that code right now, Chrome would completely ignore it.
The Use Case: You have your Native Host running, and you have the Chrome Browser open. You want them to talk.
The Problem: Chrome is designed to be a secure fortress. It does not allow random programs on your computer to send it commands. If it did, any virus could take over your browser! Chrome defaults to "Stranger Danger"βit ignores everyone.
The Solution: We need to issue a Security Badge. We must create a specific file (called a Manifest) and place it in a specific folder that Chrome monitors. This file tells Chrome:
In this chapter, we will write the code that automatically creates and installs this security badge.
We are looking at setup.ts. This file handles the logistics of getting our code registered with the Operating System.
node server.js). It can only run a single file. So, we create a simple script (a "wrapper") that points to our real code.Let's look at the structure of the "ID Card" we need to generate.
This is the data Chrome requires to trust us.
{
"name": "com.anthropic.claude_code_browser_extension",
"description": "Claude Code Browser Extension Native Host",
"path": "/Users/me/.claude/chrome/chrome-native-host",
"type": "stdio",
"allowed_origins": [
"chrome-extension://fcoeoabgfenejglbffodgkkbkcdhcgfn/"
]
}
Explanation: path tells Chrome where our translator lives. allowed_origins is the VIP listβit contains the unique ID of our Chrome Extension. If a different extension tries to connect, Chrome checks this list and blocks it.
Since Chrome can't run complex commands, we generate a tiny script file that acts as a shortcut.
// Inside createWrapperScript function...
const scriptContent = platform === 'windows'
? `@echo off
REM specific windows command
${command}`
: `#!/bin/sh
# specific mac/linux command
exec ${command}`
// Write this text to a file like "chrome-native-host.bat"
await writeFile(wrapperPath, scriptContent)
Explanation: We dynamically write a small text file. When Chrome runs this file, this file runs our actual complex Node.js server.
We don't want users to have to manually write JSON files. We create a setup function that does it all automatically.
First, we need to know where on the computer Chrome looks for these ID cards. This changes based on your Operating System.
function getNativeMessagingHostsDirs(): string[] {
const platform = getPlatform()
if (platform === 'windows') {
// Windows stores it in AppData
return [join(process.env.APPDATA, 'Claude Code', 'ChromeNativeHost')]
}
// Mac/Linux return specific folder paths like:
// ~/Library/Application Support/Google/Chrome/NativeMessagingHosts
return getAllNativeMessagingHostsDirs().map(({ path }) => path)
}
Explanation: We ask the OS, "Where is the security office?" On Mac, it's a folder in your Library. On Windows, it's in AppData.
Now we combine the location and the JSON content to "print" the badge.
export async function installChromeNativeHostManifest(
manifestBinaryPath: string,
): Promise<void> {
// 1. Get the list of folders (Security Offices)
const manifestDirs = getNativeMessagingHostsDirs()
// 2. Create the JSON object
const manifest = {
name: 'com.anthropic.claude_code_browser_extension',
path: manifestBinaryPath,
// ... other fields
}
// 3. Write the file to disk
const manifestContent = JSON.stringify(manifest, null, 2)
await writeFile(join(manifestDirs[0], 'manifest.json'), manifestContent)
}
Explanation: This function does the heavy lifting. It finds the folder, creates the JSON content pointing to our binary, and saves the file.
What happens when the user installs the Claude tool?
Windows is a bit more bureaucratic than Mac or Linux. Simply putting the file in a folder isn't enough; you have to register the location in the "Windows Registry."
function registerWindowsNativeHosts(manifestPath: string): void {
// We need to run a command line tool called 'reg'
const command = 'reg'
const args = [
'add', 'HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\...',
'/d', manifestPath, // The path to our JSON file
'/f' // Force overwrite
]
// Execute the command
execFileNoThrowWithCwd(command, args)
}
Explanation: This uses the Windows command line to add a specific key. This key points to our JSON file. It's like filing paperwork in triplicate so Windows allows Chrome to see the file.
We also include logic to check if the user actually has the extension installed. There is no point in setting up the server if the user hasn't installed the Chrome Extension from the web store yet.
export async function isChromeExtensionInstalled(): Promise<boolean> {
// Look at the user's hard drive
const browserPaths = getAllBrowserDataPaths()
// Check typical installation folders for our specific Extension ID
return isChromeExtensionInstalledPortable(browserPaths)
}
Explanation: We snoop around the file system looking for the folder name matching our Extension ID (fcoeoabgfenejglbffodgkkbkcdhcgfn). If we find it, we know the user is ready.
You have successfully automated the Installation & Manifest Registration!
Now, Chrome knows who we are and where we live. When the extension tries to connect, Chrome will check this Manifest, see that the origin matches, and allow the connection to proceed.
But waitβusers often have multiple versions of Chrome (Canary, Dev, Beta) or different browsers entirely (Brave, Edge). How do we find them all?
In the next chapter, we will learn how to handle different browser configurations.
Next Chapter: Browser Discovery & Configuration
Generated by Code IQ