diff --git a/.gitmodules b/.gitmodules index 56f07aa0..65de3a54 100644 --- a/.gitmodules +++ b/.gitmodules @@ -28,9 +28,6 @@ [submodule "thirdparty/ecs-astar"] path = thirdparty/ecs-astar url = https://github.com/esengine/ecs-astar.git -[submodule "examples/electric-world"] - path = examples/electric-world - url = https://github.com/esengine/electric-world.git [submodule "examples/lawn-mower-demo"] path = examples/lawn-mower-demo url = https://github.com/esengine/lawn-mower-demo.git \ No newline at end of file diff --git a/examples/electric-world b/examples/electric-world deleted file mode 160000 index 2b36519b..00000000 --- a/examples/electric-world +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2b36519bd964bce71cecdb92337c0a81d7555077 diff --git a/package-lock.json b/package-lock.json index 6f8c93a3..e1fc6c0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11603,7 +11603,7 @@ }, "packages/network-client": { "name": "@esengine/network-client", - "version": "1.0.0", + "version": "1.0.1", "license": "MIT", "dependencies": { "@esengine/ecs-framework": "file:../core", @@ -11622,7 +11622,7 @@ }, "packages/network-server": { "name": "@esengine/network-server", - "version": "1.0.0", + "version": "1.0.1", "license": "MIT", "dependencies": { "@esengine/ecs-framework": "file:../core", @@ -11643,7 +11643,7 @@ }, "packages/network-shared": { "name": "@esengine/network-shared", - "version": "1.0.0", + "version": "1.0.2", "license": "MIT", "dependencies": { "@esengine/ecs-framework": "file:../core", diff --git a/packages/math/package.json b/packages/math/package.json index b27761a8..6931191a 100644 --- a/packages/math/package.json +++ b/packages/math/package.json @@ -2,7 +2,6 @@ "name": "@esengine/ecs-framework-math", "version": "1.0.5", "description": "ECS框架2D数学库 - 提供向量、矩阵、几何形状和碰撞检测功能", - "type": "module", "main": "bin/index.js", "types": "bin/index.d.ts", "files": [ @@ -22,7 +21,7 @@ "typescript" ], "scripts": { - "clean": "rimraf bin dist", + "clean": "rimraf bin dist tsconfig.tsbuildinfo", "build:ts": "tsc", "prebuild": "npm run clean", "build": "npm run build:ts", diff --git a/packages/math/tsconfig.json b/packages/math/tsconfig.json index 38b03ca3..29b05f3e 100644 --- a/packages/math/tsconfig.json +++ b/packages/math/tsconfig.json @@ -6,6 +6,7 @@ "declaration": true, "sourceMap": true, "strict": true, + "composite": true, "target": "ES2020", "module": "ESNext", "moduleResolution": "node", diff --git a/packages/network-client/README.md b/packages/network-client/README.md deleted file mode 100644 index 8b0069b5..00000000 --- a/packages/network-client/README.md +++ /dev/null @@ -1,101 +0,0 @@ -# ECS Framework 网络库 - 客户端 - -该包提供了完整的网络客户端功能,包括连接管理、预测、插值等现代网络游戏必需的特性。 - -## 主要功能 - -- ✅ **传输层支持**: WebSocket 和 HTTP 两种传输方式 -- ✅ **网络客户端**: 完整的连接、认证、房间管理 -- ✅ **网络行为**: ClientNetworkBehaviour 基类和 NetworkIdentity 组件 -- ✅ **装饰器系统**: @SyncVar, @ClientRpc, @ServerRpc 装饰器 -- ✅ **客户端预测**: 减少网络延迟感知的预测系统 -- ✅ **插值系统**: 平滑的网络对象状态同步 -- ✅ **TypeScript**: 完整的类型支持 - -## 安装 - -```bash -npm install @esengine/ecs-framework-network-client -``` - -## 快速开始 - -```typescript -import { - NetworkClient, - WebSocketClientTransport, - ClientNetworkBehaviour, - SyncVar, - ServerRpc -} from '@esengine/ecs-framework-network-client'; - -// 创建网络客户端 -const client = new NetworkClient({ - transport: 'websocket', - transportConfig: { - host: 'localhost', - port: 8080, - secure: false - } -}); - -// 连接到服务器 -await client.connect(); - -// 认证 -const userInfo = await client.authenticate('username', 'password'); - -// 获取房间列表 -const rooms = await client.getRoomList(); - -// 加入房间 -const roomInfo = await client.joinRoom('room-id'); -``` - -## 网络行为示例 - -```typescript -class PlayerController extends ClientNetworkBehaviour { - @SyncVar({ clientCanModify: true }) - position: { x: number; y: number } = { x: 0, y: 0 }; - - @SyncVar() - health: number = 100; - - @ServerRpc({ requireLocalPlayer: true }) - async move(direction: string): Promise { - // 这个方法会被发送到服务器执行 - } - - @ClientRpc() - onDamaged(damage: number): void { - // 这个方法会被服务器调用 - console.log(`Received damage: ${damage}`); - } -} -``` - -## 预测和插值 - -```typescript -import { PredictionSystem, InterpolationSystem } from '@esengine/ecs-framework-network-client'; - -// 启用预测系统 -const predictionSystem = new PredictionSystem(scene, 64, 500); -scene.addSystem(predictionSystem); - -// 启用插值系统 -const interpolationSystem = new InterpolationSystem(scene, { - delay: 100, - enableExtrapolation: false -}); -scene.addSystem(interpolationSystem); -``` - -## 编译状态 - -✅ **编译成功** - 所有 TypeScript 错误已修复,包生成完成 - -## License - -MIT \ No newline at end of file diff --git a/packages/network-client/build-rollup.cjs b/packages/network-client/build-rollup.cjs index 212e2edf..cfb4aec4 100644 --- a/packages/network-client/build-rollup.cjs +++ b/packages/network-client/build-rollup.cjs @@ -2,27 +2,32 @@ const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); -console.log('🚀 使用 Rollup 构建 network-client 包...'); +console.log('🚀 使用 Rollup 构建 @esengine/network-client 包...'); async function main() { try { + // 清理旧的dist目录 if (fs.existsSync('./dist')) { console.log('🧹 清理旧的构建文件...'); execSync('rimraf ./dist', { stdio: 'inherit' }); } + // 执行Rollup构建 console.log('📦 执行 Rollup 构建...'); - execSync('rollup -c rollup.config.cjs', { stdio: 'inherit' }); + execSync('npx rollup -c rollup.config.cjs', { stdio: 'inherit' }); + // 生成package.json console.log('📋 生成 package.json...'); generatePackageJson(); + // 复制其他文件 console.log('📁 复制必要文件...'); copyFiles(); + // 输出构建结果 showBuildResults(); - console.log('✅ network-client 构建完成!'); + console.log('✅ @esengine/network-client 构建完成!'); console.log('\n🚀 发布命令:'); console.log('cd dist && npm publish'); @@ -53,7 +58,7 @@ function generatePackageJson() { files: [ 'index.mjs', 'index.mjs.map', - 'index.cjs', + 'index.cjs', 'index.cjs.map', 'index.umd.js', 'index.umd.js.map', @@ -63,18 +68,19 @@ function generatePackageJson() { ], keywords: [ 'ecs', - 'networking', + 'network', 'client', - 'prediction', - 'interpolation', - 'game-engine', + 'multiplayer', + 'game', + 'browser', + 'cocos', 'typescript' ], author: sourcePackage.author, license: sourcePackage.license, repository: sourcePackage.repository, dependencies: sourcePackage.dependencies, - peerDependencies: sourcePackage.peerDependencies, + publishConfig: sourcePackage.publishConfig, engines: { node: '>=16.0.0' }, @@ -87,7 +93,7 @@ function generatePackageJson() { function copyFiles() { const filesToCopy = [ { src: './README.md', dest: './dist/README.md' }, - { src: '../../LICENSE', dest: './dist/LICENSE' } + { src: './LICENSE', dest: './dist/LICENSE' } ]; filesToCopy.forEach(({ src, dest }) => { diff --git a/packages/network-client/jest.config.cjs b/packages/network-client/jest.config.cjs index 74a4925b..a54a15fe 100644 --- a/packages/network-client/jest.config.cjs +++ b/packages/network-client/jest.config.cjs @@ -1,7 +1,7 @@ /** @type {import('ts-jest').JestConfigWithTsJest} */ module.exports = { preset: 'ts-jest', - testEnvironment: 'jsdom', // 客户端库使用 jsdom 环境 + testEnvironment: 'jsdom', // 客户端使用jsdom环境 roots: ['/tests'], testMatch: ['**/*.test.ts', '**/*.spec.ts'], testPathIgnorePatterns: ['/node_modules/'], @@ -18,16 +18,10 @@ module.exports = { coverageReporters: ['text', 'lcov', 'html'], coverageThreshold: { global: { - branches: 60, + branches: 70, functions: 70, lines: 70, statements: 70 - }, - './src/core/': { - branches: 70, - functions: 80, - lines: 80, - statements: 80 } }, verbose: true, diff --git a/packages/network-client/package.json b/packages/network-client/package.json index 36081c48..f022a3fd 100644 --- a/packages/network-client/package.json +++ b/packages/network-client/package.json @@ -1,8 +1,7 @@ { - "name": "@esengine/ecs-framework-network-client", - "version": "1.0.17", - "description": "ECS Framework 网络库 - 客户端实现", - "type": "module", + "name": "@esengine/network-client", + "version": "1.0.1", + "description": "ECS Framework网络层 - 客户端实现", "main": "bin/index.js", "types": "bin/index.d.ts", "exports": { @@ -22,15 +21,16 @@ ], "keywords": [ "ecs", - "networking", + "network", "client", - "prediction", - "interpolation", - "game-engine", + "multiplayer", + "game", + "browser", + "cocos", "typescript" ], "scripts": { - "clean": "rimraf bin dist", + "clean": "rimraf bin dist tsconfig.tsbuildinfo", "build:ts": "tsc", "prebuild": "npm run clean", "build": "npm run build:ts", @@ -45,32 +45,21 @@ "test": "jest --config jest.config.cjs", "test:watch": "jest --watch --config jest.config.cjs", "test:coverage": "jest --coverage --config jest.config.cjs", - "test:ci": "jest --ci --coverage --config jest.config.cjs", - "test:clear": "jest --clearCache" + "test:ci": "jest --ci --coverage --config jest.config.cjs" }, "author": "yhh", "license": "MIT", "dependencies": { - "ws": "^8.18.0" - }, - "peerDependencies": { - "@esengine/ecs-framework": ">=2.1.29", - "@esengine/ecs-framework-network-shared": ">=1.0.0" + "@esengine/ecs-framework": "file:../core", + "@esengine/network-shared": "file:../network-shared", + "reflect-metadata": "^0.2.2" }, "devDependencies": { - "@esengine/ecs-framework": "*", - "@esengine/ecs-framework-network-shared": "*", - "@rollup/plugin-commonjs": "^28.0.3", - "@rollup/plugin-node-resolve": "^16.0.1", - "@rollup/plugin-terser": "^0.4.4", "@types/jest": "^29.5.14", "@types/node": "^20.19.0", - "@types/ws": "^8.5.13", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "rimraf": "^5.0.0", - "rollup": "^4.42.0", - "rollup-plugin-dts": "^6.2.1", "ts-jest": "^29.4.0", "typescript": "^5.8.3" }, diff --git a/packages/network-client/rollup.config.cjs b/packages/network-client/rollup.config.cjs index 6de2e3eb..6135e25a 100644 --- a/packages/network-client/rollup.config.cjs +++ b/packages/network-client/rollup.config.cjs @@ -7,17 +7,18 @@ const { readFileSync } = require('fs'); const pkg = JSON.parse(readFileSync('./package.json', 'utf8')); const banner = `/** - * @esengine/ecs-framework-network-client v${pkg.version} - * ECS Framework 网络库 - 客户端实现 + * @esengine/network-client v${pkg.version} + * ECS网络层客户端实现 * * @author ${pkg.author} * @license ${pkg.license} */`; +// 外部依赖,不打包进bundle const external = [ - 'ws', '@esengine/ecs-framework', - '@esengine/ecs-framework-network-shared' + '@esengine/network-shared', + 'reflect-metadata' ]; const commonPlugins = [ @@ -81,7 +82,7 @@ module.exports = [ } }, - // UMD构建 + // UMD构建 - 用于浏览器直接使用 { input: 'bin/index.js', output: { @@ -92,10 +93,9 @@ module.exports = [ sourcemap: true, exports: 'named', globals: { - 'ws': 'WebSocket', - 'uuid': 'uuid', '@esengine/ecs-framework': 'ECS', - '@esengine/ecs-framework-network-shared': 'ECSNetworkShared' + '@esengine/network-shared': 'ECSNetworkShared', + 'reflect-metadata': 'Reflect' } }, plugins: [ @@ -119,7 +119,7 @@ module.exports = [ file: 'dist/index.d.ts', format: 'es', banner: `/** - * @esengine/ecs-framework-network-client v${pkg.version} + * @esengine/network-client v${pkg.version} * TypeScript definitions */` }, diff --git a/packages/network-client/src/core/ClientNetworkBehaviour.ts b/packages/network-client/src/core/ClientNetworkBehaviour.ts deleted file mode 100644 index 90039e93..00000000 --- a/packages/network-client/src/core/ClientNetworkBehaviour.ts +++ /dev/null @@ -1,179 +0,0 @@ -/** - * 客户端网络行为基类 - * - * 类似Unity Mirror的NetworkBehaviour,提供网络功能 - */ - -import { Component, Entity } from '@esengine/ecs-framework'; -import { NetworkValue } from '@esengine/ecs-framework-network-shared'; -import { NetworkClient } from './NetworkClient'; -import { NetworkIdentity } from './NetworkIdentity'; - -/** - * 客户端网络行为基类 - */ -export abstract class ClientNetworkBehaviour extends Component { - /** 网络标识组件 */ - protected networkIdentity: NetworkIdentity | null = null; - /** 网络客户端实例 */ - protected networkClient: NetworkClient | null = null; - - /** - * 组件初始化 - */ - initialize(): void { - - // 获取网络标识组件 - this.networkIdentity = this.entity.getComponent(NetworkIdentity); - if (!this.networkIdentity) { - throw new Error('NetworkBehaviour requires NetworkIdentity component'); - } - - // 从全局获取网络客户端实例 - this.networkClient = this.getNetworkClient(); - } - - /** - * 获取网络客户端实例 - */ - protected getNetworkClient(): NetworkClient | null { - // 这里需要实现从全局管理器获取客户端实例的逻辑 - // 暂时返回null,在实际使用时需要通过单例模式或依赖注入获取 - return null; - } - - /** - * 是否为本地玩家 - */ - get isLocalPlayer(): boolean { - return this.networkIdentity?.isLocalPlayer ?? false; - } - - /** - * 是否为服务器权威 - */ - get hasAuthority(): boolean { - return this.networkIdentity?.hasAuthority ?? false; - } - - /** - * 网络ID - */ - get networkId(): string { - return this.networkIdentity?.networkId ?? ''; - } - - /** - * 是否已连接 - */ - get isConnected(): boolean { - return this.networkClient?.isInRoom() ?? false; - } - - /** - * 发送RPC到服务器 - */ - protected async sendServerRpc(methodName: string, ...args: NetworkValue[]): Promise { - if (!this.networkClient || !this.networkIdentity) { - throw new Error('Network client or identity not available'); - } - - return this.networkClient.sendRpc(this.networkIdentity.networkId, methodName, args, true); - } - - /** - * 发送不可靠RPC到服务器 - */ - protected async sendServerRpcUnreliable(methodName: string, ...args: NetworkValue[]): Promise { - if (!this.networkClient || !this.networkIdentity) { - throw new Error('Network client or identity not available'); - } - - await this.networkClient.sendRpc(this.networkIdentity.networkId, methodName, args, false); - } - - /** - * 更新SyncVar - */ - protected async updateSyncVar(fieldName: string, value: NetworkValue): Promise { - if (!this.networkClient || !this.networkIdentity) { - throw new Error('Network client or identity not available'); - } - - await this.networkClient.updateSyncVar(this.networkIdentity.networkId, fieldName, value); - } - - /** - * 当收到RPC调用时 - */ - onRpcReceived(methodName: string, args: NetworkValue[]): void { - // 尝试调用对应的方法 - const method = (this as any)[methodName]; - if (typeof method === 'function') { - try { - method.apply(this, args); - } catch (error) { - console.error(`Error calling RPC method ${methodName}:`, error); - } - } else { - console.warn(`RPC method ${methodName} not found on ${this.constructor.name}`); - } - } - - /** - * 当SyncVar更新时 - */ - onSyncVarChanged(fieldName: string, oldValue: NetworkValue, newValue: NetworkValue): void { - // 子类可以重写此方法来处理SyncVar变化 - } - - /** - * 当获得权威时 - */ - onStartAuthority(): void { - // 子类可以重写此方法 - } - - /** - * 当失去权威时 - */ - onStopAuthority(): void { - // 子类可以重写此方法 - } - - /** - * 当成为本地玩家时 - */ - onStartLocalPlayer(): void { - // 子类可以重写此方法 - } - - /** - * 当不再是本地玩家时 - */ - onStopLocalPlayer(): void { - // 子类可以重写此方法 - } - - /** - * 网络启动时调用 - */ - onNetworkStart(): void { - // 子类可以重写此方法 - } - - /** - * 网络停止时调用 - */ - onNetworkStop(): void { - // 子类可以重写此方法 - } - - /** - * 组件销毁 - */ - onDestroy(): void { - this.networkIdentity = null; - this.networkClient = null; - } -} \ No newline at end of file diff --git a/packages/network-client/src/core/NetworkClient.ts b/packages/network-client/src/core/NetworkClient.ts deleted file mode 100644 index 03b6f491..00000000 --- a/packages/network-client/src/core/NetworkClient.ts +++ /dev/null @@ -1,638 +0,0 @@ -/** - * 网络客户端主类 - * - * 管理连接、认证、房间加入等功能 - */ - -import { Scene, EntityManager, Emitter, ITimer, Core } from '@esengine/ecs-framework'; -import { - NetworkIdentity as SharedNetworkIdentity, - NetworkValue, - RpcMessage, - SyncVarMessage -} from '@esengine/ecs-framework-network-shared'; -import { - ClientTransport, - WebSocketClientTransport, - HttpClientTransport, - ConnectionState, - ClientMessage, - ClientTransportConfig, - WebSocketClientConfig, - HttpClientConfig -} from '../transport'; - -/** - * 网络客户端配置 - */ -export interface NetworkClientConfig { - /** 传输类型 */ - transport: 'websocket' | 'http'; - /** 传输配置 */ - transportConfig: WebSocketClientConfig | HttpClientConfig; - /** 是否启用预测 */ - enablePrediction?: boolean; - /** 预测缓冲区大小 */ - predictionBuffer?: number; - /** 是否启用插值 */ - enableInterpolation?: boolean; - /** 插值延迟(毫秒) */ - interpolationDelay?: number; - /** 网络对象同步间隔(毫秒) */ - syncInterval?: number; - /** 是否启用调试 */ - debug?: boolean; -} - -/** - * 用户信息 - */ -export interface UserInfo { - /** 用户ID */ - userId: string; - /** 用户名 */ - username: string; - /** 用户数据 */ - data?: NetworkValue; -} - -/** - * 房间信息 - */ -export interface RoomInfo { - /** 房间ID */ - roomId: string; - /** 房间名称 */ - name: string; - /** 当前人数 */ - playerCount: number; - /** 最大人数 */ - maxPlayers: number; - /** 房间元数据 */ - metadata?: NetworkValue; - /** 是否私有房间 */ - isPrivate?: boolean; -} - -/** - * 认证消息 - */ -export interface AuthMessage { - action: string; - username: string; - password?: string; - userData?: NetworkValue; -} - -/** - * 房间消息 - */ -export interface RoomMessage { - action: string; - roomId?: string; - name?: string; - maxPlayers?: number; - metadata?: NetworkValue; - isPrivate?: boolean; - password?: string; -} - -/** - * 网络客户端事件 - */ -export interface NetworkClientEvents { - /** 连接建立 */ - 'connected': () => void; - /** 连接断开 */ - 'disconnected': (reason: string) => void; - /** 认证成功 */ - 'authenticated': (userInfo: UserInfo) => void; - /** 加入房间成功 */ - 'joined-room': (roomInfo: RoomInfo) => void; - /** 离开房间 */ - 'left-room': (roomId: string) => void; - /** 房间列表更新 */ - 'room-list-updated': (rooms: RoomInfo[]) => void; - /** 玩家加入房间 */ - 'player-joined': (userId: string, userInfo: UserInfo) => void; - /** 玩家离开房间 */ - 'player-left': (userId: string) => void; - /** 网络对象创建 */ - 'network-object-created': (networkId: string, data: NetworkValue) => void; - /** 网络对象销毁 */ - 'network-object-destroyed': (networkId: string) => void; - /** SyncVar 更新 */ - 'syncvar-updated': (networkId: string, fieldName: string, value: NetworkValue) => void; - /** RPC 调用 */ - 'rpc-received': (networkId: string, methodName: string, args: NetworkValue[]) => void; - /** 错误发生 */ - 'error': (error: Error) => void; -} - -/** - * 网络客户端主类 - */ -export class NetworkClient { - private transport: ClientTransport; - private config: NetworkClientConfig; - private currentUser: UserInfo | null = null; - private currentRoom: RoomInfo | null = null; - private availableRooms: Map = new Map(); - private networkObjects: Map = new Map(); - private pendingRpcs: Map }> = new Map(); - private scene: Scene | null = null; - private eventEmitter: Emitter; - - constructor(config: NetworkClientConfig) { - this.eventEmitter = new Emitter(); - - this.config = { - enablePrediction: true, - predictionBuffer: 64, - enableInterpolation: true, - interpolationDelay: 100, - syncInterval: 50, - debug: false, - ...config - }; - - this.transport = this.createTransport(); - this.setupTransportEvents(); - } - - /** - * 创建传输层 - */ - private createTransport(): ClientTransport { - switch (this.config.transport) { - case 'websocket': - return new WebSocketClientTransport(this.config.transportConfig as WebSocketClientConfig); - case 'http': - return new HttpClientTransport(this.config.transportConfig as HttpClientConfig); - default: - throw new Error(`Unsupported transport type: ${this.config.transport}`); - } - } - - /** - * 设置传输层事件监听 - */ - private setupTransportEvents(): void { - this.transport.on('connected', () => { - this.eventEmitter.emit('connected'); - }); - - this.transport.on('disconnected', (reason) => { - this.handleDisconnected(reason); - }); - - this.transport.on('message', (message) => { - this.handleMessage(message); - }); - - this.transport.on('error', (error) => { - this.eventEmitter.emit('error', error); - }); - } - - /** - * 连接到服务器 - */ - async connect(): Promise { - return this.transport.connect(); - } - - /** - * 断开连接 - */ - async disconnect(): Promise { - await this.transport.disconnect(); - this.cleanup(); - } - - /** - * 用户认证 - */ - async authenticate(username: string, password?: string, userData?: NetworkValue): Promise { - if (!this.transport.isConnected()) { - throw new Error('Not connected to server'); - } - - const authMessage: AuthMessage = { - action: 'login', - username, - password, - userData - }; - - const response = await this.sendRequestWithResponse('system', authMessage as any); - - if (response.success && response.userInfo) { - this.currentUser = response.userInfo as UserInfo; - this.eventEmitter.emit('authenticated', this.currentUser); - return this.currentUser; - } else { - throw new Error(response.error || 'Authentication failed'); - } - } - - /** - * 获取房间列表 - */ - async getRoomList(): Promise { - if (!this.isAuthenticated()) { - throw new Error('Not authenticated'); - } - - const roomMessage: RoomMessage = { - action: 'list-rooms' - }; - - const response = await this.sendRequestWithResponse('system', roomMessage as any); - - if (response.success && response.rooms) { - this.availableRooms.clear(); - response.rooms.forEach((room: RoomInfo) => { - this.availableRooms.set(room.roomId, room); - }); - - this.eventEmitter.emit('room-list-updated', response.rooms); - return response.rooms; - } else { - throw new Error(response.error || 'Failed to get room list'); - } - } - - /** - * 创建房间 - */ - async createRoom(name: string, maxPlayers: number = 8, metadata?: NetworkValue, isPrivate = false): Promise { - if (!this.isAuthenticated()) { - throw new Error('Not authenticated'); - } - - const roomMessage: RoomMessage = { - action: 'create-room', - name, - maxPlayers, - metadata, - isPrivate - }; - - const response = await this.sendRequestWithResponse('system', roomMessage as any); - - if (response.success && response.room) { - this.currentRoom = response.room as RoomInfo; - this.eventEmitter.emit('joined-room', this.currentRoom); - return this.currentRoom; - } else { - throw new Error(response.error || 'Failed to create room'); - } - } - - /** - * 加入房间 - */ - async joinRoom(roomId: string, password?: string): Promise { - if (!this.isAuthenticated()) { - throw new Error('Not authenticated'); - } - - const roomMessage: RoomMessage = { - action: 'join-room', - roomId, - password - }; - - const response = await this.sendRequestWithResponse('system', roomMessage as any); - - if (response.success && response.room) { - this.currentRoom = response.room as RoomInfo; - this.eventEmitter.emit('joined-room', this.currentRoom); - return this.currentRoom; - } else { - throw new Error(response.error || 'Failed to join room'); - } - } - - /** - * 离开房间 - */ - async leaveRoom(): Promise { - if (!this.currentRoom) { - return; - } - - const roomMessage: RoomMessage = { - action: 'leave-room', - roomId: this.currentRoom.roomId - }; - - try { - await this.sendRequestWithResponse('system', roomMessage as any); - } finally { - const roomId = this.currentRoom.roomId; - this.currentRoom = null; - this.networkObjects.clear(); - this.eventEmitter.emit('left-room', roomId); - } - } - - /** - * 发送RPC调用 - */ - async sendRpc(networkId: string, methodName: string, args: NetworkValue[] = [], reliable = true): Promise { - if (!this.isInRoom()) { - throw new Error('Not in a room'); - } - - const rpcMessage: any = { - networkId, - methodName, - args, - isServer: false, - messageId: this.generateMessageId() - }; - - if (reliable) { - return this.sendRequestWithResponse('rpc', rpcMessage); - } else { - await this.transport.sendMessage({ - type: 'rpc', - data: rpcMessage as NetworkValue, - reliable: false - }); - return {}; - } - } - - /** - * 更新SyncVar - */ - async updateSyncVar(networkId: string, fieldName: string, value: NetworkValue): Promise { - if (!this.isInRoom()) { - throw new Error('Not in a room'); - } - - const syncMessage: any = { - networkId, - propertyName: fieldName, - value, - isServer: false - }; - - await this.transport.sendMessage({ - type: 'syncvar', - data: syncMessage as NetworkValue, - reliable: true - }); - } - - /** - * 设置ECS场景 - */ - setScene(scene: Scene): void { - this.scene = scene; - } - - /** - * 获取当前用户信息 - */ - getCurrentUser(): UserInfo | null { - return this.currentUser; - } - - /** - * 获取当前房间信息 - */ - getCurrentRoom(): RoomInfo | null { - return this.currentRoom; - } - - /** - * 获取连接状态 - */ - getConnectionState(): ConnectionState { - return this.transport.getState(); - } - - /** - * 是否已认证 - */ - isAuthenticated(): boolean { - return this.currentUser !== null && this.transport.isConnected(); - } - - /** - * 是否在房间中 - */ - isInRoom(): boolean { - return this.isAuthenticated() && this.currentRoom !== null; - } - - /** - * 获取网络对象 - */ - getNetworkObject(networkId: string): SharedNetworkIdentity | null { - return this.networkObjects.get(networkId) || null; - } - - /** - * 获取所有网络对象 - */ - getAllNetworkObjects(): SharedNetworkIdentity[] { - return Array.from(this.networkObjects.values()); - } - - /** - * 处理断开连接 - */ - private handleDisconnected(reason: string): void { - this.cleanup(); - this.eventEmitter.emit('disconnected', reason); - } - - /** - * 处理接收到的消息 - */ - private handleMessage(message: ClientMessage): void { - try { - switch (message.type) { - case 'system': - this.handleSystemMessage(message); - break; - case 'rpc': - this.handleRpcMessage(message); - break; - case 'syncvar': - this.handleSyncVarMessage(message); - break; - case 'custom': - this.handleCustomMessage(message); - break; - } - } catch (error) { - console.error('Error handling message:', error); - this.eventEmitter.emit('error', error as Error); - } - } - - /** - * 处理系统消息 - */ - private handleSystemMessage(message: ClientMessage): void { - const data = message.data as any; - - // 处理响应消息 - if (message.messageId && this.pendingRpcs.has(message.messageId)) { - const pending = this.pendingRpcs.get(message.messageId)!; - pending.timeout.stop(); - this.pendingRpcs.delete(message.messageId); - - if (data.success) { - pending.resolve(data); - } else { - pending.reject(new Error(data.error || 'Request failed')); - } - return; - } - - // 处理广播消息 - switch (data.action) { - case 'player-joined': - this.eventEmitter.emit('player-joined', data.userId, data.userInfo); - break; - case 'player-left': - this.eventEmitter.emit('player-left', data.userId); - break; - case 'network-object-created': - this.handleNetworkObjectCreated(data); - break; - case 'network-object-destroyed': - this.handleNetworkObjectDestroyed(data); - break; - } - } - - /** - * 处理RPC消息 - */ - private handleRpcMessage(message: ClientMessage): void { - const rpcData = message.data as any; - this.eventEmitter.emit('rpc-received', rpcData.networkId, rpcData.methodName, rpcData.args || []); - } - - /** - * 处理SyncVar消息 - */ - private handleSyncVarMessage(message: ClientMessage): void { - const syncData = message.data as any; - this.eventEmitter.emit('syncvar-updated', syncData.networkId, syncData.propertyName, syncData.value); - } - - /** - * 处理自定义消息 - */ - private handleCustomMessage(message: ClientMessage): void { - // 可扩展的自定义消息处理 - } - - /** - * 处理网络对象创建 - */ - private handleNetworkObjectCreated(data: any): void { - const networkObject = new SharedNetworkIdentity(); - this.networkObjects.set(data.networkId, networkObject); - this.eventEmitter.emit('network-object-created', data.networkId, data.data || {}); - } - - /** - * 处理网络对象销毁 - */ - private handleNetworkObjectDestroyed(data: any): void { - this.networkObjects.delete(data.networkId); - this.eventEmitter.emit('network-object-destroyed', data.networkId); - } - - /** - * 发送请求并等待响应 - */ - private sendRequestWithResponse(type: ClientMessage['type'], data: NetworkValue, timeout = 30000): Promise { - return new Promise((resolve, reject) => { - const messageId = this.generateMessageId(); - - const timeoutTimer = Core.schedule(timeout / 1000, false, this, () => { - this.pendingRpcs.delete(messageId); - reject(new Error('Request timeout')); - }); - - this.pendingRpcs.set(messageId, { - resolve, - reject, - timeout: timeoutTimer - }); - - this.transport.sendMessage({ - type, - data, - messageId, - reliable: true - }).catch(reject); - }); - } - - /** - * 生成消息ID - */ - private generateMessageId(): string { - return Date.now().toString(36) + Math.random().toString(36).substr(2); - } - - /** - * 清理资源 - */ - private cleanup(): void { - this.currentUser = null; - this.currentRoom = null; - this.availableRooms.clear(); - this.networkObjects.clear(); - - // 取消所有待处理的RPC - this.pendingRpcs.forEach(pending => { - pending.timeout.stop(); - pending.reject(new Error('Connection closed')); - }); - this.pendingRpcs.clear(); - } - - /** - * 销毁客户端 - */ - destroy(): void { - this.disconnect(); - this.transport.destroy(); - // 清理事件监听器,由于Emitter没有clear方法,我们重新创建一个 - this.eventEmitter = new Emitter(); - } - - /** - * 类型安全的事件监听 - */ - on(event: K, listener: NetworkClientEvents[K]): void { - this.eventEmitter.addObserver(event, listener, this); - } - - /** - * 移除事件监听 - */ - off(event: K, listener: NetworkClientEvents[K]): void { - this.eventEmitter.removeObserver(event, listener); - } - - /** - * 类型安全的事件触发 - */ - emit(event: K, ...args: Parameters): void { - this.eventEmitter.emit(event, ...args); - } -} \ No newline at end of file diff --git a/packages/network-client/src/core/NetworkIdentity.ts b/packages/network-client/src/core/NetworkIdentity.ts deleted file mode 100644 index 514d462f..00000000 --- a/packages/network-client/src/core/NetworkIdentity.ts +++ /dev/null @@ -1,378 +0,0 @@ -/** - * 客户端网络标识组件 - * - * 标识网络对象并管理其状态 - */ - -import { Component, Entity } from '@esengine/ecs-framework'; -import { NetworkValue } from '@esengine/ecs-framework-network-shared'; -import { ClientNetworkBehaviour } from './ClientNetworkBehaviour'; - -/** - * 网络权威类型 - */ -export enum NetworkAuthority { - /** 服务器权威 */ - SERVER = 'server', - /** 客户端权威 */ - CLIENT = 'client', - /** 所有者权威 */ - OWNER = 'owner' -} - -/** - * SyncVar信息 - */ -export interface SyncVarInfo { - /** 字段名 */ - fieldName: string; - /** 当前值 */ - currentValue: NetworkValue; - /** 上一个值 */ - previousValue: NetworkValue; - /** 最后更新时间 */ - lastUpdateTime: number; - /** 是否已变更 */ - isDirty: boolean; -} - -/** - * 网络标识组件 - */ -export class NetworkIdentity extends Component { - /** 网络ID */ - private _networkId: string = ''; - /** 所有者用户ID */ - private _ownerId: string = ''; - /** 是否为本地玩家 */ - private _isLocalPlayer: boolean = false; - /** 权威类型 */ - private _authority: NetworkAuthority = NetworkAuthority.SERVER; - /** 是否有权威 */ - private _hasAuthority: boolean = false; - /** 网络行为组件列表 */ - private networkBehaviours: ClientNetworkBehaviour[] = []; - /** SyncVar信息映射 */ - private syncVars: Map = new Map(); - /** 预测状态 */ - private predictionEnabled: boolean = false; - /** 插值状态 */ - private interpolationEnabled: boolean = true; - - /** - * 网络ID - */ - get networkId(): string { - return this._networkId; - } - - set networkId(value: string) { - this._networkId = value; - } - - /** - * 所有者用户ID - */ - get ownerId(): string { - return this._ownerId; - } - - set ownerId(value: string) { - this._ownerId = value; - } - - /** - * 是否为本地玩家 - */ - get isLocalPlayer(): boolean { - return this._isLocalPlayer; - } - - set isLocalPlayer(value: boolean) { - if (this._isLocalPlayer !== value) { - this._isLocalPlayer = value; - this.notifyLocalPlayerChanged(); - } - } - - /** - * 权威类型 - */ - get authority(): NetworkAuthority { - return this._authority; - } - - set authority(value: NetworkAuthority) { - if (this._authority !== value) { - this._authority = value; - this.updateAuthorityStatus(); - } - } - - /** - * 是否有权威 - */ - get hasAuthority(): boolean { - return this._hasAuthority; - } - - /** - * 是否启用预测 - */ - get isPredictionEnabled(): boolean { - return this.predictionEnabled; - } - - set isPredictionEnabled(value: boolean) { - this.predictionEnabled = value; - } - - /** - * 是否启用插值 - */ - get isInterpolationEnabled(): boolean { - return this.interpolationEnabled; - } - - set isInterpolationEnabled(value: boolean) { - this.interpolationEnabled = value; - } - - /** - * 组件初始化 - */ - initialize(): void { - this.collectNetworkBehaviours(); - this.notifyNetworkStart(); - } - - /** - * 收集网络行为组件 - */ - private collectNetworkBehaviours(): void { - // 暂时留空,等待实际集成时实现 - this.networkBehaviours = []; - } - - /** - * 更新权威状态 - */ - private updateAuthorityStatus(): void { - const oldHasAuthority = this._hasAuthority; - - // 根据权威类型计算是否有权威 - switch (this._authority) { - case NetworkAuthority.SERVER: - this._hasAuthority = false; // 客户端永远没有服务器权威 - break; - case NetworkAuthority.CLIENT: - this._hasAuthority = true; // 客户端权威 - break; - case NetworkAuthority.OWNER: - this._hasAuthority = this._isLocalPlayer; // 本地玩家才有权威 - break; - } - - // 通知权威变化 - if (oldHasAuthority !== this._hasAuthority) { - this.notifyAuthorityChanged(); - } - } - - /** - * 通知权威变化 - */ - private notifyAuthorityChanged(): void { - this.networkBehaviours.forEach(behaviour => { - if (this._hasAuthority) { - behaviour.onStartAuthority(); - } else { - behaviour.onStopAuthority(); - } - }); - } - - /** - * 通知本地玩家状态变化 - */ - private notifyLocalPlayerChanged(): void { - this.updateAuthorityStatus(); // 本地玩家状态影响权威 - - this.networkBehaviours.forEach(behaviour => { - if (this._isLocalPlayer) { - behaviour.onStartLocalPlayer(); - } else { - behaviour.onStopLocalPlayer(); - } - }); - } - - /** - * 通知网络启动 - */ - private notifyNetworkStart(): void { - this.networkBehaviours.forEach(behaviour => { - behaviour.onNetworkStart(); - }); - } - - /** - * 通知网络停止 - */ - private notifyNetworkStop(): void { - this.networkBehaviours.forEach(behaviour => { - behaviour.onNetworkStop(); - }); - } - - /** - * 处理RPC调用 - */ - handleRpcCall(methodName: string, args: NetworkValue[]): void { - // 将RPC调用分发给所有网络行为组件 - this.networkBehaviours.forEach(behaviour => { - behaviour.onRpcReceived(methodName, args); - }); - } - - /** - * 注册SyncVar - */ - registerSyncVar(fieldName: string, initialValue: NetworkValue): void { - this.syncVars.set(fieldName, { - fieldName, - currentValue: initialValue, - previousValue: initialValue, - lastUpdateTime: Date.now(), - isDirty: false - }); - } - - /** - * 更新SyncVar - */ - updateSyncVar(fieldName: string, newValue: NetworkValue): void { - const syncVar = this.syncVars.get(fieldName); - if (!syncVar) { - console.warn(`SyncVar ${fieldName} not registered on ${this._networkId}`); - return; - } - - const oldValue = syncVar.currentValue; - syncVar.previousValue = oldValue; - syncVar.currentValue = newValue; - syncVar.lastUpdateTime = Date.now(); - syncVar.isDirty = true; - - // 通知所有网络行为组件 - this.networkBehaviours.forEach(behaviour => { - behaviour.onSyncVarChanged(fieldName, oldValue, newValue); - }); - } - - /** - * 获取SyncVar值 - */ - getSyncVar(fieldName: string): NetworkValue | undefined { - return this.syncVars.get(fieldName)?.currentValue; - } - - /** - * 获取所有SyncVar - */ - getAllSyncVars(): Map { - return new Map(this.syncVars); - } - - /** - * 获取脏SyncVar - */ - getDirtySyncVars(): SyncVarInfo[] { - return Array.from(this.syncVars.values()).filter(syncVar => syncVar.isDirty); - } - - /** - * 清除脏标记 - */ - clearDirtyFlags(): void { - this.syncVars.forEach(syncVar => { - syncVar.isDirty = false; - }); - } - - /** - * 序列化网络状态 - */ - serializeState(): NetworkValue { - const state: any = { - networkId: this._networkId, - ownerId: this._ownerId, - isLocalPlayer: this._isLocalPlayer, - authority: this._authority, - syncVars: {} - }; - - // 序列化SyncVar - this.syncVars.forEach((syncVar, fieldName) => { - state.syncVars[fieldName] = syncVar.currentValue; - }); - - return state; - } - - /** - * 反序列化网络状态 - */ - deserializeState(state: any): void { - if (state.networkId) this._networkId = state.networkId; - if (state.ownerId) this._ownerId = state.ownerId; - if (typeof state.isLocalPlayer === 'boolean') this.isLocalPlayer = state.isLocalPlayer; - if (state.authority) this.authority = state.authority; - - // 反序列化SyncVar - if (state.syncVars) { - Object.entries(state.syncVars).forEach(([fieldName, value]) => { - if (this.syncVars.has(fieldName)) { - this.updateSyncVar(fieldName, value as NetworkValue); - } - }); - } - } - - /** - * 设置预测状态 - */ - setPredictionState(enabled: boolean): void { - this.predictionEnabled = enabled; - } - - /** - * 设置插值状态 - */ - setInterpolationState(enabled: boolean): void { - this.interpolationEnabled = enabled; - } - - /** - * 检查是否可以发送RPC - */ - canSendRpc(): boolean { - return this._hasAuthority || this._isLocalPlayer; - } - - /** - * 检查是否可以更新SyncVar - */ - canUpdateSyncVar(): boolean { - return this._hasAuthority; - } - - /** - * 组件销毁 - */ - onDestroy(): void { - this.notifyNetworkStop(); - this.networkBehaviours = []; - this.syncVars.clear(); - } -} \ No newline at end of file diff --git a/packages/network-client/src/core/index.ts b/packages/network-client/src/core/index.ts deleted file mode 100644 index b8ac8234..00000000 --- a/packages/network-client/src/core/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * 核心模块导出 - */ - -export * from './NetworkClient'; -export * from './ClientNetworkBehaviour'; -export * from './NetworkIdentity'; \ No newline at end of file diff --git a/packages/network-client/src/decorators/ClientRpc.ts b/packages/network-client/src/decorators/ClientRpc.ts deleted file mode 100644 index 8443f0f9..00000000 --- a/packages/network-client/src/decorators/ClientRpc.ts +++ /dev/null @@ -1,108 +0,0 @@ -/** - * ClientRpc装饰器 - 客户端版本 - * - * 用于标记可以从服务器调用的客户端方法 - */ - -import 'reflect-metadata'; -import { NetworkValue } from '@esengine/ecs-framework-network-shared'; - -/** - * ClientRpc配置选项 - */ -export interface ClientRpcOptions { - /** 是否可靠传输 */ - reliable?: boolean; - /** 超时时间(毫秒) */ - timeout?: number; - /** 是否仅发送给所有者 */ - ownerOnly?: boolean; - /** 是否包含发送者 */ - includeSender?: boolean; - /** 权限要求 */ - requireAuthority?: boolean; -} - -/** - * ClientRpc元数据键 - */ -export const CLIENT_RPC_METADATA_KEY = Symbol('client_rpc'); - -/** - * ClientRpc元数据 - */ -export interface ClientRpcMetadata { - /** 方法名 */ - methodName: string; - /** 配置选项 */ - options: ClientRpcOptions; - /** 原始方法 */ - originalMethod: Function; -} - -/** - * ClientRpc装饰器 - */ -export function ClientRpc(options: ClientRpcOptions = {}): MethodDecorator { - return function (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) { - const methodName = propertyKey as string; - const originalMethod = descriptor.value; - - // 获取已有的ClientRpc元数据 - const existingMetadata: ClientRpcMetadata[] = Reflect.getMetadata(CLIENT_RPC_METADATA_KEY, target.constructor) || []; - - // 添加新的ClientRpc元数据 - existingMetadata.push({ - methodName, - options: { - reliable: true, - timeout: 30000, - ownerOnly: false, - includeSender: false, - requireAuthority: false, - ...options - }, - originalMethod - }); - - // 设置元数据 - Reflect.defineMetadata(CLIENT_RPC_METADATA_KEY, existingMetadata, target.constructor); - - // 包装原方法(客户端接收RPC调用时执行) - descriptor.value = function (this: any, ...args: NetworkValue[]) { - try { - // 直接调用原方法,客户端接收RPC调用 - return originalMethod.apply(this, args); - } catch (error) { - console.error(`Error executing ClientRpc ${methodName}:`, error); - throw error; - } - }; - - return descriptor; - }; -} - -/** - * 获取类的所有ClientRpc元数据 - */ -export function getClientRpcMetadata(target: any): ClientRpcMetadata[] { - return Reflect.getMetadata(CLIENT_RPC_METADATA_KEY, target) || []; -} - -/** - * 检查方法是否为ClientRpc - */ -export function isClientRpc(target: any, methodName: string): boolean { - const metadata = getClientRpcMetadata(target); - return metadata.some(meta => meta.methodName === methodName); -} - -/** - * 获取特定方法的ClientRpc选项 - */ -export function getClientRpcOptions(target: any, methodName: string): ClientRpcOptions | null { - const metadata = getClientRpcMetadata(target); - const rpc = metadata.find(meta => meta.methodName === methodName); - return rpc ? rpc.options : null; -} \ No newline at end of file diff --git a/packages/network-client/src/decorators/ServerRpc.ts b/packages/network-client/src/decorators/ServerRpc.ts deleted file mode 100644 index 9b9f742f..00000000 --- a/packages/network-client/src/decorators/ServerRpc.ts +++ /dev/null @@ -1,138 +0,0 @@ -/** - * ServerRpc装饰器 - 客户端版本 - * - * 用于标记可以向服务器发送的RPC方法 - */ - -import 'reflect-metadata'; -import { NetworkValue } from '@esengine/ecs-framework-network-shared'; -import { ClientNetworkBehaviour } from '../core/ClientNetworkBehaviour'; - -/** - * ServerRpc配置选项 - */ -export interface ServerRpcOptions { - /** 是否可靠传输 */ - reliable?: boolean; - /** 超时时间(毫秒) */ - timeout?: number; - /** 是否需要权威 */ - requireAuthority?: boolean; - /** 是否需要是本地玩家 */ - requireLocalPlayer?: boolean; -} - -/** - * ServerRpc元数据键 - */ -export const SERVER_RPC_METADATA_KEY = Symbol('server_rpc'); - -/** - * ServerRpc元数据 - */ -export interface ServerRpcMetadata { - /** 方法名 */ - methodName: string; - /** 配置选项 */ - options: ServerRpcOptions; - /** 原始方法 */ - originalMethod: Function; -} - -/** - * ServerRpc装饰器 - */ -export function ServerRpc(options: ServerRpcOptions = {}): MethodDecorator { - return function (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) { - const methodName = propertyKey as string; - const originalMethod = descriptor.value; - - // 获取已有的ServerRpc元数据 - const existingMetadata: ServerRpcMetadata[] = Reflect.getMetadata(SERVER_RPC_METADATA_KEY, target.constructor) || []; - - // 添加新的ServerRpc元数据 - existingMetadata.push({ - methodName, - options: { - reliable: true, - timeout: 30000, - requireAuthority: false, - requireLocalPlayer: false, - ...options - }, - originalMethod - }); - - // 设置元数据 - Reflect.defineMetadata(SERVER_RPC_METADATA_KEY, existingMetadata, target.constructor); - - // 替换方法实现为发送RPC调用 - descriptor.value = async function (this: ClientNetworkBehaviour, ...args: NetworkValue[]) { - try { - // 获取NetworkIdentity - const networkIdentity = this.entity?.getComponent('NetworkIdentity' as any); - if (!networkIdentity) { - throw new Error('NetworkIdentity component not found'); - } - - // 检查权限要求 - if (options.requireAuthority && !(networkIdentity as any).hasAuthority) { - throw new Error(`ServerRpc ${methodName} requires authority`); - } - - if (options.requireLocalPlayer && !(networkIdentity as any).isLocalPlayer) { - throw new Error(`ServerRpc ${methodName} requires local player`); - } - - // 发送RPC到服务器 - if (options.reliable) { - const result = await this.sendServerRpc(methodName, ...args); - return result; - } else { - await this.sendServerRpcUnreliable(methodName, ...args); - return null; - } - - } catch (error) { - console.error(`Error sending ServerRpc ${methodName}:`, error); - throw error; - } - }; - - // 保存原方法到特殊属性,用于本地预测或调试 - (descriptor.value as any).__originalMethod = originalMethod; - - return descriptor; - }; -} - -/** - * 获取类的所有ServerRpc元数据 - */ -export function getServerRpcMetadata(target: any): ServerRpcMetadata[] { - return Reflect.getMetadata(SERVER_RPC_METADATA_KEY, target) || []; -} - -/** - * 检查方法是否为ServerRpc - */ -export function isServerRpc(target: any, methodName: string): boolean { - const metadata = getServerRpcMetadata(target); - return metadata.some(meta => meta.methodName === methodName); -} - -/** - * 获取特定方法的ServerRpc选项 - */ -export function getServerRpcOptions(target: any, methodName: string): ServerRpcOptions | null { - const metadata = getServerRpcMetadata(target); - const rpc = metadata.find(meta => meta.methodName === methodName); - return rpc ? rpc.options : null; -} - -/** - * 获取方法的原始实现(未被装饰器修改的版本) - */ -export function getOriginalMethod(method: Function): Function | null { - return (method as any).__originalMethod || null; -} \ No newline at end of file diff --git a/packages/network-client/src/decorators/SyncVar.ts b/packages/network-client/src/decorators/SyncVar.ts deleted file mode 100644 index 8ebd232f..00000000 --- a/packages/network-client/src/decorators/SyncVar.ts +++ /dev/null @@ -1,146 +0,0 @@ -/** - * SyncVar装饰器 - 客户端版本 - * - * 用于标记需要同步的变量 - */ - -import 'reflect-metadata'; -import { NetworkValue } from '@esengine/ecs-framework-network-shared'; -import { ClientNetworkBehaviour } from '../core/ClientNetworkBehaviour'; - -/** - * SyncVar配置选项 - */ -export interface SyncVarOptions { - /** 是否可从客户端修改 */ - clientCanModify?: boolean; - /** 同步间隔(毫秒),0表示立即同步 */ - syncInterval?: number; - /** 是否仅同步给所有者 */ - ownerOnly?: boolean; - /** 自定义序列化器 */ - serializer?: (value: any) => NetworkValue; - /** 自定义反序列化器 */ - deserializer?: (value: NetworkValue) => any; -} - -/** - * SyncVar元数据键 - */ -export const SYNCVAR_METADATA_KEY = Symbol('syncvar'); - -/** - * SyncVar元数据 - */ -export interface SyncVarMetadata { - /** 属性名 */ - propertyKey: string; - /** 配置选项 */ - options: SyncVarOptions; -} - -/** - * SyncVar装饰器 - */ -export function SyncVar(options: SyncVarOptions = {}): PropertyDecorator { - return function (target: any, propertyKey: string | symbol) { - const key = propertyKey as string; - - // 获取已有的SyncVar元数据 - const existingMetadata: SyncVarMetadata[] = Reflect.getMetadata(SYNCVAR_METADATA_KEY, target.constructor) || []; - - // 添加新的SyncVar元数据 - existingMetadata.push({ - propertyKey: key, - options: { - clientCanModify: false, - syncInterval: 0, - ownerOnly: false, - ...options - } - }); - - // 设置元数据 - Reflect.defineMetadata(SYNCVAR_METADATA_KEY, existingMetadata, target.constructor); - - // 存储原始属性名(用于内部存储) - const privateKey = `_syncvar_${key}`; - - // 创建属性访问器 - Object.defineProperty(target, key, { - get: function (this: ClientNetworkBehaviour) { - // 从NetworkIdentity获取SyncVar值 - const networkIdentity = this.entity?.getComponent('NetworkIdentity' as any); - if (networkIdentity) { - const syncVarValue = (networkIdentity as any).getSyncVar(key); - if (syncVarValue !== undefined) { - return options.deserializer ? options.deserializer(syncVarValue) : syncVarValue; - } - } - - // 如果网络值不存在,返回本地存储的值 - return (this as any)[privateKey]; - }, - - set: function (this: ClientNetworkBehaviour, value: any) { - const oldValue = (this as any)[privateKey]; - const newValue = options.serializer ? options.serializer(value) : value; - - // 存储到本地 - (this as any)[privateKey] = value; - - // 获取NetworkIdentity - const networkIdentity = this.entity?.getComponent('NetworkIdentity' as any); - if (!networkIdentity) { - return; - } - - // 检查是否可以修改 - if (!options.clientCanModify && !(networkIdentity as any).hasAuthority) { - console.warn(`Cannot modify SyncVar ${key} without authority`); - return; - } - - // 注册SyncVar(如果尚未注册) - (networkIdentity as any).registerSyncVar(key, newValue); - - // 更新NetworkIdentity中的值 - (networkIdentity as any).updateSyncVar(key, newValue); - - // 如果有权威且值发生变化,发送到服务器 - if ((networkIdentity as any).hasAuthority && oldValue !== value) { - this.updateSyncVar(key, newValue).catch(error => { - console.error(`Failed to sync variable ${key}:`, error); - }); - } - }, - - enumerable: true, - configurable: true - }); - }; -} - -/** - * 获取类的所有SyncVar元数据 - */ -export function getSyncVarMetadata(target: any): SyncVarMetadata[] { - return Reflect.getMetadata(SYNCVAR_METADATA_KEY, target) || []; -} - -/** - * 检查属性是否为SyncVar - */ -export function isSyncVar(target: any, propertyKey: string): boolean { - const metadata = getSyncVarMetadata(target); - return metadata.some(meta => meta.propertyKey === propertyKey); -} - -/** - * 获取特定属性的SyncVar选项 - */ -export function getSyncVarOptions(target: any, propertyKey: string): SyncVarOptions | null { - const metadata = getSyncVarMetadata(target); - const syncVar = metadata.find(meta => meta.propertyKey === propertyKey); - return syncVar ? syncVar.options : null; -} \ No newline at end of file diff --git a/packages/network-client/src/decorators/index.ts b/packages/network-client/src/decorators/index.ts deleted file mode 100644 index af8250fe..00000000 --- a/packages/network-client/src/decorators/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * 装饰器导出 - */ - -export * from './SyncVar'; -export * from './ClientRpc'; -export * from './ServerRpc'; \ No newline at end of file diff --git a/packages/network-client/src/index.ts b/packages/network-client/src/index.ts index 3ea14025..56223d67 100644 --- a/packages/network-client/src/index.ts +++ b/packages/network-client/src/index.ts @@ -1,23 +1,24 @@ /** - * ECS Framework 网络库 - 客户端 - * - * 提供网络客户端功能,包括连接管理、预测、插值等 + * @esengine/network-client + * ECS Framework网络层 - 客户端实现 */ -// 核心模块 -export * from './core'; +// 核心客户端 (待实现) +// export * from './core/NetworkClient'; +// export * from './core/ServerConnection'; -// 传输层 -export * from './transport'; +// 传输层 (待实现) +// export * from './transport/WebSocketClient'; +// export * from './transport/HttpClient'; -// 装饰器 -export * from './decorators'; +// 系统层 (待实现) +// export * from './systems/ClientSyncSystem'; +// export * from './systems/ClientRpcSystem'; +// export * from './systems/InterpolationSystem'; -// 系统 -export * from './systems'; +// 平台适配器 (待实现) +// export * from './adapters/BrowserAdapter'; +// export * from './adapters/CocosAdapter'; -// 接口 -export * from './interfaces'; - -// 版本信息 -export const VERSION = '1.0.11'; \ No newline at end of file +// 重新导出shared包的类型 +export * from '@esengine/network-shared'; \ No newline at end of file diff --git a/packages/network-client/src/interfaces/NetworkInterfaces.ts b/packages/network-client/src/interfaces/NetworkInterfaces.ts deleted file mode 100644 index 8e75bdc7..00000000 --- a/packages/network-client/src/interfaces/NetworkInterfaces.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * 网络系统相关接口 - */ - -import { NetworkValue } from '@esengine/ecs-framework-network-shared'; - -/** - * 可预测组件接口 - * - * 实现此接口的组件可以参与客户端预测系统 - */ -export interface IPredictable { - /** - * 预测更新 - * - * @param inputs 输入数据 - * @param timestamp 时间戳 - */ - predictUpdate(inputs: NetworkValue, timestamp: number): void; -} - -/** - * 可插值组件接口 - * - * 实现此接口的组件可以参与插值系统 - */ -export interface IInterpolatable { - /** - * 应用插值状态 - * - * @param state 插值后的状态数据 - */ - applyInterpolatedState(state: NetworkValue): void; -} \ No newline at end of file diff --git a/packages/network-client/src/interfaces/index.ts b/packages/network-client/src/interfaces/index.ts deleted file mode 100644 index 04f3289b..00000000 --- a/packages/network-client/src/interfaces/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -/** - * 接口导出 - */ - -export * from './NetworkInterfaces'; \ No newline at end of file diff --git a/packages/network-client/src/systems/InterpolationSystem.ts b/packages/network-client/src/systems/InterpolationSystem.ts deleted file mode 100644 index cf9cccf8..00000000 --- a/packages/network-client/src/systems/InterpolationSystem.ts +++ /dev/null @@ -1,520 +0,0 @@ -/** - * 客户端插值系统 - * - * 实现网络对象的平滑插值 - */ - -import { EntitySystem, Entity, Matcher, Time } from '@esengine/ecs-framework'; -import { NetworkValue } from '@esengine/ecs-framework-network-shared'; -import { NetworkIdentity } from '../core/NetworkIdentity'; -import { IInterpolatable } from '../interfaces/NetworkInterfaces'; - -/** - * 插值状态快照 - */ -export interface InterpolationSnapshot { - /** 时间戳 */ - timestamp: number; - /** 网络ID */ - networkId: string; - /** 状态数据 */ - state: NetworkValue; -} - -/** - * 插值目标 - */ -export interface InterpolationTarget { - /** 网络ID */ - networkId: string; - /** 起始状态 */ - fromState: NetworkValue; - /** 目标状态 */ - toState: NetworkValue; - /** 起始时间 */ - fromTime: number; - /** 结束时间 */ - toTime: number; - /** 当前插值进度 (0-1) */ - progress: number; -} - -/** - * 插值配置 - */ -export interface InterpolationConfig { - /** 插值延迟(毫秒) */ - delay: number; - /** 最大插值时间(毫秒) */ - maxTime: number; - /** 插值缓冲区大小 */ - bufferSize: number; - /** 外推是否启用 */ - enableExtrapolation: boolean; - /** 最大外推时间(毫秒) */ - maxExtrapolationTime: number; -} - -/** - * 插值算法类型 - */ -export enum InterpolationType { - /** 线性插值 */ - LINEAR = 'linear', - /** 平滑插值 */ - SMOOTHSTEP = 'smoothstep', - /** 三次贝塞尔插值 */ - CUBIC = 'cubic' -} - -/** - * 客户端插值系统 - */ -export class InterpolationSystem extends EntitySystem { - /** 插值状态缓冲区 */ - private stateBuffer: Map = new Map(); - /** 当前插值目标 */ - private interpolationTargets: Map = new Map(); - /** 插值配置 */ - private config: InterpolationConfig; - /** 当前时间 */ - private currentTime: number = 0; - - constructor(config?: Partial) { - // 使用Matcher查询具有NetworkIdentity的实体 - super(Matcher.all(NetworkIdentity)); - - this.config = { - delay: 100, - maxTime: 500, - bufferSize: 32, - enableExtrapolation: false, - maxExtrapolationTime: 50, - ...config - }; - - this.currentTime = Date.now(); - } - - /** - * 系统初始化 - */ - override initialize(): void { - super.initialize(); - this.currentTime = Date.now(); - } - - /** - * 系统更新 - */ - override update(): void { - this.currentTime = Date.now(); - this.cleanupOldStates(); - - // 调用父类update,会自动调用process方法处理匹配的实体 - super.update(); - } - - /** - * 处理匹配的实体 - */ - protected override process(entities: Entity[]): void { - const interpolationTime = this.currentTime - this.config.delay; - - for (const entity of entities) { - const networkIdentity = entity.getComponent(NetworkIdentity); - - if (networkIdentity && networkIdentity.isInterpolationEnabled) { - const networkId = networkIdentity.networkId; - const target = this.interpolationTargets.get(networkId); - - if (target) { - // 计算插值进度 - const duration = target.toTime - target.fromTime; - if (duration > 0) { - const elapsed = interpolationTime - target.fromTime; - target.progress = Math.max(0, Math.min(1, elapsed / duration)); - - // 执行插值 - const interpolatedState = this.interpolateStates( - target.fromState, - target.toState, - target.progress, - InterpolationType.LINEAR - ); - - // 应用插值状态 - this.applyInterpolatedState(entity, interpolatedState); - - // 检查是否需要外推 - if (target.progress >= 1 && this.config.enableExtrapolation) { - this.performExtrapolation(entity, target, interpolationTime); - } - } - } - } - } - } - - /** - * 添加网络状态快照 - */ - addStateSnapshot(networkId: string, state: NetworkValue, timestamp: number): void { - // 获取或创建缓冲区 - if (!this.stateBuffer.has(networkId)) { - this.stateBuffer.set(networkId, []); - } - - const buffer = this.stateBuffer.get(networkId)!; - - const snapshot: InterpolationSnapshot = { - timestamp, - networkId, - state - }; - - // 插入到正确的位置(按时间戳排序) - const insertIndex = this.findInsertIndex(buffer, timestamp); - buffer.splice(insertIndex, 0, snapshot); - - // 保持缓冲区大小 - if (buffer.length > this.config.bufferSize) { - buffer.shift(); - } - - // 更新插值目标 - this.updateInterpolationTarget(networkId); - } - - - /** - * 更新插值目标 - */ - private updateInterpolationTarget(networkId: string): void { - const buffer = this.stateBuffer.get(networkId); - if (!buffer || buffer.length < 2) { - return; - } - - const interpolationTime = this.currentTime - this.config.delay; - - // 查找插值区间 - const { from, to } = this.findInterpolationRange(buffer, interpolationTime); - - if (!from || !to) { - return; - } - - // 更新或创建插值目标 - this.interpolationTargets.set(networkId, { - networkId, - fromState: from.state, - toState: to.state, - fromTime: from.timestamp, - toTime: to.timestamp, - progress: 0 - }); - } - - /** - * 查找插值区间 - */ - private findInterpolationRange(buffer: InterpolationSnapshot[], time: number): { - from: InterpolationSnapshot | null; - to: InterpolationSnapshot | null; - } { - let from: InterpolationSnapshot | null = null; - let to: InterpolationSnapshot | null = null; - - for (let i = 0; i < buffer.length - 1; i++) { - const current = buffer[i]; - const next = buffer[i + 1]; - - if (time >= current.timestamp && time <= next.timestamp) { - from = current; - to = next; - break; - } - } - - // 如果没有找到区间,使用最近的两个状态 - if (!from && !to && buffer.length >= 2) { - if (time < buffer[0].timestamp) { - // 时间过早,使用前两个状态 - from = buffer[0]; - to = buffer[1]; - } else if (time > buffer[buffer.length - 1].timestamp) { - // 时间过晚,使用后两个状态 - from = buffer[buffer.length - 2]; - to = buffer[buffer.length - 1]; - } - } - - return { from, to }; - } - - /** - * 状态插值 - */ - private interpolateStates( - fromState: NetworkValue, - toState: NetworkValue, - progress: number, - type: InterpolationType - ): NetworkValue { - // 调整插值进度曲线 - const adjustedProgress = this.adjustProgress(progress, type); - - try { - return this.interpolateValue(fromState, toState, adjustedProgress); - } catch (error) { - console.error('Error interpolating states:', error); - return toState; // 出错时返回目标状态 - } - } - - /** - * 递归插值值 - */ - private interpolateValue(from: NetworkValue, to: NetworkValue, progress: number): NetworkValue { - // 如果类型不同,直接返回目标值 - if (typeof from !== typeof to) { - return to; - } - - // 数字插值 - if (typeof from === 'number' && typeof to === 'number') { - return from + (to - from) * progress; - } - - // 字符串插值(直接切换) - if (typeof from === 'string' && typeof to === 'string') { - return progress < 0.5 ? from : to; - } - - // 布尔插值(直接切换) - if (typeof from === 'boolean' && typeof to === 'boolean') { - return progress < 0.5 ? from : to; - } - - // 数组插值 - if (Array.isArray(from) && Array.isArray(to)) { - const result: NetworkValue[] = []; - const maxLength = Math.max(from.length, to.length); - - for (let i = 0; i < maxLength; i++) { - const fromValue = i < from.length ? from[i] : to[i]; - const toValue = i < to.length ? to[i] : from[i]; - result[i] = this.interpolateValue(fromValue, toValue, progress); - } - - return result; - } - - // 对象插值 - if (from && to && typeof from === 'object' && typeof to === 'object') { - const result: any = {}; - const allKeys = new Set([...Object.keys(from), ...Object.keys(to)]); - - for (const key of allKeys) { - const fromValue = (from as any)[key]; - const toValue = (to as any)[key]; - - if (fromValue !== undefined && toValue !== undefined) { - result[key] = this.interpolateValue(fromValue, toValue, progress); - } else { - result[key] = toValue !== undefined ? toValue : fromValue; - } - } - - return result; - } - - // 其他类型直接返回目标值 - return to; - } - - /** - * 调整插值进度曲线 - */ - private adjustProgress(progress: number, type: InterpolationType): number { - switch (type) { - case InterpolationType.LINEAR: - return progress; - - case InterpolationType.SMOOTHSTEP: - return progress * progress * (3 - 2 * progress); - - case InterpolationType.CUBIC: - return progress < 0.5 - ? 4 * progress * progress * progress - : 1 - Math.pow(-2 * progress + 2, 3) / 2; - - default: - return progress; - } - } - - /** - * 应用插值状态到实体 - */ - private applyInterpolatedState(entity: Entity, state: NetworkValue): void { - // 获取所有可插值的组件 - const components: any[] = []; - for (const component of components) { - if (this.isInterpolatable(component)) { - try { - (component as IInterpolatable).applyInterpolatedState(state); - } catch (error) { - console.error('Error applying interpolated state:', error); - } - } - } - - // 更新NetworkIdentity中的状态 - const networkIdentity = entity.getComponent(NetworkIdentity); - if (networkIdentity && typeof networkIdentity.deserializeState === 'function') { - try { - networkIdentity.deserializeState(state); - } catch (error) { - console.error('Error deserializing interpolated state:', error); - } - } - } - - /** - * 检查组件是否实现了IInterpolatable接口 - */ - private isInterpolatable(component: any): component is IInterpolatable { - return component && typeof component.applyInterpolatedState === 'function'; - } - - /** - * 执行外推 - */ - private performExtrapolation(entity: Entity, target: InterpolationTarget, currentTime: number): void { - if (!this.config.enableExtrapolation) { - return; - } - - const extrapolationTime = currentTime - target.toTime; - if (extrapolationTime > this.config.maxExtrapolationTime) { - return; - } - - // 计算外推状态 - const extrapolationProgress = extrapolationTime / (target.toTime - target.fromTime); - const extrapolatedState = this.extrapolateState( - target.fromState, - target.toState, - 1 + extrapolationProgress - ); - - // 应用外推状态 - this.applyInterpolatedState(entity, extrapolatedState); - } - - /** - * 状态外推 - */ - private extrapolateState(fromState: NetworkValue, toState: NetworkValue, progress: number): NetworkValue { - // 简单的线性外推 - return this.interpolateValue(fromState, toState, progress); - } - - /** - * 查找插入位置 - */ - private findInsertIndex(buffer: InterpolationSnapshot[], timestamp: number): number { - let left = 0; - let right = buffer.length; - - while (left < right) { - const mid = Math.floor((left + right) / 2); - if (buffer[mid].timestamp < timestamp) { - left = mid + 1; - } else { - right = mid; - } - } - - return left; - } - - /** - * 清理过期状态 - */ - private cleanupOldStates(): void { - const cutoffTime = this.currentTime - this.config.maxTime; - - this.stateBuffer.forEach((buffer, networkId) => { - // 移除过期的状态 - const validStates = buffer.filter(snapshot => snapshot.timestamp > cutoffTime); - - if (validStates.length !== buffer.length) { - this.stateBuffer.set(networkId, validStates); - } - - // 如果缓冲区为空,移除它 - if (validStates.length === 0) { - this.stateBuffer.delete(networkId); - this.interpolationTargets.delete(networkId); - } - }); - } - - /** - * 根据网络ID查找实体 - */ - private findEntityByNetworkId(networkId: string): Entity | null { - // 使用系统的entities属性来查找 - for (const entity of this.entities) { - const networkIdentity = entity.getComponent(NetworkIdentity); - if (networkIdentity && networkIdentity.networkId === networkId) { - return entity; - } - } - - return null; - } - - /** - * 设置插值配置 - */ - setInterpolationConfig(config: Partial): void { - this.config = { ...this.config, ...config }; - } - - /** - * 获取插值统计信息 - */ - getInterpolationStats(): { [networkId: string]: { bufferSize: number; progress: number } } { - const stats: { [networkId: string]: { bufferSize: number; progress: number } } = {}; - - this.stateBuffer.forEach((buffer, networkId) => { - const target = this.interpolationTargets.get(networkId); - stats[networkId] = { - bufferSize: buffer.length, - progress: target ? target.progress : 0 - }; - }); - - return stats; - } - - /** - * 清空所有插值数据 - */ - clearInterpolationData(): void { - this.stateBuffer.clear(); - this.interpolationTargets.clear(); - } - - /** - * 系统销毁 - */ - onDestroy(): void { - this.clearInterpolationData(); - } -} - diff --git a/packages/network-client/src/systems/PredictionSystem.ts b/packages/network-client/src/systems/PredictionSystem.ts deleted file mode 100644 index c7be34be..00000000 --- a/packages/network-client/src/systems/PredictionSystem.ts +++ /dev/null @@ -1,362 +0,0 @@ -/** - * 客户端预测系统 - * - * 实现客户端预测和服务器和解 - */ - -import { EntitySystem, Entity, Matcher, Time } from '@esengine/ecs-framework'; -import { NetworkValue } from '@esengine/ecs-framework-network-shared'; -import { NetworkIdentity } from '../core/NetworkIdentity'; -import { IPredictable } from '../interfaces/NetworkInterfaces'; - -/** - * 预测状态快照 - */ -export interface PredictionSnapshot { - /** 时间戳 */ - timestamp: number; - /** 网络ID */ - networkId: string; - /** 状态数据 */ - state: NetworkValue; - /** 输入数据 */ - inputs?: NetworkValue; -} - -/** - * 预测输入 - */ -export interface PredictionInput { - /** 时间戳 */ - timestamp: number; - /** 输入数据 */ - data: NetworkValue; -} - -/** - * 客户端预测系统 - */ -export class PredictionSystem extends EntitySystem { - /** 预测状态缓冲区 */ - private predictionBuffer: Map = new Map(); - /** 输入缓冲区 */ - private inputBuffer: PredictionInput[] = []; - /** 最大缓冲区大小 */ - private maxBufferSize: number = 64; - /** 预测时间窗口(毫秒) */ - private predictionWindow: number = 500; - /** 当前预测时间戳 */ - private currentPredictionTime: number = 0; - - constructor(maxBufferSize = 64, predictionWindow = 500) { - // 使用Matcher查询具有NetworkIdentity的实体 - super(Matcher.all(NetworkIdentity)); - - this.maxBufferSize = maxBufferSize; - this.predictionWindow = predictionWindow; - this.currentPredictionTime = Date.now(); - } - - /** - * 系统初始化 - */ - override initialize(): void { - super.initialize(); - this.currentPredictionTime = Date.now(); - } - - /** - * 系统更新 - */ - override update(): void { - this.currentPredictionTime = Date.now(); - this.cleanupOldSnapshots(); - - // 调用父类update,会自动调用process方法处理匹配的实体 - super.update(); - } - - /** - * 处理匹配的实体 - */ - protected override process(entities: Entity[]): void { - for (const entity of entities) { - const networkIdentity = entity.getComponent(NetworkIdentity); - - if (networkIdentity && - networkIdentity.isPredictionEnabled && - networkIdentity.isLocalPlayer) { - - // 保存当前状态快照 - this.saveSnapshot(entity); - - // 应用当前输入进行预测 - const currentInputs = this.getCurrentInputs(); - if (currentInputs) { - this.applyInputs(entity, currentInputs, this.currentPredictionTime); - } - } - } - } - - /** - * 添加预测输入 - */ - addInput(input: PredictionInput): void { - this.inputBuffer.push(input); - - // 保持输入缓冲区大小 - if (this.inputBuffer.length > this.maxBufferSize) { - this.inputBuffer.shift(); - } - - // 按时间戳排序 - this.inputBuffer.sort((a, b) => a.timestamp - b.timestamp); - } - - /** - * 保存预测状态快照 - */ - saveSnapshot(entity: Entity): void { - const networkIdentity = entity.getComponent(NetworkIdentity); - if (!networkIdentity || !networkIdentity.isPredictionEnabled) { - return; - } - - const networkId = networkIdentity.networkId; - const snapshot: PredictionSnapshot = { - timestamp: this.currentPredictionTime, - networkId, - state: networkIdentity.serializeState(), - inputs: this.getCurrentInputs() || undefined - }; - - // 获取或创建缓冲区 - if (!this.predictionBuffer.has(networkId)) { - this.predictionBuffer.set(networkId, []); - } - - const buffer = this.predictionBuffer.get(networkId)!; - buffer.push(snapshot); - - // 保持缓冲区大小 - if (buffer.length > this.maxBufferSize) { - buffer.shift(); - } - } - - /** - * 从服务器接收权威状态进行和解 - */ - reconcileWithServer(networkId: string, serverState: NetworkValue, serverTimestamp: number): void { - const buffer = this.predictionBuffer.get(networkId); - if (!buffer || buffer.length === 0) { - return; - } - - // 查找对应时间戳的预测状态 - const predictionSnapshot = this.findSnapshot(buffer, serverTimestamp); - if (!predictionSnapshot) { - return; - } - - // 比较预测状态和服务器状态 - if (this.statesMatch(predictionSnapshot.state, serverState)) { - // 预测正确,移除已确认的快照 - this.removeSnapshotsBeforeTimestamp(buffer, serverTimestamp); - return; - } - - // 预测错误,需要进行和解 - this.performReconciliation(networkId, serverState, serverTimestamp); - } - - /** - * 执行预测和解 - */ - private performReconciliation(networkId: string, serverState: NetworkValue, serverTimestamp: number): void { - const entity = this.findEntityByNetworkId(networkId); - if (!entity) { - return; - } - - const networkIdentity = entity.getComponent(NetworkIdentity); - if (!networkIdentity) { - return; - } - - // 回滚到服务器状态 - if (typeof networkIdentity.deserializeState === 'function') { - networkIdentity.deserializeState(serverState); - } - - // 重新应用服务器时间戳之后的输入 - const buffer = this.predictionBuffer.get(networkId)!; - const snapshotsToReplay = buffer.filter(snapshot => snapshot.timestamp > serverTimestamp); - - for (const snapshot of snapshotsToReplay) { - if (snapshot.inputs) { - this.applyInputs(entity, snapshot.inputs, snapshot.timestamp); - } - } - - // 清理已和解的快照 - this.removeSnapshotsBeforeTimestamp(buffer, serverTimestamp); - } - - - /** - * 应用输入进行预测计算 - */ - private applyInputs(entity: Entity, inputs: NetworkValue, timestamp: number): void { - const networkIdentity = entity.getComponent(NetworkIdentity); - if (!networkIdentity) return; - - // 获取实体的所有组件并检查是否实现了IPredictable接口 - const components: any[] = []; - for (const component of components) { - if (this.isPredictable(component)) { - try { - (component as IPredictable).predictUpdate(inputs, timestamp); - } catch (error) { - console.error('Error applying prediction:', error); - } - } - } - } - - /** - * 检查组件是否实现了IPredictable接口 - */ - private isPredictable(component: any): component is IPredictable { - return component && typeof component.predictUpdate === 'function'; - } - - /** - * 获取当前输入 - */ - private getCurrentInputs(): NetworkValue | null { - if (this.inputBuffer.length === 0) { - return null; - } - - // 获取最新的输入 - return this.inputBuffer[this.inputBuffer.length - 1].data; - } - - /** - * 查找指定时间戳的快照 - */ - private findSnapshot(buffer: PredictionSnapshot[], timestamp: number): PredictionSnapshot | null { - // 查找最接近的快照 - let closest: PredictionSnapshot | null = null; - let minDiff = Number.MAX_SAFE_INTEGER; - - for (const snapshot of buffer) { - const diff = Math.abs(snapshot.timestamp - timestamp); - if (diff < minDiff) { - minDiff = diff; - closest = snapshot; - } - } - - return closest; - } - - /** - * 比较两个状态是否匹配 - */ - private statesMatch(predictedState: NetworkValue, serverState: NetworkValue): boolean { - try { - // 简单的JSON比较,实际应用中可能需要更精确的比较 - return JSON.stringify(predictedState) === JSON.stringify(serverState); - } catch (error) { - return false; - } - } - - /** - * 移除指定时间戳之前的快照 - */ - private removeSnapshotsBeforeTimestamp(buffer: PredictionSnapshot[], timestamp: number): void { - for (let i = buffer.length - 1; i >= 0; i--) { - if (buffer[i].timestamp < timestamp) { - buffer.splice(0, i + 1); - break; - } - } - } - - /** - * 清理过期的快照 - */ - private cleanupOldSnapshots(): void { - const cutoffTime = this.currentPredictionTime - this.predictionWindow; - - this.predictionBuffer.forEach((buffer, networkId) => { - this.removeSnapshotsBeforeTimestamp(buffer, cutoffTime); - - // 如果缓冲区为空,移除它 - if (buffer.length === 0) { - this.predictionBuffer.delete(networkId); - } - }); - - // 清理过期的输入 - this.inputBuffer = this.inputBuffer.filter(input => - input.timestamp > cutoffTime - ); - } - - /** - * 根据网络ID查找实体 - */ - private findEntityByNetworkId(networkId: string): Entity | null { - // 使用系统的entities属性来查找 - for (const entity of this.entities) { - const networkIdentity = entity.getComponent(NetworkIdentity); - if (networkIdentity && networkIdentity.networkId === networkId) { - return entity; - } - } - - return null; - } - - /** - * 设置预测配置 - */ - setPredictionConfig(maxBufferSize: number, predictionWindow: number): void { - this.maxBufferSize = maxBufferSize; - this.predictionWindow = predictionWindow; - } - - /** - * 获取预测统计信息 - */ - getPredictionStats(): { [networkId: string]: number } { - const stats: { [networkId: string]: number } = {}; - - this.predictionBuffer.forEach((buffer, networkId) => { - stats[networkId] = buffer.length; - }); - - return stats; - } - - /** - * 清空所有预测数据 - */ - clearPredictionData(): void { - this.predictionBuffer.clear(); - this.inputBuffer = []; - } - - /** - * 系统销毁 - */ - onDestroy(): void { - this.clearPredictionData(); - } -} - diff --git a/packages/network-client/src/systems/index.ts b/packages/network-client/src/systems/index.ts deleted file mode 100644 index a3b19975..00000000 --- a/packages/network-client/src/systems/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * 系统导出 - */ - -export * from './PredictionSystem'; -export * from './InterpolationSystem'; \ No newline at end of file diff --git a/packages/network-client/src/transport/ClientTransport.ts b/packages/network-client/src/transport/ClientTransport.ts deleted file mode 100644 index 360873cc..00000000 --- a/packages/network-client/src/transport/ClientTransport.ts +++ /dev/null @@ -1,445 +0,0 @@ -/** - * 客户端传输层抽象接口 - */ - -import { Emitter, ITimer, Core } from '@esengine/ecs-framework'; -import { NetworkValue } from '@esengine/ecs-framework-network-shared'; - -/** - * 客户端传输配置 - */ -export interface ClientTransportConfig { - /** 服务器地址 */ - host: string; - /** 服务器端口 */ - port: number; - /** 是否使用安全连接 */ - secure?: boolean; - /** 连接超时时间(毫秒) */ - connectionTimeout?: number; - /** 重连间隔(毫秒) */ - reconnectInterval?: number; - /** 最大重连次数 */ - maxReconnectAttempts?: number; - /** 心跳间隔(毫秒) */ - heartbeatInterval?: number; - /** 消息队列最大大小 */ - maxQueueSize?: number; -} - -/** - * 连接状态 - */ -export enum ConnectionState { - /** 断开连接 */ - DISCONNECTED = 'disconnected', - /** 连接中 */ - CONNECTING = 'connecting', - /** 已连接 */ - CONNECTED = 'connected', - /** 认证中 */ - AUTHENTICATING = 'authenticating', - /** 已认证 */ - AUTHENTICATED = 'authenticated', - /** 重连中 */ - RECONNECTING = 'reconnecting', - /** 连接错误 */ - ERROR = 'error' -} - -/** - * 客户端消息 - */ -export interface ClientMessage { - /** 消息类型 */ - type: 'rpc' | 'syncvar' | 'system' | 'custom'; - /** 消息数据 */ - data: NetworkValue; - /** 消息ID(用于响应匹配) */ - messageId?: string; - /** 是否可靠传输 */ - reliable?: boolean; - /** 时间戳 */ - timestamp?: number; -} - -/** - * 连接统计信息 - */ -export interface ConnectionStats { - /** 连接时间 */ - connectedAt: Date | null; - /** 连接持续时间(毫秒) */ - connectionDuration: number; - /** 发送消息数 */ - messagesSent: number; - /** 接收消息数 */ - messagesReceived: number; - /** 发送字节数 */ - bytesSent: number; - /** 接收字节数 */ - bytesReceived: number; - /** 重连次数 */ - reconnectCount: number; - /** 丢失消息数 */ - messagesLost: number; - /** 平均延迟(毫秒) */ - averageLatency: number; -} - -/** - * 客户端传输事件 - */ -export interface ClientTransportEvents { - /** 连接建立 */ - 'connected': () => void; - /** 连接断开 */ - 'disconnected': (reason: string) => void; - /** 连接状态变化 */ - 'state-changed': (oldState: ConnectionState, newState: ConnectionState) => void; - /** 收到消息 */ - 'message': (message: ClientMessage) => void; - /** 连接错误 */ - 'error': (error: Error) => void; - /** 重连开始 */ - 'reconnecting': (attempt: number, maxAttempts: number) => void; - /** 重连成功 */ - 'reconnected': () => void; - /** 重连失败 */ - 'reconnect-failed': () => void; - /** 延迟更新 */ - 'latency-updated': (latency: number) => void; -} - -/** - * 客户端传输层抽象类 - */ -export abstract class ClientTransport { - protected config: ClientTransportConfig; - protected state: ConnectionState = ConnectionState.DISCONNECTED; - protected stats: ConnectionStats; - protected messageQueue: ClientMessage[] = []; - protected reconnectAttempts = 0; - protected reconnectTimer: ITimer | null = null; - protected heartbeatTimer: ITimer | null = null; - private latencyMeasurements: number[] = []; - private eventEmitter: Emitter; - - constructor(config: ClientTransportConfig) { - this.eventEmitter = new Emitter(); - - this.config = { - secure: false, - connectionTimeout: 10000, // 10秒 - reconnectInterval: 3000, // 3秒 - maxReconnectAttempts: 10, - heartbeatInterval: 30000, // 30秒 - maxQueueSize: 1000, - ...config - }; - - this.stats = { - connectedAt: null, - connectionDuration: 0, - messagesSent: 0, - messagesReceived: 0, - bytesSent: 0, - bytesReceived: 0, - reconnectCount: 0, - messagesLost: 0, - averageLatency: 0 - }; - } - - /** - * 连接到服务器 - */ - abstract connect(): Promise; - - /** - * 断开连接 - */ - abstract disconnect(): Promise; - - /** - * 发送消息 - */ - abstract sendMessage(message: ClientMessage): Promise; - - /** - * 获取当前连接状态 - */ - getState(): ConnectionState { - return this.state; - } - - /** - * 检查是否已连接 - */ - isConnected(): boolean { - return this.state === ConnectionState.CONNECTED || - this.state === ConnectionState.AUTHENTICATED; - } - - /** - * 获取连接统计信息 - */ - getStats(): ConnectionStats { - if (this.stats.connectedAt) { - this.stats.connectionDuration = Date.now() - this.stats.connectedAt.getTime(); - } - return { ...this.stats }; - } - - /** - * 获取配置 - */ - getConfig(): Readonly { - return this.config; - } - - /** - * 设置状态 - */ - protected setState(newState: ConnectionState): void { - if (this.state !== newState) { - const oldState = this.state; - this.state = newState; - this.eventEmitter.emit('state-changed', oldState, newState); - - // 特殊状态处理 - if (newState === ConnectionState.CONNECTED) { - this.stats.connectedAt = new Date(); - this.reconnectAttempts = 0; - this.startHeartbeat(); - this.processMessageQueue(); - this.eventEmitter.emit('connected'); - - if (oldState === ConnectionState.RECONNECTING) { - this.eventEmitter.emit('reconnected'); - } - } else if (newState === ConnectionState.DISCONNECTED) { - this.stats.connectedAt = null; - this.stopHeartbeat(); - } - } - } - - /** - * 处理接收到的消息 - */ - protected handleMessage(message: ClientMessage): void { - this.stats.messagesReceived++; - - if (message.data) { - try { - const messageSize = JSON.stringify(message.data).length; - this.stats.bytesReceived += messageSize; - } catch (error) { - // 忽略序列化错误 - } - } - - // 处理系统消息 - if (message.type === 'system') { - this.handleSystemMessage(message); - return; - } - - this.eventEmitter.emit('message', message); - } - - /** - * 处理系统消息 - */ - protected handleSystemMessage(message: ClientMessage): void { - const data = message.data as any; - - switch (data.action) { - case 'ping': - // 响应ping - this.sendMessage({ - type: 'system', - data: { action: 'pong', timestamp: data.timestamp } - }); - break; - - case 'pong': - // 计算延迟 - if (data.timestamp) { - const latency = Date.now() - data.timestamp; - this.updateLatency(latency); - } - break; - } - } - - /** - * 处理连接错误 - */ - protected handleError(error: Error): void { - console.error('Transport error:', error.message); - this.eventEmitter.emit('error', error); - - if (this.isConnected()) { - this.setState(ConnectionState.ERROR); - this.startReconnect(); - } - } - - /** - * 开始重连 - */ - protected startReconnect(): void { - if (this.reconnectAttempts >= this.config.maxReconnectAttempts!) { - this.eventEmitter.emit('reconnect-failed'); - return; - } - - this.setState(ConnectionState.RECONNECTING); - this.reconnectAttempts++; - this.stats.reconnectCount++; - - this.eventEmitter.emit('reconnecting', this.reconnectAttempts, this.config.maxReconnectAttempts!); - - this.reconnectTimer = Core.schedule(this.config.reconnectInterval! / 1000, false, this, async () => { - try { - await this.connect(); - } catch (error) { - this.startReconnect(); // 继续重连 - } - }); - } - - /** - * 停止重连 - */ - protected stopReconnect(): void { - if (this.reconnectTimer) { - this.reconnectTimer.stop(); - this.reconnectTimer = null; - } - } - - /** - * 将消息加入队列 - */ - protected queueMessage(message: ClientMessage): boolean { - if (this.messageQueue.length >= this.config.maxQueueSize!) { - this.stats.messagesLost++; - return false; - } - - this.messageQueue.push(message); - return true; - } - - /** - * 处理消息队列 - */ - protected async processMessageQueue(): Promise { - while (this.messageQueue.length > 0 && this.isConnected()) { - const message = this.messageQueue.shift()!; - await this.sendMessage(message); - } - } - - /** - * 开始心跳 - */ - protected startHeartbeat(): void { - if (this.config.heartbeatInterval && this.config.heartbeatInterval > 0) { - this.heartbeatTimer = Core.schedule(this.config.heartbeatInterval / 1000, true, this, () => { - this.sendHeartbeat(); - }); - } - } - - /** - * 停止心跳 - */ - protected stopHeartbeat(): void { - if (this.heartbeatTimer) { - this.heartbeatTimer.stop(); - this.heartbeatTimer = null; - } - } - - /** - * 发送心跳 - */ - protected sendHeartbeat(): void { - this.sendMessage({ - type: 'system', - data: { action: 'ping', timestamp: Date.now() } - }).catch(() => { - // 心跳发送失败,可能连接有问题 - }); - } - - /** - * 更新延迟统计 - */ - protected updateLatency(latency: number): void { - this.latencyMeasurements.push(latency); - - // 只保留最近的10个测量值 - if (this.latencyMeasurements.length > 10) { - this.latencyMeasurements.shift(); - } - - // 计算平均延迟 - const sum = this.latencyMeasurements.reduce((a, b) => a + b, 0); - this.stats.averageLatency = sum / this.latencyMeasurements.length; - - this.eventEmitter.emit('latency-updated', latency); - } - - /** - * 更新发送统计 - */ - protected updateSendStats(message: ClientMessage): void { - this.stats.messagesSent++; - - if (message.data) { - try { - const messageSize = JSON.stringify(message.data).length; - this.stats.bytesSent += messageSize; - } catch (error) { - // 忽略序列化错误 - } - } - } - - /** - * 销毁传输层 - */ - destroy(): void { - this.stopReconnect(); - this.stopHeartbeat(); - this.messageQueue = []; - } - - /** - * 类型安全的事件监听 - */ - on(event: K, listener: ClientTransportEvents[K]): this { - this.eventEmitter.addObserver(event, listener, this); - return this; - } - - /** - * 移除事件监听 - */ - off(event: K, listener: ClientTransportEvents[K]): this { - this.eventEmitter.removeObserver(event, listener); - return this; - } - - /** - * 类型安全的事件触发 - */ - emit(event: K, ...args: Parameters): void { - this.eventEmitter.emit(event, ...args); - } -} \ No newline at end of file diff --git a/packages/network-client/src/transport/HttpClientTransport.ts b/packages/network-client/src/transport/HttpClientTransport.ts deleted file mode 100644 index 08c42247..00000000 --- a/packages/network-client/src/transport/HttpClientTransport.ts +++ /dev/null @@ -1,427 +0,0 @@ -/** - * HTTP 客户端传输实现 - * - * 支持 REST API 和长轮询 - */ - -import { Core, ITimer } from '@esengine/ecs-framework'; -import { - ClientTransport, - ClientTransportConfig, - ConnectionState, - ClientMessage -} from './ClientTransport'; - -/** - * HTTP 客户端配置 - */ -export interface HttpClientConfig extends ClientTransportConfig { - /** API 路径前缀 */ - apiPrefix?: string; - /** 请求超时时间(毫秒) */ - requestTimeout?: number; - /** 长轮询超时时间(毫秒) */ - longPollTimeout?: number; - /** 是否启用长轮询 */ - enableLongPolling?: boolean; - /** 额外的请求头 */ - headers?: Record; - /** 认证令牌 */ - authToken?: string; -} - -/** - * HTTP 响应接口 - */ -interface HttpResponse { - success: boolean; - data?: any; - error?: string; - messages?: ClientMessage[]; -} - -/** - * HTTP 客户端传输 - */ -export class HttpClientTransport extends ClientTransport { - private connectionId: string | null = null; - private longPollController: AbortController | null = null; - private longPollRunning = false; - private connectPromise: Promise | null = null; - private requestTimers: Set> = new Set(); - - protected override config: HttpClientConfig; - - constructor(config: HttpClientConfig) { - super(config); - - this.config = { - apiPrefix: '/api', - requestTimeout: 30000, // 30秒 - longPollTimeout: 25000, // 25秒 - enableLongPolling: true, - headers: { - 'Content-Type': 'application/json' - }, - ...config - }; - } - - /** - * 连接到服务器 - */ - async connect(): Promise { - if (this.state === ConnectionState.CONNECTING || - this.state === ConnectionState.CONNECTED) { - return this.connectPromise || Promise.resolve(); - } - - this.setState(ConnectionState.CONNECTING); - this.stopReconnect(); - - this.connectPromise = this.performConnect(); - return this.connectPromise; - } - - /** - * 执行连接 - */ - private async performConnect(): Promise { - try { - // 发送连接请求 - const response = await this.makeRequest('/connect', 'POST', {}); - - if (response.success && response.data.connectionId) { - this.connectionId = response.data.connectionId; - this.setState(ConnectionState.CONNECTED); - - // 启动长轮询 - if (this.config.enableLongPolling) { - this.startLongPolling(); - } - } else { - throw new Error(response.error || 'Connection failed'); - } - } catch (error) { - this.setState(ConnectionState.ERROR); - throw error; - } - } - - /** - * 断开连接 - */ - async disconnect(): Promise { - this.stopReconnect(); - this.stopLongPolling(); - - if (this.connectionId) { - try { - await this.makeRequest('/disconnect', 'POST', { - connectionId: this.connectionId - }); - } catch (error) { - // 忽略断开连接时的错误 - } - - this.connectionId = null; - } - - this.setState(ConnectionState.DISCONNECTED); - this.connectPromise = null; - } - - /** - * 发送消息 - */ - async sendMessage(message: ClientMessage): Promise { - if (!this.connectionId) { - // 如果未连接,将消息加入队列 - if (this.state === ConnectionState.CONNECTING || - this.state === ConnectionState.RECONNECTING) { - return this.queueMessage(message); - } - return false; - } - - try { - const response = await this.makeRequest('/send', 'POST', { - connectionId: this.connectionId, - message: { - ...message, - timestamp: message.timestamp || Date.now() - } - }); - - if (response.success) { - this.updateSendStats(message); - return true; - } else { - console.error('Send message failed:', response.error); - return false; - } - - } catch (error) { - this.handleError(error as Error); - return false; - } - } - - /** - * 启动长轮询 - */ - private startLongPolling(): void { - if (this.longPollRunning || !this.connectionId) { - return; - } - - this.longPollRunning = true; - this.performLongPoll(); - } - - /** - * 停止长轮询 - */ - private stopLongPolling(): void { - this.longPollRunning = false; - - if (this.longPollController) { - this.longPollController.abort(); - this.longPollController = null; - } - } - - /** - * 执行长轮询 - */ - private async performLongPoll(): Promise { - while (this.longPollRunning && this.connectionId) { - try { - this.longPollController = new AbortController(); - - const response = await this.makeRequest('/poll', 'GET', { - connectionId: this.connectionId - }, { - signal: this.longPollController.signal, - timeout: this.config.longPollTimeout - }); - - if (response.success && response.messages && response.messages.length > 0) { - // 处理接收到的消息 - for (const message of response.messages) { - this.handleMessage(message); - } - } - - // 如果服务器指示断开连接 - if (response.data && response.data.disconnected) { - this.handleServerDisconnect(); - break; - } - - } catch (error) { - if ((error as any).name === 'AbortError') { - // 被主动取消,正常情况 - break; - } - - console.warn('Long polling error:', (error as Error).message); - - // 如果是网络错误,尝试重连 - if (this.isNetworkError(error as Error)) { - this.handleError(error as Error); - break; - } - - // 短暂等待后重试 - await this.delay(1000); - } - - this.longPollController = null; - } - } - - /** - * 处理服务器主动断开连接 - */ - private handleServerDisconnect(): void { - this.connectionId = null; - this.stopLongPolling(); - this.emit('disconnected', 'Server disconnect'); - - if (this.reconnectAttempts < this.config.maxReconnectAttempts!) { - this.startReconnect(); - } else { - this.setState(ConnectionState.DISCONNECTED); - } - } - - /** - * 发送 HTTP 请求 - */ - private async makeRequest( - path: string, - method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET', - data?: any, - options: { - signal?: AbortSignal; - timeout?: number; - } = {} - ): Promise { - const url = this.buildUrl(path); - const headers = this.buildHeaders(); - - const requestOptions: RequestInit = { - method, - headers, - signal: options.signal - }; - - // 添加请求体 - if (method !== 'GET' && data) { - requestOptions.body = JSON.stringify(data); - } else if (method === 'GET' && data) { - // GET 请求将数据作为查询参数 - const params = new URLSearchParams(); - Object.entries(data).forEach(([key, value]) => { - params.append(key, String(value)); - }); - const separator = url.includes('?') ? '&' : '?'; - return this.fetchWithTimeout(`${url}${separator}${params}`, requestOptions, options.timeout); - } - - return this.fetchWithTimeout(url, requestOptions, options.timeout); - } - - /** - * 带超时的 fetch 请求 - */ - private async fetchWithTimeout( - url: string, - options: RequestInit, - timeout?: number - ): Promise { - const actualTimeout = timeout || this.config.requestTimeout!; - - const controller = new AbortController(); - let timeoutTimer: ITimer | null = null; - - // 创建超时定时器 - timeoutTimer = Core.schedule(actualTimeout / 1000, false, this, () => { - controller.abort(); - if (timeoutTimer) { - this.requestTimers.delete(timeoutTimer); - } - }); - - this.requestTimers.add(timeoutTimer); - - try { - const response = await fetch(url, { - ...options, - signal: options.signal || controller.signal - }); - - // 清理定时器 - if (timeoutTimer) { - timeoutTimer.stop(); - this.requestTimers.delete(timeoutTimer); - } - - if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${response.statusText}`); - } - - const result = await response.json(); - return result as HttpResponse; - - } catch (error) { - // 清理定时器 - if (timeoutTimer) { - timeoutTimer.stop(); - this.requestTimers.delete(timeoutTimer); - } - throw error; - } - } - - /** - * 构建请求URL - */ - private buildUrl(path: string): string { - const protocol = this.config.secure ? 'https' : 'http'; - const basePath = this.config.apiPrefix || ''; - const cleanPath = path.startsWith('/') ? path : `/${path}`; - - return `${protocol}://${this.config.host}:${this.config.port}${basePath}${cleanPath}`; - } - - /** - * 构建请求头 - */ - private buildHeaders(): Record { - const headers = { ...this.config.headers }; - - if (this.config.authToken) { - headers['Authorization'] = `Bearer ${this.config.authToken}`; - } - - return headers; - } - - /** - * 检查是否为网络错误 - */ - private isNetworkError(error: Error): boolean { - return error.message.includes('fetch') || - error.message.includes('network') || - error.message.includes('timeout') || - error.name === 'TypeError'; - } - - /** - * 延迟函数 - */ - private delay(ms: number): Promise { - return new Promise(resolve => { - const timer = Core.schedule(ms / 1000, false, this, () => { - this.requestTimers.delete(timer); - resolve(); - }); - this.requestTimers.add(timer); - }); - } - - /** - * 设置认证令牌 - */ - setAuthToken(token: string): void { - this.config.authToken = token; - } - - /** - * 获取连接ID - */ - getConnectionId(): string | null { - return this.connectionId; - } - - /** - * 检查是否支持 Fetch API - */ - static isSupported(): boolean { - return typeof fetch !== 'undefined'; - } - - /** - * 销毁传输层 - */ - override destroy(): void { - // 清理所有请求定时器 - this.requestTimers.forEach(timer => timer.stop()); - this.requestTimers.clear(); - - this.disconnect(); - super.destroy(); - } -} \ No newline at end of file diff --git a/packages/network-client/src/transport/WebSocketClientTransport.ts b/packages/network-client/src/transport/WebSocketClientTransport.ts deleted file mode 100644 index ef4ef114..00000000 --- a/packages/network-client/src/transport/WebSocketClientTransport.ts +++ /dev/null @@ -1,282 +0,0 @@ -/** - * WebSocket 客户端传输实现 - */ - -import { Core, ITimer } from '@esengine/ecs-framework'; -import { - ClientTransport, - ClientTransportConfig, - ConnectionState, - ClientMessage -} from './ClientTransport'; - -/** - * WebSocket 客户端配置 - */ -export interface WebSocketClientConfig extends ClientTransportConfig { - /** WebSocket 路径 */ - path?: string; - /** 协议列表 */ - protocols?: string | string[]; - /** 额外的请求头 */ - headers?: Record; - /** 是否启用二进制消息 */ - binaryType?: 'blob' | 'arraybuffer'; - /** WebSocket 扩展 */ - extensions?: any; -} - -/** - * WebSocket 客户端传输 - */ -export class WebSocketClientTransport extends ClientTransport { - private websocket: WebSocket | null = null; - private connectionPromise: Promise | null = null; - private connectionTimeoutTimer: ITimer | null = null; - - protected override config: WebSocketClientConfig; - - constructor(config: WebSocketClientConfig) { - super(config); - - this.config = { - path: '/ws', - protocols: [], - headers: {}, - binaryType: 'arraybuffer', - ...config - }; - } - - /** - * 连接到服务器 - */ - async connect(): Promise { - if (this.state === ConnectionState.CONNECTING || - this.state === ConnectionState.CONNECTED) { - return this.connectionPromise || Promise.resolve(); - } - - this.setState(ConnectionState.CONNECTING); - this.stopReconnect(); // 停止任何正在进行的重连 - - this.connectionPromise = new Promise((resolve, reject) => { - try { - // 构建WebSocket URL - const protocol = this.config.secure ? 'wss' : 'ws'; - const url = `${protocol}://${this.config.host}:${this.config.port}${this.config.path}`; - - // 创建WebSocket连接 - this.websocket = new WebSocket(url, this.config.protocols); - - if (this.config.binaryType) { - this.websocket.binaryType = this.config.binaryType; - } - - // 设置连接超时 - this.connectionTimeoutTimer = Core.schedule(this.config.connectionTimeout! / 1000, false, this, () => { - if (this.websocket && this.websocket.readyState === WebSocket.CONNECTING) { - this.websocket.close(); - reject(new Error('Connection timeout')); - } - }); - - // WebSocket 事件处理 - this.websocket.onopen = () => { - if (this.connectionTimeoutTimer) { - this.connectionTimeoutTimer.stop(); - this.connectionTimeoutTimer = null; - } - this.setState(ConnectionState.CONNECTED); - resolve(); - }; - - this.websocket.onclose = (event) => { - if (this.connectionTimeoutTimer) { - this.connectionTimeoutTimer.stop(); - this.connectionTimeoutTimer = null; - } - this.handleClose(event.code, event.reason); - - if (this.state === ConnectionState.CONNECTING) { - reject(new Error(`Connection failed: ${event.reason || 'Unknown error'}`)); - } - }; - - this.websocket.onerror = (event) => { - if (this.connectionTimeoutTimer) { - this.connectionTimeoutTimer.stop(); - this.connectionTimeoutTimer = null; - } - const error = new Error('WebSocket error'); - this.handleError(error); - - if (this.state === ConnectionState.CONNECTING) { - reject(error); - } - }; - - this.websocket.onmessage = (event) => { - this.handleWebSocketMessage(event); - }; - - } catch (error) { - this.setState(ConnectionState.ERROR); - reject(error); - } - }); - - return this.connectionPromise; - } - - /** - * 断开连接 - */ - async disconnect(): Promise { - this.stopReconnect(); - - if (this.websocket) { - // 设置状态为断开连接,避免触发重连 - this.setState(ConnectionState.DISCONNECTED); - - if (this.websocket.readyState === WebSocket.OPEN || - this.websocket.readyState === WebSocket.CONNECTING) { - this.websocket.close(1000, 'Client disconnect'); - } - - this.websocket = null; - } - - this.connectionPromise = null; - } - - /** - * 发送消息 - */ - async sendMessage(message: ClientMessage): Promise { - if (!this.websocket || this.websocket.readyState !== WebSocket.OPEN) { - // 如果未连接,将消息加入队列 - if (this.state === ConnectionState.CONNECTING || - this.state === ConnectionState.RECONNECTING) { - return this.queueMessage(message); - } - return false; - } - - try { - // 序列化消息 - const serialized = JSON.stringify({ - ...message, - timestamp: message.timestamp || Date.now() - }); - - // 发送消息 - this.websocket.send(serialized); - this.updateSendStats(message); - - return true; - - } catch (error) { - this.handleError(error as Error); - return false; - } - } - - /** - * 处理 WebSocket 消息 - */ - private handleWebSocketMessage(event: MessageEvent): void { - try { - let data: string; - - if (event.data instanceof ArrayBuffer) { - // 处理二进制数据 - data = new TextDecoder().decode(event.data); - } else if (event.data instanceof Blob) { - // Blob 需要异步处理 - event.data.text().then(text => { - this.processMessage(text); - }); - return; - } else { - // 字符串数据 - data = event.data; - } - - this.processMessage(data); - - } catch (error) { - console.error('Error processing WebSocket message:', error); - } - } - - /** - * 处理消息内容 - */ - private processMessage(data: string): void { - try { - const message: ClientMessage = JSON.parse(data); - this.handleMessage(message); - } catch (error) { - console.error('Error parsing message:', error); - } - } - - /** - * 处理连接关闭 - */ - private handleClose(code: number, reason: string): void { - this.websocket = null; - this.connectionPromise = null; - - const wasConnected = this.isConnected(); - - // 根据关闭代码决定是否重连 - if (code === 1000) { - // 正常关闭,不重连 - this.setState(ConnectionState.DISCONNECTED); - this.emit('disconnected', reason || 'Normal closure'); - } else if (wasConnected && this.reconnectAttempts < this.config.maxReconnectAttempts!) { - // 异常关闭,尝试重连 - this.emit('disconnected', reason || `Abnormal closure (${code})`); - this.startReconnect(); - } else { - // 达到最大重连次数或其他情况 - this.setState(ConnectionState.DISCONNECTED); - this.emit('disconnected', reason || `Connection lost (${code})`); - } - } - - /** - * 获取 WebSocket 就绪状态 - */ - getReadyState(): number { - return this.websocket?.readyState ?? WebSocket.CLOSED; - } - - /** - * 获取 WebSocket 实例 - */ - getWebSocket(): WebSocket | null { - return this.websocket; - } - - /** - * 检查是否支持 WebSocket - */ - static isSupported(): boolean { - return typeof WebSocket !== 'undefined'; - } - - /** - * 销毁传输层 - */ - override destroy(): void { - if (this.connectionTimeoutTimer) { - this.connectionTimeoutTimer.stop(); - this.connectionTimeoutTimer = null; - } - this.disconnect(); - super.destroy(); - } -} \ No newline at end of file diff --git a/packages/network-client/src/transport/index.ts b/packages/network-client/src/transport/index.ts deleted file mode 100644 index b76ce5aa..00000000 --- a/packages/network-client/src/transport/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * 传输层导出 - */ - -export * from './ClientTransport'; -export * from './WebSocketClientTransport'; -export * from './HttpClientTransport'; \ No newline at end of file diff --git a/packages/network-client/tests/NetworkClient.integration.test.ts b/packages/network-client/tests/NetworkClient.integration.test.ts deleted file mode 100644 index 9fa634c3..00000000 --- a/packages/network-client/tests/NetworkClient.integration.test.ts +++ /dev/null @@ -1,384 +0,0 @@ -/** - * NetworkClient 集成测试 - * 测试网络客户端的完整功能,包括依赖注入和错误处理 - */ - -import { NetworkClient } from '../src/core/NetworkClient'; - -// Mock 所有外部依赖 -jest.mock('@esengine/ecs-framework', () => ({ - Core: { - scene: null, - schedule: { - scheduleRepeating: jest.fn((callback: Function, interval: number) => ({ - stop: jest.fn() - })) - } - }, - Emitter: jest.fn().mockImplementation(() => ({ - emit: jest.fn(), - on: jest.fn(), - off: jest.fn(), - removeAllListeners: jest.fn() - })) -})); - -jest.mock('@esengine/ecs-framework-network-shared', () => ({ - NetworkValue: {}, - generateMessageId: jest.fn(() => 'test-message-id-123'), - generateNetworkId: jest.fn(() => 12345), - NetworkUtils: { - generateMessageId: jest.fn(() => 'test-message-id-456'), - calculateDistance: jest.fn(() => 100), - isNodeEnvironment: jest.fn(() => false), - isBrowserEnvironment: jest.fn(() => true) - } -})); - -// Mock WebSocket -class MockWebSocket { - public readyState: number = WebSocket.CONNECTING; - public onopen: ((event: Event) => void) | null = null; - public onclose: ((event: CloseEvent) => void) | null = null; - public onmessage: ((event: MessageEvent) => void) | null = null; - public onerror: ((event: Event) => void) | null = null; - - constructor(public url: string, public protocols?: string | string[]) {} - - send(data: string | ArrayBuffer | Blob): void {} - close(code?: number, reason?: string): void { - this.readyState = WebSocket.CLOSED; - if (this.onclose) { - this.onclose(new CloseEvent('close', { code: code || 1000, reason: reason || '' })); - } - } -} - -(global as any).WebSocket = MockWebSocket; -(global as any).WebSocket.CONNECTING = 0; -(global as any).WebSocket.OPEN = 1; -(global as any).WebSocket.CLOSING = 2; -(global as any).WebSocket.CLOSED = 3; - -describe('NetworkClient 集成测试', () => { - let client: NetworkClient; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - afterEach(() => { - if (client) { - client.disconnect().catch(() => {}); - client = null as any; - } - }); - - describe('依赖注入测试', () => { - it('应该正确处理所有依赖模块', () => { - expect(() => { - client = new NetworkClient({ - transport: 'websocket', - transportConfig: { - host: 'localhost', - port: 8080 - } - }); - }).not.toThrow(); - - expect(client).toBeInstanceOf(NetworkClient); - }); - - it('应该正确使用network-shared中的工具函数', () => { - const { generateMessageId, NetworkUtils } = require('@esengine/ecs-framework-network-shared'); - - client = new NetworkClient({ - transport: 'websocket', - transportConfig: { - host: 'localhost', - port: 8080 - } - }); - - // 验证network-shared模块被正确导入 - expect(generateMessageId).toBeDefined(); - expect(NetworkUtils).toBeDefined(); - }); - - it('应该正确使用ecs-framework中的Core模块', () => { - const { Core } = require('@esengine/ecs-framework'); - - client = new NetworkClient({ - transport: 'websocket', - transportConfig: { - host: 'localhost', - port: 8080 - } - }); - - expect(Core).toBeDefined(); - expect(Core.schedule).toBeDefined(); - }); - }); - - describe('构造函数错误处理', () => { - it('应该处理network-shared模块导入失败', () => { - // 重置模块并模拟导入失败 - jest.resetModules(); - jest.doMock('@esengine/ecs-framework-network-shared', () => { - throw new Error('network-shared模块导入失败'); - }); - - expect(() => { - const { NetworkClient } = require('../src/core/NetworkClient'); - new NetworkClient({ - transportType: 'websocket', - host: 'localhost', - port: 8080 - }); - }).toThrow(); - }); - - it('应该处理ecs-framework模块导入失败', () => { - // 重置模块并模拟导入失败 - jest.resetModules(); - jest.doMock('@esengine/ecs-framework', () => { - throw new Error('ecs-framework模块导入失败'); - }); - - expect(() => { - const { NetworkClient } = require('../src/core/NetworkClient'); - new NetworkClient({ - transportType: 'websocket', - host: 'localhost', - port: 8080 - }); - }).toThrow(); - }); - - it('应该处理传输层构造失败', () => { - // Mock传输层构造函数抛出异常 - const originalWebSocket = (global as any).WebSocket; - (global as any).WebSocket = jest.fn(() => { - throw new Error('WebSocket不可用'); - }); - - client = new NetworkClient({ - transport: 'websocket', - transportConfig: { - host: 'localhost', - port: 8080 - } - }); - - expect(client.connect()).rejects.toThrow(); - - // 恢复原始WebSocket - (global as any).WebSocket = originalWebSocket; - }); - }); - - describe('功能测试', () => { - beforeEach(() => { - client = new NetworkClient({ - transport: 'websocket', - transportConfig: { - host: 'localhost', - port: 8080 - } - }); - }); - - it('应该能够成功连接', async () => { - const connectPromise = client.connect(); - - // 模拟连接成功 - setTimeout(() => { - const transport = (client as any).transport; - if (transport && transport.websocket && transport.websocket.onopen) { - transport.websocket.readyState = WebSocket.OPEN; - transport.websocket.onopen(new Event('open')); - } - }, 0); - - await expect(connectPromise).resolves.toBeUndefined(); - }); - - it('应该能够发送消息', async () => { - // 先连接 - const connectPromise = client.connect(); - setTimeout(() => { - const transport = (client as any).transport; - if (transport && transport.websocket && transport.websocket.onopen) { - transport.websocket.readyState = WebSocket.OPEN; - transport.websocket.onopen(new Event('open')); - } - }, 0); - await connectPromise; - - // 发送消息 - const message = { - type: 'custom' as const, - data: { test: 'message' }, - reliable: true - }; - - // NetworkClient没有直接的sendMessage方法,它通过RPC调用 - }); - - it('应该能够正确断开连接', async () => { - await expect(client.disconnect()).resolves.toBeUndefined(); - }); - - it('应该返回正确的认证状态', () => { - expect(client.isAuthenticated()).toBe(false); - }); - - it('应该能够获取网络对象列表', () => { - const networkObjects = client.getAllNetworkObjects(); - expect(Array.isArray(networkObjects)).toBe(true); - expect(networkObjects.length).toBe(0); - }); - }); - - describe('消息ID生成测试', () => { - beforeEach(() => { - client = new NetworkClient({ - transport: 'websocket', - transportConfig: { - host: 'localhost', - port: 8080 - } - }); - }); - - it('应该能够生成唯一的消息ID', () => { - const messageId1 = (client as any).generateMessageId(); - const messageId2 = (client as any).generateMessageId(); - - expect(typeof messageId1).toBe('string'); - expect(typeof messageId2).toBe('string'); - expect(messageId1).not.toBe(messageId2); - }); - - it('生成的消息ID应该符合预期格式', () => { - const messageId = (client as any).generateMessageId(); - - // 检查消息ID格式(时间戳 + 随机字符串) - expect(messageId).toMatch(/^[a-z0-9]+$/); - expect(messageId.length).toBeGreaterThan(10); - }); - }); - - describe('错误恢复测试', () => { - beforeEach(() => { - client = new NetworkClient({ - transportType: 'websocket', - host: 'localhost', - port: 8080, - maxReconnectAttempts: 2, - reconnectInterval: 100 - }); - }); - - it('连接失败后应该尝试重连', async () => { - let connectAttempts = 0; - const originalWebSocket = (global as any).WebSocket; - - (global as any).WebSocket = jest.fn().mockImplementation(() => { - connectAttempts++; - const ws = new originalWebSocket('ws://localhost:8080'); - // 模拟连接失败 - setTimeout(() => { - if (ws.onerror) { - ws.onerror(new Event('error')); - } - }, 0); - return ws; - }); - - await expect(client.connect()).rejects.toThrow(); - - // 等待重连尝试 - await new Promise(resolve => setTimeout(resolve, 300)); - - expect(connectAttempts).toBeGreaterThan(1); - - // 恢复原始WebSocket - (global as any).WebSocket = originalWebSocket; - }); - - it('达到最大重连次数后应该停止重连', async () => { - const maxAttempts = 2; - client = new NetworkClient({ - transportType: 'websocket', - host: 'localhost', - port: 8080, - maxReconnectAttempts: maxAttempts, - reconnectInterval: 50 - }); - - let connectAttempts = 0; - const originalWebSocket = (global as any).WebSocket; - - (global as any).WebSocket = jest.fn().mockImplementation(() => { - connectAttempts++; - const ws = new originalWebSocket('ws://localhost:8080'); - setTimeout(() => { - if (ws.onerror) { - ws.onerror(new Event('error')); - } - }, 0); - return ws; - }); - - await expect(client.connect()).rejects.toThrow(); - - // 等待所有重连尝试完成 - await new Promise(resolve => setTimeout(resolve, 200)); - - expect(connectAttempts).toBeLessThanOrEqual(maxAttempts + 1); - - // 恢复原始WebSocket - (global as any).WebSocket = originalWebSocket; - }); - }); - - describe('内存泄漏防护测试', () => { - it('断开连接时应该清理所有资源', async () => { - client = new NetworkClient({ - transport: 'websocket', - transportConfig: { - host: 'localhost', - port: 8080 - } - }); - - const { Emitter } = require('@esengine/ecs-framework'); - const emitterInstance = Emitter.mock.results[Emitter.mock.results.length - 1].value; - - await client.disconnect(); - - expect(emitterInstance.removeAllListeners).toHaveBeenCalled(); - }); - - it('多次创建和销毁客户端不应该造成内存泄漏', () => { - const initialEmitterCallCount = require('@esengine/ecs-framework').Emitter.mock.calls.length; - - // 创建和销毁多个客户端实例 - for (let i = 0; i < 5; i++) { - const tempClient = new NetworkClient({ - transportType: 'websocket', - host: 'localhost', - port: 8080 - }); - tempClient.disconnect().catch(() => {}); - } - - const finalEmitterCallCount = require('@esengine/ecs-framework').Emitter.mock.calls.length; - - // 验证Emitter实例数量符合预期 - expect(finalEmitterCallCount - initialEmitterCallCount).toBe(5); - }); - }); -}); \ No newline at end of file diff --git a/packages/network-client/tests/setup.ts b/packages/network-client/tests/setup.ts index efad751e..99f6fa44 100644 --- a/packages/network-client/tests/setup.ts +++ b/packages/network-client/tests/setup.ts @@ -1,27 +1,65 @@ +/** + * Jest测试环境设置 - 客户端 + */ + +// 导入reflect-metadata以支持装饰器 import 'reflect-metadata'; -// Mock WebSocket for testing -(global as any).WebSocket = class MockWebSocket { - onopen: ((event: Event) => void) | null = null; - onmessage: ((event: MessageEvent) => void) | null = null; - onclose: ((event: CloseEvent) => void) | null = null; - onerror: ((event: Event) => void) | null = null; - - constructor(public url: string) {} - - send(data: string | ArrayBuffer | Blob) { - // Mock implementation - } - - close() { - // Mock implementation - } -}; +// 模拟浏览器环境的WebSocket +Object.defineProperty(global, 'WebSocket', { + value: class MockWebSocket { + static CONNECTING = 0; + static OPEN = 1; + static CLOSING = 2; + static CLOSED = 3; -global.beforeEach(() => { - jest.clearAllMocks(); + readyState = MockWebSocket.CONNECTING; + url: string; + onopen: ((event: Event) => void) | null = null; + onclose: ((event: CloseEvent) => void) | null = null; + onmessage: ((event: MessageEvent) => void) | null = null; + onerror: ((event: Event) => void) | null = null; + + constructor(url: string) { + this.url = url; + // 模拟异步连接 + setTimeout(() => { + this.readyState = MockWebSocket.OPEN; + if (this.onopen) { + this.onopen(new Event('open')); + } + }, 0); + } + + send(data: string | ArrayBuffer) { + // 模拟发送 + } + + close() { + this.readyState = MockWebSocket.CLOSED; + if (this.onclose) { + this.onclose(new CloseEvent('close')); + } + } + } }); -global.afterEach(() => { - jest.restoreAllMocks(); +// 全局测试配置 +beforeAll(() => { + // 设置测试环境 + process.env.NODE_ENV = 'test'; + process.env.NETWORK_ENV = 'client'; +}); + +afterAll(() => { + // 清理测试环境 +}); + +beforeEach(() => { + // 每个测试前的准备工作 +}); + +afterEach(() => { + // 每个测试后的清理工作 + // 清理可能的网络连接、定时器等 }); \ No newline at end of file diff --git a/packages/network-client/tests/transport/ClientTransport.test.ts b/packages/network-client/tests/transport/ClientTransport.test.ts deleted file mode 100644 index 55dd027a..00000000 --- a/packages/network-client/tests/transport/ClientTransport.test.ts +++ /dev/null @@ -1,374 +0,0 @@ -/** - * ClientTransport 基类测试 - * 测试客户端传输层基类的构造函数和依赖问题 - */ - -import { ClientTransport, ClientTransportConfig, ConnectionState } from '../../src/transport/ClientTransport'; - -// Mock Emitter 和 Core -jest.mock('@esengine/ecs-framework', () => ({ - Emitter: jest.fn().mockImplementation(() => ({ - emit: jest.fn(), - on: jest.fn(), - off: jest.fn(), - removeAllListeners: jest.fn() - })), - Core: { - schedule: { - scheduleRepeating: jest.fn((callback: Function, interval: number) => ({ - stop: jest.fn() - })) - } - } -})); - -// Mock network-shared -jest.mock('@esengine/ecs-framework-network-shared', () => ({ - NetworkValue: {} -})); - -// 创建测试用的具体实现类 -class TestClientTransport extends ClientTransport { - async connect(): Promise { - return Promise.resolve(); - } - - async disconnect(): Promise { - return Promise.resolve(); - } - - async sendMessage(message: any): Promise { - return Promise.resolve(); - } -} - -describe('ClientTransport', () => { - let transport: TestClientTransport; - const defaultConfig: ClientTransportConfig = { - host: 'localhost', - port: 8080 - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - afterEach(() => { - if (transport) { - transport = null as any; - } - }); - - describe('构造函数测试', () => { - it('应该能够成功创建ClientTransport实例', () => { - expect(() => { - transport = new TestClientTransport(defaultConfig); - }).not.toThrow(); - - expect(transport).toBeInstanceOf(ClientTransport); - }); - - it('应该正确设置默认配置', () => { - transport = new TestClientTransport(defaultConfig); - - const config = (transport as any).config; - expect(config.host).toBe('localhost'); - expect(config.port).toBe(8080); - expect(config.secure).toBe(false); - expect(config.connectionTimeout).toBe(10000); - expect(config.reconnectInterval).toBe(3000); - expect(config.maxReconnectAttempts).toBe(10); - expect(config.heartbeatInterval).toBe(30000); - expect(config.maxQueueSize).toBe(1000); - }); - - it('应该允许自定义配置覆盖默认值', () => { - const customConfig: ClientTransportConfig = { - host: 'example.com', - port: 9090, - secure: true, - connectionTimeout: 15000, - reconnectInterval: 5000, - maxReconnectAttempts: 5, - heartbeatInterval: 60000, - maxQueueSize: 500 - }; - - transport = new TestClientTransport(customConfig); - - const config = (transport as any).config; - expect(config.host).toBe('example.com'); - expect(config.port).toBe(9090); - expect(config.secure).toBe(true); - expect(config.connectionTimeout).toBe(15000); - expect(config.reconnectInterval).toBe(5000); - expect(config.maxReconnectAttempts).toBe(5); - expect(config.heartbeatInterval).toBe(60000); - expect(config.maxQueueSize).toBe(500); - }); - - it('应该正确初始化内部状态', () => { - transport = new TestClientTransport(defaultConfig); - - expect((transport as any).state).toBe(ConnectionState.DISCONNECTED); - expect((transport as any).messageQueue).toEqual([]); - expect((transport as any).reconnectAttempts).toBe(0); - expect((transport as any).reconnectTimer).toBeNull(); - expect((transport as any).heartbeatTimer).toBeNull(); - expect((transport as any).latencyMeasurements).toEqual([]); - }); - - it('应该正确初始化统计信息', () => { - transport = new TestClientTransport(defaultConfig); - - const stats = transport.getStats(); - expect(stats.connectedAt).toBeNull(); - expect(stats.connectionDuration).toBe(0); - expect(stats.messagesSent).toBe(0); - expect(stats.messagesReceived).toBe(0); - expect(stats.bytesSent).toBe(0); - expect(stats.bytesReceived).toBe(0); - expect(stats.averageLatency).toBe(0); - expect(stats.averageLatency).toBe(0); - expect(stats.reconnectCount).toBe(0); - }); - }); - - describe('依赖注入测试', () => { - it('应该正确处理@esengine/ecs-framework中的Emitter', () => { - const { Emitter } = require('@esengine/ecs-framework'); - - expect(() => { - transport = new TestClientTransport(defaultConfig); - }).not.toThrow(); - - expect(Emitter).toHaveBeenCalled(); - }); - - it('构造函数中Emitter初始化失败应该抛出异常', () => { - // Mock Emitter构造函数抛出异常 - const { Emitter } = require('@esengine/ecs-framework'); - Emitter.mockImplementation(() => { - throw new Error('Emitter初始化失败'); - }); - - expect(() => { - transport = new TestClientTransport(defaultConfig); - }).toThrow('Emitter初始化失败'); - }); - - it('应该正确处理@esengine/ecs-framework-network-shared依赖', () => { - const networkShared = require('@esengine/ecs-framework-network-shared'); - - expect(() => { - transport = new TestClientTransport(defaultConfig); - }).not.toThrow(); - - expect(networkShared).toBeDefined(); - expect(networkShared.NetworkValue).toBeDefined(); - }); - }); - - describe('事件系统测试', () => { - beforeEach(() => { - transport = new TestClientTransport(defaultConfig); - }); - - it('应该能够注册事件监听器', () => { - const mockCallback = jest.fn(); - const { Emitter } = require('@esengine/ecs-framework'); - const emitterInstance = Emitter.mock.results[0].value; - - transport.on('connected', mockCallback); - - expect(emitterInstance.on).toHaveBeenCalledWith('connected', mockCallback); - }); - - it('应该能够移除事件监听器', () => { - const mockCallback = jest.fn(); - const { Emitter } = require('@esengine/ecs-framework'); - const emitterInstance = Emitter.mock.results[0].value; - - transport.off('connected', mockCallback); - - expect(emitterInstance.off).toHaveBeenCalledWith('connected', mockCallback); - }); - - it('应该能够发出事件', () => { - const { Emitter } = require('@esengine/ecs-framework'); - const emitterInstance = Emitter.mock.results[0].value; - - (transport as any).emit('connected'); - - expect(emitterInstance.emit).toHaveBeenCalledWith('connected'); - }); - }); - - describe('消息队列测试', () => { - beforeEach(() => { - transport = new TestClientTransport(defaultConfig); - }); - - it('应该能够将消息加入队列', async () => { - const message = { - type: 'custom' as const, - data: { test: 'data' }, - reliable: true, - timestamp: Date.now() - }; - - await transport.sendMessage(message); - - const messageQueue = (transport as any).messageQueue; - expect(messageQueue).toHaveLength(1); - expect(messageQueue[0]).toEqual(message); - }); - - it('消息队列达到最大大小时应该移除旧消息', async () => { - // 设置较小的队列大小 - const smallQueueConfig = { ...defaultConfig, maxQueueSize: 2 }; - transport = new TestClientTransport(smallQueueConfig); - - const message1 = { type: 'custom' as const, data: { id: 1 }, reliable: true, timestamp: Date.now() }; - const message2 = { type: 'custom' as const, data: { id: 2 }, reliable: true, timestamp: Date.now() }; - const message3 = { type: 'custom' as const, data: { id: 3 }, reliable: true, timestamp: Date.now() }; - - await transport.sendMessage(message1); - await transport.sendMessage(message2); - await transport.sendMessage(message3); - - const messageQueue = (transport as any).messageQueue; - expect(messageQueue).toHaveLength(2); - expect(messageQueue[0]).toEqual(message2); - expect(messageQueue[1]).toEqual(message3); - }); - }); - - describe('连接状态测试', () => { - beforeEach(() => { - transport = new TestClientTransport(defaultConfig); - }); - - it('应该正确获取连接状态', () => { - expect(transport.getState()).toBe(ConnectionState.DISCONNECTED); - }); - - it('应该正确检查连接状态', () => { - expect(transport.isConnected()).toBe(false); - - (transport as any).state = ConnectionState.CONNECTED; - expect(transport.isConnected()).toBe(true); - - (transport as any).state = ConnectionState.AUTHENTICATED; - expect(transport.isConnected()).toBe(true); - }); - - it('状态变化时应该发出事件', () => { - const { Emitter } = require('@esengine/ecs-framework'); - const emitterInstance = Emitter.mock.results[0].value; - - (transport as any).setState(ConnectionState.CONNECTING); - - expect(emitterInstance.emit).toHaveBeenCalledWith( - 'state-changed', - ConnectionState.DISCONNECTED, - ConnectionState.CONNECTING - ); - }); - }); - - describe('延迟测量测试', () => { - beforeEach(() => { - transport = new TestClientTransport(defaultConfig); - }); - - it('应该能够更新延迟测量', () => { - (transport as any).updateLatency(100); - (transport as any).updateLatency(200); - (transport as any).updateLatency(150); - - const stats = transport.getStats(); - expect(stats.averageLatency).toBe(150); - }); - - it('应该限制延迟测量样本数量', () => { - // 添加超过最大样本数的测量 - for (let i = 0; i < 150; i++) { - (transport as any).updateLatency(i * 10); - } - - const latencyMeasurements = (transport as any).latencyMeasurements; - expect(latencyMeasurements.length).toBeLessThanOrEqual(100); - }); - }); - - describe('配置验证测试', () => { - it('应该拒绝无效的主机名', () => { - expect(() => { - transport = new TestClientTransport({ host: '', port: 8080 }); - }).toThrow(); - }); - - it('应该拒绝无效的端口号', () => { - expect(() => { - transport = new TestClientTransport({ host: 'localhost', port: 0 }); - }).toThrow(); - - expect(() => { - transport = new TestClientTransport({ host: 'localhost', port: 65536 }); - }).toThrow(); - }); - - it('应该拒绝负数的超时配置', () => { - expect(() => { - transport = new TestClientTransport({ - host: 'localhost', - port: 8080, - connectionTimeout: -1000 - }); - }).toThrow(); - }); - }); - - describe('资源清理测试', () => { - beforeEach(() => { - transport = new TestClientTransport(defaultConfig); - }); - - it('应该能够清理所有定时器', () => { - const { Core } = require('@esengine/ecs-framework'); - const mockTimer = { stop: jest.fn() }; - Core.schedule.scheduleRepeating.mockReturnValue(mockTimer); - - // 设置一些定时器 - (transport as any).reconnectTimer = mockTimer; - (transport as any).heartbeatTimer = mockTimer; - - // 调用清理方法 - (transport as any).cleanup(); - - expect(mockTimer.stop).toHaveBeenCalledTimes(2); - expect((transport as any).reconnectTimer).toBeNull(); - expect((transport as any).heartbeatTimer).toBeNull(); - }); - - it('应该能够清理消息队列', () => { - (transport as any).messageQueue = [ - { type: 'custom', data: {}, reliable: true, timestamp: Date.now() } - ]; - - (transport as any).cleanup(); - - expect((transport as any).messageQueue).toHaveLength(0); - }); - - it('应该能够移除所有事件监听器', () => { - const { Emitter } = require('@esengine/ecs-framework'); - const emitterInstance = Emitter.mock.results[0].value; - - (transport as any).cleanup(); - - expect(emitterInstance.removeAllListeners).toHaveBeenCalled(); - }); - }); -}); \ No newline at end of file diff --git a/packages/network-client/tests/transport/WebSocketClientTransport.test.ts b/packages/network-client/tests/transport/WebSocketClientTransport.test.ts deleted file mode 100644 index b13caa09..00000000 --- a/packages/network-client/tests/transport/WebSocketClientTransport.test.ts +++ /dev/null @@ -1,348 +0,0 @@ -/** - * WebSocketClientTransport 测试 - * 测试WebSocket客户端传输层的构造函数和依赖问题 - */ - -import { WebSocketClientTransport, WebSocketClientConfig } from '../../src/transport/WebSocketClientTransport'; -import { ConnectionState } from '../../src/transport/ClientTransport'; - -// Mock WebSocket -class MockWebSocket { - public readyState: number = WebSocket.CONNECTING; - public onopen: ((event: Event) => void) | null = null; - public onclose: ((event: CloseEvent) => void) | null = null; - public onmessage: ((event: MessageEvent) => void) | null = null; - public onerror: ((event: Event) => void) | null = null; - - constructor(public url: string, public protocols?: string | string[]) {} - - send(data: string | ArrayBuffer | Blob): void {} - close(code?: number, reason?: string): void { - this.readyState = WebSocket.CLOSED; - if (this.onclose) { - this.onclose(new CloseEvent('close', { code: code || 1000, reason: reason || '' })); - } - } -} - -// Mock依赖 - 直接创建mock对象而不依赖外部模块 -const mockCore = { - schedule: { - scheduleRepeating: jest.fn((callback: Function, interval: number) => ({ - stop: jest.fn() - })) - } -}; - -const mockEmitter = { - emit: jest.fn(), - on: jest.fn(), - off: jest.fn(), - removeAllListeners: jest.fn() -}; - -const mockNetworkShared = { - NetworkValue: {}, - generateMessageId: jest.fn(() => 'mock-message-id-123') -}; - -// 设置模块mock -jest.doMock('@esengine/ecs-framework', () => ({ - Core: mockCore, - Emitter: jest.fn(() => mockEmitter) -})); - -jest.doMock('@esengine/ecs-framework-network-shared', () => mockNetworkShared); - -// 设置全局WebSocket mock -(global as any).WebSocket = MockWebSocket; -(global as any).WebSocket.CONNECTING = 0; -(global as any).WebSocket.OPEN = 1; -(global as any).WebSocket.CLOSING = 2; -(global as any).WebSocket.CLOSED = 3; - -describe('WebSocketClientTransport', () => { - let transport: WebSocketClientTransport; - const defaultConfig: WebSocketClientConfig = { - host: 'localhost', - port: 8080, - secure: false, - connectionTimeout: 5000, - reconnectInterval: 1000, - maxReconnectAttempts: 3, - heartbeatInterval: 30000 - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - afterEach(() => { - if (transport) { - transport.disconnect().catch(() => {}); - transport = null as any; - } - }); - - describe('构造函数测试', () => { - it('应该能够成功创建WebSocketClientTransport实例', () => { - expect(() => { - transport = new WebSocketClientTransport(defaultConfig); - }).not.toThrow(); - - expect(transport).toBeInstanceOf(WebSocketClientTransport); - }); - - it('应该正确合并默认配置', () => { - transport = new WebSocketClientTransport(defaultConfig); - - const config = (transport as any).config; - expect(config.path).toBe('/ws'); - expect(config.protocols).toEqual([]); - expect(config.headers).toEqual({}); - expect(config.binaryType).toBe('arraybuffer'); - expect(config.host).toBe('localhost'); - expect(config.port).toBe(8080); - }); - - it('应该允许自定义配置覆盖默认值', () => { - const customConfig: WebSocketClientConfig = { - ...defaultConfig, - path: '/custom-ws', - protocols: ['custom-protocol'], - headers: { 'X-Custom': 'value' }, - binaryType: 'blob' - }; - - transport = new WebSocketClientTransport(customConfig); - - const config = (transport as any).config; - expect(config.path).toBe('/custom-ws'); - expect(config.protocols).toEqual(['custom-protocol']); - expect(config.headers).toEqual({ 'X-Custom': 'value' }); - expect(config.binaryType).toBe('blob'); - }); - - it('应该正确初始化内部状态', () => { - transport = new WebSocketClientTransport(defaultConfig); - - expect((transport as any).websocket).toBeNull(); - expect((transport as any).connectionPromise).toBeNull(); - expect((transport as any).connectionTimeoutTimer).toBeNull(); - expect((transport as any).state).toBe(ConnectionState.DISCONNECTED); - }); - }); - - describe('依赖注入测试', () => { - it('应该正确处理@esengine/ecs-framework依赖', () => { - const { Core } = require('@esengine/ecs-framework'); - - expect(() => { - transport = new WebSocketClientTransport(defaultConfig); - }).not.toThrow(); - - expect(Core).toBeDefined(); - }); - - it('应该正确处理@esengine/ecs-framework-network-shared依赖', () => { - const { generateMessageId } = require('@esengine/ecs-framework-network-shared'); - - expect(() => { - transport = new WebSocketClientTransport(defaultConfig); - }).not.toThrow(); - - expect(generateMessageId).toBeDefined(); - expect(typeof generateMessageId).toBe('function'); - }); - }); - - describe('连接功能测试', () => { - beforeEach(() => { - transport = new WebSocketClientTransport(defaultConfig); - }); - - it('应该能够发起连接', async () => { - const connectPromise = transport.connect(); - - expect((transport as any).websocket).toBeInstanceOf(MockWebSocket); - expect((transport as any).state).toBe(ConnectionState.CONNECTING); - - // 模拟连接成功 - const ws = (transport as any).websocket as MockWebSocket; - ws.readyState = WebSocket.OPEN; - if (ws.onopen) { - ws.onopen(new Event('open')); - } - - await expect(connectPromise).resolves.toBeUndefined(); - }); - - it('应该构造正确的WebSocket URL', async () => { - transport.connect(); - - const ws = (transport as any).websocket as MockWebSocket; - expect(ws.url).toBe('ws://localhost:8080/ws'); - }); - - it('使用安全连接时应该构造HTTPS URL', async () => { - const secureConfig = { ...defaultConfig, secure: true }; - transport = new WebSocketClientTransport(secureConfig); - - transport.connect(); - - const ws = (transport as any).websocket as MockWebSocket; - expect(ws.url).toBe('wss://localhost:8080/ws'); - }); - - it('应该设置WebSocket事件处理器', async () => { - transport.connect(); - - const ws = (transport as any).websocket as MockWebSocket; - expect(ws.onopen).toBeDefined(); - expect(ws.onclose).toBeDefined(); - expect(ws.onmessage).toBeDefined(); - expect(ws.onerror).toBeDefined(); - }); - - it('连接超时应该被正确处理', async () => { - const shortTimeoutConfig = { ...defaultConfig, connectionTimeout: 100 }; - transport = new WebSocketClientTransport(shortTimeoutConfig); - - const connectPromise = transport.connect(); - - // 不触发onopen事件,让连接超时 - await expect(connectPromise).rejects.toThrow('连接超时'); - }); - - it('应该能够正确断开连接', async () => { - transport.connect(); - - // 模拟连接成功 - const ws = (transport as any).websocket as MockWebSocket; - ws.readyState = WebSocket.OPEN; - if (ws.onopen) { - ws.onopen(new Event('open')); - } - - await transport.disconnect(); - expect((transport as any).state).toBe(ConnectionState.DISCONNECTED); - }); - }); - - describe('消息发送测试', () => { - beforeEach(async () => { - transport = new WebSocketClientTransport(defaultConfig); - }); - - it('未连接时发送消息应该加入队列', async () => { - const message = { - type: 'custom' as const, - data: { test: 'data' }, - reliable: true, - timestamp: Date.now() - }; - - await transport.sendMessage(message); - - const messageQueue = (transport as any).messageQueue; - expect(messageQueue).toHaveLength(1); - expect(messageQueue[0]).toEqual(message); - }); - - it('连接后应该发送队列中的消息', async () => { - const message = { - type: 'custom' as const, - data: { test: 'data' }, - reliable: true, - timestamp: Date.now() - }; - - // 先发送消息到队列 - await transport.sendMessage(message); - - // 然后连接 - transport.connect(); - const ws = (transport as any).websocket as MockWebSocket; - const sendSpy = jest.spyOn(ws, 'send'); - - // 模拟连接成功 - ws.readyState = WebSocket.OPEN; - if (ws.onopen) { - ws.onopen(new Event('open')); - } - - expect(sendSpy).toHaveBeenCalled(); - expect((transport as any).messageQueue).toHaveLength(0); - }); - }); - - describe('错误处理测试', () => { - it('应该处理WebSocket构造函数异常', () => { - // Mock WebSocket构造函数抛出异常 - const originalWebSocket = (global as any).WebSocket; - (global as any).WebSocket = jest.fn(() => { - throw new Error('WebSocket构造失败'); - }); - - transport = new WebSocketClientTransport(defaultConfig); - - expect(transport.connect()).rejects.toThrow('WebSocket构造失败'); - - // 恢复原始WebSocket - (global as any).WebSocket = originalWebSocket; - }); - - it('应该处理网络连接错误', async () => { - transport = new WebSocketClientTransport(defaultConfig); - - const connectPromise = transport.connect(); - - // 模拟连接错误 - const ws = (transport as any).websocket as MockWebSocket; - if (ws.onerror) { - ws.onerror(new Event('error')); - } - - await expect(connectPromise).rejects.toThrow(); - }); - - it('应该处理意外的连接关闭', () => { - transport = new WebSocketClientTransport(defaultConfig); - transport.connect(); - - const ws = (transport as any).websocket as MockWebSocket; - - // 模拟连接意外关闭 - if (ws.onclose) { - ws.onclose(new CloseEvent('close', { code: 1006, reason: '意外关闭' })); - } - - expect((transport as any).state).toBe(ConnectionState.DISCONNECTED); - }); - }); - - describe('统计信息测试', () => { - it('应该正确计算连接统计信息', async () => { - transport = new WebSocketClientTransport(defaultConfig); - - const initialStats = transport.getStats(); - expect(initialStats.connectedAt).toBeNull(); - expect(initialStats.messagesSent).toBe(0); - expect(initialStats.messagesReceived).toBe(0); - }); - - it('连接后应该更新统计信息', async () => { - transport = new WebSocketClientTransport(defaultConfig); - - transport.connect(); - const ws = (transport as any).websocket as MockWebSocket; - ws.readyState = WebSocket.OPEN; - if (ws.onopen) { - ws.onopen(new Event('open')); - } - - const stats = transport.getStats(); - expect(stats.connectedAt).toBeInstanceOf(Date); - }); - }); -}); \ No newline at end of file diff --git a/packages/network-client/tsconfig.json b/packages/network-client/tsconfig.json index b6817988..c353a94b 100644 --- a/packages/network-client/tsconfig.json +++ b/packages/network-client/tsconfig.json @@ -4,10 +4,11 @@ "module": "ES2020", "moduleResolution": "node", "allowImportingTsExtensions": false, - "lib": ["ES2020", "DOM"], + "lib": ["ES2020", "DOM", "WebWorker"], "outDir": "./bin", "rootDir": "./src", "strict": true, + "composite": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "skipLibCheck": true, @@ -41,5 +42,13 @@ "bin", "**/*.test.ts", "**/*.spec.ts" + ], + "references": [ + { + "path": "../core" + }, + { + "path": "../network-shared" + } ] } \ No newline at end of file diff --git a/packages/network-server/README.md b/packages/network-server/README.md deleted file mode 100644 index 84839447..00000000 --- a/packages/network-server/README.md +++ /dev/null @@ -1,132 +0,0 @@ -# @esengine/ecs-framework-network-server - -ECS Framework 网络库 - 服务端实现 - -## 概述 - -这是 ECS Framework 网络库的服务端包,提供了: - -- 权威服务端实现 -- 客户端会话管理 -- 房间和匹配系统 -- 反作弊验证 -- 网络同步权威控制 - -## 特性 - -- **权威服务端**: 所有网络状态由服务端权威控制 -- **客户端验证**: 验证客户端输入和操作的合法性 -- **房间系统**: 支持多房间和实例管理 -- **反作弊**: 内置反作弊验证机制 -- **高性能**: 针对大量客户端连接进行优化 - -## 安装 - -```bash -npm install @esengine/ecs-framework-network-server -``` - -## 基本用法 - -```typescript -import { NetworkServerManager } from '@esengine/ecs-framework-network-server'; -import { NetworkComponent, SyncVar, ServerRpc } from '@esengine/ecs-framework-network-shared'; - -// 启动服务端 -const server = new NetworkServerManager(); -await server.startServer({ - port: 7777, - maxConnections: 100 -}); - -// 创建权威网络组件 -@NetworkComponent() -class ServerPlayerController extends NetworkBehaviour { - @SyncVar() - public position: Vector3 = { x: 0, y: 0, z: 0 }; - - @SyncVar() - public health: number = 100; - - @ServerRpc({ requiresOwnership: true, rateLimit: 10 }) - public movePlayer(direction: Vector3): void { - // 服务端权威的移动处理 - if (this.validateMovement(direction)) { - this.position.add(direction); - } - } - - @ServerRpc({ requiresAuth: true }) - public takeDamage(damage: number, attackerId: number): void { - // 服务端权威的伤害处理 - if (this.validateDamage(damage, attackerId)) { - this.health -= damage; - - if (this.health <= 0) { - this.handlePlayerDeath(); - } - } - } -} -``` - -## 房间系统 - -```typescript -import { RoomManager, Room } from '@esengine/ecs-framework-network-server'; - -// 创建房间管理器 -const roomManager = new RoomManager(); - -// 创建房间 -const gameRoom = roomManager.createRoom({ - name: 'Game Room 1', - maxPlayers: 4, - isPrivate: false -}); - -// 玩家加入房间 -gameRoom.addPlayer(clientId, playerData); - -// 房间事件处理 -gameRoom.onPlayerJoined((player) => { - console.log(`Player ${player.name} joined room ${gameRoom.name}`); -}); - -gameRoom.onPlayerLeft((player) => { - console.log(`Player ${player.name} left room ${gameRoom.name}`); -}); -``` - -## 权限验证 - -```typescript -import { AuthSystem } from '@esengine/ecs-framework-network-server'; - -// 配置认证系统 -const authSystem = new AuthSystem({ - tokenSecret: 'your-secret-key', - sessionTimeout: 30 * 60 * 1000, // 30分钟 - maxLoginAttempts: 5 -}); - -// 客户端认证 -authSystem.onClientAuth(async (clientId, credentials) => { - const user = await validateCredentials(credentials); - if (user) { - return { userId: user.id, permissions: user.permissions }; - } - return null; -}); - -// RPC 权限检查 -@ServerRpc({ requiresAuth: true, requiresOwnership: true }) -public adminCommand(command: string): void { - // 只有已认证且拥有权限的客户端可以调用 - this.executeAdminCommand(command); -} -``` - -## License - -MIT \ No newline at end of file diff --git a/packages/network-server/build-rollup.cjs b/packages/network-server/build-rollup.cjs index d50cd196..a4f9ce6c 100644 --- a/packages/network-server/build-rollup.cjs +++ b/packages/network-server/build-rollup.cjs @@ -2,27 +2,32 @@ const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); -console.log('🚀 使用 Rollup 构建 network-server 包...'); +console.log('🚀 使用 Rollup 构建 @esengine/network-server 包...'); async function main() { try { + // 清理旧的dist目录 if (fs.existsSync('./dist')) { console.log('🧹 清理旧的构建文件...'); execSync('rimraf ./dist', { stdio: 'inherit' }); } + // 执行Rollup构建 console.log('📦 执行 Rollup 构建...'); - execSync('rollup -c rollup.config.cjs', { stdio: 'inherit' }); + execSync('npx rollup -c rollup.config.cjs', { stdio: 'inherit' }); + // 生成package.json console.log('📋 生成 package.json...'); generatePackageJson(); + // 复制其他文件 console.log('📁 复制必要文件...'); copyFiles(); + // 输出构建结果 showBuildResults(); - console.log('✅ network-server 构建完成!'); + console.log('✅ @esengine/network-server 构建完成!'); console.log('\n🚀 发布命令:'); console.log('cd dist && npm publish'); @@ -52,7 +57,7 @@ function generatePackageJson() { files: [ 'index.mjs', 'index.mjs.map', - 'index.cjs', + 'index.cjs', 'index.cjs.map', 'index.d.ts', 'README.md', @@ -60,19 +65,18 @@ function generatePackageJson() { ], keywords: [ 'ecs', - 'networking', + 'network', 'server', - 'authority', - 'validation', - 'rooms', - 'game-server', + 'multiplayer', + 'game', + 'nodejs', 'typescript' ], author: sourcePackage.author, license: sourcePackage.license, repository: sourcePackage.repository, dependencies: sourcePackage.dependencies, - peerDependencies: sourcePackage.peerDependencies, + publishConfig: sourcePackage.publishConfig, engines: { node: '>=16.0.0' }, @@ -85,7 +89,7 @@ function generatePackageJson() { function copyFiles() { const filesToCopy = [ { src: './README.md', dest: './dist/README.md' }, - { src: '../../LICENSE', dest: './dist/LICENSE' } + { src: './LICENSE', dest: './dist/LICENSE' } ]; filesToCopy.forEach(({ src, dest }) => { diff --git a/packages/network-server/jest.config.cjs b/packages/network-server/jest.config.cjs index 99f23f58..b2357715 100644 --- a/packages/network-server/jest.config.cjs +++ b/packages/network-server/jest.config.cjs @@ -1,7 +1,7 @@ /** @type {import('ts-jest').JestConfigWithTsJest} */ module.exports = { preset: 'ts-jest', - testEnvironment: 'node', // 服务端库使用 node 环境 + testEnvironment: 'node', roots: ['/tests'], testMatch: ['**/*.test.ts', '**/*.spec.ts'], testPathIgnorePatterns: ['/node_modules/'], @@ -18,16 +18,10 @@ module.exports = { coverageReporters: ['text', 'lcov', 'html'], coverageThreshold: { global: { - branches: 60, + branches: 70, functions: 70, lines: 70, statements: 70 - }, - './src/core/': { - branches: 70, - functions: 80, - lines: 80, - statements: 80 } }, verbose: true, @@ -42,7 +36,7 @@ module.exports = { }, moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], setupFilesAfterEnv: ['/tests/setup.ts'], - testTimeout: 10000, + testTimeout: 15000, // 服务端测试可能需要更长时间 clearMocks: true, restoreMocks: true, modulePathIgnorePatterns: [ diff --git a/packages/network-server/package.json b/packages/network-server/package.json index 813dfafc..d51a536e 100644 --- a/packages/network-server/package.json +++ b/packages/network-server/package.json @@ -1,8 +1,7 @@ { - "name": "@esengine/ecs-framework-network-server", - "version": "1.0.5", - "description": "ECS Framework 网络库 - 服务端实现", - "type": "module", + "name": "@esengine/network-server", + "version": "1.0.1", + "description": "ECS Framework网络层 - 服务端实现", "main": "bin/index.js", "types": "bin/index.d.ts", "exports": { @@ -22,16 +21,15 @@ ], "keywords": [ "ecs", - "networking", + "network", "server", - "authority", - "validation", - "rooms", - "game-server", + "multiplayer", + "game", + "nodejs", "typescript" ], "scripts": { - "clean": "rimraf bin dist", + "clean": "rimraf bin dist tsconfig.tsbuildinfo", "build:ts": "tsc", "prebuild": "npm run clean", "build": "npm run build:ts", @@ -43,38 +41,29 @@ "publish:minor": "npm version minor && npm run build:npm && cd dist && npm publish", "publish:major": "npm version major && npm run build:npm && cd dist && npm publish", "preversion": "npm run rebuild", + "dev": "ts-node src/dev-server.ts", + "start": "node bin/index.js", "test": "jest --config jest.config.cjs", "test:watch": "jest --watch --config jest.config.cjs", "test:coverage": "jest --coverage --config jest.config.cjs", - "test:ci": "jest --ci --coverage --config jest.config.cjs", - "test:clear": "jest --clearCache" + "test:ci": "jest --ci --coverage --config jest.config.cjs" }, "author": "yhh", "license": "MIT", "dependencies": { - "ws": "^8.18.0", - "uuid": "^10.0.0" - }, - "peerDependencies": { - "@esengine/ecs-framework": ">=2.1.29", - "@esengine/ecs-framework-network-shared": ">=1.0.0" + "@esengine/ecs-framework": "file:../core", + "@esengine/network-shared": "file:../network-shared", + "ws": "^8.18.2", + "reflect-metadata": "^0.2.2" }, "devDependencies": { - "@esengine/ecs-framework": "*", - "@esengine/ecs-framework-network-shared": "*", - "@rollup/plugin-commonjs": "^28.0.3", - "@rollup/plugin-node-resolve": "^16.0.1", - "@rollup/plugin-terser": "^0.4.4", "@types/jest": "^29.5.14", "@types/node": "^20.19.0", - "@types/uuid": "^10.0.0", - "@types/ws": "^8.5.13", + "@types/ws": "^8.18.1", "jest": "^29.7.0", - "jest-environment-node": "^29.7.0", "rimraf": "^5.0.0", - "rollup": "^4.42.0", - "rollup-plugin-dts": "^6.2.1", "ts-jest": "^29.4.0", + "ts-node": "^10.9.0", "typescript": "^5.8.3" }, "publishConfig": { diff --git a/packages/network-server/rollup.config.cjs b/packages/network-server/rollup.config.cjs index 25a4a33c..ddc4900c 100644 --- a/packages/network-server/rollup.config.cjs +++ b/packages/network-server/rollup.config.cjs @@ -7,23 +7,33 @@ const { readFileSync } = require('fs'); const pkg = JSON.parse(readFileSync('./package.json', 'utf8')); const banner = `/** - * @esengine/ecs-framework-network-server v${pkg.version} - * ECS Framework 网络库 - 服务端实现 + * @esengine/network-server v${pkg.version} + * ECS网络层服务端实现 * * @author ${pkg.author} * @license ${pkg.license} */`; +// 外部依赖,不打包进bundle (Node.js环境,保持依赖外部化) const external = [ - 'ws', - 'uuid', '@esengine/ecs-framework', - '@esengine/ecs-framework-network-shared' + '@esengine/network-shared', + 'ws', + 'reflect-metadata', + 'http', + 'https', + 'crypto', + 'events', + 'stream', + 'util', + 'fs', + 'path' ]; const commonPlugins = [ resolve({ - preferBuiltins: true + preferBuiltins: true, + browser: false }), commonjs({ include: /node_modules/ @@ -57,7 +67,7 @@ module.exports = [ } }, - // CommonJS构建 + // CommonJS构建 (Node.js主要格式) { input: 'bin/index.js', output: { @@ -88,7 +98,7 @@ module.exports = [ file: 'dist/index.d.ts', format: 'es', banner: `/** - * @esengine/ecs-framework-network-server v${pkg.version} + * @esengine/network-server v${pkg.version} * TypeScript definitions */` }, diff --git a/packages/network-server/src/auth/AuthenticationManager.ts b/packages/network-server/src/auth/AuthenticationManager.ts deleted file mode 100644 index 1ccd7ac4..00000000 --- a/packages/network-server/src/auth/AuthenticationManager.ts +++ /dev/null @@ -1,622 +0,0 @@ -/** - * 身份验证管理器 - * - * 处理客户端身份验证、令牌验证等功能 - */ - -import { EventEmitter } from 'events'; -import { createHash, randomBytes } from 'crypto'; -import { NetworkValue } from '@esengine/ecs-framework-network-shared'; -import { ClientConnection } from '../core/ClientConnection'; - -/** - * 认证配置 - */ -export interface AuthConfig { - /** 令牌过期时间(毫秒) */ - tokenExpirationTime?: number; - /** 最大登录尝试次数 */ - maxLoginAttempts?: number; - /** 登录尝试重置时间(毫秒) */ - loginAttemptResetTime?: number; - /** 是否启用令牌刷新 */ - enableTokenRefresh?: boolean; - /** 令牌刷新阈值(毫秒) */ - tokenRefreshThreshold?: number; - /** 是否启用IP限制 */ - enableIpRestriction?: boolean; - /** 密码哈希算法 */ - passwordHashAlgorithm?: 'sha256' | 'sha512'; -} - -/** - * 用户信息 - */ -export interface UserInfo { - /** 用户ID */ - id: string; - /** 用户名 */ - username: string; - /** 密码哈希 */ - passwordHash: string; - /** 用户角色 */ - roles: string[]; - /** 用户元数据 */ - metadata: Record; - /** 创建时间 */ - createdAt: Date; - /** 最后登录时间 */ - lastLoginAt?: Date; - /** 是否激活 */ - isActive: boolean; - /** 允许的IP地址列表 */ - allowedIps?: string[]; -} - -/** - * 认证令牌 - */ -export interface AuthToken { - /** 令牌ID */ - id: string; - /** 用户ID */ - userId: string; - /** 令牌值 */ - token: string; - /** 创建时间 */ - createdAt: Date; - /** 过期时间 */ - expiresAt: Date; - /** 是否已撤销 */ - isRevoked: boolean; - /** 令牌元数据 */ - metadata: Record; -} - -/** - * 登录尝试记录 - */ -interface LoginAttempt { - /** IP地址 */ - ip: string; - /** 用户名 */ - username: string; - /** 尝试次数 */ - attempts: number; - /** 最后尝试时间 */ - lastAttempt: Date; -} - -/** - * 认证结果 - */ -export interface AuthResult { - /** 是否成功 */ - success: boolean; - /** 用户信息 */ - user?: UserInfo; - /** 认证令牌 */ - token?: AuthToken; - /** 错误信息 */ - error?: string; - /** 错误代码 */ - errorCode?: string; -} - -/** - * 认证管理器事件 - */ -export interface AuthManagerEvents { - /** 用户登录成功 */ - 'login-success': (user: UserInfo, token: AuthToken, clientId: string) => void; - /** 用户登录失败 */ - 'login-failed': (username: string, reason: string, clientId: string) => void; - /** 用户注销 */ - 'logout': (userId: string, clientId: string) => void; - /** 令牌过期 */ - 'token-expired': (userId: string, tokenId: string) => void; - /** 令牌刷新 */ - 'token-refreshed': (userId: string, oldTokenId: string, newTokenId: string) => void; - /** 认证错误 */ - 'auth-error': (error: Error, clientId?: string) => void; -} - -/** - * 身份验证管理器 - */ -export class AuthenticationManager extends EventEmitter { - private config: AuthConfig; - private users = new Map(); - private tokens = new Map(); - private loginAttempts = new Map(); - private cleanupTimer: NodeJS.Timeout | null = null; - - constructor(config: AuthConfig = {}) { - super(); - - this.config = { - tokenExpirationTime: 24 * 60 * 60 * 1000, // 24小时 - maxLoginAttempts: 5, - loginAttemptResetTime: 15 * 60 * 1000, // 15分钟 - enableTokenRefresh: true, - tokenRefreshThreshold: 60 * 60 * 1000, // 1小时 - enableIpRestriction: false, - passwordHashAlgorithm: 'sha256', - ...config - }; - - this.initialize(); - } - - /** - * 注册用户 - */ - async registerUser(userData: { - username: string; - password: string; - roles?: string[]; - metadata?: Record; - allowedIps?: string[]; - }): Promise { - const { username, password, roles = ['user'], metadata = {}, allowedIps } = userData; - - // 检查用户名是否已存在 - if (this.findUserByUsername(username)) { - throw new Error('Username already exists'); - } - - const userId = this.generateId(); - const passwordHash = this.hashPassword(password); - - const user: UserInfo = { - id: userId, - username, - passwordHash, - roles, - metadata, - createdAt: new Date(), - isActive: true, - allowedIps - }; - - this.users.set(userId, user); - - console.log(`User registered: ${username} (${userId})`); - return user; - } - - /** - * 用户登录 - */ - async login( - username: string, - password: string, - client: ClientConnection - ): Promise { - try { - const clientIp = client.remoteAddress; - const attemptKey = `${clientIp}-${username}`; - - // 检查登录尝试次数 - if (this.isLoginBlocked(attemptKey)) { - const result: AuthResult = { - success: false, - error: 'Too many login attempts. Please try again later.', - errorCode: 'LOGIN_BLOCKED' - }; - this.emit('login-failed', username, result.error!, client.id); - return result; - } - - // 查找用户 - const user = this.findUserByUsername(username); - if (!user || !user.isActive) { - this.recordLoginAttempt(attemptKey); - const result: AuthResult = { - success: false, - error: 'Invalid username or password', - errorCode: 'INVALID_CREDENTIALS' - }; - this.emit('login-failed', username, result.error!, client.id); - return result; - } - - // 验证密码 - const passwordHash = this.hashPassword(password); - if (user.passwordHash !== passwordHash) { - this.recordLoginAttempt(attemptKey); - const result: AuthResult = { - success: false, - error: 'Invalid username or password', - errorCode: 'INVALID_CREDENTIALS' - }; - this.emit('login-failed', username, result.error!, client.id); - return result; - } - - // IP限制检查 - if (this.config.enableIpRestriction && user.allowedIps && user.allowedIps.length > 0) { - if (!user.allowedIps.includes(clientIp)) { - const result: AuthResult = { - success: false, - error: 'Access denied from this IP address', - errorCode: 'IP_RESTRICTED' - }; - this.emit('login-failed', username, result.error!, client.id); - return result; - } - } - - // 创建认证令牌 - const token = this.createToken(user.id); - - // 更新用户最后登录时间 - user.lastLoginAt = new Date(); - - // 清除登录尝试记录 - this.loginAttempts.delete(attemptKey); - - const result: AuthResult = { - success: true, - user, - token - }; - - console.log(`User logged in: ${username} (${user.id}) from ${clientIp}`); - this.emit('login-success', user, token, client.id); - - return result; - - } catch (error) { - const result: AuthResult = { - success: false, - error: (error as Error).message, - errorCode: 'INTERNAL_ERROR' - }; - this.emit('auth-error', error as Error, client.id); - return result; - } - } - - /** - * 用户注销 - */ - async logout(tokenValue: string, client: ClientConnection): Promise { - try { - const token = this.findTokenByValue(tokenValue); - if (!token) { - return false; - } - - // 撤销令牌 - token.isRevoked = true; - - console.log(`User logged out: ${token.userId} from ${client.remoteAddress}`); - this.emit('logout', token.userId, client.id); - - return true; - - } catch (error) { - this.emit('auth-error', error as Error, client.id); - return false; - } - } - - /** - * 验证令牌 - */ - async validateToken(tokenValue: string): Promise { - try { - const token = this.findTokenByValue(tokenValue); - - if (!token || token.isRevoked) { - return { - success: false, - error: 'Invalid token', - errorCode: 'INVALID_TOKEN' - }; - } - - if (token.expiresAt < new Date()) { - token.isRevoked = true; - this.emit('token-expired', token.userId, token.id); - return { - success: false, - error: 'Token expired', - errorCode: 'TOKEN_EXPIRED' - }; - } - - const user = this.users.get(token.userId); - if (!user || !user.isActive) { - return { - success: false, - error: 'User not found or inactive', - errorCode: 'USER_NOT_FOUND' - }; - } - - return { - success: true, - user, - token - }; - - } catch (error) { - this.emit('auth-error', error as Error); - return { - success: false, - error: (error as Error).message, - errorCode: 'INTERNAL_ERROR' - }; - } - } - - /** - * 刷新令牌 - */ - async refreshToken(tokenValue: string): Promise { - try { - const validationResult = await this.validateToken(tokenValue); - if (!validationResult.success || !validationResult.user || !validationResult.token) { - return validationResult; - } - - const token = validationResult.token; - const timeUntilExpiration = token.expiresAt.getTime() - Date.now(); - - // 检查是否需要刷新 - if (timeUntilExpiration > this.config.tokenRefreshThreshold!) { - return validationResult; // 不需要刷新 - } - - // 创建新令牌 - const newToken = this.createToken(token.userId, token.metadata); - - // 撤销旧令牌 - token.isRevoked = true; - - console.log(`Token refreshed for user: ${token.userId}`); - this.emit('token-refreshed', token.userId, token.id, newToken.id); - - return { - success: true, - user: validationResult.user, - token: newToken - }; - - } catch (error) { - this.emit('auth-error', error as Error); - return { - success: false, - error: (error as Error).message, - errorCode: 'INTERNAL_ERROR' - }; - } - } - - /** - * 获取用户信息 - */ - getUserById(userId: string): UserInfo | undefined { - return this.users.get(userId); - } - - /** - * 获取用户信息(通过用户名) - */ - getUserByUsername(username: string): UserInfo | undefined { - return this.findUserByUsername(username); - } - - /** - * 更新用户信息 - */ - async updateUser(userId: string, updates: Partial): Promise { - const user = this.users.get(userId); - if (!user) { - return false; - } - - // 不允许更新某些字段 - const { id, createdAt, ...allowedUpdates } = updates as any; - Object.assign(user, allowedUpdates); - - return true; - } - - /** - * 撤销所有用户令牌 - */ - async revokeAllUserTokens(userId: string): Promise { - let revokedCount = 0; - - for (const token of this.tokens.values()) { - if (token.userId === userId && !token.isRevoked) { - token.isRevoked = true; - revokedCount++; - } - } - - return revokedCount; - } - - /** - * 获取活跃令牌数量 - */ - getActiveTokenCount(): number { - return Array.from(this.tokens.values()) - .filter(token => !token.isRevoked && token.expiresAt > new Date()).length; - } - - /** - * 清理过期令牌和登录尝试记录 - */ - cleanup(): void { - const now = new Date(); - let cleanedTokens = 0; - let cleanedAttempts = 0; - - // 清理过期令牌 - for (const [tokenId, token] of this.tokens.entries()) { - if (token.expiresAt < now || token.isRevoked) { - this.tokens.delete(tokenId); - cleanedTokens++; - } - } - - // 清理过期的登录尝试记录 - const resetTime = this.config.loginAttemptResetTime!; - for (const [attemptKey, attempt] of this.loginAttempts.entries()) { - if (now.getTime() - attempt.lastAttempt.getTime() > resetTime) { - this.loginAttempts.delete(attemptKey); - cleanedAttempts++; - } - } - - if (cleanedTokens > 0 || cleanedAttempts > 0) { - console.log(`Auth cleanup: ${cleanedTokens} tokens, ${cleanedAttempts} login attempts`); - } - } - - /** - * 销毁认证管理器 - */ - destroy(): void { - if (this.cleanupTimer) { - clearInterval(this.cleanupTimer); - this.cleanupTimer = null; - } - - this.users.clear(); - this.tokens.clear(); - this.loginAttempts.clear(); - this.removeAllListeners(); - } - - /** - * 初始化 - */ - private initialize(): void { - // 启动清理定时器(每小时清理一次) - this.cleanupTimer = setInterval(() => { - this.cleanup(); - }, 60 * 60 * 1000); - } - - /** - * 查找用户(通过用户名) - */ - private findUserByUsername(username: string): UserInfo | undefined { - return Array.from(this.users.values()) - .find(user => user.username === username); - } - - /** - * 查找令牌(通过令牌值) - */ - private findTokenByValue(tokenValue: string): AuthToken | undefined { - return Array.from(this.tokens.values()) - .find(token => token.token === tokenValue); - } - - /** - * 生成ID - */ - private generateId(): string { - return randomBytes(16).toString('hex'); - } - - /** - * 哈希密码 - */ - private hashPassword(password: string): string { - return createHash(this.config.passwordHashAlgorithm!) - .update(password) - .digest('hex'); - } - - /** - * 创建认证令牌 - */ - private createToken(userId: string, metadata: Record = {}): AuthToken { - const tokenId = this.generateId(); - const tokenValue = randomBytes(32).toString('hex'); - const now = new Date(); - const expiresAt = new Date(now.getTime() + this.config.tokenExpirationTime!); - - const token: AuthToken = { - id: tokenId, - userId, - token: tokenValue, - createdAt: now, - expiresAt, - isRevoked: false, - metadata - }; - - this.tokens.set(tokenId, token); - return token; - } - - /** - * 检查登录是否被阻止 - */ - private isLoginBlocked(attemptKey: string): boolean { - const attempt = this.loginAttempts.get(attemptKey); - if (!attempt) { - return false; - } - - const now = new Date(); - const resetTime = this.config.loginAttemptResetTime!; - - // 检查重置时间 - if (now.getTime() - attempt.lastAttempt.getTime() > resetTime) { - this.loginAttempts.delete(attemptKey); - return false; - } - - return attempt.attempts >= this.config.maxLoginAttempts!; - } - - /** - * 记录登录尝试 - */ - private recordLoginAttempt(attemptKey: string): void { - const now = new Date(); - const [ip, username] = attemptKey.split('-', 2); - - const existingAttempt = this.loginAttempts.get(attemptKey); - if (existingAttempt) { - // 检查是否需要重置 - if (now.getTime() - existingAttempt.lastAttempt.getTime() > this.config.loginAttemptResetTime!) { - existingAttempt.attempts = 1; - } else { - existingAttempt.attempts++; - } - existingAttempt.lastAttempt = now; - } else { - this.loginAttempts.set(attemptKey, { - ip, - username, - attempts: 1, - lastAttempt: now - }); - } - } - - /** - * 类型安全的事件监听 - */ - override on(event: K, listener: AuthManagerEvents[K]): this { - return super.on(event, listener); - } - - /** - * 类型安全的事件触发 - */ - override emit(event: K, ...args: Parameters): boolean { - return super.emit(event, ...args); - } -} \ No newline at end of file diff --git a/packages/network-server/src/auth/AuthorizationManager.ts b/packages/network-server/src/auth/AuthorizationManager.ts deleted file mode 100644 index 44878f12..00000000 --- a/packages/network-server/src/auth/AuthorizationManager.ts +++ /dev/null @@ -1,684 +0,0 @@ -/** - * 权限管理器 - * - * 处理用户权限、角色管理、访问控制等功能 - */ - -import { EventEmitter } from 'events'; -import { NetworkValue } from '@esengine/ecs-framework-network-shared'; -import { UserInfo } from './AuthenticationManager'; - -/** - * 权限类型 - */ -export type Permission = string; - -/** - * 角色定义 - */ -export interface Role { - /** 角色ID */ - id: string; - /** 角色名称 */ - name: string; - /** 角色描述 */ - description?: string; - /** 权限列表 */ - permissions: Permission[]; - /** 父角色ID */ - parentRoleId?: string; - /** 是否系统角色 */ - isSystemRole: boolean; - /** 角色元数据 */ - metadata: Record; - /** 创建时间 */ - createdAt: Date; -} - -/** - * 权限检查上下文 - */ -export interface PermissionContext { - /** 用户ID */ - userId: string; - /** 用户角色 */ - userRoles: string[]; - /** 请求的权限 */ - permission: Permission; - /** 资源ID(可选) */ - resourceId?: string; - /** 附加上下文数据 */ - context?: Record; -} - -/** - * 权限检查结果 - */ -export interface PermissionResult { - /** 是否允许 */ - granted: boolean; - /** 原因 */ - reason?: string; - /** 匹配的角色 */ - matchingRole?: string; - /** 使用的权限 */ - usedPermission?: Permission; -} - -/** - * 权限管理器配置 - */ -export interface AuthorizationConfig { - /** 是否启用权限继承 */ - enableInheritance?: boolean; - /** 是否启用权限缓存 */ - enableCache?: boolean; - /** 缓存过期时间(毫秒) */ - cacheExpirationTime?: number; - /** 默认权限策略 */ - defaultPolicy?: 'deny' | 'allow'; -} - -/** - * 权限管理器事件 - */ -export interface AuthorizationEvents { - /** 权限被授予 */ - 'permission-granted': (context: PermissionContext, result: PermissionResult) => void; - /** 权限被拒绝 */ - 'permission-denied': (context: PermissionContext, result: PermissionResult) => void; - /** 角色创建 */ - 'role-created': (role: Role) => void; - /** 角色更新 */ - 'role-updated': (roleId: string, updates: Partial) => void; - /** 角色删除 */ - 'role-deleted': (roleId: string) => void; - /** 权限错误 */ - 'authorization-error': (error: Error, context?: PermissionContext) => void; -} - -/** - * 权限缓存项 - */ -interface CacheItem { - result: PermissionResult; - expiresAt: Date; -} - -/** - * 预定义权限 - */ -export const Permissions = { - // 系统权限 - SYSTEM_ADMIN: 'system:admin', - SYSTEM_CONFIG: 'system:config', - - // 用户管理权限 - USER_CREATE: 'user:create', - USER_READ: 'user:read', - USER_UPDATE: 'user:update', - USER_DELETE: 'user:delete', - USER_MANAGE_ROLES: 'user:manage-roles', - - // 房间权限 - ROOM_CREATE: 'room:create', - ROOM_JOIN: 'room:join', - ROOM_LEAVE: 'room:leave', - ROOM_MANAGE: 'room:manage', - ROOM_KICK_PLAYERS: 'room:kick-players', - - // 网络权限 - NETWORK_SEND_RPC: 'network:send-rpc', - NETWORK_SYNC_VARS: 'network:sync-vars', - NETWORK_BROADCAST: 'network:broadcast', - - // 聊天权限 - CHAT_SEND: 'chat:send', - CHAT_MODERATE: 'chat:moderate', - CHAT_PRIVATE: 'chat:private', - - // 文件权限 - FILE_UPLOAD: 'file:upload', - FILE_DOWNLOAD: 'file:download', - FILE_DELETE: 'file:delete' -} as const; - -/** - * 预定义角色 - */ -export const SystemRoles = { - ADMIN: 'admin', - MODERATOR: 'moderator', - USER: 'user', - GUEST: 'guest' -} as const; - -/** - * 权限管理器 - */ -export class AuthorizationManager extends EventEmitter { - private config: AuthorizationConfig; - private roles = new Map(); - private permissionCache = new Map(); - private cleanupTimer: NodeJS.Timeout | null = null; - - constructor(config: AuthorizationConfig = {}) { - super(); - - this.config = { - enableInheritance: true, - enableCache: true, - cacheExpirationTime: 5 * 60 * 1000, // 5分钟 - defaultPolicy: 'deny', - ...config - }; - - this.initialize(); - } - - /** - * 创建角色 - */ - async createRole(roleData: { - id: string; - name: string; - description?: string; - permissions: Permission[]; - parentRoleId?: string; - metadata?: Record; - }): Promise { - const { id, name, description, permissions, parentRoleId, metadata = {} } = roleData; - - if (this.roles.has(id)) { - throw new Error(`Role with id "${id}" already exists`); - } - - // 验证父角色是否存在 - if (parentRoleId && !this.roles.has(parentRoleId)) { - throw new Error(`Parent role "${parentRoleId}" not found`); - } - - const role: Role = { - id, - name, - description, - permissions: [...permissions], - parentRoleId, - isSystemRole: false, - metadata, - createdAt: new Date() - }; - - this.roles.set(id, role); - this.clearPermissionCache(); // 清除缓存 - - console.log(`Role created: ${name} (${id})`); - this.emit('role-created', role); - - return role; - } - - /** - * 获取角色 - */ - getRole(roleId: string): Role | undefined { - return this.roles.get(roleId); - } - - /** - * 获取所有角色 - */ - getAllRoles(): Role[] { - return Array.from(this.roles.values()); - } - - /** - * 更新角色 - */ - async updateRole(roleId: string, updates: Partial): Promise { - const role = this.roles.get(roleId); - if (!role) { - return false; - } - - // 系统角色不允许修改某些字段 - if (role.isSystemRole) { - const { permissions, parentRoleId, ...allowedUpdates } = updates; - Object.assign(role, allowedUpdates); - } else { - // 不允许更新某些字段 - const { id, createdAt, isSystemRole, ...allowedUpdates } = updates as any; - Object.assign(role, allowedUpdates); - } - - this.clearPermissionCache(); // 清除缓存 - - console.log(`Role updated: ${role.name} (${roleId})`); - this.emit('role-updated', roleId, updates); - - return true; - } - - /** - * 删除角色 - */ - async deleteRole(roleId: string): Promise { - const role = this.roles.get(roleId); - if (!role) { - return false; - } - - if (role.isSystemRole) { - throw new Error('Cannot delete system role'); - } - - // 检查是否有子角色依赖此角色 - const childRoles = Array.from(this.roles.values()) - .filter(r => r.parentRoleId === roleId); - - if (childRoles.length > 0) { - throw new Error(`Cannot delete role "${roleId}": ${childRoles.length} child roles depend on it`); - } - - this.roles.delete(roleId); - this.clearPermissionCache(); // 清除缓存 - - console.log(`Role deleted: ${role.name} (${roleId})`); - this.emit('role-deleted', roleId); - - return true; - } - - /** - * 检查权限 - */ - async checkPermission(context: PermissionContext): Promise { - try { - // 检查缓存 - const cacheKey = this.getCacheKey(context); - if (this.config.enableCache) { - const cached = this.permissionCache.get(cacheKey); - if (cached && cached.expiresAt > new Date()) { - return cached.result; - } - } - - const result = await this.performPermissionCheck(context); - - // 缓存结果 - if (this.config.enableCache) { - const expiresAt = new Date(Date.now() + this.config.cacheExpirationTime!); - this.permissionCache.set(cacheKey, { result, expiresAt }); - } - - // 触发事件 - if (result.granted) { - this.emit('permission-granted', context, result); - } else { - this.emit('permission-denied', context, result); - } - - return result; - - } catch (error) { - this.emit('authorization-error', error as Error, context); - - return { - granted: this.config.defaultPolicy === 'allow', - reason: `Authorization error: ${(error as Error).message}` - }; - } - } - - /** - * 检查用户是否有权限 - */ - async hasPermission(user: UserInfo, permission: Permission, resourceId?: string): Promise { - const context: PermissionContext = { - userId: user.id, - userRoles: user.roles, - permission, - resourceId - }; - - const result = await this.checkPermission(context); - return result.granted; - } - - /** - * 获取用户的所有权限 - */ - async getUserPermissions(user: UserInfo): Promise { - const permissions = new Set(); - - for (const roleId of user.roles) { - const rolePermissions = await this.getRolePermissions(roleId); - rolePermissions.forEach(p => permissions.add(p)); - } - - return Array.from(permissions); - } - - /** - * 获取角色的所有权限(包括继承的权限) - */ - async getRolePermissions(roleId: string): Promise { - const permissions = new Set(); - const visited = new Set(); - - const collectPermissions = (currentRoleId: string) => { - if (visited.has(currentRoleId)) { - return; // 防止循环引用 - } - visited.add(currentRoleId); - - const role = this.roles.get(currentRoleId); - if (!role) { - return; - } - - // 添加当前角色的权限 - role.permissions.forEach(p => permissions.add(p)); - - // 递归添加父角色的权限 - if (this.config.enableInheritance && role.parentRoleId) { - collectPermissions(role.parentRoleId); - } - }; - - collectPermissions(roleId); - return Array.from(permissions); - } - - /** - * 为角色添加权限 - */ - async addPermissionToRole(roleId: string, permission: Permission): Promise { - const role = this.roles.get(roleId); - if (!role) { - return false; - } - - if (!role.permissions.includes(permission)) { - role.permissions.push(permission); - this.clearPermissionCache(); - console.log(`Permission "${permission}" added to role "${roleId}"`); - } - - return true; - } - - /** - * 从角色移除权限 - */ - async removePermissionFromRole(roleId: string, permission: Permission): Promise { - const role = this.roles.get(roleId); - if (!role) { - return false; - } - - const index = role.permissions.indexOf(permission); - if (index !== -1) { - role.permissions.splice(index, 1); - this.clearPermissionCache(); - console.log(`Permission "${permission}" removed from role "${roleId}"`); - } - - return true; - } - - /** - * 检查用户是否有指定角色 - */ - hasRole(user: UserInfo, roleId: string): boolean { - return user.roles.includes(roleId); - } - - /** - * 为用户添加角色 - */ - async addRoleToUser(user: UserInfo, roleId: string): Promise { - if (!this.roles.has(roleId)) { - return false; - } - - if (!user.roles.includes(roleId)) { - user.roles.push(roleId); - this.clearUserPermissionCache(user.id); - console.log(`Role "${roleId}" added to user "${user.id}"`); - } - - return true; - } - - /** - * 从用户移除角色 - */ - async removeRoleFromUser(user: UserInfo, roleId: string): Promise { - const index = user.roles.indexOf(roleId); - if (index !== -1) { - user.roles.splice(index, 1); - this.clearUserPermissionCache(user.id); - console.log(`Role "${roleId}" removed from user "${user.id}"`); - return true; - } - - return false; - } - - /** - * 清除权限缓存 - */ - clearPermissionCache(): void { - this.permissionCache.clear(); - } - - /** - * 清除指定用户的权限缓存 - */ - clearUserPermissionCache(userId: string): void { - const keysToDelete: string[] = []; - - for (const [key] of this.permissionCache) { - if (key.startsWith(`${userId}:`)) { - keysToDelete.push(key); - } - } - - keysToDelete.forEach(key => this.permissionCache.delete(key)); - } - - /** - * 销毁权限管理器 - */ - destroy(): void { - if (this.cleanupTimer) { - clearInterval(this.cleanupTimer); - this.cleanupTimer = null; - } - - this.roles.clear(); - this.permissionCache.clear(); - this.removeAllListeners(); - } - - /** - * 初始化 - */ - private initialize(): void { - // 创建系统角色 - this.createSystemRoles(); - - // 启动缓存清理定时器(每30分钟清理一次) - if (this.config.enableCache) { - this.cleanupTimer = setInterval(() => { - this.cleanupCache(); - }, 30 * 60 * 1000); - } - } - - /** - * 创建系统角色 - */ - private createSystemRoles(): void { - // 管理员角色 - const adminRole: Role = { - id: SystemRoles.ADMIN, - name: 'Administrator', - description: 'Full system access', - permissions: Object.values(Permissions), - isSystemRole: true, - metadata: {}, - createdAt: new Date() - }; - - // 版主角色 - const moderatorRole: Role = { - id: SystemRoles.MODERATOR, - name: 'Moderator', - description: 'Room and user management', - permissions: [ - Permissions.USER_READ, - Permissions.ROOM_CREATE, - Permissions.ROOM_JOIN, - Permissions.ROOM_MANAGE, - Permissions.ROOM_KICK_PLAYERS, - Permissions.NETWORK_SEND_RPC, - Permissions.NETWORK_SYNC_VARS, - Permissions.CHAT_SEND, - Permissions.CHAT_MODERATE, - Permissions.CHAT_PRIVATE - ], - parentRoleId: SystemRoles.USER, - isSystemRole: true, - metadata: {}, - createdAt: new Date() - }; - - // 普通用户角色 - const userRole: Role = { - id: SystemRoles.USER, - name: 'User', - description: 'Basic user permissions', - permissions: [ - Permissions.ROOM_JOIN, - Permissions.ROOM_LEAVE, - Permissions.NETWORK_SEND_RPC, - Permissions.NETWORK_SYNC_VARS, - Permissions.CHAT_SEND, - Permissions.FILE_DOWNLOAD - ], - parentRoleId: SystemRoles.GUEST, - isSystemRole: true, - metadata: {}, - createdAt: new Date() - }; - - // 访客角色 - const guestRole: Role = { - id: SystemRoles.GUEST, - name: 'Guest', - description: 'Limited access for guests', - permissions: [ - Permissions.ROOM_JOIN - ], - isSystemRole: true, - metadata: {}, - createdAt: new Date() - }; - - this.roles.set(adminRole.id, adminRole); - this.roles.set(moderatorRole.id, moderatorRole); - this.roles.set(userRole.id, userRole); - this.roles.set(guestRole.id, guestRole); - - console.log('System roles created'); - } - - /** - * 执行权限检查 - */ - private async performPermissionCheck(context: PermissionContext): Promise { - // 获取用户的所有角色权限 - const userPermissions = new Set(); - - for (const roleId of context.userRoles) { - const rolePermissions = await this.getRolePermissions(roleId); - rolePermissions.forEach(p => userPermissions.add(p)); - } - - // 直接权限匹配 - if (userPermissions.has(context.permission)) { - return { - granted: true, - reason: 'Direct permission match', - usedPermission: context.permission - }; - } - - // 通配符权限匹配 - const wildcardPermissions = Array.from(userPermissions) - .filter(p => p.endsWith('*')); - - for (const wildcardPerm of wildcardPermissions) { - const prefix = wildcardPerm.slice(0, -1); - if (context.permission.startsWith(prefix)) { - return { - granted: true, - reason: 'Wildcard permission match', - usedPermission: wildcardPerm - }; - } - } - - // 如果没有匹配的权限 - return { - granted: this.config.defaultPolicy === 'allow', - reason: this.config.defaultPolicy === 'allow' - ? 'Default allow policy' - : 'No matching permissions found' - }; - } - - /** - * 获取缓存键 - */ - private getCacheKey(context: PermissionContext): string { - const roleString = context.userRoles.sort().join(','); - const resourcePart = context.resourceId ? `:${context.resourceId}` : ''; - return `${context.userId}:${roleString}:${context.permission}${resourcePart}`; - } - - /** - * 清理过期缓存 - */ - private cleanupCache(): void { - const now = new Date(); - let cleanedCount = 0; - - for (const [key, item] of this.permissionCache.entries()) { - if (item.expiresAt < now) { - this.permissionCache.delete(key); - cleanedCount++; - } - } - - if (cleanedCount > 0) { - console.log(`Permission cache cleanup: ${cleanedCount} entries removed`); - } - } - - /** - * 类型安全的事件监听 - */ - override on(event: K, listener: AuthorizationEvents[K]): this { - return super.on(event, listener); - } - - /** - * 类型安全的事件触发 - */ - override emit(event: K, ...args: Parameters): boolean { - return super.emit(event, ...args); - } -} \ No newline at end of file diff --git a/packages/network-server/src/auth/index.ts b/packages/network-server/src/auth/index.ts deleted file mode 100644 index cb42810a..00000000 --- a/packages/network-server/src/auth/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * 认证系统导出 - */ - -export * from './AuthenticationManager'; -export * from './AuthorizationManager'; \ No newline at end of file diff --git a/packages/network-server/src/core/ClientConnection.ts b/packages/network-server/src/core/ClientConnection.ts deleted file mode 100644 index f069765a..00000000 --- a/packages/network-server/src/core/ClientConnection.ts +++ /dev/null @@ -1,478 +0,0 @@ -/** - * 客户端连接管理 - */ - -import { EventEmitter } from 'events'; -import { NetworkValue, NetworkMessage } from '@esengine/ecs-framework-network-shared'; -import { TransportMessage } from './Transport'; - -/** - * 客户端连接状态 - */ -export enum ClientConnectionState { - /** 连接中 */ - CONNECTING = 'connecting', - /** 已连接 */ - CONNECTED = 'connected', - /** 认证中 */ - AUTHENTICATING = 'authenticating', - /** 已认证 */ - AUTHENTICATED = 'authenticated', - /** 断开连接中 */ - DISCONNECTING = 'disconnecting', - /** 已断开 */ - DISCONNECTED = 'disconnected', - /** 错误状态 */ - ERROR = 'error' -} - -/** - * 客户端权限 - */ -export interface ClientPermissions { - /** 是否可以加入房间 */ - canJoinRooms?: boolean; - /** 是否可以创建房间 */ - canCreateRooms?: boolean; - /** 是否可以发送RPC */ - canSendRpc?: boolean; - /** 是否可以同步变量 */ - canSyncVars?: boolean; - /** 自定义权限 */ - customPermissions?: Record; -} - -/** - * 客户端连接事件 - */ -export interface ClientConnectionEvents { - /** 状态变化 */ - 'state-changed': (oldState: ClientConnectionState, newState: ClientConnectionState) => void; - /** 收到消息 */ - 'message': (message: TransportMessage) => void; - /** 连接错误 */ - 'error': (error: Error) => void; - /** 连接超时 */ - 'timeout': () => void; - /** 身份验证成功 */ - 'authenticated': (userData: Record) => void; - /** 身份验证失败 */ - 'authentication-failed': (reason: string) => void; -} - -/** - * 客户端统计信息 - */ -export interface ClientStats { - /** 消息发送数 */ - messagesSent: number; - /** 消息接收数 */ - messagesReceived: number; - /** 字节发送数 */ - bytesSent: number; - /** 字节接收数 */ - bytesReceived: number; - /** 最后活跃时间 */ - lastActivity: Date; - /** 连接时长(毫秒) */ - connectionDuration: number; -} - -/** - * 客户端连接管理类 - */ -export class ClientConnection extends EventEmitter { - /** 连接ID */ - public readonly id: string; - - /** 客户端IP地址 */ - public readonly remoteAddress: string; - - /** 连接创建时间 */ - public readonly connectedAt: Date; - - /** 当前状态 */ - private _state: ClientConnectionState = ClientConnectionState.CONNECTING; - - /** 用户数据 */ - private _userData: Record = {}; - - /** 权限信息 */ - private _permissions: ClientPermissions = {}; - - /** 所在房间ID */ - private _currentRoomId: string | null = null; - - /** 统计信息 */ - private _stats: ClientStats; - - /** 最后活跃时间 */ - private _lastActivity: Date; - - /** 超时定时器 */ - private _timeoutTimer: NodeJS.Timeout | null = null; - - /** 连接超时时间(毫秒) */ - private _connectionTimeout: number; - - /** 发送消息回调 */ - private _sendMessageCallback: (message: TransportMessage) => Promise; - - constructor( - id: string, - remoteAddress: string, - sendMessageCallback: (message: TransportMessage) => Promise, - options: { - connectionTimeout?: number; - userData?: Record; - permissions?: ClientPermissions; - } = {} - ) { - super(); - - this.id = id; - this.remoteAddress = remoteAddress; - this.connectedAt = new Date(); - this._lastActivity = new Date(); - this._connectionTimeout = options.connectionTimeout || 60000; // 1分钟 - this._sendMessageCallback = sendMessageCallback; - - if (options.userData) { - this._userData = { ...options.userData }; - } - - if (options.permissions) { - this._permissions = { ...options.permissions }; - } - - this._stats = { - messagesSent: 0, - messagesReceived: 0, - bytesSent: 0, - bytesReceived: 0, - lastActivity: this._lastActivity, - connectionDuration: 0 - }; - - this.setState(ClientConnectionState.CONNECTED); - this.startTimeout(); - } - - /** - * 获取当前状态 - */ - get state(): ClientConnectionState { - return this._state; - } - - /** - * 获取用户数据 - */ - get userData(): Readonly> { - return this._userData; - } - - /** - * 获取权限信息 - */ - get permissions(): Readonly { - return this._permissions; - } - - /** - * 获取当前房间ID - */ - get currentRoomId(): string | null { - return this._currentRoomId; - } - - /** - * 获取统计信息 - */ - get stats(): Readonly { - this._stats.connectionDuration = Date.now() - this.connectedAt.getTime(); - this._stats.lastActivity = this._lastActivity; - return this._stats; - } - - /** - * 获取最后活跃时间 - */ - get lastActivity(): Date { - return this._lastActivity; - } - - /** - * 是否已连接 - */ - get isConnected(): boolean { - return this._state === ClientConnectionState.CONNECTED || - this._state === ClientConnectionState.AUTHENTICATED; - } - - /** - * 是否已认证 - */ - get isAuthenticated(): boolean { - return this._state === ClientConnectionState.AUTHENTICATED; - } - - /** - * 发送消息 - */ - async sendMessage(message: TransportMessage): Promise { - if (!this.isConnected) { - return false; - } - - try { - const success = await this._sendMessageCallback(message); - if (success) { - this._stats.messagesSent++; - const messageSize = JSON.stringify(message).length; - this._stats.bytesSent += messageSize; - this.updateActivity(); - } - return success; - } catch (error) { - this.handleError(error as Error); - return false; - } - } - - /** - * 处理接收到的消息 - */ - handleMessage(message: TransportMessage): void { - if (!this.isConnected) { - return; - } - - this._stats.messagesReceived++; - const messageSize = JSON.stringify(message).length; - this._stats.bytesReceived += messageSize; - this.updateActivity(); - - this.emit('message', message); - } - - /** - * 设置用户数据 - */ - setUserData(key: string, value: NetworkValue): void { - this._userData[key] = value; - } - - /** - * 获取用户数据 - */ - getUserData(key: string): T | undefined { - return this._userData[key] as T; - } - - /** - * 批量设置用户数据 - */ - setUserDataBatch(data: Record): void { - Object.assign(this._userData, data); - } - - /** - * 设置权限 - */ - setPermission(permission: keyof ClientPermissions, value: boolean): void { - (this._permissions as any)[permission] = value; - } - - /** - * 检查权限 - */ - hasPermission(permission: keyof ClientPermissions): boolean { - return (this._permissions as any)[permission] || false; - } - - /** - * 设置自定义权限 - */ - setCustomPermission(permission: string, value: boolean): void { - if (!this._permissions.customPermissions) { - this._permissions.customPermissions = {}; - } - this._permissions.customPermissions[permission] = value; - } - - /** - * 检查自定义权限 - */ - hasCustomPermission(permission: string): boolean { - return this._permissions.customPermissions?.[permission] || false; - } - - /** - * 进行身份认证 - */ - async authenticate(credentials: Record): Promise { - if (this._state !== ClientConnectionState.CONNECTED) { - return false; - } - - this.setState(ClientConnectionState.AUTHENTICATING); - - try { - // 这里可以添加实际的认证逻辑 - // 目前简单地认为所有认证都成功 - - this.setUserDataBatch(credentials); - this.setState(ClientConnectionState.AUTHENTICATED); - this.emit('authenticated', credentials); - - return true; - } catch (error) { - this.setState(ClientConnectionState.CONNECTED); - this.emit('authentication-failed', (error as Error).message); - return false; - } - } - - /** - * 加入房间 - */ - joinRoom(roomId: string): void { - this._currentRoomId = roomId; - } - - /** - * 离开房间 - */ - leaveRoom(): void { - this._currentRoomId = null; - } - - /** - * 断开连接 - */ - disconnect(reason?: string): void { - if (this._state === ClientConnectionState.DISCONNECTED) { - return; - } - - this.setState(ClientConnectionState.DISCONNECTING); - this.stopTimeout(); - - // 发送断开连接消息 - this.sendMessage({ - type: 'system', - data: { - action: 'disconnect', - reason: reason || 'server-disconnect' - } - }).finally(() => { - this.setState(ClientConnectionState.DISCONNECTED); - }); - } - - /** - * 更新活跃时间 - */ - updateActivity(): void { - this._lastActivity = new Date(); - this.resetTimeout(); - } - - /** - * 设置连接状态 - */ - private setState(newState: ClientConnectionState): void { - const oldState = this._state; - if (oldState !== newState) { - this._state = newState; - this.emit('state-changed', oldState, newState); - } - } - - /** - * 处理错误 - */ - private handleError(error: Error): void { - this.setState(ClientConnectionState.ERROR); - this.emit('error', error); - } - - /** - * 启动超时检测 - */ - private startTimeout(): void { - this.resetTimeout(); - } - - /** - * 重置超时定时器 - */ - private resetTimeout(): void { - this.stopTimeout(); - - if (this._connectionTimeout > 0) { - this._timeoutTimer = setTimeout(() => { - this.handleTimeout(); - }, this._connectionTimeout); - } - } - - /** - * 停止超时检测 - */ - private stopTimeout(): void { - if (this._timeoutTimer) { - clearTimeout(this._timeoutTimer); - this._timeoutTimer = null; - } - } - - /** - * 处理超时 - */ - private handleTimeout(): void { - this.emit('timeout'); - this.disconnect('timeout'); - } - - /** - * 销毁连接 - */ - destroy(): void { - this.stopTimeout(); - this.removeAllListeners(); - this.setState(ClientConnectionState.DISCONNECTED); - } - - /** - * 类型安全的事件监听 - */ - override on(event: K, listener: ClientConnectionEvents[K]): this { - return super.on(event, listener); - } - - /** - * 类型安全的事件触发 - */ - override emit(event: K, ...args: Parameters): boolean { - return super.emit(event, ...args); - } - - /** - * 序列化连接信息 - */ - toJSON(): object { - return { - id: this.id, - remoteAddress: this.remoteAddress, - state: this._state, - connectedAt: this.connectedAt.toISOString(), - lastActivity: this._lastActivity.toISOString(), - currentRoomId: this._currentRoomId, - userData: this._userData, - permissions: this._permissions, - stats: this.stats - }; - } -} \ No newline at end of file diff --git a/packages/network-server/src/core/HttpTransport.ts b/packages/network-server/src/core/HttpTransport.ts deleted file mode 100644 index cefe2ada..00000000 --- a/packages/network-server/src/core/HttpTransport.ts +++ /dev/null @@ -1,602 +0,0 @@ -/** - * HTTP 传输层实现 - * - * 用于处理 REST API 请求和长轮询连接 - */ - -import { createServer, IncomingMessage, ServerResponse, Server as HttpServer } from 'http'; -import { parse as parseUrl } from 'url'; -import { v4 as uuidv4 } from 'uuid'; -import { Transport, TransportConfig, ClientConnectionInfo, TransportMessage } from './Transport'; - -/** - * HTTP 传输配置 - */ -export interface HttpTransportConfig extends TransportConfig { - /** API 路径前缀 */ - apiPrefix?: string; - /** 最大请求大小(字节) */ - maxRequestSize?: number; - /** 长轮询超时(毫秒) */ - longPollTimeout?: number; - /** 是否启用 CORS */ - enableCors?: boolean; - /** 允许的域名 */ - corsOrigins?: string[]; -} - -/** - * HTTP 请求上下文 - */ -interface HttpRequestContext { - /** 请求ID */ - id: string; - /** HTTP 请求 */ - request: IncomingMessage; - /** HTTP 响应 */ - response: ServerResponse; - /** 解析后的URL */ - parsedUrl: any; - /** 请求体数据 */ - body?: string; - /** 查询参数 */ - query: Record; -} - -/** - * HTTP 客户端连接信息(用于长轮询) - */ -interface HttpConnectionInfo extends ClientConnectionInfo { - /** 长轮询响应对象 */ - longPollResponse?: ServerResponse; - /** 消息队列 */ - messageQueue: TransportMessage[]; - /** 长轮询超时定时器 */ - longPollTimer?: NodeJS.Timeout; -} - -/** - * HTTP 传输层实现 - */ -export class HttpTransport extends Transport { - private httpServer: HttpServer | null = null; - private httpConnections = new Map(); - - protected override config: HttpTransportConfig; - - constructor(config: HttpTransportConfig) { - super(config); - this.config = { - apiPrefix: '/api', - maxRequestSize: 1024 * 1024, // 1MB - longPollTimeout: 30000, // 30秒 - enableCors: true, - corsOrigins: ['*'], - heartbeatInterval: 60000, - connectionTimeout: 120000, - maxConnections: 1000, - ...config - }; - } - - /** - * 启动 HTTP 服务器 - */ - async start(): Promise { - if (this.isRunning) { - throw new Error('HTTP transport is already running'); - } - - try { - this.httpServer = createServer((req, res) => { - this.handleHttpRequest(req, res); - }); - - this.httpServer.on('error', (error: Error) => { - this.handleError(error); - }); - - await new Promise((resolve, reject) => { - this.httpServer!.listen(this.config.port, this.config.host, (error?: Error) => { - if (error) { - reject(error); - } else { - this.isRunning = true; - resolve(); - } - }); - }); - - this.emit('server-started', this.config); - } catch (error) { - await this.cleanup(); - throw error; - } - } - - /** - * 停止 HTTP 服务器 - */ - async stop(): Promise { - if (!this.isRunning) { - return; - } - - this.isRunning = false; - - // 断开所有长轮询连接 - for (const [connectionId] of this.httpConnections) { - this.disconnectClient(connectionId, 'server-shutdown'); - } - - await this.cleanup(); - this.emit('server-stopped'); - } - - /** - * 发送消息给指定客户端 - */ - async sendToClient(connectionId: string, message: TransportMessage): Promise { - const connection = this.httpConnections.get(connectionId); - if (!connection) { - return false; - } - - // 如果有长轮询连接,直接发送 - if (connection.longPollResponse && !connection.longPollResponse.headersSent) { - this.sendLongPollResponse(connection, [message]); - return true; - } - - // 否则加入消息队列 - connection.messageQueue.push(message); - return true; - } - - /** - * 广播消息给所有客户端 - */ - async broadcast(message: TransportMessage, excludeId?: string): Promise { - let sentCount = 0; - - for (const [connectionId, connection] of this.httpConnections) { - if (excludeId && connectionId === excludeId) { - continue; - } - - if (await this.sendToClient(connectionId, message)) { - sentCount++; - } - } - - return sentCount; - } - - /** - * 发送消息给指定客户端列表 - */ - async sendToClients(connectionIds: string[], message: TransportMessage): Promise { - let sentCount = 0; - - for (const connectionId of connectionIds) { - if (await this.sendToClient(connectionId, message)) { - sentCount++; - } - } - - return sentCount; - } - - /** - * 断开指定客户端连接 - */ - async disconnectClient(connectionId: string, reason?: string): Promise { - const connection = this.httpConnections.get(connectionId); - if (connection) { - this.cleanupConnection(connectionId); - this.removeConnection(connectionId, reason); - } - } - - /** - * 处理 HTTP 请求 - */ - private async handleHttpRequest(req: IncomingMessage, res: ServerResponse): Promise { - try { - // 设置 CORS 头 - if (this.config.enableCors) { - this.setCorsHeaders(res); - } - - // 处理 OPTIONS 请求 - if (req.method === 'OPTIONS') { - res.writeHead(200); - res.end(); - return; - } - - const parsedUrl = parseUrl(req.url || '', true); - const pathname = parsedUrl.pathname || ''; - - // 检查是否为 API 请求 - if (!pathname.startsWith(this.config.apiPrefix!)) { - this.sendErrorResponse(res, 404, 'Not Found'); - return; - } - - const context: HttpRequestContext = { - id: uuidv4(), - request: req, - response: res, - parsedUrl, - query: parsedUrl.query as Record, - }; - - // 读取请求体 - if (req.method === 'POST' || req.method === 'PUT') { - context.body = await this.readRequestBody(req); - } - - // 路由处理 - const apiPath = pathname.substring(this.config.apiPrefix!.length); - await this.routeApiRequest(context, apiPath); - - } catch (error) { - this.handleError(error as Error); - this.sendErrorResponse(res, 500, 'Internal Server Error'); - } - } - - /** - * API 路由处理 - */ - private async routeApiRequest(context: HttpRequestContext, apiPath: string): Promise { - const { request, response } = context; - - switch (apiPath) { - case '/connect': - if (request.method === 'POST') { - await this.handleConnect(context); - } else { - this.sendErrorResponse(response, 405, 'Method Not Allowed'); - } - break; - - case '/disconnect': - if (request.method === 'POST') { - await this.handleDisconnect(context); - } else { - this.sendErrorResponse(response, 405, 'Method Not Allowed'); - } - break; - - case '/poll': - if (request.method === 'GET') { - await this.handleLongPoll(context); - } else { - this.sendErrorResponse(response, 405, 'Method Not Allowed'); - } - break; - - case '/send': - if (request.method === 'POST') { - await this.handleSendMessage(context); - } else { - this.sendErrorResponse(response, 405, 'Method Not Allowed'); - } - break; - - case '/status': - if (request.method === 'GET') { - await this.handleStatus(context); - } else { - this.sendErrorResponse(response, 405, 'Method Not Allowed'); - } - break; - - default: - this.sendErrorResponse(response, 404, 'API endpoint not found'); - break; - } - } - - /** - * 处理连接请求 - */ - private async handleConnect(context: HttpRequestContext): Promise { - const { request, response } = context; - - try { - // 检查连接数限制 - if (this.config.maxConnections && this.httpConnections.size >= this.config.maxConnections) { - this.sendErrorResponse(response, 429, 'Too many connections'); - return; - } - - const connectionId = uuidv4(); - const remoteAddress = request.socket.remoteAddress || request.headers['x-forwarded-for'] || 'unknown'; - - const connectionInfo: HttpConnectionInfo = { - id: connectionId, - remoteAddress: Array.isArray(remoteAddress) ? remoteAddress[0] : remoteAddress, - connectedAt: new Date(), - lastActivity: new Date(), - userData: {}, - messageQueue: [] - }; - - this.httpConnections.set(connectionId, connectionInfo); - this.addConnection(connectionInfo); - - this.sendJsonResponse(response, 200, { - success: true, - connectionId, - serverTime: Date.now() - }); - - } catch (error) { - this.handleError(error as Error); - this.sendErrorResponse(response, 500, 'Failed to create connection'); - } - } - - /** - * 处理断开连接请求 - */ - private async handleDisconnect(context: HttpRequestContext): Promise { - const { response, query } = context; - - const connectionId = query.connectionId; - if (!connectionId) { - this.sendErrorResponse(response, 400, 'Missing connectionId'); - return; - } - - await this.disconnectClient(connectionId, 'client-disconnect'); - - this.sendJsonResponse(response, 200, { - success: true, - message: 'Disconnected successfully' - }); - } - - /** - * 处理长轮询请求 - */ - private async handleLongPoll(context: HttpRequestContext): Promise { - const { response, query } = context; - - const connectionId = query.connectionId; - if (!connectionId) { - this.sendErrorResponse(response, 400, 'Missing connectionId'); - return; - } - - const connection = this.httpConnections.get(connectionId); - if (!connection) { - this.sendErrorResponse(response, 404, 'Connection not found'); - return; - } - - this.updateClientActivity(connectionId); - - // 如果有排队的消息,立即返回 - if (connection.messageQueue.length > 0) { - const messages = connection.messageQueue.splice(0); - this.sendLongPollResponse(connection, messages); - return; - } - - // 设置长轮询 - connection.longPollResponse = response; - - // 设置超时 - connection.longPollTimer = setTimeout(() => { - this.sendLongPollResponse(connection, []); - }, this.config.longPollTimeout); - } - - /** - * 处理发送消息请求 - */ - private async handleSendMessage(context: HttpRequestContext): Promise { - const { response, query, body } = context; - - const connectionId = query.connectionId; - if (!connectionId) { - this.sendErrorResponse(response, 400, 'Missing connectionId'); - return; - } - - const connection = this.httpConnections.get(connectionId); - if (!connection) { - this.sendErrorResponse(response, 404, 'Connection not found'); - return; - } - - if (!body) { - this.sendErrorResponse(response, 400, 'Missing message body'); - return; - } - - try { - const message = JSON.parse(body) as TransportMessage; - message.senderId = connectionId; - - this.handleMessage(connectionId, message); - - this.sendJsonResponse(response, 200, { - success: true, - message: 'Message sent successfully' - }); - - } catch (error) { - this.sendErrorResponse(response, 400, 'Invalid message format'); - } - } - - /** - * 处理状态请求 - */ - private async handleStatus(context: HttpRequestContext): Promise { - const { response } = context; - - this.sendJsonResponse(response, 200, { - success: true, - status: 'running', - connections: this.httpConnections.size, - uptime: process.uptime(), - serverTime: Date.now() - }); - } - - /** - * 读取请求体 - */ - private readRequestBody(req: IncomingMessage): Promise { - return new Promise((resolve, reject) => { - let body = ''; - let totalSize = 0; - - req.on('data', (chunk: Buffer) => { - totalSize += chunk.length; - if (totalSize > this.config.maxRequestSize!) { - reject(new Error('Request body too large')); - return; - } - body += chunk.toString(); - }); - - req.on('end', () => { - resolve(body); - }); - - req.on('error', (error) => { - reject(error); - }); - }); - } - - /** - * 发送长轮询响应 - */ - private sendLongPollResponse(connection: HttpConnectionInfo, messages: TransportMessage[]): void { - if (!connection.longPollResponse || connection.longPollResponse.headersSent) { - return; - } - - // 清理定时器 - if (connection.longPollTimer) { - clearTimeout(connection.longPollTimer); - connection.longPollTimer = undefined; - } - - this.sendJsonResponse(connection.longPollResponse, 200, { - success: true, - messages - }); - - connection.longPollResponse = undefined; - } - - /** - * 设置 CORS 头 - */ - private setCorsHeaders(res: ServerResponse): void { - const origins = this.config.corsOrigins!; - const origin = origins.includes('*') ? '*' : origins[0]; - - res.setHeader('Access-Control-Allow-Origin', origin); - res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); - res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); - res.setHeader('Access-Control-Max-Age', '86400'); - } - - /** - * 发送 JSON 响应 - */ - private sendJsonResponse(res: ServerResponse, statusCode: number, data: any): void { - if (res.headersSent) return; - - res.setHeader('Content-Type', 'application/json'); - res.writeHead(statusCode); - res.end(JSON.stringify(data)); - } - - /** - * 发送错误响应 - */ - private sendErrorResponse(res: ServerResponse, statusCode: number, message: string): void { - if (res.headersSent) return; - - this.sendJsonResponse(res, statusCode, { - success: false, - error: message, - code: statusCode - }); - } - - /** - * 清理连接资源 - */ - private cleanupConnection(connectionId: string): void { - const connection = this.httpConnections.get(connectionId); - if (connection) { - if (connection.longPollTimer) { - clearTimeout(connection.longPollTimer); - } - if (connection.longPollResponse && !connection.longPollResponse.headersSent) { - this.sendJsonResponse(connection.longPollResponse, 200, { - success: true, - messages: [], - disconnected: true - }); - } - this.httpConnections.delete(connectionId); - } - } - - /** - * 清理所有资源 - */ - private async cleanup(): Promise { - // 清理所有连接 - for (const connectionId of this.httpConnections.keys()) { - this.cleanupConnection(connectionId); - } - this.clearConnections(); - - // 关闭 HTTP 服务器 - if (this.httpServer) { - await new Promise((resolve) => { - this.httpServer!.close(() => resolve()); - }); - this.httpServer = null; - } - } - - /** - * 获取 HTTP 连接统计信息 - */ - getHttpStats(): { - totalConnections: number; - activeLongPolls: number; - queuedMessages: number; - } { - let activeLongPolls = 0; - let queuedMessages = 0; - - for (const connection of this.httpConnections.values()) { - if (connection.longPollResponse && !connection.longPollResponse.headersSent) { - activeLongPolls++; - } - queuedMessages += connection.messageQueue.length; - } - - return { - totalConnections: this.httpConnections.size, - activeLongPolls, - queuedMessages - }; - } -} \ No newline at end of file diff --git a/packages/network-server/src/core/NetworkServer.ts b/packages/network-server/src/core/NetworkServer.ts deleted file mode 100644 index a180a6d1..00000000 --- a/packages/network-server/src/core/NetworkServer.ts +++ /dev/null @@ -1,452 +0,0 @@ -/** - * 网络服务器主类 - * - * 整合 WebSocket 和 HTTP 传输,提供统一的网络服务接口 - */ - -import { EventEmitter } from 'events'; -import { Transport, TransportConfig, TransportMessage } from './Transport'; -import { WebSocketTransport, WebSocketTransportConfig } from './WebSocketTransport'; -import { HttpTransport, HttpTransportConfig } from './HttpTransport'; -import { ClientConnection, ClientConnectionState, ClientPermissions } from './ClientConnection'; -import { NetworkValue } from '@esengine/ecs-framework-network-shared'; - -/** - * 网络服务器配置 - */ -export interface NetworkServerConfig { - /** 服务器名称 */ - name?: string; - /** WebSocket 配置 */ - websocket?: WebSocketTransportConfig; - /** HTTP 配置 */ - http?: HttpTransportConfig; - /** 默认客户端权限 */ - defaultPermissions?: ClientPermissions; - /** 最大客户端连接数 */ - maxConnections?: number; - /** 客户端认证超时(毫秒) */ - authenticationTimeout?: number; - /** 是否启用统计 */ - enableStats?: boolean; -} - -/** - * 服务器统计信息 - */ -export interface ServerStats { - /** 总连接数 */ - totalConnections: number; - /** 当前活跃连接数 */ - activeConnections: number; - /** 已认证连接数 */ - authenticatedConnections: number; - /** 消息总数 */ - totalMessages: number; - /** 错误总数 */ - totalErrors: number; - /** 服务器启动时间 */ - startTime: Date; - /** 服务器运行时间(毫秒) */ - uptime: number; -} - -/** - * 网络服务器事件 - */ -export interface NetworkServerEvents { - /** 服务器启动 */ - 'server-started': () => void; - /** 服务器停止 */ - 'server-stopped': () => void; - /** 客户端连接 */ - 'client-connected': (client: ClientConnection) => void; - /** 客户端断开连接 */ - 'client-disconnected': (clientId: string, reason?: string) => void; - /** 客户端认证成功 */ - 'client-authenticated': (client: ClientConnection) => void; - /** 收到消息 */ - 'message': (client: ClientConnection, message: TransportMessage) => void; - /** 服务器错误 */ - 'error': (error: Error, clientId?: string) => void; -} - -/** - * 网络服务器主类 - */ -export class NetworkServer extends EventEmitter { - private config: NetworkServerConfig; - private wsTransport: WebSocketTransport | null = null; - private httpTransport: HttpTransport | null = null; - private clients = new Map(); - private isRunning = false; - private stats: ServerStats; - - constructor(config: NetworkServerConfig) { - super(); - - this.config = { - name: 'NetworkServer', - maxConnections: 1000, - authenticationTimeout: 30000, // 30秒 - enableStats: true, - defaultPermissions: { - canJoinRooms: true, - canCreateRooms: false, - canSendRpc: true, - canSyncVars: true - }, - ...config - }; - - this.stats = { - totalConnections: 0, - activeConnections: 0, - authenticatedConnections: 0, - totalMessages: 0, - totalErrors: 0, - startTime: new Date(), - uptime: 0 - }; - - this.initialize(); - } - - /** - * 启动服务器 - */ - async start(): Promise { - if (this.isRunning) { - throw new Error('Server is already running'); - } - - try { - const promises: Promise[] = []; - - // 启动 WebSocket 传输 - if (this.config.websocket && this.wsTransport) { - promises.push(this.wsTransport.start()); - } - - // 启动 HTTP 传输 - if (this.config.http && this.httpTransport) { - promises.push(this.httpTransport.start()); - } - - if (promises.length === 0) { - throw new Error('No transport configured. Please configure at least one transport (WebSocket or HTTP)'); - } - - await Promise.all(promises); - - this.isRunning = true; - this.stats.startTime = new Date(); - - console.log(`Network Server "${this.config.name}" started successfully`); - if (this.config.websocket) { - console.log(`- WebSocket: ws://${this.config.websocket.host || 'localhost'}:${this.config.websocket.port}${this.config.websocket.path || '/ws'}`); - } - if (this.config.http) { - console.log(`- HTTP: http://${this.config.http.host || 'localhost'}:${this.config.http.port}${this.config.http.apiPrefix || '/api'}`); - } - - this.emit('server-started'); - - } catch (error) { - await this.stop(); - throw error; - } - } - - /** - * 停止服务器 - */ - async stop(): Promise { - if (!this.isRunning) { - return; - } - - this.isRunning = false; - - // 断开所有客户端 - const clients = Array.from(this.clients.values()); - for (const client of clients) { - client.disconnect('server-shutdown'); - } - - // 停止传输层 - const promises: Promise[] = []; - - if (this.wsTransport) { - promises.push(this.wsTransport.stop()); - } - - if (this.httpTransport) { - promises.push(this.httpTransport.stop()); - } - - await Promise.all(promises); - - console.log(`Network Server "${this.config.name}" stopped`); - this.emit('server-stopped'); - } - - /** - * 获取服务器配置 - */ - getConfig(): Readonly { - return this.config; - } - - /** - * 获取服务器统计信息 - */ - getStats(): ServerStats { - this.stats.uptime = Date.now() - this.stats.startTime.getTime(); - this.stats.activeConnections = this.clients.size; - this.stats.authenticatedConnections = Array.from(this.clients.values()) - .filter(client => client.isAuthenticated).length; - - return { ...this.stats }; - } - - /** - * 获取所有客户端连接 - */ - getClients(): ClientConnection[] { - return Array.from(this.clients.values()); - } - - /** - * 获取指定客户端连接 - */ - getClient(clientId: string): ClientConnection | undefined { - return this.clients.get(clientId); - } - - /** - * 检查客户端是否存在 - */ - hasClient(clientId: string): boolean { - return this.clients.has(clientId); - } - - /** - * 获取客户端数量 - */ - getClientCount(): number { - return this.clients.size; - } - - /** - * 发送消息给指定客户端 - */ - async sendToClient(clientId: string, message: TransportMessage): Promise { - const client = this.clients.get(clientId); - if (!client) { - return false; - } - - return await client.sendMessage(message); - } - - /** - * 广播消息给所有客户端 - */ - async broadcast(message: TransportMessage, excludeId?: string): Promise { - const promises = Array.from(this.clients.entries()) - .filter(([clientId]) => clientId !== excludeId) - .map(([, client]) => client.sendMessage(message)); - - const results = await Promise.allSettled(promises); - return results.filter(result => result.status === 'fulfilled' && result.value).length; - } - - /** - * 发送消息给指定房间的所有客户端 - */ - async broadcastToRoom(roomId: string, message: TransportMessage, excludeId?: string): Promise { - const roomClients = Array.from(this.clients.values()) - .filter(client => client.currentRoomId === roomId && client.id !== excludeId); - - const promises = roomClients.map(client => client.sendMessage(message)); - const results = await Promise.allSettled(promises); - - return results.filter(result => result.status === 'fulfilled' && result.value).length; - } - - /** - * 断开指定客户端连接 - */ - async disconnectClient(clientId: string, reason?: string): Promise { - const client = this.clients.get(clientId); - if (client) { - client.disconnect(reason); - } - } - - /** - * 获取在指定房间的客户端列表 - */ - getClientsInRoom(roomId: string): ClientConnection[] { - return Array.from(this.clients.values()) - .filter(client => client.currentRoomId === roomId); - } - - /** - * 检查服务器是否正在运行 - */ - isServerRunning(): boolean { - return this.isRunning; - } - - /** - * 初始化服务器 - */ - private initialize(): void { - // 初始化 WebSocket 传输 - if (this.config.websocket) { - this.wsTransport = new WebSocketTransport(this.config.websocket); - this.setupTransportEvents(this.wsTransport); - } - - // 初始化 HTTP 传输 - if (this.config.http) { - this.httpTransport = new HttpTransport(this.config.http); - this.setupTransportEvents(this.httpTransport); - } - } - - /** - * 设置传输层事件监听 - */ - private setupTransportEvents(transport: Transport): void { - transport.on('client-connected', (connectionInfo) => { - this.handleClientConnected(connectionInfo.id, connectionInfo.remoteAddress || 'unknown', transport); - }); - - transport.on('client-disconnected', (connectionId, reason) => { - this.handleClientDisconnected(connectionId, reason); - }); - - transport.on('message', (connectionId, message) => { - this.handleTransportMessage(connectionId, message); - }); - - transport.on('error', (error, connectionId) => { - this.handleTransportError(error, connectionId); - }); - } - - /** - * 处理客户端连接 - */ - private handleClientConnected(connectionId: string, remoteAddress: string, transport: Transport): void { - // 检查连接数限制 - if (this.config.maxConnections && this.clients.size >= this.config.maxConnections) { - transport.disconnectClient(connectionId, 'Max connections reached'); - return; - } - - const client = new ClientConnection( - connectionId, - remoteAddress, - (message) => transport.sendToClient(connectionId, message), - { - connectionTimeout: this.config.authenticationTimeout, - permissions: this.config.defaultPermissions - } - ); - - // 设置客户端事件监听 - this.setupClientEvents(client); - - this.clients.set(connectionId, client); - this.stats.totalConnections++; - - console.log(`Client connected: ${connectionId} from ${remoteAddress}`); - this.emit('client-connected', client); - } - - /** - * 处理客户端断开连接 - */ - private handleClientDisconnected(connectionId: string, reason?: string): void { - const client = this.clients.get(connectionId); - if (client) { - client.destroy(); - this.clients.delete(connectionId); - - console.log(`Client disconnected: ${connectionId}, reason: ${reason || 'unknown'}`); - this.emit('client-disconnected', connectionId, reason); - } - } - - /** - * 处理传输层消息 - */ - private handleTransportMessage(connectionId: string, message: TransportMessage): void { - const client = this.clients.get(connectionId); - if (!client) { - return; - } - - client.handleMessage(message); - this.stats.totalMessages++; - - this.emit('message', client, message); - } - - /** - * 处理传输层错误 - */ - private handleTransportError(error: Error, connectionId?: string): void { - this.stats.totalErrors++; - - console.error(`Transport error${connectionId ? ` (client: ${connectionId})` : ''}:`, error.message); - this.emit('error', error, connectionId); - - // 如果是特定客户端的错误,断开该客户端 - if (connectionId) { - this.disconnectClient(connectionId, 'transport-error'); - } - } - - /** - * 设置客户端事件监听 - */ - private setupClientEvents(client: ClientConnection): void { - client.on('authenticated', (userData) => { - console.log(`Client authenticated: ${client.id}`, userData); - this.emit('client-authenticated', client); - }); - - client.on('error', (error) => { - console.error(`Client error (${client.id}):`, error.message); - this.emit('error', error, client.id); - }); - - client.on('timeout', () => { - console.log(`Client timeout: ${client.id}`); - this.disconnectClient(client.id, 'timeout'); - }); - - client.on('state-changed', (oldState, newState) => { - console.log(`Client ${client.id} state changed: ${oldState} -> ${newState}`); - }); - } - - /** - * 类型安全的事件监听 - */ - override on(event: K, listener: NetworkServerEvents[K]): this { - return super.on(event, listener); - } - - /** - * 类型安全的事件触发 - */ - override emit(event: K, ...args: Parameters): boolean { - return super.emit(event, ...args); - } -} \ No newline at end of file diff --git a/packages/network-server/src/core/Transport.ts b/packages/network-server/src/core/Transport.ts deleted file mode 100644 index 8a8f54a5..00000000 --- a/packages/network-server/src/core/Transport.ts +++ /dev/null @@ -1,224 +0,0 @@ -/** - * 网络传输层抽象接口 - */ - -import { EventEmitter } from 'events'; -import { NetworkMessage, NetworkValue } from '@esengine/ecs-framework-network-shared'; - -/** - * 传输层配置 - */ -export interface TransportConfig { - /** 服务器端口 */ - port: number; - /** 主机地址 */ - host?: string; - /** 最大连接数 */ - maxConnections?: number; - /** 心跳间隔(毫秒) */ - heartbeatInterval?: number; - /** 连接超时(毫秒) */ - connectionTimeout?: number; -} - -/** - * 客户端连接信息 - */ -export interface ClientConnectionInfo { - /** 连接ID */ - id: string; - /** 客户端IP */ - remoteAddress?: string; - /** 连接时间 */ - connectedAt: Date; - /** 最后活跃时间 */ - lastActivity: Date; - /** 用户数据 */ - userData?: Record; -} - -/** - * 网络消息包装 - */ -export interface TransportMessage { - /** 消息类型 */ - type: 'rpc' | 'syncvar' | 'system' | 'custom'; - /** 消息数据 */ - data: NetworkValue; - /** 发送者ID */ - senderId?: string; - /** 目标客户端ID(可选,用于单播) */ - targetId?: string; - /** 是否可靠传输 */ - reliable?: boolean; -} - -/** - * 网络传输层事件 - */ -export interface TransportEvents { - /** 客户端连接 */ - 'client-connected': (connectionInfo: ClientConnectionInfo) => void; - /** 客户端断开连接 */ - 'client-disconnected': (connectionId: string, reason?: string) => void; - /** 收到消息 */ - 'message': (connectionId: string, message: TransportMessage) => void; - /** 传输错误 */ - 'error': (error: Error, connectionId?: string) => void; - /** 服务器启动 */ - 'server-started': (config: TransportConfig) => void; - /** 服务器关闭 */ - 'server-stopped': () => void; -} - -/** - * 网络传输层抽象类 - */ -export abstract class Transport extends EventEmitter { - protected config: TransportConfig; - protected isRunning = false; - protected connections = new Map(); - - constructor(config: TransportConfig) { - super(); - this.config = config; - } - - /** - * 启动传输层服务 - */ - abstract start(): Promise; - - /** - * 停止传输层服务 - */ - abstract stop(): Promise; - - /** - * 发送消息给指定客户端 - */ - abstract sendToClient(connectionId: string, message: TransportMessage): Promise; - - /** - * 广播消息给所有客户端 - */ - abstract broadcast(message: TransportMessage, excludeId?: string): Promise; - - /** - * 广播消息给指定客户端列表 - */ - abstract sendToClients(connectionIds: string[], message: TransportMessage): Promise; - - /** - * 断开指定客户端连接 - */ - abstract disconnectClient(connectionId: string, reason?: string): Promise; - - /** - * 获取在线客户端数量 - */ - getConnectionCount(): number { - return this.connections.size; - } - - /** - * 获取所有连接信息 - */ - getConnections(): ClientConnectionInfo[] { - return Array.from(this.connections.values()); - } - - /** - * 获取指定连接信息 - */ - getConnection(connectionId: string): ClientConnectionInfo | undefined { - return this.connections.get(connectionId); - } - - /** - * 检查连接是否存在 - */ - hasConnection(connectionId: string): boolean { - return this.connections.has(connectionId); - } - - /** - * 服务器是否正在运行 - */ - isServerRunning(): boolean { - return this.isRunning; - } - - /** - * 获取传输层配置 - */ - getConfig(): TransportConfig { - return { ...this.config }; - } - - /** - * 更新客户端最后活跃时间 - */ - protected updateClientActivity(connectionId: string): void { - const connection = this.connections.get(connectionId); - if (connection) { - connection.lastActivity = new Date(); - } - } - - /** - * 添加客户端连接 - */ - protected addConnection(connectionInfo: ClientConnectionInfo): void { - this.connections.set(connectionInfo.id, connectionInfo); - this.emit('client-connected', connectionInfo); - } - - /** - * 移除客户端连接 - */ - protected removeConnection(connectionId: string, reason?: string): void { - if (this.connections.delete(connectionId)) { - this.emit('client-disconnected', connectionId, reason); - } - } - - /** - * 处理接收到的消息 - */ - protected handleMessage(connectionId: string, message: TransportMessage): void { - this.updateClientActivity(connectionId); - this.emit('message', connectionId, message); - } - - /** - * 处理传输错误 - */ - protected handleError(error: Error, connectionId?: string): void { - this.emit('error', error, connectionId); - } - - /** - * 清理所有连接 - */ - protected clearConnections(): void { - const connectionIds = Array.from(this.connections.keys()); - for (const id of connectionIds) { - this.removeConnection(id, 'server-shutdown'); - } - } - - /** - * 类型安全的事件监听 - */ - override on(event: K, listener: TransportEvents[K]): this { - return super.on(event, listener); - } - - /** - * 类型安全的事件触发 - */ - override emit(event: K, ...args: Parameters): boolean { - return super.emit(event, ...args); - } -} \ No newline at end of file diff --git a/packages/network-server/src/core/WebSocketTransport.ts b/packages/network-server/src/core/WebSocketTransport.ts deleted file mode 100644 index 150bb281..00000000 --- a/packages/network-server/src/core/WebSocketTransport.ts +++ /dev/null @@ -1,406 +0,0 @@ -/** - * WebSocket 传输层实现 - */ - -import { WebSocketServer, WebSocket } from 'ws'; -import { createServer, Server as HttpServer } from 'http'; -import { v4 as uuidv4 } from 'uuid'; -import { Transport, TransportConfig, ClientConnectionInfo, TransportMessage } from './Transport'; - -/** - * WebSocket 传输配置 - */ -export interface WebSocketTransportConfig extends TransportConfig { - /** WebSocket 路径 */ - path?: string; - /** 是否启用压缩 */ - compression?: boolean; - /** 最大消息大小(字节) */ - maxMessageSize?: number; - /** ping 间隔(毫秒) */ - pingInterval?: number; - /** pong 超时(毫秒) */ - pongTimeout?: number; -} - -/** - * WebSocket 客户端连接扩展信息 - */ -interface WebSocketConnectionInfo extends ClientConnectionInfo { - /** WebSocket 实例 */ - socket: WebSocket; - /** ping 定时器 */ - pingTimer?: NodeJS.Timeout; - /** pong 超时定时器 */ - pongTimer?: NodeJS.Timeout; -} - -/** - * WebSocket 传输层实现 - */ -export class WebSocketTransport extends Transport { - private httpServer: HttpServer | null = null; - private wsServer: WebSocketServer | null = null; - private wsConnections = new Map(); - - protected override config: WebSocketTransportConfig; - - constructor(config: WebSocketTransportConfig) { - super(config); - this.config = { - path: '/ws', - compression: true, - maxMessageSize: 1024 * 1024, // 1MB - pingInterval: 30000, // 30秒 - pongTimeout: 5000, // 5秒 - heartbeatInterval: 30000, - connectionTimeout: 60000, - maxConnections: 1000, - ...config - }; - } - - /** - * 启动 WebSocket 服务器 - */ - async start(): Promise { - if (this.isRunning) { - throw new Error('WebSocket transport is already running'); - } - - try { - // 创建 HTTP 服务器 - this.httpServer = createServer(); - - // 创建 WebSocket 服务器 - this.wsServer = new WebSocketServer({ - server: this.httpServer, - path: this.config.path, - maxPayload: this.config.maxMessageSize, - perMessageDeflate: this.config.compression - }); - - // 设置事件监听 - this.setupEventListeners(); - - // 启动服务器 - await new Promise((resolve, reject) => { - this.httpServer!.listen(this.config.port, this.config.host, (error?: Error) => { - if (error) { - reject(error); - } else { - this.isRunning = true; - resolve(); - } - }); - }); - - this.emit('server-started', this.config); - } catch (error) { - await this.cleanup(); - throw error; - } - } - - /** - * 停止 WebSocket 服务器 - */ - async stop(): Promise { - if (!this.isRunning) { - return; - } - - this.isRunning = false; - - // 断开所有客户端连接 - for (const [connectionId, connection] of this.wsConnections) { - this.disconnectClient(connectionId, 'server-shutdown'); - } - - await this.cleanup(); - this.emit('server-stopped'); - } - - /** - * 发送消息给指定客户端 - */ - async sendToClient(connectionId: string, message: TransportMessage): Promise { - const connection = this.wsConnections.get(connectionId); - if (!connection || connection.socket.readyState !== WebSocket.OPEN) { - return false; - } - - try { - const data = JSON.stringify(message); - connection.socket.send(data); - this.updateClientActivity(connectionId); - return true; - } catch (error) { - this.handleError(error as Error, connectionId); - return false; - } - } - - /** - * 广播消息给所有客户端 - */ - async broadcast(message: TransportMessage, excludeId?: string): Promise { - const data = JSON.stringify(message); - let sentCount = 0; - - for (const [connectionId, connection] of this.wsConnections) { - if (excludeId && connectionId === excludeId) { - continue; - } - - if (connection.socket.readyState === WebSocket.OPEN) { - try { - connection.socket.send(data); - sentCount++; - } catch (error) { - this.handleError(error as Error, connectionId); - } - } - } - - return sentCount; - } - - /** - * 发送消息给指定客户端列表 - */ - async sendToClients(connectionIds: string[], message: TransportMessage): Promise { - const data = JSON.stringify(message); - let sentCount = 0; - - for (const connectionId of connectionIds) { - const connection = this.wsConnections.get(connectionId); - if (connection && connection.socket.readyState === WebSocket.OPEN) { - try { - connection.socket.send(data); - sentCount++; - } catch (error) { - this.handleError(error as Error, connectionId); - } - } - } - - return sentCount; - } - - /** - * 断开指定客户端连接 - */ - async disconnectClient(connectionId: string, reason?: string): Promise { - const connection = this.wsConnections.get(connectionId); - if (connection) { - this.cleanupConnection(connectionId); - connection.socket.close(1000, reason); - } - } - - /** - * 设置事件监听器 - */ - private setupEventListeners(): void { - if (!this.wsServer) return; - - this.wsServer.on('connection', (socket: WebSocket, request) => { - this.handleNewConnection(socket, request); - }); - - this.wsServer.on('error', (error: Error) => { - this.handleError(error); - }); - - if (this.httpServer) { - this.httpServer.on('error', (error: Error) => { - this.handleError(error); - }); - } - } - - /** - * 处理新连接 - */ - private handleNewConnection(socket: WebSocket, request: any): void { - // 检查连接数限制 - if (this.config.maxConnections && this.wsConnections.size >= this.config.maxConnections) { - socket.close(1013, 'Too many connections'); - return; - } - - const connectionId = uuidv4(); - const remoteAddress = request.socket.remoteAddress || request.headers['x-forwarded-for'] || 'unknown'; - - const connectionInfo: WebSocketConnectionInfo = { - id: connectionId, - socket, - remoteAddress: Array.isArray(remoteAddress) ? remoteAddress[0] : remoteAddress, - connectedAt: new Date(), - lastActivity: new Date(), - userData: {} - }; - - this.wsConnections.set(connectionId, connectionInfo); - this.addConnection(connectionInfo); - - // 设置 socket 事件监听 - socket.on('message', (data: Buffer) => { - this.handleClientMessage(connectionId, data); - }); - - socket.on('close', (code: number, reason: Buffer) => { - this.handleClientDisconnect(connectionId, code, reason.toString()); - }); - - socket.on('error', (error: Error) => { - this.handleError(error, connectionId); - this.handleClientDisconnect(connectionId, 1006, 'Socket error'); - }); - - socket.on('pong', () => { - this.handlePong(connectionId); - }); - - // 启动心跳检测 - this.startHeartbeat(connectionId); - } - - /** - * 处理客户端消息 - */ - private handleClientMessage(connectionId: string, data: Buffer): void { - try { - const message = JSON.parse(data.toString()) as TransportMessage; - message.senderId = connectionId; - this.handleMessage(connectionId, message); - } catch (error) { - this.handleError(new Error(`Invalid message format from client ${connectionId}`), connectionId); - } - } - - /** - * 处理客户端断开连接 - */ - private handleClientDisconnect(connectionId: string, code: number, reason: string): void { - this.cleanupConnection(connectionId); - this.removeConnection(connectionId, `${code}: ${reason}`); - } - - /** - * 启动心跳检测 - */ - private startHeartbeat(connectionId: string): void { - const connection = this.wsConnections.get(connectionId); - if (!connection) return; - - if (this.config.pingInterval && this.config.pingInterval > 0) { - connection.pingTimer = setInterval(() => { - this.sendPing(connectionId); - }, this.config.pingInterval); - } - } - - /** - * 发送 ping - */ - private sendPing(connectionId: string): void { - const connection = this.wsConnections.get(connectionId); - if (!connection || connection.socket.readyState !== WebSocket.OPEN) { - return; - } - - connection.socket.ping(); - - // 设置 pong 超时 - if (this.config.pongTimeout && this.config.pongTimeout > 0) { - if (connection.pongTimer) { - clearTimeout(connection.pongTimer); - } - - connection.pongTimer = setTimeout(() => { - this.disconnectClient(connectionId, 'Pong timeout'); - }, this.config.pongTimeout); - } - } - - /** - * 处理 pong 响应 - */ - private handlePong(connectionId: string): void { - const connection = this.wsConnections.get(connectionId); - if (connection && connection.pongTimer) { - clearTimeout(connection.pongTimer); - connection.pongTimer = undefined; - } - this.updateClientActivity(connectionId); - } - - /** - * 清理连接资源 - */ - private cleanupConnection(connectionId: string): void { - const connection = this.wsConnections.get(connectionId); - if (connection) { - if (connection.pingTimer) { - clearInterval(connection.pingTimer); - } - if (connection.pongTimer) { - clearTimeout(connection.pongTimer); - } - this.wsConnections.delete(connectionId); - } - } - - /** - * 清理所有资源 - */ - private async cleanup(): Promise { - // 清理所有连接 - for (const connectionId of this.wsConnections.keys()) { - this.cleanupConnection(connectionId); - } - this.clearConnections(); - - // 关闭 WebSocket 服务器 - if (this.wsServer) { - this.wsServer.close(); - this.wsServer = null; - } - - // 关闭 HTTP 服务器 - if (this.httpServer) { - await new Promise((resolve) => { - this.httpServer!.close(() => resolve()); - }); - this.httpServer = null; - } - } - - /** - * 获取 WebSocket 连接统计信息 - */ - getWebSocketStats(): { - totalConnections: number; - activeConnections: number; - inactiveConnections: number; - } { - let activeConnections = 0; - let inactiveConnections = 0; - - for (const connection of this.wsConnections.values()) { - if (connection.socket.readyState === WebSocket.OPEN) { - activeConnections++; - } else { - inactiveConnections++; - } - } - - return { - totalConnections: this.wsConnections.size, - activeConnections, - inactiveConnections - }; - } -} \ No newline at end of file diff --git a/packages/network-server/src/core/index.ts b/packages/network-server/src/core/index.ts deleted file mode 100644 index 32e8f6e3..00000000 --- a/packages/network-server/src/core/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * 核心模块导出 - */ - -export * from './Transport'; -export * from './WebSocketTransport'; -export * from './HttpTransport'; -export * from './ClientConnection'; -export * from './NetworkServer'; \ No newline at end of file diff --git a/packages/network-server/src/index.ts b/packages/network-server/src/index.ts index aec17655..a1073c9e 100644 --- a/packages/network-server/src/index.ts +++ b/packages/network-server/src/index.ts @@ -1,79 +1,26 @@ /** - * ECS Framework Network Server - * - * 提供完整的网络服务端功能,包括: - * - WebSocket 和 HTTP 传输层 - * - 客户端连接管理 - * - 房间系统 - * - 身份验证和权限管理 - * - SyncVar 和 RPC 系统 - * - 消息验证 + * @esengine/network-server + * ECS Framework网络层 - 服务端实现 */ -// 核心模块 -export * from './core'; +// 核心服务器 (待实现) +// export * from './core/NetworkServer'; +// export * from './core/ClientConnection'; -// 房间系统 -export * from './rooms'; +// 传输层 (待实现) +// export * from './transport/WebSocketTransport'; +// export * from './transport/HttpTransport'; -// 认证系统 -export * from './auth'; +// 系统层 (待实现) +// export * from './systems/SyncVarSystem'; +// export * from './systems/RpcSystem'; -// 网络系统 -export * from './systems'; +// 房间管理 (待实现) +// export * from './rooms/Room'; +// export * from './rooms/RoomManager'; -// 验证系统 -export * from './validation'; +// 认证授权 (待实现) +// export * from './auth/AuthManager'; -// 版本信息 -export const VERSION = '1.0.0'; - -// 导出常用组合配置 -export interface ServerConfigPreset { - /** 服务器名称 */ - name: string; - /** WebSocket 端口 */ - wsPort: number; - /** HTTP 端口(可选) */ - httpPort?: number; - /** 最大连接数 */ - maxConnections: number; - /** 是否启用认证 */ - enableAuth: boolean; - /** 是否启用房间系统 */ - enableRooms: boolean; -} - -/** - * 预定义服务器配置 - */ -export const ServerPresets = { - /** 开发环境配置 */ - Development: { - name: 'Development Server', - wsPort: 8080, - httpPort: 3000, - maxConnections: 100, - enableAuth: false, - enableRooms: true - } as ServerConfigPreset, - - /** 生产环境配置 */ - Production: { - name: 'Production Server', - wsPort: 443, - httpPort: 80, - maxConnections: 10000, - enableAuth: true, - enableRooms: true - } as ServerConfigPreset, - - /** 测试环境配置 */ - Testing: { - name: 'Test Server', - wsPort: 9090, - maxConnections: 10, - enableAuth: false, - enableRooms: false - } as ServerConfigPreset -}; \ No newline at end of file +// 重新导出shared包的类型 +export * from '@esengine/network-shared'; \ No newline at end of file diff --git a/packages/network-server/src/rooms/Room.ts b/packages/network-server/src/rooms/Room.ts deleted file mode 100644 index 56bd62e9..00000000 --- a/packages/network-server/src/rooms/Room.ts +++ /dev/null @@ -1,637 +0,0 @@ -/** - * 房间管理 - * - * 类似于 Unity Mirror 的 Scene 概念,管理一组客户端和网络对象 - */ - -import { EventEmitter } from 'events'; -import { Entity, Scene } from '@esengine/ecs-framework'; -import { NetworkValue } from '@esengine/ecs-framework-network-shared'; -import { ClientConnection } from '../core/ClientConnection'; -import { TransportMessage } from '../core/Transport'; - -/** - * 房间状态 - */ -export enum RoomState { - /** 创建中 */ - CREATING = 'creating', - /** 活跃状态 */ - ACTIVE = 'active', - /** 暂停状态 */ - PAUSED = 'paused', - /** 关闭中 */ - CLOSING = 'closing', - /** 已关闭 */ - CLOSED = 'closed' -} - -/** - * 房间配置 - */ -export interface RoomConfig { - /** 房间ID */ - id: string; - /** 房间名称 */ - name: string; - /** 房间描述 */ - description?: string; - /** 最大玩家数 */ - maxPlayers: number; - /** 是否私有房间 */ - isPrivate?: boolean; - /** 房间密码 */ - password?: string; - /** 房间元数据 */ - metadata?: Record; - /** 是否持久化 */ - persistent?: boolean; - /** 房间过期时间(毫秒) */ - expirationTime?: number; -} - -/** - * 玩家数据 - */ -export interface PlayerData { - /** 客户端连接 */ - client: ClientConnection; - /** 加入时间 */ - joinedAt: Date; - /** 是否为房主 */ - isOwner: boolean; - /** 玩家自定义数据 */ - customData: Record; -} - -/** - * 房间统计信息 - */ -export interface RoomStats { - /** 当前玩家数 */ - currentPlayers: number; - /** 最大玩家数 */ - maxPlayers: number; - /** 总加入过的玩家数 */ - totalPlayersJoined: number; - /** 消息总数 */ - totalMessages: number; - /** 创建时间 */ - createdAt: Date; - /** 房间存活时间(毫秒) */ - lifetime: number; -} - -/** - * 房间事件 - */ -export interface RoomEvents { - /** 玩家加入 */ - 'player-joined': (player: PlayerData) => void; - /** 玩家离开 */ - 'player-left': (clientId: string, reason?: string) => void; - /** 房主变更 */ - 'owner-changed': (newOwnerId: string, oldOwnerId?: string) => void; - /** 房间状态变化 */ - 'state-changed': (oldState: RoomState, newState: RoomState) => void; - /** 收到消息 */ - 'message': (clientId: string, message: TransportMessage) => void; - /** 房间更新 */ - 'room-updated': (updatedFields: Partial) => void; - /** 房间错误 */ - 'error': (error: Error, clientId?: string) => void; - /** 房间即将关闭 */ - 'closing': (reason: string) => void; - /** 房间已关闭 */ - 'closed': (reason: string) => void; -} - -/** - * 房间类 - */ -export class Room extends EventEmitter { - private config: RoomConfig; - private state: RoomState = RoomState.CREATING; - private players = new Map(); - private ownerId: string | null = null; - private ecsScene: Scene | null = null; - private stats: RoomStats; - private expirationTimer: NodeJS.Timeout | null = null; - - constructor(config: RoomConfig) { - super(); - - this.config = { ...config }; - this.stats = { - currentPlayers: 0, - maxPlayers: config.maxPlayers, - totalPlayersJoined: 0, - totalMessages: 0, - createdAt: new Date(), - lifetime: 0 - }; - - this.initialize(); - } - - /** - * 获取房间ID - */ - get id(): string { - return this.config.id; - } - - /** - * 获取房间名称 - */ - get name(): string { - return this.config.name; - } - - /** - * 获取房间状态 - */ - get currentState(): RoomState { - return this.state; - } - - /** - * 获取房间配置 - */ - getConfig(): Readonly { - return this.config; - } - - /** - * 获取房间统计信息 - */ - getStats(): RoomStats { - this.stats.lifetime = Date.now() - this.stats.createdAt.getTime(); - this.stats.currentPlayers = this.players.size; - return { ...this.stats }; - } - - /** - * 获取所有玩家 - */ - getPlayers(): PlayerData[] { - return Array.from(this.players.values()); - } - - /** - * 获取指定玩家 - */ - getPlayer(clientId: string): PlayerData | undefined { - return this.players.get(clientId); - } - - /** - * 检查玩家是否在房间中 - */ - hasPlayer(clientId: string): boolean { - return this.players.has(clientId); - } - - /** - * 获取当前玩家数量 - */ - getPlayerCount(): number { - return this.players.size; - } - - /** - * 检查房间是否已满 - */ - isFull(): boolean { - return this.players.size >= this.config.maxPlayers; - } - - /** - * 检查房间是否为空 - */ - isEmpty(): boolean { - return this.players.size === 0; - } - - /** - * 获取房主 - */ - getOwner(): PlayerData | undefined { - return this.ownerId ? this.players.get(this.ownerId) : undefined; - } - - /** - * 获取 ECS 场景 - */ - getEcsScene(): Scene | null { - return this.ecsScene; - } - - /** - * 玩家加入房间 - */ - async addPlayer(client: ClientConnection, customData: Record = {}): Promise { - if (this.state !== RoomState.ACTIVE) { - throw new Error(`Cannot join room in state: ${this.state}`); - } - - if (this.hasPlayer(client.id)) { - throw new Error(`Player ${client.id} is already in the room`); - } - - if (this.isFull()) { - throw new Error('Room is full'); - } - - // 检查房间密码 - if (this.config.isPrivate && this.config.password) { - const providedPassword = customData.password as string; - if (providedPassword !== this.config.password) { - throw new Error('Invalid room password'); - } - } - - const isFirstPlayer = this.isEmpty(); - const playerData: PlayerData = { - client, - joinedAt: new Date(), - isOwner: isFirstPlayer, - customData: { ...customData } - }; - - this.players.set(client.id, playerData); - client.joinRoom(this.id); - - // 设置房主 - if (isFirstPlayer) { - this.ownerId = client.id; - } - - this.stats.totalPlayersJoined++; - - // 通知其他玩家 - await this.broadcast({ - type: 'system', - data: { - action: 'player-joined', - playerId: client.id, - playerData: { - id: client.id, - joinedAt: playerData.joinedAt.toISOString(), - isOwner: playerData.isOwner, - customData: playerData.customData - } - } - }, client.id); - - console.log(`Player ${client.id} joined room ${this.id}`); - this.emit('player-joined', playerData); - - return true; - } - - /** - * 玩家离开房间 - */ - async removePlayer(clientId: string, reason?: string): Promise { - const player = this.players.get(clientId); - if (!player) { - return false; - } - - this.players.delete(clientId); - player.client.leaveRoom(); - - // 如果离开的是房主,转移房主权限 - if (this.ownerId === clientId) { - await this.transferOwnership(); - } - - // 通知其他玩家 - await this.broadcast({ - type: 'system', - data: { - action: 'player-left', - playerId: clientId, - reason: reason || 'unknown' - } - }); - - console.log(`Player ${clientId} left room ${this.id}, reason: ${reason || 'unknown'}`); - this.emit('player-left', clientId, reason); - - // 如果房间为空,考虑关闭 - if (this.isEmpty() && !this.config.persistent) { - await this.close('empty-room'); - } - - return true; - } - - /** - * 转移房主权限 - */ - async transferOwnership(newOwnerId?: string): Promise { - const oldOwnerId = this.ownerId; - - if (newOwnerId) { - const newOwner = this.players.get(newOwnerId); - if (!newOwner) { - return false; - } - this.ownerId = newOwnerId; - newOwner.isOwner = true; - } else { - // 自动选择下一个玩家作为房主 - const players = Array.from(this.players.values()); - if (players.length > 0) { - const newOwner = players[0]; - this.ownerId = newOwner.client.id; - newOwner.isOwner = true; - } else { - this.ownerId = null; - } - } - - // 更新旧房主状态 - if (oldOwnerId) { - const oldOwner = this.players.get(oldOwnerId); - if (oldOwner) { - oldOwner.isOwner = false; - } - } - - // 通知所有玩家房主变更 - if (this.ownerId) { - await this.broadcast({ - type: 'system', - data: { - action: 'owner-changed', - newOwnerId: this.ownerId, - oldOwnerId: oldOwnerId || '' - } - }); - - console.log(`Room ${this.id} ownership transferred from ${oldOwnerId || 'none'} to ${this.ownerId}`); - this.emit('owner-changed', this.ownerId, oldOwnerId || undefined); - } - - return true; - } - - /** - * 广播消息给房间内所有玩家 - */ - async broadcast(message: TransportMessage, excludeClientId?: string): Promise { - const players = Array.from(this.players.values()) - .filter(player => player.client.id !== excludeClientId); - - const promises = players.map(player => player.client.sendMessage(message)); - const results = await Promise.allSettled(promises); - - return results.filter(result => result.status === 'fulfilled' && result.value).length; - } - - /** - * 发送消息给指定玩家 - */ - async sendToPlayer(clientId: string, message: TransportMessage): Promise { - const player = this.players.get(clientId); - if (!player) { - return false; - } - - return await player.client.sendMessage(message); - } - - /** - * 处理玩家消息 - */ - async handleMessage(clientId: string, message: TransportMessage): Promise { - if (!this.hasPlayer(clientId)) { - return; - } - - this.stats.totalMessages++; - this.emit('message', clientId, message); - - // 根据消息类型进行处理 - switch (message.type) { - case 'rpc': - await this.handleRpcMessage(clientId, message); - break; - case 'syncvar': - await this.handleSyncVarMessage(clientId, message); - break; - case 'system': - await this.handleSystemMessage(clientId, message); - break; - default: - // 转发自定义消息 - await this.broadcast(message, clientId); - break; - } - } - - /** - * 更新房间配置 - */ - async updateConfig(updates: Partial): Promise { - // 验证更新 - if (updates.maxPlayers !== undefined && updates.maxPlayers < this.players.size) { - throw new Error('Cannot reduce maxPlayers below current player count'); - } - - const oldConfig = { ...this.config }; - Object.assign(this.config, updates); - - // 通知所有玩家房间更新 - await this.broadcast({ - type: 'system', - data: { - action: 'room-updated', - updates - } - }); - - this.emit('room-updated', updates); - } - - /** - * 暂停房间 - */ - async pause(): Promise { - if (this.state === RoomState.ACTIVE) { - this.setState(RoomState.PAUSED); - - await this.broadcast({ - type: 'system', - data: { - action: 'room-paused' - } - }); - } - } - - /** - * 恢复房间 - */ - async resume(): Promise { - if (this.state === RoomState.PAUSED) { - this.setState(RoomState.ACTIVE); - - await this.broadcast({ - type: 'system', - data: { - action: 'room-resumed' - } - }); - } - } - - /** - * 关闭房间 - */ - async close(reason: string = 'server-shutdown'): Promise { - if (this.state === RoomState.CLOSED || this.state === RoomState.CLOSING) { - return; - } - - this.setState(RoomState.CLOSING); - this.emit('closing', reason); - - // 通知所有玩家房间即将关闭 - await this.broadcast({ - type: 'system', - data: { - action: 'room-closing', - reason - } - }); - - // 移除所有玩家 - const playerIds = Array.from(this.players.keys()); - for (const clientId of playerIds) { - await this.removePlayer(clientId, 'room-closed'); - } - - this.cleanup(); - this.setState(RoomState.CLOSED); - - console.log(`Room ${this.id} closed, reason: ${reason}`); - this.emit('closed', reason); - } - - /** - * 初始化房间 - */ - private initialize(): void { - // 创建 ECS 场景 - this.ecsScene = new Scene(); - - // 设置过期定时器 - if (this.config.expirationTime && this.config.expirationTime > 0) { - this.expirationTimer = setTimeout(() => { - this.close('expired'); - }, this.config.expirationTime); - } - - this.setState(RoomState.ACTIVE); - } - - /** - * 处理 RPC 消息 - */ - private async handleRpcMessage(clientId: string, message: TransportMessage): Promise { - // RPC 消息处理逻辑 - // 这里可以添加权限检查、速率限制等 - await this.broadcast(message, clientId); - } - - /** - * 处理 SyncVar 消息 - */ - private async handleSyncVarMessage(clientId: string, message: TransportMessage): Promise { - // SyncVar 消息处理逻辑 - // 这里可以添加权限检查、数据验证等 - await this.broadcast(message, clientId); - } - - /** - * 处理系统消息 - */ - private async handleSystemMessage(clientId: string, message: TransportMessage): Promise { - const data = message.data as any; - - switch (data.action) { - case 'request-ownership': - // 处理房主权限转移请求 - if (this.ownerId === clientId) { - await this.transferOwnership(data.newOwnerId); - } - break; - // 其他系统消息处理... - } - } - - /** - * 设置房间状态 - */ - private setState(newState: RoomState): void { - const oldState = this.state; - if (oldState !== newState) { - this.state = newState; - this.emit('state-changed', oldState, newState); - } - } - - /** - * 清理资源 - */ - private cleanup(): void { - if (this.expirationTimer) { - clearTimeout(this.expirationTimer); - this.expirationTimer = null; - } - - this.removeAllListeners(); - - if (this.ecsScene) { - this.ecsScene = null; - } - } - - /** - * 类型安全的事件监听 - */ - override on(event: K, listener: RoomEvents[K]): this { - return super.on(event, listener); - } - - /** - * 类型安全的事件触发 - */ - override emit(event: K, ...args: Parameters): boolean { - return super.emit(event, ...args); - } - - /** - * 序列化房间信息 - */ - toJSON(): object { - return { - id: this.id, - name: this.name, - state: this.state, - config: this.config, - stats: this.getStats(), - players: this.getPlayers().map(player => ({ - id: player.client.id, - joinedAt: player.joinedAt.toISOString(), - isOwner: player.isOwner, - customData: player.customData - })), - ownerId: this.ownerId - }; - } -} \ No newline at end of file diff --git a/packages/network-server/src/rooms/RoomManager.ts b/packages/network-server/src/rooms/RoomManager.ts deleted file mode 100644 index 63d1bc1c..00000000 --- a/packages/network-server/src/rooms/RoomManager.ts +++ /dev/null @@ -1,499 +0,0 @@ -/** - * 房间管理器 - * - * 管理所有房间的创建、销毁、查找等操作 - */ - -import { EventEmitter } from 'events'; -import { Room, RoomConfig, RoomState, PlayerData } from './Room'; -import { ClientConnection } from '../core/ClientConnection'; -import { NetworkValue } from '@esengine/ecs-framework-network-shared'; - -/** - * 房间管理器配置 - */ -export interface RoomManagerConfig { - /** 最大房间数量 */ - maxRooms?: number; - /** 默认房间过期时间(毫秒) */ - defaultExpirationTime?: number; - /** 是否启用房间统计 */ - enableStats?: boolean; - /** 房间清理间隔(毫秒) */ - cleanupInterval?: number; -} - -/** - * 房间查询选项 - */ -export interface RoomQueryOptions { - /** 房间名称模糊搜索 */ - namePattern?: string; - /** 房间状态过滤 */ - state?: RoomState; - /** 是否私有房间 */ - isPrivate?: boolean; - /** 最小空位数 */ - minAvailableSlots?: number; - /** 最大空位数 */ - maxAvailableSlots?: number; - /** 元数据过滤 */ - metadata?: Record; - /** 限制结果数量 */ - limit?: number; - /** 跳过条数 */ - offset?: number; -} - -/** - * 房间管理器统计信息 - */ -export interface RoomManagerStats { - /** 总房间数 */ - totalRooms: number; - /** 活跃房间数 */ - activeRooms: number; - /** 总玩家数 */ - totalPlayers: number; - /** 私有房间数 */ - privateRooms: number; - /** 持久化房间数 */ - persistentRooms: number; - /** 创建的房间总数 */ - roomsCreated: number; - /** 关闭的房间总数 */ - roomsClosed: number; -} - -/** - * 房间管理器事件 - */ -export interface RoomManagerEvents { - /** 房间创建 */ - 'room-created': (room: Room) => void; - /** 房间关闭 */ - 'room-closed': (roomId: string, reason: string) => void; - /** 玩家加入房间 */ - 'player-joined-room': (roomId: string, player: PlayerData) => void; - /** 玩家离开房间 */ - 'player-left-room': (roomId: string, clientId: string, reason?: string) => void; - /** 房间管理器错误 */ - 'error': (error: Error, roomId?: string) => void; -} - -/** - * 房间管理器 - */ -export class RoomManager extends EventEmitter { - private config: RoomManagerConfig; - private rooms = new Map(); - private stats: RoomManagerStats; - private cleanupTimer: NodeJS.Timeout | null = null; - - constructor(config: RoomManagerConfig = {}) { - super(); - - this.config = { - maxRooms: 1000, - defaultExpirationTime: 0, // 0 = 不过期 - enableStats: true, - cleanupInterval: 60000, // 1分钟 - ...config - }; - - this.stats = { - totalRooms: 0, - activeRooms: 0, - totalPlayers: 0, - privateRooms: 0, - persistentRooms: 0, - roomsCreated: 0, - roomsClosed: 0 - }; - - this.initialize(); - } - - /** - * 获取房间管理器配置 - */ - getConfig(): Readonly { - return this.config; - } - - /** - * 获取房间管理器统计信息 - */ - getStats(): RoomManagerStats { - this.updateStats(); - return { ...this.stats }; - } - - /** - * 创建房间 - */ - async createRoom(config: RoomConfig, creatorClient?: ClientConnection): Promise { - // 检查房间数量限制 - if (this.config.maxRooms && this.rooms.size >= this.config.maxRooms) { - throw new Error('Maximum number of rooms reached'); - } - - // 检查房间ID是否已存在 - if (this.rooms.has(config.id)) { - throw new Error(`Room with id "${config.id}" already exists`); - } - - // 应用默认过期时间 - const roomConfig: RoomConfig = { - expirationTime: this.config.defaultExpirationTime, - ...config - }; - - const room = new Room(roomConfig); - - // 设置房间事件监听 - this.setupRoomEvents(room); - - this.rooms.set(room.id, room); - this.stats.roomsCreated++; - - console.log(`Room created: ${room.id} by ${creatorClient?.id || 'system'}`); - this.emit('room-created', room); - - // 如果有创建者,自动加入房间 - if (creatorClient) { - try { - await room.addPlayer(creatorClient); - } catch (error) { - console.error(`Failed to add creator to room ${room.id}:`, error); - } - } - - return room; - } - - /** - * 获取房间 - */ - getRoom(roomId: string): Room | undefined { - return this.rooms.get(roomId); - } - - /** - * 检查房间是否存在 - */ - hasRoom(roomId: string): boolean { - return this.rooms.has(roomId); - } - - /** - * 获取所有房间 - */ - getAllRooms(): Room[] { - return Array.from(this.rooms.values()); - } - - /** - * 查询房间 - */ - findRooms(options: RoomQueryOptions = {}): Room[] { - let rooms = Array.from(this.rooms.values()); - - // 状态过滤 - if (options.state !== undefined) { - rooms = rooms.filter(room => room.currentState === options.state); - } - - // 私有房间过滤 - if (options.isPrivate !== undefined) { - rooms = rooms.filter(room => room.getConfig().isPrivate === options.isPrivate); - } - - // 名称模糊搜索 - if (options.namePattern) { - const pattern = options.namePattern.toLowerCase(); - rooms = rooms.filter(room => - room.getConfig().name.toLowerCase().includes(pattern) - ); - } - - // 空位数过滤 - if (options.minAvailableSlots !== undefined) { - rooms = rooms.filter(room => { - const available = room.getConfig().maxPlayers - room.getPlayerCount(); - return available >= options.minAvailableSlots!; - }); - } - - if (options.maxAvailableSlots !== undefined) { - rooms = rooms.filter(room => { - const available = room.getConfig().maxPlayers - room.getPlayerCount(); - return available <= options.maxAvailableSlots!; - }); - } - - // 元数据过滤 - if (options.metadata) { - rooms = rooms.filter(room => { - const roomMetadata = room.getConfig().metadata || {}; - return Object.entries(options.metadata!).every(([key, value]) => - roomMetadata[key] === value - ); - }); - } - - // 排序(按创建时间,最新的在前) - rooms.sort((a, b) => - b.getStats().createdAt.getTime() - a.getStats().createdAt.getTime() - ); - - // 分页 - const offset = options.offset || 0; - const limit = options.limit || rooms.length; - return rooms.slice(offset, offset + limit); - } - - /** - * 关闭房间 - */ - async closeRoom(roomId: string, reason: string = 'manual'): Promise { - const room = this.rooms.get(roomId); - if (!room) { - return false; - } - - try { - await room.close(reason); - return true; - } catch (error) { - this.emit('error', error as Error, roomId); - return false; - } - } - - /** - * 玩家加入房间 - */ - async joinRoom( - roomId: string, - client: ClientConnection, - customData: Record = {} - ): Promise { - const room = this.rooms.get(roomId); - if (!room) { - throw new Error(`Room "${roomId}" not found`); - } - - try { - return await room.addPlayer(client, customData); - } catch (error) { - this.emit('error', error as Error, roomId); - throw error; - } - } - - /** - * 玩家离开房间 - */ - async leaveRoom(roomId: string, clientId: string, reason?: string): Promise { - const room = this.rooms.get(roomId); - if (!room) { - return false; - } - - try { - return await room.removePlayer(clientId, reason); - } catch (error) { - this.emit('error', error as Error, roomId); - return false; - } - } - - /** - * 玩家离开所有房间 - */ - async leaveAllRooms(clientId: string, reason?: string): Promise { - let leftCount = 0; - - for (const room of this.rooms.values()) { - if (room.hasPlayer(clientId)) { - try { - await room.removePlayer(clientId, reason); - leftCount++; - } catch (error) { - console.error(`Error removing player ${clientId} from room ${room.id}:`, error); - } - } - } - - return leftCount; - } - - /** - * 获取玩家所在的房间 - */ - getPlayerRooms(clientId: string): Room[] { - return Array.from(this.rooms.values()) - .filter(room => room.hasPlayer(clientId)); - } - - /** - * 获取房间数量 - */ - getRoomCount(): number { - return this.rooms.size; - } - - /** - * 获取总玩家数量 - */ - getTotalPlayerCount(): number { - return Array.from(this.rooms.values()) - .reduce((total, room) => total + room.getPlayerCount(), 0); - } - - /** - * 清理空闲房间 - */ - async cleanupRooms(): Promise { - let cleanedCount = 0; - const now = Date.now(); - - for (const room of this.rooms.values()) { - const config = room.getConfig(); - const stats = room.getStats(); - - // 清理条件: - // 1. 非持久化的空房间 - // 2. 已过期的房间 - // 3. 已关闭的房间 - let shouldClean = false; - let reason = ''; - - if (room.currentState === RoomState.CLOSED) { - shouldClean = true; - reason = 'room-closed'; - } else if (!config.persistent && room.isEmpty()) { - shouldClean = true; - reason = 'empty-room'; - } else if (config.expirationTime && config.expirationTime > 0) { - const expireTime = stats.createdAt.getTime() + config.expirationTime; - if (now >= expireTime) { - shouldClean = true; - reason = 'expired'; - } - } - - if (shouldClean) { - try { - if (room.currentState !== RoomState.CLOSED) { - await room.close(reason); - } - this.rooms.delete(room.id); - cleanedCount++; - console.log(`Cleaned up room: ${room.id}, reason: ${reason}`); - } catch (error) { - console.error(`Error cleaning up room ${room.id}:`, error); - } - } - } - - return cleanedCount; - } - - /** - * 关闭所有房间 - */ - async closeAllRooms(reason: string = 'shutdown'): Promise { - const rooms = Array.from(this.rooms.values()); - const promises = rooms.map(room => room.close(reason)); - - await Promise.allSettled(promises); - this.rooms.clear(); - - console.log(`Closed ${rooms.length} rooms, reason: ${reason}`); - } - - /** - * 销毁房间管理器 - */ - async destroy(): Promise { - // 停止清理定时器 - if (this.cleanupTimer) { - clearInterval(this.cleanupTimer); - this.cleanupTimer = null; - } - - // 关闭所有房间 - await this.closeAllRooms('manager-destroyed'); - - // 移除所有事件监听器 - this.removeAllListeners(); - } - - /** - * 初始化房间管理器 - */ - private initialize(): void { - // 启动清理定时器 - if (this.config.cleanupInterval && this.config.cleanupInterval > 0) { - this.cleanupTimer = setInterval(() => { - this.cleanupRooms().catch(error => { - console.error('Error during room cleanup:', error); - }); - }, this.config.cleanupInterval); - } - } - - /** - * 设置房间事件监听 - */ - private setupRoomEvents(room: Room): void { - room.on('player-joined', (player) => { - this.emit('player-joined-room', room.id, player); - }); - - room.on('player-left', (clientId, reason) => { - this.emit('player-left-room', room.id, clientId, reason); - }); - - room.on('closed', (reason) => { - this.rooms.delete(room.id); - this.stats.roomsClosed++; - console.log(`Room ${room.id} removed from manager, reason: ${reason}`); - this.emit('room-closed', room.id, reason); - }); - - room.on('error', (error) => { - this.emit('error', error, room.id); - }); - } - - /** - * 更新统计信息 - */ - private updateStats(): void { - this.stats.totalRooms = this.rooms.size; - this.stats.activeRooms = Array.from(this.rooms.values()) - .filter(room => room.currentState === RoomState.ACTIVE).length; - this.stats.totalPlayers = this.getTotalPlayerCount(); - this.stats.privateRooms = Array.from(this.rooms.values()) - .filter(room => room.getConfig().isPrivate).length; - this.stats.persistentRooms = Array.from(this.rooms.values()) - .filter(room => room.getConfig().persistent).length; - } - - /** - * 类型安全的事件监听 - */ - override on(event: K, listener: RoomManagerEvents[K]): this { - return super.on(event, listener); - } - - /** - * 类型安全的事件触发 - */ - override emit(event: K, ...args: Parameters): boolean { - return super.emit(event, ...args); - } -} \ No newline at end of file diff --git a/packages/network-server/src/rooms/index.ts b/packages/network-server/src/rooms/index.ts deleted file mode 100644 index a42b05f9..00000000 --- a/packages/network-server/src/rooms/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * 房间系统导出 - */ - -export * from './Room'; -export * from './RoomManager'; \ No newline at end of file diff --git a/packages/network-server/src/systems/RpcSystem.ts b/packages/network-server/src/systems/RpcSystem.ts deleted file mode 100644 index a6c6f520..00000000 --- a/packages/network-server/src/systems/RpcSystem.ts +++ /dev/null @@ -1,762 +0,0 @@ -/** - * RPC 系统 - * - * 处理服务端的 RPC 调用、权限验证、参数验证等 - */ - -import { EventEmitter } from 'events'; -import { v4 as uuidv4 } from 'uuid'; -import { - NetworkValue, - RpcMetadata -} from '@esengine/ecs-framework-network-shared'; -import { ClientConnection } from '../core/ClientConnection'; -import { Room } from '../rooms/Room'; -import { TransportMessage } from '../core/Transport'; - -/** - * RPC 调用记录 - */ -export interface RpcCall { - /** 调用ID */ - id: string; - /** 网络对象ID */ - networkId: number; - /** 组件类型 */ - componentType: string; - /** 方法名 */ - methodName: string; - /** 参数 */ - parameters: NetworkValue[]; - /** 元数据 */ - metadata: RpcMetadata; - /** 发送者客户端ID */ - senderId: string; - /** 目标客户端IDs(用于 ClientRpc) */ - targetClientIds?: string[]; - /** 是否需要响应 */ - requiresResponse: boolean; - /** 时间戳 */ - timestamp: Date; - /** 过期时间 */ - expiresAt?: Date; -} - -/** - * RPC 响应 - */ -export interface RpcResponse { - /** 调用ID */ - callId: string; - /** 是否成功 */ - success: boolean; - /** 返回值 */ - result?: NetworkValue; - /** 错误信息 */ - error?: string; - /** 错误代码 */ - errorCode?: string; - /** 时间戳 */ - timestamp: Date; -} - -/** - * RPC 系统配置 - */ -export interface RpcSystemConfig { - /** RPC 调用超时时间(毫秒) */ - callTimeout?: number; - /** 最大并发 RPC 调用数 */ - maxConcurrentCalls?: number; - /** 是否启用权限检查 */ - enablePermissionCheck?: boolean; - /** 是否启用参数验证 */ - enableParameterValidation?: boolean; - /** 是否启用频率限制 */ - enableRateLimit?: boolean; - /** 最大 RPC 频率(调用/秒) */ - maxRpcRate?: number; - /** 单个参数最大大小(字节) */ - maxParameterSize?: number; -} - -/** - * RPC 系统事件 - */ -export interface RpcSystemEvents { - /** ClientRpc 调用 */ - 'client-rpc-called': (call: RpcCall) => void; - /** ServerRpc 调用 */ - 'server-rpc-called': (call: RpcCall) => void; - /** RPC 调用完成 */ - 'rpc-completed': (call: RpcCall, response?: RpcResponse) => void; - /** RPC 调用超时 */ - 'rpc-timeout': (callId: string) => void; - /** 权限验证失败 */ - 'permission-denied': (clientId: string, call: RpcCall) => void; - /** 参数验证失败 */ - 'parameter-validation-failed': (clientId: string, call: RpcCall, reason: string) => void; - /** 频率限制触发 */ - 'rate-limit-exceeded': (clientId: string) => void; - /** RPC 错误 */ - 'rpc-error': (error: Error, callId?: string, clientId?: string) => void; -} - -/** - * 客户端 RPC 状态 - */ -interface ClientRpcState { - /** 客户端ID */ - clientId: string; - /** 活跃的调用 */ - activeCalls: Map; - /** RPC 调用计数 */ - rpcCount: number; - /** 频率重置时间 */ - rateResetTime: Date; -} - -/** - * 待处理的 RPC 响应 - */ -interface PendingRpcResponse { - /** 调用信息 */ - call: RpcCall; - /** 超时定时器 */ - timeoutTimer: NodeJS.Timeout; - /** 响应回调 */ - responseCallback: (response: RpcResponse) => void; -} - -/** - * RPC 系统 - */ -export class RpcSystem extends EventEmitter { - private config: RpcSystemConfig; - private clientStates = new Map(); - private pendingCalls = new Map(); - private cleanupTimer: NodeJS.Timeout | null = null; - - constructor(config: RpcSystemConfig = {}) { - super(); - - this.config = { - callTimeout: 30000, // 30秒 - maxConcurrentCalls: 10, - enablePermissionCheck: true, - enableParameterValidation: true, - enableRateLimit: true, - maxRpcRate: 30, // 30次/秒 - maxParameterSize: 65536, // 64KB - ...config - }; - - this.initialize(); - } - - /** - * 处理 ClientRpc 调用 - */ - async handleClientRpcCall( - client: ClientConnection, - message: TransportMessage, - room: Room - ): Promise { - try { - const data = message.data as any; - const { - networkId, - componentType, - methodName, - parameters = [], - metadata, - targetFilter = 'all' - } = data; - - // 创建 RPC 调用记录 - const rpcCall: RpcCall = { - id: uuidv4(), - networkId, - componentType, - methodName, - parameters, - metadata, - senderId: client.id, - requiresResponse: metadata?.requiresResponse || false, - timestamp: new Date() - }; - - // 权限检查 - if (this.config.enablePermissionCheck) { - if (!this.checkRpcPermission(client, rpcCall, 'client-rpc')) { - this.emit('permission-denied', client.id, rpcCall); - return; - } - } - - // 频率限制检查 - if (this.config.enableRateLimit && !this.checkRpcRate(client.id)) { - this.emit('rate-limit-exceeded', client.id); - return; - } - - // 参数验证 - if (this.config.enableParameterValidation) { - const validationResult = this.validateRpcParameters(rpcCall); - if (!validationResult.valid) { - this.emit('parameter-validation-failed', client.id, rpcCall, validationResult.reason!); - return; - } - } - - // 确定目标客户端 - const targetClientIds = this.getClientRpcTargets(room, client.id, targetFilter); - rpcCall.targetClientIds = targetClientIds; - - // 记录活跃调用 - this.recordActiveCall(client.id, rpcCall); - - // 触发事件 - this.emit('client-rpc-called', rpcCall); - - // 发送到目标客户端 - await this.sendClientRpc(room, rpcCall, targetClientIds); - - // 如果不需要响应,立即标记完成 - if (!rpcCall.requiresResponse) { - this.completeRpcCall(rpcCall); - } - - } catch (error) { - this.emit('rpc-error', error as Error, undefined, client.id); - } - } - - /** - * 处理 ServerRpc 调用 - */ - async handleServerRpcCall( - client: ClientConnection, - message: TransportMessage, - room: Room - ): Promise { - try { - const data = message.data as any; - const { - networkId, - componentType, - methodName, - parameters = [], - metadata - } = data; - - // 创建 RPC 调用记录 - const rpcCall: RpcCall = { - id: uuidv4(), - networkId, - componentType, - methodName, - parameters, - metadata, - senderId: client.id, - requiresResponse: metadata?.requiresResponse || false, - timestamp: new Date() - }; - - // 权限检查 - if (this.config.enablePermissionCheck) { - if (!this.checkRpcPermission(client, rpcCall, 'server-rpc')) { - this.emit('permission-denied', client.id, rpcCall); - return; - } - } - - // 频率限制检查 - if (this.config.enableRateLimit && !this.checkRpcRate(client.id)) { - this.emit('rate-limit-exceeded', client.id); - return; - } - - // 参数验证 - if (this.config.enableParameterValidation) { - const validationResult = this.validateRpcParameters(rpcCall); - if (!validationResult.valid) { - this.emit('parameter-validation-failed', client.id, rpcCall, validationResult.reason!); - return; - } - } - - // 记录活跃调用 - this.recordActiveCall(client.id, rpcCall); - - // 触发事件 - this.emit('server-rpc-called', rpcCall); - - // ServerRpc 在服务端执行,这里需要实际的执行逻辑 - // 在实际使用中,应该通过事件或回调来执行具体的方法 - const response = await this.executeServerRpc(rpcCall); - - // 发送响应(如果需要) - if (rpcCall.requiresResponse && response) { - await this.sendRpcResponse(client, response); - } - - this.completeRpcCall(rpcCall, response || undefined); - - } catch (error) { - this.emit('rpc-error', error as Error, undefined, client.id); - - // 发送错误响应 - if (message.data && (message.data as any).requiresResponse) { - const errorResponse: RpcResponse = { - callId: (message.data as any).callId || uuidv4(), - success: false, - error: (error as Error).message, - errorCode: 'EXECUTION_ERROR', - timestamp: new Date() - }; - await this.sendRpcResponse(client, errorResponse); - } - } - } - - /** - * 处理 RPC 响应 - */ - async handleRpcResponse( - client: ClientConnection, - message: TransportMessage - ): Promise { - try { - const response = message.data as any as RpcResponse; - const pendingCall = this.pendingCalls.get(response.callId); - - if (pendingCall) { - // 清除超时定时器 - clearTimeout(pendingCall.timeoutTimer); - this.pendingCalls.delete(response.callId); - - // 调用响应回调 - pendingCall.responseCallback(response); - - // 完成调用 - this.completeRpcCall(pendingCall.call, response); - } - - } catch (error) { - this.emit('rpc-error', error as Error, undefined, client.id); - } - } - - /** - * 调用 ClientRpc(从服务端向客户端发送) - */ - async callClientRpc( - room: Room, - networkId: number, - componentType: string, - methodName: string, - parameters: NetworkValue[] = [], - options: { - targetFilter?: 'all' | 'others' | 'owner' | string[]; - requiresResponse?: boolean; - timeout?: number; - } = {} - ): Promise { - const rpcCall: RpcCall = { - id: uuidv4(), - networkId, - componentType, - methodName, - parameters, - metadata: { - methodName, - rpcType: 'client-rpc', - requiresAuth: false, - reliable: true, - requiresResponse: options.requiresResponse || false - }, - senderId: 'server', - requiresResponse: options.requiresResponse || false, - timestamp: new Date() - }; - - // 确定目标客户端 - const targetClientIds = typeof options.targetFilter === 'string' - ? this.getClientRpcTargets(room, 'server', options.targetFilter) - : options.targetFilter || []; - - rpcCall.targetClientIds = targetClientIds; - - // 发送到目标客户端 - await this.sendClientRpc(room, rpcCall, targetClientIds); - - // 如果需要响应,等待响应 - if (options.requiresResponse) { - return await this.waitForRpcResponses(rpcCall, targetClientIds, options.timeout); - } - - this.completeRpcCall(rpcCall); - return []; - } - - /** - * 获取客户端统计信息 - */ - getClientRpcStats(clientId: string): { - activeCalls: number; - totalCalls: number; - } { - const state = this.clientStates.get(clientId); - return { - activeCalls: state?.activeCalls.size || 0, - totalCalls: state?.rpcCount || 0 - }; - } - - /** - * 取消所有客户端的 RPC 调用 - */ - cancelClientRpcs(clientId: string): number { - const state = this.clientStates.get(clientId); - if (!state) { - return 0; - } - - const cancelledCount = state.activeCalls.size; - - // 取消所有活跃调用 - for (const call of state.activeCalls.values()) { - this.completeRpcCall(call); - } - - state.activeCalls.clear(); - return cancelledCount; - } - - /** - * 销毁 RPC 系统 - */ - destroy(): void { - if (this.cleanupTimer) { - clearInterval(this.cleanupTimer); - this.cleanupTimer = null; - } - - // 清除所有待处理的调用 - for (const pending of this.pendingCalls.values()) { - clearTimeout(pending.timeoutTimer); - } - - this.clientStates.clear(); - this.pendingCalls.clear(); - this.removeAllListeners(); - } - - /** - * 初始化系统 - */ - private initialize(): void { - // 启动清理定时器(每分钟清理一次) - this.cleanupTimer = setInterval(() => { - this.cleanup(); - }, 60000); - } - - /** - * 检查 RPC 权限 - */ - private checkRpcPermission( - client: ClientConnection, - call: RpcCall, - rpcType: 'client-rpc' | 'server-rpc' - ): boolean { - // 基本权限检查 - if (!client.hasPermission('canSendRpc')) { - return false; - } - - // ServerRpc 额外权限检查 - if (rpcType === 'server-rpc' && call.metadata.requiresAuth) { - if (!client.isAuthenticated) { - return false; - } - } - - // 可以添加更多特定的权限检查逻辑 - return true; - } - - /** - * 检查 RPC 频率 - */ - private checkRpcRate(clientId: string): boolean { - if (!this.config.maxRpcRate || this.config.maxRpcRate <= 0) { - return true; - } - - const now = new Date(); - let state = this.clientStates.get(clientId); - - if (!state) { - state = { - clientId, - activeCalls: new Map(), - rpcCount: 1, - rateResetTime: new Date(now.getTime() + 1000) - }; - this.clientStates.set(clientId, state); - return true; - } - - // 检查是否需要重置计数 - if (now >= state.rateResetTime) { - state.rpcCount = 1; - state.rateResetTime = new Date(now.getTime() + 1000); - return true; - } - - // 检查频率限制 - if (state.rpcCount >= this.config.maxRpcRate) { - return false; - } - - state.rpcCount++; - return true; - } - - /** - * 验证 RPC 参数 - */ - private validateRpcParameters(call: RpcCall): { valid: boolean; reason?: string } { - // 检查参数数量 - if (call.parameters.length > 10) { - return { valid: false, reason: 'Too many parameters' }; - } - - // 检查每个参数的大小 - for (let i = 0; i < call.parameters.length; i++) { - const param = call.parameters[i]; - try { - const serialized = JSON.stringify(param); - if (serialized.length > this.config.maxParameterSize!) { - return { valid: false, reason: `Parameter ${i} is too large` }; - } - } catch (error) { - return { valid: false, reason: `Parameter ${i} is not serializable` }; - } - } - - return { valid: true }; - } - - /** - * 获取 ClientRpc 目标客户端 - */ - private getClientRpcTargets( - room: Room, - senderId: string, - targetFilter: string - ): string[] { - const players = room.getPlayers(); - - switch (targetFilter) { - case 'all': - return players.map(p => p.client.id); - - case 'others': - return players - .filter(p => p.client.id !== senderId) - .map(p => p.client.id); - - case 'owner': - const owner = room.getOwner(); - return owner ? [owner.client.id] : []; - - default: - return []; - } - } - - /** - * 发送 ClientRpc - */ - private async sendClientRpc( - room: Room, - call: RpcCall, - targetClientIds: string[] - ): Promise { - const message: TransportMessage = { - type: 'rpc', - data: { - action: 'client-rpc', - callId: call.id, - networkId: call.networkId, - componentType: call.componentType, - methodName: call.methodName, - parameters: call.parameters, - metadata: call.metadata as any, - requiresResponse: call.requiresResponse, - timestamp: call.timestamp.getTime() - } as any - }; - - // 发送给目标客户端 - const promises = targetClientIds.map(clientId => - room.sendToPlayer(clientId, message) - ); - - await Promise.allSettled(promises); - } - - /** - * 执行 ServerRpc - */ - private async executeServerRpc(call: RpcCall): Promise { - // 这里应该是实际的服务端方法执行逻辑 - // 在实际实现中,可能需要通过事件或回调来执行具体的方法 - - // 示例响应 - const response: RpcResponse = { - callId: call.id, - success: true, - result: undefined, // 实际执行结果 - timestamp: new Date() - }; - - return response; - } - - /** - * 发送 RPC 响应 - */ - private async sendRpcResponse( - client: ClientConnection, - response: RpcResponse - ): Promise { - const message: TransportMessage = { - type: 'rpc', - data: { - action: 'rpc-response', - ...response - } as any - }; - - await client.sendMessage(message); - } - - /** - * 等待 RPC 响应 - */ - private async waitForRpcResponses( - call: RpcCall, - targetClientIds: string[], - timeout?: number - ): Promise { - return new Promise((resolve) => { - const responses: RpcResponse[] = []; - const responseTimeout = timeout || this.config.callTimeout!; - let responseCount = 0; - - const responseCallback = (response: RpcResponse) => { - responses.push(response); - responseCount++; - - // 如果收到所有响应,立即resolve - if (responseCount >= targetClientIds.length) { - resolve(responses); - } - }; - - // 设置超时 - const timeoutTimer = setTimeout(() => { - resolve(responses); // 返回已收到的响应 - this.emit('rpc-timeout', call.id); - }, responseTimeout); - - // 注册待处理的响应 - this.pendingCalls.set(call.id, { - call, - timeoutTimer, - responseCallback - }); - }); - } - - /** - * 记录活跃调用 - */ - private recordActiveCall(clientId: string, call: RpcCall): void { - let state = this.clientStates.get(clientId); - if (!state) { - state = { - clientId, - activeCalls: new Map(), - rpcCount: 0, - rateResetTime: new Date() - }; - this.clientStates.set(clientId, state); - } - - state.activeCalls.set(call.id, call); - } - - /** - * 完成 RPC 调用 - */ - private completeRpcCall(call: RpcCall, response?: RpcResponse): void { - // 从活跃调用中移除 - const state = this.clientStates.get(call.senderId); - if (state) { - state.activeCalls.delete(call.id); - } - - // 触发完成事件 - this.emit('rpc-completed', call, response); - } - - /** - * 清理过期的调用和状态 - */ - private cleanup(): void { - const now = new Date(); - let cleanedCalls = 0; - let cleanedStates = 0; - - // 清理过期的待处理调用 - for (const [callId, pending] of this.pendingCalls.entries()) { - if (pending.call.expiresAt && pending.call.expiresAt < now) { - clearTimeout(pending.timeoutTimer); - this.pendingCalls.delete(callId); - cleanedCalls++; - } - } - - // 清理空的客户端状态 - for (const [clientId, state] of this.clientStates.entries()) { - if (state.activeCalls.size === 0 && - now.getTime() - state.rateResetTime.getTime() > 60000) { - this.clientStates.delete(clientId); - cleanedStates++; - } - } - - if (cleanedCalls > 0 || cleanedStates > 0) { - console.log(`RPC cleanup: ${cleanedCalls} calls, ${cleanedStates} states`); - } - } - - /** - * 类型安全的事件监听 - */ - override on(event: K, listener: RpcSystemEvents[K]): this { - return super.on(event, listener); - } - - /** - * 类型安全的事件触发 - */ - override emit(event: K, ...args: Parameters): boolean { - return super.emit(event, ...args); - } -} \ No newline at end of file diff --git a/packages/network-server/src/systems/SyncVarSystem.ts b/packages/network-server/src/systems/SyncVarSystem.ts deleted file mode 100644 index 84bc2b42..00000000 --- a/packages/network-server/src/systems/SyncVarSystem.ts +++ /dev/null @@ -1,587 +0,0 @@ -/** - * SyncVar 同步系统 - * - * 处理服务端的 SyncVar 同步逻辑、权限验证、数据传播等 - */ - -import { EventEmitter } from 'events'; -import { - NetworkValue, - SyncVarMetadata, - NetworkSerializer -} from '@esengine/ecs-framework-network-shared'; -import { ClientConnection } from '../core/ClientConnection'; -import { Room } from '../rooms/Room'; -import { TransportMessage } from '../core/Transport'; - -/** - * SyncVar 更改记录 - */ -export interface SyncVarChange { - /** 网络对象ID */ - networkId: number; - /** 组件类型 */ - componentType: string; - /** 属性名 */ - propertyName: string; - /** 旧值 */ - oldValue: NetworkValue; - /** 新值 */ - newValue: NetworkValue; - /** 元数据 */ - metadata: SyncVarMetadata; - /** 发送者客户端ID */ - senderId: string; - /** 时间戳 */ - timestamp: Date; -} - -/** - * SyncVar 同步配置 - */ -export interface SyncVarSystemConfig { - /** 批量同步间隔(毫秒) */ - batchInterval?: number; - /** 单次批量最大数量 */ - maxBatchSize?: number; - /** 是否启用增量同步 */ - enableDeltaSync?: boolean; - /** 是否启用权限检查 */ - enablePermissionCheck?: boolean; - /** 是否启用数据验证 */ - enableDataValidation?: boolean; - /** 最大同步频率(次/秒) */ - maxSyncRate?: number; -} - -/** - * 网络对象状态 - */ -export interface NetworkObjectState { - /** 网络对象ID */ - networkId: number; - /** 拥有者客户端ID */ - ownerId: string; - /** 组件状态 */ - components: Map>; - /** 最后更新时间 */ - lastUpdateTime: Date; - /** 权威状态 */ - hasAuthority: boolean; -} - -/** - * SyncVar 系统事件 - */ -export interface SyncVarSystemEvents { - /** SyncVar 值变化 */ - 'syncvar-changed': (change: SyncVarChange) => void; - /** 同步批次完成 */ - 'batch-synced': (changes: SyncVarChange[], targetClients: string[]) => void; - /** 权限验证失败 */ - 'permission-denied': (clientId: string, change: SyncVarChange) => void; - /** 数据验证失败 */ - 'validation-failed': (clientId: string, change: SyncVarChange, reason: string) => void; - /** 同步错误 */ - 'sync-error': (error: Error, clientId?: string) => void; -} - -/** - * 客户端同步状态 - */ -interface ClientSyncState { - /** 客户端ID */ - clientId: string; - /** 待同步的变化列表 */ - pendingChanges: SyncVarChange[]; - /** 最后同步时间 */ - lastSyncTime: Date; - /** 同步频率限制 */ - syncCount: number; - /** 频率重置时间 */ - rateResetTime: Date; -} - -/** - * SyncVar 同步系统 - */ -export class SyncVarSystem extends EventEmitter { - private config: SyncVarSystemConfig; - private networkObjects = new Map(); - private clientSyncStates = new Map(); - private serializer: NetworkSerializer; - private batchTimer: NodeJS.Timeout | null = null; - - constructor(config: SyncVarSystemConfig = {}) { - super(); - - this.config = { - batchInterval: 50, // 50ms批量间隔 - maxBatchSize: 100, - enableDeltaSync: true, - enablePermissionCheck: true, - enableDataValidation: true, - maxSyncRate: 60, // 60次/秒 - ...config - }; - - this.serializer = new NetworkSerializer(); - this.initialize(); - } - - /** - * 注册网络对象 - */ - registerNetworkObject( - networkId: number, - ownerId: string, - hasAuthority: boolean = true - ): void { - if (this.networkObjects.has(networkId)) { - console.warn(`Network object ${networkId} is already registered`); - return; - } - - const networkObject: NetworkObjectState = { - networkId, - ownerId, - components: new Map(), - lastUpdateTime: new Date(), - hasAuthority - }; - - this.networkObjects.set(networkId, networkObject); - console.log(`Network object registered: ${networkId} owned by ${ownerId}`); - } - - /** - * 注销网络对象 - */ - unregisterNetworkObject(networkId: number): boolean { - const removed = this.networkObjects.delete(networkId); - if (removed) { - console.log(`Network object unregistered: ${networkId}`); - } - return removed; - } - - /** - * 获取网络对象 - */ - getNetworkObject(networkId: number): NetworkObjectState | undefined { - return this.networkObjects.get(networkId); - } - - /** - * 处理 SyncVar 变化消息 - */ - async handleSyncVarChange( - client: ClientConnection, - message: TransportMessage, - room?: Room - ): Promise { - try { - const data = message.data as any; - const { - networkId, - componentType, - propertyName, - oldValue, - newValue, - metadata - } = data; - - // 创建变化记录 - const change: SyncVarChange = { - networkId, - componentType, - propertyName, - oldValue, - newValue, - metadata, - senderId: client.id, - timestamp: new Date() - }; - - // 权限检查 - if (this.config.enablePermissionCheck) { - if (!this.checkSyncVarPermission(client, change)) { - this.emit('permission-denied', client.id, change); - return; - } - } - - // 频率限制检查 - if (!this.checkSyncRate(client.id)) { - console.warn(`SyncVar rate limit exceeded for client ${client.id}`); - return; - } - - // 数据验证 - if (this.config.enableDataValidation) { - const validationResult = this.validateSyncVarData(change); - if (!validationResult.valid) { - this.emit('validation-failed', client.id, change, validationResult.reason!); - return; - } - } - - // 更新网络对象状态 - this.updateNetworkObjectState(change); - - // 触发变化事件 - this.emit('syncvar-changed', change); - - // 添加到待同步列表 - if (room) { - this.addToBatchSync(change, room); - } - - } catch (error) { - this.emit('sync-error', error as Error, client.id); - } - } - - /** - * 获取网络对象的完整状态 - */ - getNetworkObjectSnapshot(networkId: number): Record | null { - const networkObject = this.networkObjects.get(networkId); - if (!networkObject) { - return null; - } - - const snapshot: Record = {}; - - for (const [componentType, componentData] of networkObject.components) { - snapshot[componentType] = {}; - for (const [propertyName, value] of componentData) { - snapshot[componentType][propertyName] = value; - } - } - - return snapshot; - } - - /** - * 向客户端发送网络对象快照 - */ - async sendNetworkObjectSnapshot( - client: ClientConnection, - networkId: number - ): Promise { - const snapshot = this.getNetworkObjectSnapshot(networkId); - if (!snapshot) { - return false; - } - - const message: TransportMessage = { - type: 'syncvar', - data: { - action: 'snapshot', - networkId, - snapshot - } - }; - - return await client.sendMessage(message); - } - - /** - * 同步所有网络对象给新客户端 - */ - async syncAllNetworkObjects(client: ClientConnection, room: Room): Promise { - let syncedCount = 0; - - for (const networkObject of this.networkObjects.values()) { - // 检查客户端是否有权限看到这个网络对象 - if (this.canClientSeeNetworkObject(client.id, networkObject)) { - const success = await this.sendNetworkObjectSnapshot(client, networkObject.networkId); - if (success) { - syncedCount++; - } - } - } - - console.log(`Synced ${syncedCount} network objects to client ${client.id}`); - return syncedCount; - } - - /** - * 设置网络对象拥有者 - */ - setNetworkObjectOwner(networkId: number, newOwnerId: string): boolean { - const networkObject = this.networkObjects.get(networkId); - if (!networkObject) { - return false; - } - - const oldOwnerId = networkObject.ownerId; - networkObject.ownerId = newOwnerId; - networkObject.lastUpdateTime = new Date(); - - console.log(`Network object ${networkId} ownership changed: ${oldOwnerId} -> ${newOwnerId}`); - return true; - } - - /** - * 获取网络对象拥有者 - */ - getNetworkObjectOwner(networkId: number): string | undefined { - const networkObject = this.networkObjects.get(networkId); - return networkObject?.ownerId; - } - - /** - * 销毁 SyncVar 系统 - */ - destroy(): void { - if (this.batchTimer) { - clearInterval(this.batchTimer); - this.batchTimer = null; - } - - this.networkObjects.clear(); - this.clientSyncStates.clear(); - this.removeAllListeners(); - } - - /** - * 初始化系统 - */ - private initialize(): void { - // 启动批量同步定时器 - if (this.config.batchInterval && this.config.batchInterval > 0) { - this.batchTimer = setInterval(() => { - this.processBatchSync(); - }, this.config.batchInterval); - } - } - - /** - * 检查 SyncVar 权限 - */ - private checkSyncVarPermission(client: ClientConnection, change: SyncVarChange): boolean { - // 检查客户端是否有网络同步权限 - if (!client.hasPermission('canSyncVars')) { - return false; - } - - // 获取网络对象 - const networkObject = this.networkObjects.get(change.networkId); - if (!networkObject) { - return false; - } - - // 检查权威权限 - if (change.metadata.authorityOnly) { - // 只有网络对象拥有者或有权威权限的客户端可以修改 - return networkObject.ownerId === client.id || networkObject.hasAuthority; - } - - return true; - } - - /** - * 检查同步频率 - */ - private checkSyncRate(clientId: string): boolean { - if (!this.config.maxSyncRate || this.config.maxSyncRate <= 0) { - return true; - } - - const now = new Date(); - let syncState = this.clientSyncStates.get(clientId); - - if (!syncState) { - syncState = { - clientId, - pendingChanges: [], - lastSyncTime: now, - syncCount: 1, - rateResetTime: new Date(now.getTime() + 1000) // 1秒后重置 - }; - this.clientSyncStates.set(clientId, syncState); - return true; - } - - // 检查是否需要重置计数 - if (now >= syncState.rateResetTime) { - syncState.syncCount = 1; - syncState.rateResetTime = new Date(now.getTime() + 1000); - return true; - } - - // 检查频率限制 - if (syncState.syncCount >= this.config.maxSyncRate) { - return false; - } - - syncState.syncCount++; - return true; - } - - /** - * 验证 SyncVar 数据 - */ - private validateSyncVarData(change: SyncVarChange): { valid: boolean; reason?: string } { - // 基本类型检查 - if (change.newValue === null || change.newValue === undefined) { - return { valid: false, reason: 'Value cannot be null or undefined' }; - } - - // 检查数据大小(防止过大的数据) - try { - const serialized = JSON.stringify(change.newValue); - if (serialized.length > 65536) { // 64KB限制 - return { valid: false, reason: 'Data too large' }; - } - } catch (error) { - return { valid: false, reason: 'Data is not serializable' }; - } - - // 可以添加更多特定的验证逻辑 - return { valid: true }; - } - - /** - * 更新网络对象状态 - */ - private updateNetworkObjectState(change: SyncVarChange): void { - let networkObject = this.networkObjects.get(change.networkId); - - if (!networkObject) { - // 如果网络对象不存在,创建一个新的(可能是客户端创建的) - networkObject = { - networkId: change.networkId, - ownerId: change.senderId, - components: new Map(), - lastUpdateTime: new Date(), - hasAuthority: true - }; - this.networkObjects.set(change.networkId, networkObject); - } - - // 获取或创建组件数据 - let componentData = networkObject.components.get(change.componentType); - if (!componentData) { - componentData = new Map(); - networkObject.components.set(change.componentType, componentData); - } - - // 更新属性值 - componentData.set(change.propertyName, change.newValue); - networkObject.lastUpdateTime = change.timestamp; - } - - /** - * 添加到批量同步 - */ - private addToBatchSync(change: SyncVarChange, room: Room): void { - // 获取房间内需要同步的客户端 - const roomPlayers = room.getPlayers(); - const targetClientIds = roomPlayers - .filter(player => player.client.id !== change.senderId) // 不发送给发送者 - .map(player => player.client.id); - - // 为每个目标客户端添加变化记录 - for (const clientId of targetClientIds) { - let syncState = this.clientSyncStates.get(clientId); - if (!syncState) { - syncState = { - clientId, - pendingChanges: [], - lastSyncTime: new Date(), - syncCount: 0, - rateResetTime: new Date() - }; - this.clientSyncStates.set(clientId, syncState); - } - - syncState.pendingChanges.push(change); - } - } - - /** - * 处理批量同步 - */ - private async processBatchSync(): Promise { - const syncPromises: Promise[] = []; - - for (const [clientId, syncState] of this.clientSyncStates.entries()) { - if (syncState.pendingChanges.length === 0) { - continue; - } - - // 获取要同步的变化(限制批量大小) - const changesToSync = syncState.pendingChanges.splice( - 0, - this.config.maxBatchSize - ); - - if (changesToSync.length > 0) { - syncPromises.push(this.sendBatchChanges(clientId, changesToSync)); - } - } - - if (syncPromises.length > 0) { - await Promise.allSettled(syncPromises); - } - } - - /** - * 发送批量变化 - */ - private async sendBatchChanges(clientId: string, changes: SyncVarChange[]): Promise { - try { - // 这里需要获取客户端连接,实际实现中可能需要从外部传入 - // 为了简化,这里假设有一个方法可以获取客户端连接 - // 实际使用时,可能需要通过回调或事件来发送消息 - - const message: TransportMessage = { - type: 'syncvar', - data: { - action: 'batch-update', - changes: changes.map(change => ({ - networkId: change.networkId, - componentType: change.componentType, - propertyName: change.propertyName, - newValue: change.newValue, - metadata: change.metadata as any, - timestamp: change.timestamp.getTime() - })) - } as any - }; - - // 这里需要实际的发送逻辑 - // 在实际使用中,应该通过事件或回调来发送消息 - this.emit('batch-synced', changes, [clientId]); - - } catch (error) { - this.emit('sync-error', error as Error, clientId); - } - } - - /** - * 检查客户端是否可以看到网络对象 - */ - private canClientSeeNetworkObject(clientId: string, networkObject: NetworkObjectState): boolean { - // 基本实现:客户端可以看到自己拥有的对象和公共对象 - // 实际实现中可能需要更复杂的可见性逻辑 - return true; - } - - /** - * 类型安全的事件监听 - */ - override on(event: K, listener: SyncVarSystemEvents[K]): this { - return super.on(event, listener); - } - - /** - * 类型安全的事件触发 - */ - override emit(event: K, ...args: Parameters): boolean { - return super.emit(event, ...args); - } -} \ No newline at end of file diff --git a/packages/network-server/src/systems/index.ts b/packages/network-server/src/systems/index.ts deleted file mode 100644 index 6e614e7f..00000000 --- a/packages/network-server/src/systems/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * 系统模块导出 - */ - -export * from './SyncVarSystem'; -export * from './RpcSystem'; \ No newline at end of file diff --git a/packages/network-server/src/validation/MessageValidator.ts b/packages/network-server/src/validation/MessageValidator.ts deleted file mode 100644 index 01698dac..00000000 --- a/packages/network-server/src/validation/MessageValidator.ts +++ /dev/null @@ -1,572 +0,0 @@ -/** - * 消息验证器 - * - * 验证网络消息的格式、大小、内容等 - */ - -import { NetworkValue } from '@esengine/ecs-framework-network-shared'; -import { TransportMessage } from '../core/Transport'; - -/** - * 验证结果 - */ -export interface ValidationResult { - /** 是否有效 */ - valid: boolean; - /** 错误信息 */ - error?: string; - /** 错误代码 */ - errorCode?: string; - /** 详细信息 */ - details?: Record; -} - -/** - * 验证配置 - */ -export interface ValidationConfig { - /** 最大消息大小(字节) */ - maxMessageSize?: number; - /** 最大数组长度 */ - maxArrayLength?: number; - /** 最大对象深度 */ - maxObjectDepth?: number; - /** 最大字符串长度 */ - maxStringLength?: number; - /** 允许的消息类型 */ - allowedMessageTypes?: string[]; - /** 是否允许null值 */ - allowNullValues?: boolean; - /** 是否允许undefined值 */ - allowUndefinedValues?: boolean; -} - -/** - * 验证规则 - */ -export interface ValidationRule { - /** 规则名称 */ - name: string; - /** 验证函数 */ - validate: (value: any, context: ValidationContext) => ValidationResult; - /** 是否必需 */ - required?: boolean; -} - -/** - * 验证上下文 - */ -export interface ValidationContext { - /** 当前路径 */ - path: string[]; - /** 当前深度 */ - depth: number; - /** 配置 */ - config: ValidationConfig; - /** 消息类型 */ - messageType?: string; -} - -/** - * 消息验证器 - */ -export class MessageValidator { - private config: ValidationConfig; - private customRules = new Map(); - - constructor(config: ValidationConfig = {}) { - this.config = { - maxMessageSize: 1024 * 1024, // 1MB - maxArrayLength: 1000, - maxObjectDepth: 10, - maxStringLength: 10000, - allowedMessageTypes: ['rpc', 'syncvar', 'system', 'custom'], - allowNullValues: true, - allowUndefinedValues: false, - ...config - }; - } - - /** - * 验证传输消息 - */ - validateMessage(message: TransportMessage): ValidationResult { - try { - // 基本结构验证 - const structureResult = this.validateMessageStructure(message); - if (!structureResult.valid) { - return structureResult; - } - - // 消息大小验证 - const sizeResult = this.validateMessageSize(message); - if (!sizeResult.valid) { - return sizeResult; - } - - // 消息类型验证 - const typeResult = this.validateMessageType(message); - if (!typeResult.valid) { - return typeResult; - } - - // 数据内容验证 - const dataResult = this.validateMessageData(message); - if (!dataResult.valid) { - return dataResult; - } - - // 自定义规则验证 - const customResult = this.validateCustomRules(message); - if (!customResult.valid) { - return customResult; - } - - return { valid: true }; - - } catch (error) { - return { - valid: false, - error: (error as Error).message, - errorCode: 'VALIDATION_ERROR' - }; - } - } - - /** - * 验证网络值 - */ - validateNetworkValue(value: NetworkValue, context?: Partial): ValidationResult { - const fullContext: ValidationContext = { - path: [], - depth: 0, - config: this.config, - ...context - }; - - return this.validateValue(value, fullContext); - } - - /** - * 添加自定义验证规则 - */ - addValidationRule(rule: ValidationRule): void { - this.customRules.set(rule.name, rule); - } - - /** - * 移除自定义验证规则 - */ - removeValidationRule(ruleName: string): boolean { - return this.customRules.delete(ruleName); - } - - /** - * 获取所有自定义规则 - */ - getCustomRules(): ValidationRule[] { - return Array.from(this.customRules.values()); - } - - /** - * 验证消息结构 - */ - private validateMessageStructure(message: TransportMessage): ValidationResult { - // 检查必需字段 - if (!message.type) { - return { - valid: false, - error: 'Message type is required', - errorCode: 'MISSING_TYPE' - }; - } - - if (message.data === undefined) { - return { - valid: false, - error: 'Message data is required', - errorCode: 'MISSING_DATA' - }; - } - - // 检查字段类型 - if (typeof message.type !== 'string') { - return { - valid: false, - error: 'Message type must be a string', - errorCode: 'INVALID_TYPE_FORMAT' - }; - } - - // 检查可选字段 - if (message.senderId && typeof message.senderId !== 'string') { - return { - valid: false, - error: 'Sender ID must be a string', - errorCode: 'INVALID_SENDER_ID' - }; - } - - if (message.targetId && typeof message.targetId !== 'string') { - return { - valid: false, - error: 'Target ID must be a string', - errorCode: 'INVALID_TARGET_ID' - }; - } - - if (message.reliable !== undefined && typeof message.reliable !== 'boolean') { - return { - valid: false, - error: 'Reliable flag must be a boolean', - errorCode: 'INVALID_RELIABLE_FLAG' - }; - } - - return { valid: true }; - } - - /** - * 验证消息大小 - */ - private validateMessageSize(message: TransportMessage): ValidationResult { - try { - const serialized = JSON.stringify(message); - const size = new TextEncoder().encode(serialized).length; - - if (size > this.config.maxMessageSize!) { - return { - valid: false, - error: `Message size (${size} bytes) exceeds maximum (${this.config.maxMessageSize} bytes)`, - errorCode: 'MESSAGE_TOO_LARGE', - details: { actualSize: size, maxSize: this.config.maxMessageSize } - }; - } - - return { valid: true }; - - } catch (error) { - return { - valid: false, - error: 'Failed to serialize message for size validation', - errorCode: 'SERIALIZATION_ERROR' - }; - } - } - - /** - * 验证消息类型 - */ - private validateMessageType(message: TransportMessage): ValidationResult { - if (!this.config.allowedMessageTypes!.includes(message.type)) { - return { - valid: false, - error: `Message type '${message.type}' is not allowed`, - errorCode: 'INVALID_MESSAGE_TYPE', - details: { - messageType: message.type, - allowedTypes: this.config.allowedMessageTypes - } - }; - } - - return { valid: true }; - } - - /** - * 验证消息数据 - */ - private validateMessageData(message: TransportMessage): ValidationResult { - const context: ValidationContext = { - path: ['data'], - depth: 0, - config: this.config, - messageType: message.type - }; - - return this.validateValue(message.data, context); - } - - /** - * 验证值 - */ - private validateValue(value: any, context: ValidationContext): ValidationResult { - // 深度检查 - if (context.depth > this.config.maxObjectDepth!) { - return { - valid: false, - error: `Object depth (${context.depth}) exceeds maximum (${this.config.maxObjectDepth})`, - errorCode: 'OBJECT_TOO_DEEP', - details: { path: context.path.join('.'), depth: context.depth } - }; - } - - // null/undefined 检查 - if (value === null) { - if (!this.config.allowNullValues) { - return { - valid: false, - error: 'Null values are not allowed', - errorCode: 'NULL_NOT_ALLOWED', - details: { path: context.path.join('.') } - }; - } - return { valid: true }; - } - - if (value === undefined) { - if (!this.config.allowUndefinedValues) { - return { - valid: false, - error: 'Undefined values are not allowed', - errorCode: 'UNDEFINED_NOT_ALLOWED', - details: { path: context.path.join('.') } - }; - } - return { valid: true }; - } - - // 根据类型验证 - switch (typeof value) { - case 'string': - return this.validateString(value, context); - - case 'number': - return this.validateNumber(value, context); - - case 'boolean': - return { valid: true }; - - case 'object': - if (Array.isArray(value)) { - return this.validateArray(value, context); - } else { - return this.validateObject(value, context); - } - - default: - return { - valid: false, - error: `Unsupported value type: ${typeof value}`, - errorCode: 'UNSUPPORTED_TYPE', - details: { path: context.path.join('.'), type: typeof value } - }; - } - } - - /** - * 验证字符串 - */ - private validateString(value: string, context: ValidationContext): ValidationResult { - if (value.length > this.config.maxStringLength!) { - return { - valid: false, - error: `String length (${value.length}) exceeds maximum (${this.config.maxStringLength})`, - errorCode: 'STRING_TOO_LONG', - details: { - path: context.path.join('.'), - actualLength: value.length, - maxLength: this.config.maxStringLength - } - }; - } - - return { valid: true }; - } - - /** - * 验证数字 - */ - private validateNumber(value: number, context: ValidationContext): ValidationResult { - // 检查是否为有效数字 - if (!Number.isFinite(value)) { - return { - valid: false, - error: 'Number must be finite', - errorCode: 'INVALID_NUMBER', - details: { path: context.path.join('.'), value } - }; - } - - return { valid: true }; - } - - /** - * 验证数组 - */ - private validateArray(value: any[], context: ValidationContext): ValidationResult { - // 长度检查 - if (value.length > this.config.maxArrayLength!) { - return { - valid: false, - error: `Array length (${value.length}) exceeds maximum (${this.config.maxArrayLength})`, - errorCode: 'ARRAY_TOO_LONG', - details: { - path: context.path.join('.'), - actualLength: value.length, - maxLength: this.config.maxArrayLength - } - }; - } - - // 验证每个元素 - for (let i = 0; i < value.length; i++) { - const elementContext: ValidationContext = { - ...context, - path: [...context.path, `[${i}]`], - depth: context.depth + 1 - }; - - const result = this.validateValue(value[i], elementContext); - if (!result.valid) { - return result; - } - } - - return { valid: true }; - } - - /** - * 验证对象 - */ - private validateObject(value: Record, context: ValidationContext): ValidationResult { - // 验证每个属性 - for (const [key, propertyValue] of Object.entries(value)) { - const propertyContext: ValidationContext = { - ...context, - path: [...context.path, key], - depth: context.depth + 1 - }; - - const result = this.validateValue(propertyValue, propertyContext); - if (!result.valid) { - return result; - } - } - - return { valid: true }; - } - - /** - * 验证自定义规则 - */ - private validateCustomRules(message: TransportMessage): ValidationResult { - for (const rule of this.customRules.values()) { - const context: ValidationContext = { - path: [], - depth: 0, - config: this.config, - messageType: message.type - }; - - const result = rule.validate(message, context); - if (!result.valid) { - return { - ...result, - details: { - ...result.details, - rule: rule.name - } - }; - } - } - - return { valid: true }; - } -} - -/** - * 预定义验证规则 - */ -export const DefaultValidationRules = { - /** - * RPC 消息验证规则 - */ - RpcMessage: { - name: 'RpcMessage', - validate: (message: TransportMessage, context: ValidationContext): ValidationResult => { - if (message.type !== 'rpc') { - return { valid: true }; // 不是 RPC 消息,跳过验证 - } - - const data = message.data as any; - - // 检查必需字段 - if (!data.networkId || typeof data.networkId !== 'number') { - return { - valid: false, - error: 'RPC message must have a valid networkId', - errorCode: 'RPC_INVALID_NETWORK_ID' - }; - } - - if (!data.componentType || typeof data.componentType !== 'string') { - return { - valid: false, - error: 'RPC message must have a valid componentType', - errorCode: 'RPC_INVALID_COMPONENT_TYPE' - }; - } - - if (!data.methodName || typeof data.methodName !== 'string') { - return { - valid: false, - error: 'RPC message must have a valid methodName', - errorCode: 'RPC_INVALID_METHOD_NAME' - }; - } - - // 检查参数数组 - if (data.parameters && !Array.isArray(data.parameters)) { - return { - valid: false, - error: 'RPC parameters must be an array', - errorCode: 'RPC_INVALID_PARAMETERS' - }; - } - - return { valid: true }; - } - } as ValidationRule, - - /** - * SyncVar 消息验证规则 - */ - SyncVarMessage: { - name: 'SyncVarMessage', - validate: (message: TransportMessage, context: ValidationContext): ValidationResult => { - if (message.type !== 'syncvar') { - return { valid: true }; // 不是 SyncVar 消息,跳过验证 - } - - const data = message.data as any; - - // 检查必需字段 - if (!data.networkId || typeof data.networkId !== 'number') { - return { - valid: false, - error: 'SyncVar message must have a valid networkId', - errorCode: 'SYNCVAR_INVALID_NETWORK_ID' - }; - } - - if (!data.componentType || typeof data.componentType !== 'string') { - return { - valid: false, - error: 'SyncVar message must have a valid componentType', - errorCode: 'SYNCVAR_INVALID_COMPONENT_TYPE' - }; - } - - if (!data.propertyName || typeof data.propertyName !== 'string') { - return { - valid: false, - error: 'SyncVar message must have a valid propertyName', - errorCode: 'SYNCVAR_INVALID_PROPERTY_NAME' - }; - } - - return { valid: true }; - } - } as ValidationRule -}; \ No newline at end of file diff --git a/packages/network-server/src/validation/RpcValidator.ts b/packages/network-server/src/validation/RpcValidator.ts deleted file mode 100644 index 163abd8d..00000000 --- a/packages/network-server/src/validation/RpcValidator.ts +++ /dev/null @@ -1,776 +0,0 @@ -/** - * RPC 验证器 - * - * 专门用于验证 RPC 调用的参数、权限、频率等 - */ - -import { NetworkValue, RpcMetadata } from '@esengine/ecs-framework-network-shared'; -import { ClientConnection } from '../core/ClientConnection'; -import { ValidationResult } from './MessageValidator'; - -/** - * RPC 验证配置 - */ -export interface RpcValidationConfig { - /** 最大参数数量 */ - maxParameterCount?: number; - /** 单个参数最大大小(字节) */ - maxParameterSize?: number; - /** 允许的参数类型 */ - allowedParameterTypes?: string[]; - /** 方法名黑名单 */ - blacklistedMethods?: string[]; - /** 方法名白名单 */ - whitelistedMethods?: string[]; - /** 是否启用参数类型检查 */ - enableTypeCheck?: boolean; - /** 是否启用参数内容过滤 */ - enableContentFilter?: boolean; -} - -/** - * RPC 调用上下文 - */ -export interface RpcCallContext { - /** 客户端连接 */ - client: ClientConnection; - /** 网络对象ID */ - networkId: number; - /** 组件类型 */ - componentType: string; - /** 方法名 */ - methodName: string; - /** 参数列表 */ - parameters: NetworkValue[]; - /** RPC 元数据 */ - metadata: RpcMetadata; - /** RPC 类型 */ - rpcType: 'client-rpc' | 'server-rpc'; -} - -/** - * 参数类型定义 - */ -export interface ParameterTypeDefinition { - /** 参数名 */ - name: string; - /** 参数类型 */ - type: 'string' | 'number' | 'boolean' | 'object' | 'array' | 'any'; - /** 是否必需 */ - required?: boolean; - /** 最小值/长度 */ - min?: number; - /** 最大值/长度 */ - max?: number; - /** 允许的值列表 */ - allowedValues?: NetworkValue[]; - /** 正则表达式(仅用于字符串) */ - pattern?: RegExp; - /** 自定义验证函数 */ - customValidator?: (value: NetworkValue) => ValidationResult; -} - -/** - * 方法签名定义 - */ -export interface MethodSignature { - /** 方法名 */ - methodName: string; - /** 组件类型 */ - componentType: string; - /** 参数定义 */ - parameters: ParameterTypeDefinition[]; - /** 返回值类型 */ - returnType?: string; - /** 是否需要权限验证 */ - requiresAuth?: boolean; - /** 所需权限 */ - requiredPermissions?: string[]; - /** 频率限制(调用/分钟) */ - rateLimit?: number; - /** 自定义验证函数 */ - customValidator?: (context: RpcCallContext) => ValidationResult; -} - -/** - * RPC 频率跟踪 - */ -interface RpcRateTracker { - /** 客户端ID */ - clientId: string; - /** 方法调用计数 */ - methodCalls: Map; - /** 最后更新时间 */ - lastUpdate: Date; -} - -/** - * RPC 验证器 - */ -export class RpcValidator { - private config: RpcValidationConfig; - private methodSignatures = new Map(); - private rateTrackers = new Map(); - private cleanupTimer: NodeJS.Timeout | null = null; - - constructor(config: RpcValidationConfig = {}) { - this.config = { - maxParameterCount: 10, - maxParameterSize: 65536, // 64KB - allowedParameterTypes: ['string', 'number', 'boolean', 'object', 'array'], - blacklistedMethods: [], - whitelistedMethods: [], - enableTypeCheck: true, - enableContentFilter: true, - ...config - }; - - this.initialize(); - } - - /** - * 验证 RPC 调用 - */ - validateRpcCall(context: RpcCallContext): ValidationResult { - try { - // 基本验证 - const basicResult = this.validateBasicRpcCall(context); - if (!basicResult.valid) { - return basicResult; - } - - // 方法名验证 - const methodResult = this.validateMethodName(context); - if (!methodResult.valid) { - return methodResult; - } - - // 权限验证 - const permissionResult = this.validateRpcPermissions(context); - if (!permissionResult.valid) { - return permissionResult; - } - - // 参数验证 - const parameterResult = this.validateParameters(context); - if (!parameterResult.valid) { - return parameterResult; - } - - // 频率限制验证 - const rateResult = this.validateRateLimit(context); - if (!rateResult.valid) { - return rateResult; - } - - // 签名验证(如果有定义) - const signatureResult = this.validateMethodSignature(context); - if (!signatureResult.valid) { - return signatureResult; - } - - return { valid: true }; - - } catch (error) { - return { - valid: false, - error: (error as Error).message, - errorCode: 'RPC_VALIDATION_ERROR' - }; - } - } - - /** - * 注册方法签名 - */ - registerMethodSignature(signature: MethodSignature): void { - const key = `${signature.componentType}.${signature.methodName}`; - this.methodSignatures.set(key, signature); - } - - /** - * 移除方法签名 - */ - removeMethodSignature(componentType: string, methodName: string): boolean { - const key = `${componentType}.${methodName}`; - return this.methodSignatures.delete(key); - } - - /** - * 获取方法签名 - */ - getMethodSignature(componentType: string, methodName: string): MethodSignature | undefined { - const key = `${componentType}.${methodName}`; - return this.methodSignatures.get(key); - } - - /** - * 添加方法到黑名单 - */ - addToBlacklist(methodName: string): void { - if (!this.config.blacklistedMethods!.includes(methodName)) { - this.config.blacklistedMethods!.push(methodName); - } - } - - /** - * 从黑名单移除方法 - */ - removeFromBlacklist(methodName: string): boolean { - const index = this.config.blacklistedMethods!.indexOf(methodName); - if (index !== -1) { - this.config.blacklistedMethods!.splice(index, 1); - return true; - } - return false; - } - - /** - * 添加方法到白名单 - */ - addToWhitelist(methodName: string): void { - if (!this.config.whitelistedMethods!.includes(methodName)) { - this.config.whitelistedMethods!.push(methodName); - } - } - - /** - * 获取客户端的 RPC 统计 - */ - getClientRpcStats(clientId: string): { - totalCalls: number; - methodStats: Record; - } { - const tracker = this.rateTrackers.get(clientId); - if (!tracker) { - return { totalCalls: 0, methodStats: {} }; - } - - let totalCalls = 0; - const methodStats: Record = {}; - - for (const [method, data] of tracker.methodCalls) { - totalCalls += data.count; - methodStats[method] = data.count; - } - - return { totalCalls, methodStats }; - } - - /** - * 重置客户端的频率限制 - */ - resetClientRateLimit(clientId: string): boolean { - const tracker = this.rateTrackers.get(clientId); - if (!tracker) { - return false; - } - - tracker.methodCalls.clear(); - tracker.lastUpdate = new Date(); - return true; - } - - /** - * 销毁验证器 - */ - destroy(): void { - if (this.cleanupTimer) { - clearInterval(this.cleanupTimer); - this.cleanupTimer = null; - } - - this.methodSignatures.clear(); - this.rateTrackers.clear(); - } - - /** - * 初始化验证器 - */ - private initialize(): void { - // 启动清理定时器(每5分钟清理一次) - this.cleanupTimer = setInterval(() => { - this.cleanupRateTrackers(); - }, 5 * 60 * 1000); - } - - /** - * 基本 RPC 调用验证 - */ - private validateBasicRpcCall(context: RpcCallContext): ValidationResult { - // 网络对象ID验证 - if (!Number.isInteger(context.networkId) || context.networkId <= 0) { - return { - valid: false, - error: 'Invalid network object ID', - errorCode: 'INVALID_NETWORK_ID' - }; - } - - // 组件类型验证 - if (!context.componentType || typeof context.componentType !== 'string') { - return { - valid: false, - error: 'Invalid component type', - errorCode: 'INVALID_COMPONENT_TYPE' - }; - } - - // 方法名验证 - if (!context.methodName || typeof context.methodName !== 'string') { - return { - valid: false, - error: 'Invalid method name', - errorCode: 'INVALID_METHOD_NAME' - }; - } - - // 参数数组验证 - if (!Array.isArray(context.parameters)) { - return { - valid: false, - error: 'Parameters must be an array', - errorCode: 'INVALID_PARAMETERS_FORMAT' - }; - } - - // 参数数量检查 - if (context.parameters.length > this.config.maxParameterCount!) { - return { - valid: false, - error: `Too many parameters: ${context.parameters.length} (max: ${this.config.maxParameterCount})`, - errorCode: 'TOO_MANY_PARAMETERS' - }; - } - - return { valid: true }; - } - - /** - * 方法名验证 - */ - private validateMethodName(context: RpcCallContext): ValidationResult { - const methodName = context.methodName; - - // 黑名单检查 - if (this.config.blacklistedMethods!.length > 0) { - if (this.config.blacklistedMethods!.includes(methodName)) { - return { - valid: false, - error: `Method '${methodName}' is blacklisted`, - errorCode: 'METHOD_BLACKLISTED' - }; - } - } - - // 白名单检查 - if (this.config.whitelistedMethods!.length > 0) { - if (!this.config.whitelistedMethods!.includes(methodName)) { - return { - valid: false, - error: `Method '${methodName}' is not whitelisted`, - errorCode: 'METHOD_NOT_WHITELISTED' - }; - } - } - - // 危险方法名检查 - const dangerousPatterns = [ - /^__/, // 私有方法 - /constructor/i, - /prototype/i, - /eval/i, - /function/i - ]; - - for (const pattern of dangerousPatterns) { - if (pattern.test(methodName)) { - return { - valid: false, - error: `Potentially dangerous method name: '${methodName}'`, - errorCode: 'DANGEROUS_METHOD_NAME' - }; - } - } - - return { valid: true }; - } - - /** - * RPC 权限验证 - */ - private validateRpcPermissions(context: RpcCallContext): ValidationResult { - // 基本 RPC 权限检查 - if (!context.client.hasPermission('canSendRpc')) { - return { - valid: false, - error: 'Client does not have RPC permission', - errorCode: 'RPC_PERMISSION_DENIED' - }; - } - - // ServerRpc 特殊权限检查 - if (context.rpcType === 'server-rpc') { - if (context.metadata.requiresAuth && !context.client.isAuthenticated) { - return { - valid: false, - error: 'Authentication required for this RPC', - errorCode: 'AUTHENTICATION_REQUIRED' - }; - } - } - - // 检查方法签名中的权限要求 - const signature = this.getMethodSignature(context.componentType, context.methodName); - if (signature && signature.requiredPermissions) { - for (const permission of signature.requiredPermissions) { - if (!context.client.hasCustomPermission(permission)) { - return { - valid: false, - error: `Required permission '${permission}' not found`, - errorCode: 'INSUFFICIENT_PERMISSIONS' - }; - } - } - } - - return { valid: true }; - } - - /** - * 参数验证 - */ - private validateParameters(context: RpcCallContext): ValidationResult { - // 参数大小检查 - for (let i = 0; i < context.parameters.length; i++) { - const param = context.parameters[i]; - - try { - const serialized = JSON.stringify(param); - const size = new TextEncoder().encode(serialized).length; - - if (size > this.config.maxParameterSize!) { - return { - valid: false, - error: `Parameter ${i} is too large: ${size} bytes (max: ${this.config.maxParameterSize})`, - errorCode: 'PARAMETER_TOO_LARGE' - }; - } - } catch (error) { - return { - valid: false, - error: `Parameter ${i} is not serializable`, - errorCode: 'PARAMETER_NOT_SERIALIZABLE' - }; - } - } - - // 参数类型检查 - if (this.config.enableTypeCheck) { - const typeResult = this.validateParameterTypes(context); - if (!typeResult.valid) { - return typeResult; - } - } - - // 参数内容过滤 - if (this.config.enableContentFilter) { - const contentResult = this.validateParameterContent(context); - if (!contentResult.valid) { - return contentResult; - } - } - - return { valid: true }; - } - - /** - * 参数类型验证 - */ - private validateParameterTypes(context: RpcCallContext): ValidationResult { - for (let i = 0; i < context.parameters.length; i++) { - const param = context.parameters[i]; - const paramType = this.getParameterType(param); - - if (!this.config.allowedParameterTypes!.includes(paramType)) { - return { - valid: false, - error: `Parameter ${i} type '${paramType}' is not allowed`, - errorCode: 'INVALID_PARAMETER_TYPE' - }; - } - } - - return { valid: true }; - } - - /** - * 参数内容验证 - */ - private validateParameterContent(context: RpcCallContext): ValidationResult { - for (let i = 0; i < context.parameters.length; i++) { - const param = context.parameters[i]; - - // 检查危险内容 - if (typeof param === 'string') { - const dangerousPatterns = [ - /