@hyve-sdk/js 1.5.1 → 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,74 +538,35 @@ 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
568
  * Returns the configured ad lifecycle callbacks.
791
- * Used by platform-specific ad providers (e.g. Playgama) to fire the same
792
- * onBeforeAd / onAfterAd / onRewardEarned hooks that the Google H5 path uses.
569
+ * Used by platform-specific providers (e.g. Playgama) to fire the same hooks.
793
570
  */
794
571
  getCallbacks() {
795
572
  return {
@@ -799,16 +576,10 @@ var AdsService = class {
799
576
  };
800
577
  }
801
578
  /**
802
- * Check if ads are enabled
803
- */
804
- isEnabled() {
805
- return this.config.enabled;
806
- }
807
- /**
808
- * Check if ads are ready to show
579
+ * Check if ads have successfully initialized and are ready to show.
809
580
  */
810
581
  isReady() {
811
- return this.config.enabled && this.ready;
582
+ return this.ready;
812
583
  }
813
584
  };
814
585
 
@@ -855,7 +626,7 @@ var PlaygamaService = class {
855
626
  this.initialized = true;
856
627
  return true;
857
628
  } catch (error) {
858
- console.warn("[PlaygamaService] Failed to initialize:", error);
629
+ logger.warn("[PlaygamaService] Failed to initialize:", error);
859
630
  return false;
860
631
  }
861
632
  }
@@ -967,6 +738,176 @@ var PlaygamaService = class {
967
738
  }
968
739
  };
969
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
+
970
911
  // src/services/billing.ts
971
912
  var BillingPlatform = /* @__PURE__ */ ((BillingPlatform3) => {
972
913
  BillingPlatform3["WEB"] = "web";
@@ -977,41 +918,28 @@ var BillingPlatform = /* @__PURE__ */ ((BillingPlatform3) => {
977
918
  var BillingService = class {
978
919
  config;
979
920
  platform = "unknown" /* UNKNOWN */;
980
- isInitialized = false;
921
+ initPromise = null;
981
922
  nativeAvailable = false;
982
- products = [];
983
923
  // Stripe instance for web payments
984
924
  stripe = null;
985
925
  checkoutElement = null;
986
926
  // Callbacks for purchase events
987
927
  onPurchaseCompleteCallback;
988
928
  onPurchaseErrorCallback;
989
- // Callback for logging (so external UI can capture logs)
990
- onLogCallback;
991
929
  constructor(config) {
992
930
  this.config = config;
993
931
  this.detectPlatform();
994
932
  }
995
933
  /**
996
- * Register a callback to receive all internal logs
997
- * Useful for displaying logs in a UI
934
+ * Update billing configuration. Resets initialization so next call re-inits with new config.
998
935
  */
999
- onLog(callback) {
1000
- this.onLogCallback = callback;
1001
- }
1002
- /**
1003
- * Internal logging method that calls both logger and custom callback
1004
- */
1005
- log(level, message, data) {
1006
- const consoleMethod = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
1007
- if (data !== void 0) {
1008
- consoleMethod(message, data);
1009
- } else {
1010
- consoleMethod(message);
1011
- }
1012
- if (this.onLogCallback) {
1013
- this.onLogCallback(level, message, data);
1014
- }
936
+ configure(config) {
937
+ this.config = {
938
+ ...this.config,
939
+ ...config
940
+ };
941
+ this.initPromise = null;
942
+ logger.info("[BillingService] Configuration updated");
1015
943
  }
1016
944
  /**
1017
945
  * Detects if running on web or native platform
@@ -1021,13 +949,13 @@ var BillingService = class {
1021
949
  const hasWindow = typeof window !== "undefined";
1022
950
  if (isNative) {
1023
951
  this.platform = "native" /* NATIVE */;
1024
- this.log("info", "[BillingService] Platform: NATIVE");
952
+ logger.info("[BillingService] Platform: NATIVE");
1025
953
  } else if (hasWindow) {
1026
954
  this.platform = "web" /* WEB */;
1027
- this.log("info", "[BillingService] Platform: WEB");
955
+ logger.info("[BillingService] Platform: WEB");
1028
956
  } else {
1029
957
  this.platform = "unknown" /* UNKNOWN */;
1030
- this.log("warn", "[BillingService] Platform: UNKNOWN");
958
+ logger.warn("[BillingService] Platform: UNKNOWN");
1031
959
  }
1032
960
  }
1033
961
  /**
@@ -1037,38 +965,40 @@ var BillingService = class {
1037
965
  return this.platform;
1038
966
  }
1039
967
  /**
1040
- * Initialize the billing service
1041
- * 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().
1042
970
  */
1043
- async initialize() {
1044
- if (this.isInitialized) {
1045
- return true;
1046
- }
1047
- this.log("info", `[BillingService] Initializing for ${this.platform} platform...`);
1048
- try {
1049
- if (this.platform === "native" /* NATIVE */) {
1050
- return await this.initializeNative();
1051
- } else if (this.platform === "web" /* WEB */) {
1052
- 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;
1053
986
  }
1054
- this.log("error", "[BillingService] Cannot initialize: unknown platform");
1055
- return false;
1056
- } catch (error) {
1057
- this.log("error", "[BillingService] Initialization failed:", error?.message);
1058
- return false;
1059
- }
987
+ })();
988
+ return this.initPromise;
1060
989
  }
1061
990
  /**
1062
991
  * Initialize native billing
1063
992
  */
1064
993
  async initializeNative() {
1065
994
  return new Promise((resolve) => {
995
+ let resolved = false;
1066
996
  try {
1067
997
  NativeBridge.initialize();
1068
998
  NativeBridge.on(
1069
999
  "PURCHASE_COMPLETE" /* PURCHASE_COMPLETE */,
1070
1000
  (payload) => {
1071
- this.log("info", "[BillingService] Purchase complete:", payload.productId);
1001
+ logger.info("[BillingService] Purchase complete:", payload.productId);
1072
1002
  const result = {
1073
1003
  success: true,
1074
1004
  productId: payload.productId,
@@ -1083,7 +1013,7 @@ var BillingService = class {
1083
1013
  NativeBridge.on(
1084
1014
  "PURCHASE_ERROR" /* PURCHASE_ERROR */,
1085
1015
  (payload) => {
1086
- this.log("error", "[BillingService] Purchase error:", payload.message);
1016
+ logger.error("[BillingService] Purchase error:", payload.message);
1087
1017
  const result = {
1088
1018
  success: false,
1089
1019
  productId: payload.productId || "unknown",
@@ -1101,8 +1031,8 @@ var BillingService = class {
1101
1031
  "IAP_AVAILABILITY_RESULT" /* IAP_AVAILABILITY_RESULT */,
1102
1032
  (payload) => {
1103
1033
  this.nativeAvailable = payload.available;
1104
- this.isInitialized = true;
1105
- this.log("info", `[BillingService] Native billing ${payload.available ? "available" : "unavailable"}`);
1034
+ logger.info(`[BillingService] Native billing ${payload.available ? "available" : "unavailable"}`);
1035
+ resolved = true;
1106
1036
  resolve(payload.available);
1107
1037
  }
1108
1038
  );
@@ -1110,15 +1040,15 @@ var BillingService = class {
1110
1040
  NativeBridge.checkIAPAvailability();
1111
1041
  }, 100);
1112
1042
  setTimeout(() => {
1113
- if (!this.isInitialized) {
1114
- this.log("warn", "[BillingService] Native initialization timeout");
1115
- this.isInitialized = true;
1043
+ if (!resolved) {
1044
+ logger.warn("[BillingService] Native initialization timeout");
1045
+ resolved = true;
1116
1046
  resolve(false);
1117
1047
  }
1118
1048
  }, 5e3);
1119
1049
  } catch (error) {
1120
- this.log("error", "[BillingService] Native initialization failed:", error?.message);
1121
- this.isInitialized = true;
1050
+ logger.error("[BillingService] Native initialization failed:", error?.message);
1051
+ resolved = true;
1122
1052
  resolve(false);
1123
1053
  }
1124
1054
  });
@@ -1128,12 +1058,12 @@ var BillingService = class {
1128
1058
  */
1129
1059
  async initializeWeb() {
1130
1060
  if (!this.config.stripePublishableKey) {
1131
- this.log("error", "[BillingService] Stripe publishable key not provided");
1061
+ logger.error("[BillingService] Stripe publishable key not provided");
1132
1062
  return false;
1133
1063
  }
1134
1064
  try {
1135
1065
  if (typeof window === "undefined") {
1136
- this.log("error", "[BillingService] Window is undefined (not in browser)");
1066
+ logger.error("[BillingService] Window is undefined (not in browser)");
1137
1067
  return false;
1138
1068
  }
1139
1069
  if (!window.Stripe) {
@@ -1141,15 +1071,14 @@ var BillingService = class {
1141
1071
  }
1142
1072
  if (window.Stripe) {
1143
1073
  this.stripe = window.Stripe(this.config.stripePublishableKey);
1144
- this.isInitialized = true;
1145
- this.log("info", "[BillingService] Web billing initialized");
1074
+ logger.info("[BillingService] Web billing initialized");
1146
1075
  return true;
1147
1076
  } else {
1148
- this.log("error", "[BillingService] Stripe not available after loading");
1077
+ logger.error("[BillingService] Stripe not available after loading");
1149
1078
  return false;
1150
1079
  }
1151
1080
  } catch (error) {
1152
- this.log("error", "[BillingService] Stripe initialization failed:", error?.message);
1081
+ logger.error("[BillingService] Stripe initialization failed:", error?.message);
1153
1082
  return false;
1154
1083
  }
1155
1084
  }
@@ -1174,11 +1103,11 @@ var BillingService = class {
1174
1103
  script.src = "https://js.stripe.com/v3/";
1175
1104
  script.async = true;
1176
1105
  script.onload = () => {
1177
- this.log("info", "[BillingService] Stripe.js loaded");
1106
+ logger.info("[BillingService] Stripe.js loaded");
1178
1107
  resolve();
1179
1108
  };
1180
1109
  script.onerror = () => {
1181
- this.log("error", "[BillingService] Failed to load Stripe.js");
1110
+ logger.error("[BillingService] Failed to load Stripe.js");
1182
1111
  reject(new Error("Failed to load Stripe.js"));
1183
1112
  };
1184
1113
  try {
@@ -1189,12 +1118,9 @@ var BillingService = class {
1189
1118
  });
1190
1119
  }
1191
1120
  /**
1192
- * Check if billing is available
1121
+ * Check if billing is available based on current config and platform state
1193
1122
  */
1194
1123
  isAvailable() {
1195
- if (!this.isInitialized) {
1196
- return false;
1197
- }
1198
1124
  if (this.platform === "native" /* NATIVE */) {
1199
1125
  return this.nativeAvailable;
1200
1126
  } else if (this.platform === "web" /* WEB */) {
@@ -1203,12 +1129,10 @@ var BillingService = class {
1203
1129
  return false;
1204
1130
  }
1205
1131
  /**
1206
- * Get available products
1132
+ * Get available products. Auto-initializes on first call.
1207
1133
  */
1208
1134
  async getProducts() {
1209
- if (!this.isInitialized) {
1210
- throw new Error("BillingService not initialized. Call initialize() first.");
1211
- }
1135
+ await this.initialize();
1212
1136
  if (this.platform === "native" /* NATIVE */) {
1213
1137
  return await this.getProductsNative();
1214
1138
  } else if (this.platform === "web" /* WEB */) {
@@ -1220,38 +1144,31 @@ var BillingService = class {
1220
1144
  * Get products from native IAP
1221
1145
  */
1222
1146
  async getProductsNative() {
1223
- return new Promise((resolve, reject) => {
1224
- if (!this.config.gameId || !this.config.checkoutUrl) {
1225
- const error = new Error("gameId and checkoutUrl required for native purchases");
1226
- this.log("error", "[BillingService]", error.message);
1227
- reject(error);
1228
- return;
1229
- }
1230
- const url = `${this.config.checkoutUrl}/get-native-packages?game_id=${this.config.gameId}`;
1231
- fetch(url).then((response) => {
1232
- if (!response.ok) {
1233
- throw new Error(`Failed to fetch native products: ${response.status}`);
1234
- }
1235
- return response.json();
1236
- }).then((data) => {
1237
- if (!data.packages || !Array.isArray(data.packages)) {
1238
- throw new Error("Invalid response format: missing packages array");
1239
- }
1240
- this.products = data.packages.map((pkg) => ({
1241
- productId: pkg.productId,
1242
- title: pkg.package_name,
1243
- description: `${pkg.game_name} - ${pkg.package_name}`,
1244
- price: pkg.price_cents / 100,
1245
- localizedPrice: pkg.price_display,
1246
- currency: "USD"
1247
- }));
1248
- this.log("info", `[BillingService] Fetched ${this.products.length} native products`);
1249
- resolve(this.products);
1250
- }).catch((error) => {
1251
- this.log("error", "[BillingService] Failed to fetch native products:", error?.message);
1252
- reject(error);
1253
- });
1254
- });
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;
1255
1172
  }
1256
1173
  /**
1257
1174
  * Get products from web API (Stripe)
@@ -1259,7 +1176,7 @@ var BillingService = class {
1259
1176
  async getProductsWeb() {
1260
1177
  if (!this.config.checkoutUrl || !this.config.gameId) {
1261
1178
  const error = new Error("checkoutUrl and gameId required for web purchases");
1262
- this.log("error", "[BillingService]", error.message);
1179
+ logger.error("[BillingService]", error.message);
1263
1180
  throw error;
1264
1181
  }
1265
1182
  try {
@@ -1272,7 +1189,7 @@ var BillingService = class {
1272
1189
  if (!data.packages || !Array.isArray(data.packages)) {
1273
1190
  throw new Error("Invalid response format: missing packages array");
1274
1191
  }
1275
- this.products = data.packages.map((pkg) => ({
1192
+ const products = data.packages.map((pkg) => ({
1276
1193
  productId: pkg.priceId || pkg.productId,
1277
1194
  // Prefer priceId for Stripe
1278
1195
  title: pkg.package_name,
@@ -1281,23 +1198,21 @@ var BillingService = class {
1281
1198
  localizedPrice: pkg.price_display,
1282
1199
  currency: "USD"
1283
1200
  }));
1284
- this.log("info", `[BillingService] Fetched ${this.products.length} web products`);
1285
- return this.products;
1201
+ logger.info(`[BillingService] Fetched ${products.length} web products`);
1202
+ return products;
1286
1203
  } catch (error) {
1287
- this.log("error", "[BillingService] Failed to fetch web products:", error?.message);
1204
+ logger.error("[BillingService] Failed to fetch web products:", error?.message);
1288
1205
  throw error;
1289
1206
  }
1290
1207
  }
1291
1208
  /**
1292
- * Purchase a product
1209
+ * Purchase a product. Auto-initializes on first call.
1293
1210
  * @param productId - The product ID (priceId for web/Stripe, productId for native)
1294
1211
  * @param options - Optional purchase options
1295
1212
  * @param options.elementId - For web: DOM element ID to mount Stripe checkout (default: 'stripe-checkout-element')
1296
1213
  */
1297
1214
  async purchase(productId, options) {
1298
- if (!this.isInitialized) {
1299
- throw new Error("BillingService not initialized. Call initialize() first.");
1300
- }
1215
+ await this.initialize();
1301
1216
  if (!this.isAvailable()) {
1302
1217
  throw new Error("Billing is not available on this platform");
1303
1218
  }
@@ -1317,7 +1232,7 @@ var BillingService = class {
1317
1232
  reject(new Error("userId is required for native purchases"));
1318
1233
  return;
1319
1234
  }
1320
- this.log("info", `[BillingService] Purchasing: ${productId}`);
1235
+ logger.info(`[BillingService] Purchasing: ${productId}`);
1321
1236
  const previousCompleteCallback = this.onPurchaseCompleteCallback;
1322
1237
  const previousErrorCallback = this.onPurchaseErrorCallback;
1323
1238
  const cleanup = () => {
@@ -1391,14 +1306,14 @@ var BillingService = class {
1391
1306
  throw new Error("No client_secret returned from checkout session");
1392
1307
  }
1393
1308
  await this.mountCheckoutElement(client_secret, elementId || "stripe-checkout-element");
1394
- this.log("info", `[BillingService] Checkout session created: ${id}`);
1309
+ logger.info(`[BillingService] Checkout session created: ${id}`);
1395
1310
  return {
1396
1311
  success: true,
1397
1312
  productId,
1398
1313
  transactionId: id
1399
1314
  };
1400
1315
  } catch (error) {
1401
- this.log("error", "[BillingService] Web purchase failed:", error?.message);
1316
+ logger.error("[BillingService] Web purchase failed:", error?.message);
1402
1317
  return {
1403
1318
  success: false,
1404
1319
  productId,
@@ -1420,7 +1335,7 @@ var BillingService = class {
1420
1335
  }
1421
1336
  try {
1422
1337
  if (this.checkoutElement) {
1423
- this.log("info", "[BillingService] Unmounting existing checkout element");
1338
+ logger.info("[BillingService] Unmounting existing checkout element");
1424
1339
  this.unmountCheckoutElement();
1425
1340
  await new Promise((resolve) => setTimeout(resolve, 100));
1426
1341
  }
@@ -1429,15 +1344,15 @@ var BillingService = class {
1429
1344
  throw new Error(`Element with id "${elementId}" not found in the DOM`);
1430
1345
  }
1431
1346
  container.innerHTML = "";
1432
- this.log("info", "[BillingService] Creating new checkout instance");
1347
+ logger.info("[BillingService] Creating new checkout instance");
1433
1348
  this.checkoutElement = await this.stripe.initEmbeddedCheckout({
1434
1349
  clientSecret
1435
1350
  });
1436
- this.log("info", "[BillingService] Mounting checkout element to DOM");
1351
+ logger.info("[BillingService] Mounting checkout element to DOM");
1437
1352
  this.checkoutElement.mount(`#${elementId}`);
1438
1353
  this.setupCheckoutEventListeners();
1439
1354
  } catch (error) {
1440
- this.log("error", "[BillingService] Failed to mount checkout:", error?.message);
1355
+ logger.error("[BillingService] Failed to mount checkout:", error?.message);
1441
1356
  throw error;
1442
1357
  }
1443
1358
  }
@@ -1458,7 +1373,7 @@ var BillingService = class {
1458
1373
  transactionId: sessionId || void 0
1459
1374
  });
1460
1375
  }
1461
- this.log("info", "[BillingService] Payment completed");
1376
+ logger.info("[BillingService] Payment completed");
1462
1377
  urlParams.delete("payment");
1463
1378
  urlParams.delete("session_id");
1464
1379
  const newUrl = `${window.location.pathname}${urlParams.toString() ? "?" + urlParams.toString() : ""}`;
@@ -1477,7 +1392,7 @@ var BillingService = class {
1477
1392
  this.checkoutElement.destroy();
1478
1393
  }
1479
1394
  } catch (error) {
1480
- this.log("warn", "[BillingService] Error unmounting checkout element:", error?.message);
1395
+ logger.warn("[BillingService] Error unmounting checkout element:", error?.message);
1481
1396
  }
1482
1397
  this.checkoutElement = null;
1483
1398
  }
@@ -1505,9 +1420,8 @@ var BillingService = class {
1505
1420
  NativeBridge.off("PURCHASE_ERROR" /* PURCHASE_ERROR */);
1506
1421
  }
1507
1422
  this.unmountCheckoutElement();
1508
- this.isInitialized = false;
1423
+ this.initPromise = null;
1509
1424
  this.nativeAvailable = false;
1510
- this.products = [];
1511
1425
  this.stripe = null;
1512
1426
  this.onPurchaseCompleteCallback = void 0;
1513
1427
  this.onPurchaseErrorCallback = void 0;
@@ -1680,10 +1594,9 @@ var HyveClient = class {
1680
1594
  adsService;
1681
1595
  playgamaService = null;
1682
1596
  playgamaInitPromise = null;
1597
+ crazyGamesService = null;
1598
+ crazyGamesInitPromise = null;
1683
1599
  billingService;
1684
- billingConfig;
1685
- // Store callbacks to preserve them when recreating BillingService
1686
- billingCallbacks = {};
1687
1600
  storageMode;
1688
1601
  cloudStorageAdapter;
1689
1602
  localStorageAdapter;
@@ -1714,13 +1627,28 @@ var HyveClient = class {
1714
1627
  return success;
1715
1628
  });
1716
1629
  }
1717
- this.billingConfig = config?.billing || {};
1718
- this.billingService = new BillingService(this.billingConfig);
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;
1635
+ });
1636
+ }
1719
1637
  this.storageMode = config?.storageMode || "cloud";
1720
1638
  this.cloudStorageAdapter = new CloudStorageAdapter(
1721
1639
  (endpoint, options) => this.callApi(endpoint, options)
1722
1640
  );
1723
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);
1724
1652
  const envSource = config?.isDev !== void 0 ? "explicit config" : "auto-detected from parent URL";
1725
1653
  logger.info("==========================================");
1726
1654
  logger.info("HyveClient Initialized");
@@ -1732,28 +1660,27 @@ var HyveClient = class {
1732
1660
  `(${envSource})`
1733
1661
  );
1734
1662
  logger.info("API Base URL:", this.apiBaseUrl);
1735
- logger.info("Ads enabled:", this.adsService.isEnabled());
1736
1663
  logger.info("Playgama platform:", this.playgamaService !== null);
1664
+ logger.info("CrazyGames platform:", this.crazyGamesService !== null);
1737
1665
  logger.info(
1738
1666
  "Billing configured:",
1739
- Object.keys(this.billingConfig).length > 0
1667
+ !!config?.billing && Object.keys(config.billing).length > 0
1740
1668
  );
1741
1669
  logger.info("Storage mode:", this.storageMode);
1670
+ logger.info("Authenticated:", this.jwtToken !== null);
1742
1671
  logger.debug("Config:", {
1743
1672
  isDev: this.telemetryConfig.isDev,
1744
1673
  hasCustomApiUrl: !!config?.apiBaseUrl,
1745
- adsEnabled: config?.ads?.enabled || false,
1746
- billingConfigured: Object.keys(this.billingConfig).length > 0,
1674
+ billingConfigured: !!config?.billing && Object.keys(config.billing).length > 0,
1747
1675
  storageMode: this.storageMode
1748
1676
  });
1749
1677
  logger.info("==========================================");
1750
1678
  }
1751
1679
  /**
1752
- * Authenticates a user from URL parameters
1753
- * @param urlParams URL parameters or search string
1754
- * @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.
1755
1682
  */
1756
- async authenticateFromUrl(urlParams) {
1683
+ _parseUrlAuth(urlParams) {
1757
1684
  try {
1758
1685
  const params = urlParams ? parseUrlParams(urlParams) : parseUrlParams(window.location.search);
1759
1686
  if (params.hyveAccess) {
@@ -1778,27 +1705,11 @@ var HyveClient = class {
1778
1705
  }
1779
1706
  if (this.jwtToken) {
1780
1707
  logger.info("Authentication successful via JWT");
1781
- return true;
1782
- }
1783
- const authResult = verifyAuthentication({
1784
- hyveToken: params.hyveToken,
1785
- signature: params.signature,
1786
- message: params.message
1787
- });
1788
- if (authResult.isValid && authResult.address) {
1789
- this.userId = authResult.address;
1790
- logger.info("Authentication successful:", authResult.address);
1791
- logger.info("Authentication method:", authResult.method);
1792
- return true;
1793
1708
  } else {
1794
- logger.error("Authentication failed:", authResult.error);
1795
- this.userId = null;
1796
- return false;
1709
+ logger.info("No hyve-access JWT token in URL \u2014 unauthenticated");
1797
1710
  }
1798
1711
  } catch (error) {
1799
- logger.error("Authentication error:", error);
1800
- this.userId = null;
1801
- return false;
1712
+ logger.error("Error parsing URL auth:", error);
1802
1713
  }
1803
1714
  }
1804
1715
  /**
@@ -1815,7 +1726,7 @@ var HyveClient = class {
1815
1726
  */
1816
1727
  async sendTelemetry(eventLocation, eventCategory, eventAction, eventSubCategory, eventSubAction, eventDetails, platformId) {
1817
1728
  if (!this.jwtToken) {
1818
- 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.");
1819
1730
  return false;
1820
1731
  }
1821
1732
  if (!this.gameId) {
@@ -1887,7 +1798,7 @@ var HyveClient = class {
1887
1798
  async callApi(endpoint, options = {}) {
1888
1799
  if (!this.jwtToken) {
1889
1800
  throw new Error(
1890
- "No JWT token available. Call authenticateFromUrl first."
1801
+ "No JWT token available. Ensure hyve-access and game-id are present in the URL."
1891
1802
  );
1892
1803
  }
1893
1804
  try {
@@ -2017,6 +1928,13 @@ var HyveClient = class {
2017
1928
  const storageMode = mode || this.storageMode;
2018
1929
  return storageMode === "local" ? this.localStorageAdapter : this.cloudStorageAdapter;
2019
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
+ }
2020
1938
  /**
2021
1939
  * Save persistent game data
2022
1940
  * @param key Data key
@@ -2025,10 +1943,7 @@ var HyveClient = class {
2025
1943
  * @returns Promise resolving to save response
2026
1944
  */
2027
1945
  async saveGameData(key, value, storage) {
2028
- const gameId = this.getGameId();
2029
- if (!gameId) {
2030
- throw new Error("game-id is required for persistent storage. Call authenticateFromUrl first.");
2031
- }
1946
+ const gameId = this.requireGameId();
2032
1947
  const storageMode = storage || this.storageMode;
2033
1948
  logger.debug(`Saving game data to ${storageMode}: ${gameId}/${key}`);
2034
1949
  const adapter = this.getStorageAdapter(storage);
@@ -2043,10 +1958,7 @@ var HyveClient = class {
2043
1958
  * @returns Promise resolving to save response
2044
1959
  */
2045
1960
  async batchSaveGameData(items, storage) {
2046
- const gameId = this.getGameId();
2047
- if (!gameId) {
2048
- throw new Error("game-id is required for persistent storage. Call authenticateFromUrl first.");
2049
- }
1961
+ const gameId = this.requireGameId();
2050
1962
  const storageMode = storage || this.storageMode;
2051
1963
  logger.debug(`Batch saving ${items.length} game data entries to ${storageMode} for game: ${gameId}`);
2052
1964
  const adapter = this.getStorageAdapter(storage);
@@ -2061,10 +1973,7 @@ var HyveClient = class {
2061
1973
  * @returns Promise resolving to game data item or null if not found
2062
1974
  */
2063
1975
  async getGameData(key, storage) {
2064
- const gameId = this.getGameId();
2065
- if (!gameId) {
2066
- throw new Error("game-id is required for persistent storage. Call authenticateFromUrl first.");
2067
- }
1976
+ const gameId = this.requireGameId();
2068
1977
  const storageMode = storage || this.storageMode;
2069
1978
  logger.debug(`Getting game data from ${storageMode}: ${gameId}/${key}`);
2070
1979
  const adapter = this.getStorageAdapter(storage);
@@ -2083,10 +1992,7 @@ var HyveClient = class {
2083
1992
  * @returns Promise resolving to array of game data items
2084
1993
  */
2085
1994
  async getMultipleGameData(keys, storage) {
2086
- const gameId = this.getGameId();
2087
- if (!gameId) {
2088
- throw new Error("game-id is required for persistent storage. Call authenticateFromUrl first.");
2089
- }
1995
+ const gameId = this.requireGameId();
2090
1996
  const storageMode = storage || this.storageMode;
2091
1997
  logger.debug(`Getting ${keys.length} game data entries from ${storageMode} for game: ${gameId}`);
2092
1998
  const adapter = this.getStorageAdapter(storage);
@@ -2101,10 +2007,7 @@ var HyveClient = class {
2101
2007
  * @returns Promise resolving to boolean indicating if data was deleted
2102
2008
  */
2103
2009
  async deleteGameData(key, storage) {
2104
- const gameId = this.getGameId();
2105
- if (!gameId) {
2106
- throw new Error("game-id is required for persistent storage. Call authenticateFromUrl first.");
2107
- }
2010
+ const gameId = this.requireGameId();
2108
2011
  const storageMode = storage || this.storageMode;
2109
2012
  logger.debug(`Deleting game data from ${storageMode}: ${gameId}/${key}`);
2110
2013
  const adapter = this.getStorageAdapter(storage);
@@ -2123,10 +2026,7 @@ var HyveClient = class {
2123
2026
  * @returns Promise resolving to number of entries deleted
2124
2027
  */
2125
2028
  async deleteMultipleGameData(keys, storage) {
2126
- const gameId = this.getGameId();
2127
- if (!gameId) {
2128
- throw new Error("game-id is required for persistent storage. Call authenticateFromUrl first.");
2129
- }
2029
+ const gameId = this.requireGameId();
2130
2030
  const storageMode = storage || this.storageMode;
2131
2031
  logger.debug(`Deleting ${keys.length} game data entries from ${storageMode} for game: ${gameId}`);
2132
2032
  const adapter = this.getStorageAdapter(storage);
@@ -2163,6 +2063,25 @@ var HyveClient = class {
2163
2063
  * @returns Promise resolving to ad result
2164
2064
  */
2165
2065
  async showAd(type) {
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
+ }
2166
2085
  if (this.playgamaService) {
2167
2086
  if (this.playgamaInitPromise) {
2168
2087
  await this.playgamaInitPromise;
@@ -2185,59 +2104,41 @@ var HyveClient = class {
2185
2104
  return this.adsService.show(type);
2186
2105
  }
2187
2106
  /**
2188
- * Check if ads are enabled
2189
- * @returns Boolean indicating if ads are enabled
2107
+ * Notifies CrazyGames that gameplay has started.
2108
+ * No-op on other platforms.
2190
2109
  */
2191
- areAdsEnabled() {
2192
- return this.adsService.isEnabled();
2110
+ async gameplayStart() {
2111
+ if (this.crazyGamesService) {
2112
+ if (this.crazyGamesInitPromise) await this.crazyGamesInitPromise;
2113
+ this.crazyGamesService.gameplayStart();
2114
+ }
2193
2115
  }
2194
2116
  /**
2195
- * Check if ads are ready to show
2196
- * @returns Boolean indicating if ads are ready
2117
+ * Notifies CrazyGames that gameplay has stopped.
2118
+ * No-op on other platforms.
2197
2119
  */
2198
- areAdsReady() {
2199
- return this.adsService.isReady();
2120
+ async gameplayStop() {
2121
+ if (this.crazyGamesService) {
2122
+ if (this.crazyGamesInitPromise) await this.crazyGamesInitPromise;
2123
+ this.crazyGamesService.gameplayStop();
2124
+ }
2200
2125
  }
2201
2126
  /**
2202
- * Configure billing service
2203
- * @param config Billing configuration
2127
+ * Triggers a celebration effect on the CrazyGames website for significant achievements.
2128
+ * No-op on other platforms.
2204
2129
  */
2205
- configureBilling(config) {
2206
- this.billingConfig = {
2207
- ...this.billingConfig,
2208
- ...config
2209
- };
2210
- logger.info("Billing configuration updated");
2130
+ async happytime() {
2131
+ if (this.crazyGamesService) {
2132
+ if (this.crazyGamesInitPromise) await this.crazyGamesInitPromise;
2133
+ this.crazyGamesService.happytime();
2134
+ }
2211
2135
  }
2212
2136
  /**
2213
- * Initialize billing service
2214
- * Must be called before using billing features
2215
- * @returns Promise resolving to boolean indicating success
2137
+ * Check if ads are ready to show
2138
+ * @returns Boolean indicating if ads have initialized successfully
2216
2139
  */
2217
- async initializeBilling() {
2218
- const mergedConfig = {
2219
- ...this.billingConfig
2220
- };
2221
- if (!mergedConfig.userId && this.userId) {
2222
- mergedConfig.userId = this.userId;
2223
- }
2224
- if (!mergedConfig.gameId && this.gameId) {
2225
- mergedConfig.gameId = Number(this.gameId);
2226
- }
2227
- if (!mergedConfig.checkoutUrl) {
2228
- mergedConfig.checkoutUrl = this.apiBaseUrl;
2229
- }
2230
- this.billingService = new BillingService(mergedConfig);
2231
- if (this.billingCallbacks.onComplete) {
2232
- this.billingService.onPurchaseComplete(this.billingCallbacks.onComplete);
2233
- }
2234
- if (this.billingCallbacks.onError) {
2235
- this.billingService.onPurchaseError(this.billingCallbacks.onError);
2236
- }
2237
- if (this.billingCallbacks.onLog) {
2238
- this.billingService.onLog(this.billingCallbacks.onLog);
2239
- }
2240
- return await this.billingService.initialize();
2140
+ areAdsReady() {
2141
+ return this.adsService.isReady();
2241
2142
  }
2242
2143
  /**
2243
2144
  * Get the billing platform
@@ -2274,7 +2175,6 @@ var HyveClient = class {
2274
2175
  * @param callback Function to call on purchase completion
2275
2176
  */
2276
2177
  onPurchaseComplete(callback) {
2277
- this.billingCallbacks.onComplete = callback;
2278
2178
  this.billingService.onPurchaseComplete(callback);
2279
2179
  }
2280
2180
  /**
@@ -2282,7 +2182,6 @@ var HyveClient = class {
2282
2182
  * @param callback Function to call on purchase error
2283
2183
  */
2284
2184
  onPurchaseError(callback) {
2285
- this.billingCallbacks.onError = callback;
2286
2185
  this.billingService.onPurchaseError(callback);
2287
2186
  }
2288
2187
  /**
@@ -2291,14 +2190,6 @@ var HyveClient = class {
2291
2190
  unmountBillingCheckout() {
2292
2191
  this.billingService.unmountCheckoutElement();
2293
2192
  }
2294
- /**
2295
- * Register a callback to receive billing logs
2296
- * @param callback Function to call with log messages
2297
- */
2298
- onBillingLog(callback) {
2299
- this.billingCallbacks.onLog = callback;
2300
- this.billingService.onLog(callback);
2301
- }
2302
2193
  };
2303
2194
  // Annotate the CommonJS export names for ESM import in node:
2304
2195
  0 && (module.exports = {
@@ -2306,6 +2197,7 @@ var HyveClient = class {
2306
2197
  BillingPlatform,
2307
2198
  BillingService,
2308
2199
  CloudStorageAdapter,
2200
+ CrazyGamesService,
2309
2201
  HyveClient,
2310
2202
  LocalStorageAdapter,
2311
2203
  Logger,
@@ -2313,11 +2205,7 @@ var HyveClient = class {
2313
2205
  NativeMessageType,
2314
2206
  PlaygamaService,
2315
2207
  generateUUID,
2316
- handleVerifyMessage,
2317
2208
  isDomainAllowed,
2318
2209
  logger,
2319
- parseUrlParams,
2320
- validateSignature,
2321
- verifyAuthentication,
2322
- verifyHyveToken
2210
+ parseUrlParams
2323
2211
  });