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