playsocketjs 1.1.8 → 1.1.10
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/dist/playsocket-client.js +11 -2
- package/dist/playsocket-server.js +65 -27
- package/package.json +1 -1
|
@@ -2287,9 +2287,17 @@ class PlaySocket {
|
|
|
2287
2287
|
}
|
|
2288
2288
|
break;
|
|
2289
2289
|
|
|
2290
|
+
case 'server_stopped':
|
|
2291
|
+
this.#triggerEvent("error", "Server restart.");
|
|
2292
|
+
this.destroy();
|
|
2293
|
+
break;
|
|
2294
|
+
|
|
2295
|
+
case 'host_migrated':
|
|
2296
|
+
this.#setHost(message.newHost);
|
|
2297
|
+
break;
|
|
2298
|
+
|
|
2290
2299
|
case 'client_disconnected':
|
|
2291
2300
|
this.#connectionCount = message.participantCount - 1; // Counted without the user themselves
|
|
2292
|
-
this.#setHost(message.host);
|
|
2293
2301
|
this.#triggerEvent("clientDisconnected", message.client);
|
|
2294
2302
|
this.#triggerEvent("status", `Client ${message.client} disconnected.`);
|
|
2295
2303
|
break;
|
|
@@ -2325,7 +2333,8 @@ class PlaySocket {
|
|
|
2325
2333
|
* @private
|
|
2326
2334
|
*/
|
|
2327
2335
|
async #attemptReconnect() {
|
|
2328
|
-
|
|
2336
|
+
this.#reconnectCount++;
|
|
2337
|
+
if (this.#reconnectCount > 9) {
|
|
2329
2338
|
this.#triggerEvent("error", "Disconnected from server.");
|
|
2330
2339
|
return this.destroy();
|
|
2331
2340
|
}
|
|
@@ -2226,6 +2226,7 @@ function requireServer () {
|
|
|
2226
2226
|
|
|
2227
2227
|
// Set up ws event handlers
|
|
2228
2228
|
this.#wss.on('connection', ws => {
|
|
2229
|
+
ws.connectionId = crypto.randomUUID();
|
|
2229
2230
|
ws.isAlive = true;
|
|
2230
2231
|
ws.on('pong', () => { ws.isAlive = true; });
|
|
2231
2232
|
ws.on('message', msg => this.#handleMessage(ws, msg));
|
|
@@ -2258,9 +2259,14 @@ function requireServer () {
|
|
|
2258
2259
|
try {
|
|
2259
2260
|
const data = decode(message);
|
|
2260
2261
|
|
|
2261
|
-
// Apply rate limiting
|
|
2262
|
-
if (
|
|
2263
|
-
|
|
2262
|
+
// Apply rate limiting to all connections (including unregistered)
|
|
2263
|
+
if (!this.#checkRateLimit(ws.connectionId, data.type)) {
|
|
2264
|
+
ws.rateLimitViolations = (ws.rateLimitViolations || 0) + 1;
|
|
2265
|
+
if (ws.rateLimitViolations > 5) {
|
|
2266
|
+
ws.terminate();
|
|
2267
|
+
return console.error(`Connection ${ws.connectionId} terminated due to repeated rate limit violations.`);
|
|
2268
|
+
}
|
|
2269
|
+
return console.error(`Connection ${ws.connectionId} rate limit exceeded.`);
|
|
2264
2270
|
}
|
|
2265
2271
|
|
|
2266
2272
|
switch (data.type) {
|
|
@@ -2306,7 +2312,7 @@ function requireServer () {
|
|
|
2306
2312
|
roomData = {
|
|
2307
2313
|
state: formerRoom.crdtManager.getState,
|
|
2308
2314
|
participantCount: formerRoom.participants.length,
|
|
2309
|
-
host: formerRoom.
|
|
2315
|
+
host: formerRoom.host,
|
|
2310
2316
|
version: this.#roomVersions.get(formerRoomId)
|
|
2311
2317
|
};
|
|
2312
2318
|
}
|
|
@@ -2332,6 +2338,7 @@ function requireServer () {
|
|
|
2332
2338
|
this.#roomVersions.set(newRoomId, 0); // Start with version 0
|
|
2333
2339
|
this.#rooms[newRoomId] = {
|
|
2334
2340
|
participants: [ws.clientId],
|
|
2341
|
+
host: ws.clientId,
|
|
2335
2342
|
maxSize: data.size || null,
|
|
2336
2343
|
crdtManager: new CRDTManager(crypto.randomUUID(), this.#debug)
|
|
2337
2344
|
};
|
|
@@ -2373,7 +2380,7 @@ function requireServer () {
|
|
|
2373
2380
|
type: 'join_accepted',
|
|
2374
2381
|
state: room.crdtManager.getState,
|
|
2375
2382
|
participantCount: room.participants.length,
|
|
2376
|
-
host: room.
|
|
2383
|
+
host: room.host,
|
|
2377
2384
|
version: this.#roomVersions.get(roomId)
|
|
2378
2385
|
}), { binary: true });
|
|
2379
2386
|
|
|
@@ -2427,6 +2434,29 @@ function requireServer () {
|
|
|
2427
2434
|
}
|
|
2428
2435
|
}
|
|
2429
2436
|
|
|
2437
|
+
/**
|
|
2438
|
+
* If the client that disconnected (and is now in the reconnection-phase) was the room host,
|
|
2439
|
+
* pick a new room host immediately to avoid host-less phase (in case important logic is attached to the host)
|
|
2440
|
+
* @param {string} roomId
|
|
2441
|
+
* @param {string} clientId
|
|
2442
|
+
*/
|
|
2443
|
+
#changeHostIfDisconnected(roomId, clientId) {
|
|
2444
|
+
const room = this.#rooms[roomId];
|
|
2445
|
+
if (room && room.host === clientId && room.participants.length > 1) {
|
|
2446
|
+
const participantsWithoutClient = room.participants.filter((e) => e !== clientId);
|
|
2447
|
+
room.host = participantsWithoutClient[0]; // Set new host
|
|
2448
|
+
|
|
2449
|
+
// Inform all participants about the new host
|
|
2450
|
+
participantsWithoutClient.forEach(p => {
|
|
2451
|
+
const client = this.#clients.get(p);
|
|
2452
|
+
if (client) client.send(encode({
|
|
2453
|
+
type: 'host_migrated',
|
|
2454
|
+
newHost: room.host,
|
|
2455
|
+
}), { binary: true });
|
|
2456
|
+
});
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2430
2460
|
/**
|
|
2431
2461
|
* Generate a random token to prevent malicious reconnect attempts
|
|
2432
2462
|
* @returns {string} - Token
|
|
@@ -2441,24 +2471,24 @@ function requireServer () {
|
|
|
2441
2471
|
* Check rate limit using token bucket algorithm
|
|
2442
2472
|
* @private
|
|
2443
2473
|
*/
|
|
2444
|
-
#checkRateLimit(
|
|
2445
|
-
const MAX_POINTS =
|
|
2474
|
+
#checkRateLimit(connectionId, operationType) {
|
|
2475
|
+
const MAX_POINTS = 20;
|
|
2446
2476
|
const now = Date.now();
|
|
2447
2477
|
|
|
2448
|
-
if (!this.#rateLimits.has(
|
|
2449
|
-
this.#rateLimits.set(
|
|
2478
|
+
if (!this.#rateLimits.has(connectionId)) {
|
|
2479
|
+
this.#rateLimits.set(connectionId, { points: MAX_POINTS, lastReset: now });
|
|
2450
2480
|
return true;
|
|
2451
2481
|
}
|
|
2452
2482
|
|
|
2453
|
-
const limit = this.#rateLimits.get(
|
|
2483
|
+
const limit = this.#rateLimits.get(connectionId);
|
|
2454
2484
|
|
|
2455
|
-
// Reset points if interval has passed (
|
|
2456
|
-
if (now - limit.lastReset >
|
|
2485
|
+
// Reset points if interval has passed (1s)
|
|
2486
|
+
if (now - limit.lastReset > 1000) {
|
|
2457
2487
|
limit.points = MAX_POINTS;
|
|
2458
2488
|
limit.lastReset = now;
|
|
2459
2489
|
}
|
|
2460
2490
|
|
|
2461
|
-
const pointCost = ['create_room', 'join_room'].includes(operationType) ?
|
|
2491
|
+
const pointCost = ['create_room', 'join_room'].includes(operationType) ? 5 : 1;
|
|
2462
2492
|
if (limit.points < pointCost) return false;
|
|
2463
2493
|
|
|
2464
2494
|
limit.points -= pointCost;
|
|
@@ -2470,18 +2500,25 @@ function requireServer () {
|
|
|
2470
2500
|
* @private
|
|
2471
2501
|
*/
|
|
2472
2502
|
#handleDisconnection(ws) {
|
|
2473
|
-
|
|
2474
|
-
this.#clients.delete(ws.clientId); // Immediately remove from active clients (otherwise, server would try to message this client)
|
|
2503
|
+
this.#rateLimits.delete(ws.connectionId);
|
|
2475
2504
|
|
|
2476
|
-
if (ws.
|
|
2477
|
-
this.#
|
|
2478
|
-
|
|
2479
|
-
//
|
|
2480
|
-
this.#
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2505
|
+
if (ws.clientId) {
|
|
2506
|
+
this.#clients.delete(ws.clientId); // Immediately remove from active clients (otherwise, server would try to message this client)
|
|
2507
|
+
|
|
2508
|
+
// If client was in a room, check if they were the host & migrate
|
|
2509
|
+
const roomId = this.#clientRooms.get(ws.clientId);
|
|
2510
|
+
if (roomId != null) this.#changeHostIfDisconnected(roomId, ws.clientId);
|
|
2511
|
+
|
|
2512
|
+
if (ws.willfulDisconnect) {
|
|
2513
|
+
this.#disconnectClient(ws); // Immediate disconnection
|
|
2514
|
+
} else {
|
|
2515
|
+
// Pending complete disconnection with 5s grace period to allow for reconnections
|
|
2516
|
+
this.#pendingDisconnects.set(ws.clientId, {
|
|
2517
|
+
timeout: setTimeout(() => {
|
|
2518
|
+
this.#disconnectClient(ws);
|
|
2519
|
+
}, 5000)
|
|
2520
|
+
});
|
|
2521
|
+
}
|
|
2485
2522
|
}
|
|
2486
2523
|
}
|
|
2487
2524
|
|
|
@@ -2492,7 +2529,6 @@ function requireServer () {
|
|
|
2492
2529
|
*/
|
|
2493
2530
|
#disconnectClient(ws) {
|
|
2494
2531
|
this.#pendingDisconnects.delete(ws.clientId);
|
|
2495
|
-
this.#rateLimits.delete(ws.clientId);
|
|
2496
2532
|
this.#clientTokens.delete(ws.clientId);
|
|
2497
2533
|
const roomId = this.#clientRooms.get(ws.clientId);
|
|
2498
2534
|
const room = this.#rooms[roomId];
|
|
@@ -2512,7 +2548,6 @@ function requireServer () {
|
|
|
2512
2548
|
if (client) client.send(encode({
|
|
2513
2549
|
type: 'client_disconnected',
|
|
2514
2550
|
client: ws.clientId,
|
|
2515
|
-
host: room.participants[0],
|
|
2516
2551
|
participantCount: room.participants.length
|
|
2517
2552
|
}), { binary: true });
|
|
2518
2553
|
});
|
|
@@ -2551,7 +2586,10 @@ function requireServer () {
|
|
|
2551
2586
|
stop() {
|
|
2552
2587
|
clearInterval(this.#heartbeatInterval);
|
|
2553
2588
|
if (this.#wss) {
|
|
2554
|
-
this.#wss.clients.forEach(client =>
|
|
2589
|
+
this.#wss.clients.forEach(client => {
|
|
2590
|
+
client.send(encode({ type: 'server_stopped' }), { binary: true });
|
|
2591
|
+
client.close();
|
|
2592
|
+
});
|
|
2555
2593
|
this.#wss.close();
|
|
2556
2594
|
}
|
|
2557
2595
|
if (this.#server && this.#ownsServer) {
|