diff --git a/electron/constants.ts b/electron/constants.ts index 991ddf6..41ef082 100644 --- a/electron/constants.ts +++ b/electron/constants.ts @@ -1,3 +1,12 @@ export const WINDOW_CLOSE_EVENT = "window-close" export const OPEN_SETTINGS_EVENT = "open-settings" export const SETTINGS_CHANGE_EVENT = "settings-change" + +export const UPDATE_AVAILABLE_EVENT = "update-available" +export const UPDATE_NOT_AVAILABLE_EVENT = "update-not-available" +export const UPDATE_DOWNLOADED = "update-downloaded" +export const UPDATE_ERROR = "update-error" +export const UPDATE_DOWNLOAD_PROGRESS = "update-download-progress" + +export const UPDATE_START_DOWNLOAD = "auto-update:startDownload" +export const UPDATE_INSTALL_AND_RESTART = "auto-update:installAndRestart" diff --git a/electron/main/auto-update.ts b/electron/main/auto-update.ts new file mode 100644 index 0000000..8de71fe --- /dev/null +++ b/electron/main/auto-update.ts @@ -0,0 +1,70 @@ +import { autoUpdater } from "electron-updater" +import { app, dialog, ipcMain } from "electron" + +import { + UPDATE_AVAILABLE_EVENT, + UPDATE_NOT_AVAILABLE_EVENT, + UPDATE_DOWNLOADED, + UPDATE_DOWNLOAD_PROGRESS, + UPDATE_ERROR, + UPDATE_START_DOWNLOAD, + UPDATE_INSTALL_AND_RESTART, + } from '../constants' + + +// will reference the main window +let window + +// configure logging +const log = require('electron-log'); + +autoUpdater.logger = log +autoUpdater.logger.transports.file.level = "info" + +autoUpdater.autoDownload = false + +autoUpdater.on('error', (error) => { + window?.webContents.send(UPDATE_ERROR, error == null ? "unknown" : (error.stack || error).toString()) + //dialog.showErrorBox('Error: ', error == null ? "unknown" : (error.stack || error).toString()) +}) + +autoUpdater.on('update-available', (info) => { + window?.webContents.send(UPDATE_AVAILABLE_EVENT, { + version: info.version, + releaseDate: info.releaseDate, + currentVersion: app.getVersion(), + }) +}) + +autoUpdater.on('update-not-available', () => { + window?.webContents.send(UPDATE_NOT_AVAILABLE_EVENT) +}) + +autoUpdater.on('update-downloaded', () => { + window?.webContents.send(UPDATE_DOWNLOADED) +}) + +autoUpdater.on('download-progress', (info) => { + window?.webContents.send(UPDATE_DOWNLOAD_PROGRESS, { + percent: info.percent, + total: info.total, + transferred: info.transferred, + bytesPerSecond: info.bytesPerSecond, + }) +}) + +// handle messages from Vue components +ipcMain.handle(UPDATE_START_DOWNLOAD, () => { + autoUpdater.downloadUpdate() +}) + +ipcMain.handle(UPDATE_INSTALL_AND_RESTART, () => { + setImmediate(() => autoUpdater.quitAndInstall()) +}) + +export function checkForUpdates(win) { + window = win + + // check for updates + autoUpdater.checkForUpdates() +} diff --git a/electron/main/index.ts b/electron/main/index.ts index 017bfac..a88111e 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -2,12 +2,15 @@ import { app, BrowserWindow, shell, ipcMain, Menu, nativeTheme, globalShortcut } import { release } from 'node:os' import { join } from 'node:path' import * as jetpack from "fs-jetpack"; + import menu from './menu' import { initialContent, initialDevContent } from '../initial-content' import { WINDOW_CLOSE_EVENT, SETTINGS_CHANGE_EVENT } from '../constants'; import CONFIG from "../config" import { onBeforeInputEvent } from "../keymap" import { isMac } from '../detect-platform'; +import { checkForUpdates } from './auto-update'; + // The built directory structure // @@ -134,7 +137,9 @@ async function createWindow() { }) } -app.whenReady().then(createWindow) +app.whenReady().then(createWindow).then(async () => { + checkForUpdates(win) +}) app.on('window-all-closed', () => { win = null diff --git a/electron/preload/index.ts b/electron/preload/index.ts index 76c2d11..6059773 100644 --- a/electron/preload/index.ts +++ b/electron/preload/index.ts @@ -2,7 +2,18 @@ const { contextBridge } = require('electron') import darkMode from "./theme-mode" import { isMac, isWindows, isLinux } from "../detect-platform" import { ipcRenderer } from "electron" -import { WINDOW_CLOSE_EVENT, OPEN_SETTINGS_EVENT, SETTINGS_CHANGE_EVENT } from "../constants" +import { + WINDOW_CLOSE_EVENT, + OPEN_SETTINGS_EVENT, + SETTINGS_CHANGE_EVENT, + UPDATE_AVAILABLE_EVENT, + UPDATE_ERROR, + UPDATE_DOWNLOAD_PROGRESS, + UPDATE_NOT_AVAILABLE_EVENT, + UPDATE_START_DOWNLOAD, + UPDATE_INSTALL_AND_RESTART, + UPDATE_DOWNLOADED, +} from "../constants" import CONFIG from "../config" //contextBridge.exposeInMainWorld("platform", ) @@ -51,6 +62,23 @@ contextBridge.exposeInMainWorld("heynote", { onSettingsChange(callback) { ipcRenderer.on(SETTINGS_CHANGE_EVENT, (event, settings) => callback(settings)) }, + + autoUpdate: { + callbacks(callbacks) { + ipcRenderer.on(UPDATE_AVAILABLE_EVENT, (event, info) => callbacks?.updateAvailable(info)) + ipcRenderer.on(UPDATE_NOT_AVAILABLE_EVENT, (event) => callbacks?.updateNotAvailable()) + ipcRenderer.on(UPDATE_DOWNLOADED, (event) => callbacks?.updateDownloaded()) + ipcRenderer.on(UPDATE_ERROR, (event, error) => callbacks?.updateError(error)) + ipcRenderer.on(UPDATE_DOWNLOAD_PROGRESS, (event, progress) => callbacks?.updateDownloadProgress(progress)) + }, + + startDownload() { + ipcRenderer.invoke(UPDATE_START_DOWNLOAD) + }, + installAndRestart() { + ipcRenderer.invoke(UPDATE_INSTALL_AND_RESTART) + }, + }, }) diff --git a/package-lock.json b/package-lock.json index fad66b5..5525b9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,9 @@ "": { "name": "Heynote", "version": "1.0.0", + "dependencies": { + "electron-log": "^4.4.8" + }, "devDependencies": { "@codemirror/commands": "^6.1.2", "@codemirror/lang-cpp": "^6.0.2", @@ -34,6 +37,7 @@ "electron-builder": "^23.6.0", "electron-builder-notarize": "^1.5.1", "electron-store": "^8.1.0", + "electron-updater": "^5.3.0", "fs-jetpack": "^5.1.0", "prettier": "^2.8.4", "sass": "^1.57.1", @@ -1321,6 +1325,12 @@ "@types/node": "*" } }, + "node_modules/@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true + }, "node_modules/@types/verror": { "version": "1.10.6", "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.6.tgz", @@ -2831,6 +2841,11 @@ "node": ">= 10.0.0" } }, + "node_modules/electron-log": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-4.4.8.tgz", + "integrity": "sha512-QQ4GvrXO+HkgqqEOYbi+DHL7hj5JM+nHi/j+qrN9zeeXVKy8ZABgbu4CnG+BBqDZ2+tbeq9tUC4DZfIWFU5AZA==" + }, "node_modules/electron-notarize": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/electron-notarize/-/electron-notarize-1.2.2.tgz", @@ -3005,6 +3020,73 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/electron-updater": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-5.3.0.tgz", + "integrity": "sha512-iKEr7yQBcvnQUPnSDYGSWC9t0eF2YbZWeYYYZzYxdl+HiRejXFENjYMnYjoOm2zxyD6Cr2JTHZhp9pqxiXuCOw==", + "dev": true, + "dependencies": { + "@types/semver": "^7.3.6", + "builder-util-runtime": "9.1.1", + "fs-extra": "^10.0.0", + "js-yaml": "^4.1.0", + "lazy-val": "^1.0.5", + "lodash.escaperegexp": "^4.1.2", + "lodash.isequal": "^4.5.0", + "semver": "^7.3.5", + "typed-emitter": "^2.1.0" + } + }, + "node_modules/electron-updater/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-updater/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-updater/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/electron-updater/node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -3943,6 +4025,18 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "dev": true + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "dev": true + }, "node_modules/lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", @@ -4676,6 +4770,16 @@ "fsevents": "~2.3.2" } }, + "node_modules/rxjs": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", + "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", + "dev": true, + "optional": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -5069,6 +5173,13 @@ "utf8-byte-length": "^1.0.1" } }, + "node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "dev": true, + "optional": true + }, "node_modules/type-fest": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", @@ -5082,6 +5193,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typed-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-2.1.0.tgz", + "integrity": "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==", + "dev": true, + "optionalDependencies": { + "rxjs": "*" + } + }, "node_modules/typescript": { "version": "4.9.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", diff --git a/package.json b/package.json index e5e38d6..a4ea826 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "electron-builder": "^23.6.0", "electron-builder-notarize": "^1.5.1", "electron-store": "^8.1.0", + "electron-updater": "^5.3.0", "fs-jetpack": "^5.1.0", "prettier": "^2.8.4", "sass": "^1.57.1", @@ -61,5 +62,8 @@ "vite-plugin-electron-renderer": "^0.11.4", "vue": "^3.2.45", "vue-tsc": "^1.0.16" + }, + "dependencies": { + "electron-log": "^4.4.8" } } diff --git a/public/icons/update.svg b/public/icons/update.svg new file mode 100644 index 0000000..8612d9a --- /dev/null +++ b/public/icons/update.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/StatusBar.vue b/src/components/StatusBar.vue index 7172339..01a3081 100644 --- a/src/components/StatusBar.vue +++ b/src/components/StatusBar.vue @@ -1,5 +1,7 @@ + + + + + Downloading update… {{ updateProgressPercent }}% + + + + Update & Restart + + + + New version available! + + + + \ No newline at end of file