/** * Two-device handoff payload. * * The NEW device publishes a key package to the coordinator and shows this * compact code as a QR. An EXISTING member scans it, consumes the key package * by ref, and the coordinator stores the Welcome for the new device. The * private key never leaves the new device, and the code is short enough for a * normal QR. */ export interface CordnDeviceLink { gid: string; kpRef: string; pubkey: string; coordinatorPubkey: string; relays: string[]; } const PREFIX = 'cordn-device:'; const HEX64 = /^[0-9a-f]{64}$/; function b64urlEncode(value: string): string { return btoa(value).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); } function b64urlDecode(value: string): string { const padded = value.replace(/-/g, '+').replace(/_/g, '/'); const pad = padded.length % 4 === 0 ? '' : '='.repeat(4 - (padded.length % 4)); return atob(padded + pad); } export function encodeDeviceLink(link: CordnDeviceLink): string { const json = JSON.stringify({ g: link.gid, k: link.kpRef, p: link.pubkey, c: link.coordinatorPubkey, r: link.relays, }); return `${PREFIX}${b64urlEncode(json)}`; } export function decodeDeviceLink(raw: string | undefined | null): CordnDeviceLink | null { if (!raw || !raw.startsWith(PREFIX)) return null; try { const parsed = JSON.parse(b64urlDecode(raw.slice(PREFIX.length))) as { g?: unknown; k?: unknown; p?: unknown; c?: unknown; r?: unknown; }; if ( typeof parsed.g !== 'string' || !parsed.g || typeof parsed.k !== 'string' || !parsed.k || typeof parsed.p !== 'string' || !HEX64.test(parsed.p) || typeof parsed.c !== 'string' || !HEX64.test(parsed.c) || !Array.isArray(parsed.r) || parsed.r.length === 0 ) { return null; } const relays = parsed.r.filter( (relay): relay is string => typeof relay === 'string' && /^wss?:\/\/[^\s]+$/i.test(relay), ); if (relays.length === 0) return null; return { gid: parsed.g, kpRef: parsed.k, pubkey: parsed.p, coordinatorPubkey: parsed.c, relays, }; } catch { return null; } }