playsocketjs 4.2.0 → 4.3.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
@@ -43,8 +43,7 @@ const socket = new PlaySocket("unique-client-id", {
43
43
 
44
44
  // Set up the event handlers you need
45
45
  socket.onEvent("status", status => console.log(status));
46
- socket.onEvent("error", error => console.error(error));
47
- ...
46
+ socket.onEvent("storageUpdated", storage => { /* ... */ });
48
47
 
49
48
  const clientId = await socket.init(); // Connect
50
49
  ```
@@ -128,7 +127,7 @@ new PlaySocket(id?: string, options: PlaySocketOptions)
128
127
  | `init()` | - | `Promise<string>` | Initialize the WebSocket connection, resolves with the client ID. |
129
128
  | `createRoom()` | `initialStorage?: object, size?: number` | `Promise<string>` | Create a new room, resolves with the room ID. Max. 500 participants. |
130
129
  | `joinRoom()` | `roomId: string` | `Promise<void>` | Join an existing room. |
131
- | `destroy()` | - | `void` | Leave room, close the connection, and destroy the instance. |
130
+ | `destroy()` | `reason?: string` | `void` | Leave room, close the connection, and destroy the instance. |
132
131
  | `updateStorage()` | `key: string, type: string, value: any, secondValue?: any` | `void` | Update a key in the shared storage. |
133
132
  | `sendRequest()` | `name: string, data?: any` | `Promise<void>` | Send a request to the server with optional attached data. |
134
133
  | `onEvent()` | `event: string, callback: Function` | `() => void` | Register an event callback. Returns unsubscribe function. |
@@ -137,10 +136,9 @@ new PlaySocket(id?: string, options: PlaySocketOptions)
137
136
 
138
137
  | Event | Callback parameter | Description |
139
138
  |-------|-------------------|-------------|
140
- | `status` | `status: string` | Connection or room status updated. |
141
- | `error` | `error: string` | Error occurred. |
139
+ | `status` | `status: string` | Connection or room status changed, e.g. display in UI during joining or room creation. |
142
140
  | `moved` | `roomId: string` | Moved to different room. |
143
- | `instanceDestroyed` | - | Instance destroyed through `destroy()` or fatal error. |
141
+ | `instanceDestroyed` | `reason: string` | Instance destroyed through `destroy()` or error. |
144
142
  | `storageUpdated` | `storage: object` | Storage state changed. Does not trigger on no-op updates. |
145
143
  | `hostMigrated` | `roomId: string` | Host was changed. |
146
144
  | `clientJoined` | `clientId: string` | New client joined the room. |
@@ -196,7 +194,7 @@ const playSocketServer = new PlaySocketServer({
196
194
 
197
195
  // Start the server
198
196
  httpServer.listen(3000, () => {
199
- console.log("Server running on port 3000");
197
+ console.log("Server running on port 3000.");
200
198
  });
201
199
 
202
200
  function shutdown() {
@@ -236,7 +234,7 @@ server.onEvent("requestReceived", async ({ roomId, clientId, name, data }) => {
236
234
  const players = server.getRoomStorage(roomId)?.players || [];
237
235
 
238
236
  // Returning false or string rejects sendRequest() on the client
239
- if (players.find(p => p.id === clientId)) return "Player already added!";
237
+ if (players.find(p => p.id === clientId)) return "Player already added";
240
238
 
241
239
  server.updateRoomStorage(roomId, "players", "array-add", { id: clientId, timestamp: Date.now() });
242
240
  }
@@ -255,7 +253,7 @@ server.onEvent("clientRegistrationRequested", async (clientId, data) => {
255
253
  // For example, data could contain a token
256
254
  authedClients.push(clientId);
257
255
  } catch (error) {
258
- return "Error occurred during auth."; // Blocks the registration
256
+ return "Error occurred during auth"; // Blocks the registration
259
257
  }
260
258
  });
261
259
 
@@ -295,7 +293,7 @@ const server = new PlaySocketServer({
295
293
  const forwarded = info.req.headers["x-forwarded-for"];
296
294
  const ip = forwarded ? forwarded.split(",")[0].trim() : info.req.socket.remoteAddress;
297
295
  if (isRateLimited(ip)) {
298
- return callback(false, 429, "Too Many Requests");
296
+ return callback(false, 429, "Too many requests");
299
297
  }
300
298
  callback(true);
301
299
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playsocketjs",
3
- "version": "4.2.0",
3
+ "version": "4.3.0",
4
4
  "description": "WebSocket wrapper for creating simple multiplayer systems with ease.",
5
5
  "type": "module",
6
6
  "module": "./src/client/client.js",
@@ -1,10 +1,7 @@
1
1
  import { encode, decode } from "@msgpack/msgpack";
2
2
  import CRDTManager from "../universal/crdtManager.js";
3
- import packageData from "../../package.json" with { type: "json" };
3
+ import { VERSION, ERROR_PREFIX, WARNING_PREFIX, LOG_PREFIX } from "../universal/constants.js";
4
4
 
5
- const ERROR_PREFIX = "PlaySocket error: ";
6
- const WARNING_PREFIX = "PlaySocket warning: ";
7
- const LOG_PREFIX = "PlaySocket log: ";
8
5
  const TIMEOUT_MS = 3000; // 3 second timeout for WS messages
9
6
 
10
7
  /** @typedef {import("../universal/crdtManager.js").PropertyUpdateType} PropertyUpdateType */
@@ -96,7 +93,7 @@ export default class PlaySocket {
96
93
  */
97
94
  #createTimeout(name) {
98
95
  return new Promise((_, reject) =>
99
- setTimeout(() => reject(new Error(`${name} timed out.`)), TIMEOUT_MS)
96
+ setTimeout(() => reject(new Error(`${name} timed out`)), TIMEOUT_MS)
100
97
  );
101
98
  }
102
99
 
@@ -107,9 +104,9 @@ export default class PlaySocket {
107
104
  * @returns {Function} - Unsubscribe
108
105
  */
109
106
  onEvent(event, callback) {
110
- const validEvents = ["status", "error", "moved", "instanceDestroyed", "storageUpdated", "hostMigrated", "clientJoined", "clientLeft"];
107
+ const validEvents = ["status", "moved", "instanceDestroyed", "storageUpdated", "hostMigrated", "clientJoined", "clientLeft"];
111
108
  if (!validEvents.includes(event)) {
112
- console.warn(WARNING_PREFIX + `Invalid event type "${event}".`);
109
+ console.warn(WARNING_PREFIX + `Invalid event type ${event}`);
113
110
  return () => { };
114
111
  };
115
112
  if (!this.#callbacks.has(event)) this.#callbacks.set(event, []);
@@ -145,9 +142,9 @@ export default class PlaySocket {
145
142
  * @returns {Promise<string>} Resolves with the client ID when the connection is established
146
143
  */
147
144
  async init() {
148
- if (this.#initialized) throw new Error("Already initialized!");
149
- if (!this.#endpoint) throw new Error("No websocket endpoint provided!");
150
- this.#triggerEvent("status", "Initializing...");
145
+ if (this.#initialized) throw new Error("Already initialized");
146
+ if (!this.#endpoint) throw new Error("No websocket endpoint provided");
147
+ this.#triggerEvent("status", "Initializing.");
151
148
 
152
149
  // Connect to WS server
153
150
  await this.#connect();
@@ -161,7 +158,7 @@ export default class PlaySocket {
161
158
  this.#sendToServer({
162
159
  type: "register",
163
160
  id: this.#id,
164
- version: packageData.version,
161
+ version: VERSION,
165
162
  customData: this.#customData || undefined
166
163
  });
167
164
  }),
@@ -205,8 +202,7 @@ export default class PlaySocket {
205
202
 
206
203
  switch (message.type) {
207
204
  case "registration_failed":
208
- this.#triggerEvent("error", "Failed to register: " + (message.reason || "No reason provided."));
209
- if (this.#pendingRegistration) this.#pendingRegistration.reject(new Error("Failed to register: " + (message.reason || "No reason provided.")));
205
+ if (this.#pendingRegistration) this.#pendingRegistration.reject(new Error("Failed to register: " + (message.reason || "No reason provided")));
210
206
  break;
211
207
 
212
208
  case "registered":
@@ -234,8 +230,8 @@ export default class PlaySocket {
234
230
  break;
235
231
 
236
232
  case "join_rejected":
237
- this.#triggerEvent("status", "Failed to join room: " + (message.reason || "No reason provided."));
238
- if (this.#pendingJoin) this.#pendingJoin.reject(new Error("Failed to join room: " + (message.reason || "No reason provided.")));
233
+ this.#triggerEvent("status", "Failed to join room: " + (message.reason || "No reason provided"));
234
+ if (this.#pendingJoin) this.#pendingJoin.reject(new Error("Failed to join room: " + (message.reason || "No reason provided")));
239
235
  break;
240
236
 
241
237
  case "reconnected":
@@ -249,9 +245,8 @@ export default class PlaySocket {
249
245
  this.#setHost(message.roomData.host); // Set host before in case there are .isHost checks in the storageUpdate fallback
250
246
  this.#triggerEvent("storageUpdated", this.storage);
251
247
  } else if (this.#inRoom) {
252
- // If no room data received, but client thinks they were in a room...
253
- this.#triggerEvent("error", "Reconnected, but room no longer exists.");
254
- return this.destroy();
248
+ // If no room data was received, but client thinks they are in a room
249
+ return this.destroy("Reconnected, but room no longer exists.");
255
250
  }
256
251
  this.#triggerEvent("status", "Reconnected.");
257
252
  if (this.#pendingReconnect) this.#pendingReconnect.resolve();
@@ -271,8 +266,7 @@ export default class PlaySocket {
271
266
  break;
272
267
 
273
268
  case "room_creation_failed":
274
- this.#triggerEvent("error", "Failed to create room: " + (message.reason || "No reason provided."));
275
- if (this.#pendingCreate) this.#pendingCreate.reject(new Error("Failed to create room: " + (message.reason || "No reason provided.")));
269
+ if (this.#pendingCreate) this.#pendingCreate.reject(new Error("Failed to create room: " + (message.reason || "No reason provided")));
276
270
  break;
277
271
 
278
272
  case "property_updated":
@@ -281,20 +275,19 @@ export default class PlaySocket {
281
275
  this.#crdtManager.importPropertyUpdate(message.update);
282
276
  if (this.#crdtManager.didPropertiesChange) this.#triggerEvent("storageUpdated", this.storage);
283
277
  if (this.#roomVersion != message.version && this.#initialized && this.#socket?.readyState === WebSocket.OPEN) {
284
- this.#triggerEvent("error", "Detected skipped update – forcing reconnect.");
278
+ console.error(ERROR_PREFIX + "Detected skipped property update, forcing reconnect");
285
279
  this.#socket?.close();
286
280
  }
287
281
  break;
288
282
 
289
283
  case "property_update_rejected":
290
- this.#triggerEvent("error", "Property update rejected, reverting: " + (message.reason || "No reason provided."));
284
+ console.warn(LOG_PREFIX + "Property update rejected: " + (message.reason || "No reason provided"));
291
285
  this.#crdtManager.revertPropertyUpdate(message.update);
292
286
  if (this.#crdtManager.didPropertiesChange) this.#triggerEvent("storageUpdated", this.storage);
293
287
  break;
294
288
 
295
289
  case "kicked":
296
- this.#triggerEvent("error", `Kicked out of room: ${message.reason || "No reason provided."}`);
297
- this.destroy();
290
+ this.destroy(`Kicked out of room: ${message.reason || "No reason provided"}`);
298
291
  break;
299
292
 
300
293
  case "host_migrated":
@@ -318,7 +311,7 @@ export default class PlaySocket {
318
311
  break;
319
312
 
320
313
  case "request_failed":
321
- this.#pendingRequests.get(message.request.uuid)?.reject(new Error(message.reason || "No reason provided."));
314
+ this.#pendingRequests.get(message.request.uuid)?.reject(new Error(message.reason || "No reason provided"));
322
315
  break;
323
316
  }
324
317
  } catch (error) {
@@ -328,8 +321,7 @@ export default class PlaySocket {
328
321
 
329
322
  // Handle socket errors
330
323
  this.#socket.onerror = () => {
331
- this.#triggerEvent("error", "WebSocket error.");
332
- if (this.#pendingConnect) this.#pendingConnect.reject(new Error("WebSocket error!"));
324
+ if (this.#pendingConnect) this.#pendingConnect.reject(new Error("WebSocket error"));
333
325
  }
334
326
 
335
327
  // Handle socket close & attempt reconnect
@@ -347,12 +339,11 @@ export default class PlaySocket {
347
339
  async #attemptReconnect() {
348
340
  this.#reconnectCount++;
349
341
  if (this.#reconnectCount > 9) {
350
- this.#triggerEvent("error", "Disconnected from server.");
351
- this.destroy();
342
+ this.destroy("Disconnected, reconnection failed.");
352
343
  return;
353
344
  }
354
345
 
355
- this.#triggerEvent("status", `Attempting to reconnect... (${this.#reconnectCount})`);
346
+ this.#triggerEvent("status", `Trying to reconnect: Attempt ${this.#reconnectCount}`);
356
347
  this.#isReconnecting = true;
357
348
  try {
358
349
  await this.#connect();
@@ -393,14 +384,13 @@ export default class PlaySocket {
393
384
  */
394
385
  #sendToServer(data) {
395
386
  if (!this.#socket || this.#socket?.readyState !== WebSocket.OPEN) {
396
- console.warn(WARNING_PREFIX + "Failed to send message - not connected.");
387
+ console.warn(WARNING_PREFIX + "Failed to send message, not connected");
397
388
  return;
398
389
  }
399
390
  try {
400
391
  this.#socket.send(encode(data));
401
392
  } catch (error) {
402
393
  console.error(ERROR_PREFIX + "Error sending message:", error);
403
- this.#triggerEvent("error", "Error sending message: " + error.message);
404
394
  }
405
395
  }
406
396
 
@@ -411,10 +401,7 @@ export default class PlaySocket {
411
401
  * @returns {Promise<string>} Resolves with room ID
412
402
  */
413
403
  async createRoom(initialStorage = {}, size) {
414
- if (!this.#initialized) {
415
- this.#triggerEvent("error", "Failed to create room - not initialized");
416
- throw new Error("Not initialized!");
417
- }
404
+ if (!this.#initialized) throw new Error("Not initialized");
418
405
 
419
406
  return /** @type {Promise<string>} */ (Promise.race([
420
407
  new Promise((resolve, reject) => {
@@ -438,17 +425,14 @@ export default class PlaySocket {
438
425
  * @returns {Promise<void>} - Resolves when connected
439
426
  */
440
427
  async joinRoom(roomId) {
441
- if (!this.#initialized) {
442
- this.#triggerEvent("error", "Failed to join room - not initialized");
443
- throw new Error("Not initialized!");
444
- }
428
+ if (!this.#initialized) throw new Error("Not initialized");
445
429
 
446
430
  return /** @type {Promise<void>} */ (Promise.race([
447
431
  new Promise((resolve, reject) => {
448
432
  this.#pendingJoin = { resolve, reject };
449
433
 
450
434
  // Send connection request
451
- this.#triggerEvent("status", `Connecting to room ${roomId}...`);
435
+ this.#triggerEvent("status", `Connecting to room ${roomId}.`);
452
436
  this.#sendToServer({
453
437
  type: "join_room",
454
438
  roomId
@@ -469,10 +453,10 @@ export default class PlaySocket {
469
453
  */
470
454
  updateStorage(key, type, value, secondValue) {
471
455
  if (!this.#inRoom) {
472
- this.#triggerEvent("error", "Failed to update storage - not in a room.");
456
+ console.error(ERROR_PREFIX + "Failed to update storage, not in a room")
473
457
  return;
474
458
  };
475
- if (this.#debug) console.log(LOG_PREFIX + `Property update for key ${key}, operation ${type}, value ${value} and secondValue ${secondValue}.`);
459
+ if (this.#debug) console.log(LOG_PREFIX + `Property update for key ${key}, operation ${type}, value ${value} and secondValue ${secondValue}`);
476
460
  const propUpdate = this.#crdtManager.updateProperty(key, type, value, secondValue);
477
461
  this.#sendToServer({
478
462
  type: "update_property",
@@ -488,10 +472,8 @@ export default class PlaySocket {
488
472
  * @returns {Promise<void>} - Request promise
489
473
  */
490
474
  async sendRequest(name, data) {
491
- if (!this.#initialized) {
492
- this.#triggerEvent("error", "Failed to send request - not initialized");
493
- throw new Error("Not initialized!");
494
- }
475
+ if (!this.#initialized) throw new Error("Not initialized");
476
+
495
477
  if (this.#debug) console.log(LOG_PREFIX + `Server request with name ${name} and data:`, data);
496
478
  const uuid = crypto.randomUUID();
497
479
  return /** @type {Promise<void>} */ (Promise.race([
@@ -511,8 +493,9 @@ export default class PlaySocket {
511
493
 
512
494
  /**
513
495
  * Destroy the PlaySocket instance and disconnect
496
+ * @param {string} [reason] - Optional reason
514
497
  */
515
- destroy() {
498
+ destroy(reason) {
516
499
  this.#initialized = false; // Set this immediately to prevent automatic reconnection
517
500
 
518
501
  if (this.#socket) {
@@ -526,7 +509,7 @@ export default class PlaySocket {
526
509
 
527
510
  // Trigger events
528
511
  this.#triggerEvent("status", "Destroyed.");
529
- this.#triggerEvent("instanceDestroyed");
512
+ this.#triggerEvent("instanceDestroyed", reason);
530
513
  }
531
514
 
532
515
  // Reset state
@@ -539,13 +522,13 @@ export default class PlaySocket {
539
522
  this.#roomVersion = 0;
540
523
 
541
524
  // Reject pending promises if currently active
542
- if (this.#pendingJoin) this.#pendingJoin.reject(new Error("Destroyed."));
543
- if (this.#pendingCreate) this.#pendingCreate.reject(new Error("Destroyed."));
544
- if (this.#pendingRegistration) this.#pendingRegistration.reject(new Error("Destroyed."));
545
- if (this.#pendingConnect) this.#pendingConnect.reject(new Error("Destroyed."));
546
- if (this.#pendingReconnect) this.#pendingReconnect.reject(new Error("Destroyed."));
525
+ if (this.#pendingJoin) this.#pendingJoin.reject(new Error("Destroyed"));
526
+ if (this.#pendingCreate) this.#pendingCreate.reject(new Error("Destroyed"));
527
+ if (this.#pendingRegistration) this.#pendingRegistration.reject(new Error("Destroyed"));
528
+ if (this.#pendingConnect) this.#pendingConnect.reject(new Error("Destroyed"));
529
+ if (this.#pendingReconnect) this.#pendingReconnect.reject(new Error("Destroyed"));
547
530
  this.#pendingRequests.forEach((request) => {
548
- request.reject(new Error("Destroyed."));
531
+ request.reject(new Error("Destroyed"));
549
532
  });
550
533
  }
551
534
 
@@ -2,8 +2,7 @@ import { WebSocketServer } from "ws";
2
2
  import { createServer } from "node:http";
3
3
  import { encode, decode } from "@msgpack/msgpack";
4
4
  import CRDTManager, { getUpdateDetails } from "../universal/crdtManager.js";
5
- import packageData from "../../package.json" with { type: "json" };
6
- import { HEARTBEAT_INTERVAL } from "../universal/constants.js";
5
+ import { HEARTBEAT_INTERVAL, VERSION, ERROR_PREFIX, WARNING_PREFIX, LOG_PREFIX } from "../universal/constants.js";
7
6
 
8
7
  const MAX_ROOM_SIZE = 500;
9
8
  export const RECONNECT_GRACE_PERIOD = 5000; // Exported for use in tests
@@ -86,7 +85,7 @@ export default class PlaySocketServer {
86
85
  this.#ownsServer = true;
87
86
  this.#server = createServer(); // Create HTTP server and start it
88
87
  this.#server.listen(port, () => {
89
- console.log(`PlaySocket server running on port ${port}.`);
88
+ console.log(LOG_PREFIX + `Server running on port ${port}`);
90
89
  });
91
90
  }
92
91
 
@@ -105,13 +104,13 @@ export default class PlaySocketServer {
105
104
  ws.on("message", msg => this.#handleMessage(ws, msg));
106
105
  ws.on("close", () => this.#handleDisconnection(ws));
107
106
  ws.on("error", (error) => {
108
- console.error(`PlaySocket WebSocket connection error for client ${ws.clientId || "unknown"}:`, error); // Catch conn errors
107
+ console.error(ERROR_PREFIX + `WebSocket connection error for client ${ws.clientId || "unknown"}:`, error); // Catch conn errors
109
108
  });
110
109
  });
111
110
 
112
111
  // Log & catch server-level errors
113
112
  this.#wss.on("error", (error) => {
114
- console.error("PlaySocket WebSocket server error:", error);
113
+ console.error(ERROR_PREFIX + "WebSocket server error:", error);
115
114
  });
116
115
 
117
116
  // Start heartbeat
@@ -139,7 +138,7 @@ export default class PlaySocketServer {
139
138
  if (!ws.isTerminating) {
140
139
  ws.isTerminating = true; // Prevent multiple terminate calls (it is async)
141
140
  ws.terminate();
142
- console.error(`PlaySocket connection ${ws.uuid} terminated due to rate limit violations.`);
141
+ console.error(ERROR_PREFIX + `Connection ${ws.uuid} terminated due to rate limit violations`);
143
142
  return;
144
143
  }
145
144
  }
@@ -147,14 +146,14 @@ export default class PlaySocketServer {
147
146
  switch (data.type) {
148
147
  case "register": {
149
148
  // Ensure client uses the same version as server
150
- if (data.version !== packageData.version) {
151
- ws.send(encode({ type: "registration_failed", reason: `Version mismatch (client ${data.version ? "v" + data.version : "legacy"}, server v${packageData.version}). Please reload the page, an update may have been released.` }), { binary: true });
149
+ if (data.version !== VERSION) {
150
+ ws.send(encode({ type: "registration_failed", reason: `Version mismatch (client ${data.version ? "v" + data.version : "legacy"}, server v${VERSION}), please reload the page` }), { binary: true });
152
151
  return;
153
152
  }
154
153
 
155
154
  // Register client ID if provided & check for a duplicate
156
155
  if (data.id && (this.#clients.get(data.id) || data.id === "server")) {
157
- ws.send(encode({ type: "registration_failed", reason: "ID is taken." }), { binary: true });
156
+ ws.send(encode({ type: "registration_failed", reason: "ID is taken" }), { binary: true });
158
157
  return;
159
158
  }
160
159
 
@@ -168,8 +167,8 @@ export default class PlaySocketServer {
168
167
  }
169
168
  }
170
169
  if (!data.id) {
171
- ws.send(encode({ type: "registration_failed", reason: "No available ID found." }), { binary: true });
172
- throw new Error("Failed to generate unique ID!");
170
+ ws.send(encode({ type: "registration_failed", reason: "No available ID found" }), { binary: true });
171
+ throw new Error("Failed to generate unique ID");
173
172
  }
174
173
  }
175
174
 
@@ -199,7 +198,7 @@ export default class PlaySocketServer {
199
198
  if (data.sessionToken !== this.#clientTokens.get(data.id)) {
200
199
  ws.send(encode({
201
200
  type: "reconnection_failed",
202
- reason: "Session token does not match."
201
+ reason: "Session token does not match"
203
202
  }), { binary: true });
204
203
  return;
205
204
  }
@@ -217,7 +216,7 @@ export default class PlaySocketServer {
217
216
  if (formerRoom) {
218
217
  // If the room has no host (they were the only one and disconnected), restore them as host
219
218
  if (formerRoom.host === null) formerRoom.host = data.id;
220
- if (this.#debug) console.log(`State sent for reconnection for room ${formerRoomId}:`, formerRoom.crdtManager.state);
219
+ if (this.#debug) console.log(LOG_PREFIX + `State sent for reconnection for room ${formerRoomId}:`, formerRoom.crdtManager.state);
221
220
  roomData = {
222
221
  state: formerRoom.crdtManager.state,
223
222
  participantCount: formerRoom.participants.length,
@@ -228,7 +227,7 @@ export default class PlaySocketServer {
228
227
 
229
228
  ws.send(encode({ type: "reconnected", roomData }), { binary: true });
230
229
  } else {
231
- ws.send(encode({ type: "reconnection_failed", reason: "Client unknown to server." }), { binary: true });
230
+ ws.send(encode({ type: "reconnection_failed", reason: "Client unknown to server" }), { binary: true });
232
231
  }
233
232
  break;
234
233
  }
@@ -239,7 +238,7 @@ export default class PlaySocketServer {
239
238
  if (this.#clientRooms.get(ws.clientId)) {
240
239
  ws.send(encode({
241
240
  type: "room_creation_failed",
242
- reason: "Already in a room."
241
+ reason: "Already in a room"
243
242
  }), { binary: true });
244
243
  return;
245
244
  }
@@ -255,7 +254,7 @@ export default class PlaySocketServer {
255
254
  try {
256
255
  // Verify client is still connected and abort if not
257
256
  if (!this.#clients.has(ws.clientId)) {
258
- if (this.#debug) console.log(`Room creation cancelled - client ${ws.clientId} disconnected during event callback.`);
257
+ if (this.#debug) console.log(LOG_PREFIX + `Room creation cancelled, client ${ws.clientId} disconnected during event callback`);
259
258
  return;
260
259
  }
261
260
 
@@ -265,7 +264,7 @@ export default class PlaySocketServer {
265
264
  this.#clientRooms.set(ws.clientId, newRoom.id); // Add client to the client-room map
266
265
  ws.send(encode({ type: "room_created", state: newRoom.state, roomId: newRoom.id, participantCount: this.#rooms[newRoom.id].participants.length }), { binary: true });
267
266
  } catch (error) {
268
- console.error("PlaySocket error creating room:", error);
267
+ console.error(ERROR_PREFIX + "Error creating room:", error);
269
268
  ws.send(encode({ type: "room_creation_failed", reason: error.message }), { binary: true });
270
269
  }
271
270
  break;
@@ -286,7 +285,7 @@ export default class PlaySocketServer {
286
285
 
287
286
  // Verify client is still connected and abort if not
288
287
  if (!this.#clients.has(ws.clientId)) {
289
- if (this.#debug) console.log(`Room join cancelled - client ${ws.clientId} disconnected during event callback.`);
288
+ if (this.#debug) console.log(LOG_PREFIX + `Room join cancelled, client ${ws.clientId} disconnected during event callback`);
290
289
  return;
291
290
  }
292
291
 
@@ -333,7 +332,7 @@ export default class PlaySocketServer {
333
332
  });
334
333
 
335
334
  this.#triggerEvent("storageUpdated", { roomId, clientId: ws.clientId, update: structuredClone(data.update), storage: this.getRoomStorage(roomId) });
336
- if (this.#debug) console.log("Property update received and imported:", data.update);
335
+ if (this.#debug) console.log(LOG_PREFIX + "Property update received and imported:", data.update);
337
336
  }
338
337
  break;
339
338
  }
@@ -353,7 +352,7 @@ export default class PlaySocketServer {
353
352
  break;
354
353
  }
355
354
  } catch (error) {
356
- console.error("PlaySocket error in message handler:", error);
355
+ console.error(ERROR_PREFIX + "Error in message handler:", error);
357
356
  }
358
357
  }
359
358
 
@@ -373,8 +372,8 @@ export default class PlaySocketServer {
373
372
  */
374
373
  #migrateHost(roomId, clientId) {
375
374
  const room = this.#rooms[roomId];
376
- if (!room) throw new Error("Room not found.");
377
- if (room.host !== clientId) throw new Error("Client is not the host.");
375
+ if (!room) throw new Error("Room not found");
376
+ if (room.host !== clientId) throw new Error("Client is not the host");
378
377
 
379
378
  const remainingParticipants = room.participants.filter(p => p !== clientId);
380
379
 
@@ -477,7 +476,7 @@ export default class PlaySocketServer {
477
476
  onEvent(event, callback) {
478
477
  const validEvents = ["clientRegistered", "clientRegistrationRequested", "clientDisconnected", "clientJoinedRoom", "clientLeftRoom", "clientJoinRequested", "roomCreated", "roomCreationRequested", "requestReceived", "storageUpdated", "storageUpdateRequested", "roomDestroyed"];
479
478
  if (!validEvents.includes(event)) {
480
- console.warn(`Invalid PlaySocket event type "${event}"`);
479
+ console.warn(WARNING_PREFIX + `Invalid event type ${event}`);
481
480
  return () => { };
482
481
  };
483
482
  if (!this.#callbacks.has(event)) this.#callbacks.set(event, []);
@@ -509,13 +508,13 @@ export default class PlaySocketServer {
509
508
  if (typeof result?.then === "function") {
510
509
  if (syncOnlyEvents.includes(event)) {
511
510
  result.catch(() => { });
512
- console.error(`PlaySocket ${event} callbacks must be synchronous.`);
511
+ console.error(ERROR_PREFIX + `${event} callbacks must be synchronous`);
513
512
  result = `${event} callback must be synchronous.`; // Rejection string to explicitly block this practice
514
513
  } else result = await result;
515
514
  }
516
515
  if (firstResult == null && result != null) firstResult = result;
517
516
  } catch (error) {
518
- console.error(`PlaySocket ${event} callback error:`, error);
517
+ console.error(ERROR_PREFIX + `${event} callback error:`, error);
519
518
  if (firstResult == null) firstResult = false; // Fail closed (false blocks for most events)
520
519
  }
521
520
  }
@@ -545,12 +544,12 @@ export default class PlaySocketServer {
545
544
  const oldRoomId = this.#clientRooms.get(clientId);
546
545
  const oldRoom = this.#rooms[oldRoomId];
547
546
  const targetRoom = this.#rooms[roomId];
548
- if (!client && !this.#pendingDisconnects.has(clientId)) throw new Error("Client not found.");
549
- if (!oldRoomId) throw new Error("Client is not in a room.");
550
- if (!oldRoom) throw new Error("Client room not found.");
551
- if (oldRoomId === roomId) throw new Error("Client is already in target room.");
552
- if (!targetRoom) throw new Error("Target room not found.");
553
- if (targetRoom.participants.length >= targetRoom.size) throw new Error("Target room is full.");
547
+ if (!client && !this.#pendingDisconnects.has(clientId)) throw new Error("Client not found");
548
+ if (!oldRoomId) throw new Error("Client is not in a room");
549
+ if (!oldRoom) throw new Error("Client room not found");
550
+ if (oldRoomId === roomId) throw new Error("Client is already in target room");
551
+ if (!targetRoom) throw new Error("Target room not found");
552
+ if (targetRoom.participants.length >= targetRoom.size) throw new Error("Target room is full");
554
553
  if (oldRoom.host === clientId) this.#migrateHost(oldRoomId, clientId);
555
554
  this.#leaveRoom(clientId, oldRoomId);
556
555
  this.#joinRoom(clientId, roomId);
@@ -584,7 +583,7 @@ export default class PlaySocketServer {
584
583
  * @param {*} [secondValue] - Second value (needed for some operations)
585
584
  */
586
585
  updateRoomStorage(roomId, key, type, value, secondValue) {
587
- if (this.#debug) console.log(`Playsocket server property update for room ${roomId}, key ${key}, operation ${type}, value ${value} and secondValue ${secondValue}.`);
586
+ if (this.#debug) console.log(LOG_PREFIX + `Property update for room ${roomId}, key ${key}, operation ${type}, value ${value} and secondValue ${secondValue}`);
588
587
  const room = this.#rooms[roomId];
589
588
  if (room) {
590
589
  const propertyUpdate = room.crdtManager.updateProperty(key, type, value, secondValue);
@@ -623,7 +622,7 @@ export default class PlaySocketServer {
623
622
  }
624
623
  }
625
624
 
626
- if (!newRoomId) throw new Error("No available ID found.");
625
+ if (!newRoomId) throw new Error("No available ID found");
627
626
  const roomCrdtManager = new CRDTManager(this.#debug);
628
627
 
629
628
  if (initialStorage) Object.entries(initialStorage)?.forEach(([key, value]) => {
@@ -639,7 +638,7 @@ export default class PlaySocketServer {
639
638
  };
640
639
 
641
640
  this.#triggerEvent("roomCreated", newRoomId);
642
- if (this.#debug) console.log(`Room ${newRoomId} created with initial storage:`, initialStorage);
641
+ if (this.#debug) console.log(LOG_PREFIX + `Room ${newRoomId} created with initial storage:`, initialStorage);
643
642
  return { state: roomCrdtManager.state, id: newRoomId };
644
643
  }
645
644
 
@@ -651,17 +650,17 @@ export default class PlaySocketServer {
651
650
  #joinRoom(clientId, roomId) {
652
651
  const room = this.#rooms[roomId];
653
652
  const client = this.#clients.get(clientId);
654
- if (!room) throw new Error("Room not found.");
655
- if (!client && !this.#pendingDisconnects.has(clientId)) throw new Error("Client not found.");
656
- if (this.#clientRooms.get(clientId)) throw new Error("Already in a room.");
657
- if (room.participants.length >= room.size) throw new Error("Room full.");
653
+ if (!room) throw new Error("Room not found");
654
+ if (!client && !this.#pendingDisconnects.has(clientId)) throw new Error("Client not found");
655
+ if (this.#clientRooms.get(clientId)) throw new Error("Already in a room");
656
+ if (room.participants.length >= room.size) throw new Error("Room full");
658
657
 
659
658
  room.participants.push(clientId);
660
659
  this.#clientRooms.set(clientId, roomId);
661
660
 
662
661
  // Notify joiner with initial storage (only if connected - pending disconnect clients get state on reconnect)
663
662
  if (client) {
664
- if (this.#debug) console.log("Room state sent for join:", room.crdtManager.state);
663
+ if (this.#debug) console.log(LOG_PREFIX + "Room state sent for join:", room.crdtManager.state);
665
664
  client.send(encode({
666
665
  type: "join_accepted",
667
666
  roomId,
@@ -707,7 +706,7 @@ export default class PlaySocketServer {
707
706
  */
708
707
  #leaveRoom(clientId, roomId) {
709
708
  const room = this.#rooms[roomId];
710
- if (!room) throw new Error("Room not found.");
709
+ if (!room) throw new Error("Room not found");
711
710
 
712
711
  room.participants = room.participants.filter(p => p !== clientId); // Remove client from room
713
712
  this.#clientRooms.delete(clientId);
@@ -734,19 +733,19 @@ export default class PlaySocketServer {
734
733
  */
735
734
  destroyRoom(roomId) {
736
735
  const room = this.#rooms[roomId];
737
- if (!room) throw new Error("Room not found.");
736
+ if (!room) throw new Error("Room not found");
738
737
 
739
738
  // Disconnect clients if still in room
740
739
  const participants = room.participants;
741
740
  participants.forEach((clientId) => {
742
- this.kick(clientId, "Room destroyed by server.");
741
+ this.kick(clientId, "Room destroyed by server");
743
742
  });
744
743
 
745
744
  // Delete the room
746
745
  delete this.#rooms[roomId];
747
746
  this.#roomVersions.delete(roomId); // Delete room version (used to ensure all clients are up-2-date)
748
747
  this.#triggerEvent("roomDestroyed", roomId);
749
- if (this.#debug) console.log("Deleted room with id " + roomId + ".");
748
+ if (this.#debug) console.log(LOG_PREFIX + "Deleted room with id " + roomId);
750
749
  }
751
750
 
752
751
  /**
@@ -755,10 +754,10 @@ export default class PlaySocketServer {
755
754
  stop() {
756
755
  clearInterval(this.#heartbeatInterval);
757
756
  if (this.#wss) {
758
- this.#clients.forEach((client, clientId) => this.kick(clientId, "Server restart."));
757
+ this.#clients.forEach((client, clientId) => this.kick(clientId, "Server restart"));
759
758
  this.#wss.close();
760
759
  }
761
- if (this.#server && this.#ownsServer) this.#server.close(() => { console.log("PlaySocket server stopped."); });
760
+ if (this.#server && this.#ownsServer) this.#server.close(() => { console.log(LOG_PREFIX + "Server stopped"); });
762
761
  this.#pendingDisconnects.forEach((data) => {
763
762
  clearTimeout(data.timeout);
764
763
  });
@@ -5,3 +5,15 @@
5
5
  * be desynced as a result of gc
6
6
  */
7
7
  export const HEARTBEAT_INTERVAL = 5000;
8
+
9
+ /**
10
+ * Library version, must match package.json
11
+ */
12
+ export const VERSION = "4.3.0";
13
+
14
+ /**
15
+ * Logging prefixes
16
+ */
17
+ export const ERROR_PREFIX = "PlaySocket error: ";
18
+ export const WARNING_PREFIX = "PlaySocket warning: ";
19
+ export const LOG_PREFIX = "PlaySocket log: ";
@@ -88,8 +88,9 @@ export default class PlaySocket {
88
88
  sendRequest(name: string, data?: any): Promise<void>;
89
89
  /**
90
90
  * Destroy the PlaySocket instance and disconnect
91
+ * @param {string} [reason] - Optional reason
91
92
  */
92
- destroy(): void;
93
+ destroy(reason?: string): void;
93
94
  get participantCount(): number;
94
95
  get storage(): Record<string, any>;
95
96
  get isHost(): boolean;
@@ -5,3 +5,13 @@
5
5
  * be desynced as a result of gc
6
6
  */
7
7
  export declare const HEARTBEAT_INTERVAL = 5000;
8
+ /**
9
+ * Library version, must match package.json
10
+ */
11
+ export declare const VERSION = "4.3.0";
12
+ /**
13
+ * Logging prefixes
14
+ */
15
+ export declare const ERROR_PREFIX = "PlaySocket error: ";
16
+ export declare const WARNING_PREFIX = "PlaySocket warning: ";
17
+ export declare const LOG_PREFIX = "PlaySocket log: ";