-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.js
More file actions
388 lines (339 loc) · 13.2 KB
/
Copy pathmain.js
File metadata and controls
388 lines (339 loc) · 13.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
// main.js
const { app, BrowserWindow,ipcMain,shell } = require('electron');
const path = require('path');
const http = require('http');
const { URL } = require('url'); // Node.js 的 URL 类
const { exec, spawn } = require('child_process');
import { initDockerSetupService, checkAndRunDockerSetup, DOCKER_SETUP_DONE_KEY } from './dockerSetupService.js';
let mainWindow;
let backendProcess; // 你的后端进程变量
// Store 实例将在异步加载后创建
let store;
const dotenv = require('dotenv');
const fs = require('fs');
const isDev = !app.isPackaged; // 是否为开发环境
// 判断环境来设定 .env 文件路径
const envPath = isDev
? path.resolve(process.cwd(), '.env') // 开发环境
: path.resolve(process.resourcesPath, '.env'); // 打包后
// 安全地加载 .env
if (fs.existsSync(envPath)) {
dotenv.config({ path: envPath });
console.log('✅ .env 加载成功:', process.env);
} else {
console.error('❌ .env 文件不存在:', envPath);
}
if (app && app.getPath) {
const dataUserPath = app.getPath("userData");
console.log("ELECTRON.APP.USER.PATH", dataUserPath);
process.env.LEMON_AI_PATH = dataUserPath;
}
// 防止多实例
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
console.log('Another instance is running, quitting...');
app.quit();
} else {
app.on('second-instance', () => {
console.log('Second instance detected');
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
}
});
function executeDockerInfo() {
return new Promise((resolve, reject) => {
const cmd = process.platform === 'win32'
? 'powershell -Command "Get-Command docker | Select-Object -ExpandProperty Source"'
: 'which docker';
exec(cmd, (error, stdout, stderr) => {
if (error) {
console.error('❌ Docker 检测失败:', error.message);
return reject({ error, stderr });
}
const dockerPath = stdout.trim();
if (!dockerPath) return reject({ error: 'Docker 路径为空', stderr });
console.log('✅ 检测到 Docker 路径:', dockerPath);
// 提取路径目录部分(不包括 docker 本身)
const dockerDir = path.dirname(dockerPath);
resolve(dockerDir);
});
});
}
function addToEnvPath(dir) {
const delimiter = process.platform === 'win32' ? ';' : ':';
const pathSet = new Set(process.env.PATH.split(delimiter));
pathSet.add(dir);
process.env.PATH = Array.from(pathSet).join(delimiter);
}
// 初始 PATH 设置(用于前置查找)
function setupInitialDockerPathEnv() {
const platform = process.platform;
const customPaths = new Set();
if (platform === 'darwin') {
customPaths.add('/usr/bin');
customPaths.add('/bin');
customPaths.add('/usr/sbin');
customPaths.add('/sbin');
customPaths.add('/Applications/Docker.app/Contents/Resources/bin');
customPaths.add('/opt/homebrew/bin');
} else if (platform === 'win32') {
customPaths.add('C:\\Program Files\\Docker\\Docker\\resources\\bin');
}
const delimiter = platform === 'win32' ? ';' : ':';
const originalPaths = process.env.PATH.split(delimiter);
originalPaths.forEach(p => customPaths.add(p));
process.env.PATH = Array.from(customPaths).join(delimiter);
}
function createWindow() {
console.log('Creating new window at:', new Date().toISOString());
console.log('preload path',path.join(__dirname, '../preload.js'), );
mainWindow = new BrowserWindow({
width: 1000,
height: 800,
webPreferences: {
nodeIntegration: false, // 保持 false,使用 preload
contextIsolation: true, // 保持 true,使用 preload
preload: path.join(__dirname, '../preload.js'), // 使用你的 preload 脚本
},
// 这里不再加载页面,加载哪个页面由 checkAndRunDockerSetup 决定
});
mainWindow.on('closed', () => {
console.log('Window closed');
mainWindow = null;
});
// 返回窗口实例
return mainWindow;
}
ipcMain.on('setup-complete-load-main', (event) => {
// 获取发送消息的窗口
// 或者直接使用外部作用域的 mainWindow 变量
const win = BrowserWindow.fromWebContents(event.sender);
if (!win) {
console.error('主进程收到加载主页面请求,但找不到对应的窗口');
return;
}
console.log('主进程收到渲染进程的加载主页面请求,开始执行加载主页面逻辑...');
// 执行加载主页面的逻辑
if (process.env.NODE_ENV === 'development') {
win.loadURL('http://localhost:5005');
} else {
win.loadFile(path.join(__dirname, 'renderer/main_window/index.html'));
}
});
let deeplinkUrl = '';
const isMac = process.platform === 'darwin';
const isWin = process.platform === 'win32';
// 注册协议(Windows 要指定 exe)
if (!app.isDefaultProtocolClient('lemonai')) {
if (isWin) {
app.setAsDefaultProtocolClient('lemonai', process.execPath, []);
} else {
app.setAsDefaultProtocolClient('lemonai');
}
}
function handleDeepLink(url) {
try {
const parsedUrl = new URL(url);
const pathname = parsedUrl.pathname;
const host = parsedUrl.hostname;
const target = host || pathname.replace(/^\//, '');
const sendToRenderer = (channel, data) => {
if (mainWindow) {
mainWindow.webContents.send(channel, data);
mainWindow.focus();
} else {
// App 还没 ready,缓存消息
deeplinkUrl = JSON.stringify({ channel, data });
}
};
switch (target) {
case 'auth': {
const code = parsedUrl.searchParams.get('code');
const state = parsedUrl.searchParams.get('state');
sendToRenderer('oauth-login-success', { code, state });
break;
}
case 'pay-result': {
const orderId = parsedUrl.searchParams.get('orderId');
const amount = parsedUrl.searchParams.get('amount');
const currency = parsedUrl.searchParams.get('currency');
const status = parsedUrl.searchParams.get('status');
sendToRenderer('stripe-payment-success', { orderId, amount, currency, status });
break;
}
case 'payment-cancelled': {
sendToRenderer('stripe-payment-cancel', {});
break;
}
default: {
console.warn('⚠️ Unknown deep link target:', target);
}
}
} catch (err) {
console.error('Failed to handle deep link:', err);
}
}
// 单实例锁
const gotLock = app.requestSingleInstanceLock();
if (!gotLock) {
app.quit();
} else {
if (isWin) {
// Windows:通过 argv 获取协议参数
app.on('second-instance', (event, argv) => {
console.log("argv ======",argv);
const urlArg = argv.find(arg => arg.startsWith('lemonai://'));
if (urlArg) {
console.log("urlArg ==== ",urlArg);
deeplinkUrl = urlArg;
handleDeepLink(urlArg);
}
});
}
if (isMac) {
// macOS:使用 open-url 事件
// 处理 macOS 的协议打开事件
app.on('open-url', (event, url) => {
event.preventDefault();
console.log('🍋 Received lemonai:// URL:', url);
handleDeepLink(url);
})
}
}
// 将整个 then 回调函数标记为 async
app.whenReady().then(async () => {
console.log('Electron Main: App ready.');
console.log('当前系统平台:', process.platform);
// 预置常见路径,避免找不到 docker
setupInitialDockerPathEnv();
try {
const dockerDir = await executeDockerInfo();
addToEnvPath(dockerDir);
console.log('🎯 Docker 所在目录添加到 PATH:', dockerDir);
} catch (err) {
console.error('🚫 获取 Docker 路径失败:', err);
}
// --- 使用动态 import 异步加载 electron-store ---
let StoreModule;
try {
// 使用 await 等待 electron-store 模块加载完成
StoreModule = await import('electron-store');
console.log('electron-store module imported dynamically.');
} catch (err) {
console.error('Failed to dynamically import electron-store:', err);
// 如果 electron-store 加载失败,这是个严重错误,可能需要退出应用或提示用户
app.quit(); // 选择退出应用
return; // 停止后续执行
}
// 从动态导入的模块中获取 Store 构造函数 (通常在 .default 属性上)
const Store = StoreModule.default;
// 创建 Store 实例 (现在确保在 electron-store 加载并获取构造函数之后)
store = new Store();
console.log('electron-store instance created.');
console.log('=== userData ====',app.getPath('userData'));
// --- 现在可以继续执行依赖 store 实例的逻辑 ---
// 初始化 Docker 设置服务,传入 store 和用户数据路径
// initDockerSetupService 负责设置 IPC 监听器
initDockerSetupService({
store: store, // 将 Store 实例传递进去
userDataPath: app.getPath('userData'), // 将用户数据路径传递进去
});
console.log('Docker setup service initialized.');
//数据库初始化 sqllite3
//node src/models/sync.js
require(path.join(__dirname, '../src/models/sync.js'));
console.log('Database initialized.');
// 启动后端进程 (时机可能需要调整,确保后端在 Docker 就绪后才能正常工作)
console.log('Spawning backend process...');
try {
// @ts-ignore
// 确保你的 '../bin/www' 文件能够通过 require 正常启动后端服务
require(path.join(__dirname, '../bin/www'));
console.log('Backend process started.');
} catch (err) {
console.error('Failed to start backend service:', err);
// 考虑在这里处理后端启动失败的情况
}
// 创建主窗口
const createdWindow = createWindow(); // 获取创建的窗口实例
console.log('Main window created.');
// 运行 Docker 设置检查和流程
checkAndRunDockerSetup(createdWindow);
console.log('Docker setup check initiated.');
// 其他 app ready 后续逻辑...
function isAllowedRedirectUrl(url) {
const whitelist = [
// 'https://accounts.google.com/o/oauth2',
// 'https://accounts.google.com/',
// 'https://www.google.com/accounts',
// 'https://checkout.stripe.com'
];
return whitelist.some(allowed => url.startsWith(allowed));
}
// 拦截新窗口打开
createdWindow.webContents.setWindowOpenHandler(({ url }) => {
if (isAllowedRedirectUrl(url)) {
// 对于谷歌登录链接,允许在应用内打开(action: 'allow')
return { action: 'allow' };
}
// 其他 http/https 链接都用系统浏览器打开
if (url.startsWith('http')) {
shell.openExternal(url);
return { action: 'deny' };
}
return { action: 'allow' };
});
// 拦截页面内跳转
createdWindow.webContents.on('will-navigate', (event, url) => {
if (isAllowedRedirectUrl(url)) {
// 对于谷歌登录相关的 URL,允许跳转,什么都不做
return;
}
// 非谷歌登录且为新的 http/https 跳转,阻止并用系统浏览器打开
if (url.startsWith('http') && url !== createdWindow.webContents.getURL()) {
event.preventDefault();
shell.openExternal(url);
}
});
});
app.on('before-quit', () => {
console.log('App quitting, killing backend process...');
// 如果你的后端是通过 spawn 启动的,在这里杀死进程
// if (backendProcess && !backendProcess.killed) {
// backendProcess.kill();
// }
});
app.on('activate', () => {
// 当应用激活但没有可见窗口时
if (mainWindow === null) {
const createdWindow = createWindow();
// 如果是新创建的窗口,同样需要运行检查
// 这里的逻辑依赖 store 已经创建和服务已经初始化,
// 考虑到这是 activate 事件,通常 app.whenReady 已经执行过了,所以 store 应该已经创建。
checkAndRunDockerSetup(createdWindow);
}
});
}