playsocketjs 5.0.0 → 5.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 +5 -3
- package/package.json +1 -1
- package/src/client/client.js +23 -10
- package/src/server/server.js +36 -14
- package/src/universal/constants.js +1 -1
- package/src/universal/crdtManager.js +20 -12
- package/types/client/client.d.ts +31 -10
- package/types/server/server.d.ts +65 -10
- package/types/universal/constants.d.ts +1 -1
- package/types/universal/crdtManager.d.ts +10 -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
|
|
|
@@ -313,7 +315,7 @@ The callback signature is `callback(verified, code?, message?)` where `code` ref
|
|
|
313
315
|
| `kick()` | `clientId: string, reason?: string` | `void` | Kick a client by their client ID. |
|
|
314
316
|
| `move()` | `clientId: string, roomId: string` | `void` | Move a client that is already in a room to a different room. |
|
|
315
317
|
| `onEvent()` | `event: string, callback: Function` | `() => void` | Register a server-side event callback. Returns unsubscribe function. |
|
|
316
|
-
| `getRoomStorage()` | `roomId: string` | `object` | Get a snapshot of the current room storage. |
|
|
318
|
+
| `getRoomStorage()` | `roomId: string, options?: { dangerouslySkipCloning?: boolean }` | `object` | Get a snapshot of the current room storage. Pass `dangerouslySkipCloning: true` to get the uncloned store for performance reasons. |
|
|
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
321
|
| `createRoom()` | `initialStorage?: object, size?: number` | `object` | Create a server-owned room (returns object containing room ID and state).|
|
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
|
*/
|
|
@@ -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)
|
|
@@ -556,19 +576,21 @@ export default class PlaySocketServer {
|
|
|
556
576
|
}
|
|
557
577
|
|
|
558
578
|
/**
|
|
559
|
-
* Get snapshot of a room's storage
|
|
579
|
+
* Get a snapshot of a room's storage
|
|
560
580
|
* @param {string} roomId - ID of the room to get the storage from
|
|
561
|
-
* @
|
|
581
|
+
* @param {object} [options] - Options
|
|
582
|
+
* @param {boolean} [options.dangerouslySkipCloning=false] - Return uncloned store (faster, but allows illegal mutation)
|
|
583
|
+
* @returns {Record<string, any> | undefined} Storage object or undefined if the room doesn't exist
|
|
562
584
|
*/
|
|
563
|
-
getRoomStorage(roomId) {
|
|
585
|
+
getRoomStorage(roomId, { dangerouslySkipCloning = false } = {}) {
|
|
564
586
|
const room = this.#rooms[roomId];
|
|
565
|
-
if (room) return room.crdtManager.propertyStore;
|
|
587
|
+
if (room) return dangerouslySkipCloning ? room.crdtManager.rawPropertyStore : room.crdtManager.propertyStore;
|
|
566
588
|
}
|
|
567
589
|
|
|
568
590
|
/**
|
|
569
591
|
* Get the operation details from a storage update (e.g. in the "storageUpdateRequested" event)
|
|
570
592
|
* @param {PropertyUpdate} update - Property update
|
|
571
|
-
* @returns {{key: string | undefined, type: PropertyUpdateType | undefined, value: *, secondValue: *}}
|
|
593
|
+
* @returns {{key: string | undefined, type: PropertyUpdateType | undefined, value: *, secondValue: *}} Operation details
|
|
572
594
|
*/
|
|
573
595
|
getUpdateDetails(update) {
|
|
574
596
|
return getUpdateDetails(update);
|
|
@@ -606,7 +628,7 @@ export default class PlaySocketServer {
|
|
|
606
628
|
|
|
607
629
|
/**
|
|
608
630
|
* Create a server-owned room
|
|
609
|
-
* @param {object} [initialStorage] - Optional initial storage object
|
|
631
|
+
* @param {object | null} [initialStorage] - Optional initial storage object
|
|
610
632
|
* @param {number} [size] - Max. room size, up to 500
|
|
611
633
|
* @returns {{ state: CRDTState, id: string }} Object containing room state and room ID
|
|
612
634
|
*/
|
|
@@ -617,7 +639,7 @@ export default class PlaySocketServer {
|
|
|
617
639
|
/**
|
|
618
640
|
* Create a room
|
|
619
641
|
* @param {string} host - Host ID (when set to "server", room will not be deleted if all clients leave)
|
|
620
|
-
* @param {object} [initialStorage] - Optional initial storage object
|
|
642
|
+
* @param {object | null} [initialStorage] - Optional initial storage object
|
|
621
643
|
* @param {number} [size] - Max. room size, up to 500
|
|
622
644
|
* @returns {{ state: CRDTState, id: string }} Object containing room state and room ID
|
|
623
645
|
*/
|
|
@@ -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,16 +362,16 @@ 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 {
|
|
369
|
+
// Set operation (before the structured clone since for set we don't care about curValue anyway)
|
|
370
|
+
if (type === "set") return value;
|
|
371
|
+
|
|
369
372
|
// Deep copy to avoid reference issues in case value is or contains object(s)
|
|
370
373
|
curValue = structuredClone(curValue);
|
|
371
374
|
|
|
372
|
-
// Set operation
|
|
373
|
-
if (type === "set") return value;
|
|
374
|
-
|
|
375
375
|
// Number increment operation
|
|
376
376
|
if (type === "number-increment") {
|
|
377
377
|
if (!Number.isFinite(curValue)) curValue = 0; // Auto-convert to number if current value isn't a finite number
|
|
@@ -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 {
|
|
@@ -461,9 +461,17 @@ export default class CRDTManager {
|
|
|
461
461
|
}
|
|
462
462
|
}
|
|
463
463
|
|
|
464
|
+
/**
|
|
465
|
+
* Get uncloned property store
|
|
466
|
+
* @returns {Record<string, any>} Object with key value pairs
|
|
467
|
+
*/
|
|
468
|
+
get rawPropertyStore() {
|
|
469
|
+
return this.#propertyStore;
|
|
470
|
+
}
|
|
471
|
+
|
|
464
472
|
/**
|
|
465
473
|
* Check for changes in the local property store
|
|
466
|
-
* @returns {boolean}
|
|
474
|
+
* @returns {boolean} Check if any values changed going from the last to the current property update
|
|
467
475
|
*/
|
|
468
476
|
get didPropertiesChange() {
|
|
469
477
|
try {
|
|
@@ -479,7 +487,7 @@ export default class CRDTManager {
|
|
|
479
487
|
|
|
480
488
|
/**
|
|
481
489
|
* Export state (can be imported using importState, converts the maps to arrays for serialization)
|
|
482
|
-
* @returns {CRDTState}
|
|
490
|
+
* @returns {CRDTState} State
|
|
483
491
|
*/
|
|
484
492
|
get state() {
|
|
485
493
|
return {
|
|
@@ -492,7 +500,7 @@ export default class CRDTManager {
|
|
|
492
500
|
/**
|
|
493
501
|
* Get the operation details from a property update
|
|
494
502
|
* @param {PropertyUpdate} update - Property update
|
|
495
|
-
* @returns {{key: string | undefined, type: PropertyUpdateType | undefined, value: *, secondValue: *}}
|
|
503
|
+
* @returns {{key: string | undefined, type: PropertyUpdateType | undefined, value: *, secondValue: *}} Operation details
|
|
496
504
|
*/
|
|
497
505
|
export function getUpdateDetails(update) {
|
|
498
506
|
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
|
|
@@ -94,15 +145,19 @@ export default class PlaySocketServer {
|
|
|
94
145
|
*/
|
|
95
146
|
move(clientId: string, roomId: string): void;
|
|
96
147
|
/**
|
|
97
|
-
* Get snapshot of a room's storage
|
|
148
|
+
* Get a snapshot of a room's storage
|
|
98
149
|
* @param {string} roomId - ID of the room to get the storage from
|
|
99
|
-
* @
|
|
150
|
+
* @param {object} [options] - Options
|
|
151
|
+
* @param {boolean} [options.dangerouslySkipCloning=false] - Return uncloned store (faster, but allows illegal mutation)
|
|
152
|
+
* @returns {Record<string, any> | undefined} Storage object or undefined if the room doesn't exist
|
|
100
153
|
*/
|
|
101
|
-
getRoomStorage(roomId: string
|
|
154
|
+
getRoomStorage(roomId: string, { dangerouslySkipCloning }?: {
|
|
155
|
+
dangerouslySkipCloning?: boolean;
|
|
156
|
+
}): Record<string, any> | undefined;
|
|
102
157
|
/**
|
|
103
158
|
* Get the operation details from a storage update (e.g. in the "storageUpdateRequested" event)
|
|
104
159
|
* @param {PropertyUpdate} update - Property update
|
|
105
|
-
* @returns {{key: string | undefined, type: PropertyUpdateType | undefined, value: *, secondValue: *}}
|
|
160
|
+
* @returns {{key: string | undefined, type: PropertyUpdateType | undefined, value: *, secondValue: *}} Operation details
|
|
106
161
|
*/
|
|
107
162
|
getUpdateDetails(update: PropertyUpdate): {
|
|
108
163
|
key: string | undefined;
|
|
@@ -121,11 +176,11 @@ export default class PlaySocketServer {
|
|
|
121
176
|
updateRoomStorage(roomId: string, key: string, type: PropertyUpdateType, value: any, secondValue?: any): void;
|
|
122
177
|
/**
|
|
123
178
|
* Create a server-owned room
|
|
124
|
-
* @param {object} [initialStorage] - Optional initial storage object
|
|
179
|
+
* @param {object | null} [initialStorage] - Optional initial storage object
|
|
125
180
|
* @param {number} [size] - Max. room size, up to 500
|
|
126
181
|
* @returns {{ state: CRDTState, id: string }} Object containing room state and room ID
|
|
127
182
|
*/
|
|
128
|
-
createRoom(initialStorage?: object, size?: number): {
|
|
183
|
+
createRoom(initialStorage?: object | null, size?: number): {
|
|
129
184
|
state: CRDTState;
|
|
130
185
|
id: string;
|
|
131
186
|
};
|
|
@@ -115,29 +115,34 @@ 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
|
+
/**
|
|
127
|
+
* Get uncloned property store
|
|
128
|
+
* @returns {Record<string, any>} Object with key value pairs
|
|
129
|
+
*/
|
|
130
|
+
get rawPropertyStore(): Record<string, any>;
|
|
126
131
|
/**
|
|
127
132
|
* Check for changes in the local property store
|
|
128
|
-
* @returns {boolean}
|
|
133
|
+
* @returns {boolean} Check if any values changed going from the last to the current property update
|
|
129
134
|
*/
|
|
130
135
|
get didPropertiesChange(): boolean;
|
|
131
136
|
/**
|
|
132
137
|
* Export state (can be imported using importState, converts the maps to arrays for serialization)
|
|
133
|
-
* @returns {CRDTState}
|
|
138
|
+
* @returns {CRDTState} State
|
|
134
139
|
*/
|
|
135
140
|
get state(): CRDTState;
|
|
136
141
|
}
|
|
137
142
|
/**
|
|
138
143
|
* Get the operation details from a property update
|
|
139
144
|
* @param {PropertyUpdate} update - Property update
|
|
140
|
-
* @returns {{key: string | undefined, type: PropertyUpdateType | undefined, value: *, secondValue: *}}
|
|
145
|
+
* @returns {{key: string | undefined, type: PropertyUpdateType | undefined, value: *, secondValue: *}} Operation details
|
|
141
146
|
*/
|
|
142
147
|
export declare function getUpdateDetails(update: PropertyUpdate): {
|
|
143
148
|
key: string | undefined;
|