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
相关产品推荐
相关产品推荐

