playsocketjs 2.6.3 → 2.7.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/README.md +22 -2
- package/dist/playsocket-server.js +31 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -216,6 +216,26 @@ new PlaySocket(options: PlaySocketServerOptions)
|
|
|
216
216
|
| `server` | http.Server | - | Existing http server (optional) |
|
|
217
217
|
| `rateLimit` | number | 20 | Adjust the messages/second rate limit |
|
|
218
218
|
| `debug` | boolean | false | Set to true to enable extra logging |
|
|
219
|
+
| `verifyClient` | function | - | Optional callback to verify connections before WebSocket upgrade |
|
|
220
|
+
|
|
221
|
+
#### verifyClient callback
|
|
222
|
+
|
|
223
|
+
The `verifyClient` option allows you to implement custom connection verification logic, such as rate limiting, before the WebSocket handshake completes.
|
|
224
|
+
|
|
225
|
+
```javascript
|
|
226
|
+
const server = new PlaySocketServer({
|
|
227
|
+
server: httpServer,
|
|
228
|
+
path: '/socket',
|
|
229
|
+
verifyClient: (info, callback) => {
|
|
230
|
+
// info.req - the HTTP request object, info.origin - the Origin header value
|
|
231
|
+
const ip = info.req.headers['x-forwarded-for'] || info.req.socket.remoteAddress;
|
|
232
|
+
if (isRateLimited(ip)) return callback(false, 429, 'Too Many Requests');
|
|
233
|
+
callback(true);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
The callback signature is `callback(verified, code?, message?)` where `code` and `message` are the optional HTTP response status for rejected connections.
|
|
219
239
|
|
|
220
240
|
### Methods
|
|
221
241
|
|
|
@@ -226,7 +246,7 @@ new PlaySocket(options: PlaySocketServerOptions)
|
|
|
226
246
|
| `onEvent()` | `event: string, callback: Function` | `void` | Register a server-side event callback |
|
|
227
247
|
| `getRoomStorage()` | `roomId: string` | `object` | Get a snapshot of the current room storage |
|
|
228
248
|
| `updateRoomStorage()` | `roomId: string, key: string, type: 'set' \| 'array-add' \| 'array-add-unique' \| 'array-remove-matching' \| 'array-update-matching', value: any, updateValue?: any` | `void` | Update a key in the shared room storage from the server |
|
|
229
|
-
| `createRoom()` | `initialStorage?: object, size?: number, host?: string` | `object` | Create a room (returns object containing room ID and state)
|
|
249
|
+
| `createRoom()` | `initialStorage?: object, size?: number, host?: string` | `object` | Create a room (returns object containing room ID and state)|
|
|
230
250
|
| `destroyRoom()` | `roomId: string` | `void` | Destroy a room & kick all participants |
|
|
231
251
|
|
|
232
252
|
### Event types
|
|
@@ -239,7 +259,7 @@ new PlaySocket(options: PlaySocketServerOptions)
|
|
|
239
259
|
| `clientJoinedRoom` | `clientId: string, roomId: string` | Client joined a room (clients can only leave by disconnecting) | - |
|
|
240
260
|
| `clientJoinRequested` | `clientId: string, roomId: string` | Client requests to join a room | Return `false` or rejection reason `string` to block |
|
|
241
261
|
| `roomCreated` | `roomId: string` | Client created a room | - |
|
|
242
|
-
| `roomDestroyed` | `roomId: string` | Room was destroyed (happens when all participants leave) | - |
|
|
262
|
+
| `roomDestroyed` | `roomId: string` | Room was destroyed (happens when all participants leave, unless room host is "server") | - |
|
|
243
263
|
| `roomCreationRequested` | `{clientId: string, initialStorage: object}` | Room creation requested by client | Return `object` to override initial storage, `false` to deny |
|
|
244
264
|
| `storageUpdated` | `{clientId: string, roomId: string, update: object, storage: object}` | Room storage property updated | - |
|
|
245
265
|
| `storageUpdateRequested` | `{clientId: string, roomId: string, update: object, storage: object}` | Room storage property update requested by client | Return `false` to block the update |
|
|
@@ -2119,9 +2119,10 @@ class PlaySocketServer {
|
|
|
2119
2119
|
* @param {string} [options.path='/'] - WebSocket endpoint path
|
|
2120
2120
|
* @param {boolean} [options.debug=false] - Enable debug logging
|
|
2121
2121
|
* @param {number} [options.rateLimit=20] - Maximum number of operations per second per client
|
|
2122
|
+
* @param {Function} [options.verifyClient] - Optional callback to verify client connections before upgrade. Receives (info, callback) where info contains { req, origin } and callback is (verified, code?, message?) => void
|
|
2122
2123
|
*/
|
|
2123
2124
|
constructor(options = {}) {
|
|
2124
|
-
const { server, port = 3000, path = '/', debug = false, rateLimit = 20 } = options;
|
|
2125
|
+
const { server, port = 3000, path = '/', debug = false, rateLimit = 20, verifyClient } = options;
|
|
2125
2126
|
|
|
2126
2127
|
if (debug) this.#debug = true; // Enable extra logging
|
|
2127
2128
|
|
|
@@ -2141,7 +2142,8 @@ class PlaySocketServer {
|
|
|
2141
2142
|
// Create WebSocket server
|
|
2142
2143
|
this.#wss = new WebSocketServer({
|
|
2143
2144
|
server: this.#server,
|
|
2144
|
-
path
|
|
2145
|
+
path,
|
|
2146
|
+
...(verifyClient && { verifyClient })
|
|
2145
2147
|
});
|
|
2146
2148
|
|
|
2147
2149
|
// Set up ws event handlers
|
|
@@ -2257,6 +2259,9 @@ class PlaySocketServer {
|
|
|
2257
2259
|
const formerRoomId = this.#clientRooms.get(data.id);
|
|
2258
2260
|
const formerRoom = this.#rooms[formerRoomId];
|
|
2259
2261
|
if (formerRoom) {
|
|
2262
|
+
// If the room has no host (they were the only one and disconnected), restore them as host
|
|
2263
|
+
if (formerRoom.host === null) formerRoom.host = data.id;
|
|
2264
|
+
|
|
2260
2265
|
if (this.#debug) console.log(`State sent for reconnection for room ${formerRoomId}:`, formerRoom.crdtManager.getState);
|
|
2261
2266
|
roomData = {
|
|
2262
2267
|
state: formerRoom.crdtManager.getState,
|
|
@@ -2339,6 +2344,17 @@ class PlaySocketServer {
|
|
|
2339
2344
|
version: this.#roomVersions.get(roomId)
|
|
2340
2345
|
}), { binary: true });
|
|
2341
2346
|
|
|
2347
|
+
// If the room has no host (previous host disconnected and was the only participant), make joiner the host
|
|
2348
|
+
// Since the room had to be empty, only the joiner needs to be notified
|
|
2349
|
+
const becameHost = room.host === null;
|
|
2350
|
+
if (becameHost) {
|
|
2351
|
+
room.host = ws.clientId;
|
|
2352
|
+
ws.send(encode({
|
|
2353
|
+
type: 'host_migrated',
|
|
2354
|
+
newHost: room.host,
|
|
2355
|
+
}), { binary: true });
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2342
2358
|
// Notify existing participants
|
|
2343
2359
|
room.participants.forEach(p => {
|
|
2344
2360
|
if (p === ws.clientId) return;
|
|
@@ -2427,18 +2443,21 @@ class PlaySocketServer {
|
|
|
2427
2443
|
*/
|
|
2428
2444
|
#changeHostIfDisconnected(roomId, clientId) {
|
|
2429
2445
|
const room = this.#rooms[roomId];
|
|
2430
|
-
if (room && room.host === clientId
|
|
2446
|
+
if (room && room.host === clientId) {
|
|
2431
2447
|
const participantsWithoutClient = room.participants.filter((e) => e !== clientId);
|
|
2432
|
-
room.host = participantsWithoutClient[0]; // Set new host
|
|
2433
2448
|
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2449
|
+
if (participantsWithoutClient.length >= 1) {
|
|
2450
|
+
room.host = participantsWithoutClient[0]; // Set new host
|
|
2451
|
+
|
|
2452
|
+
// Inform all participants about the new host
|
|
2453
|
+
participantsWithoutClient.forEach(p => {
|
|
2454
|
+
const client = this.#clients.get(p);
|
|
2455
|
+
if (client) client.send(encode({
|
|
2456
|
+
type: 'host_migrated',
|
|
2457
|
+
newHost: room.host,
|
|
2458
|
+
}), { binary: true });
|
|
2459
|
+
});
|
|
2460
|
+
} else room.host = null; // The next person to join will become the host
|
|
2442
2461
|
}
|
|
2443
2462
|
}
|
|
2444
2463
|
|