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.

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.

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 #