Files
skills/docs/foundation/data/clipboard/src/manager/manager.md
2026-05-29 08:20:56 +01:00

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, 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

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.