4.8 KiB
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,readRichContentwith consistentClipboardResultreturns.checkPermissions+onPermissionChangefor permission lifecycle.isSupported,isSecure,disposefor environment + lifecycle.- Configurable serialization (
serialize: trueby 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
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
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: "<b>bold</b>",
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<ClipboardResult<string>>
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<ClipboardResult<ClipboardData>>
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<ClipboardResult<string>>
Read plain text. Falls back to the paste-event capture when the API is
unavailable or rejects (and useFallback is true).
readRichContent(): Promise<ClipboardResult<ClipboardData>>
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<PermissionResult>
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<void>
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.