/** * Type definitions for the VibeGames guest SDK (`window.vibe`). * Copy this file into your project for editor support; the SDK itself is injected by the host. */ export type VibeTheme = 'light' | 'dark'; /** All your app learns about a player: never their e-mail address or account. */ export interface VibePlayer { /** App-scoped player id. Stable per player and app; never a global account id. */ id: string; /** The name the player chose (2 to 50 characters). */ nickname: string; /** @deprecated The same as {@link nickname}; use that. */ displayName: string; } /** * A control the shell draws on top of your app, in CSS pixels of your viewport. Today this is the menu handle: a small * pill centred at the top, below the top safe-area inset. Players tap it (or hold Esc) to open the shell menu. */ export interface VibeHostControl { kind: 'menu'; x: number; y: number; width: number; height: number; } export interface VibeContext { appId: string; appName: string; versionId: string; versionLabel: string; player: VibePlayer; locale: string; theme: VibeTheme; online: boolean; permissions: string[]; safeArea: { top: number; right: number; bottom: number; left: number }; viewport: { width: number; height: number }; /** Areas the shell's own controls cover; keep important buttons out of them. Updated with every `resize` event. */ hostControls: VibeHostControl[]; } export interface VibeKeyValue { get(key: string): Promise; set(key: string, value: T): Promise; remove(key: string): Promise; keys(): Promise; clear(): Promise; } export interface VibeMessage { id: string; from: string; /** The sender's nickname. */ fromNickname: string; /** @deprecated The same as {@link fromNickname}; use that. */ fromDisplayName: string; title: string; body: string; data?: unknown; createdAt: string; } /** A leaderboard your app declares in vibe-app.json under "leaderboards". */ export interface VibeLeaderboard { /** What you pass to `vibe.leaderboards` (1-32 letters, digits, '.', '_', '-'). */ id: string; /** What players see (up to 40 characters). */ name: string; /** `desc`: higher scores rank first (the default). `asc`: lower scores rank first, e.g. times. */ order: 'desc' | 'asc'; /** `number` (the default) or `time`: milliseconds, shown to players as 1:23.456. */ format: 'number' | 'time'; } export interface VibeLeaderboardInfo { /** The publisher turned the leaderboards on for this game. While off, scores are not recorded and boards cannot be read. */ enabled: boolean; /** * The player takes part. Players opt out per game in VibeGames, and you can remove players who break your rules (in * the publisher console); their scores are then deleted and not recorded. */ participating: boolean; /** The boards this version declares. */ boards: VibeLeaderboard[]; } export interface VibeLeaderboardEntry { /** Equal scores share a rank (1, 2, 2, 4). Null for the player's own new best while it waits to sync. */ rank: number | null; /** App-scoped player id, as in `vibe.players`. */ playerId: string; nickname: string; score: number; /** When the player reached this score; of equal scores, the earlier one is listed first. */ updatedAt: string; } export interface VibeLeaderboardPage { board: string; entries: VibeLeaderboardEntry[]; /** The player's own entry, also when it is not on this page; null without one. */ me: VibeLeaderboardEntry | null; /** How many players are on the board. */ total: number; /** True when the device is offline: the last page fetched on this device, with the player's newer best. */ offline: boolean; } export interface VibeScoreResult { /** False when the leaderboards are off, the player opted out or you removed them: nothing was stored. */ recorded: boolean; /** Offline, the score is kept on the device and sent when the connection is back. */ queued: boolean; /** The score beat the player's best (only the best score per player and board is kept). */ improved: boolean; best: number | null; /** The player's rank after this score; null offline or when not recorded. */ rank: number | null; } export type VibeEvent = | 'message' | 'pause' | 'resume' | 'theme' | 'resize' | 'fullscreenchange' | 'online' | 'offline' | 'quota'; export type VibeErrorCode = | 'NOT_READY' | 'OFFLINE' | 'NOT_ALLOWED' | 'INVALID_ARGS' | 'NOT_FOUND' | 'QUOTA' | 'RATE_LIMITED' | 'TIMEOUT' | 'INTERNAL'; export interface VibeError extends Error { code: VibeErrorCode; } export interface VibeSdk { readonly version: string; /** Resolves once the host handshake is complete and all assets are available. */ readonly ready: Promise; readonly context: VibeContext | null; /** Returns the runtime URL of a bundled file for paths computed at runtime (static paths are rewritten automatically). */ asset(path: string): string; /** Server-side storage per player and app (JSON values). Works offline; changes sync in the background. */ readonly cloud: VibeKeyValue; /** Device-only string storage (like localStorage). */ readonly local: VibeKeyValue; /** Device-only structured storage (like IndexedDB; Blob and ArrayBuffer values are allowed). */ readonly db: VibeKeyValue; readonly players: { me(): Promise; /** Finds other players of this app by nickname (at least 2 characters). Requires network. */ search(query: string, take?: number): Promise; /** * Lets the player report another player to the VibeGames admins (offensive name, a message, behaviour). The shell * shows its own dialog where the player chooses what is wrong and why, or cancels; `reported` says which. Pass the * `messageId` of a message the other player sent, to report that message. Requires network. */ report(playerId: string, options?: { messageId?: string }): Promise<{ reported: boolean }>; /** * Lets the player choose another player, e.g. an opponent: the shell opens its own dialog with the player's friends * who have your app (and can receive messages) and a name search below them. Resolves with the chosen player, the * same app-scoped `{ id, nickname }` as from `search`, or null when the player cancelled. Your app never sees the * friend list, nor whether the chosen player is a friend. One shell dialog at a time (`RATE_LIMITED`); requires * network (`OFFLINE`); not in previews (`NOT_ALLOWED`). Waits for the player without a timeout. */ pick(): Promise; /** * Lets the player send another player of this app a friend request, e.g. after a game against them: the shell asks * the player first, and the other player accepts or declines it in VibeGames. `requested` is true when the player * confirmed, false when they cancelled. Your app never learns whether the two are friends already, whether the other * accepts, or why VibeGames refused a request (it sends them only between players who each sent the other a message * in your app, and never to players under 18). One shell dialog at a time (`RATE_LIMITED`); requires network * (`OFFLINE`); not in previews (`NOT_ALLOWED`). Waits for the player without a timeout. */ addFriend(playerId: string): Promise<{ requested: boolean }>; }; readonly messages: { /** * Sends a message to another player of this app. Offline, the message is queued and `queued` is true. Sends that * overlap can arrive in either order: when order matters (a move after its invitation), wait for each send. A `QUOTA` * error means the player sent too many messages in the last minute (by default 60): nothing was sent, so wait a few * seconds and send it again. */ send(message: { to: string; title: string; body?: string; data?: unknown }): Promise<{ id: string; queued: boolean }>; /** Messages received while the app was not running. Acknowledge them with ack(). */ pending(): Promise; ack(ids: string | string[]): Promise; onMessage(handler: (message: VibeMessage) => void): () => void; }; /** * Leaderboards declared in vibe-app.json, once the publisher turned them on. Ask the player before you submit their * first score: their nickname and best score become visible to the other players of the game. */ readonly leaderboards: { /** Whether the leaderboards are on, whether the player takes part, and the boards. Works offline. */ info(): Promise; /** Records a whole-number score; the server keeps each player's best per board. Offline, it is sent later (`queued`). */ submit(board: string, score: number): Promise; /** The best entries of a board (`limit` 1-100, default 10). Offline, the last page fetched on this device. */ top(board: string, options?: { limit?: number }): Promise; /** The entries around the player's own (the best entries when the player has none). */ around(board: string, options?: { limit?: number }): Promise; }; /** Counts what happens in your game for your statistics (Publish → the app → Statistics → Events). */ readonly analytics: { /** * Counts `event`, one of the ids declared under "events" in vibe-app.json, e.g. "level-complete". `value` (0 or more, * e.g. seconds or points) adds up per event: the statistics show the total and the average, so pass one on every call * of an event or on none. Fire and forget: nothing comes back, nothing is counted for players who turned off usage * statistics, and events the running version does not declare are ignored (the devkit warns). Throws INVALID_ARGS for * an id that cannot be one or a value that is not a number of 0 or more. */ track(event: string, options?: { value?: number }): void; }; /** Feedback from the player to you, the publisher. Read it in the publisher console under Player feedback. */ readonly feedback: { /** * Opens the shell's feedback dialog, e.g. from a "Report a bug" button in your settings. The player writes the * feedback and sends it, or cancels; `sent` says which (true also when it waits for a connection). `kind` * preselects what it is about. `context` is up to 500 characters of plain text you add, such as the level and * the score; the dialog shows it to the player, and it goes with the device information, which the player can * leave out. Not in previews; one dialog at a time. */ open(options?: { kind?: 'bug' | 'idea' | 'other'; context?: string }): Promise<{ sent: boolean }>; }; readonly fullscreen: { enter(): Promise; exit(): Promise; }; /** Asks the host to open an external http(s) link in a new tab (the user confirms). */ openUrl(url: string): Promise; /** * Closes the app and returns to the shell. Offer it in your own UI; the shell's menu (handle at the top, or holding * Esc) is the player's way out when the app does not. */ exit(): void; on(event: VibeEvent, handler: (payload: unknown) => void): () => void; off(event: VibeEvent, handler: (payload: unknown) => void): void; } declare global { interface Window { readonly vibe: VibeSdk; } const vibe: VibeSdk; } export {};