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
commit b3289aff71
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 42 additions and 14 deletions

View file

@ -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);

View file

@ -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);