/** * Robust clipboard utility with multiple fallback strategies * Handles various browser environments and security contexts */ export interface CopyResult { success: boolean; method: 'clipboard-api' | 'execCommand' | 'manual-select' | 'fallback'; error?: string; } /** * Copy text to clipboard with multiple fallback strategies * @param text - Text to copy to clipboard * @returns Promise - Result object with success status and method used */ export async function copyToClipboard(text: string): Promise { if (!text || text.trim() === '') { return { success: false, method: 'fallback', error: 'No text provided' }; } // Strategy 1: Modern Clipboard API (preferred) if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') { try { await navigator.clipboard.writeText(text); return { success: true, method: 'clipboard-api' }; } catch (error) { console.warn('Clipboard API failed:', error); // Continue to fallback methods } } // Strategy 2: Legacy execCommand (for older browsers) try { const result = await copyWithExecCommand(text); if (result.success) { return result; } } catch (error) { console.warn('execCommand failed:', error); // Continue to fallback methods } // Strategy 3: Manual text selection (most compatible) try { const result = await copyWithManualSelection(text); if (result.success) { return result; } } catch (error) { console.warn('Manual selection failed:', error); } // Strategy 4: Complete fallback - return error return { success: false, method: 'fallback', error: 'All copy methods failed. Please copy the text manually.' }; } /** * Copy using legacy execCommand method */ async function copyWithExecCommand(text: string): Promise { return new Promise((resolve) => { const textarea = document.createElement('textarea'); textarea.value = text; textarea.style.position = 'fixed'; textarea.style.left = '-9999px'; textarea.style.top = '-9999px'; textarea.style.opacity = '0'; textarea.setAttribute('readonly', ''); document.body.appendChild(textarea); try { textarea.select(); textarea.setSelectionRange(0, text.length); const successful = document.execCommand('copy'); if (successful) { resolve({ success: true, method: 'execCommand' }); } else { resolve({ success: false, method: 'execCommand', error: 'execCommand returned false' }); } } catch (error) { resolve({ success: false, method: 'execCommand', error: error instanceof Error ? error.message : 'execCommand failed' }); } finally { document.body.removeChild(textarea); } }); } /** * Copy using manual text selection method */ async function copyWithManualSelection(text: string): Promise { return new Promise((resolve) => { const textarea = document.createElement('textarea'); textarea.value = text; textarea.style.position = 'absolute'; textarea.style.left = '-9999px'; textarea.style.top = '-9999px'; textarea.style.opacity = '0'; textarea.style.pointerEvents = 'none'; textarea.setAttribute('readonly', ''); textarea.setAttribute('tabindex', '-1'); document.body.appendChild(textarea); try { // Focus and select the text textarea.focus(); textarea.select(); textarea.setSelectionRange(0, text.length); // Try to trigger copy event const copyEvent = new ClipboardEvent('copy', { clipboardData: new DataTransfer() }); if (copyEvent.clipboardData) { copyEvent.clipboardData.setData('text/plain', text); document.dispatchEvent(copyEvent); resolve({ success: true, method: 'manual-select' }); } else { // Fallback: keep text selected for manual copy resolve({ success: false, method: 'manual-select', error: 'Manual selection prepared, but automatic copy failed' }); } } catch (error) { resolve({ success: false, method: 'manual-select', error: error instanceof Error ? error.message : 'Manual selection failed' }); } finally { // Clean up after a short delay to allow copy operation setTimeout(() => { if (document.body.contains(textarea)) { document.body.removeChild(textarea); } }, 100); } }); } /** * Check if clipboard functionality is available */ export function isClipboardSupported(): boolean { return !!( (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') || typeof document !== 'undefined' ); } /** * Get the best available clipboard method */ export function getBestClipboardMethod(): 'clipboard-api' | 'execCommand' | 'manual-select' | 'none' { if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') { return 'clipboard-api'; } if (typeof document !== 'undefined') { return 'execCommand'; } return 'none'; }