← Docs

Web Apps Integration

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 }
    }
  ]
}
Tip: The 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`);
});
Note: The 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