@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/react.js ADDED
@@ -0,0 +1,2173 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/react.tsx
21
+ var react_exports = {};
22
+ __export(react_exports, {
23
+ HyveSdkProvider: () => HyveSdkProvider,
24
+ useHyveSdk: () => useHyveSdk
25
+ });
26
+ module.exports = __toCommonJS(react_exports);
27
+ var import_react = require("react");
28
+
29
+ // src/utils/index.ts
30
+ var import_uuid = require("uuid");
31
+
32
+ // src/utils/logger.ts
33
+ var Logger = class _Logger {
34
+ config;
35
+ constructor() {
36
+ this.config = this.initializeConfig();
37
+ }
38
+ /**
39
+ * Initialize logger configuration based on NODE_ENV
40
+ */
41
+ initializeConfig() {
42
+ const isNode = typeof process !== "undefined" && process.versions != null && process.versions.node != null;
43
+ let enabled = false;
44
+ let levels = /* @__PURE__ */ new Set(["debug", "info", "warn", "error"]);
45
+ if (isNode) {
46
+ const nodeEnv = process.env.NODE_ENV;
47
+ enabled = nodeEnv !== "production";
48
+ const logLevelEnv = process.env.HYVE_SDK_LOG_LEVEL;
49
+ if (logLevelEnv) {
50
+ const configuredLevels = logLevelEnv.split(",").map((l) => l.trim());
51
+ levels = new Set(configuredLevels);
52
+ }
53
+ } else if (typeof window !== "undefined") {
54
+ try {
55
+ enabled = process?.env.NODE_ENV !== "production";
56
+ } catch (e) {
57
+ enabled = true;
58
+ }
59
+ try {
60
+ const localStorageLogLevel = localStorage.getItem("HYVE_SDK_LOG_LEVEL");
61
+ if (localStorageLogLevel) {
62
+ const configuredLevels = localStorageLogLevel.split(",").map((l) => l.trim());
63
+ levels = new Set(configuredLevels);
64
+ }
65
+ } catch (e) {
66
+ }
67
+ }
68
+ return {
69
+ enabled,
70
+ prefix: "[Hyve SDK]",
71
+ levels
72
+ };
73
+ }
74
+ /**
75
+ * Set which log levels to display
76
+ */
77
+ setLevels(levels) {
78
+ this.config.levels = new Set(levels);
79
+ if (typeof window !== "undefined" && typeof localStorage !== "undefined") {
80
+ try {
81
+ localStorage.setItem("HYVE_SDK_LOG_LEVEL", levels.join(","));
82
+ } catch (e) {
83
+ }
84
+ }
85
+ }
86
+ /**
87
+ * Check if logging is enabled
88
+ */
89
+ isEnabled() {
90
+ return this.config.enabled;
91
+ }
92
+ /**
93
+ * Internal log method
94
+ */
95
+ log(level, ...args) {
96
+ if (!this.config.enabled || !this.config.levels.has(level)) {
97
+ return;
98
+ }
99
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
100
+ const prefix = `${this.config.prefix} [${level.toUpperCase()}] [${timestamp}]`;
101
+ switch (level) {
102
+ case "debug":
103
+ console.debug(prefix, ...args);
104
+ break;
105
+ case "info":
106
+ console.info(prefix, ...args);
107
+ break;
108
+ case "warn":
109
+ console.warn(prefix, ...args);
110
+ break;
111
+ case "error":
112
+ console.error(prefix, ...args);
113
+ break;
114
+ }
115
+ }
116
+ /**
117
+ * Log a debug message
118
+ */
119
+ debug(...args) {
120
+ this.log("debug", ...args);
121
+ }
122
+ /**
123
+ * Log an info message
124
+ */
125
+ info(...args) {
126
+ this.log("info", ...args);
127
+ }
128
+ /**
129
+ * Log a warning message
130
+ */
131
+ warn(...args) {
132
+ this.log("warn", ...args);
133
+ }
134
+ /**
135
+ * Log an error message
136
+ */
137
+ error(...args) {
138
+ this.log("error", ...args);
139
+ }
140
+ /**
141
+ * Create a child logger with a specific prefix
142
+ */
143
+ child(prefix) {
144
+ const childLogger = new _Logger();
145
+ childLogger.config = {
146
+ ...this.config,
147
+ prefix: `${this.config.prefix} [${prefix}]`
148
+ };
149
+ return childLogger;
150
+ }
151
+ };
152
+ var logger = new Logger();
153
+
154
+ // src/utils/auth.ts
155
+ function parseUrlParams(searchParams) {
156
+ const params = typeof searchParams === "string" ? new URLSearchParams(searchParams) : searchParams;
157
+ return {
158
+ gameStartTab: params.get("game_start_tab") || "",
159
+ platform: params.get("platform") || "",
160
+ hyveAccess: params.get("hyve-access") || "",
161
+ gameId: params.get("game-id") || ""
162
+ };
163
+ }
164
+
165
+ // src/utils/native-bridge.ts
166
+ var NativeBridge = class {
167
+ static handlers = /* @__PURE__ */ new Map();
168
+ static isInitialized = false;
169
+ /**
170
+ * Checks if the app is running inside a React Native WebView
171
+ */
172
+ static isNativeContext() {
173
+ return typeof window !== "undefined" && "ReactNativeWebView" in window;
174
+ }
175
+ /**
176
+ * Initializes the native bridge message listener
177
+ * Call this once when your app starts
178
+ */
179
+ static initialize() {
180
+ if (this.isInitialized) {
181
+ logger.debug("[NativeBridge] Already initialized");
182
+ return;
183
+ }
184
+ if (typeof window === "undefined") {
185
+ logger.warn("[NativeBridge] Window not available, skipping initialization");
186
+ return;
187
+ }
188
+ const boundHandler = this.handleNativeMessage.bind(this);
189
+ window.addEventListener("message", boundHandler);
190
+ document.addEventListener("message", boundHandler);
191
+ this.isInitialized = true;
192
+ logger.info("[NativeBridge] Initialized and listening for native messages");
193
+ }
194
+ /**
195
+ * Handles incoming messages from React Native
196
+ */
197
+ static handleNativeMessage(event) {
198
+ try {
199
+ let data;
200
+ if (typeof event.data === "string") {
201
+ try {
202
+ data = JSON.parse(event.data);
203
+ } catch {
204
+ logger.debug("[NativeBridge] Ignoring non-JSON message");
205
+ return;
206
+ }
207
+ } else if (typeof event.data === "object" && event.data !== null) {
208
+ data = event.data;
209
+ } else {
210
+ return;
211
+ }
212
+ if (!data?.type) {
213
+ return;
214
+ }
215
+ const nativeResponseTypes = [
216
+ "IAP_AVAILABILITY_RESULT" /* IAP_AVAILABILITY_RESULT */,
217
+ "PUSH_PERMISSION_GRANTED" /* PUSH_PERMISSION_GRANTED */,
218
+ "PUSH_PERMISSION_DENIED" /* PUSH_PERMISSION_DENIED */,
219
+ "PRODUCTS_RESULT" /* PRODUCTS_RESULT */,
220
+ "PURCHASE_COMPLETE" /* PURCHASE_COMPLETE */,
221
+ "PURCHASE_ERROR" /* PURCHASE_ERROR */
222
+ ];
223
+ if (!nativeResponseTypes.includes(data.type)) {
224
+ return;
225
+ }
226
+ const handler = this.handlers.get(data.type);
227
+ if (handler) {
228
+ logger.debug(`[NativeBridge] Handling message: ${data.type}`);
229
+ handler(data.payload);
230
+ } else {
231
+ logger.warn(`[NativeBridge] No handler registered for: ${data.type}`);
232
+ }
233
+ } catch (error) {
234
+ logger.error("[NativeBridge] Error handling native message:", error);
235
+ }
236
+ }
237
+ /**
238
+ * Sends a message to React Native
239
+ * @param type Message type
240
+ * @param payload Optional payload data
241
+ */
242
+ static send(type, payload) {
243
+ if (!this.isNativeContext()) {
244
+ logger.debug(`[NativeBridge] Not in native context, skipping message: ${type}`);
245
+ return;
246
+ }
247
+ try {
248
+ const message = { type, payload, timestamp: Date.now() };
249
+ window.ReactNativeWebView.postMessage(JSON.stringify(message));
250
+ logger.debug(`[NativeBridge] Sent message to native: ${type}`, payload);
251
+ } catch (error) {
252
+ logger.error(`[NativeBridge] Error sending message to native:`, error);
253
+ }
254
+ }
255
+ /**
256
+ * Registers a handler for messages from React Native
257
+ * @param type Message type to listen for
258
+ * @param handler Function to call when message is received
259
+ */
260
+ static on(type, handler) {
261
+ this.handlers.set(type, handler);
262
+ logger.debug(`[NativeBridge] Registered handler for: ${type}`);
263
+ }
264
+ /**
265
+ * Unregisters a handler for a specific message type
266
+ * @param type Message type to stop listening for
267
+ */
268
+ static off(type) {
269
+ this.handlers.delete(type);
270
+ logger.debug(`[NativeBridge] Unregistered handler for: ${type}`);
271
+ }
272
+ /**
273
+ * Clears all registered handlers
274
+ */
275
+ static clearHandlers() {
276
+ this.handlers.clear();
277
+ logger.debug("[NativeBridge] Cleared all handlers");
278
+ }
279
+ /**
280
+ * Checks if In-App Purchases are available on the device
281
+ * The native app will respond with a message containing availability status
282
+ *
283
+ * @example
284
+ * // Listen for the response
285
+ * NativeBridge.on("IAP_AVAILABILITY_RESULT", (payload) => {
286
+ * console.log("IAP available:", payload.available);
287
+ * });
288
+ *
289
+ * // Send the request
290
+ * NativeBridge.checkIAPAvailability();
291
+ */
292
+ static checkIAPAvailability() {
293
+ this.send("CHECK_IAP_AVAILABILITY" /* CHECK_IAP_AVAILABILITY */);
294
+ }
295
+ /**
296
+ * Requests notification permission from the native app
297
+ * The native app will respond with PUSH_PERMISSION_GRANTED or PUSH_PERMISSION_DENIED
298
+ *
299
+ * @example
300
+ * // Listen for the response
301
+ * NativeBridge.on("PUSH_PERMISSION_GRANTED", () => {
302
+ * console.log("Permission granted");
303
+ * });
304
+ *
305
+ * NativeBridge.on("PUSH_PERMISSION_DENIED", () => {
306
+ * console.log("Permission denied");
307
+ * });
308
+ *
309
+ * // Send the request
310
+ * NativeBridge.requestNotificationPermission();
311
+ */
312
+ static requestNotificationPermission() {
313
+ this.send("REQUEST_NOTIFICATION_PERMISSION" /* REQUEST_NOTIFICATION_PERMISSION */);
314
+ }
315
+ /**
316
+ * Requests available products for a specific game
317
+ * The native app will respond with PRODUCTS_RESULT containing product details
318
+ *
319
+ * @param gameId - The game ID to fetch products for
320
+ *
321
+ * @example
322
+ * // Listen for the response
323
+ * NativeBridge.on("PRODUCTS_RESULT", (payload) => {
324
+ * console.log("Products:", payload.products);
325
+ * });
326
+ *
327
+ * // Send the request
328
+ * NativeBridge.getProducts(123);
329
+ */
330
+ static getProducts(gameId) {
331
+ this.send("GET_PRODUCTS" /* GET_PRODUCTS */, { gameId });
332
+ }
333
+ /**
334
+ * Initiates a purchase for a specific product
335
+ * The native app will respond with PURCHASE_COMPLETE or PURCHASE_ERROR
336
+ *
337
+ * @param productId - The product ID to purchase
338
+ * @param userId - The user ID making the purchase
339
+ *
340
+ * @example
341
+ * // Listen for responses
342
+ * NativeBridge.on("PURCHASE_COMPLETE", (payload) => {
343
+ * console.log("Purchase successful:", payload.productId);
344
+ * });
345
+ *
346
+ * NativeBridge.on("PURCHASE_ERROR", (payload) => {
347
+ * console.error("Purchase failed:", payload.error);
348
+ * });
349
+ *
350
+ * // Initiate purchase
351
+ * NativeBridge.purchase("product_123", "user_456");
352
+ */
353
+ static purchase(productId, userId) {
354
+ this.send("PURCHASE" /* PURCHASE */, { productId, userId });
355
+ }
356
+ };
357
+
358
+ // src/utils/jwt.ts
359
+ function decodeJwt(token) {
360
+ try {
361
+ const parts = token.split(".");
362
+ if (parts.length !== 3) {
363
+ logger.warn("Invalid JWT format - expected 3 parts");
364
+ return null;
365
+ }
366
+ const payload = parts[1];
367
+ const base64 = payload.replace(/-/g, "+").replace(/_/g, "/");
368
+ const jsonPayload = atob(base64);
369
+ const decoded = JSON.parse(jsonPayload);
370
+ logger.debug("JWT decoded successfully:", decoded);
371
+ return decoded;
372
+ } catch (error) {
373
+ logger.error("Failed to decode JWT:", error);
374
+ return null;
375
+ }
376
+ }
377
+ function extractUserIdFromJwt(token) {
378
+ const payload = decodeJwt(token);
379
+ if (!payload) {
380
+ return null;
381
+ }
382
+ const userId = payload.sub || payload.user_id?.toString();
383
+ if (userId) {
384
+ logger.info("Extracted user ID from JWT:", userId);
385
+ return userId;
386
+ }
387
+ logger.warn("No user ID found in JWT payload");
388
+ return null;
389
+ }
390
+ function extractGameIdFromJwt(token) {
391
+ const payload = decodeJwt(token);
392
+ if (!payload) {
393
+ return null;
394
+ }
395
+ const gameId = payload.game_id;
396
+ if (typeof gameId === "number") {
397
+ logger.info("Extracted game ID from JWT:", gameId);
398
+ return gameId;
399
+ }
400
+ return null;
401
+ }
402
+
403
+ // src/utils/index.ts
404
+ function generateUUID() {
405
+ return (0, import_uuid.v4)();
406
+ }
407
+
408
+ // src/services/ads.ts
409
+ var AdsService = class {
410
+ config = {
411
+ sound: "on",
412
+ debug: false,
413
+ onBeforeAd: () => {
414
+ },
415
+ onAfterAd: () => {
416
+ },
417
+ onRewardEarned: () => {
418
+ }
419
+ };
420
+ // Cached init promise — ensures adConfig() is only called once
421
+ initPromise = null;
422
+ ready = false;
423
+ /**
424
+ * Optionally configure the ads service.
425
+ * Not required — ads work without calling this.
426
+ */
427
+ configure(config) {
428
+ this.config = {
429
+ ...this.config,
430
+ ...config,
431
+ onBeforeAd: config.onBeforeAd ?? this.config.onBeforeAd,
432
+ onAfterAd: config.onAfterAd ?? this.config.onAfterAd,
433
+ onRewardEarned: config.onRewardEarned ?? this.config.onRewardEarned
434
+ };
435
+ if (this.config.debug) {
436
+ logger.debug("[AdsService] Configuration updated:", { sound: this.config.sound });
437
+ }
438
+ }
439
+ /**
440
+ * Initialize the Google H5 Ads system.
441
+ * Returns a cached promise — safe to call multiple times.
442
+ */
443
+ initialize() {
444
+ if (this.initPromise) return this.initPromise;
445
+ this.initPromise = new Promise((resolve) => {
446
+ if (!window.adConfig || !window.adBreak) {
447
+ if (this.config.debug) {
448
+ logger.debug("[AdsService] Google Ads SDK not found \u2014 ads unavailable");
449
+ }
450
+ resolve(false);
451
+ return;
452
+ }
453
+ if (this.config.debug) {
454
+ logger.debug("[AdsService] Initializing ads system...");
455
+ }
456
+ window.adConfig({
457
+ sound: this.config.sound,
458
+ preloadAdBreaks: "on",
459
+ onReady: () => {
460
+ this.ready = true;
461
+ if (this.config.debug) {
462
+ logger.debug("[AdsService] Ads ready");
463
+ }
464
+ resolve(true);
465
+ }
466
+ });
467
+ setTimeout(() => resolve(false), 5e3);
468
+ });
469
+ return this.initPromise;
470
+ }
471
+ /**
472
+ * Show an ad. Auto-initializes on the first call.
473
+ * Returns immediately with success: false if ads are disabled or unavailable.
474
+ */
475
+ async show(type) {
476
+ const requestedAt = Date.now();
477
+ const ready = await this.initialize();
478
+ if (!ready || !window.adBreak) {
479
+ return {
480
+ success: false,
481
+ type,
482
+ error: new Error("Ads not available"),
483
+ requestedAt,
484
+ completedAt: Date.now()
485
+ };
486
+ }
487
+ return this.showAdBreak(type);
488
+ }
489
+ /**
490
+ * Show an ad break via the Google H5 API.
491
+ */
492
+ async showAdBreak(type) {
493
+ const requestedAt = Date.now();
494
+ return new Promise((resolve) => {
495
+ const googleType = type === "rewarded" ? "reward" : type === "preroll" ? "start" : "next";
496
+ const adName = `${type}-ad-${Date.now()}`;
497
+ if (this.config.debug) {
498
+ logger.debug(`[AdsService] Showing ${type} ad`);
499
+ }
500
+ this.config.onBeforeAd(type);
501
+ const adBreakConfig = {
502
+ type: googleType,
503
+ name: adName,
504
+ afterAd: () => {
505
+ this.config.onAfterAd(type);
506
+ },
507
+ adBreakDone: (info) => {
508
+ const completedAt = Date.now();
509
+ const success = type === "rewarded" ? info?.breakStatus === "viewed" : info?.breakStatus !== "error";
510
+ const error = info?.breakStatus === "error" && info?.error ? new Error(info.error) : void 0;
511
+ if (this.config.debug) {
512
+ logger.debug("[AdsService] Ad break done:", { success, status: info?.breakStatus });
513
+ }
514
+ resolve({ success, type, error, requestedAt, completedAt });
515
+ }
516
+ };
517
+ if (type === "rewarded") {
518
+ adBreakConfig.beforeReward = (showAdFn) => showAdFn();
519
+ adBreakConfig.adViewed = () => this.config.onRewardEarned();
520
+ }
521
+ window.adBreak(adBreakConfig);
522
+ });
523
+ }
524
+ /**
525
+ * Returns the configured ad lifecycle callbacks.
526
+ * Used by platform-specific providers (e.g. Playgama) to fire the same hooks.
527
+ */
528
+ getCallbacks() {
529
+ return {
530
+ onBeforeAd: this.config.onBeforeAd,
531
+ onAfterAd: this.config.onAfterAd,
532
+ onRewardEarned: this.config.onRewardEarned
533
+ };
534
+ }
535
+ /**
536
+ * Check if ads have successfully initialized and are ready to show.
537
+ */
538
+ isReady() {
539
+ return this.ready;
540
+ }
541
+ };
542
+
543
+ // src/services/playgama.ts
544
+ var PLAYGAMA_BRIDGE_CDN = "https://bridge.playgama.com/v1/stable/playgama-bridge.js";
545
+ var PlaygamaService = class {
546
+ initialized = false;
547
+ /**
548
+ * Detects if the game is running on the Playgama platform.
549
+ * Playgama injects a `platform_id=playgama` URL parameter into the game URL.
550
+ * Falls back to checking document.referrer for playgama.com.
551
+ */
552
+ static isPlaygamaDomain() {
553
+ try {
554
+ const url = new URL(window.location.href);
555
+ if (url.searchParams.get("platform_id") === "playgama") {
556
+ return true;
557
+ }
558
+ if (document.referrer.includes("playgama.com")) {
559
+ return true;
560
+ }
561
+ if (window !== window.top) {
562
+ try {
563
+ if (window.parent.location.hostname.includes("playgama.com")) {
564
+ return true;
565
+ }
566
+ } catch {
567
+ }
568
+ }
569
+ return false;
570
+ } catch {
571
+ return false;
572
+ }
573
+ }
574
+ /**
575
+ * Loads the Playgama Bridge script from CDN and initializes it.
576
+ * Safe to call multiple times - resolves immediately if already initialized.
577
+ */
578
+ async initialize() {
579
+ if (this.initialized) return true;
580
+ try {
581
+ await this.loadScript();
582
+ await window.bridge.initialize();
583
+ this.initialized = true;
584
+ return true;
585
+ } catch (error) {
586
+ logger.warn("[PlaygamaService] Failed to initialize:", error);
587
+ return false;
588
+ }
589
+ }
590
+ isInitialized() {
591
+ return this.initialized;
592
+ }
593
+ async showInterstitial(callbacks) {
594
+ const requestedAt = Date.now();
595
+ const bridge = window.bridge;
596
+ if (!this.initialized || !bridge) {
597
+ return {
598
+ success: false,
599
+ type: "interstitial",
600
+ error: new Error("Playgama not initialized"),
601
+ requestedAt,
602
+ completedAt: Date.now()
603
+ };
604
+ }
605
+ if (!bridge.advertisement.isInterstitialSupported) {
606
+ return {
607
+ success: false,
608
+ type: "interstitial",
609
+ error: new Error("Interstitial ads not supported on this platform"),
610
+ requestedAt,
611
+ completedAt: Date.now()
612
+ };
613
+ }
614
+ return new Promise((resolve) => {
615
+ const handler = (state) => {
616
+ if (state === "opened") {
617
+ callbacks?.onBeforeAd?.();
618
+ } else if (state === "closed") {
619
+ bridge.advertisement.off(bridge.EVENT_NAME.INTERSTITIAL_STATE_CHANGED, handler);
620
+ callbacks?.onAfterAd?.();
621
+ resolve({ success: true, type: "interstitial", requestedAt, completedAt: Date.now() });
622
+ } else if (state === "failed") {
623
+ bridge.advertisement.off(bridge.EVENT_NAME.INTERSTITIAL_STATE_CHANGED, handler);
624
+ callbacks?.onAfterAd?.();
625
+ resolve({
626
+ success: false,
627
+ type: "interstitial",
628
+ error: new Error("Interstitial ad failed"),
629
+ requestedAt,
630
+ completedAt: Date.now()
631
+ });
632
+ }
633
+ };
634
+ bridge.advertisement.on(bridge.EVENT_NAME.INTERSTITIAL_STATE_CHANGED, handler);
635
+ bridge.advertisement.showInterstitial();
636
+ });
637
+ }
638
+ async showRewarded(callbacks) {
639
+ const requestedAt = Date.now();
640
+ const bridge = window.bridge;
641
+ if (!this.initialized || !bridge) {
642
+ return {
643
+ success: false,
644
+ type: "rewarded",
645
+ error: new Error("Playgama not initialized"),
646
+ requestedAt,
647
+ completedAt: Date.now()
648
+ };
649
+ }
650
+ if (!bridge.advertisement.isRewardedSupported) {
651
+ return {
652
+ success: false,
653
+ type: "rewarded",
654
+ error: new Error("Rewarded ads not supported on this platform"),
655
+ requestedAt,
656
+ completedAt: Date.now()
657
+ };
658
+ }
659
+ return new Promise((resolve) => {
660
+ let rewarded = false;
661
+ const handler = (state) => {
662
+ if (state === "opened") {
663
+ callbacks?.onBeforeAd?.();
664
+ } else if (state === "rewarded") {
665
+ rewarded = true;
666
+ callbacks?.onRewardEarned?.();
667
+ } else if (state === "closed" || state === "failed") {
668
+ bridge.advertisement.off(bridge.EVENT_NAME.REWARDED_STATE_CHANGED, handler);
669
+ callbacks?.onAfterAd?.();
670
+ resolve({
671
+ success: rewarded,
672
+ type: "rewarded",
673
+ error: !rewarded ? new Error("Rewarded ad not completed") : void 0,
674
+ requestedAt,
675
+ completedAt: Date.now()
676
+ });
677
+ }
678
+ };
679
+ bridge.advertisement.on(bridge.EVENT_NAME.REWARDED_STATE_CHANGED, handler);
680
+ bridge.advertisement.showRewarded();
681
+ });
682
+ }
683
+ loadScript() {
684
+ return new Promise((resolve, reject) => {
685
+ if (window.bridge) {
686
+ resolve();
687
+ return;
688
+ }
689
+ const script = document.createElement("script");
690
+ script.src = PLAYGAMA_BRIDGE_CDN;
691
+ script.onload = () => resolve();
692
+ script.onerror = () => reject(new Error("Failed to load Playgama Bridge script"));
693
+ document.head.appendChild(script);
694
+ });
695
+ }
696
+ };
697
+
698
+ // src/services/crazygames.ts
699
+ var CRAZYGAMES_SDK_CDN = "https://sdk.crazygames.com/crazygames-sdk-v2.js";
700
+ var CrazyGamesService = class {
701
+ initialized = false;
702
+ /**
703
+ * Detects if the game is running on the CrazyGames platform.
704
+ * Games on CrazyGames run inside an iframe, so we check document.referrer
705
+ * and attempt to read the parent frame location.
706
+ */
707
+ static isCrazyGamesDomain() {
708
+ try {
709
+ if (document.referrer.includes("crazygames.com")) {
710
+ return true;
711
+ }
712
+ if (window !== window.top) {
713
+ try {
714
+ if (window.parent.location.hostname.includes("crazygames.com")) {
715
+ return true;
716
+ }
717
+ } catch {
718
+ }
719
+ }
720
+ return false;
721
+ } catch {
722
+ return false;
723
+ }
724
+ }
725
+ /**
726
+ * Loads the CrazyGames SDK from CDN and confirms the environment is 'crazygames'.
727
+ * Safe to call multiple times — resolves immediately if already initialized.
728
+ */
729
+ async initialize() {
730
+ if (this.initialized) return true;
731
+ try {
732
+ await this.loadScript();
733
+ const sdk = window.CrazyGames?.SDK;
734
+ if (!sdk) {
735
+ logger.warn("[CrazyGamesService] SDK not found after script load");
736
+ return false;
737
+ }
738
+ const env = await sdk.getEnvironment();
739
+ if (env !== "crazygames" && env !== "local") {
740
+ logger.warn("[CrazyGamesService] Unexpected environment:", env);
741
+ return false;
742
+ }
743
+ this.initialized = true;
744
+ return true;
745
+ } catch (error) {
746
+ logger.warn("[CrazyGamesService] Failed to initialize:", error);
747
+ return false;
748
+ }
749
+ }
750
+ isInitialized() {
751
+ return this.initialized;
752
+ }
753
+ /**
754
+ * Shows a midgame (interstitial) ad via the CrazyGames SDK.
755
+ */
756
+ async showInterstitial(callbacks) {
757
+ const requestedAt = Date.now();
758
+ const sdk = window.CrazyGames?.SDK;
759
+ if (!this.initialized || !sdk) {
760
+ return {
761
+ success: false,
762
+ type: "interstitial",
763
+ error: new Error("CrazyGames SDK not initialized"),
764
+ requestedAt,
765
+ completedAt: Date.now()
766
+ };
767
+ }
768
+ return new Promise((resolve) => {
769
+ sdk.ad.requestAd("midgame", {
770
+ adStarted: () => {
771
+ callbacks?.onBeforeAd?.();
772
+ },
773
+ adFinished: () => {
774
+ callbacks?.onAfterAd?.();
775
+ resolve({ success: true, type: "interstitial", requestedAt, completedAt: Date.now() });
776
+ },
777
+ adError: (error) => {
778
+ callbacks?.onAfterAd?.();
779
+ resolve({
780
+ success: false,
781
+ type: "interstitial",
782
+ error: new Error(`CrazyGames interstitial ad error: ${error}`),
783
+ requestedAt,
784
+ completedAt: Date.now()
785
+ });
786
+ }
787
+ });
788
+ });
789
+ }
790
+ /**
791
+ * Shows a rewarded ad via the CrazyGames SDK.
792
+ * Resolves with success: true only when adFinished fires (ad was fully watched).
793
+ */
794
+ async showRewarded(callbacks) {
795
+ const requestedAt = Date.now();
796
+ const sdk = window.CrazyGames?.SDK;
797
+ if (!this.initialized || !sdk) {
798
+ return {
799
+ success: false,
800
+ type: "rewarded",
801
+ error: new Error("CrazyGames SDK not initialized"),
802
+ requestedAt,
803
+ completedAt: Date.now()
804
+ };
805
+ }
806
+ return new Promise((resolve) => {
807
+ sdk.ad.requestAd("rewarded", {
808
+ adStarted: () => {
809
+ callbacks?.onBeforeAd?.();
810
+ },
811
+ adFinished: () => {
812
+ callbacks?.onRewardEarned?.();
813
+ callbacks?.onAfterAd?.();
814
+ resolve({ success: true, type: "rewarded", requestedAt, completedAt: Date.now() });
815
+ },
816
+ adError: (error) => {
817
+ callbacks?.onAfterAd?.();
818
+ resolve({
819
+ success: false,
820
+ type: "rewarded",
821
+ error: new Error(`CrazyGames rewarded ad error: ${error}`),
822
+ requestedAt,
823
+ completedAt: Date.now()
824
+ });
825
+ }
826
+ });
827
+ });
828
+ }
829
+ /**
830
+ * Notifies CrazyGames that gameplay has started.
831
+ * Call when the player actively begins or resumes playing.
832
+ */
833
+ gameplayStart() {
834
+ if (!this.initialized) return;
835
+ window.CrazyGames?.SDK.game.gameplayStart();
836
+ }
837
+ /**
838
+ * Notifies CrazyGames that gameplay has stopped.
839
+ * Call during menu access, level completion, or pausing.
840
+ */
841
+ gameplayStop() {
842
+ if (!this.initialized) return;
843
+ window.CrazyGames?.SDK.game.gameplayStop();
844
+ }
845
+ /**
846
+ * Triggers a celebration effect on the CrazyGames website.
847
+ * Use sparingly for significant achievements (boss defeat, personal record, etc.)
848
+ */
849
+ happytime() {
850
+ if (!this.initialized) return;
851
+ window.CrazyGames?.SDK.game.happytime();
852
+ }
853
+ loadScript() {
854
+ return new Promise((resolve, reject) => {
855
+ if (window.CrazyGames?.SDK) {
856
+ resolve();
857
+ return;
858
+ }
859
+ const script = document.createElement("script");
860
+ script.src = CRAZYGAMES_SDK_CDN;
861
+ script.onload = () => resolve();
862
+ script.onerror = () => reject(new Error("Failed to load CrazyGames SDK script"));
863
+ document.head.appendChild(script);
864
+ });
865
+ }
866
+ };
867
+
868
+ // src/services/billing.ts
869
+ var BillingService = class {
870
+ config;
871
+ platform = "unknown" /* UNKNOWN */;
872
+ initPromise = null;
873
+ nativeAvailable = false;
874
+ // Stripe instance for web payments
875
+ stripe = null;
876
+ checkoutElement = null;
877
+ // Callbacks for purchase events
878
+ onPurchaseCompleteCallback;
879
+ onPurchaseErrorCallback;
880
+ constructor(config) {
881
+ this.config = config;
882
+ this.detectPlatform();
883
+ }
884
+ /**
885
+ * Update billing configuration. Resets initialization so next call re-inits with new config.
886
+ */
887
+ configure(config) {
888
+ this.config = {
889
+ ...this.config,
890
+ ...config
891
+ };
892
+ this.initPromise = null;
893
+ logger.info("[BillingService] Configuration updated");
894
+ }
895
+ /**
896
+ * Detects if running on web or native platform
897
+ */
898
+ detectPlatform() {
899
+ const isNative = NativeBridge.isNativeContext();
900
+ const hasWindow = typeof window !== "undefined";
901
+ if (isNative) {
902
+ this.platform = "native" /* NATIVE */;
903
+ logger.info("[BillingService] Platform: NATIVE");
904
+ } else if (hasWindow) {
905
+ this.platform = "web" /* WEB */;
906
+ logger.info("[BillingService] Platform: WEB");
907
+ } else {
908
+ this.platform = "unknown" /* UNKNOWN */;
909
+ logger.warn("[BillingService] Platform: UNKNOWN");
910
+ }
911
+ }
912
+ /**
913
+ * Get the current platform
914
+ */
915
+ getPlatform() {
916
+ return this.platform;
917
+ }
918
+ /**
919
+ * Initialize the billing service. Idempotent — returns cached promise on subsequent calls.
920
+ * Called automatically by getProducts() and purchase().
921
+ */
922
+ initialize() {
923
+ if (this.initPromise) return this.initPromise;
924
+ logger.info(`[BillingService] Initializing for ${this.platform} platform...`);
925
+ this.initPromise = (async () => {
926
+ try {
927
+ if (this.platform === "native" /* NATIVE */) {
928
+ return await this.initializeNative();
929
+ } else if (this.platform === "web" /* WEB */) {
930
+ return await this.initializeWeb();
931
+ }
932
+ logger.error("[BillingService] Cannot initialize: unknown platform");
933
+ return false;
934
+ } catch (error) {
935
+ logger.error("[BillingService] Initialization failed:", error?.message);
936
+ return false;
937
+ }
938
+ })();
939
+ return this.initPromise;
940
+ }
941
+ /**
942
+ * Initialize native billing
943
+ */
944
+ async initializeNative() {
945
+ return new Promise((resolve) => {
946
+ let resolved = false;
947
+ try {
948
+ NativeBridge.initialize();
949
+ NativeBridge.on(
950
+ "PURCHASE_COMPLETE" /* PURCHASE_COMPLETE */,
951
+ (payload) => {
952
+ logger.info("[BillingService] Purchase complete:", payload.productId);
953
+ const result = {
954
+ success: true,
955
+ productId: payload.productId,
956
+ transactionId: payload.transactionId,
957
+ transactionDate: payload.transactionDate
958
+ };
959
+ if (this.onPurchaseCompleteCallback) {
960
+ this.onPurchaseCompleteCallback(result);
961
+ }
962
+ }
963
+ );
964
+ NativeBridge.on(
965
+ "PURCHASE_ERROR" /* PURCHASE_ERROR */,
966
+ (payload) => {
967
+ logger.error("[BillingService] Purchase error:", payload.message);
968
+ const result = {
969
+ success: false,
970
+ productId: payload.productId || "unknown",
971
+ error: {
972
+ code: payload.code,
973
+ message: payload.message
974
+ }
975
+ };
976
+ if (this.onPurchaseErrorCallback) {
977
+ this.onPurchaseErrorCallback(result);
978
+ }
979
+ }
980
+ );
981
+ NativeBridge.on(
982
+ "IAP_AVAILABILITY_RESULT" /* IAP_AVAILABILITY_RESULT */,
983
+ (payload) => {
984
+ this.nativeAvailable = payload.available;
985
+ logger.info(`[BillingService] Native billing ${payload.available ? "available" : "unavailable"}`);
986
+ resolved = true;
987
+ resolve(payload.available);
988
+ }
989
+ );
990
+ setTimeout(() => {
991
+ NativeBridge.checkIAPAvailability();
992
+ }, 100);
993
+ setTimeout(() => {
994
+ if (!resolved) {
995
+ logger.warn("[BillingService] Native initialization timeout");
996
+ resolved = true;
997
+ resolve(false);
998
+ }
999
+ }, 5e3);
1000
+ } catch (error) {
1001
+ logger.error("[BillingService] Native initialization failed:", error?.message);
1002
+ resolved = true;
1003
+ resolve(false);
1004
+ }
1005
+ });
1006
+ }
1007
+ /**
1008
+ * Initialize web billing (Stripe)
1009
+ */
1010
+ async initializeWeb() {
1011
+ if (!this.config.stripePublishableKey) {
1012
+ logger.error("[BillingService] Stripe publishable key not provided");
1013
+ return false;
1014
+ }
1015
+ try {
1016
+ if (typeof window === "undefined") {
1017
+ logger.error("[BillingService] Window is undefined (not in browser)");
1018
+ return false;
1019
+ }
1020
+ if (!window.Stripe) {
1021
+ await this.loadStripeScript();
1022
+ }
1023
+ if (window.Stripe) {
1024
+ this.stripe = window.Stripe(this.config.stripePublishableKey);
1025
+ logger.info("[BillingService] Web billing initialized");
1026
+ return true;
1027
+ } else {
1028
+ logger.error("[BillingService] Stripe not available after loading");
1029
+ return false;
1030
+ }
1031
+ } catch (error) {
1032
+ logger.error("[BillingService] Stripe initialization failed:", error?.message);
1033
+ return false;
1034
+ }
1035
+ }
1036
+ /**
1037
+ * Load Stripe.js script dynamically
1038
+ */
1039
+ loadStripeScript() {
1040
+ return new Promise((resolve, reject) => {
1041
+ if (typeof window === "undefined") {
1042
+ reject(new Error("Window is not defined"));
1043
+ return;
1044
+ }
1045
+ if (window.Stripe) {
1046
+ resolve();
1047
+ return;
1048
+ }
1049
+ if (!document || !document.head) {
1050
+ reject(new Error("document is undefined"));
1051
+ return;
1052
+ }
1053
+ const script = document.createElement("script");
1054
+ script.src = "https://js.stripe.com/v3/";
1055
+ script.async = true;
1056
+ script.onload = () => {
1057
+ logger.info("[BillingService] Stripe.js loaded");
1058
+ resolve();
1059
+ };
1060
+ script.onerror = () => {
1061
+ logger.error("[BillingService] Failed to load Stripe.js");
1062
+ reject(new Error("Failed to load Stripe.js"));
1063
+ };
1064
+ try {
1065
+ document.head.appendChild(script);
1066
+ } catch (appendError) {
1067
+ reject(appendError);
1068
+ }
1069
+ });
1070
+ }
1071
+ /**
1072
+ * Check if billing is available based on current config and platform state
1073
+ */
1074
+ isAvailable() {
1075
+ if (this.platform === "native" /* NATIVE */) {
1076
+ return this.nativeAvailable;
1077
+ } else if (this.platform === "web" /* WEB */) {
1078
+ return !!this.config.stripePublishableKey;
1079
+ }
1080
+ return false;
1081
+ }
1082
+ /**
1083
+ * Get available products. Auto-initializes on first call.
1084
+ */
1085
+ async getProducts() {
1086
+ await this.initialize();
1087
+ if (this.platform === "native" /* NATIVE */) {
1088
+ return await this.getProductsNative();
1089
+ } else if (this.platform === "web" /* WEB */) {
1090
+ return await this.getProductsWeb();
1091
+ }
1092
+ throw new Error("Cannot get products: unknown platform");
1093
+ }
1094
+ /**
1095
+ * Get products from native IAP
1096
+ */
1097
+ async getProductsNative() {
1098
+ if (!this.config.gameId || !this.config.checkoutUrl) {
1099
+ const error = new Error("gameId and checkoutUrl required for native purchases");
1100
+ logger.error("[BillingService]", error.message);
1101
+ throw error;
1102
+ }
1103
+ const response = await fetch(
1104
+ `${this.config.checkoutUrl}/get-native-packages?game_id=${this.config.gameId}`
1105
+ );
1106
+ if (!response.ok) {
1107
+ throw new Error(`Failed to fetch native products: ${response.status}`);
1108
+ }
1109
+ const data = await response.json();
1110
+ if (!data.packages || !Array.isArray(data.packages)) {
1111
+ throw new Error("Invalid response format: missing packages array");
1112
+ }
1113
+ const products = data.packages.map((pkg) => ({
1114
+ productId: pkg.productId,
1115
+ title: pkg.package_name,
1116
+ description: `${pkg.game_name} - ${pkg.package_name}`,
1117
+ price: pkg.price_cents / 100,
1118
+ localizedPrice: pkg.price_display,
1119
+ currency: "USD"
1120
+ }));
1121
+ logger.info(`[BillingService] Fetched ${products.length} native products`);
1122
+ return products;
1123
+ }
1124
+ /**
1125
+ * Get products from web API (Stripe)
1126
+ */
1127
+ async getProductsWeb() {
1128
+ if (!this.config.checkoutUrl || !this.config.gameId) {
1129
+ const error = new Error("checkoutUrl and gameId required for web purchases");
1130
+ logger.error("[BillingService]", error.message);
1131
+ throw error;
1132
+ }
1133
+ try {
1134
+ const url = `${this.config.checkoutUrl}/get-packages?game_id=${this.config.gameId}`;
1135
+ const response = await fetch(url);
1136
+ if (!response.ok) {
1137
+ throw new Error(`Failed to fetch web products: ${response.status}`);
1138
+ }
1139
+ const data = await response.json();
1140
+ if (!data.packages || !Array.isArray(data.packages)) {
1141
+ throw new Error("Invalid response format: missing packages array");
1142
+ }
1143
+ const products = data.packages.map((pkg) => ({
1144
+ productId: pkg.priceId || pkg.productId,
1145
+ // Prefer priceId for Stripe
1146
+ title: pkg.package_name,
1147
+ description: `${pkg.game_name} - ${pkg.package_name}`,
1148
+ price: pkg.price_cents / 100,
1149
+ localizedPrice: pkg.price_display,
1150
+ currency: "USD"
1151
+ }));
1152
+ logger.info(`[BillingService] Fetched ${products.length} web products`);
1153
+ return products;
1154
+ } catch (error) {
1155
+ logger.error("[BillingService] Failed to fetch web products:", error?.message);
1156
+ throw error;
1157
+ }
1158
+ }
1159
+ /**
1160
+ * Purchase a product. Auto-initializes on first call.
1161
+ * @param productId - The product ID (priceId for web/Stripe, productId for native)
1162
+ * @param options - Optional purchase options
1163
+ * @param options.elementId - For web: DOM element ID to mount Stripe checkout (default: 'stripe-checkout-element')
1164
+ */
1165
+ async purchase(productId, options) {
1166
+ await this.initialize();
1167
+ if (!this.isAvailable()) {
1168
+ throw new Error("Billing is not available on this platform");
1169
+ }
1170
+ if (this.platform === "native" /* NATIVE */) {
1171
+ return await this.purchaseNative(productId);
1172
+ } else if (this.platform === "web" /* WEB */) {
1173
+ return await this.purchaseWeb(productId, options?.elementId);
1174
+ }
1175
+ throw new Error("Cannot purchase: unknown platform");
1176
+ }
1177
+ /**
1178
+ * Purchase via native IAP
1179
+ */
1180
+ async purchaseNative(productId) {
1181
+ return new Promise((resolve, reject) => {
1182
+ if (!this.config.userId) {
1183
+ reject(new Error("userId is required for native purchases"));
1184
+ return;
1185
+ }
1186
+ logger.info(`[BillingService] Purchasing: ${productId}`);
1187
+ const previousCompleteCallback = this.onPurchaseCompleteCallback;
1188
+ const previousErrorCallback = this.onPurchaseErrorCallback;
1189
+ const cleanup = () => {
1190
+ this.onPurchaseCompleteCallback = previousCompleteCallback;
1191
+ this.onPurchaseErrorCallback = previousErrorCallback;
1192
+ };
1193
+ const completeHandler = (result) => {
1194
+ if (result.productId === productId) {
1195
+ cleanup();
1196
+ resolve(result);
1197
+ if (previousCompleteCallback) {
1198
+ previousCompleteCallback(result);
1199
+ }
1200
+ }
1201
+ };
1202
+ const errorHandler = (result) => {
1203
+ if (result.productId === productId) {
1204
+ cleanup();
1205
+ reject(new Error(result.error?.message || "Purchase failed"));
1206
+ if (previousErrorCallback) {
1207
+ previousErrorCallback(result);
1208
+ }
1209
+ }
1210
+ };
1211
+ this.onPurchaseCompleteCallback = completeHandler;
1212
+ this.onPurchaseErrorCallback = errorHandler;
1213
+ NativeBridge.purchase(productId, this.config.userId);
1214
+ setTimeout(() => {
1215
+ cleanup();
1216
+ reject(new Error("Purchase timeout"));
1217
+ }, 6e4);
1218
+ });
1219
+ }
1220
+ /**
1221
+ * Purchase via web (Stripe)
1222
+ * @param productId - The priceId (Stripe price ID) for web purchases
1223
+ * @param elementId - Optional DOM element ID to mount the checkout form (default: 'stripe-checkout-element')
1224
+ */
1225
+ async purchaseWeb(productId, elementId) {
1226
+ if (!this.config.userId || !this.config.checkoutUrl) {
1227
+ throw new Error("userId and checkoutUrl are required for web purchases");
1228
+ }
1229
+ if (!this.stripe) {
1230
+ throw new Error("Stripe not initialized. Call initialize() first.");
1231
+ }
1232
+ try {
1233
+ const returnUrl = typeof window !== "undefined" ? `${window.location.href}${window.location.href.includes("?") ? "&" : "?"}payment=complete` : void 0;
1234
+ const requestBody = {
1235
+ priceId: productId,
1236
+ // API expects priceId for Stripe
1237
+ userId: this.config.userId,
1238
+ gameId: this.config.gameId,
1239
+ embedded: true,
1240
+ // Enable embedded checkout
1241
+ return_url: returnUrl
1242
+ };
1243
+ const response = await fetch(`${this.config.checkoutUrl}/create-checkout-session`, {
1244
+ method: "POST",
1245
+ headers: {
1246
+ "Content-Type": "application/json"
1247
+ },
1248
+ body: JSON.stringify(requestBody)
1249
+ });
1250
+ if (!response.ok) {
1251
+ const errorText = await response.text();
1252
+ throw new Error(`Failed to create checkout session: ${response.status} - ${errorText}`);
1253
+ }
1254
+ const responseData = await response.json();
1255
+ const { client_secret, id } = responseData;
1256
+ if (!client_secret) {
1257
+ throw new Error("No client_secret returned from checkout session");
1258
+ }
1259
+ await this.mountCheckoutElement(client_secret, elementId || "stripe-checkout-element");
1260
+ logger.info(`[BillingService] Checkout session created: ${id}`);
1261
+ return {
1262
+ success: true,
1263
+ productId,
1264
+ transactionId: id
1265
+ };
1266
+ } catch (error) {
1267
+ logger.error("[BillingService] Web purchase failed:", error?.message);
1268
+ return {
1269
+ success: false,
1270
+ productId,
1271
+ error: {
1272
+ code: "WEB_PURCHASE_FAILED",
1273
+ message: error.message || "Purchase failed"
1274
+ }
1275
+ };
1276
+ }
1277
+ }
1278
+ /**
1279
+ * Mount Stripe embedded checkout element to the DOM
1280
+ * @param clientSecret - The client secret from the checkout session
1281
+ * @param elementId - The ID of the DOM element to mount to
1282
+ */
1283
+ async mountCheckoutElement(clientSecret, elementId) {
1284
+ if (!this.stripe) {
1285
+ throw new Error("Stripe not initialized");
1286
+ }
1287
+ try {
1288
+ if (this.checkoutElement) {
1289
+ logger.info("[BillingService] Unmounting existing checkout element");
1290
+ this.unmountCheckoutElement();
1291
+ await new Promise((resolve) => setTimeout(resolve, 100));
1292
+ }
1293
+ const container = document.getElementById(elementId);
1294
+ if (!container) {
1295
+ throw new Error(`Element with id "${elementId}" not found in the DOM`);
1296
+ }
1297
+ container.innerHTML = "";
1298
+ logger.info("[BillingService] Creating new checkout instance");
1299
+ this.checkoutElement = await this.stripe.initEmbeddedCheckout({
1300
+ clientSecret
1301
+ });
1302
+ logger.info("[BillingService] Mounting checkout element to DOM");
1303
+ this.checkoutElement.mount(`#${elementId}`);
1304
+ this.setupCheckoutEventListeners();
1305
+ } catch (error) {
1306
+ logger.error("[BillingService] Failed to mount checkout:", error?.message);
1307
+ throw error;
1308
+ }
1309
+ }
1310
+ /**
1311
+ * Set up event listeners for checkout completion
1312
+ */
1313
+ setupCheckoutEventListeners() {
1314
+ if (typeof window !== "undefined") {
1315
+ const urlParams = new URLSearchParams(window.location.search);
1316
+ const paymentStatus = urlParams.get("payment");
1317
+ if (paymentStatus === "complete") {
1318
+ const sessionId = urlParams.get("session_id");
1319
+ if (this.onPurchaseCompleteCallback) {
1320
+ this.onPurchaseCompleteCallback({
1321
+ success: true,
1322
+ productId: "",
1323
+ // Would need to be tracked separately
1324
+ transactionId: sessionId || void 0
1325
+ });
1326
+ }
1327
+ logger.info("[BillingService] Payment completed");
1328
+ urlParams.delete("payment");
1329
+ urlParams.delete("session_id");
1330
+ const newUrl = `${window.location.pathname}${urlParams.toString() ? "?" + urlParams.toString() : ""}`;
1331
+ window.history.replaceState({}, "", newUrl);
1332
+ }
1333
+ }
1334
+ }
1335
+ /**
1336
+ * Unmount and destroy the checkout element
1337
+ */
1338
+ unmountCheckoutElement() {
1339
+ if (this.checkoutElement) {
1340
+ try {
1341
+ this.checkoutElement.unmount();
1342
+ if (typeof this.checkoutElement.destroy === "function") {
1343
+ this.checkoutElement.destroy();
1344
+ }
1345
+ } catch (error) {
1346
+ logger.warn("[BillingService] Error unmounting checkout element:", error?.message);
1347
+ }
1348
+ this.checkoutElement = null;
1349
+ }
1350
+ }
1351
+ /**
1352
+ * Set callback for successful purchases
1353
+ */
1354
+ onPurchaseComplete(callback) {
1355
+ this.onPurchaseCompleteCallback = callback;
1356
+ }
1357
+ /**
1358
+ * Set callback for failed purchases
1359
+ */
1360
+ onPurchaseError(callback) {
1361
+ this.onPurchaseErrorCallback = callback;
1362
+ }
1363
+ /**
1364
+ * Clean up resources
1365
+ */
1366
+ dispose() {
1367
+ if (this.platform === "native" /* NATIVE */) {
1368
+ NativeBridge.off("IAP_AVAILABILITY_RESULT" /* IAP_AVAILABILITY_RESULT */);
1369
+ NativeBridge.off("PRODUCTS_RESULT" /* PRODUCTS_RESULT */);
1370
+ NativeBridge.off("PURCHASE_COMPLETE" /* PURCHASE_COMPLETE */);
1371
+ NativeBridge.off("PURCHASE_ERROR" /* PURCHASE_ERROR */);
1372
+ }
1373
+ this.unmountCheckoutElement();
1374
+ this.initPromise = null;
1375
+ this.nativeAvailable = false;
1376
+ this.stripe = null;
1377
+ this.onPurchaseCompleteCallback = void 0;
1378
+ this.onPurchaseErrorCallback = void 0;
1379
+ }
1380
+ };
1381
+
1382
+ // src/services/storage.ts
1383
+ var CloudStorageAdapter = class {
1384
+ constructor(callApi) {
1385
+ this.callApi = callApi;
1386
+ }
1387
+ async saveGameData(gameId, key, value) {
1388
+ return this.callApi("/api/v1/persistent-game-data", {
1389
+ method: "POST",
1390
+ headers: {
1391
+ "Content-Type": "application/json"
1392
+ },
1393
+ body: JSON.stringify({ game_id: gameId, key, value })
1394
+ });
1395
+ }
1396
+ async batchSaveGameData(gameId, items) {
1397
+ return this.callApi(
1398
+ "/api/v1/persistent-game-data/batch",
1399
+ {
1400
+ method: "POST",
1401
+ headers: {
1402
+ "Content-Type": "application/json"
1403
+ },
1404
+ body: JSON.stringify({ game_id: gameId, items })
1405
+ }
1406
+ );
1407
+ }
1408
+ async getGameData(gameId, key) {
1409
+ try {
1410
+ const params = new URLSearchParams({ game_id: gameId, key });
1411
+ const response = await this.callApi(
1412
+ `/api/v1/persistent-game-data?${params}`
1413
+ );
1414
+ return response.data;
1415
+ } catch (error) {
1416
+ if (error instanceof Error && error.message.includes("404")) {
1417
+ return null;
1418
+ }
1419
+ throw error;
1420
+ }
1421
+ }
1422
+ async getMultipleGameData(gameId, keys) {
1423
+ const params = new URLSearchParams({ game_id: gameId });
1424
+ keys.forEach((key) => params.append("keys", key));
1425
+ const response = await this.callApi(
1426
+ `/api/v1/persistent-game-data?${params}`
1427
+ );
1428
+ return response.data;
1429
+ }
1430
+ async deleteGameData(gameId, key) {
1431
+ try {
1432
+ const params = new URLSearchParams({ game_id: gameId, key });
1433
+ const response = await this.callApi(
1434
+ `/api/v1/persistent-game-data?${params}`,
1435
+ { method: "DELETE" }
1436
+ );
1437
+ return response.success && response.deleted_count > 0;
1438
+ } catch (error) {
1439
+ if (error instanceof Error && error.message.includes("404")) {
1440
+ return false;
1441
+ }
1442
+ throw error;
1443
+ }
1444
+ }
1445
+ async deleteMultipleGameData(gameId, keys) {
1446
+ const params = new URLSearchParams({ game_id: gameId });
1447
+ keys.forEach((key) => params.append("keys", key));
1448
+ const response = await this.callApi(
1449
+ `/api/v1/persistent-game-data?${params}`,
1450
+ { method: "DELETE" }
1451
+ );
1452
+ return response.deleted_count;
1453
+ }
1454
+ };
1455
+ var LocalStorageAdapter = class {
1456
+ constructor(getUserId) {
1457
+ this.getUserId = getUserId;
1458
+ }
1459
+ storagePrefix = "hyve_game_data";
1460
+ getStorageKey(gameId, key) {
1461
+ return `${this.storagePrefix}:${gameId}:${key}`;
1462
+ }
1463
+ async saveGameData(gameId, key, value) {
1464
+ try {
1465
+ const storageKey = this.getStorageKey(gameId, key);
1466
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1467
+ const existing = localStorage.getItem(storageKey);
1468
+ const createdAt = existing ? JSON.parse(existing).created_at || now : now;
1469
+ const item = { key, value, created_at: createdAt, updated_at: now };
1470
+ localStorage.setItem(storageKey, JSON.stringify(item));
1471
+ return { success: true, message: "Data saved successfully" };
1472
+ } catch (error) {
1473
+ throw new Error(`Failed to save: ${error instanceof Error ? error.message : "Unknown"}`);
1474
+ }
1475
+ }
1476
+ async batchSaveGameData(gameId, items) {
1477
+ for (const item of items) {
1478
+ await this.saveGameData(gameId, item.key, item.value);
1479
+ }
1480
+ return { success: true, message: `${items.length} items saved` };
1481
+ }
1482
+ async getGameData(gameId, key) {
1483
+ const data = localStorage.getItem(this.getStorageKey(gameId, key));
1484
+ return data ? JSON.parse(data) : null;
1485
+ }
1486
+ async getMultipleGameData(gameId, keys) {
1487
+ const items = [];
1488
+ for (const key of keys) {
1489
+ const item = await this.getGameData(gameId, key);
1490
+ if (item) items.push(item);
1491
+ }
1492
+ return items;
1493
+ }
1494
+ async deleteGameData(gameId, key) {
1495
+ const storageKey = this.getStorageKey(gameId, key);
1496
+ const exists = localStorage.getItem(storageKey) !== null;
1497
+ if (exists) localStorage.removeItem(storageKey);
1498
+ return exists;
1499
+ }
1500
+ async deleteMultipleGameData(gameId, keys) {
1501
+ let count = 0;
1502
+ for (const key of keys) {
1503
+ if (await this.deleteGameData(gameId, key)) count++;
1504
+ }
1505
+ return count;
1506
+ }
1507
+ };
1508
+
1509
+ // src/core/client.ts
1510
+ function determineEnvironmentFromParentUrl() {
1511
+ try {
1512
+ let parentUrl = "";
1513
+ if (window !== window.top) {
1514
+ try {
1515
+ parentUrl = window.parent.location.href;
1516
+ } catch (e) {
1517
+ parentUrl = document.referrer;
1518
+ }
1519
+ } else {
1520
+ parentUrl = window.location.href;
1521
+ }
1522
+ logger.debug("Detected parent URL:", parentUrl);
1523
+ if (parentUrl.includes("marvin.dev.hyve.gg") || parentUrl.includes("dev.hyve.gg")) {
1524
+ logger.info("Environment detected: dev (from parent URL)");
1525
+ return true;
1526
+ }
1527
+ if (parentUrl.includes("marvin.hyve.gg") || parentUrl.includes("hyve.gg")) {
1528
+ logger.info("Environment detected: prod (from parent URL)");
1529
+ return false;
1530
+ }
1531
+ logger.info("Environment unknown, defaulting to dev");
1532
+ return true;
1533
+ } catch (error) {
1534
+ logger.warn("Failed to determine environment from parent URL:", error);
1535
+ return true;
1536
+ }
1537
+ }
1538
+ var HyveClient = class {
1539
+ telemetryConfig;
1540
+ apiBaseUrl;
1541
+ sessionId;
1542
+ userId = null;
1543
+ jwtToken = null;
1544
+ gameId = null;
1545
+ adsService;
1546
+ playgamaService = null;
1547
+ playgamaInitPromise = null;
1548
+ crazyGamesService = null;
1549
+ crazyGamesInitPromise = null;
1550
+ billingService;
1551
+ storageMode;
1552
+ cloudStorageAdapter;
1553
+ localStorageAdapter;
1554
+ /**
1555
+ * Creates a new HyveClient instance
1556
+ * @param config Optional configuration including telemetry and ads
1557
+ */
1558
+ constructor(config) {
1559
+ const isDev = config?.isDev !== void 0 ? config.isDev : determineEnvironmentFromParentUrl();
1560
+ this.telemetryConfig = {
1561
+ isDev,
1562
+ ...config
1563
+ };
1564
+ if (this.telemetryConfig.apiBaseUrl) {
1565
+ this.apiBaseUrl = this.telemetryConfig.apiBaseUrl;
1566
+ } else {
1567
+ this.apiBaseUrl = this.telemetryConfig.isDev ? "https://product-api.dev.hyve.gg" : "https://product-api.prod.hyve.gg";
1568
+ }
1569
+ this.sessionId = generateUUID();
1570
+ this.adsService = new AdsService();
1571
+ if (config?.ads) {
1572
+ this.adsService.configure(config.ads);
1573
+ }
1574
+ if (typeof window !== "undefined" && PlaygamaService.isPlaygamaDomain()) {
1575
+ this.playgamaService = new PlaygamaService();
1576
+ this.playgamaInitPromise = this.playgamaService.initialize().then((success) => {
1577
+ logger.info("Playgama Bridge initialized:", success);
1578
+ return success;
1579
+ });
1580
+ }
1581
+ if (typeof window !== "undefined" && CrazyGamesService.isCrazyGamesDomain()) {
1582
+ this.crazyGamesService = new CrazyGamesService();
1583
+ this.crazyGamesInitPromise = this.crazyGamesService.initialize().then((success) => {
1584
+ logger.info("CrazyGames SDK initialized:", success);
1585
+ return success;
1586
+ });
1587
+ }
1588
+ this.storageMode = config?.storageMode || "cloud";
1589
+ this.cloudStorageAdapter = new CloudStorageAdapter(
1590
+ (endpoint, options) => this.callApi(endpoint, options)
1591
+ );
1592
+ this.localStorageAdapter = new LocalStorageAdapter(() => this.getUserId());
1593
+ if (typeof window !== "undefined") {
1594
+ this._parseUrlAuth();
1595
+ }
1596
+ const billingConfig = {
1597
+ checkoutUrl: this.apiBaseUrl,
1598
+ userId: this.userId ?? void 0,
1599
+ gameId: this.gameId ? Number(this.gameId) : void 0,
1600
+ ...config?.billing
1601
+ };
1602
+ this.billingService = new BillingService(billingConfig);
1603
+ const envSource = config?.isDev !== void 0 ? "explicit config" : "auto-detected from parent URL";
1604
+ logger.info("==========================================");
1605
+ logger.info("HyveClient Initialized");
1606
+ logger.info("==========================================");
1607
+ logger.info("Session ID:", this.sessionId);
1608
+ logger.info(
1609
+ "Environment:",
1610
+ this.telemetryConfig.isDev ? "DEVELOPMENT" : "PRODUCTION",
1611
+ `(${envSource})`
1612
+ );
1613
+ logger.info("API Base URL:", this.apiBaseUrl);
1614
+ logger.info("Playgama platform:", this.playgamaService !== null);
1615
+ logger.info("CrazyGames platform:", this.crazyGamesService !== null);
1616
+ logger.info(
1617
+ "Billing configured:",
1618
+ !!config?.billing && Object.keys(config.billing).length > 0
1619
+ );
1620
+ logger.info("Storage mode:", this.storageMode);
1621
+ logger.info("Authenticated:", this.jwtToken !== null);
1622
+ logger.debug("Config:", {
1623
+ isDev: this.telemetryConfig.isDev,
1624
+ hasCustomApiUrl: !!config?.apiBaseUrl,
1625
+ billingConfigured: !!config?.billing && Object.keys(config.billing).length > 0,
1626
+ storageMode: this.storageMode
1627
+ });
1628
+ logger.info("==========================================");
1629
+ }
1630
+ /**
1631
+ * Parses JWT and game ID from the current window URL and stores them on the client.
1632
+ * Called automatically during construction.
1633
+ */
1634
+ _parseUrlAuth(urlParams) {
1635
+ try {
1636
+ const params = urlParams ? parseUrlParams(urlParams) : parseUrlParams(window.location.search);
1637
+ if (params.hyveAccess) {
1638
+ this.jwtToken = params.hyveAccess;
1639
+ logger.info("JWT token extracted from hyve-access parameter");
1640
+ const userId = extractUserIdFromJwt(this.jwtToken);
1641
+ if (userId) {
1642
+ this.userId = userId;
1643
+ logger.info("User ID extracted from JWT:", userId);
1644
+ }
1645
+ if (!this.gameId) {
1646
+ const gameIdFromJwt = extractGameIdFromJwt(this.jwtToken);
1647
+ if (gameIdFromJwt !== null) {
1648
+ this.gameId = gameIdFromJwt.toString();
1649
+ logger.info("Game ID extracted from JWT:", gameIdFromJwt);
1650
+ }
1651
+ }
1652
+ }
1653
+ if (params.gameId) {
1654
+ this.gameId = params.gameId;
1655
+ logger.info("Game ID extracted from game-id parameter:", this.gameId);
1656
+ }
1657
+ if (this.jwtToken) {
1658
+ logger.info("Authentication successful via JWT");
1659
+ } else {
1660
+ logger.info("No hyve-access JWT token in URL \u2014 unauthenticated");
1661
+ }
1662
+ } catch (error) {
1663
+ logger.error("Error parsing URL auth:", error);
1664
+ }
1665
+ }
1666
+ /**
1667
+ * Sends a user-level telemetry event using JWT authentication
1668
+ * Requires JWT token, authenticated user, and game ID from URL parameters
1669
+ * @param eventLocation Location where the event occurred
1670
+ * @param eventCategory Main category of the event
1671
+ * @param eventAction Primary action taken
1672
+ * @param eventSubCategory Optional sub-category
1673
+ * @param eventSubAction Optional sub-action
1674
+ * @param eventDetails Optional event details (object or JSON string)
1675
+ * @param platformId Optional platform identifier
1676
+ * @returns Promise resolving to boolean indicating success
1677
+ */
1678
+ async sendTelemetry(eventLocation, eventCategory, eventAction, eventSubCategory, eventSubAction, eventDetails, platformId) {
1679
+ if (!this.jwtToken) {
1680
+ logger.error("JWT token required. Ensure hyve-access and game-id are present in the URL.");
1681
+ return false;
1682
+ }
1683
+ if (!this.gameId) {
1684
+ logger.error("Game ID required. Ensure game-id URL parameter is set.");
1685
+ return false;
1686
+ }
1687
+ try {
1688
+ if (eventDetails) {
1689
+ try {
1690
+ if (typeof eventDetails === "string") {
1691
+ JSON.parse(eventDetails);
1692
+ } else if (typeof eventDetails === "object") {
1693
+ JSON.stringify(eventDetails);
1694
+ }
1695
+ } catch (validationError) {
1696
+ logger.error("Invalid JSON in eventDetails:", validationError);
1697
+ logger.error("eventDetails value:", eventDetails);
1698
+ return false;
1699
+ }
1700
+ }
1701
+ const toJsonString = (data) => {
1702
+ if (!data) return null;
1703
+ return typeof data === "string" ? data : JSON.stringify(data);
1704
+ };
1705
+ const telemetryEvent = {
1706
+ game_id: this.gameId,
1707
+ session_id: this.sessionId,
1708
+ platform_id: platformId || null,
1709
+ event_location: eventLocation,
1710
+ event_category: eventCategory,
1711
+ event_action: eventAction,
1712
+ event_sub_category: eventSubCategory || null,
1713
+ event_sub_action: eventSubAction || null,
1714
+ event_details: toJsonString(eventDetails)
1715
+ };
1716
+ logger.debug("Sending telemetry event:", telemetryEvent);
1717
+ const telemetryUrl = `${this.apiBaseUrl}/api/v1/analytics/send`;
1718
+ const response = await fetch(telemetryUrl, {
1719
+ method: "POST",
1720
+ headers: {
1721
+ "Content-Type": "application/json",
1722
+ Authorization: `Bearer ${this.jwtToken}`
1723
+ },
1724
+ body: JSON.stringify(telemetryEvent)
1725
+ });
1726
+ if (response.ok) {
1727
+ logger.info("Telemetry event sent successfully:", response.status);
1728
+ return true;
1729
+ } else {
1730
+ const errorText = await response.text();
1731
+ logger.error(
1732
+ "Failed to send telemetry event:",
1733
+ response.status,
1734
+ errorText
1735
+ );
1736
+ return false;
1737
+ }
1738
+ } catch (error) {
1739
+ logger.error("Error sending telemetry event:", error);
1740
+ return false;
1741
+ }
1742
+ }
1743
+ /**
1744
+ * Makes an authenticated API call using the JWT token
1745
+ * @param endpoint API endpoint path (will be appended to base URL)
1746
+ * @param options Fetch options (method, body, etc.)
1747
+ * @returns Promise resolving to the API response
1748
+ */
1749
+ async callApi(endpoint, options = {}) {
1750
+ if (!this.jwtToken) {
1751
+ throw new Error(
1752
+ "No JWT token available. Ensure hyve-access and game-id are present in the URL."
1753
+ );
1754
+ }
1755
+ try {
1756
+ const url = `${this.apiBaseUrl}${endpoint.startsWith("/") ? endpoint : `/${endpoint}`}`;
1757
+ logger.debug("Making API call to:", url);
1758
+ const response = await fetch(url, {
1759
+ ...options,
1760
+ headers: {
1761
+ "Content-Type": "application/json",
1762
+ Authorization: `Bearer ${this.jwtToken}`,
1763
+ ...options.headers
1764
+ }
1765
+ });
1766
+ if (!response.ok) {
1767
+ const errorText = await response.text();
1768
+ throw new Error(`API request failed: ${response.status} ${errorText}`);
1769
+ }
1770
+ return await response.json();
1771
+ } catch (error) {
1772
+ logger.error("API call failed:", error);
1773
+ throw error;
1774
+ }
1775
+ }
1776
+ /**
1777
+ * Gets the user's inventory
1778
+ * @returns Promise resolving to the user's inventory
1779
+ */
1780
+ async getInventory() {
1781
+ return this.callApi("/api/v1/inventory");
1782
+ }
1783
+ /**
1784
+ * Gets a specific inventory item by ID
1785
+ * @param itemId The inventory item ID
1786
+ * @returns Promise resolving to the inventory item details
1787
+ */
1788
+ async getInventoryItem(itemId) {
1789
+ return this.callApi(`/api/v1/inventory/${itemId}`);
1790
+ }
1791
+ /**
1792
+ * Updates the telemetry configuration
1793
+ * @param config New telemetry configuration
1794
+ */
1795
+ updateTelemetryConfig(config) {
1796
+ this.telemetryConfig = {
1797
+ ...this.telemetryConfig,
1798
+ ...config
1799
+ };
1800
+ if (config.apiBaseUrl !== void 0) {
1801
+ this.apiBaseUrl = config.apiBaseUrl;
1802
+ } else if (config.isDev !== void 0) {
1803
+ this.apiBaseUrl = config.isDev ? "https://product-api.dev.hyve.gg" : "https://product-api.prod.hyve.gg";
1804
+ }
1805
+ logger.info("Config updated");
1806
+ logger.info("API Base URL:", this.apiBaseUrl);
1807
+ }
1808
+ /**
1809
+ * Gets the current user ID
1810
+ * @returns Current user ID or null if not authenticated
1811
+ */
1812
+ getUserId() {
1813
+ return this.userId;
1814
+ }
1815
+ /**
1816
+ * Gets the current session ID
1817
+ * @returns Current session ID
1818
+ */
1819
+ getSessionId() {
1820
+ return this.sessionId;
1821
+ }
1822
+ /**
1823
+ * Gets the current JWT token
1824
+ * @returns Current JWT token or null if not available
1825
+ */
1826
+ getJwtToken() {
1827
+ return this.jwtToken;
1828
+ }
1829
+ /**
1830
+ * Gets the current game ID
1831
+ * @returns Current game ID or null if not available
1832
+ */
1833
+ getGameId() {
1834
+ return this.gameId;
1835
+ }
1836
+ /**
1837
+ * Gets the API base URL
1838
+ * @returns API base URL
1839
+ */
1840
+ getApiBaseUrl() {
1841
+ return this.apiBaseUrl;
1842
+ }
1843
+ /**
1844
+ * Checks if user is authenticated
1845
+ * @returns Boolean indicating authentication status
1846
+ */
1847
+ isUserAuthenticated() {
1848
+ return this.userId !== null;
1849
+ }
1850
+ /**
1851
+ * Checks if JWT token is available
1852
+ * @returns Boolean indicating if JWT token is present
1853
+ */
1854
+ hasJwtToken() {
1855
+ return this.jwtToken !== null;
1856
+ }
1857
+ /**
1858
+ * Logs out the current user
1859
+ */
1860
+ logout() {
1861
+ this.userId = null;
1862
+ this.jwtToken = null;
1863
+ this.gameId = null;
1864
+ logger.info("User logged out");
1865
+ }
1866
+ /**
1867
+ * Resets the client state
1868
+ */
1869
+ reset() {
1870
+ this.logout();
1871
+ this.sessionId = generateUUID();
1872
+ logger.info("Client reset with new sessionId:", this.sessionId);
1873
+ }
1874
+ /**
1875
+ * Get the storage adapter based on mode
1876
+ * @param mode Storage mode override (cloud or local)
1877
+ */
1878
+ getStorageAdapter(mode) {
1879
+ const storageMode = mode || this.storageMode;
1880
+ return storageMode === "local" ? this.localStorageAdapter : this.cloudStorageAdapter;
1881
+ }
1882
+ /**
1883
+ * Returns the current game ID or throws if not available.
1884
+ */
1885
+ requireGameId() {
1886
+ const gameId = this.requireGameId();
1887
+ return gameId;
1888
+ }
1889
+ /**
1890
+ * Save persistent game data
1891
+ * @param key Data key
1892
+ * @param value Data value (any JSON-serializable value)
1893
+ * @param storage Storage mode override ('cloud' or 'local')
1894
+ * @returns Promise resolving to save response
1895
+ */
1896
+ async saveGameData(key, value, storage) {
1897
+ const gameId = this.requireGameId();
1898
+ const storageMode = storage || this.storageMode;
1899
+ logger.debug(`Saving game data to ${storageMode}: ${gameId}/${key}`);
1900
+ const adapter = this.getStorageAdapter(storage);
1901
+ const response = await adapter.saveGameData(gameId, key, value);
1902
+ logger.info(`Game data saved successfully to ${storageMode}: ${gameId}/${key}`);
1903
+ return response;
1904
+ }
1905
+ /**
1906
+ * Batch save persistent game data
1907
+ * @param items Array of key-value pairs to save
1908
+ * @param storage Storage mode override ('cloud' or 'local')
1909
+ * @returns Promise resolving to save response
1910
+ */
1911
+ async batchSaveGameData(items, storage) {
1912
+ const gameId = this.requireGameId();
1913
+ const storageMode = storage || this.storageMode;
1914
+ logger.debug(`Batch saving ${items.length} game data entries to ${storageMode} for game: ${gameId}`);
1915
+ const adapter = this.getStorageAdapter(storage);
1916
+ const response = await adapter.batchSaveGameData(gameId, items);
1917
+ logger.info(`Batch saved ${items.length} game data entries successfully to ${storageMode}`);
1918
+ return response;
1919
+ }
1920
+ /**
1921
+ * Get persistent game data
1922
+ * @param key Data key
1923
+ * @param storage Storage mode override ('cloud' or 'local')
1924
+ * @returns Promise resolving to game data item or null if not found
1925
+ */
1926
+ async getGameData(key, storage) {
1927
+ const gameId = this.requireGameId();
1928
+ const storageMode = storage || this.storageMode;
1929
+ logger.debug(`Getting game data from ${storageMode}: ${gameId}/${key}`);
1930
+ const adapter = this.getStorageAdapter(storage);
1931
+ const data = await adapter.getGameData(gameId, key);
1932
+ if (data) {
1933
+ logger.info(`Game data retrieved successfully from ${storageMode}: ${gameId}/${key}`);
1934
+ } else {
1935
+ logger.debug(`Game data not found in ${storageMode}: ${key}`);
1936
+ }
1937
+ return data;
1938
+ }
1939
+ /**
1940
+ * Get multiple persistent game data entries
1941
+ * @param keys Array of data keys
1942
+ * @param storage Storage mode override ('cloud' or 'local')
1943
+ * @returns Promise resolving to array of game data items
1944
+ */
1945
+ async getMultipleGameData(keys, storage) {
1946
+ const gameId = this.requireGameId();
1947
+ const storageMode = storage || this.storageMode;
1948
+ logger.debug(`Getting ${keys.length} game data entries from ${storageMode} for game: ${gameId}`);
1949
+ const adapter = this.getStorageAdapter(storage);
1950
+ const data = await adapter.getMultipleGameData(gameId, keys);
1951
+ logger.info(`Retrieved ${data.length} game data entries from ${storageMode}`);
1952
+ return data;
1953
+ }
1954
+ /**
1955
+ * Delete persistent game data
1956
+ * @param key Data key
1957
+ * @param storage Storage mode override ('cloud' or 'local')
1958
+ * @returns Promise resolving to boolean indicating if data was deleted
1959
+ */
1960
+ async deleteGameData(key, storage) {
1961
+ const gameId = this.requireGameId();
1962
+ const storageMode = storage || this.storageMode;
1963
+ logger.debug(`Deleting game data from ${storageMode}: ${gameId}/${key}`);
1964
+ const adapter = this.getStorageAdapter(storage);
1965
+ const deleted = await adapter.deleteGameData(gameId, key);
1966
+ if (deleted) {
1967
+ logger.info(`Game data deleted successfully from ${storageMode}: ${gameId}/${key}`);
1968
+ } else {
1969
+ logger.debug(`Game data not found in ${storageMode}: ${key}`);
1970
+ }
1971
+ return deleted;
1972
+ }
1973
+ /**
1974
+ * Delete multiple persistent game data entries
1975
+ * @param keys Array of data keys
1976
+ * @param storage Storage mode override ('cloud' or 'local')
1977
+ * @returns Promise resolving to number of entries deleted
1978
+ */
1979
+ async deleteMultipleGameData(keys, storage) {
1980
+ const gameId = this.requireGameId();
1981
+ const storageMode = storage || this.storageMode;
1982
+ logger.debug(`Deleting ${keys.length} game data entries from ${storageMode} for game: ${gameId}`);
1983
+ const adapter = this.getStorageAdapter(storage);
1984
+ const deletedCount = await adapter.deleteMultipleGameData(gameId, keys);
1985
+ logger.info(`Deleted ${deletedCount} game data entries from ${storageMode}`);
1986
+ return deletedCount;
1987
+ }
1988
+ /**
1989
+ * Configure storage mode
1990
+ * @param mode Storage mode ('cloud' or 'local')
1991
+ */
1992
+ configureStorage(mode) {
1993
+ this.storageMode = mode;
1994
+ logger.info("Storage mode updated to:", mode);
1995
+ }
1996
+ /**
1997
+ * Get current storage mode
1998
+ * @returns Current storage mode
1999
+ */
2000
+ getStorageMode() {
2001
+ return this.storageMode;
2002
+ }
2003
+ /**
2004
+ * Configure ads service
2005
+ * @param config Ads configuration
2006
+ */
2007
+ configureAds(config) {
2008
+ this.adsService.configure(config);
2009
+ logger.info("Ads configuration updated");
2010
+ }
2011
+ /**
2012
+ * Show an ad
2013
+ * @param type Type of ad to show ('rewarded', 'interstitial', or 'preroll')
2014
+ * @returns Promise resolving to ad result
2015
+ */
2016
+ async showAd(type) {
2017
+ if (this.crazyGamesService) {
2018
+ if (this.crazyGamesInitPromise) {
2019
+ await this.crazyGamesInitPromise;
2020
+ }
2021
+ if (this.crazyGamesService.isInitialized()) {
2022
+ const { onBeforeAd, onAfterAd, onRewardEarned } = this.adsService.getCallbacks();
2023
+ if (type === "rewarded") {
2024
+ return this.crazyGamesService.showRewarded({
2025
+ onBeforeAd: () => onBeforeAd("rewarded"),
2026
+ onAfterAd: () => onAfterAd("rewarded"),
2027
+ onRewardEarned
2028
+ });
2029
+ }
2030
+ return this.crazyGamesService.showInterstitial({
2031
+ onBeforeAd: () => onBeforeAd(type),
2032
+ onAfterAd: () => onAfterAd(type)
2033
+ });
2034
+ }
2035
+ }
2036
+ if (this.playgamaService) {
2037
+ if (this.playgamaInitPromise) {
2038
+ await this.playgamaInitPromise;
2039
+ }
2040
+ if (this.playgamaService.isInitialized()) {
2041
+ const { onBeforeAd, onAfterAd, onRewardEarned } = this.adsService.getCallbacks();
2042
+ if (type === "rewarded") {
2043
+ return this.playgamaService.showRewarded({
2044
+ onBeforeAd: () => onBeforeAd("rewarded"),
2045
+ onAfterAd: () => onAfterAd("rewarded"),
2046
+ onRewardEarned
2047
+ });
2048
+ }
2049
+ return this.playgamaService.showInterstitial({
2050
+ onBeforeAd: () => onBeforeAd(type),
2051
+ onAfterAd: () => onAfterAd(type)
2052
+ });
2053
+ }
2054
+ }
2055
+ return this.adsService.show(type);
2056
+ }
2057
+ /**
2058
+ * Notifies CrazyGames that gameplay has started.
2059
+ * No-op on other platforms.
2060
+ */
2061
+ async gameplayStart() {
2062
+ if (this.crazyGamesService) {
2063
+ if (this.crazyGamesInitPromise) await this.crazyGamesInitPromise;
2064
+ this.crazyGamesService.gameplayStart();
2065
+ }
2066
+ }
2067
+ /**
2068
+ * Notifies CrazyGames that gameplay has stopped.
2069
+ * No-op on other platforms.
2070
+ */
2071
+ async gameplayStop() {
2072
+ if (this.crazyGamesService) {
2073
+ if (this.crazyGamesInitPromise) await this.crazyGamesInitPromise;
2074
+ this.crazyGamesService.gameplayStop();
2075
+ }
2076
+ }
2077
+ /**
2078
+ * Triggers a celebration effect on the CrazyGames website for significant achievements.
2079
+ * No-op on other platforms.
2080
+ */
2081
+ async happytime() {
2082
+ if (this.crazyGamesService) {
2083
+ if (this.crazyGamesInitPromise) await this.crazyGamesInitPromise;
2084
+ this.crazyGamesService.happytime();
2085
+ }
2086
+ }
2087
+ /**
2088
+ * Check if ads are ready to show
2089
+ * @returns Boolean indicating if ads have initialized successfully
2090
+ */
2091
+ areAdsReady() {
2092
+ return this.adsService.isReady();
2093
+ }
2094
+ /**
2095
+ * Get the billing platform
2096
+ * @returns Current billing platform
2097
+ */
2098
+ getBillingPlatform() {
2099
+ return this.billingService.getPlatform();
2100
+ }
2101
+ /**
2102
+ * Check if billing is available
2103
+ * @returns Boolean indicating if billing is available
2104
+ */
2105
+ isBillingAvailable() {
2106
+ return this.billingService.isAvailable();
2107
+ }
2108
+ /**
2109
+ * Get available billing products
2110
+ * @returns Promise resolving to array of products
2111
+ */
2112
+ async getBillingProducts() {
2113
+ return await this.billingService.getProducts();
2114
+ }
2115
+ /**
2116
+ * Purchase a product
2117
+ * @param productId Product ID to purchase
2118
+ * @param options Optional purchase options (e.g., elementId for web)
2119
+ * @returns Promise resolving to purchase result
2120
+ */
2121
+ async purchaseProduct(productId, options) {
2122
+ return await this.billingService.purchase(productId, options);
2123
+ }
2124
+ /**
2125
+ * Set callback for successful purchases
2126
+ * @param callback Function to call on purchase completion
2127
+ */
2128
+ onPurchaseComplete(callback) {
2129
+ this.billingService.onPurchaseComplete(callback);
2130
+ }
2131
+ /**
2132
+ * Set callback for failed purchases
2133
+ * @param callback Function to call on purchase error
2134
+ */
2135
+ onPurchaseError(callback) {
2136
+ this.billingService.onPurchaseError(callback);
2137
+ }
2138
+ /**
2139
+ * Unmount Stripe checkout element
2140
+ */
2141
+ unmountBillingCheckout() {
2142
+ this.billingService.unmountCheckoutElement();
2143
+ }
2144
+ };
2145
+
2146
+ // src/react.tsx
2147
+ var import_jsx_runtime = require("react/jsx-runtime");
2148
+ var HyveSdkContext = (0, import_react.createContext)(null);
2149
+ function HyveSdkProvider({
2150
+ client,
2151
+ config,
2152
+ children
2153
+ }) {
2154
+ const instanceRef = (0, import_react.useRef)(null);
2155
+ if (client) {
2156
+ instanceRef.current = client;
2157
+ } else if (!instanceRef.current) {
2158
+ instanceRef.current = new HyveClient(config);
2159
+ }
2160
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HyveSdkContext.Provider, { value: instanceRef.current, children });
2161
+ }
2162
+ function useHyveSdk() {
2163
+ const client = (0, import_react.useContext)(HyveSdkContext);
2164
+ if (!client) {
2165
+ throw new Error("useHyveSdk must be used within a HyveSdkProvider");
2166
+ }
2167
+ return client;
2168
+ }
2169
+ // Annotate the CommonJS export names for ESM import in node:
2170
+ 0 && (module.exports = {
2171
+ HyveSdkProvider,
2172
+ useHyveSdk
2173
+ });