playsocketjs 3.1.2 → 4.0.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 +243 -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 +142 -0
- package/types/universal/constants.d.ts +7 -0
- package/types/universal/crdtManager.d.ts +146 -0
- package/dist/playsocket-client.js +0 -2614
- package/dist/playsocket-server.js +0 -2816
package/README.md
CHANGED
|
@@ -1,278 +1,379 @@
|
|
|
1
|
-
# PlaySocket
|
|
1
|
+
# PlaySocket
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An optimistic-first WebSocket synchronization library. Built for creating multiplayer games & collaborative experiences with reactive web frameworks.
|
|
4
4
|
|
|
5
5
|
## Why use PlaySocket?
|
|
6
6
|
|
|
7
|
-
PlaySocket
|
|
7
|
+
PlaySocket makes developing shared experiences a breeze:
|
|
8
8
|
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
9
|
+
- **Optimistic-first**: Updates apply locally right away and merge conflict-free across clients. Calling `updateStorage()` triggers the `storageUpdated` event instantly, without waiting for a server roundtrip.
|
|
10
|
+
- **Fast prototyping**: No backend code beyond init is required, though server-authoritative validation and behavior are supported for production apps.
|
|
11
|
+
- **Built for reactivity**: Assign the synced storage to a reactive variable via a callback, ideal for React, Svelte & co.
|
|
12
|
+
- **Resilient & secure**: Automatic reconnection handling & strict rate-limiting.
|
|
13
|
+
- **Lightweight**: Uses WebSockets, has few dependencies, and utilizes `MessagePack` for maximum efficiency.
|
|
14
|
+
|
|
15
|
+
<!-- docs-start -->
|
|
13
16
|
|
|
14
17
|
## Installation
|
|
15
18
|
|
|
19
|
+
Install PlaySocket with your package manager of choice to get started.
|
|
20
|
+
|
|
16
21
|
```bash
|
|
17
22
|
npm install playsocketjs
|
|
18
23
|
```
|
|
19
24
|
|
|
20
|
-
##
|
|
25
|
+
## PlaySocket Client
|
|
26
|
+
|
|
27
|
+
The client-side part of PlaySocket.
|
|
28
|
+
|
|
29
|
+
### Examples
|
|
21
30
|
|
|
22
|
-
|
|
31
|
+
> [!NOTE]
|
|
32
|
+
> In production, you should **always try...catch** promises such as `socket.init()` as they can reject. These examples omit the error handling to keep them simple.
|
|
23
33
|
|
|
24
34
|
Initializing the client:
|
|
25
35
|
```javascript
|
|
26
|
-
import PlaySocket from
|
|
36
|
+
import PlaySocket from "playsocketjs";
|
|
27
37
|
|
|
28
38
|
// Create a new instance
|
|
29
|
-
|
|
30
|
-
|
|
39
|
+
// Omit the ID to let the server pick one
|
|
40
|
+
const socket = new PlaySocket("unique-client-id", {
|
|
41
|
+
endpoint: "wss://example.com/socket"
|
|
31
42
|
});
|
|
32
43
|
|
|
33
|
-
// Set up event handlers
|
|
34
|
-
socket.onEvent(
|
|
35
|
-
socket.onEvent(
|
|
44
|
+
// Set up the event handlers you need
|
|
45
|
+
socket.onEvent("status", status => console.log(status));
|
|
46
|
+
socket.onEvent("error", error => console.error(error));
|
|
47
|
+
...
|
|
36
48
|
|
|
37
|
-
const clientId = await socket.init(); //
|
|
49
|
+
const clientId = await socket.init(); // Connect
|
|
38
50
|
```
|
|
39
51
|
|
|
40
52
|
Creating a room:
|
|
41
53
|
```javascript
|
|
42
54
|
// Create a new room
|
|
43
55
|
const roomId = await socket.createRoom();
|
|
56
|
+
```
|
|
44
57
|
|
|
58
|
+
```javascript
|
|
45
59
|
// Optionally, with initial storage
|
|
46
60
|
const roomId = await socket.createRoom({
|
|
47
|
-
players: ["
|
|
61
|
+
players: [{ name: "Player-1", level: 35 }],
|
|
48
62
|
latestPlayer: null,
|
|
49
63
|
});
|
|
50
64
|
```
|
|
51
65
|
|
|
52
66
|
Joining a room:
|
|
53
67
|
```javascript
|
|
54
|
-
await socket.joinRoom(
|
|
68
|
+
await socket.joinRoom("room-id"); // Join an existing room
|
|
55
69
|
```
|
|
56
70
|
|
|
57
71
|
Leaving a room:
|
|
58
72
|
```javascript
|
|
59
|
-
socket.destroy(); //
|
|
73
|
+
socket.destroy(); // Destroy the instance to leave
|
|
60
74
|
```
|
|
61
75
|
|
|
62
|
-
Using the storage update event
|
|
76
|
+
Using the storage update event with reactivity:
|
|
63
77
|
```javascript
|
|
64
|
-
|
|
65
|
-
|
|
78
|
+
// Assign to useState(), $state(), reactive() etc. on update
|
|
79
|
+
const [reactiveVar, setReactiveVar] = useState({});
|
|
80
|
+
socket.onEvent("storageUpdated", storage => {
|
|
81
|
+
setReactiveVar(storage);
|
|
82
|
+
});
|
|
66
83
|
```
|
|
67
84
|
|
|
68
|
-
|
|
85
|
+
```jsx
|
|
86
|
+
<p>Players: {reactiveVar.players?.join(", ")}</p>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Interfacing with the synchronized storage:
|
|
69
90
|
```javascript
|
|
70
|
-
const currentState = socket.storage; //
|
|
71
|
-
|
|
72
|
-
socket.updateStorage(
|
|
91
|
+
const currentState = socket.storage; // Read-only access
|
|
92
|
+
|
|
93
|
+
socket.updateStorage("players", "array-add-unique", { username: "Player4", level: 2 });
|
|
94
|
+
socket.updateStorage("latestPlayer", "set", "Player4");
|
|
95
|
+
socket.updateStorage("playerInfo", "set", { date: "22-6-2026" });
|
|
96
|
+
socket.updateStorage("playerInfo", "object-set-key", "color", "red")
|
|
97
|
+
|
|
98
|
+
console.log(socket.storage.players); // Log players array
|
|
73
99
|
```
|
|
74
100
|
|
|
75
|
-
|
|
101
|
+
Sometimes it's convenient to send a traditional request to the server. For example, when you want to opt out of optimistic updates for asynchronous work, or when
|
|
102
|
+
the validation logic would be too complex otherwise:
|
|
76
103
|
```javascript
|
|
77
|
-
socket.sendRequest(
|
|
104
|
+
await socket.sendRequest("my-request-name", { fact: "You can build traditional client-server logic like this." })
|
|
78
105
|
```
|
|
79
106
|
|
|
80
|
-
|
|
107
|
+
### API
|
|
81
108
|
|
|
82
|
-
|
|
109
|
+
#### Constructor
|
|
83
110
|
|
|
84
|
-
|
|
111
|
+
Create a new PlaySocket instance with a specified ID and configuration options.
|
|
85
112
|
The ID can be set to `null` to let the server pick a unique one.
|
|
86
113
|
|
|
87
114
|
```javascript
|
|
88
115
|
new PlaySocket(id?: string, options: PlaySocketOptions)
|
|
89
116
|
```
|
|
90
117
|
|
|
91
|
-
|
|
118
|
+
| Option | Type | Required | Default | Description |
|
|
119
|
+
|--------|------|----------|---------|-------------|
|
|
120
|
+
| `endpoint` | `string` | Yes | `undefined` | WebSocket server endpoint (e.g., wss://example.com/socket). |
|
|
121
|
+
| `customData` | `object` | No | `{}` | Arbitrary data to pass to the "clientRegistered" server event. |
|
|
122
|
+
| `debug` | `boolean` | No | `false` | Set to true to enable extra logging. |
|
|
92
123
|
|
|
93
|
-
|
|
94
|
-
|--------|------|---------|-------------|
|
|
95
|
-
| `endpoint` | string | `undefined` | WebSocket server endpoint (e.g., 'wss://example.com/socket') (required) |
|
|
96
|
-
| `customData` | object | `{}` | Arbitrary data to pass to the "clientRegistered" server event |
|
|
97
|
-
| `debug` | boolean | `false` | Set to true to enable extra logging |
|
|
124
|
+
#### Methods
|
|
98
125
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
| Method | Parameters | Return type | Description |
|
|
126
|
+
| Name | Parameters | Return type | Description |
|
|
102
127
|
|--------|------------|-------------|-------------|
|
|
103
|
-
| `init()` | - | `Promise<string>` | Initialize the WebSocket connection
|
|
104
|
-
| `createRoom()` | `initialStorage?: object, size?: number` | `Promise<string>` | Create a new room
|
|
105
|
-
| `joinRoom()` | `roomId: string` | `Promise<void>` | Join an existing room |
|
|
106
|
-
| `destroy()` | - | `void` |
|
|
107
|
-
| `updateStorage()` | `key: string, type:
|
|
108
|
-
| `sendRequest()` | `name: string, data?: any` | `void
|
|
109
|
-
| `onEvent()` | `event: string, callback: Function` | `void` | Register an event callback |
|
|
128
|
+
| `init()` | - | `Promise<string>` | Initialize the WebSocket connection, resolves with the client ID. |
|
|
129
|
+
| `createRoom()` | `initialStorage?: object, size?: number` | `Promise<string>` | Create a new room, resolves with the room ID. Max. 500 participants. |
|
|
130
|
+
| `joinRoom()` | `roomId: string` | `Promise<void>` | Join an existing room. |
|
|
131
|
+
| `destroy()` | - | `void` | Leave room, close the connection, and destroy the instance. |
|
|
132
|
+
| `updateStorage()` | `key: string, type: string, value: any, secondValue?: any` | `void` | Update a key in the shared storage. |
|
|
133
|
+
| `sendRequest()` | `name: string, data?: any` | `Promise<void>` | Send a request to the server with optional attached data. |
|
|
134
|
+
| `onEvent()` | `event: string, callback: Function` | `() => void` | Register an event callback. Returns unsubscribe function. |
|
|
110
135
|
|
|
111
|
-
|
|
136
|
+
#### Events
|
|
112
137
|
|
|
113
138
|
| Event | Callback parameter | Description |
|
|
114
139
|
|-------|-------------------|-------------|
|
|
115
|
-
| `status` | `status: string` | Connection status
|
|
116
|
-
| `error` | `error: string` | Error
|
|
117
|
-
| `moved` | `roomId: string` | Moved to different room |
|
|
118
|
-
| `instanceDestroyed` | - |
|
|
119
|
-
| `storageUpdated` | `storage: object` | Storage state
|
|
120
|
-
| `hostMigrated` | `roomId: string` | Host
|
|
121
|
-
| `clientJoined` | `clientId: string` | New client joined the room |
|
|
122
|
-
| `clientLeft` | `clientId: string, roomId?: string` |
|
|
140
|
+
| `status` | `status: string` | Connection or room status updated. |
|
|
141
|
+
| `error` | `error: string` | Error occurred. |
|
|
142
|
+
| `moved` | `roomId: string` | Moved to different room. |
|
|
143
|
+
| `instanceDestroyed` | - | Instance destroyed through `destroy()` or fatal error. |
|
|
144
|
+
| `storageUpdated` | `storage: object` | Storage state changed. Does not trigger on no-op updates. |
|
|
145
|
+
| `hostMigrated` | `roomId: string` | Host was changed. |
|
|
146
|
+
| `clientJoined` | `clientId: string` | New client joined the room. |
|
|
147
|
+
| `clientLeft` | `clientId: string, roomId?: string` | A client left the room. |
|
|
123
148
|
|
|
124
|
-
|
|
149
|
+
#### Properties
|
|
125
150
|
|
|
126
151
|
| Property | Type | Description |
|
|
127
152
|
|----------|------|-------------|
|
|
128
|
-
| `id` | string | Client's unique
|
|
129
|
-
| `isHost` | boolean |
|
|
130
|
-
| `participantCount` | number | Number of active client connections in room
|
|
131
|
-
| `storage` | object | Retrieve storage object |
|
|
132
|
-
|
|
133
|
-
|
|
153
|
+
| `id` | `string` | Client's unique ID. |
|
|
154
|
+
| `isHost` | `boolean` | Whether this client is currently assigned the host role. |
|
|
155
|
+
| `participantCount` | `number` | Number of active client connections in room. |
|
|
156
|
+
| `storage` | `object` | Retrieve the storage object. |
|
|
134
157
|
|
|
135
|
-
|
|
158
|
+
## PlaySocket Server
|
|
136
159
|
|
|
137
|
-
|
|
160
|
+
The server-side part of PlaySocket.
|
|
138
161
|
|
|
139
|
-
|
|
162
|
+
### Examples
|
|
140
163
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
```bash
|
|
144
|
-
npm install playsocketjs ws
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
## Usage examples
|
|
148
|
-
|
|
149
|
-
Here are usage examples for a standalone server and an Express.js application.
|
|
150
|
-
|
|
151
|
-
### Standalone server
|
|
164
|
+
Using PlaySocket as a standalone server:
|
|
152
165
|
|
|
153
166
|
```javascript
|
|
154
|
-
import PlaySocketServer from
|
|
167
|
+
import PlaySocketServer from "playsocketjs/server";
|
|
155
168
|
|
|
156
|
-
|
|
169
|
+
// Path defaults to "/"
|
|
170
|
+
const server = new PlaySocketServer();
|
|
157
171
|
|
|
158
|
-
// Gracefully disconnect all clients and close the server (optional)
|
|
159
172
|
function shutdown() {
|
|
160
|
-
server.stop();
|
|
173
|
+
server.stop(); // Gracefully disconnect all clients
|
|
161
174
|
process.exit(0);
|
|
162
175
|
}
|
|
163
176
|
|
|
164
|
-
|
|
165
|
-
process.on(
|
|
166
|
-
process.on('SIGTERM', shutdown);
|
|
177
|
+
process.on("SIGINT", shutdown);
|
|
178
|
+
process.on("SIGTERM", shutdown);
|
|
167
179
|
```
|
|
168
180
|
|
|
169
|
-
|
|
181
|
+
Using PlaySocket together with Express.js (or other backend frameworks):
|
|
170
182
|
|
|
171
183
|
```javascript
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
184
|
+
import express from "express";
|
|
185
|
+
import http from "node:http";
|
|
186
|
+
import PlaySocketServer from "playsocketjs/server";
|
|
175
187
|
|
|
176
188
|
const app = express();
|
|
177
189
|
const httpServer = http.createServer(app);
|
|
178
190
|
|
|
179
|
-
// Create PlaySocket server with
|
|
191
|
+
// Create PlaySocket server with existing HTTP server
|
|
180
192
|
const playSocketServer = new PlaySocketServer({
|
|
181
193
|
server: httpServer,
|
|
182
|
-
path:
|
|
194
|
+
path: "/socket"
|
|
183
195
|
});
|
|
184
196
|
|
|
185
197
|
// Start the server
|
|
186
198
|
httpServer.listen(3000, () => {
|
|
187
|
-
console.log(
|
|
199
|
+
console.log("Server running on port 3000");
|
|
188
200
|
});
|
|
189
201
|
|
|
190
|
-
// Gracefully disconnect all clients and close the server (recommended)
|
|
191
202
|
function shutdown() {
|
|
192
|
-
|
|
203
|
+
playSocketServer.stop(); // Gracefully disconnect clients
|
|
193
204
|
process.exit(0);
|
|
194
205
|
}
|
|
195
206
|
|
|
196
|
-
|
|
197
|
-
process.on(
|
|
198
|
-
process.on('SIGTERM', shutdown);
|
|
207
|
+
process.on("SIGINT", shutdown);
|
|
208
|
+
process.on("SIGTERM", shutdown);
|
|
199
209
|
```
|
|
200
210
|
|
|
201
|
-
|
|
211
|
+
Validating an incoming storage update:
|
|
202
212
|
|
|
203
|
-
|
|
213
|
+
```javascript
|
|
214
|
+
const server = new PlaySocketServer();
|
|
204
215
|
|
|
205
|
-
|
|
216
|
+
server.onEvent("storageUpdateRequested", ({ roomId, clientId, update, storage }) => {
|
|
217
|
+
// Block updates on all keys except for "players" and "chats"
|
|
218
|
+
if (!["players", "chats"].includes(update.key)) return false;
|
|
219
|
+
|
|
220
|
+
if (update.key === "chats") {
|
|
221
|
+
const { type, value, secondValue } = server.getUpdateDetails(update);
|
|
222
|
+
if (type !== "array-add") return false; // Only allow adding chats
|
|
223
|
+
if (typeof value !== "string") return false; // Only allow strings
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Handling an incoming request:
|
|
206
229
|
|
|
207
230
|
```javascript
|
|
208
|
-
new
|
|
231
|
+
const server = new PlaySocketServer();
|
|
232
|
+
|
|
233
|
+
server.onEvent("requestReceived", async ({ roomId, clientId, name, data }) => {
|
|
234
|
+
if (name === "add-player-request") {
|
|
235
|
+
const players = server.getRoomStorage(roomId)?.players || [];
|
|
236
|
+
|
|
237
|
+
// Returning false or string rejects sendRequest() on the client
|
|
238
|
+
if (players.find(p => p.id === clientId)) return "Player already added!";
|
|
239
|
+
|
|
240
|
+
server.updateRoomStorage(roomId, "players", "array-add", { id: clientId, timestamp: Date.now() });
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Combining PlaySocket with a custom authentication system:
|
|
246
|
+
|
|
247
|
+
```javascript
|
|
248
|
+
const authedClients = [];
|
|
249
|
+
const server = new PlaySocketServer();
|
|
250
|
+
|
|
251
|
+
server.onEvent("clientRegistrationRequested", async (clientId, data) => {
|
|
252
|
+
try {
|
|
253
|
+
// Your custom auth logic...
|
|
254
|
+
// For example, data could contain a token
|
|
255
|
+
authedClients.push(clientId);
|
|
256
|
+
} catch (error) {
|
|
257
|
+
return "Error occurred during auth."; // Blocks the registration
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
server.onEvent("clientDisconnected", async (clientId) => {
|
|
262
|
+
const removeIndex = authedClients.indexOf(clientId);
|
|
263
|
+
if (removeIndex !== -1) authedClients.splice(removeIndex, 1);
|
|
264
|
+
});
|
|
265
|
+
|
|
209
266
|
```
|
|
210
267
|
|
|
211
|
-
###
|
|
268
|
+
### API
|
|
212
269
|
|
|
213
|
-
|
|
214
|
-
|--------|------|---------|-------------|
|
|
215
|
-
| `port` | number | 3000 | Port to listen on (used only if no server provided) |
|
|
216
|
-
| `path` | string | '/' | WebSocket endpoint path |
|
|
217
|
-
| `server` | http.Server | - | Existing http server (optional) |
|
|
218
|
-
| `rateLimit` | number | 20 | Adjust the messages/second rate limit |
|
|
219
|
-
| `debug` | boolean | false | Set to true to enable extra logging |
|
|
220
|
-
| `verifyClient` | function | - | Optional callback to verify connections before WebSocket upgrade |
|
|
270
|
+
#### Constructor
|
|
221
271
|
|
|
222
|
-
|
|
272
|
+
Create a new PlaySocket Server instance with configuration options.
|
|
273
|
+
|
|
274
|
+
```javascript
|
|
275
|
+
new PlaySocketServer(options?: PlaySocketServerOptions)
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
| Option | Type | Required | Default | Description |
|
|
279
|
+
|--------|------|----------|---------|-------------|
|
|
280
|
+
| `port` | `number` | No | 3000 | Port to listen on (used only if no server provided). |
|
|
281
|
+
| `path` | `string` | No | "/" | WebSocket endpoint path. |
|
|
282
|
+
| `server` | `http.Server` | No | - | Existing http server. |
|
|
283
|
+
| `rateLimit` | `number` | No | 20 | Messages/second rate limit. |
|
|
284
|
+
| `debug` | `boolean` | No | false | Enable debug logging. |
|
|
285
|
+
| `verifyClient` | `function` | No | - | Callback to verify connections before WebSocket upgrade. |
|
|
223
286
|
|
|
224
287
|
The `verifyClient` option allows you to implement custom connection verification logic, such as rate limiting, before the WebSocket handshake completes.
|
|
225
288
|
|
|
226
289
|
```javascript
|
|
227
290
|
const server = new PlaySocketServer({
|
|
228
291
|
server: httpServer,
|
|
229
|
-
path:
|
|
292
|
+
path: "/socket",
|
|
230
293
|
verifyClient: (info, callback) => {
|
|
231
|
-
|
|
232
|
-
const ip =
|
|
233
|
-
if (isRateLimited(ip))
|
|
294
|
+
const forwarded = info.req.headers["x-forwarded-for"];
|
|
295
|
+
const ip = forwarded ? forwarded.split(",")[0].trim() : info.req.socket.remoteAddress;
|
|
296
|
+
if (isRateLimited(ip)) {
|
|
297
|
+
return callback(false, 429, "Too Many Requests");
|
|
298
|
+
}
|
|
234
299
|
callback(true);
|
|
235
300
|
}
|
|
236
301
|
});
|
|
237
302
|
```
|
|
238
303
|
|
|
239
|
-
The callback signature is `callback(verified, code?, message?)` where `code`
|
|
304
|
+
The callback signature is `callback(verified, code?, message?)` where `code` refers to an HTTP status code and `message` to a rejection reason.
|
|
240
305
|
|
|
241
|
-
|
|
306
|
+
#### Methods
|
|
242
307
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
| `stop()` | - | `void` | Closes all active client connections, the websocket server and the underlying http server if it's standalone |
|
|
246
|
-
| `kick()` | `clientId: string, reason?: string` | `void` | Kick a client by their clientID – this will close their connection and set an error message |
|
|
247
|
-
| `move()` | `clientId: string, roomId: string` | `void` | Move a client, that is already in a room, to a different room |
|
|
248
|
-
| `onEvent()` | `event: string, callback: Function` | `void` | Register a server-side event callback |
|
|
249
|
-
| `getRoomStorage()` | `roomId: string` | `object` | Get a snapshot of the current room storage |
|
|
250
|
-
| `updateRoomStorage()` | `roomId: string, key: string, type: 'set' \| 'array-add' \| 'array-add-unique' \| 'array-remove-matching' \| 'array-update-matching', value: any, updateValue?: any` | `void` | Update a key in the shared room storage from the server |
|
|
251
|
-
| `createRoom()` | `initialStorage?: object, size?: number, host?: string` | `object` | Create a room (returns object containing room ID and state)|
|
|
252
|
-
| `destroyRoom()` | `roomId: string` | `void` | Destroy a room & kick all participants |
|
|
308
|
+
> [!IMPORTANT]
|
|
309
|
+
> Rooms created by the server default to host ID "server". If that host string is used, the room will not be auto-deleted when all participants have left and you need to take care of its lifecycle.
|
|
253
310
|
|
|
254
|
-
|
|
311
|
+
| Name | Parameters | Return type | Description |
|
|
312
|
+
|--------|------------|-------------|-------------|
|
|
313
|
+
| `stop()` | - | `void` | Closes active client connections and the underlying http server (if it's standalone). |
|
|
314
|
+
| `kick()` | `clientId: string, reason?: string` | `void` | Kick a client by their client ID. |
|
|
315
|
+
| `move()` | `clientId: string, roomId: string` | `void` | Move a client that is already in a room to a different room. |
|
|
316
|
+
| `onEvent()` | `event: string, callback: Function` | `() => void` | Register a server-side event callback. Returns unsubscribe function. |
|
|
317
|
+
| `getRoomStorage()` | `roomId: string` | `object` | Get a snapshot of the current room storage. |
|
|
318
|
+
| `getUpdateDetails()` | `update: object` | `object` | Get the details (`type`, `value` and `secondValue`) of a storage update for building validation logic. |
|
|
319
|
+
| `updateRoomStorage()` | `roomId: string, key: string, type: string, value: any, secondValue?: any` | `void` | Update a key in the shared storage of a room. |
|
|
320
|
+
| `createRoom()` | `initialStorage?: object, size?: number, host?: string` | `object` | Create a room (returns object containing room ID and state).|
|
|
321
|
+
| `destroyRoom()` | `roomId: string` | `void` | Destroy a room & kick all participants. |
|
|
322
|
+
|
|
323
|
+
#### Events
|
|
255
324
|
|
|
256
325
|
| Event | Callback parameters | Description | Return for action |
|
|
257
326
|
|-------|-------------------|-------------|--------------|
|
|
258
|
-
| `clientRegistered` | `clientId: string, customData: object` | Client registered with the server | - |
|
|
259
|
-
| `clientRegistrationRequested` | `clientId: string, customData: object` | Client
|
|
260
|
-
| `clientDisconnected` | `clientId: string` | Client disconnected
|
|
261
|
-
| `clientJoinedRoom` | `clientId: string, roomId: string` | Client joined a room | - |
|
|
262
|
-
| `clientJoinRequested` | `clientId: string, roomId: string` | Client
|
|
263
|
-
| `clientLeftRoom` | `clientId: string, roomId: string` | Client left a room | - |
|
|
264
|
-
| `roomCreated` | `roomId: string` |
|
|
265
|
-
| `roomDestroyed` | `roomId: string` | Room was destroyed
|
|
266
|
-
| `roomCreationRequested` | `{clientId: string, initialStorage: object}` |
|
|
267
|
-
| `storageUpdated` | `{clientId: string, roomId: string, update: object, storage: object}` | Room storage
|
|
268
|
-
| `storageUpdateRequested` | `{clientId: string, roomId: string, update: object, storage: object}` |
|
|
269
|
-
| `requestReceived` | `{clientId: string, roomId?: string,
|
|
270
|
-
|
|
271
|
-
|
|
327
|
+
| `clientRegistered` | `clientId: string, customData: object` | Client registered with the server. | - |
|
|
328
|
+
| `clientRegistrationRequested` | `clientId: string, customData: object` | Client requested to register. | Return `false` or rejection reason `string` to block. |
|
|
329
|
+
| `clientDisconnected` | `clientId: string` | Client disconnected. | - |
|
|
330
|
+
| `clientJoinedRoom` | `clientId: string, roomId: string` | Client joined a room. | - |
|
|
331
|
+
| `clientJoinRequested` | `clientId: string, roomId: string` | Client requested to join a room. | Return `false` or rejection reason `string` to block. |
|
|
332
|
+
| `clientLeftRoom` | `clientId: string, roomId: string` | Client left a room. | - |
|
|
333
|
+
| `roomCreated` | `roomId: string` | Room was created. | - |
|
|
334
|
+
| `roomDestroyed` | `roomId: string` | Room was destroyed. | - |
|
|
335
|
+
| `roomCreationRequested` | `{clientId: string, initialStorage: object}` | Client requested to create room. | Return `object` to override initial storage, `false` or rejection reason `string` to block. |
|
|
336
|
+
| `storageUpdated` | `{clientId: string, roomId: string, update: object, storage: object}` | Room storage updated. | - |
|
|
337
|
+
| `storageUpdateRequested` | `{clientId: string, roomId: string, update: object, storage: object}` | Client requested storage update. | Return `false` or rejection reason `string` to block the update. Callback must be synchronous. |
|
|
338
|
+
| `requestReceived` | `{clientId: string, roomId?: string, name: string, data?: any}` | Request from client. | Return `false` or rejection reason `string` to block. |
|
|
339
|
+
|
|
340
|
+
When multiple callbacks are registered for an event, all of them run, and for events that respond to a return value, the first non-null return value is used. A callback that throws counts as returning `false`.
|
|
341
|
+
|
|
342
|
+
#### Properties
|
|
272
343
|
|
|
273
344
|
| Property | Type | Description |
|
|
274
345
|
|----------|------|-------------|
|
|
275
|
-
| `rooms` | object | Retrieve the rooms object |
|
|
346
|
+
| `rooms` | `object` | Retrieve the rooms object. |
|
|
347
|
+
|
|
348
|
+
## Storage
|
|
349
|
+
|
|
350
|
+
Both `updateStorage()` and `updateRoomStorage()` work the same way. The only difference is that the latter takes `roomId` as the first argument and runs on the server. There's a limit of 100 storage keys.
|
|
351
|
+
|
|
352
|
+
Number, array and object operation types allow for conflict-free simultaneous updates. The set operation just replaces the property and ensures correct ordering.
|
|
353
|
+
|
|
354
|
+
For `-matching` operation types, `value` becomes the value to match, and `secondValue` the replacement. For object operations, `value` is the property key, and `secondValue` the property value.
|
|
355
|
+
|
|
356
|
+
### Operation types
|
|
357
|
+
- `set`
|
|
358
|
+
- `number-increment`
|
|
359
|
+
- `array-add`
|
|
360
|
+
- `array-add-unique`
|
|
361
|
+
- `array-update-matching`
|
|
362
|
+
- `array-remove-matching`
|
|
363
|
+
- `object-set-key`
|
|
364
|
+
- `object-remove-key`
|
|
365
|
+
|
|
366
|
+
Example for each type:
|
|
367
|
+
- `updateStorage("color", "set", "blue")`
|
|
368
|
+
- `updateStorage("score", "number-increment", 25)`
|
|
369
|
+
- `updateStorage("players", "array-add", { name: "Player2" })`
|
|
370
|
+
- `updateStorage("completedLevels", "array-add-unique", 14)`
|
|
371
|
+
- `updateStorage("names", "array-update-matching", "Leo_cool", "TheCoolerLeo")`
|
|
372
|
+
- `updateStorage("missingLevels", "array-remove-matching", 14)`
|
|
373
|
+
- `updateStorage("levelNames", "object-set-key", "evilSea", "Evil sea")`
|
|
374
|
+
- `updateStorage("levelNames", "object-remove-key", "darkOcean")`
|
|
375
|
+
|
|
376
|
+
<!-- docs-end -->
|
|
276
377
|
|
|
277
378
|
# License
|
|
278
379
|
|
package/package.json
CHANGED
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "playsocketjs",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "WebSocket wrapper for creating simple multiplayer systems with ease.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"module": "
|
|
6
|
+
"module": "./src/client/client.js",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": {
|
|
9
|
-
"
|
|
9
|
+
"types": "./types/client/client.d.ts",
|
|
10
|
+
"import": "./src/client/client.js"
|
|
10
11
|
},
|
|
11
12
|
"./server": {
|
|
12
|
-
"
|
|
13
|
+
"types": "./types/server/server.d.ts",
|
|
14
|
+
"import": "./src/server/server.js"
|
|
13
15
|
}
|
|
14
16
|
},
|
|
15
17
|
"files": [
|
|
16
|
-
"
|
|
18
|
+
"src",
|
|
19
|
+
"types"
|
|
17
20
|
],
|
|
18
21
|
"scripts": {
|
|
19
22
|
"start": "node example/example-server.js",
|
|
20
23
|
"test": "npm run build && npx playwright test --config e2e/playwright.config.js",
|
|
21
24
|
"test:headed": "npm run build && npx playwright test --config e2e/playwright.config.js --headed",
|
|
22
|
-
"build": "
|
|
23
|
-
"lint": "eslint
|
|
25
|
+
"build": "tsc -p tsconfig.declarations.json",
|
|
26
|
+
"lint": "eslint ."
|
|
24
27
|
},
|
|
25
28
|
"repository": {
|
|
26
29
|
"type": "git",
|
|
@@ -41,16 +44,24 @@
|
|
|
41
44
|
"homepage": "https://github.com/therealPaulPlay/PlaySocketJS#readme",
|
|
42
45
|
"devDependencies": {
|
|
43
46
|
"@playwright/test": "^1.58.1",
|
|
44
|
-
"@
|
|
47
|
+
"@stylistic/eslint-plugin": "^5.10.0",
|
|
48
|
+
"@types/node": "^26.1.1",
|
|
45
49
|
"eslint": "^9.35.0",
|
|
46
|
-
"eslint-plugin-
|
|
50
|
+
"eslint-plugin-unused-imports": "^4.4.1",
|
|
47
51
|
"globals": "^16.3.0",
|
|
48
|
-
"
|
|
52
|
+
"typescript": "^7.0.2"
|
|
49
53
|
},
|
|
50
|
-
"
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"@msgpack/msgpack": "^3.1.1",
|
|
56
|
+
"@types/ws": "^8.18.1",
|
|
51
57
|
"ws": "^8.18.1"
|
|
52
58
|
},
|
|
53
|
-
"
|
|
54
|
-
|
|
59
|
+
"types": "./types/client/client.d.ts",
|
|
60
|
+
"typesVersions": {
|
|
61
|
+
"*": {
|
|
62
|
+
"server": [
|
|
63
|
+
"./types/server/server.d.ts"
|
|
64
|
+
]
|
|
65
|
+
}
|
|
55
66
|
}
|
|
56
67
|
}
|