chrome-extensions-samples 实战:Native Messaging 原生消息通信完整指南(echo 主机安装与协议解析)
【免费下载链接】chrome-extensions-samplesChrome Extensions Samples项目地址: https://gitcode.com/gh_mirrors/ch/chrome-extensions-samples
本文基于chrome-extensions-samples仓库中的 Native Messaging 示例,完整讲解 Chrome 扩展/应用如何通过原生消息(Native Messaging)API 与本地原生程序通信:包括消息主机的清单文件结构、Windows 与 Mac/Linux 双平台安装与卸载流程、扩展端connectNative调用链,以及原生主机端基于标准输入输出实现的 4 字节长度前缀 + JSON 报文协议。读完本文,你将能独立复现并部署一个可双向收发的原生消息回显(echo)示例。
示例概览:扩展与原生程序如何"握手"
Native Messaging 允许 Web 扩展(Manifest V2 时代也支持 Chrome App)与用户机器上安装的原生可执行程序直接交换消息,适用于需要调用系统能力、本地硬件或已有桌面应用的场景。
本仓库的示例位于_archive/mv2/api/nativeMessaging/(其对应的 Manifest V3 版本位于 api-samples/nativeMessaging/),目录结构如下:
_archive/mv2/api/nativeMessaging/ ├── README.md # 示例说明与安装指引(本文依据的主体文档) ├── app/ # 扩展/应用端(Manifest V2 Chrome App) │ ├── manifest.json │ ├── main.html │ ├── main.js │ └── icon-128.png └── host/ # 原生消息主机(Native Messaging Host) ├── com.google.chrome.example.echo.json # Mac/Linux 主机清单 ├── com.google.chrome.example.echo-win.json # Windows 主机清单 ├── install_host.sh # Mac/Linux 安装脚本 ├── uninstall_host.sh # Mac/Linux 卸载脚本 ├── install_host.bat # Windows 安装脚本 ├── uninstall_host.bat # Windows 卸载脚本 ├── native-messaging-example-host # 主机本体(Python 3 实现) └── native-messaging-example-host.bat # Windows 启动包装脚本整个示例的通信链路为:Chrome(扩展端)→ 原生消息主机(manifest 指向的可执行程序)→ 回显消息。主机名为com.google.chrome.example.echo,扩展端与主机端均围绕该名字建立绑定关系。
按 README.md 的说明,要让示例跑起来,必须先安装 host 目录下的原生消息主机,否则扩展端调用chrome.runtime.connectNative()会因找不到主机而连接失败。
原生消息主机的清单文件:注册的关键
Chrome 靠一份 JSON 清单(manifest)定位原生主机,这份清单必须被安装到操作系统指定的目录或注册表位置。本示例提供了两个平台版本:
Mac/Linux 版host/com.google.chrome.example.echo.json:
{ "name": "com.google.chrome.example.echo", "description": "Chrome Native Messaging API Example Host", "path": "HOST_PATH", "type": "stdio", "allowed_origins": [ "chrome-extension://knldjmfmopnpolahpmmgbagdohdnhkik/" ] }Windows 版host/com.google.chrome.example.echo-win.json:
{ "name": "com.google.chrome.example.echo", "description": "Chrome Native Messaging API Example Host", "path": "native-messaging-example-host.bat", "type": "stdio", "allowed_origins": [ "chrome-extension://knldjmfmopnpolahpmmgbagdohdnhkik/" ] }各字段含义如下:
| 字段 | 作用 | 本示例取值 |
|---|---|---|
name | 主机唯一标识名,扩展端connectNative(hostName)必须与之一致 | com.google.chrome.example.echo |
description | 主机用途说明 | Chrome Native Messaging API Example Host |
path | 主机可执行程序的绝对路径(Linux/Mac)或启动命令(Windows) | Linux/Mac 为HOST_PATH占位符,由安装脚本替换;Windows 为native-messaging-example-host.bat |
type | 通信通道类型,原生消息必须为stdio | stdio |
allowed_origins | 允许连接本主机的扩展 ID 白名单 | chrome-extension://knldjmfmopnpolahpmmgbagdohdnhkik/ |
两点需要特别说明:
- 扩展 ID 绑定:
allowed_origins中硬编码了扩展 IDknldjmfmopnpolahpmmgbagdohdnhkik。该 ID 由 app/manifest.json 顶部的key字段(注释中同样标注了该 ID)决定——也就是"固定 key 的扩展 ID 可预测"这一机制。如果你改动了key或使用未签名的临时加载方式,扩展 ID 会变化,必须同步更新清单中的allowed_origins,否则 Chrome 会拒绝建立连接。 - Windows 路径差异:Windows 清单的
path指向native-messaging-example-host.bat(一个调用 Python 解释器启动主机的批处理脚本),因为注册表安装方式写入的是脚本所在目录的相对名;而 Mac/Linux 清单的path在安装时会被替换成native-messaging-example-host的绝对路径。
安装主机:Windows 注册表方式
在 Windows 上,运行 host 目录下的 install_host.bat 即可完成当前用户的安装:
:: Change HKCU to HKLM if you want to install globally. :: %~dp0 is the directory containing this bat script and ends with a backslash. REG ADD "HKCU\Software\Google\Chrome\NativeMessagingHosts\com.google.chrome.example.echo" /ve /t REG_SZ /d "%~dp0com.google.chrome.example.echo-win.json" /f脚本原理(与 README.md 描述一致):
- 在注册表
HKEY_CURRENT_USER\SOFTWARE\Google\Chrome\NativeMessagingHosts\com.google.chrome.example.echo下创建键; - 将该键的默认值(
/ve)设置为指向host\com.google.chrome.example.echo-win.json的完整路径(通过%~dp0取脚本所在目录,末尾带反斜杠); - Chrome 启动时读取该注册表位置,按清单中的
path启动主机。
扩展说明:
- 全局安装:如需为所有用户安装,把脚本中的
HKCU改为HKLM即可(注册表路径变为HKLM\Software\Google\Chrome\NativeMessagingHosts\...)。注意 README 明确提示:机器上需要安装 Python,因为 Windows 主机是通过 native-messaging-example-host.bat 调用python来运行主机的:
@echo off python "%~dp0/native-messaging-example-host" %*- 卸载:运行 uninstall_host.bat,它会同时删除 HKCU 与 HKLM 两处注册表项:
REG DELETE "HKCU\Software\Google\Chrome\NativeMessagingHosts\com.google.chrome.example.echo" /f REG DELETE "HKLM\Software\Google\Chrome\NativeMessagingHosts\com.google.chrome.example.echo" /f安装主机:Mac 与 Linux 目录方式
在 Mac 和 Linux 上,直接运行 host/install_host.sh:
host/install_host.sh脚本首先根据系统与运行权限计算目标目录,从源码可见完整的分支逻辑:
| 平台 | 普通用户(仅当前用户) | root/admin(全用户) |
|---|---|---|
| macOS | $HOME/Library/Application Support/Google/Chrome/NativeMessagingHosts | /Library/Google/Chrome/NativeMessagingHosts |
| Linux | $HOME/.config/google-chrome/NativeMessagingHosts | /etc/opt/chrome/native-messaging-hosts |
随后脚本依次执行:
mkdir -p "$TARGET_DIR"创建目录;cp "$DIR/$HOST_NAME.json" "$TARGET_DIR"拷贝主机清单;- 用
sed -i -e "s/HOST_PATH/$ESCAPED_HOST_PATH/"将清单中的HOST_PATH占位符替换为主机脚本的绝对路径; chmod o+r让所有用户可读清单(root 安装时还会对主机脚本执行chmod a+x赋予执行权限);- 输出安装成功信息。
对应地,host/uninstall_host.sh 会删除目标目录下的com.google.chrome.example.echo.json完成卸载:
host/uninstall_host.shREADME 特别说明:默认只安装给运行脚本的用户;若以sudo host/install_host.sh执行,则会安装到系统级目录供所有用户使用。
扩展端:发起连接与收发消息
扩展端代码位于 app/main.js,核心是调用chrome.runtime.connectNative(hostName)建立长连接。关键逻辑如下:
var port = null; function connect() { var hostName = "com.google.chrome.example.echo"; appendMessage("Connecting to native messaging host <b>" + hostName + "</b>") port = chrome.runtime.connectNative(hostName); port.onMessage.addListener(onNativeMessage); port.onDisconnect.addListener(onDisconnected); updateUiState(); } function sendNativeMessage() { message = {"text": document.getElementById('input-text').value}; port.postMessage(message); appendMessage("Sent message: <b>" + JSON.stringify(message) + "</b>"); } function onNativeMessage(message) { appendMessage("Received message: <b>" + JSON.stringify(message) + "</b>"); } function onDisconnected() { appendMessage("Failed to connect: " + chrome.runtime.lastError.message); port = null; updateUiState(); }调用链与要点:
- 连接:
connectNative(hostName)返回一个Port对象,hostName必须与主机清单中的name完全一致(即com.google.chrome.example.echo); - 收消息:通过
port.onMessage.addListener(onNativeMessage)监听原生主机发来的消息; - 发消息:通过
port.postMessage(message)发送 JSON 对象(本示例发送{"text": "..."}); - 断线处理:
port.onDisconnect触发时,通过chrome.runtime.lastError.message获取失败原因(典型如主机未安装、扩展 ID 不匹配),并将port置空以复位 UI 状态; - UI 状态机:
updateUiState()根据port是否存在切换"Connect / Send"按钮与输入框的显隐,避免在未连接时发送消息。
界面(app/main.html)提供了 Connect 按钮、文本输入框、Send 按钮以及用于回显收发记录的#response区域。
扩展本身需要声明nativeMessaging权限(见 app/manifest.json):
{ "name": "Native Messaging Example", "version": "1.0", "manifest_version": 2, "description": "Send a message to a native application.", "app": { "launch": { "local_path": "main.html" } }, "permissions": ["nativeMessaging"] }注意这是一个 Manifest V2 时代的 Chrome App(使用app.launch.local_path启动入口)。在 Manifest V3 扩展中,权限声明位置不变(permissions: ["nativeMessaging"]),而连接 API 同样为chrome.runtime.connectNative——仓库中 api-samples/nativeMessaging/extension/ 即提供了 MV3 版本的对应实现,可对照阅读。
原生主机端:stdio 通道与 4 字节长度前缀协议
原生主机本体是 Python 3 脚本 host/native-messaging-example-host,它以type: "stdio"与 Chrome 通信:通过标准输入读取扩展发来的消息,通过标准输出写回响应。
通信协议(Native Messaging 的标准报文格式)在源码中有完整实现:
发送消息(向扩展):
def send_message(message): # Write message size. sys.stdout.buffer.write(struct.pack('I', len(message))) # Write the message itself. sys.stdout.write(message) sys.stdout.flush()接收消息(来自扩展):
def read_thread_func(queue): while 1: # Read the message length (first 4 bytes). text_length_bytes = sys.stdin.buffer.read(4) if len(text_length_bytes) == 0: ... sys.exit(0) # Unpack message length as 4 byte integer. text_length = struct.unpack('@I', text_length_bytes)[0] # Read the text (JSON object) of the message. text = sys.stdin.buffer.read(text_length).decode('utf-8') ...即每条消息的编码为:前 4 字节(原生小端@I)是消息体字节长度,其后紧跟 UTF-8 编码的 JSON 文本。收发双方都必须遵循这一帧格式,才能保证 stdin/stdout 通道不会错位。
其余实现要点:
- Windows 二进制模式:脚本开头针对
win32平台调用msvcrt.setmode将 stdin/stdout 切换为O_BINARY,避免 Windows 文本模式对流内容的改写破坏二进制帧; - 回显与退出:收到
{"text":"exit"}时退出循环;在无图形界面(headless)模式下,主机直接把收到的 JSON 包装成{"echo": <原消息>}返回; - Tkinter 图形界面:若系统装有 Tkinter,主机启动一个
NativeMessagingWindow(文本框 + 输入框 + Send 按钮),收到的消息会显示在窗口中,也可手动输入消息回发给扩展——这正是示例名 "echo" 的交互体验:双向消息都会在扩展端 main.html 的响应区与主机窗口中同步可见; - 线程模型:主线程运行 Tkinter 事件循环,另起守护线程
read_thread_func持续读取 stdin,通过queue把消息投递给 UI 线程处理,避免阻塞消息读取。
运行与验证步骤
结合 README.md 与源码,完整运行流程如下:
- 安装原生主机(二选一):
- Windows:在 host 目录运行
install_host.bat(需安装 Python); - Mac/Linux:运行
host/install_host.sh(全局安装可加sudo);
- Windows:在 host 目录运行
- 在 Chrome 中加载扩展/应用目录
_archive/mv2/api/nativeMessaging/app/(chrome://extensions开启开发者模式后"加载已解压的扩展程序");MV3 版本则加载 api-samples/nativeMessaging/extension/; - 打开应用主界面,点击Connect——若主机安装成功,扩展会建立到
com.google.chrome.example.echo的连接; - 在输入框键入文本并点击Send,扩展端界面与原生主机窗口(Tkinter 可用时)都会显示收到的消息,实现双向回显;
- 需要清理时,分别运行
uninstall_host.bat或host/uninstall_host.sh卸载主机。
若连接失败,优先排查三个位置:allowed_origins中的扩展 ID 是否与当前加载扩展一致、清单path指向的程序是否存在且可执行、Windows 注册表/HKCU 或系统目录中的清单是否就位。
小结
这个 echo 示例完整覆盖了 Native Messaging 的全链路要素:主机清单的name/path/type/allowed_origins配置、Windows 注册表与 Mac/Linux 目录两种注册机制、扩展端connectNative的收发与断线处理,以及主机端基于 4 字节长度前缀 + JSON 的 stdio 帧协议。对照仓库中 app/ 与 host/ 的源码,可以清晰理解 Chrome 与本地程序之间每一次消息往返的底层路径——这也是开发真实原生消息主机(如调用本地 CLI、系统 API 或专用硬件)时可直接复用的最小可运行模板。
【免费下载链接】chrome-extensions-samplesChrome Extensions Samples项目地址: https://gitcode.com/gh_mirrors/ch/chrome-extensions-samples
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考