payload-socket-plugin 1.1.3 → 1.1.4

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,26 +108,18 @@ await initSocketIO(server);
108
108
 
109
109
  ```typescript
110
110
  // client.ts
111
- import { io, Socket } from "socket.io-client";
112
- import type {
113
- ServerToClientEvents,
114
- ClientToServerEvents,
115
- } from "payload-socket-plugin";
111
+ import { io } from "socket.io-client";
116
112
 
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
- );
113
+ const socket = io("http://localhost:3000", {
114
+ auth: {
115
+ token: "your-jwt-token", // Get from Payload login
116
+ },
117
+ });
126
118
 
127
- // Subscribe to collection events (fully typed!)
119
+ // Subscribe to collection events
128
120
  socket.emit("join-collection", "posts");
129
121
 
130
- // Listen for events (event parameter is fully typed!)
122
+ // Listen for events
131
123
  socket.on("payload:event", (event) => {
132
124
  console.log("Event received:", event);
133
125
  // {
@@ -365,107 +357,37 @@ This is handled automatically via the `"browser"` field in `package.json`, so yo
365
357
 
366
358
  ## Environment Variables
367
359
 
360
+ The plugin does not read environment variables directly. You can use environment variables in your configuration:
361
+
368
362
  ```bash
369
- # Required for Redis multi-instance support
363
+ # Example: Redis URL for multi-instance support
370
364
  REDIS_URL=redis://localhost:6379
371
365
 
372
366
  # Optional: Payload configuration
373
367
  PAYLOAD_SECRET=your-secret-key
374
368
  ```
375
369
 
376
- ## TypeScript Types
370
+ Then pass them in your plugin configuration:
377
371
 
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/).
372
+ ```typescript
373
+ socketPlugin({
374
+ redis: {
375
+ url: process.env.REDIS_URL,
376
+ },
377
+ });
378
+ ```
379
379
 
380
- ### Available Types
380
+ ## TypeScript Types
381
381
 
382
382
  ```typescript
383
383
  import type {
384
- // Event interfaces for Socket.IO
385
- ServerToClientEvents,
386
- ClientToServerEvents,
387
- InterServerEvents,
388
- SocketData,
389
-
390
- // Plugin types
391
384
  CollectionAuthorizationHandler,
392
385
  RealtimeEventPayload,
393
386
  AuthenticatedSocket,
394
387
  EventType,
395
- RealtimeEventsPluginOptions,
396
388
  } from "payload-socket-plugin";
397
389
  ```
398
390
 
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
-
469
391
  ## Troubleshooting
470
392
 
471
393
  ### Connection Issues
@@ -496,10 +418,11 @@ interface SocketData {
496
418
 
497
419
  **Solutions**:
498
420
 
499
- - Verify `REDIS_URL` environment variable is set correctly
421
+ - Verify `redis.url` is set correctly in plugin options
500
422
  - Check Redis server is running and accessible
501
423
  - Ensure both server instances use the same Redis URL
502
424
  - Check Redis logs for connection errors
425
+ - Make sure you're passing the Redis URL in the plugin configuration, not relying on environment variables
503
426
 
504
427
  ### TypeScript Errors
505
428
 
@@ -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, ServerToClientEvents, ClientToServerEvents, InterServerEvents, SocketData } from "./types";
3
+ import { RealtimeEventsPluginOptions, RealtimeEventPayload } 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<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>>;
17
+ init(server: HTTPServer): Promise<SocketIOServer>;
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<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData> | null;
42
+ getIO(): SocketIOServer | 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 with typed events
28
+ // Create Socket.IO server
29
29
  this.io = new socket_io_1.Server(server, {
30
30
  path: socketIO.path || "/socket.io",
31
31
  cors: socketIO.cors || {
@@ -42,15 +42,16 @@ 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");
45
46
  return this.io;
46
47
  }
47
48
  /**
48
49
  * Setup Redis adapter for multi-instance synchronization
49
50
  */
50
51
  async setupRedisAdapter() {
51
- const redisUrl = process.env.REDIS_URL;
52
+ const redisUrl = this.options.redis?.url;
52
53
  if (!redisUrl) {
53
- payload_1.default.logger.warn("REDIS_URL not configured. Skipping Redis adapter setup.");
54
+ payload_1.default.logger.warn("Redis URL not configured. Skipping Redis adapter setup. Set redis.url in plugin options.");
54
55
  return;
55
56
  }
56
57
  try {
@@ -67,6 +68,7 @@ class SocketIOManager {
67
68
  new Promise((resolve) => this.subClient.once("ready", resolve)),
68
69
  ]);
69
70
  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");
70
72
  }
71
73
  catch (error) {
72
74
  payload_1.default.logger.error("Failed to setup Redis adapter:", error);
@@ -98,15 +100,13 @@ class SocketIOManager {
98
100
  if (!userDoc) {
99
101
  return next(new Error("User not found"));
100
102
  }
101
- // Attach user info to socket.data
102
- socket.data.user = {
103
+ // Attach user info
104
+ socket.user = {
103
105
  id: userDoc.id,
104
106
  email: userDoc.email,
105
107
  collection: decoded.collection || "users",
106
108
  role: userDoc.role,
107
109
  };
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,6 +124,7 @@ 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}`);
127
128
  // Allow clients to subscribe to specific collections
128
129
  socket.on("subscribe", (collections) => {
129
130
  const collectionList = Array.isArray(collections)
@@ -131,6 +132,7 @@ class SocketIOManager {
131
132
  : [collections];
132
133
  collectionList.forEach((collection) => {
133
134
  socket.join(`collection:${collection}`);
135
+ payload_1.default.logger.info(`Client ${socket.id} subscribed to collection: ${collection}`);
134
136
  });
135
137
  });
136
138
  // Allow clients to unsubscribe from collections
@@ -140,20 +142,21 @@ class SocketIOManager {
140
142
  : [collections];
141
143
  collectionList.forEach((collection) => {
142
144
  socket.leave(`collection:${collection}`);
145
+ payload_1.default.logger.info(`Client ${socket.id} unsubscribed from collection: ${collection}`);
143
146
  });
144
147
  });
145
148
  // Allow clients to join collection rooms (alias for subscribe)
146
149
  socket.on("join-collection", (collection) => {
147
150
  const roomName = `collection:${collection}`;
148
151
  socket.join(roomName);
152
+ payload_1.default.logger.info(`Client ${socket.id} (${socket.user?.email}) joined collection room: ${roomName}`);
149
153
  });
150
154
  // Handle disconnection
151
155
  socket.on("disconnect", () => {
152
- // Cleanup happens automatically
156
+ payload_1.default.logger.info(`Client disconnected: ${socket.id}, User: ${socket.user?.email || socket.user?.id}`);
153
157
  });
154
158
  if (this.options.onSocketConnection) {
155
159
  try {
156
- // Cast to AuthenticatedSocket for backward compatibility
157
160
  await this.options.onSocketConnection(socket, this.io, payload_1.default);
158
161
  }
159
162
  catch (error) {
@@ -167,6 +170,7 @@ class SocketIOManager {
167
170
  */
168
171
  async emitEvent(event) {
169
172
  if (!this.io) {
173
+ payload_1.default.logger.warn("Socket.IO server not initialized, cannot emit event");
170
174
  return;
171
175
  }
172
176
  const { authorize, shouldEmit, transformEvent } = this.options;
@@ -185,9 +189,9 @@ class SocketIOManager {
185
189
  if (collectionHandler) {
186
190
  const sockets = await this.io.in(room).fetchSockets();
187
191
  for (const socket of sockets) {
188
- const user = socket.data.user || socket.user;
189
- if (user) {
190
- const isAuthorized = await collectionHandler(user, finalEvent);
192
+ const authSocket = socket;
193
+ if (authSocket.user) {
194
+ const isAuthorized = await collectionHandler(authSocket.user, finalEvent);
191
195
  if (isAuthorized) {
192
196
  socket.emit("payload:event", finalEvent);
193
197
  }
@@ -222,6 +226,7 @@ class SocketIOManager {
222
226
  if (this.subClient) {
223
227
  await this.subClient.quit();
224
228
  }
229
+ payload_1.default.logger.info("Socket.IO server closed");
225
230
  }
226
231
  }
227
232
  exports.SocketIOManager = SocketIOManager;
package/dist/types.d.ts CHANGED
@@ -24,48 +24,10 @@ 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
- }
64
27
  /**
65
28
  * Socket.IO server instance with authentication
66
- * @deprecated Use Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData> instead
67
29
  */
68
- export interface AuthenticatedSocket extends Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData> {
30
+ export interface AuthenticatedSocket extends Socket {
69
31
  user?: {
70
32
  id: string | number;
71
33
  email?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payload-socket-plugin",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
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",