Skip to main content

Hyve SDK

The core SDK for integrating with the Hyve platform, providing authentication, telemetry, and persistent storage capabilities for web applications and games.

What's New in v1.4.0

  • Persistent Game Data: Save player progress, settings, and scores with cloud or local storage
  • Storage Adapters: Flexible storage backends (cloud API or browser localStorage)
  • Batch Operations: Efficiently save/retrieve/delete multiple values at once
  • Mixed Storage: Use cloud for cross-device sync and local for offline settings

Installation

npm install @hyve-sdk/js
# or
pnpm add @hyve-sdk/js
# or
yarn add @hyve-sdk/js

Quick Start

import { HyveClient } from '@hyve-sdk/js';

// Initialize the client
const client = new HyveClient({
billing: {
stripePublishableKey: 'pk_test_...' // Optional: for in-game purchases
},
ads: {
enabled: false // Optional: ads are disabled by default
}
});

// Authenticate from URL parameters (extracts hyve-access JWT and game-id)
await client.authenticateFromUrl();

// Initialize billing (optional)
await client.initializeBilling();

// Send telemetry event (requires JWT token and game ID)
await client.sendTelemetry(
'app', // event_location
'user', // event_category
'login', // event_action
null, // event_sub_category (optional)
null, // event_sub_action (optional)
{ // event_details (optional, can be object or JSON string)
timestamp: Date.now(),
platform: 'web'
},
null // platform_id (optional)
);

Configuration

Client Configuration

const client = new HyveClient({
apiBaseUrl: 'https://...', // Optional: override API base URL
ads: { // Optional: Ads configuration (disabled by default)
enabled: false, // Enable/disable ads
sound: 'on', // 'on' | 'off'
debug: false, // Enable debug logging
onBeforeAd: (type) => console.log('Ad starting'),
onAfterAd: (type) => console.log('Ad finished'),
onRewardEarned: () => console.log('Reward earned')
}
});

Environment Detection

The SDK automatically determines the environment based on the parent URL when running in an iframe (typical for games embedded in the Hyve platform):

Automatic Detection Priority:

  1. If isDev is explicitly set in config, that value is used
  2. Otherwise, checks the parent page URL:
    • dev.hyve.gg → dev environment (isDev = true)
    • hyve.gg → prod environment (isDev = false)
    • Unknown/localhost → defaults to dev environment (isDev = true)

Examples:

// Auto-detection from parent URL (recommended)
const client = new HyveClient();
// If game loaded on https://dev.hyve.gg/games/123 → uses dev
// If game loaded on https://hyve.gg/games/123 → uses prod

// Works in both iframe and standalone contexts
// - Iframe: checks parent window URL
// - Standalone: checks current window URL
// - CORS blocked: falls back to document.referrer

// Explicit environment (optional, overrides auto-detection)
const client = new HyveClient({ isDev: false }); // Forces prod

Why This Matters:

  • Games are typically loaded in iframes within the Hyve platform
  • The SDK needs to know whether to use dev or prod API endpoints
  • Automatic detection ensures games work correctly in both environments without code changes
  • You can still explicitly set isDev for local development or testing

Environment Configuration Examples

# .env
NODE_ENV=production # Controls API environment and logging
// Option 1: Let SDK auto-detect from parent URL (recommended for games)
const client = new HyveClient();

// Option 2: Explicit configuration for testing
const client = new HyveClient({
isDev: true, // Optional: force environment
apiBaseUrl: 'http://localhost:8080' // Override API URL for local testing
});

Authentication

URL Parameter Authentication

The SDK extracts authentication and configuration from URL parameters:

JWT Authentication (Primary Method)

https://yourapp.com?hyve-access=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...&game-id=123
  • hyve-access: JWT token for authenticated API calls (required for telemetry)
  • game-id: Game identifier (required for telemetry)

Legacy Token Formats (Fallback)

# Modern format
https://yourapp.com?hyve-token=signature.address.randomBase64.timestamp

# Legacy format
https://yourapp.com?signature=0x...&message=encoded_message

Authenticating Users

// Authenticate from current URL
const isAuthenticated = await client.authenticateFromUrl();

if (isAuthenticated) {
console.log('User ID:', client.getUserId());
console.log('Session ID:', client.getSessionId());
}

// Or provide specific URL params
const params = new URLSearchParams(window.location.search);
const isAuthenticated = await client.authenticateFromUrl(params);

Checking Authentication Status

// Check if user is authenticated
if (client.isUserAuthenticated()) {
const userId = client.getUserId(); // User's wallet address
const sessionId = client.getSessionId(); // Unique session ID
}

JWT Authentication & API Integration

Overview

The SDK supports JWT-based authentication for making authenticated API calls to the Hyve platform. JWT tokens are passed via the hyve-access URL parameter and enable access to user-specific endpoints like inventory, profile data, and more.

JWT Authentication Flow

// JWT token is automatically extracted from URL
// URL format: https://your-game.com?hyve-access=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

// Authenticate from URL (extracts both hyve-token and hyve-access)
await client.authenticateFromUrl();

// Check if JWT token is available
if (client.hasJwtToken()) {
console.log('JWT token available for API calls');
const token = client.getJwtToken(); // Get raw JWT token
}

Making API Calls

The SDK provides a generic callApi method for authenticated requests:

// GET request
const userData = await client.callApi('/api/v1/user');

// POST request with body
const result = await client.callApi('/api/v1/game/score', {
method: 'POST',
body: JSON.stringify({ score: 1000, level: 5 })
});

// With TypeScript typing
interface UserProfile {
id: string;
username: string;
email: string;
}
const profile = await client.callApi<UserProfile>('/api/v1/user');

Requirements:

  • JWT token must be present (via hyve-access URL parameter)
  • User must be authenticated
  • API calls automatically include Authorization: Bearer {token} header

Error Handling

try {
const data = await client.callApi('/api/v1/user');
} catch (error) {
if (error.message.includes('No JWT token')) {
console.error('JWT token not available - check URL parameters');
} else if (error.message.includes('401')) {
console.error('JWT token expired or invalid');
} else {
console.error('API call failed:', error.message);
}
}

Inventory Management

Get User Inventory

Retrieve all items in the authenticated user's inventory:

const inventory = await client.getInventory();

console.log(`User has ${inventory.total_count} items`);

inventory.items.forEach(item => {
console.log(`${item.item_type}: ${item.quantity}x`);
if (item.metadata) {
console.log('Metadata:', item.metadata);
}
});

Response Structure:

interface Inventory {
items: InventoryItem[];
total_count: number;
}

interface InventoryItem {
id: string; // Unique item ID
user_id: string; // Owner's user ID
item_type: string; // Type: 'weapon', 'armor', 'consumable', etc.
item_id: string; // Specific item identifier
quantity: number; // Item quantity
metadata?: object; // Custom item data
created_at: string; // ISO timestamp
updated_at: string; // ISO timestamp
}

Get Specific Inventory Item

Fetch detailed information for a single inventory item:

const itemId = 'item-abc-123';
const item = await client.getInventoryItem(itemId);

console.log(`Item: ${item.item_type}`);
console.log(`Quantity: ${item.quantity}`);
console.log(`Item ID: ${item.item_id}`);

// Access custom metadata
if (item.metadata) {
console.log('Rarity:', item.metadata.rarity);
console.log('Level:', item.metadata.level);
console.log('Stats:', item.metadata.stats);
}

Inventory Example: Equipment System

class EquipmentManager {
constructor(hyveClient) {
this.client = hyveClient;
this.inventory = null;
}

async loadInventory() {
try {
// Check authentication
if (!this.client.hasJwtToken()) {
throw new Error('No JWT token - user must be authenticated');
}

// Fetch inventory
this.inventory = await this.client.getInventory();

// Track inventory load
await this.client.sendTelemetry(
'game', 'inventory', 'load', null, null,
{ total_items: this.inventory.total_count }
);

return this.inventory;
} catch (error) {
console.error('Failed to load inventory:', error);
throw error;
}
}

async getWeapons() {
if (!this.inventory) await this.loadInventory();
return this.inventory.items.filter(item => item.item_type === 'weapon');
}

async getItemDetails(itemId) {
try {
const item = await this.client.getInventoryItem(itemId);

// Track item view
await this.client.sendTelemetry(
'game', 'inventory', 'view_item', item.item_type, null,
{ item_id: itemId, quantity: item.quantity }
);

return item;
} catch (error) {
console.error('Failed to get item details:', error);
throw error;
}
}

getItemsByType(itemType) {
if (!this.inventory) return [];
return this.inventory.items.filter(item => item.item_type === itemType);
}

getItemById(itemId) {
if (!this.inventory) return null;
return this.inventory.items.find(item => item.id === itemId);
}
}

// Usage
const equipmentMgr = new EquipmentManager(client);
await equipmentMgr.loadInventory();
const weapons = await equipmentMgr.getWeapons();

Inventory API Endpoints

The SDK provides helper methods for these endpoints:

  • GET /api/v1/inventory - Get user's full inventory
  • GET /api/v1/inventory/:id - Get specific item details
// Get all inventory items
const inventory = await client.getInventory();

// Get specific item by ID
const item = await client.getInventoryItem('item-id-123');

Persistent Game Data

Save player progress, settings, and scores with cloud or local storage. All data is automatically scoped to your game and the authenticated user.

Storage Modes

The SDK supports two storage backends:

ModeSyncs Cross-DeviceRequires AuthWorks OfflineUse For
cloud✅ Yes✅ Yes❌ NoProgress, high scores, unlocks
local❌ No❌ No✅ YesSettings, preferences

Configuration

Set the default storage mode when initializing the client:

const client = new HyveClient({
storageMode: 'cloud' // Default: 'cloud' or 'local'
});

Individual method calls can override the default:

// Save to cloud regardless of default
await client.saveGameData('score', 1500, 'cloud');

// Save to local regardless of default
await client.saveGameData('settings', {...}, 'local');

Basic Operations

Save Data

Store any JSON-serializable value (numbers, strings, booleans, arrays, objects):

// Save simple values
await client.saveGameData('high_score', 1500);
await client.saveGameData('player_name', 'PlayerOne');
await client.saveGameData('sound_enabled', true);

// Save arrays
await client.saveGameData('unlocked_levels', [1, 2, 3, 5, 8]);

// Save objects
await client.saveGameData('player_progress', {
level: 10,
xp: 2500,
coins: 150,
achievements: ['first_win', 'speed_demon']
});

// Specify storage mode
await client.saveGameData('settings', {...}, 'local');

Get Data

Retrieve saved data with metadata:

const data = await client.getGameData('high_score');

if (data) {
console.log('Value:', data.value); // Your saved value
console.log('Created:', data.created_at); // ISO timestamp
console.log('Updated:', data.updated_at); // ISO timestamp
}

// Specify storage mode
const localData = await client.getGameData('settings', 'local');

Response Structure:

interface GameDataItem {
key: string; // Data key
value: GameDataValue; // Your saved value (any JSON type)
created_at: string; // ISO timestamp
updated_at: string; // ISO timestamp
}

Delete Data

Remove saved data:

const deleted = await client.deleteGameData('high_score');
console.log('Deleted:', deleted); // true if found and deleted

// Specify storage mode
await client.deleteGameData('settings', 'local');

Batch Operations

Save or retrieve multiple values at once (recommended for efficiency):

// Batch save
await client.batchSaveGameData([
{ key: 'level_1_score', value: 1000 },
{ key: 'level_2_score', value: 1500 },
{ key: 'level_3_score', value: 2000 },
{ key: 'completed_levels', value: [1, 2, 3] },
{ key: 'unlocked_items', value: ['sword', 'shield'] }
]);

// Batch get
const items = await client.getMultipleGameData([
'level_1_score',
'level_2_score',
'level_3_score',
'completed_levels',
'unlocked_items'
]);

items.forEach(item => {
console.log(`${item.key}: ${item.value}`);
});

// Batch delete
const deletedCount = await client.deleteMultipleGameData([
'level_1_score',
'level_2_score',
'level_3_score'
]);
console.log(`Deleted ${deletedCount} items`);

Complete Example

class GameManager {
constructor(client) {
this.client = client;
this.score = 0;
this.highScore = 0;
this.settings = {};
}

async initialize() {
// Load progress from cloud
const scoreData = await this.client.getGameData('high_score', 'cloud');
if (scoreData) {
this.highScore = scoreData.value;
}

// Load settings from local storage
const settingsData = await this.client.getGameData('settings', 'local');
if (settingsData) {
this.settings = settingsData.value;
this.applySettings();
}
}

async updateScore(points) {
this.score += points;

// Auto-save new high score to cloud
if (this.score > this.highScore) {
this.highScore = this.score;
await this.client.saveGameData('high_score', this.highScore, 'cloud');
}
}

async updateSettings(newSettings) {
this.settings = { ...this.settings, ...newSettings };

// Save settings locally (works offline)
await this.client.saveGameData('settings', this.settings, 'local');
this.applySettings();
}

applySettings() {
// Apply sound, graphics, etc.
}
}

// Usage
const game = new GameManager(client);
await game.initialize();

Mixed Storage Strategy

Use both cloud and local storage for optimal user experience:

// Cloud storage: Progress that syncs across devices
await client.saveGameData('high_score', 1500, 'cloud');
await client.saveGameData('unlocked_levels', [1, 2, 3], 'cloud');
await client.saveGameData('achievements', ['speedrun', 'perfectionist'], 'cloud');

// Local storage: Device-specific settings (works offline)
await client.saveGameData('sound_volume', 0.8, 'local');
await client.saveGameData('graphics_quality', 'high', 'local');
await client.saveGameData('controls', { jump: 'space', move: 'wasd' }, 'local');

Storage Limits

  • Maximum key length: 255 characters
  • Maximum value size: 1MB per item
  • Batch operations: Maximum 100 items per batch
  • Keys are case-sensitive
  • Data is automatically namespaced by game ID

Error Handling

try {
await client.saveGameData('score', 1500);
} catch (error) {
if (error.message.includes('game-id is required')) {
console.error('Missing game-id - check URL parameters');
} else if (error.message.includes('JWT token')) {
console.error('Authentication required for cloud storage');
} else {
console.error('Save failed:', error.message);
}
}

Storage Mode Methods

// Get current storage mode
const mode = client.getStorageMode(); // 'cloud' or 'local'

// Change storage mode
client.configureStorage('local');

Telemetry

Sending Telemetry Events

The sendTelemetry method provides full control over event tracking. Requires JWT token and game ID from URL parameters.

await client.sendTelemetry(
eventLocation, // Required: Where the event occurred
eventCategory, // Required: Main category
eventAction, // Required: Action taken
eventSubCategory, // Optional: Sub-category (pass null if not needed)
eventSubAction, // Optional: Sub-action (pass null if not needed)
eventDetails, // Optional: Event details as object or JSON string (pass null if not needed)
platformId // Optional: Platform identifier (pass null if not needed)
);

Telemetry Requirements

Important: Telemetry now requires JWT authentication:

  • JWT token must be present via hyve-access URL parameter
  • Game ID must be present via game-id URL parameter
  • User ID is automatically extracted from the JWT token by the backend
  • If requirements are not met, sendTelemetry will return false

JSON Validation

The SDK automatically validates eventDetails before sending telemetry events:

  • Objects must be JSON-serializable (no circular references, functions, or undefined values)
  • Strings must be valid JSON (parseable with JSON.parse())
  • If validation fails, the method returns false and logs an error
  • Use null instead of undefined for optional values

Valid examples:

// Valid: Simple object
await client.sendTelemetry('game', 'player', 'action', null, null, { score: 100 });

// Valid: JSON string
await client.sendTelemetry('game', 'player', 'action', null, null, '{"score":100}');

// Valid: Null is acceptable
await client.sendTelemetry('game', 'player', 'action', null, null, null);

Invalid examples (will return false):

// Invalid: Circular reference
const obj = { name: 'test' };
obj.self = obj;
await client.sendTelemetry('game', 'test', 'action', null, null, obj);

// Invalid: Function in object
await client.sendTelemetry('game', 'test', 'action', null, null, { onClick: () => {} });

// Invalid: Undefined values (use null instead)
await client.sendTelemetry('game', 'test', 'action', null, null, { value: undefined });

Telemetry Fields

FieldTypeDescription
game_idstringGame identifier (from URL parameter)
session_idstringUnique session identifier (auto-generated)
platform_idstring | nullPlatform/user identifier (optional)
hyve_user_idstringUser ID (extracted from JWT by backend)
event_locationstringWhere the event occurred
event_categorystringMain category of event
event_actionstringAction taken
event_sub_categorystring | nullSub-category for granular classification
event_sub_actionstring | nullSub-action for detailed tracking
event_detailsstring | nullEvent details as JSON string or object (validated before sending)

Examples

// Ensure authentication first
await client.authenticateFromUrl();

// Simple event
await client.sendTelemetry(
'app', // event_location
'system', // event_category
'startup', // event_action
null, // event_sub_category
null, // event_sub_action
null, // event_details
null // platform_id
);

// Event with detailed data
await client.sendTelemetry(
'game', // location
'player', // category
'achievement', // action
'combat', // sub-category
'first_boss_defeat', // sub-action
{ // event_details (auto-validated and stringified)
bossName: 'Dragon',
playerLevel: 5,
timeElapsed: 300,
damageDealt: 1500,
hitsTaken: 23
},
'web' // platform_id
);

// User action tracking
await client.sendTelemetry(
'app',
'navigation',
'click',
'menu',
'settings',
{
source: 'header',
timestamp: Date.now()
},
null
);

// Error tracking
await client.sendTelemetry(
'app',
'error',
'exception',
'network',
null,
{
errorCode: 'NETWORK_TIMEOUT',
endpoint: '/api/profile',
retryCount: 3
},
null
);

// Purchase tracking
client.onPurchaseComplete((result) => {
client.sendTelemetry(
'shop',
'purchase',
'complete',
'success',
null,
{
productId: result.productId,
transactionId: result.transactionId,
platform: client.getBillingPlatform()
}
);
});

Billing Service

Overview

The SDK provides integrated billing support for both web (Stripe) and native (In-App Purchases) platforms. Billing automatically detects the platform and routes to the appropriate payment method.

Quick Start

const client = new HyveClient({
billing: {
stripePublishableKey: 'pk_test_...' // Required for web payments
}
});

// Authenticate and initialize
await client.authenticateFromUrl();
await client.initializeBilling();

// Get products
const products = await client.getBillingProducts();

// Set up callbacks
client.onPurchaseComplete((result) => {
console.log('Purchase successful!', result);
});

client.onPurchaseError((result) => {
console.error('Purchase failed:', result.error);
});

// Make a purchase
await client.purchaseProduct(productId, {
elementId: 'stripe-checkout-element' // For web payments
});

Platform Support

  • Web: Stripe Embedded Checkout with automatic Stripe.js loading
  • Native: In-App Purchases via React Native bridge

Key Features

  • Automatic platform detection
  • Unified API for web and native
  • Auto-configuration with user and game data
  • Comprehensive error handling and logging
  • Stripe embedded checkout for seamless web payments
  • Native IAP integration for mobile apps

Configuration

// Basic configuration
const client = new HyveClient({
billing: {
stripePublishableKey: 'pk_test_...'
// userId, gameId, and checkoutUrl are auto-configured
}
});

// Or configure after initialization
client.configureBilling({
stripePublishableKey: 'pk_live_...',
gameId: 456, // Override if needed
userId: 'custom_user_id',
checkoutUrl: 'https://custom-api.com'
});

await client.initializeBilling();

Checking Availability

// Check if billing is initialized and available
if (client.isBillingAvailable()) {
const platform = client.getBillingPlatform();
console.log('Billing ready on:', platform); // 'web' | 'native'

// Load products
const products = await client.getBillingProducts();
}

Complete Billing Example

class GameStore {
constructor(hyveClient) {
this.client = hyveClient;
this.products = [];
}

async initialize() {
// Set up callbacks
this.client.onPurchaseComplete((result) => {
this.handlePurchaseSuccess(result);
});

this.client.onPurchaseError((result) => {
this.handlePurchaseError(result);
});

this.client.onBillingLog((level, message) => {
console.log(`[Billing ${level}]`, message);
});

// Initialize billing
const available = await this.client.initializeBilling();

if (!available) {
console.warn('Billing not available on this platform');
return false;
}

// Load products
this.products = await this.client.getBillingProducts();
return true;
}

async purchaseProduct(productId) {
try {
await this.client.purchaseProduct(productId, {
elementId: 'checkout-container'
});
} catch (error) {
console.error('Purchase failed:', error);
}
}

handlePurchaseSuccess(result) {
console.log('Purchase successful!', result);
// Credit items, update UI, etc.
}

handlePurchaseError(result) {
console.error('Purchase failed:', result.error);
// Show error message to user
}
}

For detailed billing documentation, see Billing Integration.

Ads Service

Overview

The SDK includes optional Google H5 Games Ads integration. Ads are disabled by default and must be explicitly enabled.

Prerequisites

Add the Google H5 Games Ads SDK to your HTML:

<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
<script>
window.adBreak = window.adBreak || function(o) { (window.adsbygoogle = window.adsbygoogle || []).push(o); };
window.adConfig = window.adConfig || function(o) { (window.adsbygoogle = window.adsbygoogle || []).push(o); };
</script>

Configuration

const client = new HyveClient({
ads: {
enabled: true, // Enable ads (default: false)
sound: 'on', // 'on' | 'off' (default: 'on')
debug: true, // Enable debug logging (default: false)
onBeforeAd: (type) => {
console.log(`Showing ${type} ad`);
// Pause game, mute audio, etc.
},
onAfterAd: (type) => {
console.log(`${type} ad finished`);
// Resume game, unmute audio, etc.
},
onRewardEarned: () => {
console.log('User earned reward!');
// Grant reward to player
}
}
});

// Or configure after initialization
client.configureAds({
enabled: true,
sound: 'off',
onRewardEarned: () => grantReward()
});

Showing Ads

// Check if ads are enabled and ready
if (client.areAdsEnabled() && client.areAdsReady()) {
// Show rewarded ad
const result = await client.showAd('rewarded');

if (result.success) {
console.log('Ad watched successfully!');
} else {
console.log('Ad failed or dismissed:', result.error);
}
}

// Show interstitial ad (between levels, game over, etc.)
await client.showAd('interstitial');

// Show preroll ad (at game start)
await client.showAd('preroll');

Ad Types

TypeDescriptionUse Case
rewardedUser opts in to watch for rewardsExtra lives, currency, power-ups
interstitialFull-screen ads between contentLevel transitions, game over screens
prerollAd shown at game startInitial loading screen

Ad Result

interface AdResult {
success: boolean; // Whether ad was completed successfully
type: AdType; // 'rewarded' | 'interstitial' | 'preroll'
error?: Error; // Error if ad failed
requestedAt: number; // Timestamp when ad was requested
completedAt: number; // Timestamp when ad completed/failed
}

Example: Rewarded Lives System

class LivesManager {
constructor(hyveClient) {
this.client = hyveClient;
this.lives = 3;
}

async watchAdForLife() {
if (!this.client.areAdsReady()) {
console.log('Ads not ready');
return false;
}

const result = await this.client.showAd('rewarded');

if (result.success) {
this.lives++;
console.log('Extra life earned! Lives:', this.lives);
return true;
} else {
console.log('Ad failed or dismissed');
return false;
}
}
}

Logger

The SDK includes a built-in logger that's automatically enabled in development environments.

Basic Usage

import { logger } from '@hyve-sdk/js';

logger.debug('Debug message', { data: 'value' });
logger.info('Info message');
logger.warn('Warning message');
logger.error('Error message', error);

Controlling Log Levels

// Browser: Set via localStorage
localStorage.setItem('HYVE_SDK_LOG_LEVEL', 'error,warn');

// Node.js: Set via environment variable
HYVE_SDK_LOG_LEVEL=error,warn node app.js

// Programmatically
import { Logger } from '@hyve-sdk/js';
const logger = new Logger();
logger.setLevels(['error', 'warn']);

Creating Child Loggers

import { logger } from '@hyve-sdk/js';

const gameLogger = logger.child('Game');
gameLogger.info('Game started'); // Output: [Hyve SDK] [Game] [INFO] Game started

Configuration

  • Development: Logging is enabled automatically (NODE_ENV !== 'production')
  • Production: Logging is disabled by default
  • Log Levels: debug, info, warn, error
  • Prefix: All logs are prefixed with [Hyve SDK] and timestamp

Native Bridge

For React Native WebView integration, the SDK provides NativeBridge for bidirectional communication.

Initialization

import { NativeBridge, NativeMessageType } from '@hyve-sdk/js';

// Initialize once when your app starts
NativeBridge.initialize();

// Check if running in React Native WebView
if (NativeBridge.isNativeContext()) {
console.log('Running in React Native WebView');
}

Sending Messages to Native

// Check IAP availability
NativeBridge.checkIAPAvailability();

// Request notification permission
NativeBridge.requestNotificationPermission();

// Get products for a game
NativeBridge.getProducts(123);

// Initiate a purchase
NativeBridge.purchase('product_id_123', 'user_id_456');

// Send custom message
NativeBridge.send('CUSTOM_MESSAGE', { data: 'value' });

Receiving Messages from Native

// Listen for IAP availability result
NativeBridge.on(NativeMessageType.IAP_AVAILABILITY_RESULT, (payload) => {
console.log('IAP available:', payload.available);
});

// Listen for notification permission
NativeBridge.on(NativeMessageType.PUSH_PERMISSION_GRANTED, () => {
console.log('Notification permission granted');
});

NativeBridge.on(NativeMessageType.PUSH_PERMISSION_DENIED, () => {
console.log('Notification permission denied');
});

// Listen for products result
NativeBridge.on(NativeMessageType.PRODUCTS_RESULT, (payload) => {
console.log('Products:', payload.products);
});

// Listen for purchase completion
NativeBridge.on(NativeMessageType.PURCHASE_COMPLETE, (payload) => {
console.log('Purchase successful:', payload);
});

NativeBridge.on(NativeMessageType.PURCHASE_ERROR, (payload) => {
console.error('Purchase failed:', payload.error);
});

// Unregister a handler
NativeBridge.off(NativeMessageType.PURCHASE_COMPLETE);

// Clear all handlers
NativeBridge.clearHandlers();

Message Types

enum NativeMessageType {
// Web → Native requests
CHECK_IAP_AVAILABILITY = 'CHECK_IAP_AVAILABILITY',
REQUEST_NOTIFICATION_PERMISSION = 'REQUEST_NOTIFICATION_PERMISSION',
GET_PRODUCTS = 'GET_PRODUCTS',
PURCHASE = 'PURCHASE',

// Native → Web responses
IAP_AVAILABILITY_RESULT = 'IAP_AVAILABILITY_RESULT',
PUSH_PERMISSION_GRANTED = 'PUSH_PERMISSION_GRANTED',
PUSH_PERMISSION_DENIED = 'PUSH_PERMISSION_DENIED',
PRODUCTS_RESULT = 'PRODUCTS_RESULT',
PURCHASE_COMPLETE = 'PURCHASE_COMPLETE',
PURCHASE_ERROR = 'PURCHASE_ERROR',
}

Custom Messages

// Send custom message to native
NativeBridge.send('GAME_STATE_CHANGED', {
state: 'playing',
level: 5,
score: 1000
});

// Listen for custom messages from native
NativeBridge.on('NATIVE_CUSTOM_EVENT', (payload) => {
console.log('Received from native:', payload);
});

Example: In-App Purchase Flow

class IAPManager {
constructor() {
this.setupListeners();
}

setupListeners() {
// Check if IAP is available
NativeBridge.on(NativeMessageType.IAP_AVAILABILITY_RESULT, (payload) => {
this.iapAvailable = payload.available;
if (this.iapAvailable) {
this.loadProducts();
}
});

// Handle products result
NativeBridge.on(NativeMessageType.PRODUCTS_RESULT, (payload) => {
this.products = payload.products;
this.displayProducts();
});

// Handle purchase completion
NativeBridge.on(NativeMessageType.PURCHASE_COMPLETE, (payload) => {
console.log('Purchase successful!', payload);
this.grantProduct(payload.productId);
});

// Handle purchase error
NativeBridge.on(NativeMessageType.PURCHASE_ERROR, (payload) => {
console.error('Purchase failed:', payload.error);
this.showError(payload.error);
});
}

init() {
NativeBridge.initialize();
if (NativeBridge.isNativeContext()) {
NativeBridge.checkIAPAvailability();
}
}

loadProducts() {
NativeBridge.getProducts(this.gameId);
}

purchaseProduct(productId) {
NativeBridge.purchase(productId, this.userId);
}

displayProducts() {
// Render products UI
}

grantProduct(productId) {
// Grant purchased item to user
}

showError(error) {
// Display error to user
}
}

API Reference

HyveClient

Constructor

new HyveClient(config?: HyveClientConfig)

Authentication Methods

MethodReturnsDescription
authenticateFromUrl(urlParams?)Promise<boolean>Authenticate user from URL parameters (extracts JWT and game ID)
isUserAuthenticated()booleanCheck if user is authenticated (legacy method)
hasJwtToken()booleanCheck if JWT token is available
getUserId()string | nullGet authenticated user's ID (legacy authentication)
getJwtToken()string | nullGet current JWT token
getGameId()string | nullGet current game ID from URL parameters
getSessionId()stringGet current session ID
logout()voidClear user authentication and JWT token
reset()voidReset client with new session

Telemetry Methods

MethodReturnsDescription
sendTelemetry(location, category, action, subCategory?, subAction?, details?, platformId?)Promise<boolean>Send telemetry event with JSON validation (requires JWT and game ID)
updateTelemetryConfig(config)voidUpdate telemetry configuration

API Methods

MethodReturnsDescription
callApi<T>(endpoint, options?)Promise<T>Make authenticated API call using JWT token
getInventory()Promise<Inventory>Get user's inventory items
getInventoryItem(itemId)Promise<InventoryItem>Get specific inventory item by ID
getApiBaseUrl()stringGet current API base URL

Billing Methods

MethodReturnsDescription
configureBilling(config)voidConfigure billing service
initializeBilling()Promise<boolean>Initialize billing (auto-configures with user/game data)
getBillingPlatform()BillingPlatformGet current billing platform ('web', 'native', or 'unknown')
isBillingAvailable()booleanCheck if billing is available
getBillingProducts()Promise<BillingProduct[]>Get available products
purchaseProduct(productId, options?)Promise<PurchaseResult>Purchase a product
onPurchaseComplete(callback)voidSet callback for successful purchases
onPurchaseError(callback)voidSet callback for failed purchases
unmountBillingCheckout()voidUnmount Stripe checkout element (web only)
onBillingLog(callback)voidSet callback to receive billing logs

Ads Methods

MethodReturnsDescription
configureAds(config)voidConfigure ads service
showAd(type)Promise<AdResult>Show an ad ('rewarded', 'interstitial', or 'preroll')
areAdsEnabled()booleanCheck if ads are enabled
areAdsReady()booleanCheck if ads are ready to show

Types

interface HyveClientConfig extends TelemetryConfig {
ads?: AdConfig; // Optional ads configuration
billing?: BillingConfig; // Optional billing configuration
}

interface TelemetryConfig {
isDev?: boolean; // Optional: true = dev, false = prod
// If not provided, auto-detects from parent URL
// (dev.hyve.gg = dev, hyve.gg = prod, unknown = dev)
apiBaseUrl?: string; // Optional API base URL override
}

interface TelemetryEvent {
game_id: string; // Game identifier (from URL)
session_id: string; // Unique session ID
platform_id?: string | null; // Platform identifier
event_location: string; // Where event occurred
event_category: string; // Main category
event_action: string; // Action taken
event_sub_category?: string | null; // Sub-category
event_sub_action?: string | null; // Sub-action
event_details?: Record<string, any> | string | null; // Event details (validated before sending)
}

interface AdConfig {
enabled?: boolean; // Enable/disable ads (default: false)
sound?: 'on' | 'off'; // Sound setting (default: 'on')
debug?: boolean; // Enable debug logging (default: false)
onBeforeAd?: (type: AdType) => void; // Callback before ad
onAfterAd?: (type: AdType) => void; // Callback after ad
onRewardEarned?: () => void; // Callback when reward earned
}

type AdType = 'rewarded' | 'interstitial' | 'preroll';

interface AdResult {
success: boolean; // Whether ad completed successfully
type: AdType; // Type of ad shown
error?: Error; // Error if ad failed
requestedAt: number; // Request timestamp
completedAt: number; // Completion timestamp
}

interface Inventory {
items: InventoryItem[];
total_count: number;
}

interface InventoryItem {
id: string;
user_id: string;
item_type: string;
item_id: string;
quantity: number;
metadata?: Record<string, any>;
created_at: string;
updated_at: string;
}

interface BillingConfig {
stripePublishableKey?: string; // Required for web payments
gameId?: number; // Auto-configured from client
userId?: string; // Auto-configured from client
checkoutUrl?: string; // Auto-configured from client
}

enum BillingPlatform {
WEB = 'web',
NATIVE = 'native',
UNKNOWN = 'unknown'
}

interface BillingProduct {
productId: string; // Product/Price ID
title: string; // Product name
description: string; // Product description
price: number; // Numeric price
localizedPrice: string; // Formatted price (e.g., "$9.99")
currency: string; // Currency code (e.g., "USD")
}

interface PurchaseResult {
success: boolean; // Whether purchase succeeded
productId: string; // Product that was purchased
transactionId?: string; // Transaction/receipt ID
transactionDate?: string; // Purchase timestamp
error?: {
code: string; // Error code
message: string; // Error message
};
}

Exported Utilities

// Core Client
import { HyveClient, type HyveClientConfig } from '@hyve-sdk/js';

// Billing
import {
BillingService,
BillingPlatform,
type BillingConfig,
type BillingProduct,
type PurchaseResult
} from '@hyve-sdk/js';

// Ads
import { AdsService, type AdType, type AdResult } from '@hyve-sdk/js';

// Logger
import { logger, Logger } from '@hyve-sdk/js';

logger.debug(...args);
logger.info(...args);
logger.warn(...args);
logger.error(...args);
logger.setLevels(['error', 'warn']);
const childLogger = logger.child('Prefix');

// Native Bridge
import { NativeBridge, NativeMessageType, type NativeMessage } from '@hyve-sdk/js';

NativeBridge.initialize();
NativeBridge.isNativeContext();
NativeBridge.send(type, payload);
NativeBridge.on(type, handler);
NativeBridge.off(type);
NativeBridge.clearHandlers();
NativeBridge.checkIAPAvailability();
NativeBridge.requestNotificationPermission();
NativeBridge.getProducts(gameId);
NativeBridge.purchase(productId, userId);

// Auth Utilities
import {
parseUrlParams,
validateSignature,
handleVerifyMessage,
verifyHyveToken,
verifyAuthentication,
generateUUID,
isDomainAllowed
} from '@hyve-sdk/js';

Best Practices

1. Authentication First

// Always authenticate before using SDK features
const client = new HyveClient({
billing: {
stripePublishableKey: process.env.STRIPE_PUBLIC_KEY
}
});

// Authenticate from URL (extracts JWT token and game ID)
const isAuthenticated = await client.authenticateFromUrl();

if (isAuthenticated && client.hasJwtToken()) {
// Now you can use SDK features
await client.sendTelemetry('app', 'user', 'login');

// Initialize billing
await client.initializeBilling();
} else {
console.warn('Authentication required for SDK features');
}

2. Error Handling

try {
const success = await client.sendTelemetry(
'game', 'player', 'score',
null, null, { score: 1000 }
);

if (!success) {
console.warn('Telemetry failed - check JWT token, game ID, or eventDetails format');
}
} catch (error) {
console.error('Telemetry error:', error);
// Continue app execution - don't break game flow
}

3. Validate Event Data

// Sanitize data before sending to avoid validation errors
function sanitizeEventData(data) {
try {
// Remove undefined values, replace with null
return JSON.parse(
JSON.stringify(data, (key, value) =>
value === undefined ? null : value
)
);
} catch (error) {
console.error('Failed to sanitize data:', error);
return null;
}
}

const eventData = sanitizeEventData({
score: 1000,
level: 5,
extra: undefined // Will become null
});

await client.sendTelemetry(
'game', 'player', 'score',
null, null, eventData
);

4. Session Management

// Sessions persist until reset
const sessionId = client.getSessionId();
console.log('Current session:', sessionId);

// Create new session if needed (e.g., user logout)
client.reset(); // Generates new session ID

5. Environment Configuration

// Recommended: Let SDK auto-detect environment from parent URL
// This works automatically when game is embedded in Hyve platform
const client = new HyveClient({
billing: {
stripePublishableKey: process.env.STRIPE_PUBLIC_KEY
},
ads: {
enabled: process.env.ADS_ENABLED === 'true',
debug: process.env.NODE_ENV !== 'production'
}
});

6. Billing Best Practices

// Check billing availability before showing store
if (client.isBillingAvailable()) {
const products = await client.getBillingProducts();
renderStore(products);
}

// Always set up callbacks before making purchases
client.onPurchaseComplete((result) => {
// Credit items, update UI, send telemetry
client.sendTelemetry('store', 'purchase', 'complete', null, null,
{
productId: result.productId,
transactionId: result.transactionId,
platform: client.getBillingPlatform()
}
);
});

client.onPurchaseError((result) => {
// Log error, show message to user
console.error('Purchase failed:', result.error);
});

// Use logging for debugging
client.onBillingLog((level, message, data) => {
if (level === 'error') {
console.error('[Billing]', message, data);
}
});

7. Ads Best Practices

// Check ads are ready before showing
if (client.areAdsEnabled() && client.areAdsReady()) {
// Pause game before ad
game.pause();

const result = await client.showAd('interstitial');

// Resume game after ad
game.resume();

if (!result.success) {
console.warn('Ad failed:', result.error);
}
}

// Use callbacks for game state management
client.configureAds({
enabled: true,
onBeforeAd: () => {
game.pause();
audio.mute();
},
onAfterAd: () => {
game.resume();
audio.unmute();
}
});

8. Native Bridge Best Practices

// Initialize once at app start
if (NativeBridge.isNativeContext()) {
NativeBridge.initialize();

// Set up listeners before sending requests
NativeBridge.on(NativeMessageType.IAP_AVAILABILITY_RESULT, (payload) => {
if (payload.available) {
NativeBridge.getProducts(gameId);
}
});

// Then send requests
NativeBridge.checkIAPAvailability();
}

Troubleshooting

Billing Issues

Issue: Billing initialization fails

Solutions:

  1. For web: Ensure stripePublishableKey is provided in config
  2. Verify user is authenticated: client.isUserAuthenticated()
  3. Check game ID is available: client.getGameId()
  4. Enable billing logs: client.onBillingLog((level, msg) => console.log(msg))
  5. Check browser console for Stripe.js loading errors
// Debug billing initialization
console.log('User authenticated:', client.isUserAuthenticated());
console.log('Game ID:', client.getGameId());

const result = await client.initializeBilling();
console.log('Billing initialized:', result);
console.log('Billing available:', client.isBillingAvailable());
console.log('Platform:', client.getBillingPlatform());

Issue: Products not loading

Solutions:

  1. Verify billing is initialized: await client.initializeBilling()
  2. Check game ID exists in your database
  3. Verify API endpoints return correct format (see Server Requirements)
  4. Check network requests in browser DevTools
  5. Enable billing logs to see detailed error messages

Issue: Purchase fails or hangs

Solutions:

  1. Web: Verify Stripe key is valid (test vs live)
  2. Web: Ensure checkout container exists in DOM: <div id="stripe-checkout-element"></div>
  3. Native: Ensure app is installed from store (not development build)
  4. Check product IDs match between platforms
  5. Verify server-side validation endpoints are working
  6. Check purchase callbacks are registered before calling purchaseProduct()

Telemetry Not Sending

Issue: sendTelemetry returns false

Solutions:

  1. Verify JWT token is present: client.hasJwtToken()
  2. Verify game ID is present: client.getGameId()
  3. Check URL has both hyve-access and game-id parameters
  4. Call authenticateFromUrl() before sending telemetry
  5. Check eventDetails is valid JSON (no circular references, functions, or undefined values)
  6. Check browser console for SDK logs (enabled in development) - look for "Invalid JSON in eventDetails" errors
  7. Verify network requests in browser DevTools
// Debug telemetry issues
console.log('Has JWT:', client.hasJwtToken());
console.log('Game ID:', client.getGameId());
console.log('Session ID:', client.getSessionId());

if (!client.hasJwtToken()) {
console.error('Missing JWT token - check hyve-access URL parameter');
}

if (!client.getGameId()) {
console.error('Missing game ID - check game-id URL parameter');
}

// Test eventDetails validation
const testData = { score: 100, level: 5 };
const success = await client.sendTelemetry('game', 'test', 'action', null, null, testData);
if (!success) {
console.error('Telemetry validation failed - check eventDetails format');
}

Issue: eventDetails validation failing

Solutions:

  1. Remove circular references from objects
  2. Replace undefined values with null
  3. Remove functions, symbols, or non-serializable values
  4. Test JSON stringification: JSON.stringify(eventDetails)
  5. Use a sanitization function to clean data before sending:
function sanitizeEventData(data) {
try {
return JSON.parse(
JSON.stringify(data, (key, value) =>
value === undefined ? null : value
)
);
} catch (error) {
console.error('Failed to sanitize:', error);
return null;
}
}

const cleaned = sanitizeEventData(eventData);
await client.sendTelemetry('game', 'player', 'action', null, null, cleaned);

Authentication Failing

Issue: authenticateFromUrl() returns false

Solutions:

  1. Check URL parameter format: ?hyve-access=JWT_TOKEN&game-id=123
  2. Verify JWT token is not expired
  3. For legacy auth, check hyve-token or signature+message format
  4. Enable SDK logging to see detailed authentication errors
  5. Check browser console for specific errors

Ads Not Working

Issue: Ads don't show or showAd returns error

Solutions:

  1. Verify ads are enabled: client.areAdsEnabled()
  2. Check ads are ready: client.areAdsReady()
  3. Ensure Google Ads SDK script is loaded in HTML
  4. Enable ads debug mode: ads: { debug: true }
  5. Check browser console for ad-related errors
  6. Verify ad blocker is not interfering
// Debug ads issues
console.log('Ads enabled:', client.areAdsEnabled());
console.log('Ads ready:', client.areAdsReady());

const result = await client.showAd('rewarded');
console.log('Ad result:', result);

Native Bridge Not Working

Issue: Messages not being received from React Native

Solutions:

  1. Verify running in React Native: NativeBridge.isNativeContext()
  2. Call NativeBridge.initialize() once at app start
  3. Register handlers BEFORE sending requests
  4. Check React Native WebView implementation
  5. Verify message format matches expected structure
  6. Enable SDK logging for debug messages
// Debug native bridge
console.log('Native context:', NativeBridge.isNativeContext());

// Enable detailed logging
import { logger } from '@hyve-sdk/js';
logger.setLevels(['debug', 'info', 'warn', 'error']);

API Calls Failing

Issue: callApi() throws error or returns 401

Solutions:

  1. Verify JWT token is available: client.hasJwtToken()
  2. Check JWT token is not expired
  3. Verify correct API endpoint path
  4. Check network requests in browser DevTools
  5. Verify CORS is properly configured on backend
  6. Ensure user has permission for the requested endpoint
// Debug API calls
try {
const data = await client.callApi('/api/v1/user');
console.log('API response:', data);
} catch (error) {
console.error('API error:', error);
console.log('JWT token:', client.getJwtToken() ? 'present' : 'missing');
console.log('API Base URL:', client.getApiBaseUrl());
}

Environment Detection Issues

Issue: SDK using wrong environment (dev vs prod)

Solutions:

  1. Check SDK initialization logs in browser console - it logs the detected environment
  2. Verify the parent page URL contains the correct domain (dev.hyve.gg or hyve.gg)
  3. For local development, explicitly set isDev: true in config
  4. Check if CORS is blocking parent URL access (SDK falls back to document.referrer)
  5. Explicitly set environment if auto-detection isn't working
// Debug environment detection
const client = new HyveClient();
console.log('API Base URL:', client.getApiBaseUrl());
// Should be:
// - https://product-api.dev.hyve.gg (for dev)
// - https://product-api.prod.hyve.gg (for prod)

// Force specific environment if auto-detection fails
const client = new HyveClient({ isDev: false }); // Force prod

Issue: Game works in one environment but not the other

Solutions:

  1. Ensure game has products/configuration in both dev and prod databases
  2. Verify API keys (Stripe, etc.) are correct for each environment
  3. Check game ID is valid in the target environment
  4. Test game URL with both dev.hyve.gg and hyve.gg parent URLs
  5. Enable SDK logging to see which API URLs are being called

Logging Issues

Issue: Not seeing SDK logs in development

Solutions:

  1. Logs are auto-enabled when NODE_ENV !== 'production'
  2. In browser, set localStorage: localStorage.setItem('HYVE_SDK_LOG_LEVEL', 'debug,info,warn,error')
  3. In Node.js, set env var: HYVE_SDK_LOG_LEVEL=debug,info,warn,error
  4. Check browser console filters are not hiding logs
  5. Verify logger is imported correctly
// Enable all log levels
import { logger } from '@hyve-sdk/js';
logger.setLevels(['debug', 'info', 'warn', 'error']);

// Test logging
logger.debug('Debug test');
logger.info('Info test');
logger.warn('Warn test');
logger.error('Error test');