Chrome扩展Socket.IO文件传输异常:无法接收原始文件
解决Chrome扩展中Socket.IO文件共享的Blob异常问题
问题根源
Chrome扩展的跨上下文消息传递机制(比如chrome.runtime.sendMessage或chrome.runtime.connect)会自动序列化传递的数据,Uint8Array这类二进制类型会被转换成普通的JSON对象(而非原始二进制数据)。当你在popup把Uint8Array发给background时,实际传递的是一个包含byteLength、buffer等字段的对象,而非真正的二进制数组。后续服务器转发、background接收拼接时,处理的是这些对象的字符串形式,最终生成的Blob自然不对,表现为文件内容是[object Object],大小也异常。
修复方案
1. Popup端:将Uint8Array转为Base64字符串传递
在popup.js中,不要直接发送Uint8Array,而是先转成Base64字符串,避免序列化失真:
// popup.js 读取文件并发送 document.getElementById('file-input').addEventListener('change', async (e) => { const file = e.target.files[0]; const arrayBuffer = await file.arrayBuffer(); const uint8Array = new Uint8Array(arrayBuffer); // 转成Base64字符串 const base64 = btoa(String.fromCharCode(...uint8Array)); // 发送给background chrome.runtime.sendMessage({ type: 'file-data', filename: file.name, data: base64, size: file.size }); });
2. Background端:将Base64转回Uint8Array再转发
background.js收到消息后,先把Base64转回Uint8Array,再通过Socket.IO发给服务器:
// background.js 接收并转发 chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.type === 'file-data') { // Base64转回Uint8Array const binaryStr = atob(message.data); const uint8Array = new Uint8Array(binaryStr.length); for (let i = 0; i < binaryStr.length; i++) { uint8Array[i] = binaryStr.charCodeAt(i); } // 通过Socket.IO发送给服务器 socket.emit('file-chunk', { filename: message.filename, data: uint8Array, totalSize: message.size }); } });
3. 服务器端:保持原有的分块逻辑(无需修改)
服务器的分块发送逻辑可以和Web版一致,因为Socket.IO在Node.js环境中可以正确处理Uint8Array类型的二进制数据。
4. 接收端Background:正确拼接分块为Blob
接收分块时,确保每一块都是Uint8Array类型,再收集后生成Blob:
// background.js 接收服务器分块并拼接 const fileChunks = new Map(); // key: filename, value: { chunks: [], totalSize: number } socket.on('receive-chunk', (data) => { const { filename, chunk, totalSize } = data; if (!fileChunks.has(filename)) { fileChunks.set(filename, { chunks: [], totalSize }); } // 确保chunk是Uint8Array(如果服务器转发时类型正确,这里无需额外处理) fileChunks.get(filename).chunks.push(chunk); // 检查是否收完所有分块 const receivedSize = fileChunks.get(filename).chunks.reduce((sum, chunk) => sum + chunk.length, 0); if (receivedSize === totalSize) { // 拼接所有Uint8Array为一个大的Uint8Array const combined = new Uint8Array(receivedSize); let offset = 0; for (const chunk of fileChunks.get(filename).chunks) { combined.set(chunk, offset); offset += chunk.length; } // 生成Blob const blob = new Blob([combined], { type: 'application/octet-stream' }); // 触发下载 const url = URL.createObjectURL(blob); chrome.downloads.download({ url, filename }); // 清理缓存 fileChunks.delete(filename); } });
替代方案:使用Chrome扩展的二进制消息传递
如果不想用Base64(避免额外的体积开销),可以使用chrome.runtime.connect建立长连接,通过postMessage传递ArrayBuffer类型的二进制数据,因为长连接支持直接传递二进制:
// popup.js 建立长连接并发送ArrayBuffer const port = chrome.runtime.connect({ name: 'file-transfer' }); port.postMessage({ type: 'file-data', filename: file.name, size: file.size }, [arrayBuffer]); // background.js 监听长连接 chrome.runtime.onConnect.addListener((port) => { if (port.name === 'file-transfer') { port.onMessage.addListener((message, port) => { if (message.type === 'file-data') { // port.postMessage的第二个参数是Transferable,这里arrayBuffer会被转移 const uint8Array = new Uint8Array(port.sender.data); socket.emit('file-chunk', { filename: message.filename, data: uint8Array, totalSize: message.size }); } }); } });
这种方式更高效,适合大文件传输,因为不需要Base64编码的额外开销。
内容的提问来源于stack exchange,提问作者SHREY RAJ
相关产品推荐
相关产品推荐

