feat(websocket): add ping/pong mechanism to maintain connection

- Add proxy timeouts in nginx.conf to handle long connections
- Implement ping/pong handling in server.js WebSocket listener
- Refactor App.js to class component with lifecycle methods and ping interval for WebSocket keep-alive
This commit is contained in:
sebseb7
2025-09-02 08:45:18 +00:00
parent 9cfbe4c0f7
commit a8ca291555
3 changed files with 58 additions and 27 deletions

View File

@@ -21,4 +21,6 @@
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 86400;
proxy_send_timeout 86400;
} }

View File

@@ -33,7 +33,13 @@ wss.on('connection', (ws) => {
console.log('Client connected'); console.log('Client connected');
ws.on('message', (message) => { ws.on('message', (message) => {
console.log('Received:', Buffer.from(message).toString()); const msg = Buffer.from(message).toString();
if (msg === '{"type":"ping"}') {
// Respond to ping to keep connection alive
ws.send(JSON.stringify({ type: 'pong' }));
} else {
console.log('Received:', msg);
}
}); });
ws.on('error', (error) => { ws.on('error', (error) => {

View File

@@ -1,20 +1,26 @@
import React, { useState, useEffect } from 'react'; import React from 'react';
function App() { class App extends React.Component {
const [counter, setCounter] = useState(0); constructor(props) {
const [ws, setWs] = useState(null); super(props);
this.state = {
counter: 0,
ws: null,
};
this.pingInterval = null;
}
useEffect(() => { componentDidMount() {
// Fetch initial counter value // Fetch initial counter value
fetch('/api/counter') fetch('/api/counter')
.then(response => response.json()) .then(response => response.json())
.then(data => setCounter(data.value)) .then(data => this.setState({ counter: data.value }))
.catch(error => console.error('Error fetching counter:', error)); .catch(error => console.error('Error fetching counter:', error));
// Connect to WebSocket - use dynamic URL based on current location // Connect to WebSocket - use dynamic URL based on current location
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const websocket = new WebSocket(`${protocol}//${window.location.host}/counter-ws`); const websocket = new WebSocket(`${protocol}//${window.location.host}/counter-ws`);
setWs(websocket); this.setState({ ws: websocket });
websocket.onopen = () => { websocket.onopen = () => {
console.log('WebSocket connected'); console.log('WebSocket connected');
@@ -27,7 +33,10 @@ function App() {
websocket.onmessage = (event) => { websocket.onmessage = (event) => {
const data = JSON.parse(event.data); const data = JSON.parse(event.data);
if (data.type === 'counter-update') { if (data.type === 'counter-update') {
setCounter(data.value); this.setState({ counter: data.value });
} else if (data.type === 'pong') {
// Pong received, connection is alive
console.log('Pong received');
} }
}; };
@@ -35,36 +44,50 @@ function App() {
console.log('WebSocket disconnected', 'Code:', event.code, 'Reason:', event.reason); console.log('WebSocket disconnected', 'Code:', event.code, 'Reason:', event.reason);
}; };
return () => { // Send ping every 30 seconds to keep connection alive
websocket.close(); this.pingInterval = setInterval(() => {
}; if (websocket.readyState === WebSocket.OPEN) {
}, []); websocket.send(JSON.stringify({ type: 'ping' }));
}
}, 30000);
}
const incrementCounter = () => { componentWillUnmount() {
if (this.pingInterval) {
clearInterval(this.pingInterval);
}
if (this.state.ws) {
this.state.ws.close();
}
}
incrementCounter = () => {
fetch('/api/counter/increment', { fetch('/api/counter/increment', {
method: 'POST', method: 'POST',
}) })
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
setCounter(data.value); this.setState({ counter: data.value });
// Broadcast update via WebSocket // Broadcast update via WebSocket
if (ws && ws.readyState === WebSocket.OPEN) { if (this.state.ws && this.state.ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'counter-update', value: data.value })); this.state.ws.send(JSON.stringify({ type: 'counter-update', value: data.value }));
} }
}) })
.catch(error => console.error('Error incrementing counter:', error)); .catch(error => console.error('Error incrementing counter:', error));
}; };
return ( render() {
<div style={{ textAlign: 'center', marginTop: '50px' }}> return (
<h1>Shared Counter Demo</h1> <div style={{ textAlign: 'center', marginTop: '50px' }}>
<h2>Counter: {counter}</h2> <h1>Shared Counter Demo</h1>
<button onClick={incrementCounter} style={{ fontSize: '20px', padding: '10px 20px' }}> <h2>Counter: {this.state.counter}</h2>
Increment <button onClick={this.incrementCounter} style={{ fontSize: '20px', padding: '10px 20px' }}>
</button> Increment
<p>Open this page in multiple tabs to see real-time syncing!</p> </button>
</div> <p>Open this page in multiple tabs to see real-time syncing!</p>
); </div>
);
}
} }
export default App; export default App;