@owlmeans/server-socket
v0.1.1
Published
The **@owlmeans/server-socket** package provides WebSocket server functionality for OwlMeans Common Libraries, designed for fullstack microservices and microclients development with focus on security and proper authentication and authorization.
Readme
@owlmeans/server-socket
The @owlmeans/server-socket package provides WebSocket server functionality for OwlMeans Common Libraries, designed for fullstack microservices and microclients development with focus on security and proper authentication and authorization.
Purpose
This package serves as the server-side WebSocket implementation that:
- Provides WebSocket server capabilities using Fastify WebSocket integration
- Supports real-time communication between server and clients
- Integrates with module system for WebSocket route handling
- Implements authentication and authorization for WebSocket connections
- Offers connection management with lifecycle hooks and proper cleanup
- Enables bidirectional messaging with typed message handling
Core Concepts
Socket Service
A service that manages WebSocket server functionality and integrates with the Fastify web server to provide WebSocket endpoints.
Connection Handling
Provides abstraction for WebSocket connections with proper lifecycle management, authentication, and error handling.
Module Integration
WebSocket endpoints are defined using the OwlMeans module system, allowing for consistent routing, validation, and middleware application.
Authentication & Authorization
WebSocket connections can be protected using the same guard and gate system used for HTTP endpoints.
API Reference
Services
createSocketService(alias?): SocketService
Creates a WebSocket service that can be registered with a server context.
import { createSocketService } from '@owlmeans/server-socket'
const socketService = createSocketService('websocket')Parameters:
alias: string (optional) - Service alias, defaults toDEFAULT_ALIAS('socket-server')
Returns: SocketService instance
SocketService
Interface for the WebSocket service.
interface SocketService extends InitializedService {
update: (api: ApiServer) => Promise<void>
}Methods:
update(api): Configures the WebSocket server with the provided API server instance
Helper Functions
handleConnection<T>(handler): RefedModuleHandler
Creates a WebSocket connection handler that integrates with the module system.
import { handleConnection } from '@owlmeans/server-socket'
import type { Connection } from '@owlmeans/socket'
const myConnectionHandler = handleConnection<MyConnection>(
async (conn, ctx, req, res) => {
// Handle WebSocket connection
console.log('New WebSocket connection')
conn.onMessage(async (message) => {
console.log('Received:', message)
await conn.send({ type: 'echo', data: message })
})
conn.onClose(() => {
console.log('Connection closed')
})
}
)Parameters:
handler: Function that handles WebSocket connections with signature:conn: Connection instancectx: Server contextreq: Abstract requestres: Abstract response
Returns: Module handler compatible with OwlMeans modules
Types
Config
Configuration interface extending ServerConfig.
interface Config extends ServerConfig {}Context<C>
Context interface that extends ServerContext with API server capabilities.
interface Context<C extends Config = Config> extends ServerContext<C>, ApiServerAppend {}Constants
DEFAULT_ALIAS
Default alias for the socket service.
const DEFAULT_ALIAS = 'socket-server'Usage Examples
Basic WebSocket Server Setup
import { makeServerContext, makeServerConfig } from '@owlmeans/server-context'
import { createSocketService } from '@owlmeans/server-socket'
import { createApiService } from '@owlmeans/server-api'
import { AppType } from '@owlmeans/context'
// Create server configuration
const config = makeServerConfig(AppType.Backend, 'websocket-server', {
host: 'localhost',
port: 3000
})
// Create context and services
const context = makeServerContext(config)
const apiService = createApiService()
const socketService = createSocketService()
// Register services
context.registerService(apiService)
context.registerService(socketService)
// Configure and initialize
context.configure()
await context.init()
console.log('WebSocket server running on ws://localhost:3000')WebSocket Module with Authentication
import { module, guard } from '@owlmeans/module'
import { route } from '@owlmeans/route'
import { handleConnection } from '@owlmeans/server-socket'
import type { Connection } from '@owlmeans/socket'
// Create WebSocket module with authentication
const chatModule = module(
route('chat-socket', '/ws/chat'),
guard('authenticated')
)
// Set WebSocket handler
chatModule.handle = handleConnection<Connection>(
async (conn, ctx, req, res) => {
const userId = req.auth?.user?.id
if (!userId) {
await conn.close(1008, 'Authentication required')
return
}
console.log(`User ${userId} connected to chat`)
// Join user to chat room
await joinChatRoom(conn, userId)
// Handle incoming messages
conn.onMessage(async (message) => {
if (message.type === 'chat-message') {
await broadcastMessage(userId, message.content)
}
})
// Handle disconnection
conn.onClose(() => {
console.log(`User ${userId} disconnected from chat`)
leaveChatRoom(userId)
})
}
)
// Register module with context
context.registerModule(chatModule)Real-time Data Streaming
import { handleConnection } from '@owlmeans/server-socket'
import type { Connection } from '@owlmeans/socket'
const dataStreamModule = module(
route('data-stream', '/ws/data'),
guard('authenticated')
)
dataStreamModule.handle = handleConnection<Connection>(
async (conn, ctx, req, res) => {
const userId = req.auth?.user?.id
// Start streaming data to client
const streamInterval = setInterval(async () => {
const data = await fetchLatestData(userId)
await conn.send({
type: 'data-update',
timestamp: Date.now(),
data: data
})
}, 1000)
// Handle client requests
conn.onMessage(async (message) => {
switch (message.type) {
case 'subscribe':
await subscribeToChannel(userId, message.channel)
break
case 'unsubscribe':
await unsubscribeFromChannel(userId, message.channel)
break
case 'get-history':
const history = await getHistoricalData(message.from, message.to)
await conn.send({
type: 'history-data',
requestId: message.requestId,
data: history
})
break
}
})
// Cleanup on disconnect
conn.onClose(() => {
clearInterval(streamInterval)
unsubscribeFromAllChannels(userId)
})
}
)WebSocket with Validation
import { module, filter, body } from '@owlmeans/module'
import { route } from '@owlmeans/route'
import { handleConnection } from '@owlmeans/server-socket'
// Define message validation schema
const messageSchema = {
type: 'object',
properties: {
type: { type: 'string', enum: ['join', 'leave', 'message'] },
content: { type: 'string', maxLength: 1000 },
roomId: { type: 'string' }
},
required: ['type']
}
const validatedSocketModule = module(
route('validated-socket', '/ws/validated'),
filter(body(messageSchema))
)
validatedSocketModule.handle = handleConnection<Connection>(
async (conn, ctx, req, res) => {
conn.onMessage(async (message) => {
// Message is automatically validated against schema
try {
switch (message.type) {
case 'join':
await joinRoom(conn, message.roomId)
await conn.send({ type: 'joined', roomId: message.roomId })
break
case 'leave':
await leaveRoom(conn, message.roomId)
await conn.send({ type: 'left', roomId: message.roomId })
break
case 'message':
await broadcastToRoom(message.roomId, {
type: 'room-message',
content: message.content,
sender: req.auth?.user?.id
})
break
}
} catch (error) {
await conn.send({
type: 'error',
message: error.message
})
}
})
}
)Connection Management
import { handleConnection } from '@owlmeans/server-socket'
import type { Connection } from '@owlmeans/socket'
// Global connection registry
const connections = new Map<string, Connection>()
const managedConnectionModule = module(
route('managed-connection', '/ws/managed')
)
managedConnectionModule.handle = handleConnection<Connection>(
async (conn, ctx, req, res) => {
const connectionId = generateConnectionId()
// Register connection
connections.set(connectionId, conn)
// Send connection info
await conn.send({
type: 'connection-established',
connectionId: connectionId,
serverTime: Date.now()
})
// Handle ping/pong for connection health
conn.onMessage(async (message) => {
if (message.type === 'ping') {
await conn.send({ type: 'pong', timestamp: Date.now() })
}
})
// Cleanup on disconnect
conn.onClose(() => {
connections.delete(connectionId)
console.log(`Connection ${connectionId} closed`)
})
// Send periodic server stats
const statsInterval = setInterval(async () => {
await conn.send({
type: 'server-stats',
activeConnections: connections.size,
serverUptime: process.uptime()
})
}, 30000)
conn.onClose(() => {
clearInterval(statsInterval)
})
}
)
// Function to broadcast to all connections
const broadcastToAll = async (message: any) => {
const promises = Array.from(connections.values()).map(conn =>
conn.send(message).catch(console.error)
)
await Promise.allSettled(promises)
}Error Handling
import { handleConnection } from '@owlmeans/server-socket'
import { ResilientError } from '@owlmeans/error'
const errorHandlingModule = module(
route('error-handling', '/ws/errors')
)
errorHandlingModule.handle = handleConnection<Connection>(
async (conn, ctx, req, res) => {
conn.onMessage(async (message) => {
try {
await processMessage(message)
} catch (error) {
if (error instanceof ResilientError) {
// Handle known errors gracefully
await conn.send({
type: 'error',
code: error.code,
message: error.message,
recoverable: true
})
} else {
// Handle unknown errors
console.error('Unexpected error:', error)
await conn.send({
type: 'error',
message: 'Internal server error',
recoverable: false
})
}
}
})
conn.onError((error) => {
console.error('WebSocket connection error:', error)
})
}
)Integration Patterns
With Chat Systems
// Real-time chat implementation
const chatRooms = new Map<string, Set<Connection>>()
const joinRoom = async (conn: Connection, roomId: string) => {
if (!chatRooms.has(roomId)) {
chatRooms.set(roomId, new Set())
}
chatRooms.get(roomId)!.add(conn)
}
const broadcastToRoom = async (roomId: string, message: any) => {
const room = chatRooms.get(roomId)
if (room) {
const promises = Array.from(room).map(conn => conn.send(message))
await Promise.allSettled(promises)
}
}With Gaming Applications
// Game server with real-time updates
const gameSessionModule = module(
route('game-session', '/ws/game/:sessionId'),
guard('authenticated')
)
gameSessionModule.handle = handleConnection<Connection>(
async (conn, ctx, req, res) => {
const sessionId = req.params.sessionId
const playerId = req.auth?.user?.id
await joinGameSession(sessionId, playerId, conn)
conn.onMessage(async (message) => {
switch (message.type) {
case 'player-move':
await processPlayerMove(sessionId, playerId, message.move)
await broadcastGameState(sessionId)
break
case 'player-action':
await processPlayerAction(sessionId, playerId, message.action)
break
}
})
}
)With Monitoring Systems
// Real-time monitoring dashboard
const monitoringModule = module(
route('monitoring', '/ws/monitor'),
guard('admin')
)
monitoringModule.handle = handleConnection<Connection>(
async (conn, ctx, req, res) => {
// Send initial system state
await conn.send({
type: 'system-state',
data: await getSystemMetrics()
})
// Subscribe to system events
const unsubscribe = await subscribeToSystemEvents(async (event) => {
await conn.send({
type: 'system-event',
event: event
})
})
conn.onClose(unsubscribe)
}
)Security Considerations
Authentication and Authorization
- Guard Integration - Use authentication guards to protect WebSocket endpoints
- Token Validation - Validate JWT tokens or session tokens before accepting connections
- Connection Limits - Implement connection limits per user or IP address
Data Validation
- Message Validation - Always validate incoming WebSocket messages
- Rate Limiting - Implement rate limiting for message frequency
- Sanitization - Sanitize user input to prevent injection attacks
Connection Security
- TLS/SSL - Use secure WebSocket connections (WSS) in production
- Origin Validation - Validate connection origins to prevent CSRF attacks
- Connection Timeouts - Implement proper timeout mechanisms
Best Practices
- Implement proper error handling - Always handle connection errors and message processing errors
- Use connection pooling - Manage connections efficiently to prevent memory leaks
- Implement heartbeat mechanism - Use ping/pong to detect dead connections
- Validate all messages - Never trust incoming WebSocket messages without validation
- Handle cleanup properly - Always clean up resources when connections close
- Monitor connection health - Track connection metrics and performance
Error Handling
The package integrates with OwlMeans error handling:
- ResilientError - For recoverable errors that clients can handle
- Connection errors - Proper handling of WebSocket connection failures
- Authentication errors - Clear error messages for authentication failures
- Validation errors - Detailed validation error reporting
Dependencies
This package depends on:
@owlmeans/context- For service registration and context management@owlmeans/server-api- For API server integration@owlmeans/server-context- For server-side context management@owlmeans/server-module- For module system integration@owlmeans/module- For core module functionality@owlmeans/socket- For connection abstractions@owlmeans/error- For error handling@fastify/websocket- For WebSocket server implementation
Related Packages
@owlmeans/socket- WebSocket connection abstractions@owlmeans/client-socket- Client-side WebSocket implementation@owlmeans/server-api- HTTP API server functionality@owlmeans/server-module- Server-side module system
