Web Launcher SDK
Manifest
Every field of a Fluffer app manifest.json: engine, entry, orientation, capabilities, auth scopes, validation rules and examples.
Every Fluffer app ships a manifest.json at the root of its package. It tells Fluffer what kind of app this is, how to start it, and which capabilities it may use. Fluffer reads it before loading your app and enforces the capabilities it lists.
#Example
{
"manifestVersion": 1,
"minSdkVersion": "0.1.0",
"engine": "html5",
"entry": "index.html",
"orientation": "landscape",
"capabilities": ["device.vibrate", "device.rotate"]
}
#Fields
| Field | Type | Required | Description |
|---|---|---|---|
manifestVersion | 1 | yes | Manifest format version. Always 1. |
minSdkVersion | string | yes | The minimum SDK version your app expects, as major.minor.patch (e.g. "0.1.0"). |
engine | "html5", "threejs" or "unity" | yes | What your app is built with. |
entry | string | yes | The page that starts your app. In your package this is a path relative to the package root, usually "index.html". No spaces. |
orientation | "portrait", "landscape" or "auto" | yes | The screen orientation your app needs. "auto" follows the device. |
capabilities | string[] | yes | The capabilities your app may use. No duplicates. Use [] if you need none. |
auth | { "scopes": string[] } | only with account.identity | The account scopes your app may ask for. Required when capabilities includes account.identity, and not allowed otherwise. |
compat | null | no | Reserved for legacy Fluffer titles. Omit it or set it to null. |
There are no defaults: every required field must be present.
interface AppManifest {
manifestVersion: 1;
minSdkVersion: string;
engine: 'html5' | 'threejs' | 'unity';
entry: string;
orientation: 'portrait' | 'landscape' | 'auto';
capabilities: Capability[];
auth?: { scopes: AuthScope[] };
compat?: null;
}
#Capabilities
Declare only what you use. When your app runs, Fluffer grants the overlap of what your manifest declares, what your app requests at handshake (the capabilities option of createGame()), and what the player allows. Calls outside that set fail with PERMISSION_DENIED.
| Capability | What it unlocks |
|---|---|
device.vibrate | Motor control: setAllMotors, setMotorIntensity, stopVibration, executeRoutine, shake, and every game-kit haptic. Almost every app needs it. |
device.rotate | The rotation motor: setRotationSpeed, stopRotation, haptics.rotate() |
device.battery | The toy's battery level |
user.profile | user.get(): the player's display name, after they consent |
tips.fuel | Tip and fuel events and game.tips |
media.photos | Saving photos and video to the photo library (game.media) |
account.identity | Consent-first account identity and signed tokens (game.auth) |
Starting and closing your app, setting the orientation and asking which toy is connected are always allowed, so they don't need a capability.
See Bridge protocol for the exact methods and events each capability covers.
#Declaring a capability isn't consent
Declaring user.profile or account.identity only means your app may ask. Nothing about the player's account (id, name, picture or token) reaches your app until the player allows it from a sheet that Fluffer shows. Your app must keep working when they say no. See Accounts.
#auth.scopes
An app that declares account.identity must list, up front, every account scope it might ever ask for. Fluffer shows the player only the scopes being asked for at that moment, in these words:
| Scope | Shown to the player as "Your app wants to ..." | Notes |
|---|---|---|
profile.read | see your username and picture, and whether you are age-verified | Required. Every app asks for this first. |
content.react | like, follow and report as you | |
content.post | post and share under your account | Ask when the player takes that action, not at launch. |
moderation | (never shown) | Reserved for staff tools. Never prompted; granted only to accounts with a staff role. |
{
"manifestVersion": 1,
"minSdkVersion": "0.1.0",
"engine": "html5",
"entry": "index.html",
"orientation": "portrait",
"capabilities": ["device.vibrate", "account.identity"],
"auth": { "scopes": ["profile.read", "content.react", "content.post"] }
}
Asking for a scope at runtime that isn't in auth.scopes fails with INVALID_ARGUMENT.
#Validation rules
A manifest is rejected when:
manifestVersionisn't1;minSdkVersionisn't amajor.minor.patchstring;engineororientationisn't one of the allowed values;entryis empty or contains whitespace;capabilitiesisn't an array, or contains an unknown or duplicate token;capabilitiesincludesaccount.identitybutauthis missing, orauth.scopesis missing, empty, has an unknown or duplicate scope, or doesn't includeprofile.read;authis present withoutaccount.identity, or isn't an object of the form{ "scopes": [...] };compathas a value Fluffer doesn't recognise.
npm run package runs this check before it builds your zip. You can also run it yourself with validateManifest() from @fluffer/launcher-sdk, which never throws and returns every issue at once:
import { validateManifest, assertValidManifest } from '@fluffer/launcher-sdk';
import manifest from './manifest.json';
const { ok, issues } = validateManifest(manifest);
if (!ok) {
for (const i of issues) console.error(`${i.path || '<root>'}: ${i.message}`);
}
// Or throw with every issue listed:
assertValidManifest(manifest);
The SDK also exports CAPABILITIES (every valid capability token), AUTH_SCOPES (every valid scope), and the isCapability() and isAuthScope() helpers.