| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152 |
- const { app, BrowserWindow, nativeTheme, shell, protocol, net } = require('electron');
- const fs = require('node:fs');
- const path = require('node:path');
- const { pathToFileURL } = require('node:url');
- const { registerIpcHandlers } = require('./ipc/index.cjs');
- const { setupAutoUpdate, checkAndDownloadUpdate, triggerUpdateDownload, quitAndInstall } = require('./services/updateService.cjs');
- const { getGeneratedImagesDir, getImportedImagesDir } = require('./utils/paths.cjs');
- const rendererUrl = process.env.ELECTRON_RENDERER_URL;
- const iconPath = path.join(__dirname, '../assets/icon.ico');
- const packagedIndexUrl = pathToFileURL(path.join(__dirname, '../dist/index.html')).toString();
- protocol.registerSchemesAsPrivileged([{
- scheme: 'yibiao-asset',
- privileges: { standard: true, secure: true, supportFetchAPI: true },
- }]);
- function registerAssetProtocol() {
- protocol.handle('yibiao-asset', (request) => {
- try {
- const url = new URL(request.url);
- const assetRoots = {
- 'generated-images': getGeneratedImagesDir(app),
- 'imported-images': getImportedImagesDir(app),
- };
- const rootDir = assetRoots[url.hostname];
- if (!rootDir) {
- return new Response('Not found', { status: 404 });
- }
- const relativePath = decodeURIComponent(url.pathname.replace(/^\/+/, ''));
- if (!relativePath) {
- return new Response('Not found', { status: 404 });
- }
- const baseDir = path.resolve(rootDir);
- const filePath = path.resolve(baseDir, relativePath);
- if (filePath !== baseDir && !filePath.startsWith(`${baseDir}${path.sep}`)) {
- return new Response('Forbidden', { status: 403 });
- }
- if (!fs.existsSync(filePath)) {
- return new Response('Not found', { status: 404 });
- }
- return net.fetch(pathToFileURL(filePath).toString());
- } catch {
- return new Response('Invalid asset url', { status: 400 });
- }
- });
- }
- function normalizeExternalUrl(value) {
- const raw = String(value || '').trim();
- if (!raw) return null;
- const candidate = /^www\./i.test(raw) ? `https://${raw}` : raw;
- try {
- const url = new URL(candidate);
- return ['http:', 'https:'].includes(url.protocol) ? url.toString() : null;
- } catch {
- return null;
- }
- }
- function isAllowedAppNavigation(value) {
- try {
- const url = new URL(value);
- if (rendererUrl) {
- return url.origin === new URL(rendererUrl).origin;
- }
- const indexUrl = new URL(packagedIndexUrl);
- return url.protocol === 'file:' && url.pathname === indexUrl.pathname;
- } catch {
- return false;
- }
- }
- async function openExternalUrl(value) {
- const externalUrl = normalizeExternalUrl(value);
- if (!externalUrl) return;
- try {
- await shell.openExternal(externalUrl);
- } catch (error) {
- const preview = externalUrl.length > 300 ? `${externalUrl.slice(0, 300)}...` : externalUrl;
- console.warn('[electron] 打开外部链接失败', { url: preview, message: error.message || String(error) });
- }
- }
- function createMainWindow() {
- const mainWindow = new BrowserWindow({
- width: 1440,
- height: 920,
- minWidth: 1040,
- minHeight: 720,
- backgroundColor: '#f8fafd',
- title: '易标投标工具箱',
- icon: fs.existsSync(iconPath) ? iconPath : undefined,
- titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'default',
- webPreferences: {
- preload: path.join(__dirname, 'preload.cjs'),
- contextIsolation: true,
- nodeIntegration: false,
- sandbox: false,
- },
- });
- mainWindow.setMenuBarVisibility(false);
- if (rendererUrl) {
- mainWindow.loadURL(rendererUrl);
- } else {
- mainWindow.loadFile(path.join(__dirname, '../dist/index.html'));
- }
- mainWindow.webContents.setWindowOpenHandler(({ url }) => {
- void openExternalUrl(url);
- return { action: 'deny' };
- });
- mainWindow.webContents.on('will-navigate', (event, url) => {
- if (isAllowedAppNavigation(url)) {
- return;
- }
- event.preventDefault();
- void openExternalUrl(url);
- });
- return mainWindow;
- }
- app.whenReady().then(() => {
- nativeTheme.themeSource = 'light';
- registerAssetProtocol();
- const mainWindow = createMainWindow();
- registerIpcHandlers({ app, mainWindow, checkAndDownloadUpdate, triggerUpdateDownload, quitAndInstall });
- setupAutoUpdate({ app, mainWindow });
- app.on('activate', () => {
- if (BrowserWindow.getAllWindows().length === 0) {
- createMainWindow();
- }
- });
- });
- app.on('window-all-closed', () => {
- if (process.platform !== 'darwin') {
- app.quit();
- }
- });
|