react-native-electron-platform 0.0.9 → 0.0.11
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/package.json +1 -1
- package/src/main.mjs +33 -614
- package/src/modules/autoUpdater.js +72 -0
- package/src/modules/deepLinking.js +21 -0
- package/src/modules/ipcHandlers/clipboard.js +11 -0
- package/src/modules/ipcHandlers/dialog.js +11 -0
- package/src/modules/ipcHandlers/fileOps.js +33 -0
- package/src/modules/ipcHandlers/index.js +33 -0
- package/src/modules/ipcHandlers/network.js +18 -0
- package/src/modules/ipcHandlers/pdf.js +135 -0
- package/src/modules/ipcHandlers/updater.js +17 -0
- package/src/modules/ipcHandlers/utils.js +15 -0
- package/src/modules/networkService.js +36 -0
- package/src/modules/pdfHelper.js +46 -0
- package/src/modules/safeMode.js +32 -0
- package/src/modules/windowManager.js +142 -0
- package/src/preload.mjs +1 -1
package/package.json
CHANGED
package/src/main.mjs
CHANGED
|
@@ -1,625 +1,65 @@
|
|
|
1
|
-
import {
|
|
2
|
-
app,
|
|
3
|
-
BrowserWindow,
|
|
4
|
-
session,
|
|
5
|
-
screen,
|
|
6
|
-
ipcMain,
|
|
7
|
-
dialog,
|
|
8
|
-
clipboard,
|
|
9
|
-
shell
|
|
10
|
-
} from "electron";
|
|
11
|
-
import electronUpdater from "electron-updater";
|
|
12
|
-
const { autoUpdater } = electronUpdater;
|
|
13
|
-
import path from "path";
|
|
14
|
-
import fs from "fs";
|
|
15
|
-
import { Readable } from "stream";
|
|
16
|
-
import os from "os";
|
|
1
|
+
import { app, BrowserWindow, ipcMain } from "electron";
|
|
17
2
|
import { fileURLToPath } from 'url';
|
|
18
3
|
import { dirname } from 'path';
|
|
19
4
|
import packageJson from "../../../package.json" with { type: 'json' };
|
|
20
5
|
|
|
6
|
+
// Import modules
|
|
7
|
+
import { shouldUseSafeMode, applySafeMode } from './modules/safeMode.js';
|
|
8
|
+
import { registerDeepLinkingHandlers, sendOpenURL } from './modules/deepLinking.js';
|
|
9
|
+
import { registerAllIpcHandlers } from './modules/ipcHandlers/index.js';
|
|
10
|
+
import { setupAutoUpdater } from './modules/autoUpdater.js';
|
|
11
|
+
import { createMainWindow } from './modules/windowManager.js';
|
|
12
|
+
|
|
21
13
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
22
14
|
|
|
23
15
|
// ======================================================
|
|
24
|
-
//
|
|
16
|
+
// SAFE MODE DETECTION (applies before ready)
|
|
25
17
|
// ======================================================
|
|
26
|
-
|
|
27
|
-
function isRunningFromNetwork() {
|
|
28
|
-
try {
|
|
29
|
-
const exePath = app.getPath("exe");
|
|
30
|
-
return exePath.startsWith("\\\\"); // UNC path
|
|
31
|
-
} catch {
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function shouldUseSafeMode() {
|
|
37
|
-
const network = isRunningFromNetwork();
|
|
38
|
-
const hostname = os.hostname().toLowerCase();
|
|
39
|
-
const vmHint =
|
|
40
|
-
hostname.includes("vm") ||
|
|
41
|
-
hostname.includes("virtual") ||
|
|
42
|
-
hostname.includes("vbox") ||
|
|
43
|
-
hostname.includes("hyper");
|
|
44
|
-
|
|
45
|
-
return network || vmHint;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Apply BEFORE Electron ready
|
|
49
18
|
if (shouldUseSafeMode()) {
|
|
50
|
-
|
|
51
|
-
app.disableHardwareAcceleration();
|
|
52
|
-
app.commandLine.appendSwitch("disable-gpu");
|
|
53
|
-
app.commandLine.appendSwitch("disable-software-rasterizer");
|
|
54
|
-
app.commandLine.appendSwitch("no-sandbox");
|
|
55
|
-
app.commandLine.appendSwitch("disable-dev-shm-usage");
|
|
56
|
-
} else {
|
|
57
|
-
console.log("✅ NORMAL MODE");
|
|
19
|
+
applySafeMode();
|
|
58
20
|
}
|
|
59
21
|
|
|
60
22
|
let mainWindow;
|
|
61
23
|
|
|
62
24
|
// ======================================================
|
|
63
|
-
//
|
|
64
|
-
// ======================================================
|
|
65
|
-
|
|
66
|
-
const appOpenURLTargets = new WeakSet();
|
|
67
|
-
|
|
68
|
-
function sendOpenURL(url) {
|
|
69
|
-
for (const window of BrowserWindow.getAllWindows()) {
|
|
70
|
-
if (appOpenURLTargets.has(window.webContents)) {
|
|
71
|
-
window.webContents.send('react-native-app-open-url', url);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// ======================================================
|
|
77
|
-
// REGISTER ALL IPC HANDLERS
|
|
78
|
-
// ======================================================
|
|
79
|
-
|
|
80
|
-
function registerIpcHandlers() {
|
|
81
|
-
console.log("📝 Registering IPC handlers...");
|
|
82
|
-
|
|
83
|
-
// Deep Linking Handlers
|
|
84
|
-
ipcMain.handle('react-native-add-app-open-url', (event) => {
|
|
85
|
-
appOpenURLTargets.add(event.sender);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
ipcMain.handle('react-native-get-initial-url', () => {
|
|
89
|
-
return Promise.resolve(process.argv[1]);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
ipcMain.handle('react-native-open-url', async (_event, url) => {
|
|
93
|
-
await shell.openExternal(url);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
// Clipboard Handlers
|
|
97
|
-
ipcMain.handle('react-native-get-clipboard-text', async () => {
|
|
98
|
-
return await clipboard.readText();
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
ipcMain.handle('react-native-set-clipboard-text', async (_event, text) => {
|
|
102
|
-
await clipboard.writeText(text);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
// Dialog Handlers
|
|
106
|
-
ipcMain.handle('react-native-show-alert', async (event, options) => {
|
|
107
|
-
const window = BrowserWindow.fromWebContents(event.sender);
|
|
108
|
-
if (window != null) {
|
|
109
|
-
const { response } = await dialog.showMessageBox(window, options);
|
|
110
|
-
return response;
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
// Internal support check
|
|
115
|
-
ipcMain.on('react-native-supported', (event) => {
|
|
116
|
-
event.returnValue = true;
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
// Auto-updater Handlers
|
|
120
|
-
ipcMain.handle("check-for-updates", async () => {
|
|
121
|
-
if (app.isPackaged) {
|
|
122
|
-
autoUpdater.checkForUpdates();
|
|
123
|
-
return { status: "checking" };
|
|
124
|
-
}
|
|
125
|
-
return { status: "disabled", message: "Auto-update disabled in development" };
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
ipcMain.handle("get-app-version", () => {
|
|
129
|
-
return app.getVersion();
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
// Network Service Handlers
|
|
133
|
-
ipcMain.handle("network-call", async (event, payload) => {
|
|
134
|
-
try {
|
|
135
|
-
const { method, url, params, headers } = payload;
|
|
136
|
-
console.log("IPC network-call:", { method, url });
|
|
137
|
-
return await NetworkServiceCall(method, url, params, headers);
|
|
138
|
-
} catch (err) {
|
|
139
|
-
console.error("IPC network-call error:", err);
|
|
140
|
-
return {
|
|
141
|
-
httpstatus: 500,
|
|
142
|
-
data: { title: "ERROR", message: err.message },
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
// PDF Operations Handlers
|
|
148
|
-
ipcMain.handle("save-pdf", async (event, html) => {
|
|
149
|
-
try {
|
|
150
|
-
console.log("IPC save-pdf called");
|
|
151
|
-
const tempWin = new BrowserWindow({
|
|
152
|
-
show: false,
|
|
153
|
-
webPreferences: { offscreen: true },
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
await tempWin.loadURL(
|
|
157
|
-
`data:text/html;charset=utf-8,${encodeURIComponent(html)}`
|
|
158
|
-
);
|
|
159
|
-
|
|
160
|
-
const pdfBuffer = await tempWin.webContents.printToPDF({});
|
|
161
|
-
tempWin.destroy();
|
|
162
|
-
|
|
163
|
-
const { filePath } = await dialog.showSaveDialog({
|
|
164
|
-
title: "Save PDF",
|
|
165
|
-
defaultPath: "document.pdf",
|
|
166
|
-
filters: [{ name: "PDF", extensions: ["pdf"] }],
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
if (filePath) {
|
|
170
|
-
fs.writeFileSync(filePath, pdfBuffer);
|
|
171
|
-
console.log("PDF saved:", filePath);
|
|
172
|
-
return { status: "saved", path: filePath };
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return { status: "cancelled" };
|
|
176
|
-
} catch (err) {
|
|
177
|
-
console.error("IPC save-pdf error:", err);
|
|
178
|
-
return { status: "error", message: err.message };
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
ipcMain.handle("post-pdf-preview", async (event, payload) => {
|
|
183
|
-
try {
|
|
184
|
-
const { url, data, headers = {} } = payload;
|
|
185
|
-
console.log("IPC post-pdf-preview:", { url });
|
|
186
|
-
|
|
187
|
-
const res = await fetch(url, {
|
|
188
|
-
method: "POST",
|
|
189
|
-
headers: {
|
|
190
|
-
"Content-Type": "application/json",
|
|
191
|
-
Accept: "application/pdf",
|
|
192
|
-
...headers,
|
|
193
|
-
},
|
|
194
|
-
body: data,
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
if (!res.ok) {
|
|
198
|
-
throw new Error(`HTTP ${res.status}`);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const fileName = `Report_${Date.now()}.pdf`;
|
|
202
|
-
const tempPath = path.join(app.getPath("temp"), fileName);
|
|
203
|
-
|
|
204
|
-
const nodeStream = Readable.fromWeb(res.body);
|
|
205
|
-
await new Promise((resolve, reject) => {
|
|
206
|
-
const fileStream = fs.createWriteStream(tempPath);
|
|
207
|
-
nodeStream.pipe(fileStream);
|
|
208
|
-
nodeStream.on("error", reject);
|
|
209
|
-
fileStream.on("finish", resolve);
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
return {
|
|
213
|
-
status: "ok",
|
|
214
|
-
path: `file://${tempPath}`,
|
|
215
|
-
};
|
|
216
|
-
} catch (err) {
|
|
217
|
-
console.error("post-pdf-preview error:", err);
|
|
218
|
-
return {
|
|
219
|
-
status: "error",
|
|
220
|
-
message: err.message,
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
ipcMain.handle("open-pdf-preview", async (_, pdfUrl) => {
|
|
226
|
-
try {
|
|
227
|
-
const res = await fetch(pdfUrl);
|
|
228
|
-
const buffer = Buffer.from(await res.arrayBuffer());
|
|
229
|
-
|
|
230
|
-
const tempPath = path.join(app.getPath("temp"), `preview_${Date.now()}.pdf`);
|
|
231
|
-
fs.writeFileSync(tempPath, buffer);
|
|
232
|
-
|
|
233
|
-
return `file://${tempPath}`;
|
|
234
|
-
} catch (err) {
|
|
235
|
-
console.error("open-pdf-preview error:", err);
|
|
236
|
-
throw err;
|
|
237
|
-
}
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
// File Operations Handlers
|
|
241
|
-
ipcMain.handle("select-file", async () => {
|
|
242
|
-
try {
|
|
243
|
-
const result = await dialog.showOpenDialog({
|
|
244
|
-
title: "Select a file",
|
|
245
|
-
properties: ["openFile"],
|
|
246
|
-
filters: [
|
|
247
|
-
{ name: "All Files", extensions: ["*"] },
|
|
248
|
-
],
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
if (result.canceled) {
|
|
252
|
-
return {
|
|
253
|
-
status: "cancelled",
|
|
254
|
-
filePath: null,
|
|
255
|
-
};
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return {
|
|
259
|
-
status: "selected",
|
|
260
|
-
filePath: result.filePaths[0],
|
|
261
|
-
};
|
|
262
|
-
} catch (err) {
|
|
263
|
-
console.error("select-file error:", err);
|
|
264
|
-
return {
|
|
265
|
-
status: "error",
|
|
266
|
-
message: err.message,
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
// HTML Preview Handlers
|
|
272
|
-
ipcMain.handle("preview-html", async (event, htmlContent) => {
|
|
273
|
-
try {
|
|
274
|
-
const previewWin = new BrowserWindow({
|
|
275
|
-
width: 800,
|
|
276
|
-
height: 600,
|
|
277
|
-
show: false,
|
|
278
|
-
webPreferences: {
|
|
279
|
-
contextIsolation: true,
|
|
280
|
-
sandbox: false,
|
|
281
|
-
nodeIntegration: false,
|
|
282
|
-
},
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
await previewWin.loadURL(
|
|
286
|
-
`data:text/html;charset=utf-8,${encodeURIComponent(htmlContent)}`
|
|
287
|
-
);
|
|
288
|
-
|
|
289
|
-
previewWin.show();
|
|
290
|
-
return { status: "ok" };
|
|
291
|
-
} catch (err) {
|
|
292
|
-
console.error("preview-html error:", err);
|
|
293
|
-
return { status: "error", message: err.message };
|
|
294
|
-
}
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
ipcMain.handle("html-to-pdf-preview", async (event, htmlContent) => {
|
|
298
|
-
try {
|
|
299
|
-
const pdfPath = await convertHtmlToPdfPreview(htmlContent);
|
|
300
|
-
return { status: "ok", path: pdfPath };
|
|
301
|
-
} catch (err) {
|
|
302
|
-
console.error("html-to-pdf-preview error:", err);
|
|
303
|
-
return { status: "error", message: err.message };
|
|
304
|
-
}
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
// Utility Handlers
|
|
308
|
-
ipcMain.handle("get-platform", () => {
|
|
309
|
-
return process.platform;
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
ipcMain.handle("get-app-path", () => {
|
|
313
|
-
return app.getAppPath();
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
ipcMain.handle("get-user-data-path", () => {
|
|
317
|
-
return app.getPath("userData");
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
console.log("✅ All IPC handlers registered");
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// ======================================================
|
|
324
|
-
// NETWORK SERVICE
|
|
325
|
-
// ======================================================
|
|
326
|
-
|
|
327
|
-
async function NetworkServiceCall(method, url, params = {}, headers = {}) {
|
|
328
|
-
try {
|
|
329
|
-
const upperMethod = method.toUpperCase();
|
|
330
|
-
const options = {
|
|
331
|
-
method: upperMethod,
|
|
332
|
-
headers: {
|
|
333
|
-
"Content-Type": "application/json",
|
|
334
|
-
...headers,
|
|
335
|
-
},
|
|
336
|
-
};
|
|
337
|
-
|
|
338
|
-
if (upperMethod !== "GET") {
|
|
339
|
-
options.body = JSON.stringify(params);
|
|
340
|
-
} else if (params && Object.keys(params).length > 0) {
|
|
341
|
-
const query = new URLSearchParams(params).toString();
|
|
342
|
-
url += `?${query}`;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
const response = await fetch(url, options);
|
|
346
|
-
const data = await response.json().catch(() => ({}));
|
|
347
|
-
|
|
348
|
-
if (response.ok || data?.httpstatus === 200) {
|
|
349
|
-
return { httpstatus: 200, data: data?.data || data };
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
return {
|
|
353
|
-
httpstatus: response.status,
|
|
354
|
-
data: { title: "ERROR", message: data?.message || "Network Error" },
|
|
355
|
-
};
|
|
356
|
-
} catch (err) {
|
|
357
|
-
return {
|
|
358
|
-
httpstatus: 404,
|
|
359
|
-
data: { title: "ERROR", message: err.message },
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// ======================================================
|
|
365
|
-
// PDF CONVERSION HELPER
|
|
25
|
+
// REGISTER IPC HANDLERS
|
|
366
26
|
// ======================================================
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
try {
|
|
370
|
-
const tempWin = new BrowserWindow({
|
|
371
|
-
show: false,
|
|
372
|
-
webPreferences: {
|
|
373
|
-
offscreen: true,
|
|
374
|
-
contextIsolation: true,
|
|
375
|
-
nodeIntegration: false,
|
|
376
|
-
},
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
await tempWin.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(htmlContent)}`);
|
|
380
|
-
|
|
381
|
-
const pdfBuffer = await tempWin.webContents.printToPDF({
|
|
382
|
-
printBackground: true,
|
|
383
|
-
...options,
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
tempWin.destroy();
|
|
387
|
-
|
|
388
|
-
const pdfFileName = `Preview_${Date.now()}.pdf`;
|
|
389
|
-
const pdfPath = path.join(app.getPath("temp"), pdfFileName);
|
|
390
|
-
fs.writeFileSync(pdfPath, pdfBuffer);
|
|
391
|
-
|
|
392
|
-
const previewWin = new BrowserWindow({
|
|
393
|
-
width: 900,
|
|
394
|
-
height: 700,
|
|
395
|
-
show: true,
|
|
396
|
-
webPreferences: {
|
|
397
|
-
contextIsolation: true,
|
|
398
|
-
nodeIntegration: false,
|
|
399
|
-
},
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
await previewWin.loadURL(`file://${pdfPath}`);
|
|
403
|
-
return pdfPath;
|
|
404
|
-
} catch (err) {
|
|
405
|
-
console.error("convertHtmlToPdfPreview error:", err);
|
|
406
|
-
throw err;
|
|
407
|
-
}
|
|
408
|
-
}
|
|
27
|
+
registerAllIpcHandlers();
|
|
28
|
+
registerDeepLinkingHandlers(sendOpenURL);
|
|
409
29
|
|
|
410
30
|
// ======================================================
|
|
411
|
-
//
|
|
31
|
+
// APP LIFECYCLE
|
|
412
32
|
// ======================================================
|
|
413
33
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
if (!app.isPackaged) {
|
|
418
|
-
console.log("Auto-update disabled in development.");
|
|
419
|
-
return;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
autoUpdater.autoDownload = true;
|
|
423
|
-
autoUpdater.autoInstallOnAppQuit = true;
|
|
34
|
+
// Single instance lock
|
|
35
|
+
const gotTheLock = app.requestSingleInstanceLock();
|
|
424
36
|
|
|
425
|
-
|
|
37
|
+
if (!gotTheLock) {
|
|
38
|
+
app.quit();
|
|
39
|
+
} else {
|
|
40
|
+
app.on('second-instance', (event, commandLine) => {
|
|
426
41
|
if (mainWindow) {
|
|
427
|
-
mainWindow.
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
autoUpdater.on("update-available", (info) => {
|
|
437
|
-
console.log("Update available:", info.version);
|
|
438
|
-
sendStatus({ status: "available", version: info.version });
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
autoUpdater.on("update-not-available", () => {
|
|
442
|
-
console.log("No updates available");
|
|
443
|
-
sendStatus({ status: "not-available" });
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
autoUpdater.on("download-progress", (progress) => {
|
|
447
|
-
console.log(`Download progress: ${Math.round(progress.percent)}%`);
|
|
448
|
-
sendStatus({
|
|
449
|
-
status: "downloading",
|
|
450
|
-
percent: Math.round(progress.percent),
|
|
451
|
-
});
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
autoUpdater.on("update-downloaded", () => {
|
|
455
|
-
console.log("Update downloaded");
|
|
456
|
-
sendStatus({ status: "downloaded" });
|
|
457
|
-
|
|
458
|
-
dialog
|
|
459
|
-
.showMessageBox(mainWindow, {
|
|
460
|
-
type: "info",
|
|
461
|
-
title: "Update Ready",
|
|
462
|
-
message: "A new version has been downloaded. Restart now?",
|
|
463
|
-
buttons: ["Restart", "Later"],
|
|
464
|
-
})
|
|
465
|
-
.then((result) => {
|
|
466
|
-
if (result.response === 0) {
|
|
467
|
-
autoUpdater.quitAndInstall();
|
|
468
|
-
}
|
|
469
|
-
});
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
autoUpdater.on("error", (err) => {
|
|
473
|
-
console.error("Auto-updater error:", err);
|
|
474
|
-
sendStatus({ status: "error", message: err.message });
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
// Check for updates after a short delay to ensure window is ready
|
|
478
|
-
setTimeout(() => {
|
|
479
|
-
autoUpdater.checkForUpdates();
|
|
480
|
-
}, 3000);
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
// ======================================================
|
|
484
|
-
// WINDOW CREATION
|
|
485
|
-
// ======================================================
|
|
486
|
-
|
|
487
|
-
function createWindow() {
|
|
488
|
-
const primaryDisplay = screen.getPrimaryDisplay();
|
|
489
|
-
const { x, y, width, height } = primaryDisplay.bounds;
|
|
490
|
-
|
|
491
|
-
const iconPath = path.join(app.getAppPath(), "electron/icon.ico");
|
|
492
|
-
const preloadPath = path.join(__dirname, "preload.js");
|
|
493
|
-
|
|
494
|
-
mainWindow = new BrowserWindow({
|
|
495
|
-
x,
|
|
496
|
-
y,
|
|
497
|
-
width,
|
|
498
|
-
height,
|
|
499
|
-
show: false,
|
|
500
|
-
icon: iconPath,
|
|
501
|
-
frame: true,
|
|
502
|
-
webPreferences: {
|
|
503
|
-
preload: preloadPath,
|
|
504
|
-
nodeIntegration: false,
|
|
505
|
-
contextIsolation: true,
|
|
506
|
-
sandbox: false,
|
|
507
|
-
webSecurity: false,
|
|
508
|
-
disableBlinkFeatures: "AutoLoadIconsForPage",
|
|
509
|
-
nativeWindowOpen: true,
|
|
510
|
-
spellcheck: true,
|
|
511
|
-
},
|
|
512
|
-
});
|
|
513
|
-
|
|
514
|
-
mainWindow.removeMenu();
|
|
515
|
-
mainWindow.maximize();
|
|
516
|
-
mainWindow.show();
|
|
517
|
-
|
|
518
|
-
mainWindow.on("resize", () => {
|
|
519
|
-
const bounds = mainWindow.getBounds();
|
|
520
|
-
if (bounds?.width && bounds?.height) {
|
|
521
|
-
console.log(`Window resized: ${bounds.width}x${bounds.height}`);
|
|
522
|
-
}
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
// CORS handling
|
|
526
|
-
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
|
|
527
|
-
const responseHeaders = { ...details.responseHeaders };
|
|
528
|
-
|
|
529
|
-
delete responseHeaders["access-control-allow-origin"];
|
|
530
|
-
delete responseHeaders["access-control-allow-headers"];
|
|
531
|
-
delete responseHeaders["access-control-allow-methods"];
|
|
532
|
-
delete responseHeaders["Access-Control-Allow-Origin"];
|
|
533
|
-
delete responseHeaders["Access-Control-Allow-Headers"];
|
|
534
|
-
delete responseHeaders["Access-Control-Allow-Methods"];
|
|
535
|
-
|
|
536
|
-
callback({
|
|
537
|
-
responseHeaders: {
|
|
538
|
-
...responseHeaders,
|
|
539
|
-
"Access-Control-Allow-Origin": ["*"],
|
|
540
|
-
"Access-Control-Allow-Headers": ["*"],
|
|
541
|
-
"Access-Control-Allow-Methods": ["GET, POST, PUT, DELETE, OPTIONS"],
|
|
542
|
-
},
|
|
543
|
-
});
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
const isDev =
|
|
547
|
-
process.argv.includes("--enable-remote-module") ||
|
|
548
|
-
process.env.NODE_ENV === "development";
|
|
549
|
-
|
|
550
|
-
if (isDev) {
|
|
551
|
-
mainWindow.loadURL("http://localhost:5001");
|
|
552
|
-
console.log("DEV MODE: http://localhost:5001");
|
|
553
|
-
|
|
554
|
-
// Dev tools shortcuts
|
|
555
|
-
mainWindow.webContents.on("before-input-event", (event, input) => {
|
|
556
|
-
if (input.control && input.shift && input.key.toLowerCase() === "i") {
|
|
557
|
-
mainWindow.webContents.toggleDevTools();
|
|
558
|
-
event.preventDefault();
|
|
559
|
-
} else if (input.key === "F12") {
|
|
560
|
-
mainWindow.webContents.toggleDevTools();
|
|
561
|
-
event.preventDefault();
|
|
562
|
-
} else if (
|
|
563
|
-
input.key === "F5" ||
|
|
564
|
-
(input.control && input.key.toLowerCase() === "r")
|
|
565
|
-
) {
|
|
566
|
-
mainWindow.webContents.reload();
|
|
567
|
-
event.preventDefault();
|
|
568
|
-
}
|
|
569
|
-
});
|
|
570
|
-
} else {
|
|
571
|
-
const possiblePaths = [
|
|
572
|
-
path.join(__dirname, "web-build/index.html"),
|
|
573
|
-
path.join(__dirname, "../web-build/index.html"),
|
|
574
|
-
path.join(__dirname, "../../web-build/index.html"),
|
|
575
|
-
path.join(app.getAppPath(), "web-build/index.html"),
|
|
576
|
-
];
|
|
577
|
-
|
|
578
|
-
let indexPath = null;
|
|
579
|
-
for (const p of possiblePaths) {
|
|
580
|
-
if (fs.existsSync(p)) {
|
|
581
|
-
indexPath = p;
|
|
582
|
-
break;
|
|
42
|
+
if (mainWindow.isMinimized()) mainWindow.restore();
|
|
43
|
+
mainWindow.focus();
|
|
44
|
+
|
|
45
|
+
// Handle deep link from second instance
|
|
46
|
+
const url = commandLine.pop();
|
|
47
|
+
if (url && (url.startsWith('myapp://') || url.startsWith('http'))) {
|
|
48
|
+
sendOpenURL(url);
|
|
583
49
|
}
|
|
584
50
|
}
|
|
585
|
-
|
|
586
|
-
console.log("Loading:", indexPath);
|
|
587
|
-
|
|
588
|
-
if (!indexPath) {
|
|
589
|
-
dialog.showErrorBox(
|
|
590
|
-
"Application Error",
|
|
591
|
-
`web-build/index.html not found. Tried: ${possiblePaths.join(", ")}`
|
|
592
|
-
);
|
|
593
|
-
app.quit();
|
|
594
|
-
return;
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
mainWindow.loadFile(indexPath);
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
mainWindow.webContents.on("did-finish-load", () => {
|
|
601
|
-
console.log("Page loaded successfully");
|
|
602
51
|
});
|
|
603
52
|
}
|
|
604
53
|
|
|
605
|
-
// ======================================================
|
|
606
|
-
// REGISTER IPC HANDLERS BEFORE APP READY
|
|
607
|
-
// ======================================================
|
|
608
|
-
|
|
609
|
-
registerIpcHandlers();
|
|
610
|
-
|
|
611
|
-
// ======================================================
|
|
612
|
-
// APP LIFECYCLE
|
|
613
|
-
// ======================================================
|
|
614
|
-
|
|
615
54
|
app.whenReady().then(() => {
|
|
616
55
|
if (process.platform === "win32") {
|
|
617
56
|
app.setAppUserModelId(`com.${packageJson.name}.desktop`);
|
|
618
57
|
}
|
|
619
|
-
createWindow();
|
|
620
|
-
setupAutoUpdater();
|
|
621
58
|
|
|
622
|
-
|
|
59
|
+
mainWindow = createMainWindow(__dirname);
|
|
60
|
+
setupAutoUpdater(mainWindow);
|
|
61
|
+
|
|
62
|
+
// Log registered handlers
|
|
623
63
|
console.log("📊 IPC Handlers registered:",
|
|
624
64
|
ipcMain.eventNames().filter(name =>
|
|
625
65
|
typeof name === 'string' &&
|
|
@@ -636,27 +76,6 @@ app.on("window-all-closed", () => {
|
|
|
636
76
|
|
|
637
77
|
app.on("activate", () => {
|
|
638
78
|
if (BrowserWindow.getAllWindows().length === 0) {
|
|
639
|
-
|
|
79
|
+
mainWindow = createMainWindow(__dirname);
|
|
640
80
|
}
|
|
641
|
-
});
|
|
642
|
-
|
|
643
|
-
// Handle second instance (for single instance apps)
|
|
644
|
-
const gotTheLock = app.requestSingleInstanceLock();
|
|
645
|
-
|
|
646
|
-
if (!gotTheLock) {
|
|
647
|
-
app.quit();
|
|
648
|
-
} else {
|
|
649
|
-
app.on('second-instance', (event, commandLine, workingDirectory) => {
|
|
650
|
-
// Someone tried to run a second instance, focus our window instead
|
|
651
|
-
if (mainWindow) {
|
|
652
|
-
if (mainWindow.isMinimized()) mainWindow.restore();
|
|
653
|
-
mainWindow.focus();
|
|
654
|
-
|
|
655
|
-
// Handle deep link from second instance
|
|
656
|
-
const url = commandLine.pop();
|
|
657
|
-
if (url && (url.startsWith('myapp://') || url.startsWith('http'))) {
|
|
658
|
-
sendOpenURL(url);
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
});
|
|
662
|
-
}
|
|
81
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { app, dialog } from "electron";
|
|
2
|
+
import electronUpdater from "electron-updater";
|
|
3
|
+
const { autoUpdater } = electronUpdater;
|
|
4
|
+
|
|
5
|
+
export function setupAutoUpdater(mainWindow) {
|
|
6
|
+
console.log("Current app version:", app.getVersion());
|
|
7
|
+
|
|
8
|
+
if (!app.isPackaged) {
|
|
9
|
+
console.log("Auto-update disabled in development.");
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
autoUpdater.autoDownload = true;
|
|
14
|
+
autoUpdater.autoInstallOnAppQuit = true;
|
|
15
|
+
|
|
16
|
+
const sendStatus = (data) => {
|
|
17
|
+
if (mainWindow) {
|
|
18
|
+
mainWindow.webContents.send("update-status", data);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
autoUpdater.on("checking-for-update", () => {
|
|
23
|
+
console.log("Checking for updates...");
|
|
24
|
+
sendStatus({ status: "checking" });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
autoUpdater.on("update-available", (info) => {
|
|
28
|
+
console.log("Update available:", info.version);
|
|
29
|
+
sendStatus({ status: "available", version: info.version });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
autoUpdater.on("update-not-available", () => {
|
|
33
|
+
console.log("No updates available");
|
|
34
|
+
sendStatus({ status: "not-available" });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
autoUpdater.on("download-progress", (progress) => {
|
|
38
|
+
console.log(`Download progress: ${Math.round(progress.percent)}%`);
|
|
39
|
+
sendStatus({
|
|
40
|
+
status: "downloading",
|
|
41
|
+
percent: Math.round(progress.percent),
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
autoUpdater.on("update-downloaded", () => {
|
|
46
|
+
console.log("Update downloaded");
|
|
47
|
+
sendStatus({ status: "downloaded" });
|
|
48
|
+
|
|
49
|
+
dialog
|
|
50
|
+
.showMessageBox(mainWindow, {
|
|
51
|
+
type: "info",
|
|
52
|
+
title: "Update Ready",
|
|
53
|
+
message: "A new version has been downloaded. Restart now?",
|
|
54
|
+
buttons: ["Restart", "Later"],
|
|
55
|
+
})
|
|
56
|
+
.then((result) => {
|
|
57
|
+
if (result.response === 0) {
|
|
58
|
+
autoUpdater.quitAndInstall();
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
autoUpdater.on("error", (err) => {
|
|
64
|
+
console.error("Auto-updater error:", err);
|
|
65
|
+
sendStatus({ status: "error", message: err.message });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Check for updates after a short delay
|
|
69
|
+
setTimeout(() => {
|
|
70
|
+
autoUpdater.checkForUpdates();
|
|
71
|
+
}, 3000);
|
|
72
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { BrowserWindow, ipcMain } from "electron";
|
|
2
|
+
|
|
3
|
+
const appOpenURLTargets = new WeakSet();
|
|
4
|
+
|
|
5
|
+
export function sendOpenURL(url) {
|
|
6
|
+
for (const window of BrowserWindow.getAllWindows()) {
|
|
7
|
+
if (appOpenURLTargets.has(window.webContents)) {
|
|
8
|
+
window.webContents.send('react-native-app-open-url', url);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function registerDeepLinkingHandlers(sendOpenURLCallback) {
|
|
14
|
+
ipcMain.handle('react-native-add-app-open-url', (event) => {
|
|
15
|
+
appOpenURLTargets.add(event.sender);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
ipcMain.handle('react-native-get-initial-url', () => {
|
|
19
|
+
return Promise.resolve(process.argv[1]);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ipcMain, clipboard } from "electron";
|
|
2
|
+
|
|
3
|
+
export function registerClipboardHandlers() {
|
|
4
|
+
ipcMain.handle('react-native-get-clipboard-text', async () => {
|
|
5
|
+
return await clipboard.readText();
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
ipcMain.handle('react-native-set-clipboard-text', async (_event, text) => {
|
|
9
|
+
await clipboard.writeText(text);
|
|
10
|
+
});
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ipcMain, dialog, BrowserWindow } from "electron";
|
|
2
|
+
|
|
3
|
+
export function registerDialogHandlers() {
|
|
4
|
+
ipcMain.handle('react-native-show-alert', async (event, options) => {
|
|
5
|
+
const window = BrowserWindow.fromWebContents(event.sender);
|
|
6
|
+
if (window != null) {
|
|
7
|
+
const { response } = await dialog.showMessageBox(window, options);
|
|
8
|
+
return response;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ipcMain, dialog } from "electron";
|
|
2
|
+
|
|
3
|
+
export function registerFileHandlers() {
|
|
4
|
+
ipcMain.handle("select-file", async () => {
|
|
5
|
+
try {
|
|
6
|
+
const result = await dialog.showOpenDialog({
|
|
7
|
+
title: "Select a file",
|
|
8
|
+
properties: ["openFile"],
|
|
9
|
+
filters: [
|
|
10
|
+
{ name: "All Files", extensions: ["*"] },
|
|
11
|
+
],
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
if (result.canceled) {
|
|
15
|
+
return {
|
|
16
|
+
status: "cancelled",
|
|
17
|
+
filePath: null,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
status: "selected",
|
|
23
|
+
filePath: result.filePaths[0],
|
|
24
|
+
};
|
|
25
|
+
} catch (err) {
|
|
26
|
+
console.error("select-file error:", err);
|
|
27
|
+
return {
|
|
28
|
+
status: "error",
|
|
29
|
+
message: err.message,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ipcMain, shell } from "electron";
|
|
2
|
+
import { registerClipboardHandlers } from './clipboard.js';
|
|
3
|
+
import { registerDialogHandlers } from './dialog.js';
|
|
4
|
+
import { registerUpdaterHandlers } from './updater.js';
|
|
5
|
+
import { registerNetworkHandlers } from './network.js';
|
|
6
|
+
import { registerPdfHandlers } from './pdf.js';
|
|
7
|
+
import { registerFileHandlers } from './fileOps.js';
|
|
8
|
+
import { registerUtilsHandlers } from './utils.js';
|
|
9
|
+
|
|
10
|
+
export function registerAllIpcHandlers() {
|
|
11
|
+
console.log("📝 Registering IPC handlers...");
|
|
12
|
+
|
|
13
|
+
// Deep Linking (basic)
|
|
14
|
+
ipcMain.handle('react-native-open-url', async (_event, url) => {
|
|
15
|
+
await shell.openExternal(url);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Register all category handlers
|
|
19
|
+
registerClipboardHandlers();
|
|
20
|
+
registerDialogHandlers();
|
|
21
|
+
registerUpdaterHandlers();
|
|
22
|
+
registerNetworkHandlers();
|
|
23
|
+
registerPdfHandlers();
|
|
24
|
+
registerFileHandlers();
|
|
25
|
+
registerUtilsHandlers();
|
|
26
|
+
|
|
27
|
+
// Internal support check
|
|
28
|
+
ipcMain.on('react-native-supported', (event) => {
|
|
29
|
+
event.returnValue = true;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
console.log("✅ All IPC handlers registered");
|
|
33
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ipcMain } from "electron";
|
|
2
|
+
import { networkServiceCall } from '../networkService.js';
|
|
3
|
+
|
|
4
|
+
export function registerNetworkHandlers() {
|
|
5
|
+
ipcMain.handle("network-call", async (event, payload) => {
|
|
6
|
+
try {
|
|
7
|
+
const { method, url, params, headers } = payload;
|
|
8
|
+
console.log("IPC network-call:", { method, url });
|
|
9
|
+
return await networkServiceCall(method, url, params, headers);
|
|
10
|
+
} catch (err) {
|
|
11
|
+
console.error("IPC network-call error:", err);
|
|
12
|
+
return {
|
|
13
|
+
httpstatus: 500,
|
|
14
|
+
data: { title: "ERROR", message: err.message },
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { ipcMain, dialog, BrowserWindow } from "electron";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { Readable } from "stream";
|
|
5
|
+
import { app } from "electron";
|
|
6
|
+
import { convertHtmlToPdfPreview } from '../pdfHelper.js';
|
|
7
|
+
|
|
8
|
+
export function registerPdfHandlers() {
|
|
9
|
+
ipcMain.handle("save-pdf", async (event, html) => {
|
|
10
|
+
try {
|
|
11
|
+
console.log("IPC save-pdf called");
|
|
12
|
+
const tempWin = new BrowserWindow({
|
|
13
|
+
show: false,
|
|
14
|
+
webPreferences: { offscreen: true },
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
await tempWin.loadURL(
|
|
18
|
+
`data:text/html;charset=utf-8,${encodeURIComponent(html)}`
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const pdfBuffer = await tempWin.webContents.printToPDF({});
|
|
22
|
+
tempWin.destroy();
|
|
23
|
+
|
|
24
|
+
const { filePath } = await dialog.showSaveDialog({
|
|
25
|
+
title: "Save PDF",
|
|
26
|
+
defaultPath: "document.pdf",
|
|
27
|
+
filters: [{ name: "PDF", extensions: ["pdf"] }],
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (filePath) {
|
|
31
|
+
fs.writeFileSync(filePath, pdfBuffer);
|
|
32
|
+
console.log("PDF saved:", filePath);
|
|
33
|
+
return { status: "saved", path: filePath };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return { status: "cancelled" };
|
|
37
|
+
} catch (err) {
|
|
38
|
+
console.error("IPC save-pdf error:", err);
|
|
39
|
+
return { status: "error", message: err.message };
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
ipcMain.handle("post-pdf-preview", async (event, payload) => {
|
|
44
|
+
try {
|
|
45
|
+
const { url, data, headers = {} } = payload;
|
|
46
|
+
console.log("IPC post-pdf-preview:", { url });
|
|
47
|
+
|
|
48
|
+
const res = await fetch(url, {
|
|
49
|
+
method: "POST",
|
|
50
|
+
headers: {
|
|
51
|
+
"Content-Type": "application/json",
|
|
52
|
+
Accept: "application/pdf",
|
|
53
|
+
...headers,
|
|
54
|
+
},
|
|
55
|
+
body: data,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
if (!res.ok) {
|
|
59
|
+
throw new Error(`HTTP ${res.status}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const fileName = `Report_${Date.now()}.pdf`;
|
|
63
|
+
const tempPath = path.join(app.getPath("temp"), fileName);
|
|
64
|
+
|
|
65
|
+
const nodeStream = Readable.fromWeb(res.body);
|
|
66
|
+
await new Promise((resolve, reject) => {
|
|
67
|
+
const fileStream = fs.createWriteStream(tempPath);
|
|
68
|
+
nodeStream.pipe(fileStream);
|
|
69
|
+
nodeStream.on("error", reject);
|
|
70
|
+
fileStream.on("finish", resolve);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
status: "ok",
|
|
75
|
+
path: `file://${tempPath}`,
|
|
76
|
+
};
|
|
77
|
+
} catch (err) {
|
|
78
|
+
console.error("post-pdf-preview error:", err);
|
|
79
|
+
return {
|
|
80
|
+
status: "error",
|
|
81
|
+
message: err.message,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
ipcMain.handle("open-pdf-preview", async (_, pdfUrl) => {
|
|
87
|
+
try {
|
|
88
|
+
const res = await fetch(pdfUrl);
|
|
89
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
90
|
+
|
|
91
|
+
const tempPath = path.join(app.getPath("temp"), `preview_${Date.now()}.pdf`);
|
|
92
|
+
fs.writeFileSync(tempPath, buffer);
|
|
93
|
+
|
|
94
|
+
return `file://${tempPath}`;
|
|
95
|
+
} catch (err) {
|
|
96
|
+
console.error("open-pdf-preview error:", err);
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
ipcMain.handle("preview-html", async (event, htmlContent) => {
|
|
102
|
+
try {
|
|
103
|
+
const previewWin = new BrowserWindow({
|
|
104
|
+
width: 800,
|
|
105
|
+
height: 600,
|
|
106
|
+
show: false,
|
|
107
|
+
webPreferences: {
|
|
108
|
+
contextIsolation: true,
|
|
109
|
+
sandbox: false,
|
|
110
|
+
nodeIntegration: false,
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
await previewWin.loadURL(
|
|
115
|
+
`data:text/html;charset=utf-8,${encodeURIComponent(htmlContent)}`
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
previewWin.show();
|
|
119
|
+
return { status: "ok" };
|
|
120
|
+
} catch (err) {
|
|
121
|
+
console.error("preview-html error:", err);
|
|
122
|
+
return { status: "error", message: err.message };
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
ipcMain.handle("html-to-pdf-preview", async (event, htmlContent) => {
|
|
127
|
+
try {
|
|
128
|
+
const pdfPath = await convertHtmlToPdfPreview(htmlContent);
|
|
129
|
+
return { status: "ok", path: pdfPath };
|
|
130
|
+
} catch (err) {
|
|
131
|
+
console.error("html-to-pdf-preview error:", err);
|
|
132
|
+
return { status: "error", message: err.message };
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ipcMain, app } from "electron";
|
|
2
|
+
import electronUpdater from "electron-updater";
|
|
3
|
+
const { autoUpdater } = electronUpdater;
|
|
4
|
+
|
|
5
|
+
export function registerUpdaterHandlers() {
|
|
6
|
+
ipcMain.handle("check-for-updates", async () => {
|
|
7
|
+
if (app.isPackaged) {
|
|
8
|
+
autoUpdater.checkForUpdates();
|
|
9
|
+
return { status: "checking" };
|
|
10
|
+
}
|
|
11
|
+
return { status: "disabled", message: "Auto-update disabled in development" };
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
ipcMain.handle("get-app-version", () => {
|
|
15
|
+
return app.getVersion();
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ipcMain, app } from "electron";
|
|
2
|
+
|
|
3
|
+
export function registerUtilsHandlers() {
|
|
4
|
+
ipcMain.handle("get-platform", () => {
|
|
5
|
+
return process.platform;
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
ipcMain.handle("get-app-path", () => {
|
|
9
|
+
return app.getAppPath();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
ipcMain.handle("get-user-data-path", () => {
|
|
13
|
+
return app.getPath("userData");
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export async function networkServiceCall(method, url, params = {}, headers = {}) {
|
|
2
|
+
try {
|
|
3
|
+
const upperMethod = method.toUpperCase();
|
|
4
|
+
const options = {
|
|
5
|
+
method: upperMethod,
|
|
6
|
+
headers: {
|
|
7
|
+
"Content-Type": "application/json",
|
|
8
|
+
...headers,
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
if (upperMethod !== "GET") {
|
|
13
|
+
options.body = JSON.stringify(params);
|
|
14
|
+
} else if (params && Object.keys(params).length > 0) {
|
|
15
|
+
const query = new URLSearchParams(params).toString();
|
|
16
|
+
url += `?${query}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const response = await fetch(url, options);
|
|
20
|
+
const data = await response.json().catch(() => ({}));
|
|
21
|
+
|
|
22
|
+
if (response.ok || data?.httpstatus === 200) {
|
|
23
|
+
return { httpstatus: 200, data: data?.data || data };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
httpstatus: response.status,
|
|
28
|
+
data: { title: "ERROR", message: data?.message || "Network Error" },
|
|
29
|
+
};
|
|
30
|
+
} catch (err) {
|
|
31
|
+
return {
|
|
32
|
+
httpstatus: 404,
|
|
33
|
+
data: { title: "ERROR", message: err.message },
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { BrowserWindow } from "electron";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { app } from "electron";
|
|
5
|
+
|
|
6
|
+
export async function convertHtmlToPdfPreview(htmlContent, options = {}) {
|
|
7
|
+
try {
|
|
8
|
+
const tempWin = new BrowserWindow({
|
|
9
|
+
show: false,
|
|
10
|
+
webPreferences: {
|
|
11
|
+
offscreen: true,
|
|
12
|
+
contextIsolation: true,
|
|
13
|
+
nodeIntegration: false,
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
await tempWin.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(htmlContent)}`);
|
|
18
|
+
|
|
19
|
+
const pdfBuffer = await tempWin.webContents.printToPDF({
|
|
20
|
+
printBackground: true,
|
|
21
|
+
...options,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
tempWin.destroy();
|
|
25
|
+
|
|
26
|
+
const pdfFileName = `Preview_${Date.now()}.pdf`;
|
|
27
|
+
const pdfPath = path.join(app.getPath("temp"), pdfFileName);
|
|
28
|
+
fs.writeFileSync(pdfPath, pdfBuffer);
|
|
29
|
+
|
|
30
|
+
const previewWin = new BrowserWindow({
|
|
31
|
+
width: 900,
|
|
32
|
+
height: 700,
|
|
33
|
+
show: true,
|
|
34
|
+
webPreferences: {
|
|
35
|
+
contextIsolation: true,
|
|
36
|
+
nodeIntegration: false,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
await previewWin.loadURL(`file://${pdfPath}`);
|
|
41
|
+
return pdfPath;
|
|
42
|
+
} catch (err) {
|
|
43
|
+
console.error("convertHtmlToPdfPreview error:", err);
|
|
44
|
+
throw err;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import os from "os";
|
|
2
|
+
import { app } from "electron";
|
|
3
|
+
|
|
4
|
+
function isRunningFromNetwork() {
|
|
5
|
+
try {
|
|
6
|
+
const exePath = app.getPath("exe");
|
|
7
|
+
return exePath.startsWith("\\\\"); // UNC path
|
|
8
|
+
} catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function shouldUseSafeMode() {
|
|
14
|
+
const network = isRunningFromNetwork();
|
|
15
|
+
const hostname = os.hostname().toLowerCase();
|
|
16
|
+
const vmHint =
|
|
17
|
+
hostname.includes("vm") ||
|
|
18
|
+
hostname.includes("virtual") ||
|
|
19
|
+
hostname.includes("vbox") ||
|
|
20
|
+
hostname.includes("hyper");
|
|
21
|
+
|
|
22
|
+
return network || vmHint;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function applySafeMode() {
|
|
26
|
+
console.log("⚠ SAFE MODE ENABLED");
|
|
27
|
+
app.disableHardwareAcceleration();
|
|
28
|
+
app.commandLine.appendSwitch("disable-gpu");
|
|
29
|
+
app.commandLine.appendSwitch("disable-software-rasterizer");
|
|
30
|
+
app.commandLine.appendSwitch("no-sandbox");
|
|
31
|
+
app.commandLine.appendSwitch("disable-dev-shm-usage");
|
|
32
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { BrowserWindow, screen, session } from "electron";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import { app, dialog } from "electron";
|
|
5
|
+
|
|
6
|
+
export function createMainWindow(__dirname) {
|
|
7
|
+
const primaryDisplay = screen.getPrimaryDisplay();
|
|
8
|
+
const { x, y, width, height } = primaryDisplay.bounds;
|
|
9
|
+
|
|
10
|
+
const iconPath = path.join(app.getAppPath(), "electron/icon.ico");
|
|
11
|
+
const preloadPath = path.join(__dirname, "preload.mjs");
|
|
12
|
+
|
|
13
|
+
const mainWindow = new BrowserWindow({
|
|
14
|
+
x,
|
|
15
|
+
y,
|
|
16
|
+
width,
|
|
17
|
+
height,
|
|
18
|
+
show: false,
|
|
19
|
+
icon: iconPath,
|
|
20
|
+
frame: true,
|
|
21
|
+
webPreferences: {
|
|
22
|
+
preload: preloadPath,
|
|
23
|
+
nodeIntegration: false,
|
|
24
|
+
contextIsolation: true,
|
|
25
|
+
sandbox: false,
|
|
26
|
+
webSecurity: false,
|
|
27
|
+
disableBlinkFeatures: "AutoLoadIconsForPage",
|
|
28
|
+
nativeWindowOpen: true,
|
|
29
|
+
spellcheck: true,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
mainWindow.removeMenu();
|
|
34
|
+
mainWindow.maximize();
|
|
35
|
+
mainWindow.show();
|
|
36
|
+
|
|
37
|
+
mainWindow.on("resize", () => {
|
|
38
|
+
const bounds = mainWindow.getBounds();
|
|
39
|
+
if (bounds?.width && bounds?.height) {
|
|
40
|
+
console.log(`Window resized: ${bounds.width}x${bounds.height}`);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// CORS handling
|
|
45
|
+
setupCorsHandling();
|
|
46
|
+
|
|
47
|
+
// Load the app
|
|
48
|
+
loadAppContent(mainWindow, __dirname);
|
|
49
|
+
|
|
50
|
+
// Setup dev tools shortcuts in development
|
|
51
|
+
if (isDevMode()) {
|
|
52
|
+
setupDevToolsShortcuts(mainWindow);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
mainWindow.webContents.on("did-finish-load", () => {
|
|
56
|
+
console.log("Page loaded successfully");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return mainWindow;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function setupCorsHandling() {
|
|
63
|
+
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
|
|
64
|
+
const responseHeaders = { ...details.responseHeaders };
|
|
65
|
+
|
|
66
|
+
delete responseHeaders["access-control-allow-origin"];
|
|
67
|
+
delete responseHeaders["access-control-allow-headers"];
|
|
68
|
+
delete responseHeaders["access-control-allow-methods"];
|
|
69
|
+
delete responseHeaders["Access-Control-Allow-Origin"];
|
|
70
|
+
delete responseHeaders["Access-Control-Allow-Headers"];
|
|
71
|
+
delete responseHeaders["Access-Control-Allow-Methods"];
|
|
72
|
+
|
|
73
|
+
callback({
|
|
74
|
+
responseHeaders: {
|
|
75
|
+
...responseHeaders,
|
|
76
|
+
"Access-Control-Allow-Origin": ["*"],
|
|
77
|
+
"Access-Control-Allow-Headers": ["*"],
|
|
78
|
+
"Access-Control-Allow-Methods": ["GET, POST, PUT, DELETE, OPTIONS"],
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function isDevMode() {
|
|
85
|
+
return process.argv.includes("--enable-remote-module") ||
|
|
86
|
+
process.env.NODE_ENV === "development";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function loadAppContent(mainWindow, __dirname) {
|
|
90
|
+
const isDev = isDevMode();
|
|
91
|
+
|
|
92
|
+
if (isDev) {
|
|
93
|
+
mainWindow.loadURL("http://localhost:5001");
|
|
94
|
+
console.log("DEV MODE: http://localhost:5001");
|
|
95
|
+
} else {
|
|
96
|
+
const possiblePaths = [
|
|
97
|
+
path.join(__dirname, "web-build/index.html"),
|
|
98
|
+
path.join(__dirname, "../web-build/index.html"),
|
|
99
|
+
path.join(__dirname, "../../web-build/index.html"),
|
|
100
|
+
path.join(app.getAppPath(), "web-build/index.html"),
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
let indexPath = null;
|
|
104
|
+
for (const p of possiblePaths) {
|
|
105
|
+
if (fs.existsSync(p)) {
|
|
106
|
+
indexPath = p;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
console.log("Loading:", indexPath);
|
|
112
|
+
|
|
113
|
+
if (!indexPath) {
|
|
114
|
+
dialog.showErrorBox(
|
|
115
|
+
"Application Error",
|
|
116
|
+
`web-build/index.html not found. Tried: ${possiblePaths.join(", ")}`
|
|
117
|
+
);
|
|
118
|
+
app.quit();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
mainWindow.loadFile(indexPath);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function setupDevToolsShortcuts(mainWindow) {
|
|
127
|
+
mainWindow.webContents.on("before-input-event", (event, input) => {
|
|
128
|
+
if (input.control && input.shift && input.key.toLowerCase() === "i") {
|
|
129
|
+
mainWindow.webContents.toggleDevTools();
|
|
130
|
+
event.preventDefault();
|
|
131
|
+
} else if (input.key === "F12") {
|
|
132
|
+
mainWindow.webContents.toggleDevTools();
|
|
133
|
+
event.preventDefault();
|
|
134
|
+
} else if (
|
|
135
|
+
input.key === "F5" ||
|
|
136
|
+
(input.control && input.key.toLowerCase() === "r")
|
|
137
|
+
) {
|
|
138
|
+
mainWindow.webContents.reload();
|
|
139
|
+
event.preventDefault();
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
package/src/preload.mjs
CHANGED