@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/package.json ADDED
@@ -0,0 +1,120 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/package",
3
+ "name": "@strapi-community/plugin-io",
4
+ "version": "1.0.0",
5
+ "description": "A plugin for Strapi CMS that provides the ability for Socket IO integration",
6
+ "keywords": [
7
+ "strapi",
8
+ "strapi-plugin",
9
+ "plugin",
10
+ "strapi plugin",
11
+ "socket",
12
+ "socket io",
13
+ "io"
14
+ ],
15
+ "type": "commonjs",
16
+ "exports": {
17
+ "./package.json": "./package.json",
18
+ "./strapi-admin": {
19
+ "source": "./admin/src/index.js",
20
+ "import": "./dist/admin/index.mjs",
21
+ "require": "./dist/admin/index.js",
22
+ "default": "./dist/admin/index.js"
23
+ },
24
+ "./strapi-server": {
25
+ "source": "./server/index.js",
26
+ "import": "./dist/server/index.mjs",
27
+ "require": "./dist/server/index.js",
28
+ "default": "./dist/server/index.js"
29
+ }
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "types.d.ts",
34
+ "README.md"
35
+ ],
36
+ "scripts": {
37
+ "build": "strapi-plugin build",
38
+ "watch": "strapi-plugin watch",
39
+ "watch:link": "strapi-plugin watch:link",
40
+ "verify": "strapi-plugin verify",
41
+ "lint": "eslint . --fix",
42
+ "format": "prettier --write \"./**/*.{js,json,yml,md}\""
43
+ },
44
+ "dependencies": {
45
+ "date-fns": "^3.6.0",
46
+ "socket.io": "^4.8.1",
47
+ "zod": "^3.24.1"
48
+ },
49
+ "devDependencies": {
50
+ "@semantic-release/changelog": "^6.0.3",
51
+ "@semantic-release/commit-analyzer": "^13.0.0",
52
+ "@semantic-release/git": "^10.0.1",
53
+ "@semantic-release/github": "^11.0.2",
54
+ "@semantic-release/npm": "^12.0.1",
55
+ "@semantic-release/release-notes-generator": "^14.0.1",
56
+ "@strapi/design-system": "^2.0.2",
57
+ "@strapi/icons": "^2.0.2",
58
+ "@strapi/sdk-plugin": "^5.3.2",
59
+ "@strapi/strapi": "^5.33.0",
60
+ "eslint": "^8.57.1",
61
+ "eslint-config-prettier": "^9.1.2",
62
+ "prettier": "^3.7.4",
63
+ "react": "^18.3.1",
64
+ "react-dom": "^18.3.1",
65
+ "react-router-dom": "^6.30.2",
66
+ "semantic-release": "^24.2.3",
67
+ "styled-components": "^6.1.19"
68
+ },
69
+ "peerDependencies": {
70
+ "@strapi/design-system": "^2.0.0-rc.1",
71
+ "@strapi/icons": "^2.0.0-rc.1",
72
+ "@strapi/strapi": "^5.0.0",
73
+ "react": "^18.0.0",
74
+ "react-dom": "^18.0.0",
75
+ "react-router-dom": "^6.0.0",
76
+ "styled-components": "^6.0.0"
77
+ },
78
+ "peerDependenciesMeta": {
79
+ "@strapi/design-system": {
80
+ "optional": true
81
+ },
82
+ "@strapi/icons": {
83
+ "optional": true
84
+ }
85
+ },
86
+ "strapi": {
87
+ "name": "io",
88
+ "displayName": "IO",
89
+ "description": "A plugin for Strapi CMS that provides the ability for Socket IO integration",
90
+ "kind": "plugin"
91
+ },
92
+ "engines": {
93
+ "node": ">=18.0.0 <=22.x.x",
94
+ "npm": ">=6.0.0"
95
+ },
96
+ "author": {
97
+ "name": "@ComfortablyCoding",
98
+ "url": "https://github.com/ComfortablyCoding"
99
+ },
100
+ "maintainers": [
101
+ {
102
+ "name": "@ComfortablyCoding",
103
+ "url": "https://github.com/ComfortablyCoding",
104
+ "lead": true
105
+ },
106
+ {
107
+ "name": "@hrdunn",
108
+ "url": "https://github.com/hrdunn"
109
+ }
110
+ ],
111
+ "homepage": "https://github.com/strapi-community/plugin-io#readme",
112
+ "repository": {
113
+ "type": "git",
114
+ "url": "https://github.com/strapi-community/plugin-io.git"
115
+ },
116
+ "bugs": {
117
+ "url": "https://github.com/strapi-community/plugin-io/issues"
118
+ },
119
+ "license": "MIT"
120
+ }
package/types.d.ts ADDED
@@ -0,0 +1,362 @@
1
+ /// <reference types="node" />
2
+ import { Server as SocketIOServer, Socket, Namespace } from 'socket.io';
3
+ import type { Core } from '@strapi/strapi';
4
+
5
+ declare module '@strapi/strapi' {
6
+ export interface Strapi {
7
+ /**
8
+ * Global Socket.IO instance with helper functions
9
+ * @example
10
+ * strapi.$io.server.emit('notification', data);
11
+ * strapi.$io.joinRoom(socketId, 'premium-users');
12
+ */
13
+ $io: SocketIO;
14
+
15
+ /**
16
+ * Current plugin settings (read-only)
17
+ * @example
18
+ * const maxConnections = strapi.$ioSettings.connection.maxConnections;
19
+ */
20
+ $ioSettings?: PluginSettings;
21
+ }
22
+ }
23
+
24
+ export interface SocketIOConfig {
25
+ events?: SocketEvent[];
26
+ hooks?: {
27
+ init?: (context: { strapi: Core.Strapi; $io: SocketIO }) => void | Promise<void>;
28
+ };
29
+ contentTypes?: Array<string | ContentTypeConfig>;
30
+ socket?: {
31
+ serverOptions?: any;
32
+ };
33
+ /**
34
+ * Additional sensitive field names to exclude from emitted data.
35
+ * These are added to the default list which includes:
36
+ * password, resetPasswordToken, confirmationToken, refreshToken,
37
+ * accessToken, secret, apiKey, privateKey, token, salt, hash
38
+ *
39
+ * @example
40
+ * sensitiveFields: ['creditCard', 'ssn', 'socialSecurityNumber']
41
+ */
42
+ sensitiveFields?: string[];
43
+ }
44
+
45
+ export interface SocketEvent {
46
+ name: string;
47
+ handler: (
48
+ context: { strapi: Core.Strapi; io: SocketIO },
49
+ socket: Socket,
50
+ ...args: any[]
51
+ ) => void | Promise<void>;
52
+ }
53
+
54
+ /**
55
+ * Populate configuration for content type events.
56
+ * Supports multiple formats for flexibility.
57
+ */
58
+ export type PopulateConfig =
59
+ | '*' // Populate all relations (1 level deep)
60
+ | true // Alias for '*'
61
+ | string[] // Specific relations: ['author', 'category']
62
+ | Record<string, any>; // Strapi populate syntax: { author: { fields: ['name'] } }
63
+
64
+ export interface ContentTypeConfig {
65
+ /** Content type UID (e.g., 'api::article.article') */
66
+ uid: string;
67
+ /** Actions to emit events for. Defaults to all: ['create', 'update', 'delete'] */
68
+ actions?: Array<'create' | 'update' | 'delete'>;
69
+ /**
70
+ * Populate relations when emitting events.
71
+ * When configured, the plugin will refetch the entity with populated relations
72
+ * after create/update operations before emitting the event.
73
+ *
74
+ * @example
75
+ * // Populate all relations
76
+ * populate: '*'
77
+ *
78
+ * @example
79
+ * // Populate specific relations
80
+ * populate: ['author', 'category']
81
+ *
82
+ * @example
83
+ * // Strapi populate syntax with field selection
84
+ * populate: {
85
+ * author: { fields: ['username', 'email'] },
86
+ * category: true
87
+ * }
88
+ */
89
+ populate?: PopulateConfig;
90
+ }
91
+
92
+ export interface EmitOptions {
93
+ event: 'create' | 'update' | 'delete';
94
+ schema: any;
95
+ data: any;
96
+ }
97
+
98
+ export interface RawEmitOptions {
99
+ event: string;
100
+ data: any;
101
+ rooms?: string[];
102
+ }
103
+
104
+ export interface SocketIOMetrics {
105
+ totalEmits: number;
106
+ cachedLookups: number;
107
+ connectedSockets: number;
108
+ errors: number;
109
+ cacheSize: {
110
+ rooms: number;
111
+ abilities: number;
112
+ };
113
+ }
114
+
115
+ export class SocketIO {
116
+ constructor(options?: any);
117
+
118
+ /**
119
+ * Emit a content type event with sanitization and permission checking
120
+ */
121
+ emit(options: EmitOptions): Promise<void>;
122
+
123
+ /**
124
+ * Emit a raw event to specified rooms
125
+ */
126
+ raw(options: RawEmitOptions): Promise<void>;
127
+
128
+ /**
129
+ * Invalidate the internal cache
130
+ */
131
+ invalidateCache(): void;
132
+
133
+ /**
134
+ * Get current metrics
135
+ */
136
+ getMetrics(): SocketIOMetrics;
137
+
138
+ /**
139
+ * Get the underlying Socket.IO server instance
140
+ */
141
+ readonly server: SocketIOServer;
142
+
143
+ /**
144
+ * All configured namespaces
145
+ */
146
+ namespaces?: Record<string, Namespace>;
147
+
148
+ /**
149
+ * Join socket to room
150
+ */
151
+ joinRoom?(socketId: string, roomName: string): boolean;
152
+
153
+ /**
154
+ * Remove socket from room
155
+ */
156
+ leaveRoom?(socketId: string, roomName: string): boolean;
157
+
158
+ /**
159
+ * Get all sockets in room
160
+ */
161
+ getSocketsInRoom?(roomName: string): Promise<Array<{ id: string; user: any }>>;
162
+
163
+ /**
164
+ * Send private message to specific socket
165
+ */
166
+ sendPrivateMessage?(socketId: string, event: string, data: any): void;
167
+
168
+ /**
169
+ * Broadcast from socket to all others
170
+ */
171
+ broadcast?(socketId: string, event: string, data: any): void;
172
+
173
+ /**
174
+ * Emit to namespace
175
+ */
176
+ emitToNamespace?(namespace: string, event: string, data: any): void;
177
+
178
+ /**
179
+ * Force disconnect socket
180
+ */
181
+ disconnectSocket?(socketId: string, reason?: string): boolean;
182
+
183
+ /**
184
+ * Subscribe a socket to a specific entity (server-side)
185
+ */
186
+ subscribeToEntity?(socketId: string, uid: string, id: string | number): Promise<EntitySubscriptionResult>;
187
+
188
+ /**
189
+ * Unsubscribe a socket from a specific entity
190
+ */
191
+ unsubscribeFromEntity?(socketId: string, uid: string, id: string | number): EntitySubscriptionResult;
192
+
193
+ /**
194
+ * Get all entity subscriptions for a socket
195
+ */
196
+ getEntitySubscriptions?(socketId: string): EntitySubscriptionsResult;
197
+
198
+ /**
199
+ * Emit an event to all clients subscribed to a specific entity
200
+ */
201
+ emitToEntity?(uid: string, id: string | number, event: string, data: any): void;
202
+
203
+ /**
204
+ * Get all sockets subscribed to a specific entity
205
+ */
206
+ getEntityRoomSockets?(uid: string, id: string | number): Promise<Array<{ id: string; user: any }>>;
207
+
208
+ /**
209
+ * Cleanup and destroy the Socket.IO instance
210
+ */
211
+ destroy(): Promise<void>;
212
+ }
213
+
214
+ export interface EntitySubscriptionResult {
215
+ success: boolean;
216
+ room?: string;
217
+ uid?: string;
218
+ id?: string | number;
219
+ error?: string;
220
+ }
221
+
222
+ export interface EntitySubscriptionsResult {
223
+ success: boolean;
224
+ subscriptions?: Array<{ uid: string; id: string; room: string }>;
225
+ error?: string;
226
+ }
227
+
228
+ /**
229
+ * Plugin Settings
230
+ */
231
+ export interface PluginSettings {
232
+ enabled: boolean;
233
+ cors: { origins: string[] };
234
+ connection: {
235
+ maxConnections: number;
236
+ pingTimeout: number;
237
+ pingInterval: number;
238
+ connectionTimeout: number;
239
+ };
240
+ security: {
241
+ requireAuthentication: boolean;
242
+ rateLimiting: {
243
+ enabled: boolean;
244
+ maxEventsPerSecond: number;
245
+ };
246
+ ipWhitelist: string[];
247
+ ipBlacklist: string[];
248
+ };
249
+ events: {
250
+ customEventNames: boolean;
251
+ includeRelations: boolean;
252
+ excludeFields: string[];
253
+ onlyPublished: boolean;
254
+ };
255
+ rooms: {
256
+ autoJoinByRole: Record<string, string[]>;
257
+ enablePrivateRooms: boolean;
258
+ };
259
+ rolePermissions: Record<string, RolePermission>;
260
+ redis: {
261
+ enabled: boolean;
262
+ url: string;
263
+ };
264
+ namespaces: {
265
+ enabled: boolean;
266
+ list: Record<string, { requireAuth: boolean }>;
267
+ };
268
+ monitoring: {
269
+ enableConnectionLogging: boolean;
270
+ enableEventLogging: boolean;
271
+ maxEventLogSize: number;
272
+ };
273
+ }
274
+
275
+ export interface RolePermission {
276
+ canConnect: boolean;
277
+ allowCredentials: boolean;
278
+ allowedMethods: string[];
279
+ contentTypes: Record<string, {
280
+ create: boolean;
281
+ update: boolean;
282
+ delete: boolean;
283
+ }>;
284
+ }
285
+
286
+ /**
287
+ * Settings Service
288
+ */
289
+ export interface SettingsService {
290
+ getSettings(): Promise<PluginSettings>;
291
+ setSettings(newSettings: Partial<PluginSettings>): Promise<PluginSettings>;
292
+ getDefaultSettings(): PluginSettings;
293
+ }
294
+
295
+ /**
296
+ * Monitoring Service
297
+ */
298
+ export interface MonitoringService {
299
+ getConnectionStats(): ConnectionStats;
300
+ getEventStats(): EventStats;
301
+ getEventLog(limit?: number): EventLogEntry[];
302
+ logEvent(eventType: string, data?: any): void;
303
+ resetStats(): void;
304
+ sendTestEvent(eventName?: string, data?: any): TestEventResult;
305
+ }
306
+
307
+ export interface ConnectionStats {
308
+ connected: number;
309
+ rooms: Array<{ name: string; members: number; isEntityRoom?: boolean }>;
310
+ sockets: Array<{
311
+ id: string;
312
+ connected: boolean;
313
+ rooms: string[];
314
+ entitySubscriptions?: Array<{ uid: string; id: string; room: string }>;
315
+ handshake: {
316
+ address: string;
317
+ time: string;
318
+ query: Record<string, string>;
319
+ };
320
+ user: any;
321
+ }>;
322
+ entitySubscriptions?: {
323
+ total: number;
324
+ byContentType: Record<string, number>;
325
+ rooms: string[];
326
+ };
327
+ }
328
+
329
+ export interface EventStats {
330
+ totalEvents: number;
331
+ eventsByType: Record<string, number>;
332
+ lastReset: number;
333
+ eventsPerSecond: string | number;
334
+ }
335
+
336
+ export interface EventLogEntry {
337
+ timestamp: number;
338
+ type: string;
339
+ data: any;
340
+ }
341
+
342
+ export interface TestEventResult {
343
+ success: boolean;
344
+ eventName: string;
345
+ data: any;
346
+ recipients: number;
347
+ }
348
+
349
+ /**
350
+ * Extend Strapi plugin services
351
+ */
352
+ declare module '@strapi/strapi' {
353
+ export interface PluginServices {
354
+ io?: {
355
+ settings: SettingsService;
356
+ monitoring: MonitoringService;
357
+ };
358
+ }
359
+ }
360
+
361
+ export default SocketIO;
362
+