@hyve-sdk/js 1.5.0 → 2.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/dist/index.js CHANGED
@@ -18,12 +18,13 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
 
20
20
  // src/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
23
  AdsService: () => AdsService,
24
24
  BillingPlatform: () => BillingPlatform,
25
25
  BillingService: () => BillingService,
26
26
  CloudStorageAdapter: () => CloudStorageAdapter,
27
+ CrazyGamesService: () => CrazyGamesService,
27
28
  HyveClient: () => HyveClient,
28
29
  LocalStorageAdapter: () => LocalStorageAdapter,
29
30
  Logger: () => Logger,
@@ -31,15 +32,11 @@ __export(index_exports, {
31
32
  NativeMessageType: () => NativeMessageType,
32
33
  PlaygamaService: () => PlaygamaService,
33
34
  generateUUID: () => generateUUID,
34
- handleVerifyMessage: () => handleVerifyMessage,
35
35
  isDomainAllowed: () => isDomainAllowed,
36
36
  logger: () => logger,
37
- parseUrlParams: () => parseUrlParams,
38
- validateSignature: () => validateSignature,
39
- verifyAuthentication: () => verifyAuthentication,
40
- verifyHyveToken: () => verifyHyveToken
37
+ parseUrlParams: () => parseUrlParams
41
38
  });
42
- module.exports = __toCommonJS(index_exports);
39
+ module.exports = __toCommonJS(src_exports);
43
40
 
44
41
  // src/utils/index.ts
45
42
  var import_uuid = require("uuid");
@@ -67,7 +64,7 @@ var Logger = class _Logger {
67
64
  }
68
65
  } else if (typeof window !== "undefined") {
69
66
  try {
70
- enabled = process.env.NODE_ENV !== "production";
67
+ enabled = process?.env.NODE_ENV !== "production";
71
68
  } catch (e) {
72
69
  enabled = true;
73
70
  }
@@ -167,136 +164,15 @@ var Logger = class _Logger {
167
164
  var logger = new Logger();
168
165
 
169
166
  // src/utils/auth.ts
170
- var import_ethers = require("ethers");
171
167
  function parseUrlParams(searchParams) {
172
168
  const params = typeof searchParams === "string" ? new URLSearchParams(searchParams) : searchParams;
173
169
  return {
174
- signature: params.get("signature") || "",
175
- message: params.get("message") || "",
176
170
  gameStartTab: params.get("game_start_tab") || "",
177
- hyveToken: params.get("hyve-token") || "",
178
171
  platform: params.get("platform") || "",
179
172
  hyveAccess: params.get("hyve-access") || "",
180
173
  gameId: params.get("game-id") || ""
181
174
  };
182
175
  }
183
- function validateSignature(signature, message) {
184
- try {
185
- const recoveredAddress = import_ethers.ethers.verifyMessage(message, signature);
186
- return !!recoveredAddress;
187
- } catch (error) {
188
- logger.error("Signature validation error:", error);
189
- return false;
190
- }
191
- }
192
- function handleVerifyMessage(signature, message) {
193
- try {
194
- const metadata = JSON.parse(decodeURI(message));
195
- if (!metadata.expiration || metadata.expiration < Date.now()) {
196
- return false;
197
- }
198
- const userAddress = metadata.userId || metadata.address;
199
- if (!userAddress) {
200
- return false;
201
- }
202
- const byteMessage = new TextEncoder().encode(message);
203
- const addressThatSignedMessage = import_ethers.ethers.verifyMessage(
204
- byteMessage,
205
- signature
206
- );
207
- if (!addressThatSignedMessage) {
208
- return false;
209
- }
210
- if (addressThatSignedMessage.toLowerCase() !== userAddress.toLowerCase()) {
211
- return false;
212
- }
213
- return userAddress;
214
- } catch (error) {
215
- logger.error("Error verifying message:", error);
216
- return false;
217
- }
218
- }
219
- function verifyHyveToken(hyveToken, maxAgeSec = 600) {
220
- try {
221
- const parts = hyveToken.split(".");
222
- if (parts.length !== 4) {
223
- logger.error(
224
- "Invalid hyve-token format: expected 4 parts, got",
225
- parts.length
226
- );
227
- return false;
228
- }
229
- const [signature, address, randomBase64, timestampStr] = parts;
230
- if (!signature || !address || !randomBase64 || !timestampStr) {
231
- logger.error("Missing hyve-token components");
232
- return false;
233
- }
234
- const message = `${address}.${randomBase64}.${timestampStr}`;
235
- const recoveredAddress = import_ethers.ethers.verifyMessage(message, signature);
236
- if (recoveredAddress.toLowerCase() !== address.toLowerCase()) {
237
- logger.error("Hyve-token signature verification failed");
238
- return false;
239
- }
240
- const timestamp = parseInt(timestampStr, 10);
241
- if (!Number.isFinite(timestamp)) {
242
- logger.error("Invalid hyve-token timestamp");
243
- return false;
244
- }
245
- const now = Math.floor(Date.now() / 1e3);
246
- if (now - timestamp > maxAgeSec) {
247
- logger.error(
248
- `Hyve-token expired (age: ${now - timestamp}s, max: ${maxAgeSec}s)`
249
- );
250
- return false;
251
- }
252
- return address;
253
- } catch (error) {
254
- logger.error("Hyve-token verification error:", error);
255
- return false;
256
- }
257
- }
258
- function verifyAuthentication(params, maxAgeSec = 600) {
259
- if (params.hyveToken) {
260
- const modernAddress = verifyHyveToken(params.hyveToken, maxAgeSec);
261
- if (modernAddress) {
262
- return {
263
- isValid: true,
264
- address: modernAddress,
265
- method: "modern"
266
- };
267
- } else {
268
- return {
269
- isValid: false,
270
- address: null,
271
- method: "modern",
272
- error: "Modern token verification failed"
273
- };
274
- }
275
- }
276
- if (params.signature && params.message) {
277
- const legacyAddress = handleVerifyMessage(params.signature, params.message);
278
- if (legacyAddress) {
279
- return {
280
- isValid: true,
281
- address: legacyAddress,
282
- method: "legacy"
283
- };
284
- } else {
285
- return {
286
- isValid: false,
287
- address: null,
288
- method: "legacy",
289
- error: "Legacy token verification failed"
290
- };
291
- }
292
- }
293
- return {
294
- isValid: false,
295
- address: null,
296
- method: "none",
297
- error: "No authentication tokens provided"
298
- };
299
- }
300
176
  function isDomainAllowed(allowedDomains, hostname) {
301
177
  logger.debug("Checking hostname:", hostname);
302
178
  if (!allowedDomains) return true;
@@ -337,9 +213,7 @@ var NativeBridge = class {
337
213
  * Checks if the app is running inside a React Native WebView
338
214
  */
339
215
  static isNativeContext() {
340
- const isNative = typeof window !== "undefined" && "ReactNativeWebView" in window;
341
- console.log(`[NativeBridge] isNativeContext check: ${isNative}`);
342
- return isNative;
216
+ return typeof window !== "undefined" && "ReactNativeWebView" in window;
343
217
  }
344
218
  /**
345
219
  * Initializes the native bridge message listener
@@ -348,55 +222,37 @@ var NativeBridge = class {
348
222
  static initialize() {
349
223
  if (this.isInitialized) {
350
224
  logger.debug("[NativeBridge] Already initialized");
351
- console.log("[NativeBridge] \u26A0 Already initialized, skipping");
352
225
  return;
353
226
  }
354
227
  if (typeof window === "undefined") {
355
228
  logger.warn("[NativeBridge] Window not available, skipping initialization");
356
- console.warn("[NativeBridge] \u26A0 Window not available, skipping initialization");
357
229
  return;
358
230
  }
359
- const isInIframe = window !== window.parent;
360
- console.log("[NativeBridge] ========== INITIALIZING ==========");
361
- console.log(`[NativeBridge] Running in iframe: ${isInIframe}`);
362
- console.log("[NativeBridge] Setting up message listeners on window and document");
363
231
  const boundHandler = this.handleNativeMessage.bind(this);
364
232
  window.addEventListener("message", boundHandler);
365
233
  document.addEventListener("message", boundHandler);
366
- if (isInIframe) {
367
- console.log("[NativeBridge] Setting up parent window message listener for iframe context");
368
- }
369
234
  this.isInitialized = true;
370
- console.log("[NativeBridge] \u2713 Event listeners attached to window and document");
371
- console.log("[NativeBridge] \u2713 Initialized and ready to receive messages");
372
235
  logger.info("[NativeBridge] Initialized and listening for native messages");
373
236
  }
374
237
  /**
375
238
  * Handles incoming messages from React Native
376
239
  */
377
240
  static handleNativeMessage(event) {
378
- console.log("[NativeBridge] [HANDLER] handleNativeMessage called");
379
- console.log("[NativeBridge] [HANDLER] Event source:", event.source === window ? "window" : "other");
380
- console.log("[NativeBridge] [HANDLER] Event data:", event.data);
381
241
  try {
382
242
  let data;
383
243
  if (typeof event.data === "string") {
384
244
  try {
385
245
  data = JSON.parse(event.data);
386
- logger.debug("[NativeBridge] Parsed message string:", data);
387
- } catch (parseError) {
388
- logger.debug("[NativeBridge] Failed to parse message, not JSON:", event.data);
246
+ } catch {
247
+ logger.debug("[NativeBridge] Ignoring non-JSON message");
389
248
  return;
390
249
  }
391
250
  } else if (typeof event.data === "object" && event.data !== null) {
392
251
  data = event.data;
393
- logger.debug("[NativeBridge] Received message object:", data);
394
252
  } else {
395
- logger.debug("[NativeBridge] Received invalid message type:", typeof event.data);
396
253
  return;
397
254
  }
398
- if (!data || !data.type) {
399
- logger.debug("[NativeBridge] Received message without type, ignoring");
255
+ if (!data?.type) {
400
256
  return;
401
257
  }
402
258
  const nativeResponseTypes = [
@@ -408,22 +264,16 @@ var NativeBridge = class {
408
264
  "PURCHASE_ERROR" /* PURCHASE_ERROR */
409
265
  ];
410
266
  if (!nativeResponseTypes.includes(data.type)) {
411
- logger.debug(`[NativeBridge] Ignoring non-native-response message: ${data.type}`);
412
267
  return;
413
268
  }
414
- console.log("[NativeBridge] [HANDLER] Message type:", data.type);
415
269
  const handler = this.handlers.get(data.type);
416
270
  if (handler) {
417
- console.log(`[NativeBridge] [HANDLER] \u2713 Found handler for: ${data.type}`);
418
- logger.info(`[NativeBridge] Handling message: ${data.type}`, data.payload);
271
+ logger.debug(`[NativeBridge] Handling message: ${data.type}`);
419
272
  handler(data.payload);
420
- console.log(`[NativeBridge] [HANDLER] \u2713 Handler completed for: ${data.type}`);
421
273
  } else {
422
- console.warn(`[NativeBridge] [HANDLER] \u26A0 No handler registered for: ${data.type}`);
423
274
  logger.warn(`[NativeBridge] No handler registered for: ${data.type}`);
424
275
  }
425
276
  } catch (error) {
426
- console.error("[NativeBridge] [HANDLER] \u274C Error handling native message:", error);
427
277
  logger.error("[NativeBridge] Error handling native message:", error);
428
278
  }
429
279
  }
@@ -433,27 +283,15 @@ var NativeBridge = class {
433
283
  * @param payload Optional payload data
434
284
  */
435
285
  static send(type, payload) {
436
- console.log(`[NativeBridge] [SEND] Attempting to send message: ${type}`);
437
286
  if (!this.isNativeContext()) {
438
- console.log(`[NativeBridge] [SEND] \u274C Not in native context, skipping message: ${type}`);
439
- logger.debug(
440
- `[NativeBridge] Not in native context, skipping message: ${type}`
441
- );
287
+ logger.debug(`[NativeBridge] Not in native context, skipping message: ${type}`);
442
288
  return;
443
289
  }
444
- console.log(`[NativeBridge] [SEND] \u2713 In native context, sending message`);
445
290
  try {
446
- const message = {
447
- type,
448
- payload,
449
- timestamp: Date.now()
450
- };
451
- console.log(`[NativeBridge] [SEND] Message object:`, message);
291
+ const message = { type, payload, timestamp: Date.now() };
452
292
  window.ReactNativeWebView.postMessage(JSON.stringify(message));
453
- console.log(`[NativeBridge] [SEND] \u2713 Message sent to native: ${type}`);
454
293
  logger.debug(`[NativeBridge] Sent message to native: ${type}`, payload);
455
294
  } catch (error) {
456
- console.error(`[NativeBridge] [SEND] \u274C\u274C Error sending message to native:`, error);
457
295
  logger.error(`[NativeBridge] Error sending message to native:`, error);
458
296
  }
459
297
  }
@@ -463,10 +301,7 @@ var NativeBridge = class {
463
301
  * @param handler Function to call when message is received
464
302
  */
465
303
  static on(type, handler) {
466
- console.log(`[NativeBridge] [ON] Registering handler for: ${type}`);
467
304
  this.handlers.set(type, handler);
468
- console.log(`[NativeBridge] [ON] \u2713 Handler registered. Total handlers:`, this.handlers.size);
469
- console.log(`[NativeBridge] [ON] All registered types:`, Array.from(this.handlers.keys()));
470
305
  logger.debug(`[NativeBridge] Registered handler for: ${type}`);
471
306
  }
472
307
  /**
@@ -616,7 +451,6 @@ function generateUUID() {
616
451
  // src/services/ads.ts
617
452
  var AdsService = class {
618
453
  config = {
619
- enabled: false,
620
454
  sound: "on",
621
455
  debug: false,
622
456
  onBeforeAd: () => {
@@ -626,87 +460,69 @@ var AdsService = class {
626
460
  onRewardEarned: () => {
627
461
  }
628
462
  };
629
- initialized = false;
463
+ // Cached init promise — ensures adConfig() is only called once
464
+ initPromise = null;
630
465
  ready = false;
631
466
  /**
632
- * Configure the ads service
633
- * Must set enabled: true to activate ads
467
+ * Optionally configure the ads service.
468
+ * Not required ads work without calling this.
634
469
  */
635
470
  configure(config) {
636
471
  this.config = {
637
472
  ...this.config,
638
473
  ...config,
639
- onBeforeAd: config.onBeforeAd || this.config.onBeforeAd,
640
- onAfterAd: config.onAfterAd || this.config.onAfterAd,
641
- onRewardEarned: config.onRewardEarned || this.config.onRewardEarned
474
+ onBeforeAd: config.onBeforeAd ?? this.config.onBeforeAd,
475
+ onAfterAd: config.onAfterAd ?? this.config.onAfterAd,
476
+ onRewardEarned: config.onRewardEarned ?? this.config.onRewardEarned
642
477
  };
643
478
  if (this.config.debug) {
644
- console.log("[AdsService] Configuration updated:", {
645
- enabled: this.config.enabled,
646
- sound: this.config.sound
647
- });
648
- }
649
- if (this.config.enabled && !this.initialized) {
650
- this.initialize();
479
+ logger.debug("[AdsService] Configuration updated:", { sound: this.config.sound });
651
480
  }
652
481
  }
653
482
  /**
654
- * Initialize the ads system
483
+ * Initialize the Google H5 Ads system.
484
+ * Returns a cached promise — safe to call multiple times.
655
485
  */
656
486
  initialize() {
657
- if (this.initialized) return;
658
- if (!this.config.enabled) {
659
- if (this.config.debug) {
660
- console.log("[AdsService] Ads disabled, skipping initialization");
661
- }
662
- return;
663
- }
664
- if (!window.adConfig || !window.adBreak) {
665
- console.warn("[AdsService] Google Ads SDK not found. Ads will not be available.");
666
- return;
667
- }
668
- if (this.config.debug) {
669
- console.log("[AdsService] Initializing ads system...");
670
- }
671
- const googleConfig = {
672
- sound: this.config.sound,
673
- preloadAdBreaks: "on",
674
- onReady: () => {
675
- this.ready = true;
487
+ if (this.initPromise) return this.initPromise;
488
+ this.initPromise = new Promise((resolve) => {
489
+ if (!window.adConfig || !window.adBreak) {
676
490
  if (this.config.debug) {
677
- console.log("[AdsService] Ads ready");
491
+ logger.debug("[AdsService] Google Ads SDK not found \u2014 ads unavailable");
678
492
  }
493
+ resolve(false);
494
+ return;
679
495
  }
680
- };
681
- window.adConfig(googleConfig);
682
- this.initialized = true;
496
+ if (this.config.debug) {
497
+ logger.debug("[AdsService] Initializing ads system...");
498
+ }
499
+ window.adConfig({
500
+ sound: this.config.sound,
501
+ preloadAdBreaks: "on",
502
+ onReady: () => {
503
+ this.ready = true;
504
+ if (this.config.debug) {
505
+ logger.debug("[AdsService] Ads ready");
506
+ }
507
+ resolve(true);
508
+ }
509
+ });
510
+ setTimeout(() => resolve(false), 5e3);
511
+ });
512
+ return this.initPromise;
683
513
  }
684
514
  /**
685
- * Show an ad
686
- * Returns immediately if ads are disabled
515
+ * Show an ad. Auto-initializes on the first call.
516
+ * Returns immediately with success: false if ads are disabled or unavailable.
687
517
  */
688
518
  async show(type) {
689
519
  const requestedAt = Date.now();
690
- if (!this.config.enabled) {
691
- if (this.config.debug) {
692
- console.log("[AdsService] Ads disabled, skipping ad request");
693
- }
520
+ const ready = await this.initialize();
521
+ if (!ready || !window.adBreak) {
694
522
  return {
695
523
  success: false,
696
524
  type,
697
- error: new Error("Ads are disabled"),
698
- requestedAt,
699
- completedAt: Date.now()
700
- };
701
- }
702
- if (!this.initialized) {
703
- this.initialize();
704
- }
705
- if (!this.ready || !window.adBreak) {
706
- return {
707
- success: false,
708
- type,
709
- error: new Error("Ads not ready"),
525
+ error: new Error("Ads not available"),
710
526
  requestedAt,
711
527
  completedAt: Date.now()
712
528
  };
@@ -714,7 +530,7 @@ var AdsService = class {
714
530
  return this.showAdBreak(type);
715
531
  }
716
532
  /**
717
- * Show an ad break
533
+ * Show an ad break via the Google H5 API.
718
534
  */
719
535
  async showAdBreak(type) {
720
536
  const requestedAt = Date.now();
@@ -722,81 +538,48 @@ var AdsService = class {
722
538
  const googleType = type === "rewarded" ? "reward" : type === "preroll" ? "start" : "next";
723
539
  const adName = `${type}-ad-${Date.now()}`;
724
540
  if (this.config.debug) {
725
- console.log(`[AdsService] Showing ${type} ad`);
541
+ logger.debug(`[AdsService] Showing ${type} ad`);
726
542
  }
727
543
  this.config.onBeforeAd(type);
728
544
  const adBreakConfig = {
729
545
  type: googleType,
730
546
  name: adName,
731
- beforeAd: () => {
732
- if (this.config.debug) {
733
- console.log("[AdsService] Ad started");
734
- }
735
- },
736
547
  afterAd: () => {
737
- if (this.config.debug) {
738
- console.log("[AdsService] Ad finished");
739
- }
740
548
  this.config.onAfterAd(type);
741
549
  },
742
550
  adBreakDone: (info) => {
743
551
  const completedAt = Date.now();
744
- let success = false;
745
- if (type === "rewarded") {
746
- success = info?.breakStatus === "viewed";
747
- } else {
748
- success = info?.breakStatus !== "error";
749
- }
552
+ const success = type === "rewarded" ? info?.breakStatus === "viewed" : info?.breakStatus !== "error";
750
553
  const error = info?.breakStatus === "error" && info?.error ? new Error(info.error) : void 0;
751
554
  if (this.config.debug) {
752
- console.log("[AdsService] Ad break done:", {
753
- success,
754
- status: info?.breakStatus
755
- });
555
+ logger.debug("[AdsService] Ad break done:", { success, status: info?.breakStatus });
756
556
  }
757
- const result = {
758
- success,
759
- type,
760
- error,
761
- requestedAt,
762
- completedAt
763
- };
764
- resolve(result);
557
+ resolve({ success, type, error, requestedAt, completedAt });
765
558
  }
766
559
  };
767
560
  if (type === "rewarded") {
768
- adBreakConfig.beforeReward = (showAdFn) => {
769
- if (this.config.debug) {
770
- console.log("[AdsService] beforeReward callback");
771
- }
772
- showAdFn();
773
- };
774
- adBreakConfig.adViewed = () => {
775
- if (this.config.debug) {
776
- console.log("[AdsService] Rewarded ad watched successfully");
777
- }
778
- this.config.onRewardEarned();
779
- };
780
- adBreakConfig.adDismissed = () => {
781
- if (this.config.debug) {
782
- console.log("[AdsService] Rewarded ad dismissed");
783
- }
784
- };
561
+ adBreakConfig.beforeReward = (showAdFn) => showAdFn();
562
+ adBreakConfig.adViewed = () => this.config.onRewardEarned();
785
563
  }
786
564
  window.adBreak(adBreakConfig);
787
565
  });
788
566
  }
789
567
  /**
790
- * Check if ads are enabled
568
+ * Returns the configured ad lifecycle callbacks.
569
+ * Used by platform-specific providers (e.g. Playgama) to fire the same hooks.
791
570
  */
792
- isEnabled() {
793
- return this.config.enabled;
571
+ getCallbacks() {
572
+ return {
573
+ onBeforeAd: this.config.onBeforeAd,
574
+ onAfterAd: this.config.onAfterAd,
575
+ onRewardEarned: this.config.onRewardEarned
576
+ };
794
577
  }
795
578
  /**
796
- * Check if ads are ready to show
579
+ * Check if ads have successfully initialized and are ready to show.
797
580
  */
798
581
  isReady() {
799
- return this.config.enabled && this.ready;
582
+ return this.ready;
800
583
  }
801
584
  };
802
585
 
@@ -843,16 +626,17 @@ var PlaygamaService = class {
843
626
  this.initialized = true;
844
627
  return true;
845
628
  } catch (error) {
846
- console.warn("[PlaygamaService] Failed to initialize:", error);
629
+ logger.warn("[PlaygamaService] Failed to initialize:", error);
847
630
  return false;
848
631
  }
849
632
  }
850
633
  isInitialized() {
851
634
  return this.initialized;
852
635
  }
853
- async showInterstitial() {
636
+ async showInterstitial(callbacks) {
854
637
  const requestedAt = Date.now();
855
- if (!this.initialized || !window.bridge) {
638
+ const bridge = window.bridge;
639
+ if (!this.initialized || !bridge) {
856
640
  return {
857
641
  success: false,
858
642
  type: "interstitial",
@@ -861,13 +645,26 @@ var PlaygamaService = class {
861
645
  completedAt: Date.now()
862
646
  };
863
647
  }
648
+ if (!bridge.advertisement.isInterstitialSupported) {
649
+ return {
650
+ success: false,
651
+ type: "interstitial",
652
+ error: new Error("Interstitial ads not supported on this platform"),
653
+ requestedAt,
654
+ completedAt: Date.now()
655
+ };
656
+ }
864
657
  return new Promise((resolve) => {
865
658
  const handler = (state) => {
866
- if (state === "closed") {
867
- window.bridge.advertisement.off("interstitial_state_changed", handler);
659
+ if (state === "opened") {
660
+ callbacks?.onBeforeAd?.();
661
+ } else if (state === "closed") {
662
+ bridge.advertisement.off(bridge.EVENT_NAME.INTERSTITIAL_STATE_CHANGED, handler);
663
+ callbacks?.onAfterAd?.();
868
664
  resolve({ success: true, type: "interstitial", requestedAt, completedAt: Date.now() });
869
665
  } else if (state === "failed") {
870
- window.bridge.advertisement.off("interstitial_state_changed", handler);
666
+ bridge.advertisement.off(bridge.EVENT_NAME.INTERSTITIAL_STATE_CHANGED, handler);
667
+ callbacks?.onAfterAd?.();
871
668
  resolve({
872
669
  success: false,
873
670
  type: "interstitial",
@@ -877,13 +674,14 @@ var PlaygamaService = class {
877
674
  });
878
675
  }
879
676
  };
880
- window.bridge.advertisement.on("interstitial_state_changed", handler);
881
- window.bridge.advertisement.showInterstitial();
677
+ bridge.advertisement.on(bridge.EVENT_NAME.INTERSTITIAL_STATE_CHANGED, handler);
678
+ bridge.advertisement.showInterstitial();
882
679
  });
883
680
  }
884
- async showRewarded() {
681
+ async showRewarded(callbacks) {
885
682
  const requestedAt = Date.now();
886
- if (!this.initialized || !window.bridge) {
683
+ const bridge = window.bridge;
684
+ if (!this.initialized || !bridge) {
887
685
  return {
888
686
  success: false,
889
687
  type: "rewarded",
@@ -892,13 +690,26 @@ var PlaygamaService = class {
892
690
  completedAt: Date.now()
893
691
  };
894
692
  }
693
+ if (!bridge.advertisement.isRewardedSupported) {
694
+ return {
695
+ success: false,
696
+ type: "rewarded",
697
+ error: new Error("Rewarded ads not supported on this platform"),
698
+ requestedAt,
699
+ completedAt: Date.now()
700
+ };
701
+ }
895
702
  return new Promise((resolve) => {
896
703
  let rewarded = false;
897
704
  const handler = (state) => {
898
- if (state === "rewarded") {
705
+ if (state === "opened") {
706
+ callbacks?.onBeforeAd?.();
707
+ } else if (state === "rewarded") {
899
708
  rewarded = true;
709
+ callbacks?.onRewardEarned?.();
900
710
  } else if (state === "closed" || state === "failed") {
901
- window.bridge.advertisement.off("rewarded_state_changed", handler);
711
+ bridge.advertisement.off(bridge.EVENT_NAME.REWARDED_STATE_CHANGED, handler);
712
+ callbacks?.onAfterAd?.();
902
713
  resolve({
903
714
  success: rewarded,
904
715
  type: "rewarded",
@@ -908,8 +719,8 @@ var PlaygamaService = class {
908
719
  });
909
720
  }
910
721
  };
911
- window.bridge.advertisement.on("rewarded_state_changed", handler);
912
- window.bridge.advertisement.showRewarded();
722
+ bridge.advertisement.on(bridge.EVENT_NAME.REWARDED_STATE_CHANGED, handler);
723
+ bridge.advertisement.showRewarded();
913
724
  });
914
725
  }
915
726
  loadScript() {
@@ -927,6 +738,176 @@ var PlaygamaService = class {
927
738
  }
928
739
  };
929
740
 
741
+ // src/services/crazygames.ts
742
+ var CRAZYGAMES_SDK_CDN = "https://sdk.crazygames.com/crazygames-sdk-v2.js";
743
+ var CrazyGamesService = class {
744
+ initialized = false;
745
+ /**
746
+ * Detects if the game is running on the CrazyGames platform.
747
+ * Games on CrazyGames run inside an iframe, so we check document.referrer
748
+ * and attempt to read the parent frame location.
749
+ */
750
+ static isCrazyGamesDomain() {
751
+ try {
752
+ if (document.referrer.includes("crazygames.com")) {
753
+ return true;
754
+ }
755
+ if (window !== window.top) {
756
+ try {
757
+ if (window.parent.location.hostname.includes("crazygames.com")) {
758
+ return true;
759
+ }
760
+ } catch {
761
+ }
762
+ }
763
+ return false;
764
+ } catch {
765
+ return false;
766
+ }
767
+ }
768
+ /**
769
+ * Loads the CrazyGames SDK from CDN and confirms the environment is 'crazygames'.
770
+ * Safe to call multiple times — resolves immediately if already initialized.
771
+ */
772
+ async initialize() {
773
+ if (this.initialized) return true;
774
+ try {
775
+ await this.loadScript();
776
+ const sdk = window.CrazyGames?.SDK;
777
+ if (!sdk) {
778
+ logger.warn("[CrazyGamesService] SDK not found after script load");
779
+ return false;
780
+ }
781
+ const env = await sdk.getEnvironment();
782
+ if (env !== "crazygames" && env !== "local") {
783
+ logger.warn("[CrazyGamesService] Unexpected environment:", env);
784
+ return false;
785
+ }
786
+ this.initialized = true;
787
+ return true;
788
+ } catch (error) {
789
+ logger.warn("[CrazyGamesService] Failed to initialize:", error);
790
+ return false;
791
+ }
792
+ }
793
+ isInitialized() {
794
+ return this.initialized;
795
+ }
796
+ /**
797
+ * Shows a midgame (interstitial) ad via the CrazyGames SDK.
798
+ */
799
+ async showInterstitial(callbacks) {
800
+ const requestedAt = Date.now();
801
+ const sdk = window.CrazyGames?.SDK;
802
+ if (!this.initialized || !sdk) {
803
+ return {
804
+ success: false,
805
+ type: "interstitial",
806
+ error: new Error("CrazyGames SDK not initialized"),
807
+ requestedAt,
808
+ completedAt: Date.now()
809
+ };
810
+ }
811
+ return new Promise((resolve) => {
812
+ sdk.ad.requestAd("midgame", {
813
+ adStarted: () => {
814
+ callbacks?.onBeforeAd?.();
815
+ },
816
+ adFinished: () => {
817
+ callbacks?.onAfterAd?.();
818
+ resolve({ success: true, type: "interstitial", requestedAt, completedAt: Date.now() });
819
+ },
820
+ adError: (error) => {
821
+ callbacks?.onAfterAd?.();
822
+ resolve({
823
+ success: false,
824
+ type: "interstitial",
825
+ error: new Error(`CrazyGames interstitial ad error: ${error}`),
826
+ requestedAt,
827
+ completedAt: Date.now()
828
+ });
829
+ }
830
+ });
831
+ });
832
+ }
833
+ /**
834
+ * Shows a rewarded ad via the CrazyGames SDK.
835
+ * Resolves with success: true only when adFinished fires (ad was fully watched).
836
+ */
837
+ async showRewarded(callbacks) {
838
+ const requestedAt = Date.now();
839
+ const sdk = window.CrazyGames?.SDK;
840
+ if (!this.initialized || !sdk) {
841
+ return {
842
+ success: false,
843
+ type: "rewarded",
844
+ error: new Error("CrazyGames SDK not initialized"),
845
+ requestedAt,
846
+ completedAt: Date.now()
847
+ };
848
+ }
849
+ return new Promise((resolve) => {
850
+ sdk.ad.requestAd("rewarded", {
851
+ adStarted: () => {
852
+ callbacks?.onBeforeAd?.();
853
+ },
854
+ adFinished: () => {
855
+ callbacks?.onRewardEarned?.();
856
+ callbacks?.onAfterAd?.();
857
+ resolve({ success: true, type: "rewarded", requestedAt, completedAt: Date.now() });
858
+ },
859
+ adError: (error) => {
860
+ callbacks?.onAfterAd?.();
861
+ resolve({
862
+ success: false,
863
+ type: "rewarded",
864
+ error: new Error(`CrazyGames rewarded ad error: ${error}`),
865
+ requestedAt,
866
+ completedAt: Date.now()
867
+ });
868
+ }
869
+ });
870
+ });
871
+ }
872
+ /**
873
+ * Notifies CrazyGames that gameplay has started.
874
+ * Call when the player actively begins or resumes playing.
875
+ */
876
+ gameplayStart() {
877
+ if (!this.initialized) return;
878
+ window.CrazyGames?.SDK.game.gameplayStart();
879
+ }
880
+ /**
881
+ * Notifies CrazyGames that gameplay has stopped.
882
+ * Call during menu access, level completion, or pausing.
883
+ */
884
+ gameplayStop() {
885
+ if (!this.initialized) return;
886
+ window.CrazyGames?.SDK.game.gameplayStop();
887
+ }
888
+ /**
889
+ * Triggers a celebration effect on the CrazyGames website.
890
+ * Use sparingly for significant achievements (boss defeat, personal record, etc.)
891
+ */
892
+ happytime() {
893
+ if (!this.initialized) return;
894
+ window.CrazyGames?.SDK.game.happytime();
895
+ }
896
+ loadScript() {
897
+ return new Promise((resolve, reject) => {
898
+ if (window.CrazyGames?.SDK) {
899
+ resolve();
900
+ return;
901
+ }
902
+ const script = document.createElement("script");
903
+ script.src = CRAZYGAMES_SDK_CDN;
904
+ script.onload = () => resolve();
905
+ script.onerror = () => reject(new Error("Failed to load CrazyGames SDK script"));
906
+ document.head.appendChild(script);
907
+ });
908
+ }
909
+ };
910
+
930
911
  // src/services/billing.ts
931
912
  var BillingPlatform = /* @__PURE__ */ ((BillingPlatform3) => {
932
913
  BillingPlatform3["WEB"] = "web";
@@ -937,41 +918,28 @@ var BillingPlatform = /* @__PURE__ */ ((BillingPlatform3) => {
937
918
  var BillingService = class {
938
919
  config;
939
920
  platform = "unknown" /* UNKNOWN */;
940
- isInitialized = false;
921
+ initPromise = null;
941
922
  nativeAvailable = false;
942
- products = [];
943
923
  // Stripe instance for web payments
944
924
  stripe = null;
945
925
  checkoutElement = null;
946
926
  // Callbacks for purchase events
947
927
  onPurchaseCompleteCallback;
948
928
  onPurchaseErrorCallback;
949
- // Callback for logging (so external UI can capture logs)
950
- onLogCallback;
951
929
  constructor(config) {
952
930
  this.config = config;
953
931
  this.detectPlatform();
954
932
  }
955
933
  /**
956
- * Register a callback to receive all internal logs
957
- * Useful for displaying logs in a UI
958
- */
959
- onLog(callback) {
960
- this.onLogCallback = callback;
961
- }
962
- /**
963
- * Internal logging method that calls both logger and custom callback
934
+ * Update billing configuration. Resets initialization so next call re-inits with new config.
964
935
  */
965
- log(level, message, data) {
966
- const consoleMethod = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
967
- if (data !== void 0) {
968
- consoleMethod(message, data);
969
- } else {
970
- consoleMethod(message);
971
- }
972
- if (this.onLogCallback) {
973
- this.onLogCallback(level, message, data);
974
- }
936
+ configure(config) {
937
+ this.config = {
938
+ ...this.config,
939
+ ...config
940
+ };
941
+ this.initPromise = null;
942
+ logger.info("[BillingService] Configuration updated");
975
943
  }
976
944
  /**
977
945
  * Detects if running on web or native platform
@@ -981,13 +949,13 @@ var BillingService = class {
981
949
  const hasWindow = typeof window !== "undefined";
982
950
  if (isNative) {
983
951
  this.platform = "native" /* NATIVE */;
984
- this.log("info", "[BillingService] Platform: NATIVE");
952
+ logger.info("[BillingService] Platform: NATIVE");
985
953
  } else if (hasWindow) {
986
954
  this.platform = "web" /* WEB */;
987
- this.log("info", "[BillingService] Platform: WEB");
955
+ logger.info("[BillingService] Platform: WEB");
988
956
  } else {
989
957
  this.platform = "unknown" /* UNKNOWN */;
990
- this.log("warn", "[BillingService] Platform: UNKNOWN");
958
+ logger.warn("[BillingService] Platform: UNKNOWN");
991
959
  }
992
960
  }
993
961
  /**
@@ -997,38 +965,40 @@ var BillingService = class {
997
965
  return this.platform;
998
966
  }
999
967
  /**
1000
- * Initialize the billing service
1001
- * Must be called before using any other methods
968
+ * Initialize the billing service. Idempotent — returns cached promise on subsequent calls.
969
+ * Called automatically by getProducts() and purchase().
1002
970
  */
1003
- async initialize() {
1004
- if (this.isInitialized) {
1005
- return true;
1006
- }
1007
- this.log("info", `[BillingService] Initializing for ${this.platform} platform...`);
1008
- try {
1009
- if (this.platform === "native" /* NATIVE */) {
1010
- return await this.initializeNative();
1011
- } else if (this.platform === "web" /* WEB */) {
1012
- return await this.initializeWeb();
971
+ initialize() {
972
+ if (this.initPromise) return this.initPromise;
973
+ logger.info(`[BillingService] Initializing for ${this.platform} platform...`);
974
+ this.initPromise = (async () => {
975
+ try {
976
+ if (this.platform === "native" /* NATIVE */) {
977
+ return await this.initializeNative();
978
+ } else if (this.platform === "web" /* WEB */) {
979
+ return await this.initializeWeb();
980
+ }
981
+ logger.error("[BillingService] Cannot initialize: unknown platform");
982
+ return false;
983
+ } catch (error) {
984
+ logger.error("[BillingService] Initialization failed:", error?.message);
985
+ return false;
1013
986
  }
1014
- this.log("error", "[BillingService] Cannot initialize: unknown platform");
1015
- return false;
1016
- } catch (error) {
1017
- this.log("error", "[BillingService] Initialization failed:", error?.message);
1018
- return false;
1019
- }
987
+ })();
988
+ return this.initPromise;
1020
989
  }
1021
990
  /**
1022
991
  * Initialize native billing
1023
992
  */
1024
993
  async initializeNative() {
1025
994
  return new Promise((resolve) => {
995
+ let resolved = false;
1026
996
  try {
1027
997
  NativeBridge.initialize();
1028
998
  NativeBridge.on(
1029
999
  "PURCHASE_COMPLETE" /* PURCHASE_COMPLETE */,
1030
1000
  (payload) => {
1031
- this.log("info", "[BillingService] Purchase complete:", payload.productId);
1001
+ logger.info("[BillingService] Purchase complete:", payload.productId);
1032
1002
  const result = {
1033
1003
  success: true,
1034
1004
  productId: payload.productId,
@@ -1043,7 +1013,7 @@ var BillingService = class {
1043
1013
  NativeBridge.on(
1044
1014
  "PURCHASE_ERROR" /* PURCHASE_ERROR */,
1045
1015
  (payload) => {
1046
- this.log("error", "[BillingService] Purchase error:", payload.message);
1016
+ logger.error("[BillingService] Purchase error:", payload.message);
1047
1017
  const result = {
1048
1018
  success: false,
1049
1019
  productId: payload.productId || "unknown",
@@ -1061,8 +1031,8 @@ var BillingService = class {
1061
1031
  "IAP_AVAILABILITY_RESULT" /* IAP_AVAILABILITY_RESULT */,
1062
1032
  (payload) => {
1063
1033
  this.nativeAvailable = payload.available;
1064
- this.isInitialized = true;
1065
- this.log("info", `[BillingService] Native billing ${payload.available ? "available" : "unavailable"}`);
1034
+ logger.info(`[BillingService] Native billing ${payload.available ? "available" : "unavailable"}`);
1035
+ resolved = true;
1066
1036
  resolve(payload.available);
1067
1037
  }
1068
1038
  );
@@ -1070,15 +1040,15 @@ var BillingService = class {
1070
1040
  NativeBridge.checkIAPAvailability();
1071
1041
  }, 100);
1072
1042
  setTimeout(() => {
1073
- if (!this.isInitialized) {
1074
- this.log("warn", "[BillingService] Native initialization timeout");
1075
- this.isInitialized = true;
1043
+ if (!resolved) {
1044
+ logger.warn("[BillingService] Native initialization timeout");
1045
+ resolved = true;
1076
1046
  resolve(false);
1077
1047
  }
1078
1048
  }, 5e3);
1079
1049
  } catch (error) {
1080
- this.log("error", "[BillingService] Native initialization failed:", error?.message);
1081
- this.isInitialized = true;
1050
+ logger.error("[BillingService] Native initialization failed:", error?.message);
1051
+ resolved = true;
1082
1052
  resolve(false);
1083
1053
  }
1084
1054
  });
@@ -1088,12 +1058,12 @@ var BillingService = class {
1088
1058
  */
1089
1059
  async initializeWeb() {
1090
1060
  if (!this.config.stripePublishableKey) {
1091
- this.log("error", "[BillingService] Stripe publishable key not provided");
1061
+ logger.error("[BillingService] Stripe publishable key not provided");
1092
1062
  return false;
1093
1063
  }
1094
1064
  try {
1095
1065
  if (typeof window === "undefined") {
1096
- this.log("error", "[BillingService] Window is undefined (not in browser)");
1066
+ logger.error("[BillingService] Window is undefined (not in browser)");
1097
1067
  return false;
1098
1068
  }
1099
1069
  if (!window.Stripe) {
@@ -1101,15 +1071,14 @@ var BillingService = class {
1101
1071
  }
1102
1072
  if (window.Stripe) {
1103
1073
  this.stripe = window.Stripe(this.config.stripePublishableKey);
1104
- this.isInitialized = true;
1105
- this.log("info", "[BillingService] Web billing initialized");
1074
+ logger.info("[BillingService] Web billing initialized");
1106
1075
  return true;
1107
1076
  } else {
1108
- this.log("error", "[BillingService] Stripe not available after loading");
1077
+ logger.error("[BillingService] Stripe not available after loading");
1109
1078
  return false;
1110
1079
  }
1111
1080
  } catch (error) {
1112
- this.log("error", "[BillingService] Stripe initialization failed:", error?.message);
1081
+ logger.error("[BillingService] Stripe initialization failed:", error?.message);
1113
1082
  return false;
1114
1083
  }
1115
1084
  }
@@ -1134,11 +1103,11 @@ var BillingService = class {
1134
1103
  script.src = "https://js.stripe.com/v3/";
1135
1104
  script.async = true;
1136
1105
  script.onload = () => {
1137
- this.log("info", "[BillingService] Stripe.js loaded");
1106
+ logger.info("[BillingService] Stripe.js loaded");
1138
1107
  resolve();
1139
1108
  };
1140
1109
  script.onerror = () => {
1141
- this.log("error", "[BillingService] Failed to load Stripe.js");
1110
+ logger.error("[BillingService] Failed to load Stripe.js");
1142
1111
  reject(new Error("Failed to load Stripe.js"));
1143
1112
  };
1144
1113
  try {
@@ -1149,12 +1118,9 @@ var BillingService = class {
1149
1118
  });
1150
1119
  }
1151
1120
  /**
1152
- * Check if billing is available
1121
+ * Check if billing is available based on current config and platform state
1153
1122
  */
1154
1123
  isAvailable() {
1155
- if (!this.isInitialized) {
1156
- return false;
1157
- }
1158
1124
  if (this.platform === "native" /* NATIVE */) {
1159
1125
  return this.nativeAvailable;
1160
1126
  } else if (this.platform === "web" /* WEB */) {
@@ -1163,12 +1129,10 @@ var BillingService = class {
1163
1129
  return false;
1164
1130
  }
1165
1131
  /**
1166
- * Get available products
1132
+ * Get available products. Auto-initializes on first call.
1167
1133
  */
1168
1134
  async getProducts() {
1169
- if (!this.isInitialized) {
1170
- throw new Error("BillingService not initialized. Call initialize() first.");
1171
- }
1135
+ await this.initialize();
1172
1136
  if (this.platform === "native" /* NATIVE */) {
1173
1137
  return await this.getProductsNative();
1174
1138
  } else if (this.platform === "web" /* WEB */) {
@@ -1180,38 +1144,31 @@ var BillingService = class {
1180
1144
  * Get products from native IAP
1181
1145
  */
1182
1146
  async getProductsNative() {
1183
- return new Promise((resolve, reject) => {
1184
- if (!this.config.gameId || !this.config.checkoutUrl) {
1185
- const error = new Error("gameId and checkoutUrl required for native purchases");
1186
- this.log("error", "[BillingService]", error.message);
1187
- reject(error);
1188
- return;
1189
- }
1190
- const url = `${this.config.checkoutUrl}/get-native-packages?game_id=${this.config.gameId}`;
1191
- fetch(url).then((response) => {
1192
- if (!response.ok) {
1193
- throw new Error(`Failed to fetch native products: ${response.status}`);
1194
- }
1195
- return response.json();
1196
- }).then((data) => {
1197
- if (!data.packages || !Array.isArray(data.packages)) {
1198
- throw new Error("Invalid response format: missing packages array");
1199
- }
1200
- this.products = data.packages.map((pkg) => ({
1201
- productId: pkg.productId,
1202
- title: pkg.package_name,
1203
- description: `${pkg.game_name} - ${pkg.package_name}`,
1204
- price: pkg.price_cents / 100,
1205
- localizedPrice: pkg.price_display,
1206
- currency: "USD"
1207
- }));
1208
- this.log("info", `[BillingService] Fetched ${this.products.length} native products`);
1209
- resolve(this.products);
1210
- }).catch((error) => {
1211
- this.log("error", "[BillingService] Failed to fetch native products:", error?.message);
1212
- reject(error);
1213
- });
1214
- });
1147
+ if (!this.config.gameId || !this.config.checkoutUrl) {
1148
+ const error = new Error("gameId and checkoutUrl required for native purchases");
1149
+ logger.error("[BillingService]", error.message);
1150
+ throw error;
1151
+ }
1152
+ const response = await fetch(
1153
+ `${this.config.checkoutUrl}/get-native-packages?game_id=${this.config.gameId}`
1154
+ );
1155
+ if (!response.ok) {
1156
+ throw new Error(`Failed to fetch native products: ${response.status}`);
1157
+ }
1158
+ const data = await response.json();
1159
+ if (!data.packages || !Array.isArray(data.packages)) {
1160
+ throw new Error("Invalid response format: missing packages array");
1161
+ }
1162
+ const products = data.packages.map((pkg) => ({
1163
+ productId: pkg.productId,
1164
+ title: pkg.package_name,
1165
+ description: `${pkg.game_name} - ${pkg.package_name}`,
1166
+ price: pkg.price_cents / 100,
1167
+ localizedPrice: pkg.price_display,
1168
+ currency: "USD"
1169
+ }));
1170
+ logger.info(`[BillingService] Fetched ${products.length} native products`);
1171
+ return products;
1215
1172
  }
1216
1173
  /**
1217
1174
  * Get products from web API (Stripe)
@@ -1219,7 +1176,7 @@ var BillingService = class {
1219
1176
  async getProductsWeb() {
1220
1177
  if (!this.config.checkoutUrl || !this.config.gameId) {
1221
1178
  const error = new Error("checkoutUrl and gameId required for web purchases");
1222
- this.log("error", "[BillingService]", error.message);
1179
+ logger.error("[BillingService]", error.message);
1223
1180
  throw error;
1224
1181
  }
1225
1182
  try {
@@ -1232,7 +1189,7 @@ var BillingService = class {
1232
1189
  if (!data.packages || !Array.isArray(data.packages)) {
1233
1190
  throw new Error("Invalid response format: missing packages array");
1234
1191
  }
1235
- this.products = data.packages.map((pkg) => ({
1192
+ const products = data.packages.map((pkg) => ({
1236
1193
  productId: pkg.priceId || pkg.productId,
1237
1194
  // Prefer priceId for Stripe
1238
1195
  title: pkg.package_name,
@@ -1241,23 +1198,21 @@ var BillingService = class {
1241
1198
  localizedPrice: pkg.price_display,
1242
1199
  currency: "USD"
1243
1200
  }));
1244
- this.log("info", `[BillingService] Fetched ${this.products.length} web products`);
1245
- return this.products;
1201
+ logger.info(`[BillingService] Fetched ${products.length} web products`);
1202
+ return products;
1246
1203
  } catch (error) {
1247
- this.log("error", "[BillingService] Failed to fetch web products:", error?.message);
1204
+ logger.error("[BillingService] Failed to fetch web products:", error?.message);
1248
1205
  throw error;
1249
1206
  }
1250
1207
  }
1251
1208
  /**
1252
- * Purchase a product
1209
+ * Purchase a product. Auto-initializes on first call.
1253
1210
  * @param productId - The product ID (priceId for web/Stripe, productId for native)
1254
1211
  * @param options - Optional purchase options
1255
1212
  * @param options.elementId - For web: DOM element ID to mount Stripe checkout (default: 'stripe-checkout-element')
1256
1213
  */
1257
1214
  async purchase(productId, options) {
1258
- if (!this.isInitialized) {
1259
- throw new Error("BillingService not initialized. Call initialize() first.");
1260
- }
1215
+ await this.initialize();
1261
1216
  if (!this.isAvailable()) {
1262
1217
  throw new Error("Billing is not available on this platform");
1263
1218
  }
@@ -1277,7 +1232,7 @@ var BillingService = class {
1277
1232
  reject(new Error("userId is required for native purchases"));
1278
1233
  return;
1279
1234
  }
1280
- this.log("info", `[BillingService] Purchasing: ${productId}`);
1235
+ logger.info(`[BillingService] Purchasing: ${productId}`);
1281
1236
  const previousCompleteCallback = this.onPurchaseCompleteCallback;
1282
1237
  const previousErrorCallback = this.onPurchaseErrorCallback;
1283
1238
  const cleanup = () => {
@@ -1351,14 +1306,14 @@ var BillingService = class {
1351
1306
  throw new Error("No client_secret returned from checkout session");
1352
1307
  }
1353
1308
  await this.mountCheckoutElement(client_secret, elementId || "stripe-checkout-element");
1354
- this.log("info", `[BillingService] Checkout session created: ${id}`);
1309
+ logger.info(`[BillingService] Checkout session created: ${id}`);
1355
1310
  return {
1356
1311
  success: true,
1357
1312
  productId,
1358
1313
  transactionId: id
1359
1314
  };
1360
1315
  } catch (error) {
1361
- this.log("error", "[BillingService] Web purchase failed:", error?.message);
1316
+ logger.error("[BillingService] Web purchase failed:", error?.message);
1362
1317
  return {
1363
1318
  success: false,
1364
1319
  productId,
@@ -1380,7 +1335,7 @@ var BillingService = class {
1380
1335
  }
1381
1336
  try {
1382
1337
  if (this.checkoutElement) {
1383
- this.log("info", "[BillingService] Unmounting existing checkout element");
1338
+ logger.info("[BillingService] Unmounting existing checkout element");
1384
1339
  this.unmountCheckoutElement();
1385
1340
  await new Promise((resolve) => setTimeout(resolve, 100));
1386
1341
  }
@@ -1389,15 +1344,15 @@ var BillingService = class {
1389
1344
  throw new Error(`Element with id "${elementId}" not found in the DOM`);
1390
1345
  }
1391
1346
  container.innerHTML = "";
1392
- this.log("info", "[BillingService] Creating new checkout instance");
1347
+ logger.info("[BillingService] Creating new checkout instance");
1393
1348
  this.checkoutElement = await this.stripe.initEmbeddedCheckout({
1394
1349
  clientSecret
1395
1350
  });
1396
- this.log("info", "[BillingService] Mounting checkout element to DOM");
1351
+ logger.info("[BillingService] Mounting checkout element to DOM");
1397
1352
  this.checkoutElement.mount(`#${elementId}`);
1398
1353
  this.setupCheckoutEventListeners();
1399
1354
  } catch (error) {
1400
- this.log("error", "[BillingService] Failed to mount checkout:", error?.message);
1355
+ logger.error("[BillingService] Failed to mount checkout:", error?.message);
1401
1356
  throw error;
1402
1357
  }
1403
1358
  }
@@ -1418,7 +1373,7 @@ var BillingService = class {
1418
1373
  transactionId: sessionId || void 0
1419
1374
  });
1420
1375
  }
1421
- this.log("info", "[BillingService] Payment completed");
1376
+ logger.info("[BillingService] Payment completed");
1422
1377
  urlParams.delete("payment");
1423
1378
  urlParams.delete("session_id");
1424
1379
  const newUrl = `${window.location.pathname}${urlParams.toString() ? "?" + urlParams.toString() : ""}`;
@@ -1437,7 +1392,7 @@ var BillingService = class {
1437
1392
  this.checkoutElement.destroy();
1438
1393
  }
1439
1394
  } catch (error) {
1440
- this.log("warn", "[BillingService] Error unmounting checkout element:", error?.message);
1395
+ logger.warn("[BillingService] Error unmounting checkout element:", error?.message);
1441
1396
  }
1442
1397
  this.checkoutElement = null;
1443
1398
  }
@@ -1465,9 +1420,8 @@ var BillingService = class {
1465
1420
  NativeBridge.off("PURCHASE_ERROR" /* PURCHASE_ERROR */);
1466
1421
  }
1467
1422
  this.unmountCheckoutElement();
1468
- this.isInitialized = false;
1423
+ this.initPromise = null;
1469
1424
  this.nativeAvailable = false;
1470
- this.products = [];
1471
1425
  this.stripe = null;
1472
1426
  this.onPurchaseCompleteCallback = void 0;
1473
1427
  this.onPurchaseErrorCallback = void 0;
@@ -1639,10 +1593,10 @@ var HyveClient = class {
1639
1593
  gameId = null;
1640
1594
  adsService;
1641
1595
  playgamaService = null;
1596
+ playgamaInitPromise = null;
1597
+ crazyGamesService = null;
1598
+ crazyGamesInitPromise = null;
1642
1599
  billingService;
1643
- billingConfig;
1644
- // Store callbacks to preserve them when recreating BillingService
1645
- billingCallbacks = {};
1646
1600
  storageMode;
1647
1601
  cloudStorageAdapter;
1648
1602
  localStorageAdapter;
@@ -1668,17 +1622,33 @@ var HyveClient = class {
1668
1622
  }
1669
1623
  if (typeof window !== "undefined" && PlaygamaService.isPlaygamaDomain()) {
1670
1624
  this.playgamaService = new PlaygamaService();
1671
- this.playgamaService.initialize().then((success) => {
1625
+ this.playgamaInitPromise = this.playgamaService.initialize().then((success) => {
1672
1626
  logger.info("Playgama Bridge initialized:", success);
1627
+ return success;
1628
+ });
1629
+ }
1630
+ if (typeof window !== "undefined" && CrazyGamesService.isCrazyGamesDomain()) {
1631
+ this.crazyGamesService = new CrazyGamesService();
1632
+ this.crazyGamesInitPromise = this.crazyGamesService.initialize().then((success) => {
1633
+ logger.info("CrazyGames SDK initialized:", success);
1634
+ return success;
1673
1635
  });
1674
1636
  }
1675
- this.billingConfig = config?.billing || {};
1676
- this.billingService = new BillingService(this.billingConfig);
1677
1637
  this.storageMode = config?.storageMode || "cloud";
1678
1638
  this.cloudStorageAdapter = new CloudStorageAdapter(
1679
1639
  (endpoint, options) => this.callApi(endpoint, options)
1680
1640
  );
1681
1641
  this.localStorageAdapter = new LocalStorageAdapter(() => this.getUserId());
1642
+ if (typeof window !== "undefined") {
1643
+ this._parseUrlAuth();
1644
+ }
1645
+ const billingConfig = {
1646
+ checkoutUrl: this.apiBaseUrl,
1647
+ userId: this.userId ?? void 0,
1648
+ gameId: this.gameId ? Number(this.gameId) : void 0,
1649
+ ...config?.billing
1650
+ };
1651
+ this.billingService = new BillingService(billingConfig);
1682
1652
  const envSource = config?.isDev !== void 0 ? "explicit config" : "auto-detected from parent URL";
1683
1653
  logger.info("==========================================");
1684
1654
  logger.info("HyveClient Initialized");
@@ -1690,28 +1660,27 @@ var HyveClient = class {
1690
1660
  `(${envSource})`
1691
1661
  );
1692
1662
  logger.info("API Base URL:", this.apiBaseUrl);
1693
- logger.info("Ads enabled:", this.adsService.isEnabled());
1694
1663
  logger.info("Playgama platform:", this.playgamaService !== null);
1664
+ logger.info("CrazyGames platform:", this.crazyGamesService !== null);
1695
1665
  logger.info(
1696
1666
  "Billing configured:",
1697
- Object.keys(this.billingConfig).length > 0
1667
+ !!config?.billing && Object.keys(config.billing).length > 0
1698
1668
  );
1699
1669
  logger.info("Storage mode:", this.storageMode);
1670
+ logger.info("Authenticated:", this.jwtToken !== null);
1700
1671
  logger.debug("Config:", {
1701
1672
  isDev: this.telemetryConfig.isDev,
1702
1673
  hasCustomApiUrl: !!config?.apiBaseUrl,
1703
- adsEnabled: config?.ads?.enabled || false,
1704
- billingConfigured: Object.keys(this.billingConfig).length > 0,
1674
+ billingConfigured: !!config?.billing && Object.keys(config.billing).length > 0,
1705
1675
  storageMode: this.storageMode
1706
1676
  });
1707
1677
  logger.info("==========================================");
1708
1678
  }
1709
1679
  /**
1710
- * Authenticates a user from URL parameters
1711
- * @param urlParams URL parameters or search string
1712
- * @returns Promise resolving to boolean indicating success
1680
+ * Parses JWT and game ID from the current window URL and stores them on the client.
1681
+ * Called automatically during construction.
1713
1682
  */
1714
- async authenticateFromUrl(urlParams) {
1683
+ _parseUrlAuth(urlParams) {
1715
1684
  try {
1716
1685
  const params = urlParams ? parseUrlParams(urlParams) : parseUrlParams(window.location.search);
1717
1686
  if (params.hyveAccess) {
@@ -1736,27 +1705,11 @@ var HyveClient = class {
1736
1705
  }
1737
1706
  if (this.jwtToken) {
1738
1707
  logger.info("Authentication successful via JWT");
1739
- return true;
1740
- }
1741
- const authResult = verifyAuthentication({
1742
- hyveToken: params.hyveToken,
1743
- signature: params.signature,
1744
- message: params.message
1745
- });
1746
- if (authResult.isValid && authResult.address) {
1747
- this.userId = authResult.address;
1748
- logger.info("Authentication successful:", authResult.address);
1749
- logger.info("Authentication method:", authResult.method);
1750
- return true;
1751
1708
  } else {
1752
- logger.error("Authentication failed:", authResult.error);
1753
- this.userId = null;
1754
- return false;
1709
+ logger.info("No hyve-access JWT token in URL \u2014 unauthenticated");
1755
1710
  }
1756
1711
  } catch (error) {
1757
- logger.error("Authentication error:", error);
1758
- this.userId = null;
1759
- return false;
1712
+ logger.error("Error parsing URL auth:", error);
1760
1713
  }
1761
1714
  }
1762
1715
  /**
@@ -1773,7 +1726,7 @@ var HyveClient = class {
1773
1726
  */
1774
1727
  async sendTelemetry(eventLocation, eventCategory, eventAction, eventSubCategory, eventSubAction, eventDetails, platformId) {
1775
1728
  if (!this.jwtToken) {
1776
- logger.error("JWT token required. Call authenticateFromUrl first.");
1729
+ logger.error("JWT token required. Ensure hyve-access and game-id are present in the URL.");
1777
1730
  return false;
1778
1731
  }
1779
1732
  if (!this.gameId) {
@@ -1845,7 +1798,7 @@ var HyveClient = class {
1845
1798
  async callApi(endpoint, options = {}) {
1846
1799
  if (!this.jwtToken) {
1847
1800
  throw new Error(
1848
- "No JWT token available. Call authenticateFromUrl first."
1801
+ "No JWT token available. Ensure hyve-access and game-id are present in the URL."
1849
1802
  );
1850
1803
  }
1851
1804
  try {
@@ -1975,6 +1928,13 @@ var HyveClient = class {
1975
1928
  const storageMode = mode || this.storageMode;
1976
1929
  return storageMode === "local" ? this.localStorageAdapter : this.cloudStorageAdapter;
1977
1930
  }
1931
+ /**
1932
+ * Returns the current game ID or throws if not available.
1933
+ */
1934
+ requireGameId() {
1935
+ const gameId = this.requireGameId();
1936
+ return gameId;
1937
+ }
1978
1938
  /**
1979
1939
  * Save persistent game data
1980
1940
  * @param key Data key
@@ -1983,10 +1943,7 @@ var HyveClient = class {
1983
1943
  * @returns Promise resolving to save response
1984
1944
  */
1985
1945
  async saveGameData(key, value, storage) {
1986
- const gameId = this.getGameId();
1987
- if (!gameId) {
1988
- throw new Error("game-id is required for persistent storage. Call authenticateFromUrl first.");
1989
- }
1946
+ const gameId = this.requireGameId();
1990
1947
  const storageMode = storage || this.storageMode;
1991
1948
  logger.debug(`Saving game data to ${storageMode}: ${gameId}/${key}`);
1992
1949
  const adapter = this.getStorageAdapter(storage);
@@ -2001,10 +1958,7 @@ var HyveClient = class {
2001
1958
  * @returns Promise resolving to save response
2002
1959
  */
2003
1960
  async batchSaveGameData(items, storage) {
2004
- const gameId = this.getGameId();
2005
- if (!gameId) {
2006
- throw new Error("game-id is required for persistent storage. Call authenticateFromUrl first.");
2007
- }
1961
+ const gameId = this.requireGameId();
2008
1962
  const storageMode = storage || this.storageMode;
2009
1963
  logger.debug(`Batch saving ${items.length} game data entries to ${storageMode} for game: ${gameId}`);
2010
1964
  const adapter = this.getStorageAdapter(storage);
@@ -2019,10 +1973,7 @@ var HyveClient = class {
2019
1973
  * @returns Promise resolving to game data item or null if not found
2020
1974
  */
2021
1975
  async getGameData(key, storage) {
2022
- const gameId = this.getGameId();
2023
- if (!gameId) {
2024
- throw new Error("game-id is required for persistent storage. Call authenticateFromUrl first.");
2025
- }
1976
+ const gameId = this.requireGameId();
2026
1977
  const storageMode = storage || this.storageMode;
2027
1978
  logger.debug(`Getting game data from ${storageMode}: ${gameId}/${key}`);
2028
1979
  const adapter = this.getStorageAdapter(storage);
@@ -2041,10 +1992,7 @@ var HyveClient = class {
2041
1992
  * @returns Promise resolving to array of game data items
2042
1993
  */
2043
1994
  async getMultipleGameData(keys, storage) {
2044
- const gameId = this.getGameId();
2045
- if (!gameId) {
2046
- throw new Error("game-id is required for persistent storage. Call authenticateFromUrl first.");
2047
- }
1995
+ const gameId = this.requireGameId();
2048
1996
  const storageMode = storage || this.storageMode;
2049
1997
  logger.debug(`Getting ${keys.length} game data entries from ${storageMode} for game: ${gameId}`);
2050
1998
  const adapter = this.getStorageAdapter(storage);
@@ -2059,10 +2007,7 @@ var HyveClient = class {
2059
2007
  * @returns Promise resolving to boolean indicating if data was deleted
2060
2008
  */
2061
2009
  async deleteGameData(key, storage) {
2062
- const gameId = this.getGameId();
2063
- if (!gameId) {
2064
- throw new Error("game-id is required for persistent storage. Call authenticateFromUrl first.");
2065
- }
2010
+ const gameId = this.requireGameId();
2066
2011
  const storageMode = storage || this.storageMode;
2067
2012
  logger.debug(`Deleting game data from ${storageMode}: ${gameId}/${key}`);
2068
2013
  const adapter = this.getStorageAdapter(storage);
@@ -2081,10 +2026,7 @@ var HyveClient = class {
2081
2026
  * @returns Promise resolving to number of entries deleted
2082
2027
  */
2083
2028
  async deleteMultipleGameData(keys, storage) {
2084
- const gameId = this.getGameId();
2085
- if (!gameId) {
2086
- throw new Error("game-id is required for persistent storage. Call authenticateFromUrl first.");
2087
- }
2029
+ const gameId = this.requireGameId();
2088
2030
  const storageMode = storage || this.storageMode;
2089
2031
  logger.debug(`Deleting ${keys.length} game data entries from ${storageMode} for game: ${gameId}`);
2090
2032
  const adapter = this.getStorageAdapter(storage);
@@ -2121,66 +2063,82 @@ var HyveClient = class {
2121
2063
  * @returns Promise resolving to ad result
2122
2064
  */
2123
2065
  async showAd(type) {
2124
- if (this.playgamaService?.isInitialized()) {
2125
- if (type === "rewarded") return this.playgamaService.showRewarded();
2126
- return this.playgamaService.showInterstitial();
2066
+ if (this.crazyGamesService) {
2067
+ if (this.crazyGamesInitPromise) {
2068
+ await this.crazyGamesInitPromise;
2069
+ }
2070
+ if (this.crazyGamesService.isInitialized()) {
2071
+ const { onBeforeAd, onAfterAd, onRewardEarned } = this.adsService.getCallbacks();
2072
+ if (type === "rewarded") {
2073
+ return this.crazyGamesService.showRewarded({
2074
+ onBeforeAd: () => onBeforeAd("rewarded"),
2075
+ onAfterAd: () => onAfterAd("rewarded"),
2076
+ onRewardEarned
2077
+ });
2078
+ }
2079
+ return this.crazyGamesService.showInterstitial({
2080
+ onBeforeAd: () => onBeforeAd(type),
2081
+ onAfterAd: () => onAfterAd(type)
2082
+ });
2083
+ }
2084
+ }
2085
+ if (this.playgamaService) {
2086
+ if (this.playgamaInitPromise) {
2087
+ await this.playgamaInitPromise;
2088
+ }
2089
+ if (this.playgamaService.isInitialized()) {
2090
+ const { onBeforeAd, onAfterAd, onRewardEarned } = this.adsService.getCallbacks();
2091
+ if (type === "rewarded") {
2092
+ return this.playgamaService.showRewarded({
2093
+ onBeforeAd: () => onBeforeAd("rewarded"),
2094
+ onAfterAd: () => onAfterAd("rewarded"),
2095
+ onRewardEarned
2096
+ });
2097
+ }
2098
+ return this.playgamaService.showInterstitial({
2099
+ onBeforeAd: () => onBeforeAd(type),
2100
+ onAfterAd: () => onAfterAd(type)
2101
+ });
2102
+ }
2127
2103
  }
2128
2104
  return this.adsService.show(type);
2129
2105
  }
2130
2106
  /**
2131
- * Check if ads are enabled
2132
- * @returns Boolean indicating if ads are enabled
2107
+ * Notifies CrazyGames that gameplay has started.
2108
+ * No-op on other platforms.
2133
2109
  */
2134
- areAdsEnabled() {
2135
- return this.adsService.isEnabled();
2110
+ async gameplayStart() {
2111
+ if (this.crazyGamesService) {
2112
+ if (this.crazyGamesInitPromise) await this.crazyGamesInitPromise;
2113
+ this.crazyGamesService.gameplayStart();
2114
+ }
2136
2115
  }
2137
2116
  /**
2138
- * Check if ads are ready to show
2139
- * @returns Boolean indicating if ads are ready
2117
+ * Notifies CrazyGames that gameplay has stopped.
2118
+ * No-op on other platforms.
2140
2119
  */
2141
- areAdsReady() {
2142
- return this.adsService.isReady();
2120
+ async gameplayStop() {
2121
+ if (this.crazyGamesService) {
2122
+ if (this.crazyGamesInitPromise) await this.crazyGamesInitPromise;
2123
+ this.crazyGamesService.gameplayStop();
2124
+ }
2143
2125
  }
2144
2126
  /**
2145
- * Configure billing service
2146
- * @param config Billing configuration
2127
+ * Triggers a celebration effect on the CrazyGames website for significant achievements.
2128
+ * No-op on other platforms.
2147
2129
  */
2148
- configureBilling(config) {
2149
- this.billingConfig = {
2150
- ...this.billingConfig,
2151
- ...config
2152
- };
2153
- logger.info("Billing configuration updated");
2130
+ async happytime() {
2131
+ if (this.crazyGamesService) {
2132
+ if (this.crazyGamesInitPromise) await this.crazyGamesInitPromise;
2133
+ this.crazyGamesService.happytime();
2134
+ }
2154
2135
  }
2155
2136
  /**
2156
- * Initialize billing service
2157
- * Must be called before using billing features
2158
- * @returns Promise resolving to boolean indicating success
2137
+ * Check if ads are ready to show
2138
+ * @returns Boolean indicating if ads have initialized successfully
2159
2139
  */
2160
- async initializeBilling() {
2161
- const mergedConfig = {
2162
- ...this.billingConfig
2163
- };
2164
- if (!mergedConfig.userId && this.userId) {
2165
- mergedConfig.userId = this.userId;
2166
- }
2167
- if (!mergedConfig.gameId && this.gameId) {
2168
- mergedConfig.gameId = Number(this.gameId);
2169
- }
2170
- if (!mergedConfig.checkoutUrl) {
2171
- mergedConfig.checkoutUrl = this.apiBaseUrl;
2172
- }
2173
- this.billingService = new BillingService(mergedConfig);
2174
- if (this.billingCallbacks.onComplete) {
2175
- this.billingService.onPurchaseComplete(this.billingCallbacks.onComplete);
2176
- }
2177
- if (this.billingCallbacks.onError) {
2178
- this.billingService.onPurchaseError(this.billingCallbacks.onError);
2179
- }
2180
- if (this.billingCallbacks.onLog) {
2181
- this.billingService.onLog(this.billingCallbacks.onLog);
2182
- }
2183
- return await this.billingService.initialize();
2140
+ areAdsReady() {
2141
+ return this.adsService.isReady();
2184
2142
  }
2185
2143
  /**
2186
2144
  * Get the billing platform
@@ -2217,7 +2175,6 @@ var HyveClient = class {
2217
2175
  * @param callback Function to call on purchase completion
2218
2176
  */
2219
2177
  onPurchaseComplete(callback) {
2220
- this.billingCallbacks.onComplete = callback;
2221
2178
  this.billingService.onPurchaseComplete(callback);
2222
2179
  }
2223
2180
  /**
@@ -2225,7 +2182,6 @@ var HyveClient = class {
2225
2182
  * @param callback Function to call on purchase error
2226
2183
  */
2227
2184
  onPurchaseError(callback) {
2228
- this.billingCallbacks.onError = callback;
2229
2185
  this.billingService.onPurchaseError(callback);
2230
2186
  }
2231
2187
  /**
@@ -2234,14 +2190,6 @@ var HyveClient = class {
2234
2190
  unmountBillingCheckout() {
2235
2191
  this.billingService.unmountCheckoutElement();
2236
2192
  }
2237
- /**
2238
- * Register a callback to receive billing logs
2239
- * @param callback Function to call with log messages
2240
- */
2241
- onBillingLog(callback) {
2242
- this.billingCallbacks.onLog = callback;
2243
- this.billingService.onLog(callback);
2244
- }
2245
2193
  };
2246
2194
  // Annotate the CommonJS export names for ESM import in node:
2247
2195
  0 && (module.exports = {
@@ -2249,6 +2197,7 @@ var HyveClient = class {
2249
2197
  BillingPlatform,
2250
2198
  BillingService,
2251
2199
  CloudStorageAdapter,
2200
+ CrazyGamesService,
2252
2201
  HyveClient,
2253
2202
  LocalStorageAdapter,
2254
2203
  Logger,
@@ -2256,11 +2205,7 @@ var HyveClient = class {
2256
2205
  NativeMessageType,
2257
2206
  PlaygamaService,
2258
2207
  generateUUID,
2259
- handleVerifyMessage,
2260
2208
  isDomainAllowed,
2261
2209
  logger,
2262
- parseUrlParams,
2263
- validateSignature,
2264
- verifyAuthentication,
2265
- verifyHyveToken
2210
+ parseUrlParams
2266
2211
  });