mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
- Add tauri-plugin-updater and tauri-plugin-process for in-app updates
- Rust commands (check_client_update, download_and_install_update) build
updater with dynamic endpoint at runtime for self-hosted compatibility
- Server endpoint GET /api/v1/client-update/{target}/{version} translates
GitHub Releases into Tauri updater JSON format with .sig content
- UpdateNotifier banner component with install/dismiss controls
- CI workflow produces signed .nsis.zip + .sig updater artifacts
- Self-signed TLS support via dangerousAcceptInvalidCerts config
102 lines
3.2 KiB
TypeScript
102 lines
3.2 KiB
TypeScript
// UpdateNotifier — shows a non-modal banner when a client update is available.
|
|
// Mounts at the top of the main page and allows the user to update or dismiss.
|
|
|
|
import { createElement, appendChildren } from "@lib/dom";
|
|
import { createLogger } from "@lib/logger";
|
|
import { checkForUpdate, downloadAndInstallUpdate } from "@lib/updater";
|
|
import type { MountableComponent } from "@lib/safe-render";
|
|
|
|
const log = createLogger("update-notifier");
|
|
|
|
export interface UpdateNotifierOptions {
|
|
readonly serverUrl: string;
|
|
}
|
|
|
|
export function createUpdateNotifier(options: UpdateNotifierOptions): MountableComponent {
|
|
const { serverUrl } = options;
|
|
let container: Element | null = null;
|
|
let banner: HTMLDivElement | null = null;
|
|
let dismissed = false;
|
|
|
|
async function performCheck(): Promise<void> {
|
|
if (dismissed) return;
|
|
|
|
const result = await checkForUpdate(serverUrl);
|
|
if (!result.available || result.version === null) return;
|
|
|
|
showBanner(result.version, result.body ?? "");
|
|
}
|
|
|
|
function showBanner(version: string, notes: string): void {
|
|
if (container === null || banner !== null) return;
|
|
|
|
banner = createElement("div", { class: "update-banner" });
|
|
|
|
const text = createElement("span", { class: "update-banner-text" },
|
|
`Update v${version} available`);
|
|
|
|
const updateBtn = createElement("button", { class: "update-banner-btn update-banner-install" },
|
|
"Update Now");
|
|
updateBtn.addEventListener("click", () => {
|
|
void installUpdate();
|
|
});
|
|
|
|
const laterBtn = createElement("button", { class: "update-banner-btn update-banner-later" },
|
|
"Later");
|
|
laterBtn.addEventListener("click", () => {
|
|
dismissed = true;
|
|
removeBanner();
|
|
});
|
|
|
|
appendChildren(banner, text, updateBtn, laterBtn);
|
|
container.prepend(banner);
|
|
}
|
|
|
|
async function installUpdate(): Promise<void> {
|
|
if (banner === null) return;
|
|
|
|
// Replace banner content with progress indicator
|
|
while (banner.firstChild) banner.removeChild(banner.firstChild);
|
|
const progress = createElement("span", { class: "update-banner-text" },
|
|
"Downloading update...");
|
|
banner.appendChild(progress);
|
|
|
|
try {
|
|
await downloadAndInstallUpdate(serverUrl);
|
|
// App will relaunch — this code won't execute after relaunch()
|
|
} catch (err) {
|
|
log.error("Update install failed", { error: String(err) });
|
|
while (banner.firstChild) banner.removeChild(banner.firstChild);
|
|
const errorText = createElement("span", { class: "update-banner-text" },
|
|
"Update failed. Please try again later.");
|
|
const dismissBtn = createElement("button", { class: "update-banner-btn update-banner-later" },
|
|
"Dismiss");
|
|
dismissBtn.addEventListener("click", () => {
|
|
dismissed = true;
|
|
removeBanner();
|
|
});
|
|
appendChildren(banner, errorText, dismissBtn);
|
|
}
|
|
}
|
|
|
|
function removeBanner(): void {
|
|
if (banner !== null) {
|
|
banner.remove();
|
|
banner = null;
|
|
}
|
|
}
|
|
|
|
function mount(target: Element): void {
|
|
container = target;
|
|
// Delay the check slightly so the main UI renders first
|
|
setTimeout(() => { void performCheck(); }, 3000);
|
|
}
|
|
|
|
function destroy(): void {
|
|
removeBanner();
|
|
container = null;
|
|
}
|
|
|
|
return { mount, destroy };
|
|
}
|