/** * Copy text, in a browser that may not be in a secure context. * * ── Why this exists ── * * `navigator.clipboard` is SECURE-CONTEXT ONLY. Over plain http on anything that * is not localhost the whole object is undefined, so `navigator.clipboard.writeText` * throws `TypeError: Cannot read properties of undefined`. * * Officer is reachable over plain http — `http://officer-dev:9000` on the tailnet — * which is neither https nor localhost. The owner's own browsing is over https * (`platform.officer.dev`) and was already https when this was written; the http * origins are the other ways in, and an install with no certificate has no other * way in at all. Twenty call sites were affected, in three states that all looked * fine in review: * * bare `navigator.clipboard.writeText(x)` threw, killing the handler * `navigator.clipboard?.writeText(x)` did nothing, silently * one carrying the comment "Officer is always behind HTTPS" * * The middle one is the worst: a copy button that reports success and copies * nothing is indistinguishable from a working one until you paste. * * ── The fallback ── * * `document.execCommand('copy')` over a temporary, off-screen textarea. It is * deprecated and it works in every browser that runs this app, on any origin, * because it predates the secure-context rule. Deprecated-and-working beats * modern-and-absent. * * The textarea is positioned off-screen rather than hidden: `display:none` and * `visibility:hidden` elements cannot be selected, so the copy silently fails. * `readOnly` stops a mobile keyboard appearing for the instant it is focused. * * Returns whether it worked, so a caller can say "copied" only when it did. * Reading the clipboard has no equivalent fallback — see `canReadClipboard`. */ export async function copyToClipboard(text: string): Promise { if (navigator.clipboard?.writeText) { try { await navigator.clipboard.writeText(text); return true; } catch { // Permission refused, or a document that is not focused. Fall through. } } if (typeof document === 'undefined') return false; const area = document.createElement('textarea'); area.value = text; area.setAttribute('readonly', ''); area.style.position = 'fixed'; area.style.top = '-9999px'; area.style.opacity = '0'; document.body.appendChild(area); try { area.select(); area.setSelectionRange(0, text.length); return document.execCommand('copy'); } catch { return false; } finally { document.body.removeChild(area); } } /** * Whether the clipboard can be READ on demand. * * There is no fallback for this one. `document.execCommand('paste')` was never * permitted from script, so on an insecure origin the only way to get clipboard * contents is a real paste event the user initiates — which is a different * interaction, not a drop-in. * * Callers should use this to hide a "paste" affordance rather than offer one * that cannot work. */ export function canReadClipboard(): boolean { return typeof navigator !== 'undefined' && typeof navigator.clipboard?.read === 'function'; }