playsocketjs 4.3.2 → 5.1.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 +6 -4
- package/package.json +1 -1
- package/src/client/client.js +23 -10
- package/src/server/server.js +43 -13
- package/src/universal/constants.js +1 -1
- package/src/universal/crdtManager.js +9 -9
- package/types/client/client.d.ts +31 -10
- package/types/server/server.d.ts +60 -10
- package/types/universal/constants.d.ts +1 -1
- package/types/universal/crdtManager.d.ts +5 -5
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
An optimistic-first WebSocket synchronization library. Built for creating multiplayer games & collaborative experiences with reactive web frameworks.
|
|
4
4
|
|
|
5
|
+
For an introduction to PlaySocket, visit the [PlaySocket website](https://therealpaulplay.github.io/PlaySocketJS/).
|
|
6
|
+
|
|
5
7
|
## Why use PlaySocket?
|
|
6
8
|
|
|
7
9
|
PlaySocket makes developing shared experiences a breeze:
|
|
@@ -140,9 +142,9 @@ new PlaySocket(id?: string, options: PlaySocketOptions)
|
|
|
140
142
|
| `moved` | `roomId: string` | Moved to different room. |
|
|
141
143
|
| `instanceDestroyed` | `reason: string` | Instance destroyed through `destroy()` or error. |
|
|
142
144
|
| `storageUpdated` | `storage: object` | Storage state changed. Does not trigger on no-op updates. |
|
|
143
|
-
| `hostMigrated` | `
|
|
145
|
+
| `hostMigrated` | `hostId: string` | Host was changed. |
|
|
144
146
|
| `clientJoined` | `clientId: string` | New client joined the room. |
|
|
145
|
-
| `clientLeft` | `clientId: string
|
|
147
|
+
| `clientLeft` | `clientId: string` | A client left the room. |
|
|
146
148
|
|
|
147
149
|
#### Properties
|
|
148
150
|
|
|
@@ -305,7 +307,7 @@ The callback signature is `callback(verified, code?, message?)` where `code` ref
|
|
|
305
307
|
#### Methods
|
|
306
308
|
|
|
307
309
|
> [!IMPORTANT]
|
|
308
|
-
> Rooms created by the server
|
|
310
|
+
> Rooms created by the server are always owned by the host ID "server". Such rooms are not auto-deleted when all participants have left, so you need to take care of their lifecycle.
|
|
309
311
|
|
|
310
312
|
| Name | Parameters | Return type | Description |
|
|
311
313
|
|--------|------------|-------------|-------------|
|
|
@@ -316,7 +318,7 @@ The callback signature is `callback(verified, code?, message?)` where `code` ref
|
|
|
316
318
|
| `getRoomStorage()` | `roomId: string` | `object` | Get a snapshot of the current room storage. |
|
|
317
319
|
| `getUpdateDetails()` | `update: object` | `object` | Get the details (`key`, `type`, `value` and `secondValue`) of a storage update for building validation logic. |
|
|
318
320
|
| `updateRoomStorage()` | `roomId: string, key: string, type: string, value: any, secondValue?: any` | `void` | Update a key in the shared storage of a room. |
|
|
319
|
-
| `createRoom()` | `initialStorage?: object, size?: number
|
|
321
|
+
| `createRoom()` | `initialStorage?: object, size?: number` | `object` | Create a server-owned room (returns object containing room ID and state).|
|
|
320
322
|
| `destroyRoom()` | `roomId: string` | `void` | Destroy a room & kick all participants. |
|
|
321
323
|
|
|
322
324
|
#### Events
|
package/package.json
CHANGED
package/src/client/client.js
CHANGED
|
@@ -20,6 +20,18 @@ const TIMEOUT_MS = 3000; // 3 second timeout for WS messages
|
|
|
20
20
|
|
|
21
21
|
/** @typedef {{ resolve: (value?: any) => void, reject: (reason?: any) => void }} PendingPromise */
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Event callback types
|
|
25
|
+
* @typedef {object} ClientEventMap
|
|
26
|
+
* @property {(message: string) => void} status
|
|
27
|
+
* @property {(roomId: string) => void} moved
|
|
28
|
+
* @property {(reason: string | undefined) => void} instanceDestroyed
|
|
29
|
+
* @property {(storage: Record<string, any>) => void} storageUpdated
|
|
30
|
+
* @property {(hostId: string) => void} hostMigrated
|
|
31
|
+
* @property {(clientId: string) => void} clientJoined
|
|
32
|
+
* @property {(clientId: string) => void} clientLeft
|
|
33
|
+
*/
|
|
34
|
+
|
|
23
35
|
/**
|
|
24
36
|
* PlaySocket Client
|
|
25
37
|
*/
|
|
@@ -75,11 +87,11 @@ export default class PlaySocket {
|
|
|
75
87
|
|
|
76
88
|
/**
|
|
77
89
|
* Create a new PlaySocket instance
|
|
78
|
-
* @param {string} [id] - Unique identifier for this client (assigned by the server if omitted)
|
|
90
|
+
* @param {string | null} [id] - Unique identifier for this client (assigned by the server if omitted or null)
|
|
79
91
|
* @param {PlaySocketOptions} options - Connection options
|
|
80
92
|
*/
|
|
81
93
|
constructor(id, options = /** @type {PlaySocketOptions} */ ({})) {
|
|
82
|
-
this.#id = id;
|
|
94
|
+
this.#id = id ?? undefined;
|
|
83
95
|
if (options.endpoint) this.#endpoint = options.endpoint;
|
|
84
96
|
if (options.customData) this.#customData = { ...options.customData };
|
|
85
97
|
if (options.debug) this.#debug = true; // Enabling extra logging
|
|
@@ -89,7 +101,7 @@ export default class PlaySocket {
|
|
|
89
101
|
/**
|
|
90
102
|
* Helper to create timeout promises for create room, join room etc.
|
|
91
103
|
* @param {string} name - Name of the timeout
|
|
92
|
-
* @returns {Promise<never>}
|
|
104
|
+
* @returns {Promise<never>} Promise that rejects after the timeout
|
|
93
105
|
*/
|
|
94
106
|
#createTimeout(name) {
|
|
95
107
|
return new Promise((_, reject) =>
|
|
@@ -99,9 +111,10 @@ export default class PlaySocket {
|
|
|
99
111
|
|
|
100
112
|
/**
|
|
101
113
|
* Register an event callback
|
|
102
|
-
* @
|
|
103
|
-
* @param {
|
|
104
|
-
* @
|
|
114
|
+
* @template {keyof ClientEventMap} E
|
|
115
|
+
* @param {E} event - Event name
|
|
116
|
+
* @param {ClientEventMap[E]} callback - Callback function
|
|
117
|
+
* @returns {Function} Unsubscribe
|
|
105
118
|
*/
|
|
106
119
|
onEvent(event, callback) {
|
|
107
120
|
const validEvents = ["status", "moved", "instanceDestroyed", "storageUpdated", "hostMigrated", "clientJoined", "clientLeft"];
|
|
@@ -396,11 +409,11 @@ export default class PlaySocket {
|
|
|
396
409
|
|
|
397
410
|
/**
|
|
398
411
|
* Create a new room and become host
|
|
399
|
-
* @param {object} [initialStorage] - Initial state
|
|
412
|
+
* @param {object | null} [initialStorage] - Initial state
|
|
400
413
|
* @param {number} [size] - Max number of participants
|
|
401
414
|
* @returns {Promise<string>} Resolves with room ID
|
|
402
415
|
*/
|
|
403
|
-
async createRoom(initialStorage
|
|
416
|
+
async createRoom(initialStorage, size) {
|
|
404
417
|
if (!this.#initialized) throw new Error("Not initialized");
|
|
405
418
|
|
|
406
419
|
return /** @type {Promise<string>} */ (Promise.race([
|
|
@@ -422,7 +435,7 @@ export default class PlaySocket {
|
|
|
422
435
|
/**
|
|
423
436
|
* Join an existing room
|
|
424
437
|
* @param {string} roomId - ID of the room
|
|
425
|
-
* @returns {Promise<void>}
|
|
438
|
+
* @returns {Promise<void>} Resolves when connected
|
|
426
439
|
*/
|
|
427
440
|
async joinRoom(roomId) {
|
|
428
441
|
if (!this.#initialized) throw new Error("Not initialized");
|
|
@@ -469,7 +482,7 @@ export default class PlaySocket {
|
|
|
469
482
|
* Send a custom request to the server
|
|
470
483
|
* @param {string} name - Name of the request
|
|
471
484
|
* @param {*} [data] - Custom data
|
|
472
|
-
* @returns {Promise<void>}
|
|
485
|
+
* @returns {Promise<void>} Request promise
|
|
473
486
|
*/
|
|
474
487
|
async sendRequest(name, data) {
|
|
475
488
|
if (!this.#initialized) throw new Error("Not initialized");
|
package/src/server/server.js
CHANGED
|
@@ -31,6 +31,25 @@ export const RECONNECT_GRACE_PERIOD = 5000; // Exported for use in tests
|
|
|
31
31
|
* @property {CRDTManager} crdtManager - CRDT manager holding the room storage
|
|
32
32
|
*/
|
|
33
33
|
|
|
34
|
+
/** @typedef {boolean | string | void} RejectViaStringOrBool */
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Event callback types
|
|
38
|
+
* @typedef {object} ServerEventMap
|
|
39
|
+
* @property {(clientId: string, customData: any) => RejectViaStringOrBool | Promise<RejectViaStringOrBool>} clientRegistrationRequested
|
|
40
|
+
* @property {(clientId: string, customData: any) => void} clientRegistered
|
|
41
|
+
* @property {(clientId: string) => void} clientDisconnected
|
|
42
|
+
* @property {(clientId: string, roomId: string) => void} clientJoinedRoom
|
|
43
|
+
* @property {(clientId: string, roomId: string) => void} clientLeftRoom
|
|
44
|
+
* @property {(clientId: string, roomId: string) => RejectViaStringOrBool | Promise<RejectViaStringOrBool>} clientJoinRequested
|
|
45
|
+
* @property {(roomId: string) => void} roomCreated
|
|
46
|
+
* @property {(roomId: string) => void} roomDestroyed
|
|
47
|
+
* @property {(payload: { clientId: string, initialStorage: Record<string, any> }) => Record<string, any> | RejectViaStringOrBool | Promise<Record<string, any> | RejectViaStringOrBool>} roomCreationRequested
|
|
48
|
+
* @property {(payload: { roomId: string | null, clientId: string, name: string, data: unknown }) => RejectViaStringOrBool | Promise<RejectViaStringOrBool>} requestReceived
|
|
49
|
+
* @property {(payload: { roomId: string, clientId: string, update: PropertyUpdate, storage: Record<string, any> | undefined }) => RejectViaStringOrBool} storageUpdateRequested
|
|
50
|
+
* @property {(payload: { roomId: string, clientId: string | null, update: PropertyUpdate, storage: Record<string, any> | undefined }) => void} storageUpdated
|
|
51
|
+
*/
|
|
52
|
+
|
|
34
53
|
/**
|
|
35
54
|
* PlaySocket Server
|
|
36
55
|
*/
|
|
@@ -258,7 +277,7 @@ export default class PlaySocketServer {
|
|
|
258
277
|
return;
|
|
259
278
|
}
|
|
260
279
|
|
|
261
|
-
const newRoom = this
|
|
280
|
+
const newRoom = this.#createRoom(ws.clientId, data.initialStorage, data.size); // Create room
|
|
262
281
|
|
|
263
282
|
this.#rooms[newRoom.id].participants.push(ws.clientId) // Add client to the room
|
|
264
283
|
this.#clientRooms.set(ws.clientId, newRoom.id); // Add client to the client-room map
|
|
@@ -358,7 +377,7 @@ export default class PlaySocketServer {
|
|
|
358
377
|
|
|
359
378
|
/**
|
|
360
379
|
* Generate a readable, 6 digit ID
|
|
361
|
-
* @returns {string}
|
|
380
|
+
* @returns {string} Id
|
|
362
381
|
*/
|
|
363
382
|
#generateId() {
|
|
364
383
|
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789";
|
|
@@ -392,7 +411,7 @@ export default class PlaySocketServer {
|
|
|
392
411
|
|
|
393
412
|
/**
|
|
394
413
|
* Generate a random token to prevent malicious reconnect attempts
|
|
395
|
-
* @returns {string}
|
|
414
|
+
* @returns {string} Token
|
|
396
415
|
*/
|
|
397
416
|
#generateSessionToken() {
|
|
398
417
|
let token = "";
|
|
@@ -404,7 +423,7 @@ export default class PlaySocketServer {
|
|
|
404
423
|
* Check rate limit using token bucket algorithm
|
|
405
424
|
* @param {string} connUuid - Connection UUID (which is different from the client ID)
|
|
406
425
|
* @param {string} actionType - A string describing the action
|
|
407
|
-
* @returns {boolean}
|
|
426
|
+
* @returns {boolean} Whether or not the action can be allowed, true means allowed, false means limited
|
|
408
427
|
*/
|
|
409
428
|
#checkRateLimit(connUuid, actionType) {
|
|
410
429
|
const now = Date.now();
|
|
@@ -469,9 +488,10 @@ export default class PlaySocketServer {
|
|
|
469
488
|
|
|
470
489
|
/**
|
|
471
490
|
* Register an event callback
|
|
472
|
-
* @
|
|
473
|
-
* @param {
|
|
474
|
-
* @
|
|
491
|
+
* @template {keyof ServerEventMap} E
|
|
492
|
+
* @param {E} event - Event name
|
|
493
|
+
* @param {ServerEventMap[E]} callback - Callback function
|
|
494
|
+
* @returns {Function} Unsubscribe
|
|
475
495
|
*/
|
|
476
496
|
onEvent(event, callback) {
|
|
477
497
|
const validEvents = ["clientRegistered", "clientRegistrationRequested", "clientDisconnected", "clientJoinedRoom", "clientLeftRoom", "clientJoinRequested", "roomCreated", "roomCreationRequested", "requestReceived", "storageUpdated", "storageUpdateRequested", "roomDestroyed"];
|
|
@@ -494,7 +514,7 @@ export default class PlaySocketServer {
|
|
|
494
514
|
* Trigger an event to registered callbacks
|
|
495
515
|
* @param {string} event - Event name
|
|
496
516
|
* @param {...*} args - Arguments
|
|
497
|
-
* @returns {Promise<any>}
|
|
517
|
+
* @returns {Promise<any>} The first non-null callback return value if present (all callbacks run, a throwing callback counts as returning false), otherwise true
|
|
498
518
|
*/
|
|
499
519
|
async #triggerEvent(event, ...args) {
|
|
500
520
|
const syncOnlyEvents = ["storageUpdateRequested"]; // Async storage validation could mess up GC and would lead to potentially poor UX (slow sync)
|
|
@@ -558,7 +578,7 @@ export default class PlaySocketServer {
|
|
|
558
578
|
/**
|
|
559
579
|
* Get snapshot of a room's storage
|
|
560
580
|
* @param {string} roomId - ID of the room to get the storage from
|
|
561
|
-
* @returns {Record<string, any> | undefined}
|
|
581
|
+
* @returns {Record<string, any> | undefined} Storage object or undefined if the room doesn't exist
|
|
562
582
|
*/
|
|
563
583
|
getRoomStorage(roomId) {
|
|
564
584
|
const room = this.#rooms[roomId];
|
|
@@ -568,7 +588,7 @@ export default class PlaySocketServer {
|
|
|
568
588
|
/**
|
|
569
589
|
* Get the operation details from a storage update (e.g. in the "storageUpdateRequested" event)
|
|
570
590
|
* @param {PropertyUpdate} update - Property update
|
|
571
|
-
* @returns {{key: string | undefined, type: PropertyUpdateType | undefined, value: *, secondValue: *}}
|
|
591
|
+
* @returns {{key: string | undefined, type: PropertyUpdateType | undefined, value: *, secondValue: *}} Operation details
|
|
572
592
|
*/
|
|
573
593
|
getUpdateDetails(update) {
|
|
574
594
|
return getUpdateDetails(update);
|
|
@@ -604,14 +624,24 @@ export default class PlaySocketServer {
|
|
|
604
624
|
}
|
|
605
625
|
}
|
|
606
626
|
|
|
627
|
+
/**
|
|
628
|
+
* Create a server-owned room
|
|
629
|
+
* @param {object | null} [initialStorage] - Optional initial storage object
|
|
630
|
+
* @param {number} [size] - Max. room size, up to 500
|
|
631
|
+
* @returns {{ state: CRDTState, id: string }} Object containing room state and room ID
|
|
632
|
+
*/
|
|
633
|
+
createRoom(initialStorage, size) {
|
|
634
|
+
return this.#createRoom("server", initialStorage, size);
|
|
635
|
+
}
|
|
636
|
+
|
|
607
637
|
/**
|
|
608
638
|
* Create a room
|
|
609
|
-
* @param {
|
|
639
|
+
* @param {string} host - Host ID (when set to "server", room will not be deleted if all clients leave)
|
|
640
|
+
* @param {object | null} [initialStorage] - Optional initial storage object
|
|
610
641
|
* @param {number} [size] - Max. room size, up to 500
|
|
611
|
-
* @param {string} [host] - Host ID, defaults to "server" (when set to "server", room will not be deleted if all clients leave)
|
|
612
642
|
* @returns {{ state: CRDTState, id: string }} Object containing room state and room ID
|
|
613
643
|
*/
|
|
614
|
-
createRoom(initialStorage, size
|
|
644
|
+
#createRoom(host, initialStorage, size) {
|
|
615
645
|
let newRoomId;
|
|
616
646
|
|
|
617
647
|
for (let i = 0; i < 100; i++) {
|
|
@@ -179,7 +179,7 @@ export default class CRDTManager {
|
|
|
179
179
|
* @param {PropertyUpdateType} type - Operation type
|
|
180
180
|
* @param {*} value - Value
|
|
181
181
|
* @param {*} [secondValue] - Second value (needed for some operations)
|
|
182
|
-
* @returns {PropertyUpdate | undefined}
|
|
182
|
+
* @returns {PropertyUpdate | undefined} Returns the property update
|
|
183
183
|
*/
|
|
184
184
|
updateProperty(key, type, value, secondValue) {
|
|
185
185
|
try {
|
|
@@ -276,7 +276,7 @@ export default class CRDTManager {
|
|
|
276
276
|
* Create an operation object
|
|
277
277
|
* @param {OperationData} data - Operation data, consists of type, value and secondValue
|
|
278
278
|
* @param {VectorClockEntries} vectorClock - Vector clock of the replica/client
|
|
279
|
-
* @returns {Operation}
|
|
279
|
+
* @returns {Operation} Operation object
|
|
280
280
|
*/
|
|
281
281
|
#createOperation(data, vectorClock) {
|
|
282
282
|
const newOperation = {
|
|
@@ -323,7 +323,7 @@ export default class CRDTManager {
|
|
|
323
323
|
/**
|
|
324
324
|
* Sort operations by vector clock (causal order)
|
|
325
325
|
* @param {Operation[]} operations - Full operations that include vector clocks, data and a source
|
|
326
|
-
* @returns {Operation[]}
|
|
326
|
+
* @returns {Operation[]} Sorted operations
|
|
327
327
|
*/
|
|
328
328
|
#sortByVectorClock(operations) {
|
|
329
329
|
return [...operations].sort((a, b) => {
|
|
@@ -362,7 +362,7 @@ export default class CRDTManager {
|
|
|
362
362
|
* @param {PropertyUpdateType} type - Operation type
|
|
363
363
|
* @param {*} value - Value
|
|
364
364
|
* @param {*} [secondValue] - Second value (needed for some operations)
|
|
365
|
-
* @returns {*}
|
|
365
|
+
* @returns {*} Value after the operation
|
|
366
366
|
*/
|
|
367
367
|
#handleOperation(curValue, type, value, secondValue) {
|
|
368
368
|
try {
|
|
@@ -433,7 +433,7 @@ export default class CRDTManager {
|
|
|
433
433
|
/**
|
|
434
434
|
* Remove HTML to prevent XSS and enforce size limits
|
|
435
435
|
* @param {*} value - Value to sanitize
|
|
436
|
-
* @returns {*}
|
|
436
|
+
* @returns {*} Sanitized value
|
|
437
437
|
*/
|
|
438
438
|
#sanitizeValue(value) {
|
|
439
439
|
if (value === undefined) return null; // Normalize undefined to null to avoid divergence in transport
|
|
@@ -450,7 +450,7 @@ export default class CRDTManager {
|
|
|
450
450
|
|
|
451
451
|
/**
|
|
452
452
|
* Get property store
|
|
453
|
-
* @returns {Record<string, any>}
|
|
453
|
+
* @returns {Record<string, any>} Object with key value pairs
|
|
454
454
|
*/
|
|
455
455
|
get propertyStore() {
|
|
456
456
|
try {
|
|
@@ -463,7 +463,7 @@ export default class CRDTManager {
|
|
|
463
463
|
|
|
464
464
|
/**
|
|
465
465
|
* Check for changes in the local property store
|
|
466
|
-
* @returns {boolean}
|
|
466
|
+
* @returns {boolean} Check if any values changed going from the last to the current property update
|
|
467
467
|
*/
|
|
468
468
|
get didPropertiesChange() {
|
|
469
469
|
try {
|
|
@@ -479,7 +479,7 @@ export default class CRDTManager {
|
|
|
479
479
|
|
|
480
480
|
/**
|
|
481
481
|
* Export state (can be imported using importState, converts the maps to arrays for serialization)
|
|
482
|
-
* @returns {CRDTState}
|
|
482
|
+
* @returns {CRDTState} State
|
|
483
483
|
*/
|
|
484
484
|
get state() {
|
|
485
485
|
return {
|
|
@@ -492,7 +492,7 @@ export default class CRDTManager {
|
|
|
492
492
|
/**
|
|
493
493
|
* Get the operation details from a property update
|
|
494
494
|
* @param {PropertyUpdate} update - Property update
|
|
495
|
-
* @returns {{key: string | undefined, type: PropertyUpdateType | undefined, value: *, secondValue: *}}
|
|
495
|
+
* @returns {{key: string | undefined, type: PropertyUpdateType | undefined, value: *, secondValue: *}} Operation details
|
|
496
496
|
*/
|
|
497
497
|
export function getUpdateDetails(update) {
|
|
498
498
|
const data = update?.operation?.data;
|
package/types/client/client.d.ts
CHANGED
|
@@ -23,6 +23,15 @@ export type PendingPromise = {
|
|
|
23
23
|
resolve: (value?: any) => void;
|
|
24
24
|
reject: (reason?: any) => void;
|
|
25
25
|
};
|
|
26
|
+
export type ClientEventMap = {
|
|
27
|
+
status: (message: string) => void;
|
|
28
|
+
moved: (roomId: string) => void;
|
|
29
|
+
instanceDestroyed: (reason: string | undefined) => void;
|
|
30
|
+
storageUpdated: (storage: Record<string, any>) => void;
|
|
31
|
+
hostMigrated: (hostId: string) => void;
|
|
32
|
+
clientJoined: (clientId: string) => void;
|
|
33
|
+
clientLeft: (clientId: string) => void;
|
|
34
|
+
};
|
|
26
35
|
/** @typedef {import("../universal/crdtManager.js").PropertyUpdateType} PropertyUpdateType */
|
|
27
36
|
/**
|
|
28
37
|
* @typedef {object} PlaySocketOptions
|
|
@@ -35,6 +44,17 @@ export type PendingPromise = {
|
|
|
35
44
|
* @property {string} type - Message type
|
|
36
45
|
*/
|
|
37
46
|
/** @typedef {{ resolve: (value?: any) => void, reject: (reason?: any) => void }} PendingPromise */
|
|
47
|
+
/**
|
|
48
|
+
* Event callback types
|
|
49
|
+
* @typedef {object} ClientEventMap
|
|
50
|
+
* @property {(message: string) => void} status
|
|
51
|
+
* @property {(roomId: string) => void} moved
|
|
52
|
+
* @property {(reason: string | undefined) => void} instanceDestroyed
|
|
53
|
+
* @property {(storage: Record<string, any>) => void} storageUpdated
|
|
54
|
+
* @property {(hostId: string) => void} hostMigrated
|
|
55
|
+
* @property {(clientId: string) => void} clientJoined
|
|
56
|
+
* @property {(clientId: string) => void} clientLeft
|
|
57
|
+
*/
|
|
38
58
|
/**
|
|
39
59
|
* PlaySocket Client
|
|
40
60
|
*/
|
|
@@ -42,17 +62,18 @@ export default class PlaySocket {
|
|
|
42
62
|
#private;
|
|
43
63
|
/**
|
|
44
64
|
* Create a new PlaySocket instance
|
|
45
|
-
* @param {string} [id] - Unique identifier for this client (assigned by the server if omitted)
|
|
65
|
+
* @param {string | null} [id] - Unique identifier for this client (assigned by the server if omitted or null)
|
|
46
66
|
* @param {PlaySocketOptions} options - Connection options
|
|
47
67
|
*/
|
|
48
|
-
constructor(id?: string, options?: PlaySocketOptions);
|
|
68
|
+
constructor(id?: string | null, options?: PlaySocketOptions);
|
|
49
69
|
/**
|
|
50
70
|
* Register an event callback
|
|
51
|
-
* @
|
|
52
|
-
* @param {
|
|
53
|
-
* @
|
|
71
|
+
* @template {keyof ClientEventMap} E
|
|
72
|
+
* @param {E} event - Event name
|
|
73
|
+
* @param {ClientEventMap[E]} callback - Callback function
|
|
74
|
+
* @returns {Function} Unsubscribe
|
|
54
75
|
*/
|
|
55
|
-
onEvent(event:
|
|
76
|
+
onEvent<E extends keyof ClientEventMap>(event: E, callback: ClientEventMap[E]): Function;
|
|
56
77
|
/**
|
|
57
78
|
* Initialize the PlaySocket instance by connecting to the WS server
|
|
58
79
|
* @returns {Promise<string>} Resolves with the client ID when the connection is established
|
|
@@ -60,15 +81,15 @@ export default class PlaySocket {
|
|
|
60
81
|
init(): Promise<string>;
|
|
61
82
|
/**
|
|
62
83
|
* Create a new room and become host
|
|
63
|
-
* @param {object} [initialStorage] - Initial state
|
|
84
|
+
* @param {object | null} [initialStorage] - Initial state
|
|
64
85
|
* @param {number} [size] - Max number of participants
|
|
65
86
|
* @returns {Promise<string>} Resolves with room ID
|
|
66
87
|
*/
|
|
67
|
-
createRoom(initialStorage?: object, size?: number): Promise<string>;
|
|
88
|
+
createRoom(initialStorage?: object | null, size?: number): Promise<string>;
|
|
68
89
|
/**
|
|
69
90
|
* Join an existing room
|
|
70
91
|
* @param {string} roomId - ID of the room
|
|
71
|
-
* @returns {Promise<void>}
|
|
92
|
+
* @returns {Promise<void>} Resolves when connected
|
|
72
93
|
*/
|
|
73
94
|
joinRoom(roomId: string): Promise<void>;
|
|
74
95
|
/**
|
|
@@ -83,7 +104,7 @@ export default class PlaySocket {
|
|
|
83
104
|
* Send a custom request to the server
|
|
84
105
|
* @param {string} name - Name of the request
|
|
85
106
|
* @param {*} [data] - Custom data
|
|
86
|
-
* @returns {Promise<void>}
|
|
107
|
+
* @returns {Promise<void>} Request promise
|
|
87
108
|
*/
|
|
88
109
|
sendRequest(name: string, data?: any): Promise<void>;
|
|
89
110
|
/**
|
package/types/server/server.d.ts
CHANGED
|
@@ -33,6 +33,39 @@ export type Room = {
|
|
|
33
33
|
*/
|
|
34
34
|
crdtManager: CRDTManager;
|
|
35
35
|
};
|
|
36
|
+
export type RejectViaStringOrBool = boolean | string | void;
|
|
37
|
+
export type ServerEventMap = {
|
|
38
|
+
clientRegistrationRequested: (clientId: string, customData: any) => RejectViaStringOrBool | Promise<RejectViaStringOrBool>;
|
|
39
|
+
clientRegistered: (clientId: string, customData: any) => void;
|
|
40
|
+
clientDisconnected: (clientId: string) => void;
|
|
41
|
+
clientJoinedRoom: (clientId: string, roomId: string) => void;
|
|
42
|
+
clientLeftRoom: (clientId: string, roomId: string) => void;
|
|
43
|
+
clientJoinRequested: (clientId: string, roomId: string) => RejectViaStringOrBool | Promise<RejectViaStringOrBool>;
|
|
44
|
+
roomCreated: (roomId: string) => void;
|
|
45
|
+
roomDestroyed: (roomId: string) => void;
|
|
46
|
+
roomCreationRequested: (payload: {
|
|
47
|
+
clientId: string;
|
|
48
|
+
initialStorage: Record<string, any>;
|
|
49
|
+
}) => Record<string, any> | RejectViaStringOrBool | Promise<Record<string, any> | RejectViaStringOrBool>;
|
|
50
|
+
requestReceived: (payload: {
|
|
51
|
+
roomId: string | null;
|
|
52
|
+
clientId: string;
|
|
53
|
+
name: string;
|
|
54
|
+
data: unknown;
|
|
55
|
+
}) => RejectViaStringOrBool | Promise<RejectViaStringOrBool>;
|
|
56
|
+
storageUpdateRequested: (payload: {
|
|
57
|
+
roomId: string;
|
|
58
|
+
clientId: string;
|
|
59
|
+
update: PropertyUpdate;
|
|
60
|
+
storage: Record<string, any> | undefined;
|
|
61
|
+
}) => RejectViaStringOrBool;
|
|
62
|
+
storageUpdated: (payload: {
|
|
63
|
+
roomId: string;
|
|
64
|
+
clientId: string | null;
|
|
65
|
+
update: PropertyUpdate;
|
|
66
|
+
storage: Record<string, any> | undefined;
|
|
67
|
+
}) => void;
|
|
68
|
+
};
|
|
36
69
|
/**
|
|
37
70
|
* @typedef {import("node:http").Server} HttpServer
|
|
38
71
|
* @typedef {import("../universal/crdtManager.js").PropertyUpdateType} PropertyUpdateType
|
|
@@ -51,6 +84,23 @@ export type Room = {
|
|
|
51
84
|
* @property {number} size - Max. number of participants
|
|
52
85
|
* @property {CRDTManager} crdtManager - CRDT manager holding the room storage
|
|
53
86
|
*/
|
|
87
|
+
/** @typedef {boolean | string | void} RejectViaStringOrBool */
|
|
88
|
+
/**
|
|
89
|
+
* Event callback types
|
|
90
|
+
* @typedef {object} ServerEventMap
|
|
91
|
+
* @property {(clientId: string, customData: any) => RejectViaStringOrBool | Promise<RejectViaStringOrBool>} clientRegistrationRequested
|
|
92
|
+
* @property {(clientId: string, customData: any) => void} clientRegistered
|
|
93
|
+
* @property {(clientId: string) => void} clientDisconnected
|
|
94
|
+
* @property {(clientId: string, roomId: string) => void} clientJoinedRoom
|
|
95
|
+
* @property {(clientId: string, roomId: string) => void} clientLeftRoom
|
|
96
|
+
* @property {(clientId: string, roomId: string) => RejectViaStringOrBool | Promise<RejectViaStringOrBool>} clientJoinRequested
|
|
97
|
+
* @property {(roomId: string) => void} roomCreated
|
|
98
|
+
* @property {(roomId: string) => void} roomDestroyed
|
|
99
|
+
* @property {(payload: { clientId: string, initialStorage: Record<string, any> }) => Record<string, any> | RejectViaStringOrBool | Promise<Record<string, any> | RejectViaStringOrBool>} roomCreationRequested
|
|
100
|
+
* @property {(payload: { roomId: string | null, clientId: string, name: string, data: unknown }) => RejectViaStringOrBool | Promise<RejectViaStringOrBool>} requestReceived
|
|
101
|
+
* @property {(payload: { roomId: string, clientId: string, update: PropertyUpdate, storage: Record<string, any> | undefined }) => RejectViaStringOrBool} storageUpdateRequested
|
|
102
|
+
* @property {(payload: { roomId: string, clientId: string | null, update: PropertyUpdate, storage: Record<string, any> | undefined }) => void} storageUpdated
|
|
103
|
+
*/
|
|
54
104
|
/**
|
|
55
105
|
* PlaySocket Server
|
|
56
106
|
*/
|
|
@@ -76,11 +126,12 @@ export default class PlaySocketServer {
|
|
|
76
126
|
});
|
|
77
127
|
/**
|
|
78
128
|
* Register an event callback
|
|
79
|
-
* @
|
|
80
|
-
* @param {
|
|
81
|
-
* @
|
|
129
|
+
* @template {keyof ServerEventMap} E
|
|
130
|
+
* @param {E} event - Event name
|
|
131
|
+
* @param {ServerEventMap[E]} callback - Callback function
|
|
132
|
+
* @returns {Function} Unsubscribe
|
|
82
133
|
*/
|
|
83
|
-
onEvent(event:
|
|
134
|
+
onEvent<E extends keyof ServerEventMap>(event: E, callback: ServerEventMap[E]): Function;
|
|
84
135
|
/**
|
|
85
136
|
* Kick a client from the server
|
|
86
137
|
* @param {string} clientId - Client ID
|
|
@@ -96,13 +147,13 @@ export default class PlaySocketServer {
|
|
|
96
147
|
/**
|
|
97
148
|
* Get snapshot of a room's storage
|
|
98
149
|
* @param {string} roomId - ID of the room to get the storage from
|
|
99
|
-
* @returns {Record<string, any> | undefined}
|
|
150
|
+
* @returns {Record<string, any> | undefined} Storage object or undefined if the room doesn't exist
|
|
100
151
|
*/
|
|
101
152
|
getRoomStorage(roomId: string): Record<string, any> | undefined;
|
|
102
153
|
/**
|
|
103
154
|
* Get the operation details from a storage update (e.g. in the "storageUpdateRequested" event)
|
|
104
155
|
* @param {PropertyUpdate} update - Property update
|
|
105
|
-
* @returns {{key: string | undefined, type: PropertyUpdateType | undefined, value: *, secondValue: *}}
|
|
156
|
+
* @returns {{key: string | undefined, type: PropertyUpdateType | undefined, value: *, secondValue: *}} Operation details
|
|
106
157
|
*/
|
|
107
158
|
getUpdateDetails(update: PropertyUpdate): {
|
|
108
159
|
key: string | undefined;
|
|
@@ -120,13 +171,12 @@ export default class PlaySocketServer {
|
|
|
120
171
|
*/
|
|
121
172
|
updateRoomStorage(roomId: string, key: string, type: PropertyUpdateType, value: any, secondValue?: any): void;
|
|
122
173
|
/**
|
|
123
|
-
* Create a room
|
|
124
|
-
* @param {object} [initialStorage] - Optional initial storage object
|
|
174
|
+
* Create a server-owned room
|
|
175
|
+
* @param {object | null} [initialStorage] - Optional initial storage object
|
|
125
176
|
* @param {number} [size] - Max. room size, up to 500
|
|
126
|
-
* @param {string} [host] - Host ID, defaults to "server" (when set to "server", room will not be deleted if all clients leave)
|
|
127
177
|
* @returns {{ state: CRDTState, id: string }} Object containing room state and room ID
|
|
128
178
|
*/
|
|
129
|
-
createRoom(initialStorage?: object, size?: number
|
|
179
|
+
createRoom(initialStorage?: object | null, size?: number): {
|
|
130
180
|
state: CRDTState;
|
|
131
181
|
id: string;
|
|
132
182
|
};
|
|
@@ -115,29 +115,29 @@ export default class CRDTManager {
|
|
|
115
115
|
* @param {PropertyUpdateType} type - Operation type
|
|
116
116
|
* @param {*} value - Value
|
|
117
117
|
* @param {*} [secondValue] - Second value (needed for some operations)
|
|
118
|
-
* @returns {PropertyUpdate | undefined}
|
|
118
|
+
* @returns {PropertyUpdate | undefined} Returns the property update
|
|
119
119
|
*/
|
|
120
120
|
updateProperty(key: string, type: PropertyUpdateType, value: any, secondValue?: any): PropertyUpdate | undefined;
|
|
121
121
|
/**
|
|
122
122
|
* Get property store
|
|
123
|
-
* @returns {Record<string, any>}
|
|
123
|
+
* @returns {Record<string, any>} Object with key value pairs
|
|
124
124
|
*/
|
|
125
125
|
get propertyStore(): Record<string, any>;
|
|
126
126
|
/**
|
|
127
127
|
* Check for changes in the local property store
|
|
128
|
-
* @returns {boolean}
|
|
128
|
+
* @returns {boolean} Check if any values changed going from the last to the current property update
|
|
129
129
|
*/
|
|
130
130
|
get didPropertiesChange(): boolean;
|
|
131
131
|
/**
|
|
132
132
|
* Export state (can be imported using importState, converts the maps to arrays for serialization)
|
|
133
|
-
* @returns {CRDTState}
|
|
133
|
+
* @returns {CRDTState} State
|
|
134
134
|
*/
|
|
135
135
|
get state(): CRDTState;
|
|
136
136
|
}
|
|
137
137
|
/**
|
|
138
138
|
* Get the operation details from a property update
|
|
139
139
|
* @param {PropertyUpdate} update - Property update
|
|
140
|
-
* @returns {{key: string | undefined, type: PropertyUpdateType | undefined, value: *, secondValue: *}}
|
|
140
|
+
* @returns {{key: string | undefined, type: PropertyUpdateType | undefined, value: *, secondValue: *}} Operation details
|
|
141
141
|
*/
|
|
142
142
|
export declare function getUpdateDetails(update: PropertyUpdate): {
|
|
143
143
|
key: string | undefined;
|