import { interval, Subscription } from 'rxjs'; import { UpdateCheckConfig, UpdateInfo, UpdateType } from '@ucap-webmessenger/native'; import axios from 'axios'; import semver from 'semver'; import path from 'path'; import fse from 'fs-extra'; import zlib from 'zlib'; import { BrowserWindow, app } from 'electron'; import { UpdaterChannel } from '@ucap-webmessenger/native-electron'; import { startWith } from 'rxjs/operators'; import log from 'electron-log'; export class RendererUpdater { private checkSubscription: Subscription | undefined; private readonly appPath: string; private readonly downloadPath: string; private readonly unzipPath: string; constructor( private window: BrowserWindow, private config: UpdateCheckConfig ) { this.appPath = app.getAppPath() + '/'; const appPathFolder = this.appPath.slice( 0, this.appPath.indexOf('app.asar') ); this.downloadPath = path.resolve(appPathFolder, 'update.zip'); this.unzipPath = path.resolve(appPathFolder, '_app.asar'); } startCheck() { if (!semver.valid(this.config.currentVersion)) { log.error( `RendererUpdater::error currentVersion[${this.config.currentVersion}] is not valid` ); return; } this.checkSubscription = interval(this.config.intervalHour * 60 * 60 * 1000) .pipe(startWith(0)) .subscribe(i => { axios .post(this.config.feed) .then(res => { const appVersion = res.data.appVersion; if (!semver.valid(appVersion)) { log.error( `RendererUpdater::error appVersion[${appVersion}] is not valid` ); return; } if (semver.lt(this.config.currentVersion, appVersion)) { this.download(appVersion, res.data.installUrl); } }) .catch(reason => { log.error('RendererUpdater', reason); }); }); } stopCheck() { if (!!this.checkSubscription) { this.checkSubscription.unsubscribe(); } } apply() { const downloadStream = fse.createReadStream(this.downloadPath); const unzipStream = fse.createWriteStream(this.unzipPath); const unzip = zlib.createGunzip(); downloadStream .pipe(unzip) .pipe(unzipStream) .end(chunk => { try { fse.unlinkSync(this.appPath.slice(0, -1)); } catch (error) { log.error('RendererUpdater::apply', error); return; } try { fse.renameSync(this.unzipPath, this.appPath.slice(0, -1)); } catch (error) { log.error('RendererUpdater::apply', error); return; } app.relaunch(); }); } private download(appVersion: string, installUrl: string) { axios .get(installUrl, { responseType: 'blob' }) .then(res => { fse.writeFile(this.downloadPath, res.data, err => { if (!!err) { log.error('RendererUpdater::download failed', err); return; } const updateInfo: UpdateInfo = { type: UpdateType.Differencial, version: appVersion, description: '' }; this.window.webContents.send(UpdaterChannel.ExistInstant, updateInfo); }); }) .catch(reason => { log.error('RendererUpdater::download failed', reason); }); } }