From 98385b1e0dd6c897b47ee79bbae30b1ad51bf79e Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Tue, 27 Feb 2024 19:42:29 -0800 Subject: [PATCH] Enable automatic updates on macOS using Electron's autoUpdater (#342) * clean up emain * add restart on disable auto update * save work * add zip * save * fix zip command * make build-universal more generic * clean up script * fix autoupdate config * update feed * fix update feed path * switch to custom update flow * show notification * remove enum * test change * debug sidebar * save work * remove weird import * remove listeners if present * save debug * fixed banner * fix sending of appupdatestatus, add comments * Change to primary feed * more comments, less debugs * rename to app update * code cleanup, add fireAndForget * update paths for objects --- buildres/upload-release.sh | 86 ++++++ src/app/clientsettings/clientsettings.tsx | 3 +- src/app/sidebar/sidebar.tsx | 53 +++- src/electron/emain.ts | 350 +++++++++++++++------- src/electron/preload.js | 4 + src/models/model.ts | 30 +- src/types/custom.d.ts | 5 + src/util/util.ts | 11 + 8 files changed, 415 insertions(+), 127 deletions(-) create mode 100644 buildres/upload-release.sh diff --git a/buildres/upload-release.sh b/buildres/upload-release.sh new file mode 100644 index 000000000..d0f308783 --- /dev/null +++ b/buildres/upload-release.sh @@ -0,0 +1,86 @@ +#!/bin/bash +# This script is used to upload signed and notarized releases to S3 and update the Electron auto-update release feeds. + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +BUILDS_DIR=$SCRIPT_DIR/builds +TEMP2_DIR=$SCRIPT_DIR/temp2 + +MAIN_RELEASE_PATH="dl.waveterm.dev/build" +AUTOUPDATE_RELEASE_PATH="dl.waveterm.dev/autoupdate" + +# Copy the builds to the temp2 directory +echo "Copying builds to temp2" +rm -rf $TEMP2_DIR +mkdir -p $TEMP2_DIR +cp -r $BUILDS_DIR/* $TEMP2_DIR + +UVERSION=$(cat $TEMP2_DIR/version.txt) + +if [ -z "$UVERSION" ]; then + echo "version.txt is empty" + exit 1 +fi + +# Find the DMG file +echo "Finding DMG" +DMG=$(find $TEMP2_DIR -type f -iname "*.dmg") +# Ensure there is only one +NUM_DMGS=$(echo $DMG | wc -l) +if [ "0" -eq "$NUM_DMGS" ]; then + echo "no DMG found in $TEMP2_DIR" + exit 1 +elif [ "1" -lt "$NUM_DMGS" ]; then + echo "multiple DMGs found in $TEMP2_DIR" + exit 1 +fi + +# Find the Mac zip +echo "Finding Mac zip" +MAC_ZIP=$(find $TEMP2_DIR -type f -iname "*mac*.zip") +# Ensure there is only one +NUM_MAC_ZIPS=$(echo $MAC_ZIP | wc -l) +if [ "0" -eq "$NUM_MAC_ZIPS" ]; then + echo "no Mac zip found in $TEMP2_DIR" + exit 1 +elif [ "1" -lt "$NUM_MAC_ZIPS" ]; then + echo "multiple Mac zips found in $TEMP2_DIR" + exit 1 +fi + +# Find the Linux zips +echo "Finding Linux zips" +LINUX_ZIPS=$(find $TEMP2_DIR -type f -iname "*linux*.zip") +# Ensure there is at least one +NUM_LINUX_ZIPS=$(echo $LINUX_ZIPS | wc -l) +if [ "0" -eq "$NUM_LINUX_ZIPS" ]; then + echo "no Linux zips found in $TEMP2_DIR" + exit 1 +fi + +# Upload the DMG +echo "Uploading DMG" +DMG_NAME=$(basename $DMG) +aws s3 cp $DMG s3:/$MAIN_RELEASE_PATH/$DMG_NAME + +# Upload the Linux zips +echo "Uploading Linux zips" +for LINUX_ZIP in $LINUX_ZIPS; do + LINUX_ZIP_NAME=$(basename $LINUX_ZIP) + aws s3 cp $LINUX_ZIP s3://$MAIN_RELEASE_PATH/$LINUX_ZIP_NAME +done + +# Upload the autoupdate Mac zip +echo "Uploading Mac zip" +MAC_ZIP_NAME=$(basename $MAC_ZIP) +aws s3 cp $MAC_ZIP s3://$AUTOUPDATE_RELEASE_PATH/$MAC_ZIP_NAME + +# Update the autoupdate feeds +echo "Updating autoupdate feeds" +RELEASES_CONTENTS="{\"name\":\"$UVERSION\",\"notes\":\"\",\"url\":\"https://$AUTOUPDATE_RELEASE_PATH/$MAC_ZIP_NAME\"}" +aws s3 cp - s3://$AUTOUPDATE_RELEASE_PATH/darwin/arm64/RELEASES.json <<< $RELEASES_CONTENTS +aws s3 cp - s3://$AUTOUPDATE_RELEASE_PATH/darwin/x64/RELEASES.json <<< $RELEASES_CONTENTS + +# Clean up +echo "Cleaning up" +rm -rf $TEMP2_DIR diff --git a/src/app/clientsettings/clientsettings.tsx b/src/app/clientsettings/clientsettings.tsx index 67693fc59..3403a960a 100644 --- a/src/app/clientsettings/clientsettings.tsx +++ b/src/app/clientsettings/clientsettings.tsx @@ -6,7 +6,7 @@ import * as mobxReact from "mobx-react"; import * as mobx from "mobx"; import { boundMethod } from "autobind-decorator"; import cn from "classnames"; -import { GlobalModel, GlobalCommandRunner, RemotesModel } from "@/models"; +import { GlobalModel, GlobalCommandRunner, RemotesModel, getApi } from "@/models"; import { Toggle, InlineSettingsTextEdit, SettingsError, Dropdown } from "@/common/elements"; import { commandRtnHandler, isBlank } from "@/util/util"; import * as appconst from "@/app/appconst"; @@ -64,6 +64,7 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove prtn = GlobalCommandRunner.releaseCheckAutoOff(false); } commandRtnHandler(prtn, this.errorMessage); + getApi().changeAutoUpdate(val); } getFontSizes(): DropdownItem[] { diff --git a/src/app/sidebar/sidebar.tsx b/src/app/sidebar/sidebar.tsx index aef98961a..77dc378c6 100644 --- a/src/app/sidebar/sidebar.tsx +++ b/src/app/sidebar/sidebar.tsx @@ -169,6 +169,44 @@ class MainSideBar extends React.Component { GlobalModel.modalsModel.pushModal(appconst.SESSION_SETTINGS); } + /** + * Get the update banner for the app, if we need to show it. + * @returns Either a banner to install the ready update, a link to the download page, or null if no update is available. + */ + @boundMethod + getUpdateAppBanner(): React.ReactNode { + if (GlobalModel.platform == "darwin") { + const status = GlobalModel.appUpdateStatus.get(); + if (status == "ready") { + return ( + } + contents="Click to Install Update" + onClick={() => GlobalModel.installAppUpdate()} + /> + ); + } + } else { + const clientData = this.props.clientData; + if (!clientData?.clientopts.noreleasecheck && !isBlank(clientData?.releaseinfo?.latestversion)) { + if (compareLoose(appconst.VERSION, clientData.releaseinfo.latestversion) < 0) { + return ( + } + contents="Update Available" + onClick={() => openLink("https://www.waveterm.dev/download?ref=upgrade")} + /> + ); + } + } + } + return null; + } + getSessions() { if (!GlobalModel.sessionListLoaded.get()) return
loading ...
; const sessionList: Session[] = []; @@ -227,11 +265,6 @@ class MainSideBar extends React.Component { } render() { - const clientData = this.props.clientData; - let needsUpdate = false; - if (!clientData?.clientopts.noreleasecheck && !isBlank(clientData?.releaseinfo?.latestversion)) { - needsUpdate = compareLoose(appconst.VERSION, clientData.releaseinfo.latestversion) < 0; - } const mainSidebar = GlobalModel.mainSidebarModel; const isCollapsed = mainSidebar.getCollapsed(); return ( @@ -291,15 +324,7 @@ class MainSideBar extends React.Component { {this.getSessions()}