Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add electron auto updater #1396

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
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
Binary file modified bun.lockb
Binary file not shown.
24 changes: 18 additions & 6 deletions electron/main.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { app, BrowserWindow, protocol, screen } from 'electron'
import logger from 'electron-log'
import { join } from 'path'

import { setupAutoUpdater } from './services/auto-update'
import store from './services/config-store'
import { setupNetworkService } from './services/network'

// If the app is packaged, push logs to the system instead of the console
if (app.isPackaged) {
Object.assign(console, logger.functions)
}

export const ROOT_PATH = {
dist: join(__dirname, '..'),
}
Expand Down Expand Up @@ -33,11 +40,6 @@ function createWindow(): void {
store.set('windowBounds', { x, y, width, height })
})

// Test active push message to Renderer-process.
mainWindow.webContents.on('did-finish-load', () => {
mainWindow?.webContents.send('main-process-message', new Date().toLocaleString())
})

if (process.env.VITE_DEV_SERVER_URL) {
mainWindow.loadURL(process.env.VITE_DEV_SERVER_URL)
} else {
Expand Down Expand Up @@ -71,7 +73,17 @@ protocol.registerSchemesAsPrivileged([

setupNetworkService()

app.whenReady().then(createWindow)
app.whenReady().then(async () => {
console.log('Electron app is ready.')
console.log(`Cockpit version: ${app.getVersion()}`)

console.log('Creating window...')
createWindow()

setTimeout(() => {
setupAutoUpdater(mainWindow as BrowserWindow)
}, 5000)
})

app.on('before-quit', () => {
// @ts-ignore: import.meta.env does not exist in the types
Expand Down
12 changes: 12 additions & 0 deletions electron/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,16 @@ import { contextBridge, ipcRenderer } from 'electron'

contextBridge.exposeInMainWorld('electronAPI', {
getInfoOnSubnets: () => ipcRenderer.invoke('get-info-on-subnets'),
onUpdateAvailable: (callback: (info: any) => void) =>
ipcRenderer.on('update-available', (_event, info) => callback(info)),
onUpdateDownloaded: (callback: (info: any) => void) =>
ipcRenderer.on('update-downloaded', (_event, info) => callback(info)),
onCheckingForUpdate: (callback: () => void) => ipcRenderer.on('checking-for-update', () => callback()),
onUpdateNotAvailable: (callback: (info: any) => void) =>
ipcRenderer.on('update-not-available', (_event, info) => callback(info)),
onDownloadProgress: (callback: (info: any) => void) =>
ipcRenderer.on('download-progress', (_event, info) => callback(info)),
downloadUpdate: () => ipcRenderer.send('download-update'),
installUpdate: () => ipcRenderer.send('install-update'),
cancelUpdate: () => ipcRenderer.send('cancel-update'),
})
51 changes: 51 additions & 0 deletions electron/services/auto-update.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { BrowserWindow, ipcMain } from 'electron'
import electronUpdater, { type AppUpdater } from 'electron-updater'

/**
* Setup auto updater
* @param {BrowserWindow} mainWindow - The main Electron window
*/
export const setupAutoUpdater = (mainWindow: BrowserWindow): void => {
const autoUpdater: AppUpdater = electronUpdater.autoUpdater
autoUpdater.logger = console
autoUpdater.autoDownload = false // Prevent automatic downloads

autoUpdater
.checkForUpdates()
.then((e) => console.info(e))
.catch((e) => console.error(e))

autoUpdater.on('checking-for-update', () => {
mainWindow.webContents.send('checking-for-update')
})

autoUpdater.on('update-available', (info) => {
mainWindow.webContents.send('update-available', info)
})

autoUpdater.on('update-not-available', (info) => {
mainWindow.webContents.send('update-not-available', info)
})

autoUpdater.on('download-progress', (progressInfo) => {
mainWindow.webContents.send('download-progress', progressInfo)
})

autoUpdater.on('update-downloaded', (info) => {
mainWindow.webContents.send('update-downloaded', info)
})

// Add handlers for update control
ipcMain.on('download-update', () => {
autoUpdater.downloadUpdate()
})

ipcMain.on('install-update', () => {
autoUpdater.quitAndInstall()
})

ipcMain.on('cancel-update', () => {
autoUpdater.removeAllListeners('update-downloaded')
autoUpdater.removeAllListeners('download-progress')
})
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@
"browser-or-node": "^2.0.0",
"colord": "^2.9.3",
"date-fns": "^2.29.3",
"electron-log": "^5.2.0",
"electron-store": "^10.0.0",
"electron-updater": "^6.3.9",
"file-saver": "^2.0.5",
"floating-vue": "^5.0.3",
"flowbite": "^2.2.1",
Expand Down
2 changes: 2 additions & 0 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@
<Tutorial :show-tutorial="interfaceStore.isTutorialVisible" />
<VideoLibraryModal :open-modal="interfaceStore.isVideoLibraryVisible" />
<VehicleDiscoveryDialog v-model="showDiscoveryDialog" show-auto-search-option />
<UpdateNotification v-if="isElectron()" />
</template>

<script setup lang="ts">
Expand All @@ -325,6 +326,7 @@ import { useRoute } from 'vue-router'

import GlassModal from '@/components/GlassModal.vue'
import Tutorial from '@/components/Tutorial.vue'
import UpdateNotification from '@/components/UpdateNotification.vue'
import VehicleDiscoveryDialog from '@/components/VehicleDiscoveryDialog.vue'
import VideoLibraryModal from '@/components/VideoLibraryModal.vue'
import { useInteractionDialog } from '@/composables/interactionDialog'
Expand Down
181 changes: 181 additions & 0 deletions src/components/UpdateNotification.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
<template>
<InteractionDialog
v-model="showUpdateDialog"
:title="dialogTitle"
:message="dialogMessage"
:variant="dialogVariant"
:actions="dialogActions"
max-width="560"
>
<template #content>
<div v-if="updateInfo" class="mt-2">
<strong>Update Details:</strong>
<p>Current Version: {{ app_version.version }}</p>
<p>New Version: {{ updateInfo.version }}</p>
<p>Release Date: {{ formatDate(updateInfo.releaseDate) }}</p>
</div>
<v-progress-linear
v-if="showProgress"
:model-value="downloadProgress"
color="primary"
height="25"
rounded
class="my-4"
>
<template #default>
<strong>{{ Math.round(downloadProgress) }}%</strong>
</template>
</v-progress-linear>
</template>
</InteractionDialog>
</template>

<script setup lang="ts">
import { useStorage } from '@vueuse/core'
import { onBeforeMount, ref } from 'vue'

import InteractionDialog, { type Action } from '@/components/InteractionDialog.vue'
import { app_version } from '@/libs/cosmos'
import { isElectron } from '@/libs/utils'

const showUpdateDialog = ref(false)
const dialogTitle = ref('')
const dialogMessage = ref('')
const dialogVariant = ref<'error' | 'info' | 'success' | 'warning' | 'text-only'>('info')
const showProgress = ref(false)
const downloadProgress = ref(0)
const dialogActions = ref<Action[]>([])
const updateInfo = ref({
version: '',
releaseDate: '',
releaseNotes: '',
})
const ignoredUpdateVersions = useStorage<string[]>('cockpit-ignored-update-versions', [])

const formatDate = (date: string): string => {
return new Date(date).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })
}

onBeforeMount(() => {
if (!isElectron()) {
console.info('Not in Electron environment. UpdateNotification will not be initialized.')
return
}

if (!window.electronAPI) {
console.error('window.electronAPI is not defined. UpdateNotification will not be initialized.')
return
}

// Listen for update events
window.electronAPI.onCheckingForUpdate(() => {
console.log('Checking if there are updates for the Electron app...')
dialogTitle.value = 'Checking for Updates'
dialogMessage.value = 'Looking for new versions of the application...'
dialogVariant.value = 'info'
dialogActions.value = []
showProgress.value = false
showUpdateDialog.value = true
})

window.electronAPI.onUpdateNotAvailable(() => {
console.log('No updates available for the Electron app.')
dialogTitle.value = 'No Updates Available'
dialogMessage.value = 'You are running the latest version of the application.'
dialogVariant.value = 'success'
dialogActions.value = [
{
text: 'OK',
action: () => {
showUpdateDialog.value = false
},
},
]
showProgress.value = false
})

window.electronAPI.onUpdateAvailable((info) => {
console.log('Update available for the Electron app.', info)
dialogTitle.value = 'Update Available'
dialogMessage.value = 'A new version of the application is available. Would you like to download it now?'
dialogVariant.value = 'info'
updateInfo.value = { ...info }
dialogActions.value = [
{
text: 'Ignore This Version',
action: () => {
console.log(`User chose to ignore version ${updateInfo.value.version}`)
ignoredUpdateVersions.value.push(updateInfo.value.version)
window.electronAPI!.cancelUpdate()
showUpdateDialog.value = false
},
},
{
text: 'Download',
action: () => {
window.electronAPI!.downloadUpdate()
showProgress.value = true
dialogActions.value = [
{
text: 'Cancel',
action: () => {
console.log('User chose to cancel the update for the Electron app.')
window.electronAPI!.cancelUpdate()
showUpdateDialog.value = false
dialogMessage.value = 'Downloading update...'
},
},
]
},
},
{
text: 'Not Now',
action: () => {
window.electronAPI!.cancelUpdate()
showUpdateDialog.value = false
},
},
]

// Check if this version is in the ignored list
if (ignoredUpdateVersions.value.includes(info.version)) {
console.log(`Skipping ignored version ${info.version}.`)
showUpdateDialog.value = false
return
}

showUpdateDialog.value = true
})

window.electronAPI.onDownloadProgress((progressInfo) => {
downloadProgress.value = progressInfo.percent
})

window.electronAPI.onUpdateDownloaded(() => {
console.log('Finished downloading the update for the Electron app.')
dialogTitle.value = 'Update Ready to Install'
dialogMessage.value =
'The update has been downloaded. Would you like to install it now? The application will restart during installation.'
dialogVariant.value = 'info'
showProgress.value = false
dialogActions.value = [
{
text: 'Install Now',
action: () => {
console.log('User chose to install the update for the Electron app now.')
window.electronAPI!.installUpdate()
showUpdateDialog.value = false
},
},
{
text: 'Later',
action: () => {
console.log('User chose to install the update for the Electron app later.')
showUpdateDialog.value = false
},
},
]
showUpdateDialog.value = true
})
})
</script>
Loading
Loading