assets/js/services/passkey-support-service.js

const FAILURE_KEY = 'kmp.offline.passkey-unavailable';
const RETRY_AFTER = 7 * 24 * 60 * 60 * 1000;

/** A capability hint only: never changes an existing vault or proves a passkey can decrypt it. */
export function forgetPasskeyFailure() {
    try { localStorage.removeItem(FAILURE_KEY); } catch { /* Storage is optional for this hint. */ }
}

export function unavailablePasskey(message) {
    try { localStorage.setItem(FAILURE_KEY, JSON.stringify({ browser: navigator.userAgent, until: Date.now() + RETRY_AFTER })); }
    catch { /* Still offer PIN for this setup when browser storage is unavailable. */ }
    return Object.assign(new Error(message), { code: 'PASSKEY_UNAVAILABLE' });
}

/** Check without creating a credential or opening a biometric/password-manager prompt. */
export async function checkPasskeySupport() {
    if (!globalThis.PublicKeyCredential || !navigator.credentials?.create || !navigator.credentials?.get || !crypto.subtle) {
        return { available: false, reason: 'browser' };
    }
    try {
        const failure = JSON.parse(localStorage.getItem(FAILURE_KEY) || 'null');
        if (failure?.browser === navigator.userAgent && failure.until > Date.now()) return { available: false, reason: 'provider' };
    } catch { /* Invalid or inaccessible hints do not block a fresh check. */ }
    let timer;
    try {
        return await Promise.race([
            (async () => {
                const api = globalThis.PublicKeyCredential;
                if (api.getClientCapabilities) {
                    const capabilities = await api.getClientCapabilities();
                    if (capabilities['extension:prf'] !== true) return { available: false, reason: 'browser' };
                }
                if (api.isUserVerifyingPlatformAuthenticatorAvailable && !await api.isUserVerifyingPlatformAuthenticatorAvailable()) {
                    return { available: false, reason: 'device' };
                }
                return { available: true };
            })(),
            new Promise(resolve => { timer = setTimeout(() => resolve({ available: true }), 2000); })
        ]);
    } catch { return { available: true }; }
    finally { clearTimeout(timer); }
}