hyp / apps /ui /src /lib /clipboard-utils.ts
Leon4gr45's picture
Upload folder using huggingface_hub
1dbc34b verified
/**
* Clipboard utility functions with fallbacks for non-HTTPS (insecure) contexts.
*
* The modern Clipboard API (`navigator.clipboard`) requires a Secure Context (HTTPS).
* When running on HTTP, these APIs are unavailable or throw errors.
* This module provides `writeToClipboard` and `readFromClipboard` that automatically
* fall back to the legacy `document.execCommand` approach using a hidden textarea.
*/
/**
* Check whether the modern Clipboard API is available.
* It requires a secure context (HTTPS or localhost) and the API to exist.
*/
function isClipboardApiAvailable(): boolean {
return (
typeof navigator !== 'undefined' &&
!!navigator.clipboard &&
typeof navigator.clipboard.writeText === 'function' &&
typeof navigator.clipboard.readText === 'function' &&
typeof window !== 'undefined' &&
window.isSecureContext !== false
);
}
/**
* Write text to the clipboard using the modern Clipboard API with a
* fallback to `document.execCommand('copy')` for insecure contexts.
*
* @param text - The text to write to the clipboard.
* @returns `true` if the text was successfully copied; `false` otherwise.
*/
export async function writeToClipboard(text: string): Promise<boolean> {
// Try the modern Clipboard API first
if (isClipboardApiAvailable()) {
try {
await navigator.clipboard.writeText(text);
return true;
} catch {
// Fall through to legacy approach
}
}
// Legacy fallback using a hidden textarea + execCommand
return writeToClipboardLegacy(text);
}
/**
* Read text from the clipboard using the modern Clipboard API with a
* fallback to `document.execCommand('paste')` for insecure contexts.
*
* Note: The legacy fallback for *reading* is limited. `document.execCommand('paste')`
* only works in some browsers (mainly older ones). On modern browsers in insecure
* contexts, reading from the clipboard may not be possible at all. In those cases,
* this function throws an error so the caller can show an appropriate message.
*
* @returns The text from the clipboard.
* @throws If clipboard reading is not supported or permission is denied.
*/
export async function readFromClipboard(): Promise<string> {
// Try the modern Clipboard API first
if (isClipboardApiAvailable()) {
try {
return await navigator.clipboard.readText();
} catch (err) {
// Check if this is a permission-related error
if (err instanceof Error) {
// Re-throw permission errors so they propagate to the caller
if (err.name === 'NotAllowedError' || err.name === 'NotReadableError') {
throw err;
}
}
// For other errors, fall through to legacy approach
}
}
// Legacy fallback using a hidden textarea + execCommand
return readFromClipboardLegacy();
}
/**
* Legacy clipboard write using a hidden textarea and `document.execCommand('copy')`.
* This works in both secure and insecure contexts in most browsers.
*/
function writeToClipboardLegacy(text: string): boolean {
const textarea = document.createElement('textarea');
textarea.value = text;
// Prevent scrolling and make invisible
textarea.style.position = 'fixed';
textarea.style.left = '-9999px';
textarea.style.top = '-9999px';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
try {
textarea.select();
textarea.setSelectionRange(0, text.length);
const success = document.execCommand('copy');
return success;
} catch {
return false;
} finally {
document.body.removeChild(textarea);
}
}
/**
* Legacy clipboard read using a hidden textarea and `document.execCommand('paste')`.
* This has very limited browser support. Most modern browsers block this for security.
* When it fails, we throw an error to let the caller handle it gracefully.
*/
function readFromClipboardLegacy(): string {
const textarea = document.createElement('textarea');
// Prevent scrolling and make invisible
textarea.style.position = 'fixed';
textarea.style.left = '-9999px';
textarea.style.top = '-9999px';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.focus();
try {
const success = document.execCommand('paste');
if (success && textarea.value) {
return textarea.value;
}
throw new Error(
'Clipboard paste is not supported in this browser on non-HTTPS sites. ' +
'Please use HTTPS or paste manually with keyboard shortcuts.'
);
} catch (err) {
if (err instanceof Error && err.message.includes('Clipboard paste is not supported')) {
throw err;
}
throw new Error(
'Clipboard paste is not supported in this browser on non-HTTPS sites. ' +
'Please use HTTPS or paste manually with keyboard shortcuts.'
);
} finally {
document.body.removeChild(textarea);
}
}