Skip to content

Game lifecycle on filudi.com

A game on filudi.com is a static bundle in a sandboxed frame on a page of the hub. The SDK runs inside the frame; the hub runs outside. Everything between them is a message. This page says what happens, in order, and what it means for the code you write.

Every version is unpacked under games/<slug>/<version>/ and served through the CDN from https://<slug>.<play domain>/<slug>/<version>/index.html. One origin per game: your bundle shares nothing with another game, not localStorage, not a cookie, not a cache. The host must match the path; the API answers 404 for game A on the origin of game B. Until the play domain is switched on per game, the same path is served from https://play.filudi.com.

The frame is sandbox="allow-scripts allow-same-origin allow-pointer-lock" with allow="fullscreen; autoplay". The page keeps the top-level navigation, the forms, the popups.

  1. The hub opens the frame on the version’s URL.
  2. Your bundle calls sdk.init({ gameId }). The SDK posts filudi:ready to the parent and waits 1.5 s (handshakeTimeoutMs) for an answer.
  3. The hub answers filudi:init: the API URL, a game token, the slug of the game, the player, the locale, the site, the age policy, the consent state, the version string. The SDK accepts it only from window.parent.
  4. init() resolves. From here the SDK calls the API itself with the game token: saves, scores, events.
  5. Your game draws its first frame and calls sdk.ready(). The SDK posts filudi:loaded; the hub hides its loader and counts the first step of your funnel.

No answer within 1.5 s means the bundle is not in a Filudi frame: the SDK runs on the mock and result.host says so. That is what happens in the preview, which runs at the top level.

What the frame receives

// filudi:init, posted by the hub. The SDK reads it; the game never does.
({
type: 'filudi:init',
apiUrl: 'https://api.filudi.com',
token: 'fg1.…', // game token, opaque, 24 hours
gameId: 'gloop',
player: { id: '…', nickname: 'Guest-Fox', guest: true },
locale: 'fr',
site: 'filudi',
policy: { personalizedAds: false, ageBand: 'all', accounts: true },
consent: { ads: false, analytics: true },
gameVersion: '1.4.0',
});

The player signs in to filudi.com and has a token of their own. That token never enters your frame. The hub exchanges it for a game token: limited to your game, to that player, for 24 hours. It opens your saves, your boards and your events and nothing else. The hub sends a fresh one every 12 hours (filudi:token) and the SDK uses it from the next request, without a reload.

When the API refuses the token, the SDK posts filudi:unauthorized, keeps saves in localStorage and stops calling the API; the hub fetches a new token and reloads the frame. Your game sees a reload, nothing else.

The SDK posts to the hub: filudi:gameplay { running } on gameplay.start() and stop(), filudi:save { version } after a write, filudi:score { board, score, rank } after a submit, filudi:track { event } after an event, filudi:fullscreen { request: true } on sdk.fullscreen(), filudi:ad to request an ad. The hub treats each as a claim from an untrusted frame: it may update what the page shows for your game, and nothing else.

The hub posts to the SDK: filudi:consent, filudi:locale, filudi:pause, filudi:resume, filudi:token, and the answers to fullscreen and ad requests. They reach your game as the events of host events, i18n and ads.

The bundle route sends a Content Security Policy. Scripts, styles, images, media, fonts and workers load from the bundle’s own origin only, with blob: and data: allowed for assets and wasm-unsafe-eval for WebAssembly. fetch, XHR, WebSocket and EventSource reach the bundle’s origin and the API only. No frame inside the frame, no form post. An inline <script>, an onclick="" attribute, eval, new Function and setTimeout with a string throw. Inline styles work.

The policy, as sent

default-src 'self'; script-src 'self' 'wasm-unsafe-eval' blob:; style-src 'self' 'unsafe-inline';
img-src 'self' data: blob:; media-src 'self' data: blob:; font-src 'self' data:;
connect-src 'self' data: blob: https://api.filudi.com; worker-src 'self' blob:; frame-src 'none';
object-src 'none'; base-uri 'self'; form-action 'none'; frame-ancestors <site origins>

What this rules out: a CDN library, Google Fonts, an analytics script, an error reporter, a call to your own server. Put fonts, audio and data in the bundle. Report through sdk.track(). The portal scripts of CrazyGames, Poki and Playgama never run on filudi.com; the SDK loads them on their own portals.

  • Read the player’s own token, the page around it, or another game’s storage.
  • Navigate the page, open a popup, submit a form.
  • Load code or assets from another origin, or call one.
  • Submit a score outside a play session, or replay one: scores are signed with a key the session gives.
  • Send an event named frame_loaded: the hub sends that one.
  • Go live by itself: staff activates every version.

The frame takes the size the page gives it. Read innerWidth and innerHeight or a ResizeObserver, never screen.width, and redraw on resize. The orientation you declare on the game (portrait, landscape, any) tells the hub what to do on a phone held the other way: it shows a hint, your layout does not have to cope. mobile says whether the game is listed on phones at all; minutes is the length of a typical session, shown on the game’s page.

Audio starts after a gesture: the frame allows autoplay, browsers still block sound before a tap. A pointerdown handler that calls preventDefault() keeps the frame from taking keyboard focus; call canvas.focus() in it, with tabindex="0" on the canvas, or the arrow keys scroll the hub page.