playsocketjs 1.1.12 → 1.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 +3 -2
- package/dist/playsocket-client.js +14 -11
- package/dist/playsocket-server.js +23 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -63,10 +63,11 @@ socket.destroy();
|
|
|
63
63
|
### Constructor
|
|
64
64
|
|
|
65
65
|
```javascript
|
|
66
|
-
new PlaySocket(id
|
|
66
|
+
new PlaySocket(id?: string, options: PlaySocketOptions)
|
|
67
67
|
```
|
|
68
68
|
|
|
69
69
|
Creates a new PlaySocket instance with a specified ID and configuration options.
|
|
70
|
+
Note: With PlaySocket, the id can be set to `null` to let the server pick a unique one.
|
|
70
71
|
|
|
71
72
|
#### Configuration options
|
|
72
73
|
- `endpoint`: WebSocket server endpoint (e.g., 'wss://example.com/socket')
|
|
@@ -77,7 +78,7 @@ Creates a new PlaySocket instance with a specified ID and configuration options.
|
|
|
77
78
|
|
|
78
79
|
#### Core
|
|
79
80
|
|
|
80
|
-
- `init()`: Initialize the WebSocket connection – Returns Promise (async)
|
|
81
|
+
- `init()`: Initialize the WebSocket connection – Returns Promise (async) which resolves with the client's id
|
|
81
82
|
- `createRoom(initialStorage?: object, maxSize?: number)`: Create a new room and become host – Returns Promise (async) which resolves with the room id (matches the creator's id)
|
|
82
83
|
- `joinRoom(hostId: string)`: Join an existing room – Returns Promise (async)
|
|
83
84
|
- `destroy()`: Use this to leave a room and close the connection
|
|
@@ -2061,7 +2061,7 @@ class PlaySocket {
|
|
|
2061
2061
|
|
|
2062
2062
|
// Room properties
|
|
2063
2063
|
#roomHost;
|
|
2064
|
-
#
|
|
2064
|
+
#inRoom; // If the client is currently in a room or not
|
|
2065
2065
|
#connectionCount = 0;
|
|
2066
2066
|
#crdtManager;
|
|
2067
2067
|
#roomVersion = 0; // Update version (used to compare local vs. remote state to detect package loss)
|
|
@@ -2143,15 +2143,14 @@ class PlaySocket {
|
|
|
2143
2143
|
async init() {
|
|
2144
2144
|
if (this.#initialized) return Promise.reject(new Error("Already initialized"));
|
|
2145
2145
|
if (!this.#endpoint) return Promise.reject(new Error("No websocket endpoint provided"));
|
|
2146
|
-
if (!this.#id) return Promise.reject(new Error("No id provided"));
|
|
2147
2146
|
this.#triggerEvent("status", "Initializing...");
|
|
2148
2147
|
|
|
2149
2148
|
// Connect to WS server
|
|
2150
2149
|
await this.#connect();
|
|
2151
2150
|
|
|
2152
2151
|
// Register with server
|
|
2153
|
-
await Promise.race([
|
|
2154
|
-
new Promise(
|
|
2152
|
+
const id = await Promise.race([
|
|
2153
|
+
new Promise((resolve, reject) => {
|
|
2155
2154
|
this.#pendingRegistration = { resolve, reject }; // Resolve or reject depending on answer to registration msg
|
|
2156
2155
|
|
|
2157
2156
|
// Register with server
|
|
@@ -2165,6 +2164,10 @@ class PlaySocket {
|
|
|
2165
2164
|
]).finally(() => {
|
|
2166
2165
|
this.#pendingRegistration = null;
|
|
2167
2166
|
});
|
|
2167
|
+
|
|
2168
|
+
// If the server sent out an id, store and return that
|
|
2169
|
+
if (id) this.#id = id;
|
|
2170
|
+
return this.#id;
|
|
2168
2171
|
}
|
|
2169
2172
|
|
|
2170
2173
|
/**
|
|
@@ -2202,6 +2205,7 @@ class PlaySocket {
|
|
|
2202
2205
|
// Connected to room
|
|
2203
2206
|
if (this.#pendingJoin) {
|
|
2204
2207
|
if (this.#debug) console.log(LOG_PREFIX + "State received for join:", message.state);
|
|
2208
|
+
this.#inRoom = true;
|
|
2205
2209
|
this.#crdtManager.importState(message.state);
|
|
2206
2210
|
this.#connectionCount = message.participantCount - 1; // Counted without the user themselves
|
|
2207
2211
|
this.#roomHost = message.host;
|
|
@@ -2232,7 +2236,7 @@ class PlaySocket {
|
|
|
2232
2236
|
this.#connectionCount = message.roomData.participantCount - 1; // Counted without the user themselves
|
|
2233
2237
|
this.#setHost(message.roomData.host); // Set host before in case there are .isHost checks in the storageUpdate fallback
|
|
2234
2238
|
this.#triggerEvent("storageUpdated", this.getStorage);
|
|
2235
|
-
} else if (this.#
|
|
2239
|
+
} else if (this.#inRoom) {
|
|
2236
2240
|
// If no room data received, but client thinks they were in a room...
|
|
2237
2241
|
this.#triggerEvent("error", "Reconnected, but room no longer exists.");
|
|
2238
2242
|
return this.destroy();
|
|
@@ -2248,8 +2252,9 @@ class PlaySocket {
|
|
|
2248
2252
|
|
|
2249
2253
|
case 'room_created':
|
|
2250
2254
|
if (this.#pendingCreate) {
|
|
2255
|
+
this.#inRoom = true;
|
|
2251
2256
|
this.#triggerEvent("status", `Room created${this.#pendingCreate.maxSize ? ` with max size ${this.#pendingCreate.maxSize}.` : '.'}`);
|
|
2252
|
-
this.#pendingCreate.resolve(this.#
|
|
2257
|
+
this.#pendingCreate.resolve(this.#pendingCreate.id);
|
|
2253
2258
|
}
|
|
2254
2259
|
break;
|
|
2255
2260
|
|
|
@@ -2271,7 +2276,7 @@ class PlaySocket {
|
|
|
2271
2276
|
if (this.#pendingRegistration) {
|
|
2272
2277
|
this.#sessionToken = message.sessionToken;
|
|
2273
2278
|
this.#initialized = true;
|
|
2274
|
-
this.#pendingRegistration.resolve();
|
|
2279
|
+
this.#pendingRegistration.resolve(message.id);
|
|
2275
2280
|
this.#triggerEvent("status", "Connected to server.");
|
|
2276
2281
|
}
|
|
2277
2282
|
break;
|
|
@@ -2408,9 +2413,8 @@ class PlaySocket {
|
|
|
2408
2413
|
Object.entries(initialStorage)?.forEach(([key, value]) => {
|
|
2409
2414
|
this.#crdtManager.updateProperty(key, "set", value);
|
|
2410
2415
|
});
|
|
2411
|
-
this.#roomId = this.#id; // Create room with your own ID as the room ID to mimic p2p
|
|
2412
2416
|
this.#roomHost = this.#id;
|
|
2413
|
-
this.#pendingCreate = { maxSize, resolve, reject };
|
|
2417
|
+
this.#pendingCreate = { maxSize, id: this.#id, resolve, reject };
|
|
2414
2418
|
this.#sendToServer({
|
|
2415
2419
|
type: 'create_room',
|
|
2416
2420
|
state: this.#crdtManager.getState,
|
|
@@ -2437,7 +2441,6 @@ class PlaySocket {
|
|
|
2437
2441
|
|
|
2438
2442
|
return Promise.race([
|
|
2439
2443
|
new Promise((resolve, reject) => {
|
|
2440
|
-
this.#roomId = roomId;
|
|
2441
2444
|
this.#pendingJoin = { resolve, reject };
|
|
2442
2445
|
|
|
2443
2446
|
// Send connection request
|
|
@@ -2508,7 +2511,7 @@ class PlaySocket {
|
|
|
2508
2511
|
// Reset state
|
|
2509
2512
|
clearTimeout(this.#reconnectTimeout);
|
|
2510
2513
|
this.#roomHost = null;
|
|
2511
|
-
this.#
|
|
2514
|
+
this.#inRoom = false;
|
|
2512
2515
|
this.#connectionCount = 0;
|
|
2513
2516
|
this.#isReconnecting = false;
|
|
2514
2517
|
this.#reconnectCount = 0;
|
|
@@ -2273,17 +2273,35 @@ function requireServer () {
|
|
|
2273
2273
|
|
|
2274
2274
|
switch (data.type) {
|
|
2275
2275
|
case 'register':
|
|
2276
|
-
// Register client ID
|
|
2277
|
-
if (
|
|
2278
|
-
if (this.#clients.get(data.id)) {
|
|
2276
|
+
// Register client ID if provided & check for a duplicate
|
|
2277
|
+
if (data.id && this.#clients.get(data.id)) {
|
|
2279
2278
|
ws.send(encode({ type: 'id_taken' }), { binary: true });
|
|
2280
2279
|
return;
|
|
2281
2280
|
}
|
|
2281
|
+
|
|
2282
|
+
// Generate client ID if none provided
|
|
2283
|
+
if (!data.id) {
|
|
2284
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
2285
|
+
const maxAttempts = 50;
|
|
2286
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
2287
|
+
const id = Array.from({ length: 6 }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
|
|
2288
|
+
if (!this.#clients.get(id)) {
|
|
2289
|
+
data.id = id;
|
|
2290
|
+
break;
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
if (!data.id) {
|
|
2294
|
+
ws.send(encode({ type: 'id_taken' }), { binary: true });
|
|
2295
|
+
throw new Error('Failed to generate unique ID!');
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
|
|
2282
2300
|
ws.clientId = data.id; // Adds the provided id to the ws object as clientId
|
|
2283
2301
|
this.#clients.set(data.id, ws);
|
|
2284
2302
|
const sessionToken = this.#generateSessionToken();
|
|
2285
2303
|
this.#clientTokens.set(data.id, sessionToken); // Token is used when reconnecting to prevent impersonation attacks
|
|
2286
|
-
ws.send(encode({ type: 'registered', sessionToken }), { binary: true });
|
|
2304
|
+
ws.send(encode({ type: 'registered', id: data.id, sessionToken }), { binary: true });
|
|
2287
2305
|
this.#triggerEvent("clientRegistered", data.id, data.customData);
|
|
2288
2306
|
break;
|
|
2289
2307
|
|
|
@@ -2432,7 +2450,7 @@ function requireServer () {
|
|
|
2432
2450
|
break;
|
|
2433
2451
|
}
|
|
2434
2452
|
} catch (error) {
|
|
2435
|
-
console.error('Error
|
|
2453
|
+
console.error('Error in message handler:', error);
|
|
2436
2454
|
}
|
|
2437
2455
|
}
|
|
2438
2456
|
|