aardio 文档

编写浏览器本地应用扩展

使用 aardio 编写浏览器本地应用 (Native Messaging) 扩展分为两个主要部分:

  1. 本地消息服务端:用 aardio 实现的多线程服务端,可以多线程收发消息,并提供类似 WebSocket 的单线程异步调用接口。
  2. 浏览器扩展客户端:用 JS 和 HTML 编写的扩展程序,用于实现 Native Messaging 客户端。

一. 发布安装

1. 安装消息服务端

运行发布后的 EXE 自动安装消息服务端。在安装代码中配置允许调用此 EXE 的浏览器扩展 ID。

安装未打包扩展会动态生成扩展 ID(每次都会变化)。aardio 允许在参数中指定扩展目录并自动获取动态生成的扩展 ID。示例:

web.nativeMessaging.install(
    allowed_origins = {"\crx\nativeMessagingTest"} 
)

2. 安装浏览器扩展

在浏览器扩展管理页面启用开发者模式,安装包含 manifest.json 文件的未打包扩展。

{
    "manifest_version": 3, 
    "permissions": [
        "nativeMessaging",
        "https://*/*" 
    ] 
}

二. Native Messaging 服务端示例

以下是 aardio 实现的 Native Messaging 服务端示例代码:

import win.ui;
/*DSG{{*/
var winform = win.form(text="aardio工程15";right=759;bottom=469)
winform.add(
edit={cls="edit";left=24;top=15;right=726;bottom=428;edge=1;multiline=1;z=1}
)
/*}}*/

//创建浏览器本地消息主机
import web.nativeMessaging;
var host = web.nativeMessaging();

//如果不是在浏览器中启动 host 返回 null
if( !host ){

    //安装浏览器本地消息主机
    import web.nativeMessaging.install;

    //自动生成配置文件,并在注册表中写入此应用
    var json = web.nativeMessaging.install(

        //本地应用名,命名规则与变量命名规则类似,只能使用字母数字,可用圆点分隔名称
        name = "com.my_company.my_application";

        //这个是描述,其实没什么用
        description = "My Application";

        //允许他调用此消息主机的浏览器扩展 ID 数组
        allowed_origins = {
            "agnhjnpjidnjcppanhimaidodnbnhhbp";//可以指定扩展 ID
            "\crx\nativeMessagingTest";//也可以指定解压的扩展目录路径,自动转换为扩展 ID
            "\crx\nativeMessagingTest.pem";//也可以指定私钥文件,自动转换为扩展 ID
        }
    )

    winform.edit.print("本地应用服务端已注册成功")
    winform.edit.print(json); 
}
else{

    //在Native Messaging管道中启动,客户端已连交接触发此事件
    host.onOpen = function(extension,parentWindow){

        winform.edit.print("客户端已连接:",extension)

            //不能使用 win.setParent()把chrome搞成父窗口,这样chrome会崩溃
        win.setOwner(winform.hwnd,parentWindow);
    }

    //客户端关闭时触发此事件
    host.onClose = function(){
        winform.edit.print("客户端已断开,即将退出")
        win.quitMessage();//必须及时退出
    }

    //客户端发了JSON对象过来,注意data是一个经过JSON解析得到的对象,不是JSON字符串
    host.onMessage = function(data){
        winform.edit.print("收到数据",data);
        host.send("这是来自aardio的数据");
    }

    //遇到错误了
    host.onError = function(err){
        winform.edit.print(err);
    }

    //运行消息主机,这个函数只是启动监听线程,不会阻塞
    host.run();
}

winform.show() 
win.loopMessage();

三. 浏览器实现的 Native Messaging 客户端示例

popup.html

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Native Messaging</title>
    <style>
      body {
        min-width: 300px;
        font-family: Arial, sans-serif;
        margin: 20px;
      }
      #message-form {
        display: flex;
        flex-direction: column;
      }
      input, button {
        margin: 10px 0;
      }
      button {
        padding: 10px;
        background-color: #007bff;
        color: white;
        border: none;
        cursor: pointer;
      }
      button:disabled {
        background-color: #ccc;
      }
    </style>
  </head>
  <body>
    <h1>Native Messaging</h1>
    <form id="message-form">
      <input type="text" id="message-input" placeholder="输入消息">
      <button type="button" id="send-button">发送消息</button>
    </form>
    <p id="msg"></p>
    <script src="popup.js"></script>
  </body>
</html>

popup.js

// 这里的参数改为本地应用的标识名
var port = chrome.runtime.connectNative('com.my_company.my_application');

// 连接成功后的处理函数
function onConnected(port) {
  port.onMessage.addListener(onNativeMessage);
  port.onDisconnect.addListener(onDisconnected);
  document.getElementById('send-button').disabled = false;
}

// 本地应用消息处理函数
function onNativeMessage(msg) {
  const msgElement = document.getElementById("msg");
  if (msgElement) {
    msgElement.innerText = "本地应用发过来的对象:" + JSON.stringify(msg);
  }
}

// 断开连接处理函数
function onDisconnected() {
  const msgElement = document.getElementById("msg");
  if (msgElement) {
    msgElement.innerText = "已断开连接";
  }
  document.getElementById('send-button').disabled = true;
}

// 发送消息函数
function sendNativeMessage(message) {
  if (port) {
    port.postMessage(message);
  } else {
    console.error("无法发送消息,端口未连接");
  }
}

// 发送按钮点击事件处理
document.getElementById('send-button').addEventListener('click', () => {
  const messageInput = document.getElementById('message-input');
  const messageText = messageInput.value;
  if (messageText) {
    sendNativeMessage({ text: messageText });
    messageInput.value = ''; // 清空输入框
  } else {
    alert('请输入消息');
  }
});

// 错误处理
try {
  onConnected(port);
} catch (error) {
  console.error("连接本地应用时发生错误:", error);
}

manifest.json

{
    "manifest_version": 3,
    "name": "Native Messaging Test",
    "description": "Native Messaging",
    "version": "1.0",
    "permissions": [
        "nativeMessaging",
        "https://*/*",
        "http://*/*"
    ],
    "action": {
        "default_icon": "app.ico",
        "default_popup": "popup.html"
    }
}

Markdown 格式