react-native-electron-platform 0.0.11 → 0.0.13

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/src/main.mjs CHANGED
@@ -1,71 +1,542 @@
1
- import { app, BrowserWindow, ipcMain } from "electron";
1
+ import { app, BrowserWindow, session, screen, ipcMain, dialog } from "electron";
2
+ import electronUpdater from "electron-updater";
3
+ const { autoUpdater } = electronUpdater;
4
+ import path from "path";
5
+ import fs from "fs";
6
+ import { Readable } from "stream";
7
+ import os from "os";
2
8
  import { fileURLToPath } from 'url';
3
9
  import { dirname } from 'path';
4
10
  import packageJson from "../../../package.json" with { type: 'json' };
5
11
 
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
-
13
12
  const __dirname = dirname(fileURLToPath(import.meta.url));
14
13
 
15
- // ======================================================
16
- // SAFE MODE DETECTION (applies before ready)
17
- // ======================================================
14
+ function isRunningFromNetwork() {
15
+ try {
16
+ const exePath = app.getPath("exe");
17
+ return exePath.startsWith("\\\\"); // UNC path
18
+ } catch {
19
+ return false;
20
+ }
21
+ }
22
+
23
+ function shouldUseSafeMode() {
24
+ const network = isRunningFromNetwork();
25
+
26
+ const hostname = os.hostname().toLowerCase();
27
+ const vmHint =
28
+ hostname.includes("vm") ||
29
+ hostname.includes("virtual") ||
30
+ hostname.includes("vbox") ||
31
+ hostname.includes("hyper");
32
+
33
+ return network || vmHint;
34
+ }
35
+
36
+ // Apply BEFORE Electron ready
18
37
  if (shouldUseSafeMode()) {
19
- applySafeMode();
38
+ console.log("⚠ SAFE MODE ENABLED");
39
+
40
+ app.disableHardwareAcceleration();
41
+ app.commandLine.appendSwitch("disable-gpu");
42
+ app.commandLine.appendSwitch("disable-software-rasterizer");
43
+ app.commandLine.appendSwitch("no-sandbox");
44
+ app.commandLine.appendSwitch("disable-dev-shm-usage");
45
+ } else {
46
+ console.log("✅ NORMAL MODE");
20
47
  }
21
48
 
22
49
  let mainWindow;
23
50
 
51
+ function createWindow() {
52
+ const primaryDisplay = screen.getPrimaryDisplay();
53
+ const { x, y, width, height } = primaryDisplay.bounds;
54
+
55
+ const iconPath = path.join(__dirname, "icon.ico");
56
+ const preloadPath = path.join(__dirname, "preload.js");
57
+
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
+ },
76
+ });
77
+
78
+ mainWindow.removeMenu();
79
+ mainWindow.maximize();
80
+ mainWindow.show();
81
+
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
+ }
87
+ });
88
+
89
+ session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
90
+ const responseHeaders = { ...details.responseHeaders };
91
+
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"];
98
+
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
+ });
107
+ });
108
+
109
+ const isDev =
110
+ process.argv.includes("--enable-remote-module") ||
111
+ process.env.NODE_ENV === "development";
112
+
113
+ if (isDev) {
114
+ mainWindow.loadURL("http://localhost:5001");
115
+ console.log("DEV MODE: http://localhost:5001");
116
+
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
+ ];
139
+
140
+ let indexPath = null;
141
+ for (const p of possiblePaths) {
142
+ if (fs.existsSync(p)) {
143
+ indexPath = p;
144
+ break;
145
+ }
146
+ }
147
+
148
+ console.log("Loading:", indexPath);
149
+
150
+ if (!indexPath) {
151
+ dialog.showErrorBox(
152
+ "Application Error",
153
+ `web-build/index.html not found. Tried: ${possiblePaths.join(", ")}`
154
+ );
155
+ app.quit();
156
+ return;
157
+ }
158
+
159
+ mainWindow.loadFile(indexPath);
160
+ }
161
+
162
+ mainWindow.webContents.on("did-finish-load", () => {
163
+ console.log("Page loaded successfully");
164
+ });
165
+ }
166
+
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
+
183
+ const sendStatus = (data) => {
184
+ if (mainWindow) {
185
+ mainWindow.webContents.send("update-status", data);
186
+ }
187
+ };
188
+
189
+ autoUpdater.on("checking-for-update", () => {
190
+ console.log("Checking for updates...");
191
+ sendStatus({ status: "checking" });
192
+ });
193
+
194
+ autoUpdater.on("update-available", (info) => {
195
+ console.log("Update available:", info.version);
196
+ sendStatus({ status: "available", version: info.version });
197
+ });
198
+
199
+ autoUpdater.on("update-not-available", () => {
200
+ console.log("No updates available");
201
+ sendStatus({ status: "not-available" });
202
+ });
203
+
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
+ });
210
+ });
211
+
212
+ autoUpdater.on("update-downloaded", () => {
213
+ console.log("Update downloaded");
214
+ sendStatus({ status: "downloaded" });
215
+
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
+ });
228
+ });
229
+
230
+ autoUpdater.on("error", (err) => {
231
+ console.error("Auto-updater error:", err);
232
+ sendStatus({ status: "error", message: err.message });
233
+ });
234
+
235
+ // Check for updates after a short delay to ensure window is ready
236
+ setTimeout(() => {
237
+ autoUpdater.checkForUpdates();
238
+ }, 3000);
239
+ }
240
+
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
+ //
24
255
  // ======================================================
25
- // REGISTER IPC HANDLERS
256
+ // NETWORK SERVICE
26
257
  // ======================================================
27
- registerAllIpcHandlers();
28
- registerDeepLinkingHandlers(sendOpenURL);
258
+ //
259
+
260
+ async function NetworkServiceCall(method, url, params = {}, headers = {}) {
261
+ try {
262
+ const upperMethod = method.toUpperCase();
29
263
 
264
+ const options = {
265
+ method: upperMethod,
266
+ headers: {
267
+ "Content-Type": "application/json",
268
+ ...headers,
269
+ },
270
+ };
271
+
272
+ if (upperMethod !== "GET") {
273
+ options.body = JSON.stringify(params);
274
+ } else if (params && Object.keys(params).length > 0) {
275
+ const query = new URLSearchParams(params).toString();
276
+ url += `?${query}`;
277
+ }
278
+
279
+ const response = await fetch(url, options);
280
+ const data = await response.json().catch(() => ({}));
281
+
282
+ if (response.ok || data?.httpstatus === 200) {
283
+ return { httpstatus: 200, data: data?.data || data };
284
+ }
285
+
286
+ return {
287
+ httpstatus: response.status,
288
+ data: { title: "ERROR", message: data?.message || "Network Error" },
289
+ };
290
+ } catch (err) {
291
+ return {
292
+ httpstatus: 404,
293
+ data: { title: "ERROR", message: err.message },
294
+ };
295
+ }
296
+ }
297
+
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
+ //
30
313
  // ======================================================
31
- // APP LIFECYCLE
314
+ // PDF / FILE IPC
32
315
  // ======================================================
316
+ //
33
317
 
34
- // Single instance lock
35
- const gotTheLock = app.requestSingleInstanceLock();
318
+ ipcMain.handle("save-pdf", async (event, html) => {
319
+ try {
320
+ console.log("IPC save-pdf called");
36
321
 
37
- if (!gotTheLock) {
38
- app.quit();
39
- } else {
40
- app.on('second-instance', (event, commandLine) => {
41
- if (mainWindow) {
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);
49
- }
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";
50
344
  }
51
- });
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
+
470
+ async function convertHtmlToPdfPreview(htmlContent, options = {}) {
471
+ try {
472
+ // Step 1: Create a hidden BrowserWindow
473
+ const tempWin = new BrowserWindow({
474
+ show: false,
475
+ webPreferences: {
476
+ offscreen: true,
477
+ contextIsolation: true,
478
+ nodeIntegration: false,
479
+ },
480
+ });
481
+
482
+ // Step 2: Load HTML into the window
483
+ await tempWin.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(htmlContent)}`);
484
+
485
+ // Step 3: Generate PDF
486
+ const pdfBuffer = await tempWin.webContents.printToPDF({
487
+ printBackground: true,
488
+ ...options,
489
+ });
490
+
491
+ tempWin.destroy();
492
+
493
+ // Step 4: Save PDF to temp folder
494
+ const pdfFileName = `Preview_${Date.now()}.pdf`;
495
+ const pdfPath = path.join(app.getPath("temp"), pdfFileName);
496
+ fs.writeFileSync(pdfPath, pdfBuffer);
497
+
498
+ // Step 5: Open a new window to preview the PDF
499
+ const previewWin = new BrowserWindow({
500
+ width: 900,
501
+ height: 700,
502
+ show: true,
503
+ webPreferences: {
504
+ contextIsolation: true,
505
+ nodeIntegration: false,
506
+ },
507
+ });
508
+
509
+ await previewWin.loadURL(`file://${pdfPath}`);
510
+
511
+ return pdfPath;
512
+ } catch (err) {
513
+ console.error("convertHtmlToPdfPreview error:", err);
514
+ throw err;
515
+ }
52
516
  }
53
517
 
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 };
525
+ }
526
+ });
527
+
528
+ //
529
+ // ======================================================
530
+ // APP READY
531
+ // ======================================================
532
+ //
533
+
54
534
  app.whenReady().then(() => {
55
535
  if (process.platform === "win32") {
56
536
  app.setAppUserModelId(`com.${packageJson.name}.desktop`);
57
537
  }
58
-
59
- mainWindow = createMainWindow(__dirname);
60
- setupAutoUpdater(mainWindow);
61
-
62
- // Log registered handlers
63
- console.log("📊 IPC Handlers registered:",
64
- ipcMain.eventNames().filter(name =>
65
- typeof name === 'string' &&
66
- !name.startsWith('ELECTRON_')
67
- )
68
- );
538
+ createWindow();
539
+ setupAutoUpdater();
69
540
  });
70
541
 
71
542
  app.on("window-all-closed", () => {
@@ -76,6 +547,6 @@ app.on("window-all-closed", () => {
76
547
 
77
548
  app.on("activate", () => {
78
549
  if (BrowserWindow.getAllWindows().length === 0) {
79
- mainWindow = createMainWindow(__dirname);
550
+ createWindow();
80
551
  }
81
552
  });
@@ -2,7 +2,7 @@ import path from 'path';
2
2
  import { fileURLToPath } from 'url';
3
3
  import { dirname } from 'path';
4
4
  import fs from 'fs';
5
- import { WEB_UNSUPPORTED_PACKAGES } from '../test/electron/nonmodules.mjs';
5
+ import { WEB_UNSUPPORTED_PACKAGES } from '../../../electron/nonmodules.mjs';
6
6
 
7
7
  const __dirname = dirname(fileURLToPath(import.meta.url));
8
8
 
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "my-electron-app",
3
+ "version": "1.0.0",
4
+ "description": "A React Native app with Electron support using react-native-electron-platform",
5
+ "private": true,
6
+ "main": "node_modules/react-native-electron-platform/index.mjs",
7
+ "scripts": {
8
+ "android": "react-native run-android",
9
+ "ios": "react-native run-ios",
10
+ "lint": "eslint .",
11
+ "start": "react-native start",
12
+ "test": "jest",
13
+ "web": "webpack serve --config node_modules/react-native-electron-platform/webpack.config.mjs --mode development",
14
+ "web:build": "webpack --config node_modules/react-native-electron-platform/webpack.config.mjs --mode production",
15
+ "electron": "cross-env NODE_ENV=development concurrently \"npm run web\" \"wait-on http://localhost:5001 && electron .\"",
16
+ "electron:dev": "cross-env NODE_ENV=development electron . --enable-remote-module",
17
+ "electron:build": "npm run web:build && electron-builder --config node_modules/react-native-electron-platform/electron-builder.json",
18
+ "electron:build:nsis": "npm run web:build && electron-builder --config node_modules/react-native-electron-platform/electron-builder.json --win nsis --publish never",
19
+ "electron:build:msi": "npm run web:build && electron-builder --config node_modules/react-native-electron-platform/electron-builder.json --win msi --publish never"
20
+ },
21
+ "dependencies": {
22
+ "react": "^18.0.0",
23
+ "react-native": "^0.71.0",
24
+ "react-native-electron-platform": "^0.0.12",
25
+ "cross-env": "^7.0.3",
26
+ "concurrently": "^8.0.0",
27
+ "wait-on": "^7.0.0"
28
+ },
29
+ "devDependencies": {
30
+ "webpack": "^5.0.0",
31
+ "webpack-cli": "^5.0.0",
32
+ "webpack-dev-server": "^4.0.0",
33
+ "html-webpack-plugin": "^5.0.0",
34
+ "@babel/core": "^7.0.0",
35
+ "@babel/preset-react": "^7.0.0",
36
+ "@babel/preset-env": "^7.0.0",
37
+ "babel-loader": "^9.0.0",
38
+ "electron": "^25.0.0",
39
+ "electron-builder": "^24.0.0",
40
+ "eslint": "^8.0.0",
41
+ "jest": "^29.0.0"
42
+ }
43
+ }
package/test/test.mjs ADDED
@@ -0,0 +1,27 @@
1
+ import { getAllPackages, categorizePackages, isWebSupported, generateAlias, generateFallback } from '../src/webpackConfigHelper.mjs';
2
+ import { WEB_UNSUPPORTED_PACKAGES } from './nonmodules.mjs';
3
+
4
+ console.log('Testing webpackConfigHelper...');
5
+
6
+ // Test getAllPackages
7
+ const allPackages = getAllPackages();
8
+ console.log('All packages:', Object.keys(allPackages));
9
+
10
+ // Test categorizePackages
11
+ const { webSupported, webUnsupported } = categorizePackages();
12
+ console.log('Web supported:', webSupported.length);
13
+ console.log('Web unsupported:', webUnsupported.length);
14
+
15
+ // Test isWebSupported
16
+ console.log('react-native-web supported:', isWebSupported('react-native-web'));
17
+ console.log('react-native-fs supported:', isWebSupported('react-native-fs'));
18
+
19
+ // Test generateAlias
20
+ const alias = generateAlias();
21
+ console.log('Alias keys:', Object.keys(alias));
22
+
23
+ // Test generateFallback
24
+ const fallback = generateFallback();
25
+ console.log('Fallback keys:', Object.keys(fallback));
26
+
27
+ console.log('Tests completed.');