🎯 Exemples recommandés
Balanced sample collections from various categories for you to explore
Exemples WebSocket
Exemples de communication en temps réel utilisant le protocole WebSocket incluant chat, notifications et streaming de données
💻 Client WebSocket JavaScript javascript
🟢 simple
⭐⭐
Implémentation complète de client WebSocket avec reconnexion, heartbeat et gestion des messages
⏱️ 20 min
🏷️ websocket, javascript, real-time, client
Prerequisites:
JavaScript basics, WebSocket protocol
// WebSocket Client Implementation with Reconnection and Heartbeat
class WebSocketClient {
constructor(url, options = {}) {
this.url = url;
this.options = {
reconnectInterval: 3000,
maxReconnectAttempts: 5,
heartbeatInterval: 30000,
heartbeatTimeout: 5000,
debug: false,
...options
};
this.ws = null;
this.reconnectAttempts = 0;
this.heartbeatTimer = null;
this.heartbeatTimeoutTimer = null;
this.messageQueue = [];
this.eventListeners = new Map();
this.isManualClose = false;
this.connect();
}
connect() {
try {
this.log('Connecting to WebSocket...');
this.ws = new WebSocket(this.url);
this.ws.onopen = (event) => {
this.log('WebSocket connected');
this.reconnectAttempts = 0;
this.startHeartbeat();
this.flushMessageQueue();
this.emit('open', event);
};
this.ws.onmessage = (event) => {
this.log('Message received:', event.data);
try {
const message = JSON.parse(event.data);
this.handleMessage(message);
} catch (error) {
this.log('Error parsing message:', error);
this.emit('error', new Error('Invalid JSON message'));
}
// Handle heartbeat response
if (event.data === 'pong') {
this.handleHeartbeatResponse();
}
this.emit('message', event);
};
this.ws.onerror = (event) => {
this.log('WebSocket error:', event);
this.emit('error', event);
};
this.ws.onclose = (event) => {
this.log('WebSocket closed:', event.code, event.reason);
this.stopHeartbeat();
this.emit('close', event);
if (!this.isManualClose && this.reconnectAttempts < this.options.maxReconnectAttempts) {
this.scheduleReconnect();
}
};
} catch (error) {
this.log('Connection error:', error);
this.emit('error', error);
this.scheduleReconnect();
}
}
disconnect() {
this.isManualClose = true;
this.stopHeartbeat();
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.close(1000, 'Manual disconnect');
}
}
send(data) {
const message = typeof data === 'string' ? data : JSON.stringify(data);
if (this.isConnected()) {
this.ws.send(message);
this.log('Message sent:', message);
} else {
this.log('WebSocket not connected, queuing message');
this.messageQueue.push(message);
}
}
isConnected() {
return this.ws && this.ws.readyState === WebSocket.OPEN;
}
on(event, callback) {
if (!this.eventListeners.has(event)) {
this.eventListeners.set(event, []);
}
this.eventListeners.get(event).push(callback);
}
off(event, callback) {
if (this.eventListeners.has(event)) {
const listeners = this.eventListeners.get(event);
const index = listeners.indexOf(callback);
if (index > -1) {
listeners.splice(index, 1);
}
}
}
emit(event, ...args) {
if (this.eventListeners.has(event)) {
this.eventListeners.get(event).forEach(callback => {
try {
callback(...args);
} catch (error) {
this.log('Error in event listener:', error);
}
});
}
}
// Heartbeat mechanism
startHeartbeat() {
this.heartbeatTimer = setInterval(() => {
if (this.isConnected()) {
this.ws.send('ping');
this.heartbeatTimeoutTimer = setTimeout(() => {
this.log('Heartbeat timeout, closing connection');
this.ws.close(1000, 'Heartbeat timeout');
}, this.options.heartbeatTimeout);
}
}, this.options.heartbeatInterval);
}
stopHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
if (this.heartbeatTimeoutTimer) {
clearTimeout(this.heartbeatTimeoutTimer);
this.heartbeatTimeoutTimer = null;
}
}
handleHeartbeatResponse() {
if (this.heartbeatTimeoutTimer) {
clearTimeout(this.heartbeatTimeoutTimer);
this.heartbeatTimeoutTimer = null;
}
}
// Reconnection logic
scheduleReconnect() {
this.reconnectAttempts++;
const delay = Math.min(
this.options.reconnectInterval * Math.pow(2, this.reconnectAttempts - 1),
30000
);
this.log(`Scheduling reconnect attempt ${this.reconnectAttempts} in ${delay}ms`);
setTimeout(() => {
this.connect();
}, delay);
}
// Message queue for offline messages
flushMessageQueue() {
while (this.messageQueue.length > 0 && this.isConnected()) {
const message = this.messageQueue.shift();
this.ws.send(message);
}
}
// Message handling with type routing
handleMessage(message) {
if (message.type && this.eventListeners.has(message.type)) {
this.emit(message.type, message.data);
}
}
log(...args) {
if (this.options.debug) {
console.log('[WebSocketClient]', ...args);
}
}
// Get connection state
getState() {
if (!this.ws) return 'DISCONNECTED';
switch (this.ws.readyState) {
case WebSocket.CONNECTING: return 'CONNECTING';
case WebSocket.OPEN: return 'CONNECTED';
case WebSocket.CLOSING: return 'CLOSING';
case WebSocket.CLOSED: return 'DISCONNECTED';
default: return 'UNKNOWN';
}
}
// Get statistics
getStats() {
return {
state: this.getState(),
reconnectAttempts: this.reconnectAttempts,
queuedMessages: this.messageQueue.length,
url: this.url
};
}
}
// Usage Examples
// Example 1: Basic WebSocket client
const basicClient = new WebSocketClient('wss://echo.websocket.org', {
debug: true,
reconnectInterval: 2000,
maxReconnectAttempts: 10
});
basicClient.on('open', () => {
console.log('Connected to echo server');
basicClient.send({
type: 'message',
data: 'Hello, WebSocket!'
});
});
basicClient.on('message', (event) => {
console.log('Received:', event.data);
});
basicClient.on('error', (error) => {
console.error('WebSocket error:', error);
});
// Example 2: Chat client
class ChatClient extends WebSocketClient {
constructor(url, username) {
super(url, { debug: true });
this.username = username;
this.on('open', () => {
this.send({
type: 'join',
data: { username: this.username }
});
});
this.on('chat_message', (data) => {
console.log(`${data.username}: ${data.message}`);
});
this.on('user_joined', (data) => {
console.log(`${data.username} joined the chat`);
});
this.on('user_left', (data) => {
console.log(`${data.username} left the chat`);
});
}
sendMessage(message) {
this.send({
type: 'chat_message',
data: {
username: this.username,
message: message,
timestamp: new Date().toISOString()
}
});
}
}
// Example 3: Real-time notifications client
class NotificationClient extends WebSocketClient {
constructor(url, userId) {
super(url, { debug: true });
this.userId = userId;
this.notifications = [];
this.on('open', () => {
this.send({
type: 'authenticate',
data: { userId: this.userId }
});
});
this.on('notification', (data) => {
this.notifications.push(data);
this.showNotification(data);
});
}
showNotification(notification) {
if (Notification.permission === 'granted') {
new Notification(notification.title, {
body: notification.body,
icon: notification.icon,
tag: notification.id
});
}
console.log('Notification:', notification);
}
getUnreadCount() {
return this.notifications.filter(n => !n.read).length;
}
markAsRead(notificationId) {
const notification = this.notifications.find(n => n.id === notificationId);
if (notification) {
notification.read = true;
}
}
}
// Request notification permission
if ('Notification' in window && Notification.permission === 'default') {
Notification.requestPermission();
}
// Export classes for use in other modules
if (typeof module !== 'undefined' && module.exports) {
module.exports = { WebSocketClient, ChatClient, NotificationClient };
}
💻 Serveur WebSocket Node.js javascript
🟡 intermediate
⭐⭐⭐⭐
Implémentation complète de serveur WebSocket avec Express, Socket.IO et gestion des salles
⏱️ 30 min
🏷️ websocket, nodejs, server, real-time
Prerequisites:
Node.js, Express, Socket.IO, JWT
// WebSocket Server Implementation with Socket.IO and Express
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const jwt = require('jsonwebtoken');
const rateLimit = require('express-rate-limit');
const app = express();
const server = http.createServer(app);
// Configure Socket.IO with CORS and options
const io = socketIo(server, {
cors: {
origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'],
methods: ['GET', 'POST'],
credentials: true
},
pingTimeout: 60000,
pingInterval: 25000
});
// Rate limiting for HTTP endpoints
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
app.use(limiter);
app.use(express.json());
// In-memory storage (replace with database in production)
const users = new Map();
const rooms = new Map();
const connectedSockets = new Map();
// JWT Secret
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
// Middleware for Socket.IO authentication
io.use(async (socket, next) => {
try {
const token = socket.handshake.auth.token || socket.handshake.headers.authorization?.replace('Bearer ', '');
if (!token) {
return next(new Error('Authentication token required'));
}
const decoded = jwt.verify(token, JWT_SECRET);
socket.userId = decoded.userId;
socket.username = decoded.username;
next();
} catch (error) {
next(new Error('Invalid authentication token'));
}
});
// Socket.IO connection handling
io.on('connection', (socket) => {
console.log(`User connected: ${socket.username} (${socket.userId})`);
// Store socket connection
connectedSockets.set(socket.userId, socket);
// Join user to their personal room
const personalRoom = `user_${socket.userId}`;
socket.join(personalRoom);
// Initialize user data
users.set(socket.userId, {
id: socket.userId,
username: socket.username,
socketId: socket.id,
connectedAt: new Date(),
rooms: [personalRoom],
status: 'online'
});
// Notify others about user coming online
socket.broadcast.emit('user_status_change', {
userId: socket.userId,
username: socket.username,
status: 'online'
});
// Handle joining rooms
socket.on('join_room', (data) => {
const { roomId, password } = data;
if (!rooms.has(roomId)) {
return socket.emit('error', { message: 'Room not found' });
}
const room = rooms.get(roomId);
// Check password protection
if (room.password && room.password !== password) {
return socket.emit('error', { message: 'Invalid room password' });
}
// Join room
socket.join(roomId);
users.get(socket.userId).rooms.push(roomId);
// Update room participants
if (!room.participants.includes(socket.userId)) {
room.participants.push(socket.userId);
}
socket.emit('joined_room', { roomId, roomName: room.name });
// Notify other room participants
socket.to(roomId).emit('user_joined_room', {
userId: socket.userId,
username: socket.username,
roomId
});
// Send room info to the user
socket.emit('room_info', {
roomId,
name: room.name,
participants: room.participants.map(pid => users.get(pid)?.username).filter(Boolean)
});
});
// Handle leaving rooms
socket.on('leave_room', (data) => {
const { roomId } = data;
socket.leave(roomId);
const userData = users.get(socket.userId);
if (userData) {
userData.rooms = userData.rooms.filter(room => room !== roomId);
}
const room = rooms.get(roomId);
if (room) {
room.participants = room.participants.filter(pid => pid !== socket.userId);
// Notify other room participants
socket.to(roomId).emit('user_left_room', {
userId: socket.userId,
username: socket.username,
roomId
});
// Delete room if empty and not persistent
if (room.participants.length === 0 && !room.persistent) {
rooms.delete(roomId);
}
}
socket.emit('left_room', { roomId });
});
// Handle chat messages
socket.on('chat_message', (data) => {
const { roomId, message, type = 'text' } = data;
const messageData = {
id: generateId(),
userId: socket.userId,
username: socket.username,
message: sanitizeMessage(message),
type,
timestamp: new Date().toISOString(),
roomId
};
if (roomId) {
// Send to specific room
socket.to(roomId).emit('chat_message', messageData);
} else {
// Send to all connected users
socket.broadcast.emit('chat_message', messageData);
}
// Send confirmation to sender
socket.emit('message_sent', messageData);
// Store message in room history
if (roomId && rooms.has(roomId)) {
const room = rooms.get(roomId);
if (!room.messages) room.messages = [];
room.messages.push(messageData);
// Limit message history
if (room.messages.length > 1000) {
room.messages = room.messages.slice(-1000);
}
}
});
// Handle private messages
socket.on('private_message', (data) => {
const { targetUserId, message } = data;
if (!users.has(targetUserId)) {
return socket.emit('error', { message: 'User not found' });
}
const messageData = {
id: generateId(),
userId: socket.userId,
username: socket.username,
message: sanitizeMessage(message),
timestamp: new Date().toISOString(),
private: true
};
// Send to target user
const targetSocket = connectedSockets.get(targetUserId);
if (targetSocket) {
targetSocket.emit('private_message', messageData);
}
// Send confirmation to sender
socket.emit('message_sent', messageData);
});
// Handle typing indicators
socket.on('typing_start', (data) => {
const { roomId } = data;
if (roomId) {
socket.to(roomId).emit('user_typing', {
userId: socket.userId,
username: socket.username,
typing: true
});
}
});
socket.on('typing_stop', (data) => {
const { roomId } = data;
if (roomId) {
socket.to(roomId).emit('user_typing', {
userId: socket.userId,
username: socket.username,
typing: false
});
}
});
// Handle room creation
socket.on('create_room', (data) => {
const { name, password, persistent = false } = data;
const roomId = generateId();
rooms.set(roomId, {
id: roomId,
name: sanitizeInput(name),
password: password || null,
persistent,
createdBy: socket.userId,
createdAt: new Date(),
participants: [socket.userId],
messages: []
});
socket.emit('room_created', { roomId, name });
// Auto-join creator to the room
socket.emit('join_room', { roomId });
});
// Handle get room list
socket.on('get_rooms', () => {
const roomList = Array.from(rooms.values())
.filter(room => !room.password || room.participants.includes(socket.userId))
.map(room => ({
id: room.id,
name: room.name,
participantCount: room.participants.length,
hasPassword: !!room.password,
persistent: room.persistent
}));
socket.emit('rooms_list', roomList);
});
// Handle get online users
socket.on('get_online_users', () => {
const onlineUsers = Array.from(users.values())
.filter(user => user.id !== socket.userId)
.map(user => ({
id: user.id,
username: user.username,
status: user.status
}));
socket.emit('online_users', onlineUsers);
});
// Handle disconnection
socket.on('disconnect', (reason) => {
console.log(`User disconnected: ${socket.username} (${reason})`);
// Clean up
connectedSockets.delete(socket.userId);
const userData = users.get(socket.userId);
if (userData) {
// Leave all rooms
userData.rooms.forEach(roomId => {
if (roomId !== `user_${socket.userId}`) {
const room = rooms.get(roomId);
if (room) {
room.participants = room.participants.filter(pid => pid !== socket.userId);
// Notify room participants
socket.to(roomId).emit('user_left_room', {
userId: socket.userId,
username: socket.username,
roomId
});
// Delete empty non-persistent rooms
if (room.participants.length === 0 && !room.persistent) {
rooms.delete(roomId);
}
}
}
});
users.delete(socket.userId);
}
// Notify others about user going offline
socket.broadcast.emit('user_status_change', {
userId: socket.userId,
username: socket.username,
status: 'offline'
});
});
});
// HTTP Routes
app.get('/health', (req, res) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
connectedUsers: users.size,
activeRooms: rooms.size
});
});
app.get('/api/rooms', (req, res) => {
const roomList = Array.from(rooms.values()).map(room => ({
id: room.id,
name: room.name,
participantCount: room.participants.length,
hasPassword: !!room.password,
persistent: room.persistent,
createdAt: room.createdAt
}));
res.json(roomList);
});
app.get('/api/users/online', (req, res) => {
const onlineUsers = Array.from(users.values()).map(user => ({
id: user.id,
username: user.username,
status: user.status,
connectedAt: user.connectedAt
}));
res.json(onlineUsers);
});
// Utility functions
function generateId() {
return Math.random().toString(36).substr(2, 9);
}
function sanitizeInput(input) {
return input.replace(/<[^>]*>/g, '').trim();
}
function sanitizeMessage(message) {
// Basic sanitization - in production, use a proper HTML sanitizer
return sanitizeInput(message);
}
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('SIGTERM received, shutting down gracefully');
server.close(() => {
console.log('Server closed');
process.exit(0);
});
});
process.on('SIGINT', () => {
console.log('SIGINT received, shutting down gracefully');
server.close(() => {
console.log('Server closed');
process.exit(0);
});
});
// Start server
const PORT = process.env.PORT || 3001;
server.listen(PORT, () => {
console.log(`WebSocket server running on port ${PORT}`);
});
module.exports = { app, server, io };
💻 Types et Interfaces WebSocket TypeScript typescript
🟡 intermediate
⭐⭐⭐
Définitions de types et interfaces complètes TypeScript pour applications WebSocket
⏱️ 25 min
🏷️ typescript, websocket, types, definitions
Prerequisites:
TypeScript, WebSocket concepts
// WebSocket Type Definitions and Interfaces for TypeScript
// Base WebSocket message types
export interface BaseMessage {
id: string;
timestamp: string;
type: string;
}
// Chat message types
export interface ChatMessage extends BaseMessage {
type: 'chat_message';
data: {
userId: string;
username: string;
message: string;
roomId?: string;
private?: boolean;
messageType: 'text' | 'image' | 'file' | 'emoji';
metadata?: {
fileName?: string;
fileSize?: number;
fileUrl?: string;
imageUrl?: string;
};
};
}
export interface PrivateMessage extends BaseMessage {
type: 'private_message';
data: {
userId: string;
username: string;
targetUserId: string;
targetUsername: string;
message: string;
messageType: 'text' | 'image' | 'file' | 'emoji';
};
}
// System notification types
export interface SystemNotification extends BaseMessage {
type: 'system_notification';
data: {
title: string;
message: string;
level: 'info' | 'warning' | 'error' | 'success';
icon?: string;
actionUrl?: string;
actionText?: string;
};
}
// User management types
export interface UserStatusChange extends BaseMessage {
type: 'user_status_change';
data: {
userId: string;
username: string;
status: 'online' | 'offline' | 'away' | 'busy';
lastSeen?: string;
};
}
export interface UserJoinedRoom extends BaseMessage {
type: 'user_joined_room';
data: {
userId: string;
username: string;
roomId: string;
roomName: string;
};
}
export interface UserLeftRoom extends BaseMessage {
type: 'user_left_room';
data: {
userId: string;
username: string;
roomId: string;
roomName: string;
};
}
// Room management types
export interface RoomInfo {
id: string;
name: string;
description?: string;
participantCount: number;
maxParticipants?: number;
hasPassword: boolean;
persistent: boolean;
isPrivate: boolean;
createdBy: string;
createdAt: string;
lastActivity: string;
tags: string[];
}
export interface RoomInfoResponse extends BaseMessage {
type: 'room_info';
data: RoomInfo & {
participants: Array<{
userId: string;
username: string;
role: 'owner' | 'admin' | 'moderator' | 'member';
joinedAt: string;
}>;
permissions: {
canSendMessages: boolean;
canSendFiles: boolean;
canInviteUsers: boolean;
canManageRoom: boolean;
};
};
}
export interface RoomListResponse extends BaseMessage {
type: 'rooms_list';
data: {
rooms: RoomInfo[];
totalCount: number;
hasMore: boolean;
};
}
// Typing indicators
export interface UserTyping extends BaseMessage {
type: 'user_typing';
data: {
userId: string;
username: string;
roomId?: string;
typing: boolean;
};
}
// File sharing types
export interface FileShareMessage extends BaseMessage {
type: 'file_share';
data: {
userId: string;
username: string;
fileName: string;
fileSize: number;
fileUrl: string;
mimeType: string;
roomId?: string;
private?: boolean;
thumbnailUrl?: string;
downloadUrl: string;
previewUrl?: string;
};
}
// Voice/Video call types
export interface CallInvitation extends BaseMessage {
type: 'call_invitation';
data: {
callId: string;
callerId: string;
callerUsername: string;
targetUserId: string;
targetUsername: string;
callType: 'audio' | 'video';
roomId?: string;
};
}
export interface CallResponse extends BaseMessage {
type: 'call_response';
data: {
callId: string;
response: 'accept' | 'decline' | 'busy';
userId: string;
username: string;
};
}
export interface CallEnd extends BaseMessage {
type: 'call_end';
data: {
callId: string;
endedBy: string;
endedByUsername: string;
duration: number; // in seconds
reason: 'ended' | 'rejected' | 'failed' | 'timeout';
};
}
// Authentication and authorization types
export interface AuthRequest {
type: 'authenticate';
data: {
token: string;
userId?: string;
deviceId?: string;
};
}
export interface AuthResponse extends BaseMessage {
type: 'auth_response';
data: {
success: boolean;
userId: string;
username: string;
permissions: string[];
sessionExpiresAt: string;
};
}
// Error handling types
export interface ErrorMessage extends BaseMessage {
type: 'error';
data: {
code: string;
message: string;
details?: any;
retryable: boolean;
};
}
// Presence and activity types
export interface UserActivity extends BaseMessage {
type: 'user_activity';
data: {
userId: string;
username: string;
activity: {
type: 'listening' | 'watching' | 'playing' | 'working';
details: string;
expiresAt?: string;
};
};
}
// Reaction types
export interface MessageReaction extends BaseMessage {
type: 'message_reaction';
data: {
messageId: string;
userId: string;
username: string;
reaction: string; // emoji
action: 'add' | 'remove';
};
}
// Moderation types
export interface MessageModeration extends BaseMessage {
type: 'message_moderation';
data: {
messageId: string;
moderatedBy: string;
moderatedByUsername: string;
action: 'delete' | 'edit' | 'warn';
reason?: string;
originalContent?: string;
};
}
// Union type for all possible messages
export type WebSocketMessage =
| ChatMessage
| PrivateMessage
| SystemNotification
| UserStatusChange
| UserJoinedRoom
| UserLeftRoom
| RoomInfoResponse
| RoomListResponse
| UserTyping
| FileShareMessage
| CallInvitation
| CallResponse
| CallEnd
| AuthResponse
| ErrorMessage
| UserActivity
| MessageReaction
| MessageModeration;
// Client configuration
export interface WebSocketClientConfig {
url: string;
protocols?: string[];
reconnectInterval?: number;
maxReconnectAttempts?: number;
heartbeatInterval?: number;
heartbeatTimeout?: number;
debug?: boolean;
authentication?: {
token?: string;
type?: 'bearer' | 'custom';
};
}
// Room creation data
export interface CreateRoomData {
name: string;
description?: string;
password?: string;
maxParticipants?: number;
isPrivate?: boolean;
persistent?: boolean;
tags?: string[];
}
// Room join data
export interface JoinRoomData {
roomId: string;
password?: string;
}
// Chat message data
export interface SendMessageData {
message: string;
roomId?: string;
targetUserId?: string;
messageType?: 'text' | 'image' | 'file' | 'emoji';
metadata?: {
fileName?: string;
fileSize?: number;
fileUrl?: string;
imageUrl?: string;
};
}
// File upload data
export interface FileUploadData {
file: File | Blob;
fileName?: string;
roomId?: string;
targetUserId?: string;
description?: string;
}
// Statistics and monitoring types
export interface WebSocketStats {
connectionState: 'connecting' | 'connected' | 'disconnecting' | 'disconnected';
reconnectAttempts: number;
messagesReceived: number;
messagesSent: number;
bytesReceived: number;
bytesSent: number;
latency: number; // in milliseconds
connectedAt?: string;
lastMessageAt?: string;
}
export interface RoomStats {
roomId: string;
participantCount: number;
messageCount: number;
fileCount: number;
lastActivity: string;
createdAt: string;
}
// Event handler types
export type WebSocketEventHandler<T = WebSocketMessage> = (data: T) => void;
export type WebSocketErrorEventHandler = (error: Error) => void;
export type WebSocketCloseEventHandler = (event: CloseEvent) => void;
export type WebSocketOpenEventHandler = (event: Event) => void;
// Advanced client interface
export interface IWebSocketClient {
connect(): Promise<void>;
disconnect(): void;
send<T extends WebSocketMessage>(message: T): void;
on<T extends WebSocketMessage['type']>(
eventType: T,
handler: (data: Extract<WebSocketMessage, { type: T }>) => void
): void;
off<T extends WebSocketMessage['type']>(
eventType: T,
handler: (data: Extract<WebSocketMessage, { type: T }>) => void
): void;
once<T extends WebSocketMessage['type']>(
eventType: T,
handler: (data: Extract<WebSocketMessage, { type: T }>) => void
): void;
getStats(): WebSocketStats;
getState(): string;
isConnected(): boolean;
}
// Room management interface
export interface IRoomManager {
createRoom(data: CreateRoomData): Promise<string>;
joinRoom(data: JoinRoomData): Promise<void>;
leaveRoom(roomId: string): Promise<void>;
getRoomList(): Promise<RoomInfo[]>;
getRoomInfo(roomId: string): Promise<RoomInfoResponse['data']>;
inviteToRoom(roomId: string, userIds: string[]): Promise<void>;
kickFromRoom(roomId: string, userIds: string[]): Promise<void>;
}
// Message persistence interface
export interface IMessagePersistence {
saveMessage(message: ChatMessage): Promise<void>;
getMessageHistory(roomId?: string, limit?: number, offset?: number): Promise<ChatMessage[]>;
searchMessages(query: string, roomId?: string): Promise<ChatMessage[]>;
deleteMessage(messageId: string): Promise<void>;
editMessage(messageId: string, newContent: string): Promise<void>;
}
// User presence interface
export interface IUserPresence {
updateStatus(status: 'online' | 'offline' | 'away' | 'busy'): void;
setActivity(activity: UserActivity['data']['activity']): void;
getOnlineUsers(): Promise<Array<{ id: string; username: string; status: string }>>;
subscribeToUserStatus(userId: string): void;
unsubscribeFromUserStatus(userId: string): void;
}
// File sharing interface
export interface IFileSharing {
uploadFile(data: FileUploadData): Promise<string>; // Returns file URL
downloadFile(fileId: string): Promise<Blob>;
deleteFile(fileId: string): Promise<void>;
getFileMetadata(fileId: string): Promise<FileShareMessage['data']>;
}
// Type guards for message type discrimination
export function isChatMessage(message: WebSocketMessage): message is ChatMessage {
return message.type === 'chat_message';
}
export function isPrivateMessage(message: WebSocketMessage): message is PrivateMessage {
return message.type === 'private_message';
}
export function isSystemNotification(message: WebSocketMessage): message is SystemNotification {
return message.type === 'system_notification';
}
export function isUserStatusChange(message: WebSocketMessage): message is UserStatusChange {
return message.type === 'user_status_change';
}
export function isErrorMessage(message: WebSocketMessage): message is ErrorMessage {
return message.type === 'error';
}
export function isCallInvitation(message: WebSocketMessage): message is CallInvitation {
return message.type === 'call_invitation';
}
// Utility functions
export function createChatMessage(
data: ChatMessage['data']
): ChatMessage {
return {
id: generateId(),
timestamp: new Date().toISOString(),
type: 'chat_message',
data
};
}
export function createPrivateMessage(
data: PrivateMessage['data']
): PrivateMessage {
return {
id: generateId(),
timestamp: new Date().toISOString(),
type: 'private_message',
data
};
}
export function createSystemNotification(
title: string,
message: string,
level: SystemNotification['data']['level'] = 'info'
): SystemNotification {
return {
id: generateId(),
timestamp: new Date().toISOString(),
type: 'system_notification',
data: { title, message, level }
};
}
function generateId(): string {
return Math.random().toString(36).substr(2, 9) + Date.now().toString(36);
}
// Export all types for easy importing
export * from './socket-io-types'; // If using Socket.IO specific types
💻 Composant Chat WebSocket React typescript
🟡 intermediate
⭐⭐⭐⭐
Application chat React complète avec intégration WebSocket et messagerie en temps réel
⏱️ 35 min
🏷️ react, websocket, chat, typescript, ui
Prerequisites:
React, TypeScript, WebSocket
// React WebSocket Chat Application
import React, { useState, useEffect, useRef, useCallback } from 'react';
import { WebSocketClient } from './websocket-client';
import type { ChatMessage, UserStatusChange, UserTyping } from './websocket-types';
interface ChatRoom {
id: string;
name: string;
participantCount: number;
hasPassword: boolean;
}
interface User {
id: string;
username: string;
status: 'online' | 'offline' | 'away' | 'busy';
lastSeen?: string;
}
interface ChatState {
messages: ChatMessage[];
users: User[];
rooms: ChatRoom[];
currentRoom: string | null;
isConnected: boolean;
typingUsers: Set<string>;
}
// Chat component
export const ChatApp: React.FC<{ wsUrl: string; authToken: string; username: string }> = ({
wsUrl,
authToken,
username
}) => {
const [state, setState] = useState<ChatState>({
messages: [],
users: [],
rooms: [],
currentRoom: null,
isConnected: false,
typingUsers: new Set()
});
const [inputMessage, setInputMessage] = useState('');
const [isTyping, setIsTyping] = useState(false);
const [showRoomList, setShowRoomList] = useState(false);
const wsClientRef = useRef<WebSocketClient | null>(null);
const messagesEndRef = useRef<HTMLDivElement>(null);
const typingTimeoutRef = useRef<NodeJS.Timeout | null>(null);
// Initialize WebSocket connection
useEffect(() => {
if (!wsUrl || !authToken) return;
const client = new WebSocketClient(wsUrl, {
debug: process.env.NODE_ENV === 'development',
authentication: {
token: authToken,
type: 'bearer'
}
});
wsClientRef.current = client;
// Event handlers
const handleOpen = () => {
setState(prev => ({ ...prev, isConnected: true }));
};
const handleClose = () => {
setState(prev => ({ ...prev, isConnected: false }));
};
const handleError = (error: Error) => {
console.error('WebSocket error:', error);
// Could add toast notification here
};
const handleChatMessage = (message: ChatMessage) => {
setState(prev => ({
...prev,
messages: [...prev.messages, message]
}));
scrollToBottom();
};
const handleUserStatusChange = (data: UserStatusChange['data']) => {
setState(prev => ({
...prev,
users: prev.users.map(user =>
user.id === data.userId
? { ...user, status: data.status, lastSeen: data.lastSeen }
: user
)
}));
};
const handleUserTyping = (data: UserTyping['data']) => {
setState(prev => {
const newTypingUsers = new Set(prev.typingUsers);
if (data.typing) {
newTypingUsers.add(data.username);
} else {
newTypingUsers.delete(data.username);
}
return { ...prev, typingUsers: newTypingUsers };
});
};
const handleRoomsList = (rooms: ChatRoom[]) => {
setState(prev => ({ ...prev, rooms }));
};
const handleOnlineUsers = (users: User[]) => {
setState(prev => ({ ...prev, users }));
};
// Register event listeners
client.on('open', handleOpen);
client.on('close', handleClose);
client.on('error', handleError);
client.on('chat_message', handleChatMessage);
client.on('user_status_change', (msg: any) => handleUserStatusChange(msg.data));
client.on('user_typing', (msg: any) => handleUserTyping(msg.data));
client.on('rooms_list', (msg: any) => handleRoomsList(msg.data.rooms));
client.on('online_users', (msg: any) => handleOnlineUsers(msg.data));
// Request initial data
client.on('open', () => {
client.send({ type: 'get_rooms' });
client.send({ type: 'get_online_users' });
});
return () => {
client.off('open', handleOpen);
client.off('close', handleClose);
client.off('error', handleError);
client.off('chat_message', handleChatMessage);
client.off('user_status_change', (msg: any) => handleUserStatusChange(msg.data));
client.off('user_typing', (msg: any) => handleUserTyping(msg.data));
client.off('rooms_list', (msg: any) => handleRoomsList(msg.data.rooms));
client.off('online_users', (msg: any) => handleOnlineUsers(msg.data));
client.disconnect();
};
}, [wsUrl, authToken]);
// Auto-scroll to bottom on new messages
const scrollToBottom = useCallback(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, []);
// Send message
const sendMessage = useCallback(() => {
if (!inputMessage.trim() || !wsClientRef.current) return;
const messageData = {
type: 'chat_message',
data: {
userId: '', // Will be filled by server
username,
message: inputMessage.trim(),
roomId: state.currentRoom || undefined,
messageType: 'text' as const
}
};
wsClientRef.current.send(messageData);
setInputMessage('');
stopTyping();
}, [inputMessage, username, state.currentRoom]);
// Handle typing indicators
const startTyping = useCallback(() => {
if (isTyping || !wsClientRef.current) return;
setIsTyping(true);
wsClientRef.current.send({
type: 'typing_start',
data: { roomId: state.currentRoom }
});
if (typingTimeoutRef.current) {
clearTimeout(typingTimeoutRef.current);
}
typingTimeoutRef.current = setTimeout(() => {
stopTyping();
}, 3000);
}, [isTyping, state.currentRoom]);
const stopTyping = useCallback(() => {
if (!isTyping || !wsClientRef.current) return;
setIsTyping(false);
wsClientRef.current.send({
type: 'typing_stop',
data: { roomId: state.currentRoom }
});
if (typingTimeoutRef.current) {
clearTimeout(typingTimeoutRef.current);
typingTimeoutRef.current = null;
}
}, [isTyping, state.currentRoom]);
// Join room
const joinRoom = useCallback((roomId: string) => {
if (!wsClientRef.current) return;
wsClientRef.current.send({
type: 'join_room',
data: { roomId }
});
setState(prev => ({ ...prev, currentRoom: roomId }));
setShowRoomList(false);
}, []);
// Leave room
const leaveRoom = useCallback(() => {
if (!wsClientRef.current || !state.currentRoom) return;
wsClientRef.current.send({
type: 'leave_room',
data: { roomId: state.currentRoom }
});
setState(prev => ({ ...prev, currentRoom: null }));
}, [state.currentRoom]);
// Handle input change
const handleInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setInputMessage(e.target.value);
if (e.target.value.trim()) {
startTyping();
} else {
stopTyping();
}
}, [startTyping, stopTyping]);
// Handle key press
const handleKeyPress = useCallback((e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
}, [sendMessage]);
// Get current room info
const currentRoomInfo = state.rooms.find(room => room.id === state.currentRoom);
return (
<div className="chat-app">
{/* Header */}
<div className="chat-header">
<div className="connection-status">
<span className={`status-indicator ${state.isConnected ? 'connected' : 'disconnected'}`} />
{state.isConnected ? 'Connected' : 'Disconnected'}
</div>
<div className="current-room">
{currentRoomInfo ? currentRoomInfo.name : 'General Chat'}
{state.currentRoom && (
<button onClick={leaveRoom} className="leave-room-btn">
Leave Room
</button>
)}
</div>
<div className="user-info">
{username}
</div>
</div>
<div className="chat-body">
{/* Sidebar */}
<div className="chat-sidebar">
<div className="sidebar-section">
<h3>Rooms</h3>
<button onClick={() => setShowRoomList(!showRoomList)}>
{showRoomList ? 'Hide' : 'Show'} Rooms
</button>
{showRoomList && (
<div className="room-list">
<div
className={`room-item ${!state.currentRoom ? 'active' : ''}`}
onClick={() => setState(prev => ({ ...prev, currentRoom: null }))}
>
General Chat
</div>
{state.rooms.map(room => (
<div
key={room.id}
className={`room-item ${state.currentRoom === room.id ? 'active' : ''}`}
onClick={() => joinRoom(room.id)}
>
<span>{room.name}</span>
<span className="participant-count">
{room.participantCount}
</span>
</div>
))}
</div>
)}
</div>
<div className="sidebar-section">
<h3>Online Users</h3>
<div className="user-list">
{state.users.map(user => (
<div key={user.id} className="user-item">
<span className={`user-status ${user.status}`} />
<span>{user.username}</span>
</div>
))}
</div>
</div>
</div>
{/* Main chat area */}
<div className="chat-main">
{/* Messages */}
<div className="messages-container">
{state.messages.map((message, index) => (
<div key={message.id || index} className="message">
<div className="message-header">
<span className="message-username">
{message.data.username}
</span>
<span className="message-timestamp">
{new Date(message.timestamp).toLocaleTimeString()}
</span>
</div>
<div className="message-content">
{message.data.message}
</div>
</div>
))}
<div ref={messagesEndRef} />
</div>
{/* Typing indicator */}
{state.typingUsers.size > 0 && (
<div className="typing-indicator">
{Array.from(state.typingUsers).join(', ')} {state.typingUsers.size === 1 ? 'is' : 'are'} typing...
</div>
)}
{/* Message input */}
<div className="message-input-container">
<input
type="text"
className="message-input"
placeholder="Type a message..."
value={inputMessage}
onChange={handleInputChange}
onKeyPress={handleKeyPress}
disabled={!state.isConnected}
/>
<button
className="send-button"
onClick={sendMessage}
disabled={!inputMessage.trim() || !state.isConnected}
>
Send
</button>
</div>
</div>
</div>
<style jsx>{`
.chat-app {
display: flex;
flex-direction: column;
height: 100vh;
max-width: 1200px;
margin: 0 auto;
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
}
.chat-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background: #f5f5f5;
border-bottom: 1px solid #ddd;
}
.connection-status {
display: flex;
align-items: center;
gap: 0.5rem;
}
.status-indicator {
width: 10px;
height: 10px;
border-radius: 50%;
}
.status-indicator.connected {
background: #4caf50;
}
.status-indicator.disconnected {
background: #f44336;
}
.chat-body {
display: flex;
flex: 1;
overflow: hidden;
}
.chat-sidebar {
width: 250px;
background: #fafafa;
border-right: 1px solid #ddd;
overflow-y: auto;
}
.sidebar-section {
padding: 1rem;
border-bottom: 1px solid #eee;
}
.room-list, .user-list {
margin-top: 0.5rem;
}
.room-item, .user-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem;
cursor: pointer;
border-radius: 4px;
margin-bottom: 0.25rem;
}
.room-item:hover, .user-item:hover {
background: #e3f2fd;
}
.room-item.active {
background: #2196f3;
color: white;
}
.user-status {
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 0.5rem;
}
.user-status.online { background: #4caf50; }
.user-status.offline { background: #9e9e9e; }
.user-status.away { background: #ff9800; }
.user-status.busy { background: #f44336; }
.chat-main {
flex: 1;
display: flex;
flex-direction: column;
}
.messages-container {
flex: 1;
padding: 1rem;
overflow-y: auto;
background: white;
}
.message {
margin-bottom: 1rem;
padding: 0.5rem;
border-radius: 8px;
background: #f9f9f9;
}
.message-header {
display: flex;
justify-content: space-between;
margin-bottom: 0.25rem;
font-size: 0.875rem;
}
.message-username {
font-weight: bold;
color: #2196f3;
}
.message-timestamp {
color: #666;
}
.message-content {
word-wrap: break-word;
}
.typing-indicator {
padding: 0.5rem 1rem;
color: #666;
font-style: italic;
font-size: 0.875rem;
}
.message-input-container {
display: flex;
padding: 1rem;
border-top: 1px solid #ddd;
background: #fafafa;
}
.message-input {
flex: 1;
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
.send-button {
margin-left: 0.5rem;
padding: 0.5rem 1rem;
background: #2196f3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.send-button:disabled {
background: #ccc;
cursor: not-allowed;
}
.leave-room-btn {
margin-left: 0.5rem;
padding: 0.25rem 0.5rem;
background: #f44336;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.75rem;
}
`}</style>
</div>
);
};