-
-
Notifications
You must be signed in to change notification settings - Fork 151
/
main.js
302 lines (262 loc) · 10.2 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
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
const {
app,
protocol,
BrowserWindow,
session,
ipcMain,
Menu
} = require("electron");
const {
default: installExtension,
REDUX_DEVTOOLS,
REACT_DEVELOPER_TOOLS
} = require("electron-devtools-installer");
const SecureElectronLicenseKeys = require("secure-electron-license-keys");
const Protocol = require("./protocol");
const MenuBuilder = require("./menu");
const i18nextBackend = require("i18next-electron-fs-backend");
const i18nextMainBackend = require("../localization/i18n.mainconfig");
const Store = require("secure-electron-store").default;
const ContextMenu = require("secure-electron-context-menu").default;
const path = require("path");
const fs = require("fs");
const crypto = require("crypto");
const isDev = process.env.NODE_ENV === "development";
const port = 40992; // Hardcoded; needs to match webpack.development.js and package.json
const selfHost = `http://localhost:${port}`;
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win;
let menuBuilder;
async function createWindow() {
// If you'd like to set up auto-updating for your app,
// I'd recommend looking at https://github.com/iffy/electron-updater-example
// to use the method most suitable for you.
// eg. autoUpdater.checkForUpdatesAndNotify();
if (!isDev) {
// Needs to happen before creating/loading the browser window;
// protocol is only used in prod
protocol.registerBufferProtocol(Protocol.scheme, Protocol.requestHandler); /* eng-disable PROTOCOL_HANDLER_JS_CHECK */
}
const store = new Store({
path: app.getPath("userData")
});
// Use saved config values for configuring your
// BrowserWindow, for instance.
// NOTE - this config is not passcode protected
// and stores plaintext values
//let savedConfig = store.mainInitialStore(fs);
// Create the browser window.
win = new BrowserWindow({
width: 800,
height: 600,
title: "Application is currently initializing...",
webPreferences: {
devTools: isDev,
nodeIntegration: false,
nodeIntegrationInWorker: false,
nodeIntegrationInSubFrames: false,
contextIsolation: true,
enableRemoteModule: false,
additionalArguments: [`--storePath=${store.sanitizePath(app.getPath("userData"))}`],
preload: path.join(__dirname, "preload.js"),
/* eng-disable PRELOAD_JS_CHECK */
disableBlinkFeatures: "Auxclick"
}
});
// Sets up main.js bindings for our i18next backend
i18nextBackend.mainBindings(ipcMain, win, fs);
// Sets up main.js bindings for our electron store;
// callback is optional and allows you to use store in main process
const callback = function (success, initialStore) {
console.log(`${!success ? "Un-s" : "S"}uccessfully retrieved store in main process.`);
console.log(initialStore); // {"key1": "value1", ... }
};
store.mainBindings(ipcMain, win, fs, callback);
// Sets up bindings for our custom context menu
ContextMenu.mainBindings(ipcMain, win, Menu, isDev, {
"loudAlertTemplate": [{
id: "loudAlert",
label: "AN ALERT!"
}],
"softAlertTemplate": [{
id: "softAlert",
label: "Soft alert"
}]
});
// Setup bindings for offline license verification
SecureElectronLicenseKeys.mainBindings(ipcMain, win, fs, crypto, {
root: process.cwd(),
version: app.getVersion()
});
// Load app
if (isDev) {
win.loadURL(selfHost);
} else {
win.loadURL(`${Protocol.scheme}://rse/index.html`);
}
win.webContents.on("did-finish-load", () => {
win.setTitle(`Getting started with secure-electron-template (v${app.getVersion()})`);
});
// Only do these things when in development
if (isDev) {
// Errors are thrown if the dev tools are opened
// before the DOM is ready
win.webContents.once("dom-ready", async () => {
await installExtension([REDUX_DEVTOOLS, REACT_DEVELOPER_TOOLS])
.then((name) => console.log(`Added Extension: ${name}`))
.catch((err) => console.log("An error occurred: ", err))
.finally(() => {
require("electron-debug")(); // https://github.com/sindresorhus/electron-debug
win.webContents.openDevTools();
});
});
}
// Emitted when the window is closed.
win.on("closed", () => {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
win = null;
});
// https://electronjs.org/docs/tutorial/security#4-handle-session-permission-requests-from-remote-content
const ses = session;
const partition = "default";
ses.fromPartition(partition) /* eng-disable PERMISSION_REQUEST_HANDLER_JS_CHECK */
.setPermissionRequestHandler((webContents, permission, permCallback) => {
const allowedPermissions = []; // Full list here: https://developer.chrome.com/extensions/declare_permissions#manifest
if (allowedPermissions.includes(permission)) {
permCallback(true); // Approve permission request
} else {
console.error(
`The application tried to request permission for '${permission}'. This permission was not whitelisted and has been blocked.`
);
permCallback(false); // Deny
}
});
// https://electronjs.org/docs/tutorial/security#1-only-load-secure-content;
// The below code can only run when a scheme and host are defined, I thought
// we could use this over _all_ urls
// ses.fromPartition(partition).webRequest.onBeforeRequest({urls:["http://localhost./*"]}, (listener) => {
// if (listener.url.indexOf("http://") >= 0) {
// listener.callback({
// cancel: true
// });
// }
// });
menuBuilder = MenuBuilder(win, app.name);
// Set up necessary bindings to update the menu items
// based on the current language selected
i18nextMainBackend.on("initialized", (loaded) => {
i18nextMainBackend.changeLanguage("en");
i18nextMainBackend.off("initialized"); // Remove listener to this event as it's not needed anymore
});
// When the i18n framework starts up, this event is called
// (presumably when the default language is initialized)
// BEFORE the "initialized" event is fired - this causes an
// error in the logs. To prevent said error, we only call the
// below code until AFTER the i18n framework has finished its
// "initialized" event.
i18nextMainBackend.on("languageChanged", (lng) => {
if (i18nextMainBackend.isInitialized){
menuBuilder.buildMenu(i18nextMainBackend);
}
});
}
// Needs to be called before app is ready;
// gives our scheme access to load relative files,
// as well as local storage, cookies, etc.
// https://electronjs.org/docs/api/protocol#protocolregisterschemesasprivilegedcustomschemes
protocol.registerSchemesAsPrivileged([{
scheme: Protocol.scheme,
privileges: {
standard: true,
secure: true
}
}]);
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on("ready", createWindow);
// Quit when all windows are closed.
app.on("window-all-closed", () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== "darwin") {
app.quit();
} else {
i18nextBackend.clearMainBindings(ipcMain);
ContextMenu.clearMainBindings(ipcMain);
SecureElectronLicenseKeys.clearMainBindings(ipcMain);
}
});
app.on("activate", () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (win === null) {
createWindow();
}
});
// https://electronjs.org/docs/tutorial/security#12-disable-or-limit-navigation
app.on("web-contents-created", (event, contents) => {
contents.on("will-navigate", (contentsEvent, navigationUrl) => {
/* eng-disable LIMIT_NAVIGATION_JS_CHECK */
const parsedUrl = new URL(navigationUrl);
const validOrigins = [selfHost];
// Log and prevent the app from navigating to a new page if that page's origin is not whitelisted
if (!validOrigins.includes(parsedUrl.origin)) {
console.error(
`The application tried to navigate to the following address: '${parsedUrl}'. This origin is not whitelisted and the attempt to navigate was blocked.`
);
contentsEvent.preventDefault();
}
});
contents.on("will-redirect", (contentsEvent, navigationUrl) => {
const parsedUrl = new URL(navigationUrl);
const validOrigins = [];
// Log and prevent the app from redirecting to a new page
if (!validOrigins.includes(parsedUrl.origin)) {
console.error(
`The application tried to redirect to the following address: '${navigationUrl}'. This attempt was blocked.`
);
contentsEvent.preventDefault();
}
});
// https://electronjs.org/docs/tutorial/security#11-verify-webview-options-before-creation
contents.on("will-attach-webview", (contentsEvent, webPreferences, params) => {
// Strip away preload scripts if unused or verify their location is legitimate
delete webPreferences.preload;
delete webPreferences.preloadURL;
// Disable Node.js integration
webPreferences.nodeIntegration = false;
});
// enable i18next translations in popup window
contents.on("did-create-window", (window) => {
i18nextBackend.mainBindings(ipcMain, window, fs);
});
// destroy bindings on popup window closed
contents.on("destroyed", () => {
i18nextBackend.clearMainBindings(ipcMain);
});
// https://electronjs.org/docs/tutorial/security#13-disable-or-limit-creation-of-new-windows
// This code replaces the old "new-window" event handling;
// https://github.com/electron/electron/pull/24517#issue-447670981
contents.setWindowOpenHandler(({
url
}) => {
const parsedUrl = new URL(url);
const validOrigins = [];
// Log and prevent opening up a new window
if (!validOrigins.includes(parsedUrl.origin)) {
console.error(
`The application tried to open a new window at the following address: '${url}'. This attempt was blocked.`
);
return {
action: "deny"
};
}
return {
action: "allow"
};
});
});