playsocketjs 3.1.2 → 4.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 +244 -142
- package/package.json +24 -13
- package/src/client/client.js +555 -0
- package/src/server/server.js +761 -0
- package/src/server/ws-extensions.d.ts +13 -0
- package/src/universal/constants.js +7 -0
- package/src/universal/crdtManager.js +498 -0
- package/types/client/client.d.ts +97 -0
- package/types/server/server.d.ts +143 -0
- package/types/universal/constants.d.ts +7 -0
- package/types/universal/crdtManager.d.ts +147 -0
- package/dist/playsocket-client.js +0 -2614
- package/dist/playsocket-server.js +0 -2816
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import CRDTManager from "../universal/crdtManager.js";
|
|
2
|
+
export declare const RECONNECT_GRACE_PERIOD = 5000;
|
|
3
|
+
export type HttpServer = import("node:http").Server;
|
|
4
|
+
export type PropertyUpdateType = import("../universal/crdtManager.js").PropertyUpdateType;
|
|
5
|
+
export type PropertyUpdate = import("../universal/crdtManager.js").PropertyUpdate;
|
|
6
|
+
export type CRDTState = import("../universal/crdtManager.js").CRDTState;
|
|
7
|
+
export type WebSocket = import("ws").WebSocket;
|
|
8
|
+
export type ClientMessage = {
|
|
9
|
+
/**
|
|
10
|
+
* - Message type
|
|
11
|
+
*/
|
|
12
|
+
type: string;
|
|
13
|
+
};
|
|
14
|
+
export type RateLimit = {
|
|
15
|
+
points: number;
|
|
16
|
+
lastReset: number;
|
|
17
|
+
};
|
|
18
|
+
export type Room = {
|
|
19
|
+
/**
|
|
20
|
+
* - Client IDs of the room participants
|
|
21
|
+
*/
|
|
22
|
+
participants: string[];
|
|
23
|
+
/**
|
|
24
|
+
* - Client ID of the room host ("server" for server-owned rooms, null if the room currently has no host)
|
|
25
|
+
*/
|
|
26
|
+
host: string | null;
|
|
27
|
+
/**
|
|
28
|
+
* - Max. number of participants
|
|
29
|
+
*/
|
|
30
|
+
size: number;
|
|
31
|
+
/**
|
|
32
|
+
* - CRDT manager holding the room storage
|
|
33
|
+
*/
|
|
34
|
+
crdtManager: CRDTManager;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {import("node:http").Server} HttpServer
|
|
38
|
+
* @typedef {import("../universal/crdtManager.js").PropertyUpdateType} PropertyUpdateType
|
|
39
|
+
* @typedef {import("../universal/crdtManager.js").PropertyUpdate} PropertyUpdate
|
|
40
|
+
* @typedef {import("../universal/crdtManager.js").CRDTState} CRDTState
|
|
41
|
+
*/
|
|
42
|
+
/** @typedef {import("ws").WebSocket} WebSocket */ /**
|
|
43
|
+
* @typedef {object} ClientMessage
|
|
44
|
+
* @property {string} type - Message type
|
|
45
|
+
*/
|
|
46
|
+
/** @typedef {{ points: number, lastReset: number }} RateLimit */
|
|
47
|
+
/**
|
|
48
|
+
* @typedef {object} Room
|
|
49
|
+
* @property {string[]} participants - Client IDs of the room participants
|
|
50
|
+
* @property {string | null} host - Client ID of the room host ("server" for server-owned rooms, null if the room currently has no host)
|
|
51
|
+
* @property {number} size - Max. number of participants
|
|
52
|
+
* @property {CRDTManager} crdtManager - CRDT manager holding the room storage
|
|
53
|
+
*/
|
|
54
|
+
/**
|
|
55
|
+
* PlaySocket Server
|
|
56
|
+
*/
|
|
57
|
+
export default class PlaySocketServer {
|
|
58
|
+
#private;
|
|
59
|
+
/**
|
|
60
|
+
* Create a new PlaySocketServer instance
|
|
61
|
+
* @param {object} options - Server configuration options
|
|
62
|
+
* @param {HttpServer} [options.server] - Existing http server
|
|
63
|
+
* @param {number} [options.port=3000] - Port to listen on (if no server provided)
|
|
64
|
+
* @param {string} [options.path='/'] - WebSocket endpoint path
|
|
65
|
+
* @param {boolean} [options.debug=false] - Enable debug logging
|
|
66
|
+
* @param {number} [options.rateLimit=20] - Maximum number of operations per second per client
|
|
67
|
+
* @param {import("ws").ServerOptions["verifyClient"]} [options.verifyClient] - Optional callback to verify client connections before upgrade. Receives (info, callback) where info contains { req, origin } and callback is (verified, code?, message?) => void
|
|
68
|
+
*/
|
|
69
|
+
constructor(options?: {
|
|
70
|
+
server?: HttpServer;
|
|
71
|
+
port?: number;
|
|
72
|
+
path?: string;
|
|
73
|
+
debug?: boolean;
|
|
74
|
+
rateLimit?: number;
|
|
75
|
+
verifyClient?: import("ws").ServerOptions["verifyClient"];
|
|
76
|
+
});
|
|
77
|
+
/**
|
|
78
|
+
* Register an event callback
|
|
79
|
+
* @param {string} event - Event name
|
|
80
|
+
* @param {Function} callback - Callback function
|
|
81
|
+
* @returns {Function} - Unsubscribe
|
|
82
|
+
*/
|
|
83
|
+
onEvent(event: string, callback: Function): Function;
|
|
84
|
+
/**
|
|
85
|
+
* Kick a client from the server
|
|
86
|
+
* @param {string} clientId - Client ID
|
|
87
|
+
* @param {string} [reason] - Optional reason
|
|
88
|
+
*/
|
|
89
|
+
kick(clientId: string, reason?: string): void;
|
|
90
|
+
/**
|
|
91
|
+
* Move client into a different room
|
|
92
|
+
* @param {string} clientId - Client ID
|
|
93
|
+
* @param {string} roomId - Room ID (Target)
|
|
94
|
+
*/
|
|
95
|
+
move(clientId: string, roomId: string): void;
|
|
96
|
+
/**
|
|
97
|
+
* Get snapshot of a room's storage
|
|
98
|
+
* @param {string} roomId - ID of the room to get the storage from
|
|
99
|
+
* @returns {Record<string, any> | undefined} - Storage object or undefined if the room doesn't exist
|
|
100
|
+
*/
|
|
101
|
+
getRoomStorage(roomId: string): Record<string, any> | undefined;
|
|
102
|
+
/**
|
|
103
|
+
* Get the operation details from a storage update (e.g. in the "storageUpdateRequested" event)
|
|
104
|
+
* @param {PropertyUpdate} update - Property update
|
|
105
|
+
* @returns {{key: string | undefined, type: PropertyUpdateType | undefined, value: *, secondValue: *}} - Operation details
|
|
106
|
+
*/
|
|
107
|
+
getUpdateDetails(update: PropertyUpdate): {
|
|
108
|
+
key: string | undefined;
|
|
109
|
+
type: PropertyUpdateType | undefined;
|
|
110
|
+
value: any;
|
|
111
|
+
secondValue: any;
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Update a value in a room's storage
|
|
115
|
+
* @param {string} roomId - Room ID
|
|
116
|
+
* @param {string} key - Storage key
|
|
117
|
+
* @param {PropertyUpdateType} type - Operation type
|
|
118
|
+
* @param {*} value - Value
|
|
119
|
+
* @param {*} [secondValue] - Second value (needed for some operations)
|
|
120
|
+
*/
|
|
121
|
+
updateRoomStorage(roomId: string, key: string, type: PropertyUpdateType, value: any, secondValue?: any): void;
|
|
122
|
+
/**
|
|
123
|
+
* Create a room
|
|
124
|
+
* @param {object} [initialStorage] - Optional initial storage object
|
|
125
|
+
* @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
|
+
* @returns {{ state: CRDTState, id: string }} Object containing room state and room ID
|
|
128
|
+
*/
|
|
129
|
+
createRoom(initialStorage?: object, size?: number, host?: string): {
|
|
130
|
+
state: CRDTState;
|
|
131
|
+
id: string;
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* Destroy a room
|
|
135
|
+
* @param {string} roomId - Room ID
|
|
136
|
+
*/
|
|
137
|
+
destroyRoom(roomId: string): void;
|
|
138
|
+
/**
|
|
139
|
+
* Close all client connections, then close the websocket and http server
|
|
140
|
+
*/
|
|
141
|
+
stop(): void;
|
|
142
|
+
get rooms(): Record<string, Room>;
|
|
143
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Heartbeat ping interval in milliseconds
|
|
3
|
+
* Also used as the minimum age for CRDT garbage collection - operations are
|
|
4
|
+
* retained for at least one heartbeat cycle to ensure even slow clients can never
|
|
5
|
+
* be desynced as a result of gc
|
|
6
|
+
*/
|
|
7
|
+
export declare const HEARTBEAT_INTERVAL = 5000;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
export type PropertyUpdateType = "set" | "number-increment" | "array-add" | "array-add-unique" | "array-remove-matching" | "array-update-matching" | "object-set-key" | "object-remove-key";
|
|
2
|
+
export type VectorClockEntries = [string, number][];
|
|
3
|
+
export type OperationData = {
|
|
4
|
+
/**
|
|
5
|
+
* - Operation type
|
|
6
|
+
*/
|
|
7
|
+
type: PropertyUpdateType;
|
|
8
|
+
/**
|
|
9
|
+
* - Value
|
|
10
|
+
*/
|
|
11
|
+
value: any;
|
|
12
|
+
/**
|
|
13
|
+
* - Second value (needed for some operations)
|
|
14
|
+
*/
|
|
15
|
+
secondValue?: any;
|
|
16
|
+
};
|
|
17
|
+
export type Operation = {
|
|
18
|
+
/**
|
|
19
|
+
* - Operation data
|
|
20
|
+
*/
|
|
21
|
+
data: OperationData;
|
|
22
|
+
/**
|
|
23
|
+
* - Vector clock entries at creation time
|
|
24
|
+
*/
|
|
25
|
+
vectorClock: VectorClockEntries;
|
|
26
|
+
/**
|
|
27
|
+
* - ID of the replica that created the operation
|
|
28
|
+
*/
|
|
29
|
+
source: string;
|
|
30
|
+
/**
|
|
31
|
+
* - Unique operation ID
|
|
32
|
+
*/
|
|
33
|
+
uuid: string;
|
|
34
|
+
};
|
|
35
|
+
export type PropertyUpdate = {
|
|
36
|
+
/**
|
|
37
|
+
* - Storage key
|
|
38
|
+
*/
|
|
39
|
+
key: string;
|
|
40
|
+
/**
|
|
41
|
+
* - New operation
|
|
42
|
+
*/
|
|
43
|
+
operation: Operation;
|
|
44
|
+
/**
|
|
45
|
+
* - Vector clock entries of the replica
|
|
46
|
+
*/
|
|
47
|
+
vectorClock: VectorClockEntries;
|
|
48
|
+
};
|
|
49
|
+
export type CRDTState = {
|
|
50
|
+
/**
|
|
51
|
+
* - Operations per storage key
|
|
52
|
+
*/
|
|
53
|
+
keyOperations: [string, Operation[]][];
|
|
54
|
+
/**
|
|
55
|
+
* - Vector clock entries
|
|
56
|
+
*/
|
|
57
|
+
vectorClock: VectorClockEntries;
|
|
58
|
+
};
|
|
59
|
+
/** @typedef {"set" | "number-increment" | "array-add" | "array-add-unique" | "array-remove-matching" | "array-update-matching" | "object-set-key" | "object-remove-key"} PropertyUpdateType */
|
|
60
|
+
/** @typedef {[string, number][]} VectorClockEntries */
|
|
61
|
+
/**
|
|
62
|
+
* @typedef {object} OperationData
|
|
63
|
+
* @property {PropertyUpdateType} type - Operation type
|
|
64
|
+
* @property {*} value - Value
|
|
65
|
+
* @property {*} [secondValue] - Second value (needed for some operations)
|
|
66
|
+
*/
|
|
67
|
+
/**
|
|
68
|
+
* @typedef {object} Operation
|
|
69
|
+
* @property {OperationData} data - Operation data
|
|
70
|
+
* @property {VectorClockEntries} vectorClock - Vector clock entries at creation time
|
|
71
|
+
* @property {string} source - ID of the replica that created the operation
|
|
72
|
+
* @property {string} uuid - Unique operation ID
|
|
73
|
+
*/
|
|
74
|
+
/**
|
|
75
|
+
* @typedef {object} PropertyUpdate
|
|
76
|
+
* @property {string} key - Storage key
|
|
77
|
+
* @property {Operation} operation - New operation
|
|
78
|
+
* @property {VectorClockEntries} vectorClock - Vector clock entries of the replica
|
|
79
|
+
*/
|
|
80
|
+
/**
|
|
81
|
+
* @typedef {object} CRDTState
|
|
82
|
+
* @property {[string, Operation[]][]} keyOperations - Operations per storage key
|
|
83
|
+
* @property {VectorClockEntries} vectorClock - Vector clock entries
|
|
84
|
+
*/
|
|
85
|
+
/**
|
|
86
|
+
* CRDT Manager
|
|
87
|
+
* Custom conflict-free replicated data type system with vector clocks
|
|
88
|
+
*/
|
|
89
|
+
export default class CRDTManager {
|
|
90
|
+
#private;
|
|
91
|
+
/**
|
|
92
|
+
* Create a new instance
|
|
93
|
+
* @param {boolean} [debug=false] - Enable debug logging
|
|
94
|
+
*/
|
|
95
|
+
constructor(debug?: boolean);
|
|
96
|
+
/**
|
|
97
|
+
* Import the entire state of the CRDT manager (this overwrites the old state)
|
|
98
|
+
* @param {CRDTState} state - Full state of the CRDT manager
|
|
99
|
+
*/
|
|
100
|
+
importState(state: CRDTState): void;
|
|
101
|
+
/**
|
|
102
|
+
* Import property update
|
|
103
|
+
* @param {PropertyUpdate} update - Property update
|
|
104
|
+
*/
|
|
105
|
+
importPropertyUpdate(update: PropertyUpdate): void;
|
|
106
|
+
/**
|
|
107
|
+
* Revert a local property update by removing the operation and recomputing the affected key
|
|
108
|
+
* Should NOT be used on the server or for synced ops, only for reverting local optimistic updates
|
|
109
|
+
* @param {PropertyUpdate} update - Property update
|
|
110
|
+
*/
|
|
111
|
+
revertPropertyUpdate(update: PropertyUpdate): void;
|
|
112
|
+
/**
|
|
113
|
+
* Update a property
|
|
114
|
+
* @param {string} key - Storage key
|
|
115
|
+
* @param {PropertyUpdateType} type - Operation type
|
|
116
|
+
* @param {*} value - Value
|
|
117
|
+
* @param {*} [secondValue] - Second value (needed for some operations)
|
|
118
|
+
* @returns {PropertyUpdate | undefined} - Returns the property update
|
|
119
|
+
*/
|
|
120
|
+
updateProperty(key: string, type: PropertyUpdateType, value: any, secondValue?: any): PropertyUpdate | undefined;
|
|
121
|
+
/**
|
|
122
|
+
* Get property store
|
|
123
|
+
* @returns {Record<string, any>} - Object with key value pairs
|
|
124
|
+
*/
|
|
125
|
+
get propertyStore(): Record<string, any>;
|
|
126
|
+
/**
|
|
127
|
+
* Check for changes in the local property store
|
|
128
|
+
* @returns {boolean} - Check if any values changed going from the last to the current property update
|
|
129
|
+
*/
|
|
130
|
+
get didPropertiesChange(): boolean;
|
|
131
|
+
/**
|
|
132
|
+
* Export state (can be imported using importState, converts the maps to arrays for serialization)
|
|
133
|
+
* @returns {CRDTState} - State
|
|
134
|
+
*/
|
|
135
|
+
get state(): CRDTState;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Get the operation details from a property update
|
|
139
|
+
* @param {PropertyUpdate} update - Property update
|
|
140
|
+
* @returns {{key: string | undefined, type: PropertyUpdateType | undefined, value: *, secondValue: *}} - Operation details
|
|
141
|
+
*/
|
|
142
|
+
export declare function getUpdateDetails(update: PropertyUpdate): {
|
|
143
|
+
key: string | undefined;
|
|
144
|
+
type: PropertyUpdateType | undefined;
|
|
145
|
+
value: any;
|
|
146
|
+
secondValue: any;
|
|
147
|
+
};
|