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 +22 -99
- package/dist/socketManager.d.ts +3 -3
- package/dist/socketManager.js +17 -12
- package/dist/types.d.ts +1 -39
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -108,26 +108,18 @@ await initSocketIO(server);
|
|
|
108
108
|
|
|
109
109
|
```typescript
|
|
110
110
|
// client.ts
|
|
111
|
-
import { io
|
|
112
|
-
import type {
|
|
113
|
-
ServerToClientEvents,
|
|
114
|
-
ClientToServerEvents,
|
|
115
|
-
} from "payload-socket-plugin";
|
|
111
|
+
import { io } from "socket.io-client";
|
|
116
112
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
|
119
|
+
// Subscribe to collection events
|
|
128
120
|
socket.emit("join-collection", "posts");
|
|
129
121
|
|
|
130
|
-
// Listen for events
|
|
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
|
-
#
|
|
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
|
-
|
|
370
|
+
Then pass them in your plugin configuration:
|
|
377
371
|
|
|
378
|
-
|
|
372
|
+
```typescript
|
|
373
|
+
socketPlugin({
|
|
374
|
+
redis: {
|
|
375
|
+
url: process.env.REDIS_URL,
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
```
|
|
379
379
|
|
|
380
|
-
|
|
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 `
|
|
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
|
|
package/dist/socketManager.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
42
|
+
getIO(): SocketIOServer | null;
|
|
43
43
|
/**
|
|
44
44
|
* Cleanup and close connections
|
|
45
45
|
*/
|
package/dist/socketManager.js
CHANGED
|
@@ -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
|
|
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 =
|
|
52
|
+
const redisUrl = this.options.redis?.url;
|
|
52
53
|
if (!redisUrl) {
|
|
53
|
-
payload_1.default.logger.warn("
|
|
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
|
|
102
|
-
socket.
|
|
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
|
-
|
|
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
|
|
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
|
|
30
|
+
export interface AuthenticatedSocket extends Socket {
|
|
69
31
|
user?: {
|
|
70
32
|
id: string | number;
|
|
71
33
|
email?: string;
|