Overview
SubakoAchv provides a lightweight TypeScript SDK for adding achievement tracking to web applications. The SDK is framework-agnostic and works with any JavaScript environment that supports ES2020+.
This guide covers the core concepts: manifest creation, SDK initialization, event tracking, and displaying achievements in your UI.
Installation
Option 1: NPM Package (Recommended)
npm install @subako/achv
Option 2: Vendored Build
Clone the repository and copy the built SDK from packages/sdk/dist/ into your project:
git clone https://github.com/lypticDNA/subako-achv.git
cp -r subako-achv/packages/sdk/dist ./vendor/subako-achv
Option 3: ES Module Import
Import directly from a CDN (when published):
<script type="module">
import { createSubako, loadManifest } from 'https://cdn.example.com/subako-achv/index.js';
</script>
Creating a Manifest
Achievements are defined in a JSON manifest file. Create a file called manifest.json in your project:
{
"gameId": "my-web-app",
"name": "My Web App",
"version": "1.0.0",
"achievements": [
{
"id": "first_login",
"name": "Welcome Back",
"description": "Log in for the first time",
"points": 10,
"icon": "assets/achievements/login.svg",
"trigger": { "type": "event-once", "event": "user_login" }
},
{
"id": "power_user",
"name": "Power User",
"description": "Perform 100 actions",
"points": 25,
"icon": "assets/achievements/power.svg",
"trigger": { "type": "event-count", "event": "user_action", "value": 100 }
},
{
"id": "collector",
"name": "Collector",
"description": "Accumulate 500 points",
"points": 50,
"icon": "assets/achievements/collector.svg",
"trigger": { "type": "stat-gte", "stat": "score", "value": 500 }
}
]
}
gameId must be unique across all your applications. Use a slug format like my-app-name.
Initializing the SDK
Load the manifest and create an SDK instance:
import { createSubako, loadManifest } from '@subako/achv';
// Load and validate the manifest
const manifest = await loadManifest('./manifest.json');
// Create the SDK instance
const sdk = createSubako({
gameId: manifest.gameId,
manifest: manifest,
playerId: 'user-123', // Optional: use your auth system's user ID
displayName: 'John Doe' // Optional: display name
});
console.log('SDK initialized for game:', sdk.gameId);
console.log('Player:', sdk.profile.displayName);
Player Identification
If you don't provide a playerId, the SDK will auto-generate one and store it in localStorage. For production apps, you should integrate with your authentication system:
// After user logs in
sdk.identify(auth.userId, auth.displayName);
// Switch players (for shared devices)
sdk.identify('user-456', 'Jane Smith');
Tracking Events
Events are the primary way to track user actions. Emit events when users perform actions in your app:
// Track a one-time event
sdk.emit('user_login');
// Track repeatable events
sdk.emit('user_action');
sdk.emit('item_purchased');
sdk.emit('feature_used');
Tracking Stats
Stats are numeric values that accumulate over time. Use stats for progress-based achievements:
// Set a stat to a specific value
sdk.updateStat('score', 100);
// Increment a stat
sdk.updateStat('score', (prev) => prev + 10);
// Decrement a stat
sdk.updateStat('lives', (prev) => prev - 1);
Manual Unlocks
For achievements that can't be automatically triggered, use manual unlocks:
// Manually unlock an achievement
sdk.unlock('special_event_achievement');
Listening for Unlocks
Subscribe to unlock events to show notifications:
// Listen for achievement unlocks
sdk.on('unlock', ({ achievement, at }) => {
console.log(`Unlocked: ${achievement.name}!`);
showNotification(achievement);
});
// Listen for progress updates
sdk.on('progress', ({ achievement, ratio, current, target }) => {
console.log(`${achievement.name}: ${current}/${target} (${(ratio * 100).toFixed(1)}%)`);
updateProgressBar(achievement.id, ratio);
});
// Listen for stat changes
sdk.on('stat', ({ stat, value }) => {
console.log(`Stat ${stat} is now ${value}`);
});
// Listen for all events
sdk.on('event', ({ event, count }) => {
console.log(`Event ${event} fired ${count} times`);
});
on() method returns an unsubscribe function. Call it to remove the listener:
const unsubscribe = sdk.on('unlock', handler);
// Later...
unsubscribe();
Displaying Achievements
Query the SDK to display achievements in your UI:
// Get all achievements with their status
const achievements = sdk.list();
achievements.forEach(({ achievement, unlocked, ratio, current, target }) => {
console.log(`${achievement.name}: ${unlocked ? 'Unlocked' : 'Locked'}`);
console.log(`Progress: ${current}/${target} (${(ratio * 100).toFixed(1)}%)`);
});
// Get a specific achievement
const status = sdk.get('power_user');
if (status) {
console.log('Unlocked:', status.unlocked);
console.log('Unlocked at:', status.unlockedAt ? new Date(status.unlockedAt) : 'Never');
}
// Check if unlocked
if (sdk.isUnlocked('first_login')) {
console.log('User has logged in before');
}
// Get total points
const points = sdk.totalPoints();
console.log(`Earned: ${points.earned} / ${points.possible}`);
Example: Achievement List UI
function renderAchievements() {
const container = document.getElementById('achievements');
const achievements = sdk.list();
container.innerHTML = achievements.map(({ achievement, unlocked, ratio }) => `
<div class="achievement ${unlocked ? 'unlocked' : 'locked'}">
<img src="${achievement.icon || 'default.svg'}" alt="${achievement.name}" />
<div>
<h3>${achievement.name}</h3>
<p>${achievement.description}</p>
<div class="progress-bar">
<div style="width: ${ratio * 100}%"></div>
</div>
<span>${achievement.points} points</span>
</div>
</div>
`).join('');
}
Save Import/Export
Allow users to backup or transfer their progress:
// Export current progress
const saveData = sdk.exportSave();
downloadFile('achievement-save.json', saveData);
// Import progress from another device
const importedData = await readFile('achievement-save.json');
sdk.importSave(importedData);
console.log('Progress imported successfully');
Resetting Progress
// Reset all progress for the current player
sdk.reset();
console.log('Progress reset');
Trigger Types Reference
The SDK supports seven trigger types for defining achievement conditions:
- event-once: Fires when an event is emitted at least once
- event-count: Fires when an event is emitted N or more times
- stat-gte: Fires when a stat reaches or exceeds a value
- stat-eq: Fires when a stat equals an exact value
- all-of: Fires when all sub-triggers are satisfied (AND logic)
- any-of: Fires when any sub-trigger is satisfied (OR logic)
- manual: Only unlocks via
sdk.unlock()
Complex Trigger Example
{
"id": "well_rounded",
"name": "Well Rounded",
"description": "Complete 5 tasks AND reach level 10",
"points": 30,
"trigger": {
"type": "all-of",
"of": [
{ "type": "event-count", "event": "task_completed", "value": 5 },
{ "type": "stat-gte", "stat": "level", "value": 10 }
]
}
}
Best Practices
- Initialize once: Create the SDK instance at app startup and reuse it throughout your application
- Use meaningful event names: Use snake_case for events (e.g.,
user_login,item_purchased) - Track stats for progress: Use stats for cumulative values, events for discrete actions
- Show notifications: Always provide visual feedback when achievements unlock
- Test your manifest: Use the dashboard to validate your manifest and test achievements
- Version your manifest: Increment the version field when adding new achievements
Next Steps
- View the example manifest for a complete reference
- Try the demo game to see the SDK in action
- Explore the dashboard to understand the data model
- Check the Web Games guide for game-specific patterns