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 CHANGED
@@ -1,278 +1,379 @@
1
- # PlaySocket Client
1
+ # PlaySocket
2
2
 
3
- A reactive, optimistic WebSocket library that simplifies game & app development by abstracting away complex sync logic.
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 eliminates the traditional complexity of collaborative experiences:
7
+ PlaySocket makes developing shared experiences a breeze:
8
8
 
9
- - **Streamlined architecture**: No additional backend code is required, but server-authoritative behavior supported
10
- - **State synchronization**: Built-in storage system keeps the full state synchronized across all clients, always conflict-free and in order
11
- - **Resilient & secure connections**: Automatic reconnection handling & strict rate-limiting
12
- - **Lightweight**: Uses WebSockets for efficient, predictable, reliable communication and has little dependencies
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
- ## Usage examples
25
+ ## PlaySocket Client
26
+
27
+ The client-side part of PlaySocket.
28
+
29
+ ### Examples
21
30
 
22
- Note that in production, you should **always try...catch** promises, such as socket.init() – they can reject!
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 'playsocketjs';
36
+ import PlaySocket from "playsocketjs";
27
37
 
28
38
  // Create a new instance
29
- const socket = new PlaySocket('unique-client-id', { // You can pass no ID to let the server pick one
30
- endpoint: 'wss://example.com/socket'
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 (optional)
34
- socket.onEvent('status', status => console.log('Status:', status));
35
- socket.onEvent('error', status => console.log('Error:', status));
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(); // Initialize the socket
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: ["this-player"],
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('room-id'); // Join an existing room
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(); // To leave the room, destroy the instance
73
+ socket.destroy(); // Destroy the instance to leave
60
74
  ```
61
75
 
62
- Using the storage update event for reactivity:
76
+ Using the storage update event with reactivity:
63
77
  ```javascript
64
- const reactiveVariable = useState(); // Or $state(), reactive(), depending on your framework
65
- socket.onEvent('storageUpdated', storage => (reactiveVariable = storage)); // Assign on update
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
- Interfacing with the synchronized storage (examples):
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; // Synchronous, local access
71
- socket.updateStorage('players', 'array-add-unique', { username: 'Player4', level: 2 }); // Special method to enable conflict-free additions for arrays
72
- socket.updateStorage('latestPlayer', 'set', 'Player4'); // Regular synced storage update
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
- Sending traditional requests to the server:
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('chosen-request-name', { fact: "You can build server-authoritative logic using this!" })
104
+ await socket.sendRequest("my-request-name", { fact: "You can build traditional client-server logic like this." })
78
105
  ```
79
106
 
80
- ## API reference
107
+ ### API
81
108
 
82
- ### Constructor
109
+ #### Constructor
83
110
 
84
- Creates a new PlaySocket instance with a specified ID and configuration options.
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
- #### Configuration options
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
- | Option | Type | Default | Description |
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
- ### Methods
100
-
101
- | Method | Parameters | Return type | Description |
126
+ | Name | Parameters | Return type | Description |
102
127
  |--------|------------|-------------|-------------|
103
- | `init()` | - | `Promise<string>` | Initialize the WebSocket connection – Returns a promise which resolves with the client's ID |
104
- | `createRoom()` | `initialStorage?: object, size?: number` | `Promise<string>` | Create a new room and become host – Returns a promise which resolves with the room ID. The room participant maximum is 100 |
105
- | `joinRoom()` | `roomId: string` | `Promise<void>` | Join an existing room |
106
- | `destroy()` | - | `void` | Use this to leave a room and close the connection |
107
- | `updateStorage()` | `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 storage (max. 100 keys). Array operation types allow for conflict-free simultaneous array updates. For '-matching' operations, value becomes the value to match, and updateValue the replacement |
108
- | `sendRequest()` | `name: string, data?: any` | `void` | Send requests to the server with optional custom data (handle them in the `requestReceived` server event) |
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
- ### Event types
136
+ #### Events
112
137
 
113
138
  | Event | Callback parameter | Description |
114
139
  |-------|-------------------|-------------|
115
- | `status` | `status: string` | Connection status updates |
116
- | `error` | `error: string` | Error events |
117
- | `moved` | `roomId: string` | Moved to different room |
118
- | `instanceDestroyed` | - | Destruction event - triggered by manual .destroy() method invocation or by fatal errors and disconnects |
119
- | `storageUpdated` | `storage: object` | Storage state changes |
120
- | `hostMigrated` | `roomId: string` | Host changes |
121
- | `clientJoined` | `clientId: string` | New client joined the room |
122
- | `clientLeft` | `clientId: string, roomId?: string` | Client left the room |
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
- ### Properties (Read-only)
149
+ #### Properties
125
150
 
126
151
  | Property | Type | Description |
127
152
  |----------|------|-------------|
128
- | `id` | string | Client's unique identifier on the WebSocket server |
129
- | `isHost` | boolean | If this user is currently assigned the host role |
130
- | `participantCount` | number | Number of active client connections in room (with yourself) |
131
- | `storage` | object | Retrieve storage object |
132
-
133
- &nbsp;
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
- # PlaySocket Server
158
+ ## PlaySocket Server
136
159
 
137
- PlaySocket includes a server implementation that can be set up in seconds.
160
+ The server-side part of PlaySocket.
138
161
 
139
- ## Installation
162
+ ### Examples
140
163
 
141
- To use the server component, you'll need to install playsocketjs and the ws package:
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 'playsocketjs/server'; // Both ES Module & CommonJS Module syntax is supported
167
+ import PlaySocketServer from "playsocketjs/server";
155
168
 
156
- const server = new PlaySocketServer(); // Create and start the server (default path is /)
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
- // Handle both SIGINT and SIGTERM
165
- process.on('SIGINT', shutdown);
166
- process.on('SIGTERM', shutdown);
177
+ process.on("SIGINT", shutdown);
178
+ process.on("SIGTERM", shutdown);
167
179
  ```
168
180
 
169
- ### Together with Express.js (or other Backend frameworks)
181
+ Using PlaySocket together with Express.js (or other backend frameworks):
170
182
 
171
183
  ```javascript
172
- const express = require('express');
173
- const http = require('http');
174
- const PlaySocketServer = require('playsocketjs/server');
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 your HTTP server
191
+ // Create PlaySocket server with existing HTTP server
180
192
  const playSocketServer = new PlaySocketServer({
181
193
  server: httpServer,
182
- path: '/socket'
194
+ path: "/socket"
183
195
  });
184
196
 
185
197
  // Start the server
186
198
  httpServer.listen(3000, () => {
187
- console.log('Server running on port 3000');
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
- server.stop();
203
+ playSocketServer.stop(); // Gracefully disconnect clients
193
204
  process.exit(0);
194
205
  }
195
206
 
196
- // Handle both SIGINT and SIGTERM
197
- process.on('SIGINT', shutdown);
198
- process.on('SIGTERM', shutdown);
207
+ process.on("SIGINT", shutdown);
208
+ process.on("SIGTERM", shutdown);
199
209
  ```
200
210
 
201
- ## API reference
211
+ Validating an incoming storage update:
202
212
 
203
- ### Constructor
213
+ ```javascript
214
+ const server = new PlaySocketServer();
204
215
 
205
- Creates a new PlaySocket Server instance with configuration options.
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 PlaySocket(options: PlaySocketServerOptions)
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
- ### Configuration options
268
+ ### API
212
269
 
213
- | Option | Type | Default | Description |
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
- #### verifyClient callback
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: '/socket',
292
+ path: "/socket",
230
293
  verifyClient: (info, callback) => {
231
- // info.req - the HTTP request object, info.origin - the Origin header value
232
- const ip = info.req.headers['x-forwarded-for'] || info.req.socket.remoteAddress;
233
- if (isRateLimited(ip)) return callback(false, 429, 'Too Many Requests');
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` and `message` are the optional HTTP response status for rejected connections.
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
- ### Methods
306
+ #### Methods
242
307
 
243
- | Method | Parameters | Return type | Description |
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
- ### Event types
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 requests to register | Return `false` or rejection reason `string` to block |
260
- | `clientDisconnected` | `clientId: string` | Client disconnected from the server | - |
261
- | `clientJoinedRoom` | `clientId: string, roomId: string` | Client joined a room | - |
262
- | `clientJoinRequested` | `clientId: string, roomId: string` | Client requests to join a room | Return `false` or rejection reason `string` to block |
263
- | `clientLeftRoom` | `clientId: string, roomId: string` | Client left a room | - |
264
- | `roomCreated` | `roomId: string` | Client created a room | - |
265
- | `roomDestroyed` | `roomId: string` | Room was destroyed (happens when all participants leave, unless room host is "server") | - |
266
- | `roomCreationRequested` | `{clientId: string, initialStorage: object}` | Room creation requested by client | Return `object` to override initial storage, `false` to deny |
267
- | `storageUpdated` | `{clientId: string, roomId: string, update: object, storage: object}` | Room storage property updated | - |
268
- | `storageUpdateRequested` | `{clientId: string, roomId: string, update: object, storage: object}` | Room storage property update requested by client | Return `false` to block the update |
269
- | `requestReceived` | `{clientId: string, roomId?: string, requestName: string, data?: any}` | Request from client was received by the server | - |
270
-
271
- ### Properties (Read-only)
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.1.2",
3
+ "version": "4.0.0",
4
4
  "description": "WebSocket wrapper for creating simple multiplayer systems with ease.",
5
5
  "type": "module",
6
- "module": "dist/playsocket-client.js",
6
+ "module": "./src/client/client.js",
7
7
  "exports": {
8
8
  ".": {
9
- "import": "./dist/playsocket-client.js"
9
+ "types": "./types/client/client.d.ts",
10
+ "import": "./src/client/client.js"
10
11
  },
11
12
  "./server": {
12
- "import": "./dist/playsocket-server.js"
13
+ "types": "./types/server/server.d.ts",
14
+ "import": "./src/server/server.js"
13
15
  }
14
16
  },
15
17
  "files": [
16
- "dist"
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": "rollup -c rollup.config.js",
23
- "lint": "eslint ./src"
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
- "@rollup/plugin-node-resolve": "^16.0.0",
47
+ "@stylistic/eslint-plugin": "^5.10.0",
48
+ "@types/node": "^26.1.1",
45
49
  "eslint": "^9.35.0",
46
- "eslint-plugin-jsdoc": "^54.4.0",
50
+ "eslint-plugin-unused-imports": "^4.4.1",
47
51
  "globals": "^16.3.0",
48
- "rollup": "^4.31.0"
52
+ "typescript": "^7.0.2"
49
53
  },
50
- "peerDependencies": {
54
+ "dependencies": {
55
+ "@msgpack/msgpack": "^3.1.1",
56
+ "@types/ws": "^8.18.1",
51
57
  "ws": "^8.18.1"
52
58
  },
53
- "dependencies": {
54
- "@msgpack/msgpack": "^3.1.1"
59
+ "types": "./types/client/client.d.ts",
60
+ "typesVersions": {
61
+ "*": {
62
+ "server": [
63
+ "./types/server/server.d.ts"
64
+ ]
65
+ }
55
66
  }
56
67
  }