/ src / main-process / auto-update-manager.js
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  };