@esengine/server 1.2.0 → 1.3.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/dist/ratelimit/index.d.ts +787 -0
- package/dist/ratelimit/index.js +818 -0
- package/dist/ratelimit/index.js.map +1 -0
- package/package.json +5 -1
|
@@ -0,0 +1,787 @@
|
|
|
1
|
+
import { P as Player, R as Room } from '../Room-BnKpl5Sj.js';
|
|
2
|
+
import '@esengine/rpc';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @zh 速率限制类型定义
|
|
6
|
+
* @en Rate limit type definitions
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @zh 速率限制结果
|
|
11
|
+
* @en Rate limit result
|
|
12
|
+
*/
|
|
13
|
+
interface RateLimitResult {
|
|
14
|
+
/**
|
|
15
|
+
* @zh 是否允许
|
|
16
|
+
* @en Whether allowed
|
|
17
|
+
*/
|
|
18
|
+
allowed: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* @zh 剩余配额
|
|
21
|
+
* @en Remaining quota
|
|
22
|
+
*/
|
|
23
|
+
remaining: number;
|
|
24
|
+
/**
|
|
25
|
+
* @zh 配额重置时间(毫秒时间戳)
|
|
26
|
+
* @en Quota reset time (milliseconds timestamp)
|
|
27
|
+
*/
|
|
28
|
+
resetAt: number;
|
|
29
|
+
/**
|
|
30
|
+
* @zh 重试等待时间(毫秒),仅在被限流时返回
|
|
31
|
+
* @en Retry after time (milliseconds), only returned when rate limited
|
|
32
|
+
*/
|
|
33
|
+
retryAfter?: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* @zh 速率限制策略接口
|
|
37
|
+
* @en Rate limit strategy interface
|
|
38
|
+
*/
|
|
39
|
+
interface IRateLimitStrategy {
|
|
40
|
+
/**
|
|
41
|
+
* @zh 策略名称
|
|
42
|
+
* @en Strategy name
|
|
43
|
+
*/
|
|
44
|
+
readonly name: string;
|
|
45
|
+
/**
|
|
46
|
+
* @zh 尝试消费配额
|
|
47
|
+
* @en Try to consume quota
|
|
48
|
+
*
|
|
49
|
+
* @param key - @zh 限流键(通常是玩家ID或连接ID)@en Rate limit key (usually player ID or connection ID)
|
|
50
|
+
* @param cost - @zh 消费数量(默认1)@en Consumption amount (default 1)
|
|
51
|
+
* @returns @zh 限流结果 @en Rate limit result
|
|
52
|
+
*/
|
|
53
|
+
consume(key: string, cost?: number): RateLimitResult;
|
|
54
|
+
/**
|
|
55
|
+
* @zh 获取当前状态(不消费)
|
|
56
|
+
* @en Get current status (without consuming)
|
|
57
|
+
*
|
|
58
|
+
* @param key - @zh 限流键 @en Rate limit key
|
|
59
|
+
* @returns @zh 限流结果 @en Rate limit result
|
|
60
|
+
*/
|
|
61
|
+
getStatus(key: string): RateLimitResult;
|
|
62
|
+
/**
|
|
63
|
+
* @zh 重置指定键的限流状态
|
|
64
|
+
* @en Reset rate limit status for specified key
|
|
65
|
+
*
|
|
66
|
+
* @param key - @zh 限流键 @en Rate limit key
|
|
67
|
+
*/
|
|
68
|
+
reset(key: string): void;
|
|
69
|
+
/**
|
|
70
|
+
* @zh 清理所有过期的限流记录
|
|
71
|
+
* @en Clean up all expired rate limit records
|
|
72
|
+
*/
|
|
73
|
+
cleanup(): void;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* @zh 速率限制策略类型
|
|
77
|
+
* @en Rate limit strategy type
|
|
78
|
+
*/
|
|
79
|
+
type RateLimitStrategyType = 'token-bucket' | 'sliding-window' | 'fixed-window';
|
|
80
|
+
/**
|
|
81
|
+
* @zh 速率限制配置
|
|
82
|
+
* @en Rate limit configuration
|
|
83
|
+
*/
|
|
84
|
+
interface RateLimitConfig {
|
|
85
|
+
/**
|
|
86
|
+
* @zh 每秒允许的消息数
|
|
87
|
+
* @en Messages allowed per second
|
|
88
|
+
* @defaultValue 10
|
|
89
|
+
*/
|
|
90
|
+
messagesPerSecond?: number;
|
|
91
|
+
/**
|
|
92
|
+
* @zh 突发容量(令牌桶大小)
|
|
93
|
+
* @en Burst capacity (token bucket size)
|
|
94
|
+
* @defaultValue 20
|
|
95
|
+
*/
|
|
96
|
+
burstSize?: number;
|
|
97
|
+
/**
|
|
98
|
+
* @zh 限流策略
|
|
99
|
+
* @en Rate limit strategy
|
|
100
|
+
* @defaultValue 'token-bucket'
|
|
101
|
+
*/
|
|
102
|
+
strategy?: RateLimitStrategyType;
|
|
103
|
+
/**
|
|
104
|
+
* @zh 被限流时的回调
|
|
105
|
+
* @en Callback when rate limited
|
|
106
|
+
*/
|
|
107
|
+
onLimited?: (player: Player, messageType: string, result: RateLimitResult) => void;
|
|
108
|
+
/**
|
|
109
|
+
* @zh 是否在限流时断开连接
|
|
110
|
+
* @en Whether to disconnect when rate limited
|
|
111
|
+
* @defaultValue false
|
|
112
|
+
*/
|
|
113
|
+
disconnectOnLimit?: boolean;
|
|
114
|
+
/**
|
|
115
|
+
* @zh 连续被限流多少次后断开连接(0 表示不断开)
|
|
116
|
+
* @en Disconnect after how many consecutive rate limits (0 means never)
|
|
117
|
+
* @defaultValue 0
|
|
118
|
+
*/
|
|
119
|
+
maxConsecutiveLimits?: number;
|
|
120
|
+
/**
|
|
121
|
+
* @zh 获取限流键的函数(默认使用玩家ID)
|
|
122
|
+
* @en Function to get rate limit key (default uses player ID)
|
|
123
|
+
*/
|
|
124
|
+
getKey?: (player: Player) => string;
|
|
125
|
+
/**
|
|
126
|
+
* @zh 清理间隔(毫秒)
|
|
127
|
+
* @en Cleanup interval (milliseconds)
|
|
128
|
+
* @defaultValue 60000
|
|
129
|
+
*/
|
|
130
|
+
cleanupInterval?: number;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* @zh 单个消息的速率限制配置
|
|
134
|
+
* @en Rate limit configuration for individual message
|
|
135
|
+
*/
|
|
136
|
+
interface MessageRateLimitConfig {
|
|
137
|
+
/**
|
|
138
|
+
* @zh 每秒允许的消息数
|
|
139
|
+
* @en Messages allowed per second
|
|
140
|
+
*/
|
|
141
|
+
messagesPerSecond?: number;
|
|
142
|
+
/**
|
|
143
|
+
* @zh 突发容量
|
|
144
|
+
* @en Burst capacity
|
|
145
|
+
*/
|
|
146
|
+
burstSize?: number;
|
|
147
|
+
/**
|
|
148
|
+
* @zh 消费的令牌数(默认1)
|
|
149
|
+
* @en Tokens to consume (default 1)
|
|
150
|
+
*/
|
|
151
|
+
cost?: number;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* @zh 速率限制元数据
|
|
155
|
+
* @en Rate limit metadata
|
|
156
|
+
*/
|
|
157
|
+
interface RateLimitMetadata {
|
|
158
|
+
/**
|
|
159
|
+
* @zh 是否启用速率限制
|
|
160
|
+
* @en Whether rate limit is enabled
|
|
161
|
+
*/
|
|
162
|
+
enabled: boolean;
|
|
163
|
+
/**
|
|
164
|
+
* @zh 是否豁免速率限制
|
|
165
|
+
* @en Whether exempt from rate limit
|
|
166
|
+
*/
|
|
167
|
+
exempt?: boolean;
|
|
168
|
+
/**
|
|
169
|
+
* @zh 自定义配置
|
|
170
|
+
* @en Custom configuration
|
|
171
|
+
*/
|
|
172
|
+
config?: MessageRateLimitConfig;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* @zh 速率限制上下文接口
|
|
176
|
+
* @en Rate limit context interface
|
|
177
|
+
*/
|
|
178
|
+
interface IRateLimitContext {
|
|
179
|
+
/**
|
|
180
|
+
* @zh 检查是否允许(不消费)
|
|
181
|
+
* @en Check if allowed (without consuming)
|
|
182
|
+
*/
|
|
183
|
+
check(messageType?: string): RateLimitResult;
|
|
184
|
+
/**
|
|
185
|
+
* @zh 消费配额
|
|
186
|
+
* @en Consume quota
|
|
187
|
+
*/
|
|
188
|
+
consume(messageType?: string, cost?: number): RateLimitResult;
|
|
189
|
+
/**
|
|
190
|
+
* @zh 重置限流状态
|
|
191
|
+
* @en Reset rate limit status
|
|
192
|
+
*/
|
|
193
|
+
reset(messageType?: string): void;
|
|
194
|
+
/**
|
|
195
|
+
* @zh 获取连续被限流次数
|
|
196
|
+
* @en Get consecutive limit count
|
|
197
|
+
*/
|
|
198
|
+
readonly consecutiveLimitCount: number;
|
|
199
|
+
/**
|
|
200
|
+
* @zh 重置连续限流计数
|
|
201
|
+
* @en Reset consecutive limit count
|
|
202
|
+
*/
|
|
203
|
+
resetConsecutiveCount(): void;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* @zh 带速率限制的 Room 接口
|
|
207
|
+
* @en Room interface with rate limit
|
|
208
|
+
*/
|
|
209
|
+
interface RateLimitedRoom {
|
|
210
|
+
/**
|
|
211
|
+
* @zh 获取玩家的速率限制上下文
|
|
212
|
+
* @en Get rate limit context for player
|
|
213
|
+
*/
|
|
214
|
+
getRateLimitContext(player: Player): IRateLimitContext | null;
|
|
215
|
+
/**
|
|
216
|
+
* @zh 全局速率限制策略
|
|
217
|
+
* @en Global rate limit strategy
|
|
218
|
+
*/
|
|
219
|
+
readonly rateLimitStrategy: IRateLimitStrategy;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* @zh 速率限制策略配置
|
|
223
|
+
* @en Rate limit strategy configuration
|
|
224
|
+
*/
|
|
225
|
+
interface StrategyConfig {
|
|
226
|
+
/**
|
|
227
|
+
* @zh 每秒允许的请求数
|
|
228
|
+
* @en Requests allowed per second
|
|
229
|
+
*/
|
|
230
|
+
rate: number;
|
|
231
|
+
/**
|
|
232
|
+
* @zh 容量/窗口大小
|
|
233
|
+
* @en Capacity/window size
|
|
234
|
+
*/
|
|
235
|
+
capacity: number;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* @zh 令牌桶速率限制策略
|
|
240
|
+
* @en Token bucket rate limit strategy
|
|
241
|
+
*/
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* @zh 令牌桶速率限制策略
|
|
245
|
+
* @en Token bucket rate limit strategy
|
|
246
|
+
*
|
|
247
|
+
* @zh 令牌桶算法允许突发流量,同时保持长期速率限制。
|
|
248
|
+
* 令牌以固定速率添加到桶中,每个请求消耗一个或多个令牌。
|
|
249
|
+
* @en Token bucket algorithm allows burst traffic while maintaining long-term rate limit.
|
|
250
|
+
* Tokens are added to the bucket at a fixed rate, each request consumes one or more tokens.
|
|
251
|
+
*
|
|
252
|
+
* @example
|
|
253
|
+
* ```typescript
|
|
254
|
+
* const strategy = new TokenBucketStrategy({
|
|
255
|
+
* rate: 10, // 10 tokens per second
|
|
256
|
+
* capacity: 20 // bucket can hold 20 tokens max
|
|
257
|
+
* });
|
|
258
|
+
*
|
|
259
|
+
* const result = strategy.consume('player-123');
|
|
260
|
+
* if (result.allowed) {
|
|
261
|
+
* // Process message
|
|
262
|
+
* }
|
|
263
|
+
* ```
|
|
264
|
+
*/
|
|
265
|
+
declare class TokenBucketStrategy implements IRateLimitStrategy {
|
|
266
|
+
readonly name = "token-bucket";
|
|
267
|
+
private _rate;
|
|
268
|
+
private _capacity;
|
|
269
|
+
private _buckets;
|
|
270
|
+
/**
|
|
271
|
+
* @zh 创建令牌桶策略
|
|
272
|
+
* @en Create token bucket strategy
|
|
273
|
+
*
|
|
274
|
+
* @param config - @zh 配置 @en Configuration
|
|
275
|
+
* @param config.rate - @zh 每秒添加的令牌数 @en Tokens added per second
|
|
276
|
+
* @param config.capacity - @zh 桶容量(最大令牌数)@en Bucket capacity (max tokens)
|
|
277
|
+
*/
|
|
278
|
+
constructor(config: StrategyConfig);
|
|
279
|
+
/**
|
|
280
|
+
* @zh 尝试消费令牌
|
|
281
|
+
* @en Try to consume tokens
|
|
282
|
+
*/
|
|
283
|
+
consume(key: string, cost?: number): RateLimitResult;
|
|
284
|
+
/**
|
|
285
|
+
* @zh 获取当前状态
|
|
286
|
+
* @en Get current status
|
|
287
|
+
*/
|
|
288
|
+
getStatus(key: string): RateLimitResult;
|
|
289
|
+
/**
|
|
290
|
+
* @zh 重置指定键
|
|
291
|
+
* @en Reset specified key
|
|
292
|
+
*/
|
|
293
|
+
reset(key: string): void;
|
|
294
|
+
/**
|
|
295
|
+
* @zh 清理所有记录
|
|
296
|
+
* @en Clean up all records
|
|
297
|
+
*/
|
|
298
|
+
cleanup(): void;
|
|
299
|
+
/**
|
|
300
|
+
* @zh 获取或创建桶
|
|
301
|
+
* @en Get or create bucket
|
|
302
|
+
*/
|
|
303
|
+
private _getOrCreateBucket;
|
|
304
|
+
/**
|
|
305
|
+
* @zh 补充令牌
|
|
306
|
+
* @en Refill tokens
|
|
307
|
+
*/
|
|
308
|
+
private _refillBucket;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* @zh 创建令牌桶策略
|
|
312
|
+
* @en Create token bucket strategy
|
|
313
|
+
*
|
|
314
|
+
* @example
|
|
315
|
+
* ```typescript
|
|
316
|
+
* const strategy = createTokenBucketStrategy({
|
|
317
|
+
* rate: 10,
|
|
318
|
+
* capacity: 20
|
|
319
|
+
* });
|
|
320
|
+
* ```
|
|
321
|
+
*/
|
|
322
|
+
declare function createTokenBucketStrategy(config: StrategyConfig): TokenBucketStrategy;
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* @zh 滑动窗口速率限制策略
|
|
326
|
+
* @en Sliding window rate limit strategy
|
|
327
|
+
*/
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* @zh 滑动窗口速率限制策略
|
|
331
|
+
* @en Sliding window rate limit strategy
|
|
332
|
+
*
|
|
333
|
+
* @zh 滑动窗口算法精确跟踪时间窗口内的请求数。
|
|
334
|
+
* 比固定窗口更精确,但内存开销稍大。
|
|
335
|
+
* @en Sliding window algorithm precisely tracks requests within a time window.
|
|
336
|
+
* More accurate than fixed window, but with slightly higher memory overhead.
|
|
337
|
+
*
|
|
338
|
+
* @example
|
|
339
|
+
* ```typescript
|
|
340
|
+
* const strategy = new SlidingWindowStrategy({
|
|
341
|
+
* rate: 10, // 10 requests per second
|
|
342
|
+
* capacity: 10 // window size (same as rate for 1-second window)
|
|
343
|
+
* });
|
|
344
|
+
*
|
|
345
|
+
* const result = strategy.consume('player-123');
|
|
346
|
+
* if (result.allowed) {
|
|
347
|
+
* // Process message
|
|
348
|
+
* }
|
|
349
|
+
* ```
|
|
350
|
+
*/
|
|
351
|
+
declare class SlidingWindowStrategy implements IRateLimitStrategy {
|
|
352
|
+
readonly name = "sliding-window";
|
|
353
|
+
private _rate;
|
|
354
|
+
private _capacity;
|
|
355
|
+
private _windowMs;
|
|
356
|
+
private _windows;
|
|
357
|
+
/**
|
|
358
|
+
* @zh 创建滑动窗口策略
|
|
359
|
+
* @en Create sliding window strategy
|
|
360
|
+
*
|
|
361
|
+
* @param config - @zh 配置 @en Configuration
|
|
362
|
+
* @param config.rate - @zh 每秒允许的请求数 @en Requests allowed per second
|
|
363
|
+
* @param config.capacity - @zh 窗口容量 @en Window capacity
|
|
364
|
+
*/
|
|
365
|
+
constructor(config: StrategyConfig);
|
|
366
|
+
/**
|
|
367
|
+
* @zh 尝试消费配额
|
|
368
|
+
* @en Try to consume quota
|
|
369
|
+
*/
|
|
370
|
+
consume(key: string, cost?: number): RateLimitResult;
|
|
371
|
+
/**
|
|
372
|
+
* @zh 获取当前状态
|
|
373
|
+
* @en Get current status
|
|
374
|
+
*/
|
|
375
|
+
getStatus(key: string): RateLimitResult;
|
|
376
|
+
/**
|
|
377
|
+
* @zh 重置指定键
|
|
378
|
+
* @en Reset specified key
|
|
379
|
+
*/
|
|
380
|
+
reset(key: string): void;
|
|
381
|
+
/**
|
|
382
|
+
* @zh 清理所有过期记录
|
|
383
|
+
* @en Clean up all expired records
|
|
384
|
+
*/
|
|
385
|
+
cleanup(): void;
|
|
386
|
+
/**
|
|
387
|
+
* @zh 获取或创建窗口
|
|
388
|
+
* @en Get or create window
|
|
389
|
+
*/
|
|
390
|
+
private _getOrCreateWindow;
|
|
391
|
+
/**
|
|
392
|
+
* @zh 清理过期的时间戳
|
|
393
|
+
* @en Clean expired timestamps
|
|
394
|
+
*/
|
|
395
|
+
private _cleanExpiredTimestamps;
|
|
396
|
+
/**
|
|
397
|
+
* @zh 获取重置时间
|
|
398
|
+
* @en Get reset time
|
|
399
|
+
*/
|
|
400
|
+
private _getResetAt;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* @zh 创建滑动窗口策略
|
|
404
|
+
* @en Create sliding window strategy
|
|
405
|
+
*
|
|
406
|
+
* @example
|
|
407
|
+
* ```typescript
|
|
408
|
+
* const strategy = createSlidingWindowStrategy({
|
|
409
|
+
* rate: 10,
|
|
410
|
+
* capacity: 10
|
|
411
|
+
* });
|
|
412
|
+
* ```
|
|
413
|
+
*/
|
|
414
|
+
declare function createSlidingWindowStrategy(config: StrategyConfig): SlidingWindowStrategy;
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* @zh 固定窗口速率限制策略
|
|
418
|
+
* @en Fixed window rate limit strategy
|
|
419
|
+
*/
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* @zh 固定窗口速率限制策略
|
|
423
|
+
* @en Fixed window rate limit strategy
|
|
424
|
+
*
|
|
425
|
+
* @zh 固定窗口算法将时间划分为固定长度的窗口,在每个窗口内计数请求。
|
|
426
|
+
* 实现简单,内存开销小,但在窗口边界可能有两倍突发的问题。
|
|
427
|
+
* @en Fixed window algorithm divides time into fixed-length windows and counts requests in each window.
|
|
428
|
+
* Simple to implement with low memory overhead, but may have 2x burst issue at window boundaries.
|
|
429
|
+
*
|
|
430
|
+
* @example
|
|
431
|
+
* ```typescript
|
|
432
|
+
* const strategy = new FixedWindowStrategy({
|
|
433
|
+
* rate: 10, // 10 requests per second
|
|
434
|
+
* capacity: 10 // same as rate for 1-second window
|
|
435
|
+
* });
|
|
436
|
+
*
|
|
437
|
+
* const result = strategy.consume('player-123');
|
|
438
|
+
* if (result.allowed) {
|
|
439
|
+
* // Process message
|
|
440
|
+
* }
|
|
441
|
+
* ```
|
|
442
|
+
*/
|
|
443
|
+
declare class FixedWindowStrategy implements IRateLimitStrategy {
|
|
444
|
+
readonly name = "fixed-window";
|
|
445
|
+
private _rate;
|
|
446
|
+
private _capacity;
|
|
447
|
+
private _windowMs;
|
|
448
|
+
private _windows;
|
|
449
|
+
/**
|
|
450
|
+
* @zh 创建固定窗口策略
|
|
451
|
+
* @en Create fixed window strategy
|
|
452
|
+
*
|
|
453
|
+
* @param config - @zh 配置 @en Configuration
|
|
454
|
+
* @param config.rate - @zh 每秒允许的请求数 @en Requests allowed per second
|
|
455
|
+
* @param config.capacity - @zh 窗口容量 @en Window capacity
|
|
456
|
+
*/
|
|
457
|
+
constructor(config: StrategyConfig);
|
|
458
|
+
/**
|
|
459
|
+
* @zh 尝试消费配额
|
|
460
|
+
* @en Try to consume quota
|
|
461
|
+
*/
|
|
462
|
+
consume(key: string, cost?: number): RateLimitResult;
|
|
463
|
+
/**
|
|
464
|
+
* @zh 获取当前状态
|
|
465
|
+
* @en Get current status
|
|
466
|
+
*/
|
|
467
|
+
getStatus(key: string): RateLimitResult;
|
|
468
|
+
/**
|
|
469
|
+
* @zh 重置指定键
|
|
470
|
+
* @en Reset specified key
|
|
471
|
+
*/
|
|
472
|
+
reset(key: string): void;
|
|
473
|
+
/**
|
|
474
|
+
* @zh 清理所有过期记录
|
|
475
|
+
* @en Clean up all expired records
|
|
476
|
+
*/
|
|
477
|
+
cleanup(): void;
|
|
478
|
+
/**
|
|
479
|
+
* @zh 获取或创建窗口
|
|
480
|
+
* @en Get or create window
|
|
481
|
+
*/
|
|
482
|
+
private _getOrCreateWindow;
|
|
483
|
+
/**
|
|
484
|
+
* @zh 如果需要则重置窗口
|
|
485
|
+
* @en Reset window if needed
|
|
486
|
+
*/
|
|
487
|
+
private _maybeResetWindow;
|
|
488
|
+
/**
|
|
489
|
+
* @zh 获取窗口开始时间
|
|
490
|
+
* @en Get window start time
|
|
491
|
+
*/
|
|
492
|
+
private _getWindowStart;
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* @zh 创建固定窗口策略
|
|
496
|
+
* @en Create fixed window strategy
|
|
497
|
+
*
|
|
498
|
+
* @example
|
|
499
|
+
* ```typescript
|
|
500
|
+
* const strategy = createFixedWindowStrategy({
|
|
501
|
+
* rate: 10,
|
|
502
|
+
* capacity: 10
|
|
503
|
+
* });
|
|
504
|
+
* ```
|
|
505
|
+
*/
|
|
506
|
+
declare function createFixedWindowStrategy(config: StrategyConfig): FixedWindowStrategy;
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* @zh 速率限制上下文
|
|
510
|
+
* @en Rate limit context
|
|
511
|
+
*/
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* @zh 速率限制上下文
|
|
515
|
+
* @en Rate limit context
|
|
516
|
+
*
|
|
517
|
+
* @zh 管理单个玩家的速率限制状态,支持全局限制和按消息类型限制
|
|
518
|
+
* @en Manages rate limit status for a single player, supports global and per-message-type limits
|
|
519
|
+
*
|
|
520
|
+
* @example
|
|
521
|
+
* ```typescript
|
|
522
|
+
* const context = new RateLimitContext('player-123', globalStrategy);
|
|
523
|
+
*
|
|
524
|
+
* // Check global rate limit
|
|
525
|
+
* const result = context.consume();
|
|
526
|
+
*
|
|
527
|
+
* // Check per-message rate limit
|
|
528
|
+
* const tradeResult = context.consume('Trade', 1);
|
|
529
|
+
* ```
|
|
530
|
+
*/
|
|
531
|
+
declare class RateLimitContext implements IRateLimitContext {
|
|
532
|
+
private _key;
|
|
533
|
+
private _globalStrategy;
|
|
534
|
+
private _messageStrategies;
|
|
535
|
+
private _consecutiveLimitCount;
|
|
536
|
+
/**
|
|
537
|
+
* @zh 创建速率限制上下文
|
|
538
|
+
* @en Create rate limit context
|
|
539
|
+
*
|
|
540
|
+
* @param key - @zh 限流键(通常是玩家ID)@en Rate limit key (usually player ID)
|
|
541
|
+
* @param globalStrategy - @zh 全局限流策略 @en Global rate limit strategy
|
|
542
|
+
*/
|
|
543
|
+
constructor(key: string, globalStrategy: IRateLimitStrategy);
|
|
544
|
+
/**
|
|
545
|
+
* @zh 获取连续被限流次数
|
|
546
|
+
* @en Get consecutive limit count
|
|
547
|
+
*/
|
|
548
|
+
get consecutiveLimitCount(): number;
|
|
549
|
+
/**
|
|
550
|
+
* @zh 检查是否允许(不消费)
|
|
551
|
+
* @en Check if allowed (without consuming)
|
|
552
|
+
*/
|
|
553
|
+
check(messageType?: string): RateLimitResult;
|
|
554
|
+
/**
|
|
555
|
+
* @zh 消费配额
|
|
556
|
+
* @en Consume quota
|
|
557
|
+
*/
|
|
558
|
+
consume(messageType?: string, cost?: number): RateLimitResult;
|
|
559
|
+
/**
|
|
560
|
+
* @zh 重置限流状态
|
|
561
|
+
* @en Reset rate limit status
|
|
562
|
+
*/
|
|
563
|
+
reset(messageType?: string): void;
|
|
564
|
+
/**
|
|
565
|
+
* @zh 重置连续限流计数
|
|
566
|
+
* @en Reset consecutive limit count
|
|
567
|
+
*/
|
|
568
|
+
resetConsecutiveCount(): void;
|
|
569
|
+
/**
|
|
570
|
+
* @zh 为特定消息类型设置独立的限流策略
|
|
571
|
+
* @en Set independent rate limit strategy for specific message type
|
|
572
|
+
*
|
|
573
|
+
* @param messageType - @zh 消息类型 @en Message type
|
|
574
|
+
* @param strategy - @zh 限流策略 @en Rate limit strategy
|
|
575
|
+
*/
|
|
576
|
+
setMessageStrategy(messageType: string, strategy: IRateLimitStrategy): void;
|
|
577
|
+
/**
|
|
578
|
+
* @zh 移除特定消息类型的限流策略
|
|
579
|
+
* @en Remove rate limit strategy for specific message type
|
|
580
|
+
*
|
|
581
|
+
* @param messageType - @zh 消息类型 @en Message type
|
|
582
|
+
*/
|
|
583
|
+
removeMessageStrategy(messageType: string): void;
|
|
584
|
+
/**
|
|
585
|
+
* @zh 检查是否有特定消息类型的限流策略
|
|
586
|
+
* @en Check if has rate limit strategy for specific message type
|
|
587
|
+
*
|
|
588
|
+
* @param messageType - @zh 消息类型 @en Message type
|
|
589
|
+
*/
|
|
590
|
+
hasMessageStrategy(messageType: string): boolean;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* @zh 房间速率限制 Mixin
|
|
595
|
+
* @en Room rate limit mixin
|
|
596
|
+
*/
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* @zh 带速率限制的玩家
|
|
600
|
+
* @en Player with rate limit
|
|
601
|
+
*/
|
|
602
|
+
interface RateLimitedPlayer<TData = Record<string, unknown>> extends Player<TData> {
|
|
603
|
+
/**
|
|
604
|
+
* @zh 速率限制上下文
|
|
605
|
+
* @en Rate limit context
|
|
606
|
+
*/
|
|
607
|
+
readonly rateLimit: IRateLimitContext;
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* @zh 带速率限制的房间接口
|
|
611
|
+
* @en Room with rate limit interface
|
|
612
|
+
*/
|
|
613
|
+
interface IRateLimitRoom {
|
|
614
|
+
/**
|
|
615
|
+
* @zh 获取玩家的速率限制上下文
|
|
616
|
+
* @en Get rate limit context for player
|
|
617
|
+
*/
|
|
618
|
+
getRateLimitContext(player: Player): IRateLimitContext | null;
|
|
619
|
+
/**
|
|
620
|
+
* @zh 全局速率限制策略
|
|
621
|
+
* @en Global rate limit strategy
|
|
622
|
+
*/
|
|
623
|
+
readonly rateLimitStrategy: IRateLimitStrategy;
|
|
624
|
+
/**
|
|
625
|
+
* @zh 速率限制钩子(被限流时调用)
|
|
626
|
+
* @en Rate limit hook (called when rate limited)
|
|
627
|
+
*/
|
|
628
|
+
onRateLimited?(player: Player, messageType: string, result: RateLimitResult): void;
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* @zh 速率限制房间构造器类型
|
|
632
|
+
* @en Rate limit room constructor type
|
|
633
|
+
*/
|
|
634
|
+
type RateLimitRoomClass = new (...args: any[]) => Room & IRateLimitRoom;
|
|
635
|
+
/**
|
|
636
|
+
* @zh 获取玩家的速率限制上下文
|
|
637
|
+
* @en Get rate limit context for player
|
|
638
|
+
*/
|
|
639
|
+
declare function getPlayerRateLimitContext(player: Player): IRateLimitContext | null;
|
|
640
|
+
/**
|
|
641
|
+
* @zh 包装房间类添加速率限制功能
|
|
642
|
+
* @en Wrap room class with rate limit functionality
|
|
643
|
+
*
|
|
644
|
+
* @zh 使用 mixin 模式为房间添加速率限制,在消息处理前验证速率限制
|
|
645
|
+
* @en Uses mixin pattern to add rate limit to room, validates rate before processing messages
|
|
646
|
+
*
|
|
647
|
+
* @example
|
|
648
|
+
* ```typescript
|
|
649
|
+
* import { Room, onMessage } from '@esengine/server';
|
|
650
|
+
* import { withRateLimit } from '@esengine/server/ratelimit';
|
|
651
|
+
*
|
|
652
|
+
* class GameRoom extends withRateLimit(Room, {
|
|
653
|
+
* messagesPerSecond: 10,
|
|
654
|
+
* burstSize: 20,
|
|
655
|
+
* onLimited: (player, type, result) => {
|
|
656
|
+
* player.send('Error', {
|
|
657
|
+
* code: 'RATE_LIMITED',
|
|
658
|
+
* retryAfter: result.retryAfter
|
|
659
|
+
* });
|
|
660
|
+
* }
|
|
661
|
+
* }) {
|
|
662
|
+
* @onMessage('Move')
|
|
663
|
+
* handleMove(data: { x: number, y: number }, player: Player) {
|
|
664
|
+
* // Protected by rate limit
|
|
665
|
+
* }
|
|
666
|
+
* }
|
|
667
|
+
* ```
|
|
668
|
+
*
|
|
669
|
+
* @example
|
|
670
|
+
* // Combine with auth
|
|
671
|
+
* ```typescript
|
|
672
|
+
* class GameRoom extends withRateLimit(
|
|
673
|
+
* withRoomAuth(Room, { requireAuth: true }),
|
|
674
|
+
* { messagesPerSecond: 10 }
|
|
675
|
+
* ) {
|
|
676
|
+
* // Both auth and rate limit active
|
|
677
|
+
* }
|
|
678
|
+
* ```
|
|
679
|
+
*/
|
|
680
|
+
declare function withRateLimit<TBase extends new (...args: any[]) => Room = new (...args: any[]) => Room>(Base: TBase, config?: RateLimitConfig): TBase & (new (...args: any[]) => IRateLimitRoom);
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* @zh 速率限制装饰器
|
|
684
|
+
* @en Rate limit decorators
|
|
685
|
+
*/
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* @zh 速率限制元数据存储键
|
|
689
|
+
* @en Rate limit metadata storage key
|
|
690
|
+
*/
|
|
691
|
+
declare const RATE_LIMIT_METADATA_KEY: unique symbol;
|
|
692
|
+
/**
|
|
693
|
+
* @zh 获取速率限制元数据
|
|
694
|
+
* @en Get rate limit metadata
|
|
695
|
+
*
|
|
696
|
+
* @param target - @zh 目标对象 @en Target object
|
|
697
|
+
* @param messageType - @zh 消息类型 @en Message type
|
|
698
|
+
* @returns @zh 元数据 @en Metadata
|
|
699
|
+
*/
|
|
700
|
+
declare function getRateLimitMetadata(target: any, messageType: string): RateLimitMetadata | undefined;
|
|
701
|
+
/**
|
|
702
|
+
* @zh 速率限制装饰器
|
|
703
|
+
* @en Rate limit decorator
|
|
704
|
+
*
|
|
705
|
+
* @zh 为消息处理器设置独立的速率限制配置
|
|
706
|
+
* @en Set independent rate limit configuration for message handler
|
|
707
|
+
*
|
|
708
|
+
* @example
|
|
709
|
+
* ```typescript
|
|
710
|
+
* class GameRoom extends withRateLimit(Room) {
|
|
711
|
+
* @rateLimit({ messagesPerSecond: 1, burstSize: 2 })
|
|
712
|
+
* @onMessage('Trade')
|
|
713
|
+
* handleTrade(data: TradeData, player: Player) {
|
|
714
|
+
* // This message has stricter rate limit
|
|
715
|
+
* }
|
|
716
|
+
*
|
|
717
|
+
* @rateLimit({ cost: 5 })
|
|
718
|
+
* @onMessage('ExpensiveAction')
|
|
719
|
+
* handleExpensiveAction(data: any, player: Player) {
|
|
720
|
+
* // This message consumes 5 tokens
|
|
721
|
+
* }
|
|
722
|
+
* }
|
|
723
|
+
* ```
|
|
724
|
+
*/
|
|
725
|
+
declare function rateLimit(config?: MessageRateLimitConfig): MethodDecorator;
|
|
726
|
+
/**
|
|
727
|
+
* @zh 豁免速率限制装饰器
|
|
728
|
+
* @en Exempt from rate limit decorator
|
|
729
|
+
*
|
|
730
|
+
* @zh 标记消息处理器不受速率限制
|
|
731
|
+
* @en Mark message handler as exempt from rate limit
|
|
732
|
+
*
|
|
733
|
+
* @example
|
|
734
|
+
* ```typescript
|
|
735
|
+
* class GameRoom extends withRateLimit(Room) {
|
|
736
|
+
* @noRateLimit()
|
|
737
|
+
* @onMessage('Heartbeat')
|
|
738
|
+
* handleHeartbeat(data: any, player: Player) {
|
|
739
|
+
* // This message is not rate limited
|
|
740
|
+
* }
|
|
741
|
+
*
|
|
742
|
+
* @noRateLimit()
|
|
743
|
+
* @onMessage('Ping')
|
|
744
|
+
* handlePing(data: any, player: Player) {
|
|
745
|
+
* player.send('Pong', {});
|
|
746
|
+
* }
|
|
747
|
+
* }
|
|
748
|
+
* ```
|
|
749
|
+
*/
|
|
750
|
+
declare function noRateLimit(): MethodDecorator;
|
|
751
|
+
/**
|
|
752
|
+
* @zh 速率限制消息装饰器(直接指定消息类型)
|
|
753
|
+
* @en Rate limit message decorator (directly specify message type)
|
|
754
|
+
*
|
|
755
|
+
* @zh 当无法自动获取消息类型时使用此装饰器
|
|
756
|
+
* @en Use this decorator when message type cannot be obtained automatically
|
|
757
|
+
*
|
|
758
|
+
* @example
|
|
759
|
+
* ```typescript
|
|
760
|
+
* class GameRoom extends withRateLimit(Room) {
|
|
761
|
+
* @rateLimitMessage('Trade', { messagesPerSecond: 1 })
|
|
762
|
+
* @onMessage('Trade')
|
|
763
|
+
* handleTrade(data: TradeData, player: Player) {
|
|
764
|
+
* // Explicitly rate limited
|
|
765
|
+
* }
|
|
766
|
+
* }
|
|
767
|
+
* ```
|
|
768
|
+
*/
|
|
769
|
+
declare function rateLimitMessage(messageType: string, config?: MessageRateLimitConfig): MethodDecorator;
|
|
770
|
+
/**
|
|
771
|
+
* @zh 豁免速率限制消息装饰器(直接指定消息类型)
|
|
772
|
+
* @en Exempt rate limit message decorator (directly specify message type)
|
|
773
|
+
*
|
|
774
|
+
* @example
|
|
775
|
+
* ```typescript
|
|
776
|
+
* class GameRoom extends withRateLimit(Room) {
|
|
777
|
+
* @noRateLimitMessage('Heartbeat')
|
|
778
|
+
* @onMessage('Heartbeat')
|
|
779
|
+
* handleHeartbeat(data: any, player: Player) {
|
|
780
|
+
* // Explicitly exempted
|
|
781
|
+
* }
|
|
782
|
+
* }
|
|
783
|
+
* ```
|
|
784
|
+
*/
|
|
785
|
+
declare function noRateLimitMessage(messageType: string): MethodDecorator;
|
|
786
|
+
|
|
787
|
+
export { FixedWindowStrategy, type IRateLimitContext, type IRateLimitRoom, type IRateLimitStrategy, type MessageRateLimitConfig, RATE_LIMIT_METADATA_KEY, type RateLimitConfig, RateLimitContext, type RateLimitMetadata, type RateLimitResult, type RateLimitRoomClass, type RateLimitStrategyType, type RateLimitedPlayer, type RateLimitedRoom, SlidingWindowStrategy, type StrategyConfig, TokenBucketStrategy, createFixedWindowStrategy, createSlidingWindowStrategy, createTokenBucketStrategy, getPlayerRateLimitContext, getRateLimitMetadata, noRateLimit, noRateLimitMessage, rateLimit, rateLimitMessage, withRateLimit };
|