From 04c9503036bb929821a96c785b1295275c1514fc Mon Sep 17 00:00:00 2001 From: adlee-was-taken Date: Mon, 20 Apr 2026 19:42:31 -0400 Subject: [PATCH] feat(ext): typed-item TS types mirroring relicario-core serde --- extension/src/shared/types.ts | 241 ++++++++++++++++++++++++++++++---- 1 file changed, 216 insertions(+), 25 deletions(-) diff --git a/extension/src/shared/types.ts b/extension/src/shared/types.ts index a5ac6cb..e2b7340 100644 --- a/extension/src/shared/types.ts +++ b/extension/src/shared/types.ts @@ -1,32 +1,202 @@ -/// Full credential entry (matches Rust Entry struct in relicario-core). -export interface Entry { - name: string; - url?: string; +/// Typed-item shared TypeScript types. +/// +/// These mirror the Rust core's serde serialization. See +/// crates/relicario-core/src/item.rs, item_types/, and settings.rs +/// for the source shapes. + +// --- IDs --- + +export type ItemId = string; // 16-char hex +export type FieldId = string; // 16-char hex +export type AttachmentId = string; // 16-char hex (sha256 of plaintext, truncated) + +// --- ItemType / ItemCore --- + +// snake_case from serde rename_all +export type ItemType = + | 'login' | 'secure_note' | 'identity' | 'card' | 'key' | 'document' | 'totp'; + +// ItemCore is internally-tagged on "type": +// Login → { type: 'login', username, password, url, totp } +export type ItemCore = + | ({ type: 'login' } & LoginCore) + | ({ type: 'secure_note' } & SecureNoteCore) + | ({ type: 'identity' } & IdentityCore) + | ({ type: 'card' } & CardCore) + | ({ type: 'key' } & KeyCore) + | ({ type: 'document' } & DocumentCore) + | ({ type: 'totp' } & TotpCore); + +// Optional fields use `?` because Rust #[serde(skip_serializing_if = "Option::is_none")] +// omits them from the JSON; serde_wasm_bindgen produces `undefined` on read. + +export interface LoginCore { username?: string; - password: string; + password?: string; + url?: string; + totp?: TotpConfig; +} + +export interface SecureNoteCore { body: string; } + +export interface IdentityCore { + full_name?: string; + address?: string; + phone?: string; + email?: string; + date_of_birth?: string; // "YYYY-MM-DD" +} + +export interface CardCore { + number?: string; + holder?: string; + expiry?: { month: number; year: number }; + cvv?: string; + pin?: string; + kind: CardKind; +} + +export type CardKind = 'credit' | 'debit' | 'gift' | 'loyalty' | 'other'; + +export interface KeyCore { + key_material: string; + label?: string; + public_key?: string; + algorithm?: string; +} + +export interface DocumentCore { + filename: string; + mime_type: string; + primary_attachment: AttachmentId; +} + +export interface TotpCore { + config: TotpConfig; + issuer?: string; + label?: string; +} + +// --- TOTP --- + +export type TotpKind = 'totp' | 'steam' | { hotp: { counter: number } }; + +export interface TotpConfig { + secret: number[]; // Vec → JSON number array + algorithm: 'sha1' | 'sha256' | 'sha512'; + digits: number; + period_seconds: number; + kind: TotpKind; +} + +// --- Sections + custom fields --- + +export interface Section { + name?: string; + fields: Field[]; +} + +export interface Field { + id: FieldId; + label: string; + kind: FieldKind; + value: FieldValue; + hidden_by_default: boolean; +} + +export type FieldKind = + | 'text' | 'multiline' | 'password' | 'concealed' | 'url' | 'email' + | 'phone' | 'date' | 'month_year' | 'totp' | 'reference'; + +// adjacently-tagged { tag: "kind", content: "value" } +export type FieldValue = + | { kind: 'text'; value: string } + | { kind: 'multiline'; value: string } + | { kind: 'password'; value: string } + | { kind: 'concealed'; value: string } + | { kind: 'url'; value: string } + | { kind: 'email'; value: string } + | { kind: 'phone'; value: string } + | { kind: 'date'; value: string } + | { kind: 'month_year'; value: { month: number; year: number } } + | { kind: 'totp'; value: TotpConfig } + | { kind: 'reference'; value: AttachmentId }; + +// --- Attachments + history --- + +export interface AttachmentRef { + id: AttachmentId; + filename: string; + mime_type: string; + size: number; + created: number; +} + +export interface FieldHistoryEntry { + value: string; + replaced_at: number; +} + +export interface AttachmentSummary { + id: AttachmentId; + filename: string; + mime_type: string; + size: number; +} + +// --- Item envelope --- + +export interface Item { + id: ItemId; + title: string; + type: ItemType; // Rust r#type → JSON key "type" + tags: string[]; + favorite: boolean; + group?: string; notes?: string; - totp_secret?: string; - group?: string; - created_at: string; - updated_at: string; + created: number; + modified: number; + trashed_at?: number; + core: ItemCore; + sections: Section[]; + attachments: AttachmentRef[]; + field_history: Record; } -/// Lightweight manifest entry for listing/searching without full decrypt. -export interface ManifestEntry { - name: string; - url?: string; - username?: string; - group?: string; - updated_at: string; -} +// --- Manifest (schema_version 2) --- -/// Encrypted manifest containing all entry metadata. export interface Manifest { - entries: Record; - version: number; + schema_version: number; // 2 + items: Record; } -/// Configuration for connecting to a git host. +export interface ManifestEntry { + id: ItemId; + type: ItemType; + title: string; + tags: string[]; + favorite: boolean; + group?: string; + icon_hint?: string; + modified: number; + trashed_at?: number; + attachment_summaries: AttachmentSummary[]; +} + +// --- Vault settings (only the fields α touches) --- +// Full shape lives on the Rust side and in docs/superpowers/specs/2026-04-18-relicario-typed-items-design.md +// We leave retention/generator/caps opaque to α so we don't accidentally mutate them. + +export interface VaultSettings { + trash_retention: unknown; + field_history_retention: unknown; + generator_defaults: unknown; + attachment_caps: unknown; + autofill_origin_acks: Record; +} + +// --- Vault config (device-local) --- + export interface VaultConfig { hostType: 'gitea' | 'github'; hostUrl: string; @@ -34,20 +204,41 @@ export interface VaultConfig { apiToken: string; } -/// Persisted setup state in chrome.storage.local. export interface SetupState { config: VaultConfig | null; imageBase64: string | null; isConfigured: boolean; } -/// User-configurable credential capture settings. -export interface RelicarioSettings { +// --- Device-local UX settings (chrome.storage.local — renamed from RelicarioSettings) --- + +export interface DeviceSettings { captureEnabled: boolean; captureStyle: 'bar' | 'toast'; } -export const DEFAULT_SETTINGS: RelicarioSettings = { +export const DEFAULT_DEVICE_SETTINGS: DeviceSettings = { captureEnabled: false, captureStyle: 'bar', }; + +// --- Generator request (matches Rust GeneratorRequest — tag="kind") --- + +export type GeneratorRequest = + | { kind: 'bip39'; word_count: number; separator: string; capitalization: Capitalization } + | { kind: 'random'; length: number; classes: CharClasses; symbol_charset: SymbolCharset }; + +export type Capitalization = 'lower' | 'upper' | 'first_of_each' | 'title' | 'mixed'; +export interface CharClasses { lower: boolean; upper: boolean; digits: boolean; symbols: boolean; } +export type SymbolCharset = + | { kind: 'safe_only' } + | { kind: 'extended' } + | { kind: 'custom'; value: string }; + +// Default used by the α popup "gen" button: +export const DEFAULT_PASSWORD_REQUEST: GeneratorRequest = { + kind: 'random', + length: 20, + classes: { lower: true, upper: true, digits: true, symbols: true }, + symbol_charset: { kind: 'safe_only' }, +};