@hyve-sdk/js 1.5.1 → 2.1.1

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.mjs CHANGED
@@ -24,7 +24,7 @@ var Logger = class _Logger {
24
24
  }
25
25
  } else if (typeof window !== "undefined") {
26
26
  try {
27
- enabled = process.env.NODE_ENV !== "production";
27
+ enabled = process?.env.NODE_ENV !== "production";
28
28
  } catch (e) {
29
29
  enabled = true;
30
30
  }
@@ -124,136 +124,15 @@ var Logger = class _Logger {
124
124
  var logger = new Logger();
125
125
 
126
126
  // src/utils/auth.ts
127
- import { ethers } from "ethers";
128
127
  function parseUrlParams(searchParams) {
129
128
  const params = typeof searchParams === "string" ? new URLSearchParams(searchParams) : searchParams;
130
129
  return {
131
- signature: params.get("signature") || "",
132
- message: params.get("message") || "",
133
130
  gameStartTab: params.get("game_start_tab") || "",
134
- hyveToken: params.get("hyve-token") || "",
135
131
  platform: params.get("platform") || "",
136
132
  hyveAccess: params.get("hyve-access") || "",
137
133
  gameId: params.get("game-id") || ""
138
134
  };
139
135
  }
140
- function validateSignature(signature, message) {
141
- try {
142
- const recoveredAddress = ethers.verifyMessage(message, signature);
143
- return !!recoveredAddress;
144
- } catch (error) {
145
- logger.error("Signature validation error:", error);
146
- return false;
147
- }
148
- }
149
- function handleVerifyMessage(signature, message) {
150
- try {
151
- const metadata = JSON.parse(decodeURI(message));
152
- if (!metadata.expiration || metadata.expiration < Date.now()) {
153
- return false;
154
- }
155
- const userAddress = metadata.userId || metadata.address;
156
- if (!userAddress) {
157
- return false;
158
- }
159
- const byteMessage = new TextEncoder().encode(message);
160
- const addressThatSignedMessage = ethers.verifyMessage(
161
- byteMessage,
162
- signature
163
- );
164
- if (!addressThatSignedMessage) {
165
- return false;
166
- }
167
- if (addressThatSignedMessage.toLowerCase() !== userAddress.toLowerCase()) {
168
- return false;
169
- }
170
- return userAddress;
171
- } catch (error) {
172
- logger.error("Error verifying message:", error);
173
- return false;
174
- }
175
- }
176
- function verifyHyveToken(hyveToken, maxAgeSec = 600) {
177
- try {
178
- const parts = hyveToken.split(".");
179
- if (parts.length !== 4) {
180
- logger.error(
181
- "Invalid hyve-token format: expected 4 parts, got",
182
- parts.length
183
- );
184
- return false;
185
- }
186
- const [signature, address, randomBase64, timestampStr] = parts;
187
- if (!signature || !address || !randomBase64 || !timestampStr) {
188
- logger.error("Missing hyve-token components");
189
- return false;
190
- }
191
- const message = `${address}.${randomBase64}.${timestampStr}`;
192
- const recoveredAddress = ethers.verifyMessage(message, signature);
193
- if (recoveredAddress.toLowerCase() !== address.toLowerCase()) {
194
- logger.error("Hyve-token signature verification failed");
195
- return false;
196
- }
197
- const timestamp = parseInt(timestampStr, 10);
198
- if (!Number.isFinite(timestamp)) {
199
- logger.error("Invalid hyve-token timestamp");
200
- return false;
201
- }
202
- const now = Math.floor(Date.now() / 1e3);
203
- if (now - timestamp > maxAgeSec) {
204
- logger.error(
205
- `Hyve-token expired (age: ${now - timestamp}s, max: ${maxAgeSec}s)`
206
- );
207
- return false;
208
- }
209
- return address;
210
- } catch (error) {
211
- logger.error("Hyve-token verification error:", error);
212
- return false;
213
- }
214
- }
215
- function verifyAuthentication(params, maxAgeSec = 600) {
216
- if (params.hyveToken) {
217
- const modernAddress = verifyHyveToken(params.hyveToken, maxAgeSec);
218
- if (modernAddress) {
219
- return {
220
- isValid: true,
221
- address: modernAddress,
222
- method: "modern"
223
- };
224
- } else {
225
- return {
226
- isValid: false,
227
- address: null,
228
- method: "modern",
229
- error: "Modern token verification failed"
230
- };
231
- }
232
- }
233
- if (params.signature && params.message) {
234
- const legacyAddress = handleVerifyMessage(params.signature, params.message);
235
- if (legacyAddress) {
236
- return {
237
- isValid: true,
238
- address: legacyAddress,
239
- method: "legacy"
240
- };
241
- } else {
242
- return {
243
- isValid: false,
244
- address: null,
245
- method: "legacy",
246
- error: "Legacy token verification failed"
247
- };
248
- }
249
- }
250
- return {
251
- isValid: false,
252
- address: null,
253
- method: "none",
254
- error: "No authentication tokens provided"
255
- };
256
- }
257
136
  function isDomainAllowed(allowedDomains, hostname) {
258
137
  logger.debug("Checking hostname:", hostname);
259
138
  if (!allowedDomains) return true;
@@ -294,9 +173,7 @@ var NativeBridge = class {
294
173
  * Checks if the app is running inside a React Native WebView
295
174
  */
296
175
  static isNativeContext() {
297
- const isNative = typeof window !== "undefined" && "ReactNativeWebView" in window;
298
- console.log(`[NativeBridge] isNativeContext check: ${isNative}`);
299
- return isNative;
176
+ return typeof window !== "undefined" && "ReactNativeWebView" in window;
300
177
  }
301
178
  /**
302
179
  * Initializes the native bridge message listener
@@ -305,55 +182,37 @@ var NativeBridge = class {
305
182
  static initialize() {
306
183
  if (this.isInitialized) {
307
184
  logger.debug("[NativeBridge] Already initialized");
308
- console.log("[NativeBridge] \u26A0 Already initialized, skipping");
309
185
  return;
310
186
  }
311
187
  if (typeof window === "undefined") {
312
188
  logger.warn("[NativeBridge] Window not available, skipping initialization");
313
- console.warn("[NativeBridge] \u26A0 Window not available, skipping initialization");
314
189
  return;
315
190
  }
316
- const isInIframe = window !== window.parent;
317
- console.log("[NativeBridge] ========== INITIALIZING ==========");
318
- console.log(`[NativeBridge] Running in iframe: ${isInIframe}`);
319
- console.log("[NativeBridge] Setting up message listeners on window and document");
320
191
  const boundHandler = this.handleNativeMessage.bind(this);
321
192
  window.addEventListener("message", boundHandler);
322
193
  document.addEventListener("message", boundHandler);
323
- if (isInIframe) {
324
- console.log("[NativeBridge] Setting up parent window message listener for iframe context");
325
- }
326
194
  this.isInitialized = true;
327
- console.log("[NativeBridge] \u2713 Event listeners attached to window and document");
328
- console.log("[NativeBridge] \u2713 Initialized and ready to receive messages");
329
195
  logger.info("[NativeBridge] Initialized and listening for native messages");
330
196
  }
331
197
  /**
332
198
  * Handles incoming messages from React Native
333
199
  */
334
200
  static handleNativeMessage(event) {
335
- console.log("[NativeBridge] [HANDLER] handleNativeMessage called");
336
- console.log("[NativeBridge] [HANDLER] Event source:", event.source === window ? "window" : "other");
337
- console.log("[NativeBridge] [HANDLER] Event data:", event.data);
338
201
  try {
339
202
  let data;
340
203
  if (typeof event.data === "string") {
341
204
  try {
342
205
  data = JSON.parse(event.data);
343
- logger.debug("[NativeBridge] Parsed message string:", data);
344
- } catch (parseError) {
345
- logger.debug("[NativeBridge] Failed to parse message, not JSON:", event.data);
206
+ } catch {
207
+ logger.debug("[NativeBridge] Ignoring non-JSON message");
346
208
  return;
347
209
  }
348
210
  } else if (typeof event.data === "object" && event.data !== null) {
349
211
  data = event.data;
350
- logger.debug("[NativeBridge] Received message object:", data);
351
212
  } else {
352
- logger.debug("[NativeBridge] Received invalid message type:", typeof event.data);
353
213
  return;
354
214
  }
355
- if (!data || !data.type) {
356
- logger.debug("[NativeBridge] Received message without type, ignoring");
215
+ if (!data?.type) {
357
216
  return;
358
217
  }
359
218
  const nativeResponseTypes = [
@@ -365,22 +224,16 @@ var NativeBridge = class {
365
224
  "PURCHASE_ERROR" /* PURCHASE_ERROR */
366
225
  ];
367
226
  if (!nativeResponseTypes.includes(data.type)) {
368
- logger.debug(`[NativeBridge] Ignoring non-native-response message: ${data.type}`);
369
227
  return;
370
228
  }
371
- console.log("[NativeBridge] [HANDLER] Message type:", data.type);
372
229
  const handler = this.handlers.get(data.type);
373
230
  if (handler) {
374
- console.log(`[NativeBridge] [HANDLER] \u2713 Found handler for: ${data.type}`);
375
- logger.info(`[NativeBridge] Handling message: ${data.type}`, data.payload);
231
+ logger.debug(`[NativeBridge] Handling message: ${data.type}`);
376
232
  handler(data.payload);
377
- console.log(`[NativeBridge] [HANDLER] \u2713 Handler completed for: ${data.type}`);
378
233
  } else {
379
- console.warn(`[NativeBridge] [HANDLER] \u26A0 No handler registered for: ${data.type}`);
380
234
  logger.warn(`[NativeBridge] No handler registered for: ${data.type}`);
381
235
  }
382
236
  } catch (error) {
383
- console.error("[NativeBridge] [HANDLER] \u274C Error handling native message:", error);
384
237
  logger.error("[NativeBridge] Error handling native message:", error);
385
238
  }
386
239
  }
@@ -390,27 +243,15 @@ var NativeBridge = class {
390
243
  * @param payload Optional payload data
391
244
  */
392
245
  static send(type, payload) {
393
- console.log(`[NativeBridge] [SEND] Attempting to send message: ${type}`);
394
246
  if (!this.isNativeContext()) {
395
- console.log(`[NativeBridge] [SEND] \u274C Not in native context, skipping message: ${type}`);
396
- logger.debug(
397
- `[NativeBridge] Not in native context, skipping message: ${type}`
398
- );
247
+ logger.debug(`[NativeBridge] Not in native context, skipping message: ${type}`);
399
248
  return;
400
249
  }
401
- console.log(`[NativeBridge] [SEND] \u2713 In native context, sending message`);
402
250
  try {
403
- const message = {
404
- type,
405
- payload,
406
- timestamp: Date.now()
407
- };
408
- console.log(`[NativeBridge] [SEND] Message object:`, message);
251
+ const message = { type, payload, timestamp: Date.now() };
409
252
  window.ReactNativeWebView.postMessage(JSON.stringify(message));
410
- console.log(`[NativeBridge] [SEND] \u2713 Message sent to native: ${type}`);
411
253
  logger.debug(`[NativeBridge] Sent message to native: ${type}`, payload);
412
254
  } catch (error) {
413
- console.error(`[NativeBridge] [SEND] \u274C\u274C Error sending message to native:`, error);
414
255
  logger.error(`[NativeBridge] Error sending message to native:`, error);
415
256
  }
416
257
  }
@@ -420,10 +261,7 @@ var NativeBridge = class {
420
261
  * @param handler Function to call when message is received
421
262
  */
422
263
  static on(type, handler) {
423
- console.log(`[NativeBridge] [ON] Registering handler for: ${type}`);
424
264
  this.handlers.set(type, handler);
425
- console.log(`[NativeBridge] [ON] \u2713 Handler registered. Total handlers:`, this.handlers.size);
426
- console.log(`[NativeBridge] [ON] All registered types:`, Array.from(this.handlers.keys()));
427
265
  logger.debug(`[NativeBridge] Registered handler for: ${type}`);
428
266
  }
429
267
  /**
@@ -573,7 +411,6 @@ function generateUUID() {
573
411
  // src/services/ads.ts
574
412
  var AdsService = class {
575
413
  config = {
576
- enabled: false,
577
414
  sound: "on",
578
415
  debug: false,
579
416
  onBeforeAd: () => {
@@ -583,87 +420,69 @@ var AdsService = class {
583
420
  onRewardEarned: () => {
584
421
  }
585
422
  };
586
- initialized = false;
423
+ // Cached init promise — ensures adConfig() is only called once
424
+ initPromise = null;
587
425
  ready = false;
588
426
  /**
589
- * Configure the ads service
590
- * Must set enabled: true to activate ads
427
+ * Optionally configure the ads service.
428
+ * Not required ads work without calling this.
591
429
  */
592
430
  configure(config) {
593
431
  this.config = {
594
432
  ...this.config,
595
433
  ...config,
596
- onBeforeAd: config.onBeforeAd || this.config.onBeforeAd,
597
- onAfterAd: config.onAfterAd || this.config.onAfterAd,
598
- onRewardEarned: config.onRewardEarned || this.config.onRewardEarned
434
+ onBeforeAd: config.onBeforeAd ?? this.config.onBeforeAd,
435
+ onAfterAd: config.onAfterAd ?? this.config.onAfterAd,
436
+ onRewardEarned: config.onRewardEarned ?? this.config.onRewardEarned
599
437
  };
600
438
  if (this.config.debug) {
601
- console.log("[AdsService] Configuration updated:", {
602
- enabled: this.config.enabled,
603
- sound: this.config.sound
604
- });
605
- }
606
- if (this.config.enabled && !this.initialized) {
607
- this.initialize();
439
+ logger.debug("[AdsService] Configuration updated:", { sound: this.config.sound });
608
440
  }
609
441
  }
610
442
  /**
611
- * Initialize the ads system
443
+ * Initialize the Google H5 Ads system.
444
+ * Returns a cached promise — safe to call multiple times.
612
445
  */
613
446
  initialize() {
614
- if (this.initialized) return;
615
- if (!this.config.enabled) {
616
- if (this.config.debug) {
617
- console.log("[AdsService] Ads disabled, skipping initialization");
618
- }
619
- return;
620
- }
621
- if (!window.adConfig || !window.adBreak) {
622
- console.warn("[AdsService] Google Ads SDK not found. Ads will not be available.");
623
- return;
624
- }
625
- if (this.config.debug) {
626
- console.log("[AdsService] Initializing ads system...");
627
- }
628
- const googleConfig = {
629
- sound: this.config.sound,
630
- preloadAdBreaks: "on",
631
- onReady: () => {
632
- this.ready = true;
447
+ if (this.initPromise) return this.initPromise;
448
+ this.initPromise = new Promise((resolve) => {
449
+ if (!window.adConfig || !window.adBreak) {
633
450
  if (this.config.debug) {
634
- console.log("[AdsService] Ads ready");
451
+ logger.debug("[AdsService] Google Ads SDK not found \u2014 ads unavailable");
635
452
  }
453
+ resolve(false);
454
+ return;
636
455
  }
637
- };
638
- window.adConfig(googleConfig);
639
- this.initialized = true;
456
+ if (this.config.debug) {
457
+ logger.debug("[AdsService] Initializing ads system...");
458
+ }
459
+ window.adConfig({
460
+ sound: this.config.sound,
461
+ preloadAdBreaks: "on",
462
+ onReady: () => {
463
+ this.ready = true;
464
+ if (this.config.debug) {
465
+ logger.debug("[AdsService] Ads ready");
466
+ }
467
+ resolve(true);
468
+ }
469
+ });
470
+ setTimeout(() => resolve(false), 5e3);
471
+ });
472
+ return this.initPromise;
640
473
  }
641
474
  /**
642
- * Show an ad
643
- * Returns immediately if ads are disabled
475
+ * Show an ad. Auto-initializes on the first call.
476
+ * Returns immediately with success: false if ads are disabled or unavailable.
644
477
  */
645
478
  async show(type) {
646
479
  const requestedAt = Date.now();
647
- if (!this.config.enabled) {
648
- if (this.config.debug) {
649
- console.log("[AdsService] Ads disabled, skipping ad request");
650
- }
480
+ const ready = await this.initialize();
481
+ if (!ready || !window.adBreak) {
651
482
  return {
652
483
  success: false,
653
484
  type,
654
- error: new Error("Ads are disabled"),
655
- requestedAt,
656
- completedAt: Date.now()
657
- };
658
- }
659
- if (!this.initialized) {
660
- this.initialize();
661
- }
662
- if (!this.ready || !window.adBreak) {
663
- return {
664
- success: false,
665
- type,
666
- error: new Error("Ads not ready"),
485
+ error: new Error("Ads not available"),
667
486
  requestedAt,
668
487
  completedAt: Date.now()
669
488
  };
@@ -671,7 +490,7 @@ var AdsService = class {
671
490
  return this.showAdBreak(type);
672
491
  }
673
492
  /**
674
- * Show an ad break
493
+ * Show an ad break via the Google H5 API.
675
494
  */
676
495
  async showAdBreak(type) {
677
496
  const requestedAt = Date.now();
@@ -679,74 +498,35 @@ var AdsService = class {
679
498
  const googleType = type === "rewarded" ? "reward" : type === "preroll" ? "start" : "next";
680
499
  const adName = `${type}-ad-${Date.now()}`;
681
500
  if (this.config.debug) {
682
- console.log(`[AdsService] Showing ${type} ad`);
501
+ logger.debug(`[AdsService] Showing ${type} ad`);
683
502
  }
684
503
  this.config.onBeforeAd(type);
685
504
  const adBreakConfig = {
686
505
  type: googleType,
687
506
  name: adName,
688
- beforeAd: () => {
689
- if (this.config.debug) {
690
- console.log("[AdsService] Ad started");
691
- }
692
- },
693
507
  afterAd: () => {
694
- if (this.config.debug) {
695
- console.log("[AdsService] Ad finished");
696
- }
697
508
  this.config.onAfterAd(type);
698
509
  },
699
510
  adBreakDone: (info) => {
700
511
  const completedAt = Date.now();
701
- let success = false;
702
- if (type === "rewarded") {
703
- success = info?.breakStatus === "viewed";
704
- } else {
705
- success = info?.breakStatus !== "error";
706
- }
512
+ const success = type === "rewarded" ? info?.breakStatus === "viewed" : info?.breakStatus !== "error";
707
513
  const error = info?.breakStatus === "error" && info?.error ? new Error(info.error) : void 0;
708
514
  if (this.config.debug) {
709
- console.log("[AdsService] Ad break done:", {
710
- success,
711
- status: info?.breakStatus
712
- });
515
+ logger.debug("[AdsService] Ad break done:", { success, status: info?.breakStatus });
713
516
  }
714
- const result = {
715
- success,
716
- type,
717
- error,
718
- requestedAt,
719
- completedAt
720
- };
721
- resolve(result);
517
+ resolve({ success, type, error, requestedAt, completedAt });
722
518
  }
723
519
  };
724
520
  if (type === "rewarded") {
725
- adBreakConfig.beforeReward = (showAdFn) => {
726
- if (this.config.debug) {
727
- console.log("[AdsService] beforeReward callback");
728
- }
729
- showAdFn();
730
- };
731
- adBreakConfig.adViewed = () => {
732
- if (this.config.debug) {
733
- console.log("[AdsService] Rewarded ad watched successfully");
734
- }
735
- this.config.onRewardEarned();
736
- };
737
- adBreakConfig.adDismissed = () => {
738
- if (this.config.debug) {
739
- console.log("[AdsService] Rewarded ad dismissed");
740
- }
741
- };
521
+ adBreakConfig.beforeReward = (showAdFn) => showAdFn();
522
+ adBreakConfig.adViewed = () => this.config.onRewardEarned();
742
523
  }
743
524
  window.adBreak(adBreakConfig);
744
525
  });
745
526
  }
746
527
  /**
747
528
  * Returns the configured ad lifecycle callbacks.
748
- * Used by platform-specific ad providers (e.g. Playgama) to fire the same
749
- * onBeforeAd / onAfterAd / onRewardEarned hooks that the Google H5 path uses.
529
+ * Used by platform-specific providers (e.g. Playgama) to fire the same hooks.
750
530
  */
751
531
  getCallbacks() {
752
532
  return {
@@ -756,16 +536,10 @@ var AdsService = class {
756
536
  };
757
537
  }
758
538
  /**
759
- * Check if ads are enabled
760
- */
761
- isEnabled() {
762
- return this.config.enabled;
763
- }
764
- /**
765
- * Check if ads are ready to show
539
+ * Check if ads have successfully initialized and are ready to show.
766
540
  */
767
541
  isReady() {
768
- return this.config.enabled && this.ready;
542
+ return this.ready;
769
543
  }
770
544
  };
771
545
 
@@ -812,7 +586,7 @@ var PlaygamaService = class {
812
586
  this.initialized = true;
813
587
  return true;
814
588
  } catch (error) {
815
- console.warn("[PlaygamaService] Failed to initialize:", error);
589
+ logger.warn("[PlaygamaService] Failed to initialize:", error);
816
590
  return false;
817
591
  }
818
592
  }
@@ -924,6 +698,176 @@ var PlaygamaService = class {
924
698
  }
925
699
  };
926
700
 
701
+ // src/services/crazygames.ts
702
+ var CRAZYGAMES_SDK_CDN = "https://sdk.crazygames.com/crazygames-sdk-v2.js";
703
+ var CrazyGamesService = class {
704
+ initialized = false;
705
+ /**
706
+ * Detects if the game is running on the CrazyGames platform.
707
+ * Games on CrazyGames run inside an iframe, so we check document.referrer
708
+ * and attempt to read the parent frame location.
709
+ */
710
+ static isCrazyGamesDomain() {
711
+ try {
712
+ if (document.referrer.includes("crazygames.com")) {
713
+ return true;
714
+ }
715
+ if (window !== window.top) {
716
+ try {
717
+ if (window.parent.location.hostname.includes("crazygames.com")) {
718
+ return true;
719
+ }
720
+ } catch {
721
+ }
722
+ }
723
+ return false;
724
+ } catch {
725
+ return false;
726
+ }
727
+ }
728
+ /**
729
+ * Loads the CrazyGames SDK from CDN and confirms the environment is 'crazygames'.
730
+ * Safe to call multiple times — resolves immediately if already initialized.
731
+ */
732
+ async initialize() {
733
+ if (this.initialized) return true;
734
+ try {
735
+ await this.loadScript();
736
+ const sdk = window.CrazyGames?.SDK;
737
+ if (!sdk) {
738
+ logger.warn("[CrazyGamesService] SDK not found after script load");
739
+ return false;
740
+ }
741
+ const env = await sdk.getEnvironment();
742
+ if (env !== "crazygames" && env !== "local") {
743
+ logger.warn("[CrazyGamesService] Unexpected environment:", env);
744
+ return false;
745
+ }
746
+ this.initialized = true;
747
+ return true;
748
+ } catch (error) {
749
+ logger.warn("[CrazyGamesService] Failed to initialize:", error);
750
+ return false;
751
+ }
752
+ }
753
+ isInitialized() {
754
+ return this.initialized;
755
+ }
756
+ /**
757
+ * Shows a midgame (interstitial) ad via the CrazyGames SDK.
758
+ */
759
+ async showInterstitial(callbacks) {
760
+ const requestedAt = Date.now();
761
+ const sdk = window.CrazyGames?.SDK;
762
+ if (!this.initialized || !sdk) {
763
+ return {
764
+ success: false,
765
+ type: "interstitial",
766
+ error: new Error("CrazyGames SDK not initialized"),
767
+ requestedAt,
768
+ completedAt: Date.now()
769
+ };
770
+ }
771
+ return new Promise((resolve) => {
772
+ sdk.ad.requestAd("midgame", {
773
+ adStarted: () => {
774
+ callbacks?.onBeforeAd?.();
775
+ },
776
+ adFinished: () => {
777
+ callbacks?.onAfterAd?.();
778
+ resolve({ success: true, type: "interstitial", requestedAt, completedAt: Date.now() });
779
+ },
780
+ adError: (error) => {
781
+ callbacks?.onAfterAd?.();
782
+ resolve({
783
+ success: false,
784
+ type: "interstitial",
785
+ error: new Error(`CrazyGames interstitial ad error: ${error}`),
786
+ requestedAt,
787
+ completedAt: Date.now()
788
+ });
789
+ }
790
+ });
791
+ });
792
+ }
793
+ /**
794
+ * Shows a rewarded ad via the CrazyGames SDK.
795
+ * Resolves with success: true only when adFinished fires (ad was fully watched).
796
+ */
797
+ async showRewarded(callbacks) {
798
+ const requestedAt = Date.now();
799
+ const sdk = window.CrazyGames?.SDK;
800
+ if (!this.initialized || !sdk) {
801
+ return {
802
+ success: false,
803
+ type: "rewarded",
804
+ error: new Error("CrazyGames SDK not initialized"),
805
+ requestedAt,
806
+ completedAt: Date.now()
807
+ };
808
+ }
809
+ return new Promise((resolve) => {
810
+ sdk.ad.requestAd("rewarded", {
811
+ adStarted: () => {
812
+ callbacks?.onBeforeAd?.();
813
+ },
814
+ adFinished: () => {
815
+ callbacks?.onRewardEarned?.();
816
+ callbacks?.onAfterAd?.();
817
+ resolve({ success: true, type: "rewarded", requestedAt, completedAt: Date.now() });
818
+ },
819
+ adError: (error) => {
820
+ callbacks?.onAfterAd?.();
821
+ resolve({
822
+ success: false,
823
+ type: "rewarded",
824
+ error: new Error(`CrazyGames rewarded ad error: ${error}`),
825
+ requestedAt,
826
+ completedAt: Date.now()
827
+ });
828
+ }
829
+ });
830
+ });
831
+ }
832
+ /**
833
+ * Notifies CrazyGames that gameplay has started.
834
+ * Call when the player actively begins or resumes playing.
835
+ */
836
+ gameplayStart() {
837
+ if (!this.initialized) return;
838
+ window.CrazyGames?.SDK.game.gameplayStart();
839
+ }
840
+ /**
841
+ * Notifies CrazyGames that gameplay has stopped.
842
+ * Call during menu access, level completion, or pausing.
843
+ */
844
+ gameplayStop() {
845
+ if (!this.initialized) return;
846
+ window.CrazyGames?.SDK.game.gameplayStop();
847
+ }
848
+ /**
849
+ * Triggers a celebration effect on the CrazyGames website.
850
+ * Use sparingly for significant achievements (boss defeat, personal record, etc.)
851
+ */
852
+ happytime() {
853
+ if (!this.initialized) return;
854
+ window.CrazyGames?.SDK.game.happytime();
855
+ }
856
+ loadScript() {
857
+ return new Promise((resolve, reject) => {
858
+ if (window.CrazyGames?.SDK) {
859
+ resolve();
860
+ return;
861
+ }
862
+ const script = document.createElement("script");
863
+ script.src = CRAZYGAMES_SDK_CDN;
864
+ script.onload = () => resolve();
865
+ script.onerror = () => reject(new Error("Failed to load CrazyGames SDK script"));
866
+ document.head.appendChild(script);
867
+ });
868
+ }
869
+ };
870
+
927
871
  // src/services/billing.ts
928
872
  var BillingPlatform = /* @__PURE__ */ ((BillingPlatform3) => {
929
873
  BillingPlatform3["WEB"] = "web";
@@ -934,41 +878,28 @@ var BillingPlatform = /* @__PURE__ */ ((BillingPlatform3) => {
934
878
  var BillingService = class {
935
879
  config;
936
880
  platform = "unknown" /* UNKNOWN */;
937
- isInitialized = false;
881
+ initPromise = null;
938
882
  nativeAvailable = false;
939
- products = [];
940
883
  // Stripe instance for web payments
941
884
  stripe = null;
942
885
  checkoutElement = null;
943
886
  // Callbacks for purchase events
944
887
  onPurchaseCompleteCallback;
945
888
  onPurchaseErrorCallback;
946
- // Callback for logging (so external UI can capture logs)
947
- onLogCallback;
948
889
  constructor(config) {
949
890
  this.config = config;
950
891
  this.detectPlatform();
951
892
  }
952
893
  /**
953
- * Register a callback to receive all internal logs
954
- * Useful for displaying logs in a UI
894
+ * Update billing configuration. Resets initialization so next call re-inits with new config.
955
895
  */
956
- onLog(callback) {
957
- this.onLogCallback = callback;
958
- }
959
- /**
960
- * Internal logging method that calls both logger and custom callback
961
- */
962
- log(level, message, data) {
963
- const consoleMethod = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
964
- if (data !== void 0) {
965
- consoleMethod(message, data);
966
- } else {
967
- consoleMethod(message);
968
- }
969
- if (this.onLogCallback) {
970
- this.onLogCallback(level, message, data);
971
- }
896
+ configure(config) {
897
+ this.config = {
898
+ ...this.config,
899
+ ...config
900
+ };
901
+ this.initPromise = null;
902
+ logger.info("[BillingService] Configuration updated");
972
903
  }
973
904
  /**
974
905
  * Detects if running on web or native platform
@@ -978,13 +909,13 @@ var BillingService = class {
978
909
  const hasWindow = typeof window !== "undefined";
979
910
  if (isNative) {
980
911
  this.platform = "native" /* NATIVE */;
981
- this.log("info", "[BillingService] Platform: NATIVE");
912
+ logger.info("[BillingService] Platform: NATIVE");
982
913
  } else if (hasWindow) {
983
914
  this.platform = "web" /* WEB */;
984
- this.log("info", "[BillingService] Platform: WEB");
915
+ logger.info("[BillingService] Platform: WEB");
985
916
  } else {
986
917
  this.platform = "unknown" /* UNKNOWN */;
987
- this.log("warn", "[BillingService] Platform: UNKNOWN");
918
+ logger.warn("[BillingService] Platform: UNKNOWN");
988
919
  }
989
920
  }
990
921
  /**
@@ -994,38 +925,40 @@ var BillingService = class {
994
925
  return this.platform;
995
926
  }
996
927
  /**
997
- * Initialize the billing service
998
- * Must be called before using any other methods
928
+ * Initialize the billing service. Idempotent — returns cached promise on subsequent calls.
929
+ * Called automatically by getProducts() and purchase().
999
930
  */
1000
- async initialize() {
1001
- if (this.isInitialized) {
1002
- return true;
1003
- }
1004
- this.log("info", `[BillingService] Initializing for ${this.platform} platform...`);
1005
- try {
1006
- if (this.platform === "native" /* NATIVE */) {
1007
- return await this.initializeNative();
1008
- } else if (this.platform === "web" /* WEB */) {
1009
- return await this.initializeWeb();
931
+ initialize() {
932
+ if (this.initPromise) return this.initPromise;
933
+ logger.info(`[BillingService] Initializing for ${this.platform} platform...`);
934
+ this.initPromise = (async () => {
935
+ try {
936
+ if (this.platform === "native" /* NATIVE */) {
937
+ return await this.initializeNative();
938
+ } else if (this.platform === "web" /* WEB */) {
939
+ return await this.initializeWeb();
940
+ }
941
+ logger.error("[BillingService] Cannot initialize: unknown platform");
942
+ return false;
943
+ } catch (error) {
944
+ logger.error("[BillingService] Initialization failed:", error?.message);
945
+ return false;
1010
946
  }
1011
- this.log("error", "[BillingService] Cannot initialize: unknown platform");
1012
- return false;
1013
- } catch (error) {
1014
- this.log("error", "[BillingService] Initialization failed:", error?.message);
1015
- return false;
1016
- }
947
+ })();
948
+ return this.initPromise;
1017
949
  }
1018
950
  /**
1019
951
  * Initialize native billing
1020
952
  */
1021
953
  async initializeNative() {
1022
954
  return new Promise((resolve) => {
955
+ let resolved = false;
1023
956
  try {
1024
957
  NativeBridge.initialize();
1025
958
  NativeBridge.on(
1026
959
  "PURCHASE_COMPLETE" /* PURCHASE_COMPLETE */,
1027
960
  (payload) => {
1028
- this.log("info", "[BillingService] Purchase complete:", payload.productId);
961
+ logger.info("[BillingService] Purchase complete:", payload.productId);
1029
962
  const result = {
1030
963
  success: true,
1031
964
  productId: payload.productId,
@@ -1040,7 +973,7 @@ var BillingService = class {
1040
973
  NativeBridge.on(
1041
974
  "PURCHASE_ERROR" /* PURCHASE_ERROR */,
1042
975
  (payload) => {
1043
- this.log("error", "[BillingService] Purchase error:", payload.message);
976
+ logger.error("[BillingService] Purchase error:", payload.message);
1044
977
  const result = {
1045
978
  success: false,
1046
979
  productId: payload.productId || "unknown",
@@ -1058,8 +991,8 @@ var BillingService = class {
1058
991
  "IAP_AVAILABILITY_RESULT" /* IAP_AVAILABILITY_RESULT */,
1059
992
  (payload) => {
1060
993
  this.nativeAvailable = payload.available;
1061
- this.isInitialized = true;
1062
- this.log("info", `[BillingService] Native billing ${payload.available ? "available" : "unavailable"}`);
994
+ logger.info(`[BillingService] Native billing ${payload.available ? "available" : "unavailable"}`);
995
+ resolved = true;
1063
996
  resolve(payload.available);
1064
997
  }
1065
998
  );
@@ -1067,15 +1000,15 @@ var BillingService = class {
1067
1000
  NativeBridge.checkIAPAvailability();
1068
1001
  }, 100);
1069
1002
  setTimeout(() => {
1070
- if (!this.isInitialized) {
1071
- this.log("warn", "[BillingService] Native initialization timeout");
1072
- this.isInitialized = true;
1003
+ if (!resolved) {
1004
+ logger.warn("[BillingService] Native initialization timeout");
1005
+ resolved = true;
1073
1006
  resolve(false);
1074
1007
  }
1075
1008
  }, 5e3);
1076
1009
  } catch (error) {
1077
- this.log("error", "[BillingService] Native initialization failed:", error?.message);
1078
- this.isInitialized = true;
1010
+ logger.error("[BillingService] Native initialization failed:", error?.message);
1011
+ resolved = true;
1079
1012
  resolve(false);
1080
1013
  }
1081
1014
  });
@@ -1085,12 +1018,12 @@ var BillingService = class {
1085
1018
  */
1086
1019
  async initializeWeb() {
1087
1020
  if (!this.config.stripePublishableKey) {
1088
- this.log("error", "[BillingService] Stripe publishable key not provided");
1021
+ logger.error("[BillingService] Stripe publishable key not provided");
1089
1022
  return false;
1090
1023
  }
1091
1024
  try {
1092
1025
  if (typeof window === "undefined") {
1093
- this.log("error", "[BillingService] Window is undefined (not in browser)");
1026
+ logger.error("[BillingService] Window is undefined (not in browser)");
1094
1027
  return false;
1095
1028
  }
1096
1029
  if (!window.Stripe) {
@@ -1098,15 +1031,14 @@ var BillingService = class {
1098
1031
  }
1099
1032
  if (window.Stripe) {
1100
1033
  this.stripe = window.Stripe(this.config.stripePublishableKey);
1101
- this.isInitialized = true;
1102
- this.log("info", "[BillingService] Web billing initialized");
1034
+ logger.info("[BillingService] Web billing initialized");
1103
1035
  return true;
1104
1036
  } else {
1105
- this.log("error", "[BillingService] Stripe not available after loading");
1037
+ logger.error("[BillingService] Stripe not available after loading");
1106
1038
  return false;
1107
1039
  }
1108
1040
  } catch (error) {
1109
- this.log("error", "[BillingService] Stripe initialization failed:", error?.message);
1041
+ logger.error("[BillingService] Stripe initialization failed:", error?.message);
1110
1042
  return false;
1111
1043
  }
1112
1044
  }
@@ -1131,11 +1063,11 @@ var BillingService = class {
1131
1063
  script.src = "https://js.stripe.com/v3/";
1132
1064
  script.async = true;
1133
1065
  script.onload = () => {
1134
- this.log("info", "[BillingService] Stripe.js loaded");
1066
+ logger.info("[BillingService] Stripe.js loaded");
1135
1067
  resolve();
1136
1068
  };
1137
1069
  script.onerror = () => {
1138
- this.log("error", "[BillingService] Failed to load Stripe.js");
1070
+ logger.error("[BillingService] Failed to load Stripe.js");
1139
1071
  reject(new Error("Failed to load Stripe.js"));
1140
1072
  };
1141
1073
  try {
@@ -1146,12 +1078,9 @@ var BillingService = class {
1146
1078
  });
1147
1079
  }
1148
1080
  /**
1149
- * Check if billing is available
1081
+ * Check if billing is available based on current config and platform state
1150
1082
  */
1151
1083
  isAvailable() {
1152
- if (!this.isInitialized) {
1153
- return false;
1154
- }
1155
1084
  if (this.platform === "native" /* NATIVE */) {
1156
1085
  return this.nativeAvailable;
1157
1086
  } else if (this.platform === "web" /* WEB */) {
@@ -1160,12 +1089,10 @@ var BillingService = class {
1160
1089
  return false;
1161
1090
  }
1162
1091
  /**
1163
- * Get available products
1092
+ * Get available products. Auto-initializes on first call.
1164
1093
  */
1165
1094
  async getProducts() {
1166
- if (!this.isInitialized) {
1167
- throw new Error("BillingService not initialized. Call initialize() first.");
1168
- }
1095
+ await this.initialize();
1169
1096
  if (this.platform === "native" /* NATIVE */) {
1170
1097
  return await this.getProductsNative();
1171
1098
  } else if (this.platform === "web" /* WEB */) {
@@ -1177,38 +1104,31 @@ var BillingService = class {
1177
1104
  * Get products from native IAP
1178
1105
  */
1179
1106
  async getProductsNative() {
1180
- return new Promise((resolve, reject) => {
1181
- if (!this.config.gameId || !this.config.checkoutUrl) {
1182
- const error = new Error("gameId and checkoutUrl required for native purchases");
1183
- this.log("error", "[BillingService]", error.message);
1184
- reject(error);
1185
- return;
1186
- }
1187
- const url = `${this.config.checkoutUrl}/get-native-packages?game_id=${this.config.gameId}`;
1188
- fetch(url).then((response) => {
1189
- if (!response.ok) {
1190
- throw new Error(`Failed to fetch native products: ${response.status}`);
1191
- }
1192
- return response.json();
1193
- }).then((data) => {
1194
- if (!data.packages || !Array.isArray(data.packages)) {
1195
- throw new Error("Invalid response format: missing packages array");
1196
- }
1197
- this.products = data.packages.map((pkg) => ({
1198
- productId: pkg.productId,
1199
- title: pkg.package_name,
1200
- description: `${pkg.game_name} - ${pkg.package_name}`,
1201
- price: pkg.price_cents / 100,
1202
- localizedPrice: pkg.price_display,
1203
- currency: "USD"
1204
- }));
1205
- this.log("info", `[BillingService] Fetched ${this.products.length} native products`);
1206
- resolve(this.products);
1207
- }).catch((error) => {
1208
- this.log("error", "[BillingService] Failed to fetch native products:", error?.message);
1209
- reject(error);
1210
- });
1211
- });
1107
+ if (!this.config.gameId || !this.config.checkoutUrl) {
1108
+ const error = new Error("gameId and checkoutUrl required for native purchases");
1109
+ logger.error("[BillingService]", error.message);
1110
+ throw error;
1111
+ }
1112
+ const response = await fetch(
1113
+ `${this.config.checkoutUrl}/get-native-packages?game_id=${this.config.gameId}`
1114
+ );
1115
+ if (!response.ok) {
1116
+ throw new Error(`Failed to fetch native products: ${response.status}`);
1117
+ }
1118
+ const data = await response.json();
1119
+ if (!data.packages || !Array.isArray(data.packages)) {
1120
+ throw new Error("Invalid response format: missing packages array");
1121
+ }
1122
+ const products = data.packages.map((pkg) => ({
1123
+ productId: pkg.productId,
1124
+ title: pkg.package_name,
1125
+ description: `${pkg.game_name} - ${pkg.package_name}`,
1126
+ price: pkg.price_cents / 100,
1127
+ localizedPrice: pkg.price_display,
1128
+ currency: "USD"
1129
+ }));
1130
+ logger.info(`[BillingService] Fetched ${products.length} native products`);
1131
+ return products;
1212
1132
  }
1213
1133
  /**
1214
1134
  * Get products from web API (Stripe)
@@ -1216,7 +1136,7 @@ var BillingService = class {
1216
1136
  async getProductsWeb() {
1217
1137
  if (!this.config.checkoutUrl || !this.config.gameId) {
1218
1138
  const error = new Error("checkoutUrl and gameId required for web purchases");
1219
- this.log("error", "[BillingService]", error.message);
1139
+ logger.error("[BillingService]", error.message);
1220
1140
  throw error;
1221
1141
  }
1222
1142
  try {
@@ -1229,7 +1149,7 @@ var BillingService = class {
1229
1149
  if (!data.packages || !Array.isArray(data.packages)) {
1230
1150
  throw new Error("Invalid response format: missing packages array");
1231
1151
  }
1232
- this.products = data.packages.map((pkg) => ({
1152
+ const products = data.packages.map((pkg) => ({
1233
1153
  productId: pkg.priceId || pkg.productId,
1234
1154
  // Prefer priceId for Stripe
1235
1155
  title: pkg.package_name,
@@ -1238,23 +1158,21 @@ var BillingService = class {
1238
1158
  localizedPrice: pkg.price_display,
1239
1159
  currency: "USD"
1240
1160
  }));
1241
- this.log("info", `[BillingService] Fetched ${this.products.length} web products`);
1242
- return this.products;
1161
+ logger.info(`[BillingService] Fetched ${products.length} web products`);
1162
+ return products;
1243
1163
  } catch (error) {
1244
- this.log("error", "[BillingService] Failed to fetch web products:", error?.message);
1164
+ logger.error("[BillingService] Failed to fetch web products:", error?.message);
1245
1165
  throw error;
1246
1166
  }
1247
1167
  }
1248
1168
  /**
1249
- * Purchase a product
1169
+ * Purchase a product. Auto-initializes on first call.
1250
1170
  * @param productId - The product ID (priceId for web/Stripe, productId for native)
1251
1171
  * @param options - Optional purchase options
1252
1172
  * @param options.elementId - For web: DOM element ID to mount Stripe checkout (default: 'stripe-checkout-element')
1253
1173
  */
1254
1174
  async purchase(productId, options) {
1255
- if (!this.isInitialized) {
1256
- throw new Error("BillingService not initialized. Call initialize() first.");
1257
- }
1175
+ await this.initialize();
1258
1176
  if (!this.isAvailable()) {
1259
1177
  throw new Error("Billing is not available on this platform");
1260
1178
  }
@@ -1274,7 +1192,7 @@ var BillingService = class {
1274
1192
  reject(new Error("userId is required for native purchases"));
1275
1193
  return;
1276
1194
  }
1277
- this.log("info", `[BillingService] Purchasing: ${productId}`);
1195
+ logger.info(`[BillingService] Purchasing: ${productId}`);
1278
1196
  const previousCompleteCallback = this.onPurchaseCompleteCallback;
1279
1197
  const previousErrorCallback = this.onPurchaseErrorCallback;
1280
1198
  const cleanup = () => {
@@ -1348,14 +1266,14 @@ var BillingService = class {
1348
1266
  throw new Error("No client_secret returned from checkout session");
1349
1267
  }
1350
1268
  await this.mountCheckoutElement(client_secret, elementId || "stripe-checkout-element");
1351
- this.log("info", `[BillingService] Checkout session created: ${id}`);
1269
+ logger.info(`[BillingService] Checkout session created: ${id}`);
1352
1270
  return {
1353
1271
  success: true,
1354
1272
  productId,
1355
1273
  transactionId: id
1356
1274
  };
1357
1275
  } catch (error) {
1358
- this.log("error", "[BillingService] Web purchase failed:", error?.message);
1276
+ logger.error("[BillingService] Web purchase failed:", error?.message);
1359
1277
  return {
1360
1278
  success: false,
1361
1279
  productId,
@@ -1377,7 +1295,7 @@ var BillingService = class {
1377
1295
  }
1378
1296
  try {
1379
1297
  if (this.checkoutElement) {
1380
- this.log("info", "[BillingService] Unmounting existing checkout element");
1298
+ logger.info("[BillingService] Unmounting existing checkout element");
1381
1299
  this.unmountCheckoutElement();
1382
1300
  await new Promise((resolve) => setTimeout(resolve, 100));
1383
1301
  }
@@ -1386,15 +1304,15 @@ var BillingService = class {
1386
1304
  throw new Error(`Element with id "${elementId}" not found in the DOM`);
1387
1305
  }
1388
1306
  container.innerHTML = "";
1389
- this.log("info", "[BillingService] Creating new checkout instance");
1307
+ logger.info("[BillingService] Creating new checkout instance");
1390
1308
  this.checkoutElement = await this.stripe.initEmbeddedCheckout({
1391
1309
  clientSecret
1392
1310
  });
1393
- this.log("info", "[BillingService] Mounting checkout element to DOM");
1311
+ logger.info("[BillingService] Mounting checkout element to DOM");
1394
1312
  this.checkoutElement.mount(`#${elementId}`);
1395
1313
  this.setupCheckoutEventListeners();
1396
1314
  } catch (error) {
1397
- this.log("error", "[BillingService] Failed to mount checkout:", error?.message);
1315
+ logger.error("[BillingService] Failed to mount checkout:", error?.message);
1398
1316
  throw error;
1399
1317
  }
1400
1318
  }
@@ -1415,7 +1333,7 @@ var BillingService = class {
1415
1333
  transactionId: sessionId || void 0
1416
1334
  });
1417
1335
  }
1418
- this.log("info", "[BillingService] Payment completed");
1336
+ logger.info("[BillingService] Payment completed");
1419
1337
  urlParams.delete("payment");
1420
1338
  urlParams.delete("session_id");
1421
1339
  const newUrl = `${window.location.pathname}${urlParams.toString() ? "?" + urlParams.toString() : ""}`;
@@ -1434,7 +1352,7 @@ var BillingService = class {
1434
1352
  this.checkoutElement.destroy();
1435
1353
  }
1436
1354
  } catch (error) {
1437
- this.log("warn", "[BillingService] Error unmounting checkout element:", error?.message);
1355
+ logger.warn("[BillingService] Error unmounting checkout element:", error?.message);
1438
1356
  }
1439
1357
  this.checkoutElement = null;
1440
1358
  }
@@ -1462,9 +1380,8 @@ var BillingService = class {
1462
1380
  NativeBridge.off("PURCHASE_ERROR" /* PURCHASE_ERROR */);
1463
1381
  }
1464
1382
  this.unmountCheckoutElement();
1465
- this.isInitialized = false;
1383
+ this.initPromise = null;
1466
1384
  this.nativeAvailable = false;
1467
- this.products = [];
1468
1385
  this.stripe = null;
1469
1386
  this.onPurchaseCompleteCallback = void 0;
1470
1387
  this.onPurchaseErrorCallback = void 0;
@@ -1637,10 +1554,9 @@ var HyveClient = class {
1637
1554
  adsService;
1638
1555
  playgamaService = null;
1639
1556
  playgamaInitPromise = null;
1557
+ crazyGamesService = null;
1558
+ crazyGamesInitPromise = null;
1640
1559
  billingService;
1641
- billingConfig;
1642
- // Store callbacks to preserve them when recreating BillingService
1643
- billingCallbacks = {};
1644
1560
  storageMode;
1645
1561
  cloudStorageAdapter;
1646
1562
  localStorageAdapter;
@@ -1671,13 +1587,28 @@ var HyveClient = class {
1671
1587
  return success;
1672
1588
  });
1673
1589
  }
1674
- this.billingConfig = config?.billing || {};
1675
- this.billingService = new BillingService(this.billingConfig);
1590
+ if (typeof window !== "undefined" && CrazyGamesService.isCrazyGamesDomain()) {
1591
+ this.crazyGamesService = new CrazyGamesService();
1592
+ this.crazyGamesInitPromise = this.crazyGamesService.initialize().then((success) => {
1593
+ logger.info("CrazyGames SDK initialized:", success);
1594
+ return success;
1595
+ });
1596
+ }
1676
1597
  this.storageMode = config?.storageMode || "cloud";
1677
1598
  this.cloudStorageAdapter = new CloudStorageAdapter(
1678
1599
  (endpoint, options) => this.callApi(endpoint, options)
1679
1600
  );
1680
1601
  this.localStorageAdapter = new LocalStorageAdapter(() => this.getUserId());
1602
+ if (typeof window !== "undefined") {
1603
+ this._parseUrlAuth();
1604
+ }
1605
+ const billingConfig = {
1606
+ checkoutUrl: this.apiBaseUrl,
1607
+ userId: this.userId ?? void 0,
1608
+ gameId: this.gameId ? Number(this.gameId) : void 0,
1609
+ ...config?.billing
1610
+ };
1611
+ this.billingService = new BillingService(billingConfig);
1681
1612
  const envSource = config?.isDev !== void 0 ? "explicit config" : "auto-detected from parent URL";
1682
1613
  logger.info("==========================================");
1683
1614
  logger.info("HyveClient Initialized");
@@ -1689,28 +1620,27 @@ var HyveClient = class {
1689
1620
  `(${envSource})`
1690
1621
  );
1691
1622
  logger.info("API Base URL:", this.apiBaseUrl);
1692
- logger.info("Ads enabled:", this.adsService.isEnabled());
1693
1623
  logger.info("Playgama platform:", this.playgamaService !== null);
1624
+ logger.info("CrazyGames platform:", this.crazyGamesService !== null);
1694
1625
  logger.info(
1695
1626
  "Billing configured:",
1696
- Object.keys(this.billingConfig).length > 0
1627
+ !!config?.billing && Object.keys(config.billing).length > 0
1697
1628
  );
1698
1629
  logger.info("Storage mode:", this.storageMode);
1630
+ logger.info("Authenticated:", this.jwtToken !== null);
1699
1631
  logger.debug("Config:", {
1700
1632
  isDev: this.telemetryConfig.isDev,
1701
1633
  hasCustomApiUrl: !!config?.apiBaseUrl,
1702
- adsEnabled: config?.ads?.enabled || false,
1703
- billingConfigured: Object.keys(this.billingConfig).length > 0,
1634
+ billingConfigured: !!config?.billing && Object.keys(config.billing).length > 0,
1704
1635
  storageMode: this.storageMode
1705
1636
  });
1706
1637
  logger.info("==========================================");
1707
1638
  }
1708
1639
  /**
1709
- * Authenticates a user from URL parameters
1710
- * @param urlParams URL parameters or search string
1711
- * @returns Promise resolving to boolean indicating success
1640
+ * Parses JWT and game ID from the current window URL and stores them on the client.
1641
+ * Called automatically during construction.
1712
1642
  */
1713
- async authenticateFromUrl(urlParams) {
1643
+ _parseUrlAuth(urlParams) {
1714
1644
  try {
1715
1645
  const params = urlParams ? parseUrlParams(urlParams) : parseUrlParams(window.location.search);
1716
1646
  if (params.hyveAccess) {
@@ -1735,27 +1665,11 @@ var HyveClient = class {
1735
1665
  }
1736
1666
  if (this.jwtToken) {
1737
1667
  logger.info("Authentication successful via JWT");
1738
- return true;
1739
- }
1740
- const authResult = verifyAuthentication({
1741
- hyveToken: params.hyveToken,
1742
- signature: params.signature,
1743
- message: params.message
1744
- });
1745
- if (authResult.isValid && authResult.address) {
1746
- this.userId = authResult.address;
1747
- logger.info("Authentication successful:", authResult.address);
1748
- logger.info("Authentication method:", authResult.method);
1749
- return true;
1750
1668
  } else {
1751
- logger.error("Authentication failed:", authResult.error);
1752
- this.userId = null;
1753
- return false;
1669
+ logger.info("No hyve-access JWT token in URL \u2014 unauthenticated");
1754
1670
  }
1755
1671
  } catch (error) {
1756
- logger.error("Authentication error:", error);
1757
- this.userId = null;
1758
- return false;
1672
+ logger.error("Error parsing URL auth:", error);
1759
1673
  }
1760
1674
  }
1761
1675
  /**
@@ -1772,7 +1686,7 @@ var HyveClient = class {
1772
1686
  */
1773
1687
  async sendTelemetry(eventLocation, eventCategory, eventAction, eventSubCategory, eventSubAction, eventDetails, platformId) {
1774
1688
  if (!this.jwtToken) {
1775
- logger.error("JWT token required. Call authenticateFromUrl first.");
1689
+ logger.error("JWT token required. Ensure hyve-access and game-id are present in the URL.");
1776
1690
  return false;
1777
1691
  }
1778
1692
  if (!this.gameId) {
@@ -1844,7 +1758,7 @@ var HyveClient = class {
1844
1758
  async callApi(endpoint, options = {}) {
1845
1759
  if (!this.jwtToken) {
1846
1760
  throw new Error(
1847
- "No JWT token available. Call authenticateFromUrl first."
1761
+ "No JWT token available. Ensure hyve-access and game-id are present in the URL."
1848
1762
  );
1849
1763
  }
1850
1764
  try {
@@ -1974,6 +1888,13 @@ var HyveClient = class {
1974
1888
  const storageMode = mode || this.storageMode;
1975
1889
  return storageMode === "local" ? this.localStorageAdapter : this.cloudStorageAdapter;
1976
1890
  }
1891
+ /**
1892
+ * Returns the current game ID or throws if not available.
1893
+ */
1894
+ requireGameId() {
1895
+ const gameId = this.requireGameId();
1896
+ return gameId;
1897
+ }
1977
1898
  /**
1978
1899
  * Save persistent game data
1979
1900
  * @param key Data key
@@ -1982,10 +1903,7 @@ var HyveClient = class {
1982
1903
  * @returns Promise resolving to save response
1983
1904
  */
1984
1905
  async saveGameData(key, value, storage) {
1985
- const gameId = this.getGameId();
1986
- if (!gameId) {
1987
- throw new Error("game-id is required for persistent storage. Call authenticateFromUrl first.");
1988
- }
1906
+ const gameId = this.requireGameId();
1989
1907
  const storageMode = storage || this.storageMode;
1990
1908
  logger.debug(`Saving game data to ${storageMode}: ${gameId}/${key}`);
1991
1909
  const adapter = this.getStorageAdapter(storage);
@@ -2000,10 +1918,7 @@ var HyveClient = class {
2000
1918
  * @returns Promise resolving to save response
2001
1919
  */
2002
1920
  async batchSaveGameData(items, storage) {
2003
- const gameId = this.getGameId();
2004
- if (!gameId) {
2005
- throw new Error("game-id is required for persistent storage. Call authenticateFromUrl first.");
2006
- }
1921
+ const gameId = this.requireGameId();
2007
1922
  const storageMode = storage || this.storageMode;
2008
1923
  logger.debug(`Batch saving ${items.length} game data entries to ${storageMode} for game: ${gameId}`);
2009
1924
  const adapter = this.getStorageAdapter(storage);
@@ -2018,10 +1933,7 @@ var HyveClient = class {
2018
1933
  * @returns Promise resolving to game data item or null if not found
2019
1934
  */
2020
1935
  async getGameData(key, storage) {
2021
- const gameId = this.getGameId();
2022
- if (!gameId) {
2023
- throw new Error("game-id is required for persistent storage. Call authenticateFromUrl first.");
2024
- }
1936
+ const gameId = this.requireGameId();
2025
1937
  const storageMode = storage || this.storageMode;
2026
1938
  logger.debug(`Getting game data from ${storageMode}: ${gameId}/${key}`);
2027
1939
  const adapter = this.getStorageAdapter(storage);
@@ -2040,10 +1952,7 @@ var HyveClient = class {
2040
1952
  * @returns Promise resolving to array of game data items
2041
1953
  */
2042
1954
  async getMultipleGameData(keys, storage) {
2043
- const gameId = this.getGameId();
2044
- if (!gameId) {
2045
- throw new Error("game-id is required for persistent storage. Call authenticateFromUrl first.");
2046
- }
1955
+ const gameId = this.requireGameId();
2047
1956
  const storageMode = storage || this.storageMode;
2048
1957
  logger.debug(`Getting ${keys.length} game data entries from ${storageMode} for game: ${gameId}`);
2049
1958
  const adapter = this.getStorageAdapter(storage);
@@ -2058,10 +1967,7 @@ var HyveClient = class {
2058
1967
  * @returns Promise resolving to boolean indicating if data was deleted
2059
1968
  */
2060
1969
  async deleteGameData(key, storage) {
2061
- const gameId = this.getGameId();
2062
- if (!gameId) {
2063
- throw new Error("game-id is required for persistent storage. Call authenticateFromUrl first.");
2064
- }
1970
+ const gameId = this.requireGameId();
2065
1971
  const storageMode = storage || this.storageMode;
2066
1972
  logger.debug(`Deleting game data from ${storageMode}: ${gameId}/${key}`);
2067
1973
  const adapter = this.getStorageAdapter(storage);
@@ -2080,10 +1986,7 @@ var HyveClient = class {
2080
1986
  * @returns Promise resolving to number of entries deleted
2081
1987
  */
2082
1988
  async deleteMultipleGameData(keys, storage) {
2083
- const gameId = this.getGameId();
2084
- if (!gameId) {
2085
- throw new Error("game-id is required for persistent storage. Call authenticateFromUrl first.");
2086
- }
1989
+ const gameId = this.requireGameId();
2087
1990
  const storageMode = storage || this.storageMode;
2088
1991
  logger.debug(`Deleting ${keys.length} game data entries from ${storageMode} for game: ${gameId}`);
2089
1992
  const adapter = this.getStorageAdapter(storage);
@@ -2120,6 +2023,25 @@ var HyveClient = class {
2120
2023
  * @returns Promise resolving to ad result
2121
2024
  */
2122
2025
  async showAd(type) {
2026
+ if (this.crazyGamesService) {
2027
+ if (this.crazyGamesInitPromise) {
2028
+ await this.crazyGamesInitPromise;
2029
+ }
2030
+ if (this.crazyGamesService.isInitialized()) {
2031
+ const { onBeforeAd, onAfterAd, onRewardEarned } = this.adsService.getCallbacks();
2032
+ if (type === "rewarded") {
2033
+ return this.crazyGamesService.showRewarded({
2034
+ onBeforeAd: () => onBeforeAd("rewarded"),
2035
+ onAfterAd: () => onAfterAd("rewarded"),
2036
+ onRewardEarned
2037
+ });
2038
+ }
2039
+ return this.crazyGamesService.showInterstitial({
2040
+ onBeforeAd: () => onBeforeAd(type),
2041
+ onAfterAd: () => onAfterAd(type)
2042
+ });
2043
+ }
2044
+ }
2123
2045
  if (this.playgamaService) {
2124
2046
  if (this.playgamaInitPromise) {
2125
2047
  await this.playgamaInitPromise;
@@ -2142,59 +2064,41 @@ var HyveClient = class {
2142
2064
  return this.adsService.show(type);
2143
2065
  }
2144
2066
  /**
2145
- * Check if ads are enabled
2146
- * @returns Boolean indicating if ads are enabled
2067
+ * Notifies CrazyGames that gameplay has started.
2068
+ * No-op on other platforms.
2147
2069
  */
2148
- areAdsEnabled() {
2149
- return this.adsService.isEnabled();
2070
+ async gameplayStart() {
2071
+ if (this.crazyGamesService) {
2072
+ if (this.crazyGamesInitPromise) await this.crazyGamesInitPromise;
2073
+ this.crazyGamesService.gameplayStart();
2074
+ }
2150
2075
  }
2151
2076
  /**
2152
- * Check if ads are ready to show
2153
- * @returns Boolean indicating if ads are ready
2077
+ * Notifies CrazyGames that gameplay has stopped.
2078
+ * No-op on other platforms.
2154
2079
  */
2155
- areAdsReady() {
2156
- return this.adsService.isReady();
2080
+ async gameplayStop() {
2081
+ if (this.crazyGamesService) {
2082
+ if (this.crazyGamesInitPromise) await this.crazyGamesInitPromise;
2083
+ this.crazyGamesService.gameplayStop();
2084
+ }
2157
2085
  }
2158
2086
  /**
2159
- * Configure billing service
2160
- * @param config Billing configuration
2087
+ * Triggers a celebration effect on the CrazyGames website for significant achievements.
2088
+ * No-op on other platforms.
2161
2089
  */
2162
- configureBilling(config) {
2163
- this.billingConfig = {
2164
- ...this.billingConfig,
2165
- ...config
2166
- };
2167
- logger.info("Billing configuration updated");
2090
+ async happytime() {
2091
+ if (this.crazyGamesService) {
2092
+ if (this.crazyGamesInitPromise) await this.crazyGamesInitPromise;
2093
+ this.crazyGamesService.happytime();
2094
+ }
2168
2095
  }
2169
2096
  /**
2170
- * Initialize billing service
2171
- * Must be called before using billing features
2172
- * @returns Promise resolving to boolean indicating success
2097
+ * Check if ads are ready to show
2098
+ * @returns Boolean indicating if ads have initialized successfully
2173
2099
  */
2174
- async initializeBilling() {
2175
- const mergedConfig = {
2176
- ...this.billingConfig
2177
- };
2178
- if (!mergedConfig.userId && this.userId) {
2179
- mergedConfig.userId = this.userId;
2180
- }
2181
- if (!mergedConfig.gameId && this.gameId) {
2182
- mergedConfig.gameId = Number(this.gameId);
2183
- }
2184
- if (!mergedConfig.checkoutUrl) {
2185
- mergedConfig.checkoutUrl = this.apiBaseUrl;
2186
- }
2187
- this.billingService = new BillingService(mergedConfig);
2188
- if (this.billingCallbacks.onComplete) {
2189
- this.billingService.onPurchaseComplete(this.billingCallbacks.onComplete);
2190
- }
2191
- if (this.billingCallbacks.onError) {
2192
- this.billingService.onPurchaseError(this.billingCallbacks.onError);
2193
- }
2194
- if (this.billingCallbacks.onLog) {
2195
- this.billingService.onLog(this.billingCallbacks.onLog);
2196
- }
2197
- return await this.billingService.initialize();
2100
+ areAdsReady() {
2101
+ return this.adsService.isReady();
2198
2102
  }
2199
2103
  /**
2200
2104
  * Get the billing platform
@@ -2231,7 +2135,6 @@ var HyveClient = class {
2231
2135
  * @param callback Function to call on purchase completion
2232
2136
  */
2233
2137
  onPurchaseComplete(callback) {
2234
- this.billingCallbacks.onComplete = callback;
2235
2138
  this.billingService.onPurchaseComplete(callback);
2236
2139
  }
2237
2140
  /**
@@ -2239,7 +2142,6 @@ var HyveClient = class {
2239
2142
  * @param callback Function to call on purchase error
2240
2143
  */
2241
2144
  onPurchaseError(callback) {
2242
- this.billingCallbacks.onError = callback;
2243
2145
  this.billingService.onPurchaseError(callback);
2244
2146
  }
2245
2147
  /**
@@ -2248,20 +2150,13 @@ var HyveClient = class {
2248
2150
  unmountBillingCheckout() {
2249
2151
  this.billingService.unmountCheckoutElement();
2250
2152
  }
2251
- /**
2252
- * Register a callback to receive billing logs
2253
- * @param callback Function to call with log messages
2254
- */
2255
- onBillingLog(callback) {
2256
- this.billingCallbacks.onLog = callback;
2257
- this.billingService.onLog(callback);
2258
- }
2259
2153
  };
2260
2154
  export {
2261
2155
  AdsService,
2262
2156
  BillingPlatform,
2263
2157
  BillingService,
2264
2158
  CloudStorageAdapter,
2159
+ CrazyGamesService,
2265
2160
  HyveClient,
2266
2161
  LocalStorageAdapter,
2267
2162
  Logger,
@@ -2269,11 +2164,7 @@ export {
2269
2164
  NativeMessageType,
2270
2165
  PlaygamaService,
2271
2166
  generateUUID,
2272
- handleVerifyMessage,
2273
2167
  isDomainAllowed,
2274
2168
  logger,
2275
- parseUrlParams,
2276
- validateSignature,
2277
- verifyAuthentication,
2278
- verifyHyveToken
2169
+ parseUrlParams
2279
2170
  };