@smoregg/sdk 1.3.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/controller.cjs +214 -143
- package/dist/cjs/controller.cjs.map +1 -1
- package/dist/cjs/screen.cjs +220 -177
- package/dist/cjs/screen.cjs.map +1 -1
- package/dist/cjs/testing.cjs +160 -139
- package/dist/cjs/testing.cjs.map +1 -1
- package/dist/esm/controller.js +215 -144
- package/dist/esm/controller.js.map +1 -1
- package/dist/esm/screen.js +221 -178
- package/dist/esm/screen.js.map +1 -1
- package/dist/esm/testing.js +160 -139
- package/dist/esm/testing.js.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 +250 -267
- package/dist/types/types.d.ts.map +1 -1
- package/dist/umd/smore-sdk.umd.js +594 -459
- 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/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,6 +160,10 @@ 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,
|
|
@@ -156,7 +174,7 @@ class ControllerImpl {
|
|
|
156
174
|
this.logger.lifecycle("Auto-signaling ready (autoReady enabled)");
|
|
157
175
|
this.signalReady();
|
|
158
176
|
}
|
|
159
|
-
|
|
177
|
+
this._readyResolve();
|
|
160
178
|
}
|
|
161
179
|
handleUpdate(msg) {
|
|
162
180
|
if (!this._isReady) {
|
|
@@ -176,12 +194,12 @@ class ControllerImpl {
|
|
|
176
194
|
const oldControllers = this._controllers;
|
|
177
195
|
for (const nc of newControllers) {
|
|
178
196
|
if (!oldControllers.some((oc) => oc.playerIndex === nc.playerIndex)) {
|
|
179
|
-
this.
|
|
197
|
+
this._onControllerJoinCallbacks.forEach((cb) => cb(nc.playerIndex, nc));
|
|
180
198
|
}
|
|
181
199
|
}
|
|
182
200
|
for (const oc of oldControllers) {
|
|
183
201
|
if (!newControllers.some((nc) => nc.playerIndex === oc.playerIndex)) {
|
|
184
|
-
this.
|
|
202
|
+
this._onControllerLeaveCallbacks.forEach((cb) => cb(oc.playerIndex));
|
|
185
203
|
}
|
|
186
204
|
}
|
|
187
205
|
for (const nc of newControllers) {
|
|
@@ -189,11 +207,11 @@ class ControllerImpl {
|
|
|
189
207
|
if (oc) {
|
|
190
208
|
if (oc.connected && !nc.connected) {
|
|
191
209
|
this.logger.debug("Player disconnected (via update)", { playerIndex: nc.playerIndex });
|
|
192
|
-
this.
|
|
210
|
+
this._onControllerDisconnectCallbacks.forEach((cb) => cb(nc.playerIndex));
|
|
193
211
|
}
|
|
194
212
|
if (!oc.connected && nc.connected) {
|
|
195
213
|
this.logger.debug("Player reconnected (via update)", { playerIndex: nc.playerIndex });
|
|
196
|
-
this.
|
|
214
|
+
this._onControllerReconnectCallbacks.forEach((cb) => cb(nc.playerIndex, nc));
|
|
197
215
|
}
|
|
198
216
|
}
|
|
199
217
|
}
|
|
@@ -220,7 +238,7 @@ class ControllerImpl {
|
|
|
220
238
|
};
|
|
221
239
|
this._controllers = [...this._controllers, controllerInfo];
|
|
222
240
|
this.logger.debug("Player joined", { playerIndex });
|
|
223
|
-
this.
|
|
241
|
+
this._onControllerJoinCallbacks.forEach((cb) => cb(playerIndex, controllerInfo));
|
|
224
242
|
}
|
|
225
243
|
});
|
|
226
244
|
this.registerHandler(events.SMORE_EVENTS.PLAYER_LEFT, (raw) => {
|
|
@@ -230,7 +248,7 @@ class ControllerImpl {
|
|
|
230
248
|
if (!this._controllers.some((c) => c.playerIndex === playerIndex)) return;
|
|
231
249
|
this._controllers = this._controllers.filter((c) => c.playerIndex !== playerIndex);
|
|
232
250
|
this.logger.debug("Player left", { playerIndex });
|
|
233
|
-
this.
|
|
251
|
+
this._onControllerLeaveCallbacks.forEach((cb) => cb(playerIndex));
|
|
234
252
|
}
|
|
235
253
|
});
|
|
236
254
|
this.registerHandler(events.SMORE_EVENTS.PLAYER_DISCONNECTED, (raw) => {
|
|
@@ -242,7 +260,7 @@ class ControllerImpl {
|
|
|
242
260
|
(c) => c.playerIndex === playerIndex ? { ...c, connected: false } : c
|
|
243
261
|
);
|
|
244
262
|
this.logger.debug("Player disconnected", { playerIndex });
|
|
245
|
-
this.
|
|
263
|
+
this._onControllerDisconnectCallbacks.forEach((cb) => cb(playerIndex));
|
|
246
264
|
}
|
|
247
265
|
});
|
|
248
266
|
this.registerHandler(events.SMORE_EVENTS.PLAYER_RECONNECTED, (raw) => {
|
|
@@ -264,56 +282,63 @@ class ControllerImpl {
|
|
|
264
282
|
(c) => c.playerIndex === playerIndex ? controllerInfo : c
|
|
265
283
|
);
|
|
266
284
|
this.logger.debug("Player reconnected", { playerIndex });
|
|
267
|
-
this.
|
|
285
|
+
this._onControllerReconnectCallbacks.forEach((cb) => cb(playerIndex, controllerInfo));
|
|
268
286
|
}
|
|
269
287
|
});
|
|
270
288
|
this.registerHandler(events.SMORE_EVENTS.PLAYER_CHARACTER_UPDATED, (raw) => {
|
|
271
289
|
const payload = raw;
|
|
272
290
|
const playerData = payload?.player;
|
|
273
291
|
if (playerData && typeof playerData.playerIndex === "number") {
|
|
292
|
+
const pi = playerData.playerIndex;
|
|
274
293
|
const appearance = playerData.character ?? null;
|
|
275
294
|
this._controllers = this._controllers.map(
|
|
276
|
-
(c) => c.playerIndex ===
|
|
295
|
+
(c) => c.playerIndex === pi ? { ...c, appearance } : c
|
|
277
296
|
);
|
|
278
|
-
this.logger.debug("Player character updated", { playerIndex:
|
|
279
|
-
this.
|
|
297
|
+
this.logger.debug("Player character updated", { playerIndex: pi });
|
|
298
|
+
this._onCharacterUpdatedCallbacks.forEach((cb) => cb(pi, appearance ?? null));
|
|
280
299
|
}
|
|
281
300
|
});
|
|
282
301
|
this.registerHandler(events.SMORE_EVENTS.RATE_LIMITED, (raw) => {
|
|
283
302
|
const data = raw;
|
|
284
303
|
const event = data?.event ?? "unknown";
|
|
285
304
|
this.logger.warn(`Rate limited: ${event}`);
|
|
286
|
-
this.
|
|
305
|
+
this._onRateLimitedCallbacks.forEach((cb) => cb(event));
|
|
287
306
|
});
|
|
288
307
|
this.registerHandler(events.SMORE_EVENTS.ALL_READY, () => {
|
|
289
308
|
this.logger.lifecycle("All participants ready");
|
|
290
|
-
this.
|
|
309
|
+
this._allReadyFired = true;
|
|
310
|
+
this._onAllReadyCallbacks.forEach((cb) => cb());
|
|
291
311
|
});
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
cause: err instanceof Error ? err : void 0,
|
|
310
|
-
details: { event }
|
|
311
|
-
})
|
|
312
|
-
);
|
|
313
|
-
}
|
|
314
|
-
});
|
|
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
|
+
);
|
|
315
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 });
|
|
316
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);
|
|
317
342
|
}
|
|
318
343
|
registerHandler(event, handler) {
|
|
319
344
|
if (!this.transport) return;
|
|
@@ -321,13 +346,67 @@ class ControllerImpl {
|
|
|
321
346
|
this.registeredHandlers.push({ event, handler });
|
|
322
347
|
}
|
|
323
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
|
+
// ---------------------------------------------------------------------------
|
|
324
403
|
// Communication Methods
|
|
325
404
|
// ---------------------------------------------------------------------------
|
|
326
405
|
/**
|
|
327
406
|
* Send an event to the Screen. Controller-to-Controller direct communication
|
|
328
407
|
* is not supported; all messages must go through the Screen.
|
|
329
408
|
*
|
|
330
|
-
* 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,
|
|
331
410
|
* Screen uses broadcast() or sendToController().
|
|
332
411
|
*
|
|
333
412
|
* @note Fire-and-forget sends (no callback) will silently fail if rate-limited.
|
|
@@ -361,24 +440,14 @@ class ControllerImpl {
|
|
|
361
440
|
/**
|
|
362
441
|
* Register a handler for custom events.
|
|
363
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
|
+
*
|
|
364
446
|
* When receiving events from Screen's `broadcast()`:
|
|
365
|
-
* handler receives `(data)`
|
|
447
|
+
* handler receives `(data)` -- no playerIndex included.
|
|
366
448
|
*
|
|
367
449
|
* When receiving events from Screen's `sendToController()`:
|
|
368
|
-
* handler receives `(data)`
|
|
369
|
-
*
|
|
370
|
-
* @note Unlike Screen's `on()` which receives `(playerIndex, data)`,
|
|
371
|
-
* Controller's `on()` receives only `(data)` since there's only one player per controller.
|
|
372
|
-
*
|
|
373
|
-
* Controller's on() handler signature: (data) => void
|
|
374
|
-
* Unlike Screen's (playerIndex, data) => void, Controller doesn't receive playerIndex
|
|
375
|
-
* because Controller only receives events from Screen, not from other controllers.
|
|
376
|
-
* The sender is always the Screen, so playerIndex is not applicable.
|
|
377
|
-
*
|
|
378
|
-
* **Important:** If called before the Controller is ready (i.e., before `await createController()`
|
|
379
|
-
* resolves or before the `onReady` callback fires), the handler is stored locally but
|
|
380
|
-
* will NOT receive events until the transport is initialized. Always call `on()` after
|
|
381
|
-
* initialization completes, or use `config.listeners` for handlers needed from the start.
|
|
450
|
+
* handler receives `(data)` -- targeted to this specific controller.
|
|
382
451
|
*/
|
|
383
452
|
on(event, handler) {
|
|
384
453
|
events.validateEventName(event);
|
|
@@ -388,34 +457,42 @@ class ControllerImpl {
|
|
|
388
457
|
this.eventListeners.set(event, listeners);
|
|
389
458
|
}
|
|
390
459
|
listeners.add(handler);
|
|
391
|
-
const transportHandler = (data) => {
|
|
392
|
-
this.logReceive(event, data);
|
|
393
|
-
try {
|
|
394
|
-
handler(data);
|
|
395
|
-
} catch (err) {
|
|
396
|
-
this.handleError(
|
|
397
|
-
new errors.SmoreSDKError("UNKNOWN", `Error in handler for event "${event}"`, {
|
|
398
|
-
cause: err instanceof Error ? err : void 0,
|
|
399
|
-
details: { event }
|
|
400
|
-
})
|
|
401
|
-
);
|
|
402
|
-
}
|
|
403
|
-
};
|
|
404
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
|
+
};
|
|
405
474
|
this.transport.on(event, transportHandler);
|
|
406
475
|
this.registeredHandlers.push({ event, handler: transportHandler });
|
|
407
476
|
this.handlerToTransport.set(handler, { event, transportHandler });
|
|
477
|
+
} else {
|
|
478
|
+
this._pendingHandlers.push({ event, handler });
|
|
408
479
|
}
|
|
409
480
|
return () => {
|
|
410
481
|
listeners?.delete(handler);
|
|
411
482
|
if (listeners?.size === 0) {
|
|
412
483
|
this.eventListeners.delete(event);
|
|
413
484
|
}
|
|
414
|
-
this.
|
|
415
|
-
|
|
416
|
-
(h) => h.handler !== transportHandler
|
|
485
|
+
this._pendingHandlers = this._pendingHandlers.filter(
|
|
486
|
+
(p) => !(p.event === event && p.handler === handler)
|
|
417
487
|
);
|
|
418
|
-
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
|
+
}
|
|
419
496
|
};
|
|
420
497
|
}
|
|
421
498
|
/**
|
|
@@ -423,16 +500,6 @@ class ControllerImpl {
|
|
|
423
500
|
*
|
|
424
501
|
* @note The handler is internally wrapped, so it cannot be removed via
|
|
425
502
|
* `off(event, originalHandler)`. Use the returned unsubscribe function instead.
|
|
426
|
-
*
|
|
427
|
-
* @example
|
|
428
|
-
* ```ts
|
|
429
|
-
* const unsubscribe = controller.once('game-start', (data) => {
|
|
430
|
-
* console.log('Game started!', data);
|
|
431
|
-
* });
|
|
432
|
-
*
|
|
433
|
-
* // To cancel before it fires:
|
|
434
|
-
* unsubscribe();
|
|
435
|
-
* ```
|
|
436
503
|
*/
|
|
437
504
|
once(event, handler) {
|
|
438
505
|
const unsubscribe = this.on(event, ((data) => {
|
|
@@ -443,24 +510,13 @@ class ControllerImpl {
|
|
|
443
510
|
}
|
|
444
511
|
off(event, handler) {
|
|
445
512
|
if (!handler) {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
(h) => h.handler !== val.transportHandler
|
|
452
|
-
);
|
|
453
|
-
this.handlerToTransport.delete(key);
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
} else {
|
|
457
|
-
this.eventListeners.delete(event);
|
|
458
|
-
this.transport?.off(event);
|
|
459
|
-
this.registeredHandlers = this.registeredHandlers.filter((h) => h.event !== event);
|
|
460
|
-
for (const [key, val] of this.handlerToTransport) {
|
|
461
|
-
if (val.event === event) this.handlerToTransport.delete(key);
|
|
462
|
-
}
|
|
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);
|
|
463
518
|
}
|
|
519
|
+
this._pendingHandlers = this._pendingHandlers.filter((p) => p.event !== event);
|
|
464
520
|
} else {
|
|
465
521
|
const listeners = this.eventListeners.get(event);
|
|
466
522
|
listeners?.delete(handler);
|
|
@@ -475,6 +531,9 @@ class ControllerImpl {
|
|
|
475
531
|
);
|
|
476
532
|
this.handlerToTransport.delete(handler);
|
|
477
533
|
}
|
|
534
|
+
this._pendingHandlers = this._pendingHandlers.filter(
|
|
535
|
+
(p) => !(p.event === event && p.handler === handler)
|
|
536
|
+
);
|
|
478
537
|
}
|
|
479
538
|
}
|
|
480
539
|
// ---------------------------------------------------------------------------
|
|
@@ -483,10 +542,15 @@ class ControllerImpl {
|
|
|
483
542
|
destroy() {
|
|
484
543
|
if (this._isDestroyed) return;
|
|
485
544
|
this.logger.lifecycle("Destroying controller");
|
|
486
|
-
this.cleanup();
|
|
487
545
|
this._isDestroyed = true;
|
|
546
|
+
this._isReady = false;
|
|
547
|
+
this.cleanup();
|
|
488
548
|
}
|
|
489
549
|
cleanup() {
|
|
550
|
+
if (this._initTimeoutId) {
|
|
551
|
+
clearTimeout(this._initTimeoutId);
|
|
552
|
+
this._initTimeoutId = null;
|
|
553
|
+
}
|
|
490
554
|
this._isReady = false;
|
|
491
555
|
for (const { event, handler } of this.registeredHandlers) {
|
|
492
556
|
this.transport?.off(event, handler);
|
|
@@ -494,6 +558,15 @@ class ControllerImpl {
|
|
|
494
558
|
this.registeredHandlers = [];
|
|
495
559
|
this.eventListeners.clear();
|
|
496
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();
|
|
497
570
|
if (this.transport) {
|
|
498
571
|
this.transport.destroy();
|
|
499
572
|
this.transport = null;
|
|
@@ -517,15 +590,16 @@ class ControllerImpl {
|
|
|
517
590
|
if (!this._isReady || !this.transport) {
|
|
518
591
|
throw new errors.SmoreSDKError(
|
|
519
592
|
"NOT_READY",
|
|
520
|
-
`Cannot call ${method}() before controller is ready. Use await
|
|
593
|
+
`Cannot call ${method}() before controller is ready. Use await controller.ready.`,
|
|
521
594
|
{ details: { method, isReady: this._isReady } }
|
|
522
595
|
);
|
|
523
596
|
}
|
|
524
597
|
}
|
|
525
598
|
handleError(error) {
|
|
526
599
|
this.logger.warn(`Error in handler: ${error.message}`);
|
|
527
|
-
|
|
528
|
-
|
|
600
|
+
const smoreError = error.toSmoreError();
|
|
601
|
+
if (this._onErrorCallbacks.size > 0) {
|
|
602
|
+
this._onErrorCallbacks.forEach((cb) => cb(smoreError));
|
|
529
603
|
} else {
|
|
530
604
|
this.logger.error(error.message, error.details);
|
|
531
605
|
}
|
|
@@ -538,10 +612,7 @@ class ControllerImpl {
|
|
|
538
612
|
}
|
|
539
613
|
}
|
|
540
614
|
function createController(config) {
|
|
541
|
-
|
|
542
|
-
const promise = controller.initialize().then(() => controller);
|
|
543
|
-
promise.instance = controller;
|
|
544
|
-
return promise;
|
|
615
|
+
return new ControllerImpl(config ?? {});
|
|
545
616
|
}
|
|
546
617
|
|
|
547
618
|
exports.createController = createController;
|