Installation
Install the Socket.io client library:npm install socket.io-client
Basic Connection
Driver Connection
import { io } from 'socket.io-client';
const driverSocket = io('http://localhost:3000/drivers', {
auth: {
token: 'your-jwt-access-token'
},
transports: ['websocket', 'polling'],
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 1000
});
// Listen for successful connection
driverSocket.on('connect', () => {
console.log('Connected to /drivers namespace');
console.log('Socket ID:', driverSocket.id);
});
// Listen for hello event (server confirmation)
driverSocket.on('hello', (data) => {
console.log('Server hello:', data);
// { ok: true, nsp: '/drivers' }
});
// Handle connection errors
driverSocket.on('connect_error', (error) => {
console.error('Connection error:', error.message);
});
// Handle disconnection
driverSocket.on('disconnect', (reason) => {
console.log('Disconnected:', reason);
});
Passenger Connection
import { io } from 'socket.io-client';
const passengerSocket = io('http://localhost:3000/passengers', {
auth: {
token: 'your-jwt-access-token'
}
});
passengerSocket.on('connect', () => {
console.log('Connected to /passengers namespace');
});
passengerSocket.on('hello', (data) => {
console.log('Server hello:', data);
// { ok: true, nsp: '/passengers' }
});
Admin Connection
import { io } from 'socket.io-client';
const adminSocket = io('http://localhost:3000/admin', {
auth: {
token: 'your-admin-jwt-access-token'
}
});
adminSocket.on('connect', () => {
console.log('Connected to /admin namespace');
});
Connection Lifecycle
Server-Side Validation
When a client connects, the server performs these validation steps:// From driver-availability.gateway.ts:57
async handleConnection(client: Socket) {
try {
// 1. Extract token from auth.token or Authorization header
const token =
(client.handshake.auth as any)?.token ||
(client.handshake.headers?.authorization || '').replace(
/^Bearer\s+/i,
'',
);
if (!token) throw new Error('Missing token');
// 2. Verify JWT
const payload = this.tokenService.verifyAccessToken<any>(token);
const userId = payload?.sub as string | undefined;
const sid = payload?.sid as string | undefined;
if (!userId || !sid) throw new Error('Missing sub/sid');
// 3. Validate user and session
const user = await this.usersRepo.findById(userId);
if (!user) throw new Error('User not found');
if (user.userType !== UserType.DRIVER) throw new Error('Wrong userType');
if (user.status !== UserStatus.ACTIVE) throw new Error('User inactive');
const session = await this.sessionRepo.findOne({ where: { jti: sid } });
if (!session || session.revoked) throw new Error('Invalid session');
if (
session.accessTokenExpiresAt &&
session.accessTokenExpiresAt < new Date()
) {
throw new Error('Access expired');
}
// 4. Store data in socket context
const data: DriverSocketData = {
userId,
driverId: userId,
jti: sid,
sessionId: session.id ?? sid,
userType: 'DRIVER',
};
(client.data as DriverSocketData) = data;
// 5. Join canonical rooms
await client.join(Rooms.driver(userId));
await client.join(`session:${sid}`);
// 6. Log and send hello
this.logger.log(
`Driver WS connected user=${userId} sid=${sid} nsp=${client.nsp?.name}`
);
client.emit('hello', { ok: true, nsp: '/drivers' });
} catch (e) {
this.logger.warn(`Driver WS handshake failed: ${(e as Error).message}`);
client.disconnect(true);
}
}
Disconnection Handling
// Server logs disconnection
async handleDisconnect(client: Socket) {
const d = client.data || {};
this.logger.log(
`Driver WS disconnected user=${d.driverId ?? 'unknown'} sid=${d.jti ?? 'unknown'}`
);
}
Socket Context Data
The server stores user context insocket.data:
Driver Socket Data
interface DriverSocketData {
userId: string; // User ID from JWT
driverId?: string; // Same as userId for drivers
jti: string; // Session ID from JWT (sid)
sessionId: string; // PK of sessions table
sessionType?: string; // Session type (e.g., "mobile")
userType: 'DRIVER'; // User type constant
}
Passenger Socket Data
interface PassengerSocketData {
passengerId: string; // User ID
sid: string; // Session ID
}
Admin Socket Data
interface AdminSocketData {
userId: string; // Admin user ID
sid: string; // Session ID
role: string; // User type (ADMIN, DISPATCHER, etc.)
}
Rooms and Subscriptions
Upon connection, sockets automatically join rooms:Driver Rooms
await client.join(Rooms.driver(userId)); // "driver:{userId}"
await client.join(`session:${sid}`); // "session:{sid}"
Passenger Rooms
await client.join(Rooms.passenger(userId)); // "passenger:{userId}"
await client.join(`session:${sid}`); // "session:{sid}"
Admin Rooms
await client.join('admin:all'); // All admin users
// Optional: organization-specific rooms
// await client.join(`admin:org:${orgId}`);
Connection Options
Ping/Pong Configuration
The driver gateway configures custom ping intervals:@WebSocketGateway({
namespace: '/drivers',
cors: { origin: '*', credentials: true },
pingTimeout: 20000, // 20 seconds
pingInterval: 25000, // 25 seconds
})
Transports
By default, Socket.io supports both WebSocket and polling:const socket = io(url, {
transports: ['websocket', 'polling'], // Try WebSocket first
});
const socket = io(url, {
transports: ['websocket'],
upgrade: false,
});
Reconnection Strategy
const socket = io(url, {
auth: { token },
reconnection: true,
reconnectionAttempts: 10,
reconnectionDelay: 1000,
reconnectionDelayMax: 5000,
randomizationFactor: 0.5,
});
socket.io.on('reconnect_attempt', (attempt) => {
console.log('Reconnection attempt:', attempt);
});
socket.io.on('reconnect', (attempt) => {
console.log('Reconnected after', attempt, 'attempts');
});
socket.io.on('reconnect_failed', () => {
console.error('Reconnection failed');
});
Error Handling
Common Connection Errors
socket.on('connect_error', (error) => {
switch (error.message) {
case 'Missing token':
console.error('No JWT token provided');
// Redirect to login
break;
case 'Missing sub/sid':
console.error('Invalid token payload');
// Refresh token or re-login
break;
case 'User not found':
console.error('User account not found');
// Clear local data, redirect to login
break;
case 'Wrong userType':
console.error('User type mismatch');
// Connect to correct namespace
break;
case 'User inactive':
console.error('User account is inactive');
// Show account suspended message
break;
case 'Invalid session':
console.error('Session not found or revoked');
// Re-login required
break;
case 'Access expired':
console.error('Access token expired');
// Refresh token
break;
default:
console.error('Connection error:', error.message);
}
});
Forced Disconnection
The server can force disconnect sockets in specific scenarios:Session Revocation
// From auth-realtime.publisher.ts:57
sessionRevoked(ev: SessionRevokedEvent) {
// Disconnect all sockets for this session
this.driverGateway?.server
?.to(`session:${ev.sid}`)
.disconnectSockets?.(true);
// Notify driver (if they have other active sessions)
this.driverGateway?.server
?.to(`driver:${ev.userId}`)
.emit('auth:session_revoked', {
sid: ev.sid,
reason: ev.reason ?? 'revoked',
at: ev.at,
});
}
Session Refresh
// From auth-realtime.publisher.ts:41
sessionRefreshed(ev: SessionRefreshedEvent) {
// Force reconnection with new token
this.driverGateway?.server
?.to(`session:${ev.oldSid}`)
.disconnectSockets?.(true);
}
Testing Connections
Simple Connection Test
import { io } from 'socket.io-client';
const token = 'your-jwt-token';
const socket = io('http://localhost:3000/drivers', {
auth: { token }
});
socket.on('connect', () => {
console.log('✅ Connected:', socket.id);
});
socket.on('hello', (data) => {
console.log('✅ Hello received:', data);
});
socket.on('connect_error', (err) => {
console.error('❌ Connection error:', err.message);
});
socket.on('disconnect', (reason) => {
console.log('⚠️ Disconnected:', reason);
});
Next Steps
Driver Auth Events
Authentication lifecycle events
Driver Availability
Status and location updates
Passenger Events
Trip lifecycle for passengers
Admin Events
Administrative monitoring
