types.ts

8.5 KB · raw

1/**2 * types.ts — cross-boundary types and the `MessageType` union.3 *4 * `MessageType` mixes background-bound and content-script-bound messages.5 * Background-bound types have a case in `src/background/messages.ts`.6 * Content-script-bound types (PING, SUMMON_AUTOFILL, VAULT_LOCKED,7 * RESTART_AUTO_LOCK, etc.) are sent *to* content scripts or the popup and8 * have no handler in messages.ts by design — that absence is not a missing9 * handler. Add new cross-boundary types here, not inline in call sites.10 */1112export interface ManualSummonRecord {13  firstSummonedAt: number;14  summonCount: number;15  lastSummonedAt: number;16}1718export interface Canary {19  webhookUrl: string;20  label?: string;21  lastPinged?: number;22  pingCount?: number;23}2425export type CardNetwork = "visa" | "mastercard" | "amex" | "discover" | "unknown";2627export interface VaultEntry {28  id: string; // crypto.randomUUID()29  type?: "login" | "note" | "passkey" | "card"; // defaults to "login" for existing entries30  title: string;31  username?: string; // only for login entries32  password?: string; // only for login entries33  url?: string; // only for login entries34  /**35   * Additional sites this entry should match for autofill. Login entries only.36   * Each value is a full origin or URL (e.g. `https://login.foo.com`).37   * The vault domain index keys against the hostname of every entry in this38   * list, in addition to `url`.39   */40  additionalUrls?: string[];41  totp?: string; // base32 seed, stored encrypted — only for login entries42  notes?: string;43  breach?: number; // HIBP exposure count; 0 or undefined = clean — only for login entries44  /**45   * When the password field was last changed. Set on first-write of `password`46   * and refreshed on every subsequent change. Defaults to `createdAt` when47   * legacy entries are read by the advanced health analyser.48   */49  passwordChangedAt?: number;50  canary?: Canary;51  createdAt: number; // Date.now()52  modifiedAt: number;53  deletedAt?: number;54  tags?: string[];55  rpId?: string;56  rpName?: string;57  credentialId?: string;58  publicKeyCose?: string;59  privateKeyJwk?: JsonWebKey;60  userHandle?: string;61  userName?: string;62  userDisplayName?: string;63  algorithm?: -7 | -8;64  signCount?: number;65  aaguid?: string;66  backupEligible?: boolean;67  backupState?: boolean;68  lastUsedAt?: number;69  // Card entry fields70  cardholderName?: string;71  cardNumber?: string;       // full PAN, stored encrypted in vault72  expMonth?: string;73  expYear?: string;74  cvc?: string;75  cardNickname?: string;76  network?: CardNetwork;77  last4?: string;78}7980export interface LegacyVaultEntry {81  id: string;82  title: string;83  username: string;84  password: string;85  url: string;86  totp?: string;87  notes?: string;88  breach?: number;89  created: number;90  updated: number;91  deletedAt?: number;92}9394export interface CardEntry extends VaultEntry {95  type: "card";96  cardholderName: string;97  cardNumber: string;98  expMonth: string;99  expYear: string;100  cvc: string;101}102103export interface PasskeyEntry extends VaultEntry {104  type: "passkey";105  rpId: string;106  rpName: string;107  credentialId: string;108  publicKeyCose: string;109  privateKeyJwk: JsonWebKey;110  userHandle: string;111  userName: string;112  userDisplayName: string;113  algorithm: -7 | -8;114  signCount: number;115  aaguid: string;116  backupEligible: boolean;117  backupState: boolean;118  lastUsedAt?: number;119}120121export interface PendingPasskeyRequest {122  requestId: number;123  type: "create" | "get";124  rpId: string;125  rpName: string;126  userName: string;127  userDisplayName?: string;128}129130export interface PasskeyProviderState {131  supported: boolean;132  enabled: boolean;133  attached: boolean;134  browserLabel: string;135  statusLabel: string;136  conflict?: string;137}138139export interface StoredVault {140  version: 1; // AAD for AES-GCM --- prevents downgrade141  salt: string; // hex --- 16 bytes, CSPRNG, stored not secret142  nonce: string; // hex --- 12 bytes, CSPRNG, fresh per write143  ciphertext: string; // hex --- AES-256-GCM output144  tag: string; // hex --- 16-byte GCM auth tag145}146147// Background-bound message types — sent from popup/content scripts,148// handled in src/background/messages.ts. Everything in this group must149// have a matching case in that file's switch statement.150export type MessageType =151  | "UNLOCK"152  | "LOCK"153  | "IS_UNLOCKED"154  | "HAS_VAULT"155  | "GET_ENTRIES"156  | "ADD_ENTRY"157  | "BATCH_ADD_ENTRIES"158  | "RESTORE_ENTRIES"159  | "CLEAR_VAULT_ENTRIES"160  | "UPDATE_ENTRY"161  | "ADD_ADDITIONAL_URL"162  | "DELETE_ENTRY"163  | "AUTOFILL_QUERY"164  | "COPY"165  | "CREATE_VAULT"166  | "GET_ENTRY_FOR_FILL"167  | "CHECK_BREACH"168  | "CHECK_ALL_BREACHES"169  | "GET_SETTINGS"170  | "UPDATE_SETTINGS"171  | "FIRE_CANARY_PING"172  | "ACTIVATE_LICENSE"173  | "GET_LICENSE"174  | "GET_LICENSE_KEY"175  | "IS_DURESS_ENABLED"176  | "SETUP_DURESS"177  | "DISABLE_DURESS"178  | "VERIFY_MAIN_PASSWORD"179  | "CHANGE_PASSWORD"180  | "GET_SITE_PASSWORD_POLICY"181  | "GET_SYNC_STATE"182  | "TRIGGER_SYNC"183  | "START_PAIRING"184  | "CANCEL_PAIRING"185  | "ACCEPT_PAIRING"186  | "REMOVE_PAIRED_DEVICE"187  | "REMOVE_TRUSTED_NETWORK"188  | "MARK_NETWORK_TRUSTED"189  | "GET_PENDING_PASSKEY"190  | "APPROVE_PASSKEY"191  | "DENY_PASSKEY"192  | "DELETE_PASSKEY"193  | "GET_PASSKEYS"194  | "GET_PASSKEY_PROVIDER_STATE"195  | "SET_PASSKEY_PROVIDER_ENABLED"196  | "GET_DELETED_ENTRIES"197  | "RESTORE_ENTRY"198  | "PURGE_ENTRY"199  | "PURGE_EXPIRED_TRASH"200  | "LIST_VAULT_SNAPSHOTS"201  | "RESTORE_VAULT_SNAPSHOT"202  | "COUNT_DUPLICATES"203  | "DEDUPLICATE_ENTRIES"204  | "RECORD_MANUAL_SUMMON"205  | "DECLINE_CARD_SAVE"206  | "IS_CARD_DECLINED"207  | "SUBMIT_FEEDBACK"208  | "GET_VAULT_STATUS"209  | "GET_ADVANCED_HEALTH_REPORT"210  | "INITIATE_PASSWORD_CHANGE"211  | "DEACTIVATE_LICENSE"212  | "GET_IDENTITIES"213  // Content-script-bound message types — sent BY the background TO content214  // scripts. They never reach messages.ts's switch. Kept in this union so215  // existing call sites type-check; do not add cases for them in the216  // background handler.217  | "PING"            // background → content: liveness probe218  | "SUMMON_AUTOFILL" // background → content: open the in-page palette219  | "VAULT_LOCKED"    // background → content/popup: vault just locked220  | "VAULT_UNLOCKED"  // background → content/popup: vault just unlocked221  | "PRO_GATE_TRIGGERED" // background → popup: a Pro feature was attempted on free222  | "PASSKEY_PRO_GATE" // background → content: show passkey Pro gate in inline popup223  | "OPEN_PRO_GATE" // content → background: open popup and navigate to License section224  | "SHOULD_PLAY_CELEBRATION"225  | "MARK_CELEBRATION_PLAYED";226227export type ProFeature =228  | "sync"229  | "passkeys"230  | "advanced_health"231  | "smart_password_change"232  | "ios_app";233234/**235 * License export shape included in CJK phrase and encrypted JSON backups.236 * `celebrationPlayed` is intentionally absent — a new device deserves the moment.237 */238export interface PeachLicenseExport {239  key: string;240  activatedAt?: number;241}242243export interface AdvancedHealthEntryReport {244  id: string;245  title: string;246  domain: string;247  url?: string;248  score: number;249  ageDays: number;250  reasons: string[];251  factors: {252    breached: boolean;253    breachCount: number;254    reused: boolean;255    aged: boolean;256    weak: boolean;257    missingTotpOnSensitive: boolean;258  };259}260261export interface AdvancedHealthReport {262  vaultScore: number;263  generatedAt: number;264  entryCount: number;265  reusedGroups: number;266  agedCount: number;267  remediation: AdvancedHealthEntryReport[];268}269270export const ACCENT_COLORS = [271  { name: "peach",    hex: "#F49C70" },272  { name: "rose",     hex: "#FF8AA6" },273  { name: "plum",     hex: "#D597F2" },274  { name: "lavender", hex: "#A6A8FF" },275  { name: "sky",      hex: "#5BC8EE" },276  { name: "mint",     hex: "#5EE0B0" },277  { name: "sage",     hex: "#B5D870" },278  { name: "butter",   hex: "#FFD45A" },279  { name: "sand",     hex: "#EDBC85" },280  { name: "clay",     hex: "#F08660" },281  { name: "slate",    hex: "#80BEDE" },282  { name: "stone",    hex: "#C8B8A8" },283] as const;284285export type AccentColorName = typeof ACCENT_COLORS[number]["name"];286287export interface SavedIdentity {288  value: string;289  type: "email" | "username";290  lastUsed: number;291  useCount: number;292  exampleDomain?: string;293}294295export interface PasswordPolicy {296  minLength?: number;297  maxLength?: number;298  requiresUppercase?: boolean;299  requiresLowercase?: boolean;300  requiresNumbers?: boolean;301  requiresSymbols?: boolean;302  forbidsSymbols?: boolean;303  allowedSymbols?: string;304  /** Characters the site explicitly disallows. Filtered from every pool before generation. */305  forbiddenChars?: string;306}307308export interface Message {309  type: MessageType;310  payload?: unknown;311}312313export interface MessageResponse {314  ok: boolean;315  data?: unknown;316  error?: string;317  unlocked?: boolean;318}