playsocketjs 2.4.1 → 2.5.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 CHANGED
@@ -96,7 +96,7 @@ new PlaySocket(id?: string, options: PlaySocketOptions)
96
96
  ### Methods
97
97
 
98
98
  - `init()`: Initialize the WebSocket connection – Returns Promise (async) which resolves with the client's ID
99
- - `createRoom(initialStorage?: object, maxSize?: number)`: Create a new room and become host – Returns Promise (async) which resolves with the room ID (matches the creator's ID). The absolute client maximum is 100
99
+ - `createRoom(initialStorage?: object, maxSize?: number)`: Create a new room and become host – Returns Promise (async) which resolves with the room ID. The room participant maximum is 100
100
100
  - `joinRoom(hostId: string)`: Join an existing room – Returns Promise (async)
101
101
  - `destroy()`: Use this to leave a room and close the connection
102
102
  - `updateStorage(key: string, type: 'set' | 'array-add' | 'array-add-unique' | 'array-remove-matching' | 'array-update-matching', value: any, updateValue?: any)`: Update the shared storage (max. 100 keys). Safely update arrays in storage by adding, removing, or updating items. UpdateValue is only required for the 'array-update-matching' operation type
@@ -203,6 +203,7 @@ Creates a new PlaySocket Server instance with configuration options.
203
203
  - `port`: Port to listen on (default: 3000, used only if no server provided)
204
204
  - `path`: WebSocket endpoint path (default: '/')
205
205
  - `server`: Existing http server (optional)
206
+ - `rateLimit`: Adjust the messages/second rate limit (defaults to 20)
206
207
  - `debug`: Set this property to true to enable extra logging
207
208
 
208
209
  ### Methods
@@ -2301,10 +2301,10 @@ class PlaySocket {
2301
2301
  case 'room_created':
2302
2302
  if (this.#pendingCreate) {
2303
2303
  this.#inRoom = true;
2304
- this.#triggerEvent("status", `Room created${this.#pendingCreate.maxSize ? ` with max size ${this.#pendingCreate.maxSize}.` : '.'}`);
2304
+ this.#triggerEvent("status", `Room created with max. size ${message.size}.`);
2305
2305
  this.#crdtManager.importState(message.state);
2306
2306
  this.#triggerEvent("storageUpdated", this.getStorage);
2307
- this.#pendingCreate.resolve(this.#pendingCreate.id);
2307
+ this.#pendingCreate.resolve(message.roomId);
2308
2308
  }
2309
2309
  break;
2310
2310
 
@@ -2456,7 +2456,7 @@ class PlaySocket {
2456
2456
  return Promise.race([
2457
2457
  new Promise((resolve, reject) => {
2458
2458
  this.#roomHost = this.#id;
2459
- this.#pendingCreate = { maxSize, id: this.#id, resolve, reject };
2459
+ this.#pendingCreate = { resolve, reject };
2460
2460
  this.#sendToServer({
2461
2461
  type: 'create_room',
2462
2462
  initialStorage,
@@ -2213,6 +2213,7 @@ function requireServer () {
2213
2213
  class PlaySocketServer {
2214
2214
  #server;
2215
2215
  #ownsServer = false;
2216
+ #rateLimitMaxPoints;
2216
2217
  #wss;
2217
2218
  #clients = new Map(); // ClientId -> WebSocket instance
2218
2219
  #rooms = {};
@@ -2235,10 +2236,12 @@ function requireServer () {
2235
2236
  * @param {string} [options.path='/socket'] - WebSocket endpoint path
2236
2237
  */
2237
2238
  constructor(options = {}) {
2238
- const { server, port = 3000, path = '/', debug = false } = options;
2239
+ const { server, port = 3000, path = '/', debug = false, rateLimit = 20 } = options;
2239
2240
 
2240
2241
  if (debug) this.#debug = true; // Enable extra logging
2241
2242
 
2243
+ this.#rateLimitMaxPoints = rateLimit; // Set rate limit
2244
+
2242
2245
  // Handle server creation / usage
2243
2246
  if (server) {
2244
2247
  this.#server = server; // Use provided HTTP server
@@ -2311,10 +2314,8 @@ function requireServer () {
2311
2314
 
2312
2315
  // Generate client ID if none provided
2313
2316
  if (!data.id) {
2314
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789';
2315
- const maxAttempts = 50;
2316
- for (let i = 0; i < maxAttempts; i++) {
2317
- const id = Array.from({ length: 6 }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
2317
+ for (let i = 0; i < 50; i++) {
2318
+ const id = this.#generateId();
2318
2319
  if (!this.#clients.get(id)) {
2319
2320
  data.id = id;
2320
2321
  break;
@@ -2384,16 +2385,31 @@ function requireServer () {
2384
2385
 
2385
2386
  case 'create_room':
2386
2387
  if (!ws.clientId) return;
2387
- const newRoomId = ws.clientId;
2388
2388
 
2389
- if (this.#clientRooms.get(ws.clientId) || this.#rooms[newRoomId]) {
2389
+ let newRoomId;
2390
+
2391
+ if (this.#clientRooms.get(ws.clientId)) {
2390
2392
  ws.send(encode({
2391
2393
  type: 'room_creation_failed',
2392
- reason: this.#clientRooms.get(ws.clientId) ? 'Already in a room.' : 'Room ID is taken.'
2394
+ reason: 'Already in a room.'
2393
2395
  }), { binary: true });
2394
2396
  return;
2395
2397
  }
2396
2398
 
2399
+ // Generate room ID
2400
+ for (let i = 0; i < 50; i++) {
2401
+ const id = this.#generateId();
2402
+ if (!this.#rooms[id]) {
2403
+ newRoomId = id;
2404
+ break;
2405
+ }
2406
+ }
2407
+
2408
+ if (!newRoomId) {
2409
+ ws.send(encode({ type: 'room_creation_failed', reason: 'No available ID found.' }), { binary: true });
2410
+ throw new Error('Failed to generate unique room ID!');
2411
+ }
2412
+
2397
2413
  const roomCrdtManager = new CRDTManager(this.#debug); // Create the room's crdt manager
2398
2414
 
2399
2415
  // Event callback with potential initial storage modifications
@@ -2421,18 +2437,19 @@ function requireServer () {
2421
2437
  }
2422
2438
 
2423
2439
  // Create room
2440
+ const maxSize = Math.min(Number(data.size), 100) || 100; // Max. limit is 100 clients / room
2424
2441
  this.#roomVersions.set(newRoomId, 0); // Start with version 0
2425
2442
  this.#rooms[newRoomId] = {
2426
2443
  participants: [ws.clientId],
2427
2444
  host: ws.clientId,
2428
- maxSize: Math.min(Number(data.size), 100) || 100, // Max. limit is 100 clients / room
2445
+ maxSize,
2429
2446
  crdtManager: roomCrdtManager
2430
2447
  };
2431
2448
  this.#clientRooms.set(ws.clientId, newRoomId); // Add client to the room
2432
2449
 
2433
- ws.send(encode({ type: 'room_created', state: roomCrdtManager.getState }), { binary: true });
2450
+ ws.send(encode({ type: 'room_created', state: roomCrdtManager.getState, roomId: newRoomId, size: maxSize }), { binary: true });
2434
2451
  this.#triggerEvent("roomCreated", newRoomId);
2435
- if (this.#debug) console.log("Room created with initial storage:", data.initialStorage);
2452
+ if (this.#debug) console.log(`Room ${newRoomId} created with initial storage:`, data.initialStorage);
2436
2453
  break;
2437
2454
 
2438
2455
  case 'join_room':
@@ -2518,6 +2535,7 @@ function requireServer () {
2518
2535
  break;
2519
2536
 
2520
2537
  case 'request':
2538
+ if (!ws.clientId) return;
2521
2539
  const requestorRoomId = this.#clientRooms.get(ws.clientId) || null;
2522
2540
  this.#triggerEvent("requestReceived", { roomId: requestorRoomId, clientId: ws.clientId, name: data.request.name, data: data.request.data });
2523
2541
  break;
@@ -2532,6 +2550,14 @@ function requireServer () {
2532
2550
  }
2533
2551
  }
2534
2552
 
2553
+ /**
2554
+ * Generate a readable, 6 digit ID
2555
+ */
2556
+ #generateId() {
2557
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789';
2558
+ return Array.from({ length: 6 }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
2559
+ }
2560
+
2535
2561
  /**
2536
2562
  * If the client that disconnected (and is now in the reconnection-phase) was the room host,
2537
2563
  * pick a new room host immediately to avoid host-less phase (in case important logic is attached to the host)
@@ -2570,11 +2596,10 @@ function requireServer () {
2570
2596
  * @private
2571
2597
  */
2572
2598
  #checkRateLimit(connectionId, operationType) {
2573
- const MAX_POINTS = 20;
2574
2599
  const now = Date.now();
2575
2600
 
2576
2601
  if (!this.#rateLimits.has(connectionId)) {
2577
- this.#rateLimits.set(connectionId, { points: MAX_POINTS, lastReset: now });
2602
+ this.#rateLimits.set(connectionId, { points: this.#rateLimitMaxPoints, lastReset: now });
2578
2603
  return true;
2579
2604
  }
2580
2605
 
@@ -2582,7 +2607,7 @@ function requireServer () {
2582
2607
 
2583
2608
  // Reset points if interval has passed (1s)
2584
2609
  if (now - limit.lastReset > 1000) {
2585
- limit.points = MAX_POINTS;
2610
+ limit.points = this.#rateLimitMaxPoints;
2586
2611
  limit.lastReset = now;
2587
2612
  }
2588
2613
 
@@ -2737,6 +2762,7 @@ function requireServer () {
2737
2762
  }), { binary: true });
2738
2763
  }
2739
2764
  });
2765
+ this.#triggerEvent("storageUpdated", { roomId, clientId: null, update: structuredClone(propertyUpdate), storage: this.getRoomStorage(roomId) });
2740
2766
  }
2741
2767
  }
2742
2768
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playsocketjs",
3
- "version": "2.4.1",
3
+ "version": "2.5.0",
4
4
  "description": "WebSocket wrapper for creating simple multiplayer systems with ease.",
5
5
  "module": "dist/playsocket-client.js",
6
6
  "exports": {