Skip to content

Iframe Events

Games notify the embedding Operator page about player actions through window.parent.postMessage(). The Operator decides how to handle each supported event. For a close request, it can remove the game iframe and redirect the player to its own lobby or another destination.

This browser channel is independent of the Game Launch API and Wallet API. Receiving an event does not require a new backend endpoint or a frontend SDK.

Supported Games

Game Event gameId Current integration
AviaDrone avia-drone Disconnected dialog Leave action
Classic Mines classic-mines Disconnected dialog Leave action
Plinko plinko Connection-status banner Leave action
Chicken Race chicken-race Shared channel is available; there is currently no disconnected-dialog Leave action emitting this event.

These identifiers belong to the browser event contract. They are not numeric catalog IDs or the {game} parameter of the launch endpoint. The game deployment must include this integration; older frontend builds do not emit these events.

Message Contract

The game sends a structured JavaScript object, not a JSON string. Read it from MessageEvent.data; do not call JSON.parse() on it.

{
  "source": "flexsoft.game",
  "version": 1,
  "gameId": "avia-drone",
  "type": "game.closeRequested",
  "payload": {
    "reason": "disconnected"
  }
}
Field Type Meaning
source string Contract namespace. Always flexsoft.game. This value alone does not authenticate the sender.
version integer Browser event contract version. Currently 1; independent of the HTTP API version.
gameId string Frontend game identifier from the table above.
type string Event name. Currently game.closeRequested.
payload object Event-specific public action metadata.

game.closeRequested

Direction: Game iframe → immediate parent page.

Sent when the player clicks Leave in the disconnected interface. The event asks the Operator to close the game; it does not supply a redirect URL.

payload.reason Status Meaning
disconnected Emitted now The player selected Leave after losing the connection.
user_exit Reserved for future exit actions Supported by the shared schema, but not emitted by the current disconnected Leave handlers.

Reconnect, automatic connection loss or recovery, page load, and component unmount do not emit this close request.

The event itself does not cancel or refund a bet, cash out a round, or confirm backend session termination. Continue processing wallet callbacks according to the existing Wallet API contract when the game UI closes.

Operator Implementation

The receiver must check all of the following before acting:

  • event.origin matches the trusted game origin configured for the environment.
  • event.source is the contentWindow of the specific game iframe.
  • The message namespace, version, game ID, event type and payload are supported.

Use the origin of the game frontend, including its port when applicable. Do not use the Launch API origin unless it is also the game frontend origin. Configure the trusted origin from your integration settings rather than from message data.

Validate the Message

This validator is also used in the embedding and wrapper examples below. Unknown events and unsupported versions are ignored.

function isCloseRequest(message, expectedGameId) {
  if (!message || typeof message !== 'object' || Array.isArray(message)) {
    return false;
  }

  const payload = message.payload;
  return (
    message.source === 'flexsoft.game' &&
    message.version === 1 &&
    message.gameId === expectedGameId &&
    message.type === 'game.closeRequested' &&
    payload !== null &&
    typeof payload === 'object' &&
    !Array.isArray(payload) &&
    (payload.reason === 'disconnected' || payload.reason === 'user_exit')
  );
}

Embed and Handle Leave

Replace the example origin and destination with your environment's values. Pass the launch_url returned by your backend to embedGame(). Install the listener before assigning the iframe URL and mounting it.

function embedGame(container, launchUrl) {
  const gameOrigin = 'https://games.example.com';
  const expectedGameId = 'avia-drone';
  const gameFrame = document.createElement('iframe');
  gameFrame.title = 'AviaDrone';

  if (new URL(launchUrl).origin !== gameOrigin) {
    throw new Error('Unexpected game frontend origin');
  }

  function stopListening() {
    window.removeEventListener('message', onGameMessage);
  }

  function onGameMessage(event) {
    if (event.origin !== gameOrigin) return;
    if (event.source !== gameFrame.contentWindow) return;
    if (!isCloseRequest(event.data, expectedGameId)) return;

    stopListening();
    gameFrame.remove();
    // The Operator owns the destination; no URL is taken from the event.
    window.location.assign('/casino');
  }

  window.addEventListener('message', onGameMessage);
  gameFrame.src = launchUrl;
  container.append(gameFrame);

  // Call when navigating away or disposing the embedding view in an SPA.
  return () => {
    stopListening();
    gameFrame.remove();
  };
}

Keep one listener per mounted game view and remove it when the view is disposed. Stopping the listener before closing the iframe also prevents a second queued close request from triggering duplicate navigation in this example.

Nested Iframes

Messages target the game's immediate parent. They do not bubble through the iframe hierarchy or automatically reach window.top.

If the Operator embeds a provider or aggregator wrapper that embeds the game, the wrapper must validate the game message and relay it to the Operator. The Operator then validates the wrapper's origin and window reference. The wrapper's validation is necessary because the outer page cannot authenticate the original game using the relayed event's origin or source.

For a wrapper that has its own gameFrame, use the validator above:

const gameOrigin = 'https://games.example.com';
const operatorOrigin = 'https://casino.example.com';
const expectedGameId = 'avia-drone';

function relayGameMessage(event) {
  if (event.origin !== gameOrigin) return;
  if (event.source !== gameFrame.contentWindow) return;
  if (!isCloseRequest(event.data, expectedGameId)) return;
  window.parent.postMessage(event.data, operatorOrigin);
}

window.addEventListener('message', relayGameMessage);
// Remove this listener when the wrapper is disposed:
// window.removeEventListener('message', relayGameMessage);

Delivery and Origin Handling

  • The game attempts one send per Leave click. There is no acknowledgement, retry, replay, or delivery confirmation. Do not assume exactly-once delivery.
  • The game does not wait for a host response. AviaDrone retains its local dialog dismissal; Mines and Plinko retain the disconnected interface until recovery or host closure. The host owns iframe removal and redirection.
  • If the game runs as a standalone page, there is no parent notification. If the parent has no listener, the event causes no host-side action.
  • An explicitly configured HTTP(S) target origin takes priority. Otherwise the game uses the nearest location.ancestorOrigins origin, then the HTTP(S) document.referrer origin. An invalid explicit origin prevents sending.
  • When neither browser source reveals the parent origin, the game sends public action metadata to its immediate parent with target origin *. The receiver must still validate the sender's exact origin and iframe reference.
  • Tokens, launch URLs, balances, player identifiers, seeds and bet details are not part of this channel. The source and gameId fields identify the contract; they are not authentication credentials.
  • An opaque origin such as null must not be added as a trusted game origin. Review sandbox settings with Game Provider if they make the game origin opaque.

Browser reference: Window.postMessage.

Compatibility and Verification

New event types may be added to the versioned envelope. Receivers should ignore unrecognized types and unsupported versions. Breaking changes to existing message meanings require a new contract version and integration coordination. There are currently no inbound commands or other published events in this channel.

Before enabling host closure, verify the following with the deployed game build:

  1. Leave sends game.closeRequested with the expected gameId and payload.reason: "disconnected" on mobile and desktop.
  2. Reconnect, automatic disconnect and page load do not trigger host closure.
  3. Messages from another origin, another iframe, or an unsupported schema are ignored.
  4. The Operator closes the correct iframe and uses its own redirect destination.
  5. A nested wrapper validates and relays the event; a host without referrer information still receives the close request.
  6. Disposing and reopening the view does not leave duplicate message listeners.