Files
cinny/src/app/hooks/useTauriUpdater.ts
T
jaredandClaude Opus 4.8 668bdaad7d fix(wave-2): audit fixes — account-data races, search-cache wipe, export, media
Web fixes from the Wave-2 bug-hunt (findings in LOTUS_TODO):
- F1 (security): wipe the decrypted-plaintext search index on SERVER-FORCED
  logout too (token expiry / remote sign-out) — only manual logout did before.
  F4: the delete no longer reports success while onblocked (waits, 3s cap).
- M1/M2 (data-loss): useBookmarks + useUserNotes account-data writes are now
  serialized at MODULE scope (single queue + latestRef per client, echo-driven),
  fixing the cross-instance lost-update clobber (useBookmarks mounts per message
  row, so a per-instance queue was insufficient — caught in review).
- M6: room-history export gets a 200-page cap + Cancel + unmount-abort +
  correct date-range early-break (raw paginated ts). M4: image compression
  skips PNG (was flattening transparency to black), bakes EXIF orientation via
  createImageBitmap, .jpg-renames, and falls back to the original on decode
  failure instead of dropping the file. M5: MediaGallery lightbox opens the
  right item (shared thumb guard). M8: audio speed survives async decrypt.
- Desktop web wiring: D2 badge sums leaf rooms only (space double-count, like
  the favicon fix); D3 useTauriDnd re-hydrates from get_tray_dnd on mount; D5
  updater has a terminal state.

Reviewed; M7 reverted (past-time clamp is an intentional, tested contract).
tsc/eslint/prettier clean, build OK, 678 tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 20:56:27 -04:00

52 lines
1.7 KiB
TypeScript

import { useState, useCallback } from 'react';
type TauriInternals = { invoke: (cmd: string, args?: Record<string, unknown>) => Promise<unknown> };
const tauriInvoke = (): TauriInternals['invoke'] | undefined =>
(window as unknown as { __TAURI_INTERNALS__?: TauriInternals }).__TAURI_INTERNALS__?.invoke;
type UpdateStatus =
| { state: 'idle' }
| { state: 'checking' }
| { state: 'up-to-date' }
| { state: 'available'; version: string }
| { state: 'installing' }
| { state: 'error'; message: string };
export function useTauriUpdater() {
const isTauri = !!tauriInvoke();
const [status, setStatus] = useState<UpdateStatus>({ state: 'idle' });
const check = useCallback(async () => {
const invoke = tauriInvoke();
if (!invoke) return;
setStatus({ state: 'checking' });
try {
const result = (await invoke('check_for_update')) as { available: boolean; version?: string };
setStatus(
result.available && result.version
? { state: 'available', version: result.version }
: { state: 'up-to-date' },
);
} catch (e) {
setStatus({ state: 'error', message: String(e) });
}
}, []);
const install = useCallback(async () => {
const invoke = tauriInvoke();
if (!invoke) return;
setStatus({ state: 'installing' });
try {
await invoke('install_update');
// On a successful install the native side calls app.restart(), so this
// resolve is only reached when nothing was installed (no update found) —
// don't leave the UI stuck on "installing".
setStatus({ state: 'up-to-date' });
} catch (e) {
setStatus({ state: 'error', message: String(e) });
}
}, []);
return { isTauri, status, check, install };
}