diff --git a/src/main/events.ts b/src/main/events.ts index d720daa7..8eb61c9a 100644 --- a/src/main/events.ts +++ b/src/main/events.ts @@ -3,5 +3,5 @@ export const BROWSERS_SCANNED = 'BROWSERS_SCANNED' export const FAVOURITE_CHANGED = 'FAVOURITE_CHANGED' export const HOTKEYS_RETRIEVED = 'HOTKEYS_RETRIEVED' export const PROTOCOL_STATUS = 'PROTOCOL_STATUS' -export const UPDATE_STATUS = 'UPDATE_STATUS' +export const UPDATE_DOWNLOADED = 'UPDATE_DOWNLOADED' export const URL_UPDATED = 'URL_UPDATED' diff --git a/src/main/main.ts b/src/main/main.ts index 8e3e5697..04df18f7 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -1,8 +1,9 @@ -import { app, BrowserWindow, ipcMain, Tray } from 'electron' +import { app, autoUpdater, BrowserWindow, ipcMain, Tray } from 'electron' import electronIsDev from 'electron-is-dev' import execa from 'execa' import path from 'path' +import package_ from '../../package.json' import { Browser, browsers } from '../config/browsers' import { BROWSER_SELECTED, @@ -15,10 +16,10 @@ import { RELOAD, RENDERER_LOADED, SET_AS_DEFAULT_BROWSER, + UPDATE_RESTART, } from '../renderer/events' import copyToClipboard from '../utils/copyToClipboard' import getInstalledBrowsers from '../utils/getInstalledBrowsers' -import { checkForUpdate } from '../utils/isUpdateAvailable' import { logger } from '../utils/logger' import createWindow from './createWindow' import { @@ -27,7 +28,7 @@ import { FAVOURITE_CHANGED, HOTKEYS_RETRIEVED, PROTOCOL_STATUS, - UPDATE_STATUS, + UPDATE_DOWNLOADED, URL_UPDATED, } from './events' import { Hotkeys, store } from './store' @@ -60,6 +61,35 @@ app.on('ready', async () => { }) store.set('firstRun', false) + + // Auto update on production + if (!electronIsDev) { + const feedURL = `https://update.electronjs.org/will-stone/browserosaurus/darwin-x64/${app.getVersion()}` + + autoUpdater.setFeedURL({ + url: feedURL, + headers: { + 'User-Agent': `${package_.name}/${package_.version} (darwin: x64)`, + }, + }) + + autoUpdater.on('before-quit-for-update', () => { + // All windows must be closed before an update can be applied using "restart". + bWindow?.destroy() + }) + + autoUpdater.on('update-downloaded', () => { + bWindow?.webContents.send(UPDATE_DOWNLOADED) + }) + + // 1000 * 60 * 60 * 24 + const ONE_DAY_MS = 86400000 + // Check for updates every day. The first check is done on load: in the + // RENDERER_LOADED listener. + setInterval(() => { + autoUpdater.checkForUpdates() + }, ONE_DAY_MS) + } }) // App doesn't always close on ctrl-c in console, this fixes that @@ -79,26 +109,9 @@ async function sendUrl(url: string) { } } -// 1000 * 60 * 60 * 24 -const ONE_DAY_MS = 86400000 - -async function updateChecker(forceUpdateCheck = false) { - const lastUpdateCheck = store.get('lastUpdateCheck') || 0 - const now = Date.now() - const hasNotBeenCheckedRecently = lastUpdateCheck + ONE_DAY_MS < now - - if (forceUpdateCheck || hasNotBeenCheckedRecently) { - logger('Main', 'Checking for update') - const isUpdateAvailable = await checkForUpdate(app.getVersion()) - bWindow?.webContents.send(UPDATE_STATUS, isUpdateAvailable) - store.set('lastUpdateCheck', now) - } -} - app.on('open-url', (event, url) => { event.preventDefault() sendUrl(url) - updateChecker() }) /** @@ -130,7 +143,7 @@ ipcMain.on(RENDERER_LOADED, async () => { app.isDefaultProtocolClient('http'), ) - updateChecker(true) + autoUpdater.checkForUpdates() }) interface BrowserSelectedEventArgs { @@ -198,6 +211,10 @@ ipcMain.on(RELOAD, () => { bWindow?.reload() }) +ipcMain.on(UPDATE_RESTART, () => { + autoUpdater.quitAndInstall() +}) + ipcMain.on(QUIT, () => { app.quit() }) diff --git a/src/renderer/components/the-main-listeners.tsx b/src/renderer/components/the-main-listeners.tsx index 8aa385c4..cf386b47 100644 --- a/src/renderer/components/the-main-listeners.tsx +++ b/src/renderer/components/the-main-listeners.tsx @@ -9,7 +9,7 @@ import { FAVOURITE_CHANGED, HOTKEYS_RETRIEVED, PROTOCOL_STATUS, - UPDATE_STATUS, + UPDATE_DOWNLOADED, URL_UPDATED, } from '../../main/events' import { Hotkeys } from '../../main/store' @@ -19,7 +19,7 @@ import { favBrowserIdSelector, hotkeysSelector, isDefaultBrowserAtom, - updateAvailableAtom, + isUpdateAvailableAtom, urlSelector, versionAtom, } from '../state' @@ -30,7 +30,7 @@ const TheMainListeners: React.FC = () => { const setBrowsersState = useSetRecoilState(browsersAtom) const setVersion = useSetRecoilState(versionAtom) const setFavBrowserId = useSetRecoilState(favBrowserIdSelector) - const setUpdateAvailable = useSetRecoilState(updateAvailableAtom) + const setUpdateAvailable = useSetRecoilState(isUpdateAvailableAtom) const setIsDefaultBrowser = useSetRecoilState(isDefaultBrowserAtom) const setHotkeys = useSetRecoilState(hotkeysSelector) @@ -47,8 +47,8 @@ const TheMainListeners: React.FC = () => { * Receive update availability * main -> renderer */ - electron.ipcRenderer.on(UPDATE_STATUS, (_: unknown, bool: boolean) => { - setUpdateAvailable(bool) + electron.ipcRenderer.on(UPDATE_DOWNLOADED, () => { + setUpdateAvailable(true) }) /** diff --git a/src/renderer/components/the-status-bar.tsx b/src/renderer/components/the-status-bar.tsx index 5685850d..22c8f20a 100644 --- a/src/renderer/components/the-status-bar.tsx +++ b/src/renderer/components/the-status-bar.tsx @@ -6,15 +6,14 @@ import { faSync } from '@fortawesome/pro-solid-svg-icons/faSync' import { faTimes } from '@fortawesome/pro-solid-svg-icons/faTimes' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import cc from 'classcat' -import { shell } from 'electron' import React, { useCallback } from 'react' import { useRecoilState, useRecoilValue } from 'recoil' -import { quit, reload, setAsDefaultBrowser } from '../sendToMain' +import { quit, reload, setAsDefaultBrowser, updateRestart } from '../sendToMain' import { isDefaultBrowserAtom, + isUpdateAvailableAtom, openMenuSelector, - updateAvailableAtom, versionAtom, } from '../state' import { LightButton } from './button' @@ -26,14 +25,9 @@ interface Props { const TheStatusBar: React.FC = ({ className }) => { const [openMenu, setOpenMenu] = useRecoilState(openMenuSelector) const isDefaultBrowser = useRecoilValue(isDefaultBrowserAtom) - const updateAvailable = useRecoilValue(updateAvailableAtom) + const isUpdateAvailable = useRecoilValue(isUpdateAvailableAtom) const version = useRecoilValue(versionAtom) - const handleUpdateClick = useCallback( - () => shell.openExternal('https://browserosaurus.com'), - [], - ) - const handleFavMenuClick = useCallback(() => { setOpenMenu((menu) => (menu === 'fav' ? false : 'fav')) }, [setOpenMenu]) @@ -86,20 +80,21 @@ const TheStatusBar: React.FC = ({ className }) => { {displayedVersion} - {updateAvailable && ( - + {isUpdateAvailable ? ( + - Update Available + Update + ) : ( + <> + + + + + + + )} - - - - - - - - ) diff --git a/src/renderer/events.ts b/src/renderer/events.ts index 13fef75d..7c7f1c6c 100644 --- a/src/renderer/events.ts +++ b/src/renderer/events.ts @@ -8,3 +8,4 @@ export const QUIT = 'QUIT' export const RELOAD = 'RELOAD' export const RENDERER_LOADED = 'RENDERER_LOADED' export const SET_AS_DEFAULT_BROWSER = 'SET_AS_DEFAULT_BROWSER' +export const UPDATE_RESTART = 'UPDATE_RESTART' diff --git a/src/renderer/sendToMain.ts b/src/renderer/sendToMain.ts index 37266b3f..2a9fb18a 100644 --- a/src/renderer/sendToMain.ts +++ b/src/renderer/sendToMain.ts @@ -12,6 +12,7 @@ import { QUIT, RELOAD, SET_AS_DEFAULT_BROWSER, + UPDATE_RESTART, } from './events' export const selectBrowser = ( @@ -39,6 +40,8 @@ export const escapePressed = (): void => ipcRenderer.send(ESCAPE_PRESSED) export const mainLog = (string: string): void => ipcRenderer.send(LOGGER, string) +export const updateRestart = (): void => ipcRenderer.send(UPDATE_RESTART) + export const quit = (): void => ipcRenderer.send(QUIT) export const setAsDefaultBrowser = (): void => diff --git a/src/renderer/state.ts b/src/renderer/state.ts index 44716929..b5ea4958 100644 --- a/src/renderer/state.ts +++ b/src/renderer/state.ts @@ -28,8 +28,8 @@ const openMenuAtom = atom({ default: false, }) -export const updateAvailableAtom = atom({ - key: 'updateAvailableAtom', +export const isUpdateAvailableAtom = atom({ + key: 'isUpdateAvailableAtom', default: false, }) diff --git a/src/utils/isUpdateAvailable.ts b/src/utils/isUpdateAvailable.ts deleted file mode 100644 index 04d83a61..00000000 --- a/src/utils/isUpdateAvailable.ts +++ /dev/null @@ -1,28 +0,0 @@ -import fetch from 'node-fetch' -import semver from 'semver' - -import { logger } from './logger' - -export async function checkForUpdate(currentVersion: string): Promise { - try { - const url = `https://api.github.com/repos/will-stone/browserosaurus/releases/latest` - const rawData = await fetch(url, { - headers: { 'Content-Type': 'application/json' }, - }) - - const releaseInfo = await rawData.json() - - if (!rawData) { - return false - } - - const releaseNumber = releaseInfo.tag_name.startsWith('v') - ? releaseInfo.tag_name.slice(1) - : releaseInfo.tag_name - - return semver.gt(releaseNumber, currentVersion) - } catch (error) { - logger('Main', error.message) - return false - } -}