Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 38 additions & 27 deletions src/app/githubReleaseNotification.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down Expand Up @@ -70,49 +70,40 @@ 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<boolean>} 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.
if (!latest) {
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
Expand All @@ -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)
}

Expand All @@ -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,
}
3 changes: 2 additions & 1 deletion src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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
Expand Down
17 changes: 17 additions & 0 deletions src/preload.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,23 @@ const TALK_DESKTOP = {
* @return {Promise<void>}
*/
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
*
Expand Down
51 changes: 47 additions & 4 deletions src/talk/renderer/TitleBar/components/MainMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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()
}
})

</script>

<template>
Expand All @@ -39,10 +57,13 @@ const openInWeb = () => window.open(generateUrl(getCurrentTalkRoutePath()), '_bl
container="body">
<template #icon>
<IconMenu :size="20" fill-color="var(--color-background-plain-text)" />
<div v-if="updateAvailable" class="menu-notification-badge">
<IconCircle :size="10" fill-color="#FFFFFF" />
</div>
</template>

<template v-if="isTalkInitialized">
<NcActionButton @click="openInWeb">
<NcActionButton close-after-click @click="openInWeb">
<template #icon>
<IconWeb :size="20" />
</template>
Expand All @@ -58,7 +79,21 @@ const openInWeb = () => window.open(generateUrl(getCurrentTalkRoutePath()), '_bl
</template>
{{ t('talk_desktop', 'Force reload') }}
</NcActionButton>
<NcActionLink v-if="!BUILD_CONFIG.isBranded" :href="packageInfo.bugs.create || packageInfo.bugs.url" target="_blank">
<NcActionLink
v-if="updateAvailable"
close-after-click
href="https://github.com/nextcloud/talk-desktop/releases/latest"
target="_blank">
<template #icon>
<IconCloudDownloadOutline :size="20" />
</template>
{{ t('talk_desktop', 'Update available!\nDownload latest version') }}
</NcActionLink>
<NcActionLink
v-if="!BUILD_CONFIG.isBranded"
close-after-click
:href="packageInfo.bugs.create || packageInfo.bugs.url"
target="_blank">
<template #icon>
<IconBugOutline :size="20" />
</template>
Expand All @@ -73,11 +108,19 @@ const openInWeb = () => window.open(generateUrl(getCurrentTalkRoutePath()), '_bl
</template>
{{ t('talk_desktop', 'App settings') }}
</NcActionButton>
<NcActionButton @click="showHelp">
<NcActionButton close-after-click @click="showHelp">
<template #icon>
<IconInformationOutline :size="20" />
</template>
{{ t('talk_desktop', 'About') }}
</NcActionButton>
</NcActions>
</template>

<style lang="scss" scoped>
.menu-notification-badge {
position: absolute;
top: 1px;
right: 1px;
}
</style>
Loading