fix(components): use ClipboardItem API for Safari-compatible clipboard writes#3379
fix(components): use ClipboardItem API for Safari-compatible clipboard writes#3379djchrisssssss wants to merge 2 commits into
Conversation
…d writes Safari requires clipboard writes to occur synchronously within the user-gesture call stack. The previous async/await pattern with fetch() broke this chain, causing NotAllowedError in Safari when copying export content that requires fetching from a URL. Replace async handleClick with synchronous ClipboardItem + Promise pattern. ClipboardItem accepts a Promise value, keeping the clipboard.write() call in the user-gesture context while resolving data internally. Includes a fallback to writeText() for browsers where ClipboardItem is not available. Closes inveniosoftware#3378 Refs zenodo/zenodo-rdm#1266
Samk13
left a comment
There was a problem hiding this comment.
Thanks for this, the Safari issue and the use of ClipboardItem look correct to me.
One suggestion: you can simplify the control flow slightly by resolving the source upfront, which removes the duplication between the two clipboard paths:
handleClick = () => {
const { url, text, onCopy } = this.props;
// Resolve what to copy (URL fetch or direct text)
const dataPromise = url
? fetch(url).then((r) => r.text())
: Promise.resolve(text);
if (typeof ClipboardItem !== "undefined") {
// Safari-safe: keep clipboard.write() synchronous
const item = new ClipboardItem({
"text/plain": dataPromise.then(
(t) => new Blob([t], { type: "text/plain" })
),
});
navigator.clipboard.write([item]).then(() => onCopy(text));
} else {
// Fallback for browsers without ClipboardItem
dataPromise.then((t) =>
navigator.clipboard.writeText(t).then(() => onCopy(text))
);
}
};WDYT?
Address review feedback from @Samk13: unify the url-fetch and direct-text paths into a single dataPromise, removing the outer if/else duplication. Also fix prettier formatting for the ClipboardItem callback.
djchrisssssss
left a comment
There was a problem hiding this comment.
Thanks @Samk13! Great suggestion — adopted the unified dataPromise approach in the follow-up commit. Changes:
- Unified
dataPromise: Merged theurlfetch and directtextpaths into a single Promise, removing the outerif/elseduplication — exactly as you suggested. - Removed dead
fetchUrl()method: It was no longer referenced after the initial commit. - Prettier fix: Collapsed the
ClipboardItemcallback to satisfy the project's prettier config. - Linter pass: Ran
run-js-linter.sh— CopyButton.js has zero errors/warnings.
Note: I used a follow-up commit (rather than amend) so the incremental diff is easier to review. Happy to squash before merge if preferred.
|
@djchrisssssss Could you confirm this contribution aligns with the InvenioRDM copyright policy regarding ownership and attribution? |
8bc43ef to
3e3a1bf
Compare
|
Yes — this contribution was developed by me personally, outside any employment context, and I have the right to license it under the repository's MIT license. I've also updated the commit metadata accordingly. |
|
Hi @Samk13, just checking in — is there anything else needed from my side for this PR? I've adopted your |
|
Hi @kpsherva, would you be able to take a look at this PR when you get a chance? It's a small fix for a Safari clipboard bug introduced in #3106 — the The fix uses |
|
This PR was automatically marked as stale. |
Summary
CopyButtonexport copy failing in Safari withNotAllowedErrorasync/awaitclipboard pattern with synchronousClipboardItem+ Promise APIwriteText()for browsers withoutClipboardItemsupportProblem
PR #3106 replaced
react-copy-to-clipboardwith directnavigator.clipboard.writeText()calls. When theurlprop is provided (Export section),await fetch(url)suspends execution and breaks the user-gesture call stack. Safari's Clipboard API requiresclipboard.write()to be called synchronously within the user gesture context, so it rejects the write with:DOI/Citation copy buttons are unaffected because they pass
textdirectly (no fetch needed).Solution
Use the
ClipboardItemAPI which accepts a Promise as its value. This keepsnavigator.clipboard.write()synchronous within the user gesture, while the Clipboard API internally resolves the fetch Promise:A fallback using
writeText()is included for browsers whereClipboardItemis not available (e.g., older Firefox).Reference: How to use Clipboard API in Safari
Browser Compatibility
ClipboardItemwith Promiseasync/awaitpatternTest Plan
Closes #3378
Refs zenodo/zenodo-rdm#1266
🤖 Generated with Claude Code