Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

feat: update button enhancements #2401

Merged
merged 10 commits into from
May 17, 2023
7 changes: 7 additions & 0 deletions apps/electron/layers/main/src/application-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { app, Menu } from 'electron';

import { isMacOS } from '../../utils';
import { subjects } from './events';
import { checkForUpdatesAndNotify } from './handlers/updater';

// Unique id for menuitems
const MENUITEM_NEW_PAGE = 'affine:new-page';
Expand Down Expand Up @@ -114,6 +115,12 @@ export function createApplicationMenu() {
await shell.openExternal('https://affine.pro/');
},
},
{
label: 'Check for Updates',
click: async () => {
await checkForUpdatesAndNotify(true);
},
},
],
},
];
Expand Down
23 changes: 19 additions & 4 deletions apps/electron/layers/main/src/events/updater.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
import { Subject } from 'rxjs';
import { BehaviorSubject, Subject } from 'rxjs';

import type { MainEventListener } from './type';

interface UpdateMeta {
version: string;
allowAutoUpdate: boolean;
}

export const updaterSubjects = {
// means it is ready for restart and install the new version
clientUpdateReady: new Subject<UpdateMeta>(),
updateAvailable: new Subject<UpdateMeta>(),
updateReady: new Subject<UpdateMeta>(),
downloadProgress: new BehaviorSubject<number>(0),
};

export const updaterEvents = {
onClientUpdateReady: (fn: (versionMeta: UpdateMeta) => void) => {
const sub = updaterSubjects.clientUpdateReady.subscribe(fn);
onUpdateAvailable: (fn: (versionMeta: UpdateMeta) => void) => {
const sub = updaterSubjects.updateAvailable.subscribe(fn);
return () => {
sub.unsubscribe();
};
},
onUpdateReady: (fn: (versionMeta: UpdateMeta) => void) => {
const sub = updaterSubjects.updateReady.subscribe(fn);
return () => {
sub.unsubscribe();
};
},
onDownloadProgress: (fn: (progress: number) => void) => {
const sub = updaterSubjects.downloadProgress.subscribe(fn);
return () => {
sub.unsubscribe();
};
Expand Down
14 changes: 11 additions & 3 deletions apps/electron/layers/main/src/handlers/updater/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { app } from 'electron';

import type { NamespaceHandlers } from '../type';
import { checkForUpdatesAndNotify, quitAndInstall } from './updater';

export const updaterHandlers = {
updateClient: async () => {
const { updateClient } = await import('./updater');
return updateClient();
currentVersion: async () => {
return app.getVersion();
},
quitAndInstall: async () => {
return quitAndInstall();
},
checkForUpdatesAndNotify: async () => {
return checkForUpdatesAndNotify(true);
},
} satisfies NamespaceHandlers;

Expand Down
64 changes: 46 additions & 18 deletions apps/electron/layers/main/src/handlers/updater/updater.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { app } from 'electron';
import type { AppUpdater } from 'electron-updater';
import { z } from 'zod';

Expand All @@ -21,10 +22,22 @@ const isDev = mode === 'development';

let _autoUpdater: AppUpdater | null = null;

export const updateClient = async () => {
export const quitAndInstall = async () => {
_autoUpdater?.quitAndInstall();
};

let lastCheckTime = 0;
export const checkForUpdatesAndNotify = async (force = true) => {
if (!_autoUpdater) {
return; // ?
}
// check every 30 minutes (1800 seconds) at most
if (force || lastCheckTime + 1000 * 1800 < Date.now()) {
lastCheckTime = Date.now();
return _autoUpdater.checkForUpdatesAndNotify();
}
};

export const registerUpdater = async () => {
// require it will cause some side effects and will break generate-main-exposed-meta,
// so we wrap it in a function
Expand All @@ -37,6 +50,9 @@ export const registerUpdater = async () => {
return;
}

// TODO: support auto update on windows and linux
const allowAutoUpdate = isMacOS();

_autoUpdater.autoDownload = false;
_autoUpdater.allowPrerelease = buildType !== 'stable';
_autoUpdater.autoInstallOnAppQuit = false;
Expand All @@ -49,24 +65,36 @@ export const registerUpdater = async () => {
releaseType: buildType === 'stable' ? 'release' : 'prerelease',
});

if (isMacOS()) {
_autoUpdater.on('update-available', () => {
// register events for checkForUpdatesAndNotify
_autoUpdater.on('update-available', info => {
if (allowAutoUpdate) {
_autoUpdater!.downloadUpdate();
logger.info('Update available, downloading...');
});
_autoUpdater.on('download-progress', e => {
logger.info(`Download progress: ${e.percent}`);
});
_autoUpdater.on('update-downloaded', e => {
updaterSubjects.clientUpdateReady.next({
version: e.version,
});
logger.info('Update downloaded, ready to install');
logger.info('Update available, downloading...', info);
}
updaterSubjects.updateAvailable.next({
version: info.version,
allowAutoUpdate,
});
_autoUpdater.on('error', e => {
logger.error('Error while updating client', e);
});
_autoUpdater.on('download-progress', e => {
logger.info(`Download progress: ${e.percent}`);
updaterSubjects.downloadProgress.next(e.percent);
});
_autoUpdater.on('update-downloaded', e => {
updaterSubjects.updateReady.next({
version: e.version,
allowAutoUpdate,
});
_autoUpdater.forceDevUpdateConfig = isDev;
await _autoUpdater.checkForUpdatesAndNotify();
}
// I guess we can skip it?
// updaterSubjects.clientDownloadProgress.next(100);
logger.info('Update downloaded, ready to install');
});
_autoUpdater.on('error', e => {
logger.error('Error while updating client', e);
});
_autoUpdater.forceDevUpdateConfig = isDev;

app.on('activate', async () => {
await checkForUpdatesAndNotify(false);
});
};
6 changes: 3 additions & 3 deletions apps/electron/layers/preload/preload.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/consistent-type-imports */

interface Window {
apis?: typeof import('./src/affine-apis').apis;
events?: typeof import('./src/affine-apis').events;
appInfo?: typeof import('./src/affine-apis').appInfo;
apis: typeof import('./src/affine-apis').apis;
events: typeof import('./src/affine-apis').events;
appInfo: typeof import('./src/affine-apis').appInfo;
}
7 changes: 3 additions & 4 deletions apps/web/src/components/root-app-sidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
QuickSearchInput,
SidebarContainer,
SidebarScrollableContainer,
updateAvailableAtom,
} from '@affine/component/app-sidebar';
import { config } from '@affine/env';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
Expand All @@ -20,7 +19,7 @@ import {
ShareIcon,
} from '@blocksuite/icons';
import type { Page } from '@blocksuite/store';
import { useAtom, useAtomValue } from 'jotai';
import { useAtom } from 'jotai';
import type { ReactElement } from 'react';
import type React from 'react';
import { useCallback, useEffect, useMemo } from 'react';
Expand Down Expand Up @@ -114,7 +113,6 @@ export const RootAppSidebar = ({
document.removeEventListener('keydown', keydown, { capture: true });
}, [sidebarOpen, setSidebarOpen]);

const clientUpdateAvailable = useAtomValue(updateAvailableAtom);
const [history, setHistory] = useHistoryAtom();
const router = useMemo(() => {
return {
Expand Down Expand Up @@ -192,7 +190,8 @@ export const RootAppSidebar = ({
</RouteMenuLinkItem>
</SidebarScrollableContainer>
<SidebarContainer>
{clientUpdateAvailable && <AppUpdaterButton />}
{environment.isDesktop && <AppUpdaterButton />}
<div />
<AddPageButton onClick={onClickNewPage} />
</SidebarContainer>
</AppSidebar>
Expand Down
Loading