refactor(server): use core Logger instead of console.log (#416)
* refactor(server): use core Logger instead of console.log - Add logger.ts module wrapping @esengine/ecs-framework's createLogger - Replace all console.log/warn/error with structured logger calls - Add @esengine/ecs-framework as dependency for Logger support - Fix type errors in auth/providers.test.ts and ECSRoom.test.ts - Refactor withRateLimit mixin with elegant type helper functions * chore: update pnpm-lock.yaml * fix(server): fix ReDoS vulnerability in route path regex
This commit is contained in:
@@ -42,7 +42,7 @@ describe('TokenBucketStrategy', () => {
|
||||
strategy.consume('user-1');
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 150));
|
||||
await new Promise((resolve) => setTimeout(resolve, 150));
|
||||
|
||||
const result = strategy.consume('user-1');
|
||||
expect(result.allowed).toBe(true);
|
||||
@@ -92,7 +92,7 @@ describe('TokenBucketStrategy', () => {
|
||||
it('should clean up full buckets', async () => {
|
||||
strategy.consume('user-1');
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
strategy.cleanup();
|
||||
});
|
||||
@@ -131,7 +131,7 @@ describe('SlidingWindowStrategy', () => {
|
||||
strategy.consume('user-1');
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 1100));
|
||||
|
||||
const result = strategy.consume('user-1');
|
||||
expect(result.allowed).toBe(true);
|
||||
@@ -192,7 +192,7 @@ describe('FixedWindowStrategy', () => {
|
||||
strategy.consume('user-1');
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 1100));
|
||||
|
||||
const result = strategy.consume('user-1');
|
||||
expect(result.allowed).toBe(true);
|
||||
@@ -224,7 +224,7 @@ describe('FixedWindowStrategy', () => {
|
||||
it('should clean up old windows', async () => {
|
||||
strategy.consume('user-1');
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 2100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 2100));
|
||||
|
||||
strategy.cleanup();
|
||||
});
|
||||
|
||||
@@ -100,7 +100,7 @@ function getMessageTypeFromMethod(target: any, methodName: string): string | und
|
||||
*/
|
||||
export function rateLimit(config?: MessageRateLimitConfig): MethodDecorator {
|
||||
return function (
|
||||
target: Object,
|
||||
target: object,
|
||||
propertyKey: string | symbol,
|
||||
descriptor: PropertyDescriptor
|
||||
): PropertyDescriptor {
|
||||
@@ -159,7 +159,7 @@ export function rateLimit(config?: MessageRateLimitConfig): MethodDecorator {
|
||||
*/
|
||||
export function noRateLimit(): MethodDecorator {
|
||||
return function (
|
||||
target: Object,
|
||||
target: object,
|
||||
propertyKey: string | symbol,
|
||||
descriptor: PropertyDescriptor
|
||||
): PropertyDescriptor {
|
||||
@@ -202,7 +202,7 @@ export function rateLimitMessage(
|
||||
config?: MessageRateLimitConfig
|
||||
): MethodDecorator {
|
||||
return function (
|
||||
target: Object,
|
||||
target: object,
|
||||
propertyKey: string | symbol,
|
||||
descriptor: PropertyDescriptor
|
||||
): PropertyDescriptor {
|
||||
@@ -232,7 +232,7 @@ export function rateLimitMessage(
|
||||
*/
|
||||
export function noRateLimitMessage(messageType: string): MethodDecorator {
|
||||
return function (
|
||||
target: Object,
|
||||
target: object,
|
||||
propertyKey: string | symbol,
|
||||
descriptor: PropertyDescriptor
|
||||
): PropertyDescriptor {
|
||||
|
||||
@@ -108,6 +108,50 @@ function setPlayerRateLimitContext(player: Player, context: IRateLimitContext):
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 抽象构造器类型
|
||||
* @en Abstract constructor type
|
||||
*/
|
||||
type AbstractConstructor<T = object> = abstract new (...args: any[]) => T;
|
||||
|
||||
/**
|
||||
* @zh 可混入的 Room 构造器类型(支持抽象和具体类)
|
||||
* @en Mixable Room constructor type (supports both abstract and concrete classes)
|
||||
*/
|
||||
type RoomConstructor = AbstractConstructor<Room>;
|
||||
|
||||
// ============================================================================
|
||||
// Mixin 类型辅助函数 | Mixin Type Helpers
|
||||
// ============================================================================
|
||||
// TypeScript 的 mixin 模式存在类型系统限制:
|
||||
// 1. ES6 class 语法不支持 `extends` 抽象类型参数
|
||||
// 2. 泛型类型参数无法直接用于 class extends 子句
|
||||
// 以下辅助函数封装了必要的类型转换,使 mixin 实现更清晰
|
||||
//
|
||||
// TypeScript mixin pattern has type system limitations:
|
||||
// 1. ES6 class syntax doesn't support `extends` with abstract type parameters
|
||||
// 2. Generic type parameters cannot be used directly in class extends clause
|
||||
// The following helpers encapsulate necessary type casts for cleaner mixin implementation
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @zh 将抽象 Room 构造器转换为可继承的具体构造器
|
||||
* @en Convert abstract Room constructor to extendable concrete constructor
|
||||
*/
|
||||
function toExtendable<T extends RoomConstructor>(Base: T): new (...args: any[]) => Room {
|
||||
return Base as unknown as new (...args: any[]) => Room;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 将 mixin 类转换为正确的返回类型
|
||||
* @en Cast mixin class to correct return type
|
||||
*/
|
||||
function toMixinResult<TBase extends RoomConstructor, TInterface>(
|
||||
MixinClass: AbstractConstructor<any>
|
||||
): TBase & AbstractConstructor<TInterface> {
|
||||
return MixinClass as unknown as TBase & AbstractConstructor<TInterface>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 包装房间类添加速率限制功能
|
||||
* @en Wrap room class with rate limit functionality
|
||||
@@ -148,10 +192,10 @@ function setPlayerRateLimitContext(player: Player, context: IRateLimitContext):
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function withRateLimit<TBase extends new (...args: any[]) => Room = new (...args: any[]) => Room>(
|
||||
export function withRateLimit<TBase extends RoomConstructor>(
|
||||
Base: TBase,
|
||||
config: RateLimitConfig = {}
|
||||
): TBase & (new (...args: any[]) => IRateLimitRoom) {
|
||||
): TBase & AbstractConstructor<IRateLimitRoom> {
|
||||
const {
|
||||
messagesPerSecond = 10,
|
||||
burstSize = 20,
|
||||
@@ -163,7 +207,9 @@ export function withRateLimit<TBase extends new (...args: any[]) => Room = new (
|
||||
cleanupInterval = 60000
|
||||
} = config;
|
||||
|
||||
abstract class RateLimitRoom extends (Base as new (...args: any[]) => Room) implements IRateLimitRoom {
|
||||
const BaseRoom = toExtendable(Base);
|
||||
|
||||
abstract class RateLimitRoom extends BaseRoom implements IRateLimitRoom {
|
||||
private _rateLimitStrategy: IRateLimitStrategy;
|
||||
private _playerContexts: WeakMap<Player, RateLimitContext> = new WeakMap();
|
||||
private _cleanupTimer: ReturnType<typeof setInterval> | null = null;
|
||||
@@ -381,5 +427,5 @@ export function withRateLimit<TBase extends new (...args: any[]) => Room = new (
|
||||
}
|
||||
}
|
||||
|
||||
return RateLimitRoom as unknown as TBase & (new (...args: any[]) => IRateLimitRoom);
|
||||
return toMixinResult<TBase, IRateLimitRoom>(RateLimitRoom);
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ export class SlidingWindowStrategy implements IRateLimitStrategy {
|
||||
*/
|
||||
private _cleanExpiredTimestamps(window: WindowState, now: number): void {
|
||||
const cutoff = now - this._windowMs;
|
||||
window.timestamps = window.timestamps.filter(ts => ts > cutoff);
|
||||
window.timestamps = window.timestamps.filter((ts) => ts > cutoff);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user