diff --git a/src/apps/ssh/Terminal.jsx b/src/apps/ssh/Terminal.jsx index 89f177ca..d674f0d3 100644 --- a/src/apps/ssh/Terminal.jsx +++ b/src/apps/ssh/Terminal.jsx @@ -147,10 +147,17 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidde const getClipboardText = async () => { try { + // Modern Clipboard API - this will work in secure contexts (HTTPS or localhost) if (navigator.clipboard && navigator.clipboard.readText) { - return await navigator.clipboard.readText(); + try { + return await navigator.clipboard.readText(); + } catch (clipboardErr) { + console.warn("Navigator clipboard API failed:", clipboardErr); + // Continue to fallback methods + } } + // Fallback method using document.execCommand if (document.queryCommandSupported && document.queryCommandSupported('paste')) { const textarea = document.createElement('textarea'); textarea.style.position = 'fixed'; @@ -162,12 +169,48 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidde document.execCommand('paste'); const text = textarea.value; document.body.removeChild(textarea); - return text; - } catch (e) { + if (text) return text; + } catch (execErr) { document.body.removeChild(textarea); - throw new Error('Fallback clipboard paste failed'); + console.warn("execCommand paste failed:", execErr); + // Continue to next fallback } } + + // Fallback UI prompt for non-secure contexts where clipboard API is restricted + if (!window.location.hostname.includes('localhost') && + !window.location.protocol.includes('https')) { + + // Display input prompt in the terminal itself + terminalInstance.current.write("\r\n\r\nPaste access denied. Please type or paste content here:\r\n"); + + // Use a terminal-based input method + return new Promise(resolve => { + let inputText = ''; + const dataHandler = terminalInstance.current.onData(data => { + // Check for enter key (carriage return) + if (data === '\r') { + terminalInstance.current.write('\r\n'); + dataHandler.dispose(); // Remove the handler + resolve(inputText); + return; + } + + // Handle backspace + if (data === '\x7f') { + if (inputText.length > 0) { + inputText = inputText.slice(0, -1); + terminalInstance.current.write('\b \b'); // Erase the character + } + return; + } + + // Normal character input + inputText += data; + terminalInstance.current.write(data); + }); + }); + } throw new Error('No clipboard access methods available'); } catch (err) { @@ -176,16 +219,23 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidde } }; + // Track if paste is in progress to prevent double paste + let pasteInProgress = false; + terminalInstance.current.attachCustomKeyEventHandler(async (event) => { if ((event.ctrlKey || event.metaKey) && event.key === "v") { event.preventDefault(); - if (!socketRef.current) return false; - + // Prevent double paste execution + if (pasteInProgress || !socketRef.current) return false; + + pasteInProgress = true; + try { const text = await getClipboardText(); if (!text) { terminalInstance.current.write("\r\nClipboard access denied or empty\r\n"); + pasteInProgress = false; return false; } @@ -211,6 +261,11 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidde } catch (err) { console.error("Failed to process clipboard contents:", err); } + + // Set timeout to reset paste lock + setTimeout(() => { + pasteInProgress = false; + }, 100); return false; }