first commit
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
# `permissions/permissions.ts`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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<PermissionResult>`
|
||||
|
||||
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.
|
||||
Reference in New Issue
Block a user