@hyve-sdk/js 2.12.0 → 2.13.0-canary.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/react.mjs
CHANGED
|
@@ -148,6 +148,12 @@ function parseUrlParams(searchParams) {
|
|
|
148
148
|
var NativeBridge = class {
|
|
149
149
|
static handlers = /* @__PURE__ */ new Map();
|
|
150
150
|
static isInitialized = false;
|
|
151
|
+
/**
|
|
152
|
+
* Pending `sendAdRequest()` promises, keyed by the ad type native echoes
|
|
153
|
+
* back in `AD_RESULT.type` ("rewarded" | "interstitial"). One in-flight
|
|
154
|
+
* request per type — ads are shown sequentially.
|
|
155
|
+
*/
|
|
156
|
+
static adResultResolvers = /* @__PURE__ */ new Map();
|
|
151
157
|
/**
|
|
152
158
|
* Checks if the app is running inside a React Native WebView
|
|
153
159
|
*/
|
|
@@ -200,16 +206,20 @@ var NativeBridge = class {
|
|
|
200
206
|
"PUSH_PERMISSION_DENIED" /* PUSH_PERMISSION_DENIED */,
|
|
201
207
|
"PRODUCTS_RESULT" /* PRODUCTS_RESULT */,
|
|
202
208
|
"PURCHASE_COMPLETE" /* PURCHASE_COMPLETE */,
|
|
203
|
-
"PURCHASE_ERROR" /* PURCHASE_ERROR
|
|
209
|
+
"PURCHASE_ERROR" /* PURCHASE_ERROR */,
|
|
210
|
+
"AD_RESULT" /* AD_RESULT */
|
|
204
211
|
];
|
|
205
212
|
if (!nativeResponseTypes.includes(data.type)) {
|
|
206
213
|
return;
|
|
207
214
|
}
|
|
215
|
+
if (data.type === "AD_RESULT" /* AD_RESULT */) {
|
|
216
|
+
this.resolveAdResult(data.payload);
|
|
217
|
+
}
|
|
208
218
|
const handler = this.handlers.get(data.type);
|
|
209
219
|
if (handler) {
|
|
210
220
|
logger.debug(`[NativeBridge] Handling message: ${data.type}`);
|
|
211
221
|
handler(data.payload);
|
|
212
|
-
} else {
|
|
222
|
+
} else if (data.type !== "AD_RESULT" /* AD_RESULT */) {
|
|
213
223
|
logger.warn(`[NativeBridge] No handler registered for: ${data.type}`);
|
|
214
224
|
}
|
|
215
225
|
} catch (error) {
|
|
@@ -335,6 +345,81 @@ var NativeBridge = class {
|
|
|
335
345
|
static purchase(productId, userId) {
|
|
336
346
|
this.send("PURCHASE" /* PURCHASE */, { productId, userId });
|
|
337
347
|
}
|
|
348
|
+
/**
|
|
349
|
+
* Sends a native ad request and resolves with the `AD_RESULT` native sends
|
|
350
|
+
* back. Never rejects — a missing native context, timeout, or superseded
|
|
351
|
+
* request resolves to `{ success: false }` with an error code so callers can
|
|
352
|
+
* decide whether to fall back to the web ad path.
|
|
353
|
+
*
|
|
354
|
+
* @param messageType - `SHOW_REWARDED_AD` or `SHOW_INTERSTITIAL_AD`
|
|
355
|
+
* @param payload - `{ gameId, format, placement? }`
|
|
356
|
+
* @param timeoutMs - How long to wait for `AD_RESULT` before giving up
|
|
357
|
+
*
|
|
358
|
+
* @example
|
|
359
|
+
* const result = await NativeBridge.sendAdRequest(
|
|
360
|
+
* NativeMessageType.SHOW_REWARDED_AD,
|
|
361
|
+
* { gameId: "123", format: "rewarded", placement: "level_end" },
|
|
362
|
+
* );
|
|
363
|
+
* if (result.success) grantReward();
|
|
364
|
+
*/
|
|
365
|
+
static sendAdRequest(messageType, payload, timeoutMs = 12e4) {
|
|
366
|
+
const expectedType = messageType === "SHOW_REWARDED_AD" /* SHOW_REWARDED_AD */ ? "rewarded" : "interstitial";
|
|
367
|
+
return new Promise((resolve) => {
|
|
368
|
+
if (!this.isNativeContext()) {
|
|
369
|
+
resolve({ type: expectedType, success: false, error: "no_native_context" });
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
this.initialize();
|
|
373
|
+
const existing = this.adResultResolvers.get(expectedType);
|
|
374
|
+
if (existing) {
|
|
375
|
+
logger.warn(`[NativeBridge] Superseding in-flight ${expectedType} ad request`);
|
|
376
|
+
existing({ type: expectedType, success: false, error: "superseded" });
|
|
377
|
+
}
|
|
378
|
+
let settled = false;
|
|
379
|
+
const timer = setTimeout(() => {
|
|
380
|
+
if (settled) return;
|
|
381
|
+
settled = true;
|
|
382
|
+
this.adResultResolvers.delete(expectedType);
|
|
383
|
+
logger.warn(`[NativeBridge] Ad request timed out: ${expectedType}`);
|
|
384
|
+
resolve({ type: expectedType, success: false, error: "timeout" });
|
|
385
|
+
}, timeoutMs);
|
|
386
|
+
this.adResultResolvers.set(expectedType, (result) => {
|
|
387
|
+
if (settled) return;
|
|
388
|
+
settled = true;
|
|
389
|
+
clearTimeout(timer);
|
|
390
|
+
this.adResultResolvers.delete(expectedType);
|
|
391
|
+
resolve(result);
|
|
392
|
+
});
|
|
393
|
+
this.send(messageType, payload);
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Registers a handler invoked for every `AD_RESULT` message from native,
|
|
398
|
+
* in addition to resolving any pending {@link sendAdRequest} promise.
|
|
399
|
+
* Useful for diagnostics or availability tracking.
|
|
400
|
+
*
|
|
401
|
+
* @param handler - Called with the raw `AD_RESULT` payload
|
|
402
|
+
*/
|
|
403
|
+
static onAdResult(handler) {
|
|
404
|
+
this.on("AD_RESULT" /* AD_RESULT */, handler);
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Resolves the pending {@link sendAdRequest} promise that matches an
|
|
408
|
+
* incoming `AD_RESULT` payload. No-op when nothing is waiting (e.g. a late
|
|
409
|
+
* result after a timeout).
|
|
410
|
+
*/
|
|
411
|
+
static resolveAdResult(payload) {
|
|
412
|
+
if (!payload?.type) {
|
|
413
|
+
logger.warn("[NativeBridge] AD_RESULT received without a type");
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
const resolver = this.adResultResolvers.get(payload.type);
|
|
417
|
+
if (resolver) {
|
|
418
|
+
resolver(payload);
|
|
419
|
+
} else {
|
|
420
|
+
logger.debug(`[NativeBridge] No pending ad request for: ${payload.type}`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
338
423
|
};
|
|
339
424
|
|
|
340
425
|
// src/utils/jwt.ts
|
|
@@ -691,10 +776,12 @@ function getAttributionData() {
|
|
|
691
776
|
}
|
|
692
777
|
|
|
693
778
|
// src/services/ads.ts
|
|
779
|
+
var FALLBACK_ERROR_CODES = ["not_configured", "config_fetch_failed"];
|
|
694
780
|
var AdsService = class {
|
|
695
781
|
config = {
|
|
696
782
|
sound: "on",
|
|
697
783
|
debug: false,
|
|
784
|
+
useNativeAds: false,
|
|
698
785
|
onBeforeAd: () => {
|
|
699
786
|
},
|
|
700
787
|
onAfterAd: () => {
|
|
@@ -705,6 +792,16 @@ var AdsService = class {
|
|
|
705
792
|
// Cached init promise — ensures adConfig() is only called once
|
|
706
793
|
initPromise = null;
|
|
707
794
|
ready = false;
|
|
795
|
+
// Game this SDK instance serves — sent to native so it can resolve unit IDs.
|
|
796
|
+
// The SDK never stores or resolves unit IDs itself (Decision 7).
|
|
797
|
+
gameId = null;
|
|
798
|
+
/**
|
|
799
|
+
* Set the game ID used in native ad requests. Called by the client once the
|
|
800
|
+
* game ID is known (from URL params or JWT). No-op for the web ad path.
|
|
801
|
+
*/
|
|
802
|
+
setGameId(gameId) {
|
|
803
|
+
this.gameId = gameId;
|
|
804
|
+
}
|
|
708
805
|
/**
|
|
709
806
|
* Optionally configure the ads service.
|
|
710
807
|
* Not required — ads work without calling this.
|
|
@@ -718,7 +815,10 @@ var AdsService = class {
|
|
|
718
815
|
onRewardEarned: config.onRewardEarned ?? this.config.onRewardEarned
|
|
719
816
|
};
|
|
720
817
|
if (this.config.debug) {
|
|
721
|
-
logger.debug("[AdsService] Configuration updated:", {
|
|
818
|
+
logger.debug("[AdsService] Configuration updated:", {
|
|
819
|
+
sound: this.config.sound,
|
|
820
|
+
useNativeAds: this.config.useNativeAds
|
|
821
|
+
});
|
|
722
822
|
}
|
|
723
823
|
}
|
|
724
824
|
/**
|
|
@@ -755,10 +855,93 @@ var AdsService = class {
|
|
|
755
855
|
}
|
|
756
856
|
/**
|
|
757
857
|
* Show an ad. Auto-initializes on the first call.
|
|
758
|
-
*
|
|
858
|
+
*
|
|
859
|
+
* Inside the Hyve mobile shell (`window.ReactNativeWebView` present) with
|
|
860
|
+
* `useNativeAds` enabled, the request is routed to native AdMob via the
|
|
861
|
+
* bridge. Otherwise — or when native reports the slot is `not_configured` /
|
|
862
|
+
* `config_fetch_failed` — it falls back to the Google H5 web path.
|
|
863
|
+
*
|
|
864
|
+
* Returns `success: false` if ads are disabled or unavailable.
|
|
865
|
+
*
|
|
866
|
+
* @param type - Ad type to show
|
|
867
|
+
* @param placement - Optional placement key, forwarded to native; ignored on
|
|
868
|
+
* the web path (H5 has no placement concept).
|
|
759
869
|
*/
|
|
760
|
-
async show(type) {
|
|
870
|
+
async show(type, placement) {
|
|
761
871
|
const requestedAt = Date.now();
|
|
872
|
+
if (this.isNativeAdsContext()) {
|
|
873
|
+
const nativeResult = await this.showNative(type, placement, requestedAt);
|
|
874
|
+
if (nativeResult) return nativeResult;
|
|
875
|
+
}
|
|
876
|
+
return this.showWeb(type, requestedAt, true);
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* True when running inside the Hyve mobile shell with native ads enabled.
|
|
880
|
+
*/
|
|
881
|
+
isNativeAdsContext() {
|
|
882
|
+
return NativeBridge.isNativeContext() && this.config.useNativeAds === true;
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* Route an ad request to native AdMob via the bridge (Decision 7 — native
|
|
886
|
+
* owns unit-ID resolution; the SDK only sends `{ gameId, format, placement }`).
|
|
887
|
+
*
|
|
888
|
+
* Returns the resolved `AdResult`, or `null` to indicate the caller should
|
|
889
|
+
* fall back to the web ad path for this single call.
|
|
890
|
+
*/
|
|
891
|
+
async showNative(type, placement, requestedAt) {
|
|
892
|
+
if (!this.gameId) {
|
|
893
|
+
if (this.config.debug) {
|
|
894
|
+
logger.debug("[AdsService] Native ads enabled but no gameId set \u2014 using web path");
|
|
895
|
+
}
|
|
896
|
+
return null;
|
|
897
|
+
}
|
|
898
|
+
const format = type === "rewarded" ? "rewarded" : "interstitial";
|
|
899
|
+
const messageType = type === "rewarded" ? "SHOW_REWARDED_AD" /* SHOW_REWARDED_AD */ : "SHOW_INTERSTITIAL_AD" /* SHOW_INTERSTITIAL_AD */;
|
|
900
|
+
if (this.config.debug) {
|
|
901
|
+
logger.debug(`[AdsService] Requesting native ${type} ad (format: ${format})`, {
|
|
902
|
+
gameId: this.gameId,
|
|
903
|
+
placement
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
this.config.onBeforeAd(type);
|
|
907
|
+
const result = await NativeBridge.sendAdRequest(messageType, {
|
|
908
|
+
gameId: this.gameId,
|
|
909
|
+
format,
|
|
910
|
+
placement
|
|
911
|
+
});
|
|
912
|
+
const completedAt = Date.now();
|
|
913
|
+
if (result.error && FALLBACK_ERROR_CODES.includes(result.error)) {
|
|
914
|
+
if (this.config.debug) {
|
|
915
|
+
logger.debug(`[AdsService] Native ad ${result.error} \u2014 falling back to web path`);
|
|
916
|
+
}
|
|
917
|
+
return this.showWeb(type, requestedAt, false);
|
|
918
|
+
}
|
|
919
|
+
this.config.onAfterAd(type);
|
|
920
|
+
if (type === "rewarded" && result.success) {
|
|
921
|
+
this.config.onRewardEarned();
|
|
922
|
+
}
|
|
923
|
+
if (this.config.debug) {
|
|
924
|
+
logger.debug("[AdsService] Native ad result:", {
|
|
925
|
+
type,
|
|
926
|
+
success: result.success,
|
|
927
|
+
error: result.error
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
return {
|
|
931
|
+
success: result.success,
|
|
932
|
+
type,
|
|
933
|
+
error: result.error ? new Error(result.error) : void 0,
|
|
934
|
+
requestedAt,
|
|
935
|
+
completedAt
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* Show an ad via the Google H5 web path. Auto-initializes on first call.
|
|
940
|
+
*
|
|
941
|
+
* @param fireBeforeAd - When false, `onBeforeAd` is not fired (the native
|
|
942
|
+
* fallback path already fired it before delegating here).
|
|
943
|
+
*/
|
|
944
|
+
async showWeb(type, requestedAt, fireBeforeAd) {
|
|
762
945
|
const ready = await this.initialize();
|
|
763
946
|
if (!ready || !window.adBreak) {
|
|
764
947
|
return {
|
|
@@ -769,12 +952,15 @@ var AdsService = class {
|
|
|
769
952
|
completedAt: Date.now()
|
|
770
953
|
};
|
|
771
954
|
}
|
|
772
|
-
return this.showAdBreak(type);
|
|
955
|
+
return this.showAdBreak(type, fireBeforeAd);
|
|
773
956
|
}
|
|
774
957
|
/**
|
|
775
958
|
* Show an ad break via the Google H5 API.
|
|
959
|
+
*
|
|
960
|
+
* @param fireBeforeAd - When false, skips `onBeforeAd` (already fired by the
|
|
961
|
+
* native fallback path that delegated here).
|
|
776
962
|
*/
|
|
777
|
-
async showAdBreak(type) {
|
|
963
|
+
async showAdBreak(type, fireBeforeAd = true) {
|
|
778
964
|
const requestedAt = Date.now();
|
|
779
965
|
return new Promise((resolve) => {
|
|
780
966
|
const googleType = type === "rewarded" ? "reward" : type === "preroll" ? "start" : "next";
|
|
@@ -782,7 +968,9 @@ var AdsService = class {
|
|
|
782
968
|
if (this.config.debug) {
|
|
783
969
|
logger.debug(`[AdsService] Showing ${type} ad`);
|
|
784
970
|
}
|
|
785
|
-
|
|
971
|
+
if (fireBeforeAd) {
|
|
972
|
+
this.config.onBeforeAd(type);
|
|
973
|
+
}
|
|
786
974
|
const adBreakConfig = {
|
|
787
975
|
type: googleType,
|
|
788
976
|
name: adName,
|
|
@@ -1950,6 +2138,7 @@ var HyveClient = class {
|
|
|
1950
2138
|
this.gameId = params.gameId;
|
|
1951
2139
|
logger.info("Game ID extracted from game-id parameter:", this.gameId);
|
|
1952
2140
|
}
|
|
2141
|
+
this.adsService.setGameId(this.gameId);
|
|
1953
2142
|
if (this.jwtToken) {
|
|
1954
2143
|
logger.info("Authentication successful via JWT");
|
|
1955
2144
|
} else {
|
|
@@ -2263,6 +2452,7 @@ var HyveClient = class {
|
|
|
2263
2452
|
this.userId = null;
|
|
2264
2453
|
this.jwtToken = null;
|
|
2265
2454
|
this.gameId = null;
|
|
2455
|
+
this.adsService.setGameId(null);
|
|
2266
2456
|
logger.info("User logged out");
|
|
2267
2457
|
}
|
|
2268
2458
|
/**
|
|
@@ -2437,9 +2627,11 @@ var HyveClient = class {
|
|
|
2437
2627
|
/**
|
|
2438
2628
|
* Show an ad
|
|
2439
2629
|
* @param type Type of ad to show ('rewarded', 'interstitial', or 'preroll')
|
|
2630
|
+
* @param placement Optional placement key, forwarded to the native AdMob path
|
|
2631
|
+
* to resolve a per-placement unit ID. Ignored on the web (H5/Playgama) path.
|
|
2440
2632
|
* @returns Promise resolving to ad result
|
|
2441
2633
|
*/
|
|
2442
|
-
async showAd(type) {
|
|
2634
|
+
async showAd(type, placement) {
|
|
2443
2635
|
if (this.crazyGamesService) {
|
|
2444
2636
|
if (this.crazyGamesInitPromise) {
|
|
2445
2637
|
await this.crazyGamesInitPromise;
|
|
@@ -2478,7 +2670,7 @@ var HyveClient = class {
|
|
|
2478
2670
|
});
|
|
2479
2671
|
}
|
|
2480
2672
|
}
|
|
2481
|
-
return this.adsService.show(type);
|
|
2673
|
+
return this.adsService.show(type, placement);
|
|
2482
2674
|
}
|
|
2483
2675
|
/**
|
|
2484
2676
|
* Required lifecycle telemetry — Gameplay start.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyve-sdk/js",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.0-canary.0",
|
|
4
4
|
"description": "Hyve SDK - TypeScript wrapper for Hyve game server integration",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -27,14 +27,6 @@
|
|
|
27
27
|
"README.md",
|
|
28
28
|
"LICENSE"
|
|
29
29
|
],
|
|
30
|
-
"scripts": {
|
|
31
|
-
"lint": "eslint . --max-warnings 0",
|
|
32
|
-
"check-types": "tsc --noEmit",
|
|
33
|
-
"build": "tsup",
|
|
34
|
-
"prepublishOnly": "pnpm run build && pnpm run check-types",
|
|
35
|
-
"publish:npm": "pnpm publish --access public",
|
|
36
|
-
"publish:dry-run": "pnpm publish --dry-run --access public --no-git-checks"
|
|
37
|
-
},
|
|
38
30
|
"keywords": [
|
|
39
31
|
"hyve",
|
|
40
32
|
"game",
|
|
@@ -71,12 +63,23 @@
|
|
|
71
63
|
}
|
|
72
64
|
},
|
|
73
65
|
"devDependencies": {
|
|
74
|
-
"@repo/eslint-config": "workspace:*",
|
|
75
|
-
"@repo/typescript-config": "workspace:*",
|
|
76
66
|
"@types/minimatch": "^5.1.2",
|
|
77
67
|
"@types/react": "^18.3.28",
|
|
78
68
|
"@types/uuid": "^10.0.0",
|
|
79
69
|
"tsup": "^8.4.0",
|
|
80
|
-
"typescript": "^5.3.3"
|
|
70
|
+
"typescript": "^5.3.3",
|
|
71
|
+
"vitest": "^4.1.8",
|
|
72
|
+
"@repo/typescript-config": "0.0.0",
|
|
73
|
+
"@repo/eslint-config": "0.0.0"
|
|
74
|
+
},
|
|
75
|
+
"scripts": {
|
|
76
|
+
"lint": "eslint . --max-warnings 0",
|
|
77
|
+
"test": "vitest run",
|
|
78
|
+
"test:watch": "vitest",
|
|
79
|
+
"check-types": "tsc --noEmit",
|
|
80
|
+
"build": "tsup",
|
|
81
|
+
"publish:npm": "pnpm publish --access public",
|
|
82
|
+
"publish:canary": "pnpm publish --access public --tag canary --no-git-checks",
|
|
83
|
+
"publish:dry-run": "pnpm publish --dry-run --access public --no-git-checks"
|
|
81
84
|
}
|
|
82
|
-
}
|
|
85
|
+
}
|