npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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/js

Quick 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-game

Environment detection happens automatically from the parent page URL (games run in iframes):

  • Dev: marvin.dev.hyve.gg or dev.hyve.gg
  • Prod: marvin.hyve.gg or hyve.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() and gameplayEnd() 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 started

Override 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         # Build

License

MIT