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

@energy8platform/game-sdk

v2.9.0

Published

Energy8 Platform Game SDK — postMessage-based bridge for HTML5 game integration

Readme

@energy8platform/game-sdk

PostMessage-based bridge SDK for integrating HTML5 games into the Energy8 Platform.

Games run inside an iframe; all backend communication goes through the host casino shell via window.postMessage. The game never has direct access to API endpoints or player tokens.

Running on Stake Engine? See @energy8platform/stake-bridge — a thick host wrapper that lets the same SDK-driven game run on Stake without changes.

Installation

npm install @energy8platform/game-sdk

Or via CDN (UMD build):

<script src="https://unpkg.com/@energy8platform/game-sdk/dist/casino-game-sdk.umd.js"></script>
<script>
  const sdk = new CasinoGameSDK();
</script>

Quick Start

import { CasinoGameSDK } from '@energy8platform/game-sdk';

const sdk = new CasinoGameSDK();

// 1. Initialize — tells the host the game is loaded
const { balance, currency, config, session } = await sdk.ready();

// 2. Spin
const result = await sdk.play({ action: 'spin', bet: 1.0 });
console.log(result.data.matrix, result.totalWin);

// run animations, update UI...

// 2a. Acknowledge that the game has processed the result
sdk.playAck(result);

// 3. Free spins (if a session was started)
if (result.session && result.nextActions.includes('free_spin')) {
  let done = false;
  while (!done) {
    const fs = await sdk.play({
      action: 'free_spin',
      bet: 0,
      roundId: result.roundId,
    });
    // render free spin animation ...
    done = fs.session?.completed ?? true;
  }
}

// 4. Buy bonus
const bonus = await sdk.play({ action: 'buy_bonus', bet: 50 });
// bonus.session is now active, bonus.nextActions tells you what to do next

// 5. Pick bonus
const pick = await sdk.play({
  action: 'pick',
  bet: 0,
  roundId: bonus.roundId,
  params: { choice: 2 },
});

API Reference

new CasinoGameSDK(options?)

| Option | Type | Default | Description | |--------|------|---------|-------------| | parentOrigin | string | auto (from document.referrer) | Expected origin of the host window. Set explicitly for stricter security. Use '*' only in development. | | timeout | number | 15000 | Default timeout (ms) for request-response calls. | | debug | boolean | false | Enable debug logging of all sent/received messages to the browser console. | | devMode | boolean | false | Use the in-memory channel instead of postMessage. The host (e.g. Bridge, StakeBridge) must also be created with devMode: true. |


sdk.ready(): Promise<InitData>

Signal to the host that the game is loaded. Must be called once before any other method.

Returns:

| Field | Type | Description | |-------|------|-------------| | balance | number | Player balance | | currency | string | Player currency ("USD", "EUR", etc.) | | config | GameConfigData | Full game configuration (symbols, paylines, bet levels, jurisdiction flags, …) | | session | SessionData \| null | Active session to resume (e.g. interrupted free spins), or null | | assetsUrl | string \| undefined | Base URL for game assets in S3 | | lang | string \| undefined | ISO 639-1 language code passed by the operator | | device | 'desktop' \| 'mobile' \| undefined | Device hint passed by the operator |


sdk.play(params): Promise<PlayResultData>

Execute any game action: spin, free spin, buy bonus, pick, etc. This is the single universal method for all game interactions.

Parameters (PlayParams):

| Field | Type | Description | |-------|------|-------------| | action | string | Action to execute: "spin", "free_spin", "buy_bonus", "pick", etc. | | bet | number | Total bet amount (use 0 for session-based actions like free spins) | | roundId | string \| undefined | (optional) Round ID for session-based actions | | params | Record<string, unknown> | (optional) Game-specific parameters: choice, lines, ante_bet, etc. |

Returns (PlayResultData):

| Field | Type | Description | |-------|------|-------------| | roundId | string | Unique game round ID | | action | string | Action that was executed | | balanceAfter | number | Player balance after the action | | totalWin | number | Total win amount | | currency | string | Player currency | | gameId | string | Game identifier | | data | Record<string, unknown> | Game-specific output (matrix, win_lines, multiplier, etc.) | | nextActions | string[] | Actions the client can invoke next (e.g. ["free_spin"], ["spin"]) | | session | SessionData \| null | Active session state, or null if no session / session completed | | creditPending | boolean \| undefined | true if win credit was deferred (server-side retry in progress) | | bonusFreeSpin | BonusFreeSpin \| null \| undefined | Bonus free spin grant info (grantId, remainingSpins), if applicable |

Errors: INSUFFICIENT_FUNDS, ACTIVE_SESSION_EXISTS, NO_ACTIVE_SESSION, SESSION_COMPLETED, TIMEOUT


sdk.playAck(result, id?): void

Signal to the host that the game has fully processed a PLAY_RESULT — animations finished, UI updated, player can interact again.

Call this after sdk.play() resolves and all client-side processing is complete.

const result = await sdk.play({ action: 'spin', bet: 1.0 });
await runWinAnimations(result);
sdk.playAck(result); // host now knows the client is ready for the next action

Other methods

  • sdk.getBalance(): Promise<BalanceData> — fetch current balance from the host.
  • sdk.getState(): Promise<PlayResultData | null> — query the active game session (e.g. after page reload).
  • sdk.openDeposit(): void — ask the host to open the deposit / cashier UI. Fire-and-forget.
  • sdk.destroy(): void — clean up listeners and reject pending requests.

Events

sdk.on('balanceUpdate', ({ balance }) => updateBalanceUI(balance));
sdk.on('error', (err) => console.error(err.code, err.message));
sdk.off('balanceUpdate', handler);

State Getters

| Getter | Type | Description | |--------|------|-------------| | sdk.balance | number | Current player balance | | sdk.currency | string | Player currency | | sdk.config | GameConfigData \| null | Game configuration from init | | sdk.session | SessionData \| null | Active session, or null | | sdk.isReplay | boolean | true when launched as a historical-round replay (sugar over config.replayMode) |

Replay mode

A game can be launched to replay a historical round (a previously-played round re-watched move-by-move) instead of placing real bets. The same sdk.play() / PLAY_RESULT flow is reused — only the data source and a config flag differ. No new protocol, no new SDK class.

On Stake Engine this is handled for you by @energy8platform/stake-bridge (?replay=true&game=…&version=…&mode=…&event=…). The recipe below is for the generic Energy8 host built on Bridge.

Game side (guest)

The host sets config.replayMode = true in INIT. The game reacts via the sdk.isReplay convenience getter:

const { config } = await sdk.ready();

if (sdk.isReplay) {
  hideBalanceUI();
  hideBetSelector();
  hideAutoplay();
  hideBuyBonus();
  showPlayAgainButton();        // the only CTA in replay
  renderDisclaimer(config.disclaimerLines);
}

// Spins work exactly as in live play — the host feeds recorded results back.
const result = await sdk.play({ action: 'spin', bet: config.betLevels?.[0] ?? 0 });

Behaviour is identical whether the game runs under Stake or the Energy8 host — it only reads one flag.

Host side (recipe — built on Bridge, nothing new)

The host is a thin proxy (PLAY_REQUEST → PLAY_RESULT). To replay a round it swaps the data source and flips the flag:

  1. Detect a replay launch from your own URL parameter, e.g. ?replay=1&roundId=….
  2. Fetch the recorded round once — the whole round as an ordered list of the same PlayResultPayload objects live play returns: GET /games/{gameId}/replay/{roundId} → { results: PlayResultPayload[] }.
  3. On GAME_READY send INIT with config.replayMode = true; take balance/currency from the recorded results; session: null (or restored).
  4. On PLAY_REQUEST serve results[cursor] and increment the cursor — do not call the wallet. The first spin after the list is exhausted resets cursor = 0 ("Play Again").
  5. On GET_STATE return a snapshot at the current cursor; on GET_BALANCE return the recorded ("historical") balance.
// inside the host's existing Bridge wiring, when launched as a replay
let cursor = 0;
const { results } = await fetchRecordedRound(gameId, roundId);

bridge.on('GAME_READY', (_p, id) => {
  bridge.send('INIT', {
    balance: results[0]?.balanceAfter ?? 0,
    currency: results[0]?.currency ?? 'USD',
    config: { id: gameId, type: 'slot', replayMode: true, /* … */ },
    session: null,
  }, id);
});

bridge.on('PLAY_REQUEST', (payload, id) => {
  if (payload.action === 'spin' && cursor >= results.length) cursor = 0; // Play Again
  const next = results[cursor++];
  if (!next) {
    bridge.send('PLAY_ERROR', { code: 'NO_ACTIVE_SESSION', message: 'Replay exhausted' }, id);
    return;
  }
  bridge.send('PLAY_RESULT', next, id);
});

Key Types

SessionData

Represents an active game session (free spins, bonus rounds, etc.):

| Field | Type | Description | |-------|------|-------------| | spinsRemaining | number | Remaining session actions | | spinsPlayed | number | Actions already played | | totalWin | number | Cumulative session win | | completed | boolean | Whether the session has finished | | maxWinReached | boolean | Whether the max win cap was hit | | betAmount | number \| undefined | Last bet amount | | history | Array<{ spinIndex, win, data }> | Round history within the session |

JurisdictionFlags (operator-driven)

Optional config.jurisdiction exposes operator-required toggles such as disabledTurbo, disabledAutoplay, displayRTP, minimumRoundDuration. Field is absent on platforms that don't supply jurisdictions.

const { config } = await sdk.ready();
if (config.jurisdiction?.disabledTurbo) hideTurboButton();
if (config.jurisdiction?.displayRTP) showRtpBadge();

Protocol Sub-export

For host-side integration or advanced use cases, import the raw protocol types:

import { isBridgeMessage, createMessage } from '@energy8platform/game-sdk/protocol';
import type {
  BridgeMessage,
  PlayResultPayload,
  PlayResultAckPayload,
} from '@energy8platform/game-sdk/protocol';

Error Types

| Class | Code | When | |-------|------|------| | SDKError | varies | Base error class. err.code contains the error code. | | TimeoutError | TIMEOUT | Host did not respond within the timeout period. | | BridgeNotReadyError | BRIDGE_NOT_READY | Method called before sdk.ready(). | | BridgeDestroyedError | BRIDGE_DESTROYED | Method called after sdk.destroy(). |

Host Integration (Bridge)

The SDK also exports a Bridge class for the host (casino shell) side. It handles postMessage communication with the game iframe.

import { Bridge } from '@energy8platform/game-sdk';
import type {
  PlayRequestPayload,
  PlayResultAckPayload,
  InitPayload,
} from '@energy8platform/game-sdk';

const iframe = document.getElementById('game') as HTMLIFrameElement;
const bridge = new Bridge({ iframe, targetOrigin: '*' });

bridge.on<undefined>('GAME_READY', (_payload, id) => {
  const initData: InitPayload = {
    balance: 1000,
    currency: 'USD',
    config: { id: 'my-slot', type: 'slot', betLevels: [1, 5, 10] },
    session: null,
  };
  bridge.send('INIT', initData, id);
});

bridge.on<PlayRequestPayload>('PLAY_REQUEST', async (payload, id) => {
  try {
    const result = await yourBackend.play(payload);
    bridge.send('PLAY_RESULT', result, id);
  } catch (err) {
    bridge.send('PLAY_ERROR', { code: 'INTERNAL', message: String(err) }, id);
  }
});

bridge.on('GET_BALANCE', (_payload, id) => {
  bridge.send('BALANCE_UPDATE', { balance: getCurrentBalance() }, id);
});

bridge.on('GET_STATE', (_payload, id) => {
  bridge.send('STATE_RESPONSE', { session: getLastPlayResult() }, id);
});

bridge.on('OPEN_DEPOSIT', () => openDepositModal());

bridge.on<PlayResultAckPayload>('PLAY_RESULT_ACK', (payload) => {
  console.log(`Game processed round ${payload.roundId}, win: ${payload.totalWin}`);
});

bridge.send('BALANCE_UPDATE', { balance: 1500 });

bridge.destroy();

new Bridge(options)

| Option | Type | Default | Description | |--------|------|---------|-------------| | iframe | HTMLIFrameElement | (required unless devMode) | The game iframe element | | targetOrigin | string | '*' | Target origin for postMessage. Set to the game's origin in production. | | debug | boolean | false | Enable debug logging of all sent/received messages to the browser console. | | devMode | boolean | false | Use the in-memory channel (same window, no iframe). |

Bridge Methods

| Method | Description | |--------|-------------| | bridge.on(type, handler) | Subscribe to a message type from the game | | bridge.off(type, handler) | Unsubscribe a handler | | bridge.send(type, payload, id?) | Send a message to the game | | bridge.destroy() | Remove all listeners and clean up |

Architecture

┌─────────────────────────────────────┐
│  Host (Casino Shell)                │
│                                     │
│  ┌───────────────────────────────┐  │
│  │  <iframe src="game.html">     │  │
│  │                               │  │
│  │  Game code + CasinoGameSDK    │  │
│  │         │                     │  │
│  │         │ postMessage         │  │
│  └─────────┼─────────────────────┘  │
│            │                        │
│    Bridge (host-side class)         │
│            │                        │
│    Backend API (play, wallet, …)    │
└─────────────────────────────────────┘

License

MIT