/// import { Server as SocketIOServer, Socket, Namespace } from 'socket.io'; import type { Core } from '@strapi/strapi'; declare module '@strapi/strapi' { export interface Strapi { /** * Global Socket.IO instance with helper functions * @example * strapi.$io.server.emit('notification', data); * strapi.$io.joinRoom(socketId, 'premium-users'); */ $io: SocketIO; /** * Current plugin settings (read-only) * @example * const maxConnections = strapi.$ioSettings.connection.maxConnections; */ $ioSettings?: PluginSettings; } } export interface SocketIOConfig { events?: SocketEvent[]; hooks?: { init?: (context: { strapi: Core.Strapi; $io: SocketIO }) => void | Promise; }; contentTypes?: Array; socket?: { /** * Options forwarded to the Socket.IO / Engine.IO server. * The plugin defaults `destroyUpgrade` to `false` so Strapi Data Transfer * WebSocket upgrades (`/admin/transfer/*`) are not torn down (#112). */ serverOptions?: Record & { destroyUpgrade?: boolean; destroyUpgradeTimeout?: number; path?: string; cors?: unknown; }; }; /** * Additional sensitive field names to exclude from emitted data. * These are added to the default list which includes: * password, resetPasswordToken, confirmationToken, refreshToken, * accessToken, secret, apiKey, privateKey, token, salt, hash * * @example * sensitiveFields: ['creditCard', 'ssn', 'socialSecurityNumber'] */ sensitiveFields?: string[]; } export interface SocketEvent { name: string; handler: ( context: { strapi: Core.Strapi; io: SocketIO }, socket: Socket, ...args: any[] ) => void | Promise; } /** * Populate configuration for content type events. * Supports multiple formats for flexibility. */ export type PopulateConfig = | '*' // Populate all relations (1 level deep) | true // Alias for '*' | string[] // Specific relations: ['author', 'category'] | Record; // Strapi populate syntax: { author: { fields: ['name'] } } export interface ContentTypeConfig { /** Content type UID (e.g., 'api::article.article') */ uid: string; /** Actions to emit events for. Defaults to all: ['create', 'update', 'delete'] */ actions?: Array<'create' | 'update' | 'delete'>; /** * Populate relations when emitting events. * When configured, the plugin will refetch the entity with populated relations * after create/update operations before emitting the event. * * @example * // Populate all relations * populate: '*' * * @example * // Populate specific relations * populate: ['author', 'category'] * * @example * // Strapi populate syntax with field selection * populate: { * author: { fields: ['username', 'email'] }, * category: true * } */ populate?: PopulateConfig; } export interface EmitOptions { event: 'create' | 'update' | 'delete'; schema: any; data: any; } export interface RawEmitOptions { event: string; data: any; rooms?: string[]; } export interface SocketIOMetrics { totalEmits: number; cachedLookups: number; connectedSockets: number; errors: number; cacheSize: { rooms: number; abilities: number; }; } export class SocketIO { constructor(options?: any); /** * Emit a content type event with sanitization and permission checking */ emit(options: EmitOptions): Promise; /** * Emit a raw event to specified rooms */ raw(options: RawEmitOptions): Promise; /** * Invalidate the internal cache */ invalidateCache(): void; /** * Get current metrics */ getMetrics(): SocketIOMetrics; /** * Get the underlying Socket.IO server instance */ readonly server: SocketIOServer; /** * All configured namespaces */ namespaces?: Record; /** * Join socket to room */ joinRoom?(socketId: string, roomName: string): boolean; /** * Remove socket from room */ leaveRoom?(socketId: string, roomName: string): boolean; /** * Get all sockets in room */ getSocketsInRoom?(roomName: string): Promise>; /** * Send private message to specific socket */ sendPrivateMessage?(socketId: string, event: string, data: any): void; /** * Broadcast from socket to all others */ broadcast?(socketId: string, event: string, data: any): void; /** * Emit to namespace */ emitToNamespace?(namespace: string, event: string, data: any): void; /** * Force disconnect socket */ disconnectSocket?(socketId: string, reason?: string): boolean; /** * Subscribe a socket to a specific entity (server-side) */ subscribeToEntity?(socketId: string, uid: string, id: string | number): Promise; /** * Unsubscribe a socket from a specific entity */ unsubscribeFromEntity?(socketId: string, uid: string, id: string | number): EntitySubscriptionResult; /** * Get all entity subscriptions for a socket */ getEntitySubscriptions?(socketId: string): EntitySubscriptionsResult; /** * Emit an event to all clients subscribed to a specific entity */ emitToEntity?(uid: string, id: string | number, event: string, data: any): void; /** * Get all sockets subscribed to a specific entity */ getEntityRoomSockets?(uid: string, id: string | number): Promise>; /** * Cleanup and destroy the Socket.IO instance */ destroy(): Promise; } export interface EntitySubscriptionResult { success: boolean; room?: string; uid?: string; id?: string | number; error?: string; } export interface EntitySubscriptionsResult { success: boolean; subscriptions?: Array<{ uid: string; id: string; room: string }>; error?: string; } /** * Plugin Settings */ export interface PluginSettings { enabled: boolean; cors: { origins: string[] }; connection: { maxConnections: number; pingTimeout: number; pingInterval: number; connectionTimeout: number; }; security: { requireAuthentication: boolean; rateLimiting: { enabled: boolean; maxEventsPerSecond: number; }; ipWhitelist: string[]; ipBlacklist: string[]; }; events: { customEventNames: boolean; includeRelations: boolean; excludeFields: string[]; onlyPublished: boolean; }; rooms: { autoJoinByRole: Record; enablePrivateRooms: boolean; }; rolePermissions: Record; redis: { enabled: boolean; url: string; }; namespaces: { enabled: boolean; list: Record; }; monitoring: { enableConnectionLogging: boolean; enableEventLogging: boolean; maxEventLogSize: number; }; } export interface RolePermission { canConnect: boolean; allowCredentials: boolean; allowedMethods: string[]; contentTypes: Record; } /** * Settings Service */ export interface SettingsService { getSettings(): Promise; setSettings(newSettings: Partial): Promise; getDefaultSettings(): PluginSettings; } /** * Monitoring Service */ export interface MonitoringService { getConnectionStats(): ConnectionStats; getEventStats(): EventStats; getEventLog(limit?: number): EventLogEntry[]; logEvent(eventType: string, data?: any): void; resetStats(): void; sendTestEvent(eventName?: string, data?: any): TestEventResult; } export interface ConnectionStats { connected: number; rooms: Array<{ name: string; members: number; isEntityRoom?: boolean }>; sockets: Array<{ id: string; connected: boolean; rooms: string[]; entitySubscriptions?: Array<{ uid: string; id: string; room: string }>; handshake: { address: string; time: string; query: Record; }; user: any; }>; entitySubscriptions?: { total: number; byContentType: Record; rooms: string[]; }; } export interface EventStats { totalEvents: number; eventsByType: Record; lastReset: number; eventsPerSecond: string | number; } export interface EventLogEntry { timestamp: number; type: string; data: any; } export interface TestEventResult { success: boolean; eventName: string; data: any; recipients: number; } /** * Extend Strapi plugin services */ declare module '@strapi/strapi' { export interface PluginServices { io?: { settings: SettingsService; monitoring: MonitoringService; }; } } export default SocketIO;