@carverjs/embed-sdk
v0.0.5
Published
Tiny SDK for games embedded in the CarverJS marketplace — typed postMessage to the parent shell without raw postMessage calls
Readme
@carverjs/embed-sdk
Talk to the CarverJS marketplace shell from inside your game. Dependency-free, ~1 KB.
Games on the marketplace run inside a sandboxed <iframe> on their own origin. The shell (the play page) listens for a small set of typed postMessage signals — this SDK sends them so you never hand-roll postMessage shapes, and it stays in lockstep with the shell's validator.
Beta: CarverJS is under active development. APIs may change between minor versions until 1.0.
Install
npm install @carverjs/embed-sdkUsage
import { carver } from "@carverjs/embed-sdk";
carver.progress(40); // loading bar in the shell (0–100)
carver.ready(); // hides the shell loader — call on first frame
carver.score(1280, "points"); // feeds player-profile stats
carver.event("level-complete", { level: 3 });
carver.requestFullscreen(); // call from a click / keypress handler
carver.error("asset-load-failed", "texture atlas 404");
carver.exit(); // game over; hand control back to the shell
// prove which signed-in player this is, to YOUR OWN backend
const id = await carver.getIdentity();
if (id.ok) saveToMyBackend(id.token, id.userId);
// future shell -> game hints (pause / resume, etc.)
const unsubscribe = carver.subscribe((msg) => {
if (msg.type === "carver:pause") {
/* pause your loop */
}
});Every call is a safe no-op outside an iframe (for example when you open your build locally) and in non-browser environments (SSR, tests) — nothing here ever throws, so you can leave the calls in during development.
API
| Method | When to call it |
| --- | --- |
| ready() | First frame rendered. Required — the shell loader waits for it. |
| progress(percent) | Loading progress, clamped to 0–100. |
| error(code, message) | Fatal error — the shell shows an error card. |
| event(name, payload?) | Gameplay events for stat aggregation. |
| score(value, label?) | Score reporting (finite numbers only). |
| requestFullscreen() | Ask the shell to go fullscreen (needs a user gesture). |
| exit() | Game finished. |
| getIdentity() | Promise — a signed token proving which signed-in player this is, for your own backend. See below. |
| subscribe(handler) | Shell to game messages; returns an unsubscribe function. |
| configure({ targetOrigin?, parentOrigin? }) | Optional origin pinning. |
| isEmbedded() | true when running inside the marketplace shell. |
Player identity (getIdentity)
If your game keeps player data on your own backend, getIdentity()
lets that backend know which marketplace user it's talking to — without
the game ever handling a marketplace credential.
const id = await carver.getIdentity();
if (id.ok) {
// id.token — short-lived signed JWT (RS256)
// id.userId — stable, opaque id for THIS player in THIS game
// id.expiresAt — Unix epoch (ms)
await fetch("https://my-game-backend.com/save", {
method: "POST",
headers: { Authorization: `Bearer ${id.token}` },
body: JSON.stringify({ progress: 12 }),
});
} else if (id.reason === "signin-required") {
// the player isn't signed in to the marketplace — prompt them
}It always resolves (never throws). On failure you get
{ ok: false, reason } where reason is one of "signin-required",
"not-embedded", "timeout", "rate-limited", or "error".
Verify the token on your server — never trust it in the browser. It's a standard RS256 JWT; verify it against the marketplace JWKS and check the claims:
// any backend / language with a JWT library — example uses `jose`
import { jwtVerify, createRemoteJWKSet } from "jose";
const JWKS = createRemoteJWKSet(
new URL("https://www.carverjs.dev/.well-known/jwks.json"),
);
const { payload } = await jwtVerify(token, JWKS, {
issuer: "https://www.carverjs.dev",
audience: "<your-game-id>", // also present as payload.gameId
});
// payload.sub === id.userId — your stable key for this playeruserId (the token's sub) is per-game: the same person gets a
different id in a different game, so it can't be used to track players
across the marketplace. It requires a real signed-in account —
anonymous play sessions resolve signin-required.
Security
Outbound messages are posted with targetOrigin: "*" by default. They intentionally carry no secrets — only ready / score / progress-style signals — and a sandboxed game cannot know which shell origin embeds it (production, staging, or a local preview). Inbound messages are only delivered when their source is the direct parent window. If your game ships to exactly one shell, pin it:
carver.configure({ targetOrigin: "https://carverjs.dev" });Links
- Documentation: docs.carverjs.dev
- Community: Discord
- Issues: github.com/MoneyTales/carverjs/issues
License
MIT — MoneyTales EduTech Private Limited
