@phalanx-engine/client 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +1037 -0
  2. package/dist/DesyncDetector.d.ts +80 -0
  3. package/dist/DesyncDetector.d.ts.map +1 -0
  4. package/dist/DesyncDetector.js +93 -0
  5. package/dist/DesyncDetector.js.map +1 -0
  6. package/dist/DeterministicRandom.d.ts +78 -0
  7. package/dist/DeterministicRandom.d.ts.map +1 -0
  8. package/dist/DeterministicRandom.js +122 -0
  9. package/dist/DeterministicRandom.js.map +1 -0
  10. package/dist/EventEmitter.d.ts +65 -0
  11. package/dist/EventEmitter.d.ts.map +1 -0
  12. package/dist/EventEmitter.js +102 -0
  13. package/dist/EventEmitter.js.map +1 -0
  14. package/dist/FixedMath.d.ts +22 -0
  15. package/dist/FixedMath.d.ts.map +1 -0
  16. package/dist/FixedMath.js +26 -0
  17. package/dist/FixedMath.js.map +1 -0
  18. package/dist/PhalanxClient.d.ts +335 -0
  19. package/dist/PhalanxClient.d.ts.map +1 -0
  20. package/dist/PhalanxClient.js +844 -0
  21. package/dist/PhalanxClient.js.map +1 -0
  22. package/dist/RenderLoop.d.ts +95 -0
  23. package/dist/RenderLoop.d.ts.map +1 -0
  24. package/dist/RenderLoop.js +192 -0
  25. package/dist/RenderLoop.js.map +1 -0
  26. package/dist/SocketManager.d.ts +228 -0
  27. package/dist/SocketManager.d.ts.map +1 -0
  28. package/dist/SocketManager.js +584 -0
  29. package/dist/SocketManager.js.map +1 -0
  30. package/dist/StateHasher.d.ts +76 -0
  31. package/dist/StateHasher.d.ts.map +1 -0
  32. package/dist/StateHasher.js +129 -0
  33. package/dist/StateHasher.js.map +1 -0
  34. package/dist/auth/AuthManager.d.ts +188 -0
  35. package/dist/auth/AuthManager.d.ts.map +1 -0
  36. package/dist/auth/AuthManager.js +462 -0
  37. package/dist/auth/AuthManager.js.map +1 -0
  38. package/dist/auth/adapters/GoogleOAuthAdapter.d.ts +164 -0
  39. package/dist/auth/adapters/GoogleOAuthAdapter.d.ts.map +1 -0
  40. package/dist/auth/adapters/GoogleOAuthAdapter.js +521 -0
  41. package/dist/auth/adapters/GoogleOAuthAdapter.js.map +1 -0
  42. package/dist/auth/index.d.ts +45 -0
  43. package/dist/auth/index.d.ts.map +1 -0
  44. package/dist/auth/index.js +54 -0
  45. package/dist/auth/index.js.map +1 -0
  46. package/dist/auth/storage.d.ts +56 -0
  47. package/dist/auth/storage.d.ts.map +1 -0
  48. package/dist/auth/storage.js +78 -0
  49. package/dist/auth/storage.js.map +1 -0
  50. package/dist/auth/types.d.ts +212 -0
  51. package/dist/auth/types.d.ts.map +1 -0
  52. package/dist/auth/types.js +7 -0
  53. package/dist/auth/types.js.map +1 -0
  54. package/dist/index.d.ts +70 -0
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +83 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/recovery/BrowserLifecycle.d.ts +33 -0
  59. package/dist/recovery/BrowserLifecycle.d.ts.map +1 -0
  60. package/dist/recovery/BrowserLifecycle.js +62 -0
  61. package/dist/recovery/BrowserLifecycle.js.map +1 -0
  62. package/dist/recovery/GuestPlayerIdStore.d.ts +17 -0
  63. package/dist/recovery/GuestPlayerIdStore.d.ts.map +1 -0
  64. package/dist/recovery/GuestPlayerIdStore.js +31 -0
  65. package/dist/recovery/GuestPlayerIdStore.js.map +1 -0
  66. package/dist/recovery/KeyValueStorage.d.ts +32 -0
  67. package/dist/recovery/KeyValueStorage.d.ts.map +1 -0
  68. package/dist/recovery/KeyValueStorage.js +58 -0
  69. package/dist/recovery/KeyValueStorage.js.map +1 -0
  70. package/dist/recovery/MobileTransport.d.ts +12 -0
  71. package/dist/recovery/MobileTransport.d.ts.map +1 -0
  72. package/dist/recovery/MobileTransport.js +24 -0
  73. package/dist/recovery/MobileTransport.js.map +1 -0
  74. package/dist/recovery/NetworkQuality.d.ts +22 -0
  75. package/dist/recovery/NetworkQuality.d.ts.map +1 -0
  76. package/dist/recovery/NetworkQuality.js +35 -0
  77. package/dist/recovery/NetworkQuality.js.map +1 -0
  78. package/dist/recovery/RoomPersistence.d.ts +55 -0
  79. package/dist/recovery/RoomPersistence.d.ts.map +1 -0
  80. package/dist/recovery/RoomPersistence.js +68 -0
  81. package/dist/recovery/RoomPersistence.js.map +1 -0
  82. package/dist/recovery/RoomRecoveryController.d.ts +146 -0
  83. package/dist/recovery/RoomRecoveryController.d.ts.map +1 -0
  84. package/dist/recovery/RoomRecoveryController.js +348 -0
  85. package/dist/recovery/RoomRecoveryController.js.map +1 -0
  86. package/dist/recovery/index.d.ts +13 -0
  87. package/dist/recovery/index.d.ts.map +1 -0
  88. package/dist/recovery/index.js +8 -0
  89. package/dist/recovery/index.js.map +1 -0
  90. package/dist/types.d.ts +501 -0
  91. package/dist/types.d.ts.map +1 -0
  92. package/dist/types.js +6 -0
  93. package/dist/types.js.map +1 -0
  94. package/package.json +66 -0
@@ -0,0 +1,584 @@
1
+ /**
2
+ * SocketManager - Manages socket.io connection and event handling
3
+ *
4
+ * Handles:
5
+ * - Connection/disconnection to server
6
+ * - Socket event routing
7
+ * - Reconnection logic with retries
8
+ * - Connection state tracking
9
+ */
10
+ import { io } from 'socket.io-client';
11
+ /**
12
+ * SocketManager - Handles socket.io connection and event handling
13
+ */
14
+ export class SocketManager {
15
+ socket = null;
16
+ config;
17
+ callbacks;
18
+ connectionState = 'disconnected';
19
+ reconnectAttempts = 0;
20
+ constructor(config, callbacks) {
21
+ this.config = config;
22
+ this.callbacks = callbacks;
23
+ }
24
+ // ============================================
25
+ // CONNECTION
26
+ // ============================================
27
+ /**
28
+ * Connect to the server
29
+ */
30
+ async connect() {
31
+ if (this.connectionState === 'connected') {
32
+ return;
33
+ }
34
+ this.connectionState = 'connecting';
35
+ return new Promise((resolve, reject) => {
36
+ const timeout = setTimeout(() => {
37
+ this.socket?.disconnect();
38
+ this.connectionState = 'disconnected';
39
+ reject(new Error('Connection timeout'));
40
+ }, this.config.connectionTimeoutMs);
41
+ this.socket = io(this.config.serverUrl, {
42
+ forceNew: true,
43
+ transports: [...this.config.socketTransports],
44
+ reconnection: false, // We handle reconnection ourselves
45
+ auth: this.config.authToken
46
+ ? { token: this.config.authToken }
47
+ : undefined,
48
+ });
49
+ this.socket.on('connect', () => {
50
+ clearTimeout(timeout);
51
+ this.connectionState = 'connected';
52
+ this.reconnectAttempts = 0;
53
+ this.setupEventHandlers();
54
+ this.callbacks.onConnected();
55
+ resolve();
56
+ });
57
+ this.socket.on('connect_error', (error) => {
58
+ clearTimeout(timeout);
59
+ this.connectionState = 'disconnected';
60
+ reject(new Error(`Connection failed: ${error.message}`));
61
+ });
62
+ });
63
+ }
64
+ /**
65
+ * Disconnect from the server
66
+ */
67
+ disconnect() {
68
+ if (this.socket) {
69
+ this.socket.removeAllListeners();
70
+ this.socket.disconnect();
71
+ this.socket = null;
72
+ }
73
+ this.connectionState = 'disconnected';
74
+ }
75
+ /**
76
+ * Check if connected
77
+ */
78
+ isConnected() {
79
+ return (this.connectionState === 'connected' && this.socket?.connected === true);
80
+ }
81
+ /**
82
+ * Get connection state
83
+ */
84
+ getConnectionState() {
85
+ return this.connectionState;
86
+ }
87
+ /**
88
+ * Update player credentials (after auth)
89
+ */
90
+ updateCredentials(playerId, username, authToken) {
91
+ this.config.playerId = playerId;
92
+ this.config.username = username;
93
+ this.config.authToken = authToken;
94
+ }
95
+ // ============================================
96
+ // QUEUE OPERATIONS
97
+ // ============================================
98
+ /**
99
+ * Join the matchmaking queue
100
+ */
101
+ async joinQueue() {
102
+ this.ensureConnected();
103
+ return new Promise((resolve, reject) => {
104
+ const errorHandler = (error) => {
105
+ this.socket?.off('queue-status', statusHandler);
106
+ reject(new Error(error.message));
107
+ };
108
+ const statusHandler = (status) => {
109
+ this.socket?.off('queue-error', errorHandler);
110
+ resolve(status);
111
+ };
112
+ this.socket.once('queue-status', statusHandler);
113
+ this.socket.once('queue-error', errorHandler);
114
+ this.socket.emit('queue-join', {
115
+ playerId: this.config.playerId,
116
+ username: this.config.username,
117
+ });
118
+ });
119
+ }
120
+ /**
121
+ * Leave the matchmaking queue
122
+ */
123
+ leaveQueue() {
124
+ this.ensureConnected();
125
+ this.socket.emit('queue-leave', {
126
+ playerId: this.config.playerId,
127
+ });
128
+ }
129
+ // ============================================
130
+ // PRIVATE ROOM OPERATIONS
131
+ // ============================================
132
+ /**
133
+ * Create a private room. Resolves with the room code.
134
+ */
135
+ async createRoom(gameType) {
136
+ this.ensureConnected();
137
+ return new Promise((resolve, reject) => {
138
+ const errorHandler = (error) => {
139
+ this.socket?.off('room-created', createdHandler);
140
+ reject(new Error(error.message));
141
+ };
142
+ const createdHandler = (event) => {
143
+ this.socket?.off('room-error', errorHandler);
144
+ resolve(event);
145
+ };
146
+ this.socket.once('room-created', createdHandler);
147
+ this.socket.once('room-error', errorHandler);
148
+ this.socket.emit('room-create', {
149
+ playerId: this.config.playerId,
150
+ username: this.config.username,
151
+ gameType,
152
+ });
153
+ });
154
+ }
155
+ /**
156
+ * Join an existing private room by code.
157
+ * After joining, the server will emit match-found to both players.
158
+ */
159
+ joinRoom(code) {
160
+ this.ensureConnected();
161
+ this.socket.emit('room-join', {
162
+ playerId: this.config.playerId,
163
+ username: this.config.username,
164
+ code,
165
+ });
166
+ }
167
+ /**
168
+ * Cancel a previously created private room.
169
+ */
170
+ cancelRoom() {
171
+ this.ensureConnected();
172
+ this.socket.emit('room-cancel');
173
+ }
174
+ /**
175
+ * Reclaim a private room after the underlying socket was briefly torn
176
+ * down and reconnected.
177
+ *
178
+ * This is the client half of the host-disconnect grace-period contract
179
+ * introduced server-side in PR #18 and extended with countdown/game-start
180
+ * snapshotting in PR #19: the server holds the room (and, if the guest
181
+ * already joined, the running match) open for `HOST_DISCONNECT_GRACE_MS`
182
+ * so the host's new socket can reclaim it via `room-recover`.
183
+ *
184
+ * Typical trigger: a mobile browser kills the WebSocket when the user
185
+ * switches to a messenger to share the invite link, then re-opens the
186
+ * tab. The caller (Game / UI layer) should listen for `visibilitychange`
187
+ * and, when the tab becomes visible again and the socket is dead, call
188
+ * `connect()` followed by `recoverRoom(code)`.
189
+ *
190
+ * Resolves with the `RoomRecoveredEvent` on success. Rejects with the
191
+ * server's error message on `room-error`, or with `'Recover timeout'`
192
+ * if the server never answers within `timeoutMs` (default 10 s). The
193
+ * timeout is important because a stubbornly flaky connection can cause
194
+ * `socket.emit` to succeed locally but never round-trip — we must not
195
+ * leave the host hanging on a dead promise while their countdown UI
196
+ * stays frozen.
197
+ */
198
+ async recoverRoom(code, timeoutMs = this.config.recoverRoomTimeoutMs) {
199
+ this.ensureConnected();
200
+ return new Promise((resolve, reject) => {
201
+ let settled = false;
202
+ const cleanup = () => {
203
+ this.socket?.off('room-recovered', recoveredHandler);
204
+ this.socket?.off('room-error', errorHandler);
205
+ clearTimeout(timer);
206
+ };
207
+ const recoveredHandler = (event) => {
208
+ if (settled)
209
+ return;
210
+ settled = true;
211
+ cleanup();
212
+ resolve(event);
213
+ };
214
+ const errorHandler = (error) => {
215
+ if (settled)
216
+ return;
217
+ settled = true;
218
+ cleanup();
219
+ reject(new Error(error.message));
220
+ };
221
+ const timer = setTimeout(() => {
222
+ if (settled)
223
+ return;
224
+ settled = true;
225
+ cleanup();
226
+ reject(new Error('Recover timeout'));
227
+ }, timeoutMs);
228
+ this.socket.once('room-recovered', recoveredHandler);
229
+ this.socket.once('room-error', errorHandler);
230
+ this.socket.emit('room-recover', {
231
+ playerId: this.config.playerId,
232
+ username: this.config.username,
233
+ code,
234
+ });
235
+ });
236
+ }
237
+ /**
238
+ * Register a handler for room-recovered events. Exposed alongside the
239
+ * one-shot `recoverRoom` promise so callers that want to observe
240
+ * recoveries initiated elsewhere (e.g. for analytics) can do so.
241
+ */
242
+ onRoomRecovered(handler) {
243
+ this.socket?.on('room-recovered', handler);
244
+ }
245
+ /**
246
+ * Register a handler for room-expired events.
247
+ */
248
+ onRoomExpired(handler) {
249
+ this.socket?.on('room-expired', handler);
250
+ }
251
+ /**
252
+ * Register a handler for room-cancelled events.
253
+ */
254
+ onRoomCancelled(handler) {
255
+ this.socket?.on('room-cancelled', handler);
256
+ }
257
+ /**
258
+ * Wait for match found event
259
+ */
260
+ async waitForMatch() {
261
+ this.ensureConnected();
262
+ return new Promise((resolve) => {
263
+ this.socket.once('match-found', (data) => {
264
+ resolve(data);
265
+ });
266
+ });
267
+ }
268
+ /**
269
+ * Wait for countdown to complete
270
+ */
271
+ async waitForCountdown(onCountdown) {
272
+ this.ensureConnected();
273
+ return new Promise((resolve) => {
274
+ const countdownHandler = (data) => {
275
+ this.callbacks.onCountdown(data);
276
+ onCountdown?.(data);
277
+ if (data.seconds === 0) {
278
+ this.socket?.off('countdown', countdownHandler);
279
+ resolve();
280
+ }
281
+ };
282
+ this.socket.on('countdown', countdownHandler);
283
+ });
284
+ }
285
+ /**
286
+ * Wait for game start event
287
+ */
288
+ async waitForGameStart() {
289
+ this.ensureConnected();
290
+ return new Promise((resolve) => {
291
+ this.socket.once('game-start', (data) => {
292
+ resolve(data);
293
+ });
294
+ });
295
+ }
296
+ // ============================================
297
+ // COMMANDS
298
+ // ============================================
299
+ /**
300
+ * Submit commands with acknowledgment
301
+ */
302
+ async submitCommands(tick, commands) {
303
+ this.ensureConnected();
304
+ return new Promise((resolve) => {
305
+ this.socket.once('submit-commands-ack', (ack) => {
306
+ resolve(ack);
307
+ });
308
+ this.socket.emit('submit-commands', { tick, commands });
309
+ });
310
+ }
311
+ /**
312
+ * Submit commands without acknowledgment (fire and forget)
313
+ */
314
+ submitCommandsAsync(tick, commands) {
315
+ this.ensureConnected();
316
+ this.socket.emit('submit-commands', { tick, commands });
317
+ }
318
+ /**
319
+ * Send state hash for desync detection
320
+ * @param tick - The tick this hash is for
321
+ * @param hash - The state hash string
322
+ */
323
+ sendStateHash(tick, hash) {
324
+ this.ensureConnected();
325
+ this.socket.emit('state-hash', { tick, hash });
326
+ }
327
+ /**
328
+ * Send a pause-game request to the server (fire-and-forget)
329
+ */
330
+ sendPauseGame() {
331
+ this.ensureConnected();
332
+ this.socket.emit('pause-game');
333
+ }
334
+ /**
335
+ * Send a resume-game request to the server (fire-and-forget)
336
+ */
337
+ sendResumeGame() {
338
+ this.ensureConnected();
339
+ this.socket.emit('resume-game');
340
+ }
341
+ /**
342
+ * Notify the server that this client has finished loading and is ready to receive ticks.
343
+ */
344
+ sendReady() {
345
+ this.ensureConnected();
346
+ this.socket.emit('client-ready');
347
+ }
348
+ // ============================================
349
+ // RECONNECTION
350
+ // ============================================
351
+ /**
352
+ * Reconnect to a specific match
353
+ */
354
+ async reconnectToMatch(matchId) {
355
+ this.ensureConnected();
356
+ return new Promise((resolve, reject) => {
357
+ const statusHandler = (status) => {
358
+ this.callbacks.onReconnectStatus(status);
359
+ if (!status.success) {
360
+ this.socket?.off('reconnect-state', stateHandler);
361
+ reject(new Error(status.reason || 'Reconnection failed'));
362
+ }
363
+ };
364
+ const stateHandler = (state) => {
365
+ this.socket?.off('reconnect-status', statusHandler);
366
+ // Do NOT invoke `callbacks.onReconnectState` here — the global
367
+ // `reconnect-state` handler registered in `setupEventHandlers`
368
+ // already fires for this same message. Calling it twice would
369
+ // drive downstream state updates (and any synthetic countdown /
370
+ // game-start replays) through the client twice per server event.
371
+ resolve(state);
372
+ };
373
+ this.socket.once('reconnect-status', statusHandler);
374
+ this.socket.once('reconnect-state', stateHandler);
375
+ this.socket.emit('reconnect-match', {
376
+ playerId: this.config.playerId,
377
+ matchId,
378
+ });
379
+ });
380
+ }
381
+ /**
382
+ * Attempt automatic reconnection with retries
383
+ */
384
+ async attemptReconnection() {
385
+ if (!this.config.autoReconnect) {
386
+ throw new Error('Auto-reconnect is disabled');
387
+ }
388
+ const savedMatchId = this.callbacks.getCurrentMatchId();
389
+ this.connectionState = 'reconnecting';
390
+ while (this.reconnectAttempts < this.config.maxReconnectAttempts) {
391
+ this.reconnectAttempts++;
392
+ this.callbacks.onReconnecting(this.reconnectAttempts);
393
+ try {
394
+ await this.delay(this.config.reconnectDelayMs);
395
+ await this.connect();
396
+ if (savedMatchId) {
397
+ await this.reconnectToMatch(savedMatchId);
398
+ }
399
+ return;
400
+ }
401
+ catch {
402
+ // Continue to next attempt
403
+ }
404
+ }
405
+ this.callbacks.onReconnectFailed();
406
+ throw new Error('Max reconnection attempts reached');
407
+ }
408
+ // ============================================
409
+ // PRIVATE METHODS
410
+ // ============================================
411
+ setupEventHandlers() {
412
+ if (!this.socket)
413
+ return;
414
+ if (this.config.debug) { }
415
+ // Match found
416
+ this.socket.on('match-found', (data) => {
417
+ if (this.config.debug) { }
418
+ this.callbacks.onMatchFound(data);
419
+ });
420
+ // Game start
421
+ this.socket.on('game-start', (data) => {
422
+ if (this.config.debug) { }
423
+ this.callbacks.onGameStart(data);
424
+ });
425
+ // Countdown
426
+ this.socket.on('countdown', (data) => {
427
+ if (this.config.debug) { }
428
+ this.callbacks.onCountdown(data);
429
+ });
430
+ // Tick synchronization
431
+ this.socket.on('tick-sync', (data) => {
432
+ this.callbacks.onTickSync(data);
433
+ });
434
+ // Commands batch
435
+ this.socket.on('commands-batch', (data) => {
436
+ this.callbacks.onCommandsBatch(data);
437
+ });
438
+ // Player events
439
+ this.socket.on('player-disconnected', (data) => {
440
+ this.callbacks.onPlayerDisconnected(data);
441
+ });
442
+ this.socket.on('player-reconnected', (data) => {
443
+ this.callbacks.onPlayerReconnected(data);
444
+ });
445
+ this.socket.on('player-ready', (data) => {
446
+ this.callbacks.onPlayerReady(data);
447
+ });
448
+ // Match end
449
+ this.socket.on('match-end', (data) => {
450
+ this.callbacks.onMatchEnd(data);
451
+ });
452
+ // Reconnect-state — unlike `reconnectToMatch` which attaches a
453
+ // one-shot listener for the explicit reconnect flow, this global
454
+ // handler catches snapshots delivered out-of-band, e.g. by the
455
+ // private-room host-recover path where the client initiates
456
+ // `room-recover` and the server proactively emits reconnect-state
457
+ // to wire the socket back into a match that started while the host
458
+ // was offline.
459
+ //
460
+ // When the snapshot reports an in-flight countdown or an already
461
+ // emitted `game-start`, we fan it out through the same `countdown`
462
+ // / `game-start` callbacks the normal lifecycle uses, so client
463
+ // code that blocks on `waitForCountdown` / `waitForGameStart`
464
+ // wakes up instead of hanging forever waiting for events that
465
+ // were broadcast while the socket was dead.
466
+ this.socket.on('reconnect-state', (data) => {
467
+ this.callbacks.onReconnectState(data);
468
+ // Fan the snapshot out through the same socket event bus that
469
+ // `setupEventHandlers` and `waitForCountdown`/`waitForGameStart`
470
+ // listen on. Using `socket.emit` against the local socket would
471
+ // send to the server, not the local listeners — socket.io-client
472
+ // has no public "emit-to-self", so we replay through every
473
+ // registered listener instead. This wakes callers blocked in
474
+ // `waitForCountdown` / `waitForGameStart` on top of firing the
475
+ // normal callback hooks.
476
+ if (typeof data.countdownSecondsRemaining === 'number' &&
477
+ data.countdownSecondsRemaining >= 0) {
478
+ this.emitSyntheticLocal('countdown', {
479
+ seconds: data.countdownSecondsRemaining,
480
+ });
481
+ }
482
+ // Only synthesize `game-start` when the snapshot shows the client
483
+ // is still in a pre-play phase. For an in-progress match (state
484
+ // `playing` / `paused` / `finished`) the caller is doing a normal
485
+ // mid-match reconnect, and PhalanxClient's `onGameStart` callback
486
+ // would reset `currentTick` to 0 — clobbering the authoritative
487
+ // tick that `onReconnectState` just applied. In that case the
488
+ // client is already past the game-start transition, so there's
489
+ // nothing to replay.
490
+ if (data.gameStartEmitted === true &&
491
+ (data.state === 'countdown' || data.state === 'waiting-for-ready')) {
492
+ const gameStart = {
493
+ matchId: data.matchId,
494
+ ...(typeof data.randomSeed === 'number'
495
+ ? { randomSeed: data.randomSeed }
496
+ : {}),
497
+ };
498
+ this.emitSyntheticLocal('game-start', gameStart);
499
+ }
500
+ });
501
+ // Hash comparison (for desync detection)
502
+ this.socket.on('hash-comparison', (data) => {
503
+ this.callbacks.onHashComparison(data);
504
+ });
505
+ // Pause events
506
+ this.socket.on('game-paused', (data) => {
507
+ this.callbacks.onGamePaused(data);
508
+ });
509
+ this.socket.on('game-resumed', (data) => {
510
+ this.callbacks.onGameResumed(data);
511
+ });
512
+ // Private room events
513
+ this.socket.on('room-error', (data) => {
514
+ this.callbacks.onRoomError(data);
515
+ });
516
+ this.socket.on('room-expired', (data) => {
517
+ this.callbacks.onRoomExpired(data);
518
+ });
519
+ this.socket.on('room-cancelled', (data) => {
520
+ this.callbacks.onRoomCancelled(data);
521
+ });
522
+ this.socket.on('room-recovered', (data) => {
523
+ this.callbacks.onRoomRecovered(data);
524
+ });
525
+ // Disconnection handling
526
+ this.socket.on('disconnect', () => {
527
+ const wasPlaying = this.callbacks.isPlaying();
528
+ this.connectionState = 'disconnected';
529
+ this.callbacks.onDisconnected();
530
+ if (wasPlaying && this.config.autoReconnect) {
531
+ this.attemptReconnection().catch(() => {
532
+ // Reconnection failed, already emitted reconnectFailed event
533
+ });
534
+ }
535
+ });
536
+ // Error handling
537
+ this.socket.on('error', (error) => {
538
+ this.callbacks.onError(error);
539
+ });
540
+ }
541
+ ensureConnected() {
542
+ if (!this.socket || !this.isConnected()) {
543
+ throw new Error('Not connected to server. Call connect() first.');
544
+ }
545
+ }
546
+ /**
547
+ * Replay `payload` through every listener currently registered for
548
+ * `event` on the local socket.
549
+ *
550
+ * socket.io-client's `socket.emit` sends to the server, so there is
551
+ * no public API to dispatch a server→client event against the
552
+ * client's own handlers. For the private-room recover path we need
553
+ * exactly that: the server already decided that, e.g., countdown
554
+ * should show "3", and we want every listener the application has
555
+ * wired up — including one-shot promises inside `waitForCountdown`
556
+ * / `waitForGameStart` — to observe the value as if the server had
557
+ * broadcast it on the wire.
558
+ *
559
+ * Iterating a snapshot of `socket.listeners(event)` (rather than
560
+ * the live list) is deliberate: a listener may call `socket.off`
561
+ * on itself during handling (e.g. `waitForCountdown`'s handler
562
+ * unsubscribes on `seconds === 0`), which would mutate the array
563
+ * under us and skip siblings.
564
+ */
565
+ emitSyntheticLocal(event, payload) {
566
+ if (!this.socket)
567
+ return;
568
+ const listeners = this.socket.listeners(event).slice();
569
+ for (const listener of listeners) {
570
+ try {
571
+ listener(payload);
572
+ }
573
+ catch (err) {
574
+ if (this.config.debug) {
575
+ console.error(`[SocketManager] synthetic "${event}" listener threw:`, err);
576
+ }
577
+ }
578
+ }
579
+ }
580
+ delay(ms) {
581
+ return new Promise((resolve) => setTimeout(resolve, ms));
582
+ }
583
+ }
584
+ //# sourceMappingURL=SocketManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SocketManager.js","sourceRoot":"","sources":["../src/SocketManager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * StateHasher - Deterministic state hasher using FNV-1a algorithm
3
+ *
4
+ * Works only with primitives - game-agnostic by design.
5
+ * Games use this to hash their own state structure.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * const hash = new StateHasher()
10
+ * .addInt(tick)
11
+ * .addInt(entityCount)
12
+ * .addFloat(entity.x)
13
+ * .addFloat(entity.y)
14
+ * .addString(entity.state)
15
+ * .finalize();
16
+ * ```
17
+ */
18
+ export declare class StateHasher {
19
+ private static readonly FNV_OFFSET;
20
+ private static readonly FNV_PRIME;
21
+ private static readonly FLOAT_PRECISION;
22
+ private hash;
23
+ constructor();
24
+ /**
25
+ * Add an integer to the hash
26
+ * @param value - Integer value to hash
27
+ * @returns this (for chaining)
28
+ */
29
+ addInt(value: number): this;
30
+ /**
31
+ * Add a float to the hash (converted to fixed-point for determinism)
32
+ * @param value - Float value to hash
33
+ * @returns this (for chaining)
34
+ */
35
+ addFloat(value: number): this;
36
+ /**
37
+ * Add a string to the hash
38
+ * @param value - String value to hash
39
+ * @returns this (for chaining)
40
+ */
41
+ addString(value: string): this;
42
+ /**
43
+ * Add a boolean to the hash
44
+ * @param value - Boolean value to hash
45
+ * @returns this (for chaining)
46
+ */
47
+ addBool(value: boolean): this;
48
+ /**
49
+ * Add multiple integers (useful for arrays)
50
+ * @param values - Array of integers to hash
51
+ * @returns this (for chaining)
52
+ */
53
+ addIntArray(values: number[]): this;
54
+ /**
55
+ * Add multiple floats (useful for positions, etc.)
56
+ * @param values - Array of floats to hash
57
+ * @returns this (for chaining)
58
+ */
59
+ addFloatArray(values: number[]): this;
60
+ /**
61
+ * Finalize and get the hash as a hex string
62
+ * @returns 8-character hex string (32-bit hash)
63
+ */
64
+ finalize(): string;
65
+ /**
66
+ * Reset hasher to initial state (for reuse)
67
+ * @returns this (for chaining)
68
+ */
69
+ reset(): this;
70
+ /**
71
+ * Create a new hasher (static factory)
72
+ * @returns New StateHasher instance
73
+ */
74
+ static create(): StateHasher;
75
+ }
76
+ //# sourceMappingURL=StateHasher.d.ts.map