JSPM

@vibecade/sdk

0.1.1
    • ESM via JSPM
    • ES Module Entrypoint
    • Export Map
    • Keywords
    • License
    • Repository URL
    • TypeScript Types
    • README
    • Created
    • Published
    • Downloads 11
    • Score
      100M100P100Q61567F

    JavaScript library for the Vibecade platform

    Package Exports

    • @vibecade/sdk
    • @vibecade/sdk/package.json

    Readme

    @vibecade/sdk

    What it is

    @vibecade/sdk is a JavaScript library for integrating with the Vibecade platform. It exposes a single class, Vibecade, whose methods let a web page:

    • Sign players in (anonymously by default) and read their profile.
    • Submit scores and read leaderboards.
    • Unlock achievements and record progress toward progressive ones.
    • Read and write a per-player per-game cloud save blob.
    • Emit analytics events.
    • Promote an anonymous account to a claimed one (email / OAuth).

    The SDK is optional. A web page that never loads it is still a perfectly valid consumer of the platform — the platform only requires that games be reachable at a URL. The SDK is what lets a game opt into platform features.

    Loading the SDK

    @vibecade/sdk is published in three forms. A web page can load it via any of them:

    1. Via <script> tag from the CDN

    This form requires no build tooling. Load Supabase first (the SDK's peer dependency) and then the SDK. The SDK exposes the Vibecade class on window.

    <script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2/dist/umd/supabase.js"></script>
    <script src="https://sdk.vibecade.ai/v1/sdk.js"></script>
    <script>
      const vc = new Vibecade({
        gameId: 'your-game-id',
        apiKey: 'your-api-key',
      });
    </script>

    2. As an ES module from the CDN

    <script type="module">
      import { Vibecade } from 'https://sdk.vibecade.ai/v1/sdk.esm.js';
    
      const vc = new Vibecade({
        gameId: 'your-game-id',
        apiKey: 'your-api-key',
      });
    </script>

    3. Via npm

    npm install @vibecade/sdk @supabase/supabase-js
    import { Vibecade } from '@vibecade/sdk';
    
    const vc = new Vibecade({
      gameId: 'your-game-id',
      apiKey: 'your-api-key',
    });

    Quickstart

    Shortest path from "SDK loaded" to "first leaderboard entry":

    <script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2/dist/umd/supabase.js"></script>
    <script src="https://sdk.vibecade.ai/v1/sdk.js"></script>
    <script>
      const vc = new Vibecade({
        gameId: 'your-game-id',
        apiKey: 'your-api-key',
      });
    
      async function run() {
        await vc.init();
        console.log('playing as', vc.getPlayer());
    
        const result = await vc.submitScore({ score: 1234 });
        console.log('rank', result.rank);
    
        const top = await vc.getLeaderboard({ limit: 10 });
        for (const entry of top) {
          console.log(entry.rank, entry.player.displayName, entry.score);
        }
      }
      run().catch((err) => console.error(err.code, err.message));
    </script>

    That's the full happy path. No accounts created up front; init() signs the player in anonymously, the DB creates a profile, and the score carries their rank.

    API reference

    All methods return Promises unless stated otherwise. Every call should be wrapped in try/catch — see Error handling.

    new Vibecade(config)

    Construct an SDK instance. Does not perform any network work — call init() to establish a session.

    Parameters:

    Field Type Notes
    gameId stringrequired Slug of your game on the platform.
    apiKey stringrequired Your game's API key.
    apiUrl string — optional Override for dev/staging. Defaults to the production API.
    anonKey string — optional Supabase anon key for the target environment. Required when apiUrl points at anything other than production.
    debug boolean — optional When true, SDK logs internals via console.debug.

    Throws VibecadeError with code INVALID_CONFIG if required fields are missing or wrong type.

    init(): Promise<void>

    Establish a player session. On first call with no prior session, signs in anonymously and creates a player profile server-side. On subsequent calls (or when a saved session is still valid), attaches to the existing session. Idempotent: concurrent calls share one in-flight request; repeat calls after success return immediately.

    Must be awaited before any method that requires a signed-in player. Throws VibecadeError with code AUTH_FAILED if the sign-in round-trip fails.

    isAuthenticated(): boolean

    Synchronous. Returns true when the SDK currently holds a session. Safe to use in render paths.

    getPlayer(): Player | null

    Synchronous. Returns the cached Player object (populated by init() and kept current by auth state changes) or null before init.

    interface Player {
      id: string;
      displayName: string;
      avatarUrl: string | null;
      isAnonymous: boolean;
      createdAt: string;
    }

    startPlay(): Promise<{ playToken: string }>

    Ask the platform for a one-use anti-cheat play token. Games whose platform entry has requires_play_token = true must call this at the start of a play session and pass the returned playToken to submitScore at the end. See Anti-cheat guidance.

    Tokens expire 10 minutes after issuance.

    submitScore(opts): Promise<SubmitScoreResult>

    Submit a score for the current player.

    vc.submitScore({
      score: 1234,
      meta: { level: 3, seed: 'abc' }, // optional
    });

    Returns { accepted: true, id: number, rank: number }. rank is the player's position on the all-time leaderboard for this game, counted as the number of strictly-better scores plus one.

    Play tokens are handled automatically. If the game has requires_play_token = true set, the SDK transparently calls startPlay() and retries — no manual token management needed. Tokens are single-use, so this costs one extra round-trip per submit on those games. If you'd rather manage tokens explicitly (e.g., pre-fetching for latency), pass opts.playToken and the SDK uses your token without auto-fetching:

    const { playToken } = await vc.startPlay();
    // ... gameplay ...
    vc.submitScore({ score: 1234, playToken });

    Common error codes: INVALID_API_KEY, SCORE_OUT_OF_RANGE (exceeds the game's max_plausible_score), PLAY_TOKEN_EXPIRED, PLAY_TOKEN_USED. (PLAY_TOKEN_REQUIRED is no longer thrown — the SDK auto-recovers before the error reaches the caller.)

    getLeaderboard(opts?): Promise<ScoreEntry[]>

    Fetch a page of scores for the current game.

    vc.getLeaderboard({
      scope: 'all-time',  // 'global' | 'today' | 'weekly' | 'all-time'
      limit: 20,          // default 20, max 100
      around: 'me',       // optional — centers the page on the player's rank
    });

    Returns an array of entries ordered by the game's configured direction (descending for higher-is-better games, ascending for lower-is-better):

    interface ScoreEntry {
      id: number;
      score: number;
      createdAt: string;
      meta: Record<string, unknown> | null;
      player: { id: string; displayName: string; avatarUrl: string | null };
      rank: number;
    }

    When around: 'me' is set and the player has no score in the scope, falls back to the top of the board.

    unlockAchievement(achievementId): Promise<UnlockResult>

    Unlock a one-shot achievement for the current player. Idempotent: re-unlocking preserves the original timestamp.

    const res = await vc.unlockAchievement('FIRST_CUP');
    // { newlyUnlocked: boolean, unlockedAt: string }

    Throws ACHIEVEMENT_WRONG_TYPE if the id refers to a progressive achievement — use setAchievementProgress for those.

    setAchievementProgress(achievementId, progress): Promise<ProgressResult>

    Record progress toward a progressive achievement. Progress is monotonic and max-wins — a value below the stored progress is silently clamped to the stored value (safe for retries after flaky networks); equal is an idempotent no-op. When progress reaches or passes the achievement's target for the first time, the achievement unlocks.

    const res = await vc.setAchievementProgress('CUP_CENTURY', 42);
    // { newlyUnlocked: boolean, progress: number, target: number }

    Throws ACHIEVEMENT_WRONG_TYPE if the id refers to a one-shot achievement — use unlockAchievement for those.

    getAchievements(): Promise<AchievementProgress[]>

    Return every achievement defined for the game, with the current player's progress folded in.

    interface AchievementProgress {
      id: string;
      displayName: string;
      description: string | null;
      iconUrl: string | null;
      points: number;
      isProgressive: boolean;
      targetProgress: number | null;
      progress: number;
      unlocked: boolean;
      unlockedAt: string | null;
    }

    cloudSave.get<T>(): Promise<T | null>

    Read the current player's save blob for this game. Returns null if none was saved yet.

    cloudSave.set<T>(data: T): Promise<void>

    Write (upsert) the current player's save blob for this game.

    Cloud saves are owner-only at the DB level — other players cannot read or write your save.

    trackEvent(name, properties?): void

    Synchronous, fire-and-forget. Buffers an analytics event in memory. The buffer flushes on a 5-second interval, when it reaches 20 events, and on page unload (via keepalive fetch). trackEvent never throws and never blocks — failures are silently dropped with an optional debug log.

    vc.trackEvent('level_started', { level: 3 });

    identify(opts): Promise<void>

    Promote an anonymous account to a claimed one.

    await vc.identify({ email: 'player@example.com' });
    // Magic-link email is sent; player clicks the link to confirm.
    
    await vc.identify({ provider: 'google' });
    // OAuth redirect is initiated; page reloads after return.

    Both paths cause a page reload on successful return. The SDK picks the session up automatically on the next init() via its URL-token detection.

    Authentication model

    Anonymous by default. The first time a player visits a page that loads the SDK and calls init(), the SDK asks Supabase for an anonymous session. The platform's DB creates a players row keyed to the new auth user, with a generated display name like Player_a1b2c3. Subsequent page loads reuse the stored session — anonymous players retain their identity across visits on the same device.

    Claiming an account. Calling identify({ email }) sends a magic link; on click, the anonymous session is linked to the email-backed identity. Calling identify({ provider: 'google' | 'apple' | 'discord' }) initiates an OAuth redirect. After either flow completes, the player keeps their existing id, display name, score history, and achievements — isAnonymous flips to false.

    getPlayer() stays in sync. The cached player is updated on init() and on Supabase auth state changes (token refresh, sign-out, tab sync).

    Error handling

    Every failure the SDK throws is a VibecadeError. Catch it to inspect the code field and surface something meaningful:

    try {
      await vc.submitScore({ score: 1234 });
    } catch (err) {
      if (err.code === 'SCORE_OUT_OF_RANGE') {
        console.warn('score was rejected as implausible');
      } else if (err.code === 'NETWORK_ERROR') {
        console.warn('platform offline — will retry later');
      } else {
        console.error('score submission failed:', err.code, err.message);
      }
    }

    Error codes are exported as VibecadeErrorCode for type-safe comparison:

    import { VibecadeErrorCode } from '@vibecade/sdk';
    
    if (err.code === VibecadeErrorCode.ScoreOutOfRange) { ... }

    Always wrap SDK calls in try/catch. The SDK is additive — its job is to enrich a game with platform features, not to be required for the game to work. Platform outages, network failures, or rejected calls must degrade gracefully, not crash gameplay.

    Anti-cheat guidance

    Two related knobs on a game's platform entry:

    max_plausible_score is a ceiling. If set, submitScore rejects submissions above it server-side. Set it to a value a human player might plausibly reach; scores above probably mean a bug or a cheat.

    requires_play_token makes the leaderboard competitive. When true, submitScore rejects submissions that don't include a valid playToken obtained from startPlay at the start of the session. Tokens are one-use, server-issued, 10-minute-lived, and bound to the player + game they were issued for.

    A typical flow when play tokens are required:

    await vc.init();
    
    const { playToken } = await vc.startPlay();
    // ... play happens ...
    await vc.submitScore({ score: finalScore, playToken });

    Use requires_play_token on games with meaningful competitive leaderboards. Skip it for casual games where the cost of an occasional inflated score is low.

    Nothing beyond these knobs is enforced in MVP — no replay validation, no statistical analysis, no ML. More sophisticated checks can layer on later; the SDK's anti-cheat surface is stable.

    Behavior guarantees

    • The SDK is optional. A web page that never loads the SDK is a perfectly valid consumer of the Vibecade platform. Games that integrate the SDK pick up platform features (identity, leaderboards, achievements, cloud saves, analytics). Games that don't, don't.
    • The SDK never blocks gameplay on network. Every method is async; callers are expected to try/catch. A platform outage must not prevent a game from running.
    • Anonymous auth is the default. Players don't need to sign up before playing. Identity is persisted client-side. Claiming the account later keeps all existing data.
    • Achievements are idempotent. Re-unlocking a one-shot achievement returns the original timestamp; progressive updates are monotonic and max-wins (regressions silently clamp to the stored value), so client-side dedup isn't needed.
    • Play tokens auto-handled. Games with requires_play_token get transparent startPlay() + retry on submitScore calls that don't supply a token. Manual opts.playToken still works and skips the auto path. Detection caches after the first submit so subsequent submits pre-fetch directly.
    • Analytics is fire-and-forget. trackEvent never throws; the SDK handles batching and retries best-effort internally.
    • Auto-toasts in launcher chrome. When the SDK runs inside a Vibecade launcher iframe, successful submitScore and unlockAchievement (and setAchievementProgress calls that cross the target threshold) automatically post a message to window.parent so the launcher can render a toast. Standalone games (window.parent === window) skip this step. No game-side work required to get launcher toast UX — the SDK handles it. Pass autoHostMessages: false in the config if your game already emits its own window.parent.postMessage for these events (pre-0.1.1 integrations); leaving both on produces duplicate toasts.

    Compatibility

    • Modern evergreen browsers (ES2020+).
    • Any JavaScript environment that supports fetch and localStorage.
    • Node.js is not a targeted runtime (the SDK assumes a browser-like host with window). For server-side use cases, use the Supabase client directly against the platform's Postgres.

    License

    Apache-2.0