eb8f9857ff
Backup / restore flow: - deviceBackup.ts: v2 format wrapping userId + deviceId + privateKey in an encrypted JSON payload so restore can re-seed localStorage, vault, and reattach to the existing server-side device row without provisioning a new one (conv-key bundles stay valid, no "awaiting key" state) - shared/auth: restoreDeviceFromServerRecord — verifies session.user.id matches the backup's userId, confirms the server device row still exists, then writes the private key into the local secret store - BackupExportDialog — passphrase + confirm, generates portable string, copy + download .txt - DeviceRestore — textarea + passphrase → seeds vault + writes deviceId cache, treats this install as the original device - DevicePage now has tabs "Neu einrichten" / "Backup wiederherstellen" - BackupPromptBanner — post-registration nudge, reads sessionStorage signal from fresh provisions and persists "never-ask-again" in localStorage so it stops nagging - SettingsPage backup section: uses the new dialog; removes the dangerous in-place key import (restore now lives in the device flow) Username casing: - Migration 20260420000002 drops lower() from the handle_new_user trigger and widens the regex to [A-Za-z0-9_]. profiles.username is citext so uniqueness + lookups stay case-insensitive regardless of stored casing - Shared auth: trim() only, no toLowerCase on signup/lookups/search. ilike handles CI anyway and citext makes client normalisation redundant - AuthPage regex + input preserve case, FriendsPage search preserves case - i18n (de+en): updated username_hint / username_invalid / ERR_USERNAME_INVALID to reflect the new rule Quick wins: - React Router v7 future flags (v7_startTransition + v7_relativeSplatPath) set on BrowserRouter — silences the upgrade warning - appUpdates.checkForUpdate: swallow benign network/fetch/"could not fetch valid release JSON" cases silently instead of console spam - osNotify: persist an "asked" marker in localStorage so the permission prompt only fires once per install (OS already persists the answer, but the plugin re-queries loudly otherwise)
89 lines
2.5 KiB
TypeScript
89 lines
2.5 KiB
TypeScript
import { check as checkUpdate } from '@tauri-apps/plugin-updater';
|
|
|
|
import { isTauriRuntime } from './globalShortcut';
|
|
|
|
export interface UpdateState {
|
|
available: boolean;
|
|
version: string | null;
|
|
notes: string | null;
|
|
downloading: boolean;
|
|
downloaded: boolean;
|
|
error: string | null;
|
|
}
|
|
|
|
export const IDLE_UPDATE_STATE: UpdateState = {
|
|
available: false,
|
|
version: null,
|
|
notes: null,
|
|
downloading: false,
|
|
downloaded: false,
|
|
error: null,
|
|
};
|
|
|
|
type UpdateHandle = Awaited<ReturnType<typeof checkUpdate>>;
|
|
|
|
let cachedUpdate: UpdateHandle | null = null;
|
|
|
|
export async function checkForUpdate(): Promise<UpdateState> {
|
|
if (!isTauriRuntime()) {
|
|
return { ...IDLE_UPDATE_STATE };
|
|
}
|
|
try {
|
|
const update = await checkUpdate();
|
|
if (!update) {
|
|
cachedUpdate = null;
|
|
return { ...IDLE_UPDATE_STATE };
|
|
}
|
|
cachedUpdate = update;
|
|
return {
|
|
available: true,
|
|
version: update.version ?? null,
|
|
notes: update.body ?? null,
|
|
downloading: false,
|
|
downloaded: false,
|
|
error: null,
|
|
};
|
|
} catch (err: unknown) {
|
|
// Swallow "no release on GitHub yet" / network-unreachable cases silently.
|
|
// The updater endpoint serves `latest.json` from GitHub releases; a fresh
|
|
// repo or offline machine produces a generic "Could not fetch a valid
|
|
// release JSON" error that has no actionable information for the user —
|
|
// logging it on every launch just pollutes the console.
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
const benign =
|
|
/could not fetch a valid release json|network|timed? out|failed to fetch|connection/i.test(
|
|
msg,
|
|
);
|
|
if (!benign) {
|
|
console.warn('checkForUpdate failed', err);
|
|
}
|
|
return {
|
|
...IDLE_UPDATE_STATE,
|
|
error: benign ? null : msg,
|
|
};
|
|
}
|
|
}
|
|
|
|
// Downloads + installs the previously checked update. On Windows the app
|
|
// quits during install (passive installer); on macOS/Linux tauri triggers
|
|
// a relaunch automatically.
|
|
export async function installUpdate(
|
|
onProgress?: (downloaded: number, total: number | null) => void,
|
|
): Promise<void> {
|
|
if (!cachedUpdate) {
|
|
throw new Error('no pending update — call checkForUpdate() first');
|
|
}
|
|
let total: number | null = null;
|
|
let downloaded = 0;
|
|
await cachedUpdate.downloadAndInstall((event) => {
|
|
if (event.event === 'Started') {
|
|
total = event.data.contentLength ?? null;
|
|
downloaded = 0;
|
|
} else if (event.event === 'Progress') {
|
|
downloaded += event.data.chunkLength;
|
|
}
|
|
onProgress?.(downloaded, total);
|
|
});
|
|
cachedUpdate = null;
|
|
}
|