Files
locqr/extension/src/types.ts
T
2026-08-16 22:00:08 +02:00

159 lines
5.1 KiB
TypeScript

// 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 };