diff --git a/extension/src/_locales/en/messages.json b/extension/src/_locales/en/messages.json index c71b003..424c0fa 100755 --- a/extension/src/_locales/en/messages.json +++ b/extension/src/_locales/en/messages.json @@ -331,6 +331,10 @@ "message": "Update Now...", "description": "Update now button title. Ellipsis indicates additional information as it triggers an update window popup." }, + "optionsBridgeUpdateViewChangelog": { + "message": "View Changelog", + "description": "Link text for changelog link next to bridge update button." + }, "optionsBridgeBackupEnabled": { "message": "Enable backup daemon connection on $hostPort$", diff --git a/extension/src/manifest.json b/extension/src/manifest.json index a0dba49..ae4c692 100755 --- a/extension/src/manifest.json +++ b/extension/src/manifest.json @@ -41,6 +41,7 @@ "page": "ui/options/index.html" }, "permissions": [ + "downloads", "history", "menus", "menus.overrideContext", diff --git a/extension/src/ui/options/Bridge.svelte b/extension/src/ui/options/Bridge.svelte index b333afd..99871ae 100644 --- a/extension/src/ui/options/Bridge.svelte +++ b/extension/src/ui/options/Bridge.svelte @@ -83,83 +83,126 @@ }); // Updates - let updateData: Nullable = null; + let updateData: Nullable = null; let updateStatus: Nullable = null; let updateStatusTimeout: number; let isCheckingUpdate = false; let isUpdateAvailable = false; - interface GitHubRelease { - url: string; - tag_name: string; - html_url: string; - assets: Array<{ - content_type: string; - html_url: string; - }>; + type UpdateManifestPlatform = "mac" | "win" | "linux-deb" | "linux-rpm"; + type UpdateManifestArch = "x86" | "x64" | "arm64"; + + interface UpdateManifestUpdateInfo { + update_link: string; + update_hash: string; + } + interface UpdateManifestUpdate { + version: string; + platforms: Record< + UpdateManifestPlatform, + Partial> + >; + } + interface UpdateManifest { + fx_cast_bridge: { + updates: UpdateManifestUpdate[]; + }; + } + + async function fetchLatestUpdateInfo() { + let updateManifest: UpdateManifest; + try { + updateManifest = await fetch( + "https://hensm.github.io/fx_cast/updates.json" + ).then(res => res.json()); + } catch (err) { + throw new Error( + "Failed to check for updates due to a network error!" + ); + } + + const latestUpdate = updateManifest?.fx_cast_bridge?.updates?.reduce( + (latest, next) => + semver.gt(next.version, latest.version) ? next : latest + ); + if (!latestUpdate) { + throw new Error( + "Failed to check for updates due to invalid update manifest!" + ); + } + + return latestUpdate; } async function checkUpdate() { isCheckingUpdate = true; - let releases: GitHubRelease[]; try { - releases = await fetch( - "https://api.github.com/repos/hensm/fx_cast/releases" - ).then(res => res.json()); + const latestUpdate = await fetchLatestUpdateInfo(); + /** + * Update available if no bridge found or bridge version lower + * than fetched release version. + */ + isUpdateAvailable = + !bridgeInfo || + semver.lt(bridgeInfo.version, latestUpdate.version); + + if (isUpdateAvailable) { + updateData = latestUpdate; + } else { + updateStatus = _("optionsBridgeUpdateStatusNoUpdates"); + } } catch (err) { - isCheckingUpdate = false; + if (err instanceof Error) logger.error(err.message); updateStatus = _("optionsBridgeUpdateStatusError"); return; + } finally { + isCheckingUpdate = false; + if (updateStatusTimeout) window.clearTimeout(updateStatusTimeout); + updateStatusTimeout = window.setTimeout(() => { + updateStatus = null; + }, 1500); } - - // Ensure valid response - if (!Array.isArray(releases)) { - throw logger.error("Check update response is not array.", releases); - } - - // First non-extension-only release - const latestBridgeRelease = releases.find(release => - release.assets.find( - asset => asset.content_type !== "application/x-xpinstall" - ) - ); - - if (!latestBridgeRelease) { - throw logger.error( - "Check update response does not contain release info." - ); - } - - /** - * Update available if no bridge found or bridge version lower - * than fetched release version. - */ - isUpdateAvailable = - !bridgeInfo || - semver.lt(bridgeInfo.version, latestBridgeRelease.tag_name); - - if (isUpdateAvailable) { - updateData = latestBridgeRelease; - } else { - updateStatus = _("optionsBridgeUpdateStatusNoUpdates"); - } - - isCheckingUpdate = false; - - if (updateStatusTimeout) { - window.clearTimeout(updateStatusTimeout); - } - updateStatusTimeout = window.setTimeout(() => { - updateStatus = null; - }, 1500); } - function getUpdate() { - // Open downloads page - if (updateData?.html_url) { - browser.tabs.create({ url: updateData.html_url }); + const getReleasePageUrl = (version: string) => + `https://github.com/hensm/fx_cast/releases/tag/${version}`; + + async function getUpdate() { + if (!updateData) return; + + const platformArchMap: { + [k in browser.runtime.PlatformArch]?: UpdateManifestArch; + } = { + "aarch64": "arm64", + "x86-32": "x86", + "x86-64": "x64" + }; + + let downloadUrl: Optional; + + const platform = await browser.runtime.getPlatformInfo(); + const releasePlatformArch = platformArchMap[platform.arch]; + if ( + // We can't assume which Linux binary is required + (platform.os === "mac" || platform.os === "win") && + releasePlatformArch && + platform.os in updateData.platforms + ) { + const releasePlatform = updateData.platforms[platform.os]; + const releaseInfo = releasePlatform[releasePlatformArch]; + downloadUrl = releaseInfo?.update_link; + } + + if (downloadUrl) { + // If there's a valid download URL, download that. + browser.downloads.download({ url: downloadUrl }); + } else { + // ...otherwise open a new tab for the update page. + browser.tabs.create({ + url: getReleasePageUrl(updateData.version) + }); } } @@ -341,6 +384,11 @@ > {_("optionsBridgeUpdate")} + {#if updateData} + + {_("optionsBridgeUpdateViewChangelog")} + + {/if} {:else} diff --git a/extension/src/ui/options/styles/index.css b/extension/src/ui/options/styles/index.css index 69c622d..7e1f442 100644 --- a/extension/src/ui/options/styles/index.css +++ b/extension/src/ui/options/styles/index.css @@ -184,8 +184,9 @@ input:placeholder-shown { } .bridge__update-options { + align-items: center; display: inline-flex; - flex-direction: column; + gap: 10px; margin-left: 10px; }