import type { AdoptRequest } from "../shared/vault-adopt"; import type { PairedDevice } from "../shared/sync-types"; import { storageGet, storageRemove, storageSet } from "../shared/storage"; export const PENDING_ADOPTION_HOLDER_KEY = "peach_pending_adoption_holder_v1"; export const PENDING_ADOPTION_IDENTITY_KEY = "peach_pending_adoption_identity_v2"; export type PendingAdoptionHolder = { request: AdoptRequest; createdAt: number; }; export type PendingAdoptionIdentity = { device: PairedDevice; createdAt: number; }; function isHolder(value: unknown): value is PendingAdoptionHolder { if (!value || typeof value !== "object") return false; const holder = value as Partial; return typeof holder.createdAt === "number" && Number.isFinite(holder.createdAt) && !!holder.request && typeof holder.request === "object" && typeof (holder.request as Partial).device_id === "string"; } export async function persistPendingAdoptionHolder(request: AdoptRequest): Promise { await storageSet(PENDING_ADOPTION_HOLDER_KEY, JSON.stringify({ request, createdAt: Date.now() })); } /** Returns null for absent or malformed data; malformed bytes remain for explicit recovery/removal. */ export async function readPendingAdoptionHolder(): Promise { const raw = await storageGet(PENDING_ADOPTION_HOLDER_KEY); if (!raw) return null; try { const parsed: unknown = JSON.parse(raw); return isHolder(parsed) ? parsed : null; } catch { return null; } } export async function listPendingAdoptionHolders(): Promise> { const [holder, identity] = await Promise.all([ readPendingAdoptionHolder(), readPendingAdoptionIdentity(), ]); return [holder, identity].filter((value): value is PendingAdoptionHolder | PendingAdoptionIdentity => value !== null); } export async function removePendingAdoptionHolder(): Promise { await Promise.all([ storageRemove(PENDING_ADOPTION_HOLDER_KEY), storageRemove(PENDING_ADOPTION_IDENTITY_KEY), ]); } export const revokePendingAdoptionHolder = removePendingAdoptionHolder; export async function persistPendingAdoptionIdentity( device: PairedDevice, createdAt: number, ): Promise { const { rendezvousSecretHex: _rendezvousSecretHex, ...secretlessDevice } = device; const encoded = JSON.stringify({ device: secretlessDevice, createdAt }); try { await storageSet(PENDING_ADOPTION_IDENTITY_KEY, encoded); return (await storageGet(PENDING_ADOPTION_IDENTITY_KEY)) === encoded; } catch { return false; } } export async function readPendingAdoptionIdentity(): Promise { const raw = await storageGet(PENDING_ADOPTION_IDENTITY_KEY); if (!raw) return null; try { const value = JSON.parse(raw) as { device?: PairedDevice; createdAt?: number }; return value.device?.deviceId && Number.isFinite(value.createdAt) ? { device: value.device, createdAt: value.createdAt as number } : null; } catch { return null; } } export async function completePendingAdoptionIdentity(device: PairedDevice): Promise { const pending = await readPendingAdoptionIdentity(); if (pending?.device.deviceId === device.deviceId && pending.device.longTermPublicKeyHex === device.longTermPublicKeyHex) { await storageRemove(PENDING_ADOPTION_IDENTITY_KEY); } }