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

Firefox WebExtension与原生应用通过Promise实现认证交互问题

解决WebExtension原生消息与onAuthRequired Promise关联的问题

你的核心问题是要把异步的原生消息回调和webRequest.onAuthRequired要求的Promise返回值关联起来——因为onAuthRequired需要返回一个Promise来等待认证结果,而原生应用的响应是通过监听器异步接收的。这里的关键是用一个请求映射表来保存每个认证请求对应的Promise决议函数,当原生应用返回结果时,通过请求ID找到对应的决议函数来完成Promise。

下面是调整后的完整代码,我会标注关键改动:

// 用Map存储请求ID对应的resolve函数,比数组查找更高效
const pendingAuthRequests = new Map();
let port;

try {
  port = browser.runtime.connectNative("myAPP");
  port.onMessage.addListener(onNativeMessage);
} catch(err) {
  console.error("Failed to connect to native app:", err.message);
}

// 原生消息监听器
function onNativeMessage(message) {
  // ... 其他消息处理逻辑 ...
  else if (message.response === "basicAuthentication") {
    processBasicAuth(message);
  }
}

// onAuthRequired监听器,返回Promise
function basicAuthListener(requestDetails) {
  const requestId = requestDetails.requestId;
  
  // 如果已经有pending的请求,直接返回取消的Promise
  if (pendingAuthRequests.has(requestId)) {
    return Promise.resolve({ cancel: true });
  }

  // 准备发送给原生应用的请求,务必带上requestId,让原生返回时能匹配
  const request = {
    action: "requestBasicAuth",
    requestId: requestId,
    // 其他需要的信息,比如requestDetails里的url等
    url: requestDetails.url
  };

  // 创建Promise,把resolve函数存入映射表
  const authPromise = new Promise((resolve) => {
    pendingAuthRequests.set(requestId, resolve);
  });

  // 发送请求给原生应用
  port.postMessage(request);

  return authPromise;
}

// 处理原生应用返回的认证结果
function processBasicAuth(message) {
  const requestId = message.requestId;
  const resolve = pendingAuthRequests.get(requestId);
  
  if (!resolve) return; // 找不到对应的请求,直接返回

  if (message.action === "ok") {
    // 决议Promise,返回认证凭据
    resolve({
      authCredentials: {
        username: message.username,
        password: message.password
      }
    });
  } else {
    // 决议Promise,取消认证
    resolve({ cancel: true });
  }

  // 处理完后从映射表移除
  pendingAuthRequests.delete(requestId);
}

// 清理完成/失败的请求
function completed(requestDetails) {
  const requestId = requestDetails.requestId;
  if (pendingAuthRequests.has(requestId)) {
    // 如果请求已经完成但还没收到原生响应,取消这个Promise
    pendingAuthRequests.get(requestId)({ cancel: true });
    pendingAuthRequests.delete(requestId);
  }
}

// 注意extraInfoSpec必须包含"blocking",这样才能返回Promise阻塞请求
const filter = { urls: ["<all_urls>"] }; // 根据你的需求调整过滤规则
const extraInfoSpec = ["blocking"];

browser.webRequest.onAuthRequired.addListener(
  basicAuthListener,
  filter,
  extraInfoSpec
);
browser.webRequest.onCompleted.addListener(completed, filter);
browser.webRequest.onErrorOccurred.addListener(completed, filter);

关键改动说明:

  • 用Map替代数组存储pending请求:数组查找效率低,Map可以直接通过requestId快速定位对应的resolve函数,避免遍历。
  • 请求携带requestId:发送给原生应用的请求必须包含requestId,这样原生应用返回响应时也要带回这个ID,我们才能找到对应的Promise进行决议。
  • 在basicAuthListener中创建Promise:把resolve函数存入Map,然后返回这个Promise给onAuthRequired,让浏览器等待认证结果。
  • processBasicAuth中决议Promise:收到原生响应后,通过requestId找到对应的resolve函数,根据结果决议Promise(返回凭据或取消)。
  • 清理完成/失败的请求:如果请求已经完成或出错,不管有没有收到原生响应,都要清理对应的Promise并决议取消,避免内存泄漏。

另外要注意:

  • 确保你的原生.exe应用在收到请求后,会返回包含requestId的basicAuthentication响应,否则无法匹配请求。
  • extraInfoSpec必须包含"blocking",这是onAuthRequired返回Promise的必要条件,否则浏览器不会等待Promise决议。

内容的提问来源于stack exchange,提问作者francisco javier pulido illana

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:14:55