Use same hazel endpoint to notify update to Linux users (#2497)

Add a pseudo auto-updater for Linux

Fixes #2476
This commit is contained in:
CHaBou 2017-11-29 14:26:24 +01:00 committed by GitHub
parent 59273ddb2a
commit 1fbc85760b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 114 additions and 33 deletions

50
app/auto-updater-linux.js Normal file
View file

@ -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();

View file

@ -4,7 +4,7 @@ const {parse: parseUrl} = require('url');
const uuid = require('uuid'); const uuid = require('uuid');
const fileUriToPath = require('file-uri-to-path'); const fileUriToPath = require('file-uri-to-path');
const isDev = require('electron-is-dev'); const isDev = require('electron-is-dev');
const AutoUpdater = require('../auto-updater'); const updater = require('../updater');
const toElectronBackgroundColor = require('../utils/to-electron-background-color'); const toElectronBackgroundColor = require('../utils/to-electron-background-color');
const {icon, cfgDir} = require('../config/paths'); const {icon, cfgDir} = require('../config/paths');
const createRPC = require('../rpc'); const createRPC = require('../rpc');
@ -76,8 +76,8 @@ module.exports = class Window {
delete app.windowCallback; delete app.windowCallback;
fetchNotifications(window); fetchNotifications(window);
// auto updates // auto updates
if (!isDev && process.platform !== 'linux') { if (!isDev) {
AutoUpdater(window); updater(window);
} else { } else {
//eslint-disable-next-line no-console //eslint-disable-next-line no-console
console.log('ignoring auto updates during dev'); console.log('ignoring auto updates during dev');

View file

@ -1,5 +1,6 @@
// Packages // Packages
const {autoUpdater, app} = require('electron'); const electron = require('electron');
const {app} = electron;
const ms = require('ms'); const ms = require('ms');
const retry = require('async-retry'); const retry = require('async-retry');
@ -7,17 +8,20 @@ const retry = require('async-retry');
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const notify = require('./notify'); const notify = require('./notify');
const {version} = require('./package'); const {version} = require('./package');
const {getConfig} = require('./config'); const {getDecoratedConfig} = require('./plugins');
const {platform} = process; const {platform} = process;
const isLinux = platform === 'linux';
const autoUpdater = isLinux ? require('./auto-updater-linux') : electron.autoUpdater;
let isInit = false; let isInit = false;
// Default to the "stable" update channel // Default to the "stable" update channel
let canaryUpdates = false; let canaryUpdates = false;
const buildFeedUrl = canary => { const buildFeedUrl = (canary, currentVersion) => {
const updatePrefix = canary ? 'releases-canary' : 'releases'; 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'; const isCanary = updateChannel => updateChannel === 'canary';
@ -29,7 +33,7 @@ async function init() {
}); });
const config = await retry(async () => { const config = await retry(async () => {
const content = await getConfig(); const content = await getDecoratedConfig();
if (!content) { if (!content) {
throw new Error('No config content loaded'); throw new Error('No config content loaded');
@ -43,9 +47,9 @@ async function init() {
canaryUpdates = true; canaryUpdates = true;
} }
const feedURL = buildFeedUrl(canaryUpdates); const feedURL = buildFeedUrl(canaryUpdates, version);
autoUpdater.setFeedURL(`${feedURL}/${version}`); autoUpdater.setFeedURL(feedURL);
setTimeout(() => { setTimeout(() => {
autoUpdater.checkForUpdates(); autoUpdater.checkForUpdates();
@ -65,11 +69,14 @@ module.exports = win => {
const {rpc} = win; const {rpc} = win;
const onupdate = (ev, releaseNotes, releaseName) => { const onupdate = (ev, releaseNotes, releaseName, date, updateUrl, onQuitAndInstall) => {
rpc.emit('update available', {releaseNotes, releaseName}); 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', () => { rpc.once('quit and install', () => {
autoUpdater.quitAndInstall(); autoUpdater.quitAndInstall();
@ -80,9 +87,9 @@ module.exports = win => {
const newUpdateIsCanary = isCanary(updateChannel); const newUpdateIsCanary = isCanary(updateChannel);
if (newUpdateIsCanary !== canaryUpdates) { if (newUpdateIsCanary !== canaryUpdates) {
const feedURL = buildFeedUrl(newUpdateIsCanary); const feedURL = buildFeedUrl(newUpdateIsCanary, version);
autoUpdater.setFeedURL(`${feedURL}/${version}`); autoUpdater.setFeedURL(feedURL);
autoUpdater.checkForUpdates(); autoUpdater.checkForUpdates();
canaryUpdates = newUpdateIsCanary; canaryUpdates = newUpdateIsCanary;
@ -90,6 +97,6 @@ module.exports = win => {
}); });
win.on('close', () => { win.on('close', () => {
autoUpdater.removeListener('update-downloaded', onupdate); autoUpdater.removeListener(eventName, onupdate);
}); });
}; };

View file

@ -10,10 +10,12 @@ export function installUpdate() {
}; };
} }
export function updateAvailable(version, notes) { export function updateAvailable(version, notes, releaseUrl, canInstall) {
return { return {
type: UPDATE_AVAILABLE, type: UPDATE_AVAILABLE,
version, version,
notes notes,
releaseUrl,
canInstall
}; };
} }

View file

@ -79,20 +79,38 @@ export default class Notifications extends PureComponent {
window.require('electron').shell.openExternal(ev.target.href); window.require('electron').shell.openExternal(ev.target.href);
ev.preventDefault(); ev.preventDefault();
}} }}
href={`https://github.com/zeit/hyper/releases/tag/${this.props.updateVersion}`} href={this.props.updateReleaseUrl}
> >
notes notes
</a>).{' '} </a>).{' '}
<a {this.props.updateCanInstall ? (
style={{ <a
cursor: 'pointer', style={{
textDecoration: 'underline', cursor: 'pointer',
fontWeight: 'bold' textDecoration: 'underline',
}} fontWeight: 'bold'
onClick={this.props.onUpdateInstall} }}
> onClick={this.props.onUpdateInstall}
Restart >
</a>.{' '} Restart
</a>
) : (
<a
style={{
color: '#fff',
cursor: 'pointer',
textDecoration: 'underline',
fontWeight: 'bold'
}}
onClick={ev => {
window.require('electron').shell.openExternal(ev.target.href);
ev.preventDefault();
}}
href={this.props.updateReleaseUrl}
>
Download
</a>
)}.{' '}
</Notification> </Notification>
)} )}
{this.props.customChildren} {this.props.customChildren}

View file

@ -34,7 +34,9 @@ const NotificationsContainer = connect(
Object.assign(state_, { Object.assign(state_, {
updateShowing: true, updateShowing: true,
updateVersion: ui.updateVersion, 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) { } else if (notifications.message) {
Object.assign(state_, { Object.assign(state_, {

View file

@ -147,8 +147,8 @@ rpc.on('open file', ({path}) => {
store_.dispatch(uiActions.openFile(path)); store_.dispatch(uiActions.openFile(path));
}); });
rpc.on('update available', ({releaseName, releaseNotes}) => { rpc.on('update available', ({releaseName, releaseNotes, releaseUrl, canInstall}) => {
store_.dispatch(updaterActions.updateAvailable(releaseName, releaseNotes)); store_.dispatch(updaterActions.updateAvailable(releaseName, releaseNotes, releaseUrl, canInstall));
}); });
rpc.on('move', () => { rpc.on('move', () => {

View file

@ -346,7 +346,9 @@ const reducer = (state = initial, action) => {
case UPDATE_AVAILABLE: case UPDATE_AVAILABLE:
state_ = state.merge({ state_ = state.merge({
updateVersion: action.version, updateVersion: action.version,
updateNotes: action.notes || '' updateNotes: action.notes || '',
updateReleaseUrl: action.releaseUrl,
updateCanInstall: !!action.canInstall
}); });
break; break;
} }