Files
lanspread/crates/lanspread-tauri-deno-ts/src/lib/frontendBootstrap.ts
T
ddidderr 71dbf27d8b 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
2026-08-10 13:59:40 +02:00

128 lines
3.9 KiB
TypeScript

import { type AsyncCleanup } from './asyncOwnership';
export interface FrontendCloseRequest {
preventDefault: () => void;
}
export interface FrontendBootstrapOptions {
registerCloseRequested: (
handler: (event: FrontendCloseRequest) => void | Promise<void>,
) => Promise<AsyncCleanup>;
disposeOwners: () => Promise<void>;
drainPersistence: () => Promise<void>;
destroyWindow: () => Promise<void>;
render: () => void;
reportFailure: (message: string, error: unknown) => void;
}
const report = (
options: FrontendBootstrapOptions,
message: string,
error: unknown,
): void => {
try {
options.reportFailure(message, error);
} catch {
// Reporting must not turn a handled bootstrap failure into a rejection.
}
};
const runHandled = async (
options: FrontendBootstrapOptions,
message: string,
operation: () => void | Promise<void>,
): Promise<void> => {
try {
await operation();
} catch (error) {
report(options, message, error);
}
};
/**
* Installs the webview's close boundary before mounting any React hooks.
* Registration failure closes the empty realm instead of running without an
* ownership boundary.
*/
export const bootstrapFrontend = async (
options: FrontendBootstrapOptions,
): Promise<boolean> => {
let closing: Promise<void> | undefined;
let publishCloseCleanup!: (cleanup: AsyncCleanup) => void;
const closeCleanupReady = new Promise<AsyncCleanup>(resolve => {
publishCloseCleanup = resolve;
});
const finalizeWindow = (): Promise<void> => {
if (closing !== undefined) return closing;
// Calling every operation closes its admission or starts its cleanup
// synchronously, before any asynchronous drain is awaited.
const ownerDrain = runHandled(
options,
'Failed to dispose frontend window owners:',
options.disposeOwners,
);
const persistenceDrain = runHandled(
options,
'Failed to drain frontend persistence:',
options.drainPersistence,
);
const listenerDrain = runHandled(
options,
'Failed to unregister the window close listener:',
async () => {
const cleanup = await closeCleanupReady;
await cleanup();
},
);
closing = Promise.all([
ownerDrain,
persistenceDrain,
listenerDrain,
])
.then(() => runHandled(
options,
'Failed to destroy the frontend window:',
options.destroyWindow,
));
return closing;
};
try {
const cleanupCloseListener = await options.registerCloseRequested(event => {
event.preventDefault();
return finalizeWindow();
});
publishCloseCleanup(cleanupCloseListener);
} catch (error) {
publishCloseCleanup(() => {});
report(options, 'Failed to install the frontend close-drain boundary:', error);
// Nothing has rendered, but close both admissions in case a module-level
// owner was registered while loading the bundle, then explicitly await
// destruction of the empty webview.
await finalizeWindow();
return false;
}
if (closing !== undefined) {
// A close request may arrive after the native listener is callable but
// before its registration promise acknowledges ownership. The active
// finalizer now owns the late cleanup; never mount React into a realm
// that has already begun closing.
await closing;
return false;
}
try {
options.render();
} catch (error) {
report(options, 'Failed to render the frontend:', error);
await finalizeWindow();
return false;
}
return true;
};