react-native-electron-platform 0.0.10 → 0.0.12

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.
@@ -55,13 +55,14 @@
55
55
  "!**/node_modules/react-native-*/*",
56
56
  "!**/node_modules/rn-fetch-blob/**",
57
57
  "!**/locales/**",
58
- "node_modules/react-native-electron-platform/src/**/*"
58
+ "node_modules/react-native-electron-platform/src/**/*",
59
+ "node_modules/react-native-electron-platform/index.mjs"
59
60
  ],
60
61
  "asarUnpack": [
61
62
  "**/*.node"
62
63
  ],
63
64
  "extraMetadata": {
64
- "main": "node_modules/react-native-electron-platform/src/main.mjs"
65
+ "main": "node_modules/react-native-electron-platform/index.mjs"
65
66
  },
66
67
  "win": {
67
68
  "target": [
package/index.mjs CHANGED
@@ -1 +1,84 @@
1
+ import { app, BrowserWindow, ipcMain } from "electron";
2
+ import { fileURLToPath } from 'url';
3
+ import { dirname } from 'path';
4
+ import packageJson from "../../package.json" with { type: 'json' };
5
+
6
+ // Import modules
7
+ import { shouldUseSafeMode, applySafeMode } from './src/modules/safeMode.js';
8
+ import { registerDeepLinkingHandlers, sendOpenURL } from './src/modules/deepLinking.js';
9
+ import { registerAllIpcHandlers } from './src/modules/ipcHandlers/index.js';
10
+ import { setupAutoUpdater } from './src/modules/autoUpdater.js';
11
+ import { createMainWindow } from './src/modules/windowManager.js';
12
+
13
+ const __dirname = dirname(fileURLToPath(import.meta.url));
14
+
15
+ // ======================================================
16
+ // SAFE MODE DETECTION (applies before ready)
17
+ // ======================================================
18
+ if (shouldUseSafeMode()) {
19
+ applySafeMode();
20
+ }
21
+
22
+ let mainWindow;
23
+
24
+ // ======================================================
25
+ // REGISTER IPC HANDLERS
26
+ // ======================================================
27
+ registerAllIpcHandlers();
28
+ registerDeepLinkingHandlers(sendOpenURL);
29
+
30
+ // ======================================================
31
+ // APP LIFECYCLE
32
+ // ======================================================
33
+
34
+ // Single instance lock
35
+ const gotTheLock = app.requestSingleInstanceLock();
36
+
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
+ }
50
+ }
51
+ });
52
+ }
53
+
54
+ app.whenReady().then(() => {
55
+ if (process.platform === "win32") {
56
+ app.setAppUserModelId(`com.${packageJson.name}.desktop`);
57
+ }
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
+ );
69
+ });
70
+
71
+ app.on("window-all-closed", () => {
72
+ if (process.platform !== "darwin") {
73
+ app.quit();
74
+ }
75
+ });
76
+
77
+ app.on("activate", () => {
78
+ if (BrowserWindow.getAllWindows().length === 0) {
79
+ mainWindow = createMainWindow(__dirname);
80
+ }
81
+ });
82
+
83
+ // Export webpack helper utilities
1
84
  export * from './src/webpackConfigHelper.mjs';
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "react-native-electron-platform",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
4
4
  "description": "A boilerplate and utilities for running React Native applications in Electron",
5
5
  "main": "index.mjs",
6
6
  "scripts": {
7
- "start": "electron src/main.mjs",
7
+ "start": "electron index.mjs",
8
8
  "build": "webpack --config webpack.config.mjs"
9
9
  },
10
10
  "keywords": [
@@ -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
+ }