playsocketjs 2.2.0 → 2.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 +2 -2
- package/dist/playsocket-client.js +10 -4
- package/dist/playsocket-server.js +13 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -96,10 +96,10 @@ new PlaySocket(id?: string, options: PlaySocketOptions)
|
|
|
96
96
|
### Methods
|
|
97
97
|
|
|
98
98
|
- `init()`: Initialize the WebSocket connection – Returns Promise (async) which resolves with the client's ID
|
|
99
|
-
- `createRoom(initialStorage?: object, maxSize?: number)`: Create a new room and become host – Returns Promise (async) which resolves with the room ID (matches the creator's ID)
|
|
99
|
+
- `createRoom(initialStorage?: object, maxSize?: number)`: Create a new room and become host – Returns Promise (async) which resolves with the room ID (matches the creator's ID). The absolute client maximum is 100
|
|
100
100
|
- `joinRoom(hostId: string)`: Join an existing room – Returns Promise (async)
|
|
101
101
|
- `destroy()`: Use this to leave a room and close the connection
|
|
102
|
-
- `updateStorage(key: string, type: 'set' | 'array-add' | 'array-add-unique' | 'array-remove-matching' | 'array-update-matching', value: any, updateValue?: any)`: Update the shared storage. Safely update arrays in storage by adding, removing, or updating items. UpdateValue is only required for the 'array-update-matching' operation type
|
|
102
|
+
- `updateStorage(key: string, type: 'set' | 'array-add' | 'array-add-unique' | 'array-remove-matching' | 'array-update-matching', value: any, updateValue?: any)`: Update the shared storage (max. 100 keys). Safely update arrays in storage by adding, removing, or updating items. UpdateValue is only required for the 'array-update-matching' operation type
|
|
103
103
|
- `sendRequest(name: string, data?: any)`: Send requests to the server with optional custom data (handle these in the `requestReceived` server event)
|
|
104
104
|
- `onEvent(event: string, callback: Function)`: Register an event callback
|
|
105
105
|
|
|
@@ -1758,9 +1758,11 @@ function requireCrdtManager () {
|
|
|
1758
1758
|
const operation = this.#sanitizeValue(rawOperation);
|
|
1759
1759
|
if (this.#debug) console.log(CONSOLE_PREFIX + "Importing update:", data); // Debug
|
|
1760
1760
|
|
|
1761
|
+
// Check key limit to afeguard against too many keys
|
|
1762
|
+
if (this.#keyOperations.size >= 100 && !this.#keyOperations.has(key)) throw new Error('Key limit exceeded!');
|
|
1763
|
+
|
|
1761
1764
|
// Get COPY of current ops (or empty array if none yet)
|
|
1762
1765
|
const currentOps = [...(this.#keyOperations.get(key) || [])];
|
|
1763
|
-
|
|
1764
1766
|
// Merge vector clocks (always take max value)
|
|
1765
1767
|
for (const [id, counter] of vectorClock) {
|
|
1766
1768
|
if (!this.#vectorClock.has(id) || this.#vectorClock.get(id) < counter) {
|
|
@@ -1768,7 +1770,7 @@ function requireCrdtManager () {
|
|
|
1768
1770
|
}
|
|
1769
1771
|
}
|
|
1770
1772
|
|
|
1771
|
-
//
|
|
1773
|
+
// Safeguard against massive vector clocks
|
|
1772
1774
|
if (this.#vectorClock.size > 1000) this.#vectorClock = new Map([...this.#vectorClock].slice(-100));
|
|
1773
1775
|
|
|
1774
1776
|
// Add new operation if it's not already added
|
|
@@ -2019,11 +2021,15 @@ function requireCrdtManager () {
|
|
|
2019
2021
|
}
|
|
2020
2022
|
|
|
2021
2023
|
/**
|
|
2022
|
-
* Remove HTML to prevent XSS
|
|
2023
|
-
* @param {Object} obj
|
|
2024
|
+
* Remove HTML to prevent XSS and enforce size limits
|
|
2025
|
+
* @param {Object} obj
|
|
2024
2026
|
* @returns {Object} - Sanitized object
|
|
2025
2027
|
*/
|
|
2026
2028
|
#sanitizeValue(obj) {
|
|
2029
|
+
// Check total serialized size
|
|
2030
|
+
const jsonString = JSON.stringify(obj);
|
|
2031
|
+
if (jsonString?.length > 50000) throw new Error('Value too large!'); // 50KB limit
|
|
2032
|
+
|
|
2027
2033
|
if (typeof obj === 'string') return (obj.includes('<') || obj.includes('>')) ? obj.replace(/[<>]/g, '') : obj;
|
|
2028
2034
|
if (Array.isArray(obj)) return obj.map(item => this.#sanitizeValue(item));
|
|
2029
2035
|
if (obj && typeof obj === 'object') return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, this.#sanitizeValue(v)]));
|
|
@@ -1884,9 +1884,11 @@ function requireCrdtManager () {
|
|
|
1884
1884
|
const operation = this.#sanitizeValue(rawOperation);
|
|
1885
1885
|
if (this.#debug) console.log(CONSOLE_PREFIX + "Importing update:", data); // Debug
|
|
1886
1886
|
|
|
1887
|
+
// Check key limit to afeguard against too many keys
|
|
1888
|
+
if (this.#keyOperations.size >= 100 && !this.#keyOperations.has(key)) throw new Error('Key limit exceeded!');
|
|
1889
|
+
|
|
1887
1890
|
// Get COPY of current ops (or empty array if none yet)
|
|
1888
1891
|
const currentOps = [...(this.#keyOperations.get(key) || [])];
|
|
1889
|
-
|
|
1890
1892
|
// Merge vector clocks (always take max value)
|
|
1891
1893
|
for (const [id, counter] of vectorClock) {
|
|
1892
1894
|
if (!this.#vectorClock.has(id) || this.#vectorClock.get(id) < counter) {
|
|
@@ -1894,7 +1896,7 @@ function requireCrdtManager () {
|
|
|
1894
1896
|
}
|
|
1895
1897
|
}
|
|
1896
1898
|
|
|
1897
|
-
//
|
|
1899
|
+
// Safeguard against massive vector clocks
|
|
1898
1900
|
if (this.#vectorClock.size > 1000) this.#vectorClock = new Map([...this.#vectorClock].slice(-100));
|
|
1899
1901
|
|
|
1900
1902
|
// Add new operation if it's not already added
|
|
@@ -2145,11 +2147,15 @@ function requireCrdtManager () {
|
|
|
2145
2147
|
}
|
|
2146
2148
|
|
|
2147
2149
|
/**
|
|
2148
|
-
* Remove HTML to prevent XSS
|
|
2149
|
-
* @param {Object} obj
|
|
2150
|
+
* Remove HTML to prevent XSS and enforce size limits
|
|
2151
|
+
* @param {Object} obj
|
|
2150
2152
|
* @returns {Object} - Sanitized object
|
|
2151
2153
|
*/
|
|
2152
2154
|
#sanitizeValue(obj) {
|
|
2155
|
+
// Check total serialized size
|
|
2156
|
+
const jsonString = JSON.stringify(obj);
|
|
2157
|
+
if (jsonString?.length > 50000) throw new Error('Value too large!'); // 50KB limit
|
|
2158
|
+
|
|
2153
2159
|
if (typeof obj === 'string') return (obj.includes('<') || obj.includes('>')) ? obj.replace(/[<>]/g, '') : obj;
|
|
2154
2160
|
if (Array.isArray(obj)) return obj.map(item => this.#sanitizeValue(item));
|
|
2155
2161
|
if (obj && typeof obj === 'object') return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, this.#sanitizeValue(v)]));
|
|
@@ -2412,7 +2418,7 @@ function requireServer () {
|
|
|
2412
2418
|
this.#rooms[newRoomId] = {
|
|
2413
2419
|
participants: [ws.clientId],
|
|
2414
2420
|
host: ws.clientId,
|
|
2415
|
-
maxSize: data.size ||
|
|
2421
|
+
maxSize: Math.min(Number(data.size), 100) || 100, // Max. limit is 100 clients / room
|
|
2416
2422
|
crdtManager: roomCrdtManager
|
|
2417
2423
|
};
|
|
2418
2424
|
this.#clientRooms.set(ws.clientId, newRoomId); // Add client to the room
|
|
@@ -2428,12 +2434,12 @@ function requireServer () {
|
|
|
2428
2434
|
const room = this.#rooms[roomId];
|
|
2429
2435
|
|
|
2430
2436
|
if (!room ||
|
|
2431
|
-
(room.
|
|
2437
|
+
(room.participants.length >= room.maxSize) ||
|
|
2432
2438
|
this.#clientRooms.get(ws.clientId)) {
|
|
2433
2439
|
ws.send(encode({
|
|
2434
2440
|
type: 'join_rejected',
|
|
2435
2441
|
reason: !room ? 'Room not found' :
|
|
2436
|
-
room.
|
|
2442
|
+
room.participants.length >= room.maxSize ? 'Room full' :
|
|
2437
2443
|
'Already in a room'
|
|
2438
2444
|
}), { binary: true });
|
|
2439
2445
|
return;
|