work in progress
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
// 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 };
|
||||
Reference in New Issue
Block a user