playsocketjs 2.5.2 → 2.5.4

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.
@@ -1,5 +1,5 @@
1
1
  import { WebSocketServer } from 'ws';
2
- import * as http from 'node:http';
2
+ import { createServer } from 'node:http';
3
3
 
4
4
  function utf8Count(str) {
5
5
  const strLength = str.length;
@@ -1686,13 +1686,15 @@ function decode(buffer, options) {
1686
1686
 
1687
1687
  const CONSOLE_PREFIX = "PlaySocket CRDT manager: ";
1688
1688
 
1689
+ /**
1690
+ * CRDT Manager
1691
+ */
1689
1692
  class CRDTManager {
1690
1693
  // Storage
1691
1694
  #replicaId;
1692
1695
  #keyOperations = new Map();
1693
1696
  #vectorClock = new Map();
1694
1697
 
1695
- // Local only
1696
1698
  #propertyStore = {}; // Current local values per key, as object
1697
1699
  #lastPropertyStore = {}; // Last property store to compare against
1698
1700
 
@@ -1705,6 +1707,7 @@ class CRDTManager {
1705
1707
 
1706
1708
  /**
1707
1709
  * Create a new instance
1710
+ * @param {boolean} [debug=false] - Enable debug logging
1708
1711
  */
1709
1712
  constructor(debug) {
1710
1713
  if (debug) this.#debug = true;
@@ -1714,7 +1717,7 @@ class CRDTManager {
1714
1717
 
1715
1718
  /**
1716
1719
  * Import the entire state of the CRDT manager (this overwrites the old state)
1717
- * @param {Object} state
1720
+ * @param {object} state - Full state of the CRDT manager
1718
1721
  */
1719
1722
  importState(state) {
1720
1723
  try {
@@ -1731,7 +1734,7 @@ class CRDTManager {
1731
1734
  if (!this.#vectorClock.has(this.#replicaId)) this.#vectorClock.set(this.#replicaId, 0); // Add own vector clock if it wasn't present in the imported state
1732
1735
 
1733
1736
  // Map all operation uuids to current timestamp
1734
- this.#keyOperations.forEach((value, key) => {
1737
+ this.#keyOperations.forEach((value, _key) => {
1735
1738
  value?.forEach((operation) => {
1736
1739
  this.#opUuidTimestamp.set(operation?.uuid, Date.now());
1737
1740
  });
@@ -1747,7 +1750,7 @@ class CRDTManager {
1747
1750
 
1748
1751
  /**
1749
1752
  * Import property update
1750
- * @param {Object} data - Data to import
1753
+ * @param {object} data - Property update data
1751
1754
  */
1752
1755
  importPropertyUpdate(data) {
1753
1756
  try {
@@ -1789,11 +1792,11 @@ class CRDTManager {
1789
1792
 
1790
1793
  /**
1791
1794
  * Update a property
1792
- * @param {string} key
1793
- * @param {string} type
1794
- * @param {*} value
1795
- * @param {*} updateValue
1796
- * @returns {Object} - Returns the property update
1795
+ * @param {string} key - Storage key
1796
+ * @param {string} type - Operation type
1797
+ * @param {*} value - New value, or value to match
1798
+ * @param {*} updateValue - New value for 'matching' operations
1799
+ * @returns {object | undefined} - Returns the property update
1797
1800
  */
1798
1801
  updateProperty(key, type, value, updateValue) {
1799
1802
  try {
@@ -1830,6 +1833,9 @@ class CRDTManager {
1830
1833
  }
1831
1834
  }
1832
1835
 
1836
+ /**
1837
+ * Check if garbage collected can be performed & run it
1838
+ */
1833
1839
  #checkGarbageCollection() {
1834
1840
  const MIN_GC_DELAY = 1000; // Minimum 1s delay between garbage collection runs
1835
1841
  const MIN_AGE_FOR_GC = 5000; // Garbage collect ops that are older than 5s
@@ -1855,7 +1861,7 @@ class CRDTManager {
1855
1861
  if (this.#debug) console.log(CONSOLE_PREFIX + `Running garbage collection for key ${key} with current operations:`, operations);
1856
1862
  const retainOps = operations.slice(-retainCount); // Newest ops
1857
1863
  const removeOps = operations.slice(0, -retainCount); // Oldest ops (start = idx 0, end = retainCount counted from right side)
1858
- const baselineVectorClock = removeOps[removeOps.length - 1]?.vectorClock || []; // Use the vector clock from the last operation that we remove/overwrite
1864
+ const baselineVectorClock = removeOps[removeOps.length - 1]?.vectorClock || []; // Use the vector clock from the last operation that we remove/overwrite
1859
1865
 
1860
1866
  // Calculate the value at the point where retained operations start
1861
1867
  let baselineValue = null;
@@ -1886,9 +1892,9 @@ class CRDTManager {
1886
1892
 
1887
1893
  /**
1888
1894
  * Create an operation object
1889
- * @param {Object} data
1890
- * @param {Array} vectorClock
1891
- * @returns {Object} - Operation object
1895
+ * @param {object} data - Operation data, consists of type, value and updateValue
1896
+ * @param {Array} vectorClock - Vector clock of the replica/client
1897
+ * @returns {object} - Operation object
1892
1898
  */
1893
1899
  #createOperation(data, vectorClock) {
1894
1900
  const newOperation = {
@@ -1903,7 +1909,7 @@ class CRDTManager {
1903
1909
 
1904
1910
  /**
1905
1911
  * Process a property's value by applying all operations and set it in the property store
1906
- * @param {string} key
1912
+ * @param {string} key - Storage key
1907
1913
  */
1908
1914
  #processLocalProperty(key) {
1909
1915
  try {
@@ -1930,8 +1936,8 @@ class CRDTManager {
1930
1936
  }
1931
1937
 
1932
1938
  /**
1933
- * Sort by vector clock (causal order)
1934
- * @param {Array} operations
1939
+ * Sort operations by vector clock (causal order)
1940
+ * @param {Array} operations - Full operations that include vector clocks, data and a source
1935
1941
  * @returns {Array} - Sorted operations
1936
1942
  */
1937
1943
  #sortByVectorClock(operations) {
@@ -1967,10 +1973,10 @@ class CRDTManager {
1967
1973
 
1968
1974
  /**
1969
1975
  * Handle an operation
1970
- * @param {*} curValue
1971
- * @param {string} type
1972
- * @param {*} value
1973
- * @param {*} [updateValue]
1976
+ * @param {*} curValue - Current value of the storage key
1977
+ * @param {string} type - Operation type
1978
+ * @param {*} value - New value, or value to match
1979
+ * @param {*} [updateValue] - New value for 'matching' operations
1974
1980
  * @returns {*} - Value after the operation
1975
1981
  */
1976
1982
  #handleOperation(curValue, type, value, updateValue) {
@@ -2002,10 +2008,11 @@ class CRDTManager {
2002
2008
  case 'array-remove-matching':
2003
2009
  curValue = curValue.filter(item => !compare(item));
2004
2010
  break;
2005
- case 'array-update-matching':
2011
+ case 'array-update-matching': {
2006
2012
  const index = curValue.findIndex(compare);
2007
2013
  if (index !== -1) curValue[index] = updateValue;
2008
2014
  break;
2015
+ }
2009
2016
  }
2010
2017
  }
2011
2018
 
@@ -2019,42 +2026,53 @@ class CRDTManager {
2019
2026
 
2020
2027
  /**
2021
2028
  * Remove HTML to prevent XSS and enforce size limits
2022
- * @param {Object} obj
2023
- * @returns {Object} - Sanitized object
2029
+ * @param {*} value - Value to sanitize
2030
+ * @returns {*} - Sanitized value
2024
2031
  */
2025
- #sanitizeValue(obj) {
2032
+ #sanitizeValue(value) {
2026
2033
  // Check total serialized size
2027
- const jsonString = JSON.stringify(obj);
2034
+ const jsonString = JSON.stringify(value);
2028
2035
  if (jsonString?.length > 50000) throw new Error('Value too large!'); // 50KB limit
2029
2036
 
2030
- if (typeof obj === 'string') return (obj.includes('<') || obj.includes('>')) ? obj.replace(/[<>]/g, '') : obj;
2031
- if (Array.isArray(obj)) return obj.map(item => this.#sanitizeValue(item));
2032
- if (obj && typeof obj === 'object') return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, this.#sanitizeValue(v)]));
2033
- return obj;
2037
+ if (typeof value === 'string') return (value.includes('<') || value.includes('>')) ? value.replace(/[<>]/g, '') : value;
2038
+ if (Array.isArray(value)) return value.map(item => this.#sanitizeValue(item));
2039
+ if (value && typeof value === 'object') return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, this.#sanitizeValue(v)]));
2040
+ return value;
2034
2041
  }
2035
2042
 
2036
- // Get property store
2043
+ /**
2044
+ * Get property store
2045
+ * @returns {Record<string, any>} - Object with key value pairs
2046
+ */
2037
2047
  get getPropertyStore() {
2038
2048
  try {
2039
2049
  return structuredClone(this.#propertyStore); // Return deep clone
2040
2050
  } catch {
2041
2051
  console.error(CONSOLE_PREFIX + "Failed to parse property store");
2042
- return {};
2052
+ return {}
2043
2053
  }
2044
2054
  }
2045
2055
 
2046
- // Check for changes in the local property store
2056
+ /**
2057
+ * Check for changes in the local property store
2058
+ * @returns {boolean} - Check if any values changed going from the last to the current property update
2059
+ */
2047
2060
  get didPropertiesChange() {
2048
2061
  try {
2049
2062
  if (JSON.stringify(this.#propertyStore) !== JSON.stringify(this.#lastPropertyStore)) {
2050
2063
  this.#lastPropertyStore = structuredClone(this.#propertyStore);
2051
2064
  return true;
2052
2065
  }
2053
- } catch { }
2066
+ } catch {
2067
+ console.error(CONSOLE_PREFIX + "Failed to stringify and compare property stores");
2068
+ }
2054
2069
  return false;
2055
2070
  }
2056
2071
 
2057
- // Get state (can be imported using importState, converts the maps to arrays for serialization)
2072
+ /**
2073
+ * Export state (can be imported using importState, converts the maps to arrays for serialization)
2074
+ * @returns {object} - State
2075
+ */
2058
2076
  get getState() {
2059
2077
  return {
2060
2078
  keyOperations: [...this.#keyOperations.entries()],
@@ -2063,8 +2081,15 @@ class CRDTManager {
2063
2081
  }
2064
2082
  }
2065
2083
 
2084
+ /* eslint-disable jsdoc/require-returns */
2085
+
2066
2086
  /**
2067
- * PlaySocketServer - WebSocket server for PlaySocket multiplayer library
2087
+ * @typedef {import('node:http').Server} HttpServer
2088
+ * @typedef {import('ws').WebSocket} WebSocket
2089
+ */
2090
+
2091
+ /**
2092
+ * PlaySocket Server
2068
2093
  */
2069
2094
  class PlaySocketServer {
2070
2095
  #server;
@@ -2086,7 +2111,7 @@ class PlaySocketServer {
2086
2111
 
2087
2112
  /**
2088
2113
  * Create a new PlaySocketServer instance
2089
- * @param {Object} options - Server configuration options
2114
+ * @param {object} options - Server configuration options
2090
2115
  * @param {HttpServer} [options.server] - Existing http server
2091
2116
  * @param {number} [options.port=3000] - Port to listen on (if no server provided)
2092
2117
  * @param {string} [options.path='/'] - WebSocket endpoint path
@@ -2105,7 +2130,7 @@ class PlaySocketServer {
2105
2130
  this.#server = server; // Use provided HTTP server
2106
2131
  } else {
2107
2132
  this.#ownsServer = true;
2108
- this.#server = http.createServer(); // Create HTTP server and start it
2133
+ this.#server = createServer(); // Create HTTP server and start it
2109
2134
  this.#server.listen(port, () => {
2110
2135
  console.log(`PlaySocket server running on port ${port}.`);
2111
2136
  });
@@ -2146,6 +2171,8 @@ class PlaySocketServer {
2146
2171
 
2147
2172
  /**
2148
2173
  * Handle WebSocket message
2174
+ * @param {WebSocket} ws - WebSocket client
2175
+ * @param {Buffer} message - Message encoded with messagepack
2149
2176
  * @private
2150
2177
  */
2151
2178
  async #handleMessage(ws, message) {
@@ -2158,12 +2185,13 @@ class PlaySocketServer {
2158
2185
  if (!ws.isTerminating) {
2159
2186
  ws.isTerminating = true; // Prevent multiple terminate calls (it is async)
2160
2187
  ws.terminate();
2161
- return console.error(`Connection ${ws.connectionId} terminated due to rate limit violations.`);
2188
+ console.error(`Connection ${ws.connectionId} terminated due to rate limit violations.`);
2189
+ return;
2162
2190
  }
2163
2191
  }
2164
2192
 
2165
2193
  switch (data.type) {
2166
- case 'register':
2194
+ case 'register': {
2167
2195
  // Register client ID if provided & check for a duplicate
2168
2196
  if (data.id && this.#clients.get(data.id)) {
2169
2197
  ws.send(encode({ type: 'registration_failed', reason: 'ID is taken.' }), { binary: true });
@@ -2202,8 +2230,9 @@ class PlaySocketServer {
2202
2230
  ws.send(encode({ type: 'registered', id: data.id, sessionToken }), { binary: true });
2203
2231
  this.#triggerEvent("clientRegistered", data.id, data.customData);
2204
2232
  break;
2233
+ }
2205
2234
 
2206
- case 'reconnect':
2235
+ case 'reconnect': {
2207
2236
  // If user is pending disconnect, respond (otherwise it's too late)
2208
2237
  const pd = this.#pendingDisconnects.get(data.id);
2209
2238
  if (pd && data.sessionToken) {
@@ -2240,8 +2269,9 @@ class PlaySocketServer {
2240
2269
  ws.send(encode({ type: 'reconnection_failed', reason: "Client unknown to server." }), { binary: true });
2241
2270
  }
2242
2271
  break;
2272
+ }
2243
2273
 
2244
- case 'create_room':
2274
+ case 'create_room': {
2245
2275
  if (!ws.clientId) return;
2246
2276
 
2247
2277
  let newRoomId;
@@ -2309,8 +2339,9 @@ class PlaySocketServer {
2309
2339
  this.#triggerEvent("roomCreated", newRoomId);
2310
2340
  if (this.#debug) console.log(`Room ${newRoomId} created with initial storage:`, data.initialStorage);
2311
2341
  break;
2342
+ }
2312
2343
 
2313
- case 'join_room':
2344
+ case 'join_room': {
2314
2345
  if (!data.roomId || !ws.clientId) return;
2315
2346
  const roomId = data.roomId;
2316
2347
  const room = this.#rooms[roomId];
@@ -2354,29 +2385,30 @@ class PlaySocketServer {
2354
2385
  });
2355
2386
  this.#triggerEvent("clientJoinedRoom", ws.clientId, roomId);
2356
2387
  break;
2388
+ }
2357
2389
 
2358
- case 'update_property':
2359
- const updateRoomId = this.#clientRooms.get(ws.clientId);
2360
- const updateRoom = updateRoomId ? this.#rooms[updateRoomId] : null;
2390
+ case 'update_property': {
2391
+ const roomId = this.#clientRooms.get(ws.clientId);
2392
+ const room = roomId ? this.#rooms[roomId] : null;
2361
2393
 
2362
- if (updateRoom && data.update) {
2394
+ if (room && data.update) {
2363
2395
  // Check if update is allowed via event callback (provide clone to ensure update integrity)
2364
- const updateAllowed = await this.#triggerEvent("storageUpdateRequested", { roomId: updateRoomId, clientId: ws.clientId, update: structuredClone(data.update) });
2396
+ const updateAllowed = await this.#triggerEvent("storageUpdateRequested", { roomId, clientId: ws.clientId, update: structuredClone(data.update), storage: this.getRoomStorage(roomId) });
2365
2397
  if (updateAllowed === false) {
2366
2398
  ws.send(encode({
2367
2399
  type: 'property_update_rejected',
2368
- state: updateRoom.crdtManager.getState
2400
+ state: room.crdtManager.getState
2369
2401
  }), { binary: true });
2370
2402
  return;
2371
2403
  }
2372
2404
 
2373
- updateRoom.crdtManager.importPropertyUpdate(data.update); // Import update into server state
2405
+ room.crdtManager.importPropertyUpdate(data.update); // Import update into server state
2374
2406
 
2375
2407
  // Increment version for this room
2376
- const currentVersion = this.#roomVersions.get(updateRoomId) + 1;
2377
- this.#roomVersions.set(updateRoomId, currentVersion);
2408
+ const currentVersion = this.#roomVersions.get(roomId) + 1;
2409
+ this.#roomVersions.set(roomId, currentVersion);
2378
2410
 
2379
- updateRoom.participants?.forEach(p => {
2411
+ room.participants?.forEach(p => {
2380
2412
  const client = this.#clients.get(p);
2381
2413
  if (client) {
2382
2414
  client.send(encode({
@@ -2387,16 +2419,18 @@ class PlaySocketServer {
2387
2419
  }
2388
2420
  });
2389
2421
 
2390
- this.#triggerEvent("storageUpdated", { roomId: updateRoomId, clientId: ws.clientId, update: structuredClone(data.update), storage: this.getRoomStorage(updateRoomId) });
2422
+ this.#triggerEvent("storageUpdated", { roomId, clientId: ws.clientId, update: structuredClone(data.update), storage: this.getRoomStorage(roomId) });
2391
2423
  if (this.#debug) console.log("Property update received and imported:", data.update);
2392
2424
  }
2393
2425
  break;
2426
+ }
2394
2427
 
2395
- case 'request':
2428
+ case 'request': {
2396
2429
  if (!ws.clientId) return;
2397
- const requestorRoomId = this.#clientRooms.get(ws.clientId) || null;
2398
- this.#triggerEvent("requestReceived", { roomId: requestorRoomId, clientId: ws.clientId, name: data.request.name, data: data.request.data });
2430
+ const roomId = this.#clientRooms.get(ws.clientId) || null;
2431
+ this.#triggerEvent("requestReceived", { roomId, clientId: ws.clientId, name: data.request.name, data: data.request.data });
2399
2432
  break;
2433
+ }
2400
2434
 
2401
2435
  case 'disconnect':
2402
2436
  // Client signals to server that it will will willfully disconnect soon
@@ -2410,6 +2444,7 @@ class PlaySocketServer {
2410
2444
 
2411
2445
  /**
2412
2446
  * Generate a readable, 6 digit ID
2447
+ * @returns {string} - Id
2413
2448
  */
2414
2449
  #generateId() {
2415
2450
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789';
@@ -2419,8 +2454,8 @@ class PlaySocketServer {
2419
2454
  /**
2420
2455
  * If the client that disconnected (and is now in the reconnection-phase) was the room host,
2421
2456
  * pick a new room host immediately to avoid host-less phase (in case important logic is attached to the host)
2422
- * @param {string} roomId
2423
- * @param {string} clientId
2457
+ * @param {string} roomId - ID of the room that the disconnected client was in
2458
+ * @param {string} clientId - ID of the disconnected client
2424
2459
  */
2425
2460
  #changeHostIfDisconnected(roomId, clientId) {
2426
2461
  const room = this.#rooms[roomId];
@@ -2451,9 +2486,12 @@ class PlaySocketServer {
2451
2486
 
2452
2487
  /**
2453
2488
  * Check rate limit using token bucket algorithm
2489
+ * @param {string} connectionId - Random connection UUID which is different from the client ID
2490
+ * @param {string} actionType - A string describing the action
2491
+ * @returns {boolean} - Whether or not the action can be allowed, true means allowed, false means limited
2454
2492
  * @private
2455
2493
  */
2456
- #checkRateLimit(connectionId, operationType) {
2494
+ #checkRateLimit(connectionId, actionType) {
2457
2495
  const now = Date.now();
2458
2496
 
2459
2497
  if (!this.#rateLimits.has(connectionId)) {
@@ -2469,7 +2507,7 @@ class PlaySocketServer {
2469
2507
  limit.lastReset = now;
2470
2508
  }
2471
2509
 
2472
- const pointCost = operationType == "create_room" ? 5 : 1;
2510
+ const pointCost = actionType == "create_room" ? 5 : 1;
2473
2511
  if (limit.points < pointCost) return false;
2474
2512
 
2475
2513
  limit.points -= pointCost;
@@ -2478,6 +2516,7 @@ class PlaySocketServer {
2478
2516
 
2479
2517
  /**
2480
2518
  * Handle client disconnection
2519
+ * @param {WebSocket} ws - WebSocket client
2481
2520
  * @private
2482
2521
  */
2483
2522
  #handleDisconnection(ws) {
@@ -2506,7 +2545,7 @@ class PlaySocketServer {
2506
2545
  /**
2507
2546
  * Disconnect a client
2508
2547
  * @private
2509
- * @param {Object} ws
2548
+ * @param {WebSocket} ws - WebSocket client
2510
2549
  */
2511
2550
  #disconnectClient(ws) {
2512
2551
  this.#pendingDisconnects.delete(ws.clientId);
@@ -2547,8 +2586,10 @@ class PlaySocketServer {
2547
2586
  */
2548
2587
  onEvent(event, callback) {
2549
2588
  const validEvents = ["clientRegistered", "clientRegistrationRequested", "clientDisconnected", "clientJoinedRoom", "clientJoinRequested", "roomCreated", "roomCreationRequested", "requestReceived", "storageUpdated", "storageUpdateRequested", "roomDestroyed"];
2550
- if (!validEvents.includes(event)) return console.warn(`Invalid PlaySocket event type "${event}"`);
2551
- if (!this.#callbacks.has(event)) this.#callbacks.set(event, []);
2589
+ if (!validEvents.includes(event)) {
2590
+ console.warn(`Invalid PlaySocket event type "${event}"`);
2591
+ return;
2592
+ } if (!this.#callbacks.has(event)) this.#callbacks.set(event, []);
2552
2593
  this.#callbacks.get(event).push(callback);
2553
2594
  }
2554
2595
 
@@ -2568,6 +2609,9 @@ class PlaySocketServer {
2568
2609
 
2569
2610
  /**
2570
2611
  * Trigger an event to registered callbacks
2612
+ * @param {string} event - Event name
2613
+ * @param {...*} args - Arguments
2614
+ * @returns {any} - Undefined if no callbacks, true if all callbacks ran, what the first callback with a return statement returns if present
2571
2615
  * @private
2572
2616
  */
2573
2617
  async #triggerEvent(event, ...args) {
@@ -2587,7 +2631,8 @@ class PlaySocketServer {
2587
2631
 
2588
2632
  /**
2589
2633
  * Get snapshot of a room's storage
2590
- * @returns {Object} - Storage object
2634
+ * @param {string} roomId - ID of the room to get the storage from
2635
+ * @returns {object|undefined} - Storage object or undefined if the room doesn't exist
2591
2636
  */
2592
2637
  getRoomStorage(roomId) {
2593
2638
  const room = this.#rooms[roomId];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playsocketjs",
3
- "version": "2.5.2",
3
+ "version": "2.5.4",
4
4
  "description": "WebSocket wrapper for creating simple multiplayer systems with ease.",
5
5
  "type": "module",
6
6
  "module": "dist/playsocket-client.js",
@@ -9,18 +9,17 @@
9
9
  "import": "./dist/playsocket-client.js"
10
10
  },
11
11
  "./server": {
12
- "import": "./dist/playsocket-server.mjs",
13
- "require": "./dist/playsocket-server.cjs"
12
+ "import": "./dist/playsocket-server.js"
14
13
  }
15
14
  },
16
15
  "files": [
17
16
  "dist"
18
17
  ],
19
18
  "scripts": {
20
- "start": "node example/example-server.cjs",
21
- "start-module": "node example/example-server.mjs",
19
+ "start": "node example/example-server.js",
22
20
  "test": "echo \"Error: no test specified\" && exit 1",
23
- "build": "rollup -c rollup.config.mjs"
21
+ "build": "rollup -c rollup.config.js",
22
+ "lint": "eslint ./src"
24
23
  },
25
24
  "repository": {
26
25
  "type": "git",
@@ -28,11 +27,10 @@
28
27
  },
29
28
  "keywords": [
30
29
  "WebSocket",
31
- "data channel",
30
+ "Data channel",
32
31
  "Socket",
33
- "PlayPeerJS",
34
- "multiplayer",
35
- "game"
32
+ "Multiplayer",
33
+ "Game"
36
34
  ],
37
35
  "author": "PaulPlay",
38
36
  "license": "MIT",
@@ -41,9 +39,11 @@
41
39
  },
42
40
  "homepage": "https://github.com/therealPaulPlay/PlaySocketJS#readme",
43
41
  "devDependencies": {
44
- "@rollup/plugin-commonjs": "^28.0.2",
45
42
  "@rollup/plugin-node-resolve": "^16.0.0",
46
- "rollup": "^4.31.0"
43
+ "eslint": "^9.35.0",
44
+ "eslint-plugin-jsdoc": "^54.4.0",
45
+ "rollup": "^4.31.0",
46
+ "globals": "^16.3.0"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "ws": "^8.18.1"