payload-socket-plugin 1.1.2 → 1.1.3

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 CHANGED
@@ -108,18 +108,26 @@ await initSocketIO(server);
108
108
 
109
109
  ```typescript
110
110
  // client.ts
111
- import { io } from "socket.io-client";
111
+ import { io, Socket } from "socket.io-client";
112
+ import type {
113
+ ServerToClientEvents,
114
+ ClientToServerEvents,
115
+ } from "payload-socket-plugin";
112
116
 
113
- const socket = io("http://localhost:3000", {
114
- auth: {
115
- token: "your-jwt-token", // Get from Payload login
116
- },
117
- });
117
+ // Create a typed socket for full TypeScript support
118
+ const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io(
119
+ "http://localhost:3000",
120
+ {
121
+ auth: {
122
+ token: "your-jwt-token", // Get from Payload login
123
+ },
124
+ }
125
+ );
118
126
 
119
- // Subscribe to collection events
127
+ // Subscribe to collection events (fully typed!)
120
128
  socket.emit("join-collection", "posts");
121
129
 
122
- // Listen for events
130
+ // Listen for events (event parameter is fully typed!)
123
131
  socket.on("payload:event", (event) => {
124
132
  console.log("Event received:", event);
125
133
  // {
@@ -367,15 +375,97 @@ PAYLOAD_SECRET=your-secret-key
367
375
 
368
376
  ## TypeScript Types
369
377
 
378
+ The plugin provides full TypeScript support with typed Socket.IO events following [Socket.IO v4 TypeScript best practices](https://socket.io/docs/v4/typescript/).
379
+
380
+ ### Available Types
381
+
370
382
  ```typescript
371
383
  import type {
384
+ // Event interfaces for Socket.IO
385
+ ServerToClientEvents,
386
+ ClientToServerEvents,
387
+ InterServerEvents,
388
+ SocketData,
389
+
390
+ // Plugin types
372
391
  CollectionAuthorizationHandler,
373
392
  RealtimeEventPayload,
374
393
  AuthenticatedSocket,
375
394
  EventType,
395
+ RealtimeEventsPluginOptions,
376
396
  } from "payload-socket-plugin";
377
397
  ```
378
398
 
399
+ ### Typed Socket.IO Client
400
+
401
+ Use the event interfaces for full type safety on the client side:
402
+
403
+ ```typescript
404
+ import { io, Socket } from "socket.io-client";
405
+ import type {
406
+ ServerToClientEvents,
407
+ ClientToServerEvents,
408
+ } from "payload-socket-plugin";
409
+
410
+ // Create a typed socket client
411
+ const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io(
412
+ "http://localhost:3000",
413
+ {
414
+ auth: {
415
+ token: "your-jwt-token",
416
+ },
417
+ }
418
+ );
419
+
420
+ // Now you get full autocomplete and type checking!
421
+ socket.emit("subscribe", ["posts", "users"]); // ✅ Type-safe
422
+ socket.emit("join-collection", "posts"); // ✅ Type-safe
423
+
424
+ socket.on("payload:event", (event) => {
425
+ // event is typed as RealtimeEventPayload
426
+ console.log(event.collection, event.type, event.doc);
427
+ });
428
+
429
+ socket.on("payload:event:all", (event) => {
430
+ // event is typed as RealtimeEventPayload
431
+ console.log("Global event:", event);
432
+ });
433
+ ```
434
+
435
+ ### Event Interfaces
436
+
437
+ **ServerToClientEvents** - Events the server emits to clients:
438
+
439
+ ```typescript
440
+ interface ServerToClientEvents {
441
+ "payload:event": (event: RealtimeEventPayload) => void;
442
+ "payload:event:all": (event: RealtimeEventPayload) => void;
443
+ }
444
+ ```
445
+
446
+ **ClientToServerEvents** - Events clients can send to the server:
447
+
448
+ ```typescript
449
+ interface ClientToServerEvents {
450
+ subscribe: (collections: string | string[]) => void;
451
+ unsubscribe: (collections: string | string[]) => void;
452
+ "join-collection": (collection: string) => void;
453
+ }
454
+ ```
455
+
456
+ **SocketData** - Data stored on each socket:
457
+
458
+ ```typescript
459
+ interface SocketData {
460
+ user?: {
461
+ id: string | number;
462
+ email?: string;
463
+ collection?: string;
464
+ role?: string;
465
+ };
466
+ }
467
+ ```
468
+
379
469
  ## Troubleshooting
380
470
 
381
471
  ### Connection Issues
@@ -1,6 +1,6 @@
1
1
  import { Server as SocketIOServer } from "socket.io";
2
2
  import { Server as HTTPServer } from "http";
3
- import { RealtimeEventsPluginOptions, RealtimeEventPayload } from "./types";
3
+ import { RealtimeEventsPluginOptions, RealtimeEventPayload, ServerToClientEvents, ClientToServerEvents, InterServerEvents, SocketData } from "./types";
4
4
  /**
5
5
  * Socket.IO Manager for handling real-time events with Redis adapter
6
6
  * Supports multiple Payload instances for production environments
@@ -14,7 +14,7 @@ export declare class SocketIOManager {
14
14
  /**
15
15
  * Initialize Socket.IO server with Redis adapter
16
16
  */
17
- init(server: HTTPServer): Promise<SocketIOServer>;
17
+ init(server: HTTPServer): Promise<SocketIOServer<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>>;
18
18
  /**
19
19
  * Setup Redis adapter for multi-instance synchronization
20
20
  */
@@ -39,7 +39,7 @@ export declare class SocketIOManager {
39
39
  /**
40
40
  * Get Socket.IO server instance
41
41
  */
42
- getIO(): SocketIOServer | null;
42
+ getIO(): SocketIOServer<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData> | null;
43
43
  /**
44
44
  * Cleanup and close connections
45
45
  */
@@ -25,7 +25,7 @@ class SocketIOManager {
25
25
  */
26
26
  async init(server) {
27
27
  const { redis, socketIO = {} } = this.options;
28
- // Create Socket.IO server
28
+ // Create Socket.IO server with typed events
29
29
  this.io = new socket_io_1.Server(server, {
30
30
  path: socketIO.path || "/socket.io",
31
31
  cors: socketIO.cors || {
@@ -42,7 +42,6 @@ class SocketIOManager {
42
42
  this.setupAuthentication();
43
43
  // Setup connection handlers
44
44
  this.setupConnectionHandlers();
45
- payload_1.default.logger.info("Socket.IO server initialized with real-time events plugin");
46
45
  return this.io;
47
46
  }
48
47
  /**
@@ -68,7 +67,6 @@ class SocketIOManager {
68
67
  new Promise((resolve) => this.subClient.once("ready", resolve)),
69
68
  ]);
70
69
  this.io.adapter((0, redis_adapter_1.createAdapter)(this.pubClient, this.subClient));
71
- payload_1.default.logger.info("Redis adapter configured for Socket.IO multi-instance support");
72
70
  }
73
71
  catch (error) {
74
72
  payload_1.default.logger.error("Failed to setup Redis adapter:", error);
@@ -100,13 +98,15 @@ class SocketIOManager {
100
98
  if (!userDoc) {
101
99
  return next(new Error("User not found"));
102
100
  }
103
- // Attach user info
104
- socket.user = {
101
+ // Attach user info to socket.data
102
+ socket.data.user = {
105
103
  id: userDoc.id,
106
104
  email: userDoc.email,
107
105
  collection: decoded.collection || "users",
108
106
  role: userDoc.role,
109
107
  };
108
+ // Also attach to socket.user for backward compatibility
109
+ socket.user = socket.data.user;
110
110
  next();
111
111
  }
112
112
  catch (jwtError) {
@@ -124,7 +124,6 @@ class SocketIOManager {
124
124
  */
125
125
  setupConnectionHandlers() {
126
126
  this.io.on("connection", async (socket) => {
127
- payload_1.default.logger.info(`Client connected: ${socket.id}, User: ${socket.user?.email || socket.user?.id}`);
128
127
  // Allow clients to subscribe to specific collections
129
128
  socket.on("subscribe", (collections) => {
130
129
  const collectionList = Array.isArray(collections)
@@ -132,7 +131,6 @@ class SocketIOManager {
132
131
  : [collections];
133
132
  collectionList.forEach((collection) => {
134
133
  socket.join(`collection:${collection}`);
135
- payload_1.default.logger.info(`Client ${socket.id} subscribed to collection: ${collection}`);
136
134
  });
137
135
  });
138
136
  // Allow clients to unsubscribe from collections
@@ -142,21 +140,20 @@ class SocketIOManager {
142
140
  : [collections];
143
141
  collectionList.forEach((collection) => {
144
142
  socket.leave(`collection:${collection}`);
145
- payload_1.default.logger.info(`Client ${socket.id} unsubscribed from collection: ${collection}`);
146
143
  });
147
144
  });
148
145
  // Allow clients to join collection rooms (alias for subscribe)
149
146
  socket.on("join-collection", (collection) => {
150
147
  const roomName = `collection:${collection}`;
151
148
  socket.join(roomName);
152
- payload_1.default.logger.info(`Client ${socket.id} (${socket.user?.email}) joined collection room: ${roomName}`);
153
149
  });
154
150
  // Handle disconnection
155
151
  socket.on("disconnect", () => {
156
- payload_1.default.logger.info(`Client disconnected: ${socket.id}, User: ${socket.user?.email || socket.user?.id}`);
152
+ // Cleanup happens automatically
157
153
  });
158
154
  if (this.options.onSocketConnection) {
159
155
  try {
156
+ // Cast to AuthenticatedSocket for backward compatibility
160
157
  await this.options.onSocketConnection(socket, this.io, payload_1.default);
161
158
  }
162
159
  catch (error) {
@@ -170,7 +167,6 @@ class SocketIOManager {
170
167
  */
171
168
  async emitEvent(event) {
172
169
  if (!this.io) {
173
- payload_1.default.logger.warn("Socket.IO server not initialized, cannot emit event");
174
170
  return;
175
171
  }
176
172
  const { authorize, shouldEmit, transformEvent } = this.options;
@@ -189,9 +185,9 @@ class SocketIOManager {
189
185
  if (collectionHandler) {
190
186
  const sockets = await this.io.in(room).fetchSockets();
191
187
  for (const socket of sockets) {
192
- const authSocket = socket;
193
- if (authSocket.user) {
194
- const isAuthorized = await collectionHandler(authSocket.user, finalEvent);
188
+ const user = socket.data.user || socket.user;
189
+ if (user) {
190
+ const isAuthorized = await collectionHandler(user, finalEvent);
195
191
  if (isAuthorized) {
196
192
  socket.emit("payload:event", finalEvent);
197
193
  }
@@ -226,7 +222,6 @@ class SocketIOManager {
226
222
  if (this.subClient) {
227
223
  await this.subClient.quit();
228
224
  }
229
- payload_1.default.logger.info("Socket.IO server closed");
230
225
  }
231
226
  }
232
227
  exports.SocketIOManager = SocketIOManager;
package/dist/types.d.ts CHANGED
@@ -24,10 +24,48 @@ export interface RealtimeEventPayload {
24
24
  /** Timestamp of the event */
25
25
  timestamp: string;
26
26
  }
27
+ /**
28
+ * Events that the server can emit to clients
29
+ */
30
+ export interface ServerToClientEvents {
31
+ /** Real-time event for a specific collection */
32
+ "payload:event": (event: RealtimeEventPayload) => void;
33
+ /** Real-time event broadcast to all listeners */
34
+ "payload:event:all": (event: RealtimeEventPayload) => void;
35
+ }
36
+ /**
37
+ * Events that clients can send to the server
38
+ */
39
+ export interface ClientToServerEvents {
40
+ /** Subscribe to one or more collections */
41
+ subscribe: (collections: string | string[]) => void;
42
+ /** Unsubscribe from one or more collections */
43
+ unsubscribe: (collections: string | string[]) => void;
44
+ /** Join a collection room (alias for subscribe) */
45
+ "join-collection": (collection: string) => void;
46
+ }
47
+ /**
48
+ * Events for inter-server communication (when using Redis adapter)
49
+ */
50
+ export interface InterServerEvents {
51
+ ping: () => void;
52
+ }
53
+ /**
54
+ * Data stored on each socket instance
55
+ */
56
+ export interface SocketData {
57
+ user?: {
58
+ id: string | number;
59
+ email?: string;
60
+ collection?: string;
61
+ role?: string;
62
+ };
63
+ }
27
64
  /**
28
65
  * Socket.IO server instance with authentication
66
+ * @deprecated Use Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData> instead
29
67
  */
30
- export interface AuthenticatedSocket extends Socket {
68
+ export interface AuthenticatedSocket extends Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData> {
31
69
  user?: {
32
70
  id: string | number;
33
71
  email?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payload-socket-plugin",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "Real-time Socket.IO plugin for Payload CMS with Redis support",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",