import axios , { AxiosResponse } from 'axios' ; import { ipcRenderer } from 'electron' ; import { EventEmitter } from 'events' ; import * as semver from 'semver' ; import { setTimeout , clearTimeout } from 'timers' ; import getLogger from 'common/logger' ; import IPC_EVENTS from 'common/ipc-events' ; const CHECK_INTERVAL = 2 * 60 * 60 * 1000 ; const GITHUB_BASE_URL = 'https://api.github.com' ; const GITHUB_REPO = 'jsynowiec/airqmon' ; const logger = getLogger ( 'update-checker' ) ; const currentVer = require ( '@root/package.json' ) . version ; const appName = require ( '@root/package.json' ) . name ; interface IGitHubRelease { id : number ; tag_name : string ; prerelease : boolean ; published_at : string ; body : string ; assets : { name : string ; content_type : string ; browser_download_url : string ; } [ ] ; } interface IUpdateChecker extends EventEmitter { /**
     * Emitted when there is an available update.
     */ on ( event , listener ) : this ; once ( event , listener ) : this ; addListener ( event , listener ) : this ; removeListener ( event , listener ) : this ; /**
     * Asks the server whether there is an update.
     */ checkForUpdates ( ) : void ; } class UpdateChecker extends EventEmitter implements IUpdateChecker { private checkTimer : NodeJS . Timer ; private latestKnownVersion : string = currentVer ; private retries = 0 ; protected updateAvailable = false ; protected updateUrl ? : string ; constructor ( ) { super ( ) ; ipcRenderer . on ( IPC_EVENTS . CONN_STATUS_CHANGED , ( _ , status ) => { if ( status === 'offline' ) { clearTimeout ( this . checkTimer ) ; } else { this . scheduleCheck ( 10 * 1000 ) ; } } ) ; } private getRetryBackoff ( ) { return Math . min ( Math . pow ( 2 , this . retries ) * 30 * 1000 , CHECK_INTERVAL / 2 ) ; } private retryWithBackoff ( ) { this . scheduleCheck ( this . getRetryBackoff ( ) ) ; this . retries += 1 ; } private scheduleCheck ( delay = CHECK_INTERVAL ) { this . checkTimer = setTimeout ( this . checkForUpdates . bind ( this ) , delay ) ; } checkForUpdates ( ) { axios ( { url : `${ GITHUB_BASE_URL }/repos/${ GITHUB_REPO }/releases` , } ) . then ( ( result ) => { if ( result . status === 200 ) { const availableUpdates = result . data . filter ( ( elem ) => semver . satisfies ( elem . tag_name , `>${ currentVer }` ) ) . sort ( ( r1 , r2 ) => semver . compare ( r1 . tag_name , r2 . tag_name ) ) ; if ( availableUpdates . length > 0 ) { const { tag_name : version , assets } = availableUpdates . pop ( ) ; logger . debug ( `Newer version found: ${ version }` ) ; if ( semver . gt ( version , this . latestKnownVersion ) ) { const updatePackage = `${ appName }-${ version }-${ process . platform }-${ process . arch }.zip` ; logger . debug ( `Looking for ${ updatePackage } asset` ) ; const updateUrl = assets . find ( ( asset ) => { return asset . content_type === 'application/zip' && asset . name === updatePackage ; } ) ; if ( updateUrl ) { logger . debug ( `Asset found: ${ updateUrl }` ) ; this . latestKnownVersion = version ; this . updateAvailable = true ; this . updateUrl = updateUrl . browser_download_url ; this . emit ( 'update-available' , ... [ version , this . updateUrl ] ) ; } } } this . retries = 0 ; this . scheduleCheck ( ) ; } else { this . retryWithBackoff ( ) ; } } ) . catch ( ( error ) => { logger . error ( error . message ) ; this . retryWithBackoff ( ) ; } ) ; } } export default new UpdateChecker ( ) as IUpdateChecker ;