feat(app): expose local sharing and verified transfers
Add a durable, acknowledged Local network sharing switch with fail-closed hydration, serialized mutation, and redacted ephemeral-identity diagnostics. Keep local Call-to-Play state available while gating every network action on the effective sharing generation. Render revisioned verification, invalid-source retry, and sticky source exhaustion states. Preserve opaque attempt IDs through progress delivery so out-of-order webview events cannot attach stale bytes to a successor transfer, and keep terminal exhaustion visible after the last source departs. Own listeners, native invokes, persistence, dialogs, and companion-window creation through webview close. Late creation is settled and cleaned before the parent realm is destroyed. Test Plan: - `just frontend-test` -- passed (91/91) - `just build` -- passed with TypeScript, Vite, and release Tauri compilation - `just test` -- passed on the completed stack (708 workspace tests) - `just clippy` -- passed on the completed stack - `git diff --cached --check` -- passed
This commit is contained in:
42 files changed
+7457
-2101
No files matched your search
@@ -1,28 +1,41 @@
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
import {
|
||||
createThumbnailRequestKey,
|
||||
startOwnedThumbnailGeneration,
|
||||
ThumbnailRequestGeneration,
|
||||
thumbnailIdsFromRequestKey,
|
||||
} from '../lib/thumbnailRequests';
|
||||
import { windowAsyncScope } from '../lib/asyncOwnership';
|
||||
|
||||
/**
|
||||
* Lazy, per-id cache for cover thumbnails. Returns `null` until the value is
|
||||
* known; returns the empty string when the backend has nothing for the id, so
|
||||
* callers can fall back to the placeholder cover art.
|
||||
* Per-id cache for cover thumbnails belonging to the supplied game ids.
|
||||
* Returns `null` until the value is known; returns the empty string when the
|
||||
* backend has nothing for the id, so callers can use the placeholder cover.
|
||||
*/
|
||||
export const useThumbnails = () => {
|
||||
export const useThumbnails = (ids: readonly string[]) => {
|
||||
const [thumbnails, setThumbnails] = useState<Map<string, string>>(new Map());
|
||||
const pending = useRef<Set<string>>(new Set());
|
||||
const thumbnailsRef = useRef(thumbnails);
|
||||
const requestKey = createThumbnailRequestKey(ids);
|
||||
|
||||
useEffect(() => {
|
||||
const generation = new ThumbnailRequestGeneration(
|
||||
id => invoke<string>('get_game_thumbnail', { gameId: id }),
|
||||
(id, url) => {
|
||||
const next = new Map(thumbnailsRef.current).set(id, url);
|
||||
thumbnailsRef.current = next;
|
||||
setThumbnails(next);
|
||||
},
|
||||
);
|
||||
const missingIds = thumbnailIdsFromRequestKey(requestKey)
|
||||
.filter(id => !thumbnailsRef.current.has(id));
|
||||
|
||||
return startOwnedThumbnailGeneration(windowAsyncScope, generation, missingIds);
|
||||
}, [requestKey]);
|
||||
|
||||
const get = useCallback((id: string): string | null => {
|
||||
if (thumbnails.has(id)) return thumbnails.get(id) ?? '';
|
||||
if (pending.current.has(id)) return null;
|
||||
pending.current.add(id);
|
||||
invoke<string>('get_game_thumbnail', { gameId: id })
|
||||
.then(url => {
|
||||
pending.current.delete(id);
|
||||
setThumbnails(prev => new Map(prev).set(id, url));
|
||||
})
|
||||
.catch(() => {
|
||||
pending.current.delete(id);
|
||||
setThumbnails(prev => new Map(prev).set(id, ''));
|
||||
});
|
||||
return null;
|
||||
}, [thumbnails]);
|
||||
|
||||
|
||||
Reference in new issue
Block a user