// Shared types. Port message shapes mirror interfaces.md exactly — including // its naming note: Port `type` values are snake_case; the SDK translates // these into the colon-separated `locqr.on()` event names. This file is the // extension side only, so it stays in the Port's own snake_case vocabulary. export type LocqrErrorCode = 'NOT_INSTALLED' | 'NOT_REGISTERED' | 'CERT_INVALID' | 'ACCOUNT_ERROR' | 'RUN_FAILED'; export interface LocqrError { code: LocqrErrorCode; reason?: string; security?: boolean; } // --- SDK -> Extension --- export interface InitMessage { type: 'init'; cert?: string; } export interface RequestCredentialMessage { type: 'request_credential'; } export interface AbortMessage { type: 'abort'; } export type SdkMessage = InitMessage | RequestCredentialMessage | AbortMessage; // --- Extension -> SDK --- export interface VerificationValidMessage { type: 'verification'; status: 'valid'; features: string[]; } export interface VerificationErrorMessage { type: 'verification'; status: 'error'; error: LocqrError; } export type VerificationMessage = VerificationValidMessage | VerificationErrorMessage; // --- Run lifecycle events, extension -> SDK (unsolicited) --- export interface Credential { username: string; password: string; } export interface RunStartedMessage { type: 'run_started'; } export interface RunDeliveredMessage { type: 'run_delivered'; credential: Credential; } export interface RunErrorEventMessage { type: 'run_error'; error: LocqrError; } export type ExtensionToSdkMessage = VerificationMessage | RunStartedMessage | RunDeliveredMessage | RunErrorEventMessage; // --- Popup -> Background (internal chrome.runtime.sendMessage, distinct // from the external SDK Port — these never cross to the page). --- export type PopupActionKind = 'start_run' | 'confirm_pin' | 'abort' | 'retry' | 'dismiss'; export interface PopupActionMessage { type: 'popup_action'; tabId: number; action: PopupActionKind; } // --- Relay messages (interfaces.md, "Relay messages") --- export interface RelayKemCiphertextMessage { type: 'kem_ciphertext'; payload_b64: string; } export interface RelayCredentialMessage { type: 'credential'; payload_b64: string; } export type RelayMessage = RelayKemCiphertextMessage | RelayCredentialMessage; // --- Run bundle upload (interfaces.md, "Run bundle upload") --- export interface RunBundleUploadRequest { runId: string; origin: string; x25519_pubkey: string; kem_pubkey: string; } export interface RunBundleUploadResponse { signed_token: string; qr_ttl: number; max_auto_refresh: number; pin_ttl: number; } // --- Per-tab state (extension/claude.md's state model) --- export type CertInvalidReason = | 'insecure_origin' | 'malformed' | 'signature_invalid' | 'domain_mismatch' | 'expired' | 'not_yet_valid'; export type AccountErrorReason = 'rejected' | 'unreachable'; /** * Per extension/claude.md's `run_error` classes — `alpha_hash_mismatch` and * `pin_mismatch` deliberately excluded (companion-only detections, see that * file's `run_error` note). Only `user_abort` is wired up by any code path * in this slice; the rest are real and specified but deferred to a * follow-up hardening pass (see gui.md's "Open / not yet specified" — same * pattern). */ export type RunErrorReason = 'ttl_exhausted' | 'pin_timeout' | 'user_abort' | 'backend_unreachable' | 'relay_timeout' | 'relay_error'; export type RunErrorClass = 'security' | 'timeout_user' | 'network'; /** * This union (run-phase variants included) is stored in chrome.storage.session * for popup reactivity, same mechanism as the verification states — but it * only ever carries display-safe data: QR content and PIN digits are * already what's shown on screen / scanned by a camera, not secret. The * actual keypairs, shared secrets, and run key are a *different* thing — * they live only in background/run-secrets.ts's in-memory map and are never * serialized anywhere, matching the security invariant that private key * material never leaves the service worker (extension/claude.md's Data * storage table lists these separately from "Per-tab state machine state" * for exactly this reason). */ export type TabState = | { kind: 'unverified' } | { kind: 'not_registered' } | { kind: 'cert_invalid'; reason: CertInvalidReason; security: boolean } | { kind: 'account_error'; reason: AccountErrorReason } | { kind: 'idle'; origin: string; features: string[] } | { kind: 'phase_1'; origin: string; features: string[] } | { kind: 'phase_2'; origin: string; features: string[]; qrContent: string; qrTtlSeconds: number; phaseStartedAt: number } | { kind: 'phase_3'; origin: string; features: string[]; pin: string; pinTtlSeconds: number; phaseStartedAt: number } | { kind: 'phase_4'; origin: string; features: string[] } | { kind: 'delivered'; origin: string; features: string[] } | { kind: 'run_error'; origin: string; features: string[]; reason: RunErrorReason; errorClass: RunErrorClass };