types.ts

9.0 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  // Passkeys are not portable between browsers: a Chrome passkey holds its70  // private key in Peach's vault, while a Firefox passkey delegates to the71  // platform authenticator. Tag each entry so the UI can warn cross-browser.72  browser?: "chrome" | "firefox";73  transports?: string[];74  // Card entry fields75  cardholderName?: string;76  cardNumber?: string;       // full PAN, stored encrypted in vault77  expMonth?: string;78  expYear?: string;79  cvc?: string;80  cardNickname?: string;81  network?: CardNetwork;82  last4?: string;83}8485export interface LegacyVaultEntry {86  id: string;87  title: string;88  username: string;89  password: string;90  url: string;91  totp?: string;92  notes?: string;93  breach?: number;94  created: number;95  updated: number;96  deletedAt?: number;97}9899export interface CardEntry extends VaultEntry {100  type: "card";101  cardholderName: string;102  cardNumber: string;103  expMonth: string;104  expYear: string;105  cvc: string;106}107108export interface PasskeyEntry extends VaultEntry {109  type: "passkey";110  rpId: string;111  rpName: string;112  credentialId: string;113  publicKeyCose: string;114  privateKeyJwk: JsonWebKey;115  userHandle: string;116  userName: string;117  userDisplayName: string;118  algorithm: -7 | -8;119  signCount: number;120  aaguid: string;121  backupEligible: boolean;122  backupState: boolean;123  lastUsedAt?: number;124  // Optional on read for pre-v2.10.0 entries; new entries always set it.125  browser?: "chrome" | "firefox";126  transports?: string[];127}128129export interface PendingPasskeyRequest {130  requestId: number;131  type: "create" | "get";132  rpId: string;133  rpName: string;134  userName: string;135  userDisplayName?: string;136}137138export interface PasskeyProviderState {139  supported: boolean;140  enabled: boolean;141  attached: boolean;142  browserLabel: string;143  statusLabel: string;144  conflict?: string;145}146147export interface StoredVault {148  version: 1; // AAD for AES-GCM --- prevents downgrade149  salt: string; // hex --- 16 bytes, CSPRNG, stored not secret150  nonce: string; // hex --- 12 bytes, CSPRNG, fresh per write151  ciphertext: string; // hex --- AES-256-GCM output152  tag: string; // hex --- 16-byte GCM auth tag153}154155// Background-bound message types — sent from popup/content scripts,156// handled in src/background/messages.ts. Everything in this group must157// have a matching case in that file's switch statement.158export type MessageType =159  | "UNLOCK"160  | "LOCK"161  | "IS_UNLOCKED"162  | "HAS_VAULT"163  | "GET_ENTRIES"164  | "ADD_ENTRY"165  | "BATCH_ADD_ENTRIES"166  | "RESTORE_ENTRIES"167  | "CLEAR_VAULT_ENTRIES"168  | "UPDATE_ENTRY"169  | "ADD_ADDITIONAL_URL"170  | "DELETE_ENTRY"171  | "AUTOFILL_QUERY"172  | "COPY"173  | "CREATE_VAULT"174  | "GET_ENTRY_FOR_FILL"175  | "CHECK_BREACH"176  | "CHECK_ALL_BREACHES"177  | "GET_SETTINGS"178  | "UPDATE_SETTINGS"179  | "FIRE_CANARY_PING"180  | "ACTIVATE_LICENSE"181  | "GET_LICENSE"182  | "GET_LICENSE_KEY"183  | "IS_DURESS_ENABLED"184  | "SETUP_DURESS"185  | "DISABLE_DURESS"186  | "VERIFY_MAIN_PASSWORD"187  | "CHANGE_PASSWORD"188  | "GET_SITE_PASSWORD_POLICY"189  | "GET_SYNC_STATE"190  | "TRIGGER_SYNC"191  | "START_PAIRING"192  | "CANCEL_PAIRING"193  | "ACCEPT_PAIRING"194  | "REMOVE_PAIRED_DEVICE"195  | "REMOVE_TRUSTED_NETWORK"196  | "MARK_NETWORK_TRUSTED"197  | "GET_PENDING_PASSKEY"198  | "APPROVE_PASSKEY"199  | "DENY_PASSKEY"200  | "DELETE_PASSKEY"201  | "GET_PASSKEYS"202  | "GET_PASSKEY_PROVIDER_STATE"203  | "SET_PASSKEY_PROVIDER_ENABLED"204  | "GET_DELETED_ENTRIES"205  | "RESTORE_ENTRY"206  | "PURGE_ENTRY"207  | "PURGE_EXPIRED_TRASH"208  | "LIST_VAULT_SNAPSHOTS"209  | "RESTORE_VAULT_SNAPSHOT"210  | "COUNT_DUPLICATES"211  | "DEDUPLICATE_ENTRIES"212  | "RECORD_MANUAL_SUMMON"213  | "DECLINE_CARD_SAVE"214  | "IS_CARD_DECLINED"215  | "SUBMIT_FEEDBACK"216  | "GET_VAULT_STATUS"217  | "GET_ADVANCED_HEALTH_REPORT"218  | "INITIATE_PASSWORD_CHANGE"219  | "DEACTIVATE_LICENSE"220  | "GET_IDENTITIES"221  | "DESTROY_VAULT"222  // Content-script-bound message types — sent BY the background TO content223  // scripts. They never reach messages.ts's switch. Kept in this union so224  // existing call sites type-check; do not add cases for them in the225  // background handler.226  | "PING"            // background → content: liveness probe227  | "SUMMON_AUTOFILL" // background → content: open the in-page palette228  | "VAULT_LOCKED"    // background → content/popup: vault just locked229  | "VAULT_UNLOCKED"  // background → content/popup: vault just unlocked230  | "PRO_GATE_TRIGGERED" // background → popup: a Pro feature was attempted on free231  | "PASSKEY_PRO_GATE" // background → content: show passkey Pro gate in inline popup232  | "OPEN_PRO_GATE" // content → background: open popup and navigate to License section233  | "SHOULD_PLAY_CELEBRATION"234  | "MARK_CELEBRATION_PLAYED";235236export type ProFeature =237  | "sync"238  | "passkeys"239  | "advanced_health"240  | "smart_password_change"241  | "ios_app";242243/**244 * License export shape included in PeachScript and encrypted JSON backups.245 * `celebrationPlayed` is intentionally absent — a new device deserves the moment.246 */247export interface PeachLicenseExport {248  key: string;249  activatedAt?: number;250}251252export interface AdvancedHealthEntryReport {253  id: string;254  title: string;255  domain: string;256  url?: string;257  score: number;258  ageDays: number;259  reasons: string[];260  factors: {261    breached: boolean;262    breachCount: number;263    reused: boolean;264    aged: boolean;265    weak: boolean;266    missingTotpOnSensitive: boolean;267  };268}269270export interface AdvancedHealthReport {271  vaultScore: number;272  generatedAt: number;273  entryCount: number;274  reusedGroups: number;275  agedCount: number;276  remediation: AdvancedHealthEntryReport[];277}278279export const ACCENT_COLORS = [280  { name: "peach",    hex: "#F49C70" },281  { name: "rose",     hex: "#FF8AA6" },282  { name: "plum",     hex: "#D597F2" },283  { name: "lavender", hex: "#A6A8FF" },284  { name: "sky",      hex: "#5BC8EE" },285  { name: "mint",     hex: "#5EE0B0" },286  { name: "sage",     hex: "#B5D870" },287  { name: "butter",   hex: "#FFD45A" },288  { name: "sand",     hex: "#EDBC85" },289  { name: "clay",     hex: "#F08660" },290  { name: "slate",    hex: "#80BEDE" },291  { name: "stone",    hex: "#C8B8A8" },292] as const;293294export type AccentColorName = typeof ACCENT_COLORS[number]["name"];295296export interface SavedIdentity {297  value: string;298  type: "email" | "username";299  lastUsed: number;300  useCount: number;301  exampleDomain?: string;302}303304export interface PasswordPolicy {305  minLength?: number;306  maxLength?: number;307  requiresUppercase?: boolean;308  requiresLowercase?: boolean;309  requiresNumbers?: boolean;310  requiresSymbols?: boolean;311  forbidsSymbols?: boolean;312  allowedSymbols?: string;313  /** Characters the site explicitly disallows. Filtered from every pool before generation. */314  forbiddenChars?: string;315}316317export interface Message {318  type: MessageType;319  payload?: unknown;320}321322export interface MessageResponse {323  ok: boolean;324  data?: unknown;325  error?: string;326  unlocked?: boolean;327}