diff --git a/packages/shared/IpcChannel.ts b/packages/shared/IpcChannel.ts index 8a7c12a6fb..5f8949116b 100644 --- a/packages/shared/IpcChannel.ts +++ b/packages/shared/IpcChannel.ts @@ -20,6 +20,8 @@ export enum IpcChannel { App_InstallUvBinary = 'app:install-uv-binary', App_InstallBunBinary = 'app:install-bun-binary', + Webview_SetOpenLinkExternal = 'webview:set-open-link-external', + // Open Open_Path = 'open:path', Open_Website = 'open:website', diff --git a/src/main/index.ts b/src/main/index.ts index be648d55f1..b1d161ead3 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -75,14 +75,6 @@ if (!app.requestSingleInstanceLock()) { handleProtocolUrl(url) }) - registerProtocolClient(app) - - // macOS specific: handle protocol when app is already running - app.on('open-url', (event, url) => { - event.preventDefault() - handleProtocolUrl(url) - }) - // Listen for second instance app.on('second-instance', (_event, argv) => { windowService.showMainWindow() diff --git a/src/main/ipc.ts b/src/main/ipc.ts index d8319c168f..72e5d02147 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -25,6 +25,7 @@ import { ProxyConfig, proxyManager } from './services/ProxyManager' import { searchService } from './services/SearchService' import { registerShortcuts, unregisterAllShortcuts } from './services/ShortcutService' import { TrayService } from './services/TrayService' +import { setOpenLinkExternal } from './services/WebviewService' import { windowService } from './services/WindowService' import { getResourcePath } from './utils' import { decrypt, encrypt } from './utils/aes' @@ -342,4 +343,9 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { ipcMain.handle(IpcChannel.SearchWindow_OpenUrl, async (_, uid: string, url: string) => { return await searchService.openUrlInSearchWindow(uid, url) }) + + // webview + ipcMain.handle(IpcChannel.Webview_SetOpenLinkExternal, (_, webviewId: number, isExternal: boolean) => + setOpenLinkExternal(webviewId, isExternal) + ) } diff --git a/src/main/services/WebviewService.ts b/src/main/services/WebviewService.ts new file mode 100644 index 0000000000..50da5cd1e5 --- /dev/null +++ b/src/main/services/WebviewService.ts @@ -0,0 +1,35 @@ +import { session, shell, webContents } from 'electron' + +/** + * init the useragent of the webview session + * remove the CherryStudio and Electron from the useragent + */ +export function initSessionUserAgent() { + const wvSession = session.fromPartition('persist:webview') + const newChromeVersion = '135.0.7049.96' + const originUA = wvSession.getUserAgent() + const newUA = originUA + .replace(/CherryStudio\/\S+\s/, '') + .replace(/Electron\/\S+\s/, '') + .replace(/Chrome\/\d+\.\d+\.\d+\.\d+/, `Chrome/${newChromeVersion}`) + + wvSession.setUserAgent(newUA) +} + +/** + * WebviewService handles the behavior of links opened from webview elements + * It controls whether links should be opened within the application or in an external browser + */ +export function setOpenLinkExternal(webviewId: number, isExternal: boolean) { + const webview = webContents.fromId(webviewId) + if (!webview) return + + webview.setWindowOpenHandler(({ url }) => { + if (isExternal) { + shell.openExternal(url) + return { action: 'deny' } + } else { + return { action: 'allow' } + } + }) +} diff --git a/src/main/services/WindowService.ts b/src/main/services/WindowService.ts index 62700f81c5..031e35b24d 100644 --- a/src/main/services/WindowService.ts +++ b/src/main/services/WindowService.ts @@ -11,6 +11,7 @@ import icon from '../../../build/icon.png?asset' import { titleBarOverlayDark, titleBarOverlayLight } from '../config' import { locales } from '../utils/locales' import { configManager } from './ConfigManager' +import { initSessionUserAgent } from './WebviewService' export class WindowService { private static instance: WindowService | null = null @@ -81,6 +82,9 @@ export class WindowService { this.miniWindow = this.createMiniWindow(true) } + //init the MinApp webviews' useragent + initSessionUserAgent() + return this.mainWindow } diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts index b24f206472..10fed67d3d 100644 --- a/src/preload/index.d.ts +++ b/src/preload/index.d.ts @@ -204,6 +204,9 @@ declare global { closeSearchWindow: (uid: string) => Promise openUrlInSearchWindow: (uid: string, url: string) => Promise } + webview: { + setOpenLinkExternal: (webviewId: number, isExternal: boolean) => Promise + } } } } diff --git a/src/preload/index.ts b/src/preload/index.ts index 54055d251a..251706e694 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -185,6 +185,10 @@ const api = { openSearchWindow: (uid: string) => ipcRenderer.invoke(IpcChannel.SearchWindow_Open, uid), closeSearchWindow: (uid: string) => ipcRenderer.invoke(IpcChannel.SearchWindow_Close, uid), openUrlInSearchWindow: (uid: string, url: string) => ipcRenderer.invoke(IpcChannel.SearchWindow_OpenUrl, uid, url) + }, + webview: { + setOpenLinkExternal: (webviewId: number, isExternal: boolean) => + ipcRenderer.invoke(IpcChannel.Webview_SetOpenLinkExternal, webviewId, isExternal) } } diff --git a/src/renderer/src/components/MinApp/MinappPopupContainer.tsx b/src/renderer/src/components/MinApp/MinappPopupContainer.tsx index 50a10e7fdf..9ee53bab13 100644 --- a/src/renderer/src/components/MinApp/MinappPopupContainer.tsx +++ b/src/renderer/src/components/MinApp/MinappPopupContainer.tsx @@ -3,6 +3,7 @@ import { CodeOutlined, CopyOutlined, ExportOutlined, + LinkOutlined, MinusOutlined, PushpinOutlined, ReloadOutlined @@ -14,6 +15,9 @@ import { useMinappPopup } from '@renderer/hooks/useMinappPopup' import { useMinapps } from '@renderer/hooks/useMinapps' import useNavBackgroundColor from '@renderer/hooks/useNavBackgroundColor' import { useRuntime } from '@renderer/hooks/useRuntime' +import { useSettings } from '@renderer/hooks/useSettings' +import { useAppDispatch } from '@renderer/store' +import { setMinappsOpenLinkExternal } from '@renderer/store/settings' import { MinAppType } from '@renderer/types' import { delay } from '@renderer/utils' import { Avatar, Drawer, Tooltip } from 'antd' @@ -40,6 +44,7 @@ const MinappPopupContainer: React.FC = () => { const { pinned, updatePinnedMinapps } = useMinapps() const { t } = useTranslation() const backgroundColor = useNavBackgroundColor() + const dispatch = useAppDispatch() /** control the drawer open or close */ const [isPopupShow, setIsPopupShow] = useState(true) @@ -57,6 +62,8 @@ const MinappPopupContainer: React.FC = () => { const webviewRefs = useRef>(new Map()) /** indicate whether the webview has loaded */ const webviewLoadedRefs = useRef>(new Map()) + /** whether the minapps open link external is enabled */ + const { minappsOpenLinkExternal } = useSettings() const isInDevelopment = process.env.NODE_ENV === 'development' @@ -107,9 +114,14 @@ const MinappPopupContainer: React.FC = () => { webviewLoadedRefs.current.forEach((_, appid) => { if (!webviewRefs.current.has(appid)) { webviewLoadedRefs.current.delete(appid) + } else if (appid === currentMinappId) { + const webviewId = webviewRefs.current.get(appid)?.getWebContentsId() + if (webviewId) { + window.api.webview.setOpenLinkExternal(webviewId, minappsOpenLinkExternal) + } } }) - }, [currentMinappId]) + }, [currentMinappId, minappsOpenLinkExternal]) /** only the keepalive minapp can be minimized */ const canMinimize = !(openedOneOffMinapp && openedOneOffMinapp.id == currentMinappId) @@ -175,6 +187,10 @@ const MinappPopupContainer: React.FC = () => { /** the callback function to set the webviews loaded indicator */ const handleWebviewLoaded = (appid: string) => { webviewLoadedRefs.current.set(appid, true) + const webviewId = webviewRefs.current.get(appid)?.getWebContentsId() + if (webviewId) { + window.api.webview.setOpenLinkExternal(webviewId, minappsOpenLinkExternal) + } if (appid == currentMinappId) { setTimeout(() => setIsReady(true), 200) } @@ -220,6 +236,11 @@ const MinappPopupContainer: React.FC = () => { updatePinnedMinapps(newPinned) } + /** set the open external status */ + const handleToggleOpenExternal = () => { + dispatch(setMinappsOpenLinkExternal(!minappsOpenLinkExternal)) + } + /** Title bar of the popup */ const Title = ({ appInfo, url }: { appInfo: AppInfo | null; url: string | null }) => { if (!appInfo) return null @@ -238,7 +259,7 @@ const MinappPopupContainer: React.FC = () => { } return ( - + @@ -256,6 +277,14 @@ const MinappPopupContainer: React.FC = () => { }}> handleCopyUrl(e, url ?? appInfo.url)}>{appInfo.name} + {appInfo.canOpenExternalLink && ( + + + + )} + )} - {appInfo.canOpenExternalLink && ( - - - - )} + + + {isInDevelopment && (