diff --git a/docs/updates.md b/docs/updates.md new file mode 100644 index 0000000..290827f --- /dev/null +++ b/docs/updates.md @@ -0,0 +1,11 @@ +# Client updates + +The Settings view checks the fixed stable release source for +`ReaperOfVeriod/trixy-linux-client` on Gitea. It compares the release tag with +the running client version and shows the release page when a newer version is +available. + +Applying an update remains manual: review the release, download the x86-64 +AppImage, replace the existing file, and restart it. The client never accepts +an arbitrary update URL, silently replaces itself, or stores credentials in the +update flow. diff --git a/src/App.svelte b/src/App.svelte index 643b67b..94c6021 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -78,6 +78,9 @@ let connectionState = 'OFFLINE'; let serverVersion = '—'; let serverCompatibility = ''; + let clientUpdateStatus = ''; + let clientUpdateUrl = ''; + let clientUpdateBusy = false; let diagnostics: { status: string; database: string; twitch_identity_count: number; unseen_notification_count: number; realtime: string } | undefined; let backups: Array<{ filename: string; size_bytes: number; modified_at: string | null }> = []; let backupError = ''; @@ -227,6 +230,13 @@ return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : undefined; } + function isVersionNewer(candidate: string, current: string) { + const candidateParts = versionParts(candidate); + const currentParts = versionParts(current); + if (!candidateParts || !currentParts) return false; + return candidateParts.some((part, index) => part > currentParts[index] && candidateParts.slice(0, index).every((previous, previousIndex) => previous === currentParts[previousIndex])); + } + function isClientCompatible(minimum: string, maximum: string) { const client = versionParts(CLIENT_VERSION); const minimumParts = versionParts(minimum); @@ -289,6 +299,24 @@ } } + async function checkClientUpdate() { + clientUpdateBusy = true; + clientUpdateStatus = 'Checking fixed stable release source…'; + try { + const response = await fetch('https://git.reaperofveriod.nl/api/v1/repos/ReaperOfVeriod/trixy-linux-client/releases/latest'); + if (!response.ok) throw new Error('Release source unavailable.'); + const release: { tag_name?: string; html_url?: string } = await response.json(); + const latest = release.tag_name?.replace(/^v/, '') || ''; + const current = CLIENT_VERSION.replace(/^v/, ''); + clientUpdateUrl = release.html_url || 'https://git.reaperofveriod.nl/ReaperOfVeriod/trixy-linux-client/releases/latest'; + clientUpdateStatus = latest && isVersionNewer(latest, current) ? `Update available: ${latest}` : `Up to date (${current})`; + } catch (error) { + clientUpdateStatus = error instanceof Error ? error.message : 'Release source unavailable.'; + } finally { + clientUpdateBusy = false; + } + } + async function login() { if (connectionState === 'INCOMPATIBLE') { authError = 'This client version is not compatible with the server.'; @@ -1689,6 +1717,9 @@ {/if} + {#if authenticated && activeView === 'Settings'} + CLIENT UPDATEv{CLIENT_VERSION}Updates are manual. Check the fixed stable Gitea release source, review the release, then download and replace the AppImage yourself.{clientUpdateBusy ? 'CHECKING…' : 'CHECK FOR UPDATE'}{#if clientUpdateStatus}{clientUpdateStatus}{/if}{#if clientUpdateUrl} window.open(clientUpdateUrl, '_blank', 'noopener,noreferrer')}>OPEN RELEASE PAGE{/if} + {/if} {#if authenticated && sharedChatSession}
Updates are manual. Check the fixed stable Gitea release source, review the release, then download and replace the AppImage yourself.