fix(frontend): copy to clipboard fails over HTTP (non-secure context)#9242
Conversation
The browser Clipboard API (navigator.clipboard) is only available in
secure contexts (HTTPS or localhost). When Infrahub is accessed over
plain HTTP, clipboard writes silently failed because the code attempted
the async API without checking context security first.
Use document.createRange() + window.getSelection() + execCommand("copy")
as a synchronous fallback when the secure context or Clipboard API is
unavailable. This avoids the React Aria focus-trap issue that broke the
previous textarea-based fallback (the textarea could not receive focus
while a Popover was open).
Fixes opsmill#8857
|
Tested manually on HTTP (non-localhost IP). Copy ID, Copy HFID, and Copy Token all work correctly after the fix. |
|
Hi! This fix for the clipboard issue over HTTP has been tested manually and is ready for review — could someone take a look when you have a moment? Thanks! |
pa-lem
left a comment
There was a problem hiding this comment.
Thanks for the fix, the secure-context detection and selection-based fallback are the right approach. One small follow-up to keep the public API consistent:
The hook signature changed from async (value) => Promise<void> to (value) => void. That breaks the contract for
frontend/app/src/shared/components/buttons/clipboard.tsx:21-24, which still does:
const handleCopy = async () => {
await copyToClipboard(value); // now awaits a non-Promise → resolves immediately
toast(<Alert message={alert} type={ALERT_TYPES.INFO} />);
};The toast now fires before the async navigator.clipboard.writeText() settles, so on the secure-context path the success toast can show even if the write rejects.
Suggested change would be that we keep the hook awaitable:
const copyToClipboard = React.useCallback(async (value: string) => {
function confirmCopied() {
setIsCopied(true);
setTimeout(() => setIsCopied(false), COPIED_FEEDBACK_DURATION);
}
if (!window.isSecureContext || !navigator.clipboard) {
oldSchoolCopy(value);
confirmCopied();
return;
}
try {
await navigator.clipboard.writeText(value);
confirmCopied();
} catch {
oldSchoolCopy(value);
confirmCopied();
}
}, []);This preserves the original Promise return type, so await copyToClipboard(...) in clipboard.tsx keeps its original ordering guarantee, and the other four
call sites (which don't await) remain unaffected.
Nice-to-have: a small unit test for useCopyToClipboard covering both paths (mock window.isSecureContext = false and navigator.clipboard = undefined) would
protect this dual-path logic from future regressions.
- Make copyToClipboard async (returns Promise<void>) so callers that await it (e.g. clipboard.tsx) get correct ordering guarantees - Rewrite promise chain as try/await/catch for clarity - Add unit tests covering secure-context, non-secure context, unavailable clipboard, and rejected writeText paths Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
There was a problem hiding this comment.
Thx for the review @pa-lem. Made the fix with Claude Code, restored the async contract and added unit tests covering both paths. Then, I manually verified the fix by booting up a dev instance and testing on Firefox myself.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Summary
navigator.clipboardis unavailable in non-secure contexts (HTTP, non-localhost); previous code attempted it unconditionally, causing silent failures.window.isSecureContextguard: when the Clipboard API is unavailable, a synchronousdocument.createRange()+window.getSelection()+execCommand("copy")fallback is used instead.textarea-based fallback, which was broken by React Aria's Popover focus trap — the textarea could not receive focus while the menu was open, soexecCommand("copy")had no selection to act on.Fixes #8857
Test plan
Summary by cubic
Fixes copy actions over HTTP so users can copy IDs and tokens in non-secure contexts, without breaking HTTPS behavior. Restores the async contract of
useCopyToClipboardto preserve await ordering. Fixes #8857.window.isSecureContextandnavigator.clipboard; fall back to selection-baseddocument.createRange()+window.getSelection()+document.execCommand("copy")compatible with the React Aria popover focus trap.copyToClipboardasync; add tests for secure/non-secure, missing clipboard, and rejected writes; fix Biome lint errors in the tests.Written for commit 399e428. Summary will update on new commits. Review in cubic