playsocketjs 1.2.2 → 1.3.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 CHANGED
@@ -199,6 +199,7 @@ Creates a new PlaySocket Server instance with configuration options.
199
199
  ### Methods
200
200
 
201
201
  - `stop`: Closes all active client connections, the websocket server and the underlying http server if it's standalone
202
+ - `kick(clientId: string, reason?: string)`: Kick a client by their clientID – this will close their connection and set an error message
202
203
  - `onEvent(event: string, callback: Function)`: Register a server-side event callback
203
204
 
204
205
  ##### Event types
@@ -1754,7 +1754,8 @@ function requireCrdtManager () {
1754
1754
  */
1755
1755
  importPropertyUpdate(data) {
1756
1756
  try {
1757
- const { key, operation, vectorClock } = data;
1757
+ const { key, operation: rawOperation, vectorClock } = data;
1758
+ const operation = this.#sanitizeValue(rawOperation);
1758
1759
  if (this.#debug) console.log(CONSOLE_PREFIX + "Importing update:", data); // Debug
1759
1760
 
1760
1761
  // Get COPY of current ops (or empty array if none yet)
@@ -1797,6 +1798,10 @@ function requireCrdtManager () {
1797
1798
  */
1798
1799
  updateProperty(key, operation, value, updateValue) {
1799
1800
  try {
1801
+ // Sanitize inputs
1802
+ value = this.#sanitizeValue(value);
1803
+ updateValue = this.#sanitizeValue(updateValue);
1804
+
1800
1805
  // Debug log
1801
1806
  if (this.#debug) console.log(CONSOLE_PREFIX + `Updating property with key ${key}, operation ${operation}, value ${value} and updateValue ${updateValue}.`);
1802
1807
 
@@ -1923,7 +1928,11 @@ function requireCrdtManager () {
1923
1928
  }
1924
1929
  }
1925
1930
 
1926
- // Sort by vector clock (causal order)
1931
+ /**
1932
+ * Sort by vector clock (causal order)
1933
+ * @param {Array} operations
1934
+ * @returns {Array} - Sorted operations
1935
+ */
1927
1936
  #sortByVectorClock(operations) {
1928
1937
  return [...operations].sort((a, b) => {
1929
1938
  const clockA = new Map(a.vectorClock || []);
@@ -1955,7 +1964,14 @@ function requireCrdtManager () {
1955
1964
  });
1956
1965
  }
1957
1966
 
1958
- // Handle an operation
1967
+ /**
1968
+ * Handle an operation
1969
+ * @param {*} curValue
1970
+ * @param {string} operation
1971
+ * @param {*} value
1972
+ * @param {*} [updateValue]
1973
+ * @returns {*} - Value after the operation
1974
+ */
1959
1975
  #handleOperation(curValue, operation, value, updateValue) {
1960
1976
  try {
1961
1977
  // Clone to avoid reference issues
@@ -2002,6 +2018,18 @@ function requireCrdtManager () {
2002
2018
  }
2003
2019
  }
2004
2020
 
2021
+ /**
2022
+ * Remove HTML to prevent XSS
2023
+ * @param {Object} obj
2024
+ * @returns {Object} - Sanitized object
2025
+ */
2026
+ #sanitizeValue(obj) {
2027
+ if (typeof obj === 'string') return (obj.includes('<') || obj.includes('>')) ? obj.replace(/[<>]/g, '') : obj;
2028
+ if (Array.isArray(obj)) return obj.map(item => this.#sanitizeValue(item));
2029
+ if (obj && typeof obj === 'object') return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, this.#sanitizeValue(v)]));
2030
+ return obj;
2031
+ }
2032
+
2005
2033
  // Get property store
2006
2034
  get getPropertyStore() {
2007
2035
  try {
@@ -2295,6 +2323,11 @@ class PlaySocket {
2295
2323
  this.destroy();
2296
2324
  break;
2297
2325
 
2326
+ case 'kicked':
2327
+ this.#triggerEvent("error", `Kicked out of room: ${message.reason || "No reason provided."}`);
2328
+ this.destroy();
2329
+ break;
2330
+
2298
2331
  case 'host_migrated':
2299
2332
  this.#setHost(message.newHost);
2300
2333
  break;
@@ -1880,7 +1880,8 @@ function requireCrdtManager () {
1880
1880
  */
1881
1881
  importPropertyUpdate(data) {
1882
1882
  try {
1883
- const { key, operation, vectorClock } = data;
1883
+ const { key, operation: rawOperation, vectorClock } = data;
1884
+ const operation = this.#sanitizeValue(rawOperation);
1884
1885
  if (this.#debug) console.log(CONSOLE_PREFIX + "Importing update:", data); // Debug
1885
1886
 
1886
1887
  // Get COPY of current ops (or empty array if none yet)
@@ -1923,6 +1924,10 @@ function requireCrdtManager () {
1923
1924
  */
1924
1925
  updateProperty(key, operation, value, updateValue) {
1925
1926
  try {
1927
+ // Sanitize inputs
1928
+ value = this.#sanitizeValue(value);
1929
+ updateValue = this.#sanitizeValue(updateValue);
1930
+
1926
1931
  // Debug log
1927
1932
  if (this.#debug) console.log(CONSOLE_PREFIX + `Updating property with key ${key}, operation ${operation}, value ${value} and updateValue ${updateValue}.`);
1928
1933
 
@@ -2049,7 +2054,11 @@ function requireCrdtManager () {
2049
2054
  }
2050
2055
  }
2051
2056
 
2052
- // Sort by vector clock (causal order)
2057
+ /**
2058
+ * Sort by vector clock (causal order)
2059
+ * @param {Array} operations
2060
+ * @returns {Array} - Sorted operations
2061
+ */
2053
2062
  #sortByVectorClock(operations) {
2054
2063
  return [...operations].sort((a, b) => {
2055
2064
  const clockA = new Map(a.vectorClock || []);
@@ -2081,7 +2090,14 @@ function requireCrdtManager () {
2081
2090
  });
2082
2091
  }
2083
2092
 
2084
- // Handle an operation
2093
+ /**
2094
+ * Handle an operation
2095
+ * @param {*} curValue
2096
+ * @param {string} operation
2097
+ * @param {*} value
2098
+ * @param {*} [updateValue]
2099
+ * @returns {*} - Value after the operation
2100
+ */
2085
2101
  #handleOperation(curValue, operation, value, updateValue) {
2086
2102
  try {
2087
2103
  // Clone to avoid reference issues
@@ -2128,6 +2144,18 @@ function requireCrdtManager () {
2128
2144
  }
2129
2145
  }
2130
2146
 
2147
+ /**
2148
+ * Remove HTML to prevent XSS
2149
+ * @param {Object} obj
2150
+ * @returns {Object} - Sanitized object
2151
+ */
2152
+ #sanitizeValue(obj) {
2153
+ if (typeof obj === 'string') return (obj.includes('<') || obj.includes('>')) ? obj.replace(/[<>]/g, '') : obj;
2154
+ if (Array.isArray(obj)) return obj.map(item => this.#sanitizeValue(item));
2155
+ if (obj && typeof obj === 'object') return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, this.#sanitizeValue(v)]));
2156
+ return obj;
2157
+ }
2158
+
2131
2159
  // Get property store
2132
2160
  get getPropertyStore() {
2133
2161
  try {
@@ -2279,7 +2307,7 @@ function requireServer () {
2279
2307
 
2280
2308
  // Generate client ID if none provided
2281
2309
  if (!data.id) {
2282
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
2310
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789';
2283
2311
  const maxAttempts = 50;
2284
2312
  for (let i = 0; i < maxAttempts; i++) {
2285
2313
  const id = Array.from({ length: 6 }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
@@ -2587,6 +2615,20 @@ function requireServer () {
2587
2615
  this.#callbacks.get(event).push(callback);
2588
2616
  }
2589
2617
 
2618
+ /**
2619
+ * Kick a player from the server
2620
+ * @param {string} clientId - Client ID
2621
+ * @param {string} [reason] - Optional reason
2622
+ */
2623
+ kick(clientId, reason) {
2624
+ const client = this.#clients.get(clientId);
2625
+ if (client) {
2626
+ client.willfulDisconnect = true;
2627
+ client.send(encode({ type: 'kicked', reason }), { binary: true });
2628
+ client.close();
2629
+ }
2630
+ }
2631
+
2590
2632
  /**
2591
2633
  * Trigger an event to registered callbacks
2592
2634
  * @private
@@ -2604,7 +2646,7 @@ function requireServer () {
2604
2646
  stop() {
2605
2647
  clearInterval(this.#heartbeatInterval);
2606
2648
  if (this.#wss) {
2607
- this.#wss.clients.forEach(client => {
2649
+ this.#clients.forEach(client => {
2608
2650
  client.send(encode({ type: 'server_stopped' }), { binary: true });
2609
2651
  client.close();
2610
2652
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playsocketjs",
3
- "version": "1.2.2",
3
+ "version": "1.3.1",
4
4
  "description": "WebSocket wrapper for creating simple multiplayer systems with ease.",
5
5
  "module": "dist/playsocket-client.js",
6
6
  "exports": {