diff --git a/app/auto-updater-linux.js b/app/auto-updater-linux.js
new file mode 100644
index 00000000..80c2b057
--- /dev/null
+++ b/app/auto-updater-linux.js
@@ -0,0 +1,50 @@
+'use strict';
+
+const fetch = require('node-fetch');
+const {EventEmitter} = require('events');
+
+class AutoUpdater extends EventEmitter {
+ quitAndInstall() {
+ this.emitError('QuitAndInstall unimplemented');
+ }
+ getFeedURL() {
+ return this.updateURL;
+ }
+
+ setFeedURL(updateURL) {
+ this.updateURL = updateURL;
+ }
+
+ checkForUpdates() {
+ if (!this.updateURL) {
+ return this.emitError('Update URL is not set');
+ }
+ this.emit('checking-for-update');
+
+ fetch(this.updateURL)
+ .then(res => {
+ if (res.status === 204) {
+ return this.emit('update-not-available');
+ }
+ return res.json().then(({name, notes, pub_date}) => {
+ // Only name is mandatory, needed to construct release URL.
+ if (!name) {
+ throw new Error('Malformed server response: release name is missing.');
+ }
+ // If `null` is passed to Date constructor, current time will be used. This doesn't work with `undefined`
+ const date = new Date(pub_date || null);
+ this.emit('update-available', {}, notes, name, date);
+ });
+ })
+ .catch(this.emitError.bind(this));
+ }
+
+ emitError(error) {
+ if (typeof error === 'string') {
+ error = new Error(error);
+ }
+ this.emit('error', error, error.message);
+ }
+}
+
+module.exports = new AutoUpdater();
diff --git a/app/ui/window.js b/app/ui/window.js
index 1b697a06..f9760836 100644
--- a/app/ui/window.js
+++ b/app/ui/window.js
@@ -4,7 +4,7 @@ const {parse: parseUrl} = require('url');
const uuid = require('uuid');
const fileUriToPath = require('file-uri-to-path');
const isDev = require('electron-is-dev');
-const AutoUpdater = require('../auto-updater');
+const updater = require('../updater');
const toElectronBackgroundColor = require('../utils/to-electron-background-color');
const {icon, cfgDir} = require('../config/paths');
const createRPC = require('../rpc');
@@ -76,8 +76,8 @@ module.exports = class Window {
delete app.windowCallback;
fetchNotifications(window);
// auto updates
- if (!isDev && process.platform !== 'linux') {
- AutoUpdater(window);
+ if (!isDev) {
+ updater(window);
} else {
//eslint-disable-next-line no-console
console.log('ignoring auto updates during dev');
diff --git a/app/auto-updater.js b/app/updater.js
similarity index 59%
rename from app/auto-updater.js
rename to app/updater.js
index c3703291..461f4e7d 100644
--- a/app/auto-updater.js
+++ b/app/updater.js
@@ -1,5 +1,6 @@
// Packages
-const {autoUpdater, app} = require('electron');
+const electron = require('electron');
+const {app} = electron;
const ms = require('ms');
const retry = require('async-retry');
@@ -7,17 +8,20 @@ const retry = require('async-retry');
// eslint-disable-next-line no-unused-vars
const notify = require('./notify');
const {version} = require('./package');
-const {getConfig} = require('./config');
+const {getDecoratedConfig} = require('./plugins');
const {platform} = process;
+const isLinux = platform === 'linux';
+
+const autoUpdater = isLinux ? require('./auto-updater-linux') : electron.autoUpdater;
let isInit = false;
// Default to the "stable" update channel
let canaryUpdates = false;
-const buildFeedUrl = canary => {
+const buildFeedUrl = (canary, currentVersion) => {
const updatePrefix = canary ? 'releases-canary' : 'releases';
- return `https://${updatePrefix}.hyper.is/update/${platform}`;
+ return `https://${updatePrefix}.hyper.is/update/${isLinux ? 'deb' : platform}/${currentVersion}`;
};
const isCanary = updateChannel => updateChannel === 'canary';
@@ -29,7 +33,7 @@ async function init() {
});
const config = await retry(async () => {
- const content = await getConfig();
+ const content = await getDecoratedConfig();
if (!content) {
throw new Error('No config content loaded');
@@ -43,9 +47,9 @@ async function init() {
canaryUpdates = true;
}
- const feedURL = buildFeedUrl(canaryUpdates);
+ const feedURL = buildFeedUrl(canaryUpdates, version);
- autoUpdater.setFeedURL(`${feedURL}/${version}`);
+ autoUpdater.setFeedURL(feedURL);
setTimeout(() => {
autoUpdater.checkForUpdates();
@@ -65,11 +69,14 @@ module.exports = win => {
const {rpc} = win;
- const onupdate = (ev, releaseNotes, releaseName) => {
- rpc.emit('update available', {releaseNotes, releaseName});
+ const onupdate = (ev, releaseNotes, releaseName, date, updateUrl, onQuitAndInstall) => {
+ const releaseUrl = updateUrl || `https://github.com/zeit/hyper/releases/tag/${releaseName}`;
+ rpc.emit('update available', {releaseNotes, releaseName, releaseUrl, canInstall: !!onQuitAndInstall});
};
- autoUpdater.on('update-downloaded', onupdate);
+ const eventName = isLinux ? 'update-available' : 'update-downloaded';
+
+ autoUpdater.on(eventName, onupdate);
rpc.once('quit and install', () => {
autoUpdater.quitAndInstall();
@@ -80,9 +87,9 @@ module.exports = win => {
const newUpdateIsCanary = isCanary(updateChannel);
if (newUpdateIsCanary !== canaryUpdates) {
- const feedURL = buildFeedUrl(newUpdateIsCanary);
+ const feedURL = buildFeedUrl(newUpdateIsCanary, version);
- autoUpdater.setFeedURL(`${feedURL}/${version}`);
+ autoUpdater.setFeedURL(feedURL);
autoUpdater.checkForUpdates();
canaryUpdates = newUpdateIsCanary;
@@ -90,6 +97,6 @@ module.exports = win => {
});
win.on('close', () => {
- autoUpdater.removeListener('update-downloaded', onupdate);
+ autoUpdater.removeListener(eventName, onupdate);
});
};
diff --git a/lib/actions/updater.js b/lib/actions/updater.js
index 1c55eaad..f4d26389 100644
--- a/lib/actions/updater.js
+++ b/lib/actions/updater.js
@@ -10,10 +10,12 @@ export function installUpdate() {
};
}
-export function updateAvailable(version, notes) {
+export function updateAvailable(version, notes, releaseUrl, canInstall) {
return {
type: UPDATE_AVAILABLE,
version,
- notes
+ notes,
+ releaseUrl,
+ canInstall
};
}
diff --git a/lib/components/notifications.js b/lib/components/notifications.js
index c9189721..2192cf16 100644
--- a/lib/components/notifications.js
+++ b/lib/components/notifications.js
@@ -79,20 +79,38 @@ export default class Notifications extends PureComponent {
window.require('electron').shell.openExternal(ev.target.href);
ev.preventDefault();
}}
- href={`https://github.com/zeit/hyper/releases/tag/${this.props.updateVersion}`}
+ href={this.props.updateReleaseUrl}
>
notes
).{' '}
-
- Restart
- .{' '}
+ {this.props.updateCanInstall ? (
+
+ Restart
+
+ ) : (
+ {
+ window.require('electron').shell.openExternal(ev.target.href);
+ ev.preventDefault();
+ }}
+ href={this.props.updateReleaseUrl}
+ >
+ Download
+
+ )}.{' '}
)}
{this.props.customChildren}
diff --git a/lib/containers/notifications.js b/lib/containers/notifications.js
index f761d611..202ba131 100644
--- a/lib/containers/notifications.js
+++ b/lib/containers/notifications.js
@@ -34,7 +34,9 @@ const NotificationsContainer = connect(
Object.assign(state_, {
updateShowing: true,
updateVersion: ui.updateVersion,
- updateNote: ui.updateNotes.split('\n')[0]
+ updateNote: ui.updateNotes.split('\n')[0],
+ updateReleaseUrl: ui.updateReleaseUrl,
+ updateCanInstall: ui.updateCanInstall
});
} else if (notifications.message) {
Object.assign(state_, {
diff --git a/lib/index.js b/lib/index.js
index b36ad816..b2ee32c7 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -147,8 +147,8 @@ rpc.on('open file', ({path}) => {
store_.dispatch(uiActions.openFile(path));
});
-rpc.on('update available', ({releaseName, releaseNotes}) => {
- store_.dispatch(updaterActions.updateAvailable(releaseName, releaseNotes));
+rpc.on('update available', ({releaseName, releaseNotes, releaseUrl, canInstall}) => {
+ store_.dispatch(updaterActions.updateAvailable(releaseName, releaseNotes, releaseUrl, canInstall));
});
rpc.on('move', () => {
diff --git a/lib/reducers/ui.js b/lib/reducers/ui.js
index 99e11b34..55f27beb 100644
--- a/lib/reducers/ui.js
+++ b/lib/reducers/ui.js
@@ -346,7 +346,9 @@ const reducer = (state = initial, action) => {
case UPDATE_AVAILABLE:
state_ = state.merge({
updateVersion: action.version,
- updateNotes: action.notes || ''
+ updateNotes: action.notes || '',
+ updateReleaseUrl: action.releaseUrl,
+ updateCanInstall: !!action.canInstall
});
break;
}