/** * types.ts — cross-boundary types and the `MessageType` union. * * `MessageType` mixes background-bound and content-script-bound messages. * Background-bound types have a case in `src/background/messages.ts`. * Content-script-bound types (PING, SUMMON_AUTOFILL, VAULT_LOCKED, * RESTART_AUTO_LOCK, etc.) are sent *to* content scripts or the popup and * have no handler in messages.ts by design — that absence is not a missing * handler. Add new cross-boundary types here, not inline in call sites. */ export interface ManualSummonRecord { firstSummonedAt: number; summonCount: number; lastSummonedAt: number; } export interface Canary { webhookUrl: string; label?: string; lastPinged?: number; pingCount?: number; } export type CardNetwork = "visa" | "mastercard" | "amex" | "discover" | "unknown"; export interface VaultEntry { id: string; // crypto.randomUUID() type?: "login" | "note" | "passkey" | "card"; // defaults to "login" for existing entries title: string; username?: string; // only for login entries password?: string; // only for login entries url?: string; // only for login entries /** * Additional sites this entry should match for autofill. Login entries only. * Each value is a full origin or URL (e.g. `https://login.foo.com`). * The vault domain index keys against the hostname of every entry in this * list, in addition to `url`. */ additionalUrls?: string[]; totp?: string; // base32 seed, stored encrypted — only for login entries notes?: string; breach?: number; // HIBP exposure count; 0 or undefined = clean — only for login entries /** * When the password field was last changed. Set on first-write of `password` * and refreshed on every subsequent change. Defaults to `createdAt` when * legacy entries are read by the advanced health analyser. */ passwordChangedAt?: number; canary?: Canary; createdAt: number; // Date.now() modifiedAt: number; deletedAt?: number; tags?: string[]; rpId?: string; rpName?: string; credentialId?: string; publicKeyCose?: string; privateKeyJwk?: JsonWebKey; userHandle?: string; userName?: string; userDisplayName?: string; algorithm?: -7 | -8; signCount?: number; aaguid?: string; backupEligible?: boolean; backupState?: boolean; lastUsedAt?: number; // Passkeys are not portable between browsers: a Chrome passkey holds its // private key in Peach's vault, while a Firefox passkey delegates to the // platform authenticator. Tag each entry so the UI can warn cross-browser. browser?: "chrome" | "firefox"; transports?: string[]; // Card entry fields cardholderName?: string; cardNumber?: string; // full PAN, stored encrypted in vault expMonth?: string; expYear?: string; cvc?: string; cardNickname?: string; network?: CardNetwork; last4?: string; } export interface LegacyVaultEntry { id: string; title: string; username: string; password: string; url: string; totp?: string; notes?: string; breach?: number; created: number; updated: number; deletedAt?: number; } export interface CardEntry extends VaultEntry { type: "card"; cardholderName: string; cardNumber: string; expMonth: string; expYear: string; cvc: string; } export interface PasskeyEntry extends VaultEntry { type: "passkey"; rpId: string; rpName: string; credentialId: string; publicKeyCose: string; privateKeyJwk: JsonWebKey; userHandle: string; userName: string; userDisplayName: string; algorithm: -7 | -8; signCount: number; aaguid: string; backupEligible: boolean; backupState: boolean; lastUsedAt?: number; // Optional on read for pre-v2.10.0 entries; new entries always set it. browser?: "chrome" | "firefox"; transports?: string[]; } export interface PendingPasskeyRequest { requestId: number; type: "create" | "get"; rpId: string; rpName: string; userName: string; userDisplayName?: string; } export interface PasskeyProviderState { supported: boolean; enabled: boolean; attached: boolean; browserLabel: string; statusLabel: string; conflict?: string; } export interface StoredVault { version: 1; // AAD for AES-GCM --- prevents downgrade salt: string; // hex --- 16 bytes, CSPRNG, stored not secret nonce: string; // hex --- 12 bytes, CSPRNG, fresh per write ciphertext: string; // hex --- AES-256-GCM output tag: string; // hex --- 16-byte GCM auth tag } // Background-bound message types — sent from popup/content scripts, // handled in src/background/messages.ts. Everything in this group must // have a matching case in that file's switch statement. export type MessageType = | "UNLOCK" | "LOCK" | "IS_UNLOCKED" | "HAS_VAULT" | "GET_ENTRIES" | "ADD_ENTRY" | "BATCH_ADD_ENTRIES" | "RESTORE_ENTRIES" | "CLEAR_VAULT_ENTRIES" | "UPDATE_ENTRY" | "ADD_ADDITIONAL_URL" | "DELETE_ENTRY" | "AUTOFILL_QUERY" | "COPY" | "CREATE_VAULT" | "GET_ENTRY_FOR_FILL" | "CHECK_BREACH" | "CHECK_ALL_BREACHES" | "GET_SETTINGS" | "UPDATE_SETTINGS" | "FIRE_CANARY_PING" | "ACTIVATE_LICENSE" | "GET_LICENSE" | "GET_LICENSE_KEY" | "IS_DURESS_ENABLED" | "SETUP_DURESS" | "DISABLE_DURESS" | "VERIFY_MAIN_PASSWORD" | "CHANGE_PASSWORD" | "GET_SITE_PASSWORD_POLICY" | "GET_SYNC_STATE" | "TRIGGER_SYNC" | "START_PAIRING" | "CANCEL_PAIRING" | "ACCEPT_PAIRING" | "REMOVE_PAIRED_DEVICE" | "REMOVE_TRUSTED_NETWORK" | "MARK_NETWORK_TRUSTED" | "GET_PENDING_PASSKEY" | "APPROVE_PASSKEY" | "DENY_PASSKEY" | "DELETE_PASSKEY" | "GET_PASSKEYS" | "GET_PASSKEY_PROVIDER_STATE" | "SET_PASSKEY_PROVIDER_ENABLED" | "GET_DELETED_ENTRIES" | "RESTORE_ENTRY" | "PURGE_ENTRY" | "PURGE_EXPIRED_TRASH" | "LIST_VAULT_SNAPSHOTS" | "RESTORE_VAULT_SNAPSHOT" | "COUNT_DUPLICATES" | "DEDUPLICATE_ENTRIES" | "RECORD_MANUAL_SUMMON" | "DECLINE_CARD_SAVE" | "IS_CARD_DECLINED" | "SUBMIT_FEEDBACK" | "GET_VAULT_STATUS" | "GET_ADVANCED_HEALTH_REPORT" | "INITIATE_PASSWORD_CHANGE" | "DEACTIVATE_LICENSE" | "GET_IDENTITIES" | "DESTROY_VAULT" // Content-script-bound message types — sent BY the background TO content // scripts. They never reach messages.ts's switch. Kept in this union so // existing call sites type-check; do not add cases for them in the // background handler. | "PING" // background → content: liveness probe | "SUMMON_AUTOFILL" // background → content: open the in-page palette | "VAULT_LOCKED" // background → content/popup: vault just locked | "VAULT_UNLOCKED" // background → content/popup: vault just unlocked | "PRO_GATE_TRIGGERED" // background → popup: a Pro feature was attempted on free | "PASSKEY_PRO_GATE" // background → content: show passkey Pro gate in inline popup | "OPEN_PRO_GATE" // content → background: open popup and navigate to License section | "SHOULD_PLAY_CELEBRATION" | "MARK_CELEBRATION_PLAYED"; export type ProFeature = | "sync" | "passkeys" | "advanced_health" | "smart_password_change" | "ios_app"; /** * License export shape included in PeachScript and encrypted JSON backups. * `celebrationPlayed` is intentionally absent — a new device deserves the moment. */ export interface PeachLicenseExport { key: string; activatedAt?: number; } export interface AdvancedHealthEntryReport { id: string; title: string; domain: string; url?: string; score: number; ageDays: number; reasons: string[]; factors: { breached: boolean; breachCount: number; reused: boolean; aged: boolean; weak: boolean; missingTotpOnSensitive: boolean; }; } export interface AdvancedHealthReport { vaultScore: number; generatedAt: number; entryCount: number; reusedGroups: number; agedCount: number; remediation: AdvancedHealthEntryReport[]; } export const ACCENT_COLORS = [ { name: "peach", hex: "#F49C70" }, { name: "rose", hex: "#FF8AA6" }, { name: "plum", hex: "#D597F2" }, { name: "lavender", hex: "#A6A8FF" }, { name: "sky", hex: "#5BC8EE" }, { name: "mint", hex: "#5EE0B0" }, { name: "sage", hex: "#B5D870" }, { name: "butter", hex: "#FFD45A" }, { name: "sand", hex: "#EDBC85" }, { name: "clay", hex: "#F08660" }, { name: "slate", hex: "#80BEDE" }, { name: "stone", hex: "#C8B8A8" }, ] as const; export type AccentColorName = typeof ACCENT_COLORS[number]["name"]; export interface SavedIdentity { value: string; type: "email" | "username"; lastUsed: number; useCount: number; exampleDomain?: string; } export interface PasswordPolicy { minLength?: number; maxLength?: number; requiresUppercase?: boolean; requiresLowercase?: boolean; requiresNumbers?: boolean; requiresSymbols?: boolean; forbidsSymbols?: boolean; allowedSymbols?: string; /** Characters the site explicitly disallows. Filtered from every pool before generation. */ forbiddenChars?: string; } export interface Message { type: MessageType; payload?: unknown; } export interface MessageResponse { ok: boolean; data?: unknown; error?: string; unlocked?: boolean; }