Files
Firelink/src/utils/keychainStartup.ts
T
NimBold edeef0ac54 fix(startup): enforce consent before download handoffs
Gate clipboard, extension, and deep-link inputs until startup consent is resolved, and serialize extension readiness transitions so native prompts cannot race the app explanation. Identify consent by the build revision so same-version updates re-enter the consent boundary.

Requested by [this X post](https://x.com/ixabolfazl/status/2077356127763804450?s=20).
2026-07-15 21:48:00 +03:30

55 lines
1.7 KiB
TypeScript

export type KeychainStartupState = {
portable: boolean;
appVersion: string;
approvedVersion: string;
accessGranted: boolean;
promptDismissed: boolean;
};
export type KeychainStartupDecision = {
deferKeychainHydration: boolean;
showKeychainPrompt: boolean;
};
// The semantic app version can remain unchanged across release-candidate and
// packaging rebuilds. Use the build identity so an updated binary cannot skip
// Firelink's explanation and invoke the OS prompt directly. The policy epoch
// remains only as a safe fallback for builds created outside the Git checkout.
const KEYCHAIN_CONSENT_POLICY_VERSION = '2';
const buildId = typeof import.meta.env.VITE_BUILD_ID === 'string'
? import.meta.env.VITE_BUILD_ID.trim()
: '';
export const getKeychainConsentVersion = (appVersion: string): string => {
const normalizedVersion = appVersion.trim();
const consentIdentity = buildId && buildId !== 'unknown'
? `build-${buildId}`
: `keychain-policy-${KEYCHAIN_CONSENT_POLICY_VERSION}`;
return normalizedVersion
? `${normalizedVersion}|${consentIdentity}`
: '';
};
export const getKeychainStartupDecision = ({
portable,
appVersion,
approvedVersion,
accessGranted,
promptDismissed
}: KeychainStartupState): KeychainStartupDecision => {
if (portable) {
return {
deferKeychainHydration: false,
showKeychainPrompt: false
};
}
const versionKnown = Boolean(appVersion.trim());
const versionChanged = versionKnown && approvedVersion !== appVersion;
const mustDeferAccess = !accessGranted || !versionKnown || versionChanged;
return {
deferKeychainHydration: mustDeferAccess,
showKeychainPrompt: versionChanged || (!accessGranted && !promptDismissed)
};
};