diff --git a/package.json b/package.json index ce4b4b0d..114c9c56 100644 --- a/package.json +++ b/package.json @@ -23,11 +23,11 @@ "url": "git+https://github.com/opencor/webapp.git" }, "type": "module", - "version": "0.20260208.0", + "version": "0.20260209.0", "scripts": { "archive:web": "bun src/renderer/scripts/archive.web.js", "build": "electron-vite build", - "build:web": "cd ./src/renderer && vite build", + "build:web": "cd ./src/renderer && vite build && bun scripts/generate.version.js", "clean": "bun src/renderer/scripts/clean.js", "dependencies:update": "bun clean && bun update -i && bun install && bun clean && cd ./src/renderer && bun clean && bun update -i && bun install && bun clean", "dev": "electron-vite dev --watch", diff --git a/src/renderer/package.json b/src/renderer/package.json index adaeeb81..17a58a35 100644 --- a/src/renderer/package.json +++ b/src/renderer/package.json @@ -39,9 +39,9 @@ }, "./style.css": "./dist/opencor.css" }, - "version": "0.20260208.0", + "version": "0.20260209.0", "scripts": { - "build": "vite build", + "build": "vite build && bun scripts/generate.version.js", "build:lib": "vite build --config vite.lib.config.ts && cp index.d.ts dist/index.d.ts", "clean": "bun scripts/clean.js", "dependencies:update": "cd ../.. && bun clean && bun update -i && bun install && bun clean && cd src/renderer && bun clean && bun update -i && bun install && bun clean", diff --git a/src/renderer/scripts/archive.web.js b/src/renderer/scripts/archive.web.js index 791513e2..75d3ce02 100644 --- a/src/renderer/scripts/archive.web.js +++ b/src/renderer/scripts/archive.web.js @@ -5,7 +5,7 @@ if (!fs.existsSync('dist')) { fs.mkdirSync('dist'); } -const version = JSON.parse(fs.readFileSync('package.json', 'utf8')).version; +const version = JSON.parse(fs.readFileSync('package.json')).version; tar.c( { diff --git a/src/renderer/scripts/generate.version.js b/src/renderer/scripts/generate.version.js new file mode 100644 index 00000000..b9edf6a8 --- /dev/null +++ b/src/renderer/scripts/generate.version.js @@ -0,0 +1,26 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +// Retrieve the version from the package.json file. + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const packageJsonPath = path.join(__dirname, '../package.json'); +const packageJson = JSON.parse(fs.readFileSync(packageJsonPath)); +const { version } = packageJson; + +// Make sure that the dist/assets folder exists. + +const distAssetsPath = path.join(__dirname, '../dist/assets'); + +if (!fs.existsSync(distAssetsPath)) { + fs.mkdirSync(distAssetsPath, { recursive: true }); +} + +// Write the version file. + +fs.writeFileSync(path.join(distAssetsPath, 'version.json'), JSON.stringify({ version }, null, 2)); + +// Log the generated version. + +console.log(`Generated version.json with version ${version}.`); diff --git a/src/renderer/src/common/settings.ts b/src/renderer/src/common/settings.ts index 6160e838..96451d43 100644 --- a/src/renderer/src/common/settings.ts +++ b/src/renderer/src/common/settings.ts @@ -11,7 +11,7 @@ class Settings { // Start with some default settings and then load them. // Note: to have default settings is critical when running the desktop version of OpenCOR since they are loaded // asynchronously, so we need to ensure that our settings are always defined. This is not the case for the - // web version of OpenCOR, where we can load our settings directly from the cookies. + // Web version of OpenCOR, where we can load our settings directly from the cookies. this.reset(); this.load(); diff --git a/src/renderer/src/common/version.ts b/src/renderer/src/common/version.ts new file mode 100644 index 00000000..773b11b8 --- /dev/null +++ b/src/renderer/src/common/version.ts @@ -0,0 +1,122 @@ +import * as vue from 'vue'; + +import packageJson from '../../package.json' with { type: 'json' }; + +import { electronApi } from './electronApi.ts'; + +const { version } = packageJson; + +// State to track whether an update is available and the latest version. + +const updateAvailable = vue.ref(false); +const latestVersion = vue.ref(''); + +// Check if a new version is available. + +interface IVersionInfo { + version: string; +} + +const checkForUpdates = async (): Promise => { + // Make sure that we are not running the desktop version of OpenCOR. + + if (electronApi) { + return false; + } + + // Get the latest version information from the server and compare it with the current version. + + try { + // Fetch the version.json file from the server with cache busting (i.e. by adding a timestamp query parameter to the + // URL so that the browser doesn't serve a cached version). + + const response = await fetch(`./assets/version.json?t=${Date.now()}`); + + if (!response.ok) { + console.warn('Failed to fetch the version information.'); + + updateAvailable.value = false; + latestVersion.value = ''; + + return false; + } + + const versionInfo: IVersionInfo = await response.json(); + + latestVersion.value = versionInfo.version; + + // Compare versions. + + const isNewer = isNewerVersion(latestVersion.value, version); + + updateAvailable.value = isNewer; + + return isNewer; + } catch (error) { + console.error('Failed to check for updates:', error); + + updateAvailable.value = false; + latestVersion.value = ''; + + return false; + } +}; + +// Return whether the first version is newer than the second version. + +const isNewerVersion = (versionA: string, versionB: string): boolean => { + const partsA = versionA.split('.').map(Number); + const partsB = versionB.split('.').map(Number); + + for (let i = 0; i < Math.max(partsA.length, partsB.length); ++i) { + const partA = partsA[i] || 0; + const partB = partsB[i] || 0; + + if (partA > partB) { + return true; + } + + if (partA < partB) { + return false; + } + } + + return false; +}; + +// Start periodic version checking (every 5 minutes). + +let checkInterval: number | null = null; + +const startCheck = (): void => { + // Make sure that we are not running the desktop version of OpenCOR. + + if (electronApi) { + return; + } + + // Check immediately on start. + + checkForUpdates(); + + // Then check every 5 minutes. + + if (!checkInterval) { + checkInterval = window.setInterval( + () => { + checkForUpdates(); + }, + 5 * 60 * 1000 // Every 5 minutes. + ); + } +}; + +// Reload the Web app to get the latest version (force cache bypass). + +const reloadApp = (): void => { + window.location.replace(window.location.href); +}; + +// Export the version checking functions and state. + +export { latestVersion, reloadApp, startCheck, updateAvailable }; diff --git a/src/renderer/src/components/BackgroundComponent.vue b/src/renderer/src/components/BackgroundComponent.vue index f8ed08ff..311c3d61 100644 --- a/src/renderer/src/components/BackgroundComponent.vue +++ b/src/renderer/src/components/BackgroundComponent.vue @@ -18,8 +18,9 @@ * - Height: 257.92px + 8px = 266.92px. */ -import { COPYRIGHT } from '../common/constants.ts'; import { version } from '../../package.json'; + +import { COPYRIGHT } from '../common/constants.ts'; diff --git a/src/renderer/src/components/OpenCOR.vue b/src/renderer/src/components/OpenCOR.vue index 4a43ea22..54cdc5ac 100644 --- a/src/renderer/src/components/OpenCOR.vue +++ b/src/renderer/src/components/OpenCOR.vue @@ -125,6 +125,7 @@ import { electronApi } from '../common/electronApi.ts'; import firebaseConfig, { missingFirebaseKeys } from '../common/firebaseConfig'; */ import * as locCommon from '../common/locCommon.ts'; +import * as version from '../common/version.ts'; import * as vueCommon from '../common/vueCommon.ts'; import type IContentsComponent from '../components/ContentsComponent.vue'; import * as locApi from '../libopencor/locApi.ts'; @@ -262,6 +263,10 @@ if (!window.locApi) { vue.watch(locApiInitialised, (newLocApiInitialised: boolean) => { if (newLocApiInitialised) { loadingOpencorMessageVisible.value = false; + + // We are now officially loaded, so start checking for a newer version of OpenCOR. + + version.startCheck(); } }); } diff --git a/src/renderer/src/components/dialogs/AboutDialog.vue b/src/renderer/src/components/dialogs/AboutDialog.vue index ca2d7fcd..7818c790 100644 --- a/src/renderer/src/components/dialogs/AboutDialog.vue +++ b/src/renderer/src/components/dialogs/AboutDialog.vue @@ -41,11 +41,11 @@