# `permissions/permissions.ts` _(no submodules)_ ## Purpose Probe and subscribe to clipboard permission state via the browser's Permissions API. `checkPermissions` returns a snapshot of the `clipboard-read` and `clipboard-write` permission states with graceful degradation to `"prompt"` when the API is missing or a query rejects. `onPermissionChange` subscribes a callback to `PermissionStatus.change` events on both descriptors so UI surfaces can reflect real-time grants and revocations without polling. ## Features - `checkPermissions(nav)` — `{ read, write }` snapshot; never throws. - `onPermissionChange(nav, cb)` — subscribe with one callback, unsubscribe with a single returned function. - Tolerant of partial API support: falls back to `PermissionStatus.onchange` when `addEventListener` is unavailable. - Tolerant of permission-query rejection: skips that slot without re-throwing. ## Usage: Basic ```ts import { checkPermissions } from "@suaveplan/clipboard"; const { read, write } = await checkPermissions(navigator); if (read === "denied" || write === "denied") { showFallbackUi(); } ``` ## Usage: Advanced ```ts import { onPermissionChange } from "@suaveplan/clipboard"; const unsubscribe = await onPermissionChange( navigator, (snapshot, descriptor) => { console.info( "[clipboard] permission change", descriptor, snapshot, ); if (snapshot.read === "granted" && snapshot.write === "granted") { enableCopyButtons(); } }, ); // later — on unmount / dispose unsubscribe(); ``` ## API Reference ### `checkPermissions(nav: ClipboardNavigator): Promise` Resolves with `{ read, write }`. Each slot is one of `"granted"`, `"denied"`, `"prompt"`. Returns `{ read: "prompt", write: "prompt" }` when `nav.permissions` is undefined, and `"prompt"` for any individual descriptor whose `query()` rejects or returns an unknown state. ### `onPermissionChange(nav, cb): Promise<() => void>` Subscribes `cb` to permission changes on both `clipboard-read` and `clipboard-write`. Returns an unsubscribe function. The callback receives the latest `PermissionResult` snapshot plus the descriptor name whose state changed. The change handler internally re-runs `checkPermissions` so both `read` and `write` are always up to date on every callback. ## Implementation Notes The Permissions API exposes two listener mechanisms — `addEventListener` and `onchange`. Some platforms (older WebKit / mobile) support only one, so the module attaches via `addEventListener` when available and falls back to assigning `onchange` otherwise. The unsubscribe function clears the corresponding mechanism for each subscription. The `checkPermissions` helper normalises unknown state strings to `"prompt"` so callers never have to defend against future spec values. Query rejection is treated as `"prompt"` rather than `"denied"` because a rejection typically signals "API not supported" rather than "user explicitly refused" — pessimistic mapping would block correct callers in older browsers. Tests cover both `addEventListener` and `onchange` paths, plus the unsubscribe contract.