@hyve-sdk/js
v2.14.3
Published
Hyve SDK - TypeScript wrapper for Hyve game server integration
Downloads
1,952
Readme
@hyve-sdk/js
TypeScript SDK for integrating games with the Hyve platform. Provides authentication, telemetry, persistent storage, ads, billing, and native bridge capabilities.
Installation
bun add @hyve-sdk/jsQuick Start
import { HyveSdkProvider, useHyveSdk } from "@hyve-sdk/js/react";
function App() {
return (
<HyveSdkProvider>
<Game />
</HyveSdkProvider>
);
}
function Game() {
const hyve = useHyveSdk();
const handleScore = () => {
hyve.sendTelemetry("game", "player", "score_submitted", null, null, { score: 1000 });
};
return <button onClick={handleScore}>Submit Score</button>;
}You can also pass a pre-created client:
import { HyveClient } from "@hyve-sdk/js";
import { HyveSdkProvider } from "@hyve-sdk/js/react";
const client = new HyveClient();
<HyveSdkProvider client={client}>
<App />
</HyveSdkProvider>The SDK reads authentication from URL parameters automatically during construction:
https://your-game.com?hyve-access=eyJhbGci...&game-id=my-gameEnvironment detection happens automatically from the parent page URL (games run in iframes):
- Dev:
marvin.dev.hyve.ggordev.hyve.gg - Prod:
marvin.hyve.ggorhyve.gg
Telemetry
const hyve = useHyveSdk();
await hyve.sendTelemetry(
"game", // location
"player", // category
"action", // action
"combat", // sub-category (optional)
"attack", // sub-action (optional)
{ // event details (optional)
damage: 100,
targetId: "enemy-123",
},
"playgama" // platform ID (optional)
);Requires a valid hyve-access JWT and game-id in the URL.
Persistent Game Data
Save and retrieve game data with either cloud (default) or local storage:
const hyve = useHyveSdk();
// Save a value
await hyve.saveGameData("player_level", 5);
await hyve.saveGameData("settings", { volume: 0.8, fullscreen: true });
// Atomic operations — read-modify-write server-side
await hyve.saveGameData("coins", 100, "add"); // increment
await hyve.saveGameData("high_score", 9999, "max"); // keep highest
await hyve.saveGameData("stats", 1, "add", "level"); // nested path
// Get a value
const item = await hyve.getGameData("player_level");
console.log(item?.value); // 5
// Batch save (per-item operations supported)
await hyve.batchSaveGameData([
{ key: "coins", value: 1200 },
{ key: "lives", value: 3 },
{ key: "high_score", value: 5000, operation: "max" },
]);
// Batch get
const items = await hyve.getMultipleGameData(["coins", "lives"]);
// Delete
await hyve.deleteGameData("temp_key");
await hyve.deleteMultipleGameData(["key1", "key2"]);Storage Modes
// Set mode at construction
const client = new HyveClient({ storageMode: "local" });
// Or override per-call (storage is the last parameter)
await hyve.saveGameData("key", "value", undefined, undefined, "local");
await hyve.getGameData("key", "cloud");
// Change mode at runtime
hyve.configureStorage("cloud");
console.log(hyve.getStorageMode()); // "cloud"| Mode | Description |
|------|-------------|
| cloud | Synced to backend API (default) |
| local | Browser localStorage, device-only |
On partner platforms (e.g. CrazyGames) the SDK transparently routes storage to that platform's own data store regardless of mode, since the Hyve cloud backend isn't reachable there.
Checking storage availability
Do not gate saving on isUserAuthenticated() — it only reflects a Hyve JWT and is always false on CrazyGames, whose data store needs no Hyve login (it even works for guests). Gating on auth silently disables all saving there. Use isStorageAvailable(), which reports whether a save will actually persist for the active backend:
// Correct gate — awaits platform init, accounts for CrazyGames / cloud / local
if (await hyve.isStorageAvailable()) {
await hyve.saveGameData("player_level", 5);
}isStorageAvailable() is true when: on CrazyGames the data store is ready (SDK initialized and the "Progress Save" toggle is enabled for the submission); in cloud mode a Hyve JWT is present; in local mode always (browser localStorage).
Leaderboards
Rank players by numeric values stored in persistent game data.
const hyve = useHyveSdk();
// 1. Store the player's score
await hyve.saveGameData("high_score", 4200);
// 2. Fetch the leaderboard
const board = await hyve.getGameDataLeaderboard({ key: "high_score" });
for (const entry of board.entries) {
console.log(`#${entry.rank} ${entry.username}: ${entry.score}`);
}
// The caller's own rank is included automatically
if (board.user_position) {
console.log("Your rank:", board.user_position.rank);
}Use score_path to rank by a nested field, and sort/limit/cursor for pagination:
const board = await hyve.getGameDataLeaderboard({
key: "tetris_progress",
score_path: "stats.score", // dot-notation path to numeric field
sort: "desc", // "asc" | "desc" (default: "desc")
limit: 20, // 1–100 (default: 10)
});
if (board.has_more && board.next_cursor) {
const next = await hyve.getGameDataLeaderboard({
key: "tetris_progress",
cursor: board.next_cursor,
});
}Ads
Ads are disabled by default and must be explicitly configured.
const client = new HyveClient({
ads: {
enabled: true,
onBeforeAd: (type) => game.pause(),
onAfterAd: (type) => game.resume(),
onRewardEarned: () => { player.coins += 100; },
},
});
<HyveSdkProvider client={client}>
<App />
</HyveSdkProvider>const hyve = useHyveSdk();
const result = await hyve.showAd("rewarded");
if (result.success) {
console.log("User watched the ad");
}
await hyve.showAd("interstitial"); // between levels
await hyve.showAd("preroll"); // game start
// Optional placement key, used by native AdMob to resolve a per-placement
// ad unit. Ignored on the web path.
await hyve.showAd("rewarded", "level_end");| Ad Type | Use Case |
|---------|----------|
| rewarded | User watches full ad for a reward |
| interstitial | Between levels or game screens |
| preroll | Before the game starts |
The SDK automatically routes ad calls through the appropriate platform SDK (CrazyGames, Playgama, or the default Google H5 Ads SDK) based on the current domain.
Native AdMob
Inside the Hyve mobile shell, set useNativeAds: true to serve ads via native
AdMob instead of Google H5. Routing is decided per call: when
window.ReactNativeWebView is present and useNativeAds is enabled, ad
requests go to native AdMob (preroll maps to a native interstitial);
otherwise the H5 web path is used. The game-facing API is unchanged, and any
not_configured / config_fetch_failed response from native falls back to H5
for that call. See docs/ads.md for details.
Platform Integrations
CrazyGames
When running on CrazyGames, the SDK auto-initializes the CrazyGames SDK and routes ads through it. Use these additional lifecycle methods:
const hyve = useHyveSdk();
// Call when gameplay begins (required by CrazyGames policy)
await hyve.gameplayStart();
// Call when gameplay stops (paused, died, menu, etc.)
await hyve.gameplayStop();
// Trigger a celebration effect on the CrazyGames website
await hyve.happytime();Gameplay start/stop tracking is handled automatically where the SDK has the context to do so:
gameplayStart()andgameplayEnd()lifecycle telemetry calls forward to the CrazyGames SDK, so games emitting the required Hyve lifecycle events get CrazyGames gameplay tracking for free.- Ads shown via
showAd()automatically stop gameplay tracking for the duration of the ad and resume it afterwards (only if gameplay was active when the ad was requested). - Calls are idempotent — duplicate starts or stops are not forwarded.
- Focus/tab changes are intentionally not tracked: CrazyGames handles those on their side and instructs games not to signal them.
Playgama
When running on Playgama, the SDK auto-initializes the Playgama Bridge and routes ads through it. No additional setup required.
Billing
Billing supports web (Stripe) and native (In-App Purchases) — platform is detected automatically.
const client = new HyveClient({
billing: {
stripePublishableKey: "pk_live_...",
},
});
<HyveSdkProvider client={client}>
<App />
</HyveSdkProvider>const hyve = useHyveSdk();
if (hyve.isBillingAvailable()) {
hyve.onPurchaseComplete((result) => {
console.log("Purchase successful:", result.transactionId);
});
hyve.onPurchaseError((result) => {
console.error("Purchase failed:", result.error?.message);
});
const products = await hyve.getBillingProducts();
await hyve.purchaseProduct("price_1234");
}For web, add a container element for the Stripe checkout UI:
<div id="stripe-checkout-element"></div>| Platform | Payment Method | Detection |
|----------|---------------|-----------|
| Web | Stripe Embedded Checkout | Default |
| Native iOS/Android | In-App Purchases | ReactNativeWebView in window |
Native Bridge
Type-safe bidirectional communication between your web game and a React Native WebView host.
import { NativeBridge } from "@hyve-sdk/js";
if (NativeBridge.isNativeContext()) {
NativeBridge.initialize();
// Listen for IAP availability
NativeBridge.on("IAP_AVAILABILITY_RESULT", (payload) => {
if (payload.available) {
console.log("IAP available on:", payload.platform);
}
});
NativeBridge.checkIAPAvailability();
// Request push notification permission
NativeBridge.on("PUSH_PERMISSION_GRANTED", (payload) => {
console.log("Token:", payload?.token);
});
NativeBridge.requestNotificationPermission();
// Send/receive custom messages
NativeBridge.send("GAME_EVENT", { action: "level_complete", level: 3 });
NativeBridge.on("CUSTOM_RESPONSE", (payload) => {
console.log("Received:", payload);
});
}NativeBridge API
| Method | Description |
|--------|-------------|
| isNativeContext() | Check if running in React Native WebView |
| initialize() | Start the message listener |
| send(type, payload?) | Send a message to the native app |
| on(type, handler) | Register a message handler |
| off(type) | Remove a message handler |
| clearHandlers() | Remove all handlers |
| checkIAPAvailability() | Request IAP availability from native |
| requestNotificationPermission() | Request push notification permission |
Logger
import { logger } from "@hyve-sdk/js";
logger.debug("Debug info", { data: "value" });
logger.info("Informational message");
logger.warn("Warning message");
logger.error("Error message", error);
// Namespaced child logger
const gameLogger = logger.child("Game");
gameLogger.info("Game started");
// Output: [Hyve SDK] [Game] [INFO] [timestamp] Game startedOverride log level in the browser:
localStorage.setItem("HYVE_SDK_LOG_LEVEL", "error,warn");API Reference
HyveClient Config
new HyveClient(config?: {
apiBaseUrl?: string; // Override API base URL
storageMode?: "cloud" | "local";
ads?: AdConfig;
billing?: BillingConfig;
})Authentication
| Method | Returns | Description |
|--------|---------|-------------|
| getUserId() | string \| null | Authenticated user ID |
| getGameId() | string \| null | Game ID from URL or JWT |
| getSessionId() | string | Unique session ID |
| getJwtToken() | string \| null | Raw JWT string |
| getApiBaseUrl() | string | Current API base URL |
| isUserAuthenticated() | boolean | Whether a user ID was extracted |
| hasJwtToken() | boolean | Whether a JWT is present |
| logout() | void | Clear auth state |
| reset() | void | Clear auth and generate new session ID |
API
| Method | Returns | Description |
|--------|---------|-------------|
| callApi<T>(endpoint, options?) | Promise<T> | Authenticated fetch to the Hyve API |
| getInventory() | Promise<Inventory> | Get user inventory |
| getInventoryItem(itemId) | Promise<InventoryItem> | Get a specific inventory item |
| getMachineRender(machineId) | Promise<string> | Fetch rendered HTML for a project machine |
Telemetry
| Method | Returns | Description |
|--------|---------|-------------|
| sendTelemetry(location, category, action, subCategory?, subAction?, details?, platformId?) | Promise<boolean> | Send an analytics event |
| updateTelemetryConfig(config) | void | Update telemetry settings at runtime |
Storage
| Method | Returns | Description |
|--------|---------|-------------|
| saveGameData(key, value, operation?, path?, storage?) | Promise<SaveGameDataResponse> | Save a single value; optional atomic operation and nested path |
| batchSaveGameData(items, storage?) | Promise<BatchSaveGameDataResponse> | Save multiple values; each item may include an operation and path |
| getGameData(key, storage?) | Promise<GameDataItem \| null> | Get a single value |
| getMultipleGameData(keys, storage?) | Promise<GameDataItem[]> | Get multiple values |
| deleteGameData(key, storage?) | Promise<boolean> | Delete a single value |
| deleteMultipleGameData(keys, storage?) | Promise<number> | Delete multiple values; returns count |
| getGameDataLeaderboard(params) | Promise<GameDataLeaderboardResponse> | Fetch players ranked by a game data key |
| isStorageAvailable(mode?) | Promise<boolean> | Whether a save will actually persist on the active backend; use this to gate saving, not isUserAuthenticated() |
| configureStorage(mode) | void | Set default storage mode |
| getStorageMode() | "cloud" \| "local" | Get current storage mode |
Ads
| Method | Returns | Description |
|--------|---------|-------------|
| configureAds(config) | void | Configure the ads service (incl. useNativeAds) |
| showAd(type, placement?) | Promise<AdResult> | Show an ad; placement forwarded to native AdMob |
| areAdsReady() | boolean | Check if ads have initialized |
| gameplayStart() | Promise<void> | Notify gameplay started (CrazyGames) |
| gameplayStop() | Promise<void> | Notify gameplay stopped (CrazyGames) |
| happytime() | Promise<void> | Trigger celebration effect (CrazyGames) |
Billing
| Method | Returns | Description |
|--------|---------|-------------|
| getBillingPlatform() | BillingPlatform | "web" | "native" | "unknown" |
| isBillingAvailable() | boolean | Check if billing is ready |
| getBillingProducts() | Promise<BillingProduct[]> | Fetch available products |
| purchaseProduct(productId, options?) | Promise<PurchaseResult> | Initiate a purchase |
| onPurchaseComplete(callback) | void | Register purchase success handler |
| onPurchaseError(callback) | void | Register purchase error handler |
| unmountBillingCheckout() | void | Clean up Stripe checkout UI |
Build Output
| Format | File | Use |
|--------|------|-----|
| CJS | dist/index.js | Node.js / bundler |
| ESM | dist/index.mjs | Bundler (tree-shakeable) |
| CJS (React) | dist/react.js | React integration |
| ESM (React) | dist/react.mjs | React integration (tree-shakeable) |
Development
pnpm run check-types # Type check
pnpm run lint # Lint
pnpm run build # BuildLicense
MIT
