From 1b4a0cff95780cc72b81879221123c33bf16aaa0 Mon Sep 17 00:00:00 2001 From: gagarin55 Date: Mon, 19 Oct 2020 14:38:56 +0300 Subject: [PATCH] check new version on github and notify user --- package.json | 1 + src/common/backend-types.ts | 5 ++- src/main/application/Application.ts | 19 +++++++-- .../services/updater/UpdateService.ts | 42 +++++++++++++++++++ src/main/electron.ts | 15 +++++-- src/renderer/index.tsx | 12 ++++-- src/renderer/modules/app/App.tsx | 3 +- .../modules/app/NewVersionNotification.tsx | 35 ++++++++++++++++ src/renderer/modules/app/app-slice.ts | 9 ++++ .../wallet/Transactions/TxDetailsDialog.tsx | 2 +- tsconfig.json | 3 +- yarn.lock | 2 +- 12 files changed, 131 insertions(+), 17 deletions(-) create mode 100755 src/main/application/services/updater/UpdateService.ts create mode 100644 src/renderer/modules/app/NewVersionNotification.tsx diff --git a/package.json b/package.json index 737b4d8..2f3b433 100755 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "react-dom": "16.11.0", "react-redux": "^7.2.1", "recharts": "^1.8.5", + "semver": "7.3.2", "styled-components": "^5.1.1", "typeface-roboto": "^0.0.75", "typeface-roboto-mono": "^0.0.75", diff --git a/src/common/backend-types.ts b/src/common/backend-types.ts index 8e13953..de269ac 100644 --- a/src/common/backend-types.ts +++ b/src/common/backend-types.ts @@ -9,14 +9,15 @@ export type ErgoTokenAmount = Omit & { amount: string }; export type WalletTx = walletTypes.WalletTx; export interface Event { - type: string; + type: Events; payload: any; } export enum Events { WALLET_UPDATED = 'wallet-updated', APP_READY = 'app-ready', - SETTINGS_UPDATED = 'settings-updated' + APP_LATEST_VERSION = 'app-latest-version', + SETTINGS_UPDATED = 'settings-updated', } export enum Commands { diff --git a/src/main/application/Application.ts b/src/main/application/Application.ts index 0fb93ac..c69ceeb 100755 --- a/src/main/application/Application.ts +++ b/src/main/application/Application.ts @@ -7,12 +7,16 @@ import {Vault} from "./services/vault/Vault"; import {Wallet, WalletBox} from './services/wallet/Wallet'; import {WalletImpl} from "./services/wallet/WalletImpl"; import {BlockchainService} from './services/blockchain/BlockchainService'; +import {UpdateService} from "./services/updater/UpdateService"; import * as bip39 from 'bip39'; import {SignedTransaction, UnsignedTransaction} from "./services/wallet/TransactionBuilder"; -import { EventEmitter } from 'events'; +import {EventEmitter} from 'events'; import Settings from "./Settings"; +import _ from "lodash"; export default class Application extends EventEmitter { + public static APP_READY_EVENT = 'AppReady'; + public static APP_LATEST_VERSION = 'LatestVersion'; private baseUri = "https://api.ergoplatform.com/api/v0"; @@ -21,6 +25,7 @@ export default class Application extends EventEmitter { private readonly connector: Connector; private blockchain: BlockchainService; private settings: Settings; + private updater: UpdateService; constructor() { super(); @@ -29,18 +34,26 @@ export default class Application extends EventEmitter { this.blockchain.on(BlockchainService.HEIGHT_CHANGED_EVENT, (event) => { console.log(JSON.stringify(event)); }); + this.updater = new UpdateService(); + this.updater.on(UpdateService.CURRENT_VERSION_EVENT, (event) => { + const latestVer = event.tag_name ? _.trimStart(event.tag_name, "v") : null; + console.debug(`Latest version: ${latestVer}`); + this.emit(Application.APP_LATEST_VERSION, latestVer); + }); } public start(): void { this.blockchain.start(); + this.updater.start(); this.setIpcHandlers(); // Load settings this.settings = new Settings(); - this.emit('AppReady'); + this.emit(Application.APP_READY_EVENT); } public stop(): void { + this.updater.stop(); this.blockchain.stop(); this.blockchain.removeAllListeners(BlockchainService.HEIGHT_CHANGED_EVENT); this.closeCurrentWallet(); @@ -154,7 +167,7 @@ export default class Application extends EventEmitter { } public updateSettings(settings: any) { - console.debug('Updating settings: ' + JSON.stringify(settings)); + console.debug('Updating settings: ' + JSON.stringify(settings)); this.settings.update(settings); this.emit('SettingsUpdated'); } diff --git a/src/main/application/services/updater/UpdateService.ts b/src/main/application/services/updater/UpdateService.ts new file mode 100755 index 0000000..38cf124 --- /dev/null +++ b/src/main/application/services/updater/UpdateService.ts @@ -0,0 +1,42 @@ +import {SchedulerService} from "../../../../common/SchedulerService"; +import {EventEmitter} from 'events'; +import {default as fetch} from 'node-fetch'; + +export class UpdateService extends EventEmitter { + public static CURRENT_VERSION_EVENT = 'CURRENT_VERSION_EVENT'; + + private schedulerService: SchedulerService; + + constructor() { + super(); + this.schedulerService = new SchedulerService(() => { + this.retrieveVersion(); + }, 60_000 * 10); + } + + public start(): void { + this.retrieveVersion(); + this.schedulerService.start(); + console.log("UpdateService started"); + } + + public stop(): void { + this.schedulerService.stop(); + console.log("UpdateService stopped"); + } + + + private async retrieveVersion(): Promise { + try { + const response = await fetch(`https://api.github.com/repos/ErgoWallet/ergowallet-desktop/releases/latest`); + if (!response.ok) { + console.error(new Error(`${response.status}: ${response.statusText}`)); + } + const body = await response.json(); + + this.emit(UpdateService.CURRENT_VERSION_EVENT, body); + } catch (e) { + console.error(e); + } + } +} diff --git a/src/main/electron.ts b/src/main/electron.ts index 0794dc3..fa961ee 100755 --- a/src/main/electron.ts +++ b/src/main/electron.ts @@ -52,15 +52,22 @@ if (!gotTheLock) { }); application.on('WalletUpdated', () => { - mainWindow.webContents.send('events', Events.WALLET_UPDATED); + mainWindow.webContents.send('events', { type: Events.WALLET_UPDATED }); }); - application.on('AppReady', () => { - mainWindow.webContents.send('events', Events.APP_READY); + application.on(Application.APP_READY_EVENT, () => { + mainWindow.webContents.send('events', { type: Events.APP_READY }); + }); + + application.on(Application.APP_LATEST_VERSION, (version) => { + mainWindow.webContents.send('events', { + type: Events.APP_LATEST_VERSION, + payload: version + }); }); application.on('SettingsUpdated', () => { - mainWindow.webContents.send('events', Events.SETTINGS_UPDATED); + mainWindow.webContents.send('events', { type: Events.SETTINGS_UPDATED }); }); mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription) => { diff --git a/src/renderer/index.tsx b/src/renderer/index.tsx index 1fbde8c..a46d9e7 100755 --- a/src/renderer/index.tsx +++ b/src/renderer/index.tsx @@ -6,8 +6,8 @@ import store from "./store/store"; import {ipcRenderer, IpcRendererEvent} from "electron"; import App from './modules/app/App'; import {fetchAddresses, fetchTransactions, fetchUnspentBoxes} from "./modules/wallet/wallet-slice"; -import {appReady, fetchAppSettings} from "./modules/app/app-slice"; -import {Events} from "../common/backend-types"; +import {appLatestVersion, appReady, fetchAppSettings} from "./modules/app/app-slice"; +import {Event, Events} from "../common/backend-types"; ReactDOM.render( @@ -17,10 +17,10 @@ ReactDOM.render( ); // Listen to Electron IPC events -ipcRenderer.on("events", (event: IpcRendererEvent, e: Events) => { +ipcRenderer.on("events", (event: IpcRendererEvent, e: Event) => { console.log(e); - switch (e) { + switch (e.type) { case Events.WALLET_UPDATED: store.dispatch(fetchTransactions()); store.dispatch(fetchUnspentBoxes()); @@ -34,5 +34,9 @@ ipcRenderer.on("events", (event: IpcRendererEvent, e: Events) => { case Events.SETTINGS_UPDATED: store.dispatch(fetchAppSettings()); + break; + + case Events.APP_LATEST_VERSION: + store.dispatch(appLatestVersion(e.payload)); } }); diff --git a/src/renderer/modules/app/App.tsx b/src/renderer/modules/app/App.tsx index af020a6..bc401d3 100755 --- a/src/renderer/modules/app/App.tsx +++ b/src/renderer/modules/app/App.tsx @@ -14,6 +14,7 @@ import {useDispatch, useSelector} from "react-redux"; import {RootState} from "../../store/root-reducer"; import Loading from "./Loading"; import Terms from "./Terms"; +import NewVersionNotification from "./NewVersionNotification"; let source = createMemorySource("/") let history = createHistory(source) @@ -30,7 +31,6 @@ enum CreationMode { } const App = (props: any) => { - const dispatch = useDispatch(); const app = useSelector((state: RootState) => state.app); const [loggedIn, setLoggedIn] = React.useState(false); const [creationMode, setCreationMode] = React.useState(CreationMode.Unknown); @@ -137,6 +137,7 @@ const App = (props: any) => { {content} + {/*
{props.width}
*/}
); diff --git a/src/renderer/modules/app/NewVersionNotification.tsx b/src/renderer/modules/app/NewVersionNotification.tsx new file mode 100644 index 0000000..e23a2a4 --- /dev/null +++ b/src/renderer/modules/app/NewVersionNotification.tsx @@ -0,0 +1,35 @@ +import {useSelector} from "react-redux"; +import {RootState} from "../../store/root-reducer"; +import * as React from "react"; +import {Button, Snackbar} from "@material-ui/core"; +import {Alert} from "@material-ui/lab"; +import {shell} from 'electron'; +import * as semver from 'semver'; + +const NewVersionNotification = () => { + const app = useSelector((state: RootState) => state.app); + if (!app.latestVersion) { + return null; + } + if (semver.gte(app.version, app.latestVersion)) { + return null; + } + + const openDownloadPage = () => { + shell.openExternal('https://ergowallet.io'); + }; + + return ( + + Download + )} + severity="info"> + New version is available. + + + ); +} + +export default NewVersionNotification; \ No newline at end of file diff --git a/src/renderer/modules/app/app-slice.ts b/src/renderer/modules/app/app-slice.ts index ce348af..794d1eb 100644 --- a/src/renderer/modules/app/app-slice.ts +++ b/src/renderer/modules/app/app-slice.ts @@ -2,8 +2,12 @@ import {createSlice, PayloadAction} from "@reduxjs/toolkit"; import {AppThunk} from "../../store/store"; import * as backend from "../../Backend"; +const pkg = require('../../../../package.json'); + interface AppState { ready: boolean; + version: string; + latestVersion?: string; settings: { termsVersion?: string }; @@ -11,6 +15,7 @@ interface AppState { const initialState: AppState = { ready: false, + version: pkg.version, settings: {} }; @@ -21,6 +26,9 @@ const appSlice = createSlice({ appReady(state, action: PayloadAction) { state.ready = true; }, + appLatestVersion(state, action: PayloadAction) { + state.latestVersion = action.payload; + }, getSettingsSuccess(state, action: PayloadAction) { state.settings = action.payload; }, @@ -30,6 +38,7 @@ const appSlice = createSlice({ // ------ Actions ----------- export const { appReady, + appLatestVersion, getSettingsSuccess } = appSlice.actions; diff --git a/src/renderer/modules/wallet/Transactions/TxDetailsDialog.tsx b/src/renderer/modules/wallet/Transactions/TxDetailsDialog.tsx index 269838e..1f5fd65 100755 --- a/src/renderer/modules/wallet/Transactions/TxDetailsDialog.tsx +++ b/src/renderer/modules/wallet/Transactions/TxDetailsDialog.tsx @@ -91,7 +91,7 @@ function TxDetailsDialog(props: TxDetailsProps): React.ReactElement { {/* inputs list */} - + { tx.inputs.map((i: Input) => ( diff --git a/tsconfig.json b/tsconfig.json index ec9d973..441c9e1 100755 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,8 @@ "outDir": "./app", "rootDir": "./src", "esModuleInterop": true, - "types": ["react", "jest"] + "types": ["react", "jest"], + "resolveJsonModule": true }, "include": [ "src/**/*.ts" diff --git a/yarn.lock b/yarn.lock index fddbb70..44078bc 100755 --- a/yarn.lock +++ b/yarn.lock @@ -6895,7 +6895,7 @@ semver-diff@^3.1.1: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@7.x, semver@^7.2.1, semver@^7.3.2: +semver@7.3.2, semver@7.x, semver@^7.2.1, semver@^7.3.2: version "7.3.2" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==