@glassnote/client 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALACION.md +258 -0
- package/README.md +228 -0
- package/autoStart.js +608 -0
- package/autoupdate.js +473 -0
- package/autoupdate_renderer.js +371 -0
- package/bin/glassnote.js +523 -0
- package/images/demo01.png +0 -0
- package/images/dmg-background.png +0 -0
- package/images/dmg-background.svg +37628 -0
- package/images/icon-16.png +0 -0
- package/images/icon-24.png +0 -0
- package/images/icon-512.png +0 -0
- package/images/icon.ico +0 -0
- package/images/icon.png +0 -0
- package/images/splash.svg +61 -0
- package/localserver.js +297 -0
- package/logUtilities.js +128 -0
- package/main.js +745 -0
- package/npmMode.js +139 -0
- package/npmUpdate.js +152 -0
- package/package.json +141 -0
- package/preload.js +148 -0
- package/userData.js +492 -0
package/main.js
ADDED
|
@@ -0,0 +1,745 @@
|
|
|
1
|
+
const {
|
|
2
|
+
app,
|
|
3
|
+
BrowserWindow,
|
|
4
|
+
Notification,
|
|
5
|
+
Tray,
|
|
6
|
+
Menu,
|
|
7
|
+
screen,
|
|
8
|
+
} = require('electron');
|
|
9
|
+
const packageJson = require('./package.json');
|
|
10
|
+
const LocalServer = require('./localserver');
|
|
11
|
+
const userData = require('./userData');
|
|
12
|
+
const { ipcMain } = require('electron');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const AutoStart = require('./autoStart');
|
|
16
|
+
const AutoUpdate = require('./autoupdate');
|
|
17
|
+
const NpmUpdate = require('./npmUpdate');
|
|
18
|
+
const npmMode = require('./npmMode');
|
|
19
|
+
const AutoUpdateRenderer = require('./autoupdate_renderer');
|
|
20
|
+
const { createTailUtility } = require('./logUtilities');
|
|
21
|
+
|
|
22
|
+
const logRenderer = packageJson.logRenderer !== undefined ? packageJson.logRenderer : true;
|
|
23
|
+
|
|
24
|
+
// Sin empaquetar, electron llamaría a la app "Electron" en la bandeja, el menú de macOS
|
|
25
|
+
// y las notificaciones. Instalado por npm eso es siempre el caso, así que se nombra a
|
|
26
|
+
// mano. Los datos de usuario no dependen de esto (userData.js calcula su propia ruta).
|
|
27
|
+
app.setName('glassnote');
|
|
28
|
+
|
|
29
|
+
let mainWindow;
|
|
30
|
+
let localServer = null;
|
|
31
|
+
let tray = null;
|
|
32
|
+
let lockCheckerInterval = null;
|
|
33
|
+
let lockCheckerTimeout = null;
|
|
34
|
+
let isCleaningUp = false;
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
// Inicializar y pasar datos de usuario
|
|
38
|
+
const uuid = userData.getUUID();
|
|
39
|
+
|
|
40
|
+
// Configure logging - usar el directorio personalizado en lugar de Electron
|
|
41
|
+
const logsDir = path.join(userData.getUserDataDir(), 'logs');
|
|
42
|
+
if (!fs.existsSync(logsDir)) {
|
|
43
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Clean up old logs (older than 30 days)
|
|
47
|
+
function cleanupOldLogs() {
|
|
48
|
+
try {
|
|
49
|
+
const files = fs.readdirSync(logsDir);
|
|
50
|
+
const now = Date.now();
|
|
51
|
+
const thirtyDaysAgo = now - 30 * 24 * 60 * 60 * 1000;
|
|
52
|
+
|
|
53
|
+
files.forEach((file) => {
|
|
54
|
+
if (file.endsWith('.log')) {
|
|
55
|
+
const filePath = path.join(logsDir, file);
|
|
56
|
+
const stats = fs.statSync(filePath);
|
|
57
|
+
if (stats.mtimeMs < thirtyDaysAgo) {
|
|
58
|
+
fs.unlinkSync(filePath);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error('Error cleaning up old logs:', error);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Create log file for current session
|
|
68
|
+
const currentDate = new Date().toISOString().split('T')[0];
|
|
69
|
+
const logFilePath = path.join(logsDir, `glassnote-${currentDate}.log`);
|
|
70
|
+
|
|
71
|
+
// Override console.log to write to file
|
|
72
|
+
const originalConsoleLog = console.log;
|
|
73
|
+
console.log = function (...args) {
|
|
74
|
+
const timestamp = new Date().toISOString();
|
|
75
|
+
const message = args
|
|
76
|
+
.map((arg) => (typeof arg === 'object' ? JSON.stringify(arg) : String(arg)))
|
|
77
|
+
.join(' ');
|
|
78
|
+
|
|
79
|
+
const logMessage = `[${timestamp}] ${message}\n`;
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
// Write to file
|
|
83
|
+
try {
|
|
84
|
+
fs.appendFileSync(logFilePath, logMessage, 'utf8');
|
|
85
|
+
} catch (error) {
|
|
86
|
+
originalConsoleLog.apply(console, ['Error writing to log file:', error]);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Call original console.log
|
|
90
|
+
originalConsoleLog.apply(console, args);
|
|
91
|
+
};
|
|
92
|
+
console.warn('====== APPLICATION START ======');
|
|
93
|
+
let initialLock = Date.now().toString();
|
|
94
|
+
|
|
95
|
+
// Override console.error as well
|
|
96
|
+
const originalConsoleError = console.error;
|
|
97
|
+
console.error = function (...args) {
|
|
98
|
+
const timestamp = new Date().toISOString();
|
|
99
|
+
const message = args
|
|
100
|
+
.map((arg) => (typeof arg === 'object' ? JSON.stringify(arg) : String(arg)))
|
|
101
|
+
.join(' ');
|
|
102
|
+
|
|
103
|
+
const logMessage = `[${timestamp}] ERROR: ${message}\n`;
|
|
104
|
+
|
|
105
|
+
// Write to file
|
|
106
|
+
try {
|
|
107
|
+
fs.appendFileSync(logFilePath, logMessage, 'utf8');
|
|
108
|
+
} catch (error) {
|
|
109
|
+
originalConsoleError.apply(console, ['Error writing to log file:', error]);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Call original console.error
|
|
113
|
+
originalConsoleError.apply(console, args);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// Clean up old logs on startup
|
|
117
|
+
cleanupOldLogs();
|
|
118
|
+
|
|
119
|
+
// Create tail utility in logs directory for debugging
|
|
120
|
+
createTailUtility(logsDir);
|
|
121
|
+
|
|
122
|
+
app.whenReady().then(() => {
|
|
123
|
+
// Configure auto-start - this will handle shortcut creation
|
|
124
|
+
//
|
|
125
|
+
// Corriendo por `npx` no se registra nada: la app vive en la caché de npx, que se
|
|
126
|
+
// limpia sola. Una entrada de arranque automático apuntando ahí queda rota sin que
|
|
127
|
+
// nadie se entere. Quien quiera que arranque solo, instala: `npx glassnote install`.
|
|
128
|
+
if (npmMode.isEphemeral()) {
|
|
129
|
+
console.log('[autostart] modo npx: no se registra el arranque automático');
|
|
130
|
+
} else {
|
|
131
|
+
try {
|
|
132
|
+
const autoStart = new AutoStart();
|
|
133
|
+
const autoStartResult = autoStart.configureAutoStart();
|
|
134
|
+
if (!autoStartResult) {
|
|
135
|
+
console.log('Auto-start configuration may have failed, but application will continue running normally');
|
|
136
|
+
}
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error('Auto-start configuration failed, but application will continue running normally:', error.message);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Configure auto-update
|
|
143
|
+
//
|
|
144
|
+
// Instalado por npm no hay instalador que bajar: actualizar es `npm install -g` y
|
|
145
|
+
// reabrir. El updater de S3 (.exe/.dmg) queda solo para los builds nativos, que
|
|
146
|
+
// están en deprecación — ver README, sección «Instalación».
|
|
147
|
+
const autoUpdate = npmMode.isNpmMode() ? new NpmUpdate() : new AutoUpdate();
|
|
148
|
+
autoUpdate.start();
|
|
149
|
+
|
|
150
|
+
// Create system tray with platform-appropriate icon
|
|
151
|
+
let iconPath;
|
|
152
|
+
if (process.platform === 'win32') {
|
|
153
|
+
iconPath = path.join(__dirname, 'images/icon.ico');
|
|
154
|
+
} else if (process.platform === 'darwin') {
|
|
155
|
+
// macOS uses smaller tray icons - use icon-16.png for proper sizing
|
|
156
|
+
iconPath = path.join(__dirname, 'images/icon-16.png');
|
|
157
|
+
} else {
|
|
158
|
+
iconPath = path.join(__dirname, 'images/icon.png');
|
|
159
|
+
}
|
|
160
|
+
tray = new Tray(iconPath);
|
|
161
|
+
const contextMenu = Menu.buildFromTemplate([
|
|
162
|
+
{
|
|
163
|
+
label: `glassnote v${packageJson.version}`,
|
|
164
|
+
enabled: false,
|
|
165
|
+
},
|
|
166
|
+
{ type: 'separator' },
|
|
167
|
+
{
|
|
168
|
+
label: 'Config Menu',
|
|
169
|
+
click: () => {
|
|
170
|
+
// Enviar mensaje al renderer para mostrar el ConfigMenu con vista Review
|
|
171
|
+
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
172
|
+
mainWindow.webContents.send('show-config-menu', 'review');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
]);
|
|
177
|
+
tray.setToolTip(`GlassNote v${packageJson.version}`);
|
|
178
|
+
tray.setContextMenu(contextMenu);
|
|
179
|
+
|
|
180
|
+
// Agregar funcionalidad de click izquierdo para mostrar el Config Menu
|
|
181
|
+
tray.on('click', () => {
|
|
182
|
+
// Enviar mensaje al renderer para mostrar el ConfigMenu con vista Review
|
|
183
|
+
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
184
|
+
mainWindow.webContents.send('show-config-menu', 'review');
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Platform-specific window configuration
|
|
189
|
+
// On macOS: Use screen dimensions instead of fullscreen to avoid virtual desktop behavior
|
|
190
|
+
// On Windows: Keep original fullscreen behavior
|
|
191
|
+
let windowOptions = {
|
|
192
|
+
transparent: true,
|
|
193
|
+
frame: false,
|
|
194
|
+
alwaysOnTop: true,
|
|
195
|
+
skipTaskbar: true,
|
|
196
|
+
resizable: false,
|
|
197
|
+
x: 0,
|
|
198
|
+
y: 0,
|
|
199
|
+
acceptFirstMouse: true,
|
|
200
|
+
webPreferences: {
|
|
201
|
+
nodeIntegration: false,
|
|
202
|
+
contextIsolation: true,
|
|
203
|
+
enableRemoteModule: false,
|
|
204
|
+
preload: path.join(__dirname, 'preload.js'),
|
|
205
|
+
sandbox: false, // Allow access to Node.js modules in preload
|
|
206
|
+
webSecurity: false, // Allow connections to local WebSocket servers
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
if (process.platform === 'darwin') {
|
|
211
|
+
// macOS
|
|
212
|
+
const primaryDisplay = screen.getPrimaryDisplay();
|
|
213
|
+
const { width, height } = primaryDisplay.workAreaSize;
|
|
214
|
+
windowOptions.width = width;
|
|
215
|
+
windowOptions.height = height;
|
|
216
|
+
// Explicitly set fullscreen to false for macOS
|
|
217
|
+
windowOptions.fullscreen = false;
|
|
218
|
+
} else {
|
|
219
|
+
// Windows and other platforms
|
|
220
|
+
windowOptions.width = 400;
|
|
221
|
+
windowOptions.height = 300;
|
|
222
|
+
windowOptions.fullscreen = true;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
mainWindow = new BrowserWindow(windowOptions);
|
|
226
|
+
|
|
227
|
+
// Interceptar console.log de la página renderizada
|
|
228
|
+
if (logRenderer) {
|
|
229
|
+
mainWindow.webContents.on(
|
|
230
|
+
'console-message',
|
|
231
|
+
(event, level, message, line, sourceId) => {
|
|
232
|
+
const levels = ['', 'INFO', 'WARNING', 'ERROR', 'DEBUG'];
|
|
233
|
+
const timestamp = new Date().toISOString().split('T')[1].split('.')[0];
|
|
234
|
+
const logMessage = `[${timestamp}] [RENDERER ${levels[level]}] ${message} (${sourceId}:${line})`;
|
|
235
|
+
|
|
236
|
+
// Write to both console and log file
|
|
237
|
+
console.info(logMessage);
|
|
238
|
+
// Also write directly to log file to ensure it's captured
|
|
239
|
+
const fileLogMessage = `[${timestamp}] ${logMessage}\n`;
|
|
240
|
+
try {
|
|
241
|
+
fs.appendFileSync(logFilePath, fileLogMessage, 'utf8');
|
|
242
|
+
} catch (error) {
|
|
243
|
+
console.error('Error writing renderer log to file:', error);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Generic userData operations via IPC
|
|
250
|
+
ipcMain.on('get-user-data', (event, { key, nestedKey }) => {
|
|
251
|
+
try {
|
|
252
|
+
const value = userData.get(key, nestedKey);
|
|
253
|
+
|
|
254
|
+
// Serialize the value for IPC communication
|
|
255
|
+
let serializableValue;
|
|
256
|
+
if (value === undefined || value === null) {
|
|
257
|
+
serializableValue = null;
|
|
258
|
+
} else {
|
|
259
|
+
// Convert to JSON string and back to ensure proper serialization
|
|
260
|
+
serializableValue = JSON.parse(JSON.stringify(value));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
event.reply('user-data-response', { key, nestedKey, value: serializableValue, success: true });
|
|
264
|
+
} catch (error) {
|
|
265
|
+
console.error('Error getting user data for key:', key, 'nestedKey:', nestedKey, error);
|
|
266
|
+
event.reply('user-data-response', { key, nestedKey, value: null, success: false, error: error.message });
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// Direct userData operation for IPC serialization issues
|
|
271
|
+
ipcMain.on('get-user-data-direct', (event, { key, nestedKey }) => {
|
|
272
|
+
try {
|
|
273
|
+
const value = userData.get(key, nestedKey);
|
|
274
|
+
|
|
275
|
+
// Use a different approach for direct communication
|
|
276
|
+
// Send the value as a JSON string to avoid IPC serialization issues
|
|
277
|
+
const response = {
|
|
278
|
+
key,
|
|
279
|
+
nestedKey,
|
|
280
|
+
value: value ? JSON.stringify(value) : null,
|
|
281
|
+
success: true
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
event.reply('user-data-direct-response', response);
|
|
285
|
+
} catch (error) {
|
|
286
|
+
console.error('Error getting user data directly for key:', key, 'nestedKey:', nestedKey, error);
|
|
287
|
+
event.reply('user-data-direct-response', { key, nestedKey, value: null, success: false, error: error.message });
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
ipcMain.on('set-user-data', (event, { key, nestedKey, value }) => {
|
|
292
|
+
try {
|
|
293
|
+
const success = userData.set(key, nestedKey, value);
|
|
294
|
+
event.reply('user-data-response', { key, nestedKey, value, success });
|
|
295
|
+
|
|
296
|
+
// If setting servers, send updated list to renderer
|
|
297
|
+
if (key === 'servers' && Array.isArray(value) && nestedKey === undefined) {
|
|
298
|
+
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
299
|
+
mainWindow.webContents.send('servers-list', value);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
} catch (error) {
|
|
303
|
+
console.error('Error setting user data for key:', key, 'nestedKey:', nestedKey, error);
|
|
304
|
+
event.reply('user-data-response', { key, nestedKey, value, success: false, error: error.message });
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
ipcMain.on('remove-user-data', (event, { key, nestedKey }) => {
|
|
309
|
+
try {
|
|
310
|
+
const success = userData.remove(key, nestedKey);
|
|
311
|
+
event.reply('user-data-response', { key, nestedKey, value: null, success });
|
|
312
|
+
} catch (error) {
|
|
313
|
+
console.error('Error removing user data for key:', key, 'nestedKey:', nestedKey, error);
|
|
314
|
+
event.reply('user-data-response', { key, nestedKey, value: null, success: false, error: error.message });
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// Initialize renderer auto-update after mainWindow is created
|
|
319
|
+
const autoUpdateRenderer = new AutoUpdateRenderer((version) => {
|
|
320
|
+
// Recargar la ventana para aplicar los cambios
|
|
321
|
+
setTimeout(() => {
|
|
322
|
+
reloadMainWindow();
|
|
323
|
+
}, 1000);
|
|
324
|
+
});
|
|
325
|
+
autoUpdateRenderer.start();
|
|
326
|
+
|
|
327
|
+
// Cargar el HTML del renderer (priorizar userData, fallback a app)
|
|
328
|
+
loadRendererHtml(mainWindow, uuid);
|
|
329
|
+
|
|
330
|
+
// Mouse events control
|
|
331
|
+
mainWindow.setIgnoreMouseEvents(true, { forward: true });
|
|
332
|
+
ipcMain.on('set-ignore-events-true', () => {
|
|
333
|
+
mainWindow.setIgnoreMouseEvents(true, { forward: true });
|
|
334
|
+
});
|
|
335
|
+
ipcMain.on('set-ignore-events-false', () => {
|
|
336
|
+
mainWindow.setIgnoreMouseEvents(false, { forward: true });
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// Window visibility control
|
|
340
|
+
ipcMain.on('show-window', () => {
|
|
341
|
+
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
342
|
+
mainWindow.show();
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
ipcMain.on('hide-window', () => {
|
|
347
|
+
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
348
|
+
mainWindow.hide();
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
mainWindow.setAlwaysOnTop(true, 'screen-saver');
|
|
353
|
+
mainWindow.webContents.on('did-finish-load', () => {
|
|
354
|
+
mainWindow.webContents.send('servers-list', userData.readData().servers);
|
|
355
|
+
mainWindow.webContents.send('app-data', { version: packageJson.version });
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
// Handle external URL opening
|
|
360
|
+
ipcMain.on('open-external', (event, url) => {
|
|
361
|
+
const { shell } = require('electron');
|
|
362
|
+
shell.openExternal(url);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
ipcMain.on('open-external-browser', (event, url) => {
|
|
366
|
+
const { shell } = require('electron');
|
|
367
|
+
shell.openExternal(url);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
// Handle registration window opening (larger window without menus)
|
|
371
|
+
ipcMain.on('open-registration-window', (event, url) => {
|
|
372
|
+
// Create a custom browser window for registration
|
|
373
|
+
const registrationWindow = new BrowserWindow({
|
|
374
|
+
width: 1200,
|
|
375
|
+
height: 800,
|
|
376
|
+
minWidth: 800,
|
|
377
|
+
minHeight: 600,
|
|
378
|
+
show: false,
|
|
379
|
+
titleBarStyle: 'default',
|
|
380
|
+
autoHideMenuBar: true,
|
|
381
|
+
webPreferences: {
|
|
382
|
+
nodeIntegration: false,
|
|
383
|
+
contextIsolation: true,
|
|
384
|
+
webSecurity: true
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// Remove menu bar
|
|
389
|
+
registrationWindow.setMenuBarVisibility(false);
|
|
390
|
+
|
|
391
|
+
// Load the URL
|
|
392
|
+
registrationWindow.loadURL(url);
|
|
393
|
+
|
|
394
|
+
// Show window when ready
|
|
395
|
+
registrationWindow.once('ready-to-show', () => {
|
|
396
|
+
registrationWindow.show();
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
// Handle window closed
|
|
400
|
+
registrationWindow.on('closed', () => {
|
|
401
|
+
// Window closed, no action needed
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
mainWindow.on('ready-to-show', () => {
|
|
406
|
+
mainWindow.show();
|
|
407
|
+
|
|
408
|
+
// Use LocalServer singleton
|
|
409
|
+
if (!localServer) {
|
|
410
|
+
localServer = new LocalServer();
|
|
411
|
+
localServer.start(mainWindow);
|
|
412
|
+
} else {
|
|
413
|
+
// Update the mainWindow reference if server already exists
|
|
414
|
+
localServer.updateMainWindow(mainWindow);
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
lockChecker();
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
app.on('window-all-closed', () => {
|
|
422
|
+
// Don't quit when all windows are closed if we have a tray
|
|
423
|
+
if (tray && process.platform !== 'darwin') {
|
|
424
|
+
// Keep the app running with tray
|
|
425
|
+
} else {
|
|
426
|
+
cleanupAndQuit();
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
// Clean up on quit
|
|
431
|
+
app.on('before-quit', () => {
|
|
432
|
+
if (!isCleaningUp) {
|
|
433
|
+
// Clear intervals and timeouts
|
|
434
|
+
if (lockCheckerInterval) {
|
|
435
|
+
clearInterval(lockCheckerInterval);
|
|
436
|
+
lockCheckerInterval = null;
|
|
437
|
+
}
|
|
438
|
+
if (lockCheckerTimeout) {
|
|
439
|
+
clearTimeout(lockCheckerTimeout);
|
|
440
|
+
lockCheckerTimeout = null;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Destroy main window
|
|
444
|
+
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
445
|
+
mainWindow.destroy();
|
|
446
|
+
mainWindow = null;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Stop local server
|
|
450
|
+
if (localServer) {
|
|
451
|
+
localServer.stop();
|
|
452
|
+
localServer = null;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Destroy tray
|
|
456
|
+
if (tray) {
|
|
457
|
+
tray.destroy();
|
|
458
|
+
tray = null;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
function lockChecker() {
|
|
464
|
+
lockCheckerTimeout = setTimeout(() => {
|
|
465
|
+
userData.set('lock', initialLock);
|
|
466
|
+
}, 1000);
|
|
467
|
+
lockCheckerInterval = setInterval(() => {
|
|
468
|
+
// Leer el lock directamente del archivo, no del cache
|
|
469
|
+
const currentData = userData.getLockFromFile();
|
|
470
|
+
// Si currentData es undefined, significa que el archivo .glassnote fue borrado o corrompido
|
|
471
|
+
// También verificar si el valor del lock cambió
|
|
472
|
+
if (currentData === undefined || currentData.toString() != initialLock.toString()) {
|
|
473
|
+
cleanupAndQuit();
|
|
474
|
+
}
|
|
475
|
+
}, 10000); // Check every 10 seconds
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function cleanupAndQuit() {
|
|
479
|
+
// Prevent multiple cleanup calls
|
|
480
|
+
if (isCleaningUp) {
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
isCleaningUp = true;
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
// Clear intervals and timeouts
|
|
487
|
+
if (lockCheckerInterval) {
|
|
488
|
+
clearInterval(lockCheckerInterval);
|
|
489
|
+
lockCheckerInterval = null;
|
|
490
|
+
}
|
|
491
|
+
if (lockCheckerTimeout) {
|
|
492
|
+
clearTimeout(lockCheckerTimeout);
|
|
493
|
+
lockCheckerTimeout = null;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Destroy main window
|
|
497
|
+
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
498
|
+
mainWindow.destroy();
|
|
499
|
+
mainWindow = null;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Stop local server
|
|
503
|
+
if (localServer) {
|
|
504
|
+
localServer.stop();
|
|
505
|
+
localServer = null;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Destroy tray
|
|
509
|
+
if (tray) {
|
|
510
|
+
tray.destroy();
|
|
511
|
+
tray = null;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Force exit after a timeout to ensure process terminates
|
|
515
|
+
setTimeout(() => {
|
|
516
|
+
app.exit(0);
|
|
517
|
+
// Double safety: exit process after another timeout
|
|
518
|
+
setTimeout(() => {
|
|
519
|
+
process.exit(0);
|
|
520
|
+
}, 1000);
|
|
521
|
+
}, 500);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Handle process signals for clean shutdown
|
|
525
|
+
process.on('SIGINT', () => {
|
|
526
|
+
cleanupAndQuit();
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
process.on('SIGTERM', () => {
|
|
530
|
+
cleanupAndQuit();
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
// Handle uncaught exceptions to prevent zombie processes
|
|
534
|
+
process.on('uncaughtException', (error) => {
|
|
535
|
+
console.error('Uncaught exception:', error);
|
|
536
|
+
cleanupAndQuit();
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
540
|
+
console.error('Unhandled promise rejection:', reason);
|
|
541
|
+
cleanupAndQuit();
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
// Start lock checker after everything is initialized
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Carga el HTML del renderer desde la fuente con versión SUPERIOR.
|
|
550
|
+
*
|
|
551
|
+
* Dos fuentes, y la division importa:
|
|
552
|
+
* - userData: lo que bajo el auto-update de S3. Es el canal EN CALIENTE: una version
|
|
553
|
+
* nueva del renderer llega ahi sin republicar el cliente ni tocar el binario.
|
|
554
|
+
* - baseline: el renderer que viaja con la instalacion. Es lo que ve una instalacion
|
|
555
|
+
* nueva antes de su primer update en caliente.
|
|
556
|
+
*
|
|
557
|
+
* Gana la version mas alta, sin importar de donde venga; empate, gana userData.
|
|
558
|
+
*/
|
|
559
|
+
// De donde sale el baseline. En una instalacion es la dependencia @glassnote/renderer,
|
|
560
|
+
// que npm resuelve sola. Pero si hay un clon del renderer al lado —el flujo de quien lo
|
|
561
|
+
// desarrolla, con `npm run watch-renderer`— ese gana: si no, editarlo no se veria nunca,
|
|
562
|
+
// tapado por la version publicada.
|
|
563
|
+
function baselineRendererDir() {
|
|
564
|
+
const clonLocal = path.join(__dirname, 'glassnote-renderer', 'dist');
|
|
565
|
+
if (fs.existsSync(clonLocal)) return clonLocal;
|
|
566
|
+
try {
|
|
567
|
+
return path.join(path.dirname(require.resolve('@glassnote/renderer/package.json')), 'dist');
|
|
568
|
+
} catch (error) {
|
|
569
|
+
// Sin dependencia ni clon solo queda userData; si tampoco esta, mas abajo se avisa.
|
|
570
|
+
return clonLocal;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function loadRendererHtml(window, uuid) {
|
|
575
|
+
// Definir las dos fuentes a considerar con archivos HTML alternativos
|
|
576
|
+
const sources = [
|
|
577
|
+
{
|
|
578
|
+
name: 'userData',
|
|
579
|
+
dir: path.join(userData.getUserDataDir(), 'renderer'),
|
|
580
|
+
htmlFiles: ['glassnote.html'], // userData siempre usa glassnote.html
|
|
581
|
+
type: 'auto-update'
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
name: 'baseline',
|
|
585
|
+
dir: baselineRendererDir(),
|
|
586
|
+
htmlFiles: ['glassnote.html', 'index.html'], // Intenta glassnote.html primero, luego index.html
|
|
587
|
+
type: 'paquete'
|
|
588
|
+
}
|
|
589
|
+
];
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
// Obtener versiones y verificar archivos HTML para ambas fuentes
|
|
593
|
+
const sourcesWithInfo = sources.map(source => {
|
|
594
|
+
const version = getRendererVersionFromPath(source.dir);
|
|
595
|
+
|
|
596
|
+
// Buscar el primer archivo HTML que exista
|
|
597
|
+
let htmlPath = null;
|
|
598
|
+
let htmlFile = null;
|
|
599
|
+
for (const file of source.htmlFiles) {
|
|
600
|
+
const potentialPath = path.join(source.dir, file);
|
|
601
|
+
if (fs.existsSync(potentialPath)) {
|
|
602
|
+
htmlPath = potentialPath;
|
|
603
|
+
htmlFile = file;
|
|
604
|
+
break;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const htmlExists = htmlPath !== null;
|
|
609
|
+
|
|
610
|
+
return {
|
|
611
|
+
...source,
|
|
612
|
+
version,
|
|
613
|
+
htmlPath,
|
|
614
|
+
htmlFile,
|
|
615
|
+
htmlExists
|
|
616
|
+
};
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
// Log de información encontrada
|
|
620
|
+
sourcesWithInfo.forEach(source => {
|
|
621
|
+
if (source.htmlExists) {
|
|
622
|
+
} else {
|
|
623
|
+
console.log(` ${source.name}: versión ${source.version}, SIN ARCHIVO HTML`);
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
// Comparar versiones para determinar cuál es superior
|
|
628
|
+
const versionComparison = compareVersions(
|
|
629
|
+
sourcesWithInfo[0].version,
|
|
630
|
+
sourcesWithInfo[1].version
|
|
631
|
+
);
|
|
632
|
+
|
|
633
|
+
// Determinar fuente con versión superior
|
|
634
|
+
let superiorSource;
|
|
635
|
+
if (versionComparison > 0) {
|
|
636
|
+
// userData tiene versión superior
|
|
637
|
+
superiorSource = sourcesWithInfo[0];
|
|
638
|
+
} else if (versionComparison < 0) {
|
|
639
|
+
// glassnote-renderer/dist tiene versión superior
|
|
640
|
+
superiorSource = sourcesWithInfo[1];
|
|
641
|
+
} else {
|
|
642
|
+
// Versiones iguales, priorizar userData (auto-update)
|
|
643
|
+
superiorSource = sourcesWithInfo[0];
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Intentar cargar desde la fuente con versión superior
|
|
647
|
+
if (superiorSource.htmlExists) {
|
|
648
|
+
window.loadFile(superiorSource.htmlPath, {
|
|
649
|
+
query: { uuid: uuid, version: packageJson.version }
|
|
650
|
+
});
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Si la fuente con versión superior no tiene HTML, intentar la otra fuente
|
|
655
|
+
const otherSource = superiorSource.name === 'userData' ? sourcesWithInfo[1] : sourcesWithInfo[0];
|
|
656
|
+
|
|
657
|
+
if (otherSource.htmlExists) {
|
|
658
|
+
|
|
659
|
+
window.loadFile(otherSource.htmlPath, {
|
|
660
|
+
query: { uuid: uuid, version: packageJson.version }
|
|
661
|
+
});
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// Si ninguna fuente tiene HTML, error
|
|
666
|
+
console.error('ERROR: No se encontró archivo HTML del renderer en ninguna fuente');
|
|
667
|
+
console.error('userData buscó:', sourcesWithInfo[0].htmlFiles.join(', '));
|
|
668
|
+
console.error(`baseline (${sourcesWithInfo[1].dir}) buscó:`, sourcesWithInfo[1].htmlFiles.join(', '));
|
|
669
|
+
|
|
670
|
+
// Error fatal - no hay renderer disponible
|
|
671
|
+
console.error('ERROR FATAL: No se puede cargar el renderer');
|
|
672
|
+
// Intentar cargar una página de error o cerrar la aplicación
|
|
673
|
+
window.loadURL('data:text/html,<h1>Error: No se pudo cargar el renderer</h1>');
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Compara dos versiones semánticas (formato: major.minor.patch)
|
|
678
|
+
* Retorna 1 si version1 > version2
|
|
679
|
+
* Retorna 0 si version1 == version2
|
|
680
|
+
* Retorna -1 si version1 < version2
|
|
681
|
+
*/
|
|
682
|
+
function compareVersions(version1, version2) {
|
|
683
|
+
const v1Parts = version1.split('.').map((part) => parseInt(part) || 0);
|
|
684
|
+
const v2Parts = version2.split('.').map((part) => parseInt(part) || 0);
|
|
685
|
+
|
|
686
|
+
// Comparar partes principales (major, minor, patch)
|
|
687
|
+
for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
|
|
688
|
+
const v1Part = v1Parts[i] || 0;
|
|
689
|
+
const v2Part = v2Parts[i] || 0;
|
|
690
|
+
|
|
691
|
+
if (v1Part > v2Part) return 1;
|
|
692
|
+
if (v1Part < v2Part) return -1;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
return 0; // Las versiones son iguales
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Obtiene la versión del renderer desde el package.json en el directorio especificado
|
|
700
|
+
* Si el directorio es un dist/ sin package.json, se mira el del paquete que lo contiene.
|
|
701
|
+
*/
|
|
702
|
+
function getRendererVersionFromPath(rendererPath) {
|
|
703
|
+
try {
|
|
704
|
+
// Primero intentar en el directorio especificado
|
|
705
|
+
const packageJsonPath = path.join(rendererPath, 'package.json');
|
|
706
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
707
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
708
|
+
return packageJson.version || '0.0.0';
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// Un dist/ sin package.json propio: la version la tiene el paquete de arriba.
|
|
712
|
+
if (path.basename(rendererPath) === 'dist') {
|
|
713
|
+
const parentDir = path.join(rendererPath, '..'); // Subir un nivel
|
|
714
|
+
const parentPackageJsonPath = path.join(parentDir, 'package.json');
|
|
715
|
+
if (fs.existsSync(parentPackageJsonPath)) {
|
|
716
|
+
const packageJson = JSON.parse(fs.readFileSync(parentPackageJsonPath, 'utf8'));
|
|
717
|
+
return packageJson.version || '0.0.0';
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
} catch (error) {
|
|
721
|
+
console.error(
|
|
722
|
+
'Error reading renderer version from',
|
|
723
|
+
rendererPath,
|
|
724
|
+
':',
|
|
725
|
+
error
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
return '0.0.0'; // Versión por defecto si no existe o hay error
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Recarga el main window después de una actualización del renderer
|
|
733
|
+
*/
|
|
734
|
+
function reloadMainWindow() {
|
|
735
|
+
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
736
|
+
|
|
737
|
+
// Update LocalServer with the new mainWindow reference
|
|
738
|
+
if (localServer) {
|
|
739
|
+
localServer.updateMainWindow(mainWindow);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
loadRendererHtml(mainWindow, uuid);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|