react-native-electron-platform 0.0.12 → 0.0.14
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/.editorconfig +18 -0
- package/.gitattributes +2 -0
- package/.npmignore +20 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +1 -66
- package/electron-builder.json +2 -23
- package/package.json +5 -2
- package/src/icon.ico +0 -0
- package/src/main.mjs +552 -0
- package/test/test.mjs +27 -0
package/.editorconfig
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
root = true
|
|
2
|
+
|
|
3
|
+
[*]
|
|
4
|
+
charset = utf-8
|
|
5
|
+
end_of_line = lf
|
|
6
|
+
insert_final_newline = true
|
|
7
|
+
trim_trailing_whitespace = true
|
|
8
|
+
|
|
9
|
+
[*.{js,jsx,ts,tsx,mjs}]
|
|
10
|
+
indent_style = space
|
|
11
|
+
indent_size = 2
|
|
12
|
+
|
|
13
|
+
[*.json]
|
|
14
|
+
indent_style = space
|
|
15
|
+
indent_size = 2
|
|
16
|
+
|
|
17
|
+
[*.md]
|
|
18
|
+
trim_trailing_whitespace = false
|
package/.gitattributes
ADDED
package/.npmignore
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Exclude everything by default
|
|
2
|
+
*
|
|
3
|
+
|
|
4
|
+
# Include only necessary files
|
|
5
|
+
!src/
|
|
6
|
+
!README.md
|
|
7
|
+
!LICENSE
|
|
8
|
+
!package.json
|
|
9
|
+
|
|
10
|
+
# Exclude test files if not needed
|
|
11
|
+
src/test/
|
|
12
|
+
test/
|
|
13
|
+
|
|
14
|
+
# Exclude build configs if not needed
|
|
15
|
+
webpack.config.mjs
|
|
16
|
+
electron-builder.json
|
|
17
|
+
|
|
18
|
+
# Exclude development files
|
|
19
|
+
.gitignore
|
|
20
|
+
.npmignore
|
package/CONTRIBUTING.md
CHANGED
package/README.md
CHANGED
|
@@ -113,7 +113,7 @@ Read the **[Complete Setup Guide](SETUP.md)** for step-by-step instructions on:
|
|
|
113
113
|
|
|
114
114
|
## 📖 How to Use
|
|
115
115
|
|
|
116
|
-
###
|
|
116
|
+
### As an NPM Module (Recommended) ⭐
|
|
117
117
|
|
|
118
118
|
The easiest and recommended way - install as a package and use pre-configured setup:
|
|
119
119
|
|
|
@@ -201,76 +201,11 @@ if (root) {
|
|
|
201
201
|
}
|
|
202
202
|
```
|
|
203
203
|
|
|
204
|
-
Create `index.html`:
|
|
205
|
-
```html
|
|
206
|
-
<!DOCTYPE html>
|
|
207
|
-
<html lang="en">
|
|
208
|
-
<head>
|
|
209
|
-
<meta charset="UTF-8" />
|
|
210
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
211
|
-
<title>My App</title>
|
|
212
|
-
<style>
|
|
213
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
214
|
-
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; }
|
|
215
|
-
#root { width: 100%; height: 100vh; }
|
|
216
|
-
</style>
|
|
217
|
-
</head>
|
|
218
|
-
<body>
|
|
219
|
-
<div id="root"></div>
|
|
220
|
-
</body>
|
|
221
|
-
</html>
|
|
222
|
-
```
|
|
223
|
-
|
|
224
204
|
**Run Your App:**
|
|
225
205
|
```bash
|
|
226
206
|
npm run electron
|
|
227
207
|
```
|
|
228
208
|
|
|
229
|
-
### Option 2: As a Boilerplate (Advanced)
|
|
230
|
-
|
|
231
|
-
For custom use cases, copy specific files to your Electron project:
|
|
232
|
-
|
|
233
|
-
```bash
|
|
234
|
-
# Copy Electron entry point
|
|
235
|
-
cp node_modules/react-native-electron-platform/index.mjs ./
|
|
236
|
-
|
|
237
|
-
# Copy webpack config helper
|
|
238
|
-
cp node_modules/react-native-electron-platform/src/webpackConfigHelper.mjs ./src/
|
|
239
|
-
|
|
240
|
-
# Copy preload script (for security)
|
|
241
|
-
cp node_modules/react-native-electron-platform/src/preload.mjs ./src/
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
### Option 3: As a Library (Advanced)
|
|
245
|
-
|
|
246
|
-
Use utility functions directly in your project:
|
|
247
|
-
|
|
248
|
-
```javascript
|
|
249
|
-
// Analyze dependencies
|
|
250
|
-
import webpackConfigHelper from 'react-native-electron-platform/src/webpackConfigHelper.mjs';
|
|
251
|
-
const packages = webpackConfigHelper.categorizePackages();
|
|
252
|
-
|
|
253
|
-
// Use modules
|
|
254
|
-
import { windowManager, networkService, autoUpdater } from 'react-native-electron-platform/src/modules/';
|
|
255
|
-
|
|
256
|
-
// Create window
|
|
257
|
-
windowManager.createWindow({ width: 1024, height: 768 });
|
|
258
|
-
|
|
259
|
-
// Make secure network requests
|
|
260
|
-
networkService.fetch('https://api.example.com/data');
|
|
261
|
-
|
|
262
|
-
// Check for updates
|
|
263
|
-
autoUpdater.checkForUpdates();
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
## Example Project
|
|
267
|
-
|
|
268
|
-
See the [example-project](example-project/) folder for a complete, working example with:
|
|
269
|
-
- Pre-configured `package.json`
|
|
270
|
-
- Sample App component
|
|
271
|
-
- Electron configuration
|
|
272
|
-
- HTML entry point
|
|
273
|
-
|
|
274
209
|
## 🔧 Available NPM Scripts
|
|
275
210
|
|
|
276
211
|
All scripts are configured to work with react-native-electron-platform.
|
package/electron-builder.json
CHANGED
|
@@ -27,34 +27,13 @@
|
|
|
27
27
|
"package.json",
|
|
28
28
|
"!**/*.map",
|
|
29
29
|
"!**/*.md",
|
|
30
|
-
"!**/*.ts",
|
|
31
|
-
"!**/*.tsx",
|
|
32
|
-
"!**/*.log",
|
|
33
30
|
"!**/test/**",
|
|
34
31
|
"!**/__tests__/**",
|
|
35
|
-
"!**/tests/**",
|
|
36
32
|
"!**/docs/**",
|
|
37
|
-
"!**/
|
|
38
|
-
"!**/examples/**",
|
|
39
|
-
"!**/.vscode/**",
|
|
40
|
-
"!**/.github/**",
|
|
41
|
-
"!**/node_modules/.bin",
|
|
42
|
-
"!**/node_modules/*/{CHANGELOG.md,README.md,README,readme.md}",
|
|
43
|
-
"!**/node_modules/*/{test,__tests__,tests,example,examples}",
|
|
44
|
-
"!**/node_modules/**/*.d.ts",
|
|
45
|
-
"!**/android/**",
|
|
46
|
-
"!**/ios/**",
|
|
47
|
-
"!**/build/**",
|
|
48
|
-
"!**/*.gradle",
|
|
49
|
-
"!**/*.java",
|
|
50
|
-
"!**/*.kt",
|
|
51
|
-
"!**/node_modules/**/android/**",
|
|
52
|
-
"!**/node_modules/**/ios/**",
|
|
53
|
-
"!**/node_modules/**/build/**",
|
|
33
|
+
"!**/locales/**",
|
|
54
34
|
"!**/node_modules/react-native/**",
|
|
35
|
+
"!**/node_modules/electron/**",
|
|
55
36
|
"!**/node_modules/react-native-*/*",
|
|
56
|
-
"!**/node_modules/rn-fetch-blob/**",
|
|
57
|
-
"!**/locales/**",
|
|
58
37
|
"node_modules/react-native-electron-platform/src/**/*",
|
|
59
38
|
"node_modules/react-native-electron-platform/index.mjs"
|
|
60
39
|
],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-electron-platform",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14",
|
|
4
4
|
"description": "A boilerplate and utilities for running React Native applications in Electron",
|
|
5
5
|
"main": "index.mjs",
|
|
6
6
|
"scripts": {
|
|
@@ -49,6 +49,9 @@
|
|
|
49
49
|
"CHANGELOG.md",
|
|
50
50
|
"CODE_OF_CONDUCT.md",
|
|
51
51
|
"CONTRIBUTING.md",
|
|
52
|
-
"electron-builder.json"
|
|
52
|
+
"electron-builder.json",
|
|
53
|
+
".editorconfig",
|
|
54
|
+
".gitattributes",
|
|
55
|
+
".npmignore"
|
|
53
56
|
]
|
|
54
57
|
}
|
package/src/icon.ico
ADDED
|
Binary file
|
package/src/main.mjs
ADDED
|
@@ -0,0 +1,552 @@
|
|
|
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";
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
import { dirname } from 'path';
|
|
10
|
+
import packageJson from "../../../package.json" with { type: 'json' };
|
|
11
|
+
|
|
12
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
|
|
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
|
|
37
|
+
if (shouldUseSafeMode()) {
|
|
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");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let mainWindow;
|
|
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
|
+
//
|
|
255
|
+
// ======================================================
|
|
256
|
+
// NETWORK SERVICE
|
|
257
|
+
// ======================================================
|
|
258
|
+
//
|
|
259
|
+
|
|
260
|
+
async function NetworkServiceCall(method, url, params = {}, headers = {}) {
|
|
261
|
+
try {
|
|
262
|
+
const upperMethod = method.toUpperCase();
|
|
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
|
+
//
|
|
313
|
+
// ======================================================
|
|
314
|
+
// PDF / FILE IPC
|
|
315
|
+
// ======================================================
|
|
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
|
+
|
|
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
|
+
}
|
|
516
|
+
}
|
|
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
|
+
|
|
534
|
+
app.whenReady().then(() => {
|
|
535
|
+
if (process.platform === "win32") {
|
|
536
|
+
app.setAppUserModelId(`com.${packageJson.name}.desktop`);
|
|
537
|
+
}
|
|
538
|
+
createWindow();
|
|
539
|
+
setupAutoUpdater();
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
app.on("window-all-closed", () => {
|
|
543
|
+
if (process.platform !== "darwin") {
|
|
544
|
+
app.quit();
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
app.on("activate", () => {
|
|
549
|
+
if (BrowserWindow.getAllWindows().length === 0) {
|
|
550
|
+
createWindow();
|
|
551
|
+
}
|
|
552
|
+
});
|
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.');
|