You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Chrome扩展Manifest V3:Service Worker连接蓝牙设备的方案咨询

问题解决方案

为什么Bluetooth Device对象无法传递?

Chrome扩展的消息传递机制(chrome.runtime.sendMessage/chrome.runtime.connect)仅支持JSON可序列化的数据。Bluetooth Device是浏览器原生的复杂对象,包含内部状态、方法引用等不可序列化的内容,传递时会被自动过滤,导致接收端拿到空对象。

可行的解决方案:让内容脚本代理蓝牙操作

Service Worker因无UI上下文,无法触发蓝牙授权对话框,所以必须依赖内容脚本完成设备连接,但不需要传递Device对象,而是让内容脚本持有Device引用,Service Worker通过消息指令让内容脚本执行蓝牙操作,再返回结果。

具体实现步骤

  1. 内容脚本保留Device引用并监听指令
    修改注入的openConnectionDialog函数,连接成功后保留Device对象,同时监听来自Service Worker的消息,处理蓝牙操作指令:
function openConnectionDialog() {
  let connectedDevice;
  // 监听Service Worker的指令
  chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
    if (!connectedDevice) return sendResponse({error: "No device connected"});
    
    switch(msg.action) {
      case "readCharacteristic":
        // 执行读取特征值操作
        connectedDevice.gatt.connect()
          .then(server => server.getPrimaryService('6e400001-b5a3-f393-e0a9-e50e24dcca9e'))
          .then(service => service.getCharacteristic('6e400003-b5a3-f393-e0a9-e50e24dcca9e'))
          .then(char => char.readValue())
          .then(value => {
            sendResponse({success: true, data: Array.from(value)});
          })
          .catch(err => sendResponse({error: err.message}));
        return true; // 表明异步发送响应
      case "sendData":
        // 执行发送数据操作
        connectedDevice.gatt.connect()
          .then(server => server.getPrimaryService('6e400001-b5a3-f393-e0a9-e50e24dcca9e'))
          .then(service => service.getCharacteristic('6e400002-b5a3-f393-e0a9-e50e24dcca9e'))
          .then(char => char.writeValue(new Uint8Array(msg.data)))
          .then(() => sendResponse({success: true}))
          .catch(err => sendResponse({error: err.message}));
        return true;
    }
  });

  return navigator.bluetooth.requestDevice({
    filters: [{ namePrefix: 'myDevice' }],
    optionalServices: ['6e400001-b5a3-f393-e0a9-e50e24dcca9e']
  })
  .then(device => {
    connectedDevice = device;
    console.log(device);
    chrome.runtime.sendMessage({text: "device connected", deviceId: device.id});
    return device;
  });
}
  1. Service Worker通过指令控制蓝牙操作
    Service Worker无需持有Device对象,只需向内容脚本发送操作指令,并接收结果:
// 连接成功后,发送读取指令示例
chrome.runtime.sendMessage({
  action: "readCharacteristic"
}, (response) => {
  if (response.success) {
    console.log("读取到数据:", response.data);
    // 将数据转发给其他内容脚本
    chrome.tabs.query({url: "*://*.example.com/*"}, tabs => {
      tabs.forEach(tab => chrome.tabs.sendMessage(tab.id, {type: "bluetoothData", data: response.data}));
    });
  } else {
    console.error("操作失败:", response.error);
  }
});
  1. 使用长期消息通道优化通信
    如果需要频繁交互,推荐使用chrome.runtime.connect建立长期通道,避免每次查找标签页:
  • 内容脚本连接后主动建立通道:
// 连接设备成功后
const port = chrome.runtime.connect({name: "bluetoothChannel"});
port.onMessage.addListener((msg) => {
  // 处理Service Worker的指令,逻辑同之前的onMessage
});
port.postMessage({text: "device connected", deviceId: connectedDevice.id});
  • Service Worker监听通道:
let bluetoothPort;
chrome.runtime.onConnect.addListener((port) => {
  if (port.name === "bluetoothChannel") {
    bluetoothPort = port;
    bluetoothPort.onMessage.addListener((msg) => {
      if (msg.text === "device connected") {
        console.log("设备已连接:", msg.deviceId);
      }
    });
    // 发送指令
    bluetoothPort.postMessage({action: "readCharacteristic"});
  }
});

关于自定义蓝牙连接对话框

目前Web Bluetooth API不允许自定义连接对话框,浏览器强制使用原生对话框,这是为了保障用户隐私安全,确保用户明确知晓并授权蓝牙设备访问。因此无法让Service Worker直接完成连接,必须依赖有UI上下文的内容脚本触发授权。

内容的提问来源于stack exchange,提问作者Guy Levi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 13:03:26