From e84d47540d0b7abee66c9a676d56d1170dc7409b Mon Sep 17 00:00:00 2001 From: Richard Park Date: Thu, 12 Dec 2019 17:15:09 +0900 Subject: [PATCH] renderer update is implemented --- electron-builder.json | 6 +- .../ucap-webmessenger-electron/src/index.ts | 58 ++++++--- .../src/lib/renderer-updater.ts | 122 ++++++++++++++++++ package-lock.json | 51 ++++++++ package.json | 2 + .../src/lib/services/public-api.service.ts | 25 +++- .../src/app/app-provider.module.ts | 20 +-- .../native/components/top-bar.component.html | 28 +++- .../native/components/top-bar.component.ts | 16 ++- .../src/app/resolvers/messenger.resolver.ts | 3 + .../src/app/services/native.service.ts | 48 ++++++- .../src/app/store/setting/index.ts | 26 ++-- .../src/app/store/setting/update/actions.ts | 11 ++ .../src/app/store/setting/update/effects.ts | 25 ++++ .../src/app/store/setting/update/index.ts | 4 + .../src/app/store/setting/update/reducers.ts | 13 ++ .../src/app/store/setting/update/state.ts | 16 +++ .../src/environments/environment.dev.ts | 5 + .../src/environments/environment.prod.ts | 5 + .../src/environments/environment.type.ts | 10 +- projects/ucap-webmessenger-app/src/index.html | 2 +- .../src/lib/types/device-type.type.ts | 3 +- .../lib/services/browser-native.service.ts | 17 ++- .../lib/services/electron-native.service.ts | 30 ++++- .../src/lib/types/channel.type.ts | 6 +- .../src/lib/models/update-info.ts | 16 +++ .../src/lib/services/native.service.ts | 5 + .../src/public-api.ts | 1 + 28 files changed, 509 insertions(+), 65 deletions(-) create mode 100644 electron-projects/ucap-webmessenger-electron/src/lib/renderer-updater.ts create mode 100644 projects/ucap-webmessenger-app/src/app/store/setting/update/actions.ts create mode 100644 projects/ucap-webmessenger-app/src/app/store/setting/update/effects.ts create mode 100644 projects/ucap-webmessenger-app/src/app/store/setting/update/index.ts create mode 100644 projects/ucap-webmessenger-app/src/app/store/setting/update/reducers.ts create mode 100644 projects/ucap-webmessenger-app/src/app/store/setting/update/state.ts create mode 100644 projects/ucap-webmessenger-native/src/lib/models/update-info.ts diff --git a/electron-builder.json b/electron-builder.json index 6fb6b1fe..9fd7dffe 100644 --- a/electron-builder.json +++ b/electron-builder.json @@ -27,9 +27,9 @@ "icon": "./dist/ucap-webmessenger-electron/resources/linuxicon" }, "nsis": { - "oneClick": false, - "allowToChangeInstallationDirectory": true, - "perMachine": true, + "oneClick": true, + "allowToChangeInstallationDirectory": false, + "perMachine": false, "differentialPackage": true }, "directories": { diff --git a/electron-projects/ucap-webmessenger-electron/src/index.ts b/electron-projects/ucap-webmessenger-electron/src/index.ts index 64512f29..7fdd62ce 100644 --- a/electron-projects/ucap-webmessenger-electron/src/index.ts +++ b/electron-projects/ucap-webmessenger-electron/src/index.ts @@ -1,17 +1,6 @@ -import { - app, - ipcMain, - IpcMainEvent, - remote, - Tray, - Menu, - dialog, - shell -} from 'electron'; -import * as path from 'path'; -import * as url from 'url'; -import * as fse from 'fs-extra'; -import * as fs from 'fs'; +import { app, ipcMain, IpcMainEvent, Tray, Menu, shell } from 'electron'; +import path from 'path'; +import fse from 'fs-extra'; import { AppWindow } from './app/AppWindow'; import { now } from './util/now'; @@ -32,12 +21,16 @@ import { DefaultFolder } from './lib/default-folder'; import { FileUtil } from './lib/file-util'; import { IdleChecker } from './lib/idle-checker'; -import { NotificationRequest } from '@ucap-webmessenger/native'; +import { + NotificationRequest, + UpdateCheckConfig +} from '@ucap-webmessenger/native'; import { ElectronAppChannel } from '@ucap-webmessenger/electron-core'; +import { RendererUpdater } from './lib/renderer-updater'; const appIconPath = __LINUX__ ? path.join(__dirname, 'static', 'icon-logo.png') - : path.join(__dirname, 'resources/image', 'ico_64_64.png'); + : path.join(__dirname, 'resources/image', '64_64.png'); let appWindow: AppWindow | null = null; let appTray: Tray | null = null; @@ -85,6 +78,7 @@ let isDuplicateInstance = false; const gotSingleInstanceLock = app.requestSingleInstanceLock(); isDuplicateInstance = !gotSingleInstanceLock; let idle: IdleChecker | null; +let rendererUpdater: RendererUpdater | undefined; app.on(ElectronAppChannel.SecondInstance, (event, args, workingDirectory) => { // Someone tried to run a second instance, we should focus our window. @@ -280,6 +274,38 @@ ipcMain.on(UpdaterChannel.Check, (event: IpcMainEvent, ...args: any[]) => { event.returnValue = false; }); +ipcMain.on( + UpdaterChannel.StartCheckInstant, + (event: IpcMainEvent, ...args: any[]) => { + const config = args[0] as UpdateCheckConfig; + if (!!rendererUpdater) { + rendererUpdater.stopCheck(); + rendererUpdater = null; + } + rendererUpdater = new RendererUpdater(appWindow.browserWindow, config); // default 10min + rendererUpdater.startCheck(); + } +); + +ipcMain.on( + UpdaterChannel.StopCheckInstant, + (event: IpcMainEvent, ...args: any[]) => { + if (!!rendererUpdater) { + rendererUpdater.stopCheck(); + rendererUpdater = null; + } + } +); + +ipcMain.on( + UpdaterChannel.ApplyInstant, + (event: IpcMainEvent, ...args: any[]) => { + if (!!rendererUpdater) { + rendererUpdater.apply(); + } + } +); + ipcMain.on(FileChannel.ReadFile, (event: IpcMainEvent, ...args: any[]) => { try { fse.readFile(root(args[0]), (err, data) => { diff --git a/electron-projects/ucap-webmessenger-electron/src/lib/renderer-updater.ts b/electron-projects/ucap-webmessenger-electron/src/lib/renderer-updater.ts new file mode 100644 index 00000000..1474ec94 --- /dev/null +++ b/electron-projects/ucap-webmessenger-electron/src/lib/renderer-updater.ts @@ -0,0 +1,122 @@ +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'; + +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)) { + console.log( + `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)) { + console.log( + `RendererUpdater::error appVersion[${appVersion}] is not valid` + ); + return; + } + + if (semver.lt(this.config.currentVersion, appVersion)) { + this.download(appVersion, res.data.installUrl); + } + }) + .catch(reason => { + console.log('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) { + console.log('RendererUpdater::apply', error); + return; + } + + try { + fse.renameSync(this.unzipPath, this.appPath.slice(0, -1)); + } catch (error) { + console.log('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) { + console.log('RendererUpdater::download failed', err); + return; + } + const updateInfo: UpdateInfo = { + type: UpdateType.Differencial, + version: appVersion, + description: '' + }; + this.window.webContents.send(UpdaterChannel.ExistInstant, updateInfo); + }); + }) + .catch(reason => { + console.log('RendererUpdater::download failed', reason); + }); + } +} diff --git a/package-lock.json b/package-lock.json index 5152686e..34d7c4d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2270,6 +2270,15 @@ "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==", "dev": true }, + "@types/axios": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@types/axios/-/axios-0.14.0.tgz", + "integrity": "sha1-7CMA++fX3d1+udOr+HmZlkyvzkY=", + "dev": true, + "requires": { + "axios": "*" + } + }, "@types/copy-webpack-plugin": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/copy-webpack-plugin/-/copy-webpack-plugin-5.0.0.tgz", @@ -3224,6 +3233,48 @@ "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", "dev": true }, + "axios": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz", + "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==", + "dev": true, + "requires": { + "follow-redirects": "1.5.10", + "is-buffer": "^2.0.2" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "dev": true, + "requires": { + "debug": "=3.1.0" + } + }, + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, "axobject-query": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz", diff --git a/package.json b/package.json index 1839cc1b..8effa38e 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "@ngrx/store": "^8.4.0", "@ngrx/store-devtools": "^8.4.0", "@ngx-translate/core": "^11.0.1", + "@types/axios": "^0.14.0", "@types/copy-webpack-plugin": "^5.0.0", "@types/crypto-js": "^3.1.43", "@types/detect-browser": "^4.0.0", @@ -71,6 +72,7 @@ "@types/webpack-node-externals": "^1.6.3", "angular-split": "^3.0.2", "autolinker": "^3.11.1", + "axios": "^0.19.0", "awesome-node-loader": "^1.1.1", "awesome-typescript-loader": "^5.2.1", "classlist.js": "^1.1.20150312", diff --git a/projects/ucap-webmessenger-api-public/src/lib/services/public-api.service.ts b/projects/ucap-webmessenger-api-public/src/lib/services/public-api.service.ts index 739fdb6b..7141518f 100644 --- a/projects/ucap-webmessenger-api-public/src/lib/services/public-api.service.ts +++ b/projects/ucap-webmessenger-api-public/src/lib/services/public-api.service.ts @@ -1,5 +1,5 @@ import { Injectable, Inject } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; +import { HttpClient, HttpRequest } from '@angular/common/http'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @@ -8,13 +8,13 @@ import { VersionInfo2Request, VersionInfo2Response, encodeVersionInfo2, - decodeVersionInfo2, + decodeVersionInfo2 } from '../apis/version-info2'; import { UpdateInfoRequest, UpdateInfoResponse, encodeUpdateInfo, - decodeUpdateInfo, + decodeUpdateInfo } from '../apis/update-info'; import { _MODULE_CONFIG } from '../config/token'; @@ -23,7 +23,7 @@ import { UrlConfig } from '@ucap-webmessenger/core'; import { Urls } from '../config/urls'; @Injectable({ - providedIn: 'root', + providedIn: 'root' }) export class PublicApiService { readonly urls: Urls; @@ -46,19 +46,32 @@ export class PublicApiService { this.urls.versionInfo2, {}, { - params: encodeVersionInfo2(req), + params: encodeVersionInfo2(req) } ) .pipe(map((res: any) => decodeVersionInfo2(res))); } + public urlForVersionInfo2(req: VersionInfo2Request): string { + const httpReq = new HttpRequest( + 'GET', + this.urls.versionInfo2, + {}, + { + params: encodeVersionInfo2(req) + } + ); + + return httpReq.urlWithParams; + } + public updateInfo(req: UpdateInfoRequest): Observable { return this.httpClient .post( this.urls.updateInfo, {}, { - params: encodeUpdateInfo(req), + params: encodeUpdateInfo(req) } ) .pipe(map(res => decodeUpdateInfo(res))); diff --git a/projects/ucap-webmessenger-app/src/app/app-provider.module.ts b/projects/ucap-webmessenger-app/src/app/app-provider.module.ts index 9fea381a..6d24c2e8 100644 --- a/projects/ucap-webmessenger-app/src/app/app-provider.module.ts +++ b/projects/ucap-webmessenger-app/src/app/app-provider.module.ts @@ -21,18 +21,6 @@ export function initializeApp( }; } -// export function nativeServiceFactory(httpClient: HttpClient) { -// if ('browser' === environment.runtime) { -// return import('@ucap-webmessenger/native-browser').then( -// m => new m.BrowserNativeService(httpClient) -// ); -// } else { -// return import('@ucap-webmessenger/native-electron').then( -// m => new m.ElectronNativeService() -// ); -// } -// } - @NgModule({ imports: [], exports: [], @@ -44,7 +32,7 @@ export function initializeApp( 'browser' === environment.runtime ? BrowserNativeService : ElectronNativeService, - deps: [HttpClient], + deps: [HttpClient] }, ...SERVICES, ...RESOLVERS, @@ -52,8 +40,8 @@ export function initializeApp( provide: APP_INITIALIZER, useFactory: initializeApp, deps: [AppService, UCAP_NATIVE_SERVICE], - multi: true, - }, - ], + multi: true + } + ] }) export class AppProviderModule {} diff --git a/projects/ucap-webmessenger-app/src/app/layouts/native/components/top-bar.component.html b/projects/ucap-webmessenger-app/src/app/layouts/native/components/top-bar.component.html index 6b9cc49a..b47e6592 100644 --- a/projects/ucap-webmessenger-app/src/app/layouts/native/components/top-bar.component.html +++ b/projects/ucap-webmessenger-app/src/app/layouts/native/components/top-bar.component.html @@ -2,7 +2,7 @@ -
UCAP M Messenger
+
UCAP M Messenger!!