{"id":35165440,"url":"https://github.com/strapi-community/plugin-io","last_synced_at":"2026-04-15T22:01:35.751Z","repository":{"id":37878161,"uuid":"472955771","full_name":"strapi-community/plugin-io","owner":"strapi-community","description":"A plugin for Socket IO integration with Strapi CMS.","archived":false,"fork":false,"pushed_at":"2026-03-03T22:24:59.000Z","size":4070,"stargazers_count":56,"open_issues_count":0,"forks_count":30,"subscribers_count":5,"default_branch":"main","last_synced_at":"2026-03-23T02:38:31.271Z","etag":null,"topics":["io","socket-io","socketio","strapi","strapi-cms","strapi-plugin"],"latest_commit_sha":null,"homepage":"https://strapi-plugin-io.netlify.app","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/strapi-community.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-03-22T22:20:56.000Z","updated_at":"2026-03-03T22:25:00.000Z","dependencies_parsed_at":"2023-02-08T07:16:33.386Z","dependency_job_id":"41f89075-f6eb-4815-8ee0-79c6a2ab723e","html_url":"https://github.com/strapi-community/plugin-io","commit_stats":{"total_commits":25,"total_committers":4,"mean_commits":6.25,"dds":"0.16000000000000003","last_synced_commit":"25b9141d2bbe1d447e9e3efec12cc5a955823489"},"previous_names":["comfortablycoding/strapi-plugin-io","strapi-community/plugin-io","strapi-community/strapi-plugin-io"],"tags_count":30,"template":false,"template_full_name":null,"purl":"pkg:github/strapi-community/plugin-io","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strapi-community%2Fplugin-io","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strapi-community%2Fplugin-io/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strapi-community%2Fplugin-io/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strapi-community%2Fplugin-io/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/strapi-community","download_url":"https://codeload.github.com/strapi-community/plugin-io/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strapi-community%2Fplugin-io/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31857625,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-15T15:24:51.572Z","status":"ssl_error","status_checked_at":"2026-04-15T15:24:39.138Z","response_time":63,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["io","socket-io","socketio","strapi","strapi-cms","strapi-plugin"],"created_at":"2025-12-28T19:29:18.699Z","updated_at":"2026-04-15T22:01:35.738Z","avatar_url":"https://github.com/strapi-community.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @strapi-community/plugin-io\n\n\u003e Real-time WebSocket integration for Strapi v5 with Socket.IO\n\n[![NPM Version](https://img.shields.io/npm/v/@strapi-community/plugin-io?style=flat-square)](https://www.npmjs.com/package/@strapi-community/plugin-io)\n[![NPM Downloads](https://img.shields.io/npm/dm/@strapi-community/plugin-io?style=flat-square)](https://www.npmjs.com/package/@strapi-community/plugin-io)\n[![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)\n[![Strapi Version](https://img.shields.io/badge/strapi-v5-blueviolet?style=flat-square)](https://strapi.io)\n\nAdd 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.\n\n---\n\n## Table of Contents\n\n- [Features](#features)\n- [Quick Start](#quick-start)\n- [Installation](#installation)\n- [Configuration](#configuration)\n- [Usage Examples](#usage-examples)\n- [Helper Functions](#helper-functions)\n- [Authentication](#authentication)\n- [Admin Panel](#admin-panel)\n- [Monitoring Service](#monitoring-service)\n- [TypeScript Support](#typescript-support)\n- [Performance](#performance)\n- [Migration Guide](#migration-guide)\n- [Documentation](#documentation)\n- [Contributing](#contributing)\n- [License](#license)\n\n---\n\n## Features\n\n### Core Functionality\n- **Automatic Real-Time Events** - CRUD operations broadcast automatically to connected clients\n- **Entity-Specific Subscriptions** - Subscribe to individual entities for targeted updates\n- **Role-Based Access Control** - Built-in permission checks for JWT and API tokens\n- **Multi-Client Support** - Handle 2500+ concurrent connections efficiently\n\n### Live Presence (NEW)\n- **Real-Time Presence Awareness** - See who else is editing the same content\n- **Typing Indicator** - See when someone is typing and in which field\n- **Admin Panel Sidebar** - Live presence panel integrated into Content Manager\n- **Session-Based Auth** - Secure admin authentication for Socket.IO connections\n\n### Developer Experience\n- **Visual Admin Panel** - Configure everything through the Strapi admin interface\n- **TypeScript Support** - Full type definitions for IntelliSense\n- **Helper Functions** - 12 utility methods for common tasks\n- **Comprehensive Documentation** - Detailed guides and examples\n\n### Production Ready\n- **Redis Adapter** - Scale horizontally across multiple servers\n- **Rate Limiting** - Prevent abuse with configurable limits\n- **Monitoring Dashboard** - Live connection stats and event logs\n- **Security Features** - IP whitelisting, authentication, input validation\n\n---\n\n## Quick Start\n\n### 1. Install the plugin\n\n```bash\nnpm install @strapi-community/plugin-io\n```\n\n### 2. Enable in your Strapi project\n\nCreate or update `config/plugins.js`:\n\n```javascript\nmodule.exports = {\n  io: {\n    enabled: true,\n    config: {\n      contentTypes: ['api::article.article'],\n      socket: {\n        serverOptions: {\n          cors: {\n            origin: 'http://localhost:3000',\n            methods: ['GET', 'POST']\n          }\n        }\n      }\n    }\n  }\n};\n```\n\n### 3. Start your Strapi server\n\n```bash\nnpm run develop\n```\n\n### 4. Connect from your frontend\n\n```javascript\nimport { io } from 'socket.io-client';\n\nconst socket = io('http://localhost:1337');\n\nsocket.on('article:create', (article) =\u003e {\n  console.log('New article published:', article);\n});\n\nsocket.on('article:update', (article) =\u003e {\n  console.log('Article updated:', article);\n});\n```\n\nThat's it! Your application now has real-time updates.\n\n---\n\n## Installation\n\n### Requirements\n\n- **Node.js**: 18.x - 22.x\n- **Strapi**: v5.x\n- **npm**: 6.x or higher\n\n### Install the package\n\n```bash\n# Using npm\nnpm install @strapi-community/plugin-io\n\n# Using yarn\nyarn add @strapi-community/plugin-io\n\n# Using pnpm\npnpm add @strapi-community/plugin-io\n```\n\n---\n\n## Configuration\n\n### Basic Configuration\n\nThe simplest setup to get started:\n\n```javascript\n// config/plugins.js\nmodule.exports = {\n  io: {\n    enabled: true,\n    config: {\n      // Monitor these content types for changes\n      contentTypes: [\n        'api::article.article',\n        'api::comment.comment'\n      ]\n    }\n  }\n};\n```\n\n### Advanced Configuration\n\nFine-tune the plugin behavior:\n\n```javascript\n// config/plugins.js\nmodule.exports = {\n  io: {\n    enabled: true,\n    config: {\n      // Content types with specific actions and populate\n      contentTypes: [\n        {\n          uid: 'api::article.article',\n          actions: ['create', 'update'],  // Only these events\n          populate: '*'                    // Include all relations\n        },\n        {\n          uid: 'api::comment.comment',\n          actions: ['create', 'delete'],\n          populate: ['author', 'article']  // Only specific relations\n        },\n        {\n          uid: 'api::order.order',\n          populate: {                      // Strapi populate syntax\n            customer: { fields: ['name', 'email'] },\n            items: { populate: ['product'] }\n          }\n        }\n      ],\n      \n      // Socket.IO server configuration\n      socket: {\n        serverOptions: {\n          cors: {\n            origin: process.env.CLIENT_URL || 'http://localhost:3000',\n            methods: ['GET', 'POST'],\n            credentials: true\n          },\n          pingTimeout: 60000,\n          pingInterval: 25000\n        }\n      },\n      \n      // Custom event handlers\n      events: [\n        {\n          name: 'connection',\n          handler: ({ strapi, io }, socket) =\u003e {\n            strapi.log.info(`Client connected: ${socket.id}`);\n          }\n        },\n        {\n          name: 'disconnect',\n          handler: ({ strapi, io }, socket) =\u003e {\n            strapi.log.info(`Client disconnected: ${socket.id}`);\n          }\n        }\n      ],\n      \n      // Initialization hook\n      hooks: {\n        init: ({ strapi, $io }) =\u003e {\n          strapi.log.info('[Socket.IO] Server initialized');\n        }\n      }\n    }\n  }\n};\n```\n\n### Populate Configuration\n\nInclude 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.\n\n#### Populate Formats\n\n| Format | Example | Description |\n|--------|---------|-------------|\n| `'*'` or `true` | `populate: '*'` | Include all relations (1 level deep) |\n| String array | `populate: ['author', 'category']` | Only specific relations |\n| Object | `populate: { author: { fields: ['name'] } }` | Full Strapi populate syntax |\n\n#### Examples\n\n```javascript\ncontentTypes: [\n  // All relations with wildcard\n  { uid: 'api::article.article', populate: '*' },\n  \n  // Specific relations only\n  { \n    uid: 'api::comment.comment', \n    populate: ['author', 'post'] \n  },\n  \n  // Strapi populate syntax with field selection\n  { \n    uid: 'api::order.order', \n    populate: { \n      customer: { fields: ['username', 'email'] },\n      items: { \n        populate: { product: { fields: ['name', 'price'] } }\n      }\n    } \n  },\n  \n  // No populate (only base fields)\n  { uid: 'api::log.log' }  // populate not set = no relations\n]\n```\n\n\u003e **Note**: The `populate` option only affects `create` and `update` events. Delete events always contain minimal data (id, documentId) since the entity no longer exists.\n\n### Sensitive Fields Protection\n\nThe plugin automatically removes sensitive fields from all emitted data for security. This works in addition to Strapi's built-in `private: true` field filtering.\n\n#### Default Blocked Fields\n\nThe following fields are **always removed** from emitted data:\n\n- `password`, `salt`, `hash`\n- `resetPasswordToken`, `confirmationToken`\n- `refreshToken`, `accessToken`, `token`\n- `secret`, `apiKey`, `api_key`\n- `privateKey`, `private_key`\n\n#### Custom Sensitive Fields\n\nAdd your own sensitive fields to the block list:\n\n```javascript\n// config/plugins.js\nmodule.exports = {\n  io: {\n    enabled: true,\n    config: {\n      contentTypes: ['api::user.user'],\n      \n      // Additional fields to never emit\n      sensitiveFields: [\n        'creditCard',\n        'ssn',\n        'socialSecurityNumber',\n        'bankAccount',\n        'internalNotes'\n      ]\n    }\n  }\n};\n```\n\n#### How It Works\n\n1. **Schema-level filtering**: Strapi's `sanitize.contentAPI` removes `private: true` fields\n2. **Blocklist filtering**: Plugin removes all sensitive field names recursively\n3. **Applies to all emits**: Both `emit()` and `raw()` methods are protected\n\n```javascript\n// Even with populate, sensitive fields are stripped\ncontentTypes: [\n  {\n    uid: 'api::user.user',\n    populate: '*'  // Relations included, but passwords etc. still removed\n  }\n]\n```\n\n\u003e **Security Note**: Fields are matched case-insensitively and also partial matches work (e.g., `apiKey` blocks `myApiKey`, `user_api_key`, etc.)\n\n### Environment Variables\n\nRecommended environment-based configuration:\n\n```javascript\n// config/plugins.js\nmodule.exports = ({ env }) =\u003e ({\n  io: {\n    enabled: env.bool('SOCKET_IO_ENABLED', true),\n    config: {\n      contentTypes: env.json('SOCKET_IO_CONTENT_TYPES', []),\n      socket: {\n        serverOptions: {\n          cors: {\n            origin: env('CLIENT_URL', 'http://localhost:3000')\n          }\n        }\n      }\n    }\n  }\n});\n```\n\n```env\n# .env\nSOCKET_IO_ENABLED=true\nCLIENT_URL=https://your-app.com\nSOCKET_IO_CONTENT_TYPES=[\"api::article.article\",\"api::comment.comment\"]\n```\n\n---\n\n## Usage Examples\n\n### Server-Side Usage\n\nAccess the Socket.IO instance anywhere in your Strapi application:\n\n```javascript\n// In a controller, service, or lifecycle\nconst io = strapi.$io;\n\n// Emit to all connected clients\nstrapi.$io.raw({\n  event: 'notification',\n  data: {\n    message: 'Server maintenance in 5 minutes',\n    type: 'warning'\n  }\n});\n\n// Send private message to a specific socket\nstrapi.$io.sendPrivateMessage(socketId, 'order:updated', {\n  orderId: 123,\n  status: 'shipped'\n});\n\n// Emit to all clients in a room\nstrapi.$io.server.to('admin-room').emit('dashboard:update', {\n  activeUsers: 42,\n  revenue: 15000\n});\n\n// Emit to a specific namespace\nstrapi.$io.emitToNamespace('admin', 'alert', {\n  message: 'New user registered'\n});\n```\n\n### Client-Side Usage\n\n#### Basic Connection\n\n```javascript\nimport { io } from 'socket.io-client';\n\nconst socket = io('http://localhost:1337');\n\nsocket.on('connect', () =\u003e {\n  console.log('Connected to server');\n});\n\nsocket.on('disconnect', () =\u003e {\n  console.log('Disconnected from server');\n});\n\n// Listen for content type events\nsocket.on('article:create', (data) =\u003e {\n  console.log('New article:', data);\n});\n\nsocket.on('article:update', (data) =\u003e {\n  console.log('Article updated:', data);\n});\n\nsocket.on('article:delete', (data) =\u003e {\n  console.log('Article deleted:', data.documentId);\n});\n```\n\n#### With React\n\n```jsx\nimport { useEffect, useState } from 'react';\nimport { io } from 'socket.io-client';\n\nfunction ArticlesList() {\n  const [articles, setArticles] = useState([]);\n  \n  useEffect(() =\u003e {\n    const socket = io('http://localhost:1337');\n    \n    // Listen for new articles\n    socket.on('article:create', (article) =\u003e {\n      setArticles(prev =\u003e [article, ...prev]);\n    });\n    \n    // Listen for updates\n    socket.on('article:update', (article) =\u003e {\n      setArticles(prev =\u003e \n        prev.map(a =\u003e a.documentId === article.documentId ? article : a)\n      );\n    });\n    \n    // Listen for deletions\n    socket.on('article:delete', (data) =\u003e {\n      setArticles(prev =\u003e \n        prev.filter(a =\u003e a.documentId !== data.documentId)\n      );\n    });\n    \n    return () =\u003e socket.disconnect();\n  }, []);\n  \n  return (\n    \u003cdiv\u003e\n      {articles.map(article =\u003e (\n        \u003cdiv key={article.documentId}\u003e{article.title}\u003c/div\u003e\n      ))}\n    \u003c/div\u003e\n  );\n}\n```\n\n#### With Vue 3\n\n```vue\n\u003cscript setup\u003e\nimport { ref, onMounted, onUnmounted } from 'vue';\nimport { io } from 'socket.io-client';\n\nconst articles = ref([]);\nlet socket;\n\nonMounted(() =\u003e {\n  socket = io('http://localhost:1337');\n  \n  socket.on('article:create', (article) =\u003e {\n    articles.value = [article, ...articles.value];\n  });\n  \n  socket.on('article:update', (article) =\u003e {\n    const index = articles.value.findIndex(a =\u003e a.documentId === article.documentId);\n    if (index !== -1) {\n      articles.value[index] = article;\n    }\n  });\n  \n  socket.on('article:delete', (data) =\u003e {\n    articles.value = articles.value.filter(a =\u003e a.documentId !== data.documentId);\n  });\n});\n\nonUnmounted(() =\u003e {\n  socket?.disconnect();\n});\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n  \u003cdiv v-for=\"article in articles\" :key=\"article.documentId\"\u003e\n    {{ article.title }}\n  \u003c/div\u003e\n\u003c/template\u003e\n```\n\n### Entity-Specific Subscriptions\n\nSubscribe to updates for specific entities only:\n\n```javascript\n// Client-side: Subscribe to a specific article\nsocket.emit('subscribe-entity', {\n  uid: 'api::article.article',\n  id: 123\n}, (response) =\u003e {\n  if (response.success) {\n    console.log('Subscribed to article 123');\n  }\n});\n\n// Now you only receive updates for article 123\nsocket.on('article:update', (data) =\u003e {\n  console.log('Article 123 was updated:', data);\n});\n\n// Unsubscribe when done\nsocket.emit('unsubscribe-entity', {\n  uid: 'api::article.article',\n  id: 123\n});\n```\n\n**Benefits:**\n- Reduced bandwidth - only receive relevant updates\n- Better performance - less client-side processing\n- Built-in permission checks - respects user roles\n\n### Room Management\n\nOrganize connections into rooms:\n\n```javascript\n// Server-side: Add socket to a room\nstrapi.$io.joinRoom(socketId, 'premium-users');\n\n// Get all sockets in a room\nconst sockets = await strapi.$io.getSocketsInRoom('premium-users');\nconsole.log(`${sockets.length} premium users online`);\n\n// Broadcast to a specific room\nstrapi.$io.server.to('premium-users').emit('exclusive-offer', {\n  discount: 20,\n  expiresIn: '24h'\n});\n\n// Remove socket from a room\nstrapi.$io.leaveRoom(socketId, 'premium-users');\n\n// Disconnect a specific socket\nstrapi.$io.disconnectSocket(socketId, 'Kicked by admin');\n```\n\n---\n\n## Helper Functions\n\nThe plugin provides 12 utility functions available on `strapi.$io`:\n\n### Room Management\n\n#### `joinRoom(socketId, roomName)`\nAdd a socket to a room.\n\n```javascript\nconst success = strapi.$io.joinRoom(socketId, 'premium-users');\n// Returns: true if socket found and joined, false otherwise\n```\n\n#### `leaveRoom(socketId, roomName)`\nRemove a socket from a room.\n\n```javascript\nconst success = strapi.$io.leaveRoom(socketId, 'premium-users');\n// Returns: true if socket found and left, false otherwise\n```\n\n#### `getSocketsInRoom(roomName)`\nGet all sockets in a specific room.\n\n```javascript\nconst sockets = await strapi.$io.getSocketsInRoom('premium-users');\n// Returns: [{ id: 'socket-id', user: { username: 'john' } }, ...]\n```\n\n### Messaging\n\n#### `sendPrivateMessage(socketId, event, data)`\nSend a message to a specific socket.\n\n```javascript\nstrapi.$io.sendPrivateMessage(socketId, 'order:shipped', {\n  orderId: 123,\n  trackingNumber: 'ABC123'\n});\n```\n\n#### `broadcast(socketId, event, data)`\nBroadcast from a socket to all other connected clients.\n\n```javascript\nstrapi.$io.broadcast(socketId, 'user:typing', {\n  username: 'john',\n  channel: 'general'\n});\n```\n\n#### `emitToNamespace(namespace, event, data)`\nEmit an event to all clients in a namespace.\n\n```javascript\nstrapi.$io.emitToNamespace('admin', 'alert', {\n  message: 'New user registered',\n  level: 'info'\n});\n```\n\n### Connection Management\n\n#### `disconnectSocket(socketId, reason)`\nForce disconnect a specific socket.\n\n```javascript\nconst success = strapi.$io.disconnectSocket(socketId, 'Session expired');\n// Returns: true if socket found and disconnected, false otherwise\n```\n\n### Entity Subscriptions\n\n#### `subscribeToEntity(socketId, uid, id)`\nSubscribe a socket to a specific entity (server-side).\n\n```javascript\nconst result = await strapi.$io.subscribeToEntity(socketId, 'api::article.article', 123);\n// Returns: { success: true, room: 'api::article.article:123', uid, id }\n// or: { success: false, error: 'Socket not found' }\n```\n\n#### `unsubscribeFromEntity(socketId, uid, id)`\nUnsubscribe a socket from a specific entity.\n\n```javascript\nconst result = strapi.$io.unsubscribeFromEntity(socketId, 'api::article.article', 123);\n// Returns: { success: true, room: 'api::article.article:123', uid, id }\n```\n\n#### `getEntitySubscriptions(socketId)`\nGet all entity subscriptions for a socket.\n\n```javascript\nconst result = strapi.$io.getEntitySubscriptions(socketId);\n// Returns: { \n//   success: true, \n//   subscriptions: [\n//     { uid: 'api::article.article', id: '123', room: 'api::article.article:123' }\n//   ]\n// }\n```\n\n#### `emitToEntity(uid, id, event, data)`\nEmit an event to all clients subscribed to a specific entity.\n\n```javascript\nstrapi.$io.emitToEntity('api::article.article', 123, 'article:commented', {\n  commentId: 456,\n  author: 'jane'\n});\n```\n\n#### `getEntityRoomSockets(uid, id)`\nGet all sockets subscribed to a specific entity.\n\n```javascript\nconst sockets = await strapi.$io.getEntityRoomSockets('api::article.article', 123);\n// Returns: [{ id: 'socket-id', user: { username: 'john' } }, ...]\n```\n\n---\n\n## Authentication\n\nThe plugin supports multiple authentication strategies.\n\n### Public Access (No Authentication)\n\n```javascript\nconst socket = io('http://localhost:1337');\n// No auth - placed in 'Public' role room\n```\n\n### JWT Authentication (Users \u0026 Permissions)\n\n```javascript\n// 1. Get JWT token from login\nconst response = await fetch('http://localhost:1337/api/auth/local', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    identifier: 'user@example.com',\n    password: 'password123'\n  })\n});\n\nconst { jwt } = await response.json();\n\n// 2. Connect with JWT\nconst socket = io('http://localhost:1337', {\n  auth: {\n    strategy: 'jwt',\n    token: jwt\n  }\n});\n\n// User is placed in their role room (e.g., 'Authenticated')\n```\n\n### API Token Authentication\n\n```javascript\n// 1. Create API token in Strapi Admin:\n//    Settings \u003e Global Settings \u003e API Tokens \u003e Create new token\n\n// 2. Connect with API token\nconst socket = io('http://localhost:1337', {\n  auth: {\n    strategy: 'api-token',\n    token: 'your-api-token-here'\n  }\n});\n```\n\n### Permission Enforcement\n\nEvents are automatically filtered based on the user's role:\n\n```javascript\n// Authenticated user with 'Editor' role\nsocket.on('article:create', (data) =\u003e {\n  // Only receives events for content types they have permission to access\n});\n```\n\nConfigure permissions in the Strapi admin panel:\n1. Go to **Settings \u003e Users \u0026 Permissions \u003e Roles**\n2. Select a role (e.g., \"Authenticated\")\n3. Configure Socket.IO permissions per content type\n\n---\n\n## Security\n\nThe plugin implements multiple security layers to protect your real-time connections.\n\n### Admin Session Tokens\n\nFor admin panel connections (Live Presence), the plugin uses secure session tokens:\n\n```\n+------------------+      +------------------+      +------------------+\n|  Admin Browser   | ---\u003e |  Session Endpoint| ---\u003e |   Socket.IO      |\n|  (Strapi Admin)  |      |  /io/presence/   |      |   Server         |\n+------------------+      +------------------+      +------------------+\n        |                         |                         |\n        | 1. Request session      |                         |\n        | (Admin JWT in header)   |                         |\n        +------------------------\u003e|                         |\n        |                         |                         |\n        | 2. Return session token |                         |\n        | (UUID, 10 min TTL)      |                         |\n        |\u003c------------------------+                         |\n        |                         |                         |\n        | 3. Connect Socket.IO    |                         |\n        | (Session token in auth) |                         |\n        +--------------------------------------------------\u003e|\n        |                         |                         |\n        | 4. Validate \u0026 connect   |                         |\n        |\u003c--------------------------------------------------+\n```\n\n**Security Features:**\n- **Token Hashing**: Tokens stored as SHA-256 hashes (plaintext never persisted)\n- **Short TTL**: 10-minute expiration with automatic refresh at 70%\n- **Usage Limits**: Max 10 reconnects per token to prevent replay attacks\n- **Rate Limiting**: 30-second cooldown between token requests\n- **Minimal Data**: Only essential user info stored (ID, firstname, lastname)\n\n### Rate Limiting\n\nPrevent abuse with configurable rate limits:\n\n```javascript\n// In config/plugins.js\nmodule.exports = {\n  io: {\n    enabled: true,\n    config: {\n      security: {\n        rateLimit: {\n          enabled: true,\n          maxEventsPerSecond: 10,    // Max events per socket per second\n          maxConnectionsPerIp: 50    // Max connections from single IP\n        }\n      }\n    }\n  }\n};\n```\n\n### IP Whitelisting/Blacklisting\n\nRestrict access by IP address:\n\n```javascript\n// In config/plugins.js\nmodule.exports = {\n  io: {\n    enabled: true,\n    config: {\n      security: {\n        ipWhitelist: ['192.168.1.0/24', '10.0.0.1'],  // Only these IPs allowed\n        ipBlacklist: ['203.0.113.50'],                 // These IPs blocked\n        requireAuthentication: true                    // Require JWT/API token\n      }\n    }\n  }\n};\n```\n\n### Security Monitoring API\n\nMonitor active sessions via admin API:\n\n```bash\n# Get session statistics\nGET /io/security/sessions\nAuthorization: Bearer \u003cadmin-jwt\u003e\n\n# Response:\n{\n  \"data\": {\n    \"activeSessions\": 5,\n    \"expiringSoon\": 1,\n    \"activeSocketConnections\": 3,\n    \"sessionTTL\": 600000,\n    \"refreshCooldown\": 30000\n  }\n}\n\n# Force logout a user (invalidate all their sessions)\nPOST /io/security/invalidate/:userId\nAuthorization: Bearer \u003cadmin-jwt\u003e\n\n# Response:\n{\n  \"data\": {\n    \"userId\": 1,\n    \"invalidatedSessions\": 2,\n    \"message\": \"Successfully invalidated 2 session(s)\"\n  }\n}\n```\n\n### Best Practices\n\n1. **Always use HTTPS** in production for encrypted WebSocket connections\n2. **Enable authentication** for sensitive content types\n3. **Configure CORS** to only allow your frontend domains\n4. **Monitor connections** via the admin dashboard\n5. **Set reasonable rate limits** based on your use case\n6. **Review access logs** periodically for suspicious activity\n\n---\n\n## Admin Panel\n\nThe plugin provides a full admin interface for configuration and monitoring.\n\n### Dashboard Widgets\n\n\u003e **Requires Strapi v5.13+**\n\nAfter installation, live statistics widgets appear on your Strapi admin homepage:\n\n#### Socket.IO Statistics Widget\n\n![Socket.IO Statistics Widget](./pics/widget.png)\n\n**Shows:**\n- Live connection status (pulsing indicator when active)\n- Active connections count\n- Active rooms count\n- Events per second\n- Total events processed since startup\n\nUpdates automatically every 5 seconds.\n\n#### Who's Online Widget\n\n![Who's Online Widget](./pics/whoisonlinewidget.png)\n\n**Shows:**\n- List of currently online admin users\n- User avatars with role badges\n- Online status and last activity\n- Quick access to view all activity\n\n### Settings Page\n\nNavigate to **Settings \u003e Socket.IO \u003e Settings** for visual configuration:\n\n**Path:** `/admin/settings/io/settings`\n\n![Socket.IO Settings](./pics/settings.png)\n\n#### General Settings\n- Enable/disable the plugin\n- Configure CORS origins\n- Set server options (ping timeout, etc.)\n\n#### Content Types\n- Enable automatic events for content types per role\n- Select specific actions (create, update, delete)\n- Configure role-based permissions\n\n#### Events\n- Include relations in events (`includeRelations`)\n- Custom event names\n- Exclude specific fields\n- Only published content mode\n\n#### Security\n- Require authentication\n- Rate limiting configuration\n- IP whitelisting/blacklisting\n- Input validation rules\n\n#### Rooms\n- Auto-join by role configuration\n- Enable/disable private rooms\n\n#### Advanced\n- Configure namespaces\n- Redis adapter settings for scaling\n- Entity subscription limits\n\n### Monitoring Page\n\nNavigate to **Settings \u003e Socket.IO \u003e Monitoring** for live statistics:\n\n**Path:** `/admin/settings/io/monitoring`\n\n![Monitoring \u0026 Logging](./pics/monitoringSettings.png)\n\n- View active connections with user details\n- See event logs in real-time\n- Monitor performance metrics (events/second)\n- View entity subscription statistics\n- Send test events\n- Reset statistics\n\n### Live Presence Panel\n\nWhen editing content in the Content Manager, a **Live Presence** panel appears in the sidebar showing:\n\n- **Connection Status** - Live indicator showing real-time sync is active\n- **Active Editors** - List of other users editing the same content\n- **Typing Indicator** - Shows when someone is typing and in which field\n\n![Live Presence Panel](./pics/livepresenceindi.png)\n\n**How It Works:**\n\n1. When you open a content entry, the panel connects via Socket.IO\n2. Other editors on the same entry appear in the panel\n3. Typing in any field broadcasts a typing indicator to others\n4. When you leave, others are notified\n\n---\n\n## Monitoring Service\n\nAccess monitoring data programmatically via the monitoring service:\n\n```javascript\nconst monitoringService = strapi.plugin('io').service('monitoring');\n```\n\n### Available Methods\n\n#### `getConnectionStats()`\nGet current connection statistics.\n\n```javascript\nconst stats = monitoringService.getConnectionStats();\n// Returns:\n// {\n//   connected: 42,\n//   rooms: [\n//     { name: 'Authenticated', members: 35, isEntityRoom: false },\n//     { name: 'api::article.article:123', members: 5, isEntityRoom: true }\n//   ],\n//   sockets: [\n//     {\n//       id: 'abc123',\n//       connected: true,\n//       rooms: ['Authenticated'],\n//       entitySubscriptions: [{ uid: 'api::article.article', id: '123', room: '...' }],\n//       handshake: { address: '::1', time: '...', query: {} },\n//       user: { id: 1, username: 'john' }\n//     }\n//   ],\n//   entitySubscriptions: {\n//     total: 15,\n//     byContentType: { 'api::article.article': 10, 'api::comment.comment': 5 },\n//     rooms: ['api::article.article:123', ...]\n//   }\n// }\n```\n\n#### `getEventStats()`\nGet event statistics.\n\n```javascript\nconst stats = monitoringService.getEventStats();\n// Returns:\n// {\n//   totalEvents: 1234,\n//   eventsByType: { 'create': 500, 'update': 600, 'delete': 134 },\n//   lastReset: 1703123456789,\n//   eventsPerSecond: '2.50'\n// }\n```\n\n#### `getEventLog(limit)`\nGet recent event log entries.\n\n```javascript\nconst logs = monitoringService.getEventLog(50);\n// Returns:\n// [\n//   { timestamp: 1703123456789, type: 'create', data: {...} },\n//   { timestamp: 1703123456790, type: 'connect', data: {...} }\n// ]\n```\n\n#### `logEvent(type, data)`\nManually log an event.\n\n```javascript\nmonitoringService.logEvent('custom-event', {\n  action: 'user-action',\n  userId: 123\n});\n```\n\n#### `resetStats()`\nReset all statistics and clear event log.\n\n```javascript\nmonitoringService.resetStats();\n```\n\n#### `sendTestEvent(eventName, data)`\nSend a test event to all connected clients.\n\n```javascript\nconst result = monitoringService.sendTestEvent('test', { message: 'Hello!' });\n// Returns:\n// {\n//   success: true,\n//   eventName: 'test',\n//   data: { message: 'Hello!', timestamp: 1703123456789, test: true },\n//   recipients: 42\n// }\n```\n\n### Use Cases\n\n**Custom Analytics Dashboard:**\n\n```javascript\n// In a custom controller\nasync getAnalytics(ctx) {\n  const monitoring = strapi.plugin('io').service('monitoring');\n  \n  ctx.body = {\n    connections: monitoring.getConnectionStats(),\n    events: monitoring.getEventStats(),\n    recentActivity: monitoring.getEventLog(10)\n  };\n}\n```\n\n**Health Check Endpoint:**\n\n```javascript\nasync healthCheck(ctx) {\n  const monitoring = strapi.plugin('io').service('monitoring');\n  const stats = monitoring.getConnectionStats();\n  \n  ctx.body = {\n    status: 'healthy',\n    websocket: {\n      connected: stats.connected,\n      rooms: stats.rooms.length\n    }\n  };\n}\n```\n\n---\n\n## TypeScript Support\n\nFull TypeScript definitions are included for excellent IDE support.\n\n### Import Types\n\n```typescript\nimport type { \n  SocketIO, \n  SocketIOConfig,\n  EmitOptions,\n  RawEmitOptions,\n  PluginSettings,\n  MonitoringService,\n  SettingsService,\n  ConnectionStats,\n  EventStats\n} from '@strapi-community/plugin-io/types';\n```\n\n### Configuration Example\n\n```typescript\n// config/plugins.ts\nimport type { SocketIOConfig } from '@strapi-community/plugin-io/types';\n\nexport default {\n  io: {\n    enabled: true,\n    config: {\n      contentTypes: [\n        {\n          uid: 'api::article.article',\n          actions: ['create', 'update', 'delete']\n        }\n      ],\n      socket: {\n        serverOptions: {\n          cors: {\n            origin: process.env.CLIENT_URL || 'http://localhost:3000',\n            methods: ['GET', 'POST']\n          }\n        }\n      }\n    } satisfies SocketIOConfig\n  }\n};\n```\n\n### Usage Example\n\n```typescript\n// In your Strapi code\nimport type { SocketIO } from '@strapi-community/plugin-io/types';\n\n// Type-safe access\nconst io: SocketIO = strapi.$io;\n\n// All methods have full IntelliSense\nawait io.emit({\n  event: 'create',\n  schema: strapi.contentTypes['api::article.article'],\n  data: { title: 'New Article' }\n});\n\n// Helper functions are typed\nconst sockets = await io.getSocketsInRoom('premium-users');\nio.sendPrivateMessage(sockets[0].id, 'welcome', { message: 'Hello!' });\n```\n\n---\n\n## Performance\n\nThe plugin is optimized for production environments.\n\n### Benchmarks\n\n- **Concurrent Connections**: 2500+ simultaneous connections\n- **Memory Usage**: ~17KB per connection\n- **Event Throughput**: 10,000+ events/second\n- **Latency**: \u003c10ms for local broadcasts\n\n### Optimizations\n\n#### Intelligent Caching\nRole and permission data is cached for 5 minutes, reducing database queries by up to 90%.\n\n#### Debouncing\nBulk operations are automatically debounced to prevent event flooding during data imports.\n\n#### Parallel Processing\nAll event emissions are processed in parallel for maximum throughput.\n\n#### Connection Pooling\nEfficient connection management with automatic cleanup of stale connections.\n\n### Production Configuration\n\n```javascript\n// config/plugins.js (production)\nmodule.exports = ({ env }) =\u003e ({\n  io: {\n    enabled: true,\n    config: {\n      contentTypes: env.json('SOCKET_IO_CONTENT_TYPES'),\n      \n      socket: {\n        serverOptions: {\n          cors: {\n            origin: env('CLIENT_URL'),\n            credentials: true\n          },\n          // Optimize for production\n          pingTimeout: 60000,\n          pingInterval: 25000,\n          maxHttpBufferSize: 1e6,\n          transports: ['websocket', 'polling']\n        }\n      },\n      \n      // Use Redis for horizontal scaling\n      hooks: {\n        init: async ({ strapi, $io }) =\u003e {\n          const { createAdapter } = require('@socket.io/redis-adapter');\n          const { createClient } = require('redis');\n          \n          const pubClient = createClient({ url: env('REDIS_URL') });\n          const subClient = pubClient.duplicate();\n          \n          await Promise.all([pubClient.connect(), subClient.connect()]);\n          \n          $io.server.adapter(createAdapter(pubClient, subClient));\n          \n          strapi.log.info('[Socket.IO] Redis adapter connected');\n        }\n      }\n    }\n  }\n});\n```\n\n---\n\n## Migration Guide\n\n### From v2 (Strapi v4) to v5 (Strapi v5)\n\n**Good news:** The API is 100% compatible! Most projects migrate in under 1 hour.\n\n#### Quick Migration Steps\n\n1. **Update Strapi to v5**\n   ```bash\n   npm install @strapi/strapi@5 @strapi/plugin-users-permissions@5\n   ```\n\n2. **Update the plugin**\n   ```bash\n   npm uninstall strapi-plugin-io\n   npm install @strapi-community/plugin-io@latest\n   ```\n\n3. **Test your application**\n   ```bash\n   npm run develop\n   ```\n\nYour configuration stays the same - no code changes needed!\n\n#### What Changed\n\n- **Package name**: `strapi-plugin-io` -\u003e `@strapi-community/plugin-io`\n- **Package structure**: Uses new Strapi v5 Plugin SDK\n- **Dependencies**: Updated to Strapi v5 peer dependencies\n- **Build process**: Optimized build with modern tooling\n\n#### What Stayed the Same\n\n- All API methods work identically\n- Configuration format unchanged\n- Client-side code works as-is\n- Same helper functions\n- Same event format\n\nFor detailed migration instructions, see [docs/guide/migration.md](./docs/guide/migration.md).\n\n---\n\n## Documentation\n\n### Official Documentation\n\n- **[Online Documentation](https://strapi-plugin-io.netlify.app/)** - Complete interactive docs\n- **[API Reference](./docs/api/io-class.md)** - All methods and properties\n- **[Configuration Guide](./docs/api/plugin-config.md)** - Detailed configuration options\n- **[Usage Examples](./docs/examples/)** - Real-world use cases\n- **[Migration Guide](./docs/guide/migration.md)** - Upgrade from v4 to v5\n\n### Guides\n\n- **[Getting Started](./docs/guide/getting-started.md)** - Step-by-step setup\n- **[Widget Guide](./docs/guide/widget.md)** - Dashboard widget configuration\n\n### Examples\n\n- **[Content Types](./docs/examples/content-types.md)** - Automatic CRUD events\n- **[Custom Events](./docs/examples/events.md)** - Server-client communication\n- **[Hooks \u0026 Adapters](./docs/examples/hooks.md)** - Redis, MongoDB integration\n\n---\n\n## Related Plugins\n\nBuild complete real-time applications with these complementary Strapi v5 plugins:\n\n### [Magic-Mail](https://github.com/Schero94/Magic-Mail)\nEnterprise email management with OAuth 2.0 support. Perfect for sending transactional emails triggered by Socket.IO events.\n\n**Use case:** Send email notifications when real-time events occur.\n\n### [Magic-Sessionmanager](https://github.com/Schero94/Magic-Sessionmanager)\nAdvanced session tracking and monitoring. Track Socket.IO connections, monitor active users, and analyze session patterns.\n\n**Use case:** Monitor who's connected to your WebSocket server in real-time.\n\n### [Magicmark](https://github.com/Schero94/Magicmark)\nBookmark management system with real-time sync. Share bookmarks instantly with your team using Socket.IO integration.\n\n**Use case:** Collaborative bookmark management with live updates.\n\n---\n\n## Contributing\n\nWe welcome contributions! Here's how you can help:\n\n### Report Bugs\n\nFound a bug? [Open an issue](https://github.com/strapi-community/strapi-plugin-io/issues) with:\n- Strapi version\n- Plugin version\n- Steps to reproduce\n- Expected vs actual behavior\n\n### Suggest Features\n\nHave an idea? [Start a discussion](https://github.com/strapi-community/strapi-plugin-io/discussions) to:\n- Describe the feature\n- Explain the use case\n- Discuss implementation\n\n### Submit Pull Requests\n\n1. Fork the repository\n2. Create a feature branch (`git checkout -b feature/amazing-feature`)\n3. Commit your changes (`git commit -m 'Add amazing feature'`)\n4. Push to the branch (`git push origin feature/amazing-feature`)\n5. Open a Pull Request\n\n### Development Setup\n\n```bash\n# Clone the repository\ngit clone https://github.com/strapi-community/strapi-plugin-io.git\ncd strapi-plugin-io\n\n# Install dependencies\nnpm install\n\n# Build the plugin\nnpm run build\n\n# Run in watch mode\nnpm run watch\n\n# Verify structure\nnpm run verify\n```\n\n---\n\n## Support\n\n- **Documentation**: https://strapi-plugin-io.netlify.app/\n- **GitHub Issues**: https://github.com/strapi-community/strapi-plugin-io/issues\n- **GitHub Discussions**: https://github.com/strapi-community/strapi-plugin-io/discussions\n- **Strapi Discord**: https://discord.strapi.io\n\n---\n\n## License\n\n[MIT License](./LICENSE)\n\nCopyright (c) 2024 Strapi Community\n\n---\n\n## Credits\n\n**Original Authors:**\n- [@ComfortablyCoding](https://github.com/ComfortablyCoding)\n- [@hrdunn](https://github.com/hrdunn)\n\n**Enhanced and Maintained by:**\n- [@Schero94](https://github.com/Schero94)\n\n**Maintained until:** December 2026\n\n---\n\n## Changelog\n\n### v5.1.0 (Latest)\n- **Live Presence System** - Real-time presence awareness in Content Manager\n- **Typing Indicator** - See when others are typing and in which field\n- **Admin Panel Sidebar** - Live presence panel integrated into edit view\n- **Admin Session Authentication** - Secure session tokens for Socket.IO\n- **Admin JWT Strategy** - New authentication strategy for admin users\n- **Enhanced Security** - Token hashing (SHA-256), usage limits, rate limiting\n- **Automatic Token Refresh** - Tokens auto-refresh at 70% of TTL\n- **Security Monitoring API** - Session stats and force-logout endpoints\n\n### v5.0.0\n- Strapi v5 support\n- Package renamed to `@strapi-community/plugin-io`\n- Enhanced TypeScript support\n- Entity-specific subscriptions\n- 12 helper functions\n- Admin panel with monitoring dashboard\n- Performance optimizations\n- Updated documentation\n\nFor full changelog, see [CHANGELOG.md](./CHANGELOG.md).\n\n---\n\n\u003cdiv align=\"center\"\u003e\n  \n**[Documentation](https://strapi-plugin-io.netlify.app/)** | \n**[API Reference](./docs/api/io-class.md)** | \n**[Examples](./docs/examples/)** | \n**[GitHub](https://github.com/strapi-community/strapi-plugin-io)**\n\nMade with love for the Strapi community\n\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstrapi-community%2Fplugin-io","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstrapi-community%2Fplugin-io","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstrapi-community%2Fplugin-io/lists"}