@hyve-sdk/js 1.5.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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,81 +498,48 @@ 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
- * Check if ads are enabled
528
+ * Returns the configured ad lifecycle callbacks.
529
+ * Used by platform-specific providers (e.g. Playgama) to fire the same hooks.
748
530
  */
749
- isEnabled() {
750
- return this.config.enabled;
531
+ getCallbacks() {
532
+ return {
533
+ onBeforeAd: this.config.onBeforeAd,
534
+ onAfterAd: this.config.onAfterAd,
535
+ onRewardEarned: this.config.onRewardEarned
536
+ };
751
537
  }
752
538
  /**
753
- * Check if ads are ready to show
539
+ * Check if ads have successfully initialized and are ready to show.
754
540
  */
755
541
  isReady() {
756
- return this.config.enabled && this.ready;
542
+ return this.ready;
757
543
  }
758
544
  };
759
545
 
@@ -800,16 +586,17 @@ var PlaygamaService = class {
800
586
  this.initialized = true;
801
587
  return true;
802
588
  } catch (error) {
803
- console.warn("[PlaygamaService] Failed to initialize:", error);
589
+ logger.warn("[PlaygamaService] Failed to initialize:", error);
804
590
  return false;
805
591
  }
806
592
  }
807
593
  isInitialized() {
808
594
  return this.initialized;
809
595
  }
810
- async showInterstitial() {
596
+ async showInterstitial(callbacks) {
811
597
  const requestedAt = Date.now();
812
- if (!this.initialized || !window.bridge) {
598
+ const bridge = window.bridge;
599
+ if (!this.initialized || !bridge) {
813
600
  return {
814
601
  success: false,
815
602
  type: "interstitial",
@@ -818,13 +605,26 @@ var PlaygamaService = class {
818
605
  completedAt: Date.now()
819
606
  };
820
607
  }
608
+ if (!bridge.advertisement.isInterstitialSupported) {
609
+ return {
610
+ success: false,
611
+ type: "interstitial",
612
+ error: new Error("Interstitial ads not supported on this platform"),
613
+ requestedAt,
614
+ completedAt: Date.now()
615
+ };
616
+ }
821
617
  return new Promise((resolve) => {
822
618
  const handler = (state) => {
823
- if (state === "closed") {
824
- window.bridge.advertisement.off("interstitial_state_changed", handler);
619
+ if (state === "opened") {
620
+ callbacks?.onBeforeAd?.();
621
+ } else if (state === "closed") {
622
+ bridge.advertisement.off(bridge.EVENT_NAME.INTERSTITIAL_STATE_CHANGED, handler);
623
+ callbacks?.onAfterAd?.();
825
624
  resolve({ success: true, type: "interstitial", requestedAt, completedAt: Date.now() });
826
625
  } else if (state === "failed") {
827
- window.bridge.advertisement.off("interstitial_state_changed", handler);
626
+ bridge.advertisement.off(bridge.EVENT_NAME.INTERSTITIAL_STATE_CHANGED, handler);
627
+ callbacks?.onAfterAd?.();
828
628
  resolve({
829
629
  success: false,
830
630
  type: "interstitial",
@@ -834,13 +634,14 @@ var PlaygamaService = class {
834
634
  });
835
635
  }
836
636
  };
837
- window.bridge.advertisement.on("interstitial_state_changed", handler);
838
- window.bridge.advertisement.showInterstitial();
637
+ bridge.advertisement.on(bridge.EVENT_NAME.INTERSTITIAL_STATE_CHANGED, handler);
638
+ bridge.advertisement.showInterstitial();
839
639
  });
840
640
  }
841
- async showRewarded() {
641
+ async showRewarded(callbacks) {
842
642
  const requestedAt = Date.now();
843
- if (!this.initialized || !window.bridge) {
643
+ const bridge = window.bridge;
644
+ if (!this.initialized || !bridge) {
844
645
  return {
845
646
  success: false,
846
647
  type: "rewarded",
@@ -849,13 +650,26 @@ var PlaygamaService = class {
849
650
  completedAt: Date.now()
850
651
  };
851
652
  }
653
+ if (!bridge.advertisement.isRewardedSupported) {
654
+ return {
655
+ success: false,
656
+ type: "rewarded",
657
+ error: new Error("Rewarded ads not supported on this platform"),
658
+ requestedAt,
659
+ completedAt: Date.now()
660
+ };
661
+ }
852
662
  return new Promise((resolve) => {
853
663
  let rewarded = false;
854
664
  const handler = (state) => {
855
- if (state === "rewarded") {
665
+ if (state === "opened") {
666
+ callbacks?.onBeforeAd?.();
667
+ } else if (state === "rewarded") {
856
668
  rewarded = true;
669
+ callbacks?.onRewardEarned?.();
857
670
  } else if (state === "closed" || state === "failed") {
858
- window.bridge.advertisement.off("rewarded_state_changed", handler);
671
+ bridge.advertisement.off(bridge.EVENT_NAME.REWARDED_STATE_CHANGED, handler);
672
+ callbacks?.onAfterAd?.();
859
673
  resolve({
860
674
  success: rewarded,
861
675
  type: "rewarded",
@@ -865,8 +679,8 @@ var PlaygamaService = class {
865
679
  });
866
680
  }
867
681
  };
868
- window.bridge.advertisement.on("rewarded_state_changed", handler);
869
- window.bridge.advertisement.showRewarded();
682
+ bridge.advertisement.on(bridge.EVENT_NAME.REWARDED_STATE_CHANGED, handler);
683
+ bridge.advertisement.showRewarded();
870
684
  });
871
685
  }
872
686
  loadScript() {
@@ -884,6 +698,176 @@ var PlaygamaService = class {
884
698
  }
885
699
  };
886
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
+
887
871
  // src/services/billing.ts
888
872
  var BillingPlatform = /* @__PURE__ */ ((BillingPlatform3) => {
889
873
  BillingPlatform3["WEB"] = "web";
@@ -894,41 +878,28 @@ var BillingPlatform = /* @__PURE__ */ ((BillingPlatform3) => {
894
878
  var BillingService = class {
895
879
  config;
896
880
  platform = "unknown" /* UNKNOWN */;
897
- isInitialized = false;
881
+ initPromise = null;
898
882
  nativeAvailable = false;
899
- products = [];
900
883
  // Stripe instance for web payments
901
884
  stripe = null;
902
885
  checkoutElement = null;
903
886
  // Callbacks for purchase events
904
887
  onPurchaseCompleteCallback;
905
888
  onPurchaseErrorCallback;
906
- // Callback for logging (so external UI can capture logs)
907
- onLogCallback;
908
889
  constructor(config) {
909
890
  this.config = config;
910
891
  this.detectPlatform();
911
892
  }
912
893
  /**
913
- * Register a callback to receive all internal logs
914
- * Useful for displaying logs in a UI
915
- */
916
- onLog(callback) {
917
- this.onLogCallback = callback;
918
- }
919
- /**
920
- * Internal logging method that calls both logger and custom callback
894
+ * Update billing configuration. Resets initialization so next call re-inits with new config.
921
895
  */
922
- log(level, message, data) {
923
- const consoleMethod = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
924
- if (data !== void 0) {
925
- consoleMethod(message, data);
926
- } else {
927
- consoleMethod(message);
928
- }
929
- if (this.onLogCallback) {
930
- this.onLogCallback(level, message, data);
931
- }
896
+ configure(config) {
897
+ this.config = {
898
+ ...this.config,
899
+ ...config
900
+ };
901
+ this.initPromise = null;
902
+ logger.info("[BillingService] Configuration updated");
932
903
  }
933
904
  /**
934
905
  * Detects if running on web or native platform
@@ -938,13 +909,13 @@ var BillingService = class {
938
909
  const hasWindow = typeof window !== "undefined";
939
910
  if (isNative) {
940
911
  this.platform = "native" /* NATIVE */;
941
- this.log("info", "[BillingService] Platform: NATIVE");
912
+ logger.info("[BillingService] Platform: NATIVE");
942
913
  } else if (hasWindow) {
943
914
  this.platform = "web" /* WEB */;
944
- this.log("info", "[BillingService] Platform: WEB");
915
+ logger.info("[BillingService] Platform: WEB");
945
916
  } else {
946
917
  this.platform = "unknown" /* UNKNOWN */;
947
- this.log("warn", "[BillingService] Platform: UNKNOWN");
918
+ logger.warn("[BillingService] Platform: UNKNOWN");
948
919
  }
949
920
  }
950
921
  /**
@@ -954,38 +925,40 @@ var BillingService = class {
954
925
  return this.platform;
955
926
  }
956
927
  /**
957
- * Initialize the billing service
958
- * 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().
959
930
  */
960
- async initialize() {
961
- if (this.isInitialized) {
962
- return true;
963
- }
964
- this.log("info", `[BillingService] Initializing for ${this.platform} platform...`);
965
- try {
966
- if (this.platform === "native" /* NATIVE */) {
967
- return await this.initializeNative();
968
- } else if (this.platform === "web" /* WEB */) {
969
- 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;
970
946
  }
971
- this.log("error", "[BillingService] Cannot initialize: unknown platform");
972
- return false;
973
- } catch (error) {
974
- this.log("error", "[BillingService] Initialization failed:", error?.message);
975
- return false;
976
- }
947
+ })();
948
+ return this.initPromise;
977
949
  }
978
950
  /**
979
951
  * Initialize native billing
980
952
  */
981
953
  async initializeNative() {
982
954
  return new Promise((resolve) => {
955
+ let resolved = false;
983
956
  try {
984
957
  NativeBridge.initialize();
985
958
  NativeBridge.on(
986
959
  "PURCHASE_COMPLETE" /* PURCHASE_COMPLETE */,
987
960
  (payload) => {
988
- this.log("info", "[BillingService] Purchase complete:", payload.productId);
961
+ logger.info("[BillingService] Purchase complete:", payload.productId);
989
962
  const result = {
990
963
  success: true,
991
964
  productId: payload.productId,
@@ -1000,7 +973,7 @@ var BillingService = class {
1000
973
  NativeBridge.on(
1001
974
  "PURCHASE_ERROR" /* PURCHASE_ERROR */,
1002
975
  (payload) => {
1003
- this.log("error", "[BillingService] Purchase error:", payload.message);
976
+ logger.error("[BillingService] Purchase error:", payload.message);
1004
977
  const result = {
1005
978
  success: false,
1006
979
  productId: payload.productId || "unknown",
@@ -1018,8 +991,8 @@ var BillingService = class {
1018
991
  "IAP_AVAILABILITY_RESULT" /* IAP_AVAILABILITY_RESULT */,
1019
992
  (payload) => {
1020
993
  this.nativeAvailable = payload.available;
1021
- this.isInitialized = true;
1022
- this.log("info", `[BillingService] Native billing ${payload.available ? "available" : "unavailable"}`);
994
+ logger.info(`[BillingService] Native billing ${payload.available ? "available" : "unavailable"}`);
995
+ resolved = true;
1023
996
  resolve(payload.available);
1024
997
  }
1025
998
  );
@@ -1027,15 +1000,15 @@ var BillingService = class {
1027
1000
  NativeBridge.checkIAPAvailability();
1028
1001
  }, 100);
1029
1002
  setTimeout(() => {
1030
- if (!this.isInitialized) {
1031
- this.log("warn", "[BillingService] Native initialization timeout");
1032
- this.isInitialized = true;
1003
+ if (!resolved) {
1004
+ logger.warn("[BillingService] Native initialization timeout");
1005
+ resolved = true;
1033
1006
  resolve(false);
1034
1007
  }
1035
1008
  }, 5e3);
1036
1009
  } catch (error) {
1037
- this.log("error", "[BillingService] Native initialization failed:", error?.message);
1038
- this.isInitialized = true;
1010
+ logger.error("[BillingService] Native initialization failed:", error?.message);
1011
+ resolved = true;
1039
1012
  resolve(false);
1040
1013
  }
1041
1014
  });
@@ -1045,12 +1018,12 @@ var BillingService = class {
1045
1018
  */
1046
1019
  async initializeWeb() {
1047
1020
  if (!this.config.stripePublishableKey) {
1048
- this.log("error", "[BillingService] Stripe publishable key not provided");
1021
+ logger.error("[BillingService] Stripe publishable key not provided");
1049
1022
  return false;
1050
1023
  }
1051
1024
  try {
1052
1025
  if (typeof window === "undefined") {
1053
- this.log("error", "[BillingService] Window is undefined (not in browser)");
1026
+ logger.error("[BillingService] Window is undefined (not in browser)");
1054
1027
  return false;
1055
1028
  }
1056
1029
  if (!window.Stripe) {
@@ -1058,15 +1031,14 @@ var BillingService = class {
1058
1031
  }
1059
1032
  if (window.Stripe) {
1060
1033
  this.stripe = window.Stripe(this.config.stripePublishableKey);
1061
- this.isInitialized = true;
1062
- this.log("info", "[BillingService] Web billing initialized");
1034
+ logger.info("[BillingService] Web billing initialized");
1063
1035
  return true;
1064
1036
  } else {
1065
- this.log("error", "[BillingService] Stripe not available after loading");
1037
+ logger.error("[BillingService] Stripe not available after loading");
1066
1038
  return false;
1067
1039
  }
1068
1040
  } catch (error) {
1069
- this.log("error", "[BillingService] Stripe initialization failed:", error?.message);
1041
+ logger.error("[BillingService] Stripe initialization failed:", error?.message);
1070
1042
  return false;
1071
1043
  }
1072
1044
  }
@@ -1091,11 +1063,11 @@ var BillingService = class {
1091
1063
  script.src = "https://js.stripe.com/v3/";
1092
1064
  script.async = true;
1093
1065
  script.onload = () => {
1094
- this.log("info", "[BillingService] Stripe.js loaded");
1066
+ logger.info("[BillingService] Stripe.js loaded");
1095
1067
  resolve();
1096
1068
  };
1097
1069
  script.onerror = () => {
1098
- this.log("error", "[BillingService] Failed to load Stripe.js");
1070
+ logger.error("[BillingService] Failed to load Stripe.js");
1099
1071
  reject(new Error("Failed to load Stripe.js"));
1100
1072
  };
1101
1073
  try {
@@ -1106,12 +1078,9 @@ var BillingService = class {
1106
1078
  });
1107
1079
  }
1108
1080
  /**
1109
- * Check if billing is available
1081
+ * Check if billing is available based on current config and platform state
1110
1082
  */
1111
1083
  isAvailable() {
1112
- if (!this.isInitialized) {
1113
- return false;
1114
- }
1115
1084
  if (this.platform === "native" /* NATIVE */) {
1116
1085
  return this.nativeAvailable;
1117
1086
  } else if (this.platform === "web" /* WEB */) {
@@ -1120,12 +1089,10 @@ var BillingService = class {
1120
1089
  return false;
1121
1090
  }
1122
1091
  /**
1123
- * Get available products
1092
+ * Get available products. Auto-initializes on first call.
1124
1093
  */
1125
1094
  async getProducts() {
1126
- if (!this.isInitialized) {
1127
- throw new Error("BillingService not initialized. Call initialize() first.");
1128
- }
1095
+ await this.initialize();
1129
1096
  if (this.platform === "native" /* NATIVE */) {
1130
1097
  return await this.getProductsNative();
1131
1098
  } else if (this.platform === "web" /* WEB */) {
@@ -1137,38 +1104,31 @@ var BillingService = class {
1137
1104
  * Get products from native IAP
1138
1105
  */
1139
1106
  async getProductsNative() {
1140
- return new Promise((resolve, reject) => {
1141
- if (!this.config.gameId || !this.config.checkoutUrl) {
1142
- const error = new Error("gameId and checkoutUrl required for native purchases");
1143
- this.log("error", "[BillingService]", error.message);
1144
- reject(error);
1145
- return;
1146
- }
1147
- const url = `${this.config.checkoutUrl}/get-native-packages?game_id=${this.config.gameId}`;
1148
- fetch(url).then((response) => {
1149
- if (!response.ok) {
1150
- throw new Error(`Failed to fetch native products: ${response.status}`);
1151
- }
1152
- return response.json();
1153
- }).then((data) => {
1154
- if (!data.packages || !Array.isArray(data.packages)) {
1155
- throw new Error("Invalid response format: missing packages array");
1156
- }
1157
- this.products = data.packages.map((pkg) => ({
1158
- productId: pkg.productId,
1159
- title: pkg.package_name,
1160
- description: `${pkg.game_name} - ${pkg.package_name}`,
1161
- price: pkg.price_cents / 100,
1162
- localizedPrice: pkg.price_display,
1163
- currency: "USD"
1164
- }));
1165
- this.log("info", `[BillingService] Fetched ${this.products.length} native products`);
1166
- resolve(this.products);
1167
- }).catch((error) => {
1168
- this.log("error", "[BillingService] Failed to fetch native products:", error?.message);
1169
- reject(error);
1170
- });
1171
- });
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;
1172
1132
  }
1173
1133
  /**
1174
1134
  * Get products from web API (Stripe)
@@ -1176,7 +1136,7 @@ var BillingService = class {
1176
1136
  async getProductsWeb() {
1177
1137
  if (!this.config.checkoutUrl || !this.config.gameId) {
1178
1138
  const error = new Error("checkoutUrl and gameId required for web purchases");
1179
- this.log("error", "[BillingService]", error.message);
1139
+ logger.error("[BillingService]", error.message);
1180
1140
  throw error;
1181
1141
  }
1182
1142
  try {
@@ -1189,7 +1149,7 @@ var BillingService = class {
1189
1149
  if (!data.packages || !Array.isArray(data.packages)) {
1190
1150
  throw new Error("Invalid response format: missing packages array");
1191
1151
  }
1192
- this.products = data.packages.map((pkg) => ({
1152
+ const products = data.packages.map((pkg) => ({
1193
1153
  productId: pkg.priceId || pkg.productId,
1194
1154
  // Prefer priceId for Stripe
1195
1155
  title: pkg.package_name,
@@ -1198,23 +1158,21 @@ var BillingService = class {
1198
1158
  localizedPrice: pkg.price_display,
1199
1159
  currency: "USD"
1200
1160
  }));
1201
- this.log("info", `[BillingService] Fetched ${this.products.length} web products`);
1202
- return this.products;
1161
+ logger.info(`[BillingService] Fetched ${products.length} web products`);
1162
+ return products;
1203
1163
  } catch (error) {
1204
- this.log("error", "[BillingService] Failed to fetch web products:", error?.message);
1164
+ logger.error("[BillingService] Failed to fetch web products:", error?.message);
1205
1165
  throw error;
1206
1166
  }
1207
1167
  }
1208
1168
  /**
1209
- * Purchase a product
1169
+ * Purchase a product. Auto-initializes on first call.
1210
1170
  * @param productId - The product ID (priceId for web/Stripe, productId for native)
1211
1171
  * @param options - Optional purchase options
1212
1172
  * @param options.elementId - For web: DOM element ID to mount Stripe checkout (default: 'stripe-checkout-element')
1213
1173
  */
1214
1174
  async purchase(productId, options) {
1215
- if (!this.isInitialized) {
1216
- throw new Error("BillingService not initialized. Call initialize() first.");
1217
- }
1175
+ await this.initialize();
1218
1176
  if (!this.isAvailable()) {
1219
1177
  throw new Error("Billing is not available on this platform");
1220
1178
  }
@@ -1234,7 +1192,7 @@ var BillingService = class {
1234
1192
  reject(new Error("userId is required for native purchases"));
1235
1193
  return;
1236
1194
  }
1237
- this.log("info", `[BillingService] Purchasing: ${productId}`);
1195
+ logger.info(`[BillingService] Purchasing: ${productId}`);
1238
1196
  const previousCompleteCallback = this.onPurchaseCompleteCallback;
1239
1197
  const previousErrorCallback = this.onPurchaseErrorCallback;
1240
1198
  const cleanup = () => {
@@ -1308,14 +1266,14 @@ var BillingService = class {
1308
1266
  throw new Error("No client_secret returned from checkout session");
1309
1267
  }
1310
1268
  await this.mountCheckoutElement(client_secret, elementId || "stripe-checkout-element");
1311
- this.log("info", `[BillingService] Checkout session created: ${id}`);
1269
+ logger.info(`[BillingService] Checkout session created: ${id}`);
1312
1270
  return {
1313
1271
  success: true,
1314
1272
  productId,
1315
1273
  transactionId: id
1316
1274
  };
1317
1275
  } catch (error) {
1318
- this.log("error", "[BillingService] Web purchase failed:", error?.message);
1276
+ logger.error("[BillingService] Web purchase failed:", error?.message);
1319
1277
  return {
1320
1278
  success: false,
1321
1279
  productId,
@@ -1337,7 +1295,7 @@ var BillingService = class {
1337
1295
  }
1338
1296
  try {
1339
1297
  if (this.checkoutElement) {
1340
- this.log("info", "[BillingService] Unmounting existing checkout element");
1298
+ logger.info("[BillingService] Unmounting existing checkout element");
1341
1299
  this.unmountCheckoutElement();
1342
1300
  await new Promise((resolve) => setTimeout(resolve, 100));
1343
1301
  }
@@ -1346,15 +1304,15 @@ var BillingService = class {
1346
1304
  throw new Error(`Element with id "${elementId}" not found in the DOM`);
1347
1305
  }
1348
1306
  container.innerHTML = "";
1349
- this.log("info", "[BillingService] Creating new checkout instance");
1307
+ logger.info("[BillingService] Creating new checkout instance");
1350
1308
  this.checkoutElement = await this.stripe.initEmbeddedCheckout({
1351
1309
  clientSecret
1352
1310
  });
1353
- this.log("info", "[BillingService] Mounting checkout element to DOM");
1311
+ logger.info("[BillingService] Mounting checkout element to DOM");
1354
1312
  this.checkoutElement.mount(`#${elementId}`);
1355
1313
  this.setupCheckoutEventListeners();
1356
1314
  } catch (error) {
1357
- this.log("error", "[BillingService] Failed to mount checkout:", error?.message);
1315
+ logger.error("[BillingService] Failed to mount checkout:", error?.message);
1358
1316
  throw error;
1359
1317
  }
1360
1318
  }
@@ -1375,7 +1333,7 @@ var BillingService = class {
1375
1333
  transactionId: sessionId || void 0
1376
1334
  });
1377
1335
  }
1378
- this.log("info", "[BillingService] Payment completed");
1336
+ logger.info("[BillingService] Payment completed");
1379
1337
  urlParams.delete("payment");
1380
1338
  urlParams.delete("session_id");
1381
1339
  const newUrl = `${window.location.pathname}${urlParams.toString() ? "?" + urlParams.toString() : ""}`;
@@ -1394,7 +1352,7 @@ var BillingService = class {
1394
1352
  this.checkoutElement.destroy();
1395
1353
  }
1396
1354
  } catch (error) {
1397
- this.log("warn", "[BillingService] Error unmounting checkout element:", error?.message);
1355
+ logger.warn("[BillingService] Error unmounting checkout element:", error?.message);
1398
1356
  }
1399
1357
  this.checkoutElement = null;
1400
1358
  }
@@ -1422,9 +1380,8 @@ var BillingService = class {
1422
1380
  NativeBridge.off("PURCHASE_ERROR" /* PURCHASE_ERROR */);
1423
1381
  }
1424
1382
  this.unmountCheckoutElement();
1425
- this.isInitialized = false;
1383
+ this.initPromise = null;
1426
1384
  this.nativeAvailable = false;
1427
- this.products = [];
1428
1385
  this.stripe = null;
1429
1386
  this.onPurchaseCompleteCallback = void 0;
1430
1387
  this.onPurchaseErrorCallback = void 0;
@@ -1596,10 +1553,10 @@ var HyveClient = class {
1596
1553
  gameId = null;
1597
1554
  adsService;
1598
1555
  playgamaService = null;
1556
+ playgamaInitPromise = null;
1557
+ crazyGamesService = null;
1558
+ crazyGamesInitPromise = null;
1599
1559
  billingService;
1600
- billingConfig;
1601
- // Store callbacks to preserve them when recreating BillingService
1602
- billingCallbacks = {};
1603
1560
  storageMode;
1604
1561
  cloudStorageAdapter;
1605
1562
  localStorageAdapter;
@@ -1625,17 +1582,33 @@ var HyveClient = class {
1625
1582
  }
1626
1583
  if (typeof window !== "undefined" && PlaygamaService.isPlaygamaDomain()) {
1627
1584
  this.playgamaService = new PlaygamaService();
1628
- this.playgamaService.initialize().then((success) => {
1585
+ this.playgamaInitPromise = this.playgamaService.initialize().then((success) => {
1629
1586
  logger.info("Playgama Bridge initialized:", success);
1587
+ return success;
1588
+ });
1589
+ }
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;
1630
1595
  });
1631
1596
  }
1632
- this.billingConfig = config?.billing || {};
1633
- this.billingService = new BillingService(this.billingConfig);
1634
1597
  this.storageMode = config?.storageMode || "cloud";
1635
1598
  this.cloudStorageAdapter = new CloudStorageAdapter(
1636
1599
  (endpoint, options) => this.callApi(endpoint, options)
1637
1600
  );
1638
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);
1639
1612
  const envSource = config?.isDev !== void 0 ? "explicit config" : "auto-detected from parent URL";
1640
1613
  logger.info("==========================================");
1641
1614
  logger.info("HyveClient Initialized");
@@ -1647,28 +1620,27 @@ var HyveClient = class {
1647
1620
  `(${envSource})`
1648
1621
  );
1649
1622
  logger.info("API Base URL:", this.apiBaseUrl);
1650
- logger.info("Ads enabled:", this.adsService.isEnabled());
1651
1623
  logger.info("Playgama platform:", this.playgamaService !== null);
1624
+ logger.info("CrazyGames platform:", this.crazyGamesService !== null);
1652
1625
  logger.info(
1653
1626
  "Billing configured:",
1654
- Object.keys(this.billingConfig).length > 0
1627
+ !!config?.billing && Object.keys(config.billing).length > 0
1655
1628
  );
1656
1629
  logger.info("Storage mode:", this.storageMode);
1630
+ logger.info("Authenticated:", this.jwtToken !== null);
1657
1631
  logger.debug("Config:", {
1658
1632
  isDev: this.telemetryConfig.isDev,
1659
1633
  hasCustomApiUrl: !!config?.apiBaseUrl,
1660
- adsEnabled: config?.ads?.enabled || false,
1661
- billingConfigured: Object.keys(this.billingConfig).length > 0,
1634
+ billingConfigured: !!config?.billing && Object.keys(config.billing).length > 0,
1662
1635
  storageMode: this.storageMode
1663
1636
  });
1664
1637
  logger.info("==========================================");
1665
1638
  }
1666
1639
  /**
1667
- * Authenticates a user from URL parameters
1668
- * @param urlParams URL parameters or search string
1669
- * @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.
1670
1642
  */
1671
- async authenticateFromUrl(urlParams) {
1643
+ _parseUrlAuth(urlParams) {
1672
1644
  try {
1673
1645
  const params = urlParams ? parseUrlParams(urlParams) : parseUrlParams(window.location.search);
1674
1646
  if (params.hyveAccess) {
@@ -1693,27 +1665,11 @@ var HyveClient = class {
1693
1665
  }
1694
1666
  if (this.jwtToken) {
1695
1667
  logger.info("Authentication successful via JWT");
1696
- return true;
1697
- }
1698
- const authResult = verifyAuthentication({
1699
- hyveToken: params.hyveToken,
1700
- signature: params.signature,
1701
- message: params.message
1702
- });
1703
- if (authResult.isValid && authResult.address) {
1704
- this.userId = authResult.address;
1705
- logger.info("Authentication successful:", authResult.address);
1706
- logger.info("Authentication method:", authResult.method);
1707
- return true;
1708
1668
  } else {
1709
- logger.error("Authentication failed:", authResult.error);
1710
- this.userId = null;
1711
- return false;
1669
+ logger.info("No hyve-access JWT token in URL \u2014 unauthenticated");
1712
1670
  }
1713
1671
  } catch (error) {
1714
- logger.error("Authentication error:", error);
1715
- this.userId = null;
1716
- return false;
1672
+ logger.error("Error parsing URL auth:", error);
1717
1673
  }
1718
1674
  }
1719
1675
  /**
@@ -1730,7 +1686,7 @@ var HyveClient = class {
1730
1686
  */
1731
1687
  async sendTelemetry(eventLocation, eventCategory, eventAction, eventSubCategory, eventSubAction, eventDetails, platformId) {
1732
1688
  if (!this.jwtToken) {
1733
- 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.");
1734
1690
  return false;
1735
1691
  }
1736
1692
  if (!this.gameId) {
@@ -1802,7 +1758,7 @@ var HyveClient = class {
1802
1758
  async callApi(endpoint, options = {}) {
1803
1759
  if (!this.jwtToken) {
1804
1760
  throw new Error(
1805
- "No JWT token available. Call authenticateFromUrl first."
1761
+ "No JWT token available. Ensure hyve-access and game-id are present in the URL."
1806
1762
  );
1807
1763
  }
1808
1764
  try {
@@ -1932,6 +1888,13 @@ var HyveClient = class {
1932
1888
  const storageMode = mode || this.storageMode;
1933
1889
  return storageMode === "local" ? this.localStorageAdapter : this.cloudStorageAdapter;
1934
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
+ }
1935
1898
  /**
1936
1899
  * Save persistent game data
1937
1900
  * @param key Data key
@@ -1940,10 +1903,7 @@ var HyveClient = class {
1940
1903
  * @returns Promise resolving to save response
1941
1904
  */
1942
1905
  async saveGameData(key, value, storage) {
1943
- const gameId = this.getGameId();
1944
- if (!gameId) {
1945
- throw new Error("game-id is required for persistent storage. Call authenticateFromUrl first.");
1946
- }
1906
+ const gameId = this.requireGameId();
1947
1907
  const storageMode = storage || this.storageMode;
1948
1908
  logger.debug(`Saving game data to ${storageMode}: ${gameId}/${key}`);
1949
1909
  const adapter = this.getStorageAdapter(storage);
@@ -1958,10 +1918,7 @@ var HyveClient = class {
1958
1918
  * @returns Promise resolving to save response
1959
1919
  */
1960
1920
  async batchSaveGameData(items, storage) {
1961
- const gameId = this.getGameId();
1962
- if (!gameId) {
1963
- throw new Error("game-id is required for persistent storage. Call authenticateFromUrl first.");
1964
- }
1921
+ const gameId = this.requireGameId();
1965
1922
  const storageMode = storage || this.storageMode;
1966
1923
  logger.debug(`Batch saving ${items.length} game data entries to ${storageMode} for game: ${gameId}`);
1967
1924
  const adapter = this.getStorageAdapter(storage);
@@ -1976,10 +1933,7 @@ var HyveClient = class {
1976
1933
  * @returns Promise resolving to game data item or null if not found
1977
1934
  */
1978
1935
  async getGameData(key, storage) {
1979
- const gameId = this.getGameId();
1980
- if (!gameId) {
1981
- throw new Error("game-id is required for persistent storage. Call authenticateFromUrl first.");
1982
- }
1936
+ const gameId = this.requireGameId();
1983
1937
  const storageMode = storage || this.storageMode;
1984
1938
  logger.debug(`Getting game data from ${storageMode}: ${gameId}/${key}`);
1985
1939
  const adapter = this.getStorageAdapter(storage);
@@ -1998,10 +1952,7 @@ var HyveClient = class {
1998
1952
  * @returns Promise resolving to array of game data items
1999
1953
  */
2000
1954
  async getMultipleGameData(keys, storage) {
2001
- const gameId = this.getGameId();
2002
- if (!gameId) {
2003
- throw new Error("game-id is required for persistent storage. Call authenticateFromUrl first.");
2004
- }
1955
+ const gameId = this.requireGameId();
2005
1956
  const storageMode = storage || this.storageMode;
2006
1957
  logger.debug(`Getting ${keys.length} game data entries from ${storageMode} for game: ${gameId}`);
2007
1958
  const adapter = this.getStorageAdapter(storage);
@@ -2016,10 +1967,7 @@ var HyveClient = class {
2016
1967
  * @returns Promise resolving to boolean indicating if data was deleted
2017
1968
  */
2018
1969
  async deleteGameData(key, storage) {
2019
- const gameId = this.getGameId();
2020
- if (!gameId) {
2021
- throw new Error("game-id is required for persistent storage. Call authenticateFromUrl first.");
2022
- }
1970
+ const gameId = this.requireGameId();
2023
1971
  const storageMode = storage || this.storageMode;
2024
1972
  logger.debug(`Deleting game data from ${storageMode}: ${gameId}/${key}`);
2025
1973
  const adapter = this.getStorageAdapter(storage);
@@ -2038,10 +1986,7 @@ var HyveClient = class {
2038
1986
  * @returns Promise resolving to number of entries deleted
2039
1987
  */
2040
1988
  async deleteMultipleGameData(keys, storage) {
2041
- const gameId = this.getGameId();
2042
- if (!gameId) {
2043
- throw new Error("game-id is required for persistent storage. Call authenticateFromUrl first.");
2044
- }
1989
+ const gameId = this.requireGameId();
2045
1990
  const storageMode = storage || this.storageMode;
2046
1991
  logger.debug(`Deleting ${keys.length} game data entries from ${storageMode} for game: ${gameId}`);
2047
1992
  const adapter = this.getStorageAdapter(storage);
@@ -2078,66 +2023,82 @@ var HyveClient = class {
2078
2023
  * @returns Promise resolving to ad result
2079
2024
  */
2080
2025
  async showAd(type) {
2081
- if (this.playgamaService?.isInitialized()) {
2082
- if (type === "rewarded") return this.playgamaService.showRewarded();
2083
- return this.playgamaService.showInterstitial();
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
+ }
2045
+ if (this.playgamaService) {
2046
+ if (this.playgamaInitPromise) {
2047
+ await this.playgamaInitPromise;
2048
+ }
2049
+ if (this.playgamaService.isInitialized()) {
2050
+ const { onBeforeAd, onAfterAd, onRewardEarned } = this.adsService.getCallbacks();
2051
+ if (type === "rewarded") {
2052
+ return this.playgamaService.showRewarded({
2053
+ onBeforeAd: () => onBeforeAd("rewarded"),
2054
+ onAfterAd: () => onAfterAd("rewarded"),
2055
+ onRewardEarned
2056
+ });
2057
+ }
2058
+ return this.playgamaService.showInterstitial({
2059
+ onBeforeAd: () => onBeforeAd(type),
2060
+ onAfterAd: () => onAfterAd(type)
2061
+ });
2062
+ }
2084
2063
  }
2085
2064
  return this.adsService.show(type);
2086
2065
  }
2087
2066
  /**
2088
- * Check if ads are enabled
2089
- * @returns Boolean indicating if ads are enabled
2067
+ * Notifies CrazyGames that gameplay has started.
2068
+ * No-op on other platforms.
2090
2069
  */
2091
- areAdsEnabled() {
2092
- return this.adsService.isEnabled();
2070
+ async gameplayStart() {
2071
+ if (this.crazyGamesService) {
2072
+ if (this.crazyGamesInitPromise) await this.crazyGamesInitPromise;
2073
+ this.crazyGamesService.gameplayStart();
2074
+ }
2093
2075
  }
2094
2076
  /**
2095
- * Check if ads are ready to show
2096
- * @returns Boolean indicating if ads are ready
2077
+ * Notifies CrazyGames that gameplay has stopped.
2078
+ * No-op on other platforms.
2097
2079
  */
2098
- areAdsReady() {
2099
- return this.adsService.isReady();
2080
+ async gameplayStop() {
2081
+ if (this.crazyGamesService) {
2082
+ if (this.crazyGamesInitPromise) await this.crazyGamesInitPromise;
2083
+ this.crazyGamesService.gameplayStop();
2084
+ }
2100
2085
  }
2101
2086
  /**
2102
- * Configure billing service
2103
- * @param config Billing configuration
2087
+ * Triggers a celebration effect on the CrazyGames website for significant achievements.
2088
+ * No-op on other platforms.
2104
2089
  */
2105
- configureBilling(config) {
2106
- this.billingConfig = {
2107
- ...this.billingConfig,
2108
- ...config
2109
- };
2110
- logger.info("Billing configuration updated");
2090
+ async happytime() {
2091
+ if (this.crazyGamesService) {
2092
+ if (this.crazyGamesInitPromise) await this.crazyGamesInitPromise;
2093
+ this.crazyGamesService.happytime();
2094
+ }
2111
2095
  }
2112
2096
  /**
2113
- * Initialize billing service
2114
- * Must be called before using billing features
2115
- * @returns Promise resolving to boolean indicating success
2097
+ * Check if ads are ready to show
2098
+ * @returns Boolean indicating if ads have initialized successfully
2116
2099
  */
2117
- async initializeBilling() {
2118
- const mergedConfig = {
2119
- ...this.billingConfig
2120
- };
2121
- if (!mergedConfig.userId && this.userId) {
2122
- mergedConfig.userId = this.userId;
2123
- }
2124
- if (!mergedConfig.gameId && this.gameId) {
2125
- mergedConfig.gameId = Number(this.gameId);
2126
- }
2127
- if (!mergedConfig.checkoutUrl) {
2128
- mergedConfig.checkoutUrl = this.apiBaseUrl;
2129
- }
2130
- this.billingService = new BillingService(mergedConfig);
2131
- if (this.billingCallbacks.onComplete) {
2132
- this.billingService.onPurchaseComplete(this.billingCallbacks.onComplete);
2133
- }
2134
- if (this.billingCallbacks.onError) {
2135
- this.billingService.onPurchaseError(this.billingCallbacks.onError);
2136
- }
2137
- if (this.billingCallbacks.onLog) {
2138
- this.billingService.onLog(this.billingCallbacks.onLog);
2139
- }
2140
- return await this.billingService.initialize();
2100
+ areAdsReady() {
2101
+ return this.adsService.isReady();
2141
2102
  }
2142
2103
  /**
2143
2104
  * Get the billing platform
@@ -2174,7 +2135,6 @@ var HyveClient = class {
2174
2135
  * @param callback Function to call on purchase completion
2175
2136
  */
2176
2137
  onPurchaseComplete(callback) {
2177
- this.billingCallbacks.onComplete = callback;
2178
2138
  this.billingService.onPurchaseComplete(callback);
2179
2139
  }
2180
2140
  /**
@@ -2182,7 +2142,6 @@ var HyveClient = class {
2182
2142
  * @param callback Function to call on purchase error
2183
2143
  */
2184
2144
  onPurchaseError(callback) {
2185
- this.billingCallbacks.onError = callback;
2186
2145
  this.billingService.onPurchaseError(callback);
2187
2146
  }
2188
2147
  /**
@@ -2191,20 +2150,13 @@ var HyveClient = class {
2191
2150
  unmountBillingCheckout() {
2192
2151
  this.billingService.unmountCheckoutElement();
2193
2152
  }
2194
- /**
2195
- * Register a callback to receive billing logs
2196
- * @param callback Function to call with log messages
2197
- */
2198
- onBillingLog(callback) {
2199
- this.billingCallbacks.onLog = callback;
2200
- this.billingService.onLog(callback);
2201
- }
2202
2153
  };
2203
2154
  export {
2204
2155
  AdsService,
2205
2156
  BillingPlatform,
2206
2157
  BillingService,
2207
2158
  CloudStorageAdapter,
2159
+ CrazyGamesService,
2208
2160
  HyveClient,
2209
2161
  LocalStorageAdapter,
2210
2162
  Logger,
@@ -2212,11 +2164,7 @@ export {
2212
2164
  NativeMessageType,
2213
2165
  PlaygamaService,
2214
2166
  generateUUID,
2215
- handleVerifyMessage,
2216
2167
  isDomainAllowed,
2217
2168
  logger,
2218
- parseUrlParams,
2219
- validateSignature,
2220
- verifyAuthentication,
2221
- verifyHyveToken
2169
+ parseUrlParams
2222
2170
  };