@smoregg/sdk 1.2.0 → 2.0.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.
- package/dist/cjs/config.cjs.map +1 -1
- package/dist/cjs/controller.cjs +215 -145
- package/dist/cjs/controller.cjs.map +1 -1
- package/dist/cjs/screen.cjs +220 -178
- package/dist/cjs/screen.cjs.map +1 -1
- package/dist/cjs/testing.cjs +160 -151
- package/dist/cjs/testing.cjs.map +1 -1
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/controller.js +216 -146
- package/dist/esm/controller.js.map +1 -1
- package/dist/esm/screen.js +221 -179
- package/dist/esm/screen.js.map +1 -1
- package/dist/esm/testing.js +160 -151
- package/dist/esm/testing.js.map +1 -1
- package/dist/types/config.d.ts +1 -2
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/controller.d.ts +22 -43
- package/dist/types/controller.d.ts.map +1 -1
- package/dist/types/index.d.ts +14 -14
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/screen.d.ts +26 -37
- package/dist/types/screen.d.ts.map +1 -1
- package/dist/types/testing.d.ts +16 -0
- package/dist/types/testing.d.ts.map +1 -1
- package/dist/types/types.d.ts +244 -338
- package/dist/types/types.d.ts.map +1 -1
- package/dist/umd/smore-sdk.umd.js +595 -474
- package/dist/umd/smore-sdk.umd.js.map +1 -1
- package/dist/umd/smore-sdk.umd.min.js +1 -1
- package/dist/umd/smore-sdk.umd.min.js.map +1 -1
- package/package.json +1 -1
- package/dist/cjs/SmoreHost.cjs +0 -306
- package/dist/cjs/SmoreHost.cjs.map +0 -1
- package/dist/cjs/SmorePlayer.cjs +0 -229
- package/dist/cjs/SmorePlayer.cjs.map +0 -1
- package/dist/cjs/components/DirectionPad.cjs +0 -68
- package/dist/cjs/components/DirectionPad.cjs.map +0 -1
- package/dist/cjs/components/DirectionPad.module.css.cjs +0 -12
- package/dist/cjs/components/DirectionPad.module.css.cjs.map +0 -1
- package/dist/cjs/components/HoldButton.cjs +0 -57
- package/dist/cjs/components/HoldButton.cjs.map +0 -1
- package/dist/cjs/components/HoldButton.module.css.cjs +0 -12
- package/dist/cjs/components/HoldButton.module.css.cjs.map +0 -1
- package/dist/cjs/components/IframeGameBridge.cjs +0 -115
- package/dist/cjs/components/IframeGameBridge.cjs.map +0 -1
- package/dist/cjs/components/SwipeArea.cjs +0 -58
- package/dist/cjs/components/SwipeArea.cjs.map +0 -1
- package/dist/cjs/components/SwipeArea.module.css.cjs +0 -12
- package/dist/cjs/components/SwipeArea.module.css.cjs.map +0 -1
- package/dist/cjs/components/TapButton.cjs +0 -58
- package/dist/cjs/components/TapButton.cjs.map +0 -1
- package/dist/cjs/components/TapButton.module.css.cjs +0 -12
- package/dist/cjs/components/TapButton.module.css.cjs.map +0 -1
- package/dist/cjs/context/RoomProvider.cjs +0 -118
- package/dist/cjs/context/RoomProvider.cjs.map +0 -1
- package/dist/cjs/hooks/useExternalGames.cjs +0 -49
- package/dist/cjs/hooks/useExternalGames.cjs.map +0 -1
- package/dist/cjs/hooks/useGameHost.cjs +0 -206
- package/dist/cjs/hooks/useGameHost.cjs.map +0 -1
- package/dist/cjs/hooks/useGamePlayer.cjs +0 -134
- package/dist/cjs/hooks/useGamePlayer.cjs.map +0 -1
- package/dist/cjs/iframe/index.cjs +0 -260
- package/dist/cjs/iframe/index.cjs.map +0 -1
- package/dist/cjs/node_modules/.pnpm/style-inject@0.3.0/node_modules/style-inject/dist/style-inject.es.cjs +0 -33
- package/dist/cjs/node_modules/.pnpm/style-inject@0.3.0/node_modules/style-inject/dist/style-inject.es.cjs.map +0 -1
- package/dist/cjs/server/index.cjs +0 -45
- package/dist/cjs/server/index.cjs.map +0 -1
- package/dist/cjs/transport/DirectTransport.cjs +0 -23
- package/dist/cjs/transport/DirectTransport.cjs.map +0 -1
- package/dist/cjs/utils/connectionMonitor.cjs +0 -77
- package/dist/cjs/utils/connectionMonitor.cjs.map +0 -1
- package/dist/cjs/utils/preloadAssets.cjs +0 -66
- package/dist/cjs/utils/preloadAssets.cjs.map +0 -1
- package/dist/cjs/utils/serverTime.cjs +0 -43
- package/dist/cjs/utils/serverTime.cjs.map +0 -1
- package/dist/esm/SmoreHost.js +0 -304
- package/dist/esm/SmoreHost.js.map +0 -1
- package/dist/esm/SmorePlayer.js +0 -227
- package/dist/esm/SmorePlayer.js.map +0 -1
- package/dist/esm/components/DirectionPad.js +0 -66
- package/dist/esm/components/DirectionPad.js.map +0 -1
- package/dist/esm/components/DirectionPad.module.css.js +0 -8
- package/dist/esm/components/DirectionPad.module.css.js.map +0 -1
- package/dist/esm/components/HoldButton.js +0 -55
- package/dist/esm/components/HoldButton.js.map +0 -1
- package/dist/esm/components/HoldButton.module.css.js +0 -8
- package/dist/esm/components/HoldButton.module.css.js.map +0 -1
- package/dist/esm/components/IframeGameBridge.js +0 -113
- package/dist/esm/components/IframeGameBridge.js.map +0 -1
- package/dist/esm/components/SwipeArea.js +0 -56
- package/dist/esm/components/SwipeArea.js.map +0 -1
- package/dist/esm/components/SwipeArea.module.css.js +0 -8
- package/dist/esm/components/SwipeArea.module.css.js.map +0 -1
- package/dist/esm/components/TapButton.js +0 -56
- package/dist/esm/components/TapButton.js.map +0 -1
- package/dist/esm/components/TapButton.module.css.js +0 -8
- package/dist/esm/components/TapButton.module.css.js.map +0 -1
- package/dist/esm/context/RoomProvider.js +0 -109
- package/dist/esm/context/RoomProvider.js.map +0 -1
- package/dist/esm/hooks/useExternalGames.js +0 -47
- package/dist/esm/hooks/useExternalGames.js.map +0 -1
- package/dist/esm/hooks/useGameHost.js +0 -204
- package/dist/esm/hooks/useGameHost.js.map +0 -1
- package/dist/esm/hooks/useGamePlayer.js +0 -132
- package/dist/esm/hooks/useGamePlayer.js.map +0 -1
- package/dist/esm/iframe/index.js +0 -257
- package/dist/esm/iframe/index.js.map +0 -1
- package/dist/esm/node_modules/.pnpm/style-inject@0.3.0/node_modules/style-inject/dist/style-inject.es.js +0 -29
- package/dist/esm/node_modules/.pnpm/style-inject@0.3.0/node_modules/style-inject/dist/style-inject.es.js.map +0 -1
- package/dist/esm/server/index.js +0 -43
- package/dist/esm/server/index.js.map +0 -1
- package/dist/esm/transport/DirectTransport.js +0 -21
- package/dist/esm/transport/DirectTransport.js.map +0 -1
- package/dist/esm/utils/connectionMonitor.js +0 -75
- package/dist/esm/utils/connectionMonitor.js.map +0 -1
- package/dist/esm/utils/preloadAssets.js +0 -63
- package/dist/esm/utils/preloadAssets.js.map +0 -1
- package/dist/esm/utils/serverTime.js +0 -41
- package/dist/esm/utils/serverTime.js.map +0 -1
- package/dist/types/SmoreHost.d.ts +0 -187
- package/dist/types/SmoreHost.d.ts.map +0 -1
- package/dist/types/SmorePlayer.d.ts +0 -146
- package/dist/types/SmorePlayer.d.ts.map +0 -1
- package/dist/types/components/DirectionPad.d.ts +0 -21
- package/dist/types/components/DirectionPad.d.ts.map +0 -1
- package/dist/types/components/HoldButton.d.ts +0 -22
- package/dist/types/components/HoldButton.d.ts.map +0 -1
- package/dist/types/components/IframeGameBridge.d.ts +0 -38
- package/dist/types/components/IframeGameBridge.d.ts.map +0 -1
- package/dist/types/components/SwipeArea.d.ts +0 -19
- package/dist/types/components/SwipeArea.d.ts.map +0 -1
- package/dist/types/components/TapButton.d.ts +0 -19
- package/dist/types/components/TapButton.d.ts.map +0 -1
- package/dist/types/components/index.d.ts +0 -6
- package/dist/types/components/index.d.ts.map +0 -1
- package/dist/types/context/RoomProvider.d.ts +0 -69
- package/dist/types/context/RoomProvider.d.ts.map +0 -1
- package/dist/types/context/index.d.ts +0 -3
- package/dist/types/context/index.d.ts.map +0 -1
- package/dist/types/dev/DevSimulator.d.ts +0 -31
- package/dist/types/dev/DevSimulator.d.ts.map +0 -1
- package/dist/types/dev/index.d.ts +0 -2
- package/dist/types/dev/index.d.ts.map +0 -1
- package/dist/types/hooks/index.d.ts +0 -7
- package/dist/types/hooks/index.d.ts.map +0 -1
- package/dist/types/hooks/useExternalGames.d.ts +0 -32
- package/dist/types/hooks/useExternalGames.d.ts.map +0 -1
- package/dist/types/hooks/useGameHost.d.ts +0 -67
- package/dist/types/hooks/useGameHost.d.ts.map +0 -1
- package/dist/types/hooks/useGamePlayer.d.ts +0 -55
- package/dist/types/hooks/useGamePlayer.d.ts.map +0 -1
- package/dist/types/iframe/IframeRoomProvider.d.ts +0 -31
- package/dist/types/iframe/IframeRoomProvider.d.ts.map +0 -1
- package/dist/types/iframe/index.d.ts +0 -18
- package/dist/types/iframe/index.d.ts.map +0 -1
- package/dist/types/iframe/vanilla-entry.d.ts +0 -7
- package/dist/types/iframe/vanilla-entry.d.ts.map +0 -1
- package/dist/types/iframe/vanilla.d.ts +0 -49
- package/dist/types/iframe/vanilla.d.ts.map +0 -1
- package/dist/types/server/createGameRelay.d.ts +0 -26
- package/dist/types/server/createGameRelay.d.ts.map +0 -1
- package/dist/types/server/index.d.ts +0 -3
- package/dist/types/server/index.d.ts.map +0 -1
- package/dist/types/utils/connectionMonitor.d.ts +0 -57
- package/dist/types/utils/connectionMonitor.d.ts.map +0 -1
- package/dist/types/utils/index.d.ts +0 -7
- package/dist/types/utils/index.d.ts.map +0 -1
- package/dist/types/utils/preloadAssets.d.ts +0 -29
- package/dist/types/utils/preloadAssets.d.ts.map +0 -1
- package/dist/types/utils/serverTime.d.ts +0 -28
- package/dist/types/utils/serverTime.d.ts.map +0 -1
- package/dist/umd/smore-sdk-iframe.umd.js +0 -266
- package/dist/umd/smore-sdk-iframe.umd.js.map +0 -1
- package/dist/umd/smore-sdk-iframe.umd.min.js +0 -2
- package/dist/umd/smore-sdk-iframe.umd.min.js.map +0 -1
- package/dist/umd/smore-sdk-vanilla.umd.js +0 -1275
- package/dist/umd/smore-sdk-vanilla.umd.js.map +0 -1
- package/dist/umd/smore-sdk-vanilla.umd.min.js +0 -2
- package/dist/umd/smore-sdk-vanilla.umd.min.js.map +0 -1
package/dist/cjs/config.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.cjs","sources":["../../src/config.ts"],"sourcesContent":["/**\n * Global SDK configuration.\n * Settings here apply to both Screen and Controller unless overridden per-instance.\n */\n\nexport interface SmoreGlobalConfig {\n /**\n * Automatically signal ready after initialization.\n * When true (default), the SDK calls signalReady()
|
|
1
|
+
{"version":3,"file":"config.cjs","sources":["../../src/config.ts"],"sourcesContent":["/**\n * Global SDK configuration.\n * Settings here apply to both Screen and Controller unless overridden per-instance.\n */\n\nexport interface SmoreGlobalConfig {\n /**\n * Automatically signal ready after initialization.\n * When true (default), the SDK calls signalReady() automatically after init completes.\n * Set to false if your game needs to load resources before signaling ready.\n * Applies to both Screen and Controller.\n * @default true\n */\n autoReady?: boolean;\n}\n\nlet globalConfig: SmoreGlobalConfig = {};\n\n/**\n * Set global SDK configuration.\n * Call this before createScreen() / createController().\n *\n * @example\n * ```ts\n * import { configure, createScreen, createController } from '@smoregg/sdk';\n *\n * // Set once, applies to both screen and controller\n * configure({ autoReady: false });\n *\n * const screen = await createScreen({ ... });\n * const controller = await createController({ ... });\n * ```\n */\nexport function configure(config: SmoreGlobalConfig): void {\n globalConfig = { ...globalConfig, ...config };\n}\n\n/**\n * Get current global config. Used internally by Screen and Controller.\n */\nexport function getGlobalConfig(): Readonly<SmoreGlobalConfig> {\n return globalConfig;\n}\n"],"names":[],"mappings":";;AAgBA,IAAI,eAAkC,EAAC;AAiBhC,SAAS,UAAU,MAAA,EAAiC;AACzD,EAAA,YAAA,GAAe,EAAE,GAAG,YAAA,EAAc,GAAG,MAAA,EAAO;AAC9C;AAKO,SAAS,eAAA,GAA+C;AAC7D,EAAA,OAAO,YAAA;AACT;;;;;"}
|
package/dist/cjs/controller.cjs
CHANGED
|
@@ -16,22 +16,38 @@ class ControllerImpl {
|
|
|
16
16
|
_myIndex = -1;
|
|
17
17
|
_isReady = false;
|
|
18
18
|
_isDestroyed = false;
|
|
19
|
+
_initTimeoutId = null;
|
|
19
20
|
boundMessageHandler = null;
|
|
20
21
|
registeredHandlers = [];
|
|
21
22
|
eventListeners = /* @__PURE__ */ new Map();
|
|
22
23
|
// Maps user-facing handler -> transport wrappedHandler for proper cleanup in on()/off()
|
|
23
24
|
handlerToTransport = /* @__PURE__ */ new Map();
|
|
24
25
|
_controllers = [];
|
|
25
|
-
//
|
|
26
|
-
|
|
26
|
+
// Pending handlers registered via on() before transport is ready
|
|
27
|
+
_pendingHandlers = [];
|
|
28
|
+
// Lifecycle callback arrays
|
|
29
|
+
_onAllReadyCallbacks = /* @__PURE__ */ new Set();
|
|
30
|
+
_onControllerJoinCallbacks = /* @__PURE__ */ new Set();
|
|
31
|
+
_onControllerLeaveCallbacks = /* @__PURE__ */ new Set();
|
|
32
|
+
_onControllerDisconnectCallbacks = /* @__PURE__ */ new Set();
|
|
33
|
+
_onControllerReconnectCallbacks = /* @__PURE__ */ new Set();
|
|
34
|
+
_onCharacterUpdatedCallbacks = /* @__PURE__ */ new Set();
|
|
35
|
+
_onRateLimitedCallbacks = /* @__PURE__ */ new Set();
|
|
36
|
+
_onErrorCallbacks = /* @__PURE__ */ new Set();
|
|
37
|
+
// Whether all-ready has fired
|
|
38
|
+
_allReadyFired = false;
|
|
39
|
+
// Ready promise
|
|
40
|
+
_readyResolve;
|
|
41
|
+
_readyReject;
|
|
42
|
+
ready;
|
|
27
43
|
constructor(config = {}) {
|
|
28
44
|
this.config = config;
|
|
29
45
|
this.logger = new logger.DebugLogger(config.debug, "[SmoreController]");
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
46
|
+
this.ready = new Promise((resolve, reject) => {
|
|
47
|
+
this._readyResolve = resolve;
|
|
48
|
+
this._readyReject = reject;
|
|
49
|
+
});
|
|
50
|
+
this.startInitialization();
|
|
35
51
|
}
|
|
36
52
|
// ---------------------------------------------------------------------------
|
|
37
53
|
// Properties (readonly)
|
|
@@ -67,38 +83,36 @@ class ControllerImpl {
|
|
|
67
83
|
// ---------------------------------------------------------------------------
|
|
68
84
|
// Initialization
|
|
69
85
|
// ---------------------------------------------------------------------------
|
|
70
|
-
|
|
86
|
+
startInitialization() {
|
|
71
87
|
const parentOrigin = this.config.parentOrigin ?? "*";
|
|
72
88
|
const timeout = this.config.timeout ?? DEFAULT_TIMEOUT;
|
|
73
89
|
this.logger.lifecycle("Initializing controller...", { parentOrigin, timeout });
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
window.parent.postMessage({ type: "_bridge:ready" }, parentOrigin);
|
|
99
|
-
});
|
|
90
|
+
this._initTimeoutId = setTimeout(() => {
|
|
91
|
+
this.cleanup();
|
|
92
|
+
const error = new errors.SmoreSDKError(
|
|
93
|
+
"TIMEOUT",
|
|
94
|
+
`Controller initialization timed out after ${timeout}ms. Make sure the parent window sends _bridge:init message. Check that the iframe has correct sandbox attributes (allow-scripts required) and same-origin/cross-origin settings. Create a new Controller instance to retry (this instance has been cleaned up).`,
|
|
95
|
+
{ details: { timeout } }
|
|
96
|
+
);
|
|
97
|
+
this.handleError(error);
|
|
98
|
+
this._readyReject(error);
|
|
99
|
+
}, timeout);
|
|
100
|
+
this.boundMessageHandler = (e) => {
|
|
101
|
+
if (parentOrigin !== "*" && e.origin !== parentOrigin) return;
|
|
102
|
+
const msg = e.data;
|
|
103
|
+
if (!protocol.isBridgeMessage(msg)) return;
|
|
104
|
+
if (msg.type === "_bridge:init") {
|
|
105
|
+
clearTimeout(this._initTimeoutId);
|
|
106
|
+
this.handleInit(msg, parentOrigin);
|
|
107
|
+
} else if (msg.type === "_bridge:update") {
|
|
108
|
+
this.handleUpdate(msg);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
window.addEventListener("message", this.boundMessageHandler);
|
|
112
|
+
this.logger.lifecycle("Sending _bridge:ready to parent");
|
|
113
|
+
window.parent.postMessage({ type: "_bridge:ready" }, parentOrigin);
|
|
100
114
|
}
|
|
101
|
-
handleInit(msg, parentOrigin
|
|
115
|
+
handleInit(msg, parentOrigin) {
|
|
102
116
|
const initPayload = msg.payload;
|
|
103
117
|
this.logger.debug("Received _bridge:init", initPayload);
|
|
104
118
|
try {
|
|
@@ -111,7 +125,7 @@ class ControllerImpl {
|
|
|
111
125
|
);
|
|
112
126
|
this.logger.warn("_bridge:init validation failed", error);
|
|
113
127
|
this.handleError(error);
|
|
114
|
-
|
|
128
|
+
this._readyReject(error);
|
|
115
129
|
return;
|
|
116
130
|
}
|
|
117
131
|
const initData = initPayload;
|
|
@@ -122,7 +136,7 @@ class ControllerImpl {
|
|
|
122
136
|
{ details: { side: initData.side } }
|
|
123
137
|
);
|
|
124
138
|
this.handleError(error);
|
|
125
|
-
|
|
139
|
+
this._readyReject(error);
|
|
126
140
|
return;
|
|
127
141
|
}
|
|
128
142
|
if (initData.myIndex === void 0) {
|
|
@@ -132,7 +146,7 @@ class ControllerImpl {
|
|
|
132
146
|
{ details: initData }
|
|
133
147
|
);
|
|
134
148
|
this.handleError(error);
|
|
135
|
-
|
|
149
|
+
this._readyReject(error);
|
|
136
150
|
return;
|
|
137
151
|
}
|
|
138
152
|
this.transport = new PostMessageTransport.PostMessageTransport(parentOrigin);
|
|
@@ -146,18 +160,21 @@ class ControllerImpl {
|
|
|
146
160
|
appearance: p.appearance ?? p.character
|
|
147
161
|
}));
|
|
148
162
|
this.setupEventHandlers();
|
|
163
|
+
for (const { event, handler } of this._pendingHandlers) {
|
|
164
|
+
this.setupUserEventHandler(event, handler);
|
|
165
|
+
}
|
|
166
|
+
this._pendingHandlers = [];
|
|
149
167
|
this._isReady = true;
|
|
150
168
|
this.logger.lifecycle("Controller ready", {
|
|
151
169
|
roomCode: this._roomCode,
|
|
152
170
|
myIndex: this._myIndex
|
|
153
171
|
});
|
|
154
|
-
|
|
155
|
-
const autoReady = this.config.autoReady ?? config.getGlobalConfig().autoReady ?? true;
|
|
172
|
+
const autoReady = config.getGlobalConfig().autoReady ?? true;
|
|
156
173
|
if (autoReady) {
|
|
157
174
|
this.logger.lifecycle("Auto-signaling ready (autoReady enabled)");
|
|
158
175
|
this.signalReady();
|
|
159
176
|
}
|
|
160
|
-
|
|
177
|
+
this._readyResolve();
|
|
161
178
|
}
|
|
162
179
|
handleUpdate(msg) {
|
|
163
180
|
if (!this._isReady) {
|
|
@@ -177,12 +194,12 @@ class ControllerImpl {
|
|
|
177
194
|
const oldControllers = this._controllers;
|
|
178
195
|
for (const nc of newControllers) {
|
|
179
196
|
if (!oldControllers.some((oc) => oc.playerIndex === nc.playerIndex)) {
|
|
180
|
-
this.
|
|
197
|
+
this._onControllerJoinCallbacks.forEach((cb) => cb(nc.playerIndex, nc));
|
|
181
198
|
}
|
|
182
199
|
}
|
|
183
200
|
for (const oc of oldControllers) {
|
|
184
201
|
if (!newControllers.some((nc) => nc.playerIndex === oc.playerIndex)) {
|
|
185
|
-
this.
|
|
202
|
+
this._onControllerLeaveCallbacks.forEach((cb) => cb(oc.playerIndex));
|
|
186
203
|
}
|
|
187
204
|
}
|
|
188
205
|
for (const nc of newControllers) {
|
|
@@ -190,11 +207,11 @@ class ControllerImpl {
|
|
|
190
207
|
if (oc) {
|
|
191
208
|
if (oc.connected && !nc.connected) {
|
|
192
209
|
this.logger.debug("Player disconnected (via update)", { playerIndex: nc.playerIndex });
|
|
193
|
-
this.
|
|
210
|
+
this._onControllerDisconnectCallbacks.forEach((cb) => cb(nc.playerIndex));
|
|
194
211
|
}
|
|
195
212
|
if (!oc.connected && nc.connected) {
|
|
196
213
|
this.logger.debug("Player reconnected (via update)", { playerIndex: nc.playerIndex });
|
|
197
|
-
this.
|
|
214
|
+
this._onControllerReconnectCallbacks.forEach((cb) => cb(nc.playerIndex, nc));
|
|
198
215
|
}
|
|
199
216
|
}
|
|
200
217
|
}
|
|
@@ -221,7 +238,7 @@ class ControllerImpl {
|
|
|
221
238
|
};
|
|
222
239
|
this._controllers = [...this._controllers, controllerInfo];
|
|
223
240
|
this.logger.debug("Player joined", { playerIndex });
|
|
224
|
-
this.
|
|
241
|
+
this._onControllerJoinCallbacks.forEach((cb) => cb(playerIndex, controllerInfo));
|
|
225
242
|
}
|
|
226
243
|
});
|
|
227
244
|
this.registerHandler(events.SMORE_EVENTS.PLAYER_LEFT, (raw) => {
|
|
@@ -231,7 +248,7 @@ class ControllerImpl {
|
|
|
231
248
|
if (!this._controllers.some((c) => c.playerIndex === playerIndex)) return;
|
|
232
249
|
this._controllers = this._controllers.filter((c) => c.playerIndex !== playerIndex);
|
|
233
250
|
this.logger.debug("Player left", { playerIndex });
|
|
234
|
-
this.
|
|
251
|
+
this._onControllerLeaveCallbacks.forEach((cb) => cb(playerIndex));
|
|
235
252
|
}
|
|
236
253
|
});
|
|
237
254
|
this.registerHandler(events.SMORE_EVENTS.PLAYER_DISCONNECTED, (raw) => {
|
|
@@ -243,7 +260,7 @@ class ControllerImpl {
|
|
|
243
260
|
(c) => c.playerIndex === playerIndex ? { ...c, connected: false } : c
|
|
244
261
|
);
|
|
245
262
|
this.logger.debug("Player disconnected", { playerIndex });
|
|
246
|
-
this.
|
|
263
|
+
this._onControllerDisconnectCallbacks.forEach((cb) => cb(playerIndex));
|
|
247
264
|
}
|
|
248
265
|
});
|
|
249
266
|
this.registerHandler(events.SMORE_EVENTS.PLAYER_RECONNECTED, (raw) => {
|
|
@@ -265,56 +282,63 @@ class ControllerImpl {
|
|
|
265
282
|
(c) => c.playerIndex === playerIndex ? controllerInfo : c
|
|
266
283
|
);
|
|
267
284
|
this.logger.debug("Player reconnected", { playerIndex });
|
|
268
|
-
this.
|
|
285
|
+
this._onControllerReconnectCallbacks.forEach((cb) => cb(playerIndex, controllerInfo));
|
|
269
286
|
}
|
|
270
287
|
});
|
|
271
288
|
this.registerHandler(events.SMORE_EVENTS.PLAYER_CHARACTER_UPDATED, (raw) => {
|
|
272
289
|
const payload = raw;
|
|
273
290
|
const playerData = payload?.player;
|
|
274
291
|
if (playerData && typeof playerData.playerIndex === "number") {
|
|
292
|
+
const pi = playerData.playerIndex;
|
|
275
293
|
const appearance = playerData.character ?? null;
|
|
276
294
|
this._controllers = this._controllers.map(
|
|
277
|
-
(c) => c.playerIndex ===
|
|
295
|
+
(c) => c.playerIndex === pi ? { ...c, appearance } : c
|
|
278
296
|
);
|
|
279
|
-
this.logger.debug("Player character updated", { playerIndex:
|
|
280
|
-
this.
|
|
297
|
+
this.logger.debug("Player character updated", { playerIndex: pi });
|
|
298
|
+
this._onCharacterUpdatedCallbacks.forEach((cb) => cb(pi, appearance ?? null));
|
|
281
299
|
}
|
|
282
300
|
});
|
|
283
301
|
this.registerHandler(events.SMORE_EVENTS.RATE_LIMITED, (raw) => {
|
|
284
302
|
const data = raw;
|
|
285
303
|
const event = data?.event ?? "unknown";
|
|
286
304
|
this.logger.warn(`Rate limited: ${event}`);
|
|
287
|
-
this.
|
|
305
|
+
this._onRateLimitedCallbacks.forEach((cb) => cb(event));
|
|
288
306
|
});
|
|
289
307
|
this.registerHandler(events.SMORE_EVENTS.ALL_READY, () => {
|
|
290
308
|
this.logger.lifecycle("All participants ready");
|
|
291
|
-
this.
|
|
309
|
+
this._allReadyFired = true;
|
|
310
|
+
this._onAllReadyCallbacks.forEach((cb) => cb());
|
|
292
311
|
});
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
cause: err instanceof Error ? err : void 0,
|
|
311
|
-
details: { event }
|
|
312
|
-
})
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
});
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Sets up a user event handler for controller events.
|
|
315
|
+
* Used for registering pending handlers after transport becomes available.
|
|
316
|
+
*/
|
|
317
|
+
setupUserEventHandler(event, handler) {
|
|
318
|
+
const transportHandler = (data) => {
|
|
319
|
+
this.logReceive(event, data);
|
|
320
|
+
try {
|
|
321
|
+
handler(data);
|
|
322
|
+
} catch (err) {
|
|
323
|
+
this.handleError(
|
|
324
|
+
new errors.SmoreSDKError("UNKNOWN", `Error in handler for event "${event}"`, {
|
|
325
|
+
cause: err instanceof Error ? err : void 0,
|
|
326
|
+
details: { event }
|
|
327
|
+
})
|
|
328
|
+
);
|
|
316
329
|
}
|
|
330
|
+
};
|
|
331
|
+
if (this.transport) {
|
|
332
|
+
this.transport.on(event, transportHandler);
|
|
333
|
+
this.registeredHandlers.push({ event, handler: transportHandler });
|
|
334
|
+
this.handlerToTransport.set(handler, { event, transportHandler });
|
|
317
335
|
}
|
|
336
|
+
let listeners = this.eventListeners.get(event);
|
|
337
|
+
if (!listeners) {
|
|
338
|
+
listeners = /* @__PURE__ */ new Set();
|
|
339
|
+
this.eventListeners.set(event, listeners);
|
|
340
|
+
}
|
|
341
|
+
listeners.add(handler);
|
|
318
342
|
}
|
|
319
343
|
registerHandler(event, handler) {
|
|
320
344
|
if (!this.transport) return;
|
|
@@ -322,13 +346,67 @@ class ControllerImpl {
|
|
|
322
346
|
this.registeredHandlers.push({ event, handler });
|
|
323
347
|
}
|
|
324
348
|
// ---------------------------------------------------------------------------
|
|
349
|
+
// Lifecycle Methods
|
|
350
|
+
// ---------------------------------------------------------------------------
|
|
351
|
+
onAllReady(callback) {
|
|
352
|
+
if (this._allReadyFired) {
|
|
353
|
+
callback();
|
|
354
|
+
}
|
|
355
|
+
this._onAllReadyCallbacks.add(callback);
|
|
356
|
+
return () => {
|
|
357
|
+
this._onAllReadyCallbacks.delete(callback);
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
onControllerJoin(callback) {
|
|
361
|
+
this._onControllerJoinCallbacks.add(callback);
|
|
362
|
+
return () => {
|
|
363
|
+
this._onControllerJoinCallbacks.delete(callback);
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
onControllerLeave(callback) {
|
|
367
|
+
this._onControllerLeaveCallbacks.add(callback);
|
|
368
|
+
return () => {
|
|
369
|
+
this._onControllerLeaveCallbacks.delete(callback);
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
onControllerDisconnect(callback) {
|
|
373
|
+
this._onControllerDisconnectCallbacks.add(callback);
|
|
374
|
+
return () => {
|
|
375
|
+
this._onControllerDisconnectCallbacks.delete(callback);
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
onControllerReconnect(callback) {
|
|
379
|
+
this._onControllerReconnectCallbacks.add(callback);
|
|
380
|
+
return () => {
|
|
381
|
+
this._onControllerReconnectCallbacks.delete(callback);
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
onCharacterUpdated(callback) {
|
|
385
|
+
this._onCharacterUpdatedCallbacks.add(callback);
|
|
386
|
+
return () => {
|
|
387
|
+
this._onCharacterUpdatedCallbacks.delete(callback);
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
onRateLimited(callback) {
|
|
391
|
+
this._onRateLimitedCallbacks.add(callback);
|
|
392
|
+
return () => {
|
|
393
|
+
this._onRateLimitedCallbacks.delete(callback);
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
onError(callback) {
|
|
397
|
+
this._onErrorCallbacks.add(callback);
|
|
398
|
+
return () => {
|
|
399
|
+
this._onErrorCallbacks.delete(callback);
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
// ---------------------------------------------------------------------------
|
|
325
403
|
// Communication Methods
|
|
326
404
|
// ---------------------------------------------------------------------------
|
|
327
405
|
/**
|
|
328
406
|
* Send an event to the Screen. Controller-to-Controller direct communication
|
|
329
407
|
* is not supported; all messages must go through the Screen.
|
|
330
408
|
*
|
|
331
|
-
* Data is sent to the Screen only (not to other controllers). For Screen
|
|
409
|
+
* Data is sent to the Screen only (not to other controllers). For Screen->Controller communication,
|
|
332
410
|
* Screen uses broadcast() or sendToController().
|
|
333
411
|
*
|
|
334
412
|
* @note Fire-and-forget sends (no callback) will silently fail if rate-limited.
|
|
@@ -362,24 +440,14 @@ class ControllerImpl {
|
|
|
362
440
|
/**
|
|
363
441
|
* Register a handler for custom events.
|
|
364
442
|
*
|
|
443
|
+
* Can be called before the Controller is ready. Handlers registered before ready
|
|
444
|
+
* are queued and activated when the transport becomes available.
|
|
445
|
+
*
|
|
365
446
|
* When receiving events from Screen's `broadcast()`:
|
|
366
|
-
* handler receives `(data)`
|
|
447
|
+
* handler receives `(data)` -- no playerIndex included.
|
|
367
448
|
*
|
|
368
449
|
* When receiving events from Screen's `sendToController()`:
|
|
369
|
-
* handler receives `(data)`
|
|
370
|
-
*
|
|
371
|
-
* @note Unlike Screen's `on()` which receives `(playerIndex, data)`,
|
|
372
|
-
* Controller's `on()` receives only `(data)` since there's only one player per controller.
|
|
373
|
-
*
|
|
374
|
-
* Controller's on() handler signature: (data) => void
|
|
375
|
-
* Unlike Screen's (playerIndex, data) => void, Controller doesn't receive playerIndex
|
|
376
|
-
* because Controller only receives events from Screen, not from other controllers.
|
|
377
|
-
* The sender is always the Screen, so playerIndex is not applicable.
|
|
378
|
-
*
|
|
379
|
-
* **Important:** If called before the Controller is ready (i.e., before `await createController()`
|
|
380
|
-
* resolves or before the `onReady` callback fires), the handler is stored locally but
|
|
381
|
-
* will NOT receive events until the transport is initialized. Always call `on()` after
|
|
382
|
-
* initialization completes, or use `config.listeners` for handlers needed from the start.
|
|
450
|
+
* handler receives `(data)` -- targeted to this specific controller.
|
|
383
451
|
*/
|
|
384
452
|
on(event, handler) {
|
|
385
453
|
events.validateEventName(event);
|
|
@@ -389,34 +457,42 @@ class ControllerImpl {
|
|
|
389
457
|
this.eventListeners.set(event, listeners);
|
|
390
458
|
}
|
|
391
459
|
listeners.add(handler);
|
|
392
|
-
const transportHandler = (data) => {
|
|
393
|
-
this.logReceive(event, data);
|
|
394
|
-
try {
|
|
395
|
-
handler(data);
|
|
396
|
-
} catch (err) {
|
|
397
|
-
this.handleError(
|
|
398
|
-
new errors.SmoreSDKError("UNKNOWN", `Error in handler for event "${event}"`, {
|
|
399
|
-
cause: err instanceof Error ? err : void 0,
|
|
400
|
-
details: { event }
|
|
401
|
-
})
|
|
402
|
-
);
|
|
403
|
-
}
|
|
404
|
-
};
|
|
405
460
|
if (this.transport) {
|
|
461
|
+
const transportHandler = (data) => {
|
|
462
|
+
this.logReceive(event, data);
|
|
463
|
+
try {
|
|
464
|
+
handler(data);
|
|
465
|
+
} catch (err) {
|
|
466
|
+
this.handleError(
|
|
467
|
+
new errors.SmoreSDKError("UNKNOWN", `Error in handler for event "${event}"`, {
|
|
468
|
+
cause: err instanceof Error ? err : void 0,
|
|
469
|
+
details: { event }
|
|
470
|
+
})
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
};
|
|
406
474
|
this.transport.on(event, transportHandler);
|
|
407
475
|
this.registeredHandlers.push({ event, handler: transportHandler });
|
|
408
476
|
this.handlerToTransport.set(handler, { event, transportHandler });
|
|
477
|
+
} else {
|
|
478
|
+
this._pendingHandlers.push({ event, handler });
|
|
409
479
|
}
|
|
410
480
|
return () => {
|
|
411
481
|
listeners?.delete(handler);
|
|
412
482
|
if (listeners?.size === 0) {
|
|
413
483
|
this.eventListeners.delete(event);
|
|
414
484
|
}
|
|
415
|
-
this.
|
|
416
|
-
|
|
417
|
-
(h) => h.handler !== transportHandler
|
|
485
|
+
this._pendingHandlers = this._pendingHandlers.filter(
|
|
486
|
+
(p) => !(p.event === event && p.handler === handler)
|
|
418
487
|
);
|
|
419
|
-
this.handlerToTransport.
|
|
488
|
+
const entry = this.handlerToTransport.get(handler);
|
|
489
|
+
if (entry) {
|
|
490
|
+
this.transport?.off(event, entry.transportHandler);
|
|
491
|
+
this.registeredHandlers = this.registeredHandlers.filter(
|
|
492
|
+
(h) => h.handler !== entry.transportHandler
|
|
493
|
+
);
|
|
494
|
+
this.handlerToTransport.delete(handler);
|
|
495
|
+
}
|
|
420
496
|
};
|
|
421
497
|
}
|
|
422
498
|
/**
|
|
@@ -424,16 +500,6 @@ class ControllerImpl {
|
|
|
424
500
|
*
|
|
425
501
|
* @note The handler is internally wrapped, so it cannot be removed via
|
|
426
502
|
* `off(event, originalHandler)`. Use the returned unsubscribe function instead.
|
|
427
|
-
*
|
|
428
|
-
* @example
|
|
429
|
-
* ```ts
|
|
430
|
-
* const unsubscribe = controller.once('game-start', (data) => {
|
|
431
|
-
* console.log('Game started!', data);
|
|
432
|
-
* });
|
|
433
|
-
*
|
|
434
|
-
* // To cancel before it fires:
|
|
435
|
-
* unsubscribe();
|
|
436
|
-
* ```
|
|
437
503
|
*/
|
|
438
504
|
once(event, handler) {
|
|
439
505
|
const unsubscribe = this.on(event, ((data) => {
|
|
@@ -444,24 +510,13 @@ class ControllerImpl {
|
|
|
444
510
|
}
|
|
445
511
|
off(event, handler) {
|
|
446
512
|
if (!handler) {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
(h) => h.handler !== val.transportHandler
|
|
453
|
-
);
|
|
454
|
-
this.handlerToTransport.delete(key);
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
} else {
|
|
458
|
-
this.eventListeners.delete(event);
|
|
459
|
-
this.transport?.off(event);
|
|
460
|
-
this.registeredHandlers = this.registeredHandlers.filter((h) => h.event !== event);
|
|
461
|
-
for (const [key, val] of this.handlerToTransport) {
|
|
462
|
-
if (val.event === event) this.handlerToTransport.delete(key);
|
|
463
|
-
}
|
|
513
|
+
this.eventListeners.delete(event);
|
|
514
|
+
this.transport?.off(event);
|
|
515
|
+
this.registeredHandlers = this.registeredHandlers.filter((h) => h.event !== event);
|
|
516
|
+
for (const [key, val] of this.handlerToTransport) {
|
|
517
|
+
if (val.event === event) this.handlerToTransport.delete(key);
|
|
464
518
|
}
|
|
519
|
+
this._pendingHandlers = this._pendingHandlers.filter((p) => p.event !== event);
|
|
465
520
|
} else {
|
|
466
521
|
const listeners = this.eventListeners.get(event);
|
|
467
522
|
listeners?.delete(handler);
|
|
@@ -476,6 +531,9 @@ class ControllerImpl {
|
|
|
476
531
|
);
|
|
477
532
|
this.handlerToTransport.delete(handler);
|
|
478
533
|
}
|
|
534
|
+
this._pendingHandlers = this._pendingHandlers.filter(
|
|
535
|
+
(p) => !(p.event === event && p.handler === handler)
|
|
536
|
+
);
|
|
479
537
|
}
|
|
480
538
|
}
|
|
481
539
|
// ---------------------------------------------------------------------------
|
|
@@ -484,10 +542,15 @@ class ControllerImpl {
|
|
|
484
542
|
destroy() {
|
|
485
543
|
if (this._isDestroyed) return;
|
|
486
544
|
this.logger.lifecycle("Destroying controller");
|
|
487
|
-
this.cleanup();
|
|
488
545
|
this._isDestroyed = true;
|
|
546
|
+
this._isReady = false;
|
|
547
|
+
this.cleanup();
|
|
489
548
|
}
|
|
490
549
|
cleanup() {
|
|
550
|
+
if (this._initTimeoutId) {
|
|
551
|
+
clearTimeout(this._initTimeoutId);
|
|
552
|
+
this._initTimeoutId = null;
|
|
553
|
+
}
|
|
491
554
|
this._isReady = false;
|
|
492
555
|
for (const { event, handler } of this.registeredHandlers) {
|
|
493
556
|
this.transport?.off(event, handler);
|
|
@@ -495,6 +558,15 @@ class ControllerImpl {
|
|
|
495
558
|
this.registeredHandlers = [];
|
|
496
559
|
this.eventListeners.clear();
|
|
497
560
|
this.handlerToTransport.clear();
|
|
561
|
+
this._pendingHandlers = [];
|
|
562
|
+
this._onAllReadyCallbacks.clear();
|
|
563
|
+
this._onControllerJoinCallbacks.clear();
|
|
564
|
+
this._onControllerLeaveCallbacks.clear();
|
|
565
|
+
this._onControllerDisconnectCallbacks.clear();
|
|
566
|
+
this._onControllerReconnectCallbacks.clear();
|
|
567
|
+
this._onCharacterUpdatedCallbacks.clear();
|
|
568
|
+
this._onRateLimitedCallbacks.clear();
|
|
569
|
+
this._onErrorCallbacks.clear();
|
|
498
570
|
if (this.transport) {
|
|
499
571
|
this.transport.destroy();
|
|
500
572
|
this.transport = null;
|
|
@@ -518,15 +590,16 @@ class ControllerImpl {
|
|
|
518
590
|
if (!this._isReady || !this.transport) {
|
|
519
591
|
throw new errors.SmoreSDKError(
|
|
520
592
|
"NOT_READY",
|
|
521
|
-
`Cannot call ${method}() before controller is ready. Use await
|
|
593
|
+
`Cannot call ${method}() before controller is ready. Use await controller.ready.`,
|
|
522
594
|
{ details: { method, isReady: this._isReady } }
|
|
523
595
|
);
|
|
524
596
|
}
|
|
525
597
|
}
|
|
526
598
|
handleError(error) {
|
|
527
599
|
this.logger.warn(`Error in handler: ${error.message}`);
|
|
528
|
-
|
|
529
|
-
|
|
600
|
+
const smoreError = error.toSmoreError();
|
|
601
|
+
if (this._onErrorCallbacks.size > 0) {
|
|
602
|
+
this._onErrorCallbacks.forEach((cb) => cb(smoreError));
|
|
530
603
|
} else {
|
|
531
604
|
this.logger.error(error.message, error.details);
|
|
532
605
|
}
|
|
@@ -539,10 +612,7 @@ class ControllerImpl {
|
|
|
539
612
|
}
|
|
540
613
|
}
|
|
541
614
|
function createController(config) {
|
|
542
|
-
|
|
543
|
-
const promise = controller.initialize().then(() => controller);
|
|
544
|
-
promise.instance = controller;
|
|
545
|
-
return promise;
|
|
615
|
+
return new ControllerImpl(config ?? {});
|
|
546
616
|
}
|
|
547
617
|
|
|
548
618
|
exports.createController = createController;
|