Merge pull request #582 from yuya-oc/auto-updater
Implement auto updater for Windows and Mac
This commit is contained in:
@@ -593,7 +593,7 @@ A simple spellchecker compatible with Electron
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
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.
|
||||
|
||||
1.8. "License"
|
||||
|
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"publish": [{
|
||||
"provider": "generic",
|
||||
"url": "https://releases.mattermost.com/desktop/"
|
||||
}],
|
||||
"appId": "com.mattermost.desktop",
|
||||
"artifactName": "${name}-${version}-${os}-${arch}.${ext}",
|
||||
"directories": {
|
||||
@@ -72,7 +76,7 @@
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
"squirrel",
|
||||
"nsis",
|
||||
"zip"
|
||||
],
|
||||
"extraFiles": [
|
||||
@@ -83,5 +87,8 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"nsis": {
|
||||
"artifactName": "${name}-setup-${version}-win.${ext}"
|
||||
}
|
||||
}
|
||||
|
@@ -28,10 +28,9 @@
|
||||
"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",
|
||||
"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:linux": "cross-env NODE_ENV=production npm-run-all check-build-config build && build --linux --x64 --ia32 --config.extraMetadata.name=mattermost-desktop --publish=never",
|
||||
"manipulate-windows-zip": "node scripts/manipulate_windows_zip.js",
|
||||
"package:linux": "cross-env NODE_ENV=production npm-run-all check-build-config build && build --linux --x64 --ia32 --publish=never",
|
||||
"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",
|
||||
"check-build-config": "node -r @babel/register scripts/check_build_config.js"
|
||||
|
@@ -5,11 +5,11 @@ VERSION=`cat package.json | jq -r '.version'`
|
||||
SRC=$1
|
||||
DEST=$2
|
||||
|
||||
cp "${SRC}/mattermost-${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}/squirrel-windows/mattermost-setup-${VERSION}.exe" "${DEST}/mattermost-setup-${VERSION}-win64.exe"
|
||||
cp "${SRC}/squirrel-windows-ia32/mattermost-setup-${VERSION}-ia32.exe" "${DEST}/mattermost-setup-${VERSION}-win32.exe"
|
||||
cp "${SRC}/mattermost-desktop-${VERSION}-win-ia32.zip" "${DEST}/mattermost-desktop-${VERSION}-win32.zip"
|
||||
cp "${SRC}/mattermost-desktop-${VERSION}-win-x64.zip" "${DEST}/mattermost-desktop-${VERSION}-win64.zip"
|
||||
cp "${SRC}/mattermost-desktop-setup-${VERSION}-win.exe" "${DEST}/"
|
||||
cp "${SRC}"/mattermost-desktop-*.zip "${DEST}/"
|
||||
cp "${SRC}"/*.tar.gz "${DEST}/"
|
||||
cp "${SRC}"/*.deb "${DEST}/"
|
||||
cp "${SRC}"/*.AppImage "${DEST}/"
|
||||
cp "${SRC}"/*.yml "${DEST}/"
|
||||
|
@@ -4,24 +4,30 @@
|
||||
'use strict';
|
||||
|
||||
const spawnSync = require('child_process').spawnSync;
|
||||
const path = require('path');
|
||||
|
||||
const path7za = require('7zip-bin').path7za;
|
||||
|
||||
const pkg = require('../src/package.json');
|
||||
const appVersion = pkg.version;
|
||||
const productName = pkg.productName;
|
||||
const name = pkg.name;
|
||||
|
||||
function renameInZip(zipPath, oldName, newName) {
|
||||
const result = spawnSync(path7za, ['rn', zipPath, oldName, newName]);
|
||||
return result.status === 0;
|
||||
function disableInstallUpdate(zipPath) {
|
||||
const zipFullPath = path.resolve(__dirname, '..', zipPath);
|
||||
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...');
|
||||
if (!renameInZip(`release/${productName}-${appVersion}-win.zip`, 'win-unpacked', `${productName}-${appVersion}-win64`)) {
|
||||
throw new Error('7za returned non-zero exit code for 64-bit zip');
|
||||
}
|
||||
|
||||
disableInstallUpdate(`release/${name}-${appVersion}-win-x64.zip`);
|
||||
console.log('Manipulating 32-bit zip...');
|
||||
if (!renameInZip(`release/${productName}-${appVersion}-ia32-win.zip`, 'win-ia32-unpacked', `${productName}-${appVersion}-win32`)) {
|
||||
throw new Error('7za returned non-zero exit code for 32-bit zip');
|
||||
}
|
||||
disableInstallUpdate(`release/${name}-${appVersion}-win-ia32.zip`);
|
||||
|
104
src/browser/components/UpdaterPage.jsx
Normal file
104
src/browser/components/UpdaterPage.jsx
Normal 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;
|
53
src/browser/components/UpdaterPage/UpdaterPage.stories.jsx
Normal file
53
src/browser/components/UpdaterPage/UpdaterPage.stories.jsx
Normal 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')}
|
||||
/>
|
||||
));
|
16
src/browser/css/components/UpdaterPage.css
Normal file
16
src/browser/css/components/UpdaterPage.css
Normal 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;
|
||||
}
|
@@ -7,3 +7,4 @@
|
||||
@import url("TabBar.css");
|
||||
@import url("TeamListItem.css");
|
||||
@import url("Finder.css");
|
||||
@import url("UpdaterPage.css");
|
||||
|
13
src/browser/updater.html
Normal file
13
src/browser/updater.html
Normal 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
75
src/browser/updater.jsx
Normal 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')
|
||||
);
|
@@ -24,6 +24,7 @@ const buildConfig = {
|
||||
],
|
||||
helpLink: 'https://about.mattermost.com/default-desktop-app-documentation/',
|
||||
enableServerManagement: true,
|
||||
enableAutoUpdater: true,
|
||||
};
|
||||
|
||||
export default buildConfig;
|
||||
|
2
src/dev-app-update.yml
Normal file
2
src/dev-app-update.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
provider: generic
|
||||
url: 'http://localhost:8081/'
|
32
src/main.js
32
src/main.js
@@ -23,9 +23,11 @@ import {parse as parseArgv} from 'yargs';
|
||||
|
||||
import {protocols} from '../electron-builder.json';
|
||||
|
||||
import squirrelStartup from './main/squirrelStartup';
|
||||
import AutoLauncher from './main/AutoLauncher';
|
||||
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();
|
||||
|
||||
@@ -34,11 +36,7 @@ process.on('uncaughtException', criticalErrorHandler.processUncaughtExceptionHan
|
||||
global.willAppQuit = false;
|
||||
|
||||
app.setAppUserModelId('com.squirrel.mattermost.Mattermost'); // Use explicit AppUserModelID
|
||||
if (squirrelStartup(() => {
|
||||
app.quit();
|
||||
})) {
|
||||
global.willAppQuit = true;
|
||||
}
|
||||
|
||||
import settings from './common/settings';
|
||||
import CertificateStore from './main/certificateStore';
|
||||
const certificateStore = CertificateStore.load(path.resolve(app.getPath('userData'), 'certificate.json'));
|
||||
@@ -419,6 +417,10 @@ app.on('ready', () => {
|
||||
}
|
||||
appState.lastAppVersion = app.getVersion();
|
||||
|
||||
if (!global.isDev) {
|
||||
upgradeAutoLaunch();
|
||||
}
|
||||
|
||||
if (global.isDev) {
|
||||
installExtension(REACT_DEVELOPER_TOOLS).
|
||||
then((name) => console.log(`Added Extension: ${name}`)).
|
||||
@@ -456,6 +458,9 @@ app.on('ready', () => {
|
||||
mainWindow.webContents.on('crashed', () => {
|
||||
throw new Error('webContents \'crashed\' event has been emitted');
|
||||
});
|
||||
mainWindow.on('ready-to-show', () => {
|
||||
autoUpdater.checkForUpdates();
|
||||
});
|
||||
|
||||
ipcMain.on('notified', () => {
|
||||
if (process.platform === 'win32' || process.platform === 'linux') {
|
||||
@@ -650,6 +655,21 @@ app.on('ready', () => {
|
||||
permissionManager = new PermissionManager(permissionFile, trustedURLs);
|
||||
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.
|
||||
// mainWindow.openDevTools();
|
||||
});
|
||||
|
@@ -11,4 +11,24 @@ export default class AppStateManager extends JsonFileManager {
|
||||
get 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
20
src/main/autoLaunch.js
Normal 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
194
src/main/autoUpdater.js
Normal 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,
|
||||
};
|
@@ -3,7 +3,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
'use strict';
|
||||
|
||||
import {app, dialog, Menu, shell} from 'electron';
|
||||
import {app, dialog, ipcMain, Menu, shell} from 'electron';
|
||||
|
||||
import settings from '../../common/settings';
|
||||
import buildConfig from '../../common/config/buildConfig';
|
||||
@@ -231,6 +231,14 @@ function createTemplate(mainWindow, config, isDev) {
|
||||
label: `Version ${app.getVersion()}`,
|
||||
enabled: false,
|
||||
});
|
||||
if (buildConfig.enableAutoUpdater) {
|
||||
submenu.push({
|
||||
label: 'Check for Updates...',
|
||||
click() {
|
||||
ipcMain.emit('check-for-updates', true);
|
||||
},
|
||||
});
|
||||
}
|
||||
template.push({label: '&Help', submenu});
|
||||
return template;
|
||||
}
|
||||
|
@@ -14,12 +14,14 @@
|
||||
"electron-context-menu": "0.9.0",
|
||||
"electron-devtools-installer": "^2.2.4",
|
||||
"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",
|
||||
"react": "^16.4.0",
|
||||
"react-bootstrap": "~0.32.1",
|
||||
"react-dom": "^16.4.0",
|
||||
"react-transition-group": "^2.3.1",
|
||||
"semver": "^5.5.0",
|
||||
"simple-spellchecker": "^0.9.6",
|
||||
"underscore": "^1.9.1",
|
||||
"yargs": "^3.32.0"
|
||||
|
126
src/yarn.lock
126
src/yarn.lock
@@ -14,6 +14,12 @@ applescript@^1.0.0:
|
||||
version "1.0.0"
|
||||
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:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
|
||||
@@ -50,6 +56,16 @@ binarysearch@^0.2.4:
|
||||
version "0.2.4"
|
||||
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:
|
||||
version "3.3.7"
|
||||
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"
|
||||
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:
|
||||
version "0.1.1"
|
||||
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:
|
||||
version "2.1.1"
|
||||
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"
|
||||
resolved "git://github.com/cbaatz/damerau-levenshtein.git#8d7440a51ae9dc6912e44385115c7cb1da4e8ebc"
|
||||
|
||||
debug@^2.2.0:
|
||||
version "2.6.8"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
|
||||
debug@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
@@ -165,11 +194,23 @@ electron-is-dev@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/electron-is-dev/-/electron-is-dev-0.3.0.tgz#14e6fda5c68e9e4ecbeff9ccf037cbd7c05c5afe"
|
||||
|
||||
electron-squirrel-startup@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/electron-squirrel-startup/-/electron-squirrel-startup-1.0.0.tgz#19b4e55933fa0ef8f556784b9c660f772546a0b8"
|
||||
electron-log@^2.2.15:
|
||||
version "2.2.15"
|
||||
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:
|
||||
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:
|
||||
version "0.1.12"
|
||||
@@ -177,6 +218,10 @@ encoding@^0.1.11:
|
||||
dependencies:
|
||||
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:
|
||||
version "2.2.2"
|
||||
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"
|
||||
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:
|
||||
version "1.0.0"
|
||||
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"
|
||||
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:
|
||||
version "0.4.18"
|
||||
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"
|
||||
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:
|
||||
version "2.1.9"
|
||||
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:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
|
||||
dependencies:
|
||||
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:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
|
||||
@@ -482,10 +567,18 @@ rimraf@^2.5.2:
|
||||
dependencies:
|
||||
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:
|
||||
version "5.4.1"
|
||||
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:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
|
||||
@@ -511,6 +604,21 @@ sort-keys@^1.0.0:
|
||||
dependencies:
|
||||
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:
|
||||
version "1.0.2"
|
||||
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"
|
||||
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:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/untildify/-/untildify-3.0.2.tgz#7f1f302055b3fea0f3e81dc78eb36766cb65e3f1"
|
||||
|
@@ -15,6 +15,7 @@ module.exports = merge(base, {
|
||||
entry: {
|
||||
index: './src/browser/index.jsx',
|
||||
settings: './src/browser/settings.jsx',
|
||||
updater: './src/browser/updater.jsx',
|
||||
'webview/mattermost': './src/browser/webview/mattermost.js',
|
||||
},
|
||||
output: {
|
||||
|
Reference in New Issue
Block a user