Files
Termix/src/ui/lib/desktop-updates.ts
T
62ac931468 v26.10.1 (#1599)
* feat: AI translations at release, 7 new languages and regional fallback

* fix: proper plural forms for count strings and full translations

* fix: plural forms for selected, hidden and active counts

* fix: synced plugin updates failing on desktop and server-only plugins shown as stopped

* fix: restore rounded corners on app icons and favicon

* feat: desktop app auto updates with automatic, ask first or never modes

* feat: community and custom plugin registries

* feat: plugin guide for AI tools in the SDK with a lint check

* fix: keep the terminal WebSocket open past nginx's 60s default (#1588)

* chore: update changelog

* feat: show plugin download counts only and stop CI builds inflating them

* feat: hide installed plugins in the browse tab by default

* chore: pin bundled plugins to latest releases

* chore: update translations

* fix: focusing a saved password field and saving no longer erases it

* fix: tell desktop users when their 2.8 data needs 2.9 first

* fix: point windows update info at the x64 installer

* fix: keep desktop client tunnel sockets alive behind proxies

* chore: pin bundled plugins to latest releases

* fix: cover client tunnel keepalive in the c2s runtime tests

* fix: typing after opening a host no longer goes to the host search

* fix: accept tab and = separators in SSH config import (#1598)

* chore: update changelog

* chore: drop plugin versions from core changelog

* chore: update translations

* chore: lint, format, and bump version to 26.10.1

---------

Co-authored-by: Darnel Hunter <68697450+innotelinc@users.noreply.github.com>
Co-authored-by: Iain <Iain1970@protonmail.com>
2026-10-12 11:35:48 -05:00

87 lines
2.5 KiB
TypeScript

import { useEffect, useState } from "react";
import { isElectron } from "@/lib/electron";
export type DesktopUpdateMode = "automatic" | "prompt" | "never";
export type DesktopInstallKind = "auto" | "manual" | "store" | "disabled";
export type DesktopUpdateStatus =
| "idle"
| "checking"
| "available"
| "downloading"
| "downloaded"
| "up-to-date"
| "error";
export interface DesktopUpdateState {
kind: DesktopInstallKind;
mode: DesktopUpdateMode;
currentVersion: string;
status: DesktopUpdateStatus;
version: string | null;
releaseUrl: string | null;
progress: number | null;
error: string | null;
}
interface DesktopUpdatesBridge {
getState: () => Promise<DesktopUpdateState | null>;
check: () => Promise<DesktopUpdateState | null>;
download: () => Promise<DesktopUpdateState | null>;
install: () => Promise<boolean>;
setMode: (mode: DesktopUpdateMode) => Promise<DesktopUpdateState | null>;
onState: (callback: (state: DesktopUpdateState) => void) => () => void;
}
type UpdatesWindow = Window &
typeof globalThis & {
electronAPI?: { updates?: DesktopUpdatesBridge };
};
/** The main process updater, or null in the browser and on older builds. */
export function desktopUpdates(): DesktopUpdatesBridge | null {
if (!isElectron()) return null;
return (window as UpdatesWindow).electronAPI?.updates ?? null;
}
/** Whether this install has an updater the user can configure. */
export function canConfigureUpdates(
state: DesktopUpdateState | null,
): state is DesktopUpdateState {
return !!state && state.kind !== "store" && state.kind !== "disabled";
}
/** Which toast, if any, this state calls for. */
export function updateToastFor(
state: DesktopUpdateState | null,
): "prompt" | "manual" | "ready" | null {
if (!state || !state.version || state.mode === "never") return null;
if (state.status === "downloaded") return "ready";
if (state.status !== "available") return null;
if (state.kind === "manual") return "manual";
if (state.kind === "auto" && state.mode === "prompt") return "prompt";
return null;
}
export function useDesktopUpdate(): DesktopUpdateState | null {
const [state, setState] = useState<DesktopUpdateState | null>(null);
useEffect(() => {
const bridge = desktopUpdates();
if (!bridge) return;
let active = true;
bridge
.getState()
.then((next) => active && next && setState(next))
.catch(() => {});
const off = bridge.onState((next) => active && setState(next));
return () => {
active = false;
off();
};
}, []);
return state;
}