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.
- package/README.md +3 -3
- package/dist/playsocket-client.js +76 -49
- package/dist/{playsocket-server.mjs → playsocket-server.js} +110 -65
- package/package.json +12 -12
- package/dist/playsocket-server.cjs +0 -2669
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { WebSocketServer } from 'ws';
|
|
2
|
-
import
|
|
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 {
|
|
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,
|
|
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 {
|
|
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 {
|
|
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 {
|
|
1890
|
-
* @param {Array} vectorClock
|
|
1891
|
-
* @returns {
|
|
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 {
|
|
2023
|
-
* @returns {
|
|
2029
|
+
* @param {*} value - Value to sanitize
|
|
2030
|
+
* @returns {*} - Sanitized value
|
|
2024
2031
|
*/
|
|
2025
|
-
#sanitizeValue(
|
|
2032
|
+
#sanitizeValue(value) {
|
|
2026
2033
|
// Check total serialized size
|
|
2027
|
-
const jsonString = JSON.stringify(
|
|
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
|
|
2031
|
-
if (Array.isArray(
|
|
2032
|
-
if (
|
|
2033
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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 {
|
|
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 =
|
|
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
|
-
|
|
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
|
|
2360
|
-
const
|
|
2390
|
+
case 'update_property': {
|
|
2391
|
+
const roomId = this.#clientRooms.get(ws.clientId);
|
|
2392
|
+
const room = roomId ? this.#rooms[roomId] : null;
|
|
2361
2393
|
|
|
2362
|
-
if (
|
|
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
|
|
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:
|
|
2400
|
+
state: room.crdtManager.getState
|
|
2369
2401
|
}), { binary: true });
|
|
2370
2402
|
return;
|
|
2371
2403
|
}
|
|
2372
2404
|
|
|
2373
|
-
|
|
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(
|
|
2377
|
-
this.#roomVersions.set(
|
|
2408
|
+
const currentVersion = this.#roomVersions.get(roomId) + 1;
|
|
2409
|
+
this.#roomVersions.set(roomId, currentVersion);
|
|
2378
2410
|
|
|
2379
|
-
|
|
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
|
|
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
|
|
2398
|
-
this.#triggerEvent("requestReceived", { roomId
|
|
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,
|
|
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 =
|
|
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 {
|
|
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))
|
|
2551
|
-
|
|
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
|
-
* @
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
"
|
|
30
|
+
"Data channel",
|
|
32
31
|
"Socket",
|
|
33
|
-
"
|
|
34
|
-
"
|
|
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
|
-
"
|
|
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"
|