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>
52 lines
1.7 KiB
TypeScript
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 };
|
|
}
|