本文主要记录一次在Mac上实现Chrome插件与Native通信实践。坑常有,快乐也常有(^▽^)
参考文章:《Chrome extension与Native app通信》《Native messaging》
一、Chrome插件
1、插件配置
- 本demo使用的配置版本是 manifest_version : 3,不同的版本API之间存在差异,使用时要注意。
- Chrome插件与原生端通信要设置与原生端的通信权限 nativeMessaging。
- 设置 **service_worker **后台执行脚本,并可使用DevTools进行调试。
json
{
"name": "Chrome_Plugin",
"description": "Chrome插件配置",
"version": "0.0.1",
"manifest_version": 3,
"icons": {
"16": "images/icon16.png",
"24": "images/icon24.png",
"32": "images/icon32.png",
"128": "images/icon128.png"
},
"content_scripts": [
{
"js": [
"content.js"
],
"matches": [
"https://www.google.com/*"
]
}
],
"permissions": [
"nativeMessaging"
],
"background": {
"service_worker": "background.js"
}
}
2、插件代码
content.js
demo中设置了在Google页面点击option键,触发消息发送。
javascript
document.addEventListener("keydown", handleClick);
function handleClick(event) {
if (event.altKey) {
sendMessage()
}
}
function sendMessage() {
// 在 content.js 中发送消息给 background.js
chrome.runtime.sendMessage('hello python', function (response) {
console.log("Message sent to background.js");
});
}
background.js
background.js 用来处理与native之间的通信。使用 chrome.runtime.connectNative() 可建立长连接实现双向通信,如果只需要给native发送消息,可以使用 chrome.runtime.sendNativeMessage(), 其中com.example.native为原生应用程序配置文件中的name
双向通信
javascript
// 建立与本地Python服务的连接
var port = chrome.runtime.connectNative('com.example.native');
// 接收来自本地Python服务的消息
port.onMessage.addListener(function (msg) {
console.log('Received message from native: ' + msg.message);
});
// 连接断开时的回调
port.onDisconnect.addListener(function () {
console.log('Disconnected');
});
// 接收来自content script的消息
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
console.log('Received message from content script: ' + message);
// 发送消息到本地Python服务
port.postMessage({message: message});
console.log('Send message to native: ' + message);
});
单向通信
javascript
chrome.runtime.sendNativeMessage('com.example.native', msg, function (response) {
console.log('接收到消息:\n' + response);
});
二、Native应用
1、Python代码
python
import struct
import sys
import logging
import json
def read_message():
# 读取标准输入流中的msg-header(first 4 bytes).
text_length_bytes = sys.stdin.buffer.read(4)
# Unpack message length as 4 byte integer, tuple = struct.unpack(fmt, buffer).
text_length = struct.unpack("I", text_length_bytes)[0]
# 读取标准输入流中的msg-body bytes
return sys.stdin.buffer.read(text_length)
def send_message(message):
# 将msg-header写到标准输出流, I表示int类型占4个字节,相关内容查看文档介绍python-doc/library/struct
sys.stdout.buffer.write(struct.pack("I", len(message)))
# 将msg-body写到标准输出流
sys.stdout.buffer.write(message)
sys.stdout.flush()
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG, filename='debug.log', format='%(asctime)s - %(levelname)s - %(message)s')
logging.debug("native程序启动了")
while True:
message = read_message()
if message:
logging.debug(f"收到消息:${message}")
response = {"message": "hello Chrome_Plugin"}
send_message(json.dumps(response).encode("utf-8"))
logging.debug(f"程序结束了")
注意事项: 通过标准输入输出流(stdio)读取消息,需要使用struct.unpack读取前四位,获取消息长度后再读取消息内容。GPT一直骗我用 sys.stdin.readline() ,一直读取不到,给我坑坏了。。。参考了文章 《Chrome extension与Native app通信》才得以解决。
2、Python打包
打包Python应用程序需要用到 pyinstaller,pyinstaller可将Python脚本及其依赖项打包成可执行文件,可执行文件在 dist 目录下。
- 安装:pip install pyinstaller
- 打包:pyinstaller main.py
3、Native配置
json
{
"name": "com.example.native",
"description": "本地Python程序配置",
"path": "/Users/whj/Desktop/TOOL_PROJECT/native_demo/dist/main/main",
"type": "stdio",
"allowed_origins": [
"chrome-extension://kjcamdnmifjlmnapcocdfmjmadchcbgg/"
]
}
注意事项:
- 配置文件名称要与name保持一致
- Path为可执行文件存放路径(mac上必须为绝对路径)
- allowed_origins配置的是可与native程序通信的Chrome插件,ID可在插件安装后,插件列表中查看。
- 配置文件要存放在 ~/Library/Application Support/Google/Chrome/NativeMessagingHosts/ 下
三、联调
1、插件联调
内容脚本联调
content.js可以直接进行断点调试
后台脚本联调
Chrome上可以使用DevTools来查看background.js的打印日志,
2、Python应用联调
目前知道的只能通过输出到日志文件上来查看日志(知识有限(✿◡‿◡)),如果有其它更好的方式欢迎留言分享~
3、常见报错
QA:Failed to start native messaging host.
原因分析:大概率是可执行文件的路径配置有问题,GPT骗我说直接放脚本路径🤮
QA:Native host has exited.
原因分析:Native应用报错了,检查一下Python代码有没有问题
QA:Invalid native messaging host name specified
原因分析:Native应用配置中的name有问题或跟配置文件名不一致,示例:com.example.native
QA:Access to the specified native messaging host is forbidden
原因分析:Native应用配置中没有配置 allowed_origins