playsocketjs 5.3.1 → 5.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playsocketjs",
3
- "version": "5.3.1",
3
+ "version": "5.3.2",
4
4
  "description": "WebSocket wrapper for creating simple multiplayer systems with ease.",
5
5
  "type": "module",
6
6
  "module": "./src/client/client.js",
@@ -333,7 +333,8 @@ export default class PlaySocketServer {
333
333
 
334
334
  if (room && data.update) {
335
335
  // Check if update is allowed via event callback (provide clone to ensure update integrity)
336
- const updateAllowed = await this.#triggerEvent("storageUpdateRequested", { roomId, clientId: ws.clientId, update: structuredClone(data.update), storage: this.getRoomStorage(roomId) });
336
+ // Must be synchronous so that messages that follow in the same read from the socket (which can flush multiple at once) get processed reliably in order (await would schedule to a new microtask, so the order can fall apart)
337
+ const updateAllowed = this.#triggerEventSync("storageUpdateRequested", { roomId, clientId: ws.clientId, update: structuredClone(data.update), storage: this.getRoomStorage(roomId) });
337
338
  if (updateAllowed === false || typeof updateAllowed === "string") {
338
339
  ws.send(encode({
339
340
  type: "property_update_rejected",
@@ -525,20 +526,41 @@ export default class PlaySocketServer {
525
526
  * @returns {Promise<any>} The first non-null callback return value if present (all callbacks run, a throwing callback counts as returning false), otherwise true
526
527
  */
527
528
  async #triggerEvent(event, ...args) {
528
- const syncOnlyEvents = ["storageUpdateRequested"]; // Async storage validation could mess up GC and would lead to potentially poor UX (slow sync)
529
529
  const callbacks = this.#callbacks.get(event);
530
- if (!callbacks) return;
530
+ if (!callbacks) return true;
531
+
532
+ let firstResult;
533
+ for (const callback of [...callbacks]) {
534
+ try {
535
+ let result = callback(...args);
536
+ if (typeof result?.then === "function") result = await result;
537
+ if (firstResult == null && result != null) firstResult = result;
538
+ } catch (error) {
539
+ console.error(ERROR_PREFIX + `${event} callback error:`, error);
540
+ if (firstResult == null) firstResult = false; // Fail closed (false blocks for most events)
541
+ }
542
+ }
543
+ return firstResult ?? true;
544
+ }
545
+
546
+ /**
547
+ * Trigger an event to registered synchronous callbacks (e.g. storage validation, async validation could mess up GC and lead to poor UX)
548
+ * @param {string} event - Event name
549
+ * @param {...*} args - Arguments
550
+ * @returns {any} The first non-null callback return value if present (all callbacks run, a throwing callback counts as returning false, an async one as a rejection), otherwise true
551
+ */
552
+ #triggerEventSync(event, ...args) {
553
+ const callbacks = this.#callbacks.get(event);
554
+ if (!callbacks) return true;
531
555
 
532
556
  let firstResult;
533
557
  for (const callback of [...callbacks]) {
534
558
  try {
535
559
  let result = callback(...args);
536
560
  if (typeof result?.then === "function") {
537
- if (syncOnlyEvents.includes(event)) {
538
- result.catch(() => { });
539
- console.error(ERROR_PREFIX + `${event} callbacks must be synchronous`);
540
- result = `${event} callback must be synchronous.`; // Rejection string to explicitly block this practice
541
- } else result = await result;
561
+ result.catch(() => { });
562
+ console.error(ERROR_PREFIX + `${event} callbacks must be synchronous`);
563
+ result = `${event} callback must be synchronous`; // Rejection string to explicitly block this practice
542
564
  }
543
565
  if (firstResult == null && result != null) firstResult = result;
544
566
  } catch (error) {
@@ -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.3.1";
12
+ export const VERSION = "5.3.2";
13
13
 
14
14
  /**
15
15
  * Logging prefixes
@@ -451,8 +451,8 @@ export default class CRDTManager {
451
451
  try {
452
452
  return structuredClone(this.#propertyStore); // Return deep clone
453
453
  } catch {
454
- console.error(CONSOLE_PREFIX + "Failed to parse property store")
455
- return {}
454
+ console.error(CONSOLE_PREFIX + "Failed to parse property store");
455
+ return {};
456
456
  }
457
457
  }
458
458
 
@@ -475,7 +475,7 @@ export default class CRDTManager {
475
475
  return true;
476
476
  }
477
477
  } catch {
478
- console.error(CONSOLE_PREFIX + "Failed to stringify and compare property stores")
478
+ console.error(CONSOLE_PREFIX + "Failed to stringify and compare property stores");
479
479
  }
480
480
  return false;
481
481
  }
@@ -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.3.1";
11
+ export declare const VERSION = "5.3.2";
12
12
  /**
13
13
  * Logging prefixes
14
14
  */