@strapi-community/plugin-io 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1308 -0
- package/dist/_chunks/MonitoringPage-DLZdTZpg.mjs +752 -0
- package/dist/_chunks/MonitoringPage-HxHK1nFr.js +754 -0
- package/dist/_chunks/SettingsPage-88RdJkLy.js +1411 -0
- package/dist/_chunks/SettingsPage-DBIu309c.mjs +1409 -0
- package/dist/_chunks/SocketStatsWidget--uGHcYvU.mjs +220 -0
- package/dist/_chunks/SocketStatsWidget-C1-1_VOB.js +222 -0
- package/dist/_chunks/de-BoFxKIL3.mjs +102 -0
- package/dist/_chunks/de-Crne_WJ-.js +102 -0
- package/dist/_chunks/en-B4_6Q0aQ.mjs +120 -0
- package/dist/_chunks/en-Bd2IKJzy.js +120 -0
- package/dist/_chunks/es-CCwv5Ulk.mjs +41 -0
- package/dist/_chunks/es-Xj8RgKuQ.js +41 -0
- package/dist/_chunks/fr-D_r96iuZ.js +41 -0
- package/dist/_chunks/fr-DtBI9vH_.mjs +41 -0
- package/dist/_chunks/index-BVQ20t1c.js +113 -0
- package/dist/_chunks/index-CEh8vkxY.mjs +411 -0
- package/dist/_chunks/index-DLXtrAtk.mjs +114 -0
- package/dist/_chunks/index-DkTxsEqL.js +428 -0
- package/dist/_chunks/pt-Bba2cd2e.js +41 -0
- package/dist/_chunks/pt-I9epJZpI.mjs +41 -0
- package/dist/admin/index.js +3 -0
- package/dist/admin/index.mjs +4 -0
- package/dist/server/index.js +30000 -0
- package/dist/server/index.mjs +29970 -0
- package/package.json +120 -0
- package/types.d.ts +362 -0
package/README.md
ADDED
|
@@ -0,0 +1,1308 @@
|
|
|
1
|
+
# @strapi-community/plugin-io
|
|
2
|
+
|
|
3
|
+
> Real-time WebSocket integration for Strapi v5 with Socket.IO
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@strapi-community/plugin-io)
|
|
6
|
+
[](https://www.npmjs.com/package/@strapi-community/plugin-io)
|
|
7
|
+
[](https://github.com/strapi-community/strapi-plugin-io/blob/main/LICENSE)
|
|
8
|
+
[](https://strapi.io)
|
|
9
|
+
|
|
10
|
+
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.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Table of Contents
|
|
15
|
+
|
|
16
|
+
- [Features](#features)
|
|
17
|
+
- [Quick Start](#quick-start)
|
|
18
|
+
- [Installation](#installation)
|
|
19
|
+
- [Configuration](#configuration)
|
|
20
|
+
- [Usage Examples](#usage-examples)
|
|
21
|
+
- [Helper Functions](#helper-functions)
|
|
22
|
+
- [Authentication](#authentication)
|
|
23
|
+
- [Admin Panel](#admin-panel)
|
|
24
|
+
- [Monitoring Service](#monitoring-service)
|
|
25
|
+
- [TypeScript Support](#typescript-support)
|
|
26
|
+
- [Performance](#performance)
|
|
27
|
+
- [Migration Guide](#migration-guide)
|
|
28
|
+
- [Documentation](#documentation)
|
|
29
|
+
- [Contributing](#contributing)
|
|
30
|
+
- [License](#license)
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Features
|
|
35
|
+
|
|
36
|
+
### Core Functionality
|
|
37
|
+
- **Automatic Real-Time Events** - CRUD operations broadcast automatically to connected clients
|
|
38
|
+
- **Entity-Specific Subscriptions** - Subscribe to individual entities for targeted updates
|
|
39
|
+
- **Role-Based Access Control** - Built-in permission checks for JWT and API tokens
|
|
40
|
+
- **Multi-Client Support** - Handle 2500+ concurrent connections efficiently
|
|
41
|
+
|
|
42
|
+
### Developer Experience
|
|
43
|
+
- **Visual Admin Panel** - Configure everything through the Strapi admin interface
|
|
44
|
+
- **TypeScript Support** - Full type definitions for IntelliSense
|
|
45
|
+
- **Helper Functions** - 12 utility methods for common tasks
|
|
46
|
+
- **Comprehensive Documentation** - Detailed guides and examples
|
|
47
|
+
|
|
48
|
+
### Production Ready
|
|
49
|
+
- **Redis Adapter** - Scale horizontally across multiple servers
|
|
50
|
+
- **Rate Limiting** - Prevent abuse with configurable limits
|
|
51
|
+
- **Monitoring Dashboard** - Live connection stats and event logs
|
|
52
|
+
- **Security Features** - IP whitelisting, authentication, input validation
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Quick Start
|
|
57
|
+
|
|
58
|
+
### 1. Install the plugin
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npm install @strapi-community/plugin-io
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 2. Enable in your Strapi project
|
|
65
|
+
|
|
66
|
+
Create or update `config/plugins.js`:
|
|
67
|
+
|
|
68
|
+
```javascript
|
|
69
|
+
module.exports = {
|
|
70
|
+
io: {
|
|
71
|
+
enabled: true,
|
|
72
|
+
config: {
|
|
73
|
+
contentTypes: ['api::article.article'],
|
|
74
|
+
socket: {
|
|
75
|
+
serverOptions: {
|
|
76
|
+
cors: {
|
|
77
|
+
origin: 'http://localhost:3000',
|
|
78
|
+
methods: ['GET', 'POST']
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 3. Start your Strapi server
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
npm run develop
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 4. Connect from your frontend
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
import { io } from 'socket.io-client';
|
|
97
|
+
|
|
98
|
+
const socket = io('http://localhost:1337');
|
|
99
|
+
|
|
100
|
+
socket.on('article:create', (article) => {
|
|
101
|
+
console.log('New article published:', article);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
socket.on('article:update', (article) => {
|
|
105
|
+
console.log('Article updated:', article);
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
That's it! Your application now has real-time updates.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Installation
|
|
114
|
+
|
|
115
|
+
### Requirements
|
|
116
|
+
|
|
117
|
+
- **Node.js**: 18.x - 22.x
|
|
118
|
+
- **Strapi**: v5.x
|
|
119
|
+
- **npm**: 6.x or higher
|
|
120
|
+
|
|
121
|
+
### Install the package
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
# Using npm
|
|
125
|
+
npm install @strapi-community/plugin-io
|
|
126
|
+
|
|
127
|
+
# Using yarn
|
|
128
|
+
yarn add @strapi-community/plugin-io
|
|
129
|
+
|
|
130
|
+
# Using pnpm
|
|
131
|
+
pnpm add @strapi-community/plugin-io
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Configuration
|
|
137
|
+
|
|
138
|
+
### Basic Configuration
|
|
139
|
+
|
|
140
|
+
The simplest setup to get started:
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
// config/plugins.js
|
|
144
|
+
module.exports = {
|
|
145
|
+
io: {
|
|
146
|
+
enabled: true,
|
|
147
|
+
config: {
|
|
148
|
+
// Monitor these content types for changes
|
|
149
|
+
contentTypes: [
|
|
150
|
+
'api::article.article',
|
|
151
|
+
'api::comment.comment'
|
|
152
|
+
]
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Advanced Configuration
|
|
159
|
+
|
|
160
|
+
Fine-tune the plugin behavior:
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
// config/plugins.js
|
|
164
|
+
module.exports = {
|
|
165
|
+
io: {
|
|
166
|
+
enabled: true,
|
|
167
|
+
config: {
|
|
168
|
+
// Content types with specific actions and populate
|
|
169
|
+
contentTypes: [
|
|
170
|
+
{
|
|
171
|
+
uid: 'api::article.article',
|
|
172
|
+
actions: ['create', 'update'], // Only these events
|
|
173
|
+
populate: '*' // Include all relations
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
uid: 'api::comment.comment',
|
|
177
|
+
actions: ['create', 'delete'],
|
|
178
|
+
populate: ['author', 'article'] // Only specific relations
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
uid: 'api::order.order',
|
|
182
|
+
populate: { // Strapi populate syntax
|
|
183
|
+
customer: { fields: ['name', 'email'] },
|
|
184
|
+
items: { populate: ['product'] }
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
],
|
|
188
|
+
|
|
189
|
+
// Socket.IO server configuration
|
|
190
|
+
socket: {
|
|
191
|
+
serverOptions: {
|
|
192
|
+
cors: {
|
|
193
|
+
origin: process.env.CLIENT_URL || 'http://localhost:3000',
|
|
194
|
+
methods: ['GET', 'POST'],
|
|
195
|
+
credentials: true
|
|
196
|
+
},
|
|
197
|
+
pingTimeout: 60000,
|
|
198
|
+
pingInterval: 25000
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
// Custom event handlers
|
|
203
|
+
events: [
|
|
204
|
+
{
|
|
205
|
+
name: 'connection',
|
|
206
|
+
handler: ({ strapi, io }, socket) => {
|
|
207
|
+
strapi.log.info(`Client connected: ${socket.id}`);
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
name: 'disconnect',
|
|
212
|
+
handler: ({ strapi, io }, socket) => {
|
|
213
|
+
strapi.log.info(`Client disconnected: ${socket.id}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
],
|
|
217
|
+
|
|
218
|
+
// Initialization hook
|
|
219
|
+
hooks: {
|
|
220
|
+
init: ({ strapi, $io }) => {
|
|
221
|
+
strapi.log.info('[Socket.IO] Server initialized');
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Populate Configuration
|
|
230
|
+
|
|
231
|
+
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.
|
|
232
|
+
|
|
233
|
+
#### Populate Formats
|
|
234
|
+
|
|
235
|
+
| Format | Example | Description |
|
|
236
|
+
|--------|---------|-------------|
|
|
237
|
+
| `'*'` or `true` | `populate: '*'` | Include all relations (1 level deep) |
|
|
238
|
+
| String array | `populate: ['author', 'category']` | Only specific relations |
|
|
239
|
+
| Object | `populate: { author: { fields: ['name'] } }` | Full Strapi populate syntax |
|
|
240
|
+
|
|
241
|
+
#### Examples
|
|
242
|
+
|
|
243
|
+
```javascript
|
|
244
|
+
contentTypes: [
|
|
245
|
+
// All relations with wildcard
|
|
246
|
+
{ uid: 'api::article.article', populate: '*' },
|
|
247
|
+
|
|
248
|
+
// Specific relations only
|
|
249
|
+
{
|
|
250
|
+
uid: 'api::comment.comment',
|
|
251
|
+
populate: ['author', 'post']
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
// Strapi populate syntax with field selection
|
|
255
|
+
{
|
|
256
|
+
uid: 'api::order.order',
|
|
257
|
+
populate: {
|
|
258
|
+
customer: { fields: ['username', 'email'] },
|
|
259
|
+
items: {
|
|
260
|
+
populate: { product: { fields: ['name', 'price'] } }
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
// No populate (only base fields)
|
|
266
|
+
{ uid: 'api::log.log' } // populate not set = no relations
|
|
267
|
+
]
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
> **Note**: The `populate` option only affects `create` and `update` events. Delete events always contain minimal data (id, documentId) since the entity no longer exists.
|
|
271
|
+
|
|
272
|
+
### Sensitive Fields Protection
|
|
273
|
+
|
|
274
|
+
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.
|
|
275
|
+
|
|
276
|
+
#### Default Blocked Fields
|
|
277
|
+
|
|
278
|
+
The following fields are **always removed** from emitted data:
|
|
279
|
+
|
|
280
|
+
- `password`, `salt`, `hash`
|
|
281
|
+
- `resetPasswordToken`, `confirmationToken`
|
|
282
|
+
- `refreshToken`, `accessToken`, `token`
|
|
283
|
+
- `secret`, `apiKey`, `api_key`
|
|
284
|
+
- `privateKey`, `private_key`
|
|
285
|
+
|
|
286
|
+
#### Custom Sensitive Fields
|
|
287
|
+
|
|
288
|
+
Add your own sensitive fields to the block list:
|
|
289
|
+
|
|
290
|
+
```javascript
|
|
291
|
+
// config/plugins.js
|
|
292
|
+
module.exports = {
|
|
293
|
+
io: {
|
|
294
|
+
enabled: true,
|
|
295
|
+
config: {
|
|
296
|
+
contentTypes: ['api::user.user'],
|
|
297
|
+
|
|
298
|
+
// Additional fields to never emit
|
|
299
|
+
sensitiveFields: [
|
|
300
|
+
'creditCard',
|
|
301
|
+
'ssn',
|
|
302
|
+
'socialSecurityNumber',
|
|
303
|
+
'bankAccount',
|
|
304
|
+
'internalNotes'
|
|
305
|
+
]
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
#### How It Works
|
|
312
|
+
|
|
313
|
+
1. **Schema-level filtering**: Strapi's `sanitize.contentAPI` removes `private: true` fields
|
|
314
|
+
2. **Blocklist filtering**: Plugin removes all sensitive field names recursively
|
|
315
|
+
3. **Applies to all emits**: Both `emit()` and `raw()` methods are protected
|
|
316
|
+
|
|
317
|
+
```javascript
|
|
318
|
+
// Even with populate, sensitive fields are stripped
|
|
319
|
+
contentTypes: [
|
|
320
|
+
{
|
|
321
|
+
uid: 'api::user.user',
|
|
322
|
+
populate: '*' // Relations included, but passwords etc. still removed
|
|
323
|
+
}
|
|
324
|
+
]
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
> **Security Note**: Fields are matched case-insensitively and also partial matches work (e.g., `apiKey` blocks `myApiKey`, `user_api_key`, etc.)
|
|
328
|
+
|
|
329
|
+
### Environment Variables
|
|
330
|
+
|
|
331
|
+
Recommended environment-based configuration:
|
|
332
|
+
|
|
333
|
+
```javascript
|
|
334
|
+
// config/plugins.js
|
|
335
|
+
module.exports = ({ env }) => ({
|
|
336
|
+
io: {
|
|
337
|
+
enabled: env.bool('SOCKET_IO_ENABLED', true),
|
|
338
|
+
config: {
|
|
339
|
+
contentTypes: env.json('SOCKET_IO_CONTENT_TYPES', []),
|
|
340
|
+
socket: {
|
|
341
|
+
serverOptions: {
|
|
342
|
+
cors: {
|
|
343
|
+
origin: env('CLIENT_URL', 'http://localhost:3000')
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
```env
|
|
353
|
+
# .env
|
|
354
|
+
SOCKET_IO_ENABLED=true
|
|
355
|
+
CLIENT_URL=https://your-app.com
|
|
356
|
+
SOCKET_IO_CONTENT_TYPES=["api::article.article","api::comment.comment"]
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## Usage Examples
|
|
362
|
+
|
|
363
|
+
### Server-Side Usage
|
|
364
|
+
|
|
365
|
+
Access the Socket.IO instance anywhere in your Strapi application:
|
|
366
|
+
|
|
367
|
+
```javascript
|
|
368
|
+
// In a controller, service, or lifecycle
|
|
369
|
+
const io = strapi.$io;
|
|
370
|
+
|
|
371
|
+
// Emit to all connected clients
|
|
372
|
+
strapi.$io.raw({
|
|
373
|
+
event: 'notification',
|
|
374
|
+
data: {
|
|
375
|
+
message: 'Server maintenance in 5 minutes',
|
|
376
|
+
type: 'warning'
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// Send private message to a specific socket
|
|
381
|
+
strapi.$io.sendPrivateMessage(socketId, 'order:updated', {
|
|
382
|
+
orderId: 123,
|
|
383
|
+
status: 'shipped'
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// Emit to all clients in a room
|
|
387
|
+
strapi.$io.server.to('admin-room').emit('dashboard:update', {
|
|
388
|
+
activeUsers: 42,
|
|
389
|
+
revenue: 15000
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// Emit to a specific namespace
|
|
393
|
+
strapi.$io.emitToNamespace('admin', 'alert', {
|
|
394
|
+
message: 'New user registered'
|
|
395
|
+
});
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Client-Side Usage
|
|
399
|
+
|
|
400
|
+
#### Basic Connection
|
|
401
|
+
|
|
402
|
+
```javascript
|
|
403
|
+
import { io } from 'socket.io-client';
|
|
404
|
+
|
|
405
|
+
const socket = io('http://localhost:1337');
|
|
406
|
+
|
|
407
|
+
socket.on('connect', () => {
|
|
408
|
+
console.log('Connected to server');
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
socket.on('disconnect', () => {
|
|
412
|
+
console.log('Disconnected from server');
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
// Listen for content type events
|
|
416
|
+
socket.on('article:create', (data) => {
|
|
417
|
+
console.log('New article:', data);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
socket.on('article:update', (data) => {
|
|
421
|
+
console.log('Article updated:', data);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
socket.on('article:delete', (data) => {
|
|
425
|
+
console.log('Article deleted:', data.documentId);
|
|
426
|
+
});
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
#### With React
|
|
430
|
+
|
|
431
|
+
```jsx
|
|
432
|
+
import { useEffect, useState } from 'react';
|
|
433
|
+
import { io } from 'socket.io-client';
|
|
434
|
+
|
|
435
|
+
function ArticlesList() {
|
|
436
|
+
const [articles, setArticles] = useState([]);
|
|
437
|
+
|
|
438
|
+
useEffect(() => {
|
|
439
|
+
const socket = io('http://localhost:1337');
|
|
440
|
+
|
|
441
|
+
// Listen for new articles
|
|
442
|
+
socket.on('article:create', (article) => {
|
|
443
|
+
setArticles(prev => [article, ...prev]);
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
// Listen for updates
|
|
447
|
+
socket.on('article:update', (article) => {
|
|
448
|
+
setArticles(prev =>
|
|
449
|
+
prev.map(a => a.documentId === article.documentId ? article : a)
|
|
450
|
+
);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
// Listen for deletions
|
|
454
|
+
socket.on('article:delete', (data) => {
|
|
455
|
+
setArticles(prev =>
|
|
456
|
+
prev.filter(a => a.documentId !== data.documentId)
|
|
457
|
+
);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
return () => socket.disconnect();
|
|
461
|
+
}, []);
|
|
462
|
+
|
|
463
|
+
return (
|
|
464
|
+
<div>
|
|
465
|
+
{articles.map(article => (
|
|
466
|
+
<div key={article.documentId}>{article.title}</div>
|
|
467
|
+
))}
|
|
468
|
+
</div>
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
#### With Vue 3
|
|
474
|
+
|
|
475
|
+
```vue
|
|
476
|
+
<script setup>
|
|
477
|
+
import { ref, onMounted, onUnmounted } from 'vue';
|
|
478
|
+
import { io } from 'socket.io-client';
|
|
479
|
+
|
|
480
|
+
const articles = ref([]);
|
|
481
|
+
let socket;
|
|
482
|
+
|
|
483
|
+
onMounted(() => {
|
|
484
|
+
socket = io('http://localhost:1337');
|
|
485
|
+
|
|
486
|
+
socket.on('article:create', (article) => {
|
|
487
|
+
articles.value = [article, ...articles.value];
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
socket.on('article:update', (article) => {
|
|
491
|
+
const index = articles.value.findIndex(a => a.documentId === article.documentId);
|
|
492
|
+
if (index !== -1) {
|
|
493
|
+
articles.value[index] = article;
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
socket.on('article:delete', (data) => {
|
|
498
|
+
articles.value = articles.value.filter(a => a.documentId !== data.documentId);
|
|
499
|
+
});
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
onUnmounted(() => {
|
|
503
|
+
socket?.disconnect();
|
|
504
|
+
});
|
|
505
|
+
</script>
|
|
506
|
+
|
|
507
|
+
<template>
|
|
508
|
+
<div v-for="article in articles" :key="article.documentId">
|
|
509
|
+
{{ article.title }}
|
|
510
|
+
</div>
|
|
511
|
+
</template>
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### Entity-Specific Subscriptions
|
|
515
|
+
|
|
516
|
+
Subscribe to updates for specific entities only:
|
|
517
|
+
|
|
518
|
+
```javascript
|
|
519
|
+
// Client-side: Subscribe to a specific article
|
|
520
|
+
socket.emit('subscribe-entity', {
|
|
521
|
+
uid: 'api::article.article',
|
|
522
|
+
id: 123
|
|
523
|
+
}, (response) => {
|
|
524
|
+
if (response.success) {
|
|
525
|
+
console.log('Subscribed to article 123');
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
// Now you only receive updates for article 123
|
|
530
|
+
socket.on('article:update', (data) => {
|
|
531
|
+
console.log('Article 123 was updated:', data);
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
// Unsubscribe when done
|
|
535
|
+
socket.emit('unsubscribe-entity', {
|
|
536
|
+
uid: 'api::article.article',
|
|
537
|
+
id: 123
|
|
538
|
+
});
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
**Benefits:**
|
|
542
|
+
- Reduced bandwidth - only receive relevant updates
|
|
543
|
+
- Better performance - less client-side processing
|
|
544
|
+
- Built-in permission checks - respects user roles
|
|
545
|
+
|
|
546
|
+
### Room Management
|
|
547
|
+
|
|
548
|
+
Organize connections into rooms:
|
|
549
|
+
|
|
550
|
+
```javascript
|
|
551
|
+
// Server-side: Add socket to a room
|
|
552
|
+
strapi.$io.joinRoom(socketId, 'premium-users');
|
|
553
|
+
|
|
554
|
+
// Get all sockets in a room
|
|
555
|
+
const sockets = await strapi.$io.getSocketsInRoom('premium-users');
|
|
556
|
+
console.log(`${sockets.length} premium users online`);
|
|
557
|
+
|
|
558
|
+
// Broadcast to a specific room
|
|
559
|
+
strapi.$io.server.to('premium-users').emit('exclusive-offer', {
|
|
560
|
+
discount: 20,
|
|
561
|
+
expiresIn: '24h'
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
// Remove socket from a room
|
|
565
|
+
strapi.$io.leaveRoom(socketId, 'premium-users');
|
|
566
|
+
|
|
567
|
+
// Disconnect a specific socket
|
|
568
|
+
strapi.$io.disconnectSocket(socketId, 'Kicked by admin');
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
---
|
|
572
|
+
|
|
573
|
+
## Helper Functions
|
|
574
|
+
|
|
575
|
+
The plugin provides 12 utility functions available on `strapi.$io`:
|
|
576
|
+
|
|
577
|
+
### Room Management
|
|
578
|
+
|
|
579
|
+
#### `joinRoom(socketId, roomName)`
|
|
580
|
+
Add a socket to a room.
|
|
581
|
+
|
|
582
|
+
```javascript
|
|
583
|
+
const success = strapi.$io.joinRoom(socketId, 'premium-users');
|
|
584
|
+
// Returns: true if socket found and joined, false otherwise
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
#### `leaveRoom(socketId, roomName)`
|
|
588
|
+
Remove a socket from a room.
|
|
589
|
+
|
|
590
|
+
```javascript
|
|
591
|
+
const success = strapi.$io.leaveRoom(socketId, 'premium-users');
|
|
592
|
+
// Returns: true if socket found and left, false otherwise
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
#### `getSocketsInRoom(roomName)`
|
|
596
|
+
Get all sockets in a specific room.
|
|
597
|
+
|
|
598
|
+
```javascript
|
|
599
|
+
const sockets = await strapi.$io.getSocketsInRoom('premium-users');
|
|
600
|
+
// Returns: [{ id: 'socket-id', user: { username: 'john' } }, ...]
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
### Messaging
|
|
604
|
+
|
|
605
|
+
#### `sendPrivateMessage(socketId, event, data)`
|
|
606
|
+
Send a message to a specific socket.
|
|
607
|
+
|
|
608
|
+
```javascript
|
|
609
|
+
strapi.$io.sendPrivateMessage(socketId, 'order:shipped', {
|
|
610
|
+
orderId: 123,
|
|
611
|
+
trackingNumber: 'ABC123'
|
|
612
|
+
});
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
#### `broadcast(socketId, event, data)`
|
|
616
|
+
Broadcast from a socket to all other connected clients.
|
|
617
|
+
|
|
618
|
+
```javascript
|
|
619
|
+
strapi.$io.broadcast(socketId, 'user:typing', {
|
|
620
|
+
username: 'john',
|
|
621
|
+
channel: 'general'
|
|
622
|
+
});
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
#### `emitToNamespace(namespace, event, data)`
|
|
626
|
+
Emit an event to all clients in a namespace.
|
|
627
|
+
|
|
628
|
+
```javascript
|
|
629
|
+
strapi.$io.emitToNamespace('admin', 'alert', {
|
|
630
|
+
message: 'New user registered',
|
|
631
|
+
level: 'info'
|
|
632
|
+
});
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
### Connection Management
|
|
636
|
+
|
|
637
|
+
#### `disconnectSocket(socketId, reason)`
|
|
638
|
+
Force disconnect a specific socket.
|
|
639
|
+
|
|
640
|
+
```javascript
|
|
641
|
+
const success = strapi.$io.disconnectSocket(socketId, 'Session expired');
|
|
642
|
+
// Returns: true if socket found and disconnected, false otherwise
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
### Entity Subscriptions
|
|
646
|
+
|
|
647
|
+
#### `subscribeToEntity(socketId, uid, id)`
|
|
648
|
+
Subscribe a socket to a specific entity (server-side).
|
|
649
|
+
|
|
650
|
+
```javascript
|
|
651
|
+
const result = await strapi.$io.subscribeToEntity(socketId, 'api::article.article', 123);
|
|
652
|
+
// Returns: { success: true, room: 'api::article.article:123', uid, id }
|
|
653
|
+
// or: { success: false, error: 'Socket not found' }
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
#### `unsubscribeFromEntity(socketId, uid, id)`
|
|
657
|
+
Unsubscribe a socket from a specific entity.
|
|
658
|
+
|
|
659
|
+
```javascript
|
|
660
|
+
const result = strapi.$io.unsubscribeFromEntity(socketId, 'api::article.article', 123);
|
|
661
|
+
// Returns: { success: true, room: 'api::article.article:123', uid, id }
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
#### `getEntitySubscriptions(socketId)`
|
|
665
|
+
Get all entity subscriptions for a socket.
|
|
666
|
+
|
|
667
|
+
```javascript
|
|
668
|
+
const result = strapi.$io.getEntitySubscriptions(socketId);
|
|
669
|
+
// Returns: {
|
|
670
|
+
// success: true,
|
|
671
|
+
// subscriptions: [
|
|
672
|
+
// { uid: 'api::article.article', id: '123', room: 'api::article.article:123' }
|
|
673
|
+
// ]
|
|
674
|
+
// }
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
#### `emitToEntity(uid, id, event, data)`
|
|
678
|
+
Emit an event to all clients subscribed to a specific entity.
|
|
679
|
+
|
|
680
|
+
```javascript
|
|
681
|
+
strapi.$io.emitToEntity('api::article.article', 123, 'article:commented', {
|
|
682
|
+
commentId: 456,
|
|
683
|
+
author: 'jane'
|
|
684
|
+
});
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
#### `getEntityRoomSockets(uid, id)`
|
|
688
|
+
Get all sockets subscribed to a specific entity.
|
|
689
|
+
|
|
690
|
+
```javascript
|
|
691
|
+
const sockets = await strapi.$io.getEntityRoomSockets('api::article.article', 123);
|
|
692
|
+
// Returns: [{ id: 'socket-id', user: { username: 'john' } }, ...]
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
---
|
|
696
|
+
|
|
697
|
+
## Authentication
|
|
698
|
+
|
|
699
|
+
The plugin supports multiple authentication strategies.
|
|
700
|
+
|
|
701
|
+
### Public Access (No Authentication)
|
|
702
|
+
|
|
703
|
+
```javascript
|
|
704
|
+
const socket = io('http://localhost:1337');
|
|
705
|
+
// No auth - placed in 'Public' role room
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
### JWT Authentication (Users & Permissions)
|
|
709
|
+
|
|
710
|
+
```javascript
|
|
711
|
+
// 1. Get JWT token from login
|
|
712
|
+
const response = await fetch('http://localhost:1337/api/auth/local', {
|
|
713
|
+
method: 'POST',
|
|
714
|
+
headers: { 'Content-Type': 'application/json' },
|
|
715
|
+
body: JSON.stringify({
|
|
716
|
+
identifier: 'user@example.com',
|
|
717
|
+
password: 'password123'
|
|
718
|
+
})
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
const { jwt } = await response.json();
|
|
722
|
+
|
|
723
|
+
// 2. Connect with JWT
|
|
724
|
+
const socket = io('http://localhost:1337', {
|
|
725
|
+
auth: {
|
|
726
|
+
strategy: 'jwt',
|
|
727
|
+
token: jwt
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
// User is placed in their role room (e.g., 'Authenticated')
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
### API Token Authentication
|
|
735
|
+
|
|
736
|
+
```javascript
|
|
737
|
+
// 1. Create API token in Strapi Admin:
|
|
738
|
+
// Settings > Global Settings > API Tokens > Create new token
|
|
739
|
+
|
|
740
|
+
// 2. Connect with API token
|
|
741
|
+
const socket = io('http://localhost:1337', {
|
|
742
|
+
auth: {
|
|
743
|
+
strategy: 'api-token',
|
|
744
|
+
token: 'your-api-token-here'
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
### Permission Enforcement
|
|
750
|
+
|
|
751
|
+
Events are automatically filtered based on the user's role:
|
|
752
|
+
|
|
753
|
+
```javascript
|
|
754
|
+
// Authenticated user with 'Editor' role
|
|
755
|
+
socket.on('article:create', (data) => {
|
|
756
|
+
// Only receives events for content types they have permission to access
|
|
757
|
+
});
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
Configure permissions in the Strapi admin panel:
|
|
761
|
+
1. Go to **Settings > Users & Permissions > Roles**
|
|
762
|
+
2. Select a role (e.g., "Authenticated")
|
|
763
|
+
3. Configure Socket.IO permissions per content type
|
|
764
|
+
|
|
765
|
+
---
|
|
766
|
+
|
|
767
|
+
## Admin Panel
|
|
768
|
+
|
|
769
|
+
The plugin provides a full admin interface for configuration and monitoring.
|
|
770
|
+
|
|
771
|
+
### Dashboard Widget
|
|
772
|
+
|
|
773
|
+
> **Requires Strapi v5.13+**
|
|
774
|
+
|
|
775
|
+
After installation, a live statistics widget appears on your Strapi admin homepage:
|
|
776
|
+
|
|
777
|
+
**Widget Shows:**
|
|
778
|
+
- Live connection status (pulsing indicator when active)
|
|
779
|
+
- Active connections count
|
|
780
|
+
- Active rooms count
|
|
781
|
+
- Events per second
|
|
782
|
+
- Total events processed since startup
|
|
783
|
+
|
|
784
|
+
Updates automatically every 5 seconds.
|
|
785
|
+
|
|
786
|
+
### Settings Page
|
|
787
|
+
|
|
788
|
+
Navigate to **Settings > Socket.IO > Settings** for visual configuration:
|
|
789
|
+
|
|
790
|
+
**Path:** `/admin/settings/io/settings`
|
|
791
|
+
|
|
792
|
+
#### General Settings
|
|
793
|
+
- Enable/disable the plugin
|
|
794
|
+
- Configure CORS origins
|
|
795
|
+
- Set server options (ping timeout, etc.)
|
|
796
|
+
|
|
797
|
+
#### Content Types
|
|
798
|
+
- Enable automatic events for content types per role
|
|
799
|
+
- Select specific actions (create, update, delete)
|
|
800
|
+
- Configure role-based permissions
|
|
801
|
+
|
|
802
|
+
#### Events
|
|
803
|
+
- Include relations in events (`includeRelations`)
|
|
804
|
+
- Custom event names
|
|
805
|
+
- Exclude specific fields
|
|
806
|
+
- Only published content mode
|
|
807
|
+
|
|
808
|
+
#### Security
|
|
809
|
+
- Require authentication
|
|
810
|
+
- Rate limiting configuration
|
|
811
|
+
- IP whitelisting/blacklisting
|
|
812
|
+
- Input validation rules
|
|
813
|
+
|
|
814
|
+
#### Rooms
|
|
815
|
+
- Auto-join by role configuration
|
|
816
|
+
- Enable/disable private rooms
|
|
817
|
+
|
|
818
|
+
#### Advanced
|
|
819
|
+
- Configure namespaces
|
|
820
|
+
- Redis adapter settings for scaling
|
|
821
|
+
- Entity subscription limits
|
|
822
|
+
|
|
823
|
+
### Monitoring Page
|
|
824
|
+
|
|
825
|
+
Navigate to **Settings > Socket.IO > Monitoring** for live statistics:
|
|
826
|
+
|
|
827
|
+
**Path:** `/admin/settings/io/monitoring`
|
|
828
|
+
|
|
829
|
+
- View active connections with user details
|
|
830
|
+
- See event logs in real-time
|
|
831
|
+
- Monitor performance metrics (events/second)
|
|
832
|
+
- View entity subscription statistics
|
|
833
|
+
- Send test events
|
|
834
|
+
- Reset statistics
|
|
835
|
+
|
|
836
|
+
---
|
|
837
|
+
|
|
838
|
+
## Monitoring Service
|
|
839
|
+
|
|
840
|
+
Access monitoring data programmatically via the monitoring service:
|
|
841
|
+
|
|
842
|
+
```javascript
|
|
843
|
+
const monitoringService = strapi.plugin('io').service('monitoring');
|
|
844
|
+
```
|
|
845
|
+
|
|
846
|
+
### Available Methods
|
|
847
|
+
|
|
848
|
+
#### `getConnectionStats()`
|
|
849
|
+
Get current connection statistics.
|
|
850
|
+
|
|
851
|
+
```javascript
|
|
852
|
+
const stats = monitoringService.getConnectionStats();
|
|
853
|
+
// Returns:
|
|
854
|
+
// {
|
|
855
|
+
// connected: 42,
|
|
856
|
+
// rooms: [
|
|
857
|
+
// { name: 'Authenticated', members: 35, isEntityRoom: false },
|
|
858
|
+
// { name: 'api::article.article:123', members: 5, isEntityRoom: true }
|
|
859
|
+
// ],
|
|
860
|
+
// sockets: [
|
|
861
|
+
// {
|
|
862
|
+
// id: 'abc123',
|
|
863
|
+
// connected: true,
|
|
864
|
+
// rooms: ['Authenticated'],
|
|
865
|
+
// entitySubscriptions: [{ uid: 'api::article.article', id: '123', room: '...' }],
|
|
866
|
+
// handshake: { address: '::1', time: '...', query: {} },
|
|
867
|
+
// user: { id: 1, username: 'john' }
|
|
868
|
+
// }
|
|
869
|
+
// ],
|
|
870
|
+
// entitySubscriptions: {
|
|
871
|
+
// total: 15,
|
|
872
|
+
// byContentType: { 'api::article.article': 10, 'api::comment.comment': 5 },
|
|
873
|
+
// rooms: ['api::article.article:123', ...]
|
|
874
|
+
// }
|
|
875
|
+
// }
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
#### `getEventStats()`
|
|
879
|
+
Get event statistics.
|
|
880
|
+
|
|
881
|
+
```javascript
|
|
882
|
+
const stats = monitoringService.getEventStats();
|
|
883
|
+
// Returns:
|
|
884
|
+
// {
|
|
885
|
+
// totalEvents: 1234,
|
|
886
|
+
// eventsByType: { 'create': 500, 'update': 600, 'delete': 134 },
|
|
887
|
+
// lastReset: 1703123456789,
|
|
888
|
+
// eventsPerSecond: '2.50'
|
|
889
|
+
// }
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
#### `getEventLog(limit)`
|
|
893
|
+
Get recent event log entries.
|
|
894
|
+
|
|
895
|
+
```javascript
|
|
896
|
+
const logs = monitoringService.getEventLog(50);
|
|
897
|
+
// Returns:
|
|
898
|
+
// [
|
|
899
|
+
// { timestamp: 1703123456789, type: 'create', data: {...} },
|
|
900
|
+
// { timestamp: 1703123456790, type: 'connect', data: {...} }
|
|
901
|
+
// ]
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
#### `logEvent(type, data)`
|
|
905
|
+
Manually log an event.
|
|
906
|
+
|
|
907
|
+
```javascript
|
|
908
|
+
monitoringService.logEvent('custom-event', {
|
|
909
|
+
action: 'user-action',
|
|
910
|
+
userId: 123
|
|
911
|
+
});
|
|
912
|
+
```
|
|
913
|
+
|
|
914
|
+
#### `resetStats()`
|
|
915
|
+
Reset all statistics and clear event log.
|
|
916
|
+
|
|
917
|
+
```javascript
|
|
918
|
+
monitoringService.resetStats();
|
|
919
|
+
```
|
|
920
|
+
|
|
921
|
+
#### `sendTestEvent(eventName, data)`
|
|
922
|
+
Send a test event to all connected clients.
|
|
923
|
+
|
|
924
|
+
```javascript
|
|
925
|
+
const result = monitoringService.sendTestEvent('test', { message: 'Hello!' });
|
|
926
|
+
// Returns:
|
|
927
|
+
// {
|
|
928
|
+
// success: true,
|
|
929
|
+
// eventName: 'test',
|
|
930
|
+
// data: { message: 'Hello!', timestamp: 1703123456789, test: true },
|
|
931
|
+
// recipients: 42
|
|
932
|
+
// }
|
|
933
|
+
```
|
|
934
|
+
|
|
935
|
+
### Use Cases
|
|
936
|
+
|
|
937
|
+
**Custom Analytics Dashboard:**
|
|
938
|
+
|
|
939
|
+
```javascript
|
|
940
|
+
// In a custom controller
|
|
941
|
+
async getAnalytics(ctx) {
|
|
942
|
+
const monitoring = strapi.plugin('io').service('monitoring');
|
|
943
|
+
|
|
944
|
+
ctx.body = {
|
|
945
|
+
connections: monitoring.getConnectionStats(),
|
|
946
|
+
events: monitoring.getEventStats(),
|
|
947
|
+
recentActivity: monitoring.getEventLog(10)
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
```
|
|
951
|
+
|
|
952
|
+
**Health Check Endpoint:**
|
|
953
|
+
|
|
954
|
+
```javascript
|
|
955
|
+
async healthCheck(ctx) {
|
|
956
|
+
const monitoring = strapi.plugin('io').service('monitoring');
|
|
957
|
+
const stats = monitoring.getConnectionStats();
|
|
958
|
+
|
|
959
|
+
ctx.body = {
|
|
960
|
+
status: 'healthy',
|
|
961
|
+
websocket: {
|
|
962
|
+
connected: stats.connected,
|
|
963
|
+
rooms: stats.rooms.length
|
|
964
|
+
}
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
```
|
|
968
|
+
|
|
969
|
+
---
|
|
970
|
+
|
|
971
|
+
## TypeScript Support
|
|
972
|
+
|
|
973
|
+
Full TypeScript definitions are included for excellent IDE support.
|
|
974
|
+
|
|
975
|
+
### Import Types
|
|
976
|
+
|
|
977
|
+
```typescript
|
|
978
|
+
import type {
|
|
979
|
+
SocketIO,
|
|
980
|
+
SocketIOConfig,
|
|
981
|
+
EmitOptions,
|
|
982
|
+
RawEmitOptions,
|
|
983
|
+
PluginSettings,
|
|
984
|
+
MonitoringService,
|
|
985
|
+
SettingsService,
|
|
986
|
+
ConnectionStats,
|
|
987
|
+
EventStats
|
|
988
|
+
} from '@strapi-community/plugin-io/types';
|
|
989
|
+
```
|
|
990
|
+
|
|
991
|
+
### Configuration Example
|
|
992
|
+
|
|
993
|
+
```typescript
|
|
994
|
+
// config/plugins.ts
|
|
995
|
+
import type { SocketIOConfig } from '@strapi-community/plugin-io/types';
|
|
996
|
+
|
|
997
|
+
export default {
|
|
998
|
+
io: {
|
|
999
|
+
enabled: true,
|
|
1000
|
+
config: {
|
|
1001
|
+
contentTypes: [
|
|
1002
|
+
{
|
|
1003
|
+
uid: 'api::article.article',
|
|
1004
|
+
actions: ['create', 'update', 'delete']
|
|
1005
|
+
}
|
|
1006
|
+
],
|
|
1007
|
+
socket: {
|
|
1008
|
+
serverOptions: {
|
|
1009
|
+
cors: {
|
|
1010
|
+
origin: process.env.CLIENT_URL || 'http://localhost:3000',
|
|
1011
|
+
methods: ['GET', 'POST']
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
} satisfies SocketIOConfig
|
|
1016
|
+
}
|
|
1017
|
+
};
|
|
1018
|
+
```
|
|
1019
|
+
|
|
1020
|
+
### Usage Example
|
|
1021
|
+
|
|
1022
|
+
```typescript
|
|
1023
|
+
// In your Strapi code
|
|
1024
|
+
import type { SocketIO } from '@strapi-community/plugin-io/types';
|
|
1025
|
+
|
|
1026
|
+
// Type-safe access
|
|
1027
|
+
const io: SocketIO = strapi.$io;
|
|
1028
|
+
|
|
1029
|
+
// All methods have full IntelliSense
|
|
1030
|
+
await io.emit({
|
|
1031
|
+
event: 'create',
|
|
1032
|
+
schema: strapi.contentTypes['api::article.article'],
|
|
1033
|
+
data: { title: 'New Article' }
|
|
1034
|
+
});
|
|
1035
|
+
|
|
1036
|
+
// Helper functions are typed
|
|
1037
|
+
const sockets = await io.getSocketsInRoom('premium-users');
|
|
1038
|
+
io.sendPrivateMessage(sockets[0].id, 'welcome', { message: 'Hello!' });
|
|
1039
|
+
```
|
|
1040
|
+
|
|
1041
|
+
---
|
|
1042
|
+
|
|
1043
|
+
## Performance
|
|
1044
|
+
|
|
1045
|
+
The plugin is optimized for production environments.
|
|
1046
|
+
|
|
1047
|
+
### Benchmarks
|
|
1048
|
+
|
|
1049
|
+
- **Concurrent Connections**: 2500+ simultaneous connections
|
|
1050
|
+
- **Memory Usage**: ~17KB per connection
|
|
1051
|
+
- **Event Throughput**: 10,000+ events/second
|
|
1052
|
+
- **Latency**: <10ms for local broadcasts
|
|
1053
|
+
|
|
1054
|
+
### Optimizations
|
|
1055
|
+
|
|
1056
|
+
#### Intelligent Caching
|
|
1057
|
+
Role and permission data is cached for 5 minutes, reducing database queries by up to 90%.
|
|
1058
|
+
|
|
1059
|
+
#### Debouncing
|
|
1060
|
+
Bulk operations are automatically debounced to prevent event flooding during data imports.
|
|
1061
|
+
|
|
1062
|
+
#### Parallel Processing
|
|
1063
|
+
All event emissions are processed in parallel for maximum throughput.
|
|
1064
|
+
|
|
1065
|
+
#### Connection Pooling
|
|
1066
|
+
Efficient connection management with automatic cleanup of stale connections.
|
|
1067
|
+
|
|
1068
|
+
### Production Configuration
|
|
1069
|
+
|
|
1070
|
+
```javascript
|
|
1071
|
+
// config/plugins.js (production)
|
|
1072
|
+
module.exports = ({ env }) => ({
|
|
1073
|
+
io: {
|
|
1074
|
+
enabled: true,
|
|
1075
|
+
config: {
|
|
1076
|
+
contentTypes: env.json('SOCKET_IO_CONTENT_TYPES'),
|
|
1077
|
+
|
|
1078
|
+
socket: {
|
|
1079
|
+
serverOptions: {
|
|
1080
|
+
cors: {
|
|
1081
|
+
origin: env('CLIENT_URL'),
|
|
1082
|
+
credentials: true
|
|
1083
|
+
},
|
|
1084
|
+
// Optimize for production
|
|
1085
|
+
pingTimeout: 60000,
|
|
1086
|
+
pingInterval: 25000,
|
|
1087
|
+
maxHttpBufferSize: 1e6,
|
|
1088
|
+
transports: ['websocket', 'polling']
|
|
1089
|
+
}
|
|
1090
|
+
},
|
|
1091
|
+
|
|
1092
|
+
// Use Redis for horizontal scaling
|
|
1093
|
+
hooks: {
|
|
1094
|
+
init: async ({ strapi, $io }) => {
|
|
1095
|
+
const { createAdapter } = require('@socket.io/redis-adapter');
|
|
1096
|
+
const { createClient } = require('redis');
|
|
1097
|
+
|
|
1098
|
+
const pubClient = createClient({ url: env('REDIS_URL') });
|
|
1099
|
+
const subClient = pubClient.duplicate();
|
|
1100
|
+
|
|
1101
|
+
await Promise.all([pubClient.connect(), subClient.connect()]);
|
|
1102
|
+
|
|
1103
|
+
$io.server.adapter(createAdapter(pubClient, subClient));
|
|
1104
|
+
|
|
1105
|
+
strapi.log.info('[Socket.IO] Redis adapter connected');
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
});
|
|
1111
|
+
```
|
|
1112
|
+
|
|
1113
|
+
---
|
|
1114
|
+
|
|
1115
|
+
## Migration Guide
|
|
1116
|
+
|
|
1117
|
+
### From v2 (Strapi v4) to v5 (Strapi v5)
|
|
1118
|
+
|
|
1119
|
+
**Good news:** The API is 100% compatible! Most projects migrate in under 1 hour.
|
|
1120
|
+
|
|
1121
|
+
#### Quick Migration Steps
|
|
1122
|
+
|
|
1123
|
+
1. **Update Strapi to v5**
|
|
1124
|
+
```bash
|
|
1125
|
+
npm install @strapi/strapi@5 @strapi/plugin-users-permissions@5
|
|
1126
|
+
```
|
|
1127
|
+
|
|
1128
|
+
2. **Update the plugin**
|
|
1129
|
+
```bash
|
|
1130
|
+
npm uninstall strapi-plugin-io
|
|
1131
|
+
npm install @strapi-community/plugin-io@latest
|
|
1132
|
+
```
|
|
1133
|
+
|
|
1134
|
+
3. **Test your application**
|
|
1135
|
+
```bash
|
|
1136
|
+
npm run develop
|
|
1137
|
+
```
|
|
1138
|
+
|
|
1139
|
+
Your configuration stays the same - no code changes needed!
|
|
1140
|
+
|
|
1141
|
+
#### What Changed
|
|
1142
|
+
|
|
1143
|
+
- **Package name**: `strapi-plugin-io` -> `@strapi-community/plugin-io`
|
|
1144
|
+
- **Package structure**: Uses new Strapi v5 Plugin SDK
|
|
1145
|
+
- **Dependencies**: Updated to Strapi v5 peer dependencies
|
|
1146
|
+
- **Build process**: Optimized build with modern tooling
|
|
1147
|
+
|
|
1148
|
+
#### What Stayed the Same
|
|
1149
|
+
|
|
1150
|
+
- All API methods work identically
|
|
1151
|
+
- Configuration format unchanged
|
|
1152
|
+
- Client-side code works as-is
|
|
1153
|
+
- Same helper functions
|
|
1154
|
+
- Same event format
|
|
1155
|
+
|
|
1156
|
+
For detailed migration instructions, see [docs/guide/migration.md](./docs/guide/migration.md).
|
|
1157
|
+
|
|
1158
|
+
---
|
|
1159
|
+
|
|
1160
|
+
## Documentation
|
|
1161
|
+
|
|
1162
|
+
### Official Documentation
|
|
1163
|
+
|
|
1164
|
+
- **[Online Documentation](https://strapi-plugin-io.netlify.app/)** - Complete interactive docs
|
|
1165
|
+
- **[API Reference](./docs/api/io-class.md)** - All methods and properties
|
|
1166
|
+
- **[Configuration Guide](./docs/api/plugin-config.md)** - Detailed configuration options
|
|
1167
|
+
- **[Usage Examples](./docs/examples/)** - Real-world use cases
|
|
1168
|
+
- **[Migration Guide](./docs/guide/migration.md)** - Upgrade from v4 to v5
|
|
1169
|
+
|
|
1170
|
+
### Guides
|
|
1171
|
+
|
|
1172
|
+
- **[Getting Started](./docs/guide/getting-started.md)** - Step-by-step setup
|
|
1173
|
+
- **[Widget Guide](./docs/guide/widget.md)** - Dashboard widget configuration
|
|
1174
|
+
|
|
1175
|
+
### Examples
|
|
1176
|
+
|
|
1177
|
+
- **[Content Types](./docs/examples/content-types.md)** - Automatic CRUD events
|
|
1178
|
+
- **[Custom Events](./docs/examples/events.md)** - Server-client communication
|
|
1179
|
+
- **[Hooks & Adapters](./docs/examples/hooks.md)** - Redis, MongoDB integration
|
|
1180
|
+
|
|
1181
|
+
---
|
|
1182
|
+
|
|
1183
|
+
## Related Plugins
|
|
1184
|
+
|
|
1185
|
+
Build complete real-time applications with these complementary Strapi v5 plugins:
|
|
1186
|
+
|
|
1187
|
+
### [Magic-Mail](https://github.com/Schero94/Magic-Mail)
|
|
1188
|
+
Enterprise email management with OAuth 2.0 support. Perfect for sending transactional emails triggered by Socket.IO events.
|
|
1189
|
+
|
|
1190
|
+
**Use case:** Send email notifications when real-time events occur.
|
|
1191
|
+
|
|
1192
|
+
### [Magic-Sessionmanager](https://github.com/Schero94/Magic-Sessionmanager)
|
|
1193
|
+
Advanced session tracking and monitoring. Track Socket.IO connections, monitor active users, and analyze session patterns.
|
|
1194
|
+
|
|
1195
|
+
**Use case:** Monitor who's connected to your WebSocket server in real-time.
|
|
1196
|
+
|
|
1197
|
+
### [Magicmark](https://github.com/Schero94/Magicmark)
|
|
1198
|
+
Bookmark management system with real-time sync. Share bookmarks instantly with your team using Socket.IO integration.
|
|
1199
|
+
|
|
1200
|
+
**Use case:** Collaborative bookmark management with live updates.
|
|
1201
|
+
|
|
1202
|
+
---
|
|
1203
|
+
|
|
1204
|
+
## Contributing
|
|
1205
|
+
|
|
1206
|
+
We welcome contributions! Here's how you can help:
|
|
1207
|
+
|
|
1208
|
+
### Report Bugs
|
|
1209
|
+
|
|
1210
|
+
Found a bug? [Open an issue](https://github.com/strapi-community/strapi-plugin-io/issues) with:
|
|
1211
|
+
- Strapi version
|
|
1212
|
+
- Plugin version
|
|
1213
|
+
- Steps to reproduce
|
|
1214
|
+
- Expected vs actual behavior
|
|
1215
|
+
|
|
1216
|
+
### Suggest Features
|
|
1217
|
+
|
|
1218
|
+
Have an idea? [Start a discussion](https://github.com/strapi-community/strapi-plugin-io/discussions) to:
|
|
1219
|
+
- Describe the feature
|
|
1220
|
+
- Explain the use case
|
|
1221
|
+
- Discuss implementation
|
|
1222
|
+
|
|
1223
|
+
### Submit Pull Requests
|
|
1224
|
+
|
|
1225
|
+
1. Fork the repository
|
|
1226
|
+
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
|
1227
|
+
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
1228
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
1229
|
+
5. Open a Pull Request
|
|
1230
|
+
|
|
1231
|
+
### Development Setup
|
|
1232
|
+
|
|
1233
|
+
```bash
|
|
1234
|
+
# Clone the repository
|
|
1235
|
+
git clone https://github.com/strapi-community/strapi-plugin-io.git
|
|
1236
|
+
cd strapi-plugin-io
|
|
1237
|
+
|
|
1238
|
+
# Install dependencies
|
|
1239
|
+
npm install
|
|
1240
|
+
|
|
1241
|
+
# Build the plugin
|
|
1242
|
+
npm run build
|
|
1243
|
+
|
|
1244
|
+
# Run in watch mode
|
|
1245
|
+
npm run watch
|
|
1246
|
+
|
|
1247
|
+
# Verify structure
|
|
1248
|
+
npm run verify
|
|
1249
|
+
```
|
|
1250
|
+
|
|
1251
|
+
---
|
|
1252
|
+
|
|
1253
|
+
## Support
|
|
1254
|
+
|
|
1255
|
+
- **Documentation**: https://strapi-plugin-io.netlify.app/
|
|
1256
|
+
- **GitHub Issues**: https://github.com/strapi-community/strapi-plugin-io/issues
|
|
1257
|
+
- **GitHub Discussions**: https://github.com/strapi-community/strapi-plugin-io/discussions
|
|
1258
|
+
- **Strapi Discord**: https://discord.strapi.io
|
|
1259
|
+
|
|
1260
|
+
---
|
|
1261
|
+
|
|
1262
|
+
## License
|
|
1263
|
+
|
|
1264
|
+
[MIT License](./LICENSE)
|
|
1265
|
+
|
|
1266
|
+
Copyright (c) 2024 Strapi Community
|
|
1267
|
+
|
|
1268
|
+
---
|
|
1269
|
+
|
|
1270
|
+
## Credits
|
|
1271
|
+
|
|
1272
|
+
**Original Authors:**
|
|
1273
|
+
- [@ComfortablyCoding](https://github.com/ComfortablyCoding)
|
|
1274
|
+
- [@hrdunn](https://github.com/hrdunn)
|
|
1275
|
+
|
|
1276
|
+
**Enhanced and Maintained by:**
|
|
1277
|
+
- [@Schero94](https://github.com/Schero94)
|
|
1278
|
+
|
|
1279
|
+
**Maintained until:** December 2026
|
|
1280
|
+
|
|
1281
|
+
---
|
|
1282
|
+
|
|
1283
|
+
## Changelog
|
|
1284
|
+
|
|
1285
|
+
### v5.0.0 (Latest)
|
|
1286
|
+
- Strapi v5 support
|
|
1287
|
+
- Package renamed to `@strapi-community/plugin-io`
|
|
1288
|
+
- Enhanced TypeScript support
|
|
1289
|
+
- Entity-specific subscriptions
|
|
1290
|
+
- 12 helper functions
|
|
1291
|
+
- Admin panel with monitoring dashboard
|
|
1292
|
+
- Performance optimizations
|
|
1293
|
+
- Updated documentation
|
|
1294
|
+
|
|
1295
|
+
For full changelog, see [CHANGELOG.md](./CHANGELOG.md).
|
|
1296
|
+
|
|
1297
|
+
---
|
|
1298
|
+
|
|
1299
|
+
<div align="center">
|
|
1300
|
+
|
|
1301
|
+
**[Documentation](https://strapi-plugin-io.netlify.app/)** |
|
|
1302
|
+
**[API Reference](./docs/api/io-class.md)** |
|
|
1303
|
+
**[Examples](./docs/examples/)** |
|
|
1304
|
+
**[GitHub](https://github.com/strapi-community/strapi-plugin-io)**
|
|
1305
|
+
|
|
1306
|
+
Made with love for the Strapi community
|
|
1307
|
+
|
|
1308
|
+
</div>
|