SDK Reference

Reference for @looploop/sdk. Import the LoopLoop namespace and call methods on its submodules (auth, telemetry, feedback, etc.).

Quick Start

import { LoopLoop } from '@looploop/sdk';

const session = await LoopLoop.auth.getSession();
const sessionId = crypto.randomUUID();
LoopLoop.telemetry.startSession(sessionId, '0.1.0');
startGame();

Call LoopLoop.telemetry.startSession early so telemetry, the feedback button, and error capture behave as designed. Use LoopLoop.init when you need to set API base URL, game slug, or auth mode.


LoopLoop.auth

getSession(): Promise<PlayerSession | null>

Returns the signed-in player session, or null for guests and when no session exists (including failed network requests).

login(provider: 'google' | 'discord'): Promise<void>

Starts OAuth with Google or Discord (redirect or popup depending on SDK config).

logout(): Promise<void>

Ends the server session and clears local auth state (including cached session).

isGuest(): boolean

Returns true when there is no cached session (guest). After getSession() resolves, behavior aligns with whether a session was returned.

Example

const session = await LoopLoop.auth.getSession();
if (!session) {
  // optional: LoopLoop.auth.login('google');
}

LoopLoop.telemetry

startSession(sessionId: string, gameVersion: string): void

Begins a play session: queues a session_start event, starts a 10s flush interval, initializes error capture, and auto-mounts the feedback button.

emit(event: TelemetryEvent): void

Appends a gameplay event to the batch. Batches flush when 20 events are queued or on the periodic timer (~10s).

endSession(data?: { durationSeconds, enemiesKilled, levelReached, causeOfDeath }): void

Ends the session: emits session_end, clears timers, stops error capture, unmounts the feedback button, and flushes remaining events. Pass optional summary fields in data for analytics.

flush(): void

Force-sends the current event batch.

Example

LoopLoop.telemetry.emit({
  type: 'enemy_killed',
  timestamp: Date.now(),
  data: { enemyId: 'boss-1' },
});

await LoopLoop.telemetry.endSession({
  durationSeconds: 120,
  enemiesKilled: 42,
  levelReached: 3,
  causeOfDeath: 'spikes',
});

LoopLoop.feedback

showQuickPrompt(opts: { sessionId, gameVersion, sessionMetrics? }): void

Shows the one-tap rating UI: Too Easy, Just Right, Too Hard, Boring, Buggy. Submits on tap; optional sessionMetrics attach run summary.

showDetailedForm(opts?: { gameVersion, context? }): void

Opens the full feedback form (category + text). Signed-in users get the full flow; guests may see a sign-in prompt depending on configuration.

showCrashReport(error: Error): void

Shows the crash-report UI (e.g. after an uncaught exception).

submit(payload: FeedbackPayload): Promise<void>

POSTs feedback JSON to the platform. Include sessionId and gameVersion; other fields depend on quick vs detailed feedback.

Example

await LoopLoop.feedback.submit({
  sessionId,
  gameVersion: '0.1.0',
  quickRating: 'just_right',
  sessionMetrics: {
    durationSeconds: 90,
    enemiesKilled: 10,
    levelReached: 2,
    causeOfDeath: '',
  },
});

LoopLoop.feedbackButton

mount(): void

Mounts the draggable floating feedback button (typically called for you from telemetry.startSession).

unmount(): void

Removes the button and listeners.


LoopLoop.notifications

getUnread(): Promise<Notification[]>

Returns unread notifications for the current user (empty array on error).

markRead(id: string): Promise<void>

Marks a notification as read by id.

getLatestRelease(): Promise<Release | null>

Fetches the latest platform release notes, or null if none / error.


LoopLoop.capture

startRecording(config?: { canvas?, interval?, duration?, width?, height?, quality? }): void

Records frames from the game canvas (default: find canvas under #game-container or first canvas) for GIF encoding.

stopRecording(): Promise<Blob>

Stops recording and returns a GIF Blob.

captureFrame(config?: { canvas?, width? }): Promise<Blob>

Captures a single PNG frame.

uploadThumbnail(gameId: string, blob: Blob, type: 'gif' | 'png'): Promise<string>

Uploads a thumbnail asset; returns the public URL.

uploadSplash(gameId: string, blob: Blob): Promise<string>

Uploads a wide landscape splash / hero image (server enforces max 5 MB); returns the public URL.

Example

LoopLoop.capture.startRecording({ width: 480, interval: 200 });
// ... gameplay ...
const gif = await LoopLoop.capture.stopRecording();
const gameId = await LoopLoop.gameContext.getGameId();
if (gameId) {
  await LoopLoop.capture.uploadThumbnail(gameId, gif, 'gif');
}

// Wide landscape splash (PNG), max 5 MB on server
const splashPng = await LoopLoop.capture.captureFrame({ width: 1920 });
if (gameId) {
  await LoopLoop.capture.uploadSplash(gameId, splashPng);
}

LoopLoop.errorCapture

init(): void

Starts listening for uncaught errors and unhandled rejections, and a >5s main-thread freeze watchdog (via requestAnimationFrame). Automatically started when you call telemetry.startSession.

stop(): void

Tears down listeners and the watchdog.


LoopLoop.gameContext

getGameSlug(): string

Returns the game slug from subdomain ({slug}.looploop.gg), path (/games/{slug}), or SDK config.

getGameId(): Promise<string>

Resolves and caches the game id from the slug via the API.


LoopLoop.profile

getProfile(playerId: string): Promise<PublicProfile>

Loads a public player profile by id.

promotionSeen(): Promise<void>

Dismisses the tier promotion ceremony server-side after the player has seen it.


LoopLoop.games

list(params?: { genre?, page?, limit? }): Promise<{ games, total, page }>

Browse games with optional filters and pagination.

create(brief: GameBrief): Promise<{ gameId }>

Creates a new game from a design brief; returns the new gameId (and related fields from the API).

toggleFavorite(gameId: string): Promise<{ isFavorited }>

Toggles the current user’s favorite for a game; response includes the new favorite state.

Example

const { games, total, page } = await LoopLoop.games.list({
  genre: 'action',
  page: 1,
  limit: 12,
});

Types

Key types exported from @looploop/sdk (see PlayerSession and related in the package).

PlayerSession

interface PlayerSession {
  id: string;
  displayName: string;
  email: string | null;
  avatarUrl: string | null;
  authProvider: 'google' | 'discord';
  tier: PlayerTier;
  creatorTier: CreatorTier | null;
  loopBalance: number;
  totalLoopsEarned: number;
  tierProgress: TierProgress;
  pendingPromotion: PendingPromotion | null;
}

TelemetryEvent

interface TelemetryEvent {
  type: string;
  timestamp: number;
  data: Record<string, unknown>;
}

FeedbackPayload

interface FeedbackPayload {
  gameId?: string;
  sessionId: string;
  gameVersion: string;
  quickRating?: 'too_easy' | 'just_right' | 'too_hard' | 'boring' | 'buggy';
  category?: 'balance' | 'content' | 'bug' | 'art' | 'ux' | 'other';
  text?: string;
  sessionMetrics?: {
    durationSeconds: number;
    enemiesKilled: number;
    levelReached: number;
    causeOfDeath: string;
  };
}

Notification

interface Notification {
  id: string;
  type: 'feedback_implemented' | 'release' | 'tier_promoted' | 'creator_tier_promoted' | 'top5_entered' | 'top5_left';
  title: string;
  body: string;
  feedbackId: string | null;
  releaseId: string | null;
  gameId: string | null;
  createdAt: string;
  readAt: string | null;
}

Release

interface Release {
  id: string;
  version: string;
  notesMarkdown: string;
  createdAt: string;
}

GameState

interface GameState {
  status: 'loading' | 'menu' | 'playing' | 'paused' | 'game_over' | 'upgrade_select' | 'feedback';
  sessionId: string | null;
  elapsedSeconds: number;
  gameVersion: string;
  player: {
    x: number;
    y: number;
    health: number;
    maxHealth: number;
    level: number;
    xp: number;
    xpToNextLevel: number;
    alive: boolean;
  } | null;
  entities: {
    enemies: Array<{ id: string; type: string; x: number; y: number; health: number }>;
    pickups: Array<{ id: string; type: string; x: number; y: number }>;
  };
  metrics: {
    enemiesKilled: number;
    damageDealt: number;
    damageTaken: number;
    itemsCollected: number;
    levelsGained: number;
  };
}

PublicProfile

interface PublicProfile {
  id: string;
  displayName: string;
  avatarUrl: string | null;
  tier: PlayerTier;
  creatorTier: CreatorTier | null;
  earningsLabel: EarningsLabel;
  feedbackStats: {
    submitted: number;
    implemented: number;
    implementationRate: number;
  };
  gamesCreated: string[];
  recentImplemented: Array<{
    feedbackText: string;
    changeSummary: string;
    gameTitle: string;
    version: string;
  }>;
}

Supporting enums / unions such as PlayerTier, CreatorTier, TierProgress, and PendingPromotion are defined alongside these in the SDK.