cc-haha/desktop/electron/services/navigationGuards.ts
程序员阿江(Relakkes) ef87589923 fix(desktop): set Windows AppUserModelID and guard window navigation
- applyWindowsAppUserModelId() so Windows attributes toast notifications to the
  app (kept in sync with build.appId via a test); no-op on macOS/Linux
- main window: setWindowOpenHandler denies uncontrolled popups, routes http(s)
  links to the system browser; intentionally no will-navigate guard so dev HMR
  reloads keep working
- preview view: denies popups + blocks non-http(s) navigation (file:/custom
  schemes) while allowing in-page http(s) browsing

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 14:17:13 +08:00

59 lines
1.9 KiB
TypeScript

export type WindowOpenHandlerResult = { action: 'deny' } | { action: 'allow' }
export type NavigationGuardWebContents = {
setWindowOpenHandler(handler: (details: { url: string }) => WindowOpenHandlerResult): void
on(
event: 'will-navigate',
handler: (event: { preventDefault: () => void }, url: string) => void,
): unknown
}
export type NavigationGuardOptions = {
openExternal: (url: string) => void
}
export function isHttpUrl(url: string): boolean {
try {
const { protocol } = new URL(url)
return protocol === 'http:' || protocol === 'https:'
} catch {
return false
}
}
/**
* Main app window guard. The renderer is a single-page app loaded from a fixed
* entry; it should never spawn an uncontrolled child window. Any window.open /
* target=_blank with an http(s) URL is routed to the system browser and the
* Electron popup is denied. We intentionally do NOT install a `will-navigate`
* guard here so that dev HMR reloads and in-app navigations keep working.
*/
export function installMainWindowNavigationGuards(
webContents: NavigationGuardWebContents,
{ openExternal }: NavigationGuardOptions,
): void {
webContents.setWindowOpenHandler(({ url }) => {
if (isHttpUrl(url)) openExternal(url)
return { action: 'deny' }
})
}
/**
* Preview (WebContentsView) guard. The preview renders untrusted remote pages,
* so it must keep working as a browser: in-page http(s) navigation is allowed.
* Popups are denied (http(s) ones handed to the system browser), and navigation
* to any non-http(s) scheme (file:, custom schemes) is blocked outright.
*/
export function installPreviewNavigationGuards(
webContents: NavigationGuardWebContents,
{ openExternal }: NavigationGuardOptions,
): void {
webContents.setWindowOpenHandler(({ url }) => {
if (isHttpUrl(url)) openExternal(url)
return { action: 'deny' }
})
webContents.on('will-navigate', (event, url) => {
if (!isHttpUrl(url)) event.preventDefault()
})
}