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