react-native-electron-platform 0.0.1
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/LICENSE +21 -0
- package/README.md +77 -0
- package/index.mjs +1 -0
- package/package.json +48 -0
- package/src/icon.ico +0 -0
- package/src/index.html +173 -0
- package/src/main.mjs +552 -0
- package/src/preload.mjs +61 -0
- package/src/webpackConfigHelper.mjs +94 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 JESCON TECHNOLOGIES PVT LTD
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# react-native-electron-platform
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/js/react-native-electron-platform)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://github.com/dpraful/react-native-electron-platform/actions)
|
|
6
|
+
|
|
7
|
+
A boilerplate and utility library for running React Native applications in Electron, supporting both desktop and web platforms.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- Electron main process setup with auto-updater
|
|
12
|
+
- Preload script for secure IPC communication
|
|
13
|
+
- Webpack configuration helper for React Native web builds
|
|
14
|
+
- Network service integration
|
|
15
|
+
- PDF generation and preview
|
|
16
|
+
- File selection dialogs
|
|
17
|
+
- HTML preview functionality
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install react-native-electron-platform
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
### As a Boilerplate
|
|
28
|
+
|
|
29
|
+
1. Copy the `src/main.mjs` and `src/preload.js` to your Electron project.
|
|
30
|
+
2. Use the `src/webpackConfigHelper.mjs` to configure your webpack build for React Native web.
|
|
31
|
+
|
|
32
|
+
### As a Library
|
|
33
|
+
|
|
34
|
+
```javascript
|
|
35
|
+
import { categorizePackages, isWebSupported } from 'react-native-electron-platform';
|
|
36
|
+
|
|
37
|
+
const packages = categorizePackages();
|
|
38
|
+
// Use the categorized packages for your build configuration
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Running the Example
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm start
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
This will start the Electron app with the included example.
|
|
48
|
+
|
|
49
|
+
## API
|
|
50
|
+
|
|
51
|
+
### WebpackConfigHelper
|
|
52
|
+
|
|
53
|
+
- `getAllPackages()`: Get all dependencies from package.json
|
|
54
|
+
- `categorizePackages()`: Separate packages into web-supported and unsupported
|
|
55
|
+
- `isWebSupported(packageName)`: Check if a package supports web platform
|
|
56
|
+
|
|
57
|
+
## Building
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npm run build
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Contributing
|
|
64
|
+
|
|
65
|
+
Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests.
|
|
66
|
+
|
|
67
|
+
## Code of Conduct
|
|
68
|
+
|
|
69
|
+
Please read our [Code of Conduct](CODE_OF_CONDUCT.md) to understand our community guidelines.
|
|
70
|
+
|
|
71
|
+
## Changelog
|
|
72
|
+
|
|
73
|
+
See [CHANGELOG.md](CHANGELOG.md) for a list of changes and version history.
|
|
74
|
+
|
|
75
|
+
## License
|
|
76
|
+
|
|
77
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
package/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './src/webpackConfigHelper.mjs';
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-native-electron-platform",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "A boilerplate and utilities for running React Native applications in Electron",
|
|
5
|
+
"main": "index.mjs",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "electron src/main.mjs",
|
|
8
|
+
"build": "webpack --config webpack.config.mjs",
|
|
9
|
+
"test": "echo \"No tests specified\" && exit 0"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"react-native",
|
|
13
|
+
"electron",
|
|
14
|
+
"desktop",
|
|
15
|
+
"web",
|
|
16
|
+
"boilerplate"
|
|
17
|
+
],
|
|
18
|
+
"author": "JESCON TECHNOLOGIES PVT LTD",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/dpraful/react-native-electron-platform.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/dpraful/react-native-electron-platform/issues"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/dpraful/react-native-electron-platform#readme",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"electron": "^25.0.0",
|
|
30
|
+
"electron-updater": "^6.0.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"webpack": "^5.0.0",
|
|
34
|
+
"html-webpack-plugin": "^5.0.0",
|
|
35
|
+
"webpack-cli": "^5.0.0"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"react-native": ">=0.60.0"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=14.0.0"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"src/",
|
|
45
|
+
"README.md",
|
|
46
|
+
"LICENSE"
|
|
47
|
+
]
|
|
48
|
+
}
|
package/src/icon.ico
ADDED
|
Binary file
|
package/src/index.html
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
7
|
+
<title><%= title %></title>
|
|
8
|
+
<style>
|
|
9
|
+
html,
|
|
10
|
+
body {
|
|
11
|
+
width: 100%;
|
|
12
|
+
height: 100%;
|
|
13
|
+
margin: 0;
|
|
14
|
+
padding: 0;
|
|
15
|
+
overflow: hidden;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
#root {
|
|
19
|
+
width: 100vw;
|
|
20
|
+
height: 100vh;
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
#root>div {
|
|
26
|
+
flex: 1;
|
|
27
|
+
display: flex;
|
|
28
|
+
flex-direction: column;
|
|
29
|
+
min-height: 0;
|
|
30
|
+
min-width: 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@font-face {
|
|
34
|
+
font-family: 'MaterialCommunityIcons';
|
|
35
|
+
src: url('fonts/MaterialCommunityIcons.ttf') format('truetype');
|
|
36
|
+
font-weight: normal;
|
|
37
|
+
font-style: normal;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@font-face {
|
|
41
|
+
font-family: 'MaterialIcons';
|
|
42
|
+
src: url('fonts/MaterialIcons.ttf') format('truetype');
|
|
43
|
+
font-weight: normal;
|
|
44
|
+
font-style: normal;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@font-face {
|
|
48
|
+
font-family: 'SimpleLineIcons';
|
|
49
|
+
src: url('fonts/SimpleLineIcons.ttf') format('truetype');
|
|
50
|
+
font-weight: normal;
|
|
51
|
+
font-style: normal;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@font-face {
|
|
55
|
+
font-family: 'FontAwesome';
|
|
56
|
+
src: url('fonts/FontAwesome.ttf') format('truetype');
|
|
57
|
+
font-weight: normal;
|
|
58
|
+
font-style: normal;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@font-face {
|
|
62
|
+
font-family: 'Foundation';
|
|
63
|
+
src: url('fonts/Foundation.ttf') format('truetype');
|
|
64
|
+
font-weight: normal;
|
|
65
|
+
font-style: normal;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@font-face {
|
|
69
|
+
font-family: 'EvilIcons';
|
|
70
|
+
src: url('fonts/EvilIcons.ttf') format('truetype');
|
|
71
|
+
font-weight: normal;
|
|
72
|
+
font-style: normal;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@font-face {
|
|
76
|
+
font-family: 'Octicons';
|
|
77
|
+
src: url('fonts/Octicons.ttf') format('truetype');
|
|
78
|
+
font-weight: normal;
|
|
79
|
+
font-style: normal;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@font-face {
|
|
83
|
+
font-family: 'Ionicons';
|
|
84
|
+
src: url('fonts/Ionicons.ttf') format('truetype');
|
|
85
|
+
font-weight: normal;
|
|
86
|
+
font-style: normal;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@font-face {
|
|
90
|
+
font-family: 'Feather';
|
|
91
|
+
src: url('fonts/Feather.ttf') format('truetype');
|
|
92
|
+
font-weight: normal;
|
|
93
|
+
font-style: normal;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@font-face {
|
|
97
|
+
font-family: 'Entypo';
|
|
98
|
+
src: url('fonts/Entypo.ttf') format('truetype');
|
|
99
|
+
font-weight: normal;
|
|
100
|
+
font-style: normal;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@font-face {
|
|
104
|
+
font-family: 'Zocial';
|
|
105
|
+
src: url('fonts/Zocial.ttf') format('truetype');
|
|
106
|
+
font-weight: normal;
|
|
107
|
+
font-style: normal;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@font-face {
|
|
111
|
+
font-family: 'FontAwesome5_Regular';
|
|
112
|
+
src: url('fonts/FontAwesome5_Regular.ttf') format('truetype');
|
|
113
|
+
font-weight: normal;
|
|
114
|
+
font-style: normal;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@font-face {
|
|
118
|
+
font-family: 'FontAwesome5_Solid';
|
|
119
|
+
src: url('fonts/FontAwesome5_Solid.ttf') format('truetype');
|
|
120
|
+
font-weight: bold;
|
|
121
|
+
font-style: normal;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@font-face {
|
|
125
|
+
font-family: 'FontAwesome5_Brands';
|
|
126
|
+
src: url('fonts/FontAwesome5_Brands.ttf') format('truetype');
|
|
127
|
+
font-weight: normal;
|
|
128
|
+
font-style: normal;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@font-face {
|
|
132
|
+
font-family: 'AntDesign';
|
|
133
|
+
src: url('fonts/AntDesign.ttf') format('truetype');
|
|
134
|
+
font-weight: normal;
|
|
135
|
+
font-style: normal;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@font-face {
|
|
139
|
+
font-family: 'FontAwesome6_Brands';
|
|
140
|
+
src: url('fonts/FontAwesome6_Brands.ttf') format('truetype');
|
|
141
|
+
font-weight: normal;
|
|
142
|
+
font-style: normal;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
@font-face {
|
|
146
|
+
font-family: 'FontAwesome6_Regular';
|
|
147
|
+
src: url('fonts/FontAwesome6_Regular.ttf') format('truetype');
|
|
148
|
+
font-weight: normal;
|
|
149
|
+
font-style: normal;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
@font-face {
|
|
153
|
+
font-family: 'FontAwesome6_Solid';
|
|
154
|
+
src: url('fonts/FontAwesome6_Solid.ttf') format('truetype');
|
|
155
|
+
font-weight: bold;
|
|
156
|
+
font-style: normal;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
@font-face {
|
|
160
|
+
font-family: 'Fontisto';
|
|
161
|
+
src: url('fonts/Fontisto.ttf') format('truetype');
|
|
162
|
+
font-weight: normal;
|
|
163
|
+
font-style: normal;
|
|
164
|
+
}
|
|
165
|
+
</style>
|
|
166
|
+
</head>
|
|
167
|
+
|
|
168
|
+
<body>
|
|
169
|
+
<div id="root"></div>
|
|
170
|
+
<script src="bundle.js"></script>
|
|
171
|
+
</body>
|
|
172
|
+
|
|
173
|
+
</html>
|
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/src/preload.mjs
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { contextBridge, ipcRenderer } from "electron";
|
|
2
|
+
|
|
3
|
+
contextBridge.exposeInMainWorld("electronAPI", {
|
|
4
|
+
// Network
|
|
5
|
+
networkCall: (method, url, params = {}, headers = {}) =>
|
|
6
|
+
ipcRenderer.invoke("network-call", { method, url, params, headers }),
|
|
7
|
+
|
|
8
|
+
// PDF Operations
|
|
9
|
+
savePDF: (html) =>
|
|
10
|
+
ipcRenderer.invoke("save-pdf", html),
|
|
11
|
+
|
|
12
|
+
postPdfPreview: (payload) =>
|
|
13
|
+
ipcRenderer.invoke("post-pdf-preview", payload),
|
|
14
|
+
|
|
15
|
+
openPdfPreview: (pdfUrl) =>
|
|
16
|
+
ipcRenderer.invoke("open-pdf-preview", pdfUrl),
|
|
17
|
+
|
|
18
|
+
// File Operations
|
|
19
|
+
selectFile: () => ipcRenderer.invoke("select-file"),
|
|
20
|
+
|
|
21
|
+
// HTML/PDF Preview
|
|
22
|
+
previewHTML: (htmlContent) =>
|
|
23
|
+
ipcRenderer.invoke("preview-html", htmlContent),
|
|
24
|
+
|
|
25
|
+
htmlToPdfPreview: (htmlContent) =>
|
|
26
|
+
ipcRenderer.invoke("html-to-pdf-preview", htmlContent),
|
|
27
|
+
|
|
28
|
+
// ======================================================
|
|
29
|
+
// AUTO-UPDATER APIs (NEW)
|
|
30
|
+
// ======================================================
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Listen for update status events from main process
|
|
34
|
+
* @param {Function} callback - Function to call when update status changes
|
|
35
|
+
* @returns {Function} - Cleanup function to remove listener
|
|
36
|
+
*/
|
|
37
|
+
onUpdateStatus: (callback) => {
|
|
38
|
+
// Create the listener
|
|
39
|
+
const listener = (event, data) => callback(data);
|
|
40
|
+
|
|
41
|
+
// Add the listener
|
|
42
|
+
ipcRenderer.on('update-status', listener);
|
|
43
|
+
|
|
44
|
+
// Return cleanup function
|
|
45
|
+
return () => {
|
|
46
|
+
ipcRenderer.removeListener('update-status', listener);
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Manually check for updates
|
|
52
|
+
* @returns {Promise<Object>} - Status of update check
|
|
53
|
+
*/
|
|
54
|
+
checkForUpdates: () => ipcRenderer.invoke("check-for-updates"),
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get the current app version
|
|
58
|
+
* @returns {Promise<string>} - Current version
|
|
59
|
+
*/
|
|
60
|
+
getAppVersion: () => ipcRenderer.invoke("get-app-version")
|
|
61
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { dirname } from 'path';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import { WEB_UNSUPPORTED_PACKAGES } from '../../../electron/nonmodules.mjs';
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Get all dependencies from package.json
|
|
11
|
+
* @returns {Object} All dependencies and devDependencies
|
|
12
|
+
*/
|
|
13
|
+
export function getAllPackages() {
|
|
14
|
+
const packageJsonPath = path.join(__dirname, '../../../package.json');
|
|
15
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
16
|
+
return {
|
|
17
|
+
...packageJson.dependencies,
|
|
18
|
+
...packageJson.devDependencies,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Separate packages into web-supported and unsupported
|
|
24
|
+
* @returns {Object} { webSupported: [], webUnsupported: [] }
|
|
25
|
+
*/
|
|
26
|
+
export function categorizePackages() {
|
|
27
|
+
const allPackages = getAllPackages();
|
|
28
|
+
const webSupported = [];
|
|
29
|
+
const webUnsupported = [];
|
|
30
|
+
|
|
31
|
+
Object.keys(allPackages).forEach(pkg => {
|
|
32
|
+
if (WEB_UNSUPPORTED_PACKAGES.includes(pkg)) {
|
|
33
|
+
webUnsupported.push(pkg);
|
|
34
|
+
} else {
|
|
35
|
+
webSupported.push(pkg);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return { webSupported, webUnsupported };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check if a specific package supports web
|
|
44
|
+
* @param {string} packageName - Package name to check
|
|
45
|
+
* @returns {boolean} True if package supports web, false otherwise
|
|
46
|
+
*/
|
|
47
|
+
export function isWebSupported(packageName) {
|
|
48
|
+
return !WEB_UNSUPPORTED_PACKAGES.includes(packageName);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Generate webpack alias configuration based on package.json dependencies
|
|
53
|
+
* @returns {Object} Alias configuration object
|
|
54
|
+
*/
|
|
55
|
+
export function generateAlias() {
|
|
56
|
+
const allDeps = getAllPackages();
|
|
57
|
+
|
|
58
|
+
const alias = {
|
|
59
|
+
'react-native$': 'react-native-web',
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Set packages to false if they don't support web
|
|
63
|
+
Object.keys(allDeps).forEach(pkg => {
|
|
64
|
+
if (!isWebSupported(pkg)) {
|
|
65
|
+
alias[pkg] = false;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return alias;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Generate webpack fallback configuration based on package.json dependencies
|
|
74
|
+
* @returns {Object} Fallback configuration object
|
|
75
|
+
*/
|
|
76
|
+
export function generateFallback() {
|
|
77
|
+
const { webUnsupported } = categorizePackages();
|
|
78
|
+
|
|
79
|
+
const fallback = {
|
|
80
|
+
fs: false,
|
|
81
|
+
path: false,
|
|
82
|
+
os: false,
|
|
83
|
+
AsyncStorage: false,
|
|
84
|
+
NativeModules: false,
|
|
85
|
+
Platform: false,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Add all web-unsupported packages to fallback
|
|
89
|
+
webUnsupported.forEach(pkg => {
|
|
90
|
+
fallback[pkg] = false;
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return fallback;
|
|
94
|
+
}
|