@arcadiasol/sdk 1.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.
@@ -0,0 +1,949 @@
1
+ (function (global, factory) {
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ArcadiaGameSDK = {}));
5
+ })(this, (function (exports) { 'use strict';
6
+
7
+ /**
8
+ * Message types for postMessage communication
9
+ */
10
+ var MessageType;
11
+ (function (MessageType) {
12
+ // SDK → Parent
13
+ MessageType["INIT_REQUEST"] = "INIT_REQUEST";
14
+ MessageType["GET_WALLET_ADDRESS"] = "GET_WALLET_ADDRESS";
15
+ MessageType["PAYMENT_REQUEST"] = "PAYMENT_REQUEST";
16
+ MessageType["GAME_READY"] = "GAME_READY";
17
+ // Parent → SDK
18
+ MessageType["INIT"] = "INIT";
19
+ MessageType["WALLET_ADDRESS_RESPONSE"] = "WALLET_ADDRESS_RESPONSE";
20
+ MessageType["WALLET_UPDATE"] = "WALLET_UPDATE";
21
+ MessageType["PAYMENT_RESPONSE"] = "PAYMENT_RESPONSE";
22
+ })(MessageType || (MessageType = {}));
23
+
24
+ /**
25
+ * Base error class for all SDK errors
26
+ */
27
+ class ArcadiaSDKError extends Error {
28
+ constructor(message, code) {
29
+ super(message);
30
+ this.code = code;
31
+ this.name = 'ArcadiaSDKError';
32
+ Object.setPrototypeOf(this, ArcadiaSDKError.prototype);
33
+ }
34
+ }
35
+ /**
36
+ * Error thrown when wallet is not connected
37
+ */
38
+ class WalletNotConnectedError extends ArcadiaSDKError {
39
+ constructor(message = 'Wallet is not connected. Please connect your wallet in Arcadia.') {
40
+ super(message, 'WALLET_NOT_CONNECTED');
41
+ this.name = 'WalletNotConnectedError';
42
+ Object.setPrototypeOf(this, WalletNotConnectedError.prototype);
43
+ }
44
+ }
45
+ /**
46
+ * Error thrown when payment fails
47
+ */
48
+ class PaymentFailedError extends ArcadiaSDKError {
49
+ constructor(message = 'Payment failed', txSignature) {
50
+ super(message, 'PAYMENT_FAILED');
51
+ this.txSignature = txSignature;
52
+ this.name = 'PaymentFailedError';
53
+ Object.setPrototypeOf(this, PaymentFailedError.prototype);
54
+ }
55
+ }
56
+ /**
57
+ * Error thrown when request times out
58
+ */
59
+ class TimeoutError extends ArcadiaSDKError {
60
+ constructor(message = 'Request timed out. Please try again.') {
61
+ super(message, 'TIMEOUT');
62
+ this.name = 'TimeoutError';
63
+ Object.setPrototypeOf(this, TimeoutError.prototype);
64
+ }
65
+ }
66
+ /**
67
+ * Error thrown when SDK configuration is invalid
68
+ */
69
+ class InvalidConfigError extends ArcadiaSDKError {
70
+ constructor(message = 'Invalid SDK configuration') {
71
+ super(message, 'INVALID_CONFIG');
72
+ this.name = 'InvalidConfigError';
73
+ Object.setPrototypeOf(this, InvalidConfigError.prototype);
74
+ }
75
+ }
76
+ /**
77
+ * Error thrown when SDK is not running in iframe
78
+ */
79
+ class NotInIframeError extends ArcadiaSDKError {
80
+ constructor(message = 'This function only works when the game is running in an Arcadia iframe') {
81
+ super(message, 'NOT_IN_IFRAME');
82
+ this.name = 'NotInIframeError';
83
+ Object.setPrototypeOf(this, NotInIframeError.prototype);
84
+ }
85
+ }
86
+ /**
87
+ * Error thrown when invalid amount is provided
88
+ */
89
+ class InvalidAmountError extends ArcadiaSDKError {
90
+ constructor(message = 'Invalid payment amount. Amount must be greater than 0.') {
91
+ super(message, 'INVALID_AMOUNT');
92
+ this.name = 'InvalidAmountError';
93
+ Object.setPrototypeOf(this, InvalidAmountError.prototype);
94
+ }
95
+ }
96
+ /**
97
+ * Error thrown when invalid token is provided
98
+ */
99
+ class InvalidTokenError extends ArcadiaSDKError {
100
+ constructor(message = "Invalid token. Must be 'SOL' or 'USDC'.") {
101
+ super(message, 'INVALID_TOKEN');
102
+ this.name = 'InvalidTokenError';
103
+ Object.setPrototypeOf(this, InvalidTokenError.prototype);
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Handles postMessage communication with parent window
109
+ */
110
+ class MessageHandler {
111
+ constructor(parentOrigin = '*', timeout = 30000) {
112
+ this.messageId = 0;
113
+ this.pendingRequests = new Map();
114
+ this.messageListener = null;
115
+ this.parentOrigin = parentOrigin;
116
+ this.timeout = timeout;
117
+ }
118
+ /**
119
+ * Initialize message listener
120
+ */
121
+ init() {
122
+ if (this.messageListener) {
123
+ return; // Already initialized
124
+ }
125
+ this.messageListener = (event) => {
126
+ this.handleMessage(event);
127
+ };
128
+ window.addEventListener('message', this.messageListener);
129
+ }
130
+ /**
131
+ * Cleanup message listener
132
+ */
133
+ destroy() {
134
+ if (this.messageListener) {
135
+ window.removeEventListener('message', this.messageListener);
136
+ this.messageListener = null;
137
+ }
138
+ // Clear all pending requests
139
+ this.pendingRequests.forEach((request) => {
140
+ clearTimeout(request.timeout);
141
+ request.reject(new Error('Message handler destroyed'));
142
+ });
143
+ this.pendingRequests.clear();
144
+ }
145
+ /**
146
+ * Send message to parent window and wait for response
147
+ */
148
+ sendMessage(type, payload) {
149
+ return new Promise((resolve, reject) => {
150
+ const id = ++this.messageId;
151
+ // Set up timeout
152
+ const timeout = setTimeout(() => {
153
+ this.pendingRequests.delete(id);
154
+ reject(new TimeoutError(`Request ${type} timed out after ${this.timeout}ms`));
155
+ }, this.timeout);
156
+ // Store request
157
+ this.pendingRequests.set(id, {
158
+ resolve: (value) => {
159
+ clearTimeout(timeout);
160
+ resolve(value);
161
+ },
162
+ reject: (error) => {
163
+ clearTimeout(timeout);
164
+ reject(error);
165
+ },
166
+ timeout,
167
+ });
168
+ // Send message to parent
169
+ const message = {
170
+ type,
171
+ id,
172
+ payload,
173
+ };
174
+ if (window.parent) {
175
+ window.parent.postMessage(message, this.parentOrigin);
176
+ }
177
+ else {
178
+ clearTimeout(timeout);
179
+ this.pendingRequests.delete(id);
180
+ reject(new Error('No parent window found'));
181
+ }
182
+ });
183
+ }
184
+ /**
185
+ * Handle incoming messages from parent
186
+ */
187
+ handleMessage(event) {
188
+ // Validate origin if specified
189
+ if (this.parentOrigin !== '*' && event.origin !== this.parentOrigin) {
190
+ return; // Ignore messages from unauthorized origins
191
+ }
192
+ const message = event.data;
193
+ // Handle response messages (have ID)
194
+ if (message.id !== undefined) {
195
+ const request = this.pendingRequests.get(message.id);
196
+ if (request) {
197
+ this.pendingRequests.delete(message.id);
198
+ if (message.type === MessageType.PAYMENT_RESPONSE) {
199
+ const response = message.payload;
200
+ if (response.success) {
201
+ request.resolve(response);
202
+ }
203
+ else {
204
+ request.reject(new Error(response.error || 'Payment failed'));
205
+ }
206
+ }
207
+ else if (message.type === MessageType.WALLET_ADDRESS_RESPONSE) {
208
+ request.resolve(message.payload);
209
+ }
210
+ else if (message.type === MessageType.INIT) {
211
+ request.resolve(message.payload);
212
+ }
213
+ else {
214
+ // Generic response - resolve with payload
215
+ request.resolve(message.payload);
216
+ }
217
+ }
218
+ }
219
+ }
220
+ /**
221
+ * Send one-way message (no response expected)
222
+ */
223
+ sendOneWay(type, payload) {
224
+ const message = {
225
+ type,
226
+ payload,
227
+ };
228
+ if (window.parent) {
229
+ window.parent.postMessage(message, this.parentOrigin);
230
+ }
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Wallet address management
236
+ */
237
+ class WalletManager {
238
+ constructor(messageHandler, isIframe, apiClient) {
239
+ this.walletAddress = null;
240
+ this.walletConnected = false;
241
+ this.apiClient = null;
242
+ this.walletChangeCallbacks = [];
243
+ this.messageHandler = messageHandler;
244
+ this.isIframe = isIframe;
245
+ this.apiClient = apiClient || null;
246
+ }
247
+ /**
248
+ * Get wallet address from parent window (iframe) or API (non-iframe)
249
+ */
250
+ async getWalletAddress() {
251
+ if (this.isIframe) {
252
+ // Iframe mode: use postMessage
253
+ try {
254
+ const response = await this.messageHandler.sendMessage(MessageType.GET_WALLET_ADDRESS);
255
+ this.walletAddress = response.walletAddress;
256
+ this.walletConnected = response.connected;
257
+ return this.walletAddress;
258
+ }
259
+ catch (error) {
260
+ // If request fails, assume wallet not connected
261
+ this.walletAddress = null;
262
+ this.walletConnected = false;
263
+ return null;
264
+ }
265
+ }
266
+ else {
267
+ // Non-iframe mode: use API
268
+ if (!this.apiClient) {
269
+ throw new Error('API client not initialized. Ensure apiBaseURL is configured in SDK config.');
270
+ }
271
+ try {
272
+ const response = await this.apiClient.getWalletAddress();
273
+ this.walletAddress = response.walletAddress;
274
+ this.walletConnected = response.connected;
275
+ return this.walletAddress;
276
+ }
277
+ catch (error) {
278
+ this.walletAddress = null;
279
+ this.walletConnected = false;
280
+ return null;
281
+ }
282
+ }
283
+ }
284
+ /**
285
+ * Check if wallet is connected
286
+ */
287
+ async isWalletConnected() {
288
+ // If we have cached address, return true
289
+ if (this.walletAddress) {
290
+ return true;
291
+ }
292
+ // Otherwise, fetch from parent (iframe) or API (non-iframe)
293
+ const address = await this.getWalletAddress();
294
+ return address !== null;
295
+ }
296
+ /**
297
+ * Listen for wallet connection changes
298
+ */
299
+ onWalletChange(callback) {
300
+ this.walletChangeCallbacks.push(callback);
301
+ }
302
+ /**
303
+ * Remove wallet change listener
304
+ */
305
+ offWalletChange(callback) {
306
+ const index = this.walletChangeCallbacks.indexOf(callback);
307
+ if (index > -1) {
308
+ this.walletChangeCallbacks.splice(index, 1);
309
+ }
310
+ }
311
+ /**
312
+ * Update wallet status (called by SDK when receiving WALLET_UPDATE message)
313
+ */
314
+ updateWalletStatus(walletInfo) {
315
+ const oldAddress = this.walletAddress;
316
+ const oldConnected = this.walletConnected;
317
+ this.walletAddress = walletInfo.address;
318
+ this.walletConnected = walletInfo.connected;
319
+ // Notify callbacks if status changed
320
+ if (oldAddress !== this.walletAddress || oldConnected !== this.walletConnected) {
321
+ this.walletChangeCallbacks.forEach((callback) => {
322
+ try {
323
+ callback(this.walletConnected, this.walletAddress);
324
+ }
325
+ catch (error) {
326
+ console.error('Error in wallet change callback:', error);
327
+ }
328
+ });
329
+ }
330
+ }
331
+ /**
332
+ * Get current cached wallet address (without fetching)
333
+ */
334
+ getCachedWalletAddress() {
335
+ return this.walletAddress;
336
+ }
337
+ /**
338
+ * Get current cached connection status (without fetching)
339
+ */
340
+ getCachedConnectionStatus() {
341
+ return this.walletConnected;
342
+ }
343
+ /**
344
+ * Clear cached wallet data
345
+ */
346
+ clearCache() {
347
+ this.walletAddress = null;
348
+ this.walletConnected = false;
349
+ }
350
+ }
351
+
352
+ /**
353
+ * Payment processing
354
+ */
355
+ class PaymentManager {
356
+ constructor(gameId, isIframe, messageHandler, walletManager, apiClient) {
357
+ this.apiClient = null;
358
+ this.gameId = gameId;
359
+ this.isIframe = isIframe;
360
+ this.messageHandler = messageHandler;
361
+ this.walletManager = walletManager;
362
+ this.apiClient = apiClient || null;
363
+ }
364
+ /**
365
+ * Pay to play - one-time payment to access game
366
+ * @param amount - Payment amount
367
+ * @param token - Token type: 'SOL', 'USDC', or custom token symbol/mint address
368
+ * @param txSignature - Transaction signature (required for non-iframe mode)
369
+ */
370
+ async payToPlay(amount, token, txSignature) {
371
+ this.validatePaymentParams(amount, token);
372
+ // Check wallet connection
373
+ const isConnected = await this.walletManager.isWalletConnected();
374
+ if (!isConnected) {
375
+ throw new WalletNotConnectedError();
376
+ }
377
+ const request = {
378
+ amount,
379
+ token,
380
+ type: 'pay_to_play',
381
+ gameId: this.gameId,
382
+ txSignature, // Optional for iframe mode, required for non-iframe
383
+ };
384
+ if (this.isIframe) {
385
+ // Iframe mode: use postMessage
386
+ try {
387
+ const response = await this.messageHandler.sendMessage(MessageType.PAYMENT_REQUEST, request);
388
+ if (!response.success) {
389
+ throw new PaymentFailedError(response.error || 'Payment failed', response.txSignature);
390
+ }
391
+ if (!response.txSignature) {
392
+ throw new PaymentFailedError('Payment succeeded but no transaction signature received');
393
+ }
394
+ if (!response.amount || !response.token || !response.timestamp) {
395
+ throw new PaymentFailedError('Payment succeeded but incomplete payment details received');
396
+ }
397
+ return {
398
+ success: true,
399
+ txSignature: response.txSignature,
400
+ amount: response.amount,
401
+ token: response.token,
402
+ timestamp: response.timestamp,
403
+ purchaseId: response.purchaseId,
404
+ platformFee: response.platformFee,
405
+ developerAmount: response.developerAmount,
406
+ };
407
+ }
408
+ catch (error) {
409
+ if (error instanceof PaymentFailedError || error instanceof WalletNotConnectedError) {
410
+ throw error;
411
+ }
412
+ throw new PaymentFailedError(error instanceof Error ? error.message : 'Payment failed');
413
+ }
414
+ }
415
+ else {
416
+ // Non-iframe mode: use API
417
+ if (!this.apiClient) {
418
+ throw new Error('API client not initialized. Ensure apiBaseURL is configured in SDK config.');
419
+ }
420
+ if (!txSignature) {
421
+ throw new PaymentFailedError('Transaction signature is required for non-iframe payments. ' +
422
+ 'Please sign the transaction first and provide the signature.');
423
+ }
424
+ try {
425
+ const response = await this.apiClient.sendPaymentRequest(this.gameId, {
426
+ ...request,
427
+ txSignature,
428
+ });
429
+ if (!response.success) {
430
+ throw new PaymentFailedError(response.error || 'Payment failed', response.txSignature);
431
+ }
432
+ if (!response.txSignature) {
433
+ throw new PaymentFailedError('Payment succeeded but no transaction signature received');
434
+ }
435
+ if (!response.amount || !response.token || !response.timestamp) {
436
+ throw new PaymentFailedError('Payment succeeded but incomplete payment details received');
437
+ }
438
+ return {
439
+ success: true,
440
+ txSignature: response.txSignature,
441
+ amount: response.amount,
442
+ token: response.token,
443
+ timestamp: response.timestamp,
444
+ purchaseId: response.purchaseId,
445
+ platformFee: response.platformFee,
446
+ developerAmount: response.developerAmount,
447
+ };
448
+ }
449
+ catch (error) {
450
+ if (error instanceof PaymentFailedError || error instanceof WalletNotConnectedError) {
451
+ throw error;
452
+ }
453
+ throw new PaymentFailedError(error instanceof Error ? error.message : 'Payment failed');
454
+ }
455
+ }
456
+ }
457
+ /**
458
+ * Purchase in-game item
459
+ * @param itemId - Item identifier
460
+ * @param amount - Payment amount
461
+ * @param token - Token type: 'SOL', 'USDC', or custom token symbol/mint address
462
+ * @param txSignature - Transaction signature (required for non-iframe mode)
463
+ */
464
+ async purchaseItem(itemId, amount, token, txSignature) {
465
+ this.validatePaymentParams(amount, token);
466
+ if (!itemId || typeof itemId !== 'string' || itemId.trim().length === 0) {
467
+ throw new Error('Item ID is required and must be a non-empty string');
468
+ }
469
+ // Check wallet connection
470
+ const isConnected = await this.walletManager.isWalletConnected();
471
+ if (!isConnected) {
472
+ throw new WalletNotConnectedError();
473
+ }
474
+ const request = {
475
+ amount,
476
+ token,
477
+ type: 'in_game_purchase',
478
+ gameId: this.gameId,
479
+ itemId: itemId.trim(),
480
+ txSignature, // Optional for iframe mode, required for non-iframe
481
+ };
482
+ if (this.isIframe) {
483
+ // Iframe mode: use postMessage
484
+ try {
485
+ const response = await this.messageHandler.sendMessage(MessageType.PAYMENT_REQUEST, request);
486
+ if (!response.success) {
487
+ throw new PaymentFailedError(response.error || 'Purchase failed', response.txSignature);
488
+ }
489
+ if (!response.txSignature) {
490
+ throw new PaymentFailedError('Purchase succeeded but no transaction signature received');
491
+ }
492
+ if (!response.amount || !response.token || !response.timestamp) {
493
+ throw new PaymentFailedError('Purchase succeeded but incomplete payment details received');
494
+ }
495
+ return {
496
+ success: true,
497
+ txSignature: response.txSignature,
498
+ amount: response.amount,
499
+ token: response.token,
500
+ timestamp: response.timestamp,
501
+ purchaseId: response.purchaseId,
502
+ platformFee: response.platformFee,
503
+ developerAmount: response.developerAmount,
504
+ };
505
+ }
506
+ catch (error) {
507
+ if (error instanceof PaymentFailedError || error instanceof WalletNotConnectedError) {
508
+ throw error;
509
+ }
510
+ throw new PaymentFailedError(error instanceof Error ? error.message : 'Purchase failed');
511
+ }
512
+ }
513
+ else {
514
+ // Non-iframe mode: use API
515
+ if (!this.apiClient) {
516
+ throw new Error('API client not initialized. Ensure apiBaseURL is configured in SDK config.');
517
+ }
518
+ if (!txSignature) {
519
+ throw new PaymentFailedError('Transaction signature is required for non-iframe purchases. ' +
520
+ 'Please sign the transaction first and provide the signature.');
521
+ }
522
+ try {
523
+ const response = await this.apiClient.sendPaymentRequest(this.gameId, {
524
+ ...request,
525
+ txSignature,
526
+ });
527
+ if (!response.success) {
528
+ throw new PaymentFailedError(response.error || 'Purchase failed', response.txSignature);
529
+ }
530
+ if (!response.txSignature) {
531
+ throw new PaymentFailedError('Purchase succeeded but no transaction signature received');
532
+ }
533
+ if (!response.amount || !response.token || !response.timestamp) {
534
+ throw new PaymentFailedError('Purchase succeeded but incomplete payment details received');
535
+ }
536
+ return {
537
+ success: true,
538
+ txSignature: response.txSignature,
539
+ amount: response.amount,
540
+ token: response.token,
541
+ timestamp: response.timestamp,
542
+ purchaseId: response.purchaseId,
543
+ platformFee: response.platformFee,
544
+ developerAmount: response.developerAmount,
545
+ };
546
+ }
547
+ catch (error) {
548
+ if (error instanceof PaymentFailedError || error instanceof WalletNotConnectedError) {
549
+ throw error;
550
+ }
551
+ throw new PaymentFailedError(error instanceof Error ? error.message : 'Purchase failed');
552
+ }
553
+ }
554
+ }
555
+ /**
556
+ * Validate payment parameters
557
+ */
558
+ validatePaymentParams(amount, token) {
559
+ if (typeof amount !== 'number' || isNaN(amount) || amount <= 0) {
560
+ throw new InvalidAmountError();
561
+ }
562
+ if (!token || typeof token !== 'string' || token.trim().length === 0) {
563
+ throw new InvalidTokenError();
564
+ }
565
+ // SOL and USDC are always valid
566
+ // Custom tokens will be validated by the backend
567
+ }
568
+ }
569
+
570
+ /**
571
+ * API Client for non-iframe environments
572
+ * Handles direct REST API calls when SDK is not running in iframe
573
+ */
574
+ class APIClient {
575
+ constructor(baseURL) {
576
+ this.authToken = null;
577
+ this.walletAddress = null;
578
+ // Use provided base URL or detect from environment
579
+ this.baseURL = baseURL || this.detectBaseURL();
580
+ }
581
+ /**
582
+ * Detect Arcadia base URL from environment or use default
583
+ */
584
+ detectBaseURL() {
585
+ // Check for explicit base URL in config
586
+ if (typeof window !== 'undefined' && window.ARCADIA_API_URL) {
587
+ return window.ARCADIA_API_URL;
588
+ }
589
+ // Try to detect from current page (if game is hosted on Arcadia subdomain)
590
+ if (typeof window !== 'undefined') {
591
+ const hostname = window.location.hostname;
592
+ // If on arcadia.com or subdomain, use same origin
593
+ if (hostname.includes('arcadia')) {
594
+ return `${window.location.protocol}//${hostname}`;
595
+ }
596
+ }
597
+ // Default fallback (should be configured by developer)
598
+ return 'https://arcadia.com';
599
+ }
600
+ /**
601
+ * Set authentication token (from wallet authentication)
602
+ */
603
+ setAuthToken(token) {
604
+ this.authToken = token;
605
+ }
606
+ /**
607
+ * Set wallet address (for wallet-based auth)
608
+ */
609
+ setWalletAddress(address) {
610
+ this.walletAddress = address;
611
+ }
612
+ /**
613
+ * Authenticate with wallet
614
+ * Returns auth token for subsequent API calls
615
+ */
616
+ async authenticateWithWallet(walletAddress, signature, message) {
617
+ const response = await this.request('/api/auth/wallet/connect', {
618
+ method: 'POST',
619
+ body: {
620
+ walletAddress,
621
+ signature,
622
+ message,
623
+ },
624
+ });
625
+ // Store auth token from response
626
+ // Note: You may need to extract token from response based on your auth implementation
627
+ if (response.token) {
628
+ this.authToken = response.token;
629
+ }
630
+ return response;
631
+ }
632
+ /**
633
+ * Get wallet address (for non-iframe)
634
+ * Requires authentication
635
+ */
636
+ async getWalletAddress() {
637
+ if (!this.authToken && !this.walletAddress) {
638
+ return {
639
+ walletAddress: null,
640
+ connected: false,
641
+ };
642
+ }
643
+ // If we have wallet address cached, return it
644
+ if (this.walletAddress) {
645
+ return {
646
+ walletAddress: this.walletAddress,
647
+ connected: true,
648
+ };
649
+ }
650
+ // Otherwise, fetch from profile endpoint
651
+ try {
652
+ const profile = await this.request('/api/profile', {
653
+ method: 'GET',
654
+ });
655
+ if (profile?.walletAddress) {
656
+ this.walletAddress = profile.walletAddress;
657
+ return {
658
+ walletAddress: profile.walletAddress,
659
+ connected: true,
660
+ };
661
+ }
662
+ }
663
+ catch (error) {
664
+ console.warn('Failed to fetch wallet address:', error);
665
+ }
666
+ return {
667
+ walletAddress: null,
668
+ connected: false,
669
+ };
670
+ }
671
+ /**
672
+ * Send payment request via API
673
+ */
674
+ async sendPaymentRequest(gameId, request) {
675
+ const endpoint = request.type === 'pay_to_play'
676
+ ? `/api/games/${gameId}/pay-to-play`
677
+ : `/api/games/${gameId}/purchase-item`;
678
+ return await this.request(endpoint, {
679
+ method: 'POST',
680
+ body: {
681
+ amount: request.amount,
682
+ token: request.token,
683
+ txSignature: request.txSignature, // App must provide this
684
+ itemId: request.itemId,
685
+ },
686
+ });
687
+ }
688
+ /**
689
+ * Update playtime/stats
690
+ */
691
+ async updatePlaytime(gameId, playtimeHours, status) {
692
+ await this.request('/api/library', {
693
+ method: 'POST',
694
+ body: {
695
+ gameId,
696
+ playtimeHours,
697
+ status: status || 'playing',
698
+ },
699
+ });
700
+ }
701
+ /**
702
+ * Update online status
703
+ */
704
+ async updateOnlineStatus(isOnline, gameId) {
705
+ await this.request('/api/online-status', {
706
+ method: 'POST',
707
+ body: {
708
+ isOnline,
709
+ gameId,
710
+ },
711
+ });
712
+ }
713
+ /**
714
+ * Generic request method
715
+ */
716
+ async request(endpoint, options = {}) {
717
+ const url = `${this.baseURL}${endpoint}`;
718
+ const headers = {
719
+ 'Content-Type': 'application/json',
720
+ ...options.headers,
721
+ };
722
+ // Add auth token if available
723
+ if (this.authToken) {
724
+ headers['Authorization'] = `Bearer ${this.authToken}`;
725
+ }
726
+ const response = await fetch(url, {
727
+ method: options.method || 'GET',
728
+ headers,
729
+ body: options.body ? JSON.stringify(options.body) : undefined,
730
+ });
731
+ if (!response.ok) {
732
+ const error = await response.json().catch(() => ({ error: 'Request failed' }));
733
+ throw new Error(error.error || `Request failed with status ${response.status}`);
734
+ }
735
+ return await response.json();
736
+ }
737
+ }
738
+
739
+ /**
740
+ * Main Arcadia Game SDK class
741
+ */
742
+ class ArcadiaSDK {
743
+ /**
744
+ * Create new SDK instance
745
+ */
746
+ constructor(config) {
747
+ this.initialized = false;
748
+ this.apiClient = null;
749
+ // Validate config
750
+ if (!config || !config.gameId || typeof config.gameId !== 'string' || config.gameId.trim().length === 0) {
751
+ throw new InvalidConfigError('gameId is required and must be a non-empty string');
752
+ }
753
+ this.config = {
754
+ gameId: config.gameId.trim(),
755
+ parentOrigin: config.parentOrigin || '*',
756
+ timeout: config.timeout || 30000,
757
+ };
758
+ // Detect iframe environment
759
+ this.isIframe = typeof window !== 'undefined' && window.self !== window.top;
760
+ // Initialize message handler
761
+ this.messageHandler = new MessageHandler(this.config.parentOrigin, this.config.timeout);
762
+ // Initialize managers
763
+ this.walletManager = new WalletManager(this.messageHandler, this.isIframe, this.apiClient);
764
+ this.paymentManager = new PaymentManager(this.config.gameId, this.isIframe, this.messageHandler, this.walletManager, this.apiClient);
765
+ // Set up message listener if in iframe
766
+ if (this.isIframe) {
767
+ this.messageHandler.init();
768
+ this.setupMessageListener();
769
+ }
770
+ else {
771
+ // Initialize API client for non-iframe environments
772
+ this.apiClient = new APIClient(this.config.apiBaseURL);
773
+ // Set auth token if provided
774
+ if (this.config.authToken) {
775
+ this.apiClient.setAuthToken(this.config.authToken);
776
+ }
777
+ // Set wallet address if provided
778
+ if (this.config.walletAddress) {
779
+ this.apiClient.setWalletAddress(this.config.walletAddress);
780
+ }
781
+ }
782
+ }
783
+ /**
784
+ * Initialize SDK and request initialization data from parent (iframe) or API (non-iframe)
785
+ */
786
+ async init() {
787
+ if (this.initialized) {
788
+ return; // Already initialized
789
+ }
790
+ if (this.isIframe) {
791
+ // Iframe mode: use postMessage
792
+ try {
793
+ // Request initialization from parent
794
+ const initData = await this.messageHandler.sendMessage(MessageType.INIT_REQUEST);
795
+ // Update wallet status from init data
796
+ if (initData.wallet) {
797
+ this.walletManager.updateWalletStatus(initData.wallet);
798
+ }
799
+ // Notify parent that game is ready
800
+ this.messageHandler.sendOneWay(MessageType.GAME_READY);
801
+ this.initialized = true;
802
+ }
803
+ catch (error) {
804
+ // Initialization failed, but SDK can still be used
805
+ // Wallet functions will fetch on demand
806
+ this.initialized = true;
807
+ console.warn('SDK initialization failed:', error);
808
+ }
809
+ }
810
+ else {
811
+ // Non-iframe mode: use API
812
+ if (!this.apiClient) {
813
+ throw new Error('API client not initialized. Ensure apiBaseURL is configured.');
814
+ }
815
+ try {
816
+ // Fetch wallet address from API
817
+ const walletResponse = await this.apiClient.getWalletAddress();
818
+ // Update wallet manager with API response
819
+ this.walletManager.updateWalletStatus({
820
+ connected: walletResponse.connected,
821
+ address: walletResponse.walletAddress,
822
+ });
823
+ this.initialized = true;
824
+ }
825
+ catch (error) {
826
+ // Initialization failed, but SDK can still be used
827
+ this.initialized = true;
828
+ console.warn('SDK API initialization failed:', error);
829
+ }
830
+ }
831
+ }
832
+ /**
833
+ * Get wallet address - use this as user identifier
834
+ * Games should link all save data to this wallet address
835
+ * Returns null if wallet not connected
836
+ */
837
+ async getWalletAddress() {
838
+ return this.walletManager.getWalletAddress();
839
+ }
840
+ /**
841
+ * Check if wallet is connected
842
+ */
843
+ async isWalletConnected() {
844
+ return this.walletManager.isWalletConnected();
845
+ }
846
+ /**
847
+ * Listen for wallet connection changes
848
+ */
849
+ onWalletChange(callback) {
850
+ this.walletManager.onWalletChange(callback);
851
+ }
852
+ /**
853
+ * Remove wallet change listener
854
+ */
855
+ offWalletChange(callback) {
856
+ this.walletManager.offWalletChange(callback);
857
+ }
858
+ /**
859
+ * Payment methods
860
+ */
861
+ get payment() {
862
+ return {
863
+ /**
864
+ * Pay to play - one-time payment to start/access game
865
+ */
866
+ payToPlay: (amount, token) => {
867
+ return this.paymentManager.payToPlay(amount, token);
868
+ },
869
+ /**
870
+ * In-game purchase - buy items, upgrades, etc.
871
+ */
872
+ purchaseItem: (itemId, amount, token) => {
873
+ return this.paymentManager.purchaseItem(itemId, amount, token);
874
+ },
875
+ };
876
+ }
877
+ /**
878
+ * Get SDK configuration
879
+ */
880
+ getConfig() {
881
+ return { ...this.config };
882
+ }
883
+ /**
884
+ * Check if SDK is running in iframe
885
+ */
886
+ isInIframe() {
887
+ return this.isIframe;
888
+ }
889
+ /**
890
+ * Check if SDK is initialized
891
+ */
892
+ isInitialized() {
893
+ return this.initialized;
894
+ }
895
+ /**
896
+ * Cleanup SDK resources
897
+ */
898
+ destroy() {
899
+ this.messageHandler.destroy();
900
+ this.walletManager.clearCache();
901
+ this.initialized = false;
902
+ }
903
+ /**
904
+ * Set up message listener for wallet updates
905
+ */
906
+ setupMessageListener() {
907
+ // Additional listener for wallet updates (not request/response)
908
+ const listener = (event) => {
909
+ // Validate origin if specified
910
+ if (this.config.parentOrigin !== '*' && event.origin !== this.config.parentOrigin) {
911
+ return;
912
+ }
913
+ const message = event.data;
914
+ // Handle wallet update messages
915
+ if (message.type === MessageType.WALLET_UPDATE && message.payload) {
916
+ const walletInfo = message.payload;
917
+ this.walletManager.updateWalletStatus(walletInfo);
918
+ }
919
+ };
920
+ window.addEventListener('message', listener);
921
+ // Store listener for cleanup (though SDK doesn't typically need cleanup)
922
+ // This is handled by messageHandler.destroy()
923
+ }
924
+ }
925
+
926
+ /**
927
+ * Arcadia Game SDK
928
+ *
929
+ * SDK for integrating Arcadia wallet and payment features into Web3 games.
930
+ * Works in iframe environments by communicating with parent window via postMessage.
931
+ */
932
+ // Main SDK class
933
+
934
+ exports.APIClient = APIClient;
935
+ exports.ArcadiaSDK = ArcadiaSDK;
936
+ exports.ArcadiaSDKError = ArcadiaSDKError;
937
+ exports.InvalidAmountError = InvalidAmountError;
938
+ exports.InvalidConfigError = InvalidConfigError;
939
+ exports.InvalidTokenError = InvalidTokenError;
940
+ exports.NotInIframeError = NotInIframeError;
941
+ exports.PaymentFailedError = PaymentFailedError;
942
+ exports.TimeoutError = TimeoutError;
943
+ exports.WalletNotConnectedError = WalletNotConnectedError;
944
+ exports.default = ArcadiaSDK;
945
+
946
+ Object.defineProperty(exports, '__esModule', { value: true });
947
+
948
+ }));
949
+ //# sourceMappingURL=arcadia-game-sdk.js.map