WebSocket Real-time Communication
For scenarios requiring high real-time performance (such as message notifications, real-time monitoring data), we use WebSocket for full-duplex communication.
1. Native WebSocket Encapsulation
A simple generic class encapsulation to handle connection, heartbeat, and reconnection mechanisms.
// src/api/core/socket.ts
export class SocketClient {
private url: string;
private ws: WebSocket | null = null;
private reconnectAttempts = 0;
private maxReconnectAttempts = 5;
private heartbeatInterval: any = null;
constructor(url: string) {
this.url = url;
}
public connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
console.log('WebSocket Connected');
this.reconnectAttempts = 0;
this.startHeartbeat();
};
this.ws.onmessage = (event) => {
this.handleMessage(event.data);
};
this.ws.onclose = () => {
console.log('WebSocket Closed');
this.stopHeartbeat();
this.reconnect();
};
this.ws.onerror = (error) => {
console.error('WebSocket Error', error);
};
}
private handleMessage(data: string) {
try {
const payload = JSON.parse(data);
// Dispatch events or update Store based on payload
console.log('Received:', payload);
} catch (e) {
console.error('Parse error', e);
}
}
private reconnect() {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
setTimeout(() => {
console.log(`Reconnecting... (${this.reconnectAttempts})`);
this.connect();
}, 3000);
}
}
private startHeartbeat() {
this.heartbeatInterval = setInterval(() => {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({ type: 'ping' }));
}
}, 30000);
}
private stopHeartbeat() {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
this.heartbeatInterval = null;
}
}
public send(data: any) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(data));
} else {
console.warn('WebSocket is not open');
}
}
public close() {
if (this.ws) {
this.ws.close();
}
}
}
2. Integration with SignalR (Optional)
If the backend uses .NET SignalR, it is recommended to use the @microsoft/signalr library.
import * as signalR from '@microsoft/signalr';
const connection = new signalR.HubConnectionBuilder()
.withUrl("/api/hub")
.withAutomaticReconnect()
.build();
async function start() {
try {
await connection.start();
console.log("SignalR Connected.");
} catch (err) {
console.log(err);
setTimeout(start, 5000);
}
}
connection.on("ReceiveMessage", (user, message) => {
console.log(`${user} says ${message}`);
});
start();
3. Usage in Projects
Usually, a WebSocket connection is initialized globally when the application starts (e.g., in App.vue or layout), or connected/disconnected on demand within specific pages.
// store/modules/socket.ts
import { defineStore } from 'pinia';
import { SocketClient } from '@/api/core/socket';
export const useSocketStore = defineStore('socket', () => {
let client: SocketClient | null = null;
function init() {
const url = import.meta.env.VITE_WS_URL;
client = new SocketClient(url);
client.connect();
}
function sendMessage(msg: any) {
client?.send(msg);
}
return { init, sendMessage };
});