# `manager/manager.ts` _(no submodules)_ ## Purpose `ClipboardManager` is the orchestrator class for `@suaveplan/clipboard`. It composes the navigator + window seams, the FIFO `AsyncQueue`, the typed error registry, the OpenTelemetry observability bundle, the secure-context guard, the permission probe, and the fallback path into a single ergonomic facade. Every public method records counters, the operation latency histogram, and per-code error totals so production deployments observe clipboard health without any caller-side wiring. ## Features - `copyText`, `copyRichContent`, `readText`, `readRichContent` with consistent `ClipboardResult` returns. - `checkPermissions` + `onPermissionChange` for permission lifecycle. - `isSupported`, `isSecure`, `dispose` for environment + lifecycle. - Configurable serialization (`serialize: true` by default). - Configurable secure-context guard (`requireSecureContext`). - Optional sanitizer hook applied to inbound HTML. - Optional pre-built observability bundle. - Focus-aware retry on `NotAllowedError: Document is not focused`. ## Usage: Basic ```ts import { ClipboardManager } from "@suaveplan/clipboard"; const clipboard = new ClipboardManager(); const result = await clipboard.copyText("hello"); if (!result.success) { console.warn("copy failed:", result.code, result.error); } ``` ## Usage: Advanced ```ts import { ClipboardManager, createClipboardObservability, createTagAllowlistSanitizer, } from "@suaveplan/clipboard"; const observability = createClipboardObservability(); const clipboard = new ClipboardManager({ useFallback: true, requireSecureContext: true, serialize: true, focusRetry: true, sanitizer: createTagAllowlistSanitizer(["p", "b", "i", "br"]), pasteTimeoutMs: 2000, observability, }); const png = await loadPngBlob(); await clipboard.copyRichContent({ text: "fallback text", html: "bold", image: png, }); const unsub = await clipboard.onPermissionChange((snapshot, name) => { console.info("permission change", name, snapshot); }); // later: unsub(); await clipboard.dispose(); ``` ## API Reference ### `new ClipboardManager(options?, nav?, win?)` Construct a manager. `options` is merged on top of `DEFAULT_CLIPBOARD_OPTIONS`. `nav` and `win` default to the global `navigator` and `window`; either may be supplied as a structural shape for SSR or unit tests. ### `copyText(text): Promise>` Copy plain text. Uses the async API when available; falls back to the hidden-textarea path on rejection or absence when `useFallback` is true. Short-circuits with `CLIPBOARD.INSECURE_CONTEXT` when `requireSecureContext` is set and the bound window is insecure. ### `copyRichContent(data): Promise>` Copy rich content (text / html / image / blobs) via `ClipboardItem`. The fallback path requires `data.text`; rich-only payloads without text short-circuit with `CLIPBOARD.NO_CONTENT`. Empty payloads also surface `CLIPBOARD.NO_CONTENT`. ### `readText(): Promise>` Read plain text. Falls back to the paste-event capture when the API is unavailable or rejects (and `useFallback` is true). ### `readRichContent(): Promise>` Read rich content. Returns `CLIPBOARD.API_UNAVAILABLE` when no async API is present (rich content has no usable fallback). Applies the optional sanitizer to inbound HTML. ### `checkPermissions(): Promise` Snapshot of `clipboard-read` + `clipboard-write` permission state. ### `onPermissionChange(cb): Promise<() => void>` Subscribe to permission changes. Returns an unsubscribe function. ### `isSupported(): boolean` True when `navigator.clipboard` is defined on the bound navigator. ### `isSecure(): boolean` True when the bound window does not flag `isSecureContext: false`. ### `dispose(): Promise` Drain the internal queue. After dispose, queued operations resolve with their current outcomes; new enqueues reject. ## Implementation Notes The manager keeps a single `OperationContext` reference (navigator, window, options, observability) shared with the pure operation functions in `operations.ts`. This split keeps the class under the 300-line file budget while preserving the public API. The internal `run` method wraps every operation in a latency-measuring closure and either enqueues it via the `AsyncQueue` (when `serialize: true`) or invokes it directly. The `recordResult` helper updates counters per-call and per-error-code so the OTel pipeline measures both the macro-shape (copy vs. read) and the micro-shape (which `CLIPBOARD.*` code fired). The constructor is SSR- safe: missing globals collapse to a fully-typed empty navigator and an undefined window so import-time evaluation never throws on a server.