谷歌浏览器插件开发避免跨域以及流式数据输出
配置manifest.json
"host_permissions": ["<all_urls>"],
"web_accessible_resources": [{
"resources": ["background.js"],
"matches": ["<all_urls>"]
}],
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["background.js"],
}
]
在background.js 当中写
// 监听connect事件
let abortController = null;
chrome.runtime.onConnect.addListener(function (port) {
console.log("Connected:", port);
port.onMessage.addListener(async function (message) {
if (message.action === 'stream') {
// 创建一个新的 AbortController 实例
abortController = new AbortController();
const { signal } = abortController;
try {
const response = await fetch(message.url, {
method: message.method,
body: message.body,
headers: message.headers ? message.headers : { 'Content-Type': 'application/json' },
signal // 将信号传递给 fetch 请求
});
if (!response.ok) {
port.postMessage({ type: 'error', msg: '服务器错误,请检查设置页面是否配置大模型地址' });
throw new Error('Network response was not ok');
}
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
let partialMessage = '';
while (true) {
const { done, value } = await reader.read();
if (done || signal.aborted) {
if (signal.aborted) {
console.log('Request was aborted by user.');
} else {
port.postMessage({ type: 'DONE', msg: '完成' });
}
break;
}
const chunk = decoder.decode(value, { stream: true });
partialMessage += chunk;
try {
const lines = partialMessage.split('\n').filter(line => line.trim() !== '');
if (lines.length > 0 && !lines[0].startsWith('data:')) {
partialMessage = lines.pop() || '';
}
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.startsWith('data:')) {
const jsonDataStr = line.slice(5).trim();
if (jsonDataStr === '[DONE]') {
console.log('Stream ended.');
break;
}
const jsonData = JSON.parse(jsonDataStr);
if (jsonData.choices && jsonData.choices[0].delta.content) {
const content = jsonData.choices[0].delta.content;
port.postMessage({ type: 'success', msg: content });
}
} else {
const jsonData = JSON.parse(line);
if (jsonData.message && jsonData.message.content) {
const content = jsonData.message.content;
port.postMessage({ type: 'success', msg: content });
}
if (jsonData.done === true) {
console.log('Stream ended.');
break;
}
}
}
if (lines.length > 0 && lines[0].startsWith('data:')) {
partialMessage = '';
}
} catch (e) {
console.error('Error parsing data:', e);
continue;
}
}
} catch (error) {
if (!signal.aborted) {
port.postMessage({ type: 'error', msg: error.message });
}
}
} else if (message.action === 'abort') {
// 用户请求中断
if (abortController) {
abortController.abort();
}
}
});
});
接口封装
// 监听connect事件
let abortController = null;
chrome.runtime.onConnect.addListener(function (port) {
console.log("Connected:", port);
port.onMessage.addListener(async function (message) {
if (message.action === 'stream') {
// 创建一个新的 AbortController 实例
abortController = new AbortController();
const { signal } = abortController;
try {
const response = await fetch(message.url, {
method: message.method,
body: message.body,
headers: message.headers ? message.headers : { 'Content-Type': 'application/json' },
signal // 将信号传递给 fetch 请求
});
if (!response.ok) {
port.postMessage({ type: 'error', msg: '服务器错误,请检查设置页面是否配置大模型地址' });
throw new Error('Network response was not ok');
}
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
let partialMessage = '';
while (true) {
const { done, value } = await reader.read();
if (done || signal.aborted) {
if (signal.aborted) {
console.log('Request was aborted by user.');
} else {
port.postMessage({ type: 'DONE', msg: '完成' });
}
break;
}
const chunk = decoder.decode(value, { stream: true });
partialMessage += chunk;
try {
const lines = partialMessage.split('\n').filter(line => line.trim() !== '');
if (lines.length > 0 && !lines[0].startsWith('data:')) {
partialMessage = lines.pop() || '';
}
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.startsWith('data:')) {
const jsonDataStr = line.slice(5).trim();
if (jsonDataStr === '[DONE]') {
console.log('Stream ended.');
break;
}
const jsonData = JSON.parse(jsonDataStr);
if (jsonData.choices && jsonData.choices[0].delta.content) {
const content = jsonData.choices[0].delta.content;
port.postMessage({ type: 'success', msg: content });
}
} else {
const jsonData = JSON.parse(line);
if (jsonData.message && jsonData.message.content) {
const content = jsonData.message.content;
port.postMessage({ type: 'success', msg: content });
}
if (jsonData.done === true) {
console.log('Stream ended.');
break;
}
}
}
if (lines.length > 0 && lines[0].startsWith('data:')) {
partialMessage = '';
}
} catch (e) {
console.error('Error parsing data:', e);
continue;
}
}
} catch (error) {
if (!signal.aborted) {
port.postMessage({ type: 'error', msg: error.message });
}
}
} else if (message.action === 'abort') {
// 用户请求中断
if (abortController) {
abortController.abort();
}
}
});
});
使用
chat(message, signal, {
onMessage: (data) => {
machineMessage.content = data;
this.$nextTick(() => {
this.$refs.chatList.scrollBottom();
})
},
onError: (error) => {
this.requesting = false;
machineMessage.content = error;
this.$refs.messageInput.stopRequest();
},
onDone: (data) => {
this.$refs.messageInput.stopRequest();
this.requesting = false;
}
})
参考:谷歌浏览器插件开发跨域问题(完美解决)_谷歌跨域插件-CSDN博客