fix: Prevent terminal disconnects when browser tab loses focus (#7538)

This commit is contained in:
Andras Bacsai
2025-12-08 20:48:53 +01:00
committed by GitHub
2 changed files with 42 additions and 14 deletions

View File

@@ -11,20 +11,6 @@ class Terminal extends Component
{ {
public bool $hasShell = true; 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 private function checkShellAvailability(Server $server, string $container): bool
{ {
$escapedContainer = escapeshellarg($container); $escapedContainer = escapeshellarg($container);

View File

@@ -33,6 +33,9 @@ export function initializeTerminalComponent() {
// Resize handling // Resize handling
resizeObserver: null, resizeObserver: null,
resizeTimeout: null, resizeTimeout: null,
// Visibility handling - prevent disconnects when tab loses focus
isDocumentVisible: true,
wasConnectedBeforeHidden: false,
init() { init() {
this.setupTerminal(); this.setupTerminal();
@@ -92,6 +95,11 @@ export function initializeTerminalComponent() {
}, { once: true }); }, { once: true });
}); });
// Handle visibility changes to prevent disconnects when tab loses focus
document.addEventListener('visibilitychange', () => {
this.handleVisibilityChange();
});
window.onresize = () => { window.onresize = () => {
this.resizeTerminal() this.resizeTerminal()
}; };
@@ -451,6 +459,11 @@ export function initializeTerminalComponent() {
}, },
keepAlive() { keepAlive() {
// Skip keepalive when document is hidden to prevent unnecessary disconnects
if (!this.isDocumentVisible) {
return;
}
if (this.socket && this.socket.readyState === WebSocket.OPEN) { if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.sendMessage({ ping: true }); this.sendMessage({ ping: true });
} else if (this.connectionState === 'disconnected') { } 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() { resetPingTimeout() {
if (this.pingTimeoutId) { if (this.pingTimeoutId) {
clearTimeout(this.pingTimeoutId); clearTimeout(this.pingTimeoutId);