159 lines
5.1 KiB
TypeScript
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 };
|