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