-
-
Notifications
You must be signed in to change notification settings - Fork 13
/
main.js
279 lines (219 loc) · 8.3 KB
/
main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
const { app, BrowserWindow, dialog, ipcMain, shell, systemPreferences } = require('electron');
const electronPrompt = require('electron-prompt');
const { spawn } = require('child_process');
const fs = require('fs');
const path = require('node:path');
// remember main window
var mainWindow = null;
// remember child process for exe so we can kill it when app exits
var exeChildProcess = null;
// allow fetching app version via ipc
ipcMain.handle('app-version', () => {
return app.getVersion();
});
// add support for showing an alert window via ipc
ipcMain.handle('alert', async(event, message) => {
return await dialog.showMessageBox(mainWindow, {
message: message,
});
});
// add support for showing a prompt window via ipc
ipcMain.handle('prompt', async(event, message) => {
return await electronPrompt({
title: message,
label: '',
value: '',
type: 'input',
inputAttrs: {
type: 'text',
},
});
});
// allow relaunching app via ipc
ipcMain.handle('relaunch', () => {
app.relaunch();
app.exit();
});
// allow showing a file path in os file manager
ipcMain.handle('showPathInFolder', (event, path) => {
shell.showItemInFolder(path);
});
function log(message) {
// make sure main window exists
if(!mainWindow){
return;
}
// make sure window is not destroyed
if(mainWindow.isDestroyed()){
return;
}
// log to electron console
console.log(message);
// log to web console
mainWindow.webContents.send('log', message);
}
function getDefaultStorageDir() {
// if we are running a windows portable exe, we want to use .reticulum-meshchat in the portable exe dir
// e.g if we launch "E:\Some\Path\MeshChat.exe" we want to use "E:\Some\Path\.reticulum-meshchat"
const portableExecutableDir = process.env.PORTABLE_EXECUTABLE_DIR;
if(process.platform === "win32" && portableExecutableDir != null){
return path.join(portableExecutableDir, '.reticulum-meshchat');
}
// otherwise, we will fall back to putting the storage dir in the users home directory
// e.g: ~/.reticulum-meshchat
return path.join(app.getPath('home'), '.reticulum-meshchat');
}
function getDefaultReticulumConfigDir() {
// if we are running a windows portable exe, we want to use .reticulum in the portable exe dir
// e.g if we launch "E:\Some\Path\MeshChat.exe" we want to use "E:\Some\Path\.reticulum"
const portableExecutableDir = process.env.PORTABLE_EXECUTABLE_DIR;
if(process.platform === "win32" && portableExecutableDir != null){
return path.join(portableExecutableDir, '.reticulum');
}
// otherwise, we will fall back to using the .reticulum folder in the users home directory
// e.g: ~/.reticulum
return path.join(app.getPath('home'), '.reticulum');
}
app.whenReady().then(async () => {
// create browser window
mainWindow = new BrowserWindow({
width: 1500,
height: 800,
webPreferences: {
// used to inject logging over ipc
preload: path.join(__dirname, 'preload.js'),
},
});
// open external links in default web browser instead of electron
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
// we want to open call.html in a new electron window
// but all other target="_blank" links should open in the system web browser
// we don't want /rnode-flasher/index.html to open in electron, otherwise user can't select usb devices...
if(url.startsWith("http://localhost") && url.includes("/call.html")){
return {
action: "allow",
};
}
// fallback to opening any other url in external browser
shell.openExternal(url);
return {
action: "deny",
};
});
// navigate to loading page
await mainWindow.loadFile(path.join(__dirname, 'loading.html'));
// find path to python/cxfreeze reticulum meshchat executable
const exeName = process.platform === "win32" ? "ReticulumMeshChat.exe" : "ReticulumMeshChat";
var exe = path.join(__dirname, `build/exe/${exeName}`);
// if dist exe doesn't exist, check local build
if(!fs.existsSync(exe)){
exe = path.join(__dirname, '..', `build/exe/${exeName}`);
}
// ask mac users for microphone access for audio calls to work
if(process.platform === "darwin"){
await systemPreferences.askForMediaAccess('microphone');
}
try {
// get arguments passed to application, and remove the provided application path
const userProvidedArguments = process.argv.slice(1);
// arguments we always want to pass in
const requiredArguments = [
'--headless', // reticulum meshchat usually launches default web browser, we don't want this when using electron
'--port', '9337', // FIXME: let system pick a random unused port?
// '--test-exception-message', 'Test Exception Message', // uncomment to test the crash dialog
];
// if user didn't provide reticulum config dir, we should provide it
if(!userProvidedArguments.includes("--reticulum-config-dir")){
requiredArguments.push("--reticulum-config-dir", getDefaultReticulumConfigDir());
}
// if user didn't provide storage dir, we should provide it
if(!userProvidedArguments.includes("--storage-dir")){
requiredArguments.push("--storage-dir", getDefaultStorageDir());
}
// spawn executable
exeChildProcess = await spawn(exe, [
...requiredArguments, // always provide required arguments
...userProvidedArguments, // also include any user provided arguments
]);
// log stdout
var stdoutLines = [];
exeChildProcess.stdout.setEncoding('utf8');
exeChildProcess.stdout.on('data', function(data) {
// log
log(data.toString());
// keep track of last 10 stdout lines
stdoutLines.push(data.toString());
if(stdoutLines.length > 10){
stdoutLines.shift();
}
});
// log stderr
var stderrLines = [];
exeChildProcess.stderr.setEncoding('utf8');
exeChildProcess.stderr.on('data', function(data) {
// log
log(data.toString());
// keep track of last 10 stderr lines
stderrLines.push(data.toString());
if(stderrLines.length > 10){
stderrLines.shift();
}
});
// log errors
exeChildProcess.on('error', function(error) {
log(error);
});
// quit electron app if exe dies
exeChildProcess.on('exit', async function(code) {
// if no exit code provided, we wanted exit to happen, so do nothing
if(code == null){
return;
}
// tell user that Visual C++ redistributable needs to be installed on Windows
if(code === 3221225781 && process.platform === "win32"){
await dialog.showMessageBox(mainWindow, {
message: "Microsoft Visual C++ redistributable must be installed to run this application.",
});
app.quit();
return;
}
// show crash log
const stdout = stdoutLines.join("");
const stderr = stderrLines.join("");
await dialog.showMessageBox(mainWindow, {
message: [
"MeshChat Crashed!",
"",
`Exit Code: ${code}`,
"",
`----- stdout -----`,
"",
stdout,
`----- stderr -----`,
"",
stderr,
].join("\n"),
});
// quit after dismissing error dialog
app.quit();
});
} catch(e) {
log(e);
}
});
function quit() {
// kill python process
if(exeChildProcess){
exeChildProcess.kill("SIGKILL");
}
// quit electron app
app.quit();
}
// quit electron if all windows are closed
app.on('window-all-closed', () => {
quit();
});
// make sure child process is killed if app is quiting
app.on('quit', () => {
quit();
});