From 9e660f30d64ad1055529e299d506e5b789906ff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B2=E9=B8=BF=E5=AE=87?= Date: Thu, 5 Jun 2025 20:52:10 +0800 Subject: [PATCH 1/2] =?UTF-8?q?refactor(preload):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E9=A2=84=E5=8A=A0=E8=BD=BD=E8=84=9A=E6=9C=AC=EF=BC=8C=E6=95=B4?= =?UTF-8?q?=E5=90=88=E5=85=B1=E4=BA=ABAPI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 史鸿宇 --- electron/main/common/ipc.ts | 2 +- electron/preload/index.ts | 231 +++++++++++++----------------- electron/preload/shared.ts | 277 ++++++++++++++++++++++++++++++++++++ electron/preload/types.d.ts | 50 ++++--- electron/preload/welcome.ts | 224 +++++++---------------------- 5 files changed, 456 insertions(+), 328 deletions(-) create mode 100644 electron/preload/shared.ts diff --git a/electron/main/common/ipc.ts b/electron/main/common/ipc.ts index 360a328..bb36f9c 100644 --- a/electron/main/common/ipc.ts +++ b/electron/main/common/ipc.ts @@ -194,7 +194,7 @@ async function validateServerConnection(url: string): Promise<{ const requestModule = isHttps ? https : http; return new Promise((resolve) => { - const timeout = 10000; // 10秒超时 + const timeout = 1500; // 1.5秒超时 const options = { hostname: parsedUrl.hostname, diff --git a/electron/preload/index.ts b/electron/preload/index.ts index 4181467..4c820ff 100644 --- a/electron/preload/index.ts +++ b/electron/preload/index.ts @@ -7,158 +7,131 @@ // IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR // PURPOSE. // See the Mulan PSL v2 for more details. -import { ipcRenderer, contextBridge } from 'electron'; - -function validateIPC(channel: string): true | never { - if (!channel || !channel.startsWith('copilot:')) { - // 允许窗口状态变化事件通过验证 - if ( - channel === 'window-maximized-change' || - channel === 'window-is-maximized' - ) { - return true; - } - throw new Error(`Unsupported event IPC channel '${channel}'`); - } - return true; -} +import { contextBridge } from 'electron'; +import { + safeIPC, + sharedConfigAPI, + windowAPI, + systemAPI, + themeAPI, + utilsAPI, +} from './shared'; +import type { DesktopConfig } from './types'; + +/** + * 主窗口 preload 脚本 + * 提供主应用所需的所有 API + */ const globals = { - ipcRenderer: { - invoke(channel: string, ...args: any[]): Promise { - validateIPC(channel); - return ipcRenderer.invoke(channel, ...args); - }, + // 原始 IPC 接口(兼容性) + ipcRenderer: safeIPC, - // 添加事件监听方法 - on(channel: string, listener: (...args: any[]) => void): void { - validateIPC(channel); - ipcRenderer.on(channel, (event, ...args) => listener(...args)); - }, - - // 添加一次性事件监听方法 - once(channel: string, listener: (...args: any[]) => void): void { - validateIPC(channel); - ipcRenderer.once(channel, (event, ...args) => listener(...args)); - }, - - // 添加移除特定事件监听器的方法 - removeListener(channel: string, listener: (...args: any[]) => void): void { - validateIPC(channel); - ipcRenderer.removeListener(channel, listener); - }, - - // 添加移除所有事件监听器的方法 - removeAllListeners(channel: string): void { - validateIPC(channel); - ipcRenderer.removeAllListeners(channel); - }, - }, - - // 配置管理 API + // 配置管理(主程序完整功能) config: { - // 获取完整配置 - get: async (): Promise => { - return await ipcRenderer.invoke('copilot:get-config'); - }, - - // 更新配置(部分更新) - update: async (updates: Record): Promise => { - return await ipcRenderer.invoke('copilot:update-config', updates); - }, - - // 重置配置 - reset: async (): Promise => { - return await ipcRenderer.invoke('copilot:reset-config'); - }, - - // 设置代理 URL - setProxyUrl: async (url: string): Promise => { - return await ipcRenderer.invoke('copilot:set-proxy-url', url); - }, - - // 获取代理 URL + /** + * 获取当前配置 + */ + get: async (): Promise => { + try { + return await safeIPC.invoke('copilot:get-config'); + } catch (error) { + console.error('Failed to get config:', error); + return null; + } + }, + + /** + * 更新配置 + */ + update: async ( + updates: Partial, + ): Promise => { + try { + return await safeIPC.invoke('copilot:update-config', updates); + } catch (error) { + console.error('Failed to update config:', error); + return null; + } + }, + + /** + * 重置配置为默认值 + */ + reset: async (): Promise => { + try { + return await safeIPC.invoke('copilot:reset-config'); + } catch (error) { + console.error('Failed to reset config:', error); + return null; + } + }, + + /** + * 设置代理URL + */ + setProxyUrl: sharedConfigAPI.setProxyUrl, + + /** + * 获取代理URL + */ getProxyUrl: async (): Promise => { - return await ipcRenderer.invoke('copilot:get-proxy-url'); - }, + try { + return await safeIPC.invoke('copilot:get-proxy-url'); + } catch (error) { + console.error('Failed to get proxy URL:', error); + return ''; + } + }, + + /** + * 验证服务器连接(统一接口) + */ + validateServer: sharedConfigAPI.validateServer, }, - // 欢迎界面 API - welcome: { - // 显示欢迎界面 - show: async (): Promise => { - return await ipcRenderer.invoke('copilot:show-welcome'); - }, - - // 完成欢迎流程 - complete: async (): Promise => { - return await ipcRenderer.invoke('copilot:complete-welcome'); - }, - }, - - // 窗口控制 API + // 窗口控制 window: { - // 窗口控制 + // 统一的窗口控制接口 control: async ( command: 'minimize' | 'maximize' | 'close', ): Promise => { - return await ipcRenderer.invoke('copilot:window-control', command); - }, - - // 检查窗口是否最大化 - isMaximized: async (): Promise => { - return await ipcRenderer.invoke('copilot:window-is-maximized'); - }, - - // 监听窗口最大化状态变化 - onMaximizedChange: (callback: (isMaximized: boolean) => void): void => { - ipcRenderer.on('window-maximized-change', (event, isMaximized) => - callback(isMaximized), - ); - }, - - // 移除窗口最大化状态变化监听 - offMaximizedChange: (): void => { - ipcRenderer.removeAllListeners('window-maximized-change'); - }, + switch (command) { + case 'minimize': + return await windowAPI.minimize(); + case 'maximize': + return await windowAPI.maximize(); + case 'close': + return await windowAPI.close(); + default: + throw new Error(`Unknown window command: ${command}`); + } + }, + + // 单独的方法(向后兼容) + ...windowAPI, }, - // 主题 API - theme: { - // 切换主题 - toggle: async (): Promise => { - return await ipcRenderer.invoke('copilot:toggle'); - }, + // 主题管理 + theme: themeAPI, - // 设置系统主题 - setSystem: async (): Promise => { - return await ipcRenderer.invoke('copilot:system'); - }, - }, + // 系统信息 + process: systemAPI, - process: { - get platform() { - return process.platform; - }, - get arch() { - return process.arch; - }, - get versions() { - return process.versions; - }, - get env() { - return { ...process.env }; - }, - }, + // 实用工具 + utils: utilsAPI, }; +// 暴露 API 到渲染进程 if (process.contextIsolated) { try { contextBridge.exposeInMainWorld('eulercopilot', globals); } catch (error) { - console.error(error); + console.error('Failed to expose main API:', error); } } else { (window as any).eulercopilot = globals; } + +console.log('Main preload script loaded'); diff --git a/electron/preload/shared.ts b/electron/preload/shared.ts new file mode 100644 index 0000000..a03d467 --- /dev/null +++ b/electron/preload/shared.ts @@ -0,0 +1,277 @@ +// Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +// licensed under the Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN 'AS IS' BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +// PURPOSE. +// See the Mulan PSL v2 for more details. + +import { ipcRenderer } from 'electron'; +import type { ServerValidationResult } from './types'; + +/** + * 共享的 preload 功能模块 + * 避免在多个 preload 脚本中重复代码 + */ + +/** + * IPC 通道验证 + */ +export function validateIPC(channel: string): true | never { + if (!channel || !channel.startsWith('copilot:')) { + // 允许窗口状态变化事件通过验证 + if ( + channel === 'window-maximized-change' || + channel === 'window-is-maximized' + ) { + return true; + } + throw new Error(`Unsupported event IPC channel '${channel}'`); + } + return true; +} + +/** + * 安全的 IPC 调用包装器 + */ +export const safeIPC = { + invoke: async (channel: string, ...args: any[]): Promise => { + validateIPC(channel); + return await ipcRenderer.invoke(channel, ...args); + }, + + on: (channel: string, listener: (...args: any[]) => void): void => { + validateIPC(channel); + ipcRenderer.on(channel, (event, ...args) => listener(...args)); + }, + + once: (channel: string, listener: (...args: any[]) => void): void => { + validateIPC(channel); + ipcRenderer.once(channel, (event, ...args) => listener(...args)); + }, + + removeListener: ( + channel: string, + listener: (...args: any[]) => void, + ): void => { + validateIPC(channel); + ipcRenderer.removeListener(channel, listener); + }, + + removeAllListeners: (channel: string): void => { + validateIPC(channel); + ipcRenderer.removeAllListeners(channel); + }, +}; + +/** + * 配置管理 API(代理设置和服务器验证,供所有窗口使用) + */ +export const sharedConfigAPI = { + /** + * 设置代理URL + */ + setProxyUrl: async (url: string): Promise => { + try { + return await safeIPC.invoke('copilot:set-proxy-url', url); + } catch (error) { + console.error('Failed to set proxy URL:', error); + return false; + } + }, + + /** + * 验证服务器连接(统一接口) + */ + validateServer: async (url: string): Promise => { + try { + return await safeIPC.invoke('copilot:validate-server', url); + } catch (error) { + console.error('Failed to validate server:', error); + return { + isValid: false, + error: + error instanceof Error ? error.message : '验证服务器时发生未知错误', + }; + } + }, +}; + +/** + * 窗口控制 API(共享) + */ +export const windowAPI = { + /** + * 关闭窗口 + */ + close: async (): Promise => { + try { + await safeIPC.invoke('copilot:window-control', 'close'); + } catch (error) { + console.error('Failed to close window:', error); + } + }, + + /** + * 最小化窗口 + */ + minimize: async (): Promise => { + try { + await safeIPC.invoke('copilot:window-control', 'minimize'); + } catch (error) { + console.error('Failed to minimize window:', error); + } + }, + + /** + * 最大化窗口 + */ + maximize: async (): Promise => { + try { + await safeIPC.invoke('copilot:window-control', 'maximize'); + } catch (error) { + console.error('Failed to maximize window:', error); + } + }, + + /** + * 检查窗口是否最大化 + */ + isMaximized: async (): Promise => { + try { + return await safeIPC.invoke('copilot:window-is-maximized'); + } catch (error) { + console.error('Failed to check window maximized state:', error); + return false; + } + }, + + /** + * 监听窗口最大化状态变化 + */ + onMaximizedChange: (callback: (isMaximized: boolean) => void): void => { + safeIPC.on('window-maximized-change', callback); + }, + + /** + * 移除窗口最大化状态变化监听 + */ + offMaximizedChange: (): void => { + safeIPC.removeAllListeners('window-maximized-change'); + }, +}; + +/** + * 系统信息 API(共享) + */ +export const systemAPI = { + /** + * 获取平台信息 + */ + get platform(): string { + return process.platform; + }, + + /** + * 获取架构信息 + */ + get arch(): string { + return process.arch; + }, + + /** + * 获取版本信息 + */ + get versions(): NodeJS.ProcessVersions { + return process.versions; + }, + + /** + * 获取环境变量(安全的副本) + */ + get env(): Record { + return { ...process.env }; + }, +}; + +/** + * 实用工具 API(共享) + */ +export const utilsAPI = { + /** + * 检查 URL 格式是否有效 + */ + isValidUrl: (url: string): boolean => { + try { + new URL(url); + return true; + } catch { + return false; + } + }, + + /** + * 格式化 URL(确保有协议) + */ + formatUrl: (url: string): string => { + if (!url) return ''; + + // 移除尾部斜杠 + url = url.replace(/\/+$/, ''); + + // 如果没有协议,默认添加 https:// + if (!/^https?:\/\//i.test(url)) { + url = 'https://' + url; + } + + return url; + }, + + /** + * 延迟执行 + */ + delay: (ms: number): Promise => { + return new Promise((resolve) => setTimeout(resolve, ms)); + }, +}; + +/** + * 主题 API(共享) + */ +export const themeAPI = { + /** + * 切换主题 + */ + toggle: async (): Promise => { + try { + return await safeIPC.invoke('copilot:toggle'); + } catch (error) { + console.error('Failed to toggle theme:', error); + return null; + } + }, + + /** + * 设置系统主题 + */ + setSystem: async (): Promise => { + try { + return await safeIPC.invoke('copilot:system'); + } catch (error) { + console.error('Failed to set system theme:', error); + return null; + } + }, +}; + +// 确保此文件被识别为 ES 模块 +export default { + safeIPC, + sharedConfigAPI, + windowAPI, + systemAPI, + utilsAPI, + themeAPI, +}; diff --git a/electron/preload/types.d.ts b/electron/preload/types.d.ts index a742119..ed63620 100644 --- a/electron/preload/types.d.ts +++ b/electron/preload/types.d.ts @@ -10,8 +10,15 @@ export interface DesktopConfig { [key: string]: any; } +export interface ServerValidationResult { + isValid: boolean; + error?: string; + status?: number; + responseTime?: number; +} + export interface DesktopAppAPI { - // IPC 渲染器 + // IPC 渲染器(原始接口) ipcRenderer: { invoke(channel: string, ...args: any[]): Promise; on(channel: string, listener: (...args: any[]) => void): void; @@ -22,22 +29,20 @@ export interface DesktopAppAPI { // 配置管理 config: { - get(): Promise; - update(updates: Partial): Promise; - reset(): Promise; + get(): Promise; + update(updates: Partial): Promise; + reset(): Promise; setProxyUrl(url: string): Promise; getProxyUrl(): Promise; - }; - - // 欢迎界面 - welcome: { - show(): Promise; - complete(): Promise; + validateServer(url: string): Promise; }; // 窗口控制 window: { control(command: 'minimize' | 'maximize' | 'close'): Promise; + close(): Promise; + minimize(): Promise; + maximize(): Promise; isMaximized(): Promise; onMaximizedChange(callback: (isMaximized: boolean) => void): void; offMaximizedChange(): void; @@ -54,36 +59,37 @@ export interface DesktopAppAPI { platform: string; arch: string; versions: NodeJS.ProcessVersions; - env: Record; + env: Record; + }; + + // 实用工具 + utils: { + isValidUrl(url: string): boolean; + formatUrl(url: string): string; + delay(ms: number): Promise; }; } export interface DesktopAppWelcomeAPI { - // 配置管理 + // 配置管理(代理设置和服务器验证,欢迎界面功能) config: { - get(): Promise; - update(updates: Partial): Promise; - reset(): Promise; - validateServer(url: string): Promise; + setProxyUrl(url: string): Promise; + validateServer(url: string): Promise; }; // 欢迎流程 welcome: { + show(): Promise; complete(): Promise; cancel(): Promise; }; - // 窗口控制 - window: { - close(): Promise; - minimize(): Promise; - }; - // 系统信息 system: { platform: string; arch: string; versions: NodeJS.ProcessVersions; + env: Record; }; // 实用工具 diff --git a/electron/preload/welcome.ts b/electron/preload/welcome.ts index b21ae88..4029f26 100644 --- a/electron/preload/welcome.ts +++ b/electron/preload/welcome.ts @@ -8,200 +8,72 @@ // PURPOSE. // See the Mulan PSL v2 for more details. -import { ipcRenderer, contextBridge } from 'electron'; -import type { DesktopConfig } from './types'; +import { contextBridge } from 'electron'; +import { sharedConfigAPI, systemAPI, utilsAPI, safeIPC } from './shared'; /** * 欢迎界面专用的 preload 脚本 - * 提供配置管理和欢迎流程相关的 API + * 提供配置管理和欢迎流程相关的 API,使用统一的后端验证接口 */ /** - * 欢迎界面 API + * 欢迎流程 API(专用于欢迎界面) */ -const welcomeAPI = { - // 配置管理 - config: { - /** - * 获取当前配置 - */ - get: async (): Promise => { - try { - return await ipcRenderer.invoke('copilot:get-config'); - } catch (error) { - console.error('Failed to get config:', error); - return null; - } - }, - - /** - * 更新配置 - */ - update: async ( - updates: Partial, - ): Promise => { - try { - return await ipcRenderer.invoke('copilot:update-config', updates); - } catch (error) { - console.error('Failed to update config:', error); - return null; - } - }, - - /** - * 重置配置为默认值 - */ - reset: async (): Promise => { - try { - return await ipcRenderer.invoke('copilot:reset-config'); - } catch (error) { - console.error('Failed to reset config:', error); - return null; - } - }, - - /** - * 验证服务器连接 - */ - validateServer: async (url: string): Promise => { - try { - // 这里可以添加服务器连接验证逻辑 - // 暂时返回 true,实际实现时可以通过 IPC 调用主进程进行网络检查 - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 5000); - - const response = await fetch(url, { - method: 'HEAD', - mode: 'no-cors', - signal: controller.signal, - }); - - clearTimeout(timeoutId); - return response.ok || response.type === 'opaque'; - } catch (error) { - console.warn('Server validation failed:', error); - return false; - } - }, +const welcomeFlowAPI = { + /** + * 显示欢迎界面 + */ + show: async (): Promise => { + try { + return await safeIPC.invoke('copilot:show-welcome'); + } catch (error) { + console.error('Failed to show welcome:', error); + return false; + } }, - // 欢迎流程管理 - welcome: { - /** - * 完成欢迎设置流程 - */ - complete: async (): Promise => { - try { - return await ipcRenderer.invoke('copilot:complete-welcome'); - } catch (error) { - console.error('Failed to complete welcome flow:', error); - return false; - } - }, - - /** - * 取消欢迎流程(关闭窗口) - */ - cancel: async (): Promise => { - try { - await ipcRenderer.invoke('copilot:window-control', 'close'); - } catch (error) { - console.error('Failed to cancel welcome flow:', error); - } - }, - }, - - // 窗口控制 - window: { - /** - * 关闭窗口 - */ - close: async (): Promise => { - try { - await ipcRenderer.invoke('copilot:window-control', 'close'); - } catch (error) { - console.error('Failed to close window:', error); - } - }, - - /** - * 最小化窗口 - */ - minimize: async (): Promise => { - try { - await ipcRenderer.invoke('copilot:window-control', 'minimize'); - } catch (error) { - console.error('Failed to minimize window:', error); - } - }, + /** + * 完成欢迎设置流程 + */ + complete: async (): Promise => { + try { + return await safeIPC.invoke('copilot:complete-welcome'); + } catch (error) { + console.error('Failed to complete welcome flow:', error); + return false; + } }, - // 系统信息 - system: { - /** - * 获取平台信息 - */ - get platform(): string { - return process.platform; - }, - - /** - * 获取架构信息 - */ - get arch(): string { - return process.arch; - }, - - /** - * 获取版本信息 - */ - get versions(): NodeJS.ProcessVersions { - return process.versions; - }, + /** + * 取消欢迎流程(关闭窗口) + */ + cancel: async (): Promise => { + try { + await safeIPC.invoke('copilot:window-control', 'close'); + } catch (error) { + console.error('Failed to cancel welcome flow:', error); + } }, +}; - // 实用工具 - utils: { - /** - * 检查 URL 格式是否有效 - */ - isValidUrl: (url: string): boolean => { - try { - new URL(url); - return true; - } catch { - return false; - } - }, - - /** - * 格式化 URL(确保有协议) - */ - formatUrl: (url: string): string => { - if (!url) return ''; - - // 移除尾部斜杠 - url = url.replace(/\/+$/, ''); +/** + * 欢迎界面 API + * 只提供代理设置、服务器验证和欢迎流程功能 + */ +const welcomeAPI = { + // 配置管理(仅代理设置和服务器验证) + config: sharedConfigAPI, - // 如果没有协议,默认添加 https:// - if (!/^https?:\/\//i.test(url)) { - url = 'https://' + url; - } + // 欢迎流程管理(专用实现) + welcome: welcomeFlowAPI, - return url; - }, + // 系统信息(使用共享模块) + system: systemAPI, - /** - * 延迟执行 - */ - delay: (ms: number): Promise => { - return new Promise((resolve) => setTimeout(resolve, ms)); - }, - }, + // 实用工具(使用共享模块) + utils: utilsAPI, }; -// 类型定义已在 types.d.ts 中声明 - // 暴露 API 到渲染进程 if (process.contextIsolated) { try { -- Gitee From d3e3730feae6d42cd65548636ebfb8bf51db6a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B2=E9=B8=BF=E5=AE=87?= Date: Thu, 5 Jun 2025 20:57:47 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3=E6=AC=A2=E8=BF=8E?= =?UTF-8?q?=E7=AA=97=E5=8F=A3=E9=A2=84=E5=8A=A0=E8=BD=BD=E8=84=9A=E6=9C=AC?= =?UTF-8?q?=E5=92=8CHTML=E6=96=87=E4=BB=B6=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 史鸿宇 --- electron/main/window/welcome.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/electron/main/window/welcome.ts b/electron/main/window/welcome.ts index b2ff1ec..6818244 100644 --- a/electron/main/window/welcome.ts +++ b/electron/main/window/welcome.ts @@ -43,13 +43,13 @@ export function createWelcomeWindow(): BrowserWindow { webPreferences: { nodeIntegration: false, contextIsolation: true, - preload: path.join(__dirname, '../../preload/welcome.js'), // 欢迎界面专用预加载脚本 + preload: path.join(__dirname, '../preload/welcome.js'), // 欢迎界面专用预加载脚本 }, show: false, }); // 加载欢迎界面的 HTML 文件 - welcomeWindow.loadFile(path.join(__dirname, '../../welcome/index.html')); + welcomeWindow.loadFile(path.join(__dirname, '../welcome/index.html')); // 开发模式下可以打开开发者工具 if (process.env.NODE_ENV === 'development') { -- Gitee