npm package: @strapi-community/plugin-io
Description: A plugin for Strapi CMS that provides the ability for Socket IO integration
Author: @ComfortablyCoding
Latest version: 5.3.6

# @strapi-community/plugin-io

> Real-time WebSocket integration for Strapi v5 with Socket.IO

[![NPM Version](https://img.shields.io/npm/v/@strapi-community/plugin-io?style=flat-square)](https://www.npmjs.com/package/@strapi-community/plugin-io)
[![NPM Downloads](https://img.shields.io/npm/dm/@strapi-community/plugin-io?style=flat-square)](https://www.npmjs.com/package/@strapi-community/plugin-io)
[![License](https://img.shields.io/npm/l/@strapi-community/plugin-io?style=flat-square)](https://github.com/strapi-community/strapi-plugin-io/blob/main/LICENSE)
[![Strapi Version](https://img.shields.io/badge/strapi-v5-blueviolet?style=flat-square)](https://strapi.io)

Add real-time capabilities to your Strapi application with WebSocket support. Automatically broadcast content changes, manage user connections, and build live features like chat, notifications, and collaborative editing.

---

## Table of Contents

- [Features](#features)
- [Quick Start](#quick-start)
- [Installation](#installation)
- [Configuration](#configuration)
- [Usage Examples](#usage-examples)
- [Helper Functions](#helper-functions)
- [Authentication](#authentication)
- [Admin Panel](#admin-panel)
- [Monitoring Service](#monitoring-service)
- [TypeScript Support](#typescript-support)
- [Performance](#performance)
- [Migration Guide](#migration-guide)
- [Documentation](#documentation)
- [Contributing](#contributing)
- [License](#license)

---

## Features

### Core Functionality
- **Automatic Real-Time Events** - CRUD operations broadcast automatically to connected clients
- **Entity-Specific Subscriptions** - Subscribe to individual entities for targeted updates
- **Role-Based Access Control** - Built-in permission checks for JWT and API tokens
- **Multi-Client Support** - Handle 2500+ concurrent connections efficiently

### Live Presence (NEW)
- **Real-Time Presence Awareness** - See who else is editing the same content
- **Typing Indicator** - See when someone is typing and in which field
- **Admin Panel Sidebar** - Live presence panel integrated into Content Manager
- **Session-Based Auth** - Secure admin authentication for Socket.IO connections

### Developer Experience
- **Visual Admin Panel** - Configure everything through the Strapi admin interface
- **TypeScript Support** - Full type definitions for IntelliSense
- **Helper Functions** - 12 utility methods for common tasks
- **Comprehensive Documentation** - Detailed guides and examples

### Production Ready
- **Redis Adapter** - Scale horizontally across multiple servers
- **Rate Limiting** - Prevent abuse with configurable limits
- **Monitoring Dashboard** - Live connection stats and event logs
- **Security Features** - IP whitelisting, authentication, input validation

---

## Quick Start

### 1. Install the plugin

```bash
npm install @strapi-community/plugin-io
```

### 2. Enable in your Strapi project

Create or update `config/plugins.js`:

```javascript
module.exports = {
  io: {
    enabled: true,
    config: {
      contentTypes: ['api::article.article'],
      socket: {
        serverOptions: {
          cors: {
            origin: 'http://localhost:3000',
            methods: ['GET', 'POST']
          }
        }
      }
    }
  }
};
```

### 3. Start your Strapi server

```bash
npm run develop
```

### 4. Connect from your frontend

```javascript
import { io } from 'socket.io-client';

const socket = io('http://localhost:1337');

socket.on('article:create', (article) => {
  console.log('New article published:', article);
});

socket.on('article:update', (article) => {
  console.log('Article updated:', article);
});
```

That's it! Your application now has real-time updates.

---

## Installation

### Requirements

- **Node.js**: 18.x - 22.x
- **Strapi**: v5.x
- **npm**: 6.x or higher

### Install the package

```bash
# Using npm
npm install @strapi-community/plugin-io

# Using yarn
yarn add @strapi-community/plugin-io

# Using pnpm
pnpm add @strapi-community/plugin-io
```

---

## Configuration

### Basic Configuration

The simplest setup to get started:

```javascript
// config/plugins.js
module.exports = {
  io: {
    enabled: true,
    config: {
      // Monitor these content types for changes
      contentTypes: [
        'api::article.article',
        'api::comment.comment'
      ]
    }
  }
};
```

### Advanced Configuration

Fine-tune the plugin behavior:

```javascript
// config/plugins.js
module.exports = {
  io: {
    enabled: true,
    config: {
      // Content types with specific actions and populate
      contentTypes: [
        {
          uid: 'api::article.article',
          actions: ['create', 'update'],  // Only these events
          populate: '*'                    // Include all relations
        },
        {
          uid: 'api::comment.comment',
          actions: ['create', 'delete'],
          populate: ['author', 'article']  // Only specific relations
        },
        {
          uid: 'api::order.order',
          populate: {                      // Strapi populate syntax
            customer: { fields: ['name', 'email'] },
            items: { populate: ['product'] }
          }
        }
      ],
      
      // Socket.IO server configuration
      socket: {
        serverOptions: {
          cors: {
            origin: process.env.CLIENT_URL || 'http://localhost:3000',
            methods: ['GET', 'POST'],
            credentials: true
          },
          pingTimeout: 60000,
          pingInterval: 25000
        }
      },
      
      // Custom event handlers
      events: [
        {
          name: 'connection',
          handler: ({ strapi, io }, socket) => {
            strapi.log.info(`Client connected: ${socket.id}`);
          }
        },
        {
          name: 'disconnect',
          handler: ({ strapi, io }, socket) => {
            strapi.log.info(`Client disconnected: ${socket.id}`);
          }
        }
      ],
      
      // Initialization hook
      hooks: {
        init: ({ strapi, $io }) => {
          strapi.log.info('[Socket.IO] Server initialized');
        }
      }
    }
  }
};
```

### Populate Configuration

Include relations in emitted events by adding the `populate` option to content types. When configured, the plugin refetches entities with populated relations after create/update operations.

#### Populate Formats

| Format | Example | Description |
|--------|---------|-------------|
| `'*'` or `true` | `populate: '*'` | Include all relations (1 level deep) |
| String array | `populate: ['author', 'category']` | Only specific relations |
| Object | `populate: { author: { fields: ['name'] } }` | Full Strapi populate syntax |

#### Examples

```javascript
contentTypes: [
  // All relations with wildcard
  { uid: 'api::article.article', populate: '*' },
  
  // Specific relations only
  { 
    uid: 'api::comment.comment', 
    populate: ['author', 'post'] 
  },
  
  // Strapi populate syntax with field selection
  { 
    uid: 'api::order.order', 
    populate: { 
      customer: { fields: ['username', 'email'] },
      items: { 
        populate: { product: { fields: ['name', 'price'] } }
      }
    } 
  },
  
  // No populate (only base fields)
  { uid: 'api::log.log' }  // populate not set = no relations
]
```

> **Note**: The `populate` option only affects `create` and `update` events. Delete events always contain minimal data (id, documentId) since the entity no longer exists.

### Sensitive Fields Protection

The plugin automatically removes sensitive fields from all emitted data for security. This works in addition to Strapi's built-in `private: true` field filtering.

#### Default Blocked Fields

The following fields are **always removed** from emitted data:

- `password`, `salt`, `hash`
- `resetPasswordToken`, `confirmationToken`
- `refreshToken`, `accessToken`, `token`
- `secret`, `apiKey`, `api_key`
- `privateKey`, `private_key`

#### Custom Sensitive Fields

Add your own sensitive fields to the block list:

```javascript
// config/plugins.js
module.exports = {
  io: {
    enabled: true,
    config: {
      contentTypes: ['api::user.user'],
      
      // Additional fields to never emit
      sensitiveFields: [
        'creditCard',
        'ssn',
        'socialSecurityNumber',
        'bankAccount',
        'internalNotes'
      ]
    }
  }
};
```

#### How It Works

1. **Schema-level filtering**: Strapi's `sanitize.contentAPI` removes `private: true` fields
2. **Blocklist filtering**: Plugin removes all sensitive field names recursively
3. **Applies to all emits**: Both `emit()` and `raw()` methods are protected

```javascript
// Even with populate, sensitive fields are stripped
contentTypes: [
  {
    uid: 'api::user.user',
    populate: '*'  // Relations included, but passwords etc. still removed
  }
]
```

> **Security Note**: Fields are matched case-insensitively and also partial matches work (e.g., `apiKey` blocks `myApiKey`, `user_api_key`, etc.)

### Environment Variables

Recommended environment-based configuration:

```javascript
// config/plugins.js
module.exports = ({ env }) => ({
  io: {
    enabled: env.bool('SOCKET_IO_ENABLED', true),
    config: {
      contentTypes: env.json('SOCKET_IO_CONTENT_TYPES', []),
      socket: {
        serverOptions: {
          cors: {
            origin: env('CLIENT_URL', 'http://localhost:3000')
          }
        }
      }
    }
  }
});
```

```env
# .env
SOCKET_IO_ENABLED=true
CLIENT_URL=https://your-app.com
SOCKET_IO_CONTENT_TYPES=["api::article.article","api::comment.comment"]
```

---

## Usage Examples

### Server-Side Usage

Access the Socket.IO instance anywhere in your Strapi application:

```javascript
// In a controller, service, or lifecycle
const io = strapi.$io;

// Emit to all connected clients
strapi.$io.raw({
  event: 'notification',
  data: {
    message: 'Server maintenance in 5 minutes',
    type: 'warning'
  }
});

// Send private message to a specific socket
strapi.$io.sendPrivateMessage(socketId, 'order:updated', {
  orderId: 123,
  status: 'shipped'
});

// Emit to all clients in a room
strapi.$io.server.to('admin-room').emit('dashboard:update', {
  activeUsers: 42,
  revenue: 15000
});

// Emit to a specific namespace
strapi.$io.emitToNamespace('admin', 'alert', {
  message: 'New user registered'
});
```

### Client-Side Usage

#### Basic Connection

```javascript
import { io } from 'socket.io-client';

const socket = io('http://localhost:1337');

socket.on('connect', () => {
  console.log('Connected to server');
});

socket.on('disconnect', () => {
  console.log('Disconnected from server');
});

// Listen for content type events
socket.on('article:create', (data) => {
  console.log('New article:', data);
});

socket.on('article:update', (data) => {
  console.log('Article updated:', data);
});

socket.on('article:delete', (data) => {
  console.log('Article deleted:', data.documentId);
});
```

#### With React

```jsx
import { useEffect, useState } from 'react';
import { io } from 'socket.io-client';

function ArticlesList() {
  const [articles, setArticles] = useState([]);
  
  useEffect(() => {
    const socket = io('http://localhost:1337');
    
    // Listen for new articles
    socket.on('article:create', (article) => {
      setArticles(prev => [article, ...prev]);
    });
    
    // Listen for updates
    socket.on('article:update', (article) => {
      setArticles(prev => 
        prev.map(a => a.documentId === article.documentId ? article : a)
      );
    });
    
    // Listen for deletions
    socket.on('article:delete', (data) => {
      setArticles(prev => 
        prev.filter(a => a.documentId !== data.documentId)
      );
    });
    
    return () => socket.disconnect();
  }, []);
  
  return (
    <div>
      {articles.map(article => (
        <div key={article.documentId}>{article.title}</div>
      ))}
    </div>
  );
}
```

#### With Vue 3

```vue
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { io } from 'socket.io-client';

const articles = ref([]);
let socket;

onMounted(() => {
  socket = io('http://localhost:1337');
  
  socket.on('article:create', (article) => {
    articles.value = [article, ...articles.value];
  });
  
  socket.on('article:update', (article) => {
    const index = articles.value.findIndex(a => a.documentId === article.documentId);
    if (index !== -1) {
      articles.value[index] = article;
    }
  });
  
  socket.on('article:delete', (data) => {
    articles.value = articles.value.filter(a => a.documentId !== data.documentId);
  });
});

onUnmounted(() => {
  socket?.disconnect();
});
</script>

<template>
  <div v-for="article in articles" :key="article.documentId">
    {{ article.title }}
  </div>
</template>
```

### Entity-Specific Subscriptions

Subscribe to updates for specific entities only:

```javascript
// Client-side: Subscribe to a specific article
socket.emit('subscribe-entity', {
  uid: 'api::article.article',
  id: 123
}, (response) => {
  if (response.success) {
    console.log('Subscribed to article 123');
  }
});

// Now you only receive updates for article 123
socket.on('article:update', (data) => {
  console.log('Article 123 was updated:', data);
});

// Unsubscribe when done
socket.emit('unsubscribe-entity', {
  uid: 'api::article.article',
  id: 123
});
```

**Benefits:**
- Reduced bandwidth - only receive relevant updates
- Better performance - less client-side processing
- Built-in permission checks - respects user roles

### Room Management

Organize connections into rooms:

```javascript
// Server-side: Add socket to a room
strapi.$io.joinRoom(socketId, 'premium-users');

// Get all sockets in a room
const sockets = await strapi.$io.getSocketsInRoom('premium-users');
console.log(`${sockets.length} premium users online`);

// Broadcast to a specific room
strapi.$io.server.to('premium-users').emit('exclusive-offer', {
  discount: 20,
  expiresIn: '24h'
});

// Remove socket from a room
strapi.$io.leaveRoom(socketId, 'premium-users');

// Disconnect a specific socket
strapi.$io.disconnectSocket(socketId, 'Kicked by admin');
```

---

## Helper Functions

The plugin provides 12 utility functions available on `strapi.$io`:

### Room Management

#### `joinRoom(socketId, roomName)`
Add a socket to a room.

```javascript
const success = strapi.$io.joinRoom(socketId, 'premium-users');
// Returns: true if socket found and joined, false otherwise
```

#### `leaveRoom(socketId, roomName)`
Remove a socket from a room.

```javascript
const success = strapi.$io.leaveRoom(socketId, 'premium-users');
// Returns: true if socket found and left, false otherwise
```

#### `getSocketsInRoom(roomName)`
Get all sockets in a specific room.

```javascript
const sockets = await strapi.$io.getSocketsInRoom('premium-users');
// Returns: [{ id: 'socket-id', user: { username: 'john' } }, ...]
```

### Messaging

#### `sendPrivateMessage(socketId, event, data)`
Send a message to a specific socket.

```javascript
strapi.$io.sendPrivateMessage(socketId, 'order:shipped', {
  orderId: 123,
  trackingNumber: 'ABC123'
});
```

#### `broadcast(socketId, event, data)`
Broadcast from a socket to all other connected clients.

```javascript
strapi.$io.broadcast(socketId, 'user:typing', {
  username: 'john',
  channel: 'general'
});
```

#### `emitToNamespace(namespace, event, data)`
Emit an event to all clients in a namespace.

```javascript
strapi.$io.emitToNamespace('admin', 'alert', {
  message: 'New user registered',
  level: 'info'
});
```

### Connection Management

#### `disconnectSocket(socketId, reason)`
Force disconnect a specific socket.

```javascript
const success = strapi.$io.disconnectSocket(socketId, 'Session expired');
// Returns: true if socket found and disconnected, false otherwise
```

### Entity Subscriptions

#### `subscribeToEntity(socketId, uid, id)`
Subscribe a socket to a specific entity (server-side).

```javascript
const result = await strapi.$io.subscribeToEntity(socketId, 'api::article.article', 123);
// Returns: { success: true, room: 'api::article.article:123', uid, id }
// or: { success: false, error: 'Socket not found' }
```

#### `unsubscribeFromEntity(socketId, uid, id)`
Unsubscribe a socket from a specific entity.

```javascript
const result = strapi.$io.unsubscribeFromEntity(socketId, 'api::article.article', 123);
// Returns: { success: true, room: 'api::article.article:123', uid, id }
```

#### `getEntitySubscriptions(socketId)`
Get all entity subscriptions for a socket.

```javascript
const result = strapi.$io.getEntitySubscriptions(socketId);
// Returns: { 
//   success: true, 
//   subscriptions: [
//     { uid: 'api::article.article', id: '123', room: 'api::article.article:123' }
//   ]
// }
```

#### `emitToEntity(uid, id, event, data)`
Emit an event to all clients subscribed to a specific entity.

```javascript
strapi.$io.emitToEntity('api::article.article', 123, 'article:commented', {
  commentId: 456,
  author: 'jane'
});
```

#### `getEntityRoomSockets(uid, id)`
Get all sockets subscribed to a specific entity.

```javascript
const sockets = await strapi.$io.getEntityRoomSockets('api::article.article', 123);
// Returns: [{ id: 'socket-id', user: { username: 'john' } }, ...]
```

---

## Authentication

The plugin supports multiple authentication strategies.

### Public Access (No Authentication)

```javascript
const socket = io('http://localhost:1337');
// No auth - placed in 'Public' role room
```

### JWT Authentication (Users & Permissions)

```javascript
// 1. Get JWT token from login
const response = await fetch('http://localhost:1337/api/auth/local', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    identifier: 'user@example.com',
    password: 'password123'
  })
});

const { jwt } = await response.json();

// 2. Connect with JWT
const socket = io('http://localhost:1337', {
  auth: {
    strategy: 'jwt',
    token: jwt
  }
});

// User is placed in their role room (e.g., 'Authenticated')
```

### API Token Authentication

```javascript
// 1. Create API token in Strapi Admin:
//    Settings > Global Settings > API Tokens > Create new token

// 2. Connect with API token
const socket = io('http://localhost:1337', {
  auth: {
    strategy: 'api-token',
    token: 'your-api-token-here'
  }
});
```

### Permission Enforcement

Events are automatically filtered based on the user's role:

```javascript
// Authenticated user with 'Editor' role
socket.on('article:create', (data) => {
  // Only receives events for content types they have permission to access
});
```

Configure permissions in the Strapi admin panel:
1. Go to **Settings > Users & Permissions > Roles**
2. Select a role (e.g., "Authenticated")
3. Configure Socket.IO permissions per content type

---

## Security

The plugin implements multiple security layers to protect your real-time connections.

### Admin Session Tokens

For admin panel connections (Live Presence), the plugin uses secure session tokens:

```
+------------------+      +------------------+      +------------------+
|  Admin Browser   | ---> |  Session Endpoint| ---> |   Socket.IO      |
|  (Strapi Admin)  |      |  /io/presence/   |      |   Server         |
+------------------+      +------------------+      +------------------+
        |                         |                         |
        | 1. Request session      |                         |
        | (Admin JWT in header)   |                         |
        +------------------------>|                         |
        |                         |                         |
        | 2. Return session token |                         |
        | (UUID, 10 min TTL)      |                         |
        |<------------------------+                         |
        |                         |                         |
        | 3. Connect Socket.IO    |                         |
        | (Session token in auth) |                         |
        +-------------------------------------------------->|
        |                         |                         |
        | 4. Validate & connect   |                         |
        |<--------------------------------------------------+
```

**Security Features:**
- **Token Hashing**: Tokens stored as SHA-256 hashes (plaintext never persisted)
- **Short TTL**: 10-minute expiration with automatic refresh at 70%
- **Usage Limits**: Max 10 reconnects per token to prevent replay attacks
- **Rate Limiting**: 30-second cooldown between token requests
- **Minimal Data**: Only essential user info stored (ID, firstname, lastname)

### Rate Limiting

Prevent abuse with configurable rate limits:

```javascript
// In config/plugins.js
module.exports = {
  io: {
    enabled: true,
    config: {
      security: {
        rateLimit: {
          enabled: true,
          maxEventsPerSecond: 10,    // Max events per socket per second
          maxConnectionsPerIp: 50    // Max connections from single IP
        }
      }
    }
  }
};
```

### IP Whitelisting/Blacklisting

Restrict access by IP address:

```javascript
// In config/plugins.js
module.exports = {
  io: {
    enabled: true,
    config: {
      security: {
        ipWhitelist: ['192.168.1.0/24', '10.0.0.1'],  // Only these IPs allowed
        ipBlacklist: ['203.0.113.50'],                 // These IPs blocked
        requireAuthentication: true                    // Require JWT/API token
      }
    }
  }
};
```

### Security Monitoring API

Monitor active sessions via admin API:

```bash
# Get session statistics
GET /io/security/sessions
Authorization: Bearer <admin-jwt>

# Response:
{
  "data": {
    "activeSessions": 5,
    "expiringSoon": 1,
    "activeSocketConnections": 3,
    "sessionTTL": 600000,
    "refreshCooldown": 30000
  }
}

# Force logout a user (invalidate all their sessions)
POST /io/security/invalidate/:userId
Authorization: Bearer <admin-jwt>

# Response:
{
  "data": {
    "userId": 1,
    "invalidatedSessions": 2,
    "message": "Successfully invalidated 2 session(s)"
  }
}
```

### Best Practices

1. **Always use HTTPS** in production for encrypted WebSocket connections
2. **Enable authentication** for sensitive content types
3. **Configure CORS** to only allow your frontend domains
4. **Monitor connections** via the admin dashboard
5. **Set reasonable rate limits** based on your use case
6. **Review access logs** periodically for suspicious activity

---

## Admin Panel

The plugin provides a full admin interface for configuration and monitoring.

### Dashboard Widgets

> **Requires Strapi v5.13+**

After installation, live statistics widgets appear on your Strapi admin homepage:

#### Socket.IO Statistics Widget

![Socket.IO Statistics Widget](./pics/widget.png)

**Shows:**
- Live connection status (pulsing indicator when active)
- Active connections count
- Active rooms count
- Events per second
- Total events processed since startup

Updates automatically every 5 seconds.

#### Who's Online Widget

![Who's Online Widget](./pics/whoisonlinewidget.png)

**Shows:**
- List of currently online admin users
- User avatars with role badges
- Online status and last activity
- Quick access to view all activity

### Settings Page

Navigate to **Settings > Socket.IO > Settings** for visual configuration:

**Path:** `/admin/settings/io/settings`

![Socket.IO Settings](./pics/settings.png)

#### General Settings
- Enable/disable the plugin
- Configure CORS origins
- Set server options (ping timeout, etc.)

#### Content Types
- Enable automatic events for content types per role
- Select specific actions (create, update, delete)
- Configure role-based permissions

#### Events
- Include relations in events (`includeRelations`)
- Custom event names
- Exclude specific fields
- Only published content mode

#### Security
- Require authentication
- Rate limiting configuration
- IP whitelisting/blacklisting
- Input validation rules

#### Rooms
- Auto-join by role configuration
- Enable/disable private rooms

#### Advanced
- Configure namespaces
- Redis adapter settings for scaling
- Entity subscription limits

### Monitoring Page

Navigate to **Settings > Socket.IO > Monitoring** for live statistics:

**Path:** `/admin/settings/io/monitoring`

![Monitoring & Logging](./pics/monitoringSettings.png)

- View active connections with user details
- See event logs in real-time
- Monitor performance metrics (events/second)
- View entity subscription statistics
- Send test events
- Reset statistics

### Live Presence Panel

When editing content in the Content Manager, a **Live Presence** panel appears in the sidebar showing:

- **Connection Status** - Live indicator showing real-time sync is active
- **Active Editors** - List of other users editing the same content
- **Typing Indicator** - Shows when someone is typing and in which field

![Live Presence Panel](./pics/livepresenceindi.png)

**How It Works:**

1. When you open a content entry, the panel connects via Socket.IO
2. Other editors on the same entry appear in the panel
3. Typing in any field broadcasts a typing indicator to others
4. When you leave, others are notified

---

## Monitoring Service

Access monitoring data programmatically via the monitoring service:

```javascript
const monitoringService = strapi.plugin('io').service('monitoring');
```

### Available Methods

#### `getConnectionStats()`
Get current connection statistics.

```javascript
const stats = monitoringService.getConnectionStats();
// Returns:
// {
//   connected: 42,
//   rooms: [
//     { name: 'Authenticated', members: 35, isEntityRoom: false },
//     { name: 'api::article.article:123', members: 5, isEntityRoom: true }
//   ],
//   sockets: [
//     {
//       id: 'abc123',
//       connected: true,
//       rooms: ['Authenticated'],
//       entitySubscriptions: [{ uid: 'api::article.article', id: '123', room: '...' }],
//       handshake: { address: '::1', time: '...', query: {} },
//       user: { id: 1, username: 'john' }
//     }
//   ],
//   entitySubscriptions: {
//     total: 15,
//     byContentType: { 'api::article.article': 10, 'api::comment.comment': 5 },
//     rooms: ['api::article.article:123', ...]
//   }
// }
```

#### `getEventStats()`
Get event statistics.

```javascript
const stats = monitoringService.getEventStats();
// Returns:
// {
//   totalEvents: 1234,
//   eventsByType: { 'create': 500, 'update': 600, 'delete': 134 },
//   lastReset: 1703123456789,
//   eventsPerSecond: '2.50'
// }
```

#### `getEventLog(limit)`
Get recent event log entries.

```javascript
const logs = monitoringService.getEventLog(50);
// Returns:
// [
//   { timestamp: 1703123456789, type: 'create', data: {...} },
//   { timestamp: 1703123456790, type: 'connect', data: {...} }
// ]
```

#### `logEvent(type, data)`
Manually log an event.

```javascript
monitoringService.logEvent('custom-event', {
  action: 'user-action',
  userId: 123
});
```

#### `resetStats()`
Reset all statistics and clear event log.

```javascript
monitoringService.resetStats();
```

#### `sendTestEvent(eventName, data)`
Send a test event to all connected clients.

```javascript
const result = monitoringService.sendTestEvent('test', { message: 'Hello!' });
// Returns:
// {
//   success: true,
//   eventName: 'test',
//   data: { message: 'Hello!', timestamp: 1703123456789, test: true },
//   recipients: 42
// }
```

### Use Cases

**Custom Analytics Dashboard:**

```javascript
// In a custom controller
async getAnalytics(ctx) {
  const monitoring = strapi.plugin('io').service('monitoring');
  
  ctx.body = {
    connections: monitoring.getConnectionStats(),
    events: monitoring.getEventStats(),
    recentActivity: monitoring.getEventLog(10)
  };
}
```

**Health Check Endpoint:**

```javascript
async healthCheck(ctx) {
  const monitoring = strapi.plugin('io').service('monitoring');
  const stats = monitoring.getConnectionStats();
  
  ctx.body = {
    status: 'healthy',
    websocket: {
      connected: stats.connected,
      rooms: stats.rooms.length
    }
  };
}
```

---

## TypeScript Support

Full TypeScript definitions are included for excellent IDE support.

### Import Types

```typescript
import type { 
  SocketIO, 
  SocketIOConfig,
  EmitOptions,
  RawEmitOptions,
  PluginSettings,
  MonitoringService,
  SettingsService,
  ConnectionStats,
  EventStats
} from '@strapi-community/plugin-io/types';
```

### Configuration Example

```typescript
// config/plugins.ts
import type { SocketIOConfig } from '@strapi-community/plugin-io/types';

export default {
  io: {
    enabled: true,
    config: {
      contentTypes: [
        {
          uid: 'api::article.article',
          actions: ['create', 'update', 'delete']
        }
      ],
      socket: {
        serverOptions: {
          cors: {
            origin: process.env.CLIENT_URL || 'http://localhost:3000',
            methods: ['GET', 'POST']
          }
        }
      }
    } satisfies SocketIOConfig
  }
};
```

### Usage Example

```typescript
// In your Strapi code
import type { SocketIO } from '@strapi-community/plugin-io/types';

// Type-safe access
const io: SocketIO = strapi.$io;

// All methods have full IntelliSense
await io.emit({
  event: 'create',
  schema: strapi.contentTypes['api::article.article'],
  data: { title: 'New Article' }
});

// Helper functions are typed
const sockets = await io.getSocketsInRoom('premium-users');
io.sendPrivateMessage(sockets[0].id, 'welcome', { message: 'Hello!' });
```

---

## Performance

The plugin is optimized for production environments.

### Benchmarks

- **Concurrent Connections**: 2500+ simultaneous connections
- **Memory Usage**: ~17KB per connection
- **Event Throughput**: 10,000+ events/second
- **Latency**: <10ms for local broadcasts

### Optimizations

#### Intelligent Caching
Role and permission data is cached for 5 minutes, reducing database queries by up to 90%.

#### Debouncing
Bulk operations are automatically debounced to prevent event flooding during data imports.

#### Parallel Processing
All event emissions are processed in parallel for maximum throughput.

#### Connection Pooling
Efficient connection management with automatic cleanup of stale connections.

### Production Configuration

```javascript
// config/plugins.js (production)
module.exports = ({ env }) => ({
  io: {
    enabled: true,
    config: {
      contentTypes: env.json('SOCKET_IO_CONTENT_TYPES'),
      
      socket: {
        serverOptions: {
          cors: {
            origin: env('CLIENT_URL'),
            credentials: true
          },
          // Optimize for production
          pingTimeout: 60000,
          pingInterval: 25000,
          maxHttpBufferSize: 1e6,
          transports: ['websocket', 'polling']
        }
      },
      
      // Use Redis for horizontal scaling
      hooks: {
        init: async ({ strapi, $io }) => {
          const { createAdapter } = require('@socket.io/redis-adapter');
          const { createClient } = require('redis');
          
          const pubClient = createClient({ url: env('REDIS_URL') });
          const subClient = pubClient.duplicate();
          
          await Promise.all([pubClient.connect(), subClient.connect()]);
          
          $io.server.adapter(createAdapter(pubClient, subClient));
          
          strapi.log.info('[Socket.IO] Redis adapter connected');
        }
      }
    }
  }
});
```

---

## Migration Guide

### From v2 (Strapi v4) to v5 (Strapi v5)

**Good news:** The API is 100% compatible! Most projects migrate in under 1 hour.

#### Quick Migration Steps

1. **Update Strapi to v5**
   ```bash
   npm install @strapi/strapi@5 @strapi/plugin-users-permissions@5
   ```

2. **Update the plugin**
   ```bash
   npm uninstall strapi-plugin-io
   npm install @strapi-community/plugin-io@latest
   ```

3. **Test your application**
   ```bash
   npm run develop
   ```

Your configuration stays the same - no code changes needed!

#### What Changed

- **Package name**: `strapi-plugin-io` -> `@strapi-community/plugin-io`
- **Package structure**: Uses new Strapi v5 Plugin SDK
- **Dependencies**: Updated to Strapi v5 peer dependencies
- **Build process**: Optimized build with modern tooling

#### What Stayed the Same

- All API methods work identically
- Configuration format unchanged
- Client-side code works as-is
- Same helper functions
- Same event format

For detailed migration instructions, see [docs/guide/migration.md](./docs/guide/migration.md).

---

## Documentation

### Official Documentation

- **[Online Documentation](https://strapi-plugin-io.netlify.app/)** - Complete interactive docs
- **[API Reference](./docs/api/io-class.md)** - All methods and properties
- **[Configuration Guide](./docs/api/plugin-config.md)** - Detailed configuration options
- **[Usage Examples](./docs/examples/)** - Real-world use cases
- **[Migration Guide](./docs/guide/migration.md)** - Upgrade from v4 to v5

### Guides

- **[Getting Started](./docs/guide/getting-started.md)** - Step-by-step setup
- **[Widget Guide](./docs/guide/widget.md)** - Dashboard widget configuration

### Examples

- **[Content Types](./docs/examples/content-types.md)** - Automatic CRUD events
- **[Custom Events](./docs/examples/events.md)** - Server-client communication
- **[Hooks & Adapters](./docs/examples/hooks.md)** - Redis, MongoDB integration

---

## Related Plugins

Build complete real-time applications with these complementary Strapi v5 plugins:

### [Magic-Mail](https://github.com/Schero94/Magic-Mail)
Enterprise email management with OAuth 2.0 support. Perfect for sending transactional emails triggered by Socket.IO events.

**Use case:** Send email notifications when real-time events occur.

### [Magic-Sessionmanager](https://github.com/Schero94/Magic-Sessionmanager)
Advanced session tracking and monitoring. Track Socket.IO connections, monitor active users, and analyze session patterns.

**Use case:** Monitor who's connected to your WebSocket server in real-time.

### [Magicmark](https://github.com/Schero94/Magicmark)
Bookmark management system with real-time sync. Share bookmarks instantly with your team using Socket.IO integration.

**Use case:** Collaborative bookmark management with live updates.

---

## Contributing

We welcome contributions! Here's how you can help:

### Report Bugs

Found a bug? [Open an issue](https://github.com/strapi-community/strapi-plugin-io/issues) with:
- Strapi version
- Plugin version
- Steps to reproduce
- Expected vs actual behavior

### Suggest Features

Have an idea? [Start a discussion](https://github.com/strapi-community/strapi-plugin-io/discussions) to:
- Describe the feature
- Explain the use case
- Discuss implementation

### Submit Pull Requests

1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

### Development Setup

```bash
# Clone the repository
git clone https://github.com/strapi-community/strapi-plugin-io.git
cd strapi-plugin-io

# Install dependencies
npm install

# Build the plugin
npm run build

# Run in watch mode
npm run watch

# Verify structure
npm run verify
```

---

## Support

- **Documentation**: https://strapi-plugin-io.netlify.app/
- **GitHub Issues**: https://github.com/strapi-community/strapi-plugin-io/issues
- **GitHub Discussions**: https://github.com/strapi-community/strapi-plugin-io/discussions
- **Strapi Discord**: https://discord.strapi.io

---

## License

[MIT License](./LICENSE)

Copyright (c) 2024 Strapi Community

---

## Credits

**Original Authors:**
- [@ComfortablyCoding](https://github.com/ComfortablyCoding)
- [@hrdunn](https://github.com/hrdunn)

**Enhanced and Maintained by:**
- [@Schero94](https://github.com/Schero94)

**Maintained until:** December 2026

---

## Changelog

### v5.1.0 (Latest)
- **Live Presence System** - Real-time presence awareness in Content Manager
- **Typing Indicator** - See when others are typing and in which field
- **Admin Panel Sidebar** - Live presence panel integrated into edit view
- **Admin Session Authentication** - Secure session tokens for Socket.IO
- **Admin JWT Strategy** - New authentication strategy for admin users
- **Enhanced Security** - Token hashing (SHA-256), usage limits, rate limiting
- **Automatic Token Refresh** - Tokens auto-refresh at 70% of TTL
- **Security Monitoring API** - Session stats and force-logout endpoints

### v5.0.0
- Strapi v5 support
- Package renamed to `@strapi-community/plugin-io`
- Enhanced TypeScript support
- Entity-specific subscriptions
- 12 helper functions
- Admin panel with monitoring dashboard
- Performance optimizations
- Updated documentation

For full changelog, see [CHANGELOG.md](./CHANGELOG.md).

---

<div align="center">
  
**[Documentation](https://strapi-plugin-io.netlify.app/)** | 
**[API Reference](./docs/api/io-class.md)** | 
**[Examples](./docs/examples/)** | 
**[GitHub](https://github.com/strapi-community/strapi-plugin-io)**

Made with love for the Strapi community

</div>