[MM-33828] Define CSP for all BrowserWindows, enable contextIsolation for main/settings window, other fixes (#1517)

* [MM-33828] Added CSP to renderer process pages and turned off remote module when not needed

* Turn on contextIsolation and turn off nodeIntegration for main and settings windows

* Check for details.reason instead of object
This commit is contained in:
Devin Binnie
2021-03-23 06:26:48 -04:00
committed by GitHub
parent 5ed84270c8
commit b5c59fa8ce
13 changed files with 134 additions and 125 deletions

View File

@@ -0,0 +1,28 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2015-2016 Yuya Ochiai
'use strict';
import os from 'os';
import {ipcRenderer, contextBridge} from 'electron';
contextBridge.exposeInMainWorld('ipcRenderer', {
send: ipcRenderer.send,
on: (channel, listener) => ipcRenderer.on(channel, (_, ...args) => listener(null, ...args)),
invoke: ipcRenderer.invoke,
});
contextBridge.exposeInMainWorld('os', {
isWindows10: os.platform() === 'win32' && os.release().startsWith('10'),
});
contextBridge.exposeInMainWorld('process', {
platform: process.platform,
env: process.env,
});
contextBridge.exposeInMainWorld('timers', {
setImmediate,
});

View File

@@ -43,7 +43,7 @@ export class MattermostView extends EventEmitter {
`version=${app.version}`, `version=${app.version}`,
`appName=${app.name}`, `appName=${app.name}`,
], ],
enableRemoteModule: true, enableRemoteModule: process.env.NODE_ENV === 'test',
nodeIntegration: process.env.NODE_ENV === 'test', nodeIntegration: process.env.NODE_ENV === 'test',
}, },
...options, ...options,

View File

@@ -242,8 +242,8 @@ export const addWebContentsEventListeners = (mmview, getServersFunction) => {
listeners[contents.id] = removeListeners; listeners[contents.id] = removeListeners;
contents.once('render-process-gone', (event, details) => { contents.once('render-process-gone', (event, details) => {
if (details !== 'clean-exit') { if (details.reason !== 'clean-exit') {
log.error(`Renderer process for a webcontent is no longer available: ${details}`); log.error('Renderer process for a webcontent is no longer available:', details.reason);
} }
removeListeners(); removeListeners();
}); });

View File

@@ -13,7 +13,7 @@ import {SELECT_NEXT_TAB, SELECT_PREVIOUS_TAB} from 'common/communication';
import * as Validator from '../Validator'; import * as Validator from '../Validator';
import contextMenu from '../contextMenu'; import contextMenu from '../contextMenu';
import {getLocalURLString} from '../utils'; import {getLocalPreload, getLocalURLString} from '../utils';
function saveWindowState(file, window) { function saveWindowState(file, window) {
const windowState = window.getBounds(); const windowState = window.getBounds();
@@ -37,6 +37,7 @@ function createMainWindow(config, options) {
const minimumWindowHeight = 240; const minimumWindowHeight = 240;
// Create the browser window. // Create the browser window.
const preload = getLocalPreload('mainWindow.js');
const boundsInfoPath = path.join(app.getPath('userData'), 'bounds-info.json'); const boundsInfoPath = path.join(app.getPath('userData'), 'bounds-info.json');
let windowOptions; let windowOptions;
try { try {
@@ -70,9 +71,10 @@ function createMainWindow(config, options) {
trafficLightPosition: {x: 12, y: 24}, trafficLightPosition: {x: 12, y: 24},
backgroundColor: '#fff', // prevents blurry text: https://electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do backgroundColor: '#fff', // prevents blurry text: https://electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do
webPreferences: { webPreferences: {
nodeIntegration: true, nodeIntegration: process.env.NODE_ENV === 'test',
contextIsolation: false, contextIsolation: process.env.NODE_ENV !== 'test',
disableBlinkFeatures: 'Auxclick', disableBlinkFeatures: 'Auxclick',
preload,
spellcheck, spellcheck,
enableRemoteModule: process.env.NODE_ENV === 'test', enableRemoteModule: process.env.NODE_ENV === 'test',
}, },

View File

@@ -4,17 +4,19 @@
import {BrowserWindow} from 'electron'; import {BrowserWindow} from 'electron';
import log from 'electron-log'; import log from 'electron-log';
import {getLocalURLString} from '../utils'; import {getLocalPreload, getLocalURLString} from '../utils';
export function createSettingsWindow(mainWindow, config, withDevTools) { export function createSettingsWindow(mainWindow, config, withDevTools) {
const preload = getLocalPreload('mainWindow.js');
const spellcheck = (typeof config.useSpellChecker === 'undefined' ? true : config.useSpellChecker); const spellcheck = (typeof config.useSpellChecker === 'undefined' ? true : config.useSpellChecker);
const settingsWindow = new BrowserWindow({ const settingsWindow = new BrowserWindow({
...config.data, ...config.data,
parent: mainWindow, parent: mainWindow,
title: 'Desktop App Settings', title: 'Desktop App Settings',
webPreferences: { webPreferences: {
nodeIntegration: true, nodeIntegration: false,
contextIsolation: false, contextIsolation: true,
preload,
spellcheck, spellcheck,
enableRemoteModule: process.env.NODE_ENV === 'test', enableRemoteModule: process.env.NODE_ENV === 'test',
}}); }});

View File

@@ -2,15 +2,11 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
// Copyright (c) 2015-2016 Yuya Ochiai // Copyright (c) 2015-2016 Yuya Ochiai
import os from 'os';
import React, {Fragment} from 'react'; import React, {Fragment} from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import {Grid, Row} from 'react-bootstrap'; import {Grid, Row} from 'react-bootstrap';
import DotsVerticalIcon from 'mdi-react/DotsVerticalIcon'; import DotsVerticalIcon from 'mdi-react/DotsVerticalIcon';
import {ipcRenderer} from 'electron';
import { import {
FOCUS_BROWSERVIEW, FOCUS_BROWSERVIEW,
MAXIMIZE_CHANGE, MAXIMIZE_CHANGE,
@@ -87,7 +83,7 @@ export default class MainPage extends React.PureComponent {
componentDidMount() { componentDidMount() {
// set page on retry // set page on retry
ipcRenderer.on(LOAD_RETRY, (_, server, retry, err, loadUrl) => { window.ipcRenderer.on(LOAD_RETRY, (_, server, retry, err, loadUrl) => {
console.log(`${server}: failed to load ${err}, but retrying`); console.log(`${server}: failed to load ${err}, but retrying`);
const status = this.state.tabStatus; const status = this.state.tabStatus;
const statusValue = { const statusValue = {
@@ -102,13 +98,13 @@ export default class MainPage extends React.PureComponent {
this.setState({tabStatus: status}); this.setState({tabStatus: status});
}); });
ipcRenderer.on(LOAD_SUCCESS, (_, server) => { window.ipcRenderer.on(LOAD_SUCCESS, (_, server) => {
const status = this.state.tabStatus; const status = this.state.tabStatus;
status.set(server, {status: DONE}); status.set(server, {status: DONE});
this.setState({tabStatus: status}); this.setState({tabStatus: status});
}); });
ipcRenderer.on(LOAD_FAILED, (_, server, err, loadUrl) => { window.ipcRenderer.on(LOAD_FAILED, (_, server, err, loadUrl) => {
console.log(`${server}: failed to load ${err}`); console.log(`${server}: failed to load ${err}`);
const status = this.state.tabStatus; const status = this.state.tabStatus;
const statusValue = { const statusValue = {
@@ -122,16 +118,16 @@ export default class MainPage extends React.PureComponent {
this.setState({tabStatus: status}); this.setState({tabStatus: status});
}); });
ipcRenderer.on(DARK_MODE_CHANGE, (_, darkMode) => { window.ipcRenderer.on(DARK_MODE_CHANGE, (_, darkMode) => {
this.setState({darkMode}); this.setState({darkMode});
}); });
// can't switch tabs sequentially for some reason... // can't switch tabs sequentially for some reason...
ipcRenderer.on(SET_SERVER_KEY, (event, key) => { window.ipcRenderer.on(SET_SERVER_KEY, (event, key) => {
const nextIndex = this.props.teams.findIndex((team) => team.order === key); const nextIndex = this.props.teams.findIndex((team) => team.order === key);
this.handleSetServerKey(nextIndex); this.handleSetServerKey(nextIndex);
}); });
ipcRenderer.on(SELECT_NEXT_TAB, () => { window.ipcRenderer.on(SELECT_NEXT_TAB, () => {
const currentOrder = this.props.teams[this.state.key].order; const currentOrder = this.props.teams[this.state.key].order;
const nextOrder = ((currentOrder + 1) % this.props.teams.length); const nextOrder = ((currentOrder + 1) % this.props.teams.length);
const nextIndex = this.props.teams.findIndex((team) => team.order === nextOrder); const nextIndex = this.props.teams.findIndex((team) => team.order === nextOrder);
@@ -139,7 +135,7 @@ export default class MainPage extends React.PureComponent {
this.handleSelect(team.name, nextIndex); this.handleSelect(team.name, nextIndex);
}); });
ipcRenderer.on(SELECT_PREVIOUS_TAB, () => { window.ipcRenderer.on(SELECT_PREVIOUS_TAB, () => {
const currentOrder = this.props.teams[this.state.key].order; const currentOrder = this.props.teams[this.state.key].order;
// js modulo operator returns a negative number if result is negative, so we have to ensure it's positive // js modulo operator returns a negative number if result is negative, so we have to ensure it's positive
@@ -149,32 +145,32 @@ export default class MainPage extends React.PureComponent {
this.handleSelect(team.name, nextIndex); this.handleSelect(team.name, nextIndex);
}); });
ipcRenderer.on(MAXIMIZE_CHANGE, this.handleMaximizeState); window.ipcRenderer.on(MAXIMIZE_CHANGE, this.handleMaximizeState);
ipcRenderer.on('enter-full-screen', () => this.handleFullScreenState(true)); window.ipcRenderer.on('enter-full-screen', () => this.handleFullScreenState(true));
ipcRenderer.on('leave-full-screen', () => this.handleFullScreenState(false)); window.ipcRenderer.on('leave-full-screen', () => this.handleFullScreenState(false));
ipcRenderer.on(ADD_SERVER, () => { window.ipcRenderer.on(ADD_SERVER, () => {
this.addServer(); this.addServer();
}); });
ipcRenderer.on(PLAY_SOUND, (_event, soundName) => { window.ipcRenderer.on(PLAY_SOUND, (_event, soundName) => {
playSound(soundName); playSound(soundName);
}); });
ipcRenderer.on(MODAL_OPEN, () => { window.ipcRenderer.on(MODAL_OPEN, () => {
this.setState({modalOpen: true}); this.setState({modalOpen: true});
}); });
ipcRenderer.on(MODAL_CLOSE, () => { window.ipcRenderer.on(MODAL_CLOSE, () => {
this.setState({modalOpen: false}); this.setState({modalOpen: false});
}); });
ipcRenderer.on(TOGGLE_BACK_BUTTON, (event, showExtraBar) => { window.ipcRenderer.on(TOGGLE_BACK_BUTTON, (event, showExtraBar) => {
this.setState({showExtraBar}); this.setState({showExtraBar});
}); });
ipcRenderer.on(UPDATE_MENTIONS, (_event, team, mentions, unreads, isExpired) => { window.ipcRenderer.on(UPDATE_MENTIONS, (_event, team, mentions, unreads, isExpired) => {
const key = this.props.teams.findIndex((server) => server.name === team); const key = this.props.teams.findIndex((server) => server.name === team);
const {unreadCounts, mentionCounts, sessionsExpired} = this.state; const {unreadCounts, mentionCounts, sessionsExpired} = this.state;
@@ -190,8 +186,8 @@ export default class MainPage extends React.PureComponent {
this.setState({unreadCounts: newUnreads, mentionCounts: newMentionCounts, sessionsExpired: expired}); this.setState({unreadCounts: newUnreads, mentionCounts: newMentionCounts, sessionsExpired: expired});
}); });
if (process.platform !== 'darwin') { if (window.process.platform !== 'darwin') {
ipcRenderer.on(FOCUS_THREE_DOT_MENU, () => { window.ipcRenderer.on(FOCUS_THREE_DOT_MENU, () => {
if (this.threeDotMenu.current) { if (this.threeDotMenu.current) {
this.threeDotMenu.current.focus(); this.threeDotMenu.current.focus();
} }
@@ -213,7 +209,7 @@ export default class MainPage extends React.PureComponent {
} }
handleSelect = (name, key) => { handleSelect = (name, key) => {
ipcRenderer.send(SWITCH_SERVER, name); window.ipcRenderer.send(SWITCH_SERVER, name);
this.handleSetServerKey(key); this.handleSetServerKey(key);
} }
@@ -228,40 +224,40 @@ export default class MainPage extends React.PureComponent {
handleClose = (e) => { handleClose = (e) => {
e.stopPropagation(); // since it is our button, the event goes into MainPage's onclick event, getting focus back. e.stopPropagation(); // since it is our button, the event goes into MainPage's onclick event, getting focus back.
ipcRenderer.send(WINDOW_CLOSE); window.ipcRenderer.send(WINDOW_CLOSE);
} }
handleMinimize = (e) => { handleMinimize = (e) => {
e.stopPropagation(); e.stopPropagation();
ipcRenderer.send(WINDOW_MINIMIZE); window.ipcRenderer.send(WINDOW_MINIMIZE);
} }
handleMaximize = (e) => { handleMaximize = (e) => {
e.stopPropagation(); e.stopPropagation();
ipcRenderer.send(WINDOW_MAXIMIZE); window.ipcRenderer.send(WINDOW_MAXIMIZE);
} }
handleRestore = () => { handleRestore = () => {
ipcRenderer.send(WINDOW_RESTORE); window.ipcRenderer.send(WINDOW_RESTORE);
} }
openMenu = () => { openMenu = () => {
if (process.platform !== 'darwin') { if (window.process.platform !== 'darwin') {
this.threeDotMenu.current.blur(); this.threeDotMenu.current.blur();
} }
this.props.openMenu(); this.props.openMenu();
} }
handleDoubleClick = () => { handleDoubleClick = () => {
ipcRenderer.send(DOUBLE_CLICK_ON_WINDOW); window.ipcRenderer.send(DOUBLE_CLICK_ON_WINDOW);
} }
addServer = () => { addServer = () => {
ipcRenderer.send(SHOW_NEW_SERVER_MODAL); window.ipcRenderer.send(SHOW_NEW_SERVER_MODAL);
} }
focusOnWebView = () => { focusOnWebView = () => {
ipcRenderer.send(FOCUS_BROWSERVIEW); window.ipcRenderer.send(FOCUS_BROWSERVIEW);
} }
setInputRef = (ref) => { setInputRef = (ref) => {
@@ -287,7 +283,7 @@ export default class MainPage extends React.PureComponent {
); );
let topBarClassName = 'topBar'; let topBarClassName = 'topBar';
if (process.platform === 'darwin') { if (window.process.platform === 'darwin') {
topBarClassName += ' macOS'; topBarClassName += ' macOS';
} }
if (this.state.darkMode) { if (this.state.darkMode) {
@@ -319,14 +315,14 @@ export default class MainPage extends React.PureComponent {
} }
let overlayGradient; let overlayGradient;
if (process.platform !== 'darwin') { if (window.process.platform !== 'darwin') {
overlayGradient = ( overlayGradient = (
<span className='overlay-gradient'/> <span className='overlay-gradient'/>
); );
} }
let titleBarButtons; let titleBarButtons;
if (os.platform() === 'win32' && os.release().startsWith('10')) { if (window.os.isWindows10) {
titleBarButtons = ( titleBarButtons = (
<span className='title-bar-btns'> <span className='title-bar-btns'>
<div <div
@@ -421,7 +417,7 @@ export default class MainPage extends React.PureComponent {
darkMode={this.state.darkMode} darkMode={this.state.darkMode}
show={this.state.showExtraBar} show={this.state.showExtraBar}
goBack={() => { goBack={() => {
ipcRenderer.send(HISTORY, -1); window.ipcRenderer.send(HISTORY, -1);
}} }}
/> />
<Row> <Row>

View File

@@ -8,7 +8,6 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import {Checkbox, Col, FormGroup, Grid, HelpBlock, Navbar, Radio, Row, Button} from 'react-bootstrap'; import {Checkbox, Col, FormGroup, Grid, HelpBlock, Navbar, Radio, Row, Button} from 'react-bootstrap';
import {ipcRenderer} from 'electron';
import {debounce} from 'underscore'; import {debounce} from 'underscore';
import {GET_LOCAL_CONFIGURATION, UPDATE_CONFIGURATION, DOUBLE_CLICK_ON_WINDOW, GET_DOWNLOAD_LOCATION, SWITCH_SERVER, ADD_SERVER, RELOAD_CONFIGURATION} from 'common/communication'; import {GET_LOCAL_CONFIGURATION, UPDATE_CONFIGURATION, DOUBLE_CLICK_ON_WINDOW, GET_DOWNLOAD_LOCATION, SWITCH_SERVER, ADD_SERVER, RELOAD_CONFIGURATION} from 'common/communication';
@@ -20,7 +19,7 @@ const CONFIG_TYPE_SERVERS = 'servers';
const CONFIG_TYPE_APP_OPTIONS = 'appOptions'; const CONFIG_TYPE_APP_OPTIONS = 'appOptions';
function backToIndex(serverName) { function backToIndex(serverName) {
ipcRenderer.send(SWITCH_SERVER, serverName); window.ipcRenderer.send(SWITCH_SERVER, serverName);
window.close(); window.close();
} }
@@ -54,20 +53,20 @@ export default class SettingsPage extends React.PureComponent {
} }
componentDidMount() { componentDidMount() {
ipcRenderer.on(ADD_SERVER, () => { window.ipcRenderer.on(ADD_SERVER, () => {
this.setState({ this.setState({
showAddTeamForm: true, showAddTeamForm: true,
}); });
}); });
ipcRenderer.on(RELOAD_CONFIGURATION, () => { window.ipcRenderer.on(RELOAD_CONFIGURATION, () => {
this.updateSaveState(); this.updateSaveState();
this.getConfig(); this.getConfig();
}); });
} }
getConfig = () => { getConfig = () => {
ipcRenderer.invoke(GET_LOCAL_CONFIGURATION).then((config) => { window.ipcRenderer.invoke(GET_LOCAL_CONFIGURATION).then((config) => {
this.setState({ready: true, maximized: false, ...this.convertConfigDataToState(config)}); this.setState({ready: true, maximized: false, ...this.convertConfigDataToState(config)});
}); });
} }
@@ -98,7 +97,7 @@ export default class SettingsPage extends React.PureComponent {
} }
processSaveQueue = debounce(() => { processSaveQueue = debounce(() => {
ipcRenderer.send(UPDATE_CONFIGURATION, this.saveQueue.splice(0, this.saveQueue.length)); window.ipcRenderer.send(UPDATE_CONFIGURATION, this.saveQueue.splice(0, this.saveQueue.length));
}, 500); }, 500);
updateSaveState = () => { updateSaveState = () => {
@@ -135,7 +134,7 @@ export default class SettingsPage extends React.PureComponent {
}, 2000); }, 2000);
handleTeamsChange = (teams) => { handleTeamsChange = (teams) => {
setImmediate(this.saveSetting, CONFIG_TYPE_SERVERS, {key: 'teams', data: teams}); window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_SERVERS, {key: 'teams', data: teams});
this.setState({ this.setState({
showAddTeamForm: false, showAddTeamForm: false,
teams, teams,
@@ -147,12 +146,12 @@ export default class SettingsPage extends React.PureComponent {
handleChangeShowTrayIcon = () => { handleChangeShowTrayIcon = () => {
const shouldShowTrayIcon = !this.showTrayIconRef.current.props.checked; const shouldShowTrayIcon = !this.showTrayIconRef.current.props.checked;
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'showTrayIcon', data: shouldShowTrayIcon}); window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'showTrayIcon', data: shouldShowTrayIcon});
this.setState({ this.setState({
showTrayIcon: shouldShowTrayIcon, showTrayIcon: shouldShowTrayIcon,
}); });
if (process.platform === 'darwin' && !shouldShowTrayIcon) { if (window.process.platform === 'darwin' && !shouldShowTrayIcon) {
this.setState({ this.setState({
minimizeToTray: false, minimizeToTray: false,
}); });
@@ -160,14 +159,14 @@ export default class SettingsPage extends React.PureComponent {
} }
handleChangeTrayIconTheme = (theme) => { handleChangeTrayIconTheme = (theme) => {
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'trayIconTheme', data: theme}); window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'trayIconTheme', data: theme});
this.setState({ this.setState({
trayIconTheme: theme, trayIconTheme: theme,
}); });
} }
handleChangeAutoStart = () => { handleChangeAutoStart = () => {
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'autostart', data: !this.autostartRef.current.props.checked}); window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'autostart', data: !this.autostartRef.current.props.checked});
this.setState({ this.setState({
autostart: !this.autostartRef.current.props.checked, autostart: !this.autostartRef.current.props.checked,
}); });
@@ -176,7 +175,7 @@ export default class SettingsPage extends React.PureComponent {
handleChangeMinimizeToTray = () => { handleChangeMinimizeToTray = () => {
const shouldMinimizeToTray = this.state.showTrayIcon && !this.minimizeToTrayRef.current.props.checked; const shouldMinimizeToTray = this.state.showTrayIcon && !this.minimizeToTrayRef.current.props.checked;
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'minimizeToTray', data: shouldMinimizeToTray}); window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'minimizeToTray', data: shouldMinimizeToTray});
this.setState({ this.setState({
minimizeToTray: shouldMinimizeToTray, minimizeToTray: shouldMinimizeToTray,
}); });
@@ -196,7 +195,7 @@ export default class SettingsPage extends React.PureComponent {
} }
handleFlashWindow = () => { handleFlashWindow = () => {
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, { window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {
key: 'notifications', key: 'notifications',
data: { data: {
...this.state.notifications, ...this.state.notifications,
@@ -212,7 +211,7 @@ export default class SettingsPage extends React.PureComponent {
} }
handleBounceIcon = () => { handleBounceIcon = () => {
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, { window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {
key: 'notifications', key: 'notifications',
data: { data: {
...this.state.notifications, ...this.state.notifications,
@@ -228,7 +227,7 @@ export default class SettingsPage extends React.PureComponent {
} }
handleBounceIconType = (event) => { handleBounceIconType = (event) => {
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, { window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {
key: 'notifications', key: 'notifications',
data: { data: {
...this.state.notifications, ...this.state.notifications,
@@ -244,21 +243,21 @@ export default class SettingsPage extends React.PureComponent {
} }
handleShowUnreadBadge = () => { handleShowUnreadBadge = () => {
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'showUnreadBadge', data: !this.showUnreadBadgeRef.current.props.checked}); window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'showUnreadBadge', data: !this.showUnreadBadgeRef.current.props.checked});
this.setState({ this.setState({
showUnreadBadge: !this.showUnreadBadgeRef.current.props.checked, showUnreadBadge: !this.showUnreadBadgeRef.current.props.checked,
}); });
} }
handleChangeUseSpellChecker = () => { handleChangeUseSpellChecker = () => {
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'useSpellChecker', data: !this.useSpellCheckerRef.current.props.checked}); window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'useSpellChecker', data: !this.useSpellCheckerRef.current.props.checked});
this.setState({ this.setState({
useSpellChecker: !this.useSpellCheckerRef.current.props.checked, useSpellChecker: !this.useSpellCheckerRef.current.props.checked,
}); });
} }
handleChangeEnableHardwareAcceleration = () => { handleChangeEnableHardwareAcceleration = () => {
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'enableHardwareAcceleration', data: !this.enableHardwareAccelerationRef.current.props.checked}); window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'enableHardwareAcceleration', data: !this.enableHardwareAccelerationRef.current.props.checked});
this.setState({ this.setState({
enableHardwareAcceleration: !this.enableHardwareAccelerationRef.current.props.checked, enableHardwareAcceleration: !this.enableHardwareAccelerationRef.current.props.checked,
}); });
@@ -268,7 +267,7 @@ export default class SettingsPage extends React.PureComponent {
this.setState({ this.setState({
downloadLocation: location, downloadLocation: location,
}); });
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'downloadLocation', data: location}); window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'downloadLocation', data: location});
} }
handleChangeDownloadLocation = (e) => { handleChangeDownloadLocation = (e) => {
@@ -277,7 +276,7 @@ export default class SettingsPage extends React.PureComponent {
selectDownloadLocation = () => { selectDownloadLocation = () => {
if (!this.state.userOpenedDownloadDialog) { if (!this.state.userOpenedDownloadDialog) {
ipcRenderer.invoke(GET_DOWNLOAD_LOCATION, `/Users/${process.env.USER || process.env.USERNAME}/Downloads`).then((result) => this.saveDownloadLocation(result)); window.ipcRenderer.invoke(GET_DOWNLOAD_LOCATION, `/Users/${window.process.env.USER || window.process.env.USERNAME}/Downloads`).then((result) => this.saveDownloadLocation(result));
this.setState({userOpenedDownloadDialog: true}); this.setState({userOpenedDownloadDialog: true});
} }
this.setState({userOpenedDownloadDialog: false}); this.setState({userOpenedDownloadDialog: false});
@@ -286,7 +285,7 @@ export default class SettingsPage extends React.PureComponent {
updateTeam = (index, newData) => { updateTeam = (index, newData) => {
const teams = this.state.teams; const teams = this.state.teams;
teams[index] = newData; teams[index] = newData;
setImmediate(this.saveSetting, CONFIG_TYPE_SERVERS, {key: 'teams', data: teams}); window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_SERVERS, {key: 'teams', data: teams});
this.setState({ this.setState({
teams, teams,
}); });
@@ -295,7 +294,7 @@ export default class SettingsPage extends React.PureComponent {
addServer = (team) => { addServer = (team) => {
const teams = this.state.teams; const teams = this.state.teams;
teams.push(team); teams.push(team);
setImmediate(this.saveSetting, CONFIG_TYPE_SERVERS, {key: 'teams', data: teams}); window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_SERVERS, {key: 'teams', data: teams});
this.setState({ this.setState({
teams, teams,
}); });
@@ -308,7 +307,7 @@ export default class SettingsPage extends React.PureComponent {
} }
handleDoubleClick = () => { handleDoubleClick = () => {
ipcRenderer.send(DOUBLE_CLICK_ON_WINDOW, 'settings'); window.ipcRenderer.send(DOUBLE_CLICK_ON_WINDOW, 'settings');
} }
render() { render() {
@@ -431,7 +430,7 @@ export default class SettingsPage extends React.PureComponent {
const options = []; const options = [];
// MacOS has an option in the Dock, to set the app to autostart, so we choose to not support this option for OSX // MacOS has an option in the Dock, to set the app to autostart, so we choose to not support this option for OSX
if (process.platform === 'win32' || process.platform === 'linux') { if (window.process.platform === 'win32' || window.process.platform === 'linux') {
options.push( options.push(
<Checkbox <Checkbox
key='inputAutoStart' key='inputAutoStart'
@@ -462,8 +461,8 @@ export default class SettingsPage extends React.PureComponent {
</HelpBlock> </HelpBlock>
</Checkbox>); </Checkbox>);
if (process.platform === 'darwin' || process.platform === 'win32') { if (window.process.platform === 'darwin' || window.process.platform === 'win32') {
const TASKBAR = process.platform === 'win32' ? 'taskbar' : 'Dock'; const TASKBAR = window.process.platform === 'win32' ? 'taskbar' : 'Dock';
options.push( options.push(
<Checkbox <Checkbox
key='inputShowUnreadBadge' key='inputShowUnreadBadge'
@@ -479,7 +478,7 @@ export default class SettingsPage extends React.PureComponent {
</Checkbox>); </Checkbox>);
} }
if (process.platform === 'win32' || process.platform === 'linux') { if (window.process.platform === 'win32' || window.process.platform === 'linux') {
options.push( options.push(
<Checkbox <Checkbox
key='flashWindow' key='flashWindow'
@@ -495,7 +494,7 @@ export default class SettingsPage extends React.PureComponent {
</Checkbox>); </Checkbox>);
} }
if (process.platform === 'darwin') { if (window.process.platform === 'darwin') {
options.push( options.push(
<FormGroup <FormGroup
key='OptionsForm' key='OptionsForm'
@@ -545,7 +544,7 @@ export default class SettingsPage extends React.PureComponent {
); );
} }
if (process.platform === 'darwin' || process.platform === 'linux') { if (window.process.platform === 'darwin' || window.process.platform === 'linux') {
options.push( options.push(
<Checkbox <Checkbox
key='inputShowTrayIcon' key='inputShowTrayIcon'
@@ -554,14 +553,14 @@ export default class SettingsPage extends React.PureComponent {
checked={this.state.showTrayIcon} checked={this.state.showTrayIcon}
onChange={this.handleChangeShowTrayIcon} onChange={this.handleChangeShowTrayIcon}
> >
{process.platform === 'darwin' ? `Show ${this.state.appName} icon in the menu bar` : 'Show icon in the notification area'} {window.process.platform === 'darwin' ? `Show ${this.state.appName} icon in the menu bar` : 'Show icon in the notification area'}
<HelpBlock> <HelpBlock>
{'Setting takes effect after restarting the app.'} {'Setting takes effect after restarting the app.'}
</HelpBlock> </HelpBlock>
</Checkbox>); </Checkbox>);
} }
if (process.platform === 'linux') { if (window.process.platform === 'linux') {
options.push( options.push(
<FormGroup <FormGroup
key='trayIconTheme' key='trayIconTheme'
@@ -590,7 +589,7 @@ export default class SettingsPage extends React.PureComponent {
); );
} }
if (process.platform === 'linux') { if (window.process.platform === 'linux') {
options.push( options.push(
<Checkbox <Checkbox
key='inputMinimizeToTray' key='inputMinimizeToTray'

View File

@@ -3,7 +3,6 @@
// Copyright (c) 2015-2016 Yuya Ochiai // Copyright (c) 2015-2016 Yuya Ochiai
import React from 'react'; import React from 'react';
import {ipcRenderer} from 'electron';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import {Nav, NavItem} from 'react-bootstrap'; import {Nav, NavItem} from 'react-bootstrap';
import {Container, Draggable} from 'react-smooth-dnd'; import {Container, Draggable} from 'react-smooth-dnd';
@@ -20,7 +19,7 @@ export default class TabBar extends React.PureComponent { // need "this"
} }
componentDidMount() { componentDidMount() {
ipcRenderer.invoke(GET_CONFIGURATION).then((config) => { window.ipcRenderer.invoke(GET_CONFIGURATION).then((config) => {
this.setState({hasGPOTeams: config.registryTeams && config.registryTeams.length > 0}); this.setState({hasGPOTeams: config.registryTeams && config.registryTeams.length > 0});
}); });
} }

View File

@@ -2,9 +2,10 @@
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'">
<title><%= htmlWebpackPlugin.options.title %></title> <title><%= htmlWebpackPlugin.options.title %></title>
</head> </head>
<body> <body>
<div id="app" /> <div id="app" />
</body> </body>
</html> </html>

View File

@@ -5,17 +5,8 @@
import 'bootstrap/dist/css/bootstrap.min.css'; import 'bootstrap/dist/css/bootstrap.min.css';
import 'renderer/css/index.css'; import 'renderer/css/index.css';
if (process.env.NODE_ENV === 'production') {
window.eval = global.eval = () => { // eslint-disable-line no-multi-assign, no-eval
throw new Error('Sorry, Mattermost does not support window.eval() for security reasons.');
};
} else if (module.hot) {
module.hot.accept();
}
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import {ipcRenderer} from 'electron';
import {GET_CONFIGURATION, UPDATE_TEAMS, QUIT, RELOAD_CONFIGURATION} from 'common/communication'; import {GET_CONFIGURATION, UPDATE_TEAMS, QUIT, RELOAD_CONFIGURATION} from 'common/communication';
@@ -29,11 +20,11 @@ class Root extends React.PureComponent {
async componentDidMount() { async componentDidMount() {
await this.setInitialConfig(); await this.setInitialConfig();
ipcRenderer.on('synchronize-config', () => { window.ipcRenderer.on('synchronize-config', () => {
this.reloadConfig(); this.reloadConfig();
}); });
ipcRenderer.on(RELOAD_CONFIGURATION, () => { window.ipcRenderer.on(RELOAD_CONFIGURATION, () => {
this.reloadConfig(); this.reloadConfig();
}); });
@@ -72,7 +63,7 @@ class Root extends React.PureComponent {
}; };
teamConfigChange = async (updatedTeams, callback) => { teamConfigChange = async (updatedTeams, callback) => {
const updatedConfig = await ipcRenderer.invoke(UPDATE_TEAMS, updatedTeams); const updatedConfig = await window.ipcRenderer.invoke(UPDATE_TEAMS, updatedTeams);
await this.reloadConfig(); await this.reloadConfig();
if (callback) { if (callback) {
callback(updatedConfig); callback(updatedConfig);
@@ -87,20 +78,20 @@ class Root extends React.PureComponent {
requestConfig = async (exitOnError) => { requestConfig = async (exitOnError) => {
// todo: should we block? // todo: should we block?
try { try {
const configRequest = await ipcRenderer.invoke(GET_CONFIGURATION); const configRequest = await window.ipcRenderer.invoke(GET_CONFIGURATION);
return configRequest; return configRequest;
} catch (err) { } catch (err) {
console.log(`there was an error with the config: ${err}`); console.log(`there was an error with the config: ${err}`);
if (exitOnError) { if (exitOnError) {
ipcRenderer.send(QUIT, `unable to load configuration: ${err}`, err.stack); window.ipcRenderer.send(QUIT, `unable to load configuration: ${err}`, err.stack);
} }
} }
return null; return null;
}; };
openMenu = () => { openMenu = () => {
if (process.platform !== 'darwin') { if (window.process.platform !== 'darwin') {
ipcRenderer.send('open-app-menu'); window.ipcRenderer.send('open-app-menu');
} }
} }
@@ -122,7 +113,7 @@ class Root extends React.PureComponent {
); );
} }
} }
ipcRenderer.invoke('get-app-version').then(({name, version}) => { window.ipcRenderer.invoke('get-app-version').then(({name, version}) => {
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
console.log(`Starting ${name} v${version} commit: ${__HASH_VERSION__}`); console.log(`Starting ${name} v${version} commit: ${__HASH_VERSION__}`);
}); });

View File

@@ -2,28 +2,18 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
// Copyright (c) 2015-2016 Yuya Ochiai // Copyright (c) 2015-2016 Yuya Ochiai
import {ipcRenderer} from 'electron';
import 'bootstrap/dist/css/bootstrap.min.css'; import 'bootstrap/dist/css/bootstrap.min.css';
import 'renderer/css/index.css'; import 'renderer/css/index.css';
import 'renderer/css/settings.css'; import 'renderer/css/settings.css';
if (process.env.NODE_ENV === 'production') {
window.eval = global.eval = () => { // eslint-disable-line no-multi-assign, no-eval
throw new Error('Sorry, Mattermost does not support window.eval() for security reasons.');
};
} else if (module.hot) {
module.hot.accept();
}
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import SettingsPage from './components/SettingsPage.jsx'; import SettingsPage from './components/SettingsPage.jsx';
function openMenu() { function openMenu() {
if (process.platform !== 'darwin') { if (window.process.platform !== 'darwin') {
ipcRenderer.send('open-app-menu'); window.ipcRenderer.send('open-app-menu');
} }
} }

View File

@@ -25,17 +25,17 @@ class UpdaterPageContainer extends React.PureComponent {
} }
componentDidMount() { componentDidMount() {
ipcRenderer.on('start-download', () => { window.ipcRenderer.on('start-download', () => {
this.setState({ this.setState({
isDownloading: true, isDownloading: true,
}); });
}); });
ipcRenderer.on('progress', (event, progress) => { window.ipcRenderer.on('progress', (event, progress) => {
this.setState({ this.setState({
progress, progress,
}); });
}); });
ipcRenderer.on('zoom-in', () => { window.ipcRenderer.on('zoom-in', () => {
const activeTabWebContents = this.getTabWebContents(); const activeTabWebContents = this.getTabWebContents();
if (!activeTabWebContents) { if (!activeTabWebContents) {
return; return;
@@ -46,7 +46,7 @@ class UpdaterPageContainer extends React.PureComponent {
activeTabWebContents.zoomLevel += 1; activeTabWebContents.zoomLevel += 1;
}); });
ipcRenderer.on('zoom-out', () => { window.ipcRenderer.on('zoom-out', () => {
const activeTabWebContents = this.getTabWebContents(); const activeTabWebContents = this.getTabWebContents();
if (!activeTabWebContents) { if (!activeTabWebContents) {
return; return;
@@ -57,7 +57,7 @@ class UpdaterPageContainer extends React.PureComponent {
activeTabWebContents.zoomLevel -= 1; activeTabWebContents.zoomLevel -= 1;
}); });
ipcRenderer.on('zoom-reset', () => { window.ipcRenderer.on('zoom-reset', () => {
const activeTabWebContents = this.getTabWebContents(); const activeTabWebContents = this.getTabWebContents();
if (!activeTabWebContents) { if (!activeTabWebContents) {
return; return;
@@ -65,7 +65,7 @@ class UpdaterPageContainer extends React.PureComponent {
activeTabWebContents.zoomLevel = 0; activeTabWebContents.zoomLevel = 0;
}); });
ipcRenderer.on('undo', () => { window.ipcRenderer.on('undo', () => {
const activeTabWebContents = this.getTabWebContents(); const activeTabWebContents = this.getTabWebContents();
if (!activeTabWebContents) { if (!activeTabWebContents) {
return; return;
@@ -73,7 +73,7 @@ class UpdaterPageContainer extends React.PureComponent {
activeTabWebContents.undo(); activeTabWebContents.undo();
}); });
ipcRenderer.on('redo', () => { window.ipcRenderer.on('redo', () => {
const activeTabWebContents = this.getTabWebContents(); const activeTabWebContents = this.getTabWebContents();
if (!activeTabWebContents) { if (!activeTabWebContents) {
return; return;
@@ -81,7 +81,7 @@ class UpdaterPageContainer extends React.PureComponent {
activeTabWebContents.redo(); activeTabWebContents.redo();
}); });
ipcRenderer.on('cut', () => { window.ipcRenderer.on('cut', () => {
const activeTabWebContents = this.getTabWebContents(); const activeTabWebContents = this.getTabWebContents();
if (!activeTabWebContents) { if (!activeTabWebContents) {
return; return;
@@ -89,7 +89,7 @@ class UpdaterPageContainer extends React.PureComponent {
activeTabWebContents.cut(); activeTabWebContents.cut();
}); });
ipcRenderer.on('copy', () => { window.ipcRenderer.on('copy', () => {
const activeTabWebContents = this.getTabWebContents(); const activeTabWebContents = this.getTabWebContents();
if (!activeTabWebContents) { if (!activeTabWebContents) {
return; return;
@@ -97,7 +97,7 @@ class UpdaterPageContainer extends React.PureComponent {
activeTabWebContents.copy(); activeTabWebContents.copy();
}); });
ipcRenderer.on('paste', () => { window.ipcRenderer.on('paste', () => {
const activeTabWebContents = this.getTabWebContents(); const activeTabWebContents = this.getTabWebContents();
if (!activeTabWebContents) { if (!activeTabWebContents) {
return; return;
@@ -105,7 +105,7 @@ class UpdaterPageContainer extends React.PureComponent {
activeTabWebContents.paste(); activeTabWebContents.paste();
}); });
ipcRenderer.on('paste-and-match', () => { window.ipcRenderer.on('paste-and-match', () => {
const activeTabWebContents = this.getTabWebContents(); const activeTabWebContents = this.getTabWebContents();
if (!activeTabWebContents) { if (!activeTabWebContents) {
return; return;
@@ -121,22 +121,22 @@ class UpdaterPageContainer extends React.PureComponent {
notifyOnly={this.props.notifyOnly} notifyOnly={this.props.notifyOnly}
{...this.state} {...this.state}
onClickReleaseNotes={() => { onClickReleaseNotes={() => {
ipcRenderer.send('click-release-notes'); window.ipcRenderer.send('click-release-notes');
}} }}
onClickSkip={() => { onClickSkip={() => {
ipcRenderer.send('click-skip'); window.ipcRenderer.send('click-skip');
}} }}
onClickRemind={() => { onClickRemind={() => {
ipcRenderer.send('click-remind'); window.ipcRenderer.send('click-remind');
}} }}
onClickInstall={() => { onClickInstall={() => {
ipcRenderer.send('click-install'); window.ipcRenderer.send('click-install');
}} }}
onClickDownload={() => { onClickDownload={() => {
ipcRenderer.send('click-download'); window.ipcRenderer.send('click-download');
}} }}
onClickCancel={() => { onClickCancel={() => {
ipcRenderer.send('click-cancel'); window.ipcRenderer.send('click-cancel');
}} }}
/> />
); );

View File

@@ -17,6 +17,7 @@ const base = require('./webpack.config.base');
module.exports = merge(base, { module.exports = merge(base, {
entry: { entry: {
index: './src/main/main.js', index: './src/main/main.js',
mainWindow: './src/main/preload/mainWindow.js',
preload: './src/main/preload/mattermost.js', preload: './src/main/preload/mattermost.js',
modalPreload: './src/main/preload/modalPreload.js', modalPreload: './src/main/preload/modalPreload.js',
finderPreload: './src/main/preload/finderPreload.js', finderPreload: './src/main/preload/finderPreload.js',