Skip to main content

Telemetry Guide

Comprehensive guide for tracking events and analytics with the Hyve SDK.

Overview

The sendTelemetry() method allows you to track user events and send them to the Hyve analytics service. All telemetry events are tied to authenticated users via JWT tokens and automatically validated before sending.

Method Signature

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
platformId // Optional: Platform identifier (e.g., 'web', 'ios')
)

Parameters

ParameterTypeRequiredDescription
eventLocationstringYesWhere the event occurred (e.g., 'game', 'menu', 'shop')
eventCategorystringYesMain category (e.g., 'player', 'purchase', 'ui')
eventActionstringYesPrimary action (e.g., 'click', 'complete', 'start')
eventSubCategorystring | nullNoSub-category for granular classification
eventSubActionstring | nullNoSub-action for detailed tracking
eventDetailsobject | string | nullNoEvent details (auto-validated and stringified)
platformIdstring | nullNoPlatform identifier (e.g., 'web-chrome', 'ios')

Return Value

Returns a Promise<boolean>:

  • true - Event sent successfully
  • false - Event failed to send (check logs for details)

Requirements

Before sending telemetry events, ensure:

  1. JWT token is available via hyve-access URL parameter
  2. Game ID is available via game-id URL parameter
  3. User is authenticated via authenticateFromUrl()
// Check requirements
if (!client.hasJwtToken()) {
console.error('JWT token required - check hyve-access parameter');
}

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

JSON Validation

The SDK automatically validates eventDetails before sending to prevent errors and ensure data quality.

Validation Rules

  1. Objects must be JSON-serializable

    • No circular references
    • No functions or symbols
    • No undefined values (use null instead)
  2. Strings must be valid JSON

    • Must parse successfully with JSON.parse()
  3. Failure Behavior

    • Method returns false
    • Error logged with details
    • No telemetry event sent

Valid Examples

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

// Valid: Nested objects and arrays
await client.sendTelemetry(
'game',
'inventory',
'update',
null,
null,
{
items: [
{ id: 1, name: 'sword', quantity: 1 },
{ id: 2, name: 'shield', quantity: 1 }
],
totalValue: 250
}
);

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

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

Invalid Examples

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

// Invalid: Function in object
await client.sendTelemetry(
'game',
'test',
'action',
null,
null,
{ onClick: () => {} } // Functions not serializable
);
// Returns false, logs error

// Invalid: Undefined values
await client.sendTelemetry(
'game',
'test',
'action',
null,
null,
{ value: undefined } // Use null instead
);
// Returns false, logs error

// Invalid: Malformed JSON string
await client.sendTelemetry(
'game',
'test',
'action',
null,
null,
'{invalid json}'
);
// Returns false, logs error

Common Use Cases

Game Events

// Player actions
await client.sendTelemetry(
'game',
'player',
'attack',
'combat',
'melee',
{
weaponType: 'sword',
damage: 50,
targetId: 'enemy-123',
timestamp: Date.now()
}
);

// Level completion
await client.sendTelemetry(
'game',
'level',
'complete',
'world-1',
'level-5',
{
score: 9500,
timeSeconds: 120,
stars: 3,
collectibles: 15
}
);

// Achievements
await client.sendTelemetry(
'game',
'achievement',
'unlock',
'combat',
'first-victory',
{
achievementId: 'ach_001',
points: 100
}
);

UI Interactions

// Button clicks
await client.sendTelemetry(
'menu',
'ui',
'click',
'main-menu',
'play-button',
{
screenSize: `${window.innerWidth}x${window.innerHeight}`,
timestamp: Date.now()
}
);

// Menu navigation
await client.sendTelemetry(
'menu',
'navigation',
'open',
'settings',
null,
{
previousMenu: 'main',
loadTime: 250
}
);

// Settings changes
await client.sendTelemetry(
'menu',
'settings',
'update',
'audio',
null,
{
volume: 0.8,
musicEnabled: true,
sfxEnabled: true
}
);

Purchase Tracking

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

// Purchase error
client.onPurchaseError((result) => {
client.sendTelemetry(
'shop',
'purchase',
'error',
'failed',
null,
{
productId: result.productId,
errorCode: result.error?.code,
errorMessage: result.error?.message
}
);
});

// Product viewed
await client.sendTelemetry(
'shop',
'product',
'view',
null,
null,
{
productId: 'coins_100',
price: '$0.99',
category: 'currency'
}
);

Session Tracking

// Session start
await client.sendTelemetry(
'game',
'session',
'start',
null,
null,
{
referrer: document.referrer,
userAgent: navigator.userAgent.substring(0, 100),
screenResolution: `${window.screen.width}x${window.screen.height}`,
timestamp: Date.now()
}
);

// Session end
window.addEventListener('beforeunload', () => {
// Keep minimal for quick execution
client.sendTelemetry(
'game',
'session',
'end',
null,
null,
{
durationSeconds: Math.floor((Date.now() - sessionStartTime) / 1000),
eventsTracked: eventCount
}
);
});

Error Tracking

// Network errors
try {
await fetchGameData();
} catch (error) {
await client.sendTelemetry(
'game',
'error',
'network',
null,
null,
{
errorMessage: error.message,
endpoint: '/api/game-data',
httpStatus: error.status || 0
}
);
}

// Game errors
window.addEventListener('error', (event) => {
client.sendTelemetry(
'game',
'error',
'javascript',
null,
null,
{
message: event.message,
filename: event.filename,
lineNumber: event.lineno,
columnNumber: event.colno
}
);
});

Best Practices

1. Use Descriptive Names

// Good - Clear and descriptive
await client.sendTelemetry(
'shop',
'purchase',
'complete',
'coins',
null,
{ amount: 100, price: '$0.99' }
);

// Less descriptive
await client.sendTelemetry('s', 'p', 'c', 'c', null, { a: 100, p: 0.99 });

2. Structure Data Consistently

// Consistent structure across similar events
const trackLevelEvent = (action, levelData) => {
return client.sendTelemetry(
'game',
'level',
action,
`world-${levelData.world}`,
`level-${levelData.number}`,
{
score: levelData.score,
timeSeconds: levelData.duration,
stars: levelData.stars,
timestamp: Date.now()
}
);
};

await trackLevelEvent('start', levelData);
await trackLevelEvent('complete', levelData);

3. Sanitize Data Before Sending

// Helper function to clean event data
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;
}
}

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

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

4. Handle Failures Gracefully

async function trackEvent(location, category, action, details) {
const success = await client.sendTelemetry(
location,
category,
action,
null,
null,
details
);

if (!success) {
console.warn('Telemetry failed:', { location, category, action });
// Optional: Store locally for retry
localStorage.setItem('failed-telemetry', JSON.stringify({
timestamp: Date.now(),
event: { location, category, action, details }
}));
}

return success;
}

5. Avoid Sensitive Data

// Bad - Contains PII
await client.sendTelemetry(
'auth',
'user',
'login',
null,
null,
{
email: 'user@example.com', // Don't send PII
password: 'secret123' // Never send passwords!
}
);

// Good - No PII
await client.sendTelemetry(
'auth',
'user',
'login',
null,
null,
{
loginMethod: 'email',
success: true,
timestamp: Date.now()
}
);

6. Use Platform ID for Cross-Platform Tracking

// Detect platform
const getPlatform = () => {
if (/iPhone|iPad/.test(navigator.userAgent)) return 'ios-safari';
if (/Android/.test(navigator.userAgent)) return 'android-chrome';
if (/Chrome/.test(navigator.userAgent)) return 'web-chrome';
if (/Safari/.test(navigator.userAgent)) return 'web-safari';
return 'web-other';
};

const platform = getPlatform();

await client.sendTelemetry(
'game',
'session',
'start',
null,
null,
{
screenSize: `${window.innerWidth}x${window.innerHeight}`,
devicePixelRatio: window.devicePixelRatio
},
platform
);

Performance Considerations

1. Avoid High-Frequency Events

// Bad - Sends on every frame
this.game.events.on('update', () => {
client.sendTelemetry('game', 'fps', 'update', null, null, { fps: this.game.loop.actualFps });
});

// Good - Sample or debounce
let lastFpsCheck = 0;
this.game.events.on('update', () => {
const now = Date.now();
if (now - lastFpsCheck > 5000) { // Only every 5 seconds
client.sendTelemetry('game', 'fps', 'check', null, null, { fps: Math.round(this.game.loop.actualFps) });
lastFpsCheck = now;
}
});

2. Keep Data Size Reasonable

// Good - Reasonable data size (< 100KB)
await client.sendTelemetry(
'game',
'level',
'complete',
null,
null,
{
score: 1000,
time: 120,
stars: 3
}
);

// Avoid - Very large data
await client.sendTelemetry(
'game',
'level',
'complete',
null,
null,
{
fullGameState: hugeObject, // Can be megabytes
allPlayerData: massiveArray
}
);
// Instead of many individual events
for (const item of collectedItems) {
await client.sendTelemetry('game', 'item', 'collect', null, null, { itemId: item.id });
}

// Batch into one event
await client.sendTelemetry(
'game',
'items',
'collect_batch',
null,
null,
{
itemIds: collectedItems.map(i => i.id),
count: collectedItems.length,
totalValue: collectedItems.reduce((sum, i) => sum + i.value, 0)
}
);

Troubleshooting

Telemetry Returns False

Check these in order:

  1. Authentication: Verify JWT token is present
if (!client.hasJwtToken()) {
console.error('Missing JWT token');
}
  1. Game ID: Verify game ID is present
if (!client.getGameId()) {
console.error('Missing game ID');
}
  1. JSON Validation: Check eventDetails format
const testData = { score: 100 };
try {
JSON.stringify(testData);
console.log('Data is valid JSON');
} catch (error) {
console.error('Data validation failed:', error);
}
  1. Check Browser Console: Look for SDK error messages
    • "JWT token required"
    • "Game ID required"
    • "Invalid JSON in eventDetails"

Data Validation Failures

Common issues and solutions:

  1. Circular References
// Problem
const obj = { name: 'test' };
obj.self = obj; // Circular!

// Solution: Remove circular references
const obj = { name: 'test' };
  1. Undefined Values
// Problem
const data = { score: 100, bonus: undefined };

// Solution: Use null or sanitize
const data = { score: 100, bonus: null };
// Or use sanitization function
const cleaned = sanitizeEventData(data);
  1. Functions in Objects
// Problem
const data = { onClick: () => {} };

// Solution: Remove functions, use strings
const data = { action: 'click' };

Network Errors

If events fail to send despite valid data:

  1. Check network requests in browser DevTools
  2. Verify API endpoint is accessible
  3. Check for CORS issues
  4. Verify JWT token hasn't expired

Advanced Patterns

Event Queue for Offline Support

class TelemetryQueue {
constructor(client) {
this.client = client;
this.queue = [];
this.isProcessing = false;
}

async add(location, category, action, subCategory, subAction, details, platformId) {
this.queue.push({ location, category, action, subCategory, subAction, details, platformId });
await this.process();
}

async process() {
if (this.isProcessing || this.queue.length === 0) return;

this.isProcessing = true;

while (this.queue.length > 0) {
const event = this.queue[0];
const success = await this.client.sendTelemetry(
event.location,
event.category,
event.action,
event.subCategory,
event.subAction,
event.details,
event.platformId
);

if (success) {
this.queue.shift();
} else {
// Stop processing on failure, retry later
break;
}
}

this.isProcessing = false;
}
}

TypeScript Type Safety

interface TelemetryEvent {
location: string;
category: string;
action: string;
subCategory?: string | null;
subAction?: string | null;
details?: Record<string, any> | null;
platformId?: string | null;
}

async function sendTypedTelemetry(client: HyveClient, event: TelemetryEvent): Promise<boolean> {
return await client.sendTelemetry(
event.location,
event.category,
event.action,
event.subCategory ?? null,
event.subAction ?? null,
event.details ?? null,
event.platformId ?? null
);
}

See Also