mirror of
https://github.com/tiddly-gittly/TidGi-Desktop.git
synced 2025-12-18 00:10:33 -08:00
refactor: updater
This commit is contained in:
parent
2b77b2b5a1
commit
f81cb15a58
11 changed files with 129 additions and 113 deletions
|
|
@ -6,20 +6,20 @@ import TextField from '@material-ui/core/TextField';
|
||||||
|
|
||||||
import GitHubLogin from './github-login';
|
import GitHubLogin from './github-login';
|
||||||
import type { IAuthingUserInfo } from '@services/types';
|
import type { IAuthingUserInfo } from '@services/types';
|
||||||
|
import { useUserInfoObservable } from '@services/auth/hooks';
|
||||||
|
|
||||||
const GitTokenInput = styled(TextField)``;
|
const GitTokenInput = styled(TextField)``;
|
||||||
|
|
||||||
export const setGithubToken = async (token: string | undefined): Promise<void> => await window.service.auth.set('github-token', token);
|
export const setGithubToken = async (token: string | undefined): Promise<void> => await window.service.auth.set('github-token', token);
|
||||||
export const getGithubToken = async (): Promise<string | undefined> => await window.service.auth.get('github-token');
|
export const getGithubToken = async (): Promise<string | undefined> => await window.service.auth.get('github-token');
|
||||||
|
|
||||||
export function GithubTokenForm(props: {
|
export function GithubTokenForm(props: { children?: JSX.Element | Array<JSX.Element | undefined | string> }): JSX.Element {
|
||||||
accessTokenSetter: (token?: string) => void;
|
const { children } = props;
|
||||||
userNameSetter: (userName?: string) => void;
|
|
||||||
accessToken?: string;
|
|
||||||
children: JSX.Element | Array<JSX.Element | undefined | string>;
|
|
||||||
}): JSX.Element {
|
|
||||||
const { accessToken, children } = props;
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const userInfo = useUserInfoObservable();
|
||||||
|
if (userInfo === undefined) {
|
||||||
|
return <div>Loading...</div>;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<GitHubLogin
|
<GitHubLogin
|
||||||
|
|
@ -27,7 +27,7 @@ export function GithubTokenForm(props: {
|
||||||
const accessTokenToSet = response?.userInfo?.thirdPartyIdentity?.accessToken;
|
const accessTokenToSet = response?.userInfo?.thirdPartyIdentity?.accessToken;
|
||||||
const authDataString = response?.userInfo?.oauth;
|
const authDataString = response?.userInfo?.oauth;
|
||||||
if (accessTokenToSet !== undefined) {
|
if (accessTokenToSet !== undefined) {
|
||||||
props.accessTokenSetter(accessTokenToSet);
|
void window.service.auth.set('github-token', accessTokenToSet);
|
||||||
}
|
}
|
||||||
// all data we need
|
// all data we need
|
||||||
if (accessTokenToSet !== undefined && authDataString !== undefined) {
|
if (accessTokenToSet !== undefined && authDataString !== undefined) {
|
||||||
|
|
@ -35,26 +35,29 @@ export function GithubTokenForm(props: {
|
||||||
const nextUserInfo = {
|
const nextUserInfo = {
|
||||||
...response.userInfo,
|
...response.userInfo,
|
||||||
...authData,
|
...authData,
|
||||||
...response.userInfo.thirdPartyIdentity,
|
...response.userInfo?.thirdPartyIdentity,
|
||||||
};
|
};
|
||||||
delete nextUserInfo.oauth;
|
delete nextUserInfo.oauth;
|
||||||
delete nextUserInfo.thirdPartyIdentity;
|
delete nextUserInfo.thirdPartyIdentity;
|
||||||
props.userNameSetter((nextUserInfo as IAuthingUserInfo).username);
|
void window.service.auth.set('github-userName', (nextUserInfo as IAuthingUserInfo).username);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onLogout={() => props.accessTokenSetter()}
|
onLogout={() => {
|
||||||
|
void window.service.auth.set('github-token', '');
|
||||||
|
void window.service.auth.set('github-userName', '');
|
||||||
|
}}
|
||||||
onFailure={() => {
|
onFailure={() => {
|
||||||
props.accessTokenSetter();
|
void window.service.auth.set('github-token', '');
|
||||||
props.userNameSetter();
|
void window.service.auth.set('github-userName', '');
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<GitTokenInput
|
<GitTokenInput
|
||||||
helperText={t('AddWorkspace.GitTokenDescription')}
|
helperText={t('AddWorkspace.GitTokenDescription')}
|
||||||
fullWidth
|
fullWidth
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
props.accessTokenSetter(event.target.value);
|
void window.service.auth.set('github-token', event.target.value);
|
||||||
}}
|
}}
|
||||||
value={accessToken ?? ''}
|
value={userInfo['github-token']}
|
||||||
/>
|
/>
|
||||||
{children}
|
{children}
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ export default function AddWorkspace(): JSX.Element {
|
||||||
<TabBar currentTab={currentTab} currentTabSetter={currentTabSetter} />
|
<TabBar currentTab={currentTab} currentTabSetter={currentTabSetter} />
|
||||||
<Description isCreateMainWorkspace={isCreateMainWorkspace} isCreateMainWorkspaceSetter={isCreateMainWorkspaceSetter} />
|
<Description isCreateMainWorkspace={isCreateMainWorkspace} isCreateMainWorkspaceSetter={isCreateMainWorkspaceSetter} />
|
||||||
<SyncContainer elevation={2} square>
|
<SyncContainer elevation={2} square>
|
||||||
<GithubTokenForm accessTokenSetter={accessTokenSetter} userNameSetter={userNameSetter} accessToken={accessToken}>
|
<GithubTokenForm>
|
||||||
{githubWikiUrl?.length > 0 ? (
|
{githubWikiUrl?.length > 0 ? (
|
||||||
<GithubRepoLink onClick={async () => await window.service.native.open(githubWikiUrl)} variant="subtitle2" align="center">
|
<GithubRepoLink onClick={async () => await window.service.native.open(githubWikiUrl)} variant="subtitle2" align="center">
|
||||||
({githubWikiUrl})
|
({githubWikiUrl})
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
/* eslint-disable consistent-return */
|
/* eslint-disable consistent-return */
|
||||||
import React, { useRef, useEffect, useState, useCallback } from 'react';
|
import React, { useEffect, useCallback } from 'react';
|
||||||
import { useObservable } from 'beautiful-react-hooks';
|
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import semver from 'semver';
|
import semver from 'semver';
|
||||||
import fromUnixTime from 'date-fns/fromUnixTime';
|
import fromUnixTime from 'date-fns/fromUnixTime';
|
||||||
|
|
@ -44,8 +43,9 @@ import { IPreferences, PreferenceSections } from '@services/preferences/interfac
|
||||||
import { usePreferenceSections } from './useSections';
|
import { usePreferenceSections } from './useSections';
|
||||||
import { usePromiseValue } from '@/helpers/use-service-value';
|
import { usePromiseValue } from '@/helpers/use-service-value';
|
||||||
import { usePreferenceObservable } from '@services/preferences/hooks';
|
import { usePreferenceObservable } from '@services/preferences/hooks';
|
||||||
import { useSystemPreferenceObservable } from '@services/systemPreferences/hooks';
|
import { getOpenAtLoginString, useSystemPreferenceObservable } from '@services/systemPreferences/hooks';
|
||||||
import { useUserInfoObservable } from '@services/auth/hooks';
|
import { useUserInfoObservable } from '@services/auth/hooks';
|
||||||
|
import { getUpdaterDesc, useUpdaterObservable } from '@services/updater/hooks';
|
||||||
|
|
||||||
const Root = styled.div`
|
const Root = styled.div`
|
||||||
padding: theme.spacing(2);
|
padding: theme.spacing(2);
|
||||||
|
|
@ -137,44 +137,6 @@ const getThemeString = (theme: any) => {
|
||||||
return 'System default';
|
return 'System default';
|
||||||
};
|
};
|
||||||
|
|
||||||
const getOpenAtLoginString = (openAtLogin: any) => {
|
|
||||||
if (openAtLogin === 'yes-hidden') return 'Yes, but minimized';
|
|
||||||
if (openAtLogin === 'yes') return 'Yes';
|
|
||||||
return 'No';
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatBytes = (bytes: any, decimals = 2) => {
|
|
||||||
if (bytes === 0) return '0 Bytes';
|
|
||||||
|
|
||||||
const k = 1024;
|
|
||||||
const dm = decimals < 0 ? 0 : decimals;
|
|
||||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
|
||||||
|
|
||||||
const index = Math.floor(Math.log(bytes) / Math.log(k));
|
|
||||||
|
|
||||||
return `${Number.parseFloat((bytes / k ** index).toFixed(dm))} ${sizes[index]}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getUpdaterDesc = (status: any, info: any) => {
|
|
||||||
if (status === 'download-progress') {
|
|
||||||
if (info !== null) {
|
|
||||||
const { transferred, total, bytesPerSecond } = info;
|
|
||||||
return `Downloading updates (${formatBytes(transferred)}/${formatBytes(total)} at ${formatBytes(bytesPerSecond)}/s)...`;
|
|
||||||
}
|
|
||||||
return 'Downloading updates...';
|
|
||||||
}
|
|
||||||
if (status === 'checking-for-update') {
|
|
||||||
return 'Checking for updates...';
|
|
||||||
}
|
|
||||||
if (status === 'update-available') {
|
|
||||||
return 'Downloading updates...';
|
|
||||||
}
|
|
||||||
if (status === 'update-downloaded') {
|
|
||||||
if (info && info.version) return `A new version (${info.version}) has been downloaded.`;
|
|
||||||
return 'A new version has been downloaded.';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function Preferences(): JSX.Element {
|
export default function Preferences(): JSX.Element {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const sections = usePreferenceSections();
|
const sections = usePreferenceSections();
|
||||||
|
|
@ -197,7 +159,8 @@ export default function Preferences(): JSX.Element {
|
||||||
const preference = usePreferenceObservable();
|
const preference = usePreferenceObservable();
|
||||||
const systemPreference = useSystemPreferenceObservable();
|
const systemPreference = useSystemPreferenceObservable();
|
||||||
const userInfo = useUserInfoObservable();
|
const userInfo = useUserInfoObservable();
|
||||||
if (preference === undefined || systemPreference === undefined || userInfo === undefined) {
|
const updaterMetaData = useUpdaterObservable();
|
||||||
|
if (preference === undefined || systemPreference === undefined || userInfo === undefined || updaterMetaData === undefined) {
|
||||||
return <Root>Loading...</Root>;
|
return <Root>Loading...</Root>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -241,7 +204,7 @@ export default function Preferences(): JSX.Element {
|
||||||
<List dense>
|
<List dense>
|
||||||
{Object.keys(sections).map((sectionKey, index) => {
|
{Object.keys(sections).map((sectionKey, index) => {
|
||||||
const { Icon, text, ref, hidden } = sections[sectionKey as PreferenceSections];
|
const { Icon, text, ref, hidden } = sections[sectionKey as PreferenceSections];
|
||||||
if (hidden === true) return;
|
if (hidden === true) return <></>;
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={sectionKey}>
|
<React.Fragment key={sectionKey}>
|
||||||
{index > 0 && <Divider />}
|
{index > 0 && <Divider />}
|
||||||
|
|
@ -281,7 +244,7 @@ export default function Preferences(): JSX.Element {
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemText primary={t('Preference.Token')} secondary={t('Preference.TokenDescription')} />
|
<ListItemText primary={t('Preference.Token')} secondary={t('Preference.TokenDescription')} />
|
||||||
<TokenContainer>
|
<TokenContainer>
|
||||||
<GithubTokenForm accessTokenSetter={accessTokenSetter} userInfoSetter={userInfoSetter} accessToken={accessToken} />
|
<GithubTokenForm />
|
||||||
</TokenContainer>
|
</TokenContainer>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem>
|
<ListItem>
|
||||||
|
|
@ -705,7 +668,7 @@ export default function Preferences(): JSX.Element {
|
||||||
<ListItem
|
<ListItem
|
||||||
button
|
button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.service.notification.show({
|
void window.service.notification.show({
|
||||||
title: 'Test notifications',
|
title: 'Test notifications',
|
||||||
body: 'It is working!',
|
body: 'It is working!',
|
||||||
});
|
});
|
||||||
|
|
@ -714,7 +677,7 @@ export default function Preferences(): JSX.Element {
|
||||||
primary="Test notifications"
|
primary="Test notifications"
|
||||||
secondary={(() => {
|
secondary={(() => {
|
||||||
// only show this message on macOS Catalina 10.15 & above
|
// only show this message on macOS Catalina 10.15 & above
|
||||||
if (platform === 'darwin' && oSVersion && semver.gte(oSVersion, '10.15.0')) {
|
if (platform === 'darwin' && oSVersion !== undefined && semver.gte(oSVersion, '10.15.0')) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span>If notifications don't show up,</span>
|
<span>If notifications don't show up,</span>
|
||||||
|
|
@ -957,7 +920,13 @@ export default function Preferences(): JSX.Element {
|
||||||
<SectionTitle ref={sections.advanced.ref}>Advanced</SectionTitle>
|
<SectionTitle ref={sections.advanced.ref}>Advanced</SectionTitle>
|
||||||
<Paper elevation={0}>
|
<Paper elevation={0}>
|
||||||
<List dense disablePadding>
|
<List dense disablePadding>
|
||||||
<ListItem button onClick={async () => await window.service.native.open(LOG_FOLDER, true)}>
|
<ListItem
|
||||||
|
button
|
||||||
|
onClick={() => {
|
||||||
|
if (LOG_FOLDER !== undefined) {
|
||||||
|
void window.service.native.open(LOG_FOLDER, true);
|
||||||
|
}
|
||||||
|
}}>
|
||||||
<ListItemText primary={t('Preference.OpenLogFolder')} secondary={t('Preference.OpenLogFolderDetail')} />
|
<ListItemText primary={t('Preference.OpenLogFolder')} secondary={t('Preference.OpenLogFolderDetail')} />
|
||||||
<ChevronRightIcon color="action" />
|
<ChevronRightIcon color="action" />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
@ -1037,14 +1006,13 @@ export default function Preferences(): JSX.Element {
|
||||||
button
|
button
|
||||||
onClick={async () => await window.service.updater.checkForUpdates(false)}
|
onClick={async () => await window.service.updater.checkForUpdates(false)}
|
||||||
disabled={
|
disabled={
|
||||||
updaterStatus === 'checking-for-update' ||
|
updaterMetaData.status === 'checking-for-update' ||
|
||||||
updaterStatus === 'download-progress' ||
|
updaterMetaData.status === 'download-progress' ||
|
||||||
updaterStatus === 'download-progress' ||
|
updaterMetaData.status === 'update-available'
|
||||||
updaterStatus === 'update-available'
|
|
||||||
}>
|
}>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={updaterStatus === 'update-downloaded' ? 'Restart to Apply Updates' : 'Check for Updates'}
|
primary={updaterMetaData.status === 'update-downloaded' ? 'Restart to Apply Updates' : 'Check for Updates'}
|
||||||
secondary={getUpdaterDesc(updaterStatus, updaterInfo)}
|
secondary={getUpdaterDesc(updaterMetaData.status, updaterMetaData.info)}
|
||||||
/>
|
/>
|
||||||
<ChevronRightIcon color="action" />
|
<ChevronRightIcon color="action" />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { Subject } from 'rxjs';
|
||||||
export interface IUserInfos {
|
export interface IUserInfos {
|
||||||
userName: string;
|
userName: string;
|
||||||
'github-token'?: string;
|
'github-token'?: string;
|
||||||
|
'github-userName'?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useObservable } from "beautiful-react-hooks";
|
import { useObservable } from 'beautiful-react-hooks';
|
||||||
import { useState } from "react";
|
import { useState } from 'react';
|
||||||
import { IPreferences } from "./interface";
|
import { IPreferences } from './interface';
|
||||||
|
|
||||||
export function usePreferenceObservable(): IPreferences | undefined {
|
export function usePreferenceObservable(): IPreferences | undefined {
|
||||||
const [preference, preferenceSetter] = useState<IPreferences | undefined>();
|
const [preference, preferenceSetter] = useState<IPreferences | undefined>();
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import serviceIdentifier from '@services/serviceIdentifier';
|
||||||
import type { IWindowService } from '@services/windows/interface';
|
import type { IWindowService } from '@services/windows/interface';
|
||||||
import type { INotificationService } from '@services/notifications/interface';
|
import type { INotificationService } from '@services/notifications/interface';
|
||||||
import { WindowNames } from '@services/windows/WindowProperties';
|
import { WindowNames } from '@services/windows/WindowProperties';
|
||||||
import { PreferenceChannel } from '@/constants/channels';
|
|
||||||
import { container } from '@services/container';
|
import { container } from '@services/container';
|
||||||
import i18n from '@services/libs/i18n';
|
import i18n from '@services/libs/i18n';
|
||||||
import { IPreferences, IPreferenceService } from './interface';
|
import { IPreferences, IPreferenceService } from './interface';
|
||||||
|
|
@ -83,7 +82,7 @@ export class Preference implements IPreferenceService {
|
||||||
return preferenceToSanitize;
|
return preferenceToSanitize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async set<K extends keyof IPreferences>(key: K, value: IPreferences[K]): Promise<void> {
|
public set<K extends keyof IPreferences>(key: K, value: IPreferences[K]): Promise<void> {
|
||||||
this.cachedPreferences[key] = value;
|
this.cachedPreferences[key] = value;
|
||||||
this.cachedPreferences = { ...this.cachedPreferences, ...this.sanitizePreference(this.cachedPreferences) };
|
this.cachedPreferences = { ...this.cachedPreferences, ...this.sanitizePreference(this.cachedPreferences) };
|
||||||
|
|
||||||
|
|
@ -119,7 +118,6 @@ export class Preference implements IPreferenceService {
|
||||||
this.updatePreferenceSubject();
|
this.updatePreferenceSubject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public getPreferences = (): IPreferences => {
|
public getPreferences = (): IPreferences => {
|
||||||
// store in memory to boost performance
|
// store in memory to boost performance
|
||||||
if (this.cachedPreferences === undefined) {
|
if (this.cachedPreferences === undefined) {
|
||||||
|
|
|
||||||
|
|
@ -7,3 +7,9 @@ export function useSystemPreferenceObservable(): IUsedElectionSettings | undefin
|
||||||
useObservable<IUsedElectionSettings | undefined>(window.service.systemPreference.systemPreference$, systemPreferenceSetter);
|
useObservable<IUsedElectionSettings | undefined>(window.service.systemPreference.systemPreference$, systemPreferenceSetter);
|
||||||
return systemPreference;
|
return systemPreference;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getOpenAtLoginString(openAtLogin: IUsedElectionSettings['openAtLogin']): string {
|
||||||
|
if (openAtLogin === 'yes-hidden') return 'Yes, but minimized';
|
||||||
|
if (openAtLogin === 'yes') return 'Yes';
|
||||||
|
return 'No';
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ export interface ISystemPreferenceService {
|
||||||
export const SystemPreferenceServiceIPCDescriptor = {
|
export const SystemPreferenceServiceIPCDescriptor = {
|
||||||
channel: SystemPreferenceChannel.name,
|
channel: SystemPreferenceChannel.name,
|
||||||
properties: {
|
properties: {
|
||||||
systemPreference$: ProxyPropertyType.Value$;
|
systemPreference$: ProxyPropertyType.Value$,
|
||||||
set: ProxyPropertyType.Function,
|
set: ProxyPropertyType.Function,
|
||||||
getPreferences: ProxyPropertyType.Function,
|
getPreferences: ProxyPropertyType.Function,
|
||||||
get: ProxyPropertyType.Function,
|
get: ProxyPropertyType.Function,
|
||||||
|
|
|
||||||
35
src/services/updater/hooks.ts
Normal file
35
src/services/updater/hooks.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useObservable } from 'beautiful-react-hooks';
|
||||||
|
|
||||||
|
import formatBytes from '@services/libs/format-bytes';
|
||||||
|
import { IUpdaterMetaData } from './interface';
|
||||||
|
|
||||||
|
export function useUpdaterObservable(): IUpdaterMetaData | undefined {
|
||||||
|
const [updaterMetaData, updaterMetaDataSetter] = useState<IUpdaterMetaData | undefined>();
|
||||||
|
useObservable<IUpdaterMetaData | undefined>(window.service.updater.updaterMetaData$, updaterMetaDataSetter);
|
||||||
|
return updaterMetaData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUpdaterDesc(status: IUpdaterMetaData['status'], info: IUpdaterMetaData['info']): string {
|
||||||
|
if (info instanceof Error) {
|
||||||
|
return info.message;
|
||||||
|
}
|
||||||
|
if (status === 'download-progress') {
|
||||||
|
if (info !== undefined && 'transferred' in info) {
|
||||||
|
const { transferred, total, bytesPerSecond } = info;
|
||||||
|
return `Downloading updates (${formatBytes(transferred)}/${formatBytes(total)} at ${formatBytes(bytesPerSecond)}/s)...`;
|
||||||
|
}
|
||||||
|
return 'Downloading updates...';
|
||||||
|
}
|
||||||
|
if (status === 'checking-for-update') {
|
||||||
|
return 'Checking for updates...';
|
||||||
|
}
|
||||||
|
if (status === 'update-available') {
|
||||||
|
return 'Downloading updates...';
|
||||||
|
}
|
||||||
|
if (status === 'update-downloaded') {
|
||||||
|
if (info !== undefined && 'version' in info) return `A new version (${info.version}) has been downloaded.`;
|
||||||
|
return 'A new version has been downloaded.';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
@ -8,10 +8,11 @@ import serviceIdentifier from '@services/serviceIdentifier';
|
||||||
import type { IWindowService } from '@services/windows/interface';
|
import type { IWindowService } from '@services/windows/interface';
|
||||||
import { WindowNames } from '@services/windows/WindowProperties';
|
import { WindowNames } from '@services/windows/WindowProperties';
|
||||||
import { lazyInject } from '@services/container';
|
import { lazyInject } from '@services/container';
|
||||||
import { MainChannel, UpdaterChannel } from '@/constants/channels';
|
import { MainChannel } from '@/constants/channels';
|
||||||
import { IUpdaterService, IUpdaterMetaData } from './interface';
|
import { IUpdaterService, IUpdaterMetaData } from './interface';
|
||||||
import { IMenuService } from '@services/menu/interface';
|
import { IMenuService } from '@services/menu/interface';
|
||||||
import { logger } from '@services/libs/log';
|
import { logger } from '@services/libs/log';
|
||||||
|
import { Subject } from 'rxjs';
|
||||||
|
|
||||||
// TODO: use electron-forge 's auto update solution, maybe see https://headspring.com/2020/09/24/building-signing-and-publishing-electron-forge-applications-for-windows/
|
// TODO: use electron-forge 's auto update solution, maybe see https://headspring.com/2020/09/24/building-signing-and-publishing-electron-forge-applications-for-windows/
|
||||||
@injectable()
|
@injectable()
|
||||||
|
|
@ -21,10 +22,26 @@ export class Updater implements IUpdaterService {
|
||||||
|
|
||||||
private updateSilent = true;
|
private updateSilent = true;
|
||||||
private updaterMetaData = {} as IUpdaterMetaData;
|
private updaterMetaData = {} as IUpdaterMetaData;
|
||||||
|
public updaterMetaData$: Subject<IUpdaterMetaData>;
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
this.updateSilent = true;
|
this.updateSilent = true;
|
||||||
this.configAutoUpdater();
|
this.configAutoUpdater();
|
||||||
|
this.updaterMetaData$ = new Subject<IUpdaterMetaData>();
|
||||||
|
this.updateUpdaterSubject();
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateUpdaterSubject(): void {
|
||||||
|
this.updaterMetaData$.next(this.updaterMetaData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private setMetaData(newUpdaterMetaData: IUpdaterMetaData): void {
|
||||||
|
this.updaterMetaData = {
|
||||||
|
...this.updaterMetaData,
|
||||||
|
...newUpdaterMetaData,
|
||||||
|
};
|
||||||
|
this.updateUpdaterSubject();
|
||||||
|
this.menuService.buildMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async checkForUpdates(isSilent: boolean): Promise<void> {
|
public async checkForUpdates(isSilent: boolean): Promise<void> {
|
||||||
|
|
@ -75,11 +92,9 @@ export class Updater implements IUpdaterService {
|
||||||
|
|
||||||
private configAutoUpdater(): void {
|
private configAutoUpdater(): void {
|
||||||
autoUpdater.on('checking-for-update', () => {
|
autoUpdater.on('checking-for-update', () => {
|
||||||
this.updaterMetaData = {
|
this.setMetaData({
|
||||||
status: 'checking-for-update',
|
status: 'checking-for-update',
|
||||||
};
|
});
|
||||||
this.windowService.sendToAllWindows(UpdaterChannel.updateUpdater, this.updaterMetaData);
|
|
||||||
this.menuService.buildMenu();
|
|
||||||
});
|
});
|
||||||
autoUpdater.on('update-available', (info: UpdateInfo) => {
|
autoUpdater.on('update-available', (info: UpdateInfo) => {
|
||||||
const mainWindow = this.windowService.get(WindowNames.main);
|
const mainWindow = this.windowService.get(WindowNames.main);
|
||||||
|
|
@ -93,12 +108,10 @@ export class Updater implements IUpdaterService {
|
||||||
});
|
});
|
||||||
this.updateSilent = true;
|
this.updateSilent = true;
|
||||||
}
|
}
|
||||||
this.updaterMetaData = {
|
this.setMetaData({
|
||||||
status: 'update-available',
|
status: 'update-available',
|
||||||
info,
|
info,
|
||||||
};
|
});
|
||||||
this.windowService.sendToAllWindows(UpdaterChannel.updateUpdater, this.updaterMetaData);
|
|
||||||
this.menuService.buildMenu();
|
|
||||||
});
|
});
|
||||||
autoUpdater.on('update-not-available', (info: UpdateInfo) => {
|
autoUpdater.on('update-not-available', (info: UpdateInfo) => {
|
||||||
const mainWindow = this.windowService.get(WindowNames.main);
|
const mainWindow = this.windowService.get(WindowNames.main);
|
||||||
|
|
@ -114,12 +127,10 @@ export class Updater implements IUpdaterService {
|
||||||
.catch(console.log);
|
.catch(console.log);
|
||||||
this.updateSilent = true;
|
this.updateSilent = true;
|
||||||
}
|
}
|
||||||
this.updaterMetaData = {
|
this.setMetaData({
|
||||||
status: 'update-not-available',
|
status: 'update-not-available',
|
||||||
info,
|
info,
|
||||||
};
|
});
|
||||||
this.windowService.sendToAllWindows(UpdaterChannel.updateUpdater, this.updaterMetaData);
|
|
||||||
this.menuService.buildMenu();
|
|
||||||
});
|
});
|
||||||
autoUpdater.on('error', (error: Error) => {
|
autoUpdater.on('error', (error: Error) => {
|
||||||
const mainWindow = this.windowService.get(WindowNames.main);
|
const mainWindow = this.windowService.get(WindowNames.main);
|
||||||
|
|
@ -136,46 +147,37 @@ export class Updater implements IUpdaterService {
|
||||||
this.updateSilent = true;
|
this.updateSilent = true;
|
||||||
}
|
}
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
this.updaterMetaData = {
|
this.setMetaData({
|
||||||
status: 'error',
|
status: 'error',
|
||||||
info: error,
|
info: error,
|
||||||
};
|
});
|
||||||
this.windowService.sendToAllWindows(UpdaterChannel.updateUpdater, this.updaterMetaData);
|
|
||||||
this.menuService.buildMenu();
|
|
||||||
});
|
});
|
||||||
autoUpdater.on('update-cancelled', () => {
|
autoUpdater.on('update-cancelled', () => {
|
||||||
this.updaterMetaData = {
|
this.setMetaData({
|
||||||
status: 'update-cancelled',
|
status: 'update-cancelled',
|
||||||
};
|
});
|
||||||
this.windowService.sendToAllWindows(UpdaterChannel.updateUpdater, this.updaterMetaData);
|
|
||||||
this.menuService.buildMenu();
|
|
||||||
});
|
});
|
||||||
autoUpdater.on('download-progress', (progressObject: ProgressInfo) => {
|
autoUpdater.on('download-progress', (progressObject: ProgressInfo) => {
|
||||||
this.updaterMetaData = {
|
this.setMetaData({
|
||||||
status: 'download-progress',
|
status: 'download-progress',
|
||||||
info: progressObject,
|
info: progressObject,
|
||||||
};
|
});
|
||||||
this.windowService.sendToAllWindows(UpdaterChannel.updateUpdater, this.updaterMetaData);
|
|
||||||
this.menuService.buildMenu();
|
|
||||||
});
|
});
|
||||||
autoUpdater.on('update-downloaded', (info: UpdateInfo) => {
|
autoUpdater.on('update-downloaded', (info: UpdateInfo) => {
|
||||||
const mainWindow = this.windowService.get(WindowNames.main);
|
const mainWindow = this.windowService.get(WindowNames.main);
|
||||||
if (mainWindow !== undefined) {
|
if (mainWindow !== undefined) {
|
||||||
this.updaterMetaData = {
|
this.setMetaData({
|
||||||
status: 'update-downloaded',
|
status: 'update-downloaded',
|
||||||
info,
|
info,
|
||||||
};
|
});
|
||||||
this.windowService.sendToAllWindows(UpdaterChannel.updateUpdater, this.updaterMetaData);
|
dialog
|
||||||
this.menuService.buildMenu();
|
.showMessageBox(mainWindow, {
|
||||||
const dialogOptions = {
|
|
||||||
type: 'info',
|
type: 'info',
|
||||||
buttons: ['Restart', 'Later'],
|
buttons: ['Restart', 'Later'],
|
||||||
title: 'Application Update',
|
title: 'Application Update',
|
||||||
message: `A new version (${info.version}) has been downloaded. Restart the application to apply the updates.`,
|
message: `A new version (${info.version}) has been downloaded. Restart the application to apply the updates.`,
|
||||||
cancelId: 1,
|
cancelId: 1,
|
||||||
};
|
})
|
||||||
dialog
|
|
||||||
.showMessageBox(mainWindow, dialogOptions)
|
|
||||||
.then(({ response }) => {
|
.then(({ response }) => {
|
||||||
if (response === 0) {
|
if (response === 0) {
|
||||||
// Fix autoUpdater.quitAndInstall() does not quit immediately
|
// Fix autoUpdater.quitAndInstall() does not quit immediately
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,20 @@ import type { UpdateInfo } from 'electron-updater';
|
||||||
import type { ProgressInfo } from 'builder-util-runtime';
|
import type { ProgressInfo } from 'builder-util-runtime';
|
||||||
import { ProxyPropertyType } from '@/helpers/electron-ipc-proxy/common';
|
import { ProxyPropertyType } from '@/helpers/electron-ipc-proxy/common';
|
||||||
import { UpdaterChannel } from '@/constants/channels';
|
import { UpdaterChannel } from '@/constants/channels';
|
||||||
|
import { Subject } from 'rxjs';
|
||||||
|
|
||||||
export interface IUpdaterMetaData {
|
export interface IUpdaterMetaData {
|
||||||
status?: 'update-not-available' | 'checking-for-update' | 'update-available' | 'error' | 'update-cancelled' | 'download-progress' | 'update-downloaded';
|
status?: 'update-not-available' | 'checking-for-update' | 'update-available' | 'error' | 'update-cancelled' | 'download-progress' | 'update-downloaded';
|
||||||
info?: UpdateInfo | Error | ProgressInfo;
|
info?: UpdateInfo | Error | ProgressInfo;
|
||||||
}
|
}
|
||||||
export interface IUpdaterService {
|
export interface IUpdaterService {
|
||||||
|
updaterMetaData$: Subject<IUpdaterMetaData>;
|
||||||
checkForUpdates(isSilent: boolean): Promise<void>;
|
checkForUpdates(isSilent: boolean): Promise<void>;
|
||||||
}
|
}
|
||||||
export const UpdaterServiceIPCDescriptor = {
|
export const UpdaterServiceIPCDescriptor = {
|
||||||
channel: UpdaterChannel.name,
|
channel: UpdaterChannel.name,
|
||||||
properties: {
|
properties: {
|
||||||
|
updaterMetaData$: ProxyPropertyType.Value$,
|
||||||
checkForUpdates: ProxyPropertyType.Function,
|
checkForUpdates: ProxyPropertyType.Function,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue