@hyve-sdk/js 1.3.2 → 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -797,7 +797,568 @@ var AdsService = class {
797
797
  }
798
798
  };
799
799
 
800
+ // src/services/billing.ts
801
+ var BillingPlatform = /* @__PURE__ */ ((BillingPlatform3) => {
802
+ BillingPlatform3["WEB"] = "web";
803
+ BillingPlatform3["NATIVE"] = "native";
804
+ BillingPlatform3["UNKNOWN"] = "unknown";
805
+ return BillingPlatform3;
806
+ })(BillingPlatform || {});
807
+ var BillingService = class {
808
+ config;
809
+ platform = "unknown" /* UNKNOWN */;
810
+ isInitialized = false;
811
+ nativeAvailable = false;
812
+ products = [];
813
+ // Stripe instance for web payments
814
+ stripe = null;
815
+ checkoutElement = null;
816
+ // Callbacks for purchase events
817
+ onPurchaseCompleteCallback;
818
+ onPurchaseErrorCallback;
819
+ // Callback for logging (so external UI can capture logs)
820
+ onLogCallback;
821
+ constructor(config) {
822
+ this.config = config;
823
+ this.detectPlatform();
824
+ }
825
+ /**
826
+ * Register a callback to receive all internal logs
827
+ * Useful for displaying logs in a UI
828
+ */
829
+ onLog(callback) {
830
+ this.onLogCallback = callback;
831
+ }
832
+ /**
833
+ * Internal logging method that calls both logger and custom callback
834
+ */
835
+ log(level, message, data) {
836
+ const consoleMethod = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
837
+ if (data !== void 0) {
838
+ consoleMethod(message, data);
839
+ } else {
840
+ consoleMethod(message);
841
+ }
842
+ if (this.onLogCallback) {
843
+ this.onLogCallback(level, message, data);
844
+ }
845
+ }
846
+ /**
847
+ * Detects if running on web or native platform
848
+ */
849
+ detectPlatform() {
850
+ const isNative = NativeBridge.isNativeContext();
851
+ const hasWindow = typeof window !== "undefined";
852
+ if (isNative) {
853
+ this.platform = "native" /* NATIVE */;
854
+ this.log("info", "[BillingService] Platform: NATIVE");
855
+ } else if (hasWindow) {
856
+ this.platform = "web" /* WEB */;
857
+ this.log("info", "[BillingService] Platform: WEB");
858
+ } else {
859
+ this.platform = "unknown" /* UNKNOWN */;
860
+ this.log("warn", "[BillingService] Platform: UNKNOWN");
861
+ }
862
+ }
863
+ /**
864
+ * Get the current platform
865
+ */
866
+ getPlatform() {
867
+ return this.platform;
868
+ }
869
+ /**
870
+ * Initialize the billing service
871
+ * Must be called before using any other methods
872
+ */
873
+ async initialize() {
874
+ if (this.isInitialized) {
875
+ return true;
876
+ }
877
+ this.log("info", `[BillingService] Initializing for ${this.platform} platform...`);
878
+ try {
879
+ if (this.platform === "native" /* NATIVE */) {
880
+ return await this.initializeNative();
881
+ } else if (this.platform === "web" /* WEB */) {
882
+ return await this.initializeWeb();
883
+ }
884
+ this.log("error", "[BillingService] Cannot initialize: unknown platform");
885
+ return false;
886
+ } catch (error) {
887
+ this.log("error", "[BillingService] Initialization failed:", error?.message);
888
+ return false;
889
+ }
890
+ }
891
+ /**
892
+ * Initialize native billing
893
+ */
894
+ async initializeNative() {
895
+ return new Promise((resolve) => {
896
+ try {
897
+ NativeBridge.initialize();
898
+ NativeBridge.on(
899
+ "PURCHASE_COMPLETE" /* PURCHASE_COMPLETE */,
900
+ (payload) => {
901
+ this.log("info", "[BillingService] Purchase complete:", payload.productId);
902
+ const result = {
903
+ success: true,
904
+ productId: payload.productId,
905
+ transactionId: payload.transactionId,
906
+ transactionDate: payload.transactionDate
907
+ };
908
+ if (this.onPurchaseCompleteCallback) {
909
+ this.onPurchaseCompleteCallback(result);
910
+ }
911
+ }
912
+ );
913
+ NativeBridge.on(
914
+ "PURCHASE_ERROR" /* PURCHASE_ERROR */,
915
+ (payload) => {
916
+ this.log("error", "[BillingService] Purchase error:", payload.message);
917
+ const result = {
918
+ success: false,
919
+ productId: payload.productId || "unknown",
920
+ error: {
921
+ code: payload.code,
922
+ message: payload.message
923
+ }
924
+ };
925
+ if (this.onPurchaseErrorCallback) {
926
+ this.onPurchaseErrorCallback(result);
927
+ }
928
+ }
929
+ );
930
+ NativeBridge.on(
931
+ "IAP_AVAILABILITY_RESULT" /* IAP_AVAILABILITY_RESULT */,
932
+ (payload) => {
933
+ this.nativeAvailable = payload.available;
934
+ this.isInitialized = true;
935
+ this.log("info", `[BillingService] Native billing ${payload.available ? "available" : "unavailable"}`);
936
+ resolve(payload.available);
937
+ }
938
+ );
939
+ setTimeout(() => {
940
+ NativeBridge.checkIAPAvailability();
941
+ }, 100);
942
+ setTimeout(() => {
943
+ if (!this.isInitialized) {
944
+ this.log("warn", "[BillingService] Native initialization timeout");
945
+ this.isInitialized = true;
946
+ resolve(false);
947
+ }
948
+ }, 5e3);
949
+ } catch (error) {
950
+ this.log("error", "[BillingService] Native initialization failed:", error?.message);
951
+ this.isInitialized = true;
952
+ resolve(false);
953
+ }
954
+ });
955
+ }
956
+ /**
957
+ * Initialize web billing (Stripe)
958
+ */
959
+ async initializeWeb() {
960
+ if (!this.config.stripePublishableKey) {
961
+ this.log("error", "[BillingService] Stripe publishable key not provided");
962
+ return false;
963
+ }
964
+ try {
965
+ if (typeof window === "undefined") {
966
+ this.log("error", "[BillingService] Window is undefined (not in browser)");
967
+ return false;
968
+ }
969
+ if (!window.Stripe) {
970
+ await this.loadStripeScript();
971
+ }
972
+ if (window.Stripe) {
973
+ this.stripe = window.Stripe(this.config.stripePublishableKey);
974
+ this.isInitialized = true;
975
+ this.log("info", "[BillingService] Web billing initialized");
976
+ return true;
977
+ } else {
978
+ this.log("error", "[BillingService] Stripe not available after loading");
979
+ return false;
980
+ }
981
+ } catch (error) {
982
+ this.log("error", "[BillingService] Stripe initialization failed:", error?.message);
983
+ return false;
984
+ }
985
+ }
986
+ /**
987
+ * Load Stripe.js script dynamically
988
+ */
989
+ loadStripeScript() {
990
+ return new Promise((resolve, reject) => {
991
+ if (typeof window === "undefined") {
992
+ reject(new Error("Window is not defined"));
993
+ return;
994
+ }
995
+ if (window.Stripe) {
996
+ resolve();
997
+ return;
998
+ }
999
+ if (!document || !document.head) {
1000
+ reject(new Error("document is undefined"));
1001
+ return;
1002
+ }
1003
+ const script = document.createElement("script");
1004
+ script.src = "https://js.stripe.com/v3/";
1005
+ script.async = true;
1006
+ script.onload = () => {
1007
+ this.log("info", "[BillingService] Stripe.js loaded");
1008
+ resolve();
1009
+ };
1010
+ script.onerror = () => {
1011
+ this.log("error", "[BillingService] Failed to load Stripe.js");
1012
+ reject(new Error("Failed to load Stripe.js"));
1013
+ };
1014
+ try {
1015
+ document.head.appendChild(script);
1016
+ } catch (appendError) {
1017
+ reject(appendError);
1018
+ }
1019
+ });
1020
+ }
1021
+ /**
1022
+ * Check if billing is available
1023
+ */
1024
+ isAvailable() {
1025
+ if (!this.isInitialized) {
1026
+ return false;
1027
+ }
1028
+ if (this.platform === "native" /* NATIVE */) {
1029
+ return this.nativeAvailable;
1030
+ } else if (this.platform === "web" /* WEB */) {
1031
+ return !!this.config.stripePublishableKey;
1032
+ }
1033
+ return false;
1034
+ }
1035
+ /**
1036
+ * Get available products
1037
+ */
1038
+ async getProducts() {
1039
+ if (!this.isInitialized) {
1040
+ throw new Error("BillingService not initialized. Call initialize() first.");
1041
+ }
1042
+ if (this.platform === "native" /* NATIVE */) {
1043
+ return await this.getProductsNative();
1044
+ } else if (this.platform === "web" /* WEB */) {
1045
+ return await this.getProductsWeb();
1046
+ }
1047
+ throw new Error("Cannot get products: unknown platform");
1048
+ }
1049
+ /**
1050
+ * Get products from native IAP
1051
+ */
1052
+ async getProductsNative() {
1053
+ return new Promise((resolve, reject) => {
1054
+ if (!this.config.gameId || !this.config.checkoutUrl) {
1055
+ const error = new Error("gameId and checkoutUrl required for native purchases");
1056
+ this.log("error", "[BillingService]", error.message);
1057
+ reject(error);
1058
+ return;
1059
+ }
1060
+ const url = `${this.config.checkoutUrl}/get-native-packages?game_id=${this.config.gameId}`;
1061
+ fetch(url).then((response) => {
1062
+ if (!response.ok) {
1063
+ throw new Error(`Failed to fetch native products: ${response.status}`);
1064
+ }
1065
+ return response.json();
1066
+ }).then((data) => {
1067
+ if (!data.packages || !Array.isArray(data.packages)) {
1068
+ throw new Error("Invalid response format: missing packages array");
1069
+ }
1070
+ this.products = data.packages.map((pkg) => ({
1071
+ productId: pkg.productId,
1072
+ title: pkg.package_name,
1073
+ description: `${pkg.game_name} - ${pkg.package_name}`,
1074
+ price: pkg.price_cents / 100,
1075
+ localizedPrice: pkg.price_display,
1076
+ currency: "USD"
1077
+ }));
1078
+ this.log("info", `[BillingService] Fetched ${this.products.length} native products`);
1079
+ resolve(this.products);
1080
+ }).catch((error) => {
1081
+ this.log("error", "[BillingService] Failed to fetch native products:", error?.message);
1082
+ reject(error);
1083
+ });
1084
+ });
1085
+ }
1086
+ /**
1087
+ * Get products from web API (Stripe)
1088
+ */
1089
+ async getProductsWeb() {
1090
+ if (!this.config.checkoutUrl || !this.config.gameId) {
1091
+ const error = new Error("checkoutUrl and gameId required for web purchases");
1092
+ this.log("error", "[BillingService]", error.message);
1093
+ throw error;
1094
+ }
1095
+ try {
1096
+ const url = `${this.config.checkoutUrl}/get-packages?game_id=${this.config.gameId}`;
1097
+ const response = await fetch(url);
1098
+ if (!response.ok) {
1099
+ throw new Error(`Failed to fetch web products: ${response.status}`);
1100
+ }
1101
+ const data = await response.json();
1102
+ if (!data.packages || !Array.isArray(data.packages)) {
1103
+ throw new Error("Invalid response format: missing packages array");
1104
+ }
1105
+ this.products = data.packages.map((pkg) => ({
1106
+ productId: pkg.priceId || pkg.productId,
1107
+ // Prefer priceId for Stripe
1108
+ title: pkg.package_name,
1109
+ description: `${pkg.game_name} - ${pkg.package_name}`,
1110
+ price: pkg.price_cents / 100,
1111
+ localizedPrice: pkg.price_display,
1112
+ currency: "USD"
1113
+ }));
1114
+ this.log("info", `[BillingService] Fetched ${this.products.length} web products`);
1115
+ return this.products;
1116
+ } catch (error) {
1117
+ this.log("error", "[BillingService] Failed to fetch web products:", error?.message);
1118
+ throw error;
1119
+ }
1120
+ }
1121
+ /**
1122
+ * Purchase a product
1123
+ * @param productId - The product ID (priceId for web/Stripe, productId for native)
1124
+ * @param options - Optional purchase options
1125
+ * @param options.elementId - For web: DOM element ID to mount Stripe checkout (default: 'stripe-checkout-element')
1126
+ */
1127
+ async purchase(productId, options) {
1128
+ if (!this.isInitialized) {
1129
+ throw new Error("BillingService not initialized. Call initialize() first.");
1130
+ }
1131
+ if (!this.isAvailable()) {
1132
+ throw new Error("Billing is not available on this platform");
1133
+ }
1134
+ if (this.platform === "native" /* NATIVE */) {
1135
+ return await this.purchaseNative(productId);
1136
+ } else if (this.platform === "web" /* WEB */) {
1137
+ return await this.purchaseWeb(productId, options?.elementId);
1138
+ }
1139
+ throw new Error("Cannot purchase: unknown platform");
1140
+ }
1141
+ /**
1142
+ * Purchase via native IAP
1143
+ */
1144
+ async purchaseNative(productId) {
1145
+ return new Promise((resolve, reject) => {
1146
+ if (!this.config.userId) {
1147
+ reject(new Error("userId is required for native purchases"));
1148
+ return;
1149
+ }
1150
+ this.log("info", `[BillingService] Purchasing: ${productId}`);
1151
+ const previousCompleteCallback = this.onPurchaseCompleteCallback;
1152
+ const previousErrorCallback = this.onPurchaseErrorCallback;
1153
+ const cleanup = () => {
1154
+ this.onPurchaseCompleteCallback = previousCompleteCallback;
1155
+ this.onPurchaseErrorCallback = previousErrorCallback;
1156
+ };
1157
+ const completeHandler = (result) => {
1158
+ if (result.productId === productId) {
1159
+ cleanup();
1160
+ resolve(result);
1161
+ if (previousCompleteCallback) {
1162
+ previousCompleteCallback(result);
1163
+ }
1164
+ }
1165
+ };
1166
+ const errorHandler = (result) => {
1167
+ if (result.productId === productId) {
1168
+ cleanup();
1169
+ reject(new Error(result.error?.message || "Purchase failed"));
1170
+ if (previousErrorCallback) {
1171
+ previousErrorCallback(result);
1172
+ }
1173
+ }
1174
+ };
1175
+ this.onPurchaseCompleteCallback = completeHandler;
1176
+ this.onPurchaseErrorCallback = errorHandler;
1177
+ NativeBridge.purchase(productId, this.config.userId);
1178
+ setTimeout(() => {
1179
+ cleanup();
1180
+ reject(new Error("Purchase timeout"));
1181
+ }, 6e4);
1182
+ });
1183
+ }
1184
+ /**
1185
+ * Purchase via web (Stripe)
1186
+ * @param productId - The priceId (Stripe price ID) for web purchases
1187
+ * @param elementId - Optional DOM element ID to mount the checkout form (default: 'stripe-checkout-element')
1188
+ */
1189
+ async purchaseWeb(productId, elementId) {
1190
+ if (!this.config.userId || !this.config.checkoutUrl) {
1191
+ throw new Error("userId and checkoutUrl are required for web purchases");
1192
+ }
1193
+ if (!this.stripe) {
1194
+ throw new Error("Stripe not initialized. Call initialize() first.");
1195
+ }
1196
+ try {
1197
+ const returnUrl = typeof window !== "undefined" ? `${window.location.href}${window.location.href.includes("?") ? "&" : "?"}payment=complete` : void 0;
1198
+ const requestBody = {
1199
+ priceId: productId,
1200
+ // API expects priceId for Stripe
1201
+ userId: this.config.userId,
1202
+ gameId: this.config.gameId,
1203
+ embedded: true,
1204
+ // Enable embedded checkout
1205
+ return_url: returnUrl
1206
+ };
1207
+ const response = await fetch(`${this.config.checkoutUrl}/create-checkout-session`, {
1208
+ method: "POST",
1209
+ headers: {
1210
+ "Content-Type": "application/json"
1211
+ },
1212
+ body: JSON.stringify(requestBody)
1213
+ });
1214
+ if (!response.ok) {
1215
+ const errorText = await response.text();
1216
+ throw new Error(`Failed to create checkout session: ${response.status} - ${errorText}`);
1217
+ }
1218
+ const responseData = await response.json();
1219
+ const { client_secret, id } = responseData;
1220
+ if (!client_secret) {
1221
+ throw new Error("No client_secret returned from checkout session");
1222
+ }
1223
+ await this.mountCheckoutElement(client_secret, elementId || "stripe-checkout-element");
1224
+ this.log("info", `[BillingService] Checkout session created: ${id}`);
1225
+ return {
1226
+ success: true,
1227
+ productId,
1228
+ transactionId: id
1229
+ };
1230
+ } catch (error) {
1231
+ this.log("error", "[BillingService] Web purchase failed:", error?.message);
1232
+ return {
1233
+ success: false,
1234
+ productId,
1235
+ error: {
1236
+ code: "WEB_PURCHASE_FAILED",
1237
+ message: error.message || "Purchase failed"
1238
+ }
1239
+ };
1240
+ }
1241
+ }
1242
+ /**
1243
+ * Mount Stripe embedded checkout element to the DOM
1244
+ * @param clientSecret - The client secret from the checkout session
1245
+ * @param elementId - The ID of the DOM element to mount to
1246
+ */
1247
+ async mountCheckoutElement(clientSecret, elementId) {
1248
+ if (!this.stripe) {
1249
+ throw new Error("Stripe not initialized");
1250
+ }
1251
+ try {
1252
+ const container = document.getElementById(elementId);
1253
+ if (!container) {
1254
+ throw new Error(`Element with id "${elementId}" not found in the DOM`);
1255
+ }
1256
+ container.innerHTML = "";
1257
+ this.checkoutElement = await this.stripe.initEmbeddedCheckout({
1258
+ clientSecret
1259
+ });
1260
+ this.checkoutElement.mount(`#${elementId}`);
1261
+ this.setupCheckoutEventListeners();
1262
+ } catch (error) {
1263
+ this.log("error", "[BillingService] Failed to mount checkout:", error?.message);
1264
+ throw error;
1265
+ }
1266
+ }
1267
+ /**
1268
+ * Set up event listeners for checkout completion
1269
+ */
1270
+ setupCheckoutEventListeners() {
1271
+ if (typeof window !== "undefined") {
1272
+ const urlParams = new URLSearchParams(window.location.search);
1273
+ const paymentStatus = urlParams.get("payment");
1274
+ if (paymentStatus === "complete") {
1275
+ const sessionId = urlParams.get("session_id");
1276
+ if (this.onPurchaseCompleteCallback) {
1277
+ this.onPurchaseCompleteCallback({
1278
+ success: true,
1279
+ productId: "",
1280
+ // Would need to be tracked separately
1281
+ transactionId: sessionId || void 0
1282
+ });
1283
+ }
1284
+ this.log("info", "[BillingService] Payment completed");
1285
+ urlParams.delete("payment");
1286
+ urlParams.delete("session_id");
1287
+ const newUrl = `${window.location.pathname}${urlParams.toString() ? "?" + urlParams.toString() : ""}`;
1288
+ window.history.replaceState({}, "", newUrl);
1289
+ }
1290
+ }
1291
+ }
1292
+ /**
1293
+ * Unmount and destroy the checkout element
1294
+ */
1295
+ unmountCheckoutElement() {
1296
+ if (this.checkoutElement) {
1297
+ this.checkoutElement.unmount();
1298
+ this.checkoutElement = null;
1299
+ }
1300
+ }
1301
+ /**
1302
+ * Set callback for successful purchases
1303
+ */
1304
+ onPurchaseComplete(callback) {
1305
+ this.onPurchaseCompleteCallback = callback;
1306
+ }
1307
+ /**
1308
+ * Set callback for failed purchases
1309
+ */
1310
+ onPurchaseError(callback) {
1311
+ this.onPurchaseErrorCallback = callback;
1312
+ }
1313
+ /**
1314
+ * Clean up resources
1315
+ */
1316
+ dispose() {
1317
+ if (this.platform === "native" /* NATIVE */) {
1318
+ NativeBridge.off("IAP_AVAILABILITY_RESULT" /* IAP_AVAILABILITY_RESULT */);
1319
+ NativeBridge.off("PRODUCTS_RESULT" /* PRODUCTS_RESULT */);
1320
+ NativeBridge.off("PURCHASE_COMPLETE" /* PURCHASE_COMPLETE */);
1321
+ NativeBridge.off("PURCHASE_ERROR" /* PURCHASE_ERROR */);
1322
+ }
1323
+ this.unmountCheckoutElement();
1324
+ this.isInitialized = false;
1325
+ this.nativeAvailable = false;
1326
+ this.products = [];
1327
+ this.stripe = null;
1328
+ this.onPurchaseCompleteCallback = void 0;
1329
+ this.onPurchaseErrorCallback = void 0;
1330
+ }
1331
+ };
1332
+
800
1333
  // src/core/client.ts
1334
+ function determineEnvironmentFromParentUrl() {
1335
+ try {
1336
+ let parentUrl = "";
1337
+ if (window !== window.top) {
1338
+ try {
1339
+ parentUrl = window.parent.location.href;
1340
+ } catch (e) {
1341
+ parentUrl = document.referrer;
1342
+ }
1343
+ } else {
1344
+ parentUrl = window.location.href;
1345
+ }
1346
+ logger.debug("Detected parent URL:", parentUrl);
1347
+ if (parentUrl.includes("dev.hyve.gg")) {
1348
+ logger.info("Environment detected: dev (from parent URL)");
1349
+ return true;
1350
+ }
1351
+ if (parentUrl.includes("hyve.gg")) {
1352
+ logger.info("Environment detected: prod (from parent URL)");
1353
+ return false;
1354
+ }
1355
+ logger.info("Environment unknown, defaulting to dev");
1356
+ return true;
1357
+ } catch (error) {
1358
+ logger.warn("Failed to determine environment from parent URL:", error);
1359
+ return true;
1360
+ }
1361
+ }
801
1362
  var HyveClient = class {
802
1363
  telemetryConfig;
803
1364
  apiBaseUrl;
@@ -806,14 +1367,18 @@ var HyveClient = class {
806
1367
  jwtToken = null;
807
1368
  gameId = null;
808
1369
  adsService;
1370
+ billingService;
1371
+ billingConfig;
1372
+ // Store callbacks to preserve them when recreating BillingService
1373
+ billingCallbacks = {};
809
1374
  /**
810
1375
  * Creates a new HyveClient instance
811
1376
  * @param config Optional configuration including telemetry and ads
812
1377
  */
813
1378
  constructor(config) {
1379
+ const isDev = config?.isDev !== void 0 ? config.isDev : determineEnvironmentFromParentUrl();
814
1380
  this.telemetryConfig = {
815
- isDev: true,
816
- // Default to dev environment
1381
+ isDev,
817
1382
  ...config
818
1383
  };
819
1384
  if (this.telemetryConfig.apiBaseUrl) {
@@ -826,10 +1391,24 @@ var HyveClient = class {
826
1391
  if (config?.ads) {
827
1392
  this.adsService.configure(config.ads);
828
1393
  }
829
- logger.info("Client initialized with sessionId:", this.sessionId);
1394
+ this.billingConfig = config?.billing || {};
1395
+ this.billingService = new BillingService(this.billingConfig);
1396
+ const envSource = config?.isDev !== void 0 ? "explicit config" : "auto-detected from parent URL";
1397
+ logger.info("==========================================");
1398
+ logger.info("HyveClient Initialized");
1399
+ logger.info("==========================================");
1400
+ logger.info("Session ID:", this.sessionId);
1401
+ logger.info("Environment:", this.telemetryConfig.isDev ? "DEVELOPMENT" : "PRODUCTION", `(${envSource})`);
830
1402
  logger.info("API Base URL:", this.apiBaseUrl);
831
- logger.info("Environment:", this.telemetryConfig.isDev ? "dev" : "prod");
832
1403
  logger.info("Ads enabled:", this.adsService.isEnabled());
1404
+ logger.info("Billing configured:", Object.keys(this.billingConfig).length > 0);
1405
+ logger.debug("Config:", {
1406
+ isDev: this.telemetryConfig.isDev,
1407
+ hasCustomApiUrl: !!config?.apiBaseUrl,
1408
+ adsEnabled: config?.ads?.enabled || false,
1409
+ billingConfigured: Object.keys(this.billingConfig).length > 0
1410
+ });
1411
+ logger.info("==========================================");
833
1412
  }
834
1413
  /**
835
1414
  * Authenticates a user from URL parameters
@@ -980,826 +1559,237 @@ var HyveClient = class {
980
1559
  return await response.json();
981
1560
  } catch (error) {
982
1561
  logger.error("API call failed:", error);
983
- throw error;
984
- }
985
- }
986
- /**
987
- * Gets the user's inventory
988
- * @returns Promise resolving to the user's inventory
989
- */
990
- async getInventory() {
991
- return this.callApi("/api/v1/inventory");
992
- }
993
- /**
994
- * Gets a specific inventory item by ID
995
- * @param itemId The inventory item ID
996
- * @returns Promise resolving to the inventory item details
997
- */
998
- async getInventoryItem(itemId) {
999
- return this.callApi(`/api/v1/inventory/${itemId}`);
1000
- }
1001
- /**
1002
- * Updates the telemetry configuration
1003
- * @param config New telemetry configuration
1004
- */
1005
- updateTelemetryConfig(config) {
1006
- this.telemetryConfig = {
1007
- ...this.telemetryConfig,
1008
- ...config
1009
- };
1010
- if (config.apiBaseUrl !== void 0) {
1011
- this.apiBaseUrl = config.apiBaseUrl;
1012
- } else if (config.isDev !== void 0) {
1013
- this.apiBaseUrl = config.isDev ? "https://product-api.dev.hyve.gg" : "https://product-api.prod.hyve.gg";
1014
- }
1015
- logger.info("Config updated");
1016
- logger.info("API Base URL:", this.apiBaseUrl);
1017
- }
1018
- /**
1019
- * Gets the current user ID
1020
- * @returns Current user ID or null if not authenticated
1021
- */
1022
- getUserId() {
1023
- return this.userId;
1024
- }
1025
- /**
1026
- * Gets the current session ID
1027
- * @returns Current session ID
1028
- */
1029
- getSessionId() {
1030
- return this.sessionId;
1031
- }
1032
- /**
1033
- * Gets the current JWT token
1034
- * @returns Current JWT token or null if not available
1035
- */
1036
- getJwtToken() {
1037
- return this.jwtToken;
1038
- }
1039
- /**
1040
- * Gets the current game ID
1041
- * @returns Current game ID or null if not available
1042
- */
1043
- getGameId() {
1044
- return this.gameId;
1045
- }
1046
- /**
1047
- * Gets the API base URL
1048
- * @returns API base URL
1049
- */
1050
- getApiBaseUrl() {
1051
- return this.apiBaseUrl;
1052
- }
1053
- /**
1054
- * Checks if user is authenticated
1055
- * @returns Boolean indicating authentication status
1056
- */
1057
- isUserAuthenticated() {
1058
- return this.userId !== null;
1059
- }
1060
- /**
1061
- * Checks if JWT token is available
1062
- * @returns Boolean indicating if JWT token is present
1063
- */
1064
- hasJwtToken() {
1065
- return this.jwtToken !== null;
1066
- }
1067
- /**
1068
- * Logs out the current user
1069
- */
1070
- logout() {
1071
- this.userId = null;
1072
- this.jwtToken = null;
1073
- this.gameId = null;
1074
- logger.info("User logged out");
1075
- }
1076
- /**
1077
- * Resets the client state
1078
- */
1079
- reset() {
1080
- this.logout();
1081
- this.sessionId = generateUUID();
1082
- logger.info("Client reset with new sessionId:", this.sessionId);
1083
- }
1084
- /**
1085
- * Configure ads service
1086
- * @param config Ads configuration
1087
- */
1088
- configureAds(config) {
1089
- this.adsService.configure(config);
1090
- logger.info("Ads configuration updated");
1091
- }
1092
- /**
1093
- * Show an ad
1094
- * @param type Type of ad to show ('rewarded', 'interstitial', or 'preroll')
1095
- * @returns Promise resolving to ad result
1096
- */
1097
- async showAd(type) {
1098
- return this.adsService.show(type);
1099
- }
1100
- /**
1101
- * Check if ads are enabled
1102
- * @returns Boolean indicating if ads are enabled
1103
- */
1104
- areAdsEnabled() {
1105
- return this.adsService.isEnabled();
1106
- }
1107
- /**
1108
- * Check if ads are ready to show
1109
- * @returns Boolean indicating if ads are ready
1110
- */
1111
- areAdsReady() {
1112
- return this.adsService.isReady();
1113
- }
1114
- };
1115
-
1116
- // src/services/billing.ts
1117
- var BillingPlatform = /* @__PURE__ */ ((BillingPlatform2) => {
1118
- BillingPlatform2["WEB"] = "web";
1119
- BillingPlatform2["NATIVE"] = "native";
1120
- BillingPlatform2["UNKNOWN"] = "unknown";
1121
- return BillingPlatform2;
1122
- })(BillingPlatform || {});
1123
- var BillingService = class {
1124
- config;
1125
- platform = "unknown" /* UNKNOWN */;
1126
- isInitialized = false;
1127
- nativeAvailable = false;
1128
- products = [];
1129
- // Stripe instance for web payments
1130
- stripe = null;
1131
- checkoutElement = null;
1132
- // Callbacks for purchase events
1133
- onPurchaseCompleteCallback;
1134
- onPurchaseErrorCallback;
1135
- // Callback for logging (so external UI can capture logs)
1136
- onLogCallback;
1137
- constructor(config) {
1138
- this.config = config;
1139
- this.detectPlatform();
1140
- }
1141
- /**
1142
- * Register a callback to receive all internal logs
1143
- * Useful for displaying logs in a UI
1144
- */
1145
- onLog(callback) {
1146
- this.onLogCallback = callback;
1147
- }
1148
- /**
1149
- * Internal logging method that calls both logger and custom callback
1150
- */
1151
- log(level, message, data) {
1152
- const consoleMethod = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
1153
- if (data !== void 0) {
1154
- consoleMethod(message, data);
1155
- } else {
1156
- consoleMethod(message);
1157
- }
1158
- if (this.onLogCallback) {
1159
- this.onLogCallback(level, message, data);
1562
+ throw error;
1160
1563
  }
1161
1564
  }
1162
1565
  /**
1163
- * Detects if running on web or native platform
1566
+ * Gets the user's inventory
1567
+ * @returns Promise resolving to the user's inventory
1164
1568
  */
1165
- detectPlatform() {
1166
- this.log("info", "[BillingService] ========== PLATFORM DETECTION ==========");
1167
- this.log("info", "[BillingService] Checking environment...");
1168
- this.log("info", "[BillingService] Checking NativeBridge.isNativeContext()...");
1169
- const isNative = NativeBridge.isNativeContext();
1170
- this.log("info", `[BillingService] NativeBridge.isNativeContext() = ${isNative}`);
1171
- this.log("info", "[BillingService] Checking typeof window...");
1172
- const hasWindow = typeof window !== "undefined";
1173
- this.log("info", `[BillingService] typeof window !== "undefined" = ${hasWindow}`);
1174
- if (hasWindow) {
1175
- this.log("info", "[BillingService] Checking window.ReactNativeWebView...");
1176
- const hasWebView = !!window.ReactNativeWebView;
1177
- this.log("info", `[BillingService] window.ReactNativeWebView exists = ${hasWebView}`);
1178
- }
1179
- if (isNative) {
1180
- this.platform = "native" /* NATIVE */;
1181
- this.log("info", "[BillingService] \u2713 Platform: NATIVE (React Native WebView)");
1182
- } else if (hasWindow) {
1183
- this.platform = "web" /* WEB */;
1184
- this.log("info", "[BillingService] \u2713 Platform: WEB (Browser)");
1185
- } else {
1186
- this.platform = "unknown" /* UNKNOWN */;
1187
- this.log("warn", "[BillingService] \u26A0 Platform: UNKNOWN");
1188
- }
1189
- this.log("info", "[BillingService] ========================================");
1569
+ async getInventory() {
1570
+ return this.callApi("/api/v1/inventory");
1190
1571
  }
1191
1572
  /**
1192
- * Get the current platform
1573
+ * Gets a specific inventory item by ID
1574
+ * @param itemId The inventory item ID
1575
+ * @returns Promise resolving to the inventory item details
1193
1576
  */
1194
- getPlatform() {
1195
- return this.platform;
1577
+ async getInventoryItem(itemId) {
1578
+ return this.callApi(`/api/v1/inventory/${itemId}`);
1196
1579
  }
1197
1580
  /**
1198
- * Initialize the billing service
1199
- * Must be called before using any other methods
1581
+ * Updates the telemetry configuration
1582
+ * @param config New telemetry configuration
1200
1583
  */
1201
- async initialize() {
1202
- this.log("info", "[BillingService] ========== INITIALIZATION START ==========");
1203
- this.log("info", "[BillingService] Current config:", {
1204
- hasStripeKey: !!this.config.stripePublishableKey,
1205
- stripeKeyLength: this.config.stripePublishableKey?.length,
1206
- gameId: this.config.gameId,
1207
- userId: this.config.userId,
1208
- hasCheckoutUrl: !!this.config.checkoutUrl,
1209
- checkoutUrl: this.config.checkoutUrl
1210
- });
1211
- if (this.isInitialized) {
1212
- this.log("info", "[BillingService] Already initialized");
1213
- return true;
1214
- }
1215
- this.log("info", `[BillingService] Detected platform: ${this.platform}`);
1216
- try {
1217
- if (this.platform === "native" /* NATIVE */) {
1218
- this.log("info", "[BillingService] Attempting native initialization...");
1219
- const result = await this.initializeNative();
1220
- this.log("info", `[BillingService] Native initialization result: ${result}`);
1221
- return result;
1222
- } else if (this.platform === "web" /* WEB */) {
1223
- this.log("info", "[BillingService] Attempting web initialization...");
1224
- const result = await this.initializeWeb();
1225
- this.log("info", `[BillingService] Web initialization result: ${result}`);
1226
- return result;
1227
- }
1228
- this.log("error", "[BillingService] Cannot initialize: unknown platform");
1229
- this.log("error", "[BillingService] Platform detection may have failed");
1230
- return false;
1231
- } catch (error) {
1232
- this.log("error", "[BillingService] Initialization failed with exception:", error);
1233
- this.log("error", "[BillingService] Error message:", error?.message);
1234
- this.log("error", "[BillingService] Error stack:", error?.stack);
1235
- return false;
1236
- } finally {
1237
- this.log("info", "[BillingService] ========== INITIALIZATION END ==========");
1584
+ updateTelemetryConfig(config) {
1585
+ this.telemetryConfig = {
1586
+ ...this.telemetryConfig,
1587
+ ...config
1588
+ };
1589
+ if (config.apiBaseUrl !== void 0) {
1590
+ this.apiBaseUrl = config.apiBaseUrl;
1591
+ } else if (config.isDev !== void 0) {
1592
+ this.apiBaseUrl = config.isDev ? "https://product-api.dev.hyve.gg" : "https://product-api.prod.hyve.gg";
1238
1593
  }
1594
+ logger.info("Config updated");
1595
+ logger.info("API Base URL:", this.apiBaseUrl);
1239
1596
  }
1240
1597
  /**
1241
- * Initialize native billing
1598
+ * Gets the current user ID
1599
+ * @returns Current user ID or null if not authenticated
1242
1600
  */
1243
- async initializeNative() {
1244
- this.log("info", "[BillingService] --- Native Initialization Start ---");
1245
- return new Promise((resolve) => {
1246
- this.log("info", "[BillingService] Checking NativeBridge...");
1247
- try {
1248
- this.log("info", "[BillingService] Calling NativeBridge.initialize()...");
1249
- NativeBridge.initialize();
1250
- this.log("info", "[BillingService] \u2713 NativeBridge.initialize() called");
1251
- this.log("info", "[BillingService] Setting up PURCHASE_COMPLETE listener...");
1252
- NativeBridge.on(
1253
- "PURCHASE_COMPLETE" /* PURCHASE_COMPLETE */,
1254
- (payload) => {
1255
- this.log("info", "[BillingService] \u2713 Native purchase complete:", payload);
1256
- const result = {
1257
- success: true,
1258
- productId: payload.productId,
1259
- transactionId: payload.transactionId,
1260
- transactionDate: payload.transactionDate
1261
- };
1262
- if (this.onPurchaseCompleteCallback) {
1263
- this.onPurchaseCompleteCallback(result);
1264
- }
1265
- }
1266
- );
1267
- this.log("info", "[BillingService] \u2713 PURCHASE_COMPLETE listener registered");
1268
- this.log("info", "[BillingService] Setting up PURCHASE_ERROR listener...");
1269
- NativeBridge.on(
1270
- "PURCHASE_ERROR" /* PURCHASE_ERROR */,
1271
- (payload) => {
1272
- this.log("error", "[BillingService] \u274C Native purchase error:", payload);
1273
- const result = {
1274
- success: false,
1275
- productId: payload.productId || "unknown",
1276
- error: {
1277
- code: payload.code,
1278
- message: payload.message
1279
- }
1280
- };
1281
- if (this.onPurchaseErrorCallback) {
1282
- this.onPurchaseErrorCallback(result);
1283
- }
1284
- }
1285
- );
1286
- this.log("info", "[BillingService] \u2713 PURCHASE_ERROR listener registered");
1287
- this.log("info", "[BillingService] Setting up IAP_AVAILABILITY_RESULT listener...");
1288
- NativeBridge.on(
1289
- "IAP_AVAILABILITY_RESULT" /* IAP_AVAILABILITY_RESULT */,
1290
- (payload) => {
1291
- this.log("info", "[BillingService] \u2713 IAP availability result received:", payload.available);
1292
- this.nativeAvailable = payload.available;
1293
- this.isInitialized = true;
1294
- if (payload.available) {
1295
- this.log("info", "[BillingService] \u2713\u2713\u2713 Native billing available and initialized \u2713\u2713\u2713");
1296
- } else {
1297
- this.log("warn", "[BillingService] \u26A0 Native billing initialized but IAP not available");
1298
- }
1299
- resolve(payload.available);
1300
- }
1301
- );
1302
- this.log("info", "[BillingService] \u2713 IAP_AVAILABILITY_RESULT listener registered");
1303
- this.log("info", "[BillingService] Waiting 100ms for event listeners to be ready...");
1304
- setTimeout(() => {
1305
- this.log("info", "[BillingService] Sending checkIAPAvailability message to native...");
1306
- NativeBridge.checkIAPAvailability();
1307
- this.log("info", "[BillingService] \u2713 checkIAPAvailability message sent");
1308
- this.log("info", "[BillingService] Waiting for IAP availability response (5s timeout)...");
1309
- }, 100);
1310
- setTimeout(() => {
1311
- if (!this.isInitialized) {
1312
- this.log("warn", "[BillingService] \u274C Native billing initialization TIMEOUT after 5 seconds");
1313
- this.log("warn", "[BillingService] No response from native app");
1314
- this.log("warn", "[BillingService] Marking as initialized but unavailable");
1315
- this.isInitialized = true;
1316
- resolve(false);
1317
- }
1318
- }, 5e3);
1319
- } catch (error) {
1320
- this.log("error", "[BillingService] \u274C Exception during native initialization:", error);
1321
- this.log("error", "[BillingService] Error message:", error?.message);
1322
- this.log("error", "[BillingService] Error stack:", error?.stack);
1323
- this.isInitialized = true;
1324
- resolve(false);
1325
- } finally {
1326
- this.log("info", "[BillingService] --- Native Initialization End ---");
1327
- }
1328
- });
1601
+ getUserId() {
1602
+ return this.userId;
1329
1603
  }
1330
1604
  /**
1331
- * Initialize web billing (Stripe)
1605
+ * Gets the current session ID
1606
+ * @returns Current session ID
1332
1607
  */
1333
- async initializeWeb() {
1334
- this.log("info", "[BillingService] --- Web Initialization Start ---");
1335
- this.log("info", "[BillingService] Checking for Stripe publishable key...");
1336
- if (!this.config.stripePublishableKey) {
1337
- this.log(
1338
- "error",
1339
- "[BillingService] \u274C FAILED: stripePublishableKey not provided in config"
1340
- );
1341
- this.log("error", "[BillingService] Config keys present:", Object.keys(this.config));
1342
- return false;
1343
- }
1344
- this.log(
1345
- "info",
1346
- `[BillingService] \u2713 Stripe key found (length: ${this.config.stripePublishableKey.length})`
1347
- );
1348
- this.log(
1349
- "info",
1350
- `[BillingService] Key starts with: ${this.config.stripePublishableKey.substring(0, 12)}...`
1351
- );
1352
- try {
1353
- this.log("info", "[BillingService] Checking window object...");
1354
- if (typeof window === "undefined") {
1355
- this.log("error", "[BillingService] \u274C FAILED: window is undefined (not in browser)");
1356
- return false;
1357
- }
1358
- this.log("info", "[BillingService] \u2713 Window object exists");
1359
- this.log("info", "[BillingService] Checking if Stripe.js is already loaded...");
1360
- if (!window.Stripe) {
1361
- this.log("info", "[BillingService] Stripe.js not loaded, loading now...");
1362
- await this.loadStripeScript();
1363
- this.log("info", "[BillingService] Stripe.js load complete");
1364
- } else {
1365
- this.log("info", "[BillingService] \u2713 Stripe.js already loaded");
1366
- }
1367
- this.log("info", "[BillingService] Attempting to initialize Stripe instance...");
1368
- if (window.Stripe) {
1369
- this.log("info", "[BillingService] Creating Stripe instance with publishable key...");
1370
- this.stripe = window.Stripe(this.config.stripePublishableKey);
1371
- this.log("info", "[BillingService] \u2713 Stripe instance created");
1372
- this.log("info", "[BillingService] Stripe object type:", typeof this.stripe);
1373
- this.log("info", "[BillingService] Stripe object keys:", Object.keys(this.stripe || {}));
1374
- this.isInitialized = true;
1375
- this.log("info", "[BillingService] \u2713\u2713\u2713 Web billing initialized successfully \u2713\u2713\u2713");
1376
- return true;
1377
- } else {
1378
- this.log("error", "[BillingService] \u274C FAILED: window.Stripe is undefined after loading");
1379
- this.log("error", "[BillingService] window keys:", Object.keys(window));
1380
- return false;
1381
- }
1382
- } catch (error) {
1383
- this.log("error", "[BillingService] \u274C EXCEPTION during Stripe initialization:");
1384
- this.log("error", "[BillingService] Error type:", error?.constructor?.name);
1385
- this.log("error", "[BillingService] Error message:", error?.message);
1386
- this.log("error", "[BillingService] Error stack:", error?.stack);
1387
- return false;
1388
- } finally {
1389
- this.log("info", "[BillingService] --- Web Initialization End ---");
1390
- }
1608
+ getSessionId() {
1609
+ return this.sessionId;
1391
1610
  }
1392
1611
  /**
1393
- * Load Stripe.js script dynamically
1612
+ * Gets the current JWT token
1613
+ * @returns Current JWT token or null if not available
1394
1614
  */
1395
- loadStripeScript() {
1396
- this.log("info", "[BillingService] --- loadStripeScript() Start ---");
1397
- return new Promise((resolve, reject) => {
1398
- this.log("info", "[BillingService] Checking window object in loadStripeScript...");
1399
- if (typeof window === "undefined") {
1400
- this.log("error", "[BillingService] \u274C FAILED: Window is not defined");
1401
- reject(new Error("Window is not defined"));
1402
- return;
1403
- }
1404
- this.log("info", "[BillingService] \u2713 Window exists");
1405
- this.log("info", "[BillingService] Checking if Stripe is already on window...");
1406
- if (window.Stripe) {
1407
- this.log("info", "[BillingService] \u2713 Stripe already exists, no need to load");
1408
- resolve();
1409
- return;
1410
- }
1411
- this.log("info", "[BillingService] Stripe not found, need to load script");
1412
- this.log("info", "[BillingService] Checking document object...");
1413
- if (!document) {
1414
- this.log("error", "[BillingService] \u274C FAILED: document is undefined");
1415
- reject(new Error("document is undefined"));
1416
- return;
1417
- }
1418
- this.log("info", "[BillingService] \u2713 Document exists");
1419
- this.log("info", "[BillingService] Checking document.head...");
1420
- if (!document.head) {
1421
- this.log("error", "[BillingService] \u274C FAILED: document.head is undefined");
1422
- reject(new Error("document.head is undefined"));
1423
- return;
1424
- }
1425
- this.log("info", "[BillingService] \u2713 document.head exists");
1426
- this.log("info", "[BillingService] Creating script element...");
1427
- const script = document.createElement("script");
1428
- script.src = "https://js.stripe.com/v3/";
1429
- script.async = true;
1430
- this.log("info", "[BillingService] \u2713 Script element created");
1431
- this.log("info", "[BillingService] Script src:", script.src);
1432
- script.onload = () => {
1433
- this.log("info", "[BillingService] \u2713\u2713\u2713 Stripe.js script loaded successfully \u2713\u2713\u2713");
1434
- this.log("info", "[BillingService] Checking if window.Stripe now exists...");
1435
- if (window.Stripe) {
1436
- this.log("info", "[BillingService] \u2713 window.Stripe confirmed to exist");
1437
- } else {
1438
- this.log("warn", "[BillingService] \u26A0 Warning: Script loaded but window.Stripe is still undefined");
1439
- }
1440
- resolve();
1441
- };
1442
- script.onerror = (error) => {
1443
- this.log("error", "[BillingService] \u274C\u274C\u274C Failed to load Stripe.js script");
1444
- this.log("error", "[BillingService] Script error event:", error);
1445
- this.log("error", "[BillingService] Script src attempted:", script.src);
1446
- reject(new Error("Failed to load Stripe.js"));
1447
- };
1448
- this.log("info", "[BillingService] Appending script to document.head...");
1449
- try {
1450
- document.head.appendChild(script);
1451
- this.log("info", "[BillingService] \u2713 Script appended to head, waiting for load...");
1452
- } catch (appendError) {
1453
- this.log("error", "[BillingService] \u274C Exception while appending script:", appendError);
1454
- reject(appendError);
1455
- }
1456
- });
1615
+ getJwtToken() {
1616
+ return this.jwtToken;
1457
1617
  }
1458
1618
  /**
1459
- * Check if billing is available
1619
+ * Gets the current game ID
1620
+ * @returns Current game ID or null if not available
1460
1621
  */
1461
- isAvailable() {
1462
- if (!this.isInitialized) {
1463
- this.log("warn", "[BillingService] Not initialized. Call initialize() first.");
1464
- return false;
1465
- }
1466
- if (this.platform === "native" /* NATIVE */) {
1467
- return this.nativeAvailable;
1468
- } else if (this.platform === "web" /* WEB */) {
1469
- return !!this.config.stripePublishableKey;
1470
- }
1471
- return false;
1622
+ getGameId() {
1623
+ return this.gameId;
1472
1624
  }
1473
1625
  /**
1474
- * Get available products
1626
+ * Gets the API base URL
1627
+ * @returns API base URL
1475
1628
  */
1476
- async getProducts() {
1477
- if (!this.isInitialized) {
1478
- throw new Error("BillingService not initialized. Call initialize() first.");
1479
- }
1480
- if (this.platform === "native" /* NATIVE */) {
1481
- return await this.getProductsNative();
1482
- } else if (this.platform === "web" /* WEB */) {
1483
- return await this.getProductsWeb();
1484
- }
1485
- throw new Error("Cannot get products: unknown platform");
1629
+ getApiBaseUrl() {
1630
+ return this.apiBaseUrl;
1486
1631
  }
1487
1632
  /**
1488
- * Get products from native IAP
1633
+ * Checks if user is authenticated
1634
+ * @returns Boolean indicating authentication status
1489
1635
  */
1490
- async getProductsNative() {
1491
- return new Promise((resolve, reject) => {
1492
- if (!this.config.gameId) {
1493
- const error = new Error("gameId is required for native purchases");
1494
- this.log("error", "[BillingService] getProductsNative failed:", error);
1495
- reject(error);
1496
- return;
1497
- }
1498
- if (!this.config.checkoutUrl) {
1499
- const error = new Error("checkoutUrl is required for native purchases");
1500
- this.log("error", "[BillingService] getProductsNative failed:", error);
1501
- reject(error);
1502
- return;
1503
- }
1504
- this.log(
1505
- "info",
1506
- `[BillingService] Fetching native products for gameId: ${this.config.gameId}`
1507
- );
1508
- const url = `${this.config.checkoutUrl}/get-native-packages?game_id=${this.config.gameId}`;
1509
- this.log("info", `[BillingService] Native products URL: ${url}`);
1510
- fetch(url).then((response) => {
1511
- this.log(
1512
- "info",
1513
- `[BillingService] Native products response status: ${response.status}`
1514
- );
1515
- if (!response.ok) {
1516
- throw new Error(`Failed to fetch native products: ${response.status}`);
1517
- }
1518
- return response.json();
1519
- }).then((data) => {
1520
- this.log("info", `[BillingService] Native products data:`, data);
1521
- if (!data.packages || !Array.isArray(data.packages)) {
1522
- throw new Error("Invalid response format: missing packages array");
1523
- }
1524
- this.products = data.packages.map((pkg) => ({
1525
- productId: pkg.productId,
1526
- title: pkg.package_name,
1527
- description: `${pkg.game_name} - ${pkg.package_name}`,
1528
- price: pkg.price_cents / 100,
1529
- localizedPrice: pkg.price_display,
1530
- currency: "USD"
1531
- }));
1532
- this.log(
1533
- "info",
1534
- `[BillingService] Successfully fetched ${this.products.length} native products`
1535
- );
1536
- resolve(this.products);
1537
- }).catch((error) => {
1538
- this.log("error", "[BillingService] Failed to fetch native products:", error);
1539
- reject(error);
1540
- });
1541
- });
1636
+ isUserAuthenticated() {
1637
+ return this.userId !== null;
1638
+ }
1639
+ /**
1640
+ * Checks if JWT token is available
1641
+ * @returns Boolean indicating if JWT token is present
1642
+ */
1643
+ hasJwtToken() {
1644
+ return this.jwtToken !== null;
1542
1645
  }
1543
1646
  /**
1544
- * Get products from web API (Stripe)
1647
+ * Logs out the current user
1545
1648
  */
1546
- async getProductsWeb() {
1547
- this.log("info", "[BillingService] Fetching web products (Stripe)...");
1548
- if (!this.config.checkoutUrl || !this.config.gameId) {
1549
- const error = new Error("checkoutUrl and gameId are required for web purchases");
1550
- this.log("error", "[BillingService] getProductsWeb failed:", error);
1551
- throw error;
1552
- }
1553
- try {
1554
- const url = `${this.config.checkoutUrl}/get-packages?game_id=${this.config.gameId}`;
1555
- this.log("info", `[BillingService] Web products URL: ${url}`);
1556
- const response = await fetch(url);
1557
- this.log("info", `[BillingService] Web products response status: ${response.status}`);
1558
- if (!response.ok) {
1559
- throw new Error(`Failed to fetch web products: ${response.status}`);
1560
- }
1561
- const data = await response.json();
1562
- this.log("info", `[BillingService] Web products data:`, data);
1563
- if (!data.packages || !Array.isArray(data.packages)) {
1564
- throw new Error("Invalid response format: missing packages array");
1565
- }
1566
- this.products = data.packages.map((pkg) => ({
1567
- productId: pkg.priceId || pkg.productId,
1568
- // Prefer priceId for Stripe
1569
- title: pkg.package_name,
1570
- description: `${pkg.game_name} - ${pkg.package_name}`,
1571
- price: pkg.price_cents / 100,
1572
- localizedPrice: pkg.price_display,
1573
- currency: "USD"
1574
- }));
1575
- this.log(
1576
- "info",
1577
- `[BillingService] Successfully fetched ${this.products.length} web products`
1578
- );
1579
- return this.products;
1580
- } catch (error) {
1581
- this.log("error", "[BillingService] Failed to fetch web products:", error);
1582
- throw error;
1583
- }
1649
+ logout() {
1650
+ this.userId = null;
1651
+ this.jwtToken = null;
1652
+ this.gameId = null;
1653
+ logger.info("User logged out");
1584
1654
  }
1585
1655
  /**
1586
- * Purchase a product
1587
- * @param productId - The product ID (priceId for web/Stripe, productId for native)
1588
- * @param options - Optional purchase options
1589
- * @param options.elementId - For web: DOM element ID to mount Stripe checkout (default: 'stripe-checkout-element')
1656
+ * Resets the client state
1590
1657
  */
1591
- async purchase(productId, options) {
1592
- if (!this.isInitialized) {
1593
- throw new Error("BillingService not initialized. Call initialize() first.");
1594
- }
1595
- if (!this.isAvailable()) {
1596
- throw new Error("Billing is not available on this platform");
1597
- }
1598
- if (this.platform === "native" /* NATIVE */) {
1599
- return await this.purchaseNative(productId);
1600
- } else if (this.platform === "web" /* WEB */) {
1601
- return await this.purchaseWeb(productId, options?.elementId);
1602
- }
1603
- throw new Error("Cannot purchase: unknown platform");
1658
+ reset() {
1659
+ this.logout();
1660
+ this.sessionId = generateUUID();
1661
+ logger.info("Client reset with new sessionId:", this.sessionId);
1604
1662
  }
1605
1663
  /**
1606
- * Purchase via native IAP
1664
+ * Configure ads service
1665
+ * @param config Ads configuration
1607
1666
  */
1608
- async purchaseNative(productId) {
1609
- return new Promise((resolve, reject) => {
1610
- if (!this.config.userId) {
1611
- reject(new Error("userId is required for native purchases"));
1612
- return;
1613
- }
1614
- this.log("info", `[BillingService] Initiating native purchase for: ${productId}`);
1615
- const previousCompleteCallback = this.onPurchaseCompleteCallback;
1616
- const previousErrorCallback = this.onPurchaseErrorCallback;
1617
- const cleanup = () => {
1618
- this.onPurchaseCompleteCallback = previousCompleteCallback;
1619
- this.onPurchaseErrorCallback = previousErrorCallback;
1620
- };
1621
- const completeHandler = (result) => {
1622
- if (result.productId === productId) {
1623
- cleanup();
1624
- resolve(result);
1625
- if (previousCompleteCallback) {
1626
- previousCompleteCallback(result);
1627
- }
1628
- }
1629
- };
1630
- const errorHandler = (result) => {
1631
- if (result.productId === productId) {
1632
- cleanup();
1633
- reject(new Error(result.error?.message || "Purchase failed"));
1634
- if (previousErrorCallback) {
1635
- previousErrorCallback(result);
1636
- }
1637
- }
1638
- };
1639
- this.onPurchaseCompleteCallback = completeHandler;
1640
- this.onPurchaseErrorCallback = errorHandler;
1641
- NativeBridge.purchase(productId, this.config.userId);
1642
- setTimeout(() => {
1643
- cleanup();
1644
- reject(new Error("Purchase timeout"));
1645
- }, 6e4);
1646
- });
1667
+ configureAds(config) {
1668
+ this.adsService.configure(config);
1669
+ logger.info("Ads configuration updated");
1647
1670
  }
1648
1671
  /**
1649
- * Purchase via web (Stripe)
1650
- * @param productId - The priceId (Stripe price ID) for web purchases
1651
- * @param elementId - Optional DOM element ID to mount the checkout form (default: 'stripe-checkout-element')
1672
+ * Show an ad
1673
+ * @param type Type of ad to show ('rewarded', 'interstitial', or 'preroll')
1674
+ * @returns Promise resolving to ad result
1652
1675
  */
1653
- async purchaseWeb(productId, elementId) {
1654
- this.log("info", `[BillingService] Initiating web purchase for priceId: ${productId}`);
1655
- if (!this.config.userId || !this.config.checkoutUrl) {
1656
- throw new Error("userId and checkoutUrl are required for web purchases");
1676
+ async showAd(type) {
1677
+ return this.adsService.show(type);
1678
+ }
1679
+ /**
1680
+ * Check if ads are enabled
1681
+ * @returns Boolean indicating if ads are enabled
1682
+ */
1683
+ areAdsEnabled() {
1684
+ return this.adsService.isEnabled();
1685
+ }
1686
+ /**
1687
+ * Check if ads are ready to show
1688
+ * @returns Boolean indicating if ads are ready
1689
+ */
1690
+ areAdsReady() {
1691
+ return this.adsService.isReady();
1692
+ }
1693
+ /**
1694
+ * Configure billing service
1695
+ * @param config Billing configuration
1696
+ */
1697
+ configureBilling(config) {
1698
+ this.billingConfig = {
1699
+ ...this.billingConfig,
1700
+ ...config
1701
+ };
1702
+ logger.info("Billing configuration updated");
1703
+ }
1704
+ /**
1705
+ * Initialize billing service
1706
+ * Must be called before using billing features
1707
+ * @returns Promise resolving to boolean indicating success
1708
+ */
1709
+ async initializeBilling() {
1710
+ const mergedConfig = {
1711
+ ...this.billingConfig
1712
+ };
1713
+ if (!mergedConfig.userId && this.userId) {
1714
+ mergedConfig.userId = this.userId;
1657
1715
  }
1658
- if (!this.stripe) {
1659
- throw new Error("Stripe not initialized. Call initialize() first.");
1716
+ if (!mergedConfig.gameId && this.gameId) {
1717
+ mergedConfig.gameId = Number(this.gameId);
1660
1718
  }
1661
- try {
1662
- const returnUrl = typeof window !== "undefined" ? `${window.location.href}${window.location.href.includes("?") ? "&" : "?"}payment=complete` : void 0;
1663
- this.log("info", `[BillingService] Creating checkout session with return URL: ${returnUrl}`);
1664
- const requestBody = {
1665
- priceId: productId,
1666
- // API expects priceId for Stripe
1667
- userId: this.config.userId,
1668
- gameId: this.config.gameId,
1669
- embedded: true,
1670
- // Enable embedded checkout
1671
- return_url: returnUrl
1672
- };
1673
- this.log("info", `[BillingService] Checkout session request:`, requestBody);
1674
- const response = await fetch(`${this.config.checkoutUrl}/create-checkout-session`, {
1675
- method: "POST",
1676
- headers: {
1677
- "Content-Type": "application/json"
1678
- },
1679
- body: JSON.stringify(requestBody)
1680
- });
1681
- this.log("info", `[BillingService] Checkout session response status: ${response.status}`);
1682
- if (!response.ok) {
1683
- const errorText = await response.text();
1684
- this.log("error", `[BillingService] Checkout session error: ${errorText}`);
1685
- throw new Error(`Failed to create checkout session: ${response.status} - ${errorText}`);
1686
- }
1687
- const responseData = await response.json();
1688
- this.log("info", `[BillingService] Checkout session created:`, responseData);
1689
- const { client_secret, id } = responseData;
1690
- if (!client_secret) {
1691
- throw new Error("No client_secret returned from checkout session");
1692
- }
1693
- await this.mountCheckoutElement(client_secret, elementId || "stripe-checkout-element");
1694
- return {
1695
- success: true,
1696
- productId,
1697
- transactionId: id
1698
- };
1699
- } catch (error) {
1700
- this.log("error", "[BillingService] Web purchase failed:", error);
1701
- return {
1702
- success: false,
1703
- productId,
1704
- error: {
1705
- code: "WEB_PURCHASE_FAILED",
1706
- message: error.message || "Purchase failed"
1707
- }
1708
- };
1719
+ if (!mergedConfig.checkoutUrl) {
1720
+ mergedConfig.checkoutUrl = this.apiBaseUrl;
1709
1721
  }
1722
+ this.billingService = new BillingService(mergedConfig);
1723
+ if (this.billingCallbacks.onComplete) {
1724
+ this.billingService.onPurchaseComplete(this.billingCallbacks.onComplete);
1725
+ }
1726
+ if (this.billingCallbacks.onError) {
1727
+ this.billingService.onPurchaseError(this.billingCallbacks.onError);
1728
+ }
1729
+ if (this.billingCallbacks.onLog) {
1730
+ this.billingService.onLog(this.billingCallbacks.onLog);
1731
+ }
1732
+ return await this.billingService.initialize();
1710
1733
  }
1711
1734
  /**
1712
- * Mount Stripe embedded checkout element to the DOM
1713
- * @param clientSecret - The client secret from the checkout session
1714
- * @param elementId - The ID of the DOM element to mount to
1735
+ * Get the billing platform
1736
+ * @returns Current billing platform
1715
1737
  */
1716
- async mountCheckoutElement(clientSecret, elementId) {
1717
- this.log("info", `[BillingService] Mounting checkout element to #${elementId}`);
1718
- if (!this.stripe) {
1719
- throw new Error("Stripe not initialized");
1720
- }
1721
- try {
1722
- const container = document.getElementById(elementId);
1723
- if (!container) {
1724
- throw new Error(`Element with id "${elementId}" not found in the DOM`);
1725
- }
1726
- container.innerHTML = "";
1727
- this.checkoutElement = await this.stripe.initEmbeddedCheckout({
1728
- clientSecret
1729
- });
1730
- this.checkoutElement.mount(`#${elementId}`);
1731
- this.log("info", "[BillingService] Checkout element mounted successfully");
1732
- this.setupCheckoutEventListeners();
1733
- } catch (error) {
1734
- this.log("error", "[BillingService] Failed to mount checkout element:", error);
1735
- throw error;
1736
- }
1738
+ getBillingPlatform() {
1739
+ return this.billingService.getPlatform();
1737
1740
  }
1738
1741
  /**
1739
- * Set up event listeners for checkout completion
1742
+ * Check if billing is available
1743
+ * @returns Boolean indicating if billing is available
1740
1744
  */
1741
- setupCheckoutEventListeners() {
1742
- if (typeof window !== "undefined") {
1743
- const urlParams = new URLSearchParams(window.location.search);
1744
- const paymentStatus = urlParams.get("payment");
1745
- if (paymentStatus === "complete") {
1746
- this.log("info", "[BillingService] Payment completed, processing result...");
1747
- const sessionId = urlParams.get("session_id");
1748
- if (this.onPurchaseCompleteCallback) {
1749
- this.onPurchaseCompleteCallback({
1750
- success: true,
1751
- productId: "",
1752
- // Would need to be tracked separately
1753
- transactionId: sessionId || void 0
1754
- });
1755
- }
1756
- urlParams.delete("payment");
1757
- urlParams.delete("session_id");
1758
- const newUrl = `${window.location.pathname}${urlParams.toString() ? "?" + urlParams.toString() : ""}`;
1759
- window.history.replaceState({}, "", newUrl);
1760
- }
1761
- }
1745
+ isBillingAvailable() {
1746
+ return this.billingService.isAvailable();
1762
1747
  }
1763
1748
  /**
1764
- * Unmount and destroy the checkout element
1749
+ * Get available billing products
1750
+ * @returns Promise resolving to array of products
1765
1751
  */
1766
- unmountCheckoutElement() {
1767
- if (this.checkoutElement) {
1768
- this.log("info", "[BillingService] Unmounting checkout element");
1769
- this.checkoutElement.unmount();
1770
- this.checkoutElement = null;
1771
- }
1752
+ async getBillingProducts() {
1753
+ return await this.billingService.getProducts();
1754
+ }
1755
+ /**
1756
+ * Purchase a product
1757
+ * @param productId Product ID to purchase
1758
+ * @param options Optional purchase options (e.g., elementId for web)
1759
+ * @returns Promise resolving to purchase result
1760
+ */
1761
+ async purchaseProduct(productId, options) {
1762
+ return await this.billingService.purchase(productId, options);
1772
1763
  }
1773
1764
  /**
1774
1765
  * Set callback for successful purchases
1766
+ * @param callback Function to call on purchase completion
1775
1767
  */
1776
1768
  onPurchaseComplete(callback) {
1777
- this.onPurchaseCompleteCallback = callback;
1769
+ this.billingCallbacks.onComplete = callback;
1770
+ this.billingService.onPurchaseComplete(callback);
1778
1771
  }
1779
1772
  /**
1780
1773
  * Set callback for failed purchases
1774
+ * @param callback Function to call on purchase error
1781
1775
  */
1782
1776
  onPurchaseError(callback) {
1783
- this.onPurchaseErrorCallback = callback;
1777
+ this.billingCallbacks.onError = callback;
1778
+ this.billingService.onPurchaseError(callback);
1784
1779
  }
1785
1780
  /**
1786
- * Clean up resources
1781
+ * Unmount Stripe checkout element
1787
1782
  */
1788
- dispose() {
1789
- this.log("info", "[BillingService] Disposing...");
1790
- if (this.platform === "native" /* NATIVE */) {
1791
- NativeBridge.off("IAP_AVAILABILITY_RESULT" /* IAP_AVAILABILITY_RESULT */);
1792
- NativeBridge.off("PRODUCTS_RESULT" /* PRODUCTS_RESULT */);
1793
- NativeBridge.off("PURCHASE_COMPLETE" /* PURCHASE_COMPLETE */);
1794
- NativeBridge.off("PURCHASE_ERROR" /* PURCHASE_ERROR */);
1795
- }
1796
- this.unmountCheckoutElement();
1797
- this.isInitialized = false;
1798
- this.nativeAvailable = false;
1799
- this.products = [];
1800
- this.stripe = null;
1801
- this.onPurchaseCompleteCallback = void 0;
1802
- this.onPurchaseErrorCallback = void 0;
1783
+ unmountBillingCheckout() {
1784
+ this.billingService.unmountCheckoutElement();
1785
+ }
1786
+ /**
1787
+ * Register a callback to receive billing logs
1788
+ * @param callback Function to call with log messages
1789
+ */
1790
+ onBillingLog(callback) {
1791
+ this.billingCallbacks.onLog = callback;
1792
+ this.billingService.onLog(callback);
1803
1793
  }
1804
1794
  };
1805
1795
  // Annotate the CommonJS export names for ESM import in node: