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 +98 -8
- package/dist/socketManager.d.ts +3 -3
- package/dist/socketManager.js +10 -15
- package/dist/types.d.ts +39 -1
- package/package.json +1 -1
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
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 } 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
|
*/
|
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 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
|
-
|
|
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
|
|
193
|
-
if (
|
|
194
|
-
const isAuthorized = await collectionHandler(
|
|
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;
|