diff --git a/app/Livewire/Project/Shared/Terminal.php b/app/Livewire/Project/Shared/Terminal.php index de2deeed4..3c2abc84c 100644 --- a/app/Livewire/Project/Shared/Terminal.php +++ b/app/Livewire/Project/Shared/Terminal.php @@ -11,20 +11,6 @@ class Terminal extends Component { public bool $hasShell = true; - public function getListeners() - { - $teamId = auth()->user()->currentTeam()->id; - - return [ - "echo-private:team.{$teamId},ApplicationStatusChanged" => 'closeTerminal', - ]; - } - - public function closeTerminal() - { - $this->dispatch('reloadWindow'); - } - private function checkShellAvailability(Server $server, string $container): bool { $escapedContainer = escapeshellarg($container); diff --git a/resources/js/terminal.js b/resources/js/terminal.js index b49aad9cf..6707bec98 100644 --- a/resources/js/terminal.js +++ b/resources/js/terminal.js @@ -33,6 +33,9 @@ export function initializeTerminalComponent() { // Resize handling resizeObserver: null, resizeTimeout: null, + // Visibility handling - prevent disconnects when tab loses focus + isDocumentVisible: true, + wasConnectedBeforeHidden: false, init() { this.setupTerminal(); @@ -92,6 +95,11 @@ export function initializeTerminalComponent() { }, { once: true }); }); + // Handle visibility changes to prevent disconnects when tab loses focus + document.addEventListener('visibilitychange', () => { + this.handleVisibilityChange(); + }); + window.onresize = () => { this.resizeTerminal() }; @@ -451,6 +459,11 @@ export function initializeTerminalComponent() { }, keepAlive() { + // Skip keepalive when document is hidden to prevent unnecessary disconnects + if (!this.isDocumentVisible) { + return; + } + if (this.socket && this.socket.readyState === WebSocket.OPEN) { this.sendMessage({ ping: true }); } else if (this.connectionState === 'disconnected') { @@ -459,6 +472,35 @@ export function initializeTerminalComponent() { } }, + handleVisibilityChange() { + const wasVisible = this.isDocumentVisible; + this.isDocumentVisible = !document.hidden; + + if (!this.isDocumentVisible) { + // Tab is now hidden - pause heartbeat monitoring to prevent false disconnects + this.wasConnectedBeforeHidden = this.connectionState === 'connected'; + if (this.pingTimeoutId) { + clearTimeout(this.pingTimeoutId); + this.pingTimeoutId = null; + } + console.log('[Terminal] Tab hidden, pausing heartbeat monitoring'); + } else if (wasVisible === false) { + // Tab is now visible again + console.log('[Terminal] Tab visible, resuming connection management'); + + if (this.wasConnectedBeforeHidden && this.socket && this.socket.readyState === WebSocket.OPEN) { + // Send immediate ping to verify connection is still alive + this.heartbeatMissed = 0; + this.sendMessage({ ping: true }); + this.resetPingTimeout(); + } else if (this.wasConnectedBeforeHidden && this.connectionState !== 'connected') { + // Was connected before but now disconnected - attempt reconnection + this.reconnectAttempts = 0; + this.initializeWebSocket(); + } + } + }, + resetPingTimeout() { if (this.pingTimeoutId) { clearTimeout(this.pingTimeoutId);