@air-jam/server 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/{chunk-KMAEESOE.js → chunk-CGONPUNG.js} +363 -13
- package/dist/chunk-CGONPUNG.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +12 -10
- package/dist/chunk-KMAEESOE.js.map +0 -1
|
@@ -6,7 +6,9 @@ import {
|
|
|
6
6
|
controllerStateSchema,
|
|
7
7
|
controllerSystemSchema,
|
|
8
8
|
ErrorCode,
|
|
9
|
+
hostCreateRoomSchema,
|
|
9
10
|
hostJoinAsChildSchema,
|
|
11
|
+
hostReconnectSchema,
|
|
10
12
|
hostRegisterSystemSchema,
|
|
11
13
|
hostRegistrationSchema,
|
|
12
14
|
systemLaunchGameSchema
|
|
@@ -44,12 +46,12 @@ var db = client ? drizzle(client) : null;
|
|
|
44
46
|
var AuthService = class {
|
|
45
47
|
masterKey;
|
|
46
48
|
databaseUrl;
|
|
47
|
-
|
|
49
|
+
authMode;
|
|
48
50
|
constructor() {
|
|
49
51
|
this.masterKey = process.env.AIR_JAM_MASTER_KEY;
|
|
50
52
|
this.databaseUrl = process.env.DATABASE_URL;
|
|
51
|
-
this.
|
|
52
|
-
if (this.
|
|
53
|
+
this.authMode = this.resolveAuthMode();
|
|
54
|
+
if (this.authMode === "disabled") {
|
|
53
55
|
console.log(
|
|
54
56
|
"[server] Running in development mode - authentication disabled"
|
|
55
57
|
);
|
|
@@ -59,15 +61,19 @@ var AuthService = class {
|
|
|
59
61
|
);
|
|
60
62
|
} else if (this.databaseUrl) {
|
|
61
63
|
console.log("[server] Running with database authentication");
|
|
64
|
+
} else {
|
|
65
|
+
console.log(
|
|
66
|
+
"[server] Authentication required, but no auth backend is configured (set AIR_JAM_MASTER_KEY or DATABASE_URL)"
|
|
67
|
+
);
|
|
62
68
|
}
|
|
63
69
|
}
|
|
64
70
|
/**
|
|
65
71
|
* Verify an API key
|
|
66
72
|
* Returns verification result with optional error message
|
|
67
|
-
* In dev mode, always returns success
|
|
73
|
+
* In local/dev mode, always returns success
|
|
68
74
|
*/
|
|
69
75
|
async verifyApiKey(apiKey) {
|
|
70
|
-
if (this.
|
|
76
|
+
if (this.authMode === "disabled") {
|
|
71
77
|
return { isVerified: true };
|
|
72
78
|
}
|
|
73
79
|
if (!apiKey) {
|
|
@@ -105,9 +111,66 @@ var AuthService = class {
|
|
|
105
111
|
};
|
|
106
112
|
}
|
|
107
113
|
}
|
|
114
|
+
resolveAuthMode() {
|
|
115
|
+
const configuredMode = process.env.AIR_JAM_AUTH_MODE?.toLowerCase();
|
|
116
|
+
if (configuredMode === "disabled") {
|
|
117
|
+
return "disabled";
|
|
118
|
+
}
|
|
119
|
+
if (configuredMode === "required") {
|
|
120
|
+
return "required";
|
|
121
|
+
}
|
|
122
|
+
if (this.masterKey || this.databaseUrl) {
|
|
123
|
+
return "required";
|
|
124
|
+
}
|
|
125
|
+
if (process.env.NODE_ENV === "production") {
|
|
126
|
+
return "required";
|
|
127
|
+
}
|
|
128
|
+
return "disabled";
|
|
129
|
+
}
|
|
108
130
|
};
|
|
109
131
|
var authService = new AuthService();
|
|
110
132
|
|
|
133
|
+
// src/services/rate-limit-service.ts
|
|
134
|
+
var RateLimitService = class {
|
|
135
|
+
entries = /* @__PURE__ */ new Map();
|
|
136
|
+
checks = 0;
|
|
137
|
+
check(key, limit, windowMs) {
|
|
138
|
+
if (limit <= 0 || windowMs <= 0) {
|
|
139
|
+
return { allowed: true, retryAfterMs: 0 };
|
|
140
|
+
}
|
|
141
|
+
const now = Date.now();
|
|
142
|
+
const existing = this.entries.get(key);
|
|
143
|
+
if (!existing || now >= existing.resetAt) {
|
|
144
|
+
this.entries.set(key, { count: 1, resetAt: now + windowMs });
|
|
145
|
+
this.maybeCleanup(now);
|
|
146
|
+
return { allowed: true, retryAfterMs: 0 };
|
|
147
|
+
}
|
|
148
|
+
if (existing.count >= limit) {
|
|
149
|
+
this.maybeCleanup(now);
|
|
150
|
+
return {
|
|
151
|
+
allowed: false,
|
|
152
|
+
retryAfterMs: Math.max(existing.resetAt - now, 0)
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
existing.count += 1;
|
|
156
|
+
this.entries.set(key, existing);
|
|
157
|
+
this.maybeCleanup(now);
|
|
158
|
+
return { allowed: true, retryAfterMs: 0 };
|
|
159
|
+
}
|
|
160
|
+
maybeCleanup(now) {
|
|
161
|
+
this.checks += 1;
|
|
162
|
+
if (this.checks % 200 !== 0) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
for (const [key, entry] of this.entries) {
|
|
166
|
+
if (now >= entry.resetAt) {
|
|
167
|
+
this.entries.delete(key);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
var rateLimitService = new RateLimitService();
|
|
173
|
+
|
|
111
174
|
// src/services/room-manager.ts
|
|
112
175
|
var RoomManager = class {
|
|
113
176
|
rooms = /* @__PURE__ */ new Map();
|
|
@@ -198,10 +261,42 @@ var RoomManager = class {
|
|
|
198
261
|
};
|
|
199
262
|
var roomManager = new RoomManager();
|
|
200
263
|
|
|
264
|
+
// src/utils/ids.ts
|
|
265
|
+
import { roomCodeSchema } from "@air-jam/sdk/protocol";
|
|
266
|
+
import { randomInt } from "crypto";
|
|
267
|
+
var alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
268
|
+
var generateRoomCode = () => {
|
|
269
|
+
const code = Array.from(
|
|
270
|
+
{ length: 4 },
|
|
271
|
+
() => alphabet[randomInt(0, alphabet.length)]
|
|
272
|
+
).join("");
|
|
273
|
+
return roomCodeSchema.parse(code);
|
|
274
|
+
};
|
|
275
|
+
|
|
201
276
|
// src/index.ts
|
|
277
|
+
var lastServerInputLogTime = 0;
|
|
278
|
+
var lastServerInputFailLogTime = 0;
|
|
279
|
+
var parsePositiveInt = (value, fallback) => {
|
|
280
|
+
const parsed = Number(value);
|
|
281
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
282
|
+
};
|
|
202
283
|
var PORT = Number(process.env.PORT ?? 4e3);
|
|
284
|
+
var RATE_LIMIT_WINDOW_MS = parsePositiveInt(
|
|
285
|
+
process.env.AIR_JAM_RATE_LIMIT_WINDOW_MS,
|
|
286
|
+
6e4
|
|
287
|
+
);
|
|
288
|
+
var HOST_REGISTRATION_RATE_LIMIT_MAX = parsePositiveInt(
|
|
289
|
+
process.env.AIR_JAM_HOST_REGISTRATION_RATE_LIMIT_MAX,
|
|
290
|
+
30
|
|
291
|
+
);
|
|
292
|
+
var CONTROLLER_JOIN_RATE_LIMIT_MAX = parsePositiveInt(
|
|
293
|
+
process.env.AIR_JAM_CONTROLLER_JOIN_RATE_LIMIT_MAX,
|
|
294
|
+
120
|
|
295
|
+
);
|
|
296
|
+
var allowedOrigins = process.env.AIR_JAM_ALLOWED_ORIGINS?.split(",").map((origin) => origin.trim()).filter(Boolean);
|
|
297
|
+
var corsOrigin = allowedOrigins && allowedOrigins.length > 0 ? allowedOrigins : "*";
|
|
203
298
|
var app = express();
|
|
204
|
-
app.use(cors());
|
|
299
|
+
app.use(cors({ origin: corsOrigin }));
|
|
205
300
|
app.use(express.json());
|
|
206
301
|
app.get("/health", (_, res) => {
|
|
207
302
|
res.json({ ok: true });
|
|
@@ -209,7 +304,7 @@ app.get("/health", (_, res) => {
|
|
|
209
304
|
var httpServer = createServer(app);
|
|
210
305
|
var io = new Server(httpServer, {
|
|
211
306
|
cors: {
|
|
212
|
-
origin:
|
|
307
|
+
origin: corsOrigin
|
|
213
308
|
},
|
|
214
309
|
pingInterval: 2e3,
|
|
215
310
|
pingTimeout: 5e3
|
|
@@ -220,9 +315,40 @@ var emitError = (socketId, payload) => {
|
|
|
220
315
|
io.on(
|
|
221
316
|
"connection",
|
|
222
317
|
(socket) => {
|
|
318
|
+
const isHostAuthorizedForRoom = (roomId) => {
|
|
319
|
+
return roomManager.getRoomByHostId(socket.id) === roomId;
|
|
320
|
+
};
|
|
321
|
+
const isControllerAuthorizedForRoom = (roomId, controllerId) => {
|
|
322
|
+
const controllerInfo = roomManager.getControllerInfo(socket.id);
|
|
323
|
+
if (!controllerInfo || controllerInfo.roomId !== roomId) {
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
if (controllerId && controllerInfo.controllerId !== controllerId) {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
return true;
|
|
330
|
+
};
|
|
331
|
+
const forwardedFor = socket.handshake.headers["x-forwarded-for"];
|
|
332
|
+
const socketIdentifier = typeof forwardedFor === "string" && forwardedFor.split(",")[0]?.trim() || Array.isArray(forwardedFor) && forwardedFor[0]?.split(",")[0]?.trim() || socket.handshake.address || socket.id;
|
|
333
|
+
const isRateLimited = (bucket, limit) => {
|
|
334
|
+
const result = rateLimitService.check(
|
|
335
|
+
`${bucket}:${socketIdentifier}`,
|
|
336
|
+
limit,
|
|
337
|
+
RATE_LIMIT_WINDOW_MS
|
|
338
|
+
);
|
|
339
|
+
return !result.allowed;
|
|
340
|
+
};
|
|
223
341
|
socket.on(
|
|
224
342
|
"host:registerSystem",
|
|
225
343
|
async (payload, callback) => {
|
|
344
|
+
if (isRateLimited("host-registration", HOST_REGISTRATION_RATE_LIMIT_MAX)) {
|
|
345
|
+
callback({
|
|
346
|
+
ok: false,
|
|
347
|
+
message: "Too many host registration attempts. Please try again.",
|
|
348
|
+
code: ErrorCode.SERVICE_UNAVAILABLE
|
|
349
|
+
});
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
226
352
|
const parsed = hostRegisterSystemSchema.safeParse(payload);
|
|
227
353
|
if (!parsed.success) {
|
|
228
354
|
callback({
|
|
@@ -268,6 +394,139 @@ io.on(
|
|
|
268
394
|
io.to(roomId).emit("server:roomReady", { roomId });
|
|
269
395
|
}
|
|
270
396
|
);
|
|
397
|
+
socket.on(
|
|
398
|
+
"host:createRoom",
|
|
399
|
+
async (payload, callback) => {
|
|
400
|
+
if (isRateLimited("host-registration", HOST_REGISTRATION_RATE_LIMIT_MAX)) {
|
|
401
|
+
callback({
|
|
402
|
+
ok: false,
|
|
403
|
+
message: "Too many host registration attempts. Please try again.",
|
|
404
|
+
code: ErrorCode.SERVICE_UNAVAILABLE
|
|
405
|
+
});
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
const parsed = hostCreateRoomSchema.safeParse(payload);
|
|
409
|
+
if (!parsed.success) {
|
|
410
|
+
callback({
|
|
411
|
+
ok: false,
|
|
412
|
+
message: parsed.error.message,
|
|
413
|
+
code: ErrorCode.INVALID_PAYLOAD
|
|
414
|
+
});
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
const { maxPlayers, apiKey } = parsed.data;
|
|
418
|
+
const verification = await authService.verifyApiKey(apiKey);
|
|
419
|
+
if (!verification.isVerified) {
|
|
420
|
+
callback({
|
|
421
|
+
ok: false,
|
|
422
|
+
message: verification.error,
|
|
423
|
+
code: ErrorCode.INVALID_API_KEY
|
|
424
|
+
});
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
const existingRoomId = roomManager.getRoomByHostId(socket.id);
|
|
428
|
+
if (existingRoomId) {
|
|
429
|
+
const existingSession = roomManager.getRoom(existingRoomId);
|
|
430
|
+
if (existingSession && existingSession.masterHostSocketId === socket.id) {
|
|
431
|
+
console.log(
|
|
432
|
+
`[server] Host ${socket.id} already has room ${existingRoomId}, returning existing.`
|
|
433
|
+
);
|
|
434
|
+
callback({ ok: true, roomId: existingRoomId });
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
let roomId;
|
|
439
|
+
let attempts = 0;
|
|
440
|
+
do {
|
|
441
|
+
roomId = generateRoomCode();
|
|
442
|
+
attempts++;
|
|
443
|
+
if (attempts > 10) {
|
|
444
|
+
callback({
|
|
445
|
+
ok: false,
|
|
446
|
+
message: "Failed to generate unique room ID",
|
|
447
|
+
code: ErrorCode.CONNECTION_FAILED
|
|
448
|
+
});
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
} while (roomManager.getRoom(roomId));
|
|
452
|
+
const session = {
|
|
453
|
+
roomId,
|
|
454
|
+
masterHostSocketId: socket.id,
|
|
455
|
+
focus: "SYSTEM",
|
|
456
|
+
controllers: /* @__PURE__ */ new Map(),
|
|
457
|
+
maxPlayers: maxPlayers ?? 8,
|
|
458
|
+
gameState: "paused"
|
|
459
|
+
};
|
|
460
|
+
roomManager.setRoom(roomId, session);
|
|
461
|
+
roomManager.setHostRoom(socket.id, roomId);
|
|
462
|
+
socket.join(roomId);
|
|
463
|
+
console.log(`[server] Created room ${roomId} for host ${socket.id}`);
|
|
464
|
+
callback({ ok: true, roomId });
|
|
465
|
+
io.to(roomId).emit("server:roomReady", { roomId });
|
|
466
|
+
}
|
|
467
|
+
);
|
|
468
|
+
socket.on(
|
|
469
|
+
"host:reconnect",
|
|
470
|
+
async (payload, callback) => {
|
|
471
|
+
if (isRateLimited("host-registration", HOST_REGISTRATION_RATE_LIMIT_MAX)) {
|
|
472
|
+
callback({
|
|
473
|
+
ok: false,
|
|
474
|
+
message: "Too many host registration attempts. Please try again.",
|
|
475
|
+
code: ErrorCode.SERVICE_UNAVAILABLE
|
|
476
|
+
});
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
const parsed = hostReconnectSchema.safeParse(payload);
|
|
480
|
+
if (!parsed.success) {
|
|
481
|
+
callback({
|
|
482
|
+
ok: false,
|
|
483
|
+
message: parsed.error.message,
|
|
484
|
+
code: ErrorCode.INVALID_PAYLOAD
|
|
485
|
+
});
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
const { roomId, apiKey } = parsed.data;
|
|
489
|
+
const verification = await authService.verifyApiKey(apiKey);
|
|
490
|
+
if (!verification.isVerified) {
|
|
491
|
+
callback({
|
|
492
|
+
ok: false,
|
|
493
|
+
message: verification.error,
|
|
494
|
+
code: ErrorCode.INVALID_API_KEY
|
|
495
|
+
});
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
const session = roomManager.getRoom(roomId);
|
|
499
|
+
if (!session) {
|
|
500
|
+
callback({
|
|
501
|
+
ok: false,
|
|
502
|
+
message: "Room not found",
|
|
503
|
+
code: ErrorCode.ROOM_NOT_FOUND
|
|
504
|
+
});
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
const previousMasterSocket = io.sockets.sockets.get(
|
|
508
|
+
session.masterHostSocketId
|
|
509
|
+
);
|
|
510
|
+
const isPreviousHostConnected = previousMasterSocket?.connected ?? false;
|
|
511
|
+
if (!isPreviousHostConnected || session.masterHostSocketId === socket.id) {
|
|
512
|
+
session.masterHostSocketId = socket.id;
|
|
513
|
+
roomManager.setRoom(roomId, session);
|
|
514
|
+
roomManager.setHostRoom(socket.id, roomId);
|
|
515
|
+
socket.join(roomId);
|
|
516
|
+
console.log(
|
|
517
|
+
`[server] Host ${socket.id} reconnected to room ${roomId}`
|
|
518
|
+
);
|
|
519
|
+
callback({ ok: true, roomId });
|
|
520
|
+
io.to(roomId).emit("server:roomReady", { roomId });
|
|
521
|
+
} else {
|
|
522
|
+
callback({
|
|
523
|
+
ok: false,
|
|
524
|
+
message: "Room already has an active host",
|
|
525
|
+
code: ErrorCode.ALREADY_CONNECTED
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
);
|
|
271
530
|
socket.on(
|
|
272
531
|
"system:launchGame",
|
|
273
532
|
(payload, callback) => {
|
|
@@ -397,10 +656,33 @@ io.on(
|
|
|
397
656
|
session.joinToken = void 0;
|
|
398
657
|
session.activeControllerUrl = void 0;
|
|
399
658
|
io.to(roomId).emit("client:unloadUi");
|
|
659
|
+
if (session.masterHostSocketId) {
|
|
660
|
+
const masterSocket = io.sockets.sockets.get(session.masterHostSocketId);
|
|
661
|
+
if (masterSocket) {
|
|
662
|
+
setTimeout(() => {
|
|
663
|
+
session.controllers.forEach((c) => {
|
|
664
|
+
const notice = {
|
|
665
|
+
controllerId: c.controllerId,
|
|
666
|
+
nickname: c.nickname,
|
|
667
|
+
player: c.playerProfile
|
|
668
|
+
};
|
|
669
|
+
masterSocket.emit("server:controllerJoined", notice);
|
|
670
|
+
});
|
|
671
|
+
}, 100);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
400
674
|
});
|
|
401
675
|
socket.on(
|
|
402
676
|
"host:register",
|
|
403
677
|
async (payload, callback) => {
|
|
678
|
+
if (isRateLimited("host-registration", HOST_REGISTRATION_RATE_LIMIT_MAX)) {
|
|
679
|
+
callback({
|
|
680
|
+
ok: false,
|
|
681
|
+
message: "Too many host registration attempts. Please try again.",
|
|
682
|
+
code: ErrorCode.SERVICE_UNAVAILABLE
|
|
683
|
+
});
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
404
686
|
const parsed = hostRegistrationSchema.safeParse(payload);
|
|
405
687
|
if (!parsed.success) {
|
|
406
688
|
callback({
|
|
@@ -410,7 +692,16 @@ io.on(
|
|
|
410
692
|
});
|
|
411
693
|
return;
|
|
412
694
|
}
|
|
413
|
-
const { roomId, maxPlayers } = parsed.data;
|
|
695
|
+
const { roomId, maxPlayers, apiKey } = parsed.data;
|
|
696
|
+
const verification = await authService.verifyApiKey(apiKey);
|
|
697
|
+
if (!verification.isVerified) {
|
|
698
|
+
callback({
|
|
699
|
+
ok: false,
|
|
700
|
+
message: verification.error,
|
|
701
|
+
code: ErrorCode.INVALID_API_KEY
|
|
702
|
+
});
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
414
705
|
let session = roomManager.getRoom(roomId);
|
|
415
706
|
if (session) {
|
|
416
707
|
session.masterHostSocketId = socket.id;
|
|
@@ -434,6 +725,14 @@ io.on(
|
|
|
434
725
|
}
|
|
435
726
|
);
|
|
436
727
|
socket.on("controller:join", (payload, callback) => {
|
|
728
|
+
if (isRateLimited("controller-join", CONTROLLER_JOIN_RATE_LIMIT_MAX)) {
|
|
729
|
+
callback({
|
|
730
|
+
ok: false,
|
|
731
|
+
message: "Too many join attempts. Please try again.",
|
|
732
|
+
code: ErrorCode.SERVICE_UNAVAILABLE
|
|
733
|
+
});
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
437
736
|
const parsed = controllerJoinSchema.safeParse(payload);
|
|
438
737
|
if (!parsed.success) {
|
|
439
738
|
callback({
|
|
@@ -552,6 +851,9 @@ io.on(
|
|
|
552
851
|
return;
|
|
553
852
|
}
|
|
554
853
|
const { roomId, controllerId } = parsed.data;
|
|
854
|
+
if (!isControllerAuthorizedForRoom(roomId, controllerId)) {
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
555
857
|
const session = roomManager.getRoom(roomId);
|
|
556
858
|
if (!session) {
|
|
557
859
|
return;
|
|
@@ -566,18 +868,40 @@ io.on(
|
|
|
566
868
|
socket.leave(roomId);
|
|
567
869
|
});
|
|
568
870
|
socket.on("controller:input", (payload) => {
|
|
871
|
+
const now = Date.now();
|
|
872
|
+
const input = payload?.input;
|
|
873
|
+
const hasActiveInput = input && (input.action === true || typeof input.vector === "object" && input.vector !== null && (Math.abs(input.vector.x ?? 0) > 0.01 || Math.abs(input.vector.y ?? 0) > 0.01));
|
|
874
|
+
if (hasActiveInput && (!lastServerInputLogTime || now - lastServerInputLogTime > 1e3)) {
|
|
875
|
+
lastServerInputLogTime = now;
|
|
876
|
+
}
|
|
569
877
|
const result = controllerInputSchema.safeParse(payload);
|
|
570
878
|
if (!result.success) {
|
|
879
|
+
if (!lastServerInputFailLogTime || now - lastServerInputFailLogTime > 1e3) {
|
|
880
|
+
lastServerInputFailLogTime = now;
|
|
881
|
+
}
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
const { roomId, controllerId } = result.data;
|
|
885
|
+
if (!isControllerAuthorizedForRoom(roomId, controllerId)) {
|
|
571
886
|
return;
|
|
572
887
|
}
|
|
573
|
-
const { roomId } = result.data;
|
|
574
888
|
const session = roomManager.getRoom(roomId);
|
|
575
889
|
if (!session) {
|
|
890
|
+
if (!lastServerInputFailLogTime || now - lastServerInputFailLogTime > 1e3) {
|
|
891
|
+
lastServerInputFailLogTime = now;
|
|
892
|
+
}
|
|
576
893
|
return;
|
|
577
894
|
}
|
|
578
895
|
const targetHostId = roomManager.getActiveHostId(session);
|
|
579
896
|
if (targetHostId) {
|
|
897
|
+
if (!lastServerInputLogTime || now - lastServerInputLogTime > 1e3) {
|
|
898
|
+
lastServerInputLogTime = now;
|
|
899
|
+
}
|
|
580
900
|
io.to(targetHostId).emit("server:input", result.data);
|
|
901
|
+
} else {
|
|
902
|
+
if (!lastServerInputFailLogTime || now - lastServerInputFailLogTime > 1e3) {
|
|
903
|
+
lastServerInputFailLogTime = now;
|
|
904
|
+
}
|
|
581
905
|
}
|
|
582
906
|
});
|
|
583
907
|
socket.on("controller:system", (payload) => {
|
|
@@ -586,6 +910,9 @@ io.on(
|
|
|
586
910
|
return;
|
|
587
911
|
}
|
|
588
912
|
const { roomId, command } = parsed.data;
|
|
913
|
+
if (!isControllerAuthorizedForRoom(roomId)) {
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
589
916
|
const session = roomManager.getRoom(roomId);
|
|
590
917
|
if (!session) {
|
|
591
918
|
return;
|
|
@@ -624,6 +951,9 @@ io.on(
|
|
|
624
951
|
return;
|
|
625
952
|
}
|
|
626
953
|
const { roomId, command } = parsed.data;
|
|
954
|
+
if (!isHostAuthorizedForRoom(roomId)) {
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
627
957
|
const session = roomManager.getRoom(roomId);
|
|
628
958
|
if (!session) {
|
|
629
959
|
return;
|
|
@@ -643,6 +973,9 @@ io.on(
|
|
|
643
973
|
const result = controllerStateSchema.safeParse(payload);
|
|
644
974
|
if (!result.success) return;
|
|
645
975
|
const { roomId, state } = result.data;
|
|
976
|
+
if (!isHostAuthorizedForRoom(roomId)) {
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
646
979
|
const session = roomManager.getRoom(roomId);
|
|
647
980
|
if (session) {
|
|
648
981
|
if (state.gameState) {
|
|
@@ -689,6 +1022,9 @@ io.on(
|
|
|
689
1022
|
});
|
|
690
1023
|
socket.on("controller:play_sound", (payload) => {
|
|
691
1024
|
const { roomId, soundId, volume, loop } = payload;
|
|
1025
|
+
if (!isControllerAuthorizedForRoom(roomId)) {
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
692
1028
|
const session = roomManager.getRoom(roomId);
|
|
693
1029
|
if (!session) return;
|
|
694
1030
|
io.to(roomManager.getActiveHostId(session)).emit("server:playSound", {
|
|
@@ -723,9 +1059,6 @@ io.on(
|
|
|
723
1059
|
return;
|
|
724
1060
|
}
|
|
725
1061
|
if (controllerSession.socketId !== socket.id) {
|
|
726
|
-
roomManager.deleteController(controllerSession.socketId);
|
|
727
|
-
controllerSession.socketId = socket.id;
|
|
728
|
-
roomManager.setController(socket.id, { roomId, controllerId });
|
|
729
1062
|
socket.join(roomId);
|
|
730
1063
|
}
|
|
731
1064
|
const hostId = roomManager.getActiveHostId(session);
|
|
@@ -754,6 +1087,23 @@ io.on(
|
|
|
754
1087
|
session.joinToken = void 0;
|
|
755
1088
|
session.activeControllerUrl = void 0;
|
|
756
1089
|
io.to(roomId).emit("client:unloadUi");
|
|
1090
|
+
if (session.masterHostSocketId) {
|
|
1091
|
+
const masterSocket = io.sockets.sockets.get(
|
|
1092
|
+
session.masterHostSocketId
|
|
1093
|
+
);
|
|
1094
|
+
if (masterSocket) {
|
|
1095
|
+
setTimeout(() => {
|
|
1096
|
+
session.controllers.forEach((c) => {
|
|
1097
|
+
const notice = {
|
|
1098
|
+
controllerId: c.controllerId,
|
|
1099
|
+
nickname: c.nickname,
|
|
1100
|
+
player: c.playerProfile
|
|
1101
|
+
};
|
|
1102
|
+
masterSocket.emit("server:controllerJoined", notice);
|
|
1103
|
+
});
|
|
1104
|
+
}, 100);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
757
1107
|
} else if (socket.id === session.masterHostSocketId) {
|
|
758
1108
|
console.log(`[server] Host disconnected from room ${roomId}`);
|
|
759
1109
|
setTimeout(() => {
|
|
@@ -788,4 +1138,4 @@ io.on(
|
|
|
788
1138
|
httpServer.listen(PORT, () => {
|
|
789
1139
|
console.log(`[air-jam] server listening on http://localhost:${PORT}`);
|
|
790
1140
|
});
|
|
791
|
-
//# sourceMappingURL=chunk-
|
|
1141
|
+
//# sourceMappingURL=chunk-CGONPUNG.js.map
|