playsocketjs 2.1.0 → 2.2.0
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 +22 -14
- package/dist/playsocket-client.js +61 -59
- package/dist/playsocket-server.js +58 -48
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,12 +72,17 @@ socket.updateStorage('players', 'array-add-unique', { username: 'Player4', level
|
|
|
72
72
|
socket.updateStorage('latestPlayer', 'set', 'Player4'); // Regular synced storage update
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
+
Sending traditional requests to the server:
|
|
76
|
+
```javascript
|
|
77
|
+
socket.sendRequest('chosen-request-name', { fact: "You can build server-authoritative logic using this!" })
|
|
78
|
+
```
|
|
79
|
+
|
|
75
80
|
## API Reference
|
|
76
81
|
|
|
77
82
|
### Constructor
|
|
78
83
|
|
|
79
84
|
Creates a new PlaySocket instance with a specified ID and configuration options.
|
|
80
|
-
The
|
|
85
|
+
The ID can be set to `null` to let the server pick a unique one.
|
|
81
86
|
|
|
82
87
|
```javascript
|
|
83
88
|
new PlaySocket(id?: string, options: PlaySocketOptions)
|
|
@@ -90,11 +95,12 @@ new PlaySocket(id?: string, options: PlaySocketOptions)
|
|
|
90
95
|
|
|
91
96
|
### Methods
|
|
92
97
|
|
|
93
|
-
- `init()`: Initialize the WebSocket connection – Returns Promise (async) which resolves with the client's
|
|
94
|
-
- `createRoom(initialStorage?: object, maxSize?: number)`: Create a new room and become host – Returns Promise (async) which resolves with the room
|
|
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)
|
|
95
100
|
- `joinRoom(hostId: string)`: Join an existing room – Returns Promise (async)
|
|
96
101
|
- `destroy()`: Use this to leave a room and close the connection
|
|
97
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
|
|
103
|
+
- `sendRequest(name: string, data?: any)`: Send requests to the server with optional custom data (handle these in the `requestReceived` server event)
|
|
98
104
|
- `onEvent(event: string, callback: Function)`: Register an event callback
|
|
99
105
|
|
|
100
106
|
#### Event types
|
|
@@ -103,15 +109,15 @@ new PlaySocket(id?: string, options: PlaySocketOptions)
|
|
|
103
109
|
- `error`: Error events (returns error `string`)
|
|
104
110
|
- `instanceDestroyed`: Destruction event - triggered by manual .destroy() method invocation or by fatal errors and disconnects
|
|
105
111
|
- `storageUpdated`: Storage state changes (returns storage `object`)
|
|
106
|
-
- `hostMigrated`: Host changes (returns the new host's
|
|
107
|
-
- `clientConnected`: New client connected to the room (returns client
|
|
108
|
-
- `clientDisconnected`: Client disconnected from the room (returns client
|
|
112
|
+
- `hostMigrated`: Host changes (returns the new host's ID `string`)
|
|
113
|
+
- `clientConnected`: New client connected to the room (returns client's ID `string`)
|
|
114
|
+
- `clientDisconnected`: Client disconnected from the room (returns client's ID `string`, room ID (if available) `string`)
|
|
109
115
|
|
|
110
116
|
### Properties (Read-only)
|
|
111
117
|
|
|
112
118
|
- `id`: Client's unique identifier on the WebSocket server
|
|
113
119
|
- `isHost`: If this user is currently assigned the host role
|
|
114
|
-
- `connectionCount`: Number of active client connections in room (without
|
|
120
|
+
- `connectionCount`: Number of active client connections in room (without yourself)
|
|
115
121
|
- `getStorage`: Retrieve storage object
|
|
116
122
|
|
|
117
123
|
|
|
@@ -209,13 +215,15 @@ Creates a new PlaySocket Server instance with configuration options.
|
|
|
209
215
|
|
|
210
216
|
#### Event types
|
|
211
217
|
|
|
212
|
-
- `clientRegistered`: Client registered with the server (returns the client's
|
|
213
|
-
- `
|
|
214
|
-
- `
|
|
215
|
-
- `
|
|
216
|
-
- `
|
|
217
|
-
- `
|
|
218
|
-
- `
|
|
218
|
+
- `clientRegistered`: Client registered with the server (returns the client's ID `string`, customData `object`)
|
|
219
|
+
- `clientRegistrationRequested`: Client requests to register (returns the client's ID `string`, customData `object`) – return `false` or a rejection reason `string` to block the registration
|
|
220
|
+
- `clientDisconnected`: Client disconnected from the server (returns the client's ID `string`)
|
|
221
|
+
- `clientJoinedRoom`: Client joined a room – note that clients can only leave by disconnecting (returns the client's ID `string`, room ID `string`)
|
|
222
|
+
- `roomCreated`: Client created a room (returns room ID `string`)
|
|
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
|
|
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
|
+
- `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`)
|
|
219
227
|
|
|
220
228
|
### Properties (Read-only)
|
|
221
229
|
|
|
@@ -1687,7 +1687,7 @@ function requireCrdtManager () {
|
|
|
1687
1687
|
hasRequiredCrdtManager = 1;
|
|
1688
1688
|
// Custom conflict-free replicated data type system with vector clocks
|
|
1689
1689
|
|
|
1690
|
-
const CONSOLE_PREFIX = "CRDT
|
|
1690
|
+
const CONSOLE_PREFIX = "PlaySocket CRDT manager: ";
|
|
1691
1691
|
|
|
1692
1692
|
class CRDTManager {
|
|
1693
1693
|
// Storage
|
|
@@ -1835,50 +1835,53 @@ function requireCrdtManager () {
|
|
|
1835
1835
|
const MIN_GC_DELAY = 1000; // Minimum 1s delay between garbage collection runs
|
|
1836
1836
|
const MIN_AGE_FOR_GC = 5000; // Garbage collect ops that are older than 5s
|
|
1837
1837
|
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1838
|
+
try {
|
|
1839
|
+
if (Date.now() - this.#lastGCCheck < MIN_GC_DELAY) return;
|
|
1840
|
+
this.#lastGCCheck = Date.now();
|
|
1841
|
+
|
|
1842
|
+
for (const [key, operations] of this.#keyOperations.entries()) {
|
|
1843
|
+
if (operations?.length < 5) continue; // Min op amount per key for garbage collection to run (otherwise not worth it)
|
|
1844
|
+
|
|
1845
|
+
let retainCount = operations.length;
|
|
1846
|
+
operations.forEach((op, index) => {
|
|
1847
|
+
// Count how many ops, from last to latest, are older than the min-age in a row
|
|
1848
|
+
if (op.uuid && this.#opUuidTimestamp.has(op.uuid) && (Date.now() - this.#opUuidTimestamp.get(op.uuid)) > MIN_AGE_FOR_GC) {
|
|
1849
|
+
const removeCount = index + 1;
|
|
1850
|
+
retainCount = operations.length - removeCount;
|
|
1851
|
+
this.#opUuidTimestamp.delete(op.uuid); // Remove from timestamps if set to be deleted
|
|
1852
|
+
}
|
|
1853
|
+
});
|
|
1853
1854
|
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1855
|
+
if (retainCount < operations.length) {
|
|
1856
|
+
if (this.#debug) console.log(CONSOLE_PREFIX + `Running garbage collection for key ${key} with current operations:`, operations);
|
|
1857
|
+
const retainOps = operations.slice(-retainCount); // Newest ops
|
|
1858
|
+
const removeOps = operations.slice(0, -retainCount); // Oldest ops (start = idx 0, end = retainCount counted from right side)
|
|
1859
|
+
const baselineVectorClock = removeOps[removeOps.length - 1]?.vectorClock || []; // Use the vector clock from the last operation that we remove/overwrite
|
|
1860
|
+
|
|
1861
|
+
// Calculate the value at the point where retained operations start
|
|
1862
|
+
let baselineValue = null;
|
|
1863
|
+
for (const op of removeOps) {
|
|
1864
|
+
if (!op.data) continue;
|
|
1865
|
+
baselineValue = this.#handleOperation(
|
|
1866
|
+
baselineValue,
|
|
1867
|
+
op.data.type,
|
|
1868
|
+
op.data.value,
|
|
1869
|
+
op.data.updateValue
|
|
1870
|
+
);
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
// Create a compact operation with baseline value and appropriate vector clock
|
|
1874
|
+
const compactOp = this.#createOperation({ type: "set", value: baselineValue }, baselineVectorClock);
|
|
1875
|
+
|
|
1876
|
+
// Debug log
|
|
1877
|
+
if (this.#debug) console.log(CONSOLE_PREFIX + "Operations after garbage collection for this key:", [compactOp, ...retainOps]);
|
|
1878
|
+
|
|
1879
|
+
// Combine compact op with retained operations
|
|
1880
|
+
this.#keyOperations.set(key, [compactOp, ...retainOps]);
|
|
1871
1881
|
}
|
|
1872
|
-
|
|
1873
|
-
// Create a compact operation with baseline value and appropriate vector clock
|
|
1874
|
-
const compactOp = this.#createOperation({ type: "set", value: baselineValue }, baselineVectorClock);
|
|
1875
|
-
|
|
1876
|
-
// Debug log
|
|
1877
|
-
if (this.#debug) console.log(CONSOLE_PREFIX + "Operations after garbage collection for this key:", [compactOp, ...retainOps]);
|
|
1878
|
-
|
|
1879
|
-
// Combine compact op with retained operations
|
|
1880
|
-
this.#keyOperations.set(key, [compactOp, ...retainOps]);
|
|
1881
1882
|
}
|
|
1883
|
+
} catch (error) {
|
|
1884
|
+
console.error(CONSOLE_PREFIX + "Error running garbage collection:", error);
|
|
1882
1885
|
}
|
|
1883
1886
|
}
|
|
1884
1887
|
|
|
@@ -1913,7 +1916,6 @@ function requireCrdtManager () {
|
|
|
1913
1916
|
|
|
1914
1917
|
for (const op of ops) {
|
|
1915
1918
|
if (!op.data) continue;
|
|
1916
|
-
if (op.data.type.startsWith('array') && !Array.isArray(value)) value = []; // Initialize array if required
|
|
1917
1919
|
value = this.#handleOperation(
|
|
1918
1920
|
value,
|
|
1919
1921
|
op.data.type,
|
|
@@ -2225,6 +2227,22 @@ class PlaySocket {
|
|
|
2225
2227
|
if (!message.type) return;
|
|
2226
2228
|
|
|
2227
2229
|
switch (message.type) {
|
|
2230
|
+
case 'registration_failed':
|
|
2231
|
+
if (this.#pendingRegistration) {
|
|
2232
|
+
this.#pendingRegistration.reject(new Error("Failed to register: " + message.reason));
|
|
2233
|
+
this.#triggerEvent("error", "Failed to register: " + message.reason);
|
|
2234
|
+
}
|
|
2235
|
+
break;
|
|
2236
|
+
|
|
2237
|
+
case 'registered':
|
|
2238
|
+
if (this.#pendingRegistration) {
|
|
2239
|
+
this.#sessionToken = message.sessionToken;
|
|
2240
|
+
this.#initialized = true;
|
|
2241
|
+
this.#pendingRegistration.resolve(message.id);
|
|
2242
|
+
this.#triggerEvent("status", "Connected to server.");
|
|
2243
|
+
}
|
|
2244
|
+
break;
|
|
2245
|
+
|
|
2228
2246
|
case 'join_accepted':
|
|
2229
2247
|
// Connected to room
|
|
2230
2248
|
if (this.#pendingJoin) {
|
|
@@ -2291,22 +2309,6 @@ class PlaySocket {
|
|
|
2291
2309
|
}
|
|
2292
2310
|
break;
|
|
2293
2311
|
|
|
2294
|
-
case 'id_taken':
|
|
2295
|
-
if (this.#pendingRegistration) {
|
|
2296
|
-
this.#pendingRegistration.reject(new Error("This id is already in use"));
|
|
2297
|
-
this.#triggerEvent("error", "This id is taken.");
|
|
2298
|
-
}
|
|
2299
|
-
break;
|
|
2300
|
-
|
|
2301
|
-
case 'registered':
|
|
2302
|
-
if (this.#pendingRegistration) {
|
|
2303
|
-
this.#sessionToken = message.sessionToken;
|
|
2304
|
-
this.#initialized = true;
|
|
2305
|
-
this.#pendingRegistration.resolve(message.id);
|
|
2306
|
-
this.#triggerEvent("status", "Connected to server.");
|
|
2307
|
-
}
|
|
2308
|
-
break;
|
|
2309
|
-
|
|
2310
2312
|
case 'property_updated':
|
|
2311
2313
|
this.#roomVersion++; // Increment room version
|
|
2312
2314
|
if (this.#debug) console.log(LOG_PREFIX + "Property update received:", message.update);
|
|
@@ -1813,7 +1813,7 @@ function requireCrdtManager () {
|
|
|
1813
1813
|
hasRequiredCrdtManager = 1;
|
|
1814
1814
|
// Custom conflict-free replicated data type system with vector clocks
|
|
1815
1815
|
|
|
1816
|
-
const CONSOLE_PREFIX = "CRDT
|
|
1816
|
+
const CONSOLE_PREFIX = "PlaySocket CRDT manager: ";
|
|
1817
1817
|
|
|
1818
1818
|
class CRDTManager {
|
|
1819
1819
|
// Storage
|
|
@@ -1961,50 +1961,53 @@ function requireCrdtManager () {
|
|
|
1961
1961
|
const MIN_GC_DELAY = 1000; // Minimum 1s delay between garbage collection runs
|
|
1962
1962
|
const MIN_AGE_FOR_GC = 5000; // Garbage collect ops that are older than 5s
|
|
1963
1963
|
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1964
|
+
try {
|
|
1965
|
+
if (Date.now() - this.#lastGCCheck < MIN_GC_DELAY) return;
|
|
1966
|
+
this.#lastGCCheck = Date.now();
|
|
1967
|
+
|
|
1968
|
+
for (const [key, operations] of this.#keyOperations.entries()) {
|
|
1969
|
+
if (operations?.length < 5) continue; // Min op amount per key for garbage collection to run (otherwise not worth it)
|
|
1970
|
+
|
|
1971
|
+
let retainCount = operations.length;
|
|
1972
|
+
operations.forEach((op, index) => {
|
|
1973
|
+
// Count how many ops, from last to latest, are older than the min-age in a row
|
|
1974
|
+
if (op.uuid && this.#opUuidTimestamp.has(op.uuid) && (Date.now() - this.#opUuidTimestamp.get(op.uuid)) > MIN_AGE_FOR_GC) {
|
|
1975
|
+
const removeCount = index + 1;
|
|
1976
|
+
retainCount = operations.length - removeCount;
|
|
1977
|
+
this.#opUuidTimestamp.delete(op.uuid); // Remove from timestamps if set to be deleted
|
|
1978
|
+
}
|
|
1979
|
+
});
|
|
1979
1980
|
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
}
|
|
1981
|
+
if (retainCount < operations.length) {
|
|
1982
|
+
if (this.#debug) console.log(CONSOLE_PREFIX + `Running garbage collection for key ${key} with current operations:`, operations);
|
|
1983
|
+
const retainOps = operations.slice(-retainCount); // Newest ops
|
|
1984
|
+
const removeOps = operations.slice(0, -retainCount); // Oldest ops (start = idx 0, end = retainCount counted from right side)
|
|
1985
|
+
const baselineVectorClock = removeOps[removeOps.length - 1]?.vectorClock || []; // Use the vector clock from the last operation that we remove/overwrite
|
|
1986
|
+
|
|
1987
|
+
// Calculate the value at the point where retained operations start
|
|
1988
|
+
let baselineValue = null;
|
|
1989
|
+
for (const op of removeOps) {
|
|
1990
|
+
if (!op.data) continue;
|
|
1991
|
+
baselineValue = this.#handleOperation(
|
|
1992
|
+
baselineValue,
|
|
1993
|
+
op.data.type,
|
|
1994
|
+
op.data.value,
|
|
1995
|
+
op.data.updateValue
|
|
1996
|
+
);
|
|
1997
|
+
}
|
|
1998
1998
|
|
|
1999
|
-
|
|
2000
|
-
|
|
1999
|
+
// Create a compact operation with baseline value and appropriate vector clock
|
|
2000
|
+
const compactOp = this.#createOperation({ type: "set", value: baselineValue }, baselineVectorClock);
|
|
2001
2001
|
|
|
2002
|
-
|
|
2003
|
-
|
|
2002
|
+
// Debug log
|
|
2003
|
+
if (this.#debug) console.log(CONSOLE_PREFIX + "Operations after garbage collection for this key:", [compactOp, ...retainOps]);
|
|
2004
2004
|
|
|
2005
|
-
|
|
2006
|
-
|
|
2005
|
+
// Combine compact op with retained operations
|
|
2006
|
+
this.#keyOperations.set(key, [compactOp, ...retainOps]);
|
|
2007
|
+
}
|
|
2007
2008
|
}
|
|
2009
|
+
} catch (error) {
|
|
2010
|
+
console.error(CONSOLE_PREFIX + "Error running garbage collection:", error);
|
|
2008
2011
|
}
|
|
2009
2012
|
}
|
|
2010
2013
|
|
|
@@ -2039,7 +2042,6 @@ function requireCrdtManager () {
|
|
|
2039
2042
|
|
|
2040
2043
|
for (const op of ops) {
|
|
2041
2044
|
if (!op.data) continue;
|
|
2042
|
-
if (op.data.type.startsWith('array') && !Array.isArray(value)) value = []; // Initialize array if required
|
|
2043
2045
|
value = this.#handleOperation(
|
|
2044
2046
|
value,
|
|
2045
2047
|
op.data.type,
|
|
@@ -2297,7 +2299,7 @@ function requireServer () {
|
|
|
2297
2299
|
case 'register':
|
|
2298
2300
|
// Register client ID if provided & check for a duplicate
|
|
2299
2301
|
if (data.id && this.#clients.get(data.id)) {
|
|
2300
|
-
ws.send(encode({ type: '
|
|
2302
|
+
ws.send(encode({ type: 'registration_failed', reason: 'ID is taken.' }), { binary: true });
|
|
2301
2303
|
return;
|
|
2302
2304
|
}
|
|
2303
2305
|
|
|
@@ -2313,11 +2315,20 @@ function requireServer () {
|
|
|
2313
2315
|
}
|
|
2314
2316
|
}
|
|
2315
2317
|
if (!data.id) {
|
|
2316
|
-
ws.send(encode({ type: '
|
|
2318
|
+
ws.send(encode({ type: 'registration_failed', reason: 'No available ID found.' }), { binary: true });
|
|
2317
2319
|
throw new Error('Failed to generate unique ID!');
|
|
2318
2320
|
}
|
|
2319
2321
|
}
|
|
2320
2322
|
|
|
2323
|
+
// Event callback
|
|
2324
|
+
const registrationAllowed = await this.#triggerEvent("clientRegistrationRequested", data.id, data.customData);
|
|
2325
|
+
if (registrationAllowed === false || typeof registrationAllowed === 'string') {
|
|
2326
|
+
ws.send(encode({
|
|
2327
|
+
type: 'registration_failed',
|
|
2328
|
+
reason: typeof registrationAllowed === 'string' ? registrationAllowed : 'Denied.'
|
|
2329
|
+
}), { binary: true });
|
|
2330
|
+
return;
|
|
2331
|
+
}
|
|
2321
2332
|
|
|
2322
2333
|
ws.clientId = data.id; // Adds the provided id to the ws object as clientId
|
|
2323
2334
|
this.#clients.set(data.id, ws);
|
|
@@ -2462,8 +2473,8 @@ function requireServer () {
|
|
|
2462
2473
|
|
|
2463
2474
|
if (updateRoom && data.update) {
|
|
2464
2475
|
// Check if update is allowed via event callback (provide clone to ensure update integrity)
|
|
2465
|
-
const
|
|
2466
|
-
if (
|
|
2476
|
+
const updateAllowed = await this.#triggerEvent("storageUpdateRequested", { roomId: updateRoomId, clientId: ws.clientId, update: structuredClone(data.update) });
|
|
2477
|
+
if (updateAllowed === false) {
|
|
2467
2478
|
ws.send(encode({
|
|
2468
2479
|
type: 'property_update_rejected',
|
|
2469
2480
|
state: updateRoom.crdtManager.getState
|
|
@@ -2636,7 +2647,7 @@ function requireServer () {
|
|
|
2636
2647
|
* @param {Function} callback - Callback function
|
|
2637
2648
|
*/
|
|
2638
2649
|
onEvent(event, callback) {
|
|
2639
|
-
const validEvents = ["clientRegistered", "clientDisconnected", "clientJoinedRoom", "roomCreationRequested", "requestReceived", "roomCreated", "storageUpdateRequested", "roomDestroyed"];
|
|
2650
|
+
const validEvents = ["clientRegistered", "clientRegistrationRequested", "clientDisconnected", "clientJoinedRoom", "roomCreationRequested", "requestReceived", "roomCreated", "storageUpdateRequested", "roomDestroyed"];
|
|
2640
2651
|
if (!validEvents.includes(event)) return console.warn(`Invalid PlaySocket event type "${event}"`);
|
|
2641
2652
|
if (!this.#callbacks.has(event)) this.#callbacks.set(event, []);
|
|
2642
2653
|
this.#callbacks.get(event).push(callback);
|
|
@@ -2667,8 +2678,7 @@ function requireServer () {
|
|
|
2667
2678
|
for (const callback of callbacks) {
|
|
2668
2679
|
try {
|
|
2669
2680
|
const result = await callback(...args);
|
|
2670
|
-
if (result
|
|
2671
|
-
if (typeof result === 'object') return result; // If response object is provided -> pass on
|
|
2681
|
+
if (result != null) return result; // Return any non-null/undefined result
|
|
2672
2682
|
} catch (error) {
|
|
2673
2683
|
console.error(`PlaySocket ${event} callback error:`, error);
|
|
2674
2684
|
}
|