@rooguys/js
v1.0.0
Published
Official Browser SDK for Rooguys API
Maintainers
Readme
Rooguys JavaScript SDK
The official Browser SDK for the Rooguys Gamification API. Lightweight and dependency-free (uses native fetch).
Installation
NPM
npm install @rooguys/jsDirect Import (ES Modules)
import Rooguys from './path/to/sdk/index.js';Initialization
import Rooguys from '@rooguys/js';
const client = new Rooguys('YOUR_API_KEY', {
baseUrl: 'https://api.rooguys.com/v1',
timeout: 10000,
// Rate limit handling
onRateLimitWarning: (info) => {
console.warn(`Rate limit warning: ${info.remaining}/${info.limit} remaining`);
},
autoRetry: true,
maxRetries: 3,
});Migration Guide (v1.x to v2.x)
Breaking Changes
Event Tracking Endpoint: The SDK now uses
/v1/eventsinstead of/v1/event// Old (deprecated, still works with warning) client.events.trackLegacy('event_name', 'user_id', properties); // New (recommended) client.events.track('event-name', 'user_id', properties);Global Leaderboard Endpoint: Now uses
/v1/leaderboards/globalwithtimeframequery parameter// Both signatures work client.leaderboards.getGlobal('weekly', 1, 10); client.leaderboards.getGlobal({ timeframe: 'weekly', page: 1, limit: 10 });Response Format: All responses now follow standardized format
{ success: true, data: {...} }
New Features
- Batch event tracking (
events.trackBatch) - User management (
users.create,users.update,users.createBatch) - User search (
users.search) - Field selection for user profiles
- Leaderboard filters (persona, level range, date range)
- "Around me" leaderboard view (
leaderboards.getAroundUser) - Health check endpoints
- Rate limit handling with auto-retry
- Typed error classes
Usage Examples
Events
Track a Single Event
const response = await client.events.track('level-completed', 'user_123', {
difficulty: 'hard',
score: 1500
}, {
includeProfile: true,
idempotencyKey: 'unique-request-id'
});
console.log('Event tracked:', response.status);
if (response.profile) {
console.log('Updated points:', response.profile.points);
}Track Events with Custom Timestamp
// Track historical events (up to 7 days in the past)
const response = await client.events.track('purchase', 'user_123', {
amount: 99.99
}, {
timestamp: new Date('2024-01-15T10:30:00Z')
});Batch Event Tracking
// Track up to 100 events in a single request
const response = await client.events.trackBatch([
{ eventName: 'page-view', userId: 'user_123', properties: { page: '/home' } },
{ eventName: 'button-click', userId: 'user_123', properties: { button: 'signup' } },
{ eventName: 'purchase', userId: 'user_456', properties: { amount: 50 }, timestamp: new Date() }
], {
idempotencyKey: 'batch-123'
});
// Check individual results
response.results.forEach((result, index) => {
if (result.status === 'queued') {
console.log(`Event ${index} queued successfully`);
} else {
console.error(`Event ${index} failed:`, result.error);
}
});Users
Create a New User
const user = await client.users.create({
userId: 'user_123',
displayName: 'John Doe',
email: '[email protected]',
firstName: 'John',
lastName: 'Doe',
metadata: { plan: 'premium' }
});Update User Profile
// Partial update - only sends provided fields
const updated = await client.users.update('user_123', {
displayName: 'Johnny Doe',
metadata: { plan: 'enterprise' }
});Batch User Creation
const response = await client.users.createBatch([
{ userId: 'user_1', displayName: 'User One', email: '[email protected]' },
{ userId: 'user_2', displayName: 'User Two', email: '[email protected]' },
// ... up to 100 users
]);Get User Profile with Field Selection
// Only fetch specific fields
const user = await client.users.get('user_123', {
fields: ['points', 'level', 'badges']
});Search Users
const results = await client.users.search('john', {
page: 1,
limit: 20,
fields: ['userId', 'displayName', 'points']
});
results.users.forEach(user => {
console.log(`${user.displayName}: ${user.points} points`);
});Access Enhanced Profile Data
const user = await client.users.get('user_123');
// Activity summary
if (user.activitySummary) {
console.log(`Last active: ${user.activitySummary.lastEventAt}`);
console.log(`Total events: ${user.activitySummary.eventCount}`);
console.log(`Days active: ${user.activitySummary.daysActive}`);
}
// Streak information
if (user.streak) {
console.log(`Current streak: ${user.streak.currentStreak} days`);
console.log(`Longest streak: ${user.streak.longestStreak} days`);
}
// Inventory summary
if (user.inventory) {
console.log(`Items owned: ${user.inventory.itemCount}`);
console.log(`Active effects: ${user.inventory.activeEffects.join(', ')}`);
}Leaderboards
Global Leaderboard with Filters
// Using options object (recommended)
const leaderboard = await client.leaderboards.getGlobal({
timeframe: 'weekly',
page: 1,
limit: 10,
persona: 'competitor',
minLevel: 5,
maxLevel: 20,
startDate: new Date('2024-01-01'),
endDate: new Date('2024-01-31')
});
// Access cache metadata
if (leaderboard.cacheMetadata) {
console.log(`Cached at: ${leaderboard.cacheMetadata.cachedAt}`);
console.log(`TTL: ${leaderboard.cacheMetadata.ttl}s`);
}
// Rankings include percentile
leaderboard.rankings.forEach(entry => {
console.log(`#${entry.rank} ${entry.userId}: ${entry.score} pts (top ${entry.percentile}%)`);
});Custom Leaderboard with Filters
const customLb = await client.leaderboards.getCustom('leaderboard_id', {
page: 1,
limit: 10,
persona: 'achiever',
minLevel: 10
});"Around Me" View
// Get entries around a specific user
const aroundMe = await client.leaderboards.getAroundUser(
'leaderboard_id',
'user_123',
5 // 5 entries above and below
);
aroundMe.rankings.forEach(entry => {
const marker = entry.userId === 'user_123' ? '→' : ' ';
console.log(`${marker} #${entry.rank} ${entry.userId}: ${entry.score}`);
});Get User Rank with Percentile
const rank = await client.leaderboards.getUserRank('leaderboard_id', 'user_123');
console.log(`Rank: #${rank.rank}`);
console.log(`Score: ${rank.score}`);
console.log(`Percentile: top ${rank.percentile}%`);Health Checks
// Full health check
const health = await client.health.check();
console.log(`Status: ${health.status}`);
console.log(`Version: ${health.version}`);
// Quick availability check
const isReady = await client.health.isReady();
if (isReady) {
console.log('API is ready');
}Aha Score
// Declare user activation milestone (1-5)
const result = await client.aha.declare('user_123', 4);
console.log(result.message);
// Get user's aha score
const score = await client.aha.getUserScore('user_123');
console.log(`Current Score: ${score.data.current_score}`);
console.log(`Status: ${score.data.status}`);API Reference
Events
| Method | Description |
|--------|-------------|
| track(eventName, userId, properties?, options?) | Track a single event |
| trackBatch(events, options?) | Track multiple events (max 100) |
| trackLegacy(eventName, userId, properties?, options?) | Deprecated - Use track() |
Users
| Method | Description |
|--------|-------------|
| create(userData) | Create a new user |
| update(userId, userData) | Update user profile (partial update) |
| createBatch(users) | Create multiple users (max 100) |
| get(userId, options?) | Get user profile with optional field selection |
| search(query, options?) | Search users with pagination |
| getBulk(userIds) | Get multiple user profiles |
| getBadges(userId) | Get user's badges |
| getRank(userId, timeframe?) | Get user's global rank |
| submitAnswers(userId, questionnaireId, answers) | Submit questionnaire answers |
Leaderboards
| Method | Description |
|--------|-------------|
| getGlobal(timeframeOrOptions?, page?, limit?, options?) | Get global leaderboard with filters |
| list(pageOrOptions?, limit?, search?) | List all leaderboards |
| getCustom(leaderboardId, pageOrOptions?, limit?, search?, options?) | Get custom leaderboard with filters |
| getUserRank(leaderboardId, userId) | Get user's rank in leaderboard |
| getAroundUser(leaderboardId, userId, range?) | Get entries around a user |
Badges
| Method | Description |
|--------|-------------|
| list(page?, limit?, activeOnly?) | List all badges |
Levels
| Method | Description |
|--------|-------------|
| list(page?, limit?) | List all levels |
Questionnaires
| Method | Description |
|--------|-------------|
| get(slug) | Get questionnaire by slug |
| getActive() | Get active questionnaire |
Aha Score
| Method | Description |
|--------|-------------|
| declare(userId, value) | Declare aha score (1-5) |
| getUserScore(userId) | Get user's aha score |
Health
| Method | Description |
|--------|-------------|
| check() | Get full health status |
| isReady() | Quick availability check |
Error Handling
The SDK provides typed error classes for different error scenarios:
import Rooguys, {
ValidationError,
AuthenticationError,
NotFoundError,
ConflictError,
RateLimitError,
ServerError
} from '@rooguys/js';
try {
await client.users.create({ userId: 'user_123', email: 'invalid-email' });
} catch (error) {
if (error instanceof ValidationError) {
console.error('Validation failed:', error.message);
console.error('Field errors:', error.fieldErrors);
console.error('Error code:', error.code);
} else if (error instanceof AuthenticationError) {
console.error('Invalid API key');
} else if (error instanceof NotFoundError) {
console.error('Resource not found');
} else if (error instanceof ConflictError) {
console.error('Resource already exists');
} else if (error instanceof RateLimitError) {
console.error(`Rate limited. Retry after ${error.retryAfter} seconds`);
} else if (error instanceof ServerError) {
console.error('Server error:', error.message);
}
// All errors include requestId for debugging
console.error('Request ID:', error.requestId);
}Error Types
| Error Class | HTTP Status | Description |
|-------------|-------------|-------------|
| ValidationError | 400 | Invalid input data |
| AuthenticationError | 401 | Invalid or missing API key |
| ForbiddenError | 403 | Insufficient permissions |
| NotFoundError | 404 | Resource not found |
| ConflictError | 409 | Resource already exists |
| RateLimitError | 429 | Rate limit exceeded |
| ServerError | 500+ | Server-side error |
Error Properties
All errors include:
message- Human-readable error messagecode- Machine-readable error code (e.g.,INVALID_EMAIL,USER_NOT_FOUND)requestId- Unique request identifier for debuggingstatusCode- HTTP status code
ValidationError also includes:
fieldErrors- Array of{ field, message }for field-level errors
RateLimitError also includes:
retryAfter- Seconds until rate limit resets
Rate Limiting
The SDK provides built-in rate limit handling:
const client = new Rooguys('YOUR_API_KEY', {
// Get notified when 80% of rate limit is consumed
onRateLimitWarning: (info) => {
console.warn(`Rate limit: ${info.remaining}/${info.limit} remaining`);
console.warn(`Resets at: ${new Date(info.reset * 1000)}`);
},
// Automatically retry rate-limited requests
autoRetry: true,
maxRetries: 3
});Rate limit info is available in response metadata:
const response = await client._httpClient.get('/users/user_123');
console.log('Rate limit:', response.rateLimit);
// { limit: 1000, remaining: 950, reset: 1704067200 }Testing
npm test # Run all tests
npm run test:coverage # Run with coverage reportThe SDK maintains >90% test coverage with:
- Unit tests for all API methods
- Property-based tests using fast-check
- Error handling validation
- Rate limit handling tests
Requirements
- Browser with support for:
fetchAPIPromiseAbortController(for timeouts)
- Most modern browsers support these features
License
MIT
