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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-electron-platform",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "A boilerplate and utilities for running React Native applications in Electron",
5
5
  "main": "index.mjs",
6
6
  "scripts": {
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
- // AUTO SAFE MODE DETECTION
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
- console.log("⚠ SAFE MODE ENABLED");
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
- // DEEP LINKING / URL HANDLING
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
- async function convertHtmlToPdfPreview(htmlContent, options = {}) {
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
- // AUTO UPDATER SETUP
31
+ // APP LIFECYCLE
412
32
  // ======================================================
413
33
 
414
- function setupAutoUpdater() {
415
- console.log("Current app version:", app.getVersion());
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
- const sendStatus = (data) => {
37
+ if (!gotTheLock) {
38
+ app.quit();
39
+ } else {
40
+ app.on('second-instance', (event, commandLine) => {
426
41
  if (mainWindow) {
427
- mainWindow.webContents.send("update-status", data);
428
- }
429
- };
430
-
431
- autoUpdater.on("checking-for-update", () => {
432
- console.log("Checking for updates...");
433
- sendStatus({ status: "checking" });
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
- // Log all registered IPC handlers for debugging
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
- createWindow();
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
@@ -1,4 +1,4 @@
1
- const { contextBridge, ipcRenderer } = require("electron");
1
+ import { contextBridge, ipcRenderer } from "electron";
2
2
 
3
3
  /**
4
4
  * Enhanced Electron API with comprehensive features