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.originmatches the trusted game origin configured for the environment.event.sourceis thecontentWindowof 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.ancestorOriginsorigin, then the HTTP(S)document.referrerorigin. 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
sourceandgameIdfields identify the contract; they are not authentication credentials. - An opaque origin such as
nullmust 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:
- Leave sends
game.closeRequestedwith the expectedgameIdandpayload.reason: "disconnected"on mobile and desktop. - Reconnect, automatic disconnect and page load do not trigger host closure.
- Messages from another origin, another iframe, or an unsupported schema are ignored.
- The Operator closes the correct iframe and uses its own redirect destination.
- A nested wrapper validates and relays the event; a host without referrer information still receives the close request.
- Disposing and reopening the view does not leave duplicate message listeners.