@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/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
+ [![NPM Version](https://img.shields.io/npm/v/@strapi-community/plugin-io?style=flat-square)](https://www.npmjs.com/package/@strapi-community/plugin-io)
6
+ [![NPM Downloads](https://img.shields.io/npm/dm/@strapi-community/plugin-io?style=flat-square)](https://www.npmjs.com/package/@strapi-community/plugin-io)
7
+ [![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)
8
+ [![Strapi Version](https://img.shields.io/badge/strapi-v5-blueviolet?style=flat-square)](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>