react-native-electron-platform 0.0.8 → 0.0.10

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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/src/main.mjs +466 -356
  3. package/src/preload.mjs +175 -20
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-electron-platform",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
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,4 +1,13 @@
1
- import { app, BrowserWindow, session, screen, ipcMain, dialog } from "electron";
1
+ import {
2
+ app,
3
+ BrowserWindow,
4
+ session,
5
+ screen,
6
+ ipcMain,
7
+ dialog,
8
+ clipboard,
9
+ shell
10
+ } from "electron";
2
11
  import electronUpdater from "electron-updater";
3
12
  const { autoUpdater } = electronUpdater;
4
13
  import path from "path";
@@ -11,6 +20,10 @@ import packageJson from "../../../package.json" with { type: 'json' };
11
20
 
12
21
  const __dirname = dirname(fileURLToPath(import.meta.url));
13
22
 
23
+ // ======================================================
24
+ // AUTO SAFE MODE DETECTION
25
+ // ======================================================
26
+
14
27
  function isRunningFromNetwork() {
15
28
  try {
16
29
  const exePath = app.getPath("exe");
@@ -22,7 +35,6 @@ function isRunningFromNetwork() {
22
35
 
23
36
  function shouldUseSafeMode() {
24
37
  const network = isRunningFromNetwork();
25
-
26
38
  const hostname = os.hostname().toLowerCase();
27
39
  const vmHint =
28
40
  hostname.includes("vm") ||
@@ -36,7 +48,6 @@ function shouldUseSafeMode() {
36
48
  // Apply BEFORE Electron ready
37
49
  if (shouldUseSafeMode()) {
38
50
  console.log("⚠ SAFE MODE ENABLED");
39
-
40
51
  app.disableHardwareAcceleration();
41
52
  app.commandLine.appendSwitch("disable-gpu");
42
53
  app.commandLine.appendSwitch("disable-software-rasterizer");
@@ -48,219 +59,274 @@ if (shouldUseSafeMode()) {
48
59
 
49
60
  let mainWindow;
50
61
 
51
- function createWindow() {
52
- const primaryDisplay = screen.getPrimaryDisplay();
53
- const { x, y, width, height } = primaryDisplay.bounds;
62
+ // ======================================================
63
+ // DEEP LINKING / URL HANDLING
64
+ // ======================================================
54
65
 
55
- const iconPath = path.join(app.getAppPath(), "electron/icon.ico");
56
- const preloadPath = path.join(__dirname, "preload.js");
66
+ const appOpenURLTargets = new WeakSet();
57
67
 
58
- mainWindow = new BrowserWindow({
59
- x,
60
- y,
61
- width,
62
- height,
63
- show: false,
64
- icon: iconPath,
65
- frame: true,
66
- webPreferences: {
67
- preload: preloadPath,
68
- nodeIntegration: false,
69
- contextIsolation: true,
70
- sandbox: false,
71
- webSecurity: false,
72
- disableBlinkFeatures: "AutoLoadIconsForPage",
73
- nativeWindowOpen: true,
74
- spellcheck: true,
75
- },
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);
76
86
  });
77
87
 
78
- mainWindow.removeMenu();
79
- mainWindow.maximize();
80
- mainWindow.show();
88
+ ipcMain.handle('react-native-get-initial-url', () => {
89
+ return Promise.resolve(process.argv[1]);
90
+ });
81
91
 
82
- mainWindow.on("resize", () => {
83
- const bounds = mainWindow.getBounds();
84
- if (bounds?.width && bounds?.height) {
85
- console.log(`Window resized: ${bounds.width}x${bounds.height}`);
86
- }
92
+ ipcMain.handle('react-native-open-url', async (_event, url) => {
93
+ await shell.openExternal(url);
87
94
  });
88
95
 
89
- session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
90
- const responseHeaders = { ...details.responseHeaders };
96
+ // Clipboard Handlers
97
+ ipcMain.handle('react-native-get-clipboard-text', async () => {
98
+ return await clipboard.readText();
99
+ });
91
100
 
92
- delete responseHeaders["access-control-allow-origin"];
93
- delete responseHeaders["access-control-allow-headers"];
94
- delete responseHeaders["access-control-allow-methods"];
95
- delete responseHeaders["Access-Control-Allow-Origin"];
96
- delete responseHeaders["Access-Control-Allow-Headers"];
97
- delete responseHeaders["Access-Control-Allow-Methods"];
101
+ ipcMain.handle('react-native-set-clipboard-text', async (_event, text) => {
102
+ await clipboard.writeText(text);
103
+ });
98
104
 
99
- callback({
100
- responseHeaders: {
101
- ...responseHeaders,
102
- "Access-Control-Allow-Origin": ["*"],
103
- "Access-Control-Allow-Headers": ["*"],
104
- "Access-Control-Allow-Methods": ["GET, POST, PUT, DELETE, OPTIONS"],
105
- },
106
- });
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
+ }
107
112
  });
108
113
 
109
- const isDev =
110
- process.argv.includes("--enable-remote-module") ||
111
- process.env.NODE_ENV === "development";
114
+ // Internal support check
115
+ ipcMain.on('react-native-supported', (event) => {
116
+ event.returnValue = true;
117
+ });
112
118
 
113
- if (isDev) {
114
- mainWindow.loadURL("http://localhost:5001");
115
- console.log("DEV MODE: http://localhost:5001");
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
+ });
116
127
 
117
- mainWindow.webContents.on("before-input-event", (event, input) => {
118
- if (input.control && input.shift && input.key.toLowerCase() === "i") {
119
- mainWindow.webContents.toggleDevTools();
120
- event.preventDefault();
121
- } else if (input.key === "F12") {
122
- mainWindow.webContents.toggleDevTools();
123
- event.preventDefault();
124
- } else if (
125
- input.key === "F5" ||
126
- (input.control && input.key.toLowerCase() === "r")
127
- ) {
128
- mainWindow.webContents.reload();
129
- event.preventDefault();
130
- }
131
- });
132
- } else {
133
- const possiblePaths = [
134
- path.join(__dirname, "web-build/index.html"),
135
- path.join(__dirname, "../web-build/index.html"),
136
- path.join(__dirname, "../../web-build/index.html"),
137
- path.join(app.getAppPath(), "web-build/index.html"),
138
- ];
128
+ ipcMain.handle("get-app-version", () => {
129
+ return app.getVersion();
130
+ });
139
131
 
140
- let indexPath = null;
141
- for (const p of possiblePaths) {
142
- if (fs.existsSync(p)) {
143
- indexPath = p;
144
- break;
145
- }
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
+ };
146
144
  }
145
+ });
147
146
 
148
- console.log("Loading:", indexPath);
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
+ });
149
155
 
150
- if (!indexPath) {
151
- dialog.showErrorBox(
152
- "Application Error",
153
- `web-build/index.html not found. Tried: ${possiblePaths.join(", ")}`
156
+ await tempWin.loadURL(
157
+ `data:text/html;charset=utf-8,${encodeURIComponent(html)}`
154
158
  );
155
- app.quit();
156
- return;
157
- }
158
159
 
159
- mainWindow.loadFile(indexPath);
160
- }
160
+ const pdfBuffer = await tempWin.webContents.printToPDF({});
161
+ tempWin.destroy();
161
162
 
162
- mainWindow.webContents.on("did-finish-load", () => {
163
- console.log("Page loaded successfully");
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
+ }
164
180
  });
165
- }
166
181
 
167
- /**
168
- * ----------------------------------------------------
169
- * AUTO UPDATE (NSIS)
170
- * ----------------------------------------------------
171
- */
172
- function setupAutoUpdater() {
173
- console.log("Current app version:", app.getVersion());
174
-
175
- if (!app.isPackaged) {
176
- console.log("Auto-update disabled in development.");
177
- return;
178
- }
179
-
180
- autoUpdater.autoDownload = true;
181
- autoUpdater.autoInstallOnAppQuit = true;
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
+ });
182
196
 
183
- const sendStatus = (data) => {
184
- if (mainWindow) {
185
- mainWindow.webContents.send("update-status", data);
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
+ };
186
222
  }
187
- };
223
+ });
188
224
 
189
- autoUpdater.on("checking-for-update", () => {
190
- console.log("Checking for updates...");
191
- sendStatus({ status: "checking" });
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
+ }
192
238
  });
193
239
 
194
- autoUpdater.on("update-available", (info) => {
195
- console.log("Update available:", info.version);
196
- sendStatus({ status: "available", version: info.version });
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
+ }
197
269
  });
198
270
 
199
- autoUpdater.on("update-not-available", () => {
200
- console.log("No updates available");
201
- sendStatus({ status: "not-available" });
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
+ }
202
295
  });
203
296
 
204
- autoUpdater.on("download-progress", (progress) => {
205
- console.log(`Download progress: ${Math.round(progress.percent)}%`);
206
- sendStatus({
207
- status: "downloading",
208
- percent: Math.round(progress.percent),
209
- });
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
+ }
210
305
  });
211
306
 
212
- autoUpdater.on("update-downloaded", () => {
213
- console.log("Update downloaded");
214
- sendStatus({ status: "downloaded" });
307
+ // Utility Handlers
308
+ ipcMain.handle("get-platform", () => {
309
+ return process.platform;
310
+ });
215
311
 
216
- dialog
217
- .showMessageBox(mainWindow, {
218
- type: "info",
219
- title: "Update Ready",
220
- message: "A new version has been downloaded. Restart now?",
221
- buttons: ["Restart", "Later"],
222
- })
223
- .then((result) => {
224
- if (result.response === 0) {
225
- autoUpdater.quitAndInstall();
226
- }
227
- });
312
+ ipcMain.handle("get-app-path", () => {
313
+ return app.getAppPath();
228
314
  });
229
315
 
230
- autoUpdater.on("error", (err) => {
231
- console.error("Auto-updater error:", err);
232
- sendStatus({ status: "error", message: err.message });
316
+ ipcMain.handle("get-user-data-path", () => {
317
+ return app.getPath("userData");
233
318
  });
234
319
 
235
- // Check for updates after a short delay to ensure window is ready
236
- setTimeout(() => {
237
- autoUpdater.checkForUpdates();
238
- }, 3000);
320
+ console.log("✅ All IPC handlers registered");
239
321
  }
240
322
 
241
- /**
242
- * ----------------------------------------------------
243
- * IPC: MANUAL UPDATE CHECK
244
- * ----------------------------------------------------
245
- */
246
- ipcMain.handle("check-for-updates", async () => {
247
- if (app.isPackaged) {
248
- autoUpdater.checkForUpdates();
249
- return { status: "checking" };
250
- }
251
- return { status: "disabled", message: "Auto-update disabled in development" };
252
- });
253
-
254
- //
255
323
  // ======================================================
256
324
  // NETWORK SERVICE
257
325
  // ======================================================
258
- //
259
326
 
260
327
  async function NetworkServiceCall(method, url, params = {}, headers = {}) {
261
328
  try {
262
329
  const upperMethod = method.toUpperCase();
263
-
264
330
  const options = {
265
331
  method: upperMethod,
266
332
  headers: {
@@ -295,181 +361,12 @@ async function NetworkServiceCall(method, url, params = {}, headers = {}) {
295
361
  }
296
362
  }
297
363
 
298
- ipcMain.handle("network-call", async (event, payload) => {
299
- try {
300
- const { method, url, params, headers } = payload;
301
- console.log("IPC network-call:", { method, url });
302
- return NetworkServiceCall(method, url, params, headers);
303
- } catch (err) {
304
- console.error("IPC network-call error:", err);
305
- return {
306
- httpstatus: 500,
307
- data: { title: "ERROR", message: err.message },
308
- };
309
- }
310
- });
311
-
312
- //
313
364
  // ======================================================
314
- // PDF / FILE IPC
365
+ // PDF CONVERSION HELPER
315
366
  // ======================================================
316
- //
317
-
318
- ipcMain.handle("save-pdf", async (event, html) => {
319
- try {
320
- console.log("IPC save-pdf called");
321
-
322
- const tempWin = new BrowserWindow({
323
- show: false,
324
- webPreferences: { offscreen: true },
325
- });
326
-
327
- await tempWin.loadURL(
328
- `data:text/html;charset=utf-8,${encodeURIComponent(html)}`
329
- );
330
-
331
- const pdfBuffer = await tempWin.webContents.printToPDF({});
332
- tempWin.destroy();
333
-
334
- const { filePath } = await dialog.showSaveDialog({
335
- title: "Save PDF",
336
- defaultPath: "document.pdf",
337
- filters: [{ name: "PDF", extensions: ["pdf"] }],
338
- });
339
-
340
- if (filePath) {
341
- fs.writeFileSync(filePath, pdfBuffer);
342
- console.log("PDF saved:", filePath);
343
- return "saved";
344
- }
345
-
346
- return "cancelled";
347
- } catch (err) {
348
- console.error("IPC save-pdf error:", err);
349
- throw err;
350
- }
351
- });
352
-
353
- ipcMain.handle("post-pdf-preview", async (event, payload) => {
354
- try {
355
- const { url, data, headers = {} } = payload;
356
- console.log("IPC post-pdf-preview:", { url });
357
-
358
- const res = await fetch(url, {
359
- method: "POST",
360
- headers: {
361
- "Content-Type": "application/json",
362
- Accept: "application/pdf",
363
- ...headers,
364
- },
365
- body: data,
366
- });
367
-
368
- if (!res.ok) {
369
- throw new Error(`HTTP ${res.status}`);
370
- }
371
-
372
- const fileName = `Report_${Date.now()}.pdf`;
373
- const tempPath = path.join(app.getPath("temp"), fileName);
374
-
375
- const nodeStream = Readable.fromWeb(res.body);
376
- await new Promise((resolve, reject) => {
377
- const fileStream = fs.createWriteStream(tempPath);
378
- nodeStream.pipe(fileStream);
379
- nodeStream.on("error", reject);
380
- fileStream.on("finish", resolve);
381
- });
382
-
383
- return {
384
- status: "ok",
385
- path: `file://${tempPath}`,
386
- };
387
- } catch (err) {
388
- console.error("post-pdf-preview error:", err);
389
- return {
390
- status: "error",
391
- message: err.message,
392
- };
393
- }
394
- });
395
-
396
- ipcMain.handle("open-pdf-preview", async (_, pdfUrl) => {
397
- try {
398
- const res = await fetch(pdfUrl);
399
- const buffer = Buffer.from(await res.arrayBuffer());
400
-
401
- const tempPath = path.join(app.getPath("temp"), `preview_${Date.now()}.pdf`);
402
- fs.writeFileSync(tempPath, buffer);
403
-
404
- return `file://${tempPath}`;
405
- } catch (err) {
406
- console.error("open-pdf-preview error:", err);
407
- throw err;
408
- }
409
- });
410
-
411
- ipcMain.handle("select-file", async () => {
412
- try {
413
- const result = await dialog.showOpenDialog({
414
- title: "Select a file",
415
- properties: ["openFile"],
416
- filters: [
417
- { name: "All Files", extensions: ["*"] },
418
- ],
419
- });
420
-
421
- if (result.canceled) {
422
- return {
423
- status: "cancelled",
424
- filePath: null,
425
- };
426
- }
427
-
428
- return {
429
- status: "selected",
430
- filePath: result.filePaths[0],
431
- };
432
- } catch (err) {
433
- console.error("select-file error:", err);
434
- return {
435
- status: "error",
436
- message: err.message,
437
- };
438
- }
439
- });
440
-
441
- ipcMain.handle("preview-html", async (event, htmlContent) => {
442
- try {
443
- // Create a new hidden BrowserWindow for preview
444
- const previewWin = new BrowserWindow({
445
- width: 800,
446
- height: 600,
447
- show: false, // hide until ready
448
- webPreferences: {
449
- contextIsolation: true,
450
- sandbox: false,
451
- nodeIntegration: false,
452
- },
453
- });
454
-
455
- // Load the HTML content
456
- await previewWin.loadURL(
457
- `data:text/html;charset=utf-8,${encodeURIComponent(htmlContent)}`
458
- );
459
-
460
- // Show the window once loaded
461
- previewWin.show();
462
-
463
- return { status: "ok" };
464
- } catch (err) {
465
- console.error("preview-html error:", err);
466
- return { status: "error", message: err.message };
467
- }
468
- });
469
367
 
470
368
  async function convertHtmlToPdfPreview(htmlContent, options = {}) {
471
369
  try {
472
- // Step 1: Create a hidden BrowserWindow
473
370
  const tempWin = new BrowserWindow({
474
371
  show: false,
475
372
  webPreferences: {
@@ -479,10 +376,8 @@ async function convertHtmlToPdfPreview(htmlContent, options = {}) {
479
376
  },
480
377
  });
481
378
 
482
- // Step 2: Load HTML into the window
483
379
  await tempWin.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(htmlContent)}`);
484
380
 
485
- // Step 3: Generate PDF
486
381
  const pdfBuffer = await tempWin.webContents.printToPDF({
487
382
  printBackground: true,
488
383
  ...options,
@@ -490,12 +385,10 @@ async function convertHtmlToPdfPreview(htmlContent, options = {}) {
490
385
 
491
386
  tempWin.destroy();
492
387
 
493
- // Step 4: Save PDF to temp folder
494
388
  const pdfFileName = `Preview_${Date.now()}.pdf`;
495
389
  const pdfPath = path.join(app.getPath("temp"), pdfFileName);
496
390
  fs.writeFileSync(pdfPath, pdfBuffer);
497
391
 
498
- // Step 5: Open a new window to preview the PDF
499
392
  const previewWin = new BrowserWindow({
500
393
  width: 900,
501
394
  height: 700,
@@ -507,7 +400,6 @@ async function convertHtmlToPdfPreview(htmlContent, options = {}) {
507
400
  });
508
401
 
509
402
  await previewWin.loadURL(`file://${pdfPath}`);
510
-
511
403
  return pdfPath;
512
404
  } catch (err) {
513
405
  console.error("convertHtmlToPdfPreview error:", err);
@@ -515,21 +407,210 @@ async function convertHtmlToPdfPreview(htmlContent, options = {}) {
515
407
  }
516
408
  }
517
409
 
518
- ipcMain.handle("html-to-pdf-preview", async (event, htmlContent) => {
519
- try {
520
- const pdfPath = await convertHtmlToPdfPreview(htmlContent);
521
- return { status: "ok", path: pdfPath };
522
- } catch (err) {
523
- console.error("html-to-pdf-preview error:", err);
524
- return { status: "error", message: err.message };
410
+ // ======================================================
411
+ // AUTO UPDATER SETUP
412
+ // ======================================================
413
+
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;
525
420
  }
526
- });
421
+
422
+ autoUpdater.autoDownload = true;
423
+ autoUpdater.autoInstallOnAppQuit = true;
424
+
425
+ const sendStatus = (data) => {
426
+ 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.mjs");
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;
583
+ }
584
+ }
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
+ });
603
+ }
604
+
605
+ // ======================================================
606
+ // REGISTER IPC HANDLERS BEFORE APP READY
607
+ // ======================================================
608
+
609
+ registerIpcHandlers();
527
610
 
528
- //
529
611
  // ======================================================
530
- // APP READY
612
+ // APP LIFECYCLE
531
613
  // ======================================================
532
- //
533
614
 
534
615
  app.whenReady().then(() => {
535
616
  if (process.platform === "win32") {
@@ -537,6 +618,14 @@ app.whenReady().then(() => {
537
618
  }
538
619
  createWindow();
539
620
  setupAutoUpdater();
621
+
622
+ // Log all registered IPC handlers for debugging
623
+ console.log("📊 IPC Handlers registered:",
624
+ ipcMain.eventNames().filter(name =>
625
+ typeof name === 'string' &&
626
+ !name.startsWith('ELECTRON_')
627
+ )
628
+ );
540
629
  });
541
630
 
542
631
  app.on("window-all-closed", () => {
@@ -549,4 +638,25 @@ app.on("activate", () => {
549
638
  if (BrowserWindow.getAllWindows().length === 0) {
550
639
  createWindow();
551
640
  }
552
- });
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
+ }
package/src/preload.mjs CHANGED
@@ -1,32 +1,152 @@
1
1
  import { contextBridge, ipcRenderer } from "electron";
2
2
 
3
+ /**
4
+ * Enhanced Electron API with comprehensive features
5
+ * Combines functionality from both codebases
6
+ */
3
7
  contextBridge.exposeInMainWorld("electronAPI", {
4
- // Network
8
+ // ======================================================
9
+ // PLATFORM INFORMATION
10
+ // ======================================================
11
+ platform: process.platform,
12
+
13
+ // ======================================================
14
+ // DEEP LINKING / URL HANDLING
15
+ // ======================================================
16
+
17
+ appOpenURL: {
18
+ /**
19
+ * Add listener for app open URL events
20
+ * @param {Function} listener - Callback function for URL events
21
+ */
22
+ addListener: (listener) => {
23
+ ipcRenderer.addListener('react-native-app-open-url', listener);
24
+ ipcRenderer.invoke('react-native-add-app-open-url').catch(console.error);
25
+ },
26
+
27
+ /**
28
+ * Remove listener for app open URL events
29
+ * @param {Function} listener - Callback function to remove
30
+ */
31
+ removeListener: (listener) => {
32
+ ipcRenderer.removeListener('react-native-app-open-url', listener);
33
+ }
34
+ },
35
+
36
+ /**
37
+ * Get the initial URL that opened the app
38
+ * @returns {Promise<string>} - Initial URL or null
39
+ */
40
+ getInitialURL: () => ipcRenderer.invoke('react-native-get-initial-url'),
41
+
42
+ /**
43
+ * Open a URL in the default browser
44
+ * @param {string} url - URL to open
45
+ * @returns {Promise<void>}
46
+ */
47
+ openURL: (url) => ipcRenderer.invoke('react-native-open-url', url),
48
+
49
+ // ======================================================
50
+ // CLIPBOARD OPERATIONS
51
+ // ======================================================
52
+
53
+ /**
54
+ * Get text from clipboard
55
+ * @returns {Promise<string>} - Clipboard text
56
+ */
57
+ getClipboardText: () => ipcRenderer.invoke('react-native-get-clipboard-text'),
58
+
59
+ /**
60
+ * Set text to clipboard
61
+ * @param {string} text - Text to copy to clipboard
62
+ * @returns {Promise<void>}
63
+ */
64
+ setClipboardText: (text) => ipcRenderer.invoke('react-native-set-clipboard-text', text),
65
+
66
+ // ======================================================
67
+ // DIALOGS
68
+ // ======================================================
69
+
70
+ /**
71
+ * Show an alert dialog
72
+ * @param {Object} config - Alert configuration
73
+ * @param {string} config.title - Alert title
74
+ * @param {string} config.message - Alert message
75
+ * @param {Array} config.buttons - Button configurations
76
+ * @returns {Promise<Object>} - Alert result
77
+ */
78
+ showAlert: (config) => ipcRenderer.invoke('react-native-show-alert', config),
79
+
80
+ // ======================================================
81
+ // NETWORK OPERATIONS
82
+ // ======================================================
83
+
84
+ /**
85
+ * Make a network call
86
+ * @param {string} method - HTTP method (GET, POST, etc.)
87
+ * @param {string} url - Request URL
88
+ * @param {Object} params - Request parameters
89
+ * @param {Object} headers - Request headers
90
+ * @returns {Promise<Object>} - Network response
91
+ */
5
92
  networkCall: (method, url, params = {}, headers = {}) =>
6
93
  ipcRenderer.invoke("network-call", { method, url, params, headers }),
7
94
 
8
- // PDF Operations
9
- savePDF: (html) =>
10
- ipcRenderer.invoke("save-pdf", html),
95
+ // ======================================================
96
+ // PDF OPERATIONS
97
+ // ======================================================
98
+
99
+ /**
100
+ * Save HTML as PDF
101
+ * @param {string} html - HTML content to convert
102
+ * @returns {Promise<Object>} - Save result
103
+ */
104
+ savePDF: (html) => ipcRenderer.invoke("save-pdf", html),
105
+
106
+ /**
107
+ * Post PDF preview
108
+ * @param {Object} payload - PDF preview payload
109
+ * @returns {Promise<Object>} - Preview result
110
+ */
111
+ postPdfPreview: (payload) => ipcRenderer.invoke("post-pdf-preview", payload),
11
112
 
12
- postPdfPreview: (payload) =>
13
- ipcRenderer.invoke("post-pdf-preview", payload),
113
+ /**
114
+ * Open PDF preview
115
+ * @param {string} pdfUrl - URL of PDF to preview
116
+ * @returns {Promise<Object>} - Open result
117
+ */
118
+ openPdfPreview: (pdfUrl) => ipcRenderer.invoke("open-pdf-preview", pdfUrl),
14
119
 
15
- openPdfPreview: (pdfUrl) =>
16
- ipcRenderer.invoke("open-pdf-preview", pdfUrl),
120
+ // ======================================================
121
+ // FILE OPERATIONS
122
+ // ======================================================
17
123
 
18
- // File Operations
124
+ /**
125
+ * Open file selection dialog
126
+ * @returns {Promise<Object>} - Selected file info
127
+ */
19
128
  selectFile: () => ipcRenderer.invoke("select-file"),
20
129
 
21
- // HTML/PDF Preview
22
- previewHTML: (htmlContent) =>
23
- ipcRenderer.invoke("preview-html", htmlContent),
130
+ // ======================================================
131
+ // HTML/PDF PREVIEW
132
+ // ======================================================
24
133
 
25
- htmlToPdfPreview: (htmlContent) =>
26
- ipcRenderer.invoke("html-to-pdf-preview", htmlContent),
134
+ /**
135
+ * Preview HTML content
136
+ * @param {string} htmlContent - HTML to preview
137
+ * @returns {Promise<Object>} - Preview result
138
+ */
139
+ previewHTML: (htmlContent) => ipcRenderer.invoke("preview-html", htmlContent),
140
+
141
+ /**
142
+ * Convert HTML to PDF and preview
143
+ * @param {string} htmlContent - HTML to convert
144
+ * @returns {Promise<Object>} - PDF preview result
145
+ */
146
+ htmlToPdfPreview: (htmlContent) => ipcRenderer.invoke("html-to-pdf-preview", htmlContent),
27
147
 
28
148
  // ======================================================
29
- // AUTO-UPDATER APIs (NEW)
149
+ // AUTO-UPDATER APIS
30
150
  // ======================================================
31
151
 
32
152
  /**
@@ -35,10 +155,7 @@ contextBridge.exposeInMainWorld("electronAPI", {
35
155
  * @returns {Function} - Cleanup function to remove listener
36
156
  */
37
157
  onUpdateStatus: (callback) => {
38
- // Create the listener
39
158
  const listener = (event, data) => callback(data);
40
-
41
- // Add the listener
42
159
  ipcRenderer.on('update-status', listener);
43
160
 
44
161
  // Return cleanup function
@@ -57,5 +174,43 @@ contextBridge.exposeInMainWorld("electronAPI", {
57
174
  * Get the current app version
58
175
  * @returns {Promise<string>} - Current version
59
176
  */
60
- getAppVersion: () => ipcRenderer.invoke("get-app-version")
61
- });
177
+ getAppVersion: () => ipcRenderer.invoke("get-app-version"),
178
+
179
+ // ======================================================
180
+ // UTILITY FUNCTIONS
181
+ // ======================================================
182
+
183
+ /**
184
+ * Generic event listener with cleanup pattern
185
+ * @param {string} channel - IPC channel to listen on
186
+ * @param {Function} callback - Callback function
187
+ * @returns {Function} - Cleanup function
188
+ */
189
+ on: (channel, callback) => {
190
+ const listener = (event, ...args) => callback(...args);
191
+ ipcRenderer.on(channel, listener);
192
+ return () => {
193
+ ipcRenderer.removeListener(channel, listener);
194
+ };
195
+ },
196
+
197
+ /**
198
+ * Send an IPC message without waiting for response
199
+ * @param {string} channel - IPC channel
200
+ * @param {...any} args - Arguments to send
201
+ */
202
+ send: (channel, ...args) => {
203
+ ipcRenderer.send(channel, ...args);
204
+ },
205
+
206
+ /**
207
+ * Invoke an IPC method and wait for response
208
+ * @param {string} channel - IPC channel
209
+ * @param {...any} args - Arguments to send
210
+ * @returns {Promise<any>} - Response from main process
211
+ */
212
+ invoke: (channel, ...args) => ipcRenderer.invoke(channel, ...args)
213
+ });
214
+
215
+ // Log that preload has loaded
216
+ console.log('✅ Electron API exposed to renderer process');