@energy8platform/platform-core 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +482 -0
  2. package/bin/simulate.ts +139 -0
  3. package/dist/dev-bridge.cjs.js +237 -0
  4. package/dist/dev-bridge.cjs.js.map +1 -0
  5. package/dist/dev-bridge.d.ts +141 -0
  6. package/dist/dev-bridge.esm.js +235 -0
  7. package/dist/dev-bridge.esm.js.map +1 -0
  8. package/dist/index.cjs.js +569 -0
  9. package/dist/index.cjs.js.map +1 -0
  10. package/dist/index.d.ts +439 -0
  11. package/dist/index.esm.js +560 -0
  12. package/dist/index.esm.js.map +1 -0
  13. package/dist/loading.cjs.js +190 -0
  14. package/dist/loading.cjs.js.map +1 -0
  15. package/dist/loading.d.ts +86 -0
  16. package/dist/loading.esm.js +185 -0
  17. package/dist/loading.esm.js.map +1 -0
  18. package/dist/lua.cjs.js +1129 -0
  19. package/dist/lua.cjs.js.map +1 -0
  20. package/dist/lua.d.ts +319 -0
  21. package/dist/lua.esm.js +1119 -0
  22. package/dist/lua.esm.js.map +1 -0
  23. package/dist/simulation.cjs.js +374 -0
  24. package/dist/simulation.cjs.js.map +1 -0
  25. package/dist/simulation.d.ts +190 -0
  26. package/dist/simulation.esm.js +368 -0
  27. package/dist/simulation.esm.js.map +1 -0
  28. package/dist/vite.cjs.js +179 -0
  29. package/dist/vite.cjs.js.map +1 -0
  30. package/dist/vite.d.ts +13 -0
  31. package/dist/vite.esm.js +176 -0
  32. package/dist/vite.esm.js.map +1 -0
  33. package/package.json +100 -0
  34. package/scripts/install-simulate.mjs +101 -0
  35. package/src/EventEmitter.ts +55 -0
  36. package/src/PlatformSession.ts +156 -0
  37. package/src/dev-bridge/DevBridge.ts +305 -0
  38. package/src/dev-bridge/index.ts +2 -0
  39. package/src/index.ts +98 -0
  40. package/src/loading/CSSPreloader.ts +129 -0
  41. package/src/loading/index.ts +3 -0
  42. package/src/loading/logo.ts +95 -0
  43. package/src/lua/ActionRouter.ts +132 -0
  44. package/src/lua/LuaEngine.ts +412 -0
  45. package/src/lua/LuaEngineAPI.ts +314 -0
  46. package/src/lua/PersistentState.ts +80 -0
  47. package/src/lua/SessionManager.ts +227 -0
  48. package/src/lua/SimulationRunner.ts +192 -0
  49. package/src/lua/fengari.d.ts +10 -0
  50. package/src/lua/index.ts +28 -0
  51. package/src/lua/types.ts +149 -0
  52. package/src/simulation/NativeSimulationRunner.ts +367 -0
  53. package/src/simulation/ParallelSimulationRunner.ts +156 -0
  54. package/src/simulation/SimulationWorker.ts +44 -0
  55. package/src/simulation/index.ts +21 -0
  56. package/src/types.ts +85 -0
  57. package/src/vite/index.ts +196 -0
@@ -0,0 +1,176 @@
1
+ // ─── DevBridge Plugin ────────────────────────────────────
2
+ /**
3
+ * Vite plugin that auto-injects the DevBridge mock-host bootstrapper
4
+ * into the HTML during development, so the game can communicate with
5
+ * a mock casino host without manual setup.
6
+ *
7
+ * Pair with `luaPlugin` to also enable `.lua` raw imports and serve a
8
+ * Lua execution endpoint at POST /__lua-play.
9
+ */
10
+ const VIRTUAL_ID = '/@dev-bridge-entry.js';
11
+ function devBridgePlugin(configPath) {
12
+ let entrySrc = '';
13
+ let viteRoot = '';
14
+ let resolvedConfigPath = configPath;
15
+ return {
16
+ name: 'platform-core:dev-bridge',
17
+ apply: 'serve', // dev only
18
+ enforce: 'pre',
19
+ configResolved(config) {
20
+ viteRoot = config.root;
21
+ // Resolve relative config path against Vite root so the virtual
22
+ // module can import it with an absolute path.
23
+ if (configPath.startsWith('.')) {
24
+ resolvedConfigPath = config.root + '/' + configPath.replace(/^\.\//, '');
25
+ }
26
+ },
27
+ resolveId(id) {
28
+ if (id === VIRTUAL_ID)
29
+ return id;
30
+ },
31
+ load(id) {
32
+ if (id === VIRTUAL_ID) {
33
+ // This goes through Vite's pipeline so bare imports are resolved
34
+ return `
35
+ import { DevBridge } from '@energy8platform/platform-core/dev-bridge';
36
+
37
+ try {
38
+ const mod = await import('${resolvedConfigPath}');
39
+ const config = mod.default ?? mod.config ?? mod;
40
+ new DevBridge(config).start();
41
+ } catch (e) {
42
+ console.warn('[DevBridge] Failed to load config:', e);
43
+ }
44
+
45
+ await import('${entrySrc}');
46
+ `;
47
+ }
48
+ },
49
+ transformIndexHtml(html) {
50
+ // Find the app's entry module script (skip Vite internal /@... scripts)
51
+ const scriptRegex = /<script\s+type="module"\s+src="((?!\/@)[^"]+)"\s*>\s*<\/script>/;
52
+ const match = html.match(scriptRegex);
53
+ if (!match) {
54
+ console.warn('[DevBridge] Could not find entry module script in index.html');
55
+ return html;
56
+ }
57
+ entrySrc = match[1];
58
+ if (entrySrc.startsWith('.')) {
59
+ entrySrc = viteRoot + '/' + entrySrc.replace(/^\.\//, '');
60
+ }
61
+ else if (entrySrc.startsWith('/')) {
62
+ entrySrc = viteRoot + entrySrc;
63
+ }
64
+ return html.replace(match[0], `<script type="module" src="${VIRTUAL_ID}"></script>`);
65
+ },
66
+ };
67
+ }
68
+ // ─── Lua Plugin ─────────────────────────────────────────
69
+ /**
70
+ * Vite plugin that:
71
+ * 1. Enables importing `.lua` files as raw strings with HMR
72
+ * 2. Runs a LuaEngine on the Vite dev server (Node.js) via POST /__lua-play
73
+ *
74
+ * fengari runs server-side only — no browser shims needed.
75
+ */
76
+ function luaPlugin(configPath) {
77
+ let luaEngine = null;
78
+ let viteServer = null;
79
+ async function initEngine() {
80
+ if (!viteServer)
81
+ return;
82
+ try {
83
+ // Invalidate cached modules so HMR picks up changes
84
+ const root = viteServer.config.root;
85
+ const fullConfigPath = configPath.startsWith('.')
86
+ ? root + '/' + configPath.replace(/^\.\//, '')
87
+ : configPath;
88
+ // Invalidate the config module and its dependencies
89
+ const configMod = viteServer.moduleGraph.getModuleById(fullConfigPath);
90
+ if (configMod)
91
+ viteServer.moduleGraph.invalidateModule(configMod);
92
+ // ssrLoadModule handles TS transpilation and resolves all imports
93
+ const mod = await viteServer.ssrLoadModule(fullConfigPath);
94
+ const config = mod.default ?? mod.config ?? mod;
95
+ if (!config.luaScript || !config.gameDefinition) {
96
+ console.log('[LuaPlugin] No luaScript/gameDefinition in config — Lua server disabled');
97
+ luaEngine = null;
98
+ return;
99
+ }
100
+ // Load LuaEngine via SSR (fengari runs natively in Node.js)
101
+ const luaMod = await viteServer.ssrLoadModule('@energy8platform/platform-core/lua');
102
+ const { LuaEngine } = luaMod;
103
+ if (luaEngine)
104
+ luaEngine.destroy();
105
+ luaEngine = new LuaEngine({
106
+ script: config.luaScript,
107
+ gameDefinition: config.gameDefinition,
108
+ seed: config.luaSeed,
109
+ });
110
+ console.log('[LuaPlugin] LuaEngine initialized (server-side)');
111
+ }
112
+ catch (e) {
113
+ console.warn('[LuaPlugin] Failed to initialize LuaEngine:', e.message);
114
+ luaEngine = null;
115
+ }
116
+ }
117
+ return {
118
+ name: 'platform-core:lua',
119
+ apply: 'serve',
120
+ async configureServer(server) {
121
+ viteServer = server;
122
+ await initEngine();
123
+ // POST /__lua-play — execute Lua on the server
124
+ server.middlewares.use('/__lua-play', (req, res) => {
125
+ if (req.method !== 'POST') {
126
+ res.statusCode = 405;
127
+ res.end('Method Not Allowed');
128
+ return;
129
+ }
130
+ let body = '';
131
+ req.on('data', (chunk) => { body += chunk; });
132
+ req.on('end', () => {
133
+ try {
134
+ if (!luaEngine) {
135
+ res.statusCode = 503;
136
+ res.setHeader('Content-Type', 'application/json');
137
+ res.end(JSON.stringify({ error: 'LuaEngine not initialized' }));
138
+ return;
139
+ }
140
+ const params = JSON.parse(body);
141
+ const result = luaEngine.execute(params);
142
+ res.statusCode = 200;
143
+ res.setHeader('Content-Type', 'application/json');
144
+ res.end(JSON.stringify(result));
145
+ }
146
+ catch (e) {
147
+ res.statusCode = 500;
148
+ res.setHeader('Content-Type', 'application/json');
149
+ res.end(JSON.stringify({ error: e.message }));
150
+ }
151
+ });
152
+ });
153
+ },
154
+ transform(code, id) {
155
+ if (id.endsWith('.lua')) {
156
+ return {
157
+ code: `export default ${JSON.stringify(code)};`,
158
+ map: null,
159
+ };
160
+ }
161
+ },
162
+ async handleHotUpdate({ file, server }) {
163
+ if (file.endsWith('.lua') || file.includes('dev.config')) {
164
+ console.log('[LuaPlugin] Reloading LuaEngine...');
165
+ // Invalidate all SSR modules so ssrLoadModule picks up fresh code
166
+ server.moduleGraph.invalidateAll();
167
+ await initEngine();
168
+ server.ws.send({ type: 'full-reload' });
169
+ return [];
170
+ }
171
+ },
172
+ };
173
+ }
174
+
175
+ export { devBridgePlugin, luaPlugin };
176
+ //# sourceMappingURL=vite.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite.esm.js","sources":["../src/vite/index.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,100 @@
1
+ {
2
+ "name": "@energy8platform/platform-core",
3
+ "version": "0.16.0",
4
+ "description": "Energy8 platform core: Lua engine, DevBridge, RTP simulation, and SDK session orchestration. Renderer-agnostic — pair with any game framework (Pixi, Phaser, Three.js, custom).",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs.js",
7
+ "module": "./dist/index.esm.js",
8
+ "types": "./dist/index.d.ts",
9
+ "bin": {
10
+ "platform-core-simulate": "./bin/simulate.ts"
11
+ },
12
+ "exports": {
13
+ ".": {
14
+ "import": "./dist/index.esm.js",
15
+ "require": "./dist/index.cjs.js",
16
+ "types": "./dist/index.d.ts"
17
+ },
18
+ "./lua": {
19
+ "import": "./dist/lua.esm.js",
20
+ "require": "./dist/lua.cjs.js",
21
+ "types": "./dist/lua.d.ts"
22
+ },
23
+ "./simulation": {
24
+ "import": "./dist/simulation.esm.js",
25
+ "require": "./dist/simulation.cjs.js",
26
+ "types": "./dist/simulation.d.ts"
27
+ },
28
+ "./dev-bridge": {
29
+ "import": "./dist/dev-bridge.esm.js",
30
+ "require": "./dist/dev-bridge.cjs.js",
31
+ "types": "./dist/dev-bridge.d.ts"
32
+ },
33
+ "./vite": {
34
+ "import": "./dist/vite.esm.js",
35
+ "require": "./dist/vite.cjs.js",
36
+ "types": "./dist/vite.d.ts"
37
+ },
38
+ "./loading": {
39
+ "import": "./dist/loading.esm.js",
40
+ "require": "./dist/loading.cjs.js",
41
+ "types": "./dist/loading.d.ts"
42
+ }
43
+ },
44
+ "files": [
45
+ "dist",
46
+ "src",
47
+ "bin/simulate.ts",
48
+ "scripts"
49
+ ],
50
+ "scripts": {
51
+ "simulate": "tsx bin/simulate.ts",
52
+ "build": "rollup -c rollup.config.mjs",
53
+ "dev": "rollup -c rollup.config.mjs -w",
54
+ "lint": "eslint src/ --ext .ts",
55
+ "typecheck": "tsc --noEmit",
56
+ "test": "vitest run",
57
+ "test:watch": "vitest",
58
+ "postinstall": "node scripts/install-simulate.mjs",
59
+ "prepublishOnly": "npm run build"
60
+ },
61
+ "peerDependencies": {
62
+ "@energy8platform/game-sdk": "^2.7.0",
63
+ "fengari": "^0.1.4",
64
+ "vite": "^5.0.0 || ^6.0.0"
65
+ },
66
+ "peerDependenciesMeta": {
67
+ "vite": {
68
+ "optional": true
69
+ }
70
+ },
71
+ "devDependencies": {
72
+ "@energy8platform/game-sdk": "^2.7.0",
73
+ "@rollup/plugin-typescript": "^12.1.0",
74
+ "@types/node": "^25.6.0",
75
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
76
+ "@typescript-eslint/parser": "^8.0.0",
77
+ "eslint": "^9.0.0",
78
+ "fengari": "^0.1.5",
79
+ "fengari-web": "^0.1.4",
80
+ "rollup": "^4.24.0",
81
+ "rollup-plugin-dts": "^6.1.0",
82
+ "tslib": "^2.8.0",
83
+ "tsx": "^4.21.0",
84
+ "typescript": "^5.6.0",
85
+ "vitest": "^2.0.0"
86
+ },
87
+ "keywords": [
88
+ "casino",
89
+ "energy8",
90
+ "lua",
91
+ "rtp-simulation",
92
+ "igaming"
93
+ ],
94
+ "license": "MIT",
95
+ "repository": {
96
+ "type": "git",
97
+ "url": "https://github.com/energy8platform/game-engine.git",
98
+ "directory": "packages/platform-core"
99
+ }
100
+ }
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Postinstall script: downloads the native simulation binary for the current
5
+ * platform from GitHub Releases. Falls back silently — the JS simulation
6
+ * will be used if the binary is unavailable.
7
+ */
8
+
9
+ import { createWriteStream, chmodSync, existsSync, mkdirSync } from 'fs';
10
+ import { join, dirname } from 'path';
11
+ import { fileURLToPath } from 'url';
12
+ import { get } from 'https';
13
+
14
+ const __dirname = dirname(fileURLToPath(import.meta.url));
15
+ const BIN_DIR = join(__dirname, '..', 'bin');
16
+
17
+ // ─── Config ─────────────────────────────────────────────
18
+
19
+ const REPO = 'energy8platform/game-engine';
20
+ // Binary version — update this when new Go binaries are built and uploaded to GitHub Releases.
21
+ // The binary is backwards-compatible: it runs any Lua script, so it doesn't need to match
22
+ // the engine version exactly. Only bump when the Go simulate CLI itself changes.
23
+ const BINARY_VERSION = '0.13.0';
24
+
25
+ const PLATFORM_MAP = {
26
+ 'darwin-arm64': 'simulate-darwin-arm64',
27
+ 'darwin-x64': 'simulate-darwin-amd64',
28
+ 'linux-x64': 'simulate-linux-amd64',
29
+ 'linux-arm64': 'simulate-linux-arm64',
30
+ 'win32-x64': 'simulate-windows-amd64.exe',
31
+ };
32
+
33
+ // ─── Main ───────────────────────────────────────────────
34
+
35
+ async function main() {
36
+ const key = `${process.platform}-${process.arch}`;
37
+ const binaryName = PLATFORM_MAP[key];
38
+
39
+ if (!binaryName) {
40
+ console.log(`[simulate] No native binary available for ${key}, will use JS simulation.`);
41
+ return;
42
+ }
43
+
44
+ const dest = join(BIN_DIR, binaryName);
45
+
46
+ // Skip if already downloaded
47
+ if (existsSync(dest)) {
48
+ return;
49
+ }
50
+
51
+ const tag = `v${BINARY_VERSION}`;
52
+
53
+ const url = `https://github.com/${REPO}/releases/download/${tag}/${binaryName}`;
54
+
55
+ console.log(`[simulate] Downloading native binary for ${key}...`);
56
+
57
+ try {
58
+ if (!existsSync(BIN_DIR)) {
59
+ mkdirSync(BIN_DIR, { recursive: true });
60
+ }
61
+ await download(url, dest);
62
+ chmodSync(dest, 0o755);
63
+ console.log(`[simulate] Installed ${binaryName}`);
64
+ } catch (err) {
65
+ // Non-fatal — JS simulation is the fallback
66
+ console.log(`[simulate] Could not download native binary: ${err.message}`);
67
+ console.log(`[simulate] Will use JS simulation instead.`);
68
+ }
69
+ }
70
+
71
+ // ─── Download with redirect following ───────────────────
72
+
73
+ function download(url, dest, redirects = 5) {
74
+ return new Promise((resolve, reject) => {
75
+ if (redirects <= 0) {
76
+ return reject(new Error('Too many redirects'));
77
+ }
78
+
79
+ get(url, { headers: { 'User-Agent': 'game-engine-postinstall' } }, (res) => {
80
+ // Follow redirects (GitHub sends 302 to S3)
81
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
82
+ res.resume();
83
+ return download(res.headers.location, dest, redirects - 1).then(resolve, reject);
84
+ }
85
+
86
+ if (res.statusCode !== 200) {
87
+ res.resume();
88
+ return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
89
+ }
90
+
91
+ const file = createWriteStream(dest);
92
+ res.pipe(file);
93
+ file.on('finish', () => file.close(resolve));
94
+ file.on('error', reject);
95
+ }).on('error', reject);
96
+ });
97
+ }
98
+
99
+ main().catch(() => {
100
+ // Never fail the install
101
+ });
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Minimal typed event emitter — internal utility for platform-core.
3
+ *
4
+ * Supports `void` event types — events that carry no data can be emitted
5
+ * without arguments: `emitter.emit('eventName')`.
6
+ *
7
+ * Mirrors the EventEmitter shipped from game-engine's core, copied here
8
+ * so platform-core has no upward dependency on game-engine.
9
+ */
10
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
11
+ export class EventEmitter<TEvents extends {}> {
12
+ private listeners = new Map<keyof TEvents, Set<(data: any) => void>>();
13
+
14
+ on<K extends keyof TEvents>(event: K, handler: (data: TEvents[K]) => void): this {
15
+ if (!this.listeners.has(event)) {
16
+ this.listeners.set(event, new Set());
17
+ }
18
+ this.listeners.get(event)!.add(handler);
19
+ return this;
20
+ }
21
+
22
+ once<K extends keyof TEvents>(event: K, handler: (data: TEvents[K]) => void): this {
23
+ const wrapper = (data: TEvents[K]) => {
24
+ this.off(event, wrapper);
25
+ handler(data);
26
+ };
27
+ return this.on(event, wrapper);
28
+ }
29
+
30
+ off<K extends keyof TEvents>(event: K, handler: (data: TEvents[K]) => void): this {
31
+ this.listeners.get(event)?.delete(handler);
32
+ return this;
33
+ }
34
+
35
+ emit<K extends keyof TEvents>(
36
+ ...args: TEvents[K] extends void ? [event: K] : [event: K, data: TEvents[K]]
37
+ ): void {
38
+ const [event, data] = args as [K, TEvents[K]];
39
+ const handlers = this.listeners.get(event);
40
+ if (handlers) {
41
+ for (const handler of handlers) {
42
+ handler(data);
43
+ }
44
+ }
45
+ }
46
+
47
+ removeAllListeners(event?: keyof TEvents): this {
48
+ if (event) {
49
+ this.listeners.delete(event);
50
+ } else {
51
+ this.listeners.clear();
52
+ }
53
+ return this;
54
+ }
55
+ }
@@ -0,0 +1,156 @@
1
+ import { CasinoGameSDK } from '@energy8platform/game-sdk';
2
+ import type {
3
+ InitData,
4
+ PlayParams,
5
+ PlayResultData,
6
+ BalanceData,
7
+ } from '@energy8platform/game-sdk';
8
+ import { DevBridge, type DevBridgeConfig } from './dev-bridge/DevBridge';
9
+ import { EventEmitter } from './EventEmitter';
10
+
11
+ /**
12
+ * Options for {@link createPlatformSession}.
13
+ */
14
+ export interface PlatformSessionConfig {
15
+ /**
16
+ * Optional DevBridge mock-host config. When provided, a DevBridge is started
17
+ * in-process and the SDK connects to it via in-memory channel — no real
18
+ * casino backend required. Use this for local development and offline
19
+ * testing.
20
+ */
21
+ dev?: DevBridgeConfig;
22
+
23
+ /**
24
+ * SDK configuration. Pass an options object, or `false` to skip SDK
25
+ * initialization entirely (no host communication, used by tests and
26
+ * head-less simulation).
27
+ */
28
+ sdk?: SDKOptions | false;
29
+ }
30
+
31
+ export interface SDKOptions {
32
+ parentOrigin?: string;
33
+ timeout?: number;
34
+ debug?: boolean;
35
+ /** Use in-memory channel instead of postMessage (no iframe required) */
36
+ devMode?: boolean;
37
+ }
38
+
39
+ /**
40
+ * Events forwarded from the underlying SDK by the PlatformSession.
41
+ */
42
+ export interface PlatformSessionEvents {
43
+ /** Player balance changed (forwarded from CasinoGameSDK) */
44
+ balanceUpdate: BalanceData;
45
+ /** SDK or transport error */
46
+ error: Error;
47
+ }
48
+
49
+ /**
50
+ * Lifecycle wrapper around CasinoGameSDK + (optional) DevBridge.
51
+ *
52
+ * Use `createPlatformSession()` to construct one. The session owns the SDK
53
+ * handshake, optional in-process dev host, and a typed event bus that
54
+ * forwards SDK events upward.
55
+ *
56
+ * Phaser/Three/custom-engine consumers use this directly:
57
+ *
58
+ * ```ts
59
+ * const session = await createPlatformSession({
60
+ * dev: { luaScript, gameDefinition, balance: 10000, currency: 'EUR' },
61
+ * });
62
+ *
63
+ * session.on('balanceUpdate', ({ balance }) => updateHud(balance));
64
+ * const result = await session.play({ action: 'spin', bet: 1 });
65
+ * ```
66
+ */
67
+ export class PlatformSession extends EventEmitter<PlatformSessionEvents> {
68
+ /** SDK instance, or null when `sdk: false` was passed. */
69
+ public readonly sdk: CasinoGameSDK | null;
70
+ /** Data returned by the SDK handshake, or null in offline mode. */
71
+ public readonly initData: InitData | null;
72
+ /** DevBridge mock host, or null when `dev` was not provided. */
73
+ public readonly devBridge: DevBridge | null;
74
+
75
+ constructor(opts: {
76
+ sdk: CasinoGameSDK | null;
77
+ initData: InitData | null;
78
+ devBridge: DevBridge | null;
79
+ }) {
80
+ super();
81
+ this.sdk = opts.sdk;
82
+ this.initData = opts.initData;
83
+ this.devBridge = opts.devBridge;
84
+ }
85
+
86
+ /** Current player balance from the SDK (0 if no SDK). */
87
+ get balance(): number {
88
+ return this.sdk?.balance ?? 0;
89
+ }
90
+
91
+ /** Current currency from the SDK ('USD' fallback). */
92
+ get currency(): string {
93
+ return this.sdk?.currency ?? 'USD';
94
+ }
95
+
96
+ /**
97
+ * Send a play request through the SDK and resolve with the host result.
98
+ * Throws if the session was constructed with `sdk: false`.
99
+ */
100
+ async play(params: PlayParams): Promise<PlayResultData> {
101
+ if (!this.sdk) {
102
+ throw new Error('[PlatformSession] play() requires an active SDK (constructed with sdk: false)');
103
+ }
104
+ return this.sdk.play(params);
105
+ }
106
+
107
+ /** Tear down the SDK, DevBridge, and clear listeners. */
108
+ destroy(): void {
109
+ this.sdk?.destroy();
110
+ this.devBridge?.destroy();
111
+ this.removeAllListeners();
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Build a PlatformSession.
117
+ *
118
+ * Steps performed:
119
+ * 1. If `config.dev` is set → start a DevBridge with those options
120
+ * 2. Unless `config.sdk === false` → construct CasinoGameSDK and await its handshake
121
+ * 3. Forward `error` and `balanceUpdate` events from the SDK
122
+ */
123
+ export async function createPlatformSession(
124
+ config: PlatformSessionConfig = {},
125
+ ): Promise<PlatformSession> {
126
+ // 1. Optionally start the DevBridge mock host
127
+ let devBridge: DevBridge | null = null;
128
+ if (config.dev) {
129
+ devBridge = new DevBridge(config.dev);
130
+ devBridge.start();
131
+ }
132
+
133
+ // 2. Initialize SDK (unless explicitly disabled)
134
+ let sdk: CasinoGameSDK | null = null;
135
+ let initData: InitData | null = null;
136
+
137
+ if (config.sdk !== false) {
138
+ const sdkOpts = typeof config.sdk === 'object' ? config.sdk : {};
139
+ sdk = new CasinoGameSDK(sdkOpts);
140
+ initData = await sdk.ready();
141
+ }
142
+
143
+ // 3. Build the session and wire SDK event forwarding
144
+ const session = new PlatformSession({ sdk, initData, devBridge });
145
+
146
+ if (sdk) {
147
+ sdk.on('error', (err: Error) => {
148
+ session.emit('error', err);
149
+ });
150
+ sdk.on('balanceUpdate', (data: BalanceData) => {
151
+ session.emit('balanceUpdate', data);
152
+ });
153
+ }
154
+
155
+ return session;
156
+ }