auto-update-manager.js
1 const { EventEmitter } = require('events'); 2 const os = require('os'); 3 const path = require('path'); 4 5 const IdleState = 'idle'; 6 const CheckingState = 'checking'; 7 const DownloadingState = 'downloading'; 8 const UpdateAvailableState = 'update-available'; 9 const NoUpdateAvailableState = 'no-update-available'; 10 const UnsupportedState = 'unsupported'; 11 const ErrorState = 'error'; 12 13 let autoUpdater = null; 14 15 module.exports = class AutoUpdateManager extends EventEmitter { 16 constructor(version, testMode, config) { 17 super(); 18 this.onUpdateNotAvailable = this.onUpdateNotAvailable.bind(this); 19 this.onUpdateError = this.onUpdateError.bind(this); 20 this.version = version; 21 this.testMode = testMode; 22 this.config = config; 23 this.state = IdleState; 24 this.iconPath = path.resolve( 25 __dirname, 26 '..', 27 '..', 28 'resources', 29 'atom.png' 30 ); 31 this.updateUrlPrefix = 32 process.env.ATOM_UPDATE_URL_PREFIX || 'https://atom.io'; 33 } 34 35 initialize() { 36 if (process.platform === 'win32') { 37 const archSuffix = process.arch === 'ia32' ? '' : `-${process.arch}`; 38 this.feedUrl = 39 this.updateUrlPrefix + 40 `/api/updates${archSuffix}?version=${this.version}&os_version=${ 41 os.release 42 }`; 43 autoUpdater = require('./auto-updater-win32'); 44 } else { 45 this.feedUrl = 46 this.updateUrlPrefix + 47 `/api/updates?version=${this.version}&os_version=${os.release}`; 48 ({ autoUpdater } = require('electron')); 49 } 50 51 autoUpdater.on('error', (event, message) => { 52 this.setState(ErrorState, message); 53 this.emitWindowEvent('update-error'); 54 console.error(`Error Downloading Update: ${message}`); 55 }); 56 57 autoUpdater.setFeedURL(this.feedUrl); 58 59 autoUpdater.on('checking-for-update', () => { 60 this.setState(CheckingState); 61 this.emitWindowEvent('checking-for-update'); 62 }); 63 64 autoUpdater.on('update-not-available', () => { 65 this.setState(NoUpdateAvailableState); 66 this.emitWindowEvent('update-not-available'); 67 }); 68 69 autoUpdater.on('update-available', () => { 70 this.setState(DownloadingState); 71 // We use sendMessage to send an event called 'update-available' in 'update-downloaded' 72 // once the update download is complete. This mismatch between the electron 73 // autoUpdater events is unfortunate but in the interest of not changing the 74 // one existing event handled by applicationDelegate 75 this.emitWindowEvent('did-begin-downloading-update'); 76 this.emit('did-begin-download'); 77 }); 78 79 autoUpdater.on( 80 'update-downloaded', 81 (event, releaseNotes, releaseVersion) => { 82 this.releaseVersion = releaseVersion; 83 this.setState(UpdateAvailableState); 84 this.emitUpdateAvailableEvent(); 85 } 86 ); 87 88 this.config.onDidChange('core.automaticallyUpdate', ({ newValue }) => { 89 if (newValue) { 90 this.scheduleUpdateCheck(); 91 } else { 92 this.cancelScheduledUpdateCheck(); 93 } 94 }); 95 96 if (this.config.get('core.automaticallyUpdate')) this.scheduleUpdateCheck(); 97 98 switch (process.platform) { 99 case 'win32': 100 if (!autoUpdater.supportsUpdates()) { 101 this.setState(UnsupportedState); 102 } 103 break; 104 case 'linux': 105 this.setState(UnsupportedState); 106 } 107 } 108 109 emitUpdateAvailableEvent() { 110 if (this.releaseVersion == null) return; 111 this.emitWindowEvent('update-available', { 112 releaseVersion: this.releaseVersion 113 }); 114 } 115 116 emitWindowEvent(eventName, payload) { 117 for (let atomWindow of this.getWindows()) { 118 atomWindow.sendMessage(eventName, payload); 119 } 120 } 121 122 setState(state, errorMessage) { 123 if (this.state === state) return; 124 this.state = state; 125 this.errorMessage = errorMessage; 126 this.emit('state-changed', this.state); 127 } 128 129 getState() { 130 return this.state; 131 } 132 133 getErrorMessage() { 134 return this.errorMessage; 135 } 136 137 scheduleUpdateCheck() { 138 // Only schedule update check periodically if running in release version and 139 // and there is no existing scheduled update check. 140 if (!/-dev/.test(this.version) && !this.checkForUpdatesIntervalID) { 141 const checkForUpdates = () => this.check({ hidePopups: true }); 142 const fourHours = 1000 * 60 * 60 * 4; 143 this.checkForUpdatesIntervalID = setInterval(checkForUpdates, fourHours); 144 checkForUpdates(); 145 } 146 } 147 148 cancelScheduledUpdateCheck() { 149 if (this.checkForUpdatesIntervalID) { 150 clearInterval(this.checkForUpdatesIntervalID); 151 this.checkForUpdatesIntervalID = null; 152 } 153 } 154 155 check({ hidePopups } = {}) { 156 if (!hidePopups) { 157 autoUpdater.once('update-not-available', this.onUpdateNotAvailable); 158 autoUpdater.once('error', this.onUpdateError); 159 } 160 161 if (!this.testMode) autoUpdater.checkForUpdates(); 162 } 163 164 install() { 165 if (!this.testMode) autoUpdater.quitAndInstall(); 166 } 167 168 onUpdateNotAvailable() { 169 autoUpdater.removeListener('error', this.onUpdateError); 170 const { dialog } = require('electron'); 171 dialog.showMessageBox( 172 { 173 type: 'info', 174 buttons: ['OK'], 175 icon: this.iconPath, 176 message: 'No update available.', 177 title: 'No Update Available', 178 detail: `Version ${this.version} is the latest version.` 179 }, 180 () => {} 181 ); // noop callback to get async behavior 182 } 183 184 onUpdateError(event, message) { 185 autoUpdater.removeListener( 186 'update-not-available', 187 this.onUpdateNotAvailable 188 ); 189 const { dialog } = require('electron'); 190 dialog.showMessageBox( 191 { 192 type: 'warning', 193 buttons: ['OK'], 194 icon: this.iconPath, 195 message: 'There was an error checking for updates.', 196 title: 'Update Error', 197 detail: message 198 }, 199 () => {} 200 ); // noop callback to get async behavior 201 } 202 203 getWindows() { 204 return global.atomApplication.getAllWindows(); 205 } 206 };