Skip to content

Commit 559d756

Browse files
committed
merged with server separation branch
2 parents be1d731 + cd45229 commit 559d756

File tree

93 files changed

+2433
-1008
lines changed

Some content is hidden

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

93 files changed

+2433
-1008
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: 355 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,355 @@
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: true,
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+
137+
const partition = 'default';
138+
ses
139+
.fromPartition(partition)
140+
.setPermissionRequestHandler((webContents, permission, callback) => {
141+
let allowedPermissions = []; // Full list here: https://developer.chrome.com/extensions/declare_permissions#manifest
142+
143+
if (allowedPermissions.includes(permission)) {
144+
callback(true); // Approve permission request
145+
} else {
146+
console.error(
147+
`The application tried to request permission for '${permission}'. This permission was not whitelisted and has been blocked.`
148+
);
149+
150+
callback(false); // Deny
151+
}
152+
});
153+
154+
// https://electronjs.org/docs/tutorial/security#1-only-load-secure-content;
155+
// The below code can only run when a scheme and host are defined, I thought
156+
// we could use this over _all_ urls
157+
ses
158+
.fromPartition(partition)
159+
.webRequest.onBeforeRequest({ urls: ['http://localhost./*'] }, listener => {
160+
if (listener.url.indexOf('http://') >= 0) {
161+
listener.callback({
162+
cancel: true
163+
});
164+
}
165+
});
166+
167+
menuBuilder = MenuBuilder(win, 'ReacType');
168+
menuBuilder.buildMenu();
169+
}
170+
171+
// TODO: unclear of whether this is necsssary or not. Looks like a security best practice but will likely introduce complications
172+
// Needs to be called before app is ready;
173+
// gives our scheme access to load relative files,
174+
// as well as local storage, cookies, etc.
175+
// https://electronjs.org/docs/api/protocol#protocolregisterschemesasprivilegedcustomschemes
176+
protocol.registerSchemesAsPrivileged([
177+
{
178+
scheme: Protocol.scheme,
179+
privileges: {
180+
standard: true,
181+
secure: true,
182+
allowServiceWorkers: true,
183+
supportFetchAPI: true
184+
}
185+
}
186+
]);
187+
188+
// This method will be called when Electron has finished
189+
// initialization and is ready to create browser windows.
190+
// Some APIs can only be used after this event occurs.
191+
app.on('ready', createWindow);
192+
193+
// Quit when all windows are closed.
194+
app.on('window-all-closed', () => {
195+
// On macOS it is common for applications and their menu bar
196+
// to stay active until the user quits explicitly with Cmd + Q
197+
if (process.platform !== 'darwin') {
198+
app.quit();
199+
} else {
200+
// TODO: remove i18nextbackend
201+
// i18nextBackend.clearMainBindings(ipcMain);
202+
ContextMenu.clearMainBindings(ipcMain);
203+
}
204+
});
205+
206+
app.on('activate', () => {
207+
// On macOS it's common to re-create a window in the app when the
208+
// dock icon is clicked and there are no other windows open.
209+
if (win === null) {
210+
createWindow();
211+
}
212+
});
213+
214+
// https://electronjs.org/docs/tutorial/security#12-disable-or-limit-navigation
215+
// limits navigation within the app to a whitelisted array
216+
// redirects are a common attack vector
217+
// TODO: add github to the validOrigins whitelist array
218+
219+
// after the contents of the webpage are rendered, set up event listeners on the webContents
220+
app.on('web-contents-created', (event, contents) => {
221+
contents.on('will-navigate', (event, navigationUrl) => {
222+
const parsedUrl = new URL(navigationUrl);
223+
const validOrigins = [
224+
selfHost,
225+
'https://localhost:8081',
226+
'https://github.com/'
227+
];
228+
// Log and prevent the app from navigating to a new page if that page's origin is not whitelisted
229+
if (!validOrigins.includes(parsedUrl.origin)) {
230+
console.error(
231+
`The application tried to navigate to the following address: '${parsedUrl}'. This origin is not whitelisted and the attempt to navigate was blocked.`
232+
);
233+
// if the requested URL is not in the whitelisted array, then don't navigate there
234+
event.preventDefault();
235+
return;
236+
}
237+
});
238+
239+
contents.on('will-redirect', (event, navigationUrl) => {
240+
const parsedUrl = new URL(navigationUrl);
241+
//console.log('parsedUrl is', parsedUrl);
242+
//console.log('parsedUrl.origin is', parsedUrl.origin);
243+
const validOrigins = [
244+
selfHost,
245+
'https://localhost:8081',
246+
'https://github.com',
247+
'app://rse/'
248+
];
249+
250+
// Log and prevent the app from redirecting to a new page
251+
if (
252+
!validOrigins.includes(parsedUrl.origin) &&
253+
!validOrigins.includes(parsedUrl.href)
254+
) {
255+
console.error(
256+
`The application tried to redirect to the following address: '${navigationUrl}'. This attempt was blocked.`
257+
);
258+
259+
event.preventDefault();
260+
return;
261+
}
262+
});
263+
264+
// https://electronjs.org/docs/tutorial/security#11-verify-webview-options-before-creation
265+
// The web-view is used to embed guest content in a page
266+
// This event listener deletes webviews if they happen to occur in the app
267+
// https://www.electronjs.org/docs/api/web-contents#event-will-attach-webview
268+
contents.on('will-attach-webview', (event, webPreferences, params) => {
269+
// Strip away preload scripts if unused or verify their location is legitimate
270+
delete webPreferences.preload;
271+
delete webPreferences.preloadURL;
272+
273+
// Disable Node.js integration
274+
webPreferences.nodeIntegration = false;
275+
});
276+
277+
// https://electronjs.org/docs/tutorial/security#13-disable-or-limit-creation-of-new-windows
278+
contents.on('new-window', async (event, navigationUrl) => {
279+
// Log and prevent opening up a new window
280+
console.error(
281+
`The application tried to open a new window at the following address: '${navigationUrl}'. This attempt was blocked.`
282+
);
283+
284+
event.preventDefault();
285+
return;
286+
});
287+
});
288+
289+
// Filter loading any module via remote;
290+
// you shouldn't be using remote at all, though
291+
// https://electronjs.org/docs/tutorial/security#16-filter-the-remote-module
292+
app.on('remote-require', (event, webContents, moduleName) => {
293+
event.preventDefault();
294+
});
295+
296+
// built-ins are modules such as "app"
297+
app.on('remote-get-builtin', (event, webContents, moduleName) => {
298+
event.preventDefault();
299+
});
300+
301+
app.on('remote-get-global', (event, webContents, globalName) => {
302+
event.preventDefault();
303+
});
304+
305+
app.on('remote-get-current-window', (event, webContents) => {
306+
event.preventDefault();
307+
});
308+
309+
app.on('remote-get-current-web-contents', (event, webContents) => {
310+
event.preventDefault();
311+
});
312+
313+
// When a user selects "Export project", a function (chooseAppDir loaded via preload.js)
314+
// is triggered that sends a "choose_app_dir" message to the main process
315+
// when the "choose_app_dir" message is received it triggers this event listener
316+
ipcMain.on('choose_app_dir', event => {
317+
// dialog displays the native system's dialogue for selecting files
318+
// once a directory is chosen send a message back to the renderer with the path of the directory
319+
dialog
320+
.showOpenDialog(win, {
321+
properties: ['openDirectory'],
322+
buttonLabel: 'Export'
323+
})
324+
.then(directory => {
325+
if (!directory) return;
326+
event.sender.send('app_dir_selected', directory.filePaths[0]);
327+
})
328+
.catch(err => console.log('ERROR on "choose_app_dir" event: ', err));
329+
});
330+
331+
// for github oauth login in production, since cookies are not accessible through document.cookie on local filesystem, we need electron to grab the cookie that is set from oauth, this listens for an set cookie event from the renderer process then sends back the cookie
332+
ipcMain.on('set_cookie', event => {
333+
session.defaultSession.cookies
334+
.get({ url: 'https://localhost:8081' })
335+
.then(cookie => {
336+
console.log(cookie);
337+
event.reply('give_cookie', cookie);
338+
})
339+
.catch(error => {
340+
console.log('Error giving cookies in set_cookie:', error);
341+
});
342+
});
343+
344+
// again for production, document.cookie is not accessible so we need this listener on main to delete the cookie on logout
345+
ipcMain.on('delete_cookie', event => {
346+
session.defaultSession.cookies
347+
.remove('https://localhost:8081', 'ssid')
348+
.then(removed => {
349+
console.log('Cookies deleted', removed);
350+
})
351+
.catch(err => console.log('Error deleting cookie:', err));
352+
});
353+
354+
// bypass ssl certification validation error
355+
// app.commandLine.appendSwitch('ignore-certificate-errors', 'true');

0 commit comments

Comments
 (0)