W3 / DevelopersSDK 2.0.0Source & examples ↗

Build app features

Use the generated application runtime as your normal integration point. Its lifecycle snapshot combines connection status, current state, resolved settings, and host capabilities. The complete data model explains individual fields; the API reference lists exported signatures and types.

Render the initial state and subsequent changes

import { defineApplication } from '@w3booster/sdk/app';

// Complete standalone demo. Registered apps import their generated w3boosterApp instead.
const w3boosterApp = defineApplication({
  clientId: 'documentation_demo', revision: 'demo',
  scopes: ['match:read', 'players:read'] as const,
  settingsDefaults: {}
});
const runtime = w3boosterApp.createRuntime({ demo: true });
const output = document.createElement('p');
document.body.append(output);
runtime.lifecycle.subscribe(snapshot => {
  output.textContent = snapshot.state?.players.map(player => player.name).join(' vs ')
    || (snapshot.status === 'connected' ? 'Waiting for players' : snapshot.status);
}, { signal: runtime.signal });
await runtime.start();
window.addEventListener('pagehide', () => { void runtime.stop(); }, { once: true });

The subscription runs immediately, including before match state exists. An already-running match is present in the initial snapshot; it does not synthesize a match.started event. Use the state to render and domain events for transitions such as a new hero or ended match.

During reconnect, retained state may still be visible while isSynchronized is false. Label stale data appropriately. Do not start another retry loop: the runtime already owns retries. Use snapshot.retry for progress, snapshot.error for connection failure, and structured issue events for nonfatal recorder or consumer problems.

Optional data is a normal state

Check state.capabilities before exposing a feature, and still check each optional field. A granted scope does not guarantee that the current source has a value. Show “Unavailable” when absence has meaning; do not turn unknown gold or health into a misleading zero.

Match data uses whole game-time seconds. Ability activation timing and upgrade timestamps use different documented units. Prefer SDK formatting and cooldown helpers. See the data model and standard-game helpers.

Read a finished match's result

With match:read, a finished match can include:

{
  "id": "match-123",
  "status": "finished",
  "result": { "playerId": "0", "outcome": "won" }
}

This is a partial match example; normal snapshots also contain the other match fields.

Field Meaning
match.result Optional recorder-confirmed outcome for the actual local player
match.result.playerId That player's string ID within this match; can be joined to state.players with players:read
match.result.outcome Exactly "won" or "lost"

The result uses match:read on all plans; there is no extra result scope. It is absent when unknown, in observer games, and in replays. It is not a complete list of winners and losers, and it does not describe an observer's selected player. An ended match without result is not evidence of a loss.

A result can arrive after the first finished snapshot and its match.ended event. Subscribe to state updates to receive that later information. The platform retains the current finished match until the next match starts, so hydration or reconnection can deliver it again. This is the current terminal match, not a historical results service. Use match.endedAt when present; lifecycle observedAt is the client's observation time.

import type { W3BoosterClient } from '@w3booster/sdk';

export function displayMatchResult(
  client: W3BoosterClient,
  output: HTMLElement,
  signal: AbortSignal
): () => void {
  return client.state.subscribe(state => {
    output.textContent = 'Result unavailable';
    const match = state?.match;
    if (match?.status !== 'finished' || !match.result) return;
    const result = match.result;

    output.textContent = `Match ${match.id}: player ${result.playerId} ${result.outcome}`;
  }, { signal });
}

Call displayMatchResult(runtime.client, outputElement, runtime.signal) during runtime setup. This example updates a display without incrementing a counter on each state publication.

Apps own scoring policy. For a persistent counter, wait for all required player facts, deduplicate by match.id, and save the processed ID together with the counter in one atomic update. Multiple windows and retries must not increment the same match twice. Excluding AI games or short losses, disabling automatic updates, and resetting sessions are app decisions, not SDK behavior.

Settings: define, sync, read, write

Create settings in the W3Booster editor. Run npx w3booster-settings init YOUR_CLIENT_ID --endpoint https://api.w3booster.com once, then npm run w3booster:sync after definition changes. Commit the generated file. npm run w3booster:check performs a strict online comparison for connected CI; it is not an offline build check.

Read snapshot.settings for defaults merged with the user's saved values. Use runtime.client.host.setSetting(path, value) to save through a supported host, and await the result. Concurrent writes are serialized by the SDK. Avoid writing on every render; use explicit user actions or deliberate debouncing.

An APPLICATION_DEFINITION_MISMATCH means the deployed bundle carries an older definition. Regenerate, rebuild, and redeploy it. Normal builds use committed bindings; optional --install-hooks adds online refresh hooks. Plain JavaScript apps can generate --output src/w3booster.generated.js.

Host capabilities and OS access

Apps are sandboxed web pages. The SDK does not expose arbitrary filesystem paths, shell commands, processes, or Node.js modules. Browser features such as user-selected files and browser storage remain subject to browser rules. Browser storage is tied to the origin and browser profile; it is not shared application settings.

Action Embedded application Detached application Standalone demo / overlay
Open an app window Discover window:open Discover window:open Do not assume support
Close this window Embedded surface cannot close itself Discover window:close Do not assume support
Save app settings Discover settings:write Discover settings:write Do not assume support
Read match data Granted scopes and available source Same grant Demo fixtures or authenticated overlay data

Always use reactive capability discovery; available features depend on the actual host. client.host.command() is an application command channel, not a shell. The platform provides generic app storage; applications own their scoring and other feature logic. Do not assume that a host supports a command just because the SDK can send it; handle rejected actions.

Shared game context and app data

With SDK 2.0, every authorized app receives state.gameContext automatically. No scope or plan is required, including for an app that requests no match data.

Field Meaning
hudScale Warcraft HUD scale from 0.5 to 1.0; always present, defaults to 1
chatbarOpen Whether the game chat input is open; absent when unknown
teamColors The team-color preference; absent when unknown

The SDK's gameContext(state) selector supplies the same scale-1 fallback before hydration. overlay:read and overlay.runtime are removed. Use gameContext directly; every generated binding uses the current contract.

App-specific runtime data belongs in state.application.data, separately from saved configuration in state.application.settings. Match Vision reads its wins/losses counter from state.application.data.matchScore. Only Match Vision receives that counter; there is no legacy score fallback. Adding overlay:read does not grant another app access. Match Vision owns its counter mutations and automatic scoring rules; the platform stores and transports app data without interpreting its fields.

Observer Economy needs only match:read, players:read, and resources:read. Recorder discovery is transport metadata; the SDK hides recorder URLs and applies updates under the granted data capabilities. Missing resource records remain unavailable instead of becoming fabricated zeroes.

Observer/replay data can use a local recorder even with the production API. Browser permission to access that local service is separate from W3Booster data scopes.

Open a compact window

Use runtime.client.host.openWindow({ path: '?view=compact', width: 520, height: 620 }). The path is relative to your registered application URL. Await the host acknowledgement and show a useful error on failure. Use paths or query parameters for routing; authorization reserves the URL fragment.

Integrate your framework

Create a runtime inside the component or application lifetime, subscribe before calling start(), and call stop() at teardown. Catch startup rejection. Avoid a new runtime on every render. Framework examples are application code; the SDK remains framework-neutral.

  • React: adapt runtime.lifecycle using createReactStore and useSyncExternalStore. Keep the runtime/store stable for one mounted lifetime. Create a new runtime for a new mount, including development Strict Mode remounts. The adapter defaults its server snapshot from the store; avoid constructing an incomplete lifecycle object.
  • Angular: use createSubscribable(runtime.lifecycle) with toSignal(), provide runtime.lifecycle.get() as the initial value, and stop through the owning destroy lifecycle. Match Vision's integration is a complete example.
  • Vue/Svelte: subscribe once in the mounted lifetime, assign the immutable snapshot to framework state, and unsubscribe/stop at unmount. SDK subscription methods return an unsubscribe function and support AbortSignal.
  • Plain JavaScript/TypeScript: App Starter includes rendering, startup, page teardown, and hot-module cleanup.

Do not render private match data on a public server or persist credentials to solve hot reload. A full page/runtime reload may require reopening the app from the host for a fresh launch.