@esengine/server 4.4.0 → 4.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,938 +0,0 @@
1
- import { createLogger } from './chunk-I4QQSQ72.js';
2
- import { __name, __publicField } from './chunk-T626JPC7.js';
3
- import * as path from 'path';
4
- import { createServer as createServer$1 } from 'http';
5
- import { serve } from '@esengine/rpc/server';
6
- import { rpc } from '@esengine/rpc';
7
- import * as fs from 'fs';
8
- import { pathToFileURL } from 'url';
9
-
10
- // src/http/router.ts
11
- var logger = createLogger("HTTP");
12
- function parseRoutePath(path3) {
13
- const paramNames = [];
14
- const isStatic = !path3.includes(":");
15
- if (isStatic) {
16
- return {
17
- pattern: new RegExp(`^${escapeRegex(path3)}$`),
18
- paramNames,
19
- isStatic: true
20
- };
21
- }
22
- const segments = path3.split("/").map((segment) => {
23
- if (segment.startsWith(":")) {
24
- const paramName = segment.slice(1);
25
- paramNames.push(paramName);
26
- return "([^/]+)";
27
- }
28
- return escapeRegex(segment);
29
- });
30
- return {
31
- pattern: new RegExp(`^${segments.join("/")}$`),
32
- paramNames,
33
- isStatic: false
34
- };
35
- }
36
- __name(parseRoutePath, "parseRoutePath");
37
- function escapeRegex(str) {
38
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
39
- }
40
- __name(escapeRegex, "escapeRegex");
41
- function matchRoute(routes, path3, method) {
42
- for (const route of routes) {
43
- if (!route.isStatic) continue;
44
- if (route.method !== "*" && route.method !== method) continue;
45
- if (route.pattern.test(path3)) {
46
- return {
47
- route,
48
- params: {}
49
- };
50
- }
51
- }
52
- for (const route of routes) {
53
- if (route.isStatic) continue;
54
- if (route.method !== "*" && route.method !== method) continue;
55
- const match = path3.match(route.pattern);
56
- if (match) {
57
- const params = {};
58
- route.paramNames.forEach((name, index) => {
59
- params[name] = decodeURIComponent(match[index + 1]);
60
- });
61
- return {
62
- route,
63
- params
64
- };
65
- }
66
- }
67
- return null;
68
- }
69
- __name(matchRoute, "matchRoute");
70
- async function createRequest(req, params = {}) {
71
- const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
72
- const query = {};
73
- url.searchParams.forEach((value, key) => {
74
- query[key] = value;
75
- });
76
- let body = null;
77
- if (req.method === "POST" || req.method === "PUT" || req.method === "PATCH") {
78
- body = await parseBody(req);
79
- }
80
- const ip = req.headers["x-forwarded-for"]?.split(",")[0]?.trim() || req.socket?.remoteAddress || "unknown";
81
- return {
82
- raw: req,
83
- method: req.method ?? "GET",
84
- path: url.pathname,
85
- params,
86
- query,
87
- headers: req.headers,
88
- body,
89
- ip
90
- };
91
- }
92
- __name(createRequest, "createRequest");
93
- function parseBody(req) {
94
- return new Promise((resolve2) => {
95
- const chunks = [];
96
- req.on("data", (chunk) => {
97
- chunks.push(chunk);
98
- });
99
- req.on("end", () => {
100
- const rawBody = Buffer.concat(chunks).toString("utf-8");
101
- if (!rawBody) {
102
- resolve2(null);
103
- return;
104
- }
105
- const contentType = req.headers["content-type"] ?? "";
106
- if (contentType.includes("application/json")) {
107
- try {
108
- resolve2(JSON.parse(rawBody));
109
- } catch {
110
- resolve2(rawBody);
111
- }
112
- } else if (contentType.includes("application/x-www-form-urlencoded")) {
113
- const params = new URLSearchParams(rawBody);
114
- const result = {};
115
- params.forEach((value, key) => {
116
- result[key] = value;
117
- });
118
- resolve2(result);
119
- } else {
120
- resolve2(rawBody);
121
- }
122
- });
123
- req.on("error", () => {
124
- resolve2(null);
125
- });
126
- });
127
- }
128
- __name(parseBody, "parseBody");
129
- function createResponse(res) {
130
- let statusCode = 200;
131
- let ended = false;
132
- const response = {
133
- raw: res,
134
- status(code) {
135
- statusCode = code;
136
- return response;
137
- },
138
- header(name, value) {
139
- if (!ended) {
140
- res.setHeader(name, value);
141
- }
142
- return response;
143
- },
144
- json(data) {
145
- if (ended) return;
146
- ended = true;
147
- res.setHeader("Content-Type", "application/json; charset=utf-8");
148
- res.statusCode = statusCode;
149
- res.end(JSON.stringify(data));
150
- },
151
- text(data) {
152
- if (ended) return;
153
- ended = true;
154
- res.setHeader("Content-Type", "text/plain; charset=utf-8");
155
- res.statusCode = statusCode;
156
- res.end(data);
157
- },
158
- error(code, message) {
159
- if (ended) return;
160
- ended = true;
161
- res.setHeader("Content-Type", "application/json; charset=utf-8");
162
- res.statusCode = code;
163
- res.end(JSON.stringify({
164
- error: message
165
- }));
166
- }
167
- };
168
- return response;
169
- }
170
- __name(createResponse, "createResponse");
171
- function createOriginWhitelist(origins) {
172
- const whitelist = {};
173
- for (const origin of origins) {
174
- whitelist[origin] = true;
175
- }
176
- return whitelist;
177
- }
178
- __name(createOriginWhitelist, "createOriginWhitelist");
179
- function applyCors(res, req, cors) {
180
- const credentials = cors.credentials ?? false;
181
- if (typeof cors.origin === "string" && cors.origin !== "*") {
182
- res.setHeader("Access-Control-Allow-Origin", cors.origin);
183
- if (credentials) {
184
- res.setHeader("Access-Control-Allow-Credentials", "true");
185
- }
186
- } else if (Array.isArray(cors.origin)) {
187
- const requestOrigin = req.headers.origin;
188
- if (typeof requestOrigin === "string") {
189
- const whitelist = createOriginWhitelist(cors.origin);
190
- if (requestOrigin in whitelist) {
191
- res.setHeader("Access-Control-Allow-Origin", requestOrigin);
192
- if (credentials) {
193
- res.setHeader("Access-Control-Allow-Credentials", "true");
194
- }
195
- }
196
- }
197
- } else if (!credentials) {
198
- if (cors.origin === "*" || cors.origin === true) {
199
- res.setHeader("Access-Control-Allow-Origin", "*");
200
- }
201
- }
202
- res.setHeader("Access-Control-Allow-Methods", cors.methods?.join(", ") ?? "GET, POST, PUT, DELETE, PATCH, OPTIONS");
203
- res.setHeader("Access-Control-Allow-Headers", cors.allowedHeaders?.join(", ") ?? "Content-Type, Authorization");
204
- if (cors.maxAge) {
205
- res.setHeader("Access-Control-Max-Age", String(cors.maxAge));
206
- }
207
- }
208
- __name(applyCors, "applyCors");
209
- async function executeMiddlewares(middlewares, req, res, finalHandler) {
210
- let index = 0;
211
- const next = /* @__PURE__ */ __name(async () => {
212
- if (index < middlewares.length) {
213
- const middleware = middlewares[index++];
214
- await middleware(req, res, next);
215
- } else {
216
- await finalHandler();
217
- }
218
- }, "next");
219
- await next();
220
- }
221
- __name(executeMiddlewares, "executeMiddlewares");
222
- async function executeWithTimeout(handler, timeoutMs, res) {
223
- let resolved = false;
224
- const timeoutPromise = new Promise((_, reject) => {
225
- setTimeout(() => {
226
- if (!resolved) {
227
- reject(new Error("Request timeout"));
228
- }
229
- }, timeoutMs);
230
- });
231
- try {
232
- await Promise.race([
233
- handler().then(() => {
234
- resolved = true;
235
- }),
236
- timeoutPromise
237
- ]);
238
- } catch (error) {
239
- if (error instanceof Error && error.message === "Request timeout") {
240
- if (!res.writableEnded) {
241
- res.statusCode = 408;
242
- res.setHeader("Content-Type", "application/json; charset=utf-8");
243
- res.end(JSON.stringify({
244
- error: "Request Timeout"
245
- }));
246
- }
247
- } else {
248
- throw error;
249
- }
250
- }
251
- }
252
- __name(executeWithTimeout, "executeWithTimeout");
253
- function isHandlerDefinition(value) {
254
- return typeof value === "object" && value !== null && "handler" in value && typeof value.handler === "function";
255
- }
256
- __name(isHandlerDefinition, "isHandlerDefinition");
257
- function isRouteMethods(value) {
258
- if (typeof value !== "object" || value === null) return false;
259
- const methods = [
260
- "GET",
261
- "POST",
262
- "PUT",
263
- "DELETE",
264
- "PATCH",
265
- "OPTIONS"
266
- ];
267
- return Object.keys(value).some((key) => methods.includes(key));
268
- }
269
- __name(isRouteMethods, "isRouteMethods");
270
- function extractHandler(methodHandler) {
271
- if (isHandlerDefinition(methodHandler)) {
272
- return {
273
- handler: methodHandler.handler,
274
- middlewares: methodHandler.middlewares ?? [],
275
- timeout: methodHandler.timeout
276
- };
277
- }
278
- return {
279
- handler: methodHandler,
280
- middlewares: [],
281
- timeout: void 0
282
- };
283
- }
284
- __name(extractHandler, "extractHandler");
285
- function createHttpRouter(routes, options = {}) {
286
- const globalMiddlewares = options.middlewares ?? [];
287
- const globalTimeout = options.timeout;
288
- const parsedRoutes = [];
289
- for (const [path3, handlerOrMethods] of Object.entries(routes)) {
290
- const { pattern, paramNames, isStatic } = parseRoutePath(path3);
291
- if (typeof handlerOrMethods === "function") {
292
- parsedRoutes.push({
293
- method: "*",
294
- path: path3,
295
- handler: handlerOrMethods,
296
- pattern,
297
- paramNames,
298
- middlewares: [],
299
- timeout: void 0,
300
- isStatic
301
- });
302
- } else if (isRouteMethods(handlerOrMethods)) {
303
- for (const [method, methodHandler] of Object.entries(handlerOrMethods)) {
304
- if (methodHandler !== void 0) {
305
- const { handler, middlewares, timeout } = extractHandler(methodHandler);
306
- parsedRoutes.push({
307
- method,
308
- path: path3,
309
- handler,
310
- pattern,
311
- paramNames,
312
- middlewares,
313
- timeout,
314
- isStatic
315
- });
316
- }
317
- }
318
- } else if (isHandlerDefinition(handlerOrMethods)) {
319
- const { handler, middlewares, timeout } = extractHandler(handlerOrMethods);
320
- parsedRoutes.push({
321
- method: "*",
322
- path: path3,
323
- handler,
324
- pattern,
325
- paramNames,
326
- middlewares,
327
- timeout,
328
- isStatic
329
- });
330
- }
331
- }
332
- const corsOptions = options.cors === true ? {
333
- origin: "*"
334
- } : options.cors === false ? null : options.cors ?? null;
335
- return /* @__PURE__ */ __name(async function handleRequest(req, res) {
336
- const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
337
- const path3 = url.pathname;
338
- const method = req.method ?? "GET";
339
- if (corsOptions) {
340
- applyCors(res, req, corsOptions);
341
- if (method === "OPTIONS") {
342
- res.statusCode = 204;
343
- res.end();
344
- return true;
345
- }
346
- }
347
- const match = matchRoute(parsedRoutes, path3, method);
348
- if (!match) {
349
- return false;
350
- }
351
- const { route, params } = match;
352
- try {
353
- const httpReq = await createRequest(req, params);
354
- const httpRes = createResponse(res);
355
- const allMiddlewares = [
356
- ...globalMiddlewares,
357
- ...route.middlewares
358
- ];
359
- const timeout = route.timeout ?? globalTimeout;
360
- const finalHandler = /* @__PURE__ */ __name(async () => {
361
- await route.handler(httpReq, httpRes);
362
- }, "finalHandler");
363
- const executeHandler = /* @__PURE__ */ __name(async () => {
364
- if (allMiddlewares.length > 0) {
365
- await executeMiddlewares(allMiddlewares, httpReq, httpRes, finalHandler);
366
- } else {
367
- await finalHandler();
368
- }
369
- }, "executeHandler");
370
- if (timeout && timeout > 0) {
371
- await executeWithTimeout(executeHandler, timeout, res);
372
- } else {
373
- await executeHandler();
374
- }
375
- return true;
376
- } catch (error) {
377
- logger.error("Route handler error:", error);
378
- if (!res.writableEnded) {
379
- res.statusCode = 500;
380
- res.setHeader("Content-Type", "application/json");
381
- res.end(JSON.stringify({
382
- error: "Internal Server Error"
383
- }));
384
- }
385
- return true;
386
- }
387
- }, "handleRequest");
388
- }
389
- __name(createHttpRouter, "createHttpRouter");
390
- var logger2 = createLogger("Server");
391
- function fileNameToHandlerName(fileName) {
392
- const baseName = fileName.replace(/\.(ts|js|mts|mjs)$/, "");
393
- return baseName.split(/[-_]/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
394
- }
395
- __name(fileNameToHandlerName, "fileNameToHandlerName");
396
- function scanDirectory(dir) {
397
- if (!fs.existsSync(dir)) {
398
- return [];
399
- }
400
- const files = [];
401
- const entries = fs.readdirSync(dir, {
402
- withFileTypes: true
403
- });
404
- for (const entry of entries) {
405
- if (entry.isFile() && /\.(ts|js|mts|mjs)$/.test(entry.name)) {
406
- if (entry.name.startsWith("_") || entry.name.startsWith("index.")) {
407
- continue;
408
- }
409
- files.push(path.join(dir, entry.name));
410
- }
411
- }
412
- return files;
413
- }
414
- __name(scanDirectory, "scanDirectory");
415
- async function loadApiHandlers(apiDir) {
416
- const files = scanDirectory(apiDir);
417
- const handlers = [];
418
- for (const filePath of files) {
419
- try {
420
- const fileUrl = pathToFileURL(filePath).href;
421
- const module = await import(fileUrl);
422
- const definition = module.default;
423
- if (definition && typeof definition.handler === "function") {
424
- const name = fileNameToHandlerName(path.basename(filePath));
425
- handlers.push({
426
- name,
427
- path: filePath,
428
- definition
429
- });
430
- }
431
- } catch (err) {
432
- logger2.warn(`Failed to load API handler: ${filePath}`, err);
433
- }
434
- }
435
- return handlers;
436
- }
437
- __name(loadApiHandlers, "loadApiHandlers");
438
- async function loadMsgHandlers(msgDir) {
439
- const files = scanDirectory(msgDir);
440
- const handlers = [];
441
- for (const filePath of files) {
442
- try {
443
- const fileUrl = pathToFileURL(filePath).href;
444
- const module = await import(fileUrl);
445
- const definition = module.default;
446
- if (definition && typeof definition.handler === "function") {
447
- const name = fileNameToHandlerName(path.basename(filePath));
448
- handlers.push({
449
- name,
450
- path: filePath,
451
- definition
452
- });
453
- }
454
- } catch (err) {
455
- logger2.warn(`Failed to load msg handler: ${filePath}`, err);
456
- }
457
- }
458
- return handlers;
459
- }
460
- __name(loadMsgHandlers, "loadMsgHandlers");
461
- function scanDirectoryRecursive(dir, baseDir = dir) {
462
- if (!fs.existsSync(dir)) {
463
- return [];
464
- }
465
- const files = [];
466
- const entries = fs.readdirSync(dir, {
467
- withFileTypes: true
468
- });
469
- for (const entry of entries) {
470
- const fullPath = path.join(dir, entry.name);
471
- if (entry.isDirectory()) {
472
- files.push(...scanDirectoryRecursive(fullPath, baseDir));
473
- } else if (entry.isFile() && /\.(ts|js|mts|mjs)$/.test(entry.name)) {
474
- if (entry.name.startsWith("_") || entry.name.startsWith("index.")) {
475
- continue;
476
- }
477
- const relativePath = path.relative(baseDir, fullPath);
478
- files.push({
479
- filePath: fullPath,
480
- relativePath
481
- });
482
- }
483
- }
484
- return files;
485
- }
486
- __name(scanDirectoryRecursive, "scanDirectoryRecursive");
487
- function filePathToRoute(relativePath, prefix) {
488
- let route = relativePath.replace(/\.(ts|js|mts|mjs)$/, "").replace(/\\/g, "/").replace(/\[(\w+)\]/g, ":$1");
489
- if (!route.startsWith("/")) {
490
- route = "/" + route;
491
- }
492
- const fullRoute = prefix.endsWith("/") ? prefix.slice(0, -1) + route : prefix + route;
493
- return fullRoute;
494
- }
495
- __name(filePathToRoute, "filePathToRoute");
496
- async function loadHttpHandlers(httpDir, prefix = "/api") {
497
- const files = scanDirectoryRecursive(httpDir);
498
- const handlers = [];
499
- for (const { filePath, relativePath } of files) {
500
- try {
501
- const fileUrl = pathToFileURL(filePath).href;
502
- const module = await import(fileUrl);
503
- const definition = module.default;
504
- if (definition && typeof definition.handler === "function") {
505
- const route = filePathToRoute(relativePath, prefix);
506
- const method = definition.method ?? "POST";
507
- handlers.push({
508
- route,
509
- method,
510
- path: filePath,
511
- definition
512
- });
513
- }
514
- } catch (err) {
515
- logger2.warn(`Failed to load HTTP handler: ${filePath}`, err);
516
- }
517
- }
518
- return handlers;
519
- }
520
- __name(loadHttpHandlers, "loadHttpHandlers");
521
-
522
- // src/room/RoomManager.ts
523
- var logger3 = createLogger("Room");
524
- var _RoomManager = class _RoomManager {
525
- constructor(sendFn) {
526
- __publicField(this, "_definitions", /* @__PURE__ */ new Map());
527
- __publicField(this, "_rooms", /* @__PURE__ */ new Map());
528
- __publicField(this, "_playerToRoom", /* @__PURE__ */ new Map());
529
- __publicField(this, "_nextRoomId", 1);
530
- __publicField(this, "_sendFn");
531
- this._sendFn = sendFn;
532
- }
533
- /**
534
- * @zh 注册房间类型
535
- * @en Define room type
536
- */
537
- define(name, roomClass) {
538
- this._definitions.set(name, {
539
- roomClass
540
- });
541
- }
542
- /**
543
- * @zh 创建房间
544
- * @en Create room
545
- */
546
- async create(name, options) {
547
- const def = this._definitions.get(name);
548
- if (!def) {
549
- logger3.warn(`Room type not found: ${name}`);
550
- return null;
551
- }
552
- const roomId = this._generateRoomId();
553
- const room = new def.roomClass();
554
- room._init({
555
- id: roomId,
556
- sendFn: this._sendFn,
557
- broadcastFn: /* @__PURE__ */ __name((type, data) => {
558
- for (const player of room.players) {
559
- player.send(type, data);
560
- }
561
- }, "broadcastFn"),
562
- disposeFn: /* @__PURE__ */ __name(() => {
563
- this._rooms.delete(roomId);
564
- }, "disposeFn")
565
- });
566
- this._rooms.set(roomId, room);
567
- await room._create(options);
568
- logger3.info(`Created: ${name} (${roomId})`);
569
- return room;
570
- }
571
- /**
572
- * @zh 加入或创建房间
573
- * @en Join or create room
574
- */
575
- async joinOrCreate(name, playerId, conn, options) {
576
- let room = this._findAvailableRoom(name);
577
- if (!room) {
578
- room = await this.create(name, options);
579
- if (!room) return null;
580
- }
581
- const player = await room._addPlayer(playerId, conn);
582
- if (!player) return null;
583
- this._playerToRoom.set(playerId, room.id);
584
- logger3.info(`Player ${playerId} joined ${room.id}`);
585
- return {
586
- room,
587
- player
588
- };
589
- }
590
- /**
591
- * @zh 加入指定房间
592
- * @en Join specific room
593
- */
594
- async joinById(roomId, playerId, conn) {
595
- const room = this._rooms.get(roomId);
596
- if (!room) return null;
597
- const player = await room._addPlayer(playerId, conn);
598
- if (!player) return null;
599
- this._playerToRoom.set(playerId, room.id);
600
- logger3.info(`Player ${playerId} joined ${room.id}`);
601
- return {
602
- room,
603
- player
604
- };
605
- }
606
- /**
607
- * @zh 玩家离开
608
- * @en Player leave
609
- */
610
- async leave(playerId, reason) {
611
- const roomId = this._playerToRoom.get(playerId);
612
- if (!roomId) return;
613
- const room = this._rooms.get(roomId);
614
- if (room) {
615
- await room._removePlayer(playerId, reason);
616
- }
617
- this._playerToRoom.delete(playerId);
618
- logger3.info(`Player ${playerId} left ${roomId}`);
619
- }
620
- /**
621
- * @zh 处理消息
622
- * @en Handle message
623
- */
624
- handleMessage(playerId, type, data) {
625
- const roomId = this._playerToRoom.get(playerId);
626
- if (!roomId) return;
627
- const room = this._rooms.get(roomId);
628
- if (room) {
629
- room._handleMessage(type, data, playerId);
630
- }
631
- }
632
- /**
633
- * @zh 获取房间
634
- * @en Get room
635
- */
636
- getRoom(roomId) {
637
- return this._rooms.get(roomId);
638
- }
639
- /**
640
- * @zh 获取玩家所在房间
641
- * @en Get player's room
642
- */
643
- getPlayerRoom(playerId) {
644
- const roomId = this._playerToRoom.get(playerId);
645
- return roomId ? this._rooms.get(roomId) : void 0;
646
- }
647
- /**
648
- * @zh 获取所有房间
649
- * @en Get all rooms
650
- */
651
- getRooms() {
652
- return Array.from(this._rooms.values());
653
- }
654
- /**
655
- * @zh 获取指定类型的所有房间
656
- * @en Get all rooms of a type
657
- */
658
- getRoomsByType(name) {
659
- const def = this._definitions.get(name);
660
- if (!def) return [];
661
- return Array.from(this._rooms.values()).filter((room) => room instanceof def.roomClass);
662
- }
663
- _findAvailableRoom(name) {
664
- const def = this._definitions.get(name);
665
- if (!def) return null;
666
- for (const room of this._rooms.values()) {
667
- if (room instanceof def.roomClass && !room.isFull && !room.isLocked && !room.isDisposed) {
668
- return room;
669
- }
670
- }
671
- return null;
672
- }
673
- _generateRoomId() {
674
- return `room_${this._nextRoomId++}`;
675
- }
676
- };
677
- __name(_RoomManager, "RoomManager");
678
- var RoomManager = _RoomManager;
679
-
680
- // src/core/server.ts
681
- var DEFAULT_CONFIG = {
682
- port: 3e3,
683
- apiDir: "src/api",
684
- msgDir: "src/msg",
685
- httpDir: "src/http",
686
- httpPrefix: "/api",
687
- tickRate: 20
688
- };
689
- async function createServer(config = {}) {
690
- const opts = {
691
- ...DEFAULT_CONFIG,
692
- ...config
693
- };
694
- const cwd = process.cwd();
695
- const logger4 = createLogger("Server");
696
- const apiHandlers = await loadApiHandlers(path.resolve(cwd, opts.apiDir));
697
- const msgHandlers = await loadMsgHandlers(path.resolve(cwd, opts.msgDir));
698
- const httpDir = config.httpDir ?? opts.httpDir;
699
- const httpPrefix = config.httpPrefix ?? opts.httpPrefix;
700
- const httpHandlers = await loadHttpHandlers(path.resolve(cwd, httpDir), httpPrefix);
701
- if (apiHandlers.length > 0) {
702
- logger4.info(`Loaded ${apiHandlers.length} API handlers`);
703
- }
704
- if (msgHandlers.length > 0) {
705
- logger4.info(`Loaded ${msgHandlers.length} message handlers`);
706
- }
707
- if (httpHandlers.length > 0) {
708
- logger4.info(`Loaded ${httpHandlers.length} HTTP handlers`);
709
- }
710
- const mergedHttpRoutes = {};
711
- for (const handler of httpHandlers) {
712
- const existingRoute = mergedHttpRoutes[handler.route];
713
- if (existingRoute && typeof existingRoute !== "function") {
714
- existingRoute[handler.method] = handler.definition.handler;
715
- } else {
716
- mergedHttpRoutes[handler.route] = {
717
- [handler.method]: handler.definition.handler
718
- };
719
- }
720
- }
721
- if (config.http) {
722
- for (const [route, handlerOrMethods] of Object.entries(config.http)) {
723
- if (typeof handlerOrMethods === "function") {
724
- mergedHttpRoutes[route] = handlerOrMethods;
725
- } else {
726
- const existing = mergedHttpRoutes[route];
727
- if (existing && typeof existing !== "function") {
728
- Object.assign(existing, handlerOrMethods);
729
- } else {
730
- mergedHttpRoutes[route] = handlerOrMethods;
731
- }
732
- }
733
- }
734
- }
735
- const hasHttpRoutes = Object.keys(mergedHttpRoutes).length > 0;
736
- const apiDefs = {
737
- // 内置 API
738
- JoinRoom: rpc.api(),
739
- LeaveRoom: rpc.api()
740
- };
741
- const msgDefs = {
742
- // 内置消息(房间消息透传)
743
- RoomMessage: rpc.msg()
744
- };
745
- for (const handler of apiHandlers) {
746
- apiDefs[handler.name] = rpc.api();
747
- }
748
- for (const handler of msgHandlers) {
749
- msgDefs[handler.name] = rpc.msg();
750
- }
751
- const protocol = rpc.define({
752
- api: apiDefs,
753
- msg: msgDefs
754
- });
755
- let currentTick = 0;
756
- let tickInterval = null;
757
- let rpcServer = null;
758
- let httpServer = null;
759
- const roomManager = new RoomManager((conn, type, data) => {
760
- rpcServer?.send(conn, "RoomMessage", {
761
- type,
762
- data
763
- });
764
- });
765
- const apiMap = {};
766
- for (const handler of apiHandlers) {
767
- apiMap[handler.name] = handler;
768
- }
769
- const msgMap = {};
770
- for (const handler of msgHandlers) {
771
- msgMap[handler.name] = handler;
772
- }
773
- const gameServer = {
774
- get connections() {
775
- return rpcServer?.connections ?? [];
776
- },
777
- get tick() {
778
- return currentTick;
779
- },
780
- get rooms() {
781
- return roomManager;
782
- },
783
- /**
784
- * @zh 注册房间类型
785
- * @en Define room type
786
- */
787
- define(name, roomClass) {
788
- roomManager.define(name, roomClass);
789
- },
790
- async start() {
791
- const apiHandlersObj = {};
792
- apiHandlersObj["JoinRoom"] = async (input, conn) => {
793
- const { roomType, roomId, options } = input;
794
- if (roomId) {
795
- const result = await roomManager.joinById(roomId, conn.id, conn);
796
- if (!result) {
797
- throw new Error("Failed to join room");
798
- }
799
- return {
800
- roomId: result.room.id,
801
- playerId: result.player.id
802
- };
803
- }
804
- if (roomType) {
805
- const result = await roomManager.joinOrCreate(roomType, conn.id, conn, options);
806
- if (!result) {
807
- throw new Error("Failed to join or create room");
808
- }
809
- return {
810
- roomId: result.room.id,
811
- playerId: result.player.id
812
- };
813
- }
814
- throw new Error("roomType or roomId required");
815
- };
816
- apiHandlersObj["LeaveRoom"] = async (_input, conn) => {
817
- await roomManager.leave(conn.id);
818
- return {
819
- success: true
820
- };
821
- };
822
- for (const [name, handler] of Object.entries(apiMap)) {
823
- apiHandlersObj[name] = async (input, conn) => {
824
- const ctx = {
825
- conn,
826
- server: gameServer
827
- };
828
- return handler.definition.handler(input, ctx);
829
- };
830
- }
831
- const msgHandlersObj = {};
832
- msgHandlersObj["RoomMessage"] = async (data, conn) => {
833
- const { type, data: payload } = data;
834
- roomManager.handleMessage(conn.id, type, payload);
835
- };
836
- for (const [name, handler] of Object.entries(msgMap)) {
837
- msgHandlersObj[name] = async (data, conn) => {
838
- const ctx = {
839
- conn,
840
- server: gameServer
841
- };
842
- await handler.definition.handler(data, ctx);
843
- };
844
- }
845
- if (hasHttpRoutes) {
846
- const httpRouter = createHttpRouter(mergedHttpRoutes, {
847
- cors: config.cors ?? true
848
- });
849
- httpServer = createServer$1(async (req, res) => {
850
- const handled = await httpRouter(req, res);
851
- if (!handled) {
852
- res.statusCode = 404;
853
- res.setHeader("Content-Type", "application/json");
854
- res.end(JSON.stringify({
855
- error: "Not Found"
856
- }));
857
- }
858
- });
859
- rpcServer = serve(protocol, {
860
- server: httpServer,
861
- createConnData: /* @__PURE__ */ __name(() => ({}), "createConnData"),
862
- onStart: /* @__PURE__ */ __name(() => {
863
- logger4.info(`Started on http://localhost:${opts.port}`);
864
- opts.onStart?.(opts.port);
865
- }, "onStart"),
866
- onConnect: /* @__PURE__ */ __name(async (conn) => {
867
- await config.onConnect?.(conn);
868
- }, "onConnect"),
869
- onDisconnect: /* @__PURE__ */ __name(async (conn) => {
870
- await roomManager?.leave(conn.id, "disconnected");
871
- await config.onDisconnect?.(conn);
872
- }, "onDisconnect"),
873
- api: apiHandlersObj,
874
- msg: msgHandlersObj
875
- });
876
- await rpcServer.start();
877
- await new Promise((resolve2) => {
878
- httpServer.listen(opts.port, () => resolve2());
879
- });
880
- } else {
881
- rpcServer = serve(protocol, {
882
- port: opts.port,
883
- createConnData: /* @__PURE__ */ __name(() => ({}), "createConnData"),
884
- onStart: /* @__PURE__ */ __name((p) => {
885
- logger4.info(`Started on ws://localhost:${p}`);
886
- opts.onStart?.(p);
887
- }, "onStart"),
888
- onConnect: /* @__PURE__ */ __name(async (conn) => {
889
- await config.onConnect?.(conn);
890
- }, "onConnect"),
891
- onDisconnect: /* @__PURE__ */ __name(async (conn) => {
892
- await roomManager?.leave(conn.id, "disconnected");
893
- await config.onDisconnect?.(conn);
894
- }, "onDisconnect"),
895
- api: apiHandlersObj,
896
- msg: msgHandlersObj
897
- });
898
- await rpcServer.start();
899
- }
900
- if (opts.tickRate > 0) {
901
- tickInterval = setInterval(() => {
902
- currentTick++;
903
- }, 1e3 / opts.tickRate);
904
- }
905
- },
906
- async stop() {
907
- if (tickInterval) {
908
- clearInterval(tickInterval);
909
- tickInterval = null;
910
- }
911
- if (rpcServer) {
912
- await rpcServer.stop();
913
- rpcServer = null;
914
- }
915
- if (httpServer) {
916
- await new Promise((resolve2, reject) => {
917
- httpServer.close((err) => {
918
- if (err) reject(err);
919
- else resolve2();
920
- });
921
- });
922
- httpServer = null;
923
- }
924
- },
925
- broadcast(name, data) {
926
- rpcServer?.broadcast(name, data);
927
- },
928
- send(conn, name, data) {
929
- rpcServer?.send(conn, name, data);
930
- }
931
- };
932
- return gameServer;
933
- }
934
- __name(createServer, "createServer");
935
-
936
- export { createHttpRouter, createServer };
937
- //# sourceMappingURL=chunk-M7VONMZJ.js.map
938
- //# sourceMappingURL=chunk-M7VONMZJ.js.map