@aegis-fluxion/core 0.7.0 → 0.7.1
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 +39 -1
- package/dist/index.cjs +265 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -1
- package/dist/index.d.ts +29 -1
- package/dist/index.js +265 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Low-level E2E-encrypted WebSocket primitives for the `aegis-fluxion` ecosystem.
|
|
|
4
4
|
|
|
5
5
|
If you prefer a single user-facing package, use [`aegis-fluxion`](../aegis-fluxion/README.md).
|
|
6
6
|
|
|
7
|
-
Version: **0.7.
|
|
7
|
+
Version: **0.7.1**
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -20,6 +20,7 @@ Version: **0.7.0**
|
|
|
20
20
|
- **Server middleware pipeline** via `SecureServer.use(...)`
|
|
21
21
|
- Middleware phases: `connection`, `incoming`, `outgoing`
|
|
22
22
|
- Per-socket middleware metadata available as `SecureServerClient.metadata`
|
|
23
|
+
- **Rate limiting & DDoS shield** with per-connection/per-IP controls
|
|
23
24
|
- Optional MCP bridge package: `@aegis-fluxion/mcp-adapter`
|
|
24
25
|
|
|
25
26
|
---
|
|
@@ -107,6 +108,40 @@ Notes:
|
|
|
107
108
|
|
|
108
109
|
---
|
|
109
110
|
|
|
111
|
+
## Rate Limiting & DDoS Protection (0.7.1)
|
|
112
|
+
|
|
113
|
+
`SecureServer` can enforce burst limits per connection and per source IP before event handlers run.
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import { SecureServer } from "@aegis-fluxion/core";
|
|
117
|
+
|
|
118
|
+
const server = new SecureServer({
|
|
119
|
+
host: "127.0.0.1",
|
|
120
|
+
port: 8080,
|
|
121
|
+
rateLimit: {
|
|
122
|
+
enabled: true,
|
|
123
|
+
windowMs: 1_000,
|
|
124
|
+
maxEventsPerConnection: 120,
|
|
125
|
+
maxEventsPerIp: 300,
|
|
126
|
+
action: "throttle",
|
|
127
|
+
throttleMs: 150,
|
|
128
|
+
maxThrottleMs: 2_000,
|
|
129
|
+
disconnectAfterViolations: 4,
|
|
130
|
+
disconnectCode: 1013,
|
|
131
|
+
disconnectReason: "Rate limit exceeded. Please retry later."
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Behavior summary:
|
|
137
|
+
|
|
138
|
+
- When limits are exceeded, the server can **throttle** or **disconnect** the peer.
|
|
139
|
+
- Throttle mode delays the first over-limit message and drops subsequent flood packets during the throttle window.
|
|
140
|
+
- Disconnect mode closes abusive sockets with your configured close code/reason.
|
|
141
|
+
- Source IP is resolved from `x-forwarded-for` (first hop) or socket remote address.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
110
145
|
## MCP Adapter Integration (0.7.0)
|
|
111
146
|
|
|
112
147
|
Use `@aegis-fluxion/mcp-adapter` to carry MCP JSON-RPC messages through your encrypted core
|
|
@@ -265,6 +300,8 @@ client.on("ready", async () => {
|
|
|
265
300
|
- `SecureServerConnectionMiddlewareContext`
|
|
266
301
|
- `SecureServerMessageMiddlewareContext`
|
|
267
302
|
- `SecureServerMiddlewareNext`
|
|
303
|
+
- `SecureServerRateLimitOptions`
|
|
304
|
+
- `SecureServerRateLimitAction`
|
|
268
305
|
|
|
269
306
|
### `SecureClient`
|
|
270
307
|
|
|
@@ -287,6 +324,7 @@ client.on("ready", async () => {
|
|
|
287
324
|
- Authentication tags are verified on every packet (tampered packets are dropped).
|
|
288
325
|
- Internal transport events are reserved (`__handshake`, `__rpc:req`, `__rpc:res`).
|
|
289
326
|
- Pending ACK requests are rejected on timeout/disconnect.
|
|
327
|
+
- Overload traffic can be throttled or disconnected before custom handlers are invoked.
|
|
290
328
|
- Middleware-level policy rejection uses WebSocket close code `1008`.
|
|
291
329
|
|
|
292
330
|
---
|
package/dist/index.cjs
CHANGED
|
@@ -32,6 +32,14 @@ var DEFAULT_RECONNECT_MAX_DELAY_MS = 1e4;
|
|
|
32
32
|
var DEFAULT_RECONNECT_FACTOR = 2;
|
|
33
33
|
var DEFAULT_RECONNECT_JITTER_RATIO = 0.2;
|
|
34
34
|
var DEFAULT_RPC_TIMEOUT_MS = 5e3;
|
|
35
|
+
var DEFAULT_RATE_LIMIT_WINDOW_MS = 1e3;
|
|
36
|
+
var DEFAULT_RATE_LIMIT_MAX_EVENTS_PER_CONNECTION = 120;
|
|
37
|
+
var DEFAULT_RATE_LIMIT_MAX_EVENTS_PER_IP = 300;
|
|
38
|
+
var DEFAULT_RATE_LIMIT_THROTTLE_MS = 150;
|
|
39
|
+
var DEFAULT_RATE_LIMIT_MAX_THROTTLE_MS = 2e3;
|
|
40
|
+
var DEFAULT_RATE_LIMIT_DISCONNECT_AFTER_VIOLATIONS = 4;
|
|
41
|
+
var DEFAULT_RATE_LIMIT_CLOSE_CODE = 1013;
|
|
42
|
+
var DEFAULT_RATE_LIMIT_CLOSE_REASON = "Rate limit exceeded. Please retry later.";
|
|
35
43
|
function normalizeToError(error, fallbackMessage) {
|
|
36
44
|
if (error instanceof Error) {
|
|
37
45
|
return error;
|
|
@@ -65,6 +73,11 @@ function rawDataToBuffer(rawData) {
|
|
|
65
73
|
}
|
|
66
74
|
return Buffer.from(rawData);
|
|
67
75
|
}
|
|
76
|
+
function delay(ms) {
|
|
77
|
+
return new Promise((resolve) => {
|
|
78
|
+
setTimeout(resolve, ms);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
68
81
|
function isBlobValue(value) {
|
|
69
82
|
return typeof Blob !== "undefined" && value instanceof Blob;
|
|
70
83
|
}
|
|
@@ -357,6 +370,7 @@ function decryptSerializedEnvelope(rawData, encryptionKey) {
|
|
|
357
370
|
var SecureServer = class {
|
|
358
371
|
socketServer;
|
|
359
372
|
heartbeatConfig;
|
|
373
|
+
rateLimitConfig;
|
|
360
374
|
heartbeatIntervalHandle = null;
|
|
361
375
|
clientsById = /* @__PURE__ */ new Map();
|
|
362
376
|
clientIdBySocket = /* @__PURE__ */ new Map();
|
|
@@ -375,9 +389,13 @@ var SecureServer = class {
|
|
|
375
389
|
heartbeatStateBySocket = /* @__PURE__ */ new WeakMap();
|
|
376
390
|
roomMembersByName = /* @__PURE__ */ new Map();
|
|
377
391
|
roomNamesByClientId = /* @__PURE__ */ new Map();
|
|
392
|
+
clientIpByClientId = /* @__PURE__ */ new Map();
|
|
393
|
+
rateLimitBucketsByClientId = /* @__PURE__ */ new Map();
|
|
394
|
+
rateLimitBucketsByIp = /* @__PURE__ */ new Map();
|
|
378
395
|
constructor(options) {
|
|
379
|
-
const { heartbeat, ...socketServerOptions } = options;
|
|
396
|
+
const { heartbeat, rateLimit, ...socketServerOptions } = options;
|
|
380
397
|
this.heartbeatConfig = this.resolveHeartbeatConfig(heartbeat);
|
|
398
|
+
this.rateLimitConfig = this.resolveRateLimitConfig(rateLimit);
|
|
381
399
|
this.socketServer = new WebSocket.WebSocketServer(socketServerOptions);
|
|
382
400
|
this.bindSocketServerEvents();
|
|
383
401
|
this.startHeartbeatLoop();
|
|
@@ -559,6 +577,9 @@ var SecureServer = class {
|
|
|
559
577
|
client.socket.close(code, reason);
|
|
560
578
|
}
|
|
561
579
|
}
|
|
580
|
+
this.rateLimitBucketsByClientId.clear();
|
|
581
|
+
this.rateLimitBucketsByIp.clear();
|
|
582
|
+
this.clientIpByClientId.clear();
|
|
562
583
|
this.socketServer.close();
|
|
563
584
|
} catch (error) {
|
|
564
585
|
this.notifyError(normalizeToError(error, "Failed to close server."));
|
|
@@ -579,6 +600,211 @@ var SecureServer = class {
|
|
|
579
600
|
timeoutMs
|
|
580
601
|
};
|
|
581
602
|
}
|
|
603
|
+
resolveRateLimitConfig(rateLimitOptions) {
|
|
604
|
+
const windowMs = rateLimitOptions?.windowMs ?? DEFAULT_RATE_LIMIT_WINDOW_MS;
|
|
605
|
+
const maxEventsPerConnection = rateLimitOptions?.maxEventsPerConnection ?? DEFAULT_RATE_LIMIT_MAX_EVENTS_PER_CONNECTION;
|
|
606
|
+
const maxEventsPerIp = rateLimitOptions?.maxEventsPerIp ?? DEFAULT_RATE_LIMIT_MAX_EVENTS_PER_IP;
|
|
607
|
+
const action = rateLimitOptions?.action ?? "throttle";
|
|
608
|
+
const throttleMs = rateLimitOptions?.throttleMs ?? DEFAULT_RATE_LIMIT_THROTTLE_MS;
|
|
609
|
+
const maxThrottleMs = rateLimitOptions?.maxThrottleMs ?? DEFAULT_RATE_LIMIT_MAX_THROTTLE_MS;
|
|
610
|
+
const disconnectAfterViolations = rateLimitOptions?.disconnectAfterViolations ?? DEFAULT_RATE_LIMIT_DISCONNECT_AFTER_VIOLATIONS;
|
|
611
|
+
const disconnectCode = rateLimitOptions?.disconnectCode ?? DEFAULT_RATE_LIMIT_CLOSE_CODE;
|
|
612
|
+
const disconnectReason = rateLimitOptions?.disconnectReason ?? DEFAULT_RATE_LIMIT_CLOSE_REASON;
|
|
613
|
+
if (!Number.isFinite(windowMs) || windowMs <= 0) {
|
|
614
|
+
throw new Error("Server rateLimit windowMs must be a positive number.");
|
|
615
|
+
}
|
|
616
|
+
if (!Number.isFinite(maxEventsPerConnection) || maxEventsPerConnection <= 0) {
|
|
617
|
+
throw new Error(
|
|
618
|
+
"Server rateLimit maxEventsPerConnection must be a positive number."
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
if (!Number.isFinite(maxEventsPerIp) || maxEventsPerIp <= 0) {
|
|
622
|
+
throw new Error("Server rateLimit maxEventsPerIp must be a positive number.");
|
|
623
|
+
}
|
|
624
|
+
if (action !== "throttle" && action !== "disconnect") {
|
|
625
|
+
throw new Error('Server rateLimit action must be either "throttle" or "disconnect".');
|
|
626
|
+
}
|
|
627
|
+
if (!Number.isFinite(throttleMs) || throttleMs <= 0) {
|
|
628
|
+
throw new Error("Server rateLimit throttleMs must be a positive number.");
|
|
629
|
+
}
|
|
630
|
+
if (!Number.isFinite(maxThrottleMs) || maxThrottleMs <= 0) {
|
|
631
|
+
throw new Error("Server rateLimit maxThrottleMs must be a positive number.");
|
|
632
|
+
}
|
|
633
|
+
if (maxThrottleMs < throttleMs) {
|
|
634
|
+
throw new Error(
|
|
635
|
+
"Server rateLimit maxThrottleMs must be greater than or equal to throttleMs."
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
if (!Number.isInteger(disconnectAfterViolations) || disconnectAfterViolations <= 0) {
|
|
639
|
+
throw new Error(
|
|
640
|
+
"Server rateLimit disconnectAfterViolations must be a positive integer."
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
if (!Number.isInteger(disconnectCode) || disconnectCode < 1e3 || disconnectCode > 4999) {
|
|
644
|
+
throw new Error("Server rateLimit disconnectCode must be a valid WebSocket close code.");
|
|
645
|
+
}
|
|
646
|
+
return {
|
|
647
|
+
enabled: rateLimitOptions?.enabled ?? true,
|
|
648
|
+
windowMs,
|
|
649
|
+
maxEventsPerConnection,
|
|
650
|
+
maxEventsPerIp,
|
|
651
|
+
action,
|
|
652
|
+
throttleMs,
|
|
653
|
+
maxThrottleMs,
|
|
654
|
+
disconnectAfterViolations,
|
|
655
|
+
disconnectCode,
|
|
656
|
+
disconnectReason
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
createRateLimitBucket(now) {
|
|
660
|
+
return {
|
|
661
|
+
windowStartedAt: now,
|
|
662
|
+
count: 0,
|
|
663
|
+
violationCount: 0,
|
|
664
|
+
throttleUntil: 0,
|
|
665
|
+
lastSeenAt: now
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
getOrCreateRateLimitBucket(map, key, now) {
|
|
669
|
+
const existingBucket = map.get(key);
|
|
670
|
+
if (existingBucket) {
|
|
671
|
+
return existingBucket;
|
|
672
|
+
}
|
|
673
|
+
const bucket = this.createRateLimitBucket(now);
|
|
674
|
+
map.set(key, bucket);
|
|
675
|
+
return bucket;
|
|
676
|
+
}
|
|
677
|
+
updateRateLimitBucket(bucket, now) {
|
|
678
|
+
if (now - bucket.windowStartedAt >= this.rateLimitConfig.windowMs) {
|
|
679
|
+
bucket.windowStartedAt = now;
|
|
680
|
+
bucket.count = 0;
|
|
681
|
+
bucket.violationCount = 0;
|
|
682
|
+
bucket.throttleUntil = 0;
|
|
683
|
+
}
|
|
684
|
+
bucket.count += 1;
|
|
685
|
+
bucket.lastSeenAt = now;
|
|
686
|
+
}
|
|
687
|
+
pruneRateLimitBucketMap(map, now, maxIdleMs) {
|
|
688
|
+
for (const [key, bucket] of map.entries()) {
|
|
689
|
+
if (now - bucket.lastSeenAt >= maxIdleMs) {
|
|
690
|
+
map.delete(key);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
pruneRateLimitBuckets(now) {
|
|
695
|
+
const maxIdleMs = this.rateLimitConfig.windowMs * 4;
|
|
696
|
+
this.pruneRateLimitBucketMap(this.rateLimitBucketsByClientId, now, maxIdleMs);
|
|
697
|
+
this.pruneRateLimitBucketMap(this.rateLimitBucketsByIp, now, maxIdleMs);
|
|
698
|
+
}
|
|
699
|
+
normalizeIpAddress(ipAddress) {
|
|
700
|
+
let normalized = ipAddress.trim().toLowerCase();
|
|
701
|
+
if (normalized.startsWith("::ffff:")) {
|
|
702
|
+
normalized = normalized.slice(7);
|
|
703
|
+
}
|
|
704
|
+
if (normalized.startsWith("[") && normalized.endsWith("]")) {
|
|
705
|
+
normalized = normalized.slice(1, -1);
|
|
706
|
+
}
|
|
707
|
+
const zoneIndex = normalized.indexOf("%");
|
|
708
|
+
if (zoneIndex >= 0) {
|
|
709
|
+
normalized = normalized.slice(0, zoneIndex);
|
|
710
|
+
}
|
|
711
|
+
return normalized.length > 0 ? normalized : "unknown";
|
|
712
|
+
}
|
|
713
|
+
resolveClientIp(request) {
|
|
714
|
+
const forwardedHeader = request.headers["x-forwarded-for"];
|
|
715
|
+
const forwardedValue = Array.isArray(forwardedHeader) ? forwardedHeader[0] : forwardedHeader;
|
|
716
|
+
if (typeof forwardedValue === "string") {
|
|
717
|
+
const firstForwardedIp = forwardedValue.split(",").map((item) => item.trim()).find((item) => item.length > 0);
|
|
718
|
+
if (firstForwardedIp) {
|
|
719
|
+
return this.normalizeIpAddress(firstForwardedIp);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
return this.normalizeIpAddress(request.socket.remoteAddress ?? "unknown");
|
|
723
|
+
}
|
|
724
|
+
isIpStillConnected(ipAddress) {
|
|
725
|
+
for (const connectedIp of this.clientIpByClientId.values()) {
|
|
726
|
+
if (connectedIp === ipAddress) {
|
|
727
|
+
return true;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return false;
|
|
731
|
+
}
|
|
732
|
+
evaluateIncomingRateLimit(client) {
|
|
733
|
+
const noLimitDecision = {
|
|
734
|
+
shouldDisconnect: false,
|
|
735
|
+
shouldDrop: false,
|
|
736
|
+
throttleDelayMs: 0
|
|
737
|
+
};
|
|
738
|
+
if (!this.rateLimitConfig.enabled) {
|
|
739
|
+
return noLimitDecision;
|
|
740
|
+
}
|
|
741
|
+
const now = Date.now();
|
|
742
|
+
const clientBucket = this.getOrCreateRateLimitBucket(
|
|
743
|
+
this.rateLimitBucketsByClientId,
|
|
744
|
+
client.id,
|
|
745
|
+
now
|
|
746
|
+
);
|
|
747
|
+
this.updateRateLimitBucket(clientBucket, now);
|
|
748
|
+
const clientIp = this.clientIpByClientId.get(client.id);
|
|
749
|
+
const ipBucket = clientIp ? this.getOrCreateRateLimitBucket(this.rateLimitBucketsByIp, clientIp, now) : null;
|
|
750
|
+
if (ipBucket) {
|
|
751
|
+
this.updateRateLimitBucket(ipBucket, now);
|
|
752
|
+
}
|
|
753
|
+
const activeThrottleUntil = Math.max(
|
|
754
|
+
clientBucket.throttleUntil,
|
|
755
|
+
ipBucket?.throttleUntil ?? 0
|
|
756
|
+
);
|
|
757
|
+
if (activeThrottleUntil > now) {
|
|
758
|
+
return {
|
|
759
|
+
shouldDisconnect: false,
|
|
760
|
+
shouldDrop: true,
|
|
761
|
+
throttleDelayMs: 0
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
const isConnectionLimitExceeded = clientBucket.count > this.rateLimitConfig.maxEventsPerConnection;
|
|
765
|
+
const isIpLimitExceeded = ipBucket ? ipBucket.count > this.rateLimitConfig.maxEventsPerIp : false;
|
|
766
|
+
if (!isConnectionLimitExceeded && !isIpLimitExceeded) {
|
|
767
|
+
if (this.rateLimitBucketsByClientId.size > 1024 || this.rateLimitBucketsByIp.size > 1024) {
|
|
768
|
+
this.pruneRateLimitBuckets(now);
|
|
769
|
+
}
|
|
770
|
+
return noLimitDecision;
|
|
771
|
+
}
|
|
772
|
+
if (isConnectionLimitExceeded) {
|
|
773
|
+
clientBucket.violationCount += 1;
|
|
774
|
+
}
|
|
775
|
+
if (ipBucket && isIpLimitExceeded) {
|
|
776
|
+
ipBucket.violationCount += 1;
|
|
777
|
+
}
|
|
778
|
+
const violationCount = Math.max(
|
|
779
|
+
clientBucket.violationCount,
|
|
780
|
+
ipBucket?.violationCount ?? 0
|
|
781
|
+
);
|
|
782
|
+
const shouldDisconnect = this.rateLimitConfig.action === "disconnect" || violationCount >= this.rateLimitConfig.disconnectAfterViolations;
|
|
783
|
+
if (shouldDisconnect) {
|
|
784
|
+
return {
|
|
785
|
+
shouldDisconnect: true,
|
|
786
|
+
shouldDrop: true,
|
|
787
|
+
throttleDelayMs: 0
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
const throttleDelayMs = Math.min(
|
|
791
|
+
this.rateLimitConfig.maxThrottleMs,
|
|
792
|
+
Math.max(
|
|
793
|
+
this.rateLimitConfig.throttleMs,
|
|
794
|
+
this.rateLimitConfig.throttleMs * violationCount
|
|
795
|
+
)
|
|
796
|
+
);
|
|
797
|
+
const throttleUntil = now + throttleDelayMs;
|
|
798
|
+
clientBucket.throttleUntil = throttleUntil;
|
|
799
|
+
if (ipBucket) {
|
|
800
|
+
ipBucket.throttleUntil = throttleUntil;
|
|
801
|
+
}
|
|
802
|
+
return {
|
|
803
|
+
shouldDisconnect: false,
|
|
804
|
+
shouldDrop: false,
|
|
805
|
+
throttleDelayMs
|
|
806
|
+
};
|
|
807
|
+
}
|
|
582
808
|
startHeartbeatLoop() {
|
|
583
809
|
if (!this.heartbeatConfig.enabled || this.heartbeatIntervalHandle) {
|
|
584
810
|
return;
|
|
@@ -681,6 +907,8 @@ var SecureServer = class {
|
|
|
681
907
|
try {
|
|
682
908
|
const clientId = crypto.randomUUID();
|
|
683
909
|
const handshakeState = this.createServerHandshakeState();
|
|
910
|
+
const clientIp = this.resolveClientIp(request);
|
|
911
|
+
connectionMetadata.set("network.ip", clientIp);
|
|
684
912
|
const client = this.createSecureServerClient(
|
|
685
913
|
clientId,
|
|
686
914
|
socket,
|
|
@@ -689,6 +917,7 @@ var SecureServer = class {
|
|
|
689
917
|
);
|
|
690
918
|
this.clientsById.set(clientId, client);
|
|
691
919
|
this.clientIdBySocket.set(socket, clientId);
|
|
920
|
+
this.clientIpByClientId.set(clientId, clientIp);
|
|
692
921
|
this.handshakeStateBySocket.set(socket, handshakeState);
|
|
693
922
|
this.pendingPayloadsBySocket.set(socket, []);
|
|
694
923
|
this.pendingRpcRequestsBySocket.set(socket, /* @__PURE__ */ new Map());
|
|
@@ -723,6 +952,35 @@ var SecureServer = class {
|
|
|
723
952
|
}
|
|
724
953
|
async handleIncomingMessage(client, rawData) {
|
|
725
954
|
try {
|
|
955
|
+
const rateLimitDecision = this.evaluateIncomingRateLimit(client);
|
|
956
|
+
if (rateLimitDecision.shouldDisconnect) {
|
|
957
|
+
this.notifyError(
|
|
958
|
+
new Error(
|
|
959
|
+
`Rate limit disconnect triggered for client ${client.id}.`
|
|
960
|
+
)
|
|
961
|
+
);
|
|
962
|
+
if (client.socket.readyState === WebSocket__default.default.OPEN || client.socket.readyState === WebSocket__default.default.CONNECTING) {
|
|
963
|
+
client.socket.close(
|
|
964
|
+
this.rateLimitConfig.disconnectCode,
|
|
965
|
+
this.rateLimitConfig.disconnectReason
|
|
966
|
+
);
|
|
967
|
+
}
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
if (rateLimitDecision.shouldDrop) {
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
if (rateLimitDecision.throttleDelayMs > 0) {
|
|
974
|
+
this.notifyError(
|
|
975
|
+
new Error(
|
|
976
|
+
`Rate limit throttle applied to client ${client.id} for ${rateLimitDecision.throttleDelayMs}ms.`
|
|
977
|
+
)
|
|
978
|
+
);
|
|
979
|
+
await delay(rateLimitDecision.throttleDelayMs);
|
|
980
|
+
if (client.socket.readyState !== WebSocket__default.default.OPEN) {
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
726
984
|
let envelope = null;
|
|
727
985
|
try {
|
|
728
986
|
envelope = parseEnvelope(rawData);
|
|
@@ -784,6 +1042,12 @@ var SecureServer = class {
|
|
|
784
1042
|
client.leaveAll();
|
|
785
1043
|
this.clientsById.delete(client.id);
|
|
786
1044
|
this.clientIdBySocket.delete(client.socket);
|
|
1045
|
+
const disconnectedIp = this.clientIpByClientId.get(client.id);
|
|
1046
|
+
this.clientIpByClientId.delete(client.id);
|
|
1047
|
+
this.rateLimitBucketsByClientId.delete(client.id);
|
|
1048
|
+
if (disconnectedIp && !this.isIpStillConnected(disconnectedIp)) {
|
|
1049
|
+
this.rateLimitBucketsByIp.delete(disconnectedIp);
|
|
1050
|
+
}
|
|
787
1051
|
this.handshakeStateBySocket.delete(client.socket);
|
|
788
1052
|
this.sharedSecretBySocket.delete(client.socket);
|
|
789
1053
|
this.encryptionKeyBySocket.delete(client.socket);
|