@falcongames/falcon-playable-ads-sdk
v1.3.0
Published
A comprehensive SDK for tracking events in playable ads
Maintainers
Readme
Falcon Playable Ads SDK
A TypeScript SDK for tracking events in playable advertisements.
Installation
npm install @falcongames/falcon-playable-ads-sdkQuick Start
import FalconPlayableSDK from "@falcongames/falcon-playable-ads-sdk";
// `initialize()` is async: await it so a dynamic playable's config is fetched before the game
// starts. For non-dynamic creatives it resolves immediately. (Calling without await still works.)
await FalconPlayableSDK.initialize();
// Lifecycle events
FalconPlayableSDK.onLoaded(); // Ad finished loading
FalconPlayableSDK.onFrame(); // First frame renderedinside ad
FalconPlayableSDK.onEndGame(); // Game/ad ended
FalconPlayableSDK.onCTA(); // User clicked CTA buttonIf your ESNext setup imports the SDK as a namespace, you can also call the top-level exports directly:
import * as FalconPlayableSDK from "@falcongames/falcon-playable-ads-sdk";
FalconPlayableSDK.initialize();
FalconPlayableSDK.onCTA();Default Event Flow
The SDK has a built-in event hierarchy:
LOADED → FRAME → INTERACTION → [custom events...]
↓
END_GAME → CTAEND_GAME and CTA are terminal nodes — they always fire after all custom events, regardless of how deep the hierarchy is.
Custom Events
1. Register a custom event node
Use registerPlayableEventNode to define and register a custom event. It is automatically added to the collection — no extra step needed.
import {
FalconPlayableSDK,
registerPlayableEventNode,
DefaultPlayableEventNodes,
} from "@falcongames/falcon-playable-ads-sdk";
// Default parent is INTERACTION
const LEVEL_START = registerPlayableEventNode("level_start");
// Or specify a custom parent
const LEVEL_COMPLETE = registerPlayableEventNode("level_complete", LEVEL_START);2. Fire a custom event
FalconPlayableSDK.logEvent(LEVEL_START);
FalconPlayableSDK.logEvent(LEVEL_COMPLETE);3. Define events via SDK (registers + tracks definition)
const levelStart = FalconPlayableSDK.defineEventNode({
key: "level_start",
parent: DefaultPlayableEventNodes.INTERACTION,
});
// Define multiple at once
FalconPlayableSDK.defineEventNodes([
{ key: "level_start" },
{ key: "level_complete", parent: levelStart },
]);4. Inspect the event hierarchy
const hierarchy = FalconPlayableSDK.getEventHierarchy();
// hierarchy.roots — the main event tree (LOADED → ... → custom events)
// hierarchy.terminalNode — END_GAME → CTA (detached)
console.log(JSON.stringify(hierarchy, null, 2));Example output:
{
"roots": [
{
"eventId": "loaded",
"parentEventId": null,
"isDefault": true,
"children": [
{
"eventId": "frame",
"parentEventId": "loaded",
"isDefault": true,
"children": [
{
"eventId": "interaction",
"parentEventId": "frame",
"isDefault": true,
"children": [
{
"eventId": "level_start",
"parentEventId": "interaction",
"isDefault": false,
"children": []
}
]
}
]
}
]
}
],
"terminalNode": {
"eventId": "end_game",
"parentEventId": null,
"isDefault": true,
"children": [
{
"eventId": "cta",
"parentEventId": "end_game",
"isDefault": true,
"children": []
}
]
}
}Circular parent detection
The SDK throws an error if a circular dependency is detected when registering a node:
const A = registerPlayableEventNode("a");
const B = registerPlayableEventNode("b", A);
// Error: Circular event definition detected: "a" is an ancestor of itself.
registerPlayableEventNode("a", B);API Reference
Lifecycle Methods
| Method | Description |
| -------------------- | --------------------------------------------------------------- |
| initialize(config) | Initialize SDK — must be called first |
| onLoaded() | Ad finished loading |
| onFrame() | First frame rendered |
| onInternalClick() | User clicked inside ad (fires pa:internal_click window event) |
| onEndGame() | Game/ad ended |
| onCTA() | User clicked CTA — routes to store automatically |
| isReady() | Returns true if SDK is initialized |
| configure(config) | Update configuration after initialization |
| reset() | Reset SDK state (for testing) |
Custom Event Methods
| Method | Description |
| -------------------------- | ----------------------------------------------- |
| logEvent(event) | Fire a custom event |
| defineEventNode(input) | Register a custom node definition |
| defineEventNodes(inputs) | Register multiple node definitions |
| getEventDefinitions() | Get all registered event definitions |
| getEventHierarchy() | Get full event tree as PlayableEventHierarchy |
Utility Methods
| Method | Description |
| ----------------------------------- | ------------------------------------- |
| dispatchGainScoreEvent(score) | Dispatch pa:gain_score window event |
| dispatchLoseScoreEvent(score) | Dispatch pa:lose_score window event |
| dispatchWinEvent() | Dispatch pa:win_game window event |
| dispatchLoseEvent() | Dispatch pa:lose_game window event |
| dispatchCustomEvent(type, detail) | Dispatch any custom window event |
| getVersion() | Get SDK version string |
| getAdNetworkInfo() | Get PlayableAdsType enum value |
| isPlaySound | false for Adwords, Facebook, IronSource; otherwise true |
registerPlayableEventNode(key, parent?)
import {
registerPlayableEventNode,
DefaultPlayableEventNodes,
} from "@falcongames/falcon-playable-ads-sdk";
// parent defaults to DefaultPlayableEventNodes.INTERACTION
const MY_EVENT = registerPlayableEventNode("my_event");
// Custom parent
const MY_CHILD = registerPlayableEventNode("my_child", MY_EVENT);DefaultPlayableEventNodes
import { DefaultPlayableEventNodes } from "@falcongames/falcon-playable-ads-sdk";
DefaultPlayableEventNodes.LOADED;
DefaultPlayableEventNodes.FRAME;
DefaultPlayableEventNodes.INTERACTION;
DefaultPlayableEventNodes.END_GAME;
DefaultPlayableEventNodes.CTA;Extending with Custom HTTP Messages
import { CSHttpMessage } from "@falcongames/falcon-playable-ads-sdk";
class MyCustomMessage extends CSHttpMessage {
static event = "my_custom_event";
playerId: string = "";
score: number = 0;
constructor(playerId: string, score: number) {
super();
this.playerId = playerId;
this.score = score;
}
}
new MyCustomMessage("player-123", 1000).send();See src/examples/ for more examples.
Dynamic Playables
A dynamic playable is a single creative that holds several config variants; at run time the backend serves one variant by round-robin. Old (non-dynamic) creatives are unaffected — the feature only activates when the build marks the creative as dynamic.
Build signal
The build must mark the creative by replacing the {{IS_DYNAMIC}} placeholder with true (it stays
false/unreplaced for normal creatives). creativeId and baseURL must also be set (they already
are for event tracking), so the SDK can derive the serve URL:
{baseURL}/playable/creatives/{creativeId}/serveOptionally inject a full URL via {{DYNAMIC_CONFIG_URL}} to override that derivation.
FalconPlayableSDK.initialize({
baseURL: "{{BASE_URL}}",
creativeId: "{{CREATIVE_ID}}",
isDynamic: true, // build replaces {{IS_DYNAMIC}} → true for dynamic creatives
});Fetching the variant
When dynamic, initialize() automatically fetches the config (fire-and-forget) and dispatches a
falcon:dynamic-config window event. For strict ordering, await it before starting the game:
import FalconPlayableSDK, { DynamicVariant } from "@falcongames/falcon-playable-ads-sdk";
FalconPlayableSDK.initialize();
const variant: DynamicVariant | null =
await FalconPlayableSDK.fetchDynamicConfig(); // null for non-dynamic creatives
if (variant) {
// variant.values = { fieldKey: { type, value } } — the overridden fields only.
applyOverridesOntoBase(variant.values); // your playable applies the delta onto its base config
}
// Or react to the event instead of awaiting:
window.addEventListener("falcon:dynamic-config", (e) => {
applyOverridesOntoBase((e as CustomEvent<DynamicVariant>).detail.values);
});fetchDynamicConfig() returns null (and does nothing) for non-dynamic creatives, so it is safe to
call unconditionally. The fetched variant is also exposed on window.__FALCON_DYNAMIC_CONFIG__.
The SDK only fetches the variant; applying
valuesonto the base config is the playable's responsibility (it knows its own config shape).
Development
npm install # Install dependencies
npm run build # Compile TypeScript
npm run watch # Watch modeLicense
ISC
