···11import * as React from 'react';
22import { CheckCircleOutlined, LinkOutlined } from '@ant-design/icons';
33import { message, Button } from 'antd';
44+import { copyText } from '../../lib/copyText';
4556interface CopyLinkButtonProps {
67 link: string;
···2526 }, [showCopySuccess]);
26272728 const handleCopyLink = async () => {
2828- await navigator.clipboard.writeText(link);
2929+ try {
3030+ await copyText(link);
3131+ } catch (err) {
3232+ message.error(`Error copying text to clipboard: ${err}`);
3333+ return;
3434+ }
2935 message.success('Link copied to clipboard', 1);
3036 setShowCopySuccess(true);
3137 };
+33
osprey_ui/src/lib/copyText.ts
···11+// the clipboard api is not available when using a non-secure host (aside from localhost)
22+// this can be pretty common for some setups that are running i.e. on tailscale, so ideally
33+// we don't want to break things when a user tries to copy text somewhere. here we use a
44+// fallback when either that api is not available or `secureContext` is false
55+// see: https://developer.mozilla.org/en-US/docs/Web/API/Navigator/clipboard
66+export async function copyText(text: string): Promise<void> {
77+ if (navigator.clipboard && window.isSecureContext) {
88+ return navigator.clipboard.writeText(text);
99+ } else {
1010+ // create a textarea that gets appended to the body, then focus+copy text
1111+ const body = document.body;
1212+ const textarea = document.createElement('textarea');
1313+ textarea.setAttribute('aria-hidden', 'true');
1414+ body.appendChild(textarea);
1515+ textarea.value = text;
1616+ textarea.focus();
1717+ textarea.select();
1818+1919+ // NOTE: execCommand is actually deprecated, so this might break in future browser versions
2020+ // unfortunately. we'll log an error in the event that things do fail.
2121+ // see: https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand
2222+ try {
2323+ document.execCommand('copy');
2424+ } catch (err) {
2525+ console.error(`copyText: fallback failed: ${err}`);
2626+ // still throw the error so that calling code can optionally alert the user that the copy
2727+ // failed
2828+ throw err;
2929+ } finally {
3030+ textarea.remove();
3131+ }
3232+ }
3333+}