playsocketjs 2.2.0 → 2.2.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/README.md +3 -3
- package/dist/playsocket-client.js +10 -4
- package/dist/playsocket-server.js +20 -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
|
|
|
@@ -221,7 +221,7 @@ Creates a new PlaySocket Server instance with configuration options.
|
|
|
221
221
|
- `clientJoinedRoom`: Client joined a room – note that clients can only leave by disconnecting (returns the client's ID `string`, room ID `string`)
|
|
222
222
|
- `roomCreated`: Client created a room (returns room ID `string`)
|
|
223
223
|
- `roomDestroyed`: Room was destroyed, this happens when all participants leave (returns room ID `string`)
|
|
224
|
-
- `roomCreationRequested`: Room creation requested by client (returns `object` containing the client's ID `string`, room ID `string` and the initialStorage `object`) – if you return an `object` in the callback, it will take that as the initial storage instead
|
|
224
|
+
- `roomCreationRequested`: Room creation requested by client (returns `object` containing the client's ID `string`, room ID `string` and the initialStorage `object`) – if you return an `object` in the callback, it will take that as the initial storage instead. If you return `false`, the creation will be denied
|
|
225
225
|
- `storageUpdateRequested`: Room storage property update requested by client (returns `object` containing the client's ID `string`, room ID `string` and the update `object`) – if you return `false` in the callback, the update will be blocked
|
|
226
226
|
- `requestReceived`: Request from client was received by the server (returns `object` containing client's ID `string`, if in room – room ID `string`, request name `string` and optional passed data of type `any`)
|
|
227
227
|
|
|
@@ -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)]));
|
|
@@ -2393,6 +2399,13 @@ function requireServer () {
|
|
|
2393
2399
|
// Event callback with potential initial storage modifications
|
|
2394
2400
|
const reviewedStorage = await this.#triggerEvent("roomCreationRequested", { roomId: newRoomId, clientId: ws.clientId, initialStorage: structuredClone({ ...data.initialStorage }) });
|
|
2395
2401
|
if (typeof reviewedStorage === 'object') data.initialStorage = reviewedStorage;
|
|
2402
|
+
if (reviewedStorage === false) {
|
|
2403
|
+
ws.send(encode({
|
|
2404
|
+
type: 'room_creation_failed',
|
|
2405
|
+
reason: 'Room creation denied.'
|
|
2406
|
+
}), { binary: true });
|
|
2407
|
+
return;
|
|
2408
|
+
}
|
|
2396
2409
|
|
|
2397
2410
|
// Check if client/creator is still connected
|
|
2398
2411
|
if (!this.#clients.has(ws.clientId)) {
|
|
@@ -2412,7 +2425,7 @@ function requireServer () {
|
|
|
2412
2425
|
this.#rooms[newRoomId] = {
|
|
2413
2426
|
participants: [ws.clientId],
|
|
2414
2427
|
host: ws.clientId,
|
|
2415
|
-
maxSize: data.size ||
|
|
2428
|
+
maxSize: Math.min(Number(data.size), 100) || 100, // Max. limit is 100 clients / room
|
|
2416
2429
|
crdtManager: roomCrdtManager
|
|
2417
2430
|
};
|
|
2418
2431
|
this.#clientRooms.set(ws.clientId, newRoomId); // Add client to the room
|
|
@@ -2428,12 +2441,12 @@ function requireServer () {
|
|
|
2428
2441
|
const room = this.#rooms[roomId];
|
|
2429
2442
|
|
|
2430
2443
|
if (!room ||
|
|
2431
|
-
(room.
|
|
2444
|
+
(room.participants.length >= room.maxSize) ||
|
|
2432
2445
|
this.#clientRooms.get(ws.clientId)) {
|
|
2433
2446
|
ws.send(encode({
|
|
2434
2447
|
type: 'join_rejected',
|
|
2435
2448
|
reason: !room ? 'Room not found' :
|
|
2436
|
-
room.
|
|
2449
|
+
room.participants.length >= room.maxSize ? 'Room full' :
|
|
2437
2450
|
'Already in a room'
|
|
2438
2451
|
}), { binary: true });
|
|
2439
2452
|
return;
|