Skip to content

Commit a6c61d2

Browse files
authored
Merge pull request #30 from oslabs-beta/staging-faast
Upgrade Electron by Tyler
2 parents 0810da0 + 68efef1 commit a6c61d2

File tree

84 files changed

+1448
-230
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+1448
-230
lines changed

.babelrc

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
{
2-
"presets": [
2+
"plugins": [
33
[
4-
"env",
4+
"module-resolver",
55
{
6-
"modules": false
6+
"cwd": "babelrc",
7+
"alias": {
8+
"Components": "./app/src/components",
9+
"Containers": "./app/src/containers"
10+
}
711
}
8-
],
9-
"react",
10-
"stage-0"
12+
]
1113
],
12-
"plugins": ["transform-es2015-modules-commonjs"],
13-
"env": {
14-
"test": {
15-
"plugins": ["transform-es2015-modules-commonjs"]
16-
}
17-
}
14+
// presets are a set of of plug-ins
15+
"presets": [
16+
"@babel/preset-typescript",
17+
"@babel/preset-env",
18+
"@babel/preset-react"
19+
]
1820
}

app/electron/main.js

Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
const {
2+
app,
3+
protocol,
4+
dialog,
5+
BrowserWindow,
6+
session,
7+
ipcMain,
8+
Menu
9+
} = require('electron');
10+
// The splash screen is what appears while the app is loading
11+
const { initSplashScreen, OfficeTemplate } = require('electron-splashscreen');
12+
const { resolve } = require('app-root-path');
13+
const {
14+
default: installExtension,
15+
REACT_DEVELOPER_TOOLS
16+
} = require('electron-devtools-installer');
17+
const debug = require('electron-debug');
18+
19+
const Protocol = require('./protocol');
20+
// menu from another file to modularize the code
21+
const MenuBuilder = require('./menu');
22+
23+
const path = require('path');
24+
// const fs = require('fs');
25+
26+
console.log('NODE ENV is ', process.env.NODE_ENV);
27+
const isDev = process.env.NODE_ENV === 'development';
28+
const port = 8080;
29+
const selfHost = `http://localhost:${port}`;
30+
31+
// Keep a global reference of the window object, if you don't, the window will
32+
// be closed automatically when the JavaScript object is garbage collected.
33+
let win;
34+
let menuBuilder;
35+
36+
async function createWindow() {
37+
if (isDev) {
38+
await installExtension([REACT_DEVELOPER_TOOLS])
39+
.then(name => console.log(`Added Extension: ${name}`))
40+
.catch(err => console.log('An error occurred: ', err));
41+
} else {
42+
// Needs to happen before creating/loading the browser window;
43+
// not necessarily instead of extensions, just using this code block
44+
// so I don't have to write another 'if' statement
45+
protocol.registerBufferProtocol(Protocol.scheme, Protocol.requestHandler);
46+
}
47+
48+
// Create the browser window.
49+
win = new BrowserWindow({
50+
// full screen
51+
width: 1920,
52+
height: 1080,
53+
// window title
54+
title: `ReacType`,
55+
show: false,
56+
icon: path.join(__dirname, '../src/public/icons/png/256x256.png'),
57+
win: {
58+
icon: path.join(__dirname, '../src/public/icons/win/icon.ico'),
59+
target: ['portable']
60+
},
61+
webPreferences: {
62+
zoomFactor: 0.7,
63+
// enable devtools
64+
devTools: isDev,
65+
// crucial security feature - blocks rendering process from having access to node moduels
66+
nodeIntegration: false,
67+
// web workers will not have access to node
68+
nodeIntegrationInWorker: false,
69+
// disallow experimental feature to allow node.js suppport in subframes (iframes/child windows)
70+
nodeIntegrationInSubFrames: false,
71+
// runs electron apis and preload script in a seperate JS context
72+
// sepearate context has access to document/window but has it's own built-ins and is isolate from chagnes to gloval environment by locaded page
73+
// Electron API only available from preload, not loaded page
74+
contextIsolation: true,
75+
// disables remote module. critical for ensuring that rendering process doesn't have access to node functionality
76+
enableRemoteModule: false,
77+
// path of preload script. preload is how the renderer page will have access to electron functionality
78+
preload: path.join(__dirname, 'preload.js')
79+
}
80+
});
81+
82+
console.log('PATH is ', resolve('/'));
83+
84+
//splash screen deets
85+
// TODO: splash screen logo/icon aren't loading in dev mode
86+
const hideSplashscreen = initSplashScreen({
87+
mainWindow: win,
88+
icon: resolve('app/src/public/icons/png/64x64.png'),
89+
url: OfficeTemplate,
90+
width: 500,
91+
height: 300,
92+
brand: 'OS Labs',
93+
productName: 'ReacType',
94+
logo: resolve('app/src/public/icons/png/64x64.png'),
95+
color: '#3BBCAF',
96+
website: 'www.reactype.io',
97+
text: 'Initializing ...'
98+
});
99+
100+
// Load app
101+
if (isDev) {
102+
// load app from webdev server
103+
win.loadURL(selfHost);
104+
} else {
105+
// load local file if in production
106+
win.loadURL(`${Protocol.scheme}://rse/index-prod.html`);
107+
}
108+
109+
// load page once window is loaded
110+
win.once('ready-to-show', () => {
111+
win.show();
112+
hideSplashscreen();
113+
});
114+
115+
// Only do these things when in development
116+
if (isDev) {
117+
// automatically open DevTools when opening the app
118+
win.webContents.once('dom-ready', () => {
119+
debug();
120+
win.webContents.openDevTools();
121+
});
122+
}
123+
124+
// Emitted when the window is closed.
125+
win.on('closed', () => {
126+
// Dereference the window object, usually you would store windows
127+
// in an array if your app supports multi windows, this is the time
128+
// when you should delete the corresponding element.
129+
win = null;
130+
});
131+
132+
// https://electronjs.org/docs/tutorial/security#4-handle-session-permission-requests-from-remote-content
133+
// TODO: is this the same type of sessions that have in react type
134+
// Could potentially remove this session capability - it appears to be more focused on approving requests from 3rd party notifications
135+
const ses = session;
136+
const partition = 'default';
137+
ses
138+
.fromPartition(partition)
139+
.setPermissionRequestHandler((webContents, permission, callback) => {
140+
let allowedPermissions = []; // Full list here: https://developer.chrome.com/extensions/declare_permissions#manifest
141+
142+
if (allowedPermissions.includes(permission)) {
143+
callback(true); // Approve permission request
144+
} else {
145+
console.error(
146+
`The application tried to request permission for '${permission}'. This permission was not whitelisted and has been blocked.`
147+
);
148+
149+
callback(false); // Deny
150+
}
151+
});
152+
153+
// https://electronjs.org/docs/tutorial/security#1-only-load-secure-content;
154+
// The below code can only run when a scheme and host are defined, I thought
155+
// we could use this over _all_ urls
156+
ses
157+
.fromPartition(partition)
158+
.webRequest.onBeforeRequest({ urls: ['http://localhost./*'] }, listener => {
159+
if (listener.url.indexOf('http://') >= 0) {
160+
listener.callback({
161+
cancel: true
162+
});
163+
}
164+
});
165+
166+
menuBuilder = MenuBuilder(win, 'ReacType');
167+
menuBuilder.buildMenu();
168+
}
169+
170+
// TODO: unclear of whether this is necsssary or not. Looks like a security best practice but will likely introduce complications
171+
// Needs to be called before app is ready;
172+
// gives our scheme access to load relative files,
173+
// as well as local storage, cookies, etc.
174+
// https://electronjs.org/docs/api/protocol#protocolregisterschemesasprivilegedcustomschemes
175+
protocol.registerSchemesAsPrivileged([
176+
{
177+
scheme: Protocol.scheme,
178+
privileges: {
179+
standard: true,
180+
secure: true
181+
}
182+
}
183+
]);
184+
185+
// This method will be called when Electron has finished
186+
// initialization and is ready to create browser windows.
187+
// Some APIs can only be used after this event occurs.
188+
app.on('ready', createWindow);
189+
190+
// Quit when all windows are closed.
191+
app.on('window-all-closed', () => {
192+
// On macOS it is common for applications and their menu bar
193+
// to stay active until the user quits explicitly with Cmd + Q
194+
if (process.platform !== 'darwin') {
195+
app.quit();
196+
} else {
197+
// TODO: remove i18nextbackend
198+
// i18nextBackend.clearMainBindings(ipcMain);
199+
ContextMenu.clearMainBindings(ipcMain);
200+
}
201+
});
202+
203+
app.on('activate', () => {
204+
// On macOS it's common to re-create a window in the app when the
205+
// dock icon is clicked and there are no other windows open.
206+
if (win === null) {
207+
createWindow();
208+
}
209+
});
210+
211+
// https://electronjs.org/docs/tutorial/security#12-disable-or-limit-navigation
212+
// limits navigation within the app to a whitelisted array
213+
// redirects are a common attack vector
214+
// TODO: add github to the validOrigins whitelist array
215+
216+
// after the contents of the webpage are rendered, set up event listeners on the webContents
217+
app.on('web-contents-created', (event, contents) => {
218+
contents.on('will-navigate', (event, navigationUrl) => {
219+
const parsedUrl = new URL(navigationUrl);
220+
const validOrigins = [
221+
selfHost,
222+
'https://localhost:8080',
223+
'https://github.com/'
224+
];
225+
// Log and prevent the app from navigating to a new page if that page's origin is not whitelisted
226+
if (!validOrigins.includes(parsedUrl.origin)) {
227+
console.error(
228+
`The application tried to redirect to the following address: '${parsedUrl}'. This origin is not whitelisted and the attempt to navigate was blocked.`
229+
);
230+
// if the requested URL is not in the whitelisted array, then don't navigate there
231+
event.preventDefault();
232+
return;
233+
}
234+
});
235+
236+
contents.on('will-redirect', (event, navigationUrl) => {
237+
const parsedUrl = new URL(navigationUrl);
238+
const validOrigins = [
239+
selfHost,
240+
'https://localhost:8080',
241+
'https://github.com/'
242+
];
243+
244+
// Log and prevent the app from redirecting to a new page
245+
if (!validOrigins.includes(parsedUrl.origin)) {
246+
console.error(
247+
`The application tried to redirect to the following address: '${navigationUrl}'. This attempt was blocked.`
248+
);
249+
250+
event.preventDefault();
251+
return;
252+
}
253+
});
254+
255+
// https://electronjs.org/docs/tutorial/security#11-verify-webview-options-before-creation
256+
// The web-view is used to embed guest content in a page
257+
// This event listener deletes webviews if they happen to occur in the app
258+
// https://www.electronjs.org/docs/api/web-contents#event-will-attach-webview
259+
contents.on('will-attach-webview', (event, webPreferences, params) => {
260+
// Strip away preload scripts if unused or verify their location is legitimate
261+
delete webPreferences.preload;
262+
delete webPreferences.preloadURL;
263+
264+
// Disable Node.js integration
265+
webPreferences.nodeIntegration = false;
266+
});
267+
268+
// https://electronjs.org/docs/tutorial/security#13-disable-or-limit-creation-of-new-windows
269+
contents.on('new-window', async (event, navigationUrl) => {
270+
// Log and prevent opening up a new window
271+
console.error(
272+
`The application tried to open a new window at the following address: '${navigationUrl}'. This attempt was blocked.`
273+
);
274+
275+
event.preventDefault();
276+
return;
277+
});
278+
});
279+
280+
// Filter loading any module via remote;
281+
// you shouldn't be using remote at all, though
282+
// https://electronjs.org/docs/tutorial/security#16-filter-the-remote-module
283+
app.on('remote-require', (event, webContents, moduleName) => {
284+
event.preventDefault();
285+
});
286+
287+
// built-ins are modules such as "app"
288+
app.on('remote-get-builtin', (event, webContents, moduleName) => {
289+
event.preventDefault();
290+
});
291+
292+
app.on('remote-get-global', (event, webContents, globalName) => {
293+
event.preventDefault();
294+
});
295+
296+
app.on('remote-get-current-window', (event, webContents) => {
297+
event.preventDefault();
298+
});
299+
300+
app.on('remote-get-current-web-contents', (event, webContents) => {
301+
event.preventDefault();
302+
});
303+
304+
// When a user selects "Export project", a function (chooseAppDir loaded via preload.js)
305+
// is triggered that sends a "choose_app_dir" message to the main process
306+
// when the "choose_app_dir" message is received it triggers this event listener
307+
ipcMain.on('choose_app_dir', event => {
308+
// dialog displays the native system's dialogue for selecting files
309+
// once a directory is chosen send a message back to the renderer with the path of the directory
310+
dialog
311+
.showOpenDialog(win, {
312+
properties: ['openDirectory'],
313+
buttonLabel: 'Export'
314+
})
315+
.then(directory => {
316+
if (!directory) return;
317+
event.sender.send('app_dir_selected', directory.filePaths[0]);
318+
})
319+
.catch(err => console.log('ERROR on "choose_app_dir" event: ', err));
320+
});

0 commit comments

Comments
 (0)