@pokertools/sdk 1.0.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.
@@ -0,0 +1,1099 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var types = require('@pokertools/types');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+
7
+ // src/react/index.tsx
8
+
9
+ // src/types.ts
10
+ var PokerSDKError = class extends Error {
11
+ constructor(message, code, statusCode, details) {
12
+ super(message);
13
+ this.code = code;
14
+ this.statusCode = statusCode;
15
+ this.details = details;
16
+ this.name = "PokerSDKError";
17
+ }
18
+ };
19
+
20
+ // src/client.ts
21
+ var DEFAULT_CONFIG = {
22
+ timeout: 3e4,
23
+ retry: {
24
+ count: 3,
25
+ delay: 1e3,
26
+ backoff: 2
27
+ }
28
+ };
29
+ var PokerClient = class {
30
+ constructor(config) {
31
+ this.baseUrl = config.baseUrl.replace(/\/$/, "");
32
+ this.timeout = config.timeout ?? DEFAULT_CONFIG.timeout;
33
+ this.retry = {
34
+ count: config.retry?.count ?? DEFAULT_CONFIG.retry.count,
35
+ delay: config.retry?.delay ?? DEFAULT_CONFIG.retry.delay,
36
+ backoff: config.retry?.backoff ?? DEFAULT_CONFIG.retry.backoff
37
+ };
38
+ this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
39
+ this.debug = config.debug ?? false;
40
+ this.token = config.token ?? null;
41
+ }
42
+ // ============================================================================
43
+ // Configuration
44
+ // ============================================================================
45
+ /**
46
+ * Set the authentication token
47
+ */
48
+ setToken(token) {
49
+ this.token = token;
50
+ }
51
+ /**
52
+ * Get current token
53
+ */
54
+ getToken() {
55
+ return this.token;
56
+ }
57
+ /**
58
+ * Check if client is authenticated
59
+ */
60
+ isAuthenticated() {
61
+ return this.token !== null;
62
+ }
63
+ // ============================================================================
64
+ // Authentication
65
+ // ============================================================================
66
+ /**
67
+ * Get a nonce for SIWE authentication
68
+ */
69
+ async getNonce() {
70
+ const response = await this.request("POST", "/auth/nonce");
71
+ return response.nonce;
72
+ }
73
+ /**
74
+ * Login with SIWE signature
75
+ */
76
+ async login(request) {
77
+ const response = await this.request("POST", "/auth/login", request);
78
+ this.token = response.token;
79
+ return response;
80
+ }
81
+ /**
82
+ * Logout and revoke session
83
+ */
84
+ async logout() {
85
+ await this.request("POST", "/auth/logout");
86
+ this.token = null;
87
+ }
88
+ // ============================================================================
89
+ // Tables
90
+ // ============================================================================
91
+ /**
92
+ * Get list of active tables
93
+ */
94
+ async getTables() {
95
+ const response = await this.request("GET", "/tables");
96
+ return response.tables;
97
+ }
98
+ /**
99
+ * Create a new table
100
+ */
101
+ async createTable(config) {
102
+ const response = await this.request("POST", "/tables", config);
103
+ return response.tableId;
104
+ }
105
+ /**
106
+ * Get table state
107
+ * @param tableId - Table ID
108
+ * @param since - Optional version for conditional fetch (returns null if unchanged)
109
+ */
110
+ async getTableState(tableId, since) {
111
+ const query = since !== void 0 ? `?since=${since}` : "";
112
+ try {
113
+ const response = await this.request(
114
+ "GET",
115
+ `/tables/${tableId}${query}`
116
+ );
117
+ return response.state;
118
+ } catch (error) {
119
+ if (error instanceof PokerSDKError && error.statusCode === 304) {
120
+ return null;
121
+ }
122
+ throw error;
123
+ }
124
+ }
125
+ /**
126
+ * Buy in to a table
127
+ */
128
+ async buyIn(tableId, request) {
129
+ await this.request("POST", `/tables/${tableId}/buy-in`, request);
130
+ }
131
+ /**
132
+ * Execute a game action
133
+ */
134
+ async action(tableId, request) {
135
+ const response = await this.request(
136
+ "POST",
137
+ `/tables/${tableId}/action`,
138
+ request
139
+ );
140
+ return response.state;
141
+ }
142
+ /**
143
+ * Add chips to stack (rebuy/top-up)
144
+ */
145
+ async addChips(tableId, request) {
146
+ await this.request("POST", `/tables/${tableId}/add-chips`, request);
147
+ }
148
+ /**
149
+ * Stand from table (leave and cash out)
150
+ */
151
+ async stand(tableId) {
152
+ await this.request("POST", `/tables/${tableId}/stand`);
153
+ }
154
+ // ============================================================================
155
+ // Convenience Action Methods
156
+ // ============================================================================
157
+ /**
158
+ * Fold hand
159
+ */
160
+ async fold(tableId) {
161
+ return this.action(tableId, { type: "FOLD" });
162
+ }
163
+ /**
164
+ * Check (pass action)
165
+ */
166
+ async check(tableId) {
167
+ return this.action(tableId, { type: "CHECK" });
168
+ }
169
+ /**
170
+ * Call current bet
171
+ */
172
+ async call(tableId) {
173
+ return this.action(tableId, { type: "CALL" });
174
+ }
175
+ /**
176
+ * Place a bet
177
+ */
178
+ async bet(tableId, amount) {
179
+ return this.action(tableId, { type: "BET", amount });
180
+ }
181
+ /**
182
+ * Raise the current bet
183
+ */
184
+ async raise(tableId, amount) {
185
+ return this.action(tableId, { type: "RAISE", amount });
186
+ }
187
+ /**
188
+ * Deal new hand
189
+ */
190
+ async deal(tableId) {
191
+ return this.action(tableId, { type: "DEAL" });
192
+ }
193
+ /**
194
+ * Show cards at showdown
195
+ */
196
+ async show(tableId, cardIndices) {
197
+ return this.action(tableId, { type: "SHOW", cardIndices });
198
+ }
199
+ /**
200
+ * Muck cards at showdown
201
+ */
202
+ async muck(tableId) {
203
+ return this.action(tableId, { type: "MUCK" });
204
+ }
205
+ /**
206
+ * Use time bank
207
+ */
208
+ async timeBank(tableId) {
209
+ return this.action(tableId, { type: "TIME_BANK" });
210
+ }
211
+ // ============================================================================
212
+ // User
213
+ // ============================================================================
214
+ /**
215
+ * Get current user profile and balances
216
+ */
217
+ async getProfile() {
218
+ return this.request("GET", "/user/me");
219
+ }
220
+ /**
221
+ * Get hand history
222
+ */
223
+ async getHandHistory() {
224
+ const response = await this.request("GET", "/user/history");
225
+ return response.history;
226
+ }
227
+ /**
228
+ * Request a withdrawal
229
+ */
230
+ async withdraw(request) {
231
+ return this.request("POST", "/user/withdraw", request);
232
+ }
233
+ /**
234
+ * Get withdrawal history
235
+ */
236
+ async getWithdrawals() {
237
+ const response = await this.request(
238
+ "GET",
239
+ "/user/withdrawals"
240
+ );
241
+ return response.withdrawals;
242
+ }
243
+ // ============================================================================
244
+ // Finance
245
+ // ============================================================================
246
+ /**
247
+ * Get supported blockchains and tokens
248
+ */
249
+ async getChains() {
250
+ return this.request("GET", "/finance/chains");
251
+ }
252
+ /**
253
+ * Start deposit monitoring session
254
+ */
255
+ async startDeposit() {
256
+ return this.request("POST", "/finance/deposit/start");
257
+ }
258
+ /**
259
+ * Get deposit address
260
+ */
261
+ async getDepositAddress() {
262
+ const response = await this.request("GET", "/finance/deposit/address");
263
+ return response.address;
264
+ }
265
+ /**
266
+ * Get deposit history
267
+ */
268
+ async getDeposits() {
269
+ const response = await this.request("GET", "/finance/deposits");
270
+ return response.deposits;
271
+ }
272
+ // ============================================================================
273
+ // Notes
274
+ // ============================================================================
275
+ /**
276
+ * Get all notes by current user
277
+ */
278
+ async getNotes() {
279
+ const response = await this.request("GET", "/notes");
280
+ return response.notes;
281
+ }
282
+ /**
283
+ * Get note for specific player
284
+ */
285
+ async getNote(targetId) {
286
+ const response = await this.request("GET", `/notes/${targetId}`);
287
+ return response.note;
288
+ }
289
+ /**
290
+ * Save or update note
291
+ */
292
+ async saveNote(targetId, content, label) {
293
+ const response = await this.request("POST", "/notes", {
294
+ targetId,
295
+ content,
296
+ label
297
+ });
298
+ return response.note;
299
+ }
300
+ /**
301
+ * Delete note
302
+ */
303
+ async deleteNote(targetId) {
304
+ await this.request("DELETE", `/notes/${targetId}`);
305
+ }
306
+ // ============================================================================
307
+ // Health
308
+ // ============================================================================
309
+ /**
310
+ * Health check
311
+ */
312
+ async health() {
313
+ return this.request("GET", "/health");
314
+ }
315
+ // ============================================================================
316
+ // Private Methods
317
+ // ============================================================================
318
+ /**
319
+ * Make HTTP request with retry logic
320
+ */
321
+ async request(method, path, body) {
322
+ const url = `${this.baseUrl}${path}`;
323
+ const headers = {
324
+ "Content-Type": "application/json"
325
+ };
326
+ if (this.token) {
327
+ headers.Authorization = `Bearer ${this.token}`;
328
+ }
329
+ let lastError = null;
330
+ for (let attempt = 0; attempt <= this.retry.count; attempt++) {
331
+ try {
332
+ const controller = new AbortController();
333
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
334
+ if (this.debug) {
335
+ console.log(`[PokerSDK] ${method} ${path}`, body);
336
+ }
337
+ const response = await this.fetchFn(url, {
338
+ method,
339
+ headers,
340
+ body: body ? JSON.stringify(body) : void 0,
341
+ signal: controller.signal
342
+ });
343
+ clearTimeout(timeoutId);
344
+ if (response.status === 304) {
345
+ throw new PokerSDKError("Not Modified", "NOT_MODIFIED", 304);
346
+ }
347
+ if (!response.ok) {
348
+ const errorData = await response.json().catch(() => ({}));
349
+ throw new PokerSDKError(
350
+ errorData.message ?? errorData.error ?? `HTTP ${response.status}`,
351
+ errorData.code ?? errorData.error ?? "HTTP_ERROR",
352
+ response.status,
353
+ errorData
354
+ );
355
+ }
356
+ const data = await response.json();
357
+ if (this.debug) {
358
+ console.log(`[PokerSDK] Response:`, data);
359
+ }
360
+ return data;
361
+ } catch (error) {
362
+ lastError = error;
363
+ if (error instanceof PokerSDKError) {
364
+ if (error.statusCode && error.statusCode >= 400 && error.statusCode < 500 && error.statusCode !== 429) {
365
+ throw error;
366
+ }
367
+ }
368
+ if (error instanceof Error && error.name === "AbortError") {
369
+ throw new PokerSDKError("Request timeout", "TIMEOUT", void 0, {
370
+ timeout: this.timeout
371
+ });
372
+ }
373
+ if (attempt < this.retry.count) {
374
+ const delay = this.retry.delay * Math.pow(this.retry.backoff, attempt);
375
+ if (this.debug) {
376
+ console.log(`[PokerSDK] Retry ${attempt + 1}/${this.retry.count} in ${delay}ms`);
377
+ }
378
+ await this.sleep(delay);
379
+ }
380
+ }
381
+ }
382
+ throw lastError ?? new PokerSDKError("Request failed", "REQUEST_FAILED");
383
+ }
384
+ /**
385
+ * Sleep helper
386
+ */
387
+ sleep(ms) {
388
+ return new Promise((resolve) => setTimeout(resolve, ms));
389
+ }
390
+ };
391
+ var DEFAULT_SOCKET_CONFIG = {
392
+ heartbeatInterval: 25e3,
393
+ reconnectAttempts: 10,
394
+ reconnectDelay: 1e3,
395
+ maxReconnectDelay: 3e4
396
+ };
397
+ var PokerSocket = class _PokerSocket {
398
+ constructor(config) {
399
+ this.ws = null;
400
+ this.connectionState = "disconnected";
401
+ this.reconnectCount = 0;
402
+ this.heartbeatTimer = null;
403
+ this.pendingRequests = /* @__PURE__ */ new Map();
404
+ this.joinedTables = /* @__PURE__ */ new Set();
405
+ this.listeners = /* @__PURE__ */ new Map();
406
+ this.shouldReconnect = true;
407
+ // Latest state cache for each table
408
+ this.stateCache = /* @__PURE__ */ new Map();
409
+ const wsUrl = new URL(config.url);
410
+ wsUrl.searchParams.set("token", config.token);
411
+ this.url = wsUrl.toString();
412
+ this.token = config.token;
413
+ this.heartbeatInterval = config.heartbeatInterval ?? DEFAULT_SOCKET_CONFIG.heartbeatInterval;
414
+ this.reconnectAttempts = config.reconnectAttempts ?? DEFAULT_SOCKET_CONFIG.reconnectAttempts;
415
+ this.reconnectDelay = config.reconnectDelay ?? DEFAULT_SOCKET_CONFIG.reconnectDelay;
416
+ this.maxReconnectDelay = config.maxReconnectDelay ?? DEFAULT_SOCKET_CONFIG.maxReconnectDelay;
417
+ this.WebSocketImpl = config.WebSocket ?? globalThis.WebSocket;
418
+ this.debug = config.debug ?? false;
419
+ }
420
+ /**
421
+ * Create a PokerSocket from SDK config
422
+ */
423
+ static fromConfig(config) {
424
+ if (!config.token) {
425
+ throw new PokerSDKError("Token is required for WebSocket connection", "AUTH_REQUIRED");
426
+ }
427
+ const baseUrl = config.baseUrl.replace(/\/$/, "");
428
+ const wsUrl = config.wsUrl ?? baseUrl.replace(/^http/, "ws") + "/ws/play";
429
+ return new _PokerSocket({
430
+ url: wsUrl,
431
+ token: config.token,
432
+ WebSocket: config.WebSocket,
433
+ debug: config.debug
434
+ });
435
+ }
436
+ // ============================================================================
437
+ // Connection Management
438
+ // ============================================================================
439
+ /**
440
+ * Connect to the WebSocket server
441
+ */
442
+ connect() {
443
+ return new Promise((resolve, reject) => {
444
+ if (this.connectionState === "connected") {
445
+ resolve();
446
+ return;
447
+ }
448
+ if (this.connectionState === "connecting") {
449
+ const checkConnection = () => {
450
+ if (this.connectionState === "connected") {
451
+ resolve();
452
+ } else if (this.connectionState === "disconnected") {
453
+ reject(new PokerSDKError("Connection failed", "CONNECTION_FAILED"));
454
+ } else {
455
+ setTimeout(checkConnection, 100);
456
+ }
457
+ };
458
+ checkConnection();
459
+ return;
460
+ }
461
+ this.shouldReconnect = true;
462
+ this.connectionState = "connecting";
463
+ this.log("Connecting to", this.url);
464
+ try {
465
+ this.ws = new this.WebSocketImpl(this.url);
466
+ this.ws.onopen = () => {
467
+ this.connectionState = "connected";
468
+ this.reconnectCount = 0;
469
+ this.startHeartbeat();
470
+ this.emit("connect");
471
+ this.log("Connected");
472
+ void this.rejoinTables();
473
+ resolve();
474
+ };
475
+ this.ws.onclose = (event) => {
476
+ this.handleDisconnect(event.reason || "Connection closed");
477
+ };
478
+ this.ws.onerror = (event) => {
479
+ this.log("WebSocket error:", event);
480
+ if (this.connectionState === "connecting") {
481
+ reject(new PokerSDKError("Connection failed", "CONNECTION_FAILED"));
482
+ }
483
+ };
484
+ this.ws.onmessage = (event) => {
485
+ this.handleMessage(event.data);
486
+ };
487
+ } catch (error) {
488
+ this.connectionState = "disconnected";
489
+ reject(error);
490
+ }
491
+ });
492
+ }
493
+ /**
494
+ * Disconnect from the WebSocket server
495
+ */
496
+ disconnect() {
497
+ this.shouldReconnect = false;
498
+ this.stopHeartbeat();
499
+ this.clearPendingRequests("Connection closed");
500
+ if (this.ws) {
501
+ this.ws.close(1e3, "Client disconnect");
502
+ this.ws = null;
503
+ }
504
+ this.connectionState = "disconnected";
505
+ this.emit("disconnect", "Client disconnect");
506
+ this.log("Disconnected");
507
+ }
508
+ /**
509
+ * Get current connection state
510
+ */
511
+ getState() {
512
+ return this.connectionState;
513
+ }
514
+ /**
515
+ * Check if connected
516
+ */
517
+ isConnected() {
518
+ return this.connectionState === "connected";
519
+ }
520
+ // ============================================================================
521
+ // Table Subscription
522
+ // ============================================================================
523
+ /**
524
+ * Join a table to receive real-time updates
525
+ */
526
+ async join(tableId) {
527
+ if (this.connectionState !== "connected") {
528
+ throw new PokerSDKError("Not connected", "NOT_CONNECTED");
529
+ }
530
+ const requestId = this.generateRequestId();
531
+ const message = {
532
+ type: "JOIN",
533
+ tableId,
534
+ requestId
535
+ };
536
+ this.joinedTables.add(tableId);
537
+ this.send(message);
538
+ return new Promise((resolve, reject) => {
539
+ const timeout = setTimeout(() => {
540
+ this.pendingRequests.delete(requestId);
541
+ reject(new PokerSDKError("Join timeout", "TIMEOUT"));
542
+ }, 1e4);
543
+ this.pendingRequests.set(`snapshot:${tableId}`, {
544
+ resolve: (state) => {
545
+ clearTimeout(timeout);
546
+ resolve(state);
547
+ },
548
+ reject,
549
+ timeout
550
+ });
551
+ });
552
+ }
553
+ /**
554
+ * Leave a table
555
+ */
556
+ leave(tableId) {
557
+ if (this.connectionState !== "connected") {
558
+ return;
559
+ }
560
+ this.joinedTables.delete(tableId);
561
+ this.stateCache.delete(tableId);
562
+ const message = {
563
+ type: "LEAVE",
564
+ tableId
565
+ };
566
+ this.send(message);
567
+ }
568
+ /**
569
+ * Get currently joined tables
570
+ */
571
+ getJoinedTables() {
572
+ return Array.from(this.joinedTables);
573
+ }
574
+ /**
575
+ * Get cached state for a table
576
+ */
577
+ getCachedState(tableId) {
578
+ return this.stateCache.get(tableId);
579
+ }
580
+ // ============================================================================
581
+ // Event Handling
582
+ // ============================================================================
583
+ /**
584
+ * Subscribe to an event
585
+ */
586
+ on(event, listener) {
587
+ if (!this.listeners.has(event)) {
588
+ this.listeners.set(event, /* @__PURE__ */ new Set());
589
+ }
590
+ this.listeners.get(event).add(listener);
591
+ return () => {
592
+ this.off(event, listener);
593
+ };
594
+ }
595
+ /**
596
+ * Unsubscribe from an event
597
+ */
598
+ off(event, listener) {
599
+ const listeners = this.listeners.get(event);
600
+ if (listeners) {
601
+ listeners.delete(listener);
602
+ }
603
+ }
604
+ /**
605
+ * Subscribe to an event (once)
606
+ */
607
+ once(event, listener) {
608
+ const onceWrapper = ((...args) => {
609
+ this.off(event, onceWrapper);
610
+ listener(...args);
611
+ });
612
+ return this.on(event, onceWrapper);
613
+ }
614
+ // ============================================================================
615
+ // Ping
616
+ // ============================================================================
617
+ /**
618
+ * Send application-level ping (not WebSocket ping)
619
+ */
620
+ async ping() {
621
+ if (this.connectionState !== "connected") {
622
+ throw new PokerSDKError("Not connected", "NOT_CONNECTED");
623
+ }
624
+ const requestId = this.generateRequestId();
625
+ const startTime = Date.now();
626
+ const message = {
627
+ type: "PING",
628
+ requestId,
629
+ timestamp: startTime
630
+ };
631
+ this.send(message);
632
+ return new Promise((resolve, reject) => {
633
+ const timeout = setTimeout(() => {
634
+ this.pendingRequests.delete(requestId);
635
+ reject(new PokerSDKError("Ping timeout", "TIMEOUT"));
636
+ }, 5e3);
637
+ this.pendingRequests.set(requestId, {
638
+ resolve: () => {
639
+ resolve(Date.now() - startTime);
640
+ },
641
+ reject,
642
+ timeout
643
+ });
644
+ });
645
+ }
646
+ // ============================================================================
647
+ // Private Methods
648
+ // ============================================================================
649
+ /**
650
+ * Send a message to the server
651
+ */
652
+ send(message) {
653
+ if (this.ws?.readyState !== this.WebSocketImpl.OPEN) {
654
+ throw new PokerSDKError("WebSocket not open", "NOT_CONNECTED");
655
+ }
656
+ this.log("Sending:", message);
657
+ this.ws.send(JSON.stringify(message));
658
+ }
659
+ /**
660
+ * Handle incoming message
661
+ */
662
+ handleMessage(data) {
663
+ try {
664
+ const parsed = JSON.parse(data);
665
+ const result = types.safeParseServerMessage(parsed);
666
+ if (!result.success) {
667
+ this.log("Invalid server message:", result.error);
668
+ return;
669
+ }
670
+ const message = result.data;
671
+ this.log("Received:", message);
672
+ switch (message.type) {
673
+ case "SNAPSHOT": {
674
+ this.stateCache.set(message.tableId, message.state);
675
+ const pending = this.pendingRequests.get(`snapshot:${message.tableId}`);
676
+ if (pending) {
677
+ this.pendingRequests.delete(`snapshot:${message.tableId}`);
678
+ pending.resolve(message.state);
679
+ }
680
+ this.emit("snapshot", message.tableId, message.state);
681
+ break;
682
+ }
683
+ case "STATE_UPDATE": {
684
+ const cachedState = this.stateCache.get(message.tableId);
685
+ if (cachedState) {
686
+ const updatedState = { ...cachedState, version: message.version };
687
+ this.stateCache.set(message.tableId, updatedState);
688
+ this.emit("stateUpdate", message.tableId, updatedState);
689
+ } else {
690
+ this.emit("stateUpdate", message.tableId, {
691
+ version: message.version
692
+ });
693
+ }
694
+ break;
695
+ }
696
+ case "ACTION": {
697
+ this.emit(
698
+ "action",
699
+ message.tableId,
700
+ message.playerId,
701
+ message.actionType,
702
+ message.amount
703
+ );
704
+ break;
705
+ }
706
+ case "ACK": {
707
+ const pending = this.pendingRequests.get(message.requestId);
708
+ if (pending) {
709
+ clearTimeout(pending.timeout);
710
+ this.pendingRequests.delete(message.requestId);
711
+ pending.resolve(void 0);
712
+ }
713
+ break;
714
+ }
715
+ case "PONG": {
716
+ const pending = this.pendingRequests.get(message.requestId);
717
+ if (pending) {
718
+ clearTimeout(pending.timeout);
719
+ this.pendingRequests.delete(message.requestId);
720
+ pending.resolve(message.timestamp);
721
+ }
722
+ break;
723
+ }
724
+ case "ERROR": {
725
+ this.log("Server error:", message);
726
+ if (message.requestId) {
727
+ const pending = this.pendingRequests.get(message.requestId);
728
+ if (pending) {
729
+ clearTimeout(pending.timeout);
730
+ this.pendingRequests.delete(message.requestId);
731
+ pending.reject(new PokerSDKError(message.message, message.code));
732
+ }
733
+ }
734
+ this.emit("error", new PokerSDKError(message.message, message.code));
735
+ break;
736
+ }
737
+ }
738
+ } catch (error) {
739
+ this.log("Failed to parse message:", error);
740
+ }
741
+ }
742
+ /**
743
+ * Handle disconnect
744
+ */
745
+ handleDisconnect(reason) {
746
+ this.stopHeartbeat();
747
+ this.ws = null;
748
+ const wasConnected = this.connectionState === "connected";
749
+ this.connectionState = "disconnected";
750
+ if (wasConnected) {
751
+ this.emit("disconnect", reason);
752
+ }
753
+ if (this.shouldReconnect && this.reconnectCount < this.reconnectAttempts) {
754
+ void this.reconnect();
755
+ }
756
+ }
757
+ /**
758
+ * Attempt to reconnect
759
+ */
760
+ async reconnect() {
761
+ this.reconnectCount++;
762
+ this.connectionState = "reconnecting";
763
+ const delay = Math.min(
764
+ this.reconnectDelay * Math.pow(2, this.reconnectCount - 1),
765
+ this.maxReconnectDelay
766
+ );
767
+ this.log(
768
+ `Reconnecting in ${delay}ms (attempt ${this.reconnectCount}/${this.reconnectAttempts})`
769
+ );
770
+ this.emit("reconnect", this.reconnectCount);
771
+ await this.sleep(delay);
772
+ if (!this.shouldReconnect) {
773
+ return;
774
+ }
775
+ try {
776
+ await this.connect();
777
+ } catch {
778
+ }
779
+ }
780
+ /**
781
+ * Rejoin previously joined tables
782
+ */
783
+ async rejoinTables() {
784
+ for (const tableId of this.joinedTables) {
785
+ try {
786
+ await this.join(tableId);
787
+ } catch (error) {
788
+ this.log("Failed to rejoin table:", tableId, error);
789
+ }
790
+ }
791
+ }
792
+ /**
793
+ * Start heartbeat timer
794
+ */
795
+ startHeartbeat() {
796
+ this.stopHeartbeat();
797
+ this.heartbeatTimer = setInterval(() => {
798
+ if (this.connectionState === "connected") {
799
+ void this.ping().catch(() => {
800
+ this.log("Heartbeat failed");
801
+ });
802
+ }
803
+ }, this.heartbeatInterval);
804
+ }
805
+ /**
806
+ * Stop heartbeat timer
807
+ */
808
+ stopHeartbeat() {
809
+ if (this.heartbeatTimer) {
810
+ clearInterval(this.heartbeatTimer);
811
+ this.heartbeatTimer = null;
812
+ }
813
+ }
814
+ /**
815
+ * Clear all pending requests
816
+ */
817
+ clearPendingRequests(reason) {
818
+ for (const pending of this.pendingRequests.values()) {
819
+ clearTimeout(pending.timeout);
820
+ pending.reject(new PokerSDKError(reason, "CONNECTION_CLOSED"));
821
+ }
822
+ this.pendingRequests.clear();
823
+ }
824
+ /**
825
+ * Emit event to listeners
826
+ */
827
+ emit(event, ...args) {
828
+ const listeners = this.listeners.get(event);
829
+ if (listeners) {
830
+ for (const listener of listeners) {
831
+ try {
832
+ listener(...args);
833
+ } catch (error) {
834
+ this.log("Listener error:", error);
835
+ }
836
+ }
837
+ }
838
+ }
839
+ /**
840
+ * Generate unique request ID
841
+ */
842
+ generateRequestId() {
843
+ return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
844
+ }
845
+ /**
846
+ * Sleep helper
847
+ */
848
+ sleep(ms) {
849
+ return new Promise((resolve) => setTimeout(resolve, ms));
850
+ }
851
+ /**
852
+ * Debug logger
853
+ */
854
+ log(...args) {
855
+ if (this.debug) {
856
+ console.log("[PokerSocket]", ...args);
857
+ }
858
+ }
859
+ };
860
+ var PokerContext = react.createContext(null);
861
+ function PokerProvider({ config, children, autoConnect = true }) {
862
+ const [connectionState, setConnectionState] = react.useState("disconnected");
863
+ const clientRef = react.useRef(null);
864
+ const socketRef = react.useRef(null);
865
+ clientRef.current ?? (clientRef.current = new PokerClient(config));
866
+ react.useEffect(() => {
867
+ if (clientRef.current) {
868
+ clientRef.current.setToken(config.token ?? null);
869
+ }
870
+ }, [config.token]);
871
+ react.useEffect(() => {
872
+ if (config.token && autoConnect && !socketRef.current) {
873
+ socketRef.current = PokerSocket.fromConfig(config);
874
+ socketRef.current.on("connect", () => setConnectionState("connected"));
875
+ socketRef.current.on("disconnect", () => setConnectionState("disconnected"));
876
+ socketRef.current.on("reconnect", () => setConnectionState("reconnecting"));
877
+ void socketRef.current.connect().catch(console.error);
878
+ }
879
+ return () => {
880
+ if (socketRef.current) {
881
+ socketRef.current.disconnect();
882
+ socketRef.current = null;
883
+ }
884
+ };
885
+ }, [config.token, autoConnect, config]);
886
+ const connect = react.useCallback(async () => {
887
+ if (!config.token) {
888
+ throw new Error("Token required to connect");
889
+ }
890
+ if (!socketRef.current) {
891
+ socketRef.current = PokerSocket.fromConfig(config);
892
+ socketRef.current.on("connect", () => setConnectionState("connected"));
893
+ socketRef.current.on("disconnect", () => setConnectionState("disconnected"));
894
+ socketRef.current.on("reconnect", () => setConnectionState("reconnecting"));
895
+ }
896
+ setConnectionState("connecting");
897
+ await socketRef.current.connect();
898
+ }, [config]);
899
+ const disconnect = react.useCallback(() => {
900
+ if (socketRef.current) {
901
+ socketRef.current.disconnect();
902
+ }
903
+ }, []);
904
+ const value = react.useMemo(
905
+ () => ({
906
+ client: clientRef.current,
907
+ socket: socketRef.current,
908
+ isAuthenticated: !!config.token,
909
+ connectionState,
910
+ connect,
911
+ disconnect
912
+ }),
913
+ [config.token, connectionState, connect, disconnect]
914
+ );
915
+ return /* @__PURE__ */ jsxRuntime.jsx(PokerContext.Provider, { value, children });
916
+ }
917
+ function usePoker() {
918
+ const context = react.useContext(PokerContext);
919
+ if (!context) {
920
+ throw new Error("usePoker must be used within a PokerProvider");
921
+ }
922
+ return context;
923
+ }
924
+ function usePokerClient() {
925
+ return usePoker().client;
926
+ }
927
+ function usePokerSocket() {
928
+ return usePoker().socket;
929
+ }
930
+ function useTable(tableId, options = {}) {
931
+ const { pollInterval, autoJoin = true } = options;
932
+ const { client, socket } = usePoker();
933
+ const [state, setState] = react.useState(null);
934
+ const [isLoading, setIsLoading] = react.useState(true);
935
+ const [error, setError] = react.useState(null);
936
+ const versionRef = react.useRef(0);
937
+ const refresh = react.useCallback(async () => {
938
+ try {
939
+ setIsLoading(true);
940
+ const newState = await client.getTableState(tableId);
941
+ if (newState) {
942
+ setState(newState);
943
+ versionRef.current = newState.version;
944
+ }
945
+ setError(null);
946
+ } catch (err) {
947
+ setError(err);
948
+ } finally {
949
+ setIsLoading(false);
950
+ }
951
+ }, [client, tableId]);
952
+ react.useEffect(() => {
953
+ void refresh();
954
+ }, [refresh]);
955
+ react.useEffect(() => {
956
+ if (!socket || !autoJoin) return;
957
+ const handleStateUpdate = (id, newState) => {
958
+ if (id === tableId) {
959
+ setState(newState);
960
+ versionRef.current = newState.version;
961
+ }
962
+ };
963
+ const handleSnapshot = (id, newState) => {
964
+ if (id === tableId) {
965
+ setState(newState);
966
+ versionRef.current = newState.version;
967
+ setIsLoading(false);
968
+ }
969
+ };
970
+ socket.on("stateUpdate", handleStateUpdate);
971
+ socket.on("snapshot", handleSnapshot);
972
+ if (socket.isConnected()) {
973
+ void socket.join(tableId).catch(console.error);
974
+ }
975
+ return () => {
976
+ socket.off("stateUpdate", handleStateUpdate);
977
+ socket.off("snapshot", handleSnapshot);
978
+ socket.leave(tableId);
979
+ };
980
+ }, [socket, tableId, autoJoin]);
981
+ react.useEffect(() => {
982
+ if (!pollInterval) return;
983
+ const interval = setInterval(() => {
984
+ void (async () => {
985
+ const newState = await client.getTableState(tableId, versionRef.current);
986
+ if (newState) {
987
+ setState(newState);
988
+ versionRef.current = newState.version;
989
+ }
990
+ })();
991
+ }, pollInterval);
992
+ return () => clearInterval(interval);
993
+ }, [client, tableId, pollInterval]);
994
+ const action = react.useCallback(
995
+ async (type, amount) => {
996
+ const newState = await client.action(tableId, { type, amount });
997
+ setState(newState);
998
+ versionRef.current = newState.version;
999
+ },
1000
+ [client, tableId]
1001
+ );
1002
+ const leave = react.useCallback(async () => {
1003
+ await client.stand(tableId);
1004
+ if (socket) {
1005
+ socket.leave(tableId);
1006
+ }
1007
+ }, [client, socket, tableId]);
1008
+ return { state, isLoading, error, refresh, action, leave };
1009
+ }
1010
+ function useUser() {
1011
+ const { client, isAuthenticated } = usePoker();
1012
+ const [profile, setProfile] = react.useState(null);
1013
+ const [isLoading, setIsLoading] = react.useState(false);
1014
+ const [error, setError] = react.useState(null);
1015
+ const refresh = react.useCallback(async () => {
1016
+ if (!isAuthenticated) return;
1017
+ try {
1018
+ setIsLoading(true);
1019
+ const data = await client.getProfile();
1020
+ setProfile(data);
1021
+ setError(null);
1022
+ } catch (err) {
1023
+ setError(err);
1024
+ } finally {
1025
+ setIsLoading(false);
1026
+ }
1027
+ }, [client, isAuthenticated]);
1028
+ react.useEffect(() => {
1029
+ if (isAuthenticated) {
1030
+ void refresh();
1031
+ } else {
1032
+ setProfile(null);
1033
+ }
1034
+ }, [isAuthenticated, refresh]);
1035
+ return {
1036
+ profile,
1037
+ balances: profile?.balances ?? null,
1038
+ isLoading,
1039
+ error,
1040
+ refresh
1041
+ };
1042
+ }
1043
+ function useTables() {
1044
+ const { client } = usePoker();
1045
+ const [tables, setTables] = react.useState([]);
1046
+ const [isLoading, setIsLoading] = react.useState(true);
1047
+ const [error, setError] = react.useState(null);
1048
+ const refresh = react.useCallback(async () => {
1049
+ try {
1050
+ setIsLoading(true);
1051
+ const data = await client.getTables();
1052
+ setTables(data);
1053
+ setError(null);
1054
+ } catch (err) {
1055
+ setError(err);
1056
+ } finally {
1057
+ setIsLoading(false);
1058
+ }
1059
+ }, [client]);
1060
+ react.useEffect(() => {
1061
+ void refresh();
1062
+ }, [refresh]);
1063
+ return { tables, isLoading, error, refresh };
1064
+ }
1065
+ function useConnection() {
1066
+ const { socket, connectionState, connect, disconnect } = usePoker();
1067
+ const [latency, setLatency] = react.useState(null);
1068
+ const ping = react.useCallback(async () => {
1069
+ if (!socket?.isConnected()) return null;
1070
+ try {
1071
+ const ms = await socket.ping();
1072
+ setLatency(ms);
1073
+ return ms;
1074
+ } catch {
1075
+ return null;
1076
+ }
1077
+ }, [socket]);
1078
+ return {
1079
+ state: connectionState,
1080
+ isConnected: connectionState === "connected",
1081
+ isConnecting: connectionState === "connecting",
1082
+ isReconnecting: connectionState === "reconnecting",
1083
+ latency,
1084
+ connect,
1085
+ disconnect,
1086
+ ping
1087
+ };
1088
+ }
1089
+
1090
+ exports.PokerProvider = PokerProvider;
1091
+ exports.useConnection = useConnection;
1092
+ exports.usePoker = usePoker;
1093
+ exports.usePokerClient = usePokerClient;
1094
+ exports.usePokerSocket = usePokerSocket;
1095
+ exports.useTable = useTable;
1096
+ exports.useTables = useTables;
1097
+ exports.useUser = useUser;
1098
+ //# sourceMappingURL=index.cjs.map
1099
+ //# sourceMappingURL=index.cjs.map