diff --git a/src/_locales/en/main.json b/src/_locales/en/main.json index 8ad74e6ef..7f666c256 100644 --- a/src/_locales/en/main.json +++ b/src/_locales/en/main.json @@ -124,10 +124,18 @@ "Override provider temperature": "Override provider temperature", "The temperature parameter is not sent. The provider or model default is used.": "The temperature parameter is not sent. The provider or model default is used.", "The current model does not accept a custom temperature. The parameter will not be sent.": "The current model does not accept a custom temperature. The parameter will not be sent.", + "Extra Request Body (JSON)": "Extra Request Body (JSON)", + "Merged into the API request body. Must be a JSON object, other values are ignored.": "Merged into the API request body. Must be a JSON object, other values are ignored.", + "Invalid JSON object, this value is ignored.": "Invalid JSON object, this value is ignored.", "API Url": "API Url", "Provider": "Provider", "Others": "Others", "API Modes": "API Modes", + "Test": "Test", + "Testing...": "Testing...", + "Reachable": "Reachable", + "Unreachable": "Unreachable", + "Not testable": "Not testable", "Disable web mode history for better privacy protection, but it will result in unavailable conversations after a period of time": "Disable web mode history for better privacy protection, but it will result in unavailable conversations after a period of time", "Display selection tools next to input box to avoid blocking": "Display selection tools next to input box to avoid blocking", "Close All Chats In This Page": "Close All Chats In This Page", diff --git a/src/_locales/zh-hans/main.json b/src/_locales/zh-hans/main.json index cfad6d2d1..d2b20e24a 100644 --- a/src/_locales/zh-hans/main.json +++ b/src/_locales/zh-hans/main.json @@ -118,10 +118,18 @@ "Override provider temperature": "覆盖提供商的温度参数", "The temperature parameter is not sent. The provider or model default is used.": "不会发送温度参数,将使用提供商或模型的默认值。", "The current model does not accept a custom temperature. The parameter will not be sent.": "当前模型不接受自定义温度参数,因此不会发送该参数。", + "Extra Request Body (JSON)": "额外请求参数 (JSON)", + "Merged into the API request body. Must be a JSON object, other values are ignored.": "会合并进 API 请求体,必须是 JSON 对象,其他类型的值会被忽略。", + "Invalid JSON object, this value is ignored.": "不是合法的 JSON 对象,该值会被忽略。", "API Url": "API地址", "Provider": "提供商", "Others": "其他", "API Modes": "API模式", + "Test": "测试", + "Testing...": "测试中…", + "Reachable": "可连通", + "Unreachable": "无法连通", + "Not testable": "无法测试", "Disable web mode history for better privacy protection, but it will result in unavailable conversations after a period of time": "禁用网页版模式历史记录以获得更好的隐私保护, 但会导致对话在一段时间后不可用", "Display selection tools next to input box to avoid blocking": "将选择浮动工具显示在输入框旁边以避免遮挡", "Close All Chats In This Page": "关闭本页所有聊天", diff --git a/src/_locales/zh-hant/main.json b/src/_locales/zh-hant/main.json index 3dd20a229..50cadc6f0 100644 --- a/src/_locales/zh-hant/main.json +++ b/src/_locales/zh-hant/main.json @@ -118,10 +118,18 @@ "Override provider temperature": "覆寫供應商的溫度參數", "The temperature parameter is not sent. The provider or model default is used.": "不會傳送溫度參數,將使用供應商或模型的預設值。", "The current model does not accept a custom temperature. The parameter will not be sent.": "目前的模型不接受自訂溫度參數,因此不會傳送這個參數。", + "Extra Request Body (JSON)": "額外請求參數 (JSON)", + "Merged into the API request body. Must be a JSON object, other values are ignored.": "會合併進 API 請求主體,必須是 JSON 物件,其他類型的值會被忽略。", + "Invalid JSON object, this value is ignored.": "不是合法的 JSON 物件,這個值會被忽略。", "API Url": "API 網址", "Provider": "供應商", "Others": "其他", "API Modes": "API 模式", + "Test": "測試", + "Testing...": "測試中…", + "Reachable": "可連線", + "Unreachable": "無法連線", + "Not testable": "無法測試", "Disable web mode history for better privacy protection, but it will result in unavailable conversations after a period of time": "停用網頁版模式歷史記錄以提升隱私保護,但會導致對話記錄在一段時間後無法使用", "Display selection tools next to input box to avoid blocking": "將選擇浮動工具顯示在輸入框旁邊以避免遮擋", "Close All Chats In This Page": "關閉本頁所有對話", diff --git a/src/background/index.mjs b/src/background/index.mjs index 79b4f0c0d..177e7c054 100644 --- a/src/background/index.mjs +++ b/src/background/index.mjs @@ -47,10 +47,12 @@ import { import { refreshMenu } from './menus.mjs' import { registerCommands } from './commands.mjs' import { generateAnswersWithBardWebApi } from '../services/apis/bard-web.mjs' +import { testConnection } from '../services/apis/test-connection.mjs' import { generateAnswersWithClaudeWebApi } from '../services/apis/claude-web.mjs' import { generateAnswersWithMoonshotWebApi } from '../services/apis/moonshot-web.mjs' import { isUsingModelName } from '../utils/model-name-convert.mjs' import { redactSensitiveFields } from './redact.mjs' +import { isTrustedExtensionSender } from './message-sender.mjs' import { clearProxyReconnectErrorSuppression, consumeProxyReconnectErrorSuppression, @@ -615,6 +617,17 @@ Browser.runtime.onMessage.addListener(async (message, sender) => { await deleteConversation(token, message.data.conversationId) break } + case 'TEST_API_CONNECTION': { + if (!isTrustedExtensionSender(sender)) { + console.warn( + '[background] Rejecting TEST_API_CONNECTION message from untrusted sender:', + sender, + ) + return { ok: false, elapsedMs: 0, error: 'unauthorized-sender' } + } + console.log('[background] Processing TEST_API_CONNECTION message') + return testConnection(message.data.session) + } case 'NEW_URL': { console.log('[background] Processing NEW_URL message:', message.data) await Browser.tabs.create({ @@ -693,13 +706,7 @@ Browser.runtime.onMessage.addListener(async (message, sender) => { break } case 'FETCH': { - const senderId = sender?.id - const senderUrl = sender?.url || sender?.documentUrl || sender?.origin - const extensionOrigin = new URL(Browser.runtime.getURL('/')).origin - const isTrustedExtensionSenderWithoutId = - !senderId && typeof senderUrl === 'string' && senderUrl.startsWith(`${extensionOrigin}/`) - - if (senderId !== Browser.runtime.id && !isTrustedExtensionSenderWithoutId) { + if (!isTrustedExtensionSender(sender)) { console.warn('[background] Rejecting FETCH message from untrusted sender:', sender) return [null, { message: 'Unauthorized sender' }] } diff --git a/src/background/message-sender.mjs b/src/background/message-sender.mjs new file mode 100644 index 000000000..8dc0ae6ec --- /dev/null +++ b/src/background/message-sender.mjs @@ -0,0 +1,16 @@ +import Browser from 'webextension-polyfill' + +/** + * Messages that answer with data or reach stored credentials must come from extension + * code. A sender that reports an id is trusted only when it is this extension; extension + * pages in some browsers report no id, so their own URL is the fallback signal. + * @param {{id?: string, url?: string, documentUrl?: string, origin?: string}} sender + * @returns {boolean} + */ +export function isTrustedExtensionSender(sender) { + if (sender?.id === Browser.runtime.id) return true + if (sender?.id) return false + const senderUrl = sender?.url || sender?.documentUrl || sender?.origin + if (typeof senderUrl !== 'string') return false + return senderUrl.startsWith(Browser.runtime.getURL('/')) +} diff --git a/src/config/index.mjs b/src/config/index.mjs index 81c956e46..1f1853fda 100644 --- a/src/config/index.mjs +++ b/src/config/index.mjs @@ -855,6 +855,7 @@ export const defaultConfig = { maxConversationContextLength: 9, temperatureOverrideEnabled: false, temperature: 1, + extraBody: '', customChatGptWebApiUrl: 'https://chatgpt.com', customChatGptWebApiPath: '/backend-api/conversation', customOpenAiApiUrl: 'https://api.openai.com', diff --git a/src/popup/sections/AdvancedPart.jsx b/src/popup/sections/AdvancedPart.jsx index 017b9191f..134535198 100644 --- a/src/popup/sections/AdvancedPart.jsx +++ b/src/popup/sections/AdvancedPart.jsx @@ -3,6 +3,7 @@ import { parseFloatWithClamp, parseIntWithClamp } from '../../utils/index.mjs' import { getModelValue } from '../../utils/model-name-convert.mjs' import { isUsingAzureOpenAiApiModel } from '../../config/index.mjs' import { canApplyTemperatureOverride } from '../../services/apis/temperature-params.mjs' +import { parseExtraBody } from '../../services/apis/extra-body-params.mjs' import PropTypes from 'prop-types' import { Tab, TabList, TabPanel, Tabs } from 'react-tabs' import Browser from 'webextension-polyfill' @@ -22,6 +23,8 @@ function ApiParams({ config, updateConfig }) { ? config.customModelName : getModelValue(config) const temperatureOverrideAvailable = canApplyTemperatureOverride(selectedModel) + const extraBodyValue = typeof config.extraBody === 'string' ? config.extraBody : '' + const extraBodyInvalid = extraBodyValue.trim() !== '' && !parseExtraBody(extraBodyValue) return ( <> @@ -89,6 +92,21 @@ function ApiParams({ config, updateConfig }) { /> )} +