Javascript
Copy to Clipboard async using Clipboard API
Copy to Clipboard async (think from a Promise like fetch returns) using Clipboard API, working in Chrome, Safari and Firefox.
Wolfgang Rittner
updated June 30, 2024 · 3 min read
It's complicated, sort of
I wanted to fetch
some text from the server and copy that to the Clipboard for my Single-File Ruby Templates app.
While Chrome doesn't really care what and when you copy into the User's Clipboard, Safari locks the Clipboard API down to only work when triggered by a direct user interaction. That sounds sensible, but makes it a bit tricky to use if you'd like copy something into the Clipboard from a Promise like the one fetch
returns.
When I finally found out how to use Clipboard API in Safari even with async functions, I tested my solution in Firefox to make sure it worked there, too.
You'll never believe what happened next! It didn't.
After examining the error on the console, checking browser support on caniuse again, and reading through the documentation and details on browser support on mdn, I sighed:
Firefox has working support for the Clipboard API, but the ClipboardItem
class and the navigator.clipboard.write
function I used previously for making things work in Safari is behind the dom.events.asyncClipboard.clipboardItem
preference on Firefox, which is turned off by default. You'd need to turn that on in about:config
manually.
The only thing currently available in Firefox without changing preferences is navigator.clipboard.writeText
. And that function only takes a string as an argument, so it can't be handed a Promise like navigator.clipboard.write
, which was necessary to satisfy Safari's security constraints.
Checking for support
Sadly I was not able to find a solution to my initial problem - fetch
something and copy the response into the clipboard - that worked across Chrome, Safari and Firefox. I ended up with introducing a conditional checking for ClipboardItem
support. When satisfied, which will be the case on Chrome and Safari for now, we are going to use ClipboardItem
and the navigator.clipboard.write
function. For browsers like Firefox that do not support those APIs yet, it falls back to crossing fingers that the browser is not locked down to only support sync access to the clipboard and using writeText
instead:
if (typeof ClipboardItem && navigator.clipboard.write) {
// NOTE: Safari locks down the clipboard API to only work when triggered
// by a direct user interaction. You can't use it async in a promise.
// But! You can wrap the promise in a ClipboardItem, and give that to
// the clipboard API.
// Found this on https://developer.apple.com/forums/thread/691873
const text = new ClipboardItem({
"text/plain": fetch(someUrl)
.then((response) => response.text())
.then((text) => new Blob([text], { type: "text/plain" }))
})
navigator.clipboard.write([text])
} else {
// NOTE: Firefox has support for ClipboardItem and navigator.clipboard.write,
// but those are behind `dom.events.asyncClipboard.clipboardItem` preference.
// Good news is that other than Safari, Firefox does not care about
// Clipboard API being used async in a Promise.
fetch(someUrl)
.then((response) => response.text())
.then((text) => navigator.clipboard.writeText(text))
}
Hooray
At last, copying text fetched from a URL works across Chrome, Safari and Firefox. And all it took was an if
statement like it's 2004.