import { compareVersions } from 'compare-versions' import electron from 'electron' import React from 'react' import axios from 'axios' import Close from '@mui/icons-material/Close' import CloudDownload from '@mui/icons-material/CloudDownload' import { AppState } from '../reducers' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' import { green } from '@mui/material/colors' import { Theme } from '@mui/material/styles' import { withStyles } from '@mui/styles' import { updateNotifierActions } from '../actions' import { Button, IconButton, Modal, Paper, Snackbar, SnackbarContent, Typography } from '@mui/material' import { rendererRpc, getAppVersion } from '../../../events' interface Props { showUpdateNotification: boolean showUpdateDetails: boolean classes: any actions: any } interface GithubRelease { url: string assets?: Array published_at: string // "2019-01-25T20:14:39Z" body_html: string body: string body_text: string tag_name: string prerelease: boolean } interface GithubAsset { id: number node_id: string url: string browser_download_url: string name: string label: string } interface State { newerVersions: Array } class UpdateNotifier extends React.PureComponent { constructor(props: any) { super(props) this.state = { newerVersions: [] } this.checkForUpdates() } private async checkForUpdates() { const ownVersion = await rendererRpc.call(getAppVersion, undefined, 10000) const releases = await this.fetchReleases() const newerVersions = releases .filter(release => this.allowPrereleaseIfOwnVersionIsBeta(release, ownVersion)) .filter(release => compareVersions(release.tag_name, ownVersion) > 0) .sort((a, b) => compareVersions(b.tag_name, a.tag_name)) if (newerVersions.length > 0) { this.setState({ newerVersions }) this.props.actions.showUpdateNotification(true) } } private allowPrereleaseIfOwnVersionIsBeta(release: GithubRelease, ownVersion: string) { const ownVersionIsBeta = !/alpha|beta/.test(ownVersion) return ownVersionIsBeta || !release.prerelease } private async fetchReleases(): Promise> { const res = await axios.get('https://api.github.com/repos/thomasnordquist/mqtt-explorer/releases', { headers: { accept: 'application/vnd.github.v3.full+json', }, }) return res.data as Array } private onCloseNotification = (event: any, reason: any) => { if (reason === 'clickaway') { return } this.props.actions.showUpdateNotification(false) } private closeNotification = () => { this.props.actions.showUpdateNotification(false) } private showDetails = () => { this.props.actions.showUpdateNotification(false) this.props.actions.showUpdateDetails(true) } private hideDetails = () => { this.props.actions.showUpdateDetails(false) } private renderUpdateNotification() { const snackbarAnchor: any = { vertical: 'top', horizontal: 'right', } return ( ) } private notificationActions() { return [ , , ] } private renderUpdateDetails() { const latestUpdate = this.state.newerVersions[0] if (!latestUpdate) { return null } const releaseNotes = this.state.newerVersions .map(release => `

${release.tag_name}

${release.body_html}

`) .join('
') return ( Version {latestUpdate.tag_name} Changelog
{this.renderDownloads()} ) } private openHomePage = () => { this.openUrl('https://mqtt-explorer.com') } private openUrl = (url: string) => { electron.shell.openExternal(url) } private assetForCurrentPlatform(asset: GithubAsset) { let regex: RegExp const platform = this.getPlatform() if (platform === 'darwin') { regex = /\.dmg$/ } else if (platform === 'win32') { regex = /\.exe$/ } else { regex = /\.AppImage$/ } return regex.test(asset.name) } private getPlatform(): string { if (typeof window === 'undefined') return 'linux' const userAgent = window.navigator.userAgent.toLowerCase() if (userAgent.includes('mac')) return 'darwin' if (userAgent.includes('win')) return 'win32' return 'linux' } private renderDownloads() { const latestUpdate = this.state.newerVersions[0] if (!latestUpdate || !latestUpdate.assets) { return null } return latestUpdate.assets.filter(this.assetForCurrentPlatform).map(asset => (
)) } public render() { return (
{this.renderUpdateNotification()} {this.renderUpdateDetails()}
) } } const styles = (theme: Theme) => ({ success: { backgroundColor: green[600], color: theme.typography.button.color, }, close: { padding: '4px', }, root: { minWidth: '350px', maxWidth: '500px', backgroundColor: theme.palette.background.default, margin: '20vh auto auto auto', padding: theme.spacing(2), outline: 'none', }, title: { color: theme.palette.text.primary, }, releaseNotes: { overflow: 'auto scroll', color: theme.palette.text.secondary, backgroundColor: 'rgba(60, 60, 60, 0.6)', maxHeight: '28vh', }, paper: { padding: theme.spacing(2), color: theme.palette.text.secondary, }, download: { width: '100%', }, closeButton: { display: 'block', margin: '0 0 0 auto', }, }) const mapStateToProps = (state: AppState) => { return { showUpdateNotification: state.globalState.get('showUpdateNotification'), showUpdateDetails: state.globalState.get('showUpdateDetails'), } } const mapDispatchToProps = (dispatch: any) => { return { actions: bindActionCreators(updateNotifierActions, dispatch), } } export default withStyles(styles)(connect(mapStateToProps, mapDispatchToProps)(UpdateNotifier))