playsocketjs 5.1.0 → 5.2.1

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
@@ -315,7 +315,7 @@ The callback signature is `callback(verified, code?, message?)` where `code` ref
315
315
  | `kick()` | `clientId: string, reason?: string` | `void` | Kick a client by their client ID. |
316
316
  | `move()` | `clientId: string, roomId: string` | `void` | Move a client that is already in a room to a different room. |
317
317
  | `onEvent()` | `event: string, callback: Function` | `() => void` | Register a server-side event callback. Returns unsubscribe function. |
318
- | `getRoomStorage()` | `roomId: string` | `object` | Get a snapshot of the current room storage. |
318
+ | `getRoomStorage()` | `roomId: string, options?: { dangerouslySkipCloning?: boolean }` | `object` | Get a snapshot of the current room storage. Pass `dangerouslySkipCloning: true` to get the uncloned store for performance reasons. |
319
319
  | `getUpdateDetails()` | `update: object` | `object` | Get the details (`key`, `type`, `value` and `secondValue`) of a storage update for building validation logic. |
320
320
  | `updateRoomStorage()` | `roomId: string, key: string, type: string, value: any, secondValue?: any` | `void` | Update a key in the shared storage of a room. |
321
321
  | `createRoom()` | `initialStorage?: object, size?: number` | `object` | Create a server-owned room (returns object containing room ID and state).|
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playsocketjs",
3
- "version": "5.1.0",
3
+ "version": "5.2.1",
4
4
  "description": "WebSocket wrapper for creating simple multiplayer systems with ease.",
5
5
  "type": "module",
6
6
  "module": "./src/client/client.js",
@@ -576,13 +576,15 @@ export default class PlaySocketServer {
576
576
  }
577
577
 
578
578
  /**
579
- * Get snapshot of a room's storage
579
+ * Get a snapshot of a room's storage
580
580
  * @param {string} roomId - ID of the room to get the storage from
581
+ * @param {object} [options] - Options
582
+ * @param {boolean} [options.dangerouslySkipCloning=false] - Return uncloned store (faster, but allows illegal mutation)
581
583
  * @returns {Record<string, any> | undefined} Storage object or undefined if the room doesn't exist
582
584
  */
583
- getRoomStorage(roomId) {
585
+ getRoomStorage(roomId, { dangerouslySkipCloning = false } = {}) {
584
586
  const room = this.#rooms[roomId];
585
- if (room) return room.crdtManager.propertyStore;
587
+ if (room) return dangerouslySkipCloning ? room.crdtManager.rawPropertyStore : room.crdtManager.propertyStore;
586
588
  }
587
589
 
588
590
  /**
@@ -9,7 +9,7 @@ export const HEARTBEAT_INTERVAL = 5000;
9
9
  /**
10
10
  * Library version, must match package.json
11
11
  */
12
- export const VERSION = "5.1.0";
12
+ export const VERSION = "5.2.1";
13
13
 
14
14
  /**
15
15
  * Logging prefixes
@@ -110,7 +110,7 @@ export default class CRDTManager {
110
110
  importPropertyUpdate(update) {
111
111
  try {
112
112
  const { key, operation: rawOperation, vectorClock } = update;
113
- const operation = /** @type {Operation} */ (this.#sanitizeValue(rawOperation));
113
+ const operation = /** @type {Operation} */ (this.#normalizeAndCopy(rawOperation));
114
114
  if (this.#debug) console.log(CONSOLE_PREFIX + "Importing update:", update); // Debug
115
115
 
116
116
  // Check key limit to safeguard against too many keys
@@ -183,9 +183,9 @@ export default class CRDTManager {
183
183
  */
184
184
  updateProperty(key, type, value, secondValue) {
185
185
  try {
186
- // Sanitize inputs
187
- value = this.#sanitizeValue(value);
188
- secondValue = this.#sanitizeValue(secondValue);
186
+ // Normalize inputs
187
+ value = this.#normalizeAndCopy(value);
188
+ secondValue = this.#normalizeAndCopy(secondValue);
189
189
 
190
190
  // Debug log
191
191
  if (this.#debug) console.log(CONSOLE_PREFIX + `Updating property with key ${key}, type ${type}, value ${value} and secondValue ${secondValue}`);
@@ -366,12 +366,12 @@ export default class CRDTManager {
366
366
  */
367
367
  #handleOperation(curValue, type, value, secondValue) {
368
368
  try {
369
+ // Set operation (before the structured clone since for set we don't care about curValue anyway)
370
+ if (type === "set") return value;
371
+
369
372
  // Deep copy to avoid reference issues in case value is or contains object(s)
370
373
  curValue = structuredClone(curValue);
371
374
 
372
- // Set operation
373
- if (type === "set") return value;
374
-
375
375
  // Number increment operation
376
376
  if (type === "number-increment") {
377
377
  if (!Number.isFinite(curValue)) curValue = 0; // Auto-convert to number if current value isn't a finite number
@@ -431,20 +431,15 @@ export default class CRDTManager {
431
431
  }
432
432
 
433
433
  /**
434
- * Remove HTML to prevent XSS and enforce size limits
435
- * @param {*} value - Value to sanitize
436
- * @returns {*} Sanitized value
434
+ * Enforce size limit, normalize undefined to null, and return deep copy
435
+ * @param {*} value - Value to normalize
436
+ * @returns {*} Normalized value
437
437
  */
438
- #sanitizeValue(value) {
439
- if (value === undefined) return null; // Normalize undefined to null to avoid divergence in transport
440
-
441
- // Check total serialized size
442
- const jsonString = JSON.stringify(value);
443
- if (jsonString?.length > 50000) throw new Error("Value too large"); // 50KB limit
444
-
445
- if (typeof value === "string") return (value.includes("<") || value.includes(">")) ? value.replace(/[<>]/g, "") : value;
446
- if (Array.isArray(value)) return Array.from(value, item => this.#sanitizeValue(item));
447
- if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, this.#sanitizeValue(v)]));
438
+ #normalizeAndCopy(value) {
439
+ if (value === undefined) return null;
440
+ if (JSON.stringify(value)?.length > 50_000) throw new Error("Value too large"); // 50KB limit
441
+ if (Array.isArray(value)) return Array.from(value, item => this.#normalizeAndCopy(item));
442
+ if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, this.#normalizeAndCopy(v)]));
448
443
  return value;
449
444
  }
450
445
 
@@ -461,6 +456,14 @@ export default class CRDTManager {
461
456
  }
462
457
  }
463
458
 
459
+ /**
460
+ * Get uncloned property store
461
+ * @returns {Record<string, any>} Object with key value pairs
462
+ */
463
+ get rawPropertyStore() {
464
+ return this.#propertyStore;
465
+ }
466
+
464
467
  /**
465
468
  * Check for changes in the local property store
466
469
  * @returns {boolean} Check if any values changed going from the last to the current property update
@@ -145,11 +145,15 @@ export default class PlaySocketServer {
145
145
  */
146
146
  move(clientId: string, roomId: string): void;
147
147
  /**
148
- * Get snapshot of a room's storage
148
+ * Get a snapshot of a room's storage
149
149
  * @param {string} roomId - ID of the room to get the storage from
150
+ * @param {object} [options] - Options
151
+ * @param {boolean} [options.dangerouslySkipCloning=false] - Return uncloned store (faster, but allows illegal mutation)
150
152
  * @returns {Record<string, any> | undefined} Storage object or undefined if the room doesn't exist
151
153
  */
152
- getRoomStorage(roomId: string): Record<string, any> | undefined;
154
+ getRoomStorage(roomId: string, { dangerouslySkipCloning }?: {
155
+ dangerouslySkipCloning?: boolean;
156
+ }): Record<string, any> | undefined;
153
157
  /**
154
158
  * Get the operation details from a storage update (e.g. in the "storageUpdateRequested" event)
155
159
  * @param {PropertyUpdate} update - Property update
@@ -8,7 +8,7 @@ export declare const HEARTBEAT_INTERVAL = 5000;
8
8
  /**
9
9
  * Library version, must match package.json
10
10
  */
11
- export declare const VERSION = "5.1.0";
11
+ export declare const VERSION = "5.2.1";
12
12
  /**
13
13
  * Logging prefixes
14
14
  */
@@ -123,6 +123,11 @@ export default class CRDTManager {
123
123
  * @returns {Record<string, any>} Object with key value pairs
124
124
  */
125
125
  get propertyStore(): Record<string, any>;
126
+ /**
127
+ * Get uncloned property store
128
+ * @returns {Record<string, any>} Object with key value pairs
129
+ */
130
+ get rawPropertyStore(): Record<string, any>;
126
131
  /**
127
132
  * Check for changes in the local property store
128
133
  * @returns {boolean} Check if any values changed going from the last to the current property update