diff --git a/src/background/commands.mjs b/src/background/commands.mjs index 1440d74d..47577cf6 100644 --- a/src/background/commands.mjs +++ b/src/background/commands.mjs @@ -12,7 +12,25 @@ export function registerCommands() { if (command in menuConfig) { if (menuConfig[command].action) { - menuConfig[command].action(true, tab) + // The action may return a Promise (e.g. openSidePanel returns the + // chrome.sidePanel.open() Promise). Keep the call synchronous so the + // user-gesture context is preserved, but observe the Promise so a + // rejection does not become an unhandled rejection in the background. + // Also wrap in try/catch because Browser.commands.onCommand documents + // `tab` as optional, so an action that dereferences tab.* (e.g. the + // openSidePanel call) can throw synchronously. + let result + try { + result = menuConfig[command].action(true, tab) + } catch (error) { + console.error(`failed to run command action "${command}"`, error) + return + } + if (result && typeof result.catch === 'function') { + result.catch((error) => { + console.error(`failed to run command action "${command}"`, error) + }) + } } if (menuConfig[command].genPrompt) { diff --git a/src/background/menus.mjs b/src/background/menus.mjs index a92bd7ba..7f086499 100644 --- a/src/background/menus.mjs +++ b/src/background/menus.mjs @@ -5,10 +5,39 @@ import { config as menuConfig } from '../content-script/menu-tools/index.mjs' const menuId = 'ChatGPTBox-Menu' const onClickMenu = (info, tab) => { + const itemId = info.menuItemId.replace(menuId, '') + + // sidePanel.open() must be called synchronously within the user gesture handler. + // Calling it inside a Promise callback (e.g. Browser.tabs.query().then()) breaks + // Chrome's user gesture requirement and causes the error: + // "sidePanel.open() may only be called in response to a user gesture." + if (itemId === 'openSidePanel' && menuConfig.openSidePanel?.action) { + // Keep the call synchronous to preserve the user-gesture requirement, + // but observe the returned Promise so a rejected sidePanel.open() does + // not become an unhandled rejection in the background script. + // Also wrap in try/catch because contextMenus.onClicked documents `tab` + // as optional ("If the click did not take place in a tab, this parameter + // will be missing"), so the openSidePanel action that dereferences + // tab.windowId/tab.id can throw synchronously. + let result + try { + result = menuConfig.openSidePanel.action(true, tab) + } catch (error) { + console.error('failed to open side panel', error) + return + } + if (result && typeof result.catch === 'function') { + result.catch((error) => { + console.error('failed to open side panel', error) + }) + } + return + } + Browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => { const currentTab = tabs[0] const message = { - itemId: info.menuItemId.replace(menuId, ''), + itemId, selectionText: info.selectionText, useMenuPosition: tab.id === currentTab.id, } diff --git a/src/content-script/menu-tools/index.mjs b/src/content-script/menu-tools/index.mjs index 0f46392e..21e82eea 100644 --- a/src/content-script/menu-tools/index.mjs +++ b/src/content-script/menu-tools/index.mjs @@ -59,14 +59,19 @@ export const config = { }, openSidePanel: { label: 'Open Side Panel', - action: async (fromBackground, tab) => { + action: (fromBackground, tab) => { console.debug('action is from background', fromBackground) if (fromBackground) { // eslint-disable-next-line no-undef - chrome.sidePanel.open({ windowId: tab.windowId, tabId: tab.id }) - } else { - // side panel is not supported + if (typeof chrome === 'undefined' || !chrome.sidePanel?.open) { + // sidePanel API is not available in this browser (e.g. Firefox) + return Promise.reject(new Error('chrome.sidePanel API is not available')) + } + // eslint-disable-next-line no-undef + return chrome.sidePanel.open({ windowId: tab.windowId, tabId: tab.id }) } + // side panel is not supported + return undefined }, }, closeAllChats: { diff --git a/src/services/clients/claude/index.mjs b/src/services/clients/claude/index.mjs index c026f710..b20a487b 100644 --- a/src/services/clients/claude/index.mjs +++ b/src/services/clients/claude/index.mjs @@ -74,8 +74,8 @@ export class Claude { if (!sessionKey) { throw new Error('Session key required') } - if (!sessionKey.startsWith('sk-ant-sid01')) { - throw new Error('Session key invalid: Must be in the format sk-ant-sid01-*****') + if (!sessionKey.startsWith('sk-ant-sid')) { + throw new Error('Session key invalid: Must be in the format sk-ant-sid01-***** or sk-ant-sid02-*****') } if (fetch) { this.fetch = fetch