Embed SDK

@carverjs/embed-sdk lets your game talk to the CarverJS marketplace shell from inside the play page. It is dependency-free, around 1 KB, and works with any engine — CarverJS, Phaser, Three.js, plain canvas, or a static HTML build.

Since 1.1.0 it also speaks protocol v2, which opens the other direction: the shell can hand your game its runtime configuration — a session token, a locale, TURN credentials, a multiplayer room — and your game can report structured learning telemetry back. It is fully additive, so everything on this page still holds. See Protocol v2.

Games on the marketplace run inside a sandboxed <iframe> on their own origin (https://g-{gameId}.carverjs.dev). The shell — the page a player opens to play your game — listens for a small set of typed postMessage signals. This SDK sends them for you, so you never hand-roll postMessage shapes, and it stays in lockstep with the shell's validator.

ts
import { carver } from "@carverjs/embed-sdk";

carver.progress(40);                    // loading bar in the shell (0–100)
carver.ready();                         // hides the loader — call on first frame
carver.score(1280, "points");           // feeds player-profile stats
carver.event("level-complete", { level: 3 });

Install #

bash
npm install @carverjs/embed-sdk

The package ships ESM and CommonJS builds with TypeScript types. It has no dependencies and no peer dependencies — drop it into any front-end build.

Quick start #

Wire the two calls every game should make — progress while loading, ready on the first rendered frame — then report whatever stats fit your game.

ts
import { carver } from "@carverjs/embed-sdk";

async function boot() {
  carver.progress(0);
  await loadAssets((pct) => carver.progress(pct)); // 0–100 as you go

  startRenderLoop();
  carver.ready();                        // first frame is up — shell hides its loader
}

// during play
function onCoinCollected(total: number) {
  carver.score(total, "coins");
  carver.event("coin-collected", { total });
}

// when the run ends
function onGameOver(finalScore: number) {
  carver.score(finalScore, "points");
  carver.exit();
}

boot();

Every call is a safe no-op outside an iframe — for example when you open your build locally during development — and in non-browser environments (SSR, tests). Nothing in the SDK ever throws, so you can leave the calls in place everywhere.

How it works #

Your game and the shell live on different origins, so they can only talk through window.postMessage. The SDK wraps that channel:

text
 ┌──────────────────────────┐   postMessage    ┌──────────────────────┐
 │  your game               │ ───────────────► │  marketplace shell   │
 │  g-{gameId}.carverjs.dev │ ◄─────────────── │  carverjs.dev        │
 └──────────────────────────┘   (validated)    └──────────────────────┘

You never need to know the shell's origin, build a message object by hand, or check whether you're embedded — the SDK handles all of it.

There is a second transport for native WebViews. When your game runs top-level and the shell has injected window.__carverNativeBridge, outbound messages travel through it as JSON text, and inbound ones arrive when the shell calls window.__carverShellDeliver. The message schemas are identical on both transports, an iframe always wins, and with neither a parent frame nor a bridge every call stays the same safe no-op. See the native bridge.

The boot lifecycle #

The shell shows a loader until your game signals it is ready. Drive it with two calls:

CallWhen
carver.progress(percent)As assets load. Clamped to 0–100. Drives the loader's progress readout.
carver.ready()The moment your first frame renders. The shell hides the loader and starts counting the play.

If your game never calls ready(), the shell falls back to a short grace window after the iframe loads and then reveals the game anyway — so a plain HTML game without the SDK still works. Calling ready() is strongly recommended: it makes the loader accurate and the play-count honest.

When something goes fatally wrong, call carver.error(code, message) — the shell swaps the game for an error card instead of leaving the player staring at a spinner.

What's next #