npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@falcongames/falcon-playable-ads-sdk

v1.3.0

Published

A comprehensive SDK for tracking events in playable ads

Readme

Falcon Playable Ads SDK

A TypeScript SDK for tracking events in playable advertisements.

Installation

npm install @falcongames/falcon-playable-ads-sdk

Quick 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 button

If 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 → CTA

END_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}/serve

Optionally 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 values onto 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 mode

License

ISC