/** * Package secrets for the Audible tokens. Code writes values once with * `secretSet` and afterwards handles only opaque `{{secret:…}}` refs, which * Kody fills in on the way out to approved hosts. */ import { kody, packageSecrets } from 'kody:runtime' import { NotConnectedError } from './device.ts' export async function saveSecret(input: { name: string value: string description: string expiresAt?: string | null }): Promise<{ packageId: string | null }> { const saved = (await kody.secretSet({ name: input.name, value: input.value, description: input.description, scope: 'package', ...(input.expiresAt !== undefined ? { expires_at: input.expiresAt } : {}), })) as { package_id?: unknown } | null return { packageId: typeof saved?.package_id === 'string' ? saved.package_id : null } } /** Delete a package secret. Returns false when Kody refuses or it is already gone. */ export async function deleteSecret(name: string): Promise { try { const result = (await kody.secretDelete({ name, scope: 'package' })) as { deleted?: unknown } | null return result?.deleted === true } catch { return false } } /** Opaque placeholder for a package secret. Never a value. */ export async function secretRef(name: string): Promise { let ref: unknown try { ref = await packageSecrets.get(name) } catch { ref = null } if (typeof ref !== 'string' || !ref.startsWith('{{secret')) { throw new NotConnectedError() } return ref } export type SecretState = { exists: boolean hosts: string[] expiresAt: string | null packageId: string | null } /** * Metadata only: which hosts may receive each secret. Returns null when the * runtime does not list package secrets (the hosted app surface does not). */ export async function secretStates(names: string[]): Promise | null> { const states: Record = {} for (const name of names) states[name] = { exists: false, hosts: [], expiresAt: null, packageId: null } let found = false try { const listed = (await kody.secretList({ scope: 'package' })) as | Array> | { secrets?: Array> } const rows = Array.isArray(listed) ? listed : (listed?.secrets ?? []) for (const row of rows) { const name = String(row.name ?? '') if (!(name in states)) continue found = true states[name] = { exists: true, hosts: Array.isArray(row.allowed_hosts) ? row.allowed_hosts.map(String) : [], expiresAt: typeof row.expires_at === 'string' ? row.expires_at : null, packageId: typeof row.package_id === 'string' ? row.package_id : null, } } } catch { return null } return found ? states : null }