playsocketjs 2.2.0 → 2.2.2

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,10 +96,10 @@ 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)
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
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
- - `updateStorage(key: string, type: 'set' | 'array-add' | 'array-add-unique' | 'array-remove-matching' | 'array-update-matching', value: any, updateValue?: any)`: Update the shared storage. Safely update arrays in storage by adding, removing, or updating items. UpdateValue is only required for the 'array-update-matching' operation type
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
103
103
  - `sendRequest(name: string, data?: any)`: Send requests to the server with optional custom data (handle these in the `requestReceived` server event)
104
104
  - `onEvent(event: string, callback: Function)`: Register an event callback
105
105
 
@@ -221,7 +221,7 @@ Creates a new PlaySocket Server instance with configuration options.
221
221
  - `clientJoinedRoom`: Client joined a room – note that clients can only leave by disconnecting (returns the client's ID `string`, room ID `string`)
222
222
  - `roomCreated`: Client created a room (returns room ID `string`)
223
223
  - `roomDestroyed`: Room was destroyed, this happens when all participants leave (returns room ID `string`)
224
- - `roomCreationRequested`: Room creation requested by client (returns `object` containing the client's ID `string`, room ID `string` and the initialStorage `object`) – if you return an `object` in the callback, it will take that as the initial storage instead
224
+ - `roomCreationRequested`: Room creation requested by client (returns `object` containing the client's ID `string`, room ID `string` and the initialStorage `object`) – if you return an `object` in the callback, it will take that as the initial storage instead. If you return `false`, the creation will be denied
225
225
  - `storageUpdateRequested`: Room storage property update requested by client (returns `object` containing the client's ID `string`, room ID `string` and the update `object`) – if you return `false` in the callback, the update will be blocked
226
226
  - `requestReceived`: Request from client was received by the server (returns `object` containing client's ID `string`, if in room – room ID `string`, request name `string` and optional passed data of type `any`)
227
227
 
@@ -1758,9 +1758,11 @@ function requireCrdtManager () {
1758
1758
  const operation = this.#sanitizeValue(rawOperation);
1759
1759
  if (this.#debug) console.log(CONSOLE_PREFIX + "Importing update:", data); // Debug
1760
1760
 
1761
+ // Check key limit to afeguard against too many keys
1762
+ if (this.#keyOperations.size >= 100 && !this.#keyOperations.has(key)) throw new Error('Key limit exceeded!');
1763
+
1761
1764
  // Get COPY of current ops (or empty array if none yet)
1762
1765
  const currentOps = [...(this.#keyOperations.get(key) || [])];
1763
-
1764
1766
  // Merge vector clocks (always take max value)
1765
1767
  for (const [id, counter] of vectorClock) {
1766
1768
  if (!this.#vectorClock.has(id) || this.#vectorClock.get(id) < counter) {
@@ -1768,7 +1770,7 @@ function requireCrdtManager () {
1768
1770
  }
1769
1771
  }
1770
1772
 
1771
- // Saveguard
1773
+ // Safeguard against massive vector clocks
1772
1774
  if (this.#vectorClock.size > 1000) this.#vectorClock = new Map([...this.#vectorClock].slice(-100));
1773
1775
 
1774
1776
  // Add new operation if it's not already added
@@ -2019,11 +2021,15 @@ function requireCrdtManager () {
2019
2021
  }
2020
2022
 
2021
2023
  /**
2022
- * Remove HTML to prevent XSS
2023
- * @param {Object} obj
2024
+ * Remove HTML to prevent XSS and enforce size limits
2025
+ * @param {Object} obj
2024
2026
  * @returns {Object} - Sanitized object
2025
2027
  */
2026
2028
  #sanitizeValue(obj) {
2029
+ // Check total serialized size
2030
+ const jsonString = JSON.stringify(obj);
2031
+ if (jsonString?.length > 50000) throw new Error('Value too large!'); // 50KB limit
2032
+
2027
2033
  if (typeof obj === 'string') return (obj.includes('<') || obj.includes('>')) ? obj.replace(/[<>]/g, '') : obj;
2028
2034
  if (Array.isArray(obj)) return obj.map(item => this.#sanitizeValue(item));
2029
2035
  if (obj && typeof obj === 'object') return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, this.#sanitizeValue(v)]));
@@ -1884,9 +1884,11 @@ function requireCrdtManager () {
1884
1884
  const operation = this.#sanitizeValue(rawOperation);
1885
1885
  if (this.#debug) console.log(CONSOLE_PREFIX + "Importing update:", data); // Debug
1886
1886
 
1887
+ // Check key limit to afeguard against too many keys
1888
+ if (this.#keyOperations.size >= 100 && !this.#keyOperations.has(key)) throw new Error('Key limit exceeded!');
1889
+
1887
1890
  // Get COPY of current ops (or empty array if none yet)
1888
1891
  const currentOps = [...(this.#keyOperations.get(key) || [])];
1889
-
1890
1892
  // Merge vector clocks (always take max value)
1891
1893
  for (const [id, counter] of vectorClock) {
1892
1894
  if (!this.#vectorClock.has(id) || this.#vectorClock.get(id) < counter) {
@@ -1894,7 +1896,7 @@ function requireCrdtManager () {
1894
1896
  }
1895
1897
  }
1896
1898
 
1897
- // Saveguard
1899
+ // Safeguard against massive vector clocks
1898
1900
  if (this.#vectorClock.size > 1000) this.#vectorClock = new Map([...this.#vectorClock].slice(-100));
1899
1901
 
1900
1902
  // Add new operation if it's not already added
@@ -2145,11 +2147,15 @@ function requireCrdtManager () {
2145
2147
  }
2146
2148
 
2147
2149
  /**
2148
- * Remove HTML to prevent XSS
2149
- * @param {Object} obj
2150
+ * Remove HTML to prevent XSS and enforce size limits
2151
+ * @param {Object} obj
2150
2152
  * @returns {Object} - Sanitized object
2151
2153
  */
2152
2154
  #sanitizeValue(obj) {
2155
+ // Check total serialized size
2156
+ const jsonString = JSON.stringify(obj);
2157
+ if (jsonString?.length > 50000) throw new Error('Value too large!'); // 50KB limit
2158
+
2153
2159
  if (typeof obj === 'string') return (obj.includes('<') || obj.includes('>')) ? obj.replace(/[<>]/g, '') : obj;
2154
2160
  if (Array.isArray(obj)) return obj.map(item => this.#sanitizeValue(item));
2155
2161
  if (obj && typeof obj === 'object') return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, this.#sanitizeValue(v)]));
@@ -2393,6 +2399,13 @@ function requireServer () {
2393
2399
  // Event callback with potential initial storage modifications
2394
2400
  const reviewedStorage = await this.#triggerEvent("roomCreationRequested", { roomId: newRoomId, clientId: ws.clientId, initialStorage: structuredClone({ ...data.initialStorage }) });
2395
2401
  if (typeof reviewedStorage === 'object') data.initialStorage = reviewedStorage;
2402
+ if (reviewedStorage === false) {
2403
+ ws.send(encode({
2404
+ type: 'room_creation_failed',
2405
+ reason: 'Room creation denied.'
2406
+ }), { binary: true });
2407
+ return;
2408
+ }
2396
2409
 
2397
2410
  // Check if client/creator is still connected
2398
2411
  if (!this.#clients.has(ws.clientId)) {
@@ -2412,7 +2425,7 @@ function requireServer () {
2412
2425
  this.#rooms[newRoomId] = {
2413
2426
  participants: [ws.clientId],
2414
2427
  host: ws.clientId,
2415
- maxSize: data.size || null,
2428
+ maxSize: Math.min(Number(data.size), 100) || 100, // Max. limit is 100 clients / room
2416
2429
  crdtManager: roomCrdtManager
2417
2430
  };
2418
2431
  this.#clientRooms.set(ws.clientId, newRoomId); // Add client to the room
@@ -2428,12 +2441,12 @@ function requireServer () {
2428
2441
  const room = this.#rooms[roomId];
2429
2442
 
2430
2443
  if (!room ||
2431
- (room.maxSize && room.participants.length >= room.maxSize) ||
2444
+ (room.participants.length >= room.maxSize) ||
2432
2445
  this.#clientRooms.get(ws.clientId)) {
2433
2446
  ws.send(encode({
2434
2447
  type: 'join_rejected',
2435
2448
  reason: !room ? 'Room not found' :
2436
- room.maxSize && room.participants.length >= room.maxSize ? 'Room full' :
2449
+ room.participants.length >= room.maxSize ? 'Room full' :
2437
2450
  'Already in a room'
2438
2451
  }), { binary: true });
2439
2452
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playsocketjs",
3
- "version": "2.2.0",
3
+ "version": "2.2.2",
4
4
  "description": "WebSocket wrapper for creating simple multiplayer systems with ease.",
5
5
  "module": "dist/playsocket-client.js",
6
6
  "exports": {