Merge pull request #582 from yuya-oc/auto-updater

Implement auto updater for Windows and Mac
This commit is contained in:
Yuya Ochiai
2018-12-19 23:53:45 +09:00
committed by GitHub
21 changed files with 689 additions and 35 deletions

View File

@@ -593,7 +593,7 @@ A simple spellchecker compatible with Electron
means any form of the work other than Source Code Form. means any form of the work other than Source Code Form.
1.7. "Larger Work" 1.7. "Larger Work"
means a work that combines Covered Software with other material, in means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software. a separate file or files, that is not Covered Software.
1.8. "License" 1.8. "License"

View File

@@ -1,4 +1,8 @@
{ {
"publish": [{
"provider": "generic",
"url": "https://releases.mattermost.com/desktop/"
}],
"appId": "com.mattermost.desktop", "appId": "com.mattermost.desktop",
"artifactName": "${name}-${version}-${os}-${arch}.${ext}", "artifactName": "${name}-${version}-${os}-${arch}.${ext}",
"directories": { "directories": {
@@ -72,7 +76,7 @@
}, },
"win": { "win": {
"target": [ "target": [
"squirrel", "nsis",
"zip" "zip"
], ],
"extraFiles": [ "extraFiles": [
@@ -83,5 +87,8 @@
] ]
} }
] ]
},
"nsis": {
"artifactName": "${name}-setup-${version}-win.${ext}"
} }
} }

View File

@@ -28,10 +28,9 @@
"test": "npm-run-all test:* lint:*", "test": "npm-run-all test:* lint:*",
"test:app": "cross-env NODE_ENV=production npm run build && mocha -r @babel/register --reporter mocha-circleci-reporter --recursive test/specs", "test:app": "cross-env NODE_ENV=production npm run build && mocha -r @babel/register --reporter mocha-circleci-reporter --recursive test/specs",
"package:all": "cross-env NODE_ENV=production npm-run-all check-build-config package:windows package:mac package:linux", "package:all": "cross-env NODE_ENV=production npm-run-all check-build-config package:windows package:mac package:linux",
"package:windows": "cross-env NODE_ENV=production npm-run-all check-build-config build && build --win --x64 --ia32 --config.extraMetadata.name=mattermost --publish=never", "package:windows": "cross-env NODE_ENV=production npm-run-all check-build-config build && build --win --x64 --ia32 --publish=never",
"package:mac": "cross-env NODE_ENV=production npm-run-all check-build-config build && build --mac --publish=never", "package:mac": "cross-env NODE_ENV=production npm-run-all check-build-config build && build --mac --publish=never",
"package:linux": "cross-env NODE_ENV=production npm-run-all check-build-config build && build --linux --x64 --ia32 --config.extraMetadata.name=mattermost-desktop --publish=never", "package:linux": "cross-env NODE_ENV=production npm-run-all check-build-config build && build --linux --x64 --ia32 --publish=never",
"manipulate-windows-zip": "node scripts/manipulate_windows_zip.js",
"lint:js": "eslint --ignore-path .gitignore --ignore-pattern node_modules --ext .js --ext .jsx .", "lint:js": "eslint --ignore-path .gitignore --ignore-pattern node_modules --ext .js --ext .jsx .",
"fix:js": "eslint --ignore-path .gitignore --ignore-pattern node_modules --quiet --ext .js --ext .jsx . --fix", "fix:js": "eslint --ignore-path .gitignore --ignore-pattern node_modules --quiet --ext .js --ext .jsx . --fix",
"check-build-config": "node -r @babel/register scripts/check_build_config.js" "check-build-config": "node -r @babel/register scripts/check_build_config.js"

View File

@@ -5,11 +5,11 @@ VERSION=`cat package.json | jq -r '.version'`
SRC=$1 SRC=$1
DEST=$2 DEST=$2
cp "${SRC}/mattermost-${VERSION}-win-ia32.zip" "${DEST}/mattermost-desktop-${VERSION}-win32.zip" cp "${SRC}/mattermost-desktop-${VERSION}-win-ia32.zip" "${DEST}/mattermost-desktop-${VERSION}-win32.zip"
cp "${SRC}/mattermost-${VERSION}-win-x64.zip" "${DEST}/mattermost-desktop-${VERSION}-win64.zip" cp "${SRC}/mattermost-desktop-${VERSION}-win-x64.zip" "${DEST}/mattermost-desktop-${VERSION}-win64.zip"
cp "${SRC}/squirrel-windows/mattermost-setup-${VERSION}.exe" "${DEST}/mattermost-setup-${VERSION}-win64.exe" cp "${SRC}/mattermost-desktop-setup-${VERSION}-win.exe" "${DEST}/"
cp "${SRC}/squirrel-windows-ia32/mattermost-setup-${VERSION}-ia32.exe" "${DEST}/mattermost-setup-${VERSION}-win32.exe"
cp "${SRC}"/mattermost-desktop-*.zip "${DEST}/" cp "${SRC}"/mattermost-desktop-*.zip "${DEST}/"
cp "${SRC}"/*.tar.gz "${DEST}/" cp "${SRC}"/*.tar.gz "${DEST}/"
cp "${SRC}"/*.deb "${DEST}/" cp "${SRC}"/*.deb "${DEST}/"
cp "${SRC}"/*.AppImage "${DEST}/" cp "${SRC}"/*.AppImage "${DEST}/"
cp "${SRC}"/*.yml "${DEST}/"

View File

@@ -4,24 +4,30 @@
'use strict'; 'use strict';
const spawnSync = require('child_process').spawnSync; const spawnSync = require('child_process').spawnSync;
const path = require('path');
const path7za = require('7zip-bin').path7za; const path7za = require('7zip-bin').path7za;
const pkg = require('../src/package.json'); const pkg = require('../src/package.json');
const appVersion = pkg.version; const appVersion = pkg.version;
const productName = pkg.productName; const name = pkg.name;
function renameInZip(zipPath, oldName, newName) { function disableInstallUpdate(zipPath) {
const result = spawnSync(path7za, ['rn', zipPath, oldName, newName]); const zipFullPath = path.resolve(__dirname, '..', zipPath);
return result.status === 0; const appUpdaterConfigFile = 'app-updater-config.json';
const addResult = spawnSync(path7za, ['a', zipFullPath, appUpdaterConfigFile], {cwd: 'resources/windows'});
if (addResult.status !== 0) {
throw new Error(`7za a returned non-zero exit code for ${zipPath}`);
}
const renameResult = spawnSync(path7za, ['rn', zipFullPath, appUpdaterConfigFile, `resources/${appUpdaterConfigFile}`]);
if (renameResult.status !== 0) {
throw new Error(`7za rn returned non-zero exit code for ${zipPath}`);
}
} }
console.log('Manipulating 64-bit zip...'); console.log('Manipulating 64-bit zip...');
if (!renameInZip(`release/${productName}-${appVersion}-win.zip`, 'win-unpacked', `${productName}-${appVersion}-win64`)) { disableInstallUpdate(`release/${name}-${appVersion}-win-x64.zip`);
throw new Error('7za returned non-zero exit code for 64-bit zip');
}
console.log('Manipulating 32-bit zip...'); console.log('Manipulating 32-bit zip...');
if (!renameInZip(`release/${productName}-${appVersion}-ia32-win.zip`, 'win-ia32-unpacked', `${productName}-${appVersion}-win32`)) { disableInstallUpdate(`release/${name}-${appVersion}-win-ia32.zip`);
throw new Error('7za returned non-zero exit code for 32-bit zip');
}

View File

@@ -0,0 +1,104 @@
// Copyright (c) 2015-2016 Yuya Ochiai
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import propTypes from 'prop-types';
import {Button, Navbar, ProgressBar} from 'react-bootstrap';
function InstallButton(props) {
if (props.notifyOnly) {
return (
<Button
bsStyle='primary'
onClick={props.onClickDownload}
>{'Download Update'}</Button>
);
}
return (
<Button
bsStyle='primary'
onClick={props.onClickInstall}
>{'Install Update'}</Button>
);
}
InstallButton.propTypes = {
notifyOnly: propTypes.bool.isRequired,
onClickInstall: propTypes.func.isRequired,
onClickDownload: propTypes.func.isRequired,
};
function UpdaterPage(props) {
return (
<div className='UpdaterPage'>
<Navbar fluid={true} >
<h1 className='UpdaterPage-heading'>{'New update is available'}</h1>
</Navbar>
<div className='container-fluid'>
<p>{`A new version of the ${props.appName} is available!`}</p>
<p>{'Read the '}
<a
href='#'
onClick={props.onClickReleaseNotes}
>{'release notes'}</a>
{' to learn more.'}
</p>
</div>
{props.isDownloading ?
<Navbar
className='UpdaterPage-footer'
fixedBottom={true}
fluid={true}
>
<ProgressBar
active={true}
now={props.progress}
label={`${props.progress}%`}
/>
<div className='pull-right'>
<Button
onClick={props.onClickCancel}
>{'Cancel'}</Button>
</div>
</Navbar> :
<Navbar
className='UpdaterPage-footer'
fixedBottom={true}
fluid={true}
>
<Button
className='UpdaterPage-skipButton'
bsStyle='link'
onClick={props.onClickSkip}
>{'Skip this version'}</Button>
<div className='pull-right'>
<Button
bsStyle='link'
onClick={props.onClickRemind}
>{'Remind me in 2 days'}</Button>
<InstallButton
notifyOnly={props.notifyOnly}
onClickInstall={props.onClickInstall}
onClickDownload={props.onClickDownload}
/>
</div>
</Navbar>
}
</div>
);
}
UpdaterPage.propTypes = {
appName: propTypes.string.isRequired,
notifyOnly: propTypes.bool.isRequired,
isDownloading: propTypes.bool.isRequired,
progress: propTypes.number,
onClickInstall: propTypes.func.isRequired,
onClickDownload: propTypes.func.isRequired,
onClickReleaseNotes: propTypes.func.isRequired,
onClickRemind: propTypes.func.isRequired,
onClickSkip: propTypes.func.isRequired,
onClickCancel: propTypes.func.isRequired,
};
export default UpdaterPage;

View File

@@ -0,0 +1,53 @@
// Copyright (c) 2015-2016 Yuya Ochiai
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {storiesOf} from '@storybook/react';
import {action} from '@storybook/addon-actions';
import UpdaterPage from '../UpdaterPage.jsx';
import '../../css/components/UpdaterPage.css';
/*
appName: propTypes.string.isRequired,
notifyOnly: propTypes.bool.isRequired,
isDownloading: propTypes.bool.isRequired,
progress: propTypes.number,
onClickInstall: propTypes.func.isRequired,
onClickDownload: propTypes.func.isRequired,
onClickReleaseNotes: propTypes.func.isRequired,
onClickRemind: propTypes.func.isRequired,
onClickSkip: propTypes.func.isRequired,
*/
const appName = 'Storybook App';
storiesOf('UpdaterPage', module).
add('Normal', () => (
<UpdaterPage
appName={appName}
notifyOnly={false}
isDownloading={false}
progress={0}
onClickInstall={action('clicked install')}
onClickReleaseNotes={action('clicked release notes')}
onClickRemind={action('clicked remind')}
onClickSkip={action('clicked skip')}
/>
)).
add('NotifyOnly', () => (
<UpdaterPage
appName={appName}
notifyOnly={true}
onClickDownload={action('clicked download')}
/>
)).
add('Downloading', () => (
<UpdaterPage
appName={appName}
isDownloading={true}
progress={0}
onClickCancel={action('clicked cancel')}
/>
));

View File

@@ -0,0 +1,16 @@
.UpdaterPage-heading {
font-size: 20pt;
margin: 10px 0px;
}
.UpdaterPage-skipButton {
padding-left: 0px;
}
.UpdaterPage-footer {
padding: 1em 0;
}
.UpdaterPage .progress-bar {
min-width: 2em;
}

View File

@@ -7,3 +7,4 @@
@import url("TabBar.css"); @import url("TabBar.css");
@import url("TeamListItem.css"); @import url("TeamListItem.css");
@import url("Finder.css"); @import url("Finder.css");
@import url("UpdaterPage.css");

13
src/browser/updater.html Normal file
View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Updater</title>
<link rel="stylesheet" href="../node_modules/bootstrap/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="css/components/index.css">
</head>
<body>
<div id="content"></div>
<script src="updater_bundle.js"></script>
</body>
</html>

75
src/browser/updater.jsx Normal file
View File

@@ -0,0 +1,75 @@
// Copyright (c) 2015-2016 Yuya Ochiai
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import url from 'url';
import React from 'react';
import ReactDOM from 'react-dom';
import propTypes from 'prop-types';
import {ipcRenderer, remote} from 'electron';
import UpdaterPage from './components/UpdaterPage.jsx';
const thisURL = url.parse(location.href, true);
const notifyOnly = thisURL.query.notifyOnly === 'true';
class UpdaterPageContainer extends React.Component {
constructor(props) {
super(props);
this.state = props.initialState;
}
componentDidMount() {
ipcRenderer.on('start-download', () => {
this.setState({
isDownloading: true,
});
});
ipcRenderer.on('progress', (event, progress) => {
this.setState({
progress,
});
});
}
render() {
return (
<UpdaterPage
appName={`${remote.app.getName()} Desktop App`}
notifyOnly={this.props.notifyOnly}
{...this.state}
onClickReleaseNotes={() => {
ipcRenderer.send('click-release-notes');
}}
onClickSkip={() => {
ipcRenderer.send('click-skip');
}}
onClickRemind={() => {
ipcRenderer.send('click-remind');
}}
onClickInstall={() => {
ipcRenderer.send('click-install');
}}
onClickDownload={() => {
ipcRenderer.send('click-download');
}}
onClickCancel={() => {
ipcRenderer.send('click-cancel');
}}
/>
);
}
}
UpdaterPageContainer.propTypes = {
notifyOnly: propTypes.bool,
initialState: propTypes.object,
};
ReactDOM.render(
<UpdaterPageContainer
notifyOnly={notifyOnly}
initialState={{isDownloading: false, progress: 0}}
/>,
document.getElementById('content')
);

View File

@@ -24,6 +24,7 @@ const buildConfig = {
], ],
helpLink: 'https://about.mattermost.com/default-desktop-app-documentation/', helpLink: 'https://about.mattermost.com/default-desktop-app-documentation/',
enableServerManagement: true, enableServerManagement: true,
enableAutoUpdater: true,
}; };
export default buildConfig; export default buildConfig;

2
src/dev-app-update.yml Normal file
View File

@@ -0,0 +1,2 @@
provider: generic
url: 'http://localhost:8081/'

View File

@@ -23,9 +23,11 @@ import {parse as parseArgv} from 'yargs';
import {protocols} from '../electron-builder.json'; import {protocols} from '../electron-builder.json';
import squirrelStartup from './main/squirrelStartup';
import AutoLauncher from './main/AutoLauncher'; import AutoLauncher from './main/AutoLauncher';
import CriticalErrorHandler from './main/CriticalErrorHandler'; import CriticalErrorHandler from './main/CriticalErrorHandler';
import upgradeAutoLaunch from './main/autoLaunch';
import autoUpdater from './main/autoUpdater';
import buildConfig from './common/config/buildConfig';
const criticalErrorHandler = new CriticalErrorHandler(); const criticalErrorHandler = new CriticalErrorHandler();
@@ -34,11 +36,7 @@ process.on('uncaughtException', criticalErrorHandler.processUncaughtExceptionHan
global.willAppQuit = false; global.willAppQuit = false;
app.setAppUserModelId('com.squirrel.mattermost.Mattermost'); // Use explicit AppUserModelID app.setAppUserModelId('com.squirrel.mattermost.Mattermost'); // Use explicit AppUserModelID
if (squirrelStartup(() => {
app.quit();
})) {
global.willAppQuit = true;
}
import settings from './common/settings'; import settings from './common/settings';
import CertificateStore from './main/certificateStore'; import CertificateStore from './main/certificateStore';
const certificateStore = CertificateStore.load(path.resolve(app.getPath('userData'), 'certificate.json')); const certificateStore = CertificateStore.load(path.resolve(app.getPath('userData'), 'certificate.json'));
@@ -419,6 +417,10 @@ app.on('ready', () => {
} }
appState.lastAppVersion = app.getVersion(); appState.lastAppVersion = app.getVersion();
if (!global.isDev) {
upgradeAutoLaunch();
}
if (global.isDev) { if (global.isDev) {
installExtension(REACT_DEVELOPER_TOOLS). installExtension(REACT_DEVELOPER_TOOLS).
then((name) => console.log(`Added Extension: ${name}`)). then((name) => console.log(`Added Extension: ${name}`)).
@@ -456,6 +458,9 @@ app.on('ready', () => {
mainWindow.webContents.on('crashed', () => { mainWindow.webContents.on('crashed', () => {
throw new Error('webContents \'crashed\' event has been emitted'); throw new Error('webContents \'crashed\' event has been emitted');
}); });
mainWindow.on('ready-to-show', () => {
autoUpdater.checkForUpdates();
});
ipcMain.on('notified', () => { ipcMain.on('notified', () => {
if (process.platform === 'win32' || process.platform === 'linux') { if (process.platform === 'win32' || process.platform === 'linux') {
@@ -650,6 +655,21 @@ app.on('ready', () => {
permissionManager = new PermissionManager(permissionFile, trustedURLs); permissionManager = new PermissionManager(permissionFile, trustedURLs);
session.defaultSession.setPermissionRequestHandler(permissionRequestHandler(mainWindow, permissionManager)); session.defaultSession.setPermissionRequestHandler(permissionRequestHandler(mainWindow, permissionManager));
if (buildConfig.enableAutoUpdater) {
const updaterConfig = autoUpdater.loadConfig();
autoUpdater.initialize(appState, mainWindow, updaterConfig.isNotifyOnly());
ipcMain.on('check-for-updates', autoUpdater.checkForUpdates);
mainWindow.once('show', () => {
if (autoUpdater.shouldCheckForUpdatesOnStart(appState.updateCheckedDate)) {
ipcMain.emit('check-for-updates');
} else {
setTimeout(() => {
ipcMain.emit('check-for-updates');
}, autoUpdater.UPDATER_INTERVAL_IN_MS);
}
});
}
// Open the DevTools. // Open the DevTools.
// mainWindow.openDevTools(); // mainWindow.openDevTools();
}); });

View File

@@ -11,4 +11,24 @@ export default class AppStateManager extends JsonFileManager {
get lastAppVersion() { get lastAppVersion() {
return this.getValue('lastAppVersion'); return this.getValue('lastAppVersion');
} }
set skippedVersion(version) {
this.setValue('skippedVersion', version);
}
get skippedVersion() {
return this.getValue('skippedVersion');
}
set updateCheckedDate(date) {
this.setValue('updateCheckedDate', date.toISOString());
}
get updateCheckedDate() {
const date = this.getValue('updateCheckedDate');
if (date) {
return new Date(date);
}
return null;
}
} }

20
src/main/autoLaunch.js Normal file
View File

@@ -0,0 +1,20 @@
// Copyright (c) 2015-2016 Yuya Ochiai
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import AutoLaunch from 'auto-launch';
async function upgradeAutoLaunch() {
if (process.platform === 'darwin') {
return;
}
const appLauncher = new AutoLaunch({
name: 'Mattermost',
isHidden: true,
});
const enabled = await appLauncher.isEnabled();
if (enabled) {
await appLauncher.enable();
}
}
export default upgradeAutoLaunch;

194
src/main/autoUpdater.js Normal file
View File

@@ -0,0 +1,194 @@
// Copyright (c) 2015-2016 Yuya Ochiai
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import path from 'path';
import {app, BrowserWindow, dialog, ipcMain, shell} from 'electron';
import logger from 'electron-log';
import {autoUpdater, CancellationToken} from 'electron-updater';
import semver from 'semver';
// eslint-disable-next-line no-magic-numbers
const UPDATER_INTERVAL_IN_MS = 48 * 60 * 60 * 1000; // 48 hours
autoUpdater.logger = logger;
autoUpdater.logger.transports.file.level = 'info';
let updaterModal = null;
function createEventListener(win, eventName) {
return (event) => {
if (event.sender === win.webContents) {
win.emit(eventName);
}
};
}
function createUpdaterModal(parentWindow, options) {
const windowWidth = 480;
const windowHeight = 280;
const windowOptions = {
title: `${app.getName()} Updater`,
parent: parentWindow,
modal: true,
maximizable: false,
show: false,
width: windowWidth,
height: windowHeight,
resizable: false,
autoHideMenuBar: true,
};
if (process.platform === 'linux') {
windowOptions.icon = options.linuxAppIcon;
}
const modal = new BrowserWindow(windowOptions);
modal.once('ready-to-show', () => {
modal.show();
});
let updaterURL = (global.isDev ? 'http://localhost:8080' : `file://${app.getAppPath()}`) + '/browser/updater.html';
if (options.notifyOnly) {
updaterURL += '?notifyOnly=true';
}
modal.loadURL(updaterURL);
for (const eventName of ['click-release-notes', 'click-skip', 'click-remind', 'click-install', 'click-download', 'click-cancel']) {
const listener = createEventListener(modal, eventName);
ipcMain.on(eventName, listener);
modal.on('closed', () => {
ipcMain.removeListener(eventName, listener);
});
}
return modal;
}
function isUpdateApplicable(now, skippedVersion, updateInfo) {
const releaseTime = new Date(updateInfo.releaseDate).getTime();
// 48 hours after a new version is added to releases.mattermost.com, user receives a “New update is available” dialog
if (now.getTime() - releaseTime < UPDATER_INTERVAL_IN_MS) {
return false;
}
// If a version was skipped, compare version.
if (skippedVersion) {
return semver.gt(updateInfo.version, skippedVersion);
}
return true;
}
function downloadAndInstall(cancellationToken) {
autoUpdater.on('update-downloaded', () => {
global.willAppQuit = true;
autoUpdater.quitAndInstall();
});
autoUpdater.downloadUpdate(cancellationToken);
}
function initialize(appState, mainWindow, notifyOnly = false) {
autoUpdater.autoDownload = false; // To prevent upgrading on quit
const assetsDir = path.resolve(app.getAppPath(), 'assets');
autoUpdater.on('error', (err) => {
console.error('Error in autoUpdater:', err.message);
}).on('update-available', (info) => {
let cancellationToken = null;
if (isUpdateApplicable(new Date(), appState.skippedVersion, info)) {
updaterModal = createUpdaterModal(mainWindow, {
linuxAppIcon: path.join(assetsDir, 'appicon.png'),
notifyOnly,
});
updaterModal.on('closed', () => {
updaterModal = null;
});
updaterModal.on('click-skip', () => {
appState.skippedVersion = info.version;
updaterModal.close();
}).on('click-remind', () => {
appState.updateCheckedDate = new Date();
setTimeout(() => { // eslint-disable-line max-nested-callbacks
autoUpdater.checkForUpdates();
}, UPDATER_INTERVAL_IN_MS);
updaterModal.close();
}).on('click-install', () => {
updaterModal.webContents.send('start-download');
autoUpdater.signals.progress((data) => { // eslint-disable-line max-nested-callbacks
updaterModal.send('progress', Math.floor(data.percent));
console.log('progress:', data);
});
cancellationToken = new CancellationToken();
downloadAndInstall(cancellationToken);
}).on('click-download', () => {
shell.openExternal('https://about.mattermost.com/download/#mattermostApps');
}).on('click-release-notes', () => {
shell.openExternal(`https://github.com/mattermost/desktop/releases/v${info.version}`);
}).on('click-cancel', () => {
cancellationToken.cancel();
updaterModal.close();
});
updaterModal.focus();
} else if (autoUpdater.isManual) {
autoUpdater.emit('update-not-available');
}
}).on('update-not-available', () => {
if (autoUpdater.isManual) {
dialog.showMessageBox(mainWindow, {
type: 'info',
buttons: ['Close'],
title: 'Your Desktop App is up to date',
message: 'You have the latest version of the Mattermost Desktop App.',
}, () => {}); // eslint-disable-line no-empty-function
}
setTimeout(() => {
autoUpdater.checkForUpdates();
}, UPDATER_INTERVAL_IN_MS);
});
}
function shouldCheckForUpdatesOnStart(updateCheckedDate) {
if (updateCheckedDate) {
if (Date.now() - updateCheckedDate.getTime() < UPDATER_INTERVAL_IN_MS) {
return false;
}
}
return true;
}
function checkForUpdates(isManual = false) {
autoUpdater.isManual = isManual;
if (!updaterModal) {
autoUpdater.checkForUpdates();
}
}
class AutoUpdaterConfig {
constructor() {
this.data = {};
}
isNotifyOnly() {
if (process.platform === 'win32') {
return true;
}
if (this.data.notifyOnly === true) {
return true;
}
return false;
}
}
function loadConfig() {
return new AutoUpdaterConfig();
}
export default {
UPDATER_INTERVAL_IN_MS,
checkForUpdates,
shouldCheckForUpdatesOnStart,
initialize,
loadConfig,
};

View File

@@ -3,7 +3,7 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
'use strict'; 'use strict';
import {app, dialog, Menu, shell} from 'electron'; import {app, dialog, ipcMain, Menu, shell} from 'electron';
import settings from '../../common/settings'; import settings from '../../common/settings';
import buildConfig from '../../common/config/buildConfig'; import buildConfig from '../../common/config/buildConfig';
@@ -231,6 +231,14 @@ function createTemplate(mainWindow, config, isDev) {
label: `Version ${app.getVersion()}`, label: `Version ${app.getVersion()}`,
enabled: false, enabled: false,
}); });
if (buildConfig.enableAutoUpdater) {
submenu.push({
label: 'Check for Updates...',
click() {
ipcMain.emit('check-for-updates', true);
},
});
}
template.push({label: '&Help', submenu}); template.push({label: '&Help', submenu});
return template; return template;
} }

View File

@@ -14,12 +14,14 @@
"electron-context-menu": "0.9.0", "electron-context-menu": "0.9.0",
"electron-devtools-installer": "^2.2.4", "electron-devtools-installer": "^2.2.4",
"electron-is-dev": "^0.3.0", "electron-is-dev": "^0.3.0",
"electron-squirrel-startup": "^1.0.0", "electron-log": "^2.2.15",
"electron-updater": "2.21.10",
"prop-types": "^15.6.1", "prop-types": "^15.6.1",
"react": "^16.4.0", "react": "^16.4.0",
"react-bootstrap": "~0.32.1", "react-bootstrap": "~0.32.1",
"react-dom": "^16.4.0", "react-dom": "^16.4.0",
"react-transition-group": "^2.3.1", "react-transition-group": "^2.3.1",
"semver": "^5.5.0",
"simple-spellchecker": "^0.9.6", "simple-spellchecker": "^0.9.6",
"underscore": "^1.9.1", "underscore": "^1.9.1",
"yargs": "^3.32.0" "yargs": "^3.32.0"

View File

@@ -14,6 +14,12 @@ applescript@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/applescript/-/applescript-1.0.0.tgz#bb87af568cad034a4e48c4bdaf6067a3a2701317" resolved "https://registry.yarnpkg.com/applescript/-/applescript-1.0.0.tgz#bb87af568cad034a4e48c4bdaf6067a3a2701317"
argparse@^1.0.7:
version "1.0.9"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86"
dependencies:
sprintf-js "~1.0.2"
asap@~2.0.3: asap@~2.0.3:
version "2.0.6" version "2.0.6"
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
@@ -50,6 +56,16 @@ binarysearch@^0.2.4:
version "0.2.4" version "0.2.4"
resolved "https://registry.yarnpkg.com/binarysearch/-/binarysearch-0.2.4.tgz#46ef3e03fd4529e9328662e68e40328e7a0bf2ac" resolved "https://registry.yarnpkg.com/binarysearch/-/binarysearch-0.2.4.tgz#46ef3e03fd4529e9328662e68e40328e7a0bf2ac"
bluebird-lst@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/bluebird-lst/-/bluebird-lst-1.0.5.tgz#bebc83026b7e92a72871a3dc599e219cbfb002a9"
dependencies:
bluebird "^3.5.1"
bluebird@^3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9"
bootstrap@^3.3.7: bootstrap@^3.3.7:
version "3.3.7" version "3.3.7"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-3.3.7.tgz#5a389394549f23330875a3b150656574f8a9eb71" resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-3.3.7.tgz#5a389394549f23330875a3b150656574f8a9eb71"
@@ -61,10 +77,23 @@ brace-expansion@^1.1.7:
balanced-match "^1.0.0" balanced-match "^1.0.0"
concat-map "0.0.1" concat-map "0.0.1"
buffer-from@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.0.tgz#87fcaa3a298358e0ade6e442cfce840740d1ad04"
buffers@~0.1.1: buffers@~0.1.1:
version "0.1.1" version "0.1.1"
resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb"
builder-util-runtime@~4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-4.2.1.tgz#0caa358f1331d70680010141ca591952b69b35bc"
dependencies:
bluebird-lst "^1.0.5"
debug "^3.1.0"
fs-extra-p "^4.6.0"
sax "^1.2.4"
camelcase@^2.0.1: camelcase@^2.0.1:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
@@ -115,9 +144,9 @@ cross-unzip@0.0.2:
version "0.0.1" version "0.0.1"
resolved "git://github.com/cbaatz/damerau-levenshtein.git#8d7440a51ae9dc6912e44385115c7cb1da4e8ebc" resolved "git://github.com/cbaatz/damerau-levenshtein.git#8d7440a51ae9dc6912e44385115c7cb1da4e8ebc"
debug@^2.2.0: debug@^3.1.0:
version "2.6.8" version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
dependencies: dependencies:
ms "2.0.0" ms "2.0.0"
@@ -165,11 +194,23 @@ electron-is-dev@^0.3.0:
version "0.3.0" version "0.3.0"
resolved "https://registry.yarnpkg.com/electron-is-dev/-/electron-is-dev-0.3.0.tgz#14e6fda5c68e9e4ecbeff9ccf037cbd7c05c5afe" resolved "https://registry.yarnpkg.com/electron-is-dev/-/electron-is-dev-0.3.0.tgz#14e6fda5c68e9e4ecbeff9ccf037cbd7c05c5afe"
electron-squirrel-startup@^1.0.0: electron-log@^2.2.15:
version "1.0.0" version "2.2.15"
resolved "https://registry.yarnpkg.com/electron-squirrel-startup/-/electron-squirrel-startup-1.0.0.tgz#19b4e55933fa0ef8f556784b9c660f772546a0b8" resolved "https://registry.yarnpkg.com/electron-log/-/electron-log-2.2.15.tgz#28c2dd65c9437fcf31f09f5f856c338a83daab76"
electron-updater@2.21.10:
version "2.21.10"
resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-2.21.10.tgz#aa66757ebf966f4247f247a8433af45cfe8e93b0"
dependencies: dependencies:
debug "^2.2.0" bluebird-lst "^1.0.5"
builder-util-runtime "~4.2.1"
electron-is-dev "^0.3.0"
fs-extra-p "^4.6.0"
js-yaml "^3.11.0"
lazy-val "^1.0.3"
lodash.isequal "^4.5.0"
semver "^5.5.0"
source-map-support "^0.5.5"
encoding@^0.1.11: encoding@^0.1.11:
version "0.1.12" version "0.1.12"
@@ -177,6 +218,10 @@ encoding@^0.1.11:
dependencies: dependencies:
iconv-lite "~0.4.13" iconv-lite "~0.4.13"
esprima@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804"
ext-list@^2.0.0: ext-list@^2.0.0:
version "2.2.2" version "2.2.2"
resolved "https://registry.yarnpkg.com/ext-list/-/ext-list-2.2.2.tgz#0b98e64ed82f5acf0f2931babf69212ef52ddd37" resolved "https://registry.yarnpkg.com/ext-list/-/ext-list-2.2.2.tgz#0b98e64ed82f5acf0f2931babf69212ef52ddd37"
@@ -214,6 +259,21 @@ fbjs@^0.8.9:
setimmediate "^1.0.5" setimmediate "^1.0.5"
ua-parser-js "^0.7.9" ua-parser-js "^0.7.9"
fs-extra-p@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/fs-extra-p/-/fs-extra-p-4.6.0.tgz#c7b7117f0dcf8a99c9b2ed589067c960abcf3ef9"
dependencies:
bluebird-lst "^1.0.5"
fs-extra "^6.0.0"
fs-extra@^6.0.0:
version "6.0.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-6.0.1.tgz#8abc128f7946e310135ddc93b98bddb410e7a34b"
dependencies:
graceful-fs "^4.1.2"
jsonfile "^4.0.0"
universalify "^0.1.0"
fs.realpath@^1.0.0: fs.realpath@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@@ -229,6 +289,10 @@ glob@^7.0.5:
once "^1.3.0" once "^1.3.0"
path-is-absolute "^1.0.0" path-is-absolute "^1.0.0"
graceful-fs@^4.1.2, graceful-fs@^4.1.6:
version "4.1.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
iconv-lite@~0.4.13: iconv-lite@~0.4.13:
version "0.4.18" version "0.4.18"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.18.tgz#23d8656b16aae6742ac29732ea8f0336a4789cf2" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.18.tgz#23d8656b16aae6742ac29732ea8f0336a4789cf2"
@@ -283,16 +347,37 @@ js-tokens@^3.0.0:
version "3.0.2" version "3.0.2"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
js-yaml@^3.11.0:
version "3.12.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1"
dependencies:
argparse "^1.0.7"
esprima "^4.0.0"
jsonfile@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
optionalDependencies:
graceful-fs "^4.1.6"
keycode@^2.1.2: keycode@^2.1.2:
version "2.1.9" version "2.1.9"
resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.1.9.tgz#964a23c54e4889405b4861a5c9f0480d45141dfa" resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.1.9.tgz#964a23c54e4889405b4861a5c9f0480d45141dfa"
lazy-val@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.3.tgz#bb97b200ef00801d94c317e29dc6ed39e31c5edc"
lcid@^1.0.0: lcid@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
dependencies: dependencies:
invert-kv "^1.0.0" invert-kv "^1.0.0"
lodash.isequal@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1: loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1:
version "1.3.1" version "1.3.1"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
@@ -482,10 +567,18 @@ rimraf@^2.5.2:
dependencies: dependencies:
glob "^7.0.5" glob "^7.0.5"
sax@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
semver@^5.3.0: semver@^5.3.0:
version "5.4.1" version "5.4.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e"
semver@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
setimmediate@^1.0.5: setimmediate@^1.0.5:
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
@@ -511,6 +604,21 @@ sort-keys@^1.0.0:
dependencies: dependencies:
is-plain-obj "^1.0.0" is-plain-obj "^1.0.0"
source-map-support@^0.5.5:
version "0.5.6"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.6.tgz#4435cee46b1aab62b8e8610ce60f788091c51c13"
dependencies:
buffer-from "^1.0.0"
source-map "^0.6.0"
source-map@^0.6.0:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
string-width@^1.0.1: string-width@^1.0.1:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
@@ -549,6 +657,10 @@ underscore@^1.9.1:
version "1.9.1" version "1.9.1"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961"
universalify@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7"
untildify@^3.0.2: untildify@^3.0.2:
version "3.0.2" version "3.0.2"
resolved "https://registry.yarnpkg.com/untildify/-/untildify-3.0.2.tgz#7f1f302055b3fea0f3e81dc78eb36766cb65e3f1" resolved "https://registry.yarnpkg.com/untildify/-/untildify-3.0.2.tgz#7f1f302055b3fea0f3e81dc78eb36766cb65e3f1"

View File

@@ -15,6 +15,7 @@ module.exports = merge(base, {
entry: { entry: {
index: './src/browser/index.jsx', index: './src/browser/index.jsx',
settings: './src/browser/settings.jsx', settings: './src/browser/settings.jsx',
updater: './src/browser/updater.jsx',
'webview/mattermost': './src/browser/webview/mattermost.js', 'webview/mattermost': './src/browser/webview/mattermost.js',
}, },
output: { output: {