diff --git a/src/app/githubReleaseNotification.service.js b/src/app/githubReleaseNotification.service.js index 93db2db27..655dfbb00 100644 --- a/src/app/githubReleaseNotification.service.js +++ b/src/app/githubReleaseNotification.service.js @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -const { Notification, shell } = require('electron') +const { BrowserWindow, ipcMain } = require('electron') const semver = require('semver') const packageJson = require('../../package.json') @@ -70,33 +70,14 @@ async function getLatestStableReleaseVersion() { } } -/** - * Show native notification about the new version. - * It opens the release page on click. - * - * @param {string} version - New version - */ -function notifyAboutNewVersion(version) { - const notification = new Notification({ - title: '🎉 New version of Nextcloud Talk is available!', - body: `Nextcloud Talk ${version} is now available to download from the release page. Click to open the page.`, - }) - notification.on('click', () => { - shell.openExternal(`https://github.com/nextcloud-releases/talk-desktop/releases/${version}`) - }) - notification.show() -} - /** * Check if there is a new Nextcloud Talk * * @param {object} options options - * @param {boolean} [options.showNotification] Show native notification if new version is available * @param {boolean} [options.forceRequest] Force request even if there is a cached new version * @return {Promise} true if there is a new version */ -async function checkForNewVersion({ showNotification = false, forceRequest = false }) { - // Request a new version or get cached +async function checkForNewVersion({ forceRequest = false }) { const latest = (!forceRequest && cachedNewLatestVersion) ? cachedNewLatestVersion : await getLatestReleaseVersion(__CHANNEL__ === 'beta') // Something goes wrong... No worries, we will try again later. @@ -104,15 +85,25 @@ async function checkForNewVersion({ showNotification = false, forceRequest = fal return false } + // No new version compared to the running one if (semver.lte(latest, packageJson.version)) { return false } - // There is a new version! Now we may cache it and stop requesting a new version + // There is a new version! Cache it and notify renderers cachedNewLatestVersion = latest - if (showNotification) { - notifyAboutNewVersion(latest) + // Send an IPC message to all renderer windows so UI can update + try { + BrowserWindow.getAllWindows().forEach((window) => { + try { + window.webContents.send('github-release:new-version') + } catch (e) { + console.error('Failed to notify window about new release', e) + } + }) + } catch (e) { + console.error('Failed to broadcast new release', e) } return true @@ -132,10 +123,10 @@ function setupReleaseNotificationScheduler(intervalInMin = 60) { if (schedulerIntervalId !== undefined) { stopReleaseNotificationScheduler() } - checkForNewVersion({ showNotification: true }) + checkForNewVersion({ }) const MS_IN_MIN = 60 * 1000 schedulerIntervalId = setInterval(() => { - checkForNewVersion({ showNotification: true }) + checkForNewVersion({ }) }, intervalInMin * MS_IN_MIN) } @@ -145,11 +136,31 @@ function setupReleaseNotificationScheduler(intervalInMin = 60) { * @return {void} */ function stopReleaseNotificationScheduler() { - if (schedulerIntervalId) { + if (schedulerIntervalId !== undefined) { clearInterval(schedulerIntervalId) + schedulerIntervalId = undefined } } +/** + * Register IPC handlers used by renderers + */ +function registerUpdateIpcHandlers() { + if (!ipcMain) { + return + } + ipcMain.handle('github-release:check', async () => { + try { + const result = await checkForNewVersion({ }) + return { available: !!result } + } catch (e) { + console.error('github-release:check handler failed', e) + return { available: false } + } + }) +} + module.exports = { setupReleaseNotificationScheduler, + registerUpdateIpcHandlers, } diff --git a/src/main.js b/src/main.js index f2cac3462..53398df3b 100644 --- a/src/main.js +++ b/src/main.js @@ -14,7 +14,7 @@ const { registerAppProtocolHandler } = require('./app/appProtocol.ts') const { verifyCertificate, promptCertificateTrust } = require('./app/certificate.service.ts') const { openChromeWebRtcInternals } = require('./app/dev.utils.ts') const { triggerDownloadUrl } = require('./app/downloads.ts') -const { setupReleaseNotificationScheduler } = require('./app/githubReleaseNotification.service.js') +const { setupReleaseNotificationScheduler, registerUpdateIpcHandlers } = require('./app/githubReleaseNotification.service.js') const { initLaunchAtStartupListener } = require('./app/launchAtStartup.config.ts') const { systemInfo, isLinux, isMac, isWindows, isSameExecution } = require('./app/system.utils.ts') const { applyTheme } = require('./app/theme.config.ts') @@ -150,6 +150,7 @@ app.whenReady().then(async () => { applyTheme() initLaunchAtStartupListener() registerAppProtocolHandler() + registerUpdateIpcHandlers() // Open in the background if it is explicitly set, or the app was open at login on macOS const openInBackground = ARGUMENTS.openInBackground || app.getLoginItemSettings().wasOpenedAtLogin diff --git a/src/preload.js b/src/preload.js index 76c774029..7c9498634 100644 --- a/src/preload.js +++ b/src/preload.js @@ -186,6 +186,23 @@ const TALK_DESKTOP = { * @return {Promise} */ showHelp: () => ipcRenderer.invoke('help:show'), + /** + * Check for a new GitHub release and receive info + * + * @return {Promise<{available:boolean,tag:string|null,url:string|null}>} + */ + checkForUpdate: () => ipcRenderer.invoke('github-release:check'), + /** + * Listen for push notifications about new releases + * + * @param {() => void} callback - Callback + * @return {() => void} unsubscribe + */ + onNewVersion: (callback) => { + const handler = () => callback() + ipcRenderer.on('github-release:new-version', handler) + return () => ipcRenderer.removeListener('github-release:new-version', handler) + }, /** * Show the upgrade window * diff --git a/src/talk/renderer/TitleBar/components/MainMenu.vue b/src/talk/renderer/TitleBar/components/MainMenu.vue index 47fc3d64d..d73ef7190 100644 --- a/src/talk/renderer/TitleBar/components/MainMenu.vue +++ b/src/talk/renderer/TitleBar/components/MainMenu.vue @@ -8,12 +8,14 @@ import type { Ref } from 'vue' import { t } from '@nextcloud/l10n' import { generateUrl } from '@nextcloud/router' -import { inject } from 'vue' +import { inject, onBeforeUnmount, onMounted, ref } from 'vue' import NcActionButton from '@nextcloud/vue/components/NcActionButton' import NcActionLink from '@nextcloud/vue/components/NcActionLink' import NcActions from '@nextcloud/vue/components/NcActions' import NcActionSeparator from '@nextcloud/vue/components/NcActionSeparator' import IconBugOutline from 'vue-material-design-icons/BugOutline.vue' +import IconCircle from 'vue-material-design-icons/Circle.vue' +import IconCloudDownloadOutline from 'vue-material-design-icons/CloudDownloadOutline.vue' import IconCogOutline from 'vue-material-design-icons/CogOutline.vue' import IconInformationOutline from 'vue-material-design-icons/InformationOutline.vue' import IconMenu from 'vue-material-design-icons/Menu.vue' @@ -30,6 +32,22 @@ const showHelp = () => window.TALK_DESKTOP.showHelp() const reload = () => window.location.reload() const openSettings = () => window.OCA.Talk.Settings.open() const openInWeb = () => window.open(generateUrl(getCurrentTalkRoutePath()), '_blank') + +const updateAvailable = ref(false) + +let unsubscribeNewVersion = null +onMounted(() => { + unsubscribeNewVersion = window.TALK_DESKTOP.onNewVersion(() => { + updateAvailable.value = true + }) + window.TALK_DESKTOP.checkForUpdate() +}) +onBeforeUnmount(() => { + if (typeof unsubscribeNewVersion === 'function') { + unsubscribeNewVersion() + } +}) + {{ t('talk_desktop', 'App settings') }} - + @@ -81,3 +116,11 @@ const openInWeb = () => window.open(generateUrl(getCurrentTalkRoutePath()), '_bl + +