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
69 lines
2.4 KiB
TypeScript
69 lines
2.4 KiB
TypeScript
import {
|
|
EPHEMERAL_IDENTITY_NOTICE,
|
|
IDENTITY_STATUS_UNAVAILABLE_NOTICE,
|
|
INITIAL_IDENTITY_DIAGNOSTIC_SNAPSHOT,
|
|
type IdentityDiagnosticSnapshot,
|
|
newestIdentityDiagnosticSnapshot,
|
|
} from '../src/lib/identityDiagnostic.ts';
|
|
|
|
const assertEquals = <T>(actual: T, expected: T, message: string) => {
|
|
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
|
|
throw new Error(
|
|
`${message}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`,
|
|
);
|
|
}
|
|
};
|
|
|
|
Deno.test('newer identity event beats delayed query and a later clear wins', () => {
|
|
const delayedQuery: IdentityDiagnosticSnapshot = { revision: 1, diagnostic: null };
|
|
const ephemeral: IdentityDiagnosticSnapshot = {
|
|
revision: 2,
|
|
diagnostic: 'ephemeral',
|
|
};
|
|
const afterEvent = newestIdentityDiagnosticSnapshot(
|
|
INITIAL_IDENTITY_DIAGNOSTIC_SNAPSHOT,
|
|
ephemeral,
|
|
);
|
|
assertEquals(
|
|
newestIdentityDiagnosticSnapshot(afterEvent, delayedQuery),
|
|
ephemeral,
|
|
'delayed query must lose',
|
|
);
|
|
|
|
const cleared: IdentityDiagnosticSnapshot = { revision: 3, diagnostic: null };
|
|
assertEquals(
|
|
newestIdentityDiagnosticSnapshot(ephemeral, cleared),
|
|
cleared,
|
|
'higher-revision clear must win',
|
|
);
|
|
assertEquals(
|
|
newestIdentityDiagnosticSnapshot(cleared, ephemeral),
|
|
cleared,
|
|
'stale event must not resurrect diagnostic',
|
|
);
|
|
});
|
|
|
|
Deno.test('ephemeral identity copy is exact and contains no implementation detail', () => {
|
|
assertEquals(
|
|
EPHEMERAL_IDENTITY_NOTICE,
|
|
"This installation's network identity could not be saved and will change the next time Lanspread starts.",
|
|
'identity diagnostic copy',
|
|
);
|
|
for (const forbidden of ['path', 'key', 'permission', '.json', 'error']) {
|
|
if (EPHEMERAL_IDENTITY_NOTICE.toLowerCase().includes(forbidden)) {
|
|
throw new Error(`identity copy leaked forbidden detail: ${forbidden}`);
|
|
}
|
|
}
|
|
});
|
|
|
|
Deno.test('identity initialization failure has a distinct redacted warning', () => {
|
|
assertEquals(
|
|
IDENTITY_STATUS_UNAVAILABLE_NOTICE,
|
|
"This installation's network identity status could not be checked.",
|
|
'unavailable diagnostic copy',
|
|
);
|
|
if (IDENTITY_STATUS_UNAVAILABLE_NOTICE.includes('could not be saved')) {
|
|
throw new Error('unavailable status must not claim confirmed ephemeral identity');
|
|
}
|
|
});
|