@hyve-sdk/js 2.12.0 → 2.13.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/README.md +16 -2
- package/dist/index.d.mts +140 -5
- package/dist/index.d.ts +140 -5
- package/dist/index.js +205 -10
- package/dist/index.mjs +205 -10
- package/dist/react.d.mts +20 -1
- package/dist/react.d.ts +20 -1
- package/dist/react.js +202 -10
- package/dist/react.mjs +202 -10
- package/package.json +16 -13
package/dist/index.js
CHANGED
|
@@ -198,17 +198,26 @@ var NativeMessageType = /* @__PURE__ */ ((NativeMessageType2) => {
|
|
|
198
198
|
NativeMessageType2["REQUEST_NOTIFICATION_PERMISSION"] = "REQUEST_NOTIFICATION_PERMISSION";
|
|
199
199
|
NativeMessageType2["GET_PRODUCTS"] = "GET_PRODUCTS";
|
|
200
200
|
NativeMessageType2["PURCHASE"] = "PURCHASE";
|
|
201
|
+
NativeMessageType2["SHOW_REWARDED_AD"] = "SHOW_REWARDED_AD";
|
|
202
|
+
NativeMessageType2["SHOW_INTERSTITIAL_AD"] = "SHOW_INTERSTITIAL_AD";
|
|
201
203
|
NativeMessageType2["IAP_AVAILABILITY_RESULT"] = "IAP_AVAILABILITY_RESULT";
|
|
202
204
|
NativeMessageType2["PUSH_PERMISSION_GRANTED"] = "PUSH_PERMISSION_GRANTED";
|
|
203
205
|
NativeMessageType2["PUSH_PERMISSION_DENIED"] = "PUSH_PERMISSION_DENIED";
|
|
204
206
|
NativeMessageType2["PRODUCTS_RESULT"] = "PRODUCTS_RESULT";
|
|
205
207
|
NativeMessageType2["PURCHASE_COMPLETE"] = "PURCHASE_COMPLETE";
|
|
206
208
|
NativeMessageType2["PURCHASE_ERROR"] = "PURCHASE_ERROR";
|
|
209
|
+
NativeMessageType2["AD_RESULT"] = "AD_RESULT";
|
|
207
210
|
return NativeMessageType2;
|
|
208
211
|
})(NativeMessageType || {});
|
|
209
212
|
var NativeBridge = class {
|
|
210
213
|
static handlers = /* @__PURE__ */ new Map();
|
|
211
214
|
static isInitialized = false;
|
|
215
|
+
/**
|
|
216
|
+
* Pending `sendAdRequest()` promises, keyed by the ad type native echoes
|
|
217
|
+
* back in `AD_RESULT.type` ("rewarded" | "interstitial"). One in-flight
|
|
218
|
+
* request per type — ads are shown sequentially.
|
|
219
|
+
*/
|
|
220
|
+
static adResultResolvers = /* @__PURE__ */ new Map();
|
|
212
221
|
/**
|
|
213
222
|
* Checks if the app is running inside a React Native WebView
|
|
214
223
|
*/
|
|
@@ -261,16 +270,20 @@ var NativeBridge = class {
|
|
|
261
270
|
"PUSH_PERMISSION_DENIED" /* PUSH_PERMISSION_DENIED */,
|
|
262
271
|
"PRODUCTS_RESULT" /* PRODUCTS_RESULT */,
|
|
263
272
|
"PURCHASE_COMPLETE" /* PURCHASE_COMPLETE */,
|
|
264
|
-
"PURCHASE_ERROR" /* PURCHASE_ERROR
|
|
273
|
+
"PURCHASE_ERROR" /* PURCHASE_ERROR */,
|
|
274
|
+
"AD_RESULT" /* AD_RESULT */
|
|
265
275
|
];
|
|
266
276
|
if (!nativeResponseTypes.includes(data.type)) {
|
|
267
277
|
return;
|
|
268
278
|
}
|
|
279
|
+
if (data.type === "AD_RESULT" /* AD_RESULT */) {
|
|
280
|
+
this.resolveAdResult(data.payload);
|
|
281
|
+
}
|
|
269
282
|
const handler = this.handlers.get(data.type);
|
|
270
283
|
if (handler) {
|
|
271
284
|
logger.debug(`[NativeBridge] Handling message: ${data.type}`);
|
|
272
285
|
handler(data.payload);
|
|
273
|
-
} else {
|
|
286
|
+
} else if (data.type !== "AD_RESULT" /* AD_RESULT */) {
|
|
274
287
|
logger.warn(`[NativeBridge] No handler registered for: ${data.type}`);
|
|
275
288
|
}
|
|
276
289
|
} catch (error) {
|
|
@@ -396,6 +409,81 @@ var NativeBridge = class {
|
|
|
396
409
|
static purchase(productId, userId) {
|
|
397
410
|
this.send("PURCHASE" /* PURCHASE */, { productId, userId });
|
|
398
411
|
}
|
|
412
|
+
/**
|
|
413
|
+
* Sends a native ad request and resolves with the `AD_RESULT` native sends
|
|
414
|
+
* back. Never rejects — a missing native context, timeout, or superseded
|
|
415
|
+
* request resolves to `{ success: false }` with an error code so callers can
|
|
416
|
+
* decide whether to fall back to the web ad path.
|
|
417
|
+
*
|
|
418
|
+
* @param messageType - `SHOW_REWARDED_AD` or `SHOW_INTERSTITIAL_AD`
|
|
419
|
+
* @param payload - `{ gameId, format, placement? }`
|
|
420
|
+
* @param timeoutMs - How long to wait for `AD_RESULT` before giving up
|
|
421
|
+
*
|
|
422
|
+
* @example
|
|
423
|
+
* const result = await NativeBridge.sendAdRequest(
|
|
424
|
+
* NativeMessageType.SHOW_REWARDED_AD,
|
|
425
|
+
* { gameId: "123", format: "rewarded", placement: "level_end" },
|
|
426
|
+
* );
|
|
427
|
+
* if (result.success) grantReward();
|
|
428
|
+
*/
|
|
429
|
+
static sendAdRequest(messageType, payload, timeoutMs = 12e4) {
|
|
430
|
+
const expectedType = messageType === "SHOW_REWARDED_AD" /* SHOW_REWARDED_AD */ ? "rewarded" : "interstitial";
|
|
431
|
+
return new Promise((resolve) => {
|
|
432
|
+
if (!this.isNativeContext()) {
|
|
433
|
+
resolve({ type: expectedType, success: false, error: "no_native_context" });
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
this.initialize();
|
|
437
|
+
const existing = this.adResultResolvers.get(expectedType);
|
|
438
|
+
if (existing) {
|
|
439
|
+
logger.warn(`[NativeBridge] Superseding in-flight ${expectedType} ad request`);
|
|
440
|
+
existing({ type: expectedType, success: false, error: "superseded" });
|
|
441
|
+
}
|
|
442
|
+
let settled = false;
|
|
443
|
+
const timer = setTimeout(() => {
|
|
444
|
+
if (settled) return;
|
|
445
|
+
settled = true;
|
|
446
|
+
this.adResultResolvers.delete(expectedType);
|
|
447
|
+
logger.warn(`[NativeBridge] Ad request timed out: ${expectedType}`);
|
|
448
|
+
resolve({ type: expectedType, success: false, error: "timeout" });
|
|
449
|
+
}, timeoutMs);
|
|
450
|
+
this.adResultResolvers.set(expectedType, (result) => {
|
|
451
|
+
if (settled) return;
|
|
452
|
+
settled = true;
|
|
453
|
+
clearTimeout(timer);
|
|
454
|
+
this.adResultResolvers.delete(expectedType);
|
|
455
|
+
resolve(result);
|
|
456
|
+
});
|
|
457
|
+
this.send(messageType, payload);
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Registers a handler invoked for every `AD_RESULT` message from native,
|
|
462
|
+
* in addition to resolving any pending {@link sendAdRequest} promise.
|
|
463
|
+
* Useful for diagnostics or availability tracking.
|
|
464
|
+
*
|
|
465
|
+
* @param handler - Called with the raw `AD_RESULT` payload
|
|
466
|
+
*/
|
|
467
|
+
static onAdResult(handler) {
|
|
468
|
+
this.on("AD_RESULT" /* AD_RESULT */, handler);
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Resolves the pending {@link sendAdRequest} promise that matches an
|
|
472
|
+
* incoming `AD_RESULT` payload. No-op when nothing is waiting (e.g. a late
|
|
473
|
+
* result after a timeout).
|
|
474
|
+
*/
|
|
475
|
+
static resolveAdResult(payload) {
|
|
476
|
+
if (!payload?.type) {
|
|
477
|
+
logger.warn("[NativeBridge] AD_RESULT received without a type");
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
const resolver = this.adResultResolvers.get(payload.type);
|
|
481
|
+
if (resolver) {
|
|
482
|
+
resolver(payload);
|
|
483
|
+
} else {
|
|
484
|
+
logger.debug(`[NativeBridge] No pending ad request for: ${payload.type}`);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
399
487
|
};
|
|
400
488
|
|
|
401
489
|
// src/utils/jwt.ts
|
|
@@ -752,10 +840,12 @@ function getAttributionData() {
|
|
|
752
840
|
}
|
|
753
841
|
|
|
754
842
|
// src/services/ads.ts
|
|
843
|
+
var FALLBACK_ERROR_CODES = ["not_configured", "config_fetch_failed"];
|
|
755
844
|
var AdsService = class {
|
|
756
845
|
config = {
|
|
757
846
|
sound: "on",
|
|
758
847
|
debug: false,
|
|
848
|
+
useNativeAds: false,
|
|
759
849
|
onBeforeAd: () => {
|
|
760
850
|
},
|
|
761
851
|
onAfterAd: () => {
|
|
@@ -766,6 +856,16 @@ var AdsService = class {
|
|
|
766
856
|
// Cached init promise — ensures adConfig() is only called once
|
|
767
857
|
initPromise = null;
|
|
768
858
|
ready = false;
|
|
859
|
+
// Game this SDK instance serves — sent to native so it can resolve unit IDs.
|
|
860
|
+
// The SDK never stores or resolves unit IDs itself (Decision 7).
|
|
861
|
+
gameId = null;
|
|
862
|
+
/**
|
|
863
|
+
* Set the game ID used in native ad requests. Called by the client once the
|
|
864
|
+
* game ID is known (from URL params or JWT). No-op for the web ad path.
|
|
865
|
+
*/
|
|
866
|
+
setGameId(gameId) {
|
|
867
|
+
this.gameId = gameId;
|
|
868
|
+
}
|
|
769
869
|
/**
|
|
770
870
|
* Optionally configure the ads service.
|
|
771
871
|
* Not required — ads work without calling this.
|
|
@@ -779,7 +879,10 @@ var AdsService = class {
|
|
|
779
879
|
onRewardEarned: config.onRewardEarned ?? this.config.onRewardEarned
|
|
780
880
|
};
|
|
781
881
|
if (this.config.debug) {
|
|
782
|
-
logger.debug("[AdsService] Configuration updated:", {
|
|
882
|
+
logger.debug("[AdsService] Configuration updated:", {
|
|
883
|
+
sound: this.config.sound,
|
|
884
|
+
useNativeAds: this.config.useNativeAds
|
|
885
|
+
});
|
|
783
886
|
}
|
|
784
887
|
}
|
|
785
888
|
/**
|
|
@@ -816,10 +919,93 @@ var AdsService = class {
|
|
|
816
919
|
}
|
|
817
920
|
/**
|
|
818
921
|
* Show an ad. Auto-initializes on the first call.
|
|
819
|
-
*
|
|
922
|
+
*
|
|
923
|
+
* Inside the Hyve mobile shell (`window.ReactNativeWebView` present) with
|
|
924
|
+
* `useNativeAds` enabled, the request is routed to native AdMob via the
|
|
925
|
+
* bridge. Otherwise — or when native reports the slot is `not_configured` /
|
|
926
|
+
* `config_fetch_failed` — it falls back to the Google H5 web path.
|
|
927
|
+
*
|
|
928
|
+
* Returns `success: false` if ads are disabled or unavailable.
|
|
929
|
+
*
|
|
930
|
+
* @param type - Ad type to show
|
|
931
|
+
* @param placement - Optional placement key, forwarded to native; ignored on
|
|
932
|
+
* the web path (H5 has no placement concept).
|
|
820
933
|
*/
|
|
821
|
-
async show(type) {
|
|
934
|
+
async show(type, placement) {
|
|
822
935
|
const requestedAt = Date.now();
|
|
936
|
+
if (this.isNativeAdsContext()) {
|
|
937
|
+
const nativeResult = await this.showNative(type, placement, requestedAt);
|
|
938
|
+
if (nativeResult) return nativeResult;
|
|
939
|
+
}
|
|
940
|
+
return this.showWeb(type, requestedAt, true);
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* True when running inside the Hyve mobile shell with native ads enabled.
|
|
944
|
+
*/
|
|
945
|
+
isNativeAdsContext() {
|
|
946
|
+
return NativeBridge.isNativeContext() && this.config.useNativeAds === true;
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Route an ad request to native AdMob via the bridge (Decision 7 — native
|
|
950
|
+
* owns unit-ID resolution; the SDK only sends `{ gameId, format, placement }`).
|
|
951
|
+
*
|
|
952
|
+
* Returns the resolved `AdResult`, or `null` to indicate the caller should
|
|
953
|
+
* fall back to the web ad path for this single call.
|
|
954
|
+
*/
|
|
955
|
+
async showNative(type, placement, requestedAt) {
|
|
956
|
+
if (!this.gameId) {
|
|
957
|
+
if (this.config.debug) {
|
|
958
|
+
logger.debug("[AdsService] Native ads enabled but no gameId set \u2014 using web path");
|
|
959
|
+
}
|
|
960
|
+
return null;
|
|
961
|
+
}
|
|
962
|
+
const format = type === "rewarded" ? "rewarded" : "interstitial";
|
|
963
|
+
const messageType = type === "rewarded" ? "SHOW_REWARDED_AD" /* SHOW_REWARDED_AD */ : "SHOW_INTERSTITIAL_AD" /* SHOW_INTERSTITIAL_AD */;
|
|
964
|
+
if (this.config.debug) {
|
|
965
|
+
logger.debug(`[AdsService] Requesting native ${type} ad (format: ${format})`, {
|
|
966
|
+
gameId: this.gameId,
|
|
967
|
+
placement
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
this.config.onBeforeAd(type);
|
|
971
|
+
const result = await NativeBridge.sendAdRequest(messageType, {
|
|
972
|
+
gameId: this.gameId,
|
|
973
|
+
format,
|
|
974
|
+
placement
|
|
975
|
+
});
|
|
976
|
+
const completedAt = Date.now();
|
|
977
|
+
if (result.error && FALLBACK_ERROR_CODES.includes(result.error)) {
|
|
978
|
+
if (this.config.debug) {
|
|
979
|
+
logger.debug(`[AdsService] Native ad ${result.error} \u2014 falling back to web path`);
|
|
980
|
+
}
|
|
981
|
+
return this.showWeb(type, requestedAt, false);
|
|
982
|
+
}
|
|
983
|
+
this.config.onAfterAd(type);
|
|
984
|
+
if (type === "rewarded" && result.success) {
|
|
985
|
+
this.config.onRewardEarned();
|
|
986
|
+
}
|
|
987
|
+
if (this.config.debug) {
|
|
988
|
+
logger.debug("[AdsService] Native ad result:", {
|
|
989
|
+
type,
|
|
990
|
+
success: result.success,
|
|
991
|
+
error: result.error
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
return {
|
|
995
|
+
success: result.success,
|
|
996
|
+
type,
|
|
997
|
+
error: result.error ? new Error(result.error) : void 0,
|
|
998
|
+
requestedAt,
|
|
999
|
+
completedAt
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Show an ad via the Google H5 web path. Auto-initializes on first call.
|
|
1004
|
+
*
|
|
1005
|
+
* @param fireBeforeAd - When false, `onBeforeAd` is not fired (the native
|
|
1006
|
+
* fallback path already fired it before delegating here).
|
|
1007
|
+
*/
|
|
1008
|
+
async showWeb(type, requestedAt, fireBeforeAd) {
|
|
823
1009
|
const ready = await this.initialize();
|
|
824
1010
|
if (!ready || !window.adBreak) {
|
|
825
1011
|
return {
|
|
@@ -830,12 +1016,15 @@ var AdsService = class {
|
|
|
830
1016
|
completedAt: Date.now()
|
|
831
1017
|
};
|
|
832
1018
|
}
|
|
833
|
-
return this.showAdBreak(type);
|
|
1019
|
+
return this.showAdBreak(type, fireBeforeAd);
|
|
834
1020
|
}
|
|
835
1021
|
/**
|
|
836
1022
|
* Show an ad break via the Google H5 API.
|
|
1023
|
+
*
|
|
1024
|
+
* @param fireBeforeAd - When false, skips `onBeforeAd` (already fired by the
|
|
1025
|
+
* native fallback path that delegated here).
|
|
837
1026
|
*/
|
|
838
|
-
async showAdBreak(type) {
|
|
1027
|
+
async showAdBreak(type, fireBeforeAd = true) {
|
|
839
1028
|
const requestedAt = Date.now();
|
|
840
1029
|
return new Promise((resolve) => {
|
|
841
1030
|
const googleType = type === "rewarded" ? "reward" : type === "preroll" ? "start" : "next";
|
|
@@ -843,7 +1032,9 @@ var AdsService = class {
|
|
|
843
1032
|
if (this.config.debug) {
|
|
844
1033
|
logger.debug(`[AdsService] Showing ${type} ad`);
|
|
845
1034
|
}
|
|
846
|
-
|
|
1035
|
+
if (fireBeforeAd) {
|
|
1036
|
+
this.config.onBeforeAd(type);
|
|
1037
|
+
}
|
|
847
1038
|
const adBreakConfig = {
|
|
848
1039
|
type: googleType,
|
|
849
1040
|
name: adName,
|
|
@@ -2017,6 +2208,7 @@ var HyveClient = class {
|
|
|
2017
2208
|
this.gameId = params.gameId;
|
|
2018
2209
|
logger.info("Game ID extracted from game-id parameter:", this.gameId);
|
|
2019
2210
|
}
|
|
2211
|
+
this.adsService.setGameId(this.gameId);
|
|
2020
2212
|
if (this.jwtToken) {
|
|
2021
2213
|
logger.info("Authentication successful via JWT");
|
|
2022
2214
|
} else {
|
|
@@ -2330,6 +2522,7 @@ var HyveClient = class {
|
|
|
2330
2522
|
this.userId = null;
|
|
2331
2523
|
this.jwtToken = null;
|
|
2332
2524
|
this.gameId = null;
|
|
2525
|
+
this.adsService.setGameId(null);
|
|
2333
2526
|
logger.info("User logged out");
|
|
2334
2527
|
}
|
|
2335
2528
|
/**
|
|
@@ -2504,9 +2697,11 @@ var HyveClient = class {
|
|
|
2504
2697
|
/**
|
|
2505
2698
|
* Show an ad
|
|
2506
2699
|
* @param type Type of ad to show ('rewarded', 'interstitial', or 'preroll')
|
|
2700
|
+
* @param placement Optional placement key, forwarded to the native AdMob path
|
|
2701
|
+
* to resolve a per-placement unit ID. Ignored on the web (H5/Playgama) path.
|
|
2507
2702
|
* @returns Promise resolving to ad result
|
|
2508
2703
|
*/
|
|
2509
|
-
async showAd(type) {
|
|
2704
|
+
async showAd(type, placement) {
|
|
2510
2705
|
if (this.crazyGamesService) {
|
|
2511
2706
|
if (this.crazyGamesInitPromise) {
|
|
2512
2707
|
await this.crazyGamesInitPromise;
|
|
@@ -2545,7 +2740,7 @@ var HyveClient = class {
|
|
|
2545
2740
|
});
|
|
2546
2741
|
}
|
|
2547
2742
|
}
|
|
2548
|
-
return this.adsService.show(type);
|
|
2743
|
+
return this.adsService.show(type, placement);
|
|
2549
2744
|
}
|
|
2550
2745
|
/**
|
|
2551
2746
|
* Required lifecycle telemetry — Gameplay start.
|
package/dist/index.mjs
CHANGED
|
@@ -158,17 +158,26 @@ var NativeMessageType = /* @__PURE__ */ ((NativeMessageType2) => {
|
|
|
158
158
|
NativeMessageType2["REQUEST_NOTIFICATION_PERMISSION"] = "REQUEST_NOTIFICATION_PERMISSION";
|
|
159
159
|
NativeMessageType2["GET_PRODUCTS"] = "GET_PRODUCTS";
|
|
160
160
|
NativeMessageType2["PURCHASE"] = "PURCHASE";
|
|
161
|
+
NativeMessageType2["SHOW_REWARDED_AD"] = "SHOW_REWARDED_AD";
|
|
162
|
+
NativeMessageType2["SHOW_INTERSTITIAL_AD"] = "SHOW_INTERSTITIAL_AD";
|
|
161
163
|
NativeMessageType2["IAP_AVAILABILITY_RESULT"] = "IAP_AVAILABILITY_RESULT";
|
|
162
164
|
NativeMessageType2["PUSH_PERMISSION_GRANTED"] = "PUSH_PERMISSION_GRANTED";
|
|
163
165
|
NativeMessageType2["PUSH_PERMISSION_DENIED"] = "PUSH_PERMISSION_DENIED";
|
|
164
166
|
NativeMessageType2["PRODUCTS_RESULT"] = "PRODUCTS_RESULT";
|
|
165
167
|
NativeMessageType2["PURCHASE_COMPLETE"] = "PURCHASE_COMPLETE";
|
|
166
168
|
NativeMessageType2["PURCHASE_ERROR"] = "PURCHASE_ERROR";
|
|
169
|
+
NativeMessageType2["AD_RESULT"] = "AD_RESULT";
|
|
167
170
|
return NativeMessageType2;
|
|
168
171
|
})(NativeMessageType || {});
|
|
169
172
|
var NativeBridge = class {
|
|
170
173
|
static handlers = /* @__PURE__ */ new Map();
|
|
171
174
|
static isInitialized = false;
|
|
175
|
+
/**
|
|
176
|
+
* Pending `sendAdRequest()` promises, keyed by the ad type native echoes
|
|
177
|
+
* back in `AD_RESULT.type` ("rewarded" | "interstitial"). One in-flight
|
|
178
|
+
* request per type — ads are shown sequentially.
|
|
179
|
+
*/
|
|
180
|
+
static adResultResolvers = /* @__PURE__ */ new Map();
|
|
172
181
|
/**
|
|
173
182
|
* Checks if the app is running inside a React Native WebView
|
|
174
183
|
*/
|
|
@@ -221,16 +230,20 @@ var NativeBridge = class {
|
|
|
221
230
|
"PUSH_PERMISSION_DENIED" /* PUSH_PERMISSION_DENIED */,
|
|
222
231
|
"PRODUCTS_RESULT" /* PRODUCTS_RESULT */,
|
|
223
232
|
"PURCHASE_COMPLETE" /* PURCHASE_COMPLETE */,
|
|
224
|
-
"PURCHASE_ERROR" /* PURCHASE_ERROR
|
|
233
|
+
"PURCHASE_ERROR" /* PURCHASE_ERROR */,
|
|
234
|
+
"AD_RESULT" /* AD_RESULT */
|
|
225
235
|
];
|
|
226
236
|
if (!nativeResponseTypes.includes(data.type)) {
|
|
227
237
|
return;
|
|
228
238
|
}
|
|
239
|
+
if (data.type === "AD_RESULT" /* AD_RESULT */) {
|
|
240
|
+
this.resolveAdResult(data.payload);
|
|
241
|
+
}
|
|
229
242
|
const handler = this.handlers.get(data.type);
|
|
230
243
|
if (handler) {
|
|
231
244
|
logger.debug(`[NativeBridge] Handling message: ${data.type}`);
|
|
232
245
|
handler(data.payload);
|
|
233
|
-
} else {
|
|
246
|
+
} else if (data.type !== "AD_RESULT" /* AD_RESULT */) {
|
|
234
247
|
logger.warn(`[NativeBridge] No handler registered for: ${data.type}`);
|
|
235
248
|
}
|
|
236
249
|
} catch (error) {
|
|
@@ -356,6 +369,81 @@ var NativeBridge = class {
|
|
|
356
369
|
static purchase(productId, userId) {
|
|
357
370
|
this.send("PURCHASE" /* PURCHASE */, { productId, userId });
|
|
358
371
|
}
|
|
372
|
+
/**
|
|
373
|
+
* Sends a native ad request and resolves with the `AD_RESULT` native sends
|
|
374
|
+
* back. Never rejects — a missing native context, timeout, or superseded
|
|
375
|
+
* request resolves to `{ success: false }` with an error code so callers can
|
|
376
|
+
* decide whether to fall back to the web ad path.
|
|
377
|
+
*
|
|
378
|
+
* @param messageType - `SHOW_REWARDED_AD` or `SHOW_INTERSTITIAL_AD`
|
|
379
|
+
* @param payload - `{ gameId, format, placement? }`
|
|
380
|
+
* @param timeoutMs - How long to wait for `AD_RESULT` before giving up
|
|
381
|
+
*
|
|
382
|
+
* @example
|
|
383
|
+
* const result = await NativeBridge.sendAdRequest(
|
|
384
|
+
* NativeMessageType.SHOW_REWARDED_AD,
|
|
385
|
+
* { gameId: "123", format: "rewarded", placement: "level_end" },
|
|
386
|
+
* );
|
|
387
|
+
* if (result.success) grantReward();
|
|
388
|
+
*/
|
|
389
|
+
static sendAdRequest(messageType, payload, timeoutMs = 12e4) {
|
|
390
|
+
const expectedType = messageType === "SHOW_REWARDED_AD" /* SHOW_REWARDED_AD */ ? "rewarded" : "interstitial";
|
|
391
|
+
return new Promise((resolve) => {
|
|
392
|
+
if (!this.isNativeContext()) {
|
|
393
|
+
resolve({ type: expectedType, success: false, error: "no_native_context" });
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
this.initialize();
|
|
397
|
+
const existing = this.adResultResolvers.get(expectedType);
|
|
398
|
+
if (existing) {
|
|
399
|
+
logger.warn(`[NativeBridge] Superseding in-flight ${expectedType} ad request`);
|
|
400
|
+
existing({ type: expectedType, success: false, error: "superseded" });
|
|
401
|
+
}
|
|
402
|
+
let settled = false;
|
|
403
|
+
const timer = setTimeout(() => {
|
|
404
|
+
if (settled) return;
|
|
405
|
+
settled = true;
|
|
406
|
+
this.adResultResolvers.delete(expectedType);
|
|
407
|
+
logger.warn(`[NativeBridge] Ad request timed out: ${expectedType}`);
|
|
408
|
+
resolve({ type: expectedType, success: false, error: "timeout" });
|
|
409
|
+
}, timeoutMs);
|
|
410
|
+
this.adResultResolvers.set(expectedType, (result) => {
|
|
411
|
+
if (settled) return;
|
|
412
|
+
settled = true;
|
|
413
|
+
clearTimeout(timer);
|
|
414
|
+
this.adResultResolvers.delete(expectedType);
|
|
415
|
+
resolve(result);
|
|
416
|
+
});
|
|
417
|
+
this.send(messageType, payload);
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Registers a handler invoked for every `AD_RESULT` message from native,
|
|
422
|
+
* in addition to resolving any pending {@link sendAdRequest} promise.
|
|
423
|
+
* Useful for diagnostics or availability tracking.
|
|
424
|
+
*
|
|
425
|
+
* @param handler - Called with the raw `AD_RESULT` payload
|
|
426
|
+
*/
|
|
427
|
+
static onAdResult(handler) {
|
|
428
|
+
this.on("AD_RESULT" /* AD_RESULT */, handler);
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Resolves the pending {@link sendAdRequest} promise that matches an
|
|
432
|
+
* incoming `AD_RESULT` payload. No-op when nothing is waiting (e.g. a late
|
|
433
|
+
* result after a timeout).
|
|
434
|
+
*/
|
|
435
|
+
static resolveAdResult(payload) {
|
|
436
|
+
if (!payload?.type) {
|
|
437
|
+
logger.warn("[NativeBridge] AD_RESULT received without a type");
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
const resolver = this.adResultResolvers.get(payload.type);
|
|
441
|
+
if (resolver) {
|
|
442
|
+
resolver(payload);
|
|
443
|
+
} else {
|
|
444
|
+
logger.debug(`[NativeBridge] No pending ad request for: ${payload.type}`);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
359
447
|
};
|
|
360
448
|
|
|
361
449
|
// src/utils/jwt.ts
|
|
@@ -712,10 +800,12 @@ function getAttributionData() {
|
|
|
712
800
|
}
|
|
713
801
|
|
|
714
802
|
// src/services/ads.ts
|
|
803
|
+
var FALLBACK_ERROR_CODES = ["not_configured", "config_fetch_failed"];
|
|
715
804
|
var AdsService = class {
|
|
716
805
|
config = {
|
|
717
806
|
sound: "on",
|
|
718
807
|
debug: false,
|
|
808
|
+
useNativeAds: false,
|
|
719
809
|
onBeforeAd: () => {
|
|
720
810
|
},
|
|
721
811
|
onAfterAd: () => {
|
|
@@ -726,6 +816,16 @@ var AdsService = class {
|
|
|
726
816
|
// Cached init promise — ensures adConfig() is only called once
|
|
727
817
|
initPromise = null;
|
|
728
818
|
ready = false;
|
|
819
|
+
// Game this SDK instance serves — sent to native so it can resolve unit IDs.
|
|
820
|
+
// The SDK never stores or resolves unit IDs itself (Decision 7).
|
|
821
|
+
gameId = null;
|
|
822
|
+
/**
|
|
823
|
+
* Set the game ID used in native ad requests. Called by the client once the
|
|
824
|
+
* game ID is known (from URL params or JWT). No-op for the web ad path.
|
|
825
|
+
*/
|
|
826
|
+
setGameId(gameId) {
|
|
827
|
+
this.gameId = gameId;
|
|
828
|
+
}
|
|
729
829
|
/**
|
|
730
830
|
* Optionally configure the ads service.
|
|
731
831
|
* Not required — ads work without calling this.
|
|
@@ -739,7 +839,10 @@ var AdsService = class {
|
|
|
739
839
|
onRewardEarned: config.onRewardEarned ?? this.config.onRewardEarned
|
|
740
840
|
};
|
|
741
841
|
if (this.config.debug) {
|
|
742
|
-
logger.debug("[AdsService] Configuration updated:", {
|
|
842
|
+
logger.debug("[AdsService] Configuration updated:", {
|
|
843
|
+
sound: this.config.sound,
|
|
844
|
+
useNativeAds: this.config.useNativeAds
|
|
845
|
+
});
|
|
743
846
|
}
|
|
744
847
|
}
|
|
745
848
|
/**
|
|
@@ -776,10 +879,93 @@ var AdsService = class {
|
|
|
776
879
|
}
|
|
777
880
|
/**
|
|
778
881
|
* Show an ad. Auto-initializes on the first call.
|
|
779
|
-
*
|
|
882
|
+
*
|
|
883
|
+
* Inside the Hyve mobile shell (`window.ReactNativeWebView` present) with
|
|
884
|
+
* `useNativeAds` enabled, the request is routed to native AdMob via the
|
|
885
|
+
* bridge. Otherwise — or when native reports the slot is `not_configured` /
|
|
886
|
+
* `config_fetch_failed` — it falls back to the Google H5 web path.
|
|
887
|
+
*
|
|
888
|
+
* Returns `success: false` if ads are disabled or unavailable.
|
|
889
|
+
*
|
|
890
|
+
* @param type - Ad type to show
|
|
891
|
+
* @param placement - Optional placement key, forwarded to native; ignored on
|
|
892
|
+
* the web path (H5 has no placement concept).
|
|
780
893
|
*/
|
|
781
|
-
async show(type) {
|
|
894
|
+
async show(type, placement) {
|
|
782
895
|
const requestedAt = Date.now();
|
|
896
|
+
if (this.isNativeAdsContext()) {
|
|
897
|
+
const nativeResult = await this.showNative(type, placement, requestedAt);
|
|
898
|
+
if (nativeResult) return nativeResult;
|
|
899
|
+
}
|
|
900
|
+
return this.showWeb(type, requestedAt, true);
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* True when running inside the Hyve mobile shell with native ads enabled.
|
|
904
|
+
*/
|
|
905
|
+
isNativeAdsContext() {
|
|
906
|
+
return NativeBridge.isNativeContext() && this.config.useNativeAds === true;
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Route an ad request to native AdMob via the bridge (Decision 7 — native
|
|
910
|
+
* owns unit-ID resolution; the SDK only sends `{ gameId, format, placement }`).
|
|
911
|
+
*
|
|
912
|
+
* Returns the resolved `AdResult`, or `null` to indicate the caller should
|
|
913
|
+
* fall back to the web ad path for this single call.
|
|
914
|
+
*/
|
|
915
|
+
async showNative(type, placement, requestedAt) {
|
|
916
|
+
if (!this.gameId) {
|
|
917
|
+
if (this.config.debug) {
|
|
918
|
+
logger.debug("[AdsService] Native ads enabled but no gameId set \u2014 using web path");
|
|
919
|
+
}
|
|
920
|
+
return null;
|
|
921
|
+
}
|
|
922
|
+
const format = type === "rewarded" ? "rewarded" : "interstitial";
|
|
923
|
+
const messageType = type === "rewarded" ? "SHOW_REWARDED_AD" /* SHOW_REWARDED_AD */ : "SHOW_INTERSTITIAL_AD" /* SHOW_INTERSTITIAL_AD */;
|
|
924
|
+
if (this.config.debug) {
|
|
925
|
+
logger.debug(`[AdsService] Requesting native ${type} ad (format: ${format})`, {
|
|
926
|
+
gameId: this.gameId,
|
|
927
|
+
placement
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
this.config.onBeforeAd(type);
|
|
931
|
+
const result = await NativeBridge.sendAdRequest(messageType, {
|
|
932
|
+
gameId: this.gameId,
|
|
933
|
+
format,
|
|
934
|
+
placement
|
|
935
|
+
});
|
|
936
|
+
const completedAt = Date.now();
|
|
937
|
+
if (result.error && FALLBACK_ERROR_CODES.includes(result.error)) {
|
|
938
|
+
if (this.config.debug) {
|
|
939
|
+
logger.debug(`[AdsService] Native ad ${result.error} \u2014 falling back to web path`);
|
|
940
|
+
}
|
|
941
|
+
return this.showWeb(type, requestedAt, false);
|
|
942
|
+
}
|
|
943
|
+
this.config.onAfterAd(type);
|
|
944
|
+
if (type === "rewarded" && result.success) {
|
|
945
|
+
this.config.onRewardEarned();
|
|
946
|
+
}
|
|
947
|
+
if (this.config.debug) {
|
|
948
|
+
logger.debug("[AdsService] Native ad result:", {
|
|
949
|
+
type,
|
|
950
|
+
success: result.success,
|
|
951
|
+
error: result.error
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
return {
|
|
955
|
+
success: result.success,
|
|
956
|
+
type,
|
|
957
|
+
error: result.error ? new Error(result.error) : void 0,
|
|
958
|
+
requestedAt,
|
|
959
|
+
completedAt
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
963
|
+
* Show an ad via the Google H5 web path. Auto-initializes on first call.
|
|
964
|
+
*
|
|
965
|
+
* @param fireBeforeAd - When false, `onBeforeAd` is not fired (the native
|
|
966
|
+
* fallback path already fired it before delegating here).
|
|
967
|
+
*/
|
|
968
|
+
async showWeb(type, requestedAt, fireBeforeAd) {
|
|
783
969
|
const ready = await this.initialize();
|
|
784
970
|
if (!ready || !window.adBreak) {
|
|
785
971
|
return {
|
|
@@ -790,12 +976,15 @@ var AdsService = class {
|
|
|
790
976
|
completedAt: Date.now()
|
|
791
977
|
};
|
|
792
978
|
}
|
|
793
|
-
return this.showAdBreak(type);
|
|
979
|
+
return this.showAdBreak(type, fireBeforeAd);
|
|
794
980
|
}
|
|
795
981
|
/**
|
|
796
982
|
* Show an ad break via the Google H5 API.
|
|
983
|
+
*
|
|
984
|
+
* @param fireBeforeAd - When false, skips `onBeforeAd` (already fired by the
|
|
985
|
+
* native fallback path that delegated here).
|
|
797
986
|
*/
|
|
798
|
-
async showAdBreak(type) {
|
|
987
|
+
async showAdBreak(type, fireBeforeAd = true) {
|
|
799
988
|
const requestedAt = Date.now();
|
|
800
989
|
return new Promise((resolve) => {
|
|
801
990
|
const googleType = type === "rewarded" ? "reward" : type === "preroll" ? "start" : "next";
|
|
@@ -803,7 +992,9 @@ var AdsService = class {
|
|
|
803
992
|
if (this.config.debug) {
|
|
804
993
|
logger.debug(`[AdsService] Showing ${type} ad`);
|
|
805
994
|
}
|
|
806
|
-
|
|
995
|
+
if (fireBeforeAd) {
|
|
996
|
+
this.config.onBeforeAd(type);
|
|
997
|
+
}
|
|
807
998
|
const adBreakConfig = {
|
|
808
999
|
type: googleType,
|
|
809
1000
|
name: adName,
|
|
@@ -1977,6 +2168,7 @@ var HyveClient = class {
|
|
|
1977
2168
|
this.gameId = params.gameId;
|
|
1978
2169
|
logger.info("Game ID extracted from game-id parameter:", this.gameId);
|
|
1979
2170
|
}
|
|
2171
|
+
this.adsService.setGameId(this.gameId);
|
|
1980
2172
|
if (this.jwtToken) {
|
|
1981
2173
|
logger.info("Authentication successful via JWT");
|
|
1982
2174
|
} else {
|
|
@@ -2290,6 +2482,7 @@ var HyveClient = class {
|
|
|
2290
2482
|
this.userId = null;
|
|
2291
2483
|
this.jwtToken = null;
|
|
2292
2484
|
this.gameId = null;
|
|
2485
|
+
this.adsService.setGameId(null);
|
|
2293
2486
|
logger.info("User logged out");
|
|
2294
2487
|
}
|
|
2295
2488
|
/**
|
|
@@ -2464,9 +2657,11 @@ var HyveClient = class {
|
|
|
2464
2657
|
/**
|
|
2465
2658
|
* Show an ad
|
|
2466
2659
|
* @param type Type of ad to show ('rewarded', 'interstitial', or 'preroll')
|
|
2660
|
+
* @param placement Optional placement key, forwarded to the native AdMob path
|
|
2661
|
+
* to resolve a per-placement unit ID. Ignored on the web (H5/Playgama) path.
|
|
2467
2662
|
* @returns Promise resolving to ad result
|
|
2468
2663
|
*/
|
|
2469
|
-
async showAd(type) {
|
|
2664
|
+
async showAd(type, placement) {
|
|
2470
2665
|
if (this.crazyGamesService) {
|
|
2471
2666
|
if (this.crazyGamesInitPromise) {
|
|
2472
2667
|
await this.crazyGamesInitPromise;
|
|
@@ -2505,7 +2700,7 @@ var HyveClient = class {
|
|
|
2505
2700
|
});
|
|
2506
2701
|
}
|
|
2507
2702
|
}
|
|
2508
|
-
return this.adsService.show(type);
|
|
2703
|
+
return this.adsService.show(type, placement);
|
|
2509
2704
|
}
|
|
2510
2705
|
/**
|
|
2511
2706
|
* Required lifecycle telemetry — Gameplay start.
|