@arcadiasol/game-sdk 1.0.0 → 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.
- package/README.md +63 -382
- package/dist/esm/api-client.d.ts +54 -0
- package/dist/esm/api-client.d.ts.map +1 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +410 -98
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/payment.d.ts +12 -3
- package/dist/esm/payment.d.ts.map +1 -1
- package/dist/esm/sdk.d.ts +2 -1
- package/dist/esm/sdk.d.ts.map +1 -1
- package/dist/esm/types.d.ts +16 -6
- package/dist/esm/types.d.ts.map +1 -1
- package/dist/esm/wallet.d.ts +4 -2
- package/dist/esm/wallet.d.ts.map +1 -1
- package/dist/umd/arcadia-game-sdk.js +410 -97
- package/dist/umd/arcadia-game-sdk.js.map +1 -1
- package/dist/umd/arcadia-game-sdk.min.js +1 -1
- package/package.json +1 -1
|
@@ -235,45 +235,61 @@
|
|
|
235
235
|
* Wallet address management
|
|
236
236
|
*/
|
|
237
237
|
class WalletManager {
|
|
238
|
-
constructor(messageHandler, isIframe) {
|
|
238
|
+
constructor(messageHandler, isIframe, apiClient) {
|
|
239
239
|
this.walletAddress = null;
|
|
240
240
|
this.walletConnected = false;
|
|
241
|
+
this.apiClient = null;
|
|
241
242
|
this.walletChangeCallbacks = [];
|
|
242
243
|
this.messageHandler = messageHandler;
|
|
243
244
|
this.isIframe = isIframe;
|
|
245
|
+
this.apiClient = apiClient || null;
|
|
244
246
|
}
|
|
245
247
|
/**
|
|
246
|
-
* Get wallet address from parent window
|
|
248
|
+
* Get wallet address from parent window (iframe) or API (non-iframe)
|
|
247
249
|
*/
|
|
248
250
|
async getWalletAddress() {
|
|
249
|
-
if (
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
+
}
|
|
257
265
|
}
|
|
258
|
-
|
|
259
|
-
//
|
|
260
|
-
this.
|
|
261
|
-
|
|
262
|
-
|
|
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
|
+
}
|
|
263
282
|
}
|
|
264
283
|
}
|
|
265
284
|
/**
|
|
266
285
|
* Check if wallet is connected
|
|
267
286
|
*/
|
|
268
287
|
async isWalletConnected() {
|
|
269
|
-
if (!this.isIframe) {
|
|
270
|
-
return false;
|
|
271
|
-
}
|
|
272
288
|
// If we have cached address, return true
|
|
273
289
|
if (this.walletAddress) {
|
|
274
290
|
return true;
|
|
275
291
|
}
|
|
276
|
-
// Otherwise, fetch from parent
|
|
292
|
+
// Otherwise, fetch from parent (iframe) or API (non-iframe)
|
|
277
293
|
const address = await this.getWalletAddress();
|
|
278
294
|
return address !== null;
|
|
279
295
|
}
|
|
@@ -337,20 +353,22 @@
|
|
|
337
353
|
* Payment processing
|
|
338
354
|
*/
|
|
339
355
|
class PaymentManager {
|
|
340
|
-
constructor(gameId, isIframe, messageHandler, walletManager) {
|
|
356
|
+
constructor(gameId, isIframe, messageHandler, walletManager, apiClient) {
|
|
357
|
+
this.apiClient = null;
|
|
341
358
|
this.gameId = gameId;
|
|
342
359
|
this.isIframe = isIframe;
|
|
343
360
|
this.messageHandler = messageHandler;
|
|
344
361
|
this.walletManager = walletManager;
|
|
362
|
+
this.apiClient = apiClient || null;
|
|
345
363
|
}
|
|
346
364
|
/**
|
|
347
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)
|
|
348
369
|
*/
|
|
349
|
-
async payToPlay(amount, token) {
|
|
370
|
+
async payToPlay(amount, token, txSignature) {
|
|
350
371
|
this.validatePaymentParams(amount, token);
|
|
351
|
-
if (!this.isIframe) {
|
|
352
|
-
throw new NotInIframeError('payToPlay only works when the game is running in an Arcadia iframe');
|
|
353
|
-
}
|
|
354
372
|
// Check wallet connection
|
|
355
373
|
const isConnected = await this.walletManager.isWalletConnected();
|
|
356
374
|
if (!isConnected) {
|
|
@@ -361,47 +379,93 @@
|
|
|
361
379
|
token,
|
|
362
380
|
type: 'pay_to_play',
|
|
363
381
|
gameId: this.gameId,
|
|
382
|
+
txSignature, // Optional for iframe mode, required for non-iframe
|
|
364
383
|
};
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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
|
+
};
|
|
372
407
|
}
|
|
373
|
-
|
|
374
|
-
|
|
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');
|
|
375
413
|
}
|
|
376
|
-
return {
|
|
377
|
-
success: true,
|
|
378
|
-
txSignature: response.txSignature,
|
|
379
|
-
amount: response.amount,
|
|
380
|
-
token: response.token,
|
|
381
|
-
timestamp: response.timestamp,
|
|
382
|
-
purchaseId: response.purchaseId,
|
|
383
|
-
platformFee: response.platformFee,
|
|
384
|
-
developerAmount: response.developerAmount,
|
|
385
|
-
};
|
|
386
414
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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');
|
|
390
454
|
}
|
|
391
|
-
throw new PaymentFailedError(error instanceof Error ? error.message : 'Payment failed');
|
|
392
455
|
}
|
|
393
456
|
}
|
|
394
457
|
/**
|
|
395
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)
|
|
396
463
|
*/
|
|
397
|
-
async purchaseItem(itemId, amount, token) {
|
|
464
|
+
async purchaseItem(itemId, amount, token, txSignature) {
|
|
398
465
|
this.validatePaymentParams(amount, token);
|
|
399
466
|
if (!itemId || typeof itemId !== 'string' || itemId.trim().length === 0) {
|
|
400
467
|
throw new Error('Item ID is required and must be a non-empty string');
|
|
401
468
|
}
|
|
402
|
-
if (!this.isIframe) {
|
|
403
|
-
throw new NotInIframeError('purchaseItem only works when the game is running in an Arcadia iframe');
|
|
404
|
-
}
|
|
405
469
|
// Check wallet connection
|
|
406
470
|
const isConnected = await this.walletManager.isWalletConnected();
|
|
407
471
|
if (!isConnected) {
|
|
@@ -413,34 +477,79 @@
|
|
|
413
477
|
type: 'in_game_purchase',
|
|
414
478
|
gameId: this.gameId,
|
|
415
479
|
itemId: itemId.trim(),
|
|
480
|
+
txSignature, // Optional for iframe mode, required for non-iframe
|
|
416
481
|
};
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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
|
+
};
|
|
424
505
|
}
|
|
425
|
-
|
|
426
|
-
|
|
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');
|
|
427
511
|
}
|
|
428
|
-
return {
|
|
429
|
-
success: true,
|
|
430
|
-
txSignature: response.txSignature,
|
|
431
|
-
amount: response.amount,
|
|
432
|
-
token: response.token,
|
|
433
|
-
timestamp: response.timestamp,
|
|
434
|
-
purchaseId: response.purchaseId,
|
|
435
|
-
platformFee: response.platformFee,
|
|
436
|
-
developerAmount: response.developerAmount,
|
|
437
|
-
};
|
|
438
512
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
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');
|
|
442
552
|
}
|
|
443
|
-
throw new PaymentFailedError(error instanceof Error ? error.message : 'Purchase failed');
|
|
444
553
|
}
|
|
445
554
|
}
|
|
446
555
|
/**
|
|
@@ -450,9 +559,180 @@
|
|
|
450
559
|
if (typeof amount !== 'number' || isNaN(amount) || amount <= 0) {
|
|
451
560
|
throw new InvalidAmountError();
|
|
452
561
|
}
|
|
453
|
-
if (token !== '
|
|
562
|
+
if (!token || typeof token !== 'string' || token.trim().length === 0) {
|
|
454
563
|
throw new InvalidTokenError();
|
|
455
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();
|
|
456
736
|
}
|
|
457
737
|
}
|
|
458
738
|
|
|
@@ -465,6 +745,7 @@
|
|
|
465
745
|
*/
|
|
466
746
|
constructor(config) {
|
|
467
747
|
this.initialized = false;
|
|
748
|
+
this.apiClient = null;
|
|
468
749
|
// Validate config
|
|
469
750
|
if (!config || !config.gameId || typeof config.gameId !== 'string' || config.gameId.trim().length === 0) {
|
|
470
751
|
throw new InvalidConfigError('gameId is required and must be a non-empty string');
|
|
@@ -479,42 +760,73 @@
|
|
|
479
760
|
// Initialize message handler
|
|
480
761
|
this.messageHandler = new MessageHandler(this.config.parentOrigin, this.config.timeout);
|
|
481
762
|
// Initialize managers
|
|
482
|
-
this.walletManager = new WalletManager(this.messageHandler, this.isIframe);
|
|
483
|
-
this.paymentManager = new PaymentManager(this.config.gameId, this.isIframe, this.messageHandler, this.walletManager);
|
|
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);
|
|
484
765
|
// Set up message listener if in iframe
|
|
485
766
|
if (this.isIframe) {
|
|
486
767
|
this.messageHandler.init();
|
|
487
768
|
this.setupMessageListener();
|
|
488
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
|
+
}
|
|
489
782
|
}
|
|
490
783
|
/**
|
|
491
|
-
* Initialize SDK and request initialization data from parent
|
|
784
|
+
* Initialize SDK and request initialization data from parent (iframe) or API (non-iframe)
|
|
492
785
|
*/
|
|
493
786
|
async init() {
|
|
494
787
|
if (this.initialized) {
|
|
495
788
|
return; // Already initialized
|
|
496
789
|
}
|
|
497
|
-
if (
|
|
498
|
-
//
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
this.
|
|
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);
|
|
508
808
|
}
|
|
509
|
-
// Notify parent that game is ready
|
|
510
|
-
this.messageHandler.sendOneWay(MessageType.GAME_READY);
|
|
511
|
-
this.initialized = true;
|
|
512
809
|
}
|
|
513
|
-
|
|
514
|
-
//
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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
|
+
}
|
|
518
830
|
}
|
|
519
831
|
}
|
|
520
832
|
/**
|
|
@@ -619,6 +931,7 @@
|
|
|
619
931
|
*/
|
|
620
932
|
// Main SDK class
|
|
621
933
|
|
|
934
|
+
exports.APIClient = APIClient;
|
|
622
935
|
exports.ArcadiaSDK = ArcadiaSDK;
|
|
623
936
|
exports.ArcadiaSDKError = ArcadiaSDKError;
|
|
624
937
|
exports.InvalidAmountError = InvalidAmountError;
|