@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
|
@@ -277,122 +277,139 @@
|
|
|
277
277
|
_roomCode = "";
|
|
278
278
|
_isReady = false;
|
|
279
279
|
_isDestroyed = false;
|
|
280
|
+
_initTimeoutId = null;
|
|
280
281
|
eventHandlers = /* @__PURE__ */ new Map();
|
|
281
282
|
registeredTransportHandlers = [];
|
|
282
283
|
boundMessageHandler = null;
|
|
283
|
-
// Maps user-facing handler
|
|
284
|
+
// Maps user-facing handler -> transport wrappedHandler for proper cleanup in on()/off()
|
|
284
285
|
handlerToTransport = /* @__PURE__ */ new Map();
|
|
285
|
-
//
|
|
286
|
-
|
|
286
|
+
// Pending handlers registered via on() before transport is ready
|
|
287
|
+
_pendingHandlers = [];
|
|
288
|
+
// Lifecycle callback arrays
|
|
289
|
+
_onAllReadyCallbacks = /* @__PURE__ */ new Set();
|
|
290
|
+
_onControllerJoinCallbacks = /* @__PURE__ */ new Set();
|
|
291
|
+
_onControllerLeaveCallbacks = /* @__PURE__ */ new Set();
|
|
292
|
+
_onControllerDisconnectCallbacks = /* @__PURE__ */ new Set();
|
|
293
|
+
_onControllerReconnectCallbacks = /* @__PURE__ */ new Set();
|
|
294
|
+
_onCharacterUpdatedCallbacks = /* @__PURE__ */ new Set();
|
|
295
|
+
_onRateLimitedCallbacks = /* @__PURE__ */ new Set();
|
|
296
|
+
_onErrorCallbacks = /* @__PURE__ */ new Set();
|
|
297
|
+
// Whether all-ready has fired
|
|
298
|
+
_allReadyFired = false;
|
|
299
|
+
// Ready promise
|
|
300
|
+
_readyResolve;
|
|
301
|
+
_readyReject;
|
|
302
|
+
ready;
|
|
287
303
|
constructor(config = {}) {
|
|
288
304
|
this.config = config;
|
|
289
305
|
this.logger = new DebugLogger(config.debug, "[SmoreScreen]");
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
306
|
+
this.ready = new Promise((resolve, reject) => {
|
|
307
|
+
this._readyResolve = resolve;
|
|
308
|
+
this._readyReject = reject;
|
|
309
|
+
});
|
|
310
|
+
this.startInitialization();
|
|
295
311
|
}
|
|
296
312
|
// ---------------------------------------------------------------------------
|
|
297
|
-
// Initialization (called
|
|
313
|
+
// Initialization (called in constructor)
|
|
298
314
|
// ---------------------------------------------------------------------------
|
|
299
|
-
|
|
315
|
+
startInitialization() {
|
|
300
316
|
this.logger.lifecycle("Initializing screen...");
|
|
301
317
|
const parentOrigin = this.config.parentOrigin ?? "*";
|
|
302
318
|
const timeout = this.config.timeout ?? DEFAULT_TIMEOUT$1;
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
this.
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
this.
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
}
|
|
319
|
+
this._initTimeoutId = setTimeout(() => {
|
|
320
|
+
this.cleanup();
|
|
321
|
+
const error = new SmoreSDKError(
|
|
322
|
+
"TIMEOUT",
|
|
323
|
+
`Screen initialization timed out after ${timeout}ms. Make sure the parent frame sends _bridge:init. Check that the iframe has correct sandbox attributes (allow-scripts required) and same-origin/cross-origin settings. Create a new Screen instance to retry (this instance has been cleaned up).`,
|
|
324
|
+
{ details: { timeout } }
|
|
325
|
+
);
|
|
326
|
+
this.handleError(error);
|
|
327
|
+
this._readyReject(error);
|
|
328
|
+
}, timeout);
|
|
329
|
+
this.boundMessageHandler = (e) => {
|
|
330
|
+
if (parentOrigin !== "*" && e.origin !== parentOrigin) return;
|
|
331
|
+
const msg = e.data;
|
|
332
|
+
if (!isBridgeMessage(msg)) return;
|
|
333
|
+
if (msg.type === "_bridge:init") {
|
|
334
|
+
clearTimeout(this._initTimeoutId);
|
|
335
|
+
const initPayload = msg.payload;
|
|
336
|
+
try {
|
|
337
|
+
validateInitPayload(initPayload);
|
|
338
|
+
} catch (err) {
|
|
339
|
+
const error = new SmoreSDKError(
|
|
340
|
+
"INIT_FAILED",
|
|
341
|
+
`Invalid _bridge:init payload: ${err instanceof Error ? err.message : String(err)}`,
|
|
342
|
+
{ details: { payload: initPayload } }
|
|
343
|
+
);
|
|
344
|
+
this.logger.warn("_bridge:init validation failed", error);
|
|
345
|
+
this.handleError(error);
|
|
346
|
+
this._readyReject(error);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
const initData = initPayload;
|
|
350
|
+
if (initData.side !== "host") {
|
|
351
|
+
const error = new SmoreSDKError(
|
|
352
|
+
"INIT_FAILED",
|
|
353
|
+
`Received init for wrong side: ${initData.side}. Expected "host".`,
|
|
354
|
+
{ details: { side: initData.side } }
|
|
355
|
+
);
|
|
356
|
+
this.handleError(error);
|
|
357
|
+
this._readyReject(error);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
this.transport = new PostMessageTransport(parentOrigin);
|
|
361
|
+
this._roomCode = initData.roomCode;
|
|
362
|
+
this._controllers = this.mapControllersFromInit(initData.players);
|
|
363
|
+
if (this._controllers.length === 0) {
|
|
364
|
+
this.logger.warn("Screen initialized with zero controllers");
|
|
365
|
+
}
|
|
366
|
+
this.setupEventHandlers();
|
|
367
|
+
for (const { event, handler } of this._pendingHandlers) {
|
|
368
|
+
this.setupUserEventHandler(event, handler);
|
|
369
|
+
}
|
|
370
|
+
this._pendingHandlers = [];
|
|
371
|
+
this._isReady = true;
|
|
372
|
+
this.logger.lifecycle("Screen ready", {
|
|
373
|
+
roomCode: this._roomCode,
|
|
374
|
+
controllers: this._controllers.length
|
|
375
|
+
});
|
|
376
|
+
const autoReady = getGlobalConfig().autoReady ?? true;
|
|
377
|
+
if (autoReady) {
|
|
378
|
+
this.logger.lifecycle("Auto-signaling ready (autoReady enabled)");
|
|
379
|
+
this.signalReady();
|
|
380
|
+
}
|
|
381
|
+
this._readyResolve();
|
|
382
|
+
} else if (msg.type === "_bridge:update") {
|
|
383
|
+
if (!this._isReady) {
|
|
384
|
+
this.logger.debug("Ignoring _bridge:update before init completes");
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
const updateData = msg.payload;
|
|
388
|
+
if (updateData.players && Array.isArray(updateData.players)) {
|
|
389
|
+
const oldControllers = this._controllers;
|
|
390
|
+
const newControllers = this.mapControllersFromInit(updateData.players);
|
|
391
|
+
this._controllers = newControllers;
|
|
392
|
+
for (const nc of newControllers) {
|
|
393
|
+
if (!oldControllers.some((oc) => oc.playerIndex === nc.playerIndex)) {
|
|
394
|
+
this.logger.lifecycle("Controller joined (via update)", { playerIndex: nc.playerIndex });
|
|
395
|
+
this._onControllerJoinCallbacks.forEach((cb) => cb(nc.playerIndex, nc));
|
|
379
396
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
397
|
+
}
|
|
398
|
+
for (const oc of oldControllers) {
|
|
399
|
+
if (!newControllers.some((nc) => nc.playerIndex === oc.playerIndex)) {
|
|
400
|
+
this.logger.lifecycle("Controller left (via update)", { playerIndex: oc.playerIndex });
|
|
401
|
+
this._onControllerLeaveCallbacks.forEach((cb) => cb(oc.playerIndex));
|
|
385
402
|
}
|
|
386
403
|
}
|
|
387
|
-
this.logger.lifecycle("Room updated", {
|
|
388
|
-
controllers: this._controllers.length
|
|
389
|
-
});
|
|
390
404
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
}
|
|
405
|
+
this.logger.lifecycle("Room updated", {
|
|
406
|
+
controllers: this._controllers.length
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
window.addEventListener("message", this.boundMessageHandler);
|
|
411
|
+
window.parent.postMessage({ type: "_bridge:ready" }, parentOrigin);
|
|
412
|
+
this.logger.lifecycle("Sent _bridge:ready to parent");
|
|
396
413
|
}
|
|
397
414
|
mapControllersFromInit(players) {
|
|
398
415
|
return players.map((p, index) => ({
|
|
@@ -419,7 +436,7 @@
|
|
|
419
436
|
if (this._controllers.some((c) => c.playerIndex === controllerInfo.playerIndex)) return;
|
|
420
437
|
this._controllers = [...this._controllers, controllerInfo];
|
|
421
438
|
this.logger.lifecycle("Controller joined", { playerIndex: controllerInfo.playerIndex });
|
|
422
|
-
this.
|
|
439
|
+
this._onControllerJoinCallbacks.forEach((cb) => cb(controllerInfo.playerIndex, controllerInfo));
|
|
423
440
|
}
|
|
424
441
|
});
|
|
425
442
|
this.registerTransportHandler(SMORE_EVENTS.PLAYER_LEFT, (data) => {
|
|
@@ -429,7 +446,7 @@
|
|
|
429
446
|
if (!this._controllers.some((c) => c.playerIndex === playerIndex)) return;
|
|
430
447
|
this._controllers = this._controllers.filter((c) => c.playerIndex !== playerIndex);
|
|
431
448
|
this.logger.lifecycle("Controller left", { playerIndex });
|
|
432
|
-
this.
|
|
449
|
+
this._onControllerLeaveCallbacks.forEach((cb) => cb(playerIndex));
|
|
433
450
|
}
|
|
434
451
|
});
|
|
435
452
|
this.registerTransportHandler(SMORE_EVENTS.PLAYER_DISCONNECTED, (data) => {
|
|
@@ -440,7 +457,7 @@
|
|
|
440
457
|
(c) => c.playerIndex === playerIndex ? { ...c, connected: false } : c
|
|
441
458
|
);
|
|
442
459
|
this.logger.lifecycle("Controller disconnected", { playerIndex });
|
|
443
|
-
this.
|
|
460
|
+
this._onControllerDisconnectCallbacks.forEach((cb) => cb(playerIndex));
|
|
444
461
|
}
|
|
445
462
|
});
|
|
446
463
|
this.registerTransportHandler(SMORE_EVENTS.PLAYER_RECONNECTED, (data) => {
|
|
@@ -457,38 +474,33 @@
|
|
|
457
474
|
(c) => c.playerIndex === controllerInfo.playerIndex ? controllerInfo : c
|
|
458
475
|
);
|
|
459
476
|
this.logger.lifecycle("Controller reconnected", { playerIndex: controllerInfo.playerIndex });
|
|
460
|
-
this.
|
|
477
|
+
this._onControllerReconnectCallbacks.forEach((cb) => cb(controllerInfo.playerIndex, controllerInfo));
|
|
461
478
|
}
|
|
462
479
|
});
|
|
463
480
|
this.registerTransportHandler(SMORE_EVENTS.PLAYER_CHARACTER_UPDATED, (data) => {
|
|
464
481
|
const payload = data;
|
|
465
482
|
const playerData = payload?.player;
|
|
466
483
|
if (playerData && typeof playerData.playerIndex === "number") {
|
|
484
|
+
const pi = playerData.playerIndex;
|
|
467
485
|
const appearance = playerData.character ?? null;
|
|
468
486
|
this._controllers = this._controllers.map(
|
|
469
|
-
(c) => c.playerIndex ===
|
|
487
|
+
(c) => c.playerIndex === pi ? { ...c, appearance } : c
|
|
470
488
|
);
|
|
471
|
-
this.logger.lifecycle("Player character updated", { playerIndex:
|
|
472
|
-
this.
|
|
489
|
+
this.logger.lifecycle("Player character updated", { playerIndex: pi });
|
|
490
|
+
this._onCharacterUpdatedCallbacks.forEach((cb) => cb(pi, appearance ?? null));
|
|
473
491
|
}
|
|
474
492
|
});
|
|
475
493
|
this.registerTransportHandler(SMORE_EVENTS.RATE_LIMITED, (data) => {
|
|
476
494
|
const payload = data;
|
|
477
495
|
const event = payload?.event ?? "unknown";
|
|
478
496
|
this.logger.warn(`Rate limited: ${event}`);
|
|
479
|
-
this.
|
|
497
|
+
this._onRateLimitedCallbacks.forEach((cb) => cb(event));
|
|
480
498
|
});
|
|
481
499
|
this.registerTransportHandler(SMORE_EVENTS.ALL_READY, () => {
|
|
482
500
|
this.logger.lifecycle("All participants ready");
|
|
483
|
-
this.
|
|
501
|
+
this._allReadyFired = true;
|
|
502
|
+
this._onAllReadyCallbacks.forEach((cb) => cb());
|
|
484
503
|
});
|
|
485
|
-
if (this.config.listeners) {
|
|
486
|
-
for (const [event, handler] of Object.entries(this.config.listeners)) {
|
|
487
|
-
if (!handler) continue;
|
|
488
|
-
this._configListenerEvents.add(event);
|
|
489
|
-
this.setupUserEventHandler(event, handler);
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
504
|
}
|
|
493
505
|
/**
|
|
494
506
|
* Sets up a user event handler with playerIndex extraction.
|
|
@@ -530,6 +542,7 @@
|
|
|
530
542
|
this.eventHandlers.set(event, handlers);
|
|
531
543
|
}
|
|
532
544
|
handlers.add(handler);
|
|
545
|
+
this.handlerToTransport.set(handler, { event, transportHandler: wrappedHandler });
|
|
533
546
|
}
|
|
534
547
|
registerTransportHandler(event, handler) {
|
|
535
548
|
if (!this.transport) return;
|
|
@@ -556,6 +569,60 @@
|
|
|
556
569
|
return this._isDestroyed;
|
|
557
570
|
}
|
|
558
571
|
// ---------------------------------------------------------------------------
|
|
572
|
+
// Lifecycle Methods
|
|
573
|
+
// ---------------------------------------------------------------------------
|
|
574
|
+
onAllReady(callback) {
|
|
575
|
+
if (this._allReadyFired) {
|
|
576
|
+
callback();
|
|
577
|
+
}
|
|
578
|
+
this._onAllReadyCallbacks.add(callback);
|
|
579
|
+
return () => {
|
|
580
|
+
this._onAllReadyCallbacks.delete(callback);
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
onControllerJoin(callback) {
|
|
584
|
+
this._onControllerJoinCallbacks.add(callback);
|
|
585
|
+
return () => {
|
|
586
|
+
this._onControllerJoinCallbacks.delete(callback);
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
onControllerLeave(callback) {
|
|
590
|
+
this._onControllerLeaveCallbacks.add(callback);
|
|
591
|
+
return () => {
|
|
592
|
+
this._onControllerLeaveCallbacks.delete(callback);
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
onControllerDisconnect(callback) {
|
|
596
|
+
this._onControllerDisconnectCallbacks.add(callback);
|
|
597
|
+
return () => {
|
|
598
|
+
this._onControllerDisconnectCallbacks.delete(callback);
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
onControllerReconnect(callback) {
|
|
602
|
+
this._onControllerReconnectCallbacks.add(callback);
|
|
603
|
+
return () => {
|
|
604
|
+
this._onControllerReconnectCallbacks.delete(callback);
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
onCharacterUpdated(callback) {
|
|
608
|
+
this._onCharacterUpdatedCallbacks.add(callback);
|
|
609
|
+
return () => {
|
|
610
|
+
this._onCharacterUpdatedCallbacks.delete(callback);
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
onRateLimited(callback) {
|
|
614
|
+
this._onRateLimitedCallbacks.add(callback);
|
|
615
|
+
return () => {
|
|
616
|
+
this._onRateLimitedCallbacks.delete(callback);
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
onError(callback) {
|
|
620
|
+
this._onErrorCallbacks.add(callback);
|
|
621
|
+
return () => {
|
|
622
|
+
this._onErrorCallbacks.delete(callback);
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
// ---------------------------------------------------------------------------
|
|
559
626
|
// Communication Methods
|
|
560
627
|
// ---------------------------------------------------------------------------
|
|
561
628
|
/**
|
|
@@ -662,11 +729,8 @@
|
|
|
662
729
|
/**
|
|
663
730
|
* Register an event handler for messages from controllers.
|
|
664
731
|
*
|
|
665
|
-
*
|
|
666
|
-
*
|
|
667
|
-
* will NOT be registered with the transport layer. This means the handler will never
|
|
668
|
-
* fire for events received during the pre-ready window. Always call `on()` after
|
|
669
|
-
* initialization completes, or use `config.listeners` for handlers needed from the start.
|
|
732
|
+
* Can be called before the Screen is ready. Handlers registered before ready
|
|
733
|
+
* are queued and activated when the transport becomes available.
|
|
670
734
|
*/
|
|
671
735
|
on(event, handler) {
|
|
672
736
|
validateEventName(event);
|
|
@@ -676,9 +740,8 @@
|
|
|
676
740
|
this.eventHandlers.set(event, handlers);
|
|
677
741
|
}
|
|
678
742
|
handlers.add(handler);
|
|
679
|
-
let wrappedHandler = null;
|
|
680
743
|
if (this.transport) {
|
|
681
|
-
wrappedHandler = (data) => {
|
|
744
|
+
const wrappedHandler = (data) => {
|
|
682
745
|
this.logger.receive(event, data);
|
|
683
746
|
const payload = data;
|
|
684
747
|
const { playerIndex, ...rest } = payload;
|
|
@@ -698,16 +761,22 @@
|
|
|
698
761
|
};
|
|
699
762
|
this.registerTransportHandler(event, wrappedHandler);
|
|
700
763
|
this.handlerToTransport.set(handler, { event, transportHandler: wrappedHandler });
|
|
764
|
+
} else {
|
|
765
|
+
this._pendingHandlers.push({ event, handler });
|
|
701
766
|
}
|
|
702
767
|
return () => {
|
|
703
768
|
handlers?.delete(handler);
|
|
704
769
|
if (handlers?.size === 0) {
|
|
705
770
|
this.eventHandlers.delete(event);
|
|
706
771
|
}
|
|
707
|
-
|
|
708
|
-
|
|
772
|
+
this._pendingHandlers = this._pendingHandlers.filter(
|
|
773
|
+
(p) => !(p.event === event && p.handler === handler)
|
|
774
|
+
);
|
|
775
|
+
const entry = this.handlerToTransport.get(handler);
|
|
776
|
+
if (entry) {
|
|
777
|
+
this.transport?.off(event, entry.transportHandler);
|
|
709
778
|
this.registeredTransportHandlers = this.registeredTransportHandlers.filter(
|
|
710
|
-
(h) => h.handler !==
|
|
779
|
+
(h) => h.handler !== entry.transportHandler
|
|
711
780
|
);
|
|
712
781
|
this.handlerToTransport.delete(handler);
|
|
713
782
|
}
|
|
@@ -724,16 +793,6 @@
|
|
|
724
793
|
* @param event - Event name to listen for
|
|
725
794
|
* @param handler - Handler function to call once
|
|
726
795
|
* @returns Unsubscribe function to remove the handler before it fires
|
|
727
|
-
*
|
|
728
|
-
* @example
|
|
729
|
-
* ```ts
|
|
730
|
-
* const unsubscribe = screen.once('ready', (playerIndex, data) => {
|
|
731
|
-
* console.log('Ready event received');
|
|
732
|
-
* });
|
|
733
|
-
*
|
|
734
|
-
* // To remove before the event fires:
|
|
735
|
-
* unsubscribe();
|
|
736
|
-
* ```
|
|
737
796
|
*/
|
|
738
797
|
once(event, handler) {
|
|
739
798
|
const wrappedHandler = (playerIndex, data) => {
|
|
@@ -745,24 +804,13 @@
|
|
|
745
804
|
}
|
|
746
805
|
off(event, handler) {
|
|
747
806
|
if (!handler) {
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
(h) => h.handler !== val.transportHandler
|
|
754
|
-
);
|
|
755
|
-
this.handlerToTransport.delete(key);
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
} else {
|
|
759
|
-
this.eventHandlers.delete(event);
|
|
760
|
-
this.transport?.off(event);
|
|
761
|
-
this.registeredTransportHandlers = this.registeredTransportHandlers.filter((h) => h.event !== event);
|
|
762
|
-
for (const [key, val] of this.handlerToTransport) {
|
|
763
|
-
if (val.event === event) this.handlerToTransport.delete(key);
|
|
764
|
-
}
|
|
807
|
+
this.eventHandlers.delete(event);
|
|
808
|
+
this.transport?.off(event);
|
|
809
|
+
this.registeredTransportHandlers = this.registeredTransportHandlers.filter((h) => h.event !== event);
|
|
810
|
+
for (const [key, val] of this.handlerToTransport) {
|
|
811
|
+
if (val.event === event) this.handlerToTransport.delete(key);
|
|
765
812
|
}
|
|
813
|
+
this._pendingHandlers = this._pendingHandlers.filter((p) => p.event !== event);
|
|
766
814
|
} else {
|
|
767
815
|
const handlers = this.eventHandlers.get(event);
|
|
768
816
|
handlers?.delete(handler);
|
|
@@ -777,6 +825,9 @@
|
|
|
777
825
|
);
|
|
778
826
|
this.handlerToTransport.delete(handler);
|
|
779
827
|
}
|
|
828
|
+
this._pendingHandlers = this._pendingHandlers.filter(
|
|
829
|
+
(p) => !(p.event === event && p.handler === handler)
|
|
830
|
+
);
|
|
780
831
|
}
|
|
781
832
|
}
|
|
782
833
|
// ---------------------------------------------------------------------------
|
|
@@ -788,25 +839,6 @@
|
|
|
788
839
|
getControllerCount() {
|
|
789
840
|
return this._controllers.filter((c) => c.connected).length;
|
|
790
841
|
}
|
|
791
|
-
/**
|
|
792
|
-
* Check if there is at least one connected controller.
|
|
793
|
-
* Useful for detecting when all players have disconnected
|
|
794
|
-
* (e.g., to pause the game or show a waiting screen).
|
|
795
|
-
*
|
|
796
|
-
* Use this in onControllerDisconnect callback to detect when all controllers have disconnected.
|
|
797
|
-
*
|
|
798
|
-
* @example
|
|
799
|
-
* ```ts
|
|
800
|
-
* const screen = await createScreen<MyEvents>({
|
|
801
|
-
* onControllerDisconnect: (playerIndex) => {
|
|
802
|
-
* if (!screen.hasAnyConnectedControllers()) {
|
|
803
|
-
* console.log('All controllers disconnected!');
|
|
804
|
-
* screen.broadcast('waiting-for-players', {});
|
|
805
|
-
* }
|
|
806
|
-
* },
|
|
807
|
-
* });
|
|
808
|
-
* ```
|
|
809
|
-
*/
|
|
810
842
|
hasAnyConnectedControllers() {
|
|
811
843
|
return this._controllers.some((c) => c.connected);
|
|
812
844
|
}
|
|
@@ -822,12 +854,25 @@
|
|
|
822
854
|
this.logger.lifecycle("Screen destroyed");
|
|
823
855
|
}
|
|
824
856
|
cleanup() {
|
|
857
|
+
if (this._initTimeoutId) {
|
|
858
|
+
clearTimeout(this._initTimeoutId);
|
|
859
|
+
this._initTimeoutId = null;
|
|
860
|
+
}
|
|
825
861
|
for (const { event, handler } of this.registeredTransportHandlers) {
|
|
826
862
|
this.transport?.off(event, handler);
|
|
827
863
|
}
|
|
828
864
|
this.registeredTransportHandlers = [];
|
|
829
865
|
this.eventHandlers.clear();
|
|
830
866
|
this.handlerToTransport.clear();
|
|
867
|
+
this._pendingHandlers = [];
|
|
868
|
+
this._onAllReadyCallbacks.clear();
|
|
869
|
+
this._onControllerJoinCallbacks.clear();
|
|
870
|
+
this._onControllerLeaveCallbacks.clear();
|
|
871
|
+
this._onControllerDisconnectCallbacks.clear();
|
|
872
|
+
this._onControllerReconnectCallbacks.clear();
|
|
873
|
+
this._onCharacterUpdatedCallbacks.clear();
|
|
874
|
+
this._onRateLimitedCallbacks.clear();
|
|
875
|
+
this._onErrorCallbacks.clear();
|
|
831
876
|
if (this.transport instanceof PostMessageTransport) {
|
|
832
877
|
this.transport.destroy();
|
|
833
878
|
}
|
|
@@ -843,8 +888,8 @@
|
|
|
843
888
|
handleError(error) {
|
|
844
889
|
this.logger.warn(`Error in handler: ${error.message}`);
|
|
845
890
|
const smoreError = error.toSmoreError();
|
|
846
|
-
if (this.
|
|
847
|
-
this.
|
|
891
|
+
if (this._onErrorCallbacks.size > 0) {
|
|
892
|
+
this._onErrorCallbacks.forEach((cb) => cb(smoreError));
|
|
848
893
|
} else {
|
|
849
894
|
this.logger.error(error.message, error.details);
|
|
850
895
|
}
|
|
@@ -860,17 +905,14 @@
|
|
|
860
905
|
if (!this._isReady || !this.transport) {
|
|
861
906
|
throw new SmoreSDKError(
|
|
862
907
|
"NOT_READY",
|
|
863
|
-
`Cannot call ${method}() before screen is ready. Use await
|
|
908
|
+
`Cannot call ${method}() before screen is ready. Use await screen.ready.`,
|
|
864
909
|
{ details: { method } }
|
|
865
910
|
);
|
|
866
911
|
}
|
|
867
912
|
}
|
|
868
913
|
}
|
|
869
914
|
function createScreen(config) {
|
|
870
|
-
|
|
871
|
-
const promise = screen.initialize().then(() => screen);
|
|
872
|
-
promise.instance = screen;
|
|
873
|
-
return promise;
|
|
915
|
+
return new ScreenImpl(config);
|
|
874
916
|
}
|
|
875
917
|
|
|
876
918
|
const DEFAULT_TIMEOUT = 1e4;
|
|
@@ -882,22 +924,38 @@
|
|
|
882
924
|
_myIndex = -1;
|
|
883
925
|
_isReady = false;
|
|
884
926
|
_isDestroyed = false;
|
|
927
|
+
_initTimeoutId = null;
|
|
885
928
|
boundMessageHandler = null;
|
|
886
929
|
registeredHandlers = [];
|
|
887
930
|
eventListeners = /* @__PURE__ */ new Map();
|
|
888
931
|
// Maps user-facing handler -> transport wrappedHandler for proper cleanup in on()/off()
|
|
889
932
|
handlerToTransport = /* @__PURE__ */ new Map();
|
|
890
933
|
_controllers = [];
|
|
891
|
-
//
|
|
892
|
-
|
|
934
|
+
// Pending handlers registered via on() before transport is ready
|
|
935
|
+
_pendingHandlers = [];
|
|
936
|
+
// Lifecycle callback arrays
|
|
937
|
+
_onAllReadyCallbacks = /* @__PURE__ */ new Set();
|
|
938
|
+
_onControllerJoinCallbacks = /* @__PURE__ */ new Set();
|
|
939
|
+
_onControllerLeaveCallbacks = /* @__PURE__ */ new Set();
|
|
940
|
+
_onControllerDisconnectCallbacks = /* @__PURE__ */ new Set();
|
|
941
|
+
_onControllerReconnectCallbacks = /* @__PURE__ */ new Set();
|
|
942
|
+
_onCharacterUpdatedCallbacks = /* @__PURE__ */ new Set();
|
|
943
|
+
_onRateLimitedCallbacks = /* @__PURE__ */ new Set();
|
|
944
|
+
_onErrorCallbacks = /* @__PURE__ */ new Set();
|
|
945
|
+
// Whether all-ready has fired
|
|
946
|
+
_allReadyFired = false;
|
|
947
|
+
// Ready promise
|
|
948
|
+
_readyResolve;
|
|
949
|
+
_readyReject;
|
|
950
|
+
ready;
|
|
893
951
|
constructor(config = {}) {
|
|
894
952
|
this.config = config;
|
|
895
953
|
this.logger = new DebugLogger(config.debug, "[SmoreController]");
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
954
|
+
this.ready = new Promise((resolve, reject) => {
|
|
955
|
+
this._readyResolve = resolve;
|
|
956
|
+
this._readyReject = reject;
|
|
957
|
+
});
|
|
958
|
+
this.startInitialization();
|
|
901
959
|
}
|
|
902
960
|
// ---------------------------------------------------------------------------
|
|
903
961
|
// Properties (readonly)
|
|
@@ -933,38 +991,36 @@
|
|
|
933
991
|
// ---------------------------------------------------------------------------
|
|
934
992
|
// Initialization
|
|
935
993
|
// ---------------------------------------------------------------------------
|
|
936
|
-
|
|
994
|
+
startInitialization() {
|
|
937
995
|
const parentOrigin = this.config.parentOrigin ?? "*";
|
|
938
996
|
const timeout = this.config.timeout ?? DEFAULT_TIMEOUT;
|
|
939
997
|
this.logger.lifecycle("Initializing controller...", { parentOrigin, timeout });
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
window.parent.postMessage({ type: "_bridge:ready" }, parentOrigin);
|
|
965
|
-
});
|
|
998
|
+
this._initTimeoutId = setTimeout(() => {
|
|
999
|
+
this.cleanup();
|
|
1000
|
+
const error = new SmoreSDKError(
|
|
1001
|
+
"TIMEOUT",
|
|
1002
|
+
`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).`,
|
|
1003
|
+
{ details: { timeout } }
|
|
1004
|
+
);
|
|
1005
|
+
this.handleError(error);
|
|
1006
|
+
this._readyReject(error);
|
|
1007
|
+
}, timeout);
|
|
1008
|
+
this.boundMessageHandler = (e) => {
|
|
1009
|
+
if (parentOrigin !== "*" && e.origin !== parentOrigin) return;
|
|
1010
|
+
const msg = e.data;
|
|
1011
|
+
if (!isBridgeMessage(msg)) return;
|
|
1012
|
+
if (msg.type === "_bridge:init") {
|
|
1013
|
+
clearTimeout(this._initTimeoutId);
|
|
1014
|
+
this.handleInit(msg, parentOrigin);
|
|
1015
|
+
} else if (msg.type === "_bridge:update") {
|
|
1016
|
+
this.handleUpdate(msg);
|
|
1017
|
+
}
|
|
1018
|
+
};
|
|
1019
|
+
window.addEventListener("message", this.boundMessageHandler);
|
|
1020
|
+
this.logger.lifecycle("Sending _bridge:ready to parent");
|
|
1021
|
+
window.parent.postMessage({ type: "_bridge:ready" }, parentOrigin);
|
|
966
1022
|
}
|
|
967
|
-
handleInit(msg, parentOrigin
|
|
1023
|
+
handleInit(msg, parentOrigin) {
|
|
968
1024
|
const initPayload = msg.payload;
|
|
969
1025
|
this.logger.debug("Received _bridge:init", initPayload);
|
|
970
1026
|
try {
|
|
@@ -977,7 +1033,7 @@
|
|
|
977
1033
|
);
|
|
978
1034
|
this.logger.warn("_bridge:init validation failed", error);
|
|
979
1035
|
this.handleError(error);
|
|
980
|
-
|
|
1036
|
+
this._readyReject(error);
|
|
981
1037
|
return;
|
|
982
1038
|
}
|
|
983
1039
|
const initData = initPayload;
|
|
@@ -988,7 +1044,7 @@
|
|
|
988
1044
|
{ details: { side: initData.side } }
|
|
989
1045
|
);
|
|
990
1046
|
this.handleError(error);
|
|
991
|
-
|
|
1047
|
+
this._readyReject(error);
|
|
992
1048
|
return;
|
|
993
1049
|
}
|
|
994
1050
|
if (initData.myIndex === void 0) {
|
|
@@ -998,7 +1054,7 @@
|
|
|
998
1054
|
{ details: initData }
|
|
999
1055
|
);
|
|
1000
1056
|
this.handleError(error);
|
|
1001
|
-
|
|
1057
|
+
this._readyReject(error);
|
|
1002
1058
|
return;
|
|
1003
1059
|
}
|
|
1004
1060
|
this.transport = new PostMessageTransport(parentOrigin);
|
|
@@ -1012,18 +1068,21 @@
|
|
|
1012
1068
|
appearance: p.appearance ?? p.character
|
|
1013
1069
|
}));
|
|
1014
1070
|
this.setupEventHandlers();
|
|
1071
|
+
for (const { event, handler } of this._pendingHandlers) {
|
|
1072
|
+
this.setupUserEventHandler(event, handler);
|
|
1073
|
+
}
|
|
1074
|
+
this._pendingHandlers = [];
|
|
1015
1075
|
this._isReady = true;
|
|
1016
1076
|
this.logger.lifecycle("Controller ready", {
|
|
1017
1077
|
roomCode: this._roomCode,
|
|
1018
1078
|
myIndex: this._myIndex
|
|
1019
1079
|
});
|
|
1020
|
-
|
|
1021
|
-
const autoReady = this.config.autoReady ?? getGlobalConfig().autoReady ?? true;
|
|
1080
|
+
const autoReady = getGlobalConfig().autoReady ?? true;
|
|
1022
1081
|
if (autoReady) {
|
|
1023
1082
|
this.logger.lifecycle("Auto-signaling ready (autoReady enabled)");
|
|
1024
1083
|
this.signalReady();
|
|
1025
1084
|
}
|
|
1026
|
-
|
|
1085
|
+
this._readyResolve();
|
|
1027
1086
|
}
|
|
1028
1087
|
handleUpdate(msg) {
|
|
1029
1088
|
if (!this._isReady) {
|
|
@@ -1043,12 +1102,12 @@
|
|
|
1043
1102
|
const oldControllers = this._controllers;
|
|
1044
1103
|
for (const nc of newControllers) {
|
|
1045
1104
|
if (!oldControllers.some((oc) => oc.playerIndex === nc.playerIndex)) {
|
|
1046
|
-
this.
|
|
1105
|
+
this._onControllerJoinCallbacks.forEach((cb) => cb(nc.playerIndex, nc));
|
|
1047
1106
|
}
|
|
1048
1107
|
}
|
|
1049
1108
|
for (const oc of oldControllers) {
|
|
1050
1109
|
if (!newControllers.some((nc) => nc.playerIndex === oc.playerIndex)) {
|
|
1051
|
-
this.
|
|
1110
|
+
this._onControllerLeaveCallbacks.forEach((cb) => cb(oc.playerIndex));
|
|
1052
1111
|
}
|
|
1053
1112
|
}
|
|
1054
1113
|
for (const nc of newControllers) {
|
|
@@ -1056,11 +1115,11 @@
|
|
|
1056
1115
|
if (oc) {
|
|
1057
1116
|
if (oc.connected && !nc.connected) {
|
|
1058
1117
|
this.logger.debug("Player disconnected (via update)", { playerIndex: nc.playerIndex });
|
|
1059
|
-
this.
|
|
1118
|
+
this._onControllerDisconnectCallbacks.forEach((cb) => cb(nc.playerIndex));
|
|
1060
1119
|
}
|
|
1061
1120
|
if (!oc.connected && nc.connected) {
|
|
1062
1121
|
this.logger.debug("Player reconnected (via update)", { playerIndex: nc.playerIndex });
|
|
1063
|
-
this.
|
|
1122
|
+
this._onControllerReconnectCallbacks.forEach((cb) => cb(nc.playerIndex, nc));
|
|
1064
1123
|
}
|
|
1065
1124
|
}
|
|
1066
1125
|
}
|
|
@@ -1087,7 +1146,7 @@
|
|
|
1087
1146
|
};
|
|
1088
1147
|
this._controllers = [...this._controllers, controllerInfo];
|
|
1089
1148
|
this.logger.debug("Player joined", { playerIndex });
|
|
1090
|
-
this.
|
|
1149
|
+
this._onControllerJoinCallbacks.forEach((cb) => cb(playerIndex, controllerInfo));
|
|
1091
1150
|
}
|
|
1092
1151
|
});
|
|
1093
1152
|
this.registerHandler(SMORE_EVENTS.PLAYER_LEFT, (raw) => {
|
|
@@ -1097,7 +1156,7 @@
|
|
|
1097
1156
|
if (!this._controllers.some((c) => c.playerIndex === playerIndex)) return;
|
|
1098
1157
|
this._controllers = this._controllers.filter((c) => c.playerIndex !== playerIndex);
|
|
1099
1158
|
this.logger.debug("Player left", { playerIndex });
|
|
1100
|
-
this.
|
|
1159
|
+
this._onControllerLeaveCallbacks.forEach((cb) => cb(playerIndex));
|
|
1101
1160
|
}
|
|
1102
1161
|
});
|
|
1103
1162
|
this.registerHandler(SMORE_EVENTS.PLAYER_DISCONNECTED, (raw) => {
|
|
@@ -1109,7 +1168,7 @@
|
|
|
1109
1168
|
(c) => c.playerIndex === playerIndex ? { ...c, connected: false } : c
|
|
1110
1169
|
);
|
|
1111
1170
|
this.logger.debug("Player disconnected", { playerIndex });
|
|
1112
|
-
this.
|
|
1171
|
+
this._onControllerDisconnectCallbacks.forEach((cb) => cb(playerIndex));
|
|
1113
1172
|
}
|
|
1114
1173
|
});
|
|
1115
1174
|
this.registerHandler(SMORE_EVENTS.PLAYER_RECONNECTED, (raw) => {
|
|
@@ -1131,56 +1190,63 @@
|
|
|
1131
1190
|
(c) => c.playerIndex === playerIndex ? controllerInfo : c
|
|
1132
1191
|
);
|
|
1133
1192
|
this.logger.debug("Player reconnected", { playerIndex });
|
|
1134
|
-
this.
|
|
1193
|
+
this._onControllerReconnectCallbacks.forEach((cb) => cb(playerIndex, controllerInfo));
|
|
1135
1194
|
}
|
|
1136
1195
|
});
|
|
1137
1196
|
this.registerHandler(SMORE_EVENTS.PLAYER_CHARACTER_UPDATED, (raw) => {
|
|
1138
1197
|
const payload = raw;
|
|
1139
1198
|
const playerData = payload?.player;
|
|
1140
1199
|
if (playerData && typeof playerData.playerIndex === "number") {
|
|
1200
|
+
const pi = playerData.playerIndex;
|
|
1141
1201
|
const appearance = playerData.character ?? null;
|
|
1142
1202
|
this._controllers = this._controllers.map(
|
|
1143
|
-
(c) => c.playerIndex ===
|
|
1203
|
+
(c) => c.playerIndex === pi ? { ...c, appearance } : c
|
|
1144
1204
|
);
|
|
1145
|
-
this.logger.debug("Player character updated", { playerIndex:
|
|
1146
|
-
this.
|
|
1205
|
+
this.logger.debug("Player character updated", { playerIndex: pi });
|
|
1206
|
+
this._onCharacterUpdatedCallbacks.forEach((cb) => cb(pi, appearance ?? null));
|
|
1147
1207
|
}
|
|
1148
1208
|
});
|
|
1149
1209
|
this.registerHandler(SMORE_EVENTS.RATE_LIMITED, (raw) => {
|
|
1150
1210
|
const data = raw;
|
|
1151
1211
|
const event = data?.event ?? "unknown";
|
|
1152
1212
|
this.logger.warn(`Rate limited: ${event}`);
|
|
1153
|
-
this.
|
|
1213
|
+
this._onRateLimitedCallbacks.forEach((cb) => cb(event));
|
|
1154
1214
|
});
|
|
1155
1215
|
this.registerHandler(SMORE_EVENTS.ALL_READY, () => {
|
|
1156
1216
|
this.logger.lifecycle("All participants ready");
|
|
1157
|
-
this.
|
|
1217
|
+
this._allReadyFired = true;
|
|
1218
|
+
this._onAllReadyCallbacks.forEach((cb) => cb());
|
|
1158
1219
|
});
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
cause: err instanceof Error ? err : void 0,
|
|
1177
|
-
details: { event }
|
|
1178
|
-
})
|
|
1179
|
-
);
|
|
1180
|
-
}
|
|
1181
|
-
});
|
|
1220
|
+
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Sets up a user event handler for controller events.
|
|
1223
|
+
* Used for registering pending handlers after transport becomes available.
|
|
1224
|
+
*/
|
|
1225
|
+
setupUserEventHandler(event, handler) {
|
|
1226
|
+
const transportHandler = (data) => {
|
|
1227
|
+
this.logReceive(event, data);
|
|
1228
|
+
try {
|
|
1229
|
+
handler(data);
|
|
1230
|
+
} catch (err) {
|
|
1231
|
+
this.handleError(
|
|
1232
|
+
new SmoreSDKError("UNKNOWN", `Error in handler for event "${event}"`, {
|
|
1233
|
+
cause: err instanceof Error ? err : void 0,
|
|
1234
|
+
details: { event }
|
|
1235
|
+
})
|
|
1236
|
+
);
|
|
1182
1237
|
}
|
|
1238
|
+
};
|
|
1239
|
+
if (this.transport) {
|
|
1240
|
+
this.transport.on(event, transportHandler);
|
|
1241
|
+
this.registeredHandlers.push({ event, handler: transportHandler });
|
|
1242
|
+
this.handlerToTransport.set(handler, { event, transportHandler });
|
|
1243
|
+
}
|
|
1244
|
+
let listeners = this.eventListeners.get(event);
|
|
1245
|
+
if (!listeners) {
|
|
1246
|
+
listeners = /* @__PURE__ */ new Set();
|
|
1247
|
+
this.eventListeners.set(event, listeners);
|
|
1183
1248
|
}
|
|
1249
|
+
listeners.add(handler);
|
|
1184
1250
|
}
|
|
1185
1251
|
registerHandler(event, handler) {
|
|
1186
1252
|
if (!this.transport) return;
|
|
@@ -1188,13 +1254,67 @@
|
|
|
1188
1254
|
this.registeredHandlers.push({ event, handler });
|
|
1189
1255
|
}
|
|
1190
1256
|
// ---------------------------------------------------------------------------
|
|
1257
|
+
// Lifecycle Methods
|
|
1258
|
+
// ---------------------------------------------------------------------------
|
|
1259
|
+
onAllReady(callback) {
|
|
1260
|
+
if (this._allReadyFired) {
|
|
1261
|
+
callback();
|
|
1262
|
+
}
|
|
1263
|
+
this._onAllReadyCallbacks.add(callback);
|
|
1264
|
+
return () => {
|
|
1265
|
+
this._onAllReadyCallbacks.delete(callback);
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
onControllerJoin(callback) {
|
|
1269
|
+
this._onControllerJoinCallbacks.add(callback);
|
|
1270
|
+
return () => {
|
|
1271
|
+
this._onControllerJoinCallbacks.delete(callback);
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
onControllerLeave(callback) {
|
|
1275
|
+
this._onControllerLeaveCallbacks.add(callback);
|
|
1276
|
+
return () => {
|
|
1277
|
+
this._onControllerLeaveCallbacks.delete(callback);
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
onControllerDisconnect(callback) {
|
|
1281
|
+
this._onControllerDisconnectCallbacks.add(callback);
|
|
1282
|
+
return () => {
|
|
1283
|
+
this._onControllerDisconnectCallbacks.delete(callback);
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
onControllerReconnect(callback) {
|
|
1287
|
+
this._onControllerReconnectCallbacks.add(callback);
|
|
1288
|
+
return () => {
|
|
1289
|
+
this._onControllerReconnectCallbacks.delete(callback);
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
onCharacterUpdated(callback) {
|
|
1293
|
+
this._onCharacterUpdatedCallbacks.add(callback);
|
|
1294
|
+
return () => {
|
|
1295
|
+
this._onCharacterUpdatedCallbacks.delete(callback);
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
onRateLimited(callback) {
|
|
1299
|
+
this._onRateLimitedCallbacks.add(callback);
|
|
1300
|
+
return () => {
|
|
1301
|
+
this._onRateLimitedCallbacks.delete(callback);
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
onError(callback) {
|
|
1305
|
+
this._onErrorCallbacks.add(callback);
|
|
1306
|
+
return () => {
|
|
1307
|
+
this._onErrorCallbacks.delete(callback);
|
|
1308
|
+
};
|
|
1309
|
+
}
|
|
1310
|
+
// ---------------------------------------------------------------------------
|
|
1191
1311
|
// Communication Methods
|
|
1192
1312
|
// ---------------------------------------------------------------------------
|
|
1193
1313
|
/**
|
|
1194
1314
|
* Send an event to the Screen. Controller-to-Controller direct communication
|
|
1195
1315
|
* is not supported; all messages must go through the Screen.
|
|
1196
1316
|
*
|
|
1197
|
-
* Data is sent to the Screen only (not to other controllers). For Screen
|
|
1317
|
+
* Data is sent to the Screen only (not to other controllers). For Screen->Controller communication,
|
|
1198
1318
|
* Screen uses broadcast() or sendToController().
|
|
1199
1319
|
*
|
|
1200
1320
|
* @note Fire-and-forget sends (no callback) will silently fail if rate-limited.
|
|
@@ -1228,24 +1348,14 @@
|
|
|
1228
1348
|
/**
|
|
1229
1349
|
* Register a handler for custom events.
|
|
1230
1350
|
*
|
|
1351
|
+
* Can be called before the Controller is ready. Handlers registered before ready
|
|
1352
|
+
* are queued and activated when the transport becomes available.
|
|
1353
|
+
*
|
|
1231
1354
|
* When receiving events from Screen's `broadcast()`:
|
|
1232
|
-
* handler receives `(data)`
|
|
1355
|
+
* handler receives `(data)` -- no playerIndex included.
|
|
1233
1356
|
*
|
|
1234
1357
|
* When receiving events from Screen's `sendToController()`:
|
|
1235
|
-
* handler receives `(data)`
|
|
1236
|
-
*
|
|
1237
|
-
* @note Unlike Screen's `on()` which receives `(playerIndex, data)`,
|
|
1238
|
-
* Controller's `on()` receives only `(data)` since there's only one player per controller.
|
|
1239
|
-
*
|
|
1240
|
-
* Controller's on() handler signature: (data) => void
|
|
1241
|
-
* Unlike Screen's (playerIndex, data) => void, Controller doesn't receive playerIndex
|
|
1242
|
-
* because Controller only receives events from Screen, not from other controllers.
|
|
1243
|
-
* The sender is always the Screen, so playerIndex is not applicable.
|
|
1244
|
-
*
|
|
1245
|
-
* **Important:** If called before the Controller is ready (i.e., before `await createController()`
|
|
1246
|
-
* resolves or before the `onReady` callback fires), the handler is stored locally but
|
|
1247
|
-
* will NOT receive events until the transport is initialized. Always call `on()` after
|
|
1248
|
-
* initialization completes, or use `config.listeners` for handlers needed from the start.
|
|
1358
|
+
* handler receives `(data)` -- targeted to this specific controller.
|
|
1249
1359
|
*/
|
|
1250
1360
|
on(event, handler) {
|
|
1251
1361
|
validateEventName(event);
|
|
@@ -1255,34 +1365,42 @@
|
|
|
1255
1365
|
this.eventListeners.set(event, listeners);
|
|
1256
1366
|
}
|
|
1257
1367
|
listeners.add(handler);
|
|
1258
|
-
const transportHandler = (data) => {
|
|
1259
|
-
this.logReceive(event, data);
|
|
1260
|
-
try {
|
|
1261
|
-
handler(data);
|
|
1262
|
-
} catch (err) {
|
|
1263
|
-
this.handleError(
|
|
1264
|
-
new SmoreSDKError("UNKNOWN", `Error in handler for event "${event}"`, {
|
|
1265
|
-
cause: err instanceof Error ? err : void 0,
|
|
1266
|
-
details: { event }
|
|
1267
|
-
})
|
|
1268
|
-
);
|
|
1269
|
-
}
|
|
1270
|
-
};
|
|
1271
1368
|
if (this.transport) {
|
|
1369
|
+
const transportHandler = (data) => {
|
|
1370
|
+
this.logReceive(event, data);
|
|
1371
|
+
try {
|
|
1372
|
+
handler(data);
|
|
1373
|
+
} catch (err) {
|
|
1374
|
+
this.handleError(
|
|
1375
|
+
new SmoreSDKError("UNKNOWN", `Error in handler for event "${event}"`, {
|
|
1376
|
+
cause: err instanceof Error ? err : void 0,
|
|
1377
|
+
details: { event }
|
|
1378
|
+
})
|
|
1379
|
+
);
|
|
1380
|
+
}
|
|
1381
|
+
};
|
|
1272
1382
|
this.transport.on(event, transportHandler);
|
|
1273
1383
|
this.registeredHandlers.push({ event, handler: transportHandler });
|
|
1274
1384
|
this.handlerToTransport.set(handler, { event, transportHandler });
|
|
1385
|
+
} else {
|
|
1386
|
+
this._pendingHandlers.push({ event, handler });
|
|
1275
1387
|
}
|
|
1276
1388
|
return () => {
|
|
1277
1389
|
listeners?.delete(handler);
|
|
1278
1390
|
if (listeners?.size === 0) {
|
|
1279
1391
|
this.eventListeners.delete(event);
|
|
1280
1392
|
}
|
|
1281
|
-
this.
|
|
1282
|
-
|
|
1283
|
-
(h) => h.handler !== transportHandler
|
|
1393
|
+
this._pendingHandlers = this._pendingHandlers.filter(
|
|
1394
|
+
(p) => !(p.event === event && p.handler === handler)
|
|
1284
1395
|
);
|
|
1285
|
-
this.handlerToTransport.
|
|
1396
|
+
const entry = this.handlerToTransport.get(handler);
|
|
1397
|
+
if (entry) {
|
|
1398
|
+
this.transport?.off(event, entry.transportHandler);
|
|
1399
|
+
this.registeredHandlers = this.registeredHandlers.filter(
|
|
1400
|
+
(h) => h.handler !== entry.transportHandler
|
|
1401
|
+
);
|
|
1402
|
+
this.handlerToTransport.delete(handler);
|
|
1403
|
+
}
|
|
1286
1404
|
};
|
|
1287
1405
|
}
|
|
1288
1406
|
/**
|
|
@@ -1290,16 +1408,6 @@
|
|
|
1290
1408
|
*
|
|
1291
1409
|
* @note The handler is internally wrapped, so it cannot be removed via
|
|
1292
1410
|
* `off(event, originalHandler)`. Use the returned unsubscribe function instead.
|
|
1293
|
-
*
|
|
1294
|
-
* @example
|
|
1295
|
-
* ```ts
|
|
1296
|
-
* const unsubscribe = controller.once('game-start', (data) => {
|
|
1297
|
-
* console.log('Game started!', data);
|
|
1298
|
-
* });
|
|
1299
|
-
*
|
|
1300
|
-
* // To cancel before it fires:
|
|
1301
|
-
* unsubscribe();
|
|
1302
|
-
* ```
|
|
1303
1411
|
*/
|
|
1304
1412
|
once(event, handler) {
|
|
1305
1413
|
const unsubscribe = this.on(event, ((data) => {
|
|
@@ -1310,24 +1418,13 @@
|
|
|
1310
1418
|
}
|
|
1311
1419
|
off(event, handler) {
|
|
1312
1420
|
if (!handler) {
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
(h) => h.handler !== val.transportHandler
|
|
1319
|
-
);
|
|
1320
|
-
this.handlerToTransport.delete(key);
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1323
|
-
} else {
|
|
1324
|
-
this.eventListeners.delete(event);
|
|
1325
|
-
this.transport?.off(event);
|
|
1326
|
-
this.registeredHandlers = this.registeredHandlers.filter((h) => h.event !== event);
|
|
1327
|
-
for (const [key, val] of this.handlerToTransport) {
|
|
1328
|
-
if (val.event === event) this.handlerToTransport.delete(key);
|
|
1329
|
-
}
|
|
1421
|
+
this.eventListeners.delete(event);
|
|
1422
|
+
this.transport?.off(event);
|
|
1423
|
+
this.registeredHandlers = this.registeredHandlers.filter((h) => h.event !== event);
|
|
1424
|
+
for (const [key, val] of this.handlerToTransport) {
|
|
1425
|
+
if (val.event === event) this.handlerToTransport.delete(key);
|
|
1330
1426
|
}
|
|
1427
|
+
this._pendingHandlers = this._pendingHandlers.filter((p) => p.event !== event);
|
|
1331
1428
|
} else {
|
|
1332
1429
|
const listeners = this.eventListeners.get(event);
|
|
1333
1430
|
listeners?.delete(handler);
|
|
@@ -1342,6 +1439,9 @@
|
|
|
1342
1439
|
);
|
|
1343
1440
|
this.handlerToTransport.delete(handler);
|
|
1344
1441
|
}
|
|
1442
|
+
this._pendingHandlers = this._pendingHandlers.filter(
|
|
1443
|
+
(p) => !(p.event === event && p.handler === handler)
|
|
1444
|
+
);
|
|
1345
1445
|
}
|
|
1346
1446
|
}
|
|
1347
1447
|
// ---------------------------------------------------------------------------
|
|
@@ -1350,10 +1450,15 @@
|
|
|
1350
1450
|
destroy() {
|
|
1351
1451
|
if (this._isDestroyed) return;
|
|
1352
1452
|
this.logger.lifecycle("Destroying controller");
|
|
1353
|
-
this.cleanup();
|
|
1354
1453
|
this._isDestroyed = true;
|
|
1454
|
+
this._isReady = false;
|
|
1455
|
+
this.cleanup();
|
|
1355
1456
|
}
|
|
1356
1457
|
cleanup() {
|
|
1458
|
+
if (this._initTimeoutId) {
|
|
1459
|
+
clearTimeout(this._initTimeoutId);
|
|
1460
|
+
this._initTimeoutId = null;
|
|
1461
|
+
}
|
|
1357
1462
|
this._isReady = false;
|
|
1358
1463
|
for (const { event, handler } of this.registeredHandlers) {
|
|
1359
1464
|
this.transport?.off(event, handler);
|
|
@@ -1361,6 +1466,15 @@
|
|
|
1361
1466
|
this.registeredHandlers = [];
|
|
1362
1467
|
this.eventListeners.clear();
|
|
1363
1468
|
this.handlerToTransport.clear();
|
|
1469
|
+
this._pendingHandlers = [];
|
|
1470
|
+
this._onAllReadyCallbacks.clear();
|
|
1471
|
+
this._onControllerJoinCallbacks.clear();
|
|
1472
|
+
this._onControllerLeaveCallbacks.clear();
|
|
1473
|
+
this._onControllerDisconnectCallbacks.clear();
|
|
1474
|
+
this._onControllerReconnectCallbacks.clear();
|
|
1475
|
+
this._onCharacterUpdatedCallbacks.clear();
|
|
1476
|
+
this._onRateLimitedCallbacks.clear();
|
|
1477
|
+
this._onErrorCallbacks.clear();
|
|
1364
1478
|
if (this.transport) {
|
|
1365
1479
|
this.transport.destroy();
|
|
1366
1480
|
this.transport = null;
|
|
@@ -1384,15 +1498,16 @@
|
|
|
1384
1498
|
if (!this._isReady || !this.transport) {
|
|
1385
1499
|
throw new SmoreSDKError(
|
|
1386
1500
|
"NOT_READY",
|
|
1387
|
-
`Cannot call ${method}() before controller is ready. Use await
|
|
1501
|
+
`Cannot call ${method}() before controller is ready. Use await controller.ready.`,
|
|
1388
1502
|
{ details: { method, isReady: this._isReady } }
|
|
1389
1503
|
);
|
|
1390
1504
|
}
|
|
1391
1505
|
}
|
|
1392
1506
|
handleError(error) {
|
|
1393
1507
|
this.logger.warn(`Error in handler: ${error.message}`);
|
|
1394
|
-
|
|
1395
|
-
|
|
1508
|
+
const smoreError = error.toSmoreError();
|
|
1509
|
+
if (this._onErrorCallbacks.size > 0) {
|
|
1510
|
+
this._onErrorCallbacks.forEach((cb) => cb(smoreError));
|
|
1396
1511
|
} else {
|
|
1397
1512
|
this.logger.error(error.message, error.details);
|
|
1398
1513
|
}
|
|
@@ -1405,49 +1520,32 @@
|
|
|
1405
1520
|
}
|
|
1406
1521
|
}
|
|
1407
1522
|
function createController(config) {
|
|
1408
|
-
|
|
1409
|
-
const promise = controller.initialize().then(() => controller);
|
|
1410
|
-
promise.instance = controller;
|
|
1411
|
-
return promise;
|
|
1523
|
+
return new ControllerImpl(config ?? {});
|
|
1412
1524
|
}
|
|
1413
1525
|
|
|
1414
1526
|
function createMockScreen(options = {}) {
|
|
1415
1527
|
const {
|
|
1416
1528
|
roomCode = "TEST",
|
|
1417
1529
|
controllers: initialControllers = [],
|
|
1418
|
-
autoReady = true
|
|
1419
|
-
onReady: onReadyCb,
|
|
1420
|
-
onControllerJoin: onJoinCb,
|
|
1421
|
-
onControllerLeave: onLeaveCb,
|
|
1422
|
-
onControllerDisconnect: onDisconnectCb,
|
|
1423
|
-
onControllerReconnect: onReconnectCb,
|
|
1424
|
-
onCharacterUpdated: onCharacterUpdatedCb,
|
|
1425
|
-
onRateLimited: onRateLimitedCb,
|
|
1426
|
-
onAllReady: onAllReadyCb,
|
|
1427
|
-
onError: onErrorCb
|
|
1530
|
+
autoReady = true
|
|
1428
1531
|
} = options;
|
|
1429
1532
|
let _controllers = [...initialControllers];
|
|
1430
1533
|
let _isReady = false;
|
|
1431
1534
|
let _isDestroyed = false;
|
|
1432
1535
|
const listeners = /* @__PURE__ */ new Map();
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
let
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
onControllerReconnectCallback = onReconnectCb;
|
|
1447
|
-
onCharacterUpdatedCallback = onCharacterUpdatedCb;
|
|
1448
|
-
onRateLimitedCallback = onRateLimitedCb;
|
|
1449
|
-
onAllReadyCallback = onAllReadyCb;
|
|
1450
|
-
onErrorCallback = onErrorCb;
|
|
1536
|
+
const _onAllReadyCallbacks = /* @__PURE__ */ new Set();
|
|
1537
|
+
const _onControllerJoinCallbacks = /* @__PURE__ */ new Set();
|
|
1538
|
+
const _onControllerLeaveCallbacks = /* @__PURE__ */ new Set();
|
|
1539
|
+
const _onControllerDisconnectCallbacks = /* @__PURE__ */ new Set();
|
|
1540
|
+
const _onControllerReconnectCallbacks = /* @__PURE__ */ new Set();
|
|
1541
|
+
const _onCharacterUpdatedCallbacks = /* @__PURE__ */ new Set();
|
|
1542
|
+
const _onRateLimitedCallbacks = /* @__PURE__ */ new Set();
|
|
1543
|
+
const _onErrorCallbacks = /* @__PURE__ */ new Set();
|
|
1544
|
+
let _allReadyFired = false;
|
|
1545
|
+
let _readyResolve;
|
|
1546
|
+
const _readyPromise = new Promise((resolve) => {
|
|
1547
|
+
_readyResolve = resolve;
|
|
1548
|
+
});
|
|
1451
1549
|
const broadcasts = [];
|
|
1452
1550
|
const sends = [];
|
|
1453
1551
|
const screen = {
|
|
@@ -1464,6 +1562,61 @@
|
|
|
1464
1562
|
get isDestroyed() {
|
|
1465
1563
|
return _isDestroyed;
|
|
1466
1564
|
},
|
|
1565
|
+
get ready() {
|
|
1566
|
+
return _readyPromise;
|
|
1567
|
+
},
|
|
1568
|
+
// === Lifecycle Methods ===
|
|
1569
|
+
onAllReady(callback) {
|
|
1570
|
+
if (_allReadyFired) {
|
|
1571
|
+
callback();
|
|
1572
|
+
}
|
|
1573
|
+
_onAllReadyCallbacks.add(callback);
|
|
1574
|
+
return () => {
|
|
1575
|
+
_onAllReadyCallbacks.delete(callback);
|
|
1576
|
+
};
|
|
1577
|
+
},
|
|
1578
|
+
onControllerJoin(callback) {
|
|
1579
|
+
_onControllerJoinCallbacks.add(callback);
|
|
1580
|
+
return () => {
|
|
1581
|
+
_onControllerJoinCallbacks.delete(callback);
|
|
1582
|
+
};
|
|
1583
|
+
},
|
|
1584
|
+
onControllerLeave(callback) {
|
|
1585
|
+
_onControllerLeaveCallbacks.add(callback);
|
|
1586
|
+
return () => {
|
|
1587
|
+
_onControllerLeaveCallbacks.delete(callback);
|
|
1588
|
+
};
|
|
1589
|
+
},
|
|
1590
|
+
onControllerDisconnect(callback) {
|
|
1591
|
+
_onControllerDisconnectCallbacks.add(callback);
|
|
1592
|
+
return () => {
|
|
1593
|
+
_onControllerDisconnectCallbacks.delete(callback);
|
|
1594
|
+
};
|
|
1595
|
+
},
|
|
1596
|
+
onControllerReconnect(callback) {
|
|
1597
|
+
_onControllerReconnectCallbacks.add(callback);
|
|
1598
|
+
return () => {
|
|
1599
|
+
_onControllerReconnectCallbacks.delete(callback);
|
|
1600
|
+
};
|
|
1601
|
+
},
|
|
1602
|
+
onCharacterUpdated(callback) {
|
|
1603
|
+
_onCharacterUpdatedCallbacks.add(callback);
|
|
1604
|
+
return () => {
|
|
1605
|
+
_onCharacterUpdatedCallbacks.delete(callback);
|
|
1606
|
+
};
|
|
1607
|
+
},
|
|
1608
|
+
onRateLimited(callback) {
|
|
1609
|
+
_onRateLimitedCallbacks.add(callback);
|
|
1610
|
+
return () => {
|
|
1611
|
+
_onRateLimitedCallbacks.delete(callback);
|
|
1612
|
+
};
|
|
1613
|
+
},
|
|
1614
|
+
onError(callback) {
|
|
1615
|
+
_onErrorCallbacks.add(callback);
|
|
1616
|
+
return () => {
|
|
1617
|
+
_onErrorCallbacks.delete(callback);
|
|
1618
|
+
};
|
|
1619
|
+
},
|
|
1467
1620
|
// === Communication Methods ===
|
|
1468
1621
|
broadcast(event, data) {
|
|
1469
1622
|
if (_isDestroyed) {
|
|
@@ -1591,24 +1744,14 @@
|
|
|
1591
1744
|
},
|
|
1592
1745
|
simulateControllerJoin(info) {
|
|
1593
1746
|
_controllers.push(info);
|
|
1594
|
-
|
|
1595
|
-
onControllerJoinCallback(info.playerIndex, info);
|
|
1596
|
-
}
|
|
1747
|
+
_onControllerJoinCallbacks.forEach((cb) => cb(info.playerIndex, info));
|
|
1597
1748
|
},
|
|
1598
1749
|
simulateControllerLeave(playerIndex) {
|
|
1599
1750
|
_controllers = _controllers.filter((c) => c.playerIndex !== playerIndex);
|
|
1600
|
-
|
|
1601
|
-
onControllerLeaveCallback(playerIndex);
|
|
1602
|
-
}
|
|
1751
|
+
_onControllerLeaveCallbacks.forEach((cb) => cb(playerIndex));
|
|
1603
1752
|
},
|
|
1604
1753
|
/**
|
|
1605
1754
|
* Simulate a controller network disconnect (player still in room but unreachable).
|
|
1606
|
-
*
|
|
1607
|
-
* @example
|
|
1608
|
-
* ```ts
|
|
1609
|
-
* screen.simulateControllerDisconnect(0);
|
|
1610
|
-
* expect(screen.getController(0)?.connected).toBe(false);
|
|
1611
|
-
* ```
|
|
1612
1755
|
*/
|
|
1613
1756
|
simulateControllerDisconnect(playerIndex) {
|
|
1614
1757
|
const controller = _controllers.find((c) => c.playerIndex === playerIndex);
|
|
@@ -1618,19 +1761,10 @@
|
|
|
1618
1761
|
_controllers = _controllers.map(
|
|
1619
1762
|
(c) => c.playerIndex === playerIndex ? { ...c, connected: false } : c
|
|
1620
1763
|
);
|
|
1621
|
-
|
|
1622
|
-
onControllerDisconnectCallback(playerIndex);
|
|
1623
|
-
}
|
|
1764
|
+
_onControllerDisconnectCallbacks.forEach((cb) => cb(playerIndex));
|
|
1624
1765
|
},
|
|
1625
1766
|
/**
|
|
1626
1767
|
* Simulate a controller network reconnect after disconnect.
|
|
1627
|
-
*
|
|
1628
|
-
* @example
|
|
1629
|
-
* ```ts
|
|
1630
|
-
* screen.simulateControllerDisconnect(0);
|
|
1631
|
-
* screen.simulateControllerReconnect(0);
|
|
1632
|
-
* expect(screen.getController(0)?.connected).toBe(true);
|
|
1633
|
-
* ```
|
|
1634
1768
|
*/
|
|
1635
1769
|
simulateControllerReconnect(playerIndex) {
|
|
1636
1770
|
const controller = _controllers.find((c) => c.playerIndex === playerIndex);
|
|
@@ -1641,9 +1775,7 @@
|
|
|
1641
1775
|
_controllers = _controllers.map(
|
|
1642
1776
|
(c) => c.playerIndex === playerIndex ? reconnectedController : c
|
|
1643
1777
|
);
|
|
1644
|
-
|
|
1645
|
-
onControllerReconnectCallback(playerIndex, reconnectedController);
|
|
1646
|
-
}
|
|
1778
|
+
_onControllerReconnectCallbacks.forEach((cb) => cb(playerIndex, reconnectedController));
|
|
1647
1779
|
},
|
|
1648
1780
|
simulateCharacterUpdate(playerIndex, appearance) {
|
|
1649
1781
|
const controller = _controllers.find((c) => c.playerIndex === playerIndex);
|
|
@@ -1653,24 +1785,17 @@
|
|
|
1653
1785
|
_controllers = _controllers.map(
|
|
1654
1786
|
(c) => c.playerIndex === playerIndex ? { ...c, appearance } : c
|
|
1655
1787
|
);
|
|
1656
|
-
|
|
1657
|
-
onCharacterUpdatedCallback(playerIndex, appearance);
|
|
1658
|
-
}
|
|
1788
|
+
_onCharacterUpdatedCallbacks.forEach((cb) => cb(playerIndex, appearance));
|
|
1659
1789
|
},
|
|
1660
1790
|
simulateRateLimited(event) {
|
|
1661
|
-
|
|
1662
|
-
onRateLimitedCallback(event);
|
|
1663
|
-
}
|
|
1791
|
+
_onRateLimitedCallbacks.forEach((cb) => cb(event));
|
|
1664
1792
|
},
|
|
1665
1793
|
simulateAllReady() {
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
}
|
|
1794
|
+
_allReadyFired = true;
|
|
1795
|
+
_onAllReadyCallbacks.forEach((cb) => cb());
|
|
1669
1796
|
},
|
|
1670
1797
|
simulateError(error) {
|
|
1671
|
-
|
|
1672
|
-
onErrorCallback(error);
|
|
1673
|
-
}
|
|
1798
|
+
_onErrorCallbacks.forEach((cb) => cb(error));
|
|
1674
1799
|
},
|
|
1675
1800
|
getBroadcasts() {
|
|
1676
1801
|
return [...broadcasts];
|
|
@@ -1684,9 +1809,7 @@
|
|
|
1684
1809
|
},
|
|
1685
1810
|
triggerReady() {
|
|
1686
1811
|
_isReady = true;
|
|
1687
|
-
|
|
1688
|
-
onReadyCallback();
|
|
1689
|
-
}
|
|
1812
|
+
_readyResolve();
|
|
1690
1813
|
}
|
|
1691
1814
|
};
|
|
1692
1815
|
if (autoReady) {
|
|
@@ -1698,39 +1821,25 @@
|
|
|
1698
1821
|
const {
|
|
1699
1822
|
roomCode = "TEST",
|
|
1700
1823
|
myIndex = 0,
|
|
1701
|
-
autoReady = true
|
|
1702
|
-
onReady: onReadyCb,
|
|
1703
|
-
onControllerJoin: onJoinCb,
|
|
1704
|
-
onControllerLeave: onLeaveCb,
|
|
1705
|
-
onControllerDisconnect: onDisconnectCb,
|
|
1706
|
-
onControllerReconnect: onReconnectCb,
|
|
1707
|
-
onCharacterUpdated: onCharacterUpdatedCb,
|
|
1708
|
-
onRateLimited: onRateLimitedCb,
|
|
1709
|
-
onAllReady: onAllReadyCb,
|
|
1710
|
-
onError: onErrorCb
|
|
1824
|
+
autoReady = true
|
|
1711
1825
|
} = options;
|
|
1712
1826
|
let _isReady = false;
|
|
1713
1827
|
let _isDestroyed = false;
|
|
1714
1828
|
let _controllers = options.controllers ?? [];
|
|
1715
1829
|
const listeners = /* @__PURE__ */ new Map();
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
let
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
onControllerReconnectCallback = onReconnectCb;
|
|
1730
|
-
onCharacterUpdatedCallback = onCharacterUpdatedCb;
|
|
1731
|
-
onRateLimitedCallback = onRateLimitedCb;
|
|
1732
|
-
onAllReadyCallback = onAllReadyCb;
|
|
1733
|
-
onErrorCallback = onErrorCb;
|
|
1830
|
+
const _onAllReadyCallbacks = /* @__PURE__ */ new Set();
|
|
1831
|
+
const _onControllerJoinCallbacks = /* @__PURE__ */ new Set();
|
|
1832
|
+
const _onControllerLeaveCallbacks = /* @__PURE__ */ new Set();
|
|
1833
|
+
const _onControllerDisconnectCallbacks = /* @__PURE__ */ new Set();
|
|
1834
|
+
const _onControllerReconnectCallbacks = /* @__PURE__ */ new Set();
|
|
1835
|
+
const _onCharacterUpdatedCallbacks = /* @__PURE__ */ new Set();
|
|
1836
|
+
const _onRateLimitedCallbacks = /* @__PURE__ */ new Set();
|
|
1837
|
+
const _onErrorCallbacks = /* @__PURE__ */ new Set();
|
|
1838
|
+
let _allReadyFired = false;
|
|
1839
|
+
let _readyResolve;
|
|
1840
|
+
const _readyPromise = new Promise((resolve) => {
|
|
1841
|
+
_readyResolve = resolve;
|
|
1842
|
+
});
|
|
1734
1843
|
const sentEvents = [];
|
|
1735
1844
|
const controller = {
|
|
1736
1845
|
// === Properties ===
|
|
@@ -1749,6 +1858,61 @@
|
|
|
1749
1858
|
get controllers() {
|
|
1750
1859
|
return [..._controllers];
|
|
1751
1860
|
},
|
|
1861
|
+
get ready() {
|
|
1862
|
+
return _readyPromise;
|
|
1863
|
+
},
|
|
1864
|
+
// === Lifecycle Methods ===
|
|
1865
|
+
onAllReady(callback) {
|
|
1866
|
+
if (_allReadyFired) {
|
|
1867
|
+
callback();
|
|
1868
|
+
}
|
|
1869
|
+
_onAllReadyCallbacks.add(callback);
|
|
1870
|
+
return () => {
|
|
1871
|
+
_onAllReadyCallbacks.delete(callback);
|
|
1872
|
+
};
|
|
1873
|
+
},
|
|
1874
|
+
onControllerJoin(callback) {
|
|
1875
|
+
_onControllerJoinCallbacks.add(callback);
|
|
1876
|
+
return () => {
|
|
1877
|
+
_onControllerJoinCallbacks.delete(callback);
|
|
1878
|
+
};
|
|
1879
|
+
},
|
|
1880
|
+
onControllerLeave(callback) {
|
|
1881
|
+
_onControllerLeaveCallbacks.add(callback);
|
|
1882
|
+
return () => {
|
|
1883
|
+
_onControllerLeaveCallbacks.delete(callback);
|
|
1884
|
+
};
|
|
1885
|
+
},
|
|
1886
|
+
onControllerDisconnect(callback) {
|
|
1887
|
+
_onControllerDisconnectCallbacks.add(callback);
|
|
1888
|
+
return () => {
|
|
1889
|
+
_onControllerDisconnectCallbacks.delete(callback);
|
|
1890
|
+
};
|
|
1891
|
+
},
|
|
1892
|
+
onControllerReconnect(callback) {
|
|
1893
|
+
_onControllerReconnectCallbacks.add(callback);
|
|
1894
|
+
return () => {
|
|
1895
|
+
_onControllerReconnectCallbacks.delete(callback);
|
|
1896
|
+
};
|
|
1897
|
+
},
|
|
1898
|
+
onCharacterUpdated(callback) {
|
|
1899
|
+
_onCharacterUpdatedCallbacks.add(callback);
|
|
1900
|
+
return () => {
|
|
1901
|
+
_onCharacterUpdatedCallbacks.delete(callback);
|
|
1902
|
+
};
|
|
1903
|
+
},
|
|
1904
|
+
onRateLimited(callback) {
|
|
1905
|
+
_onRateLimitedCallbacks.add(callback);
|
|
1906
|
+
return () => {
|
|
1907
|
+
_onRateLimitedCallbacks.delete(callback);
|
|
1908
|
+
};
|
|
1909
|
+
},
|
|
1910
|
+
onError(callback) {
|
|
1911
|
+
_onErrorCallbacks.add(callback);
|
|
1912
|
+
return () => {
|
|
1913
|
+
_onErrorCallbacks.delete(callback);
|
|
1914
|
+
};
|
|
1915
|
+
},
|
|
1752
1916
|
getControllerCount() {
|
|
1753
1917
|
return _controllers.filter((c) => c.connected).length;
|
|
1754
1918
|
},
|
|
@@ -1829,105 +1993,62 @@
|
|
|
1829
1993
|
},
|
|
1830
1994
|
triggerReady() {
|
|
1831
1995
|
_isReady = true;
|
|
1832
|
-
|
|
1833
|
-
onReadyCallback();
|
|
1834
|
-
}
|
|
1996
|
+
_readyResolve();
|
|
1835
1997
|
},
|
|
1836
1998
|
/**
|
|
1837
1999
|
* Simulate a new player joining the room.
|
|
1838
2000
|
* Stores full ControllerInfo (nickname, appearance, etc.) for later retrieval.
|
|
1839
|
-
*
|
|
1840
|
-
* @example
|
|
1841
|
-
* ```ts
|
|
1842
|
-
* controller.simulatePlayerJoin(2, { playerIndex: 2, nickname: 'Alice', connected: true });
|
|
1843
|
-
* expect(controller.getControllerCount()).toBe(3);
|
|
1844
|
-
* expect(controller.controllers.find(c => c.playerIndex === 2)?.nickname).toBe('Alice');
|
|
1845
|
-
* ```
|
|
1846
2001
|
*/
|
|
1847
2002
|
simulatePlayerJoin(playerIndex, info) {
|
|
1848
2003
|
if (!_controllers.some((c) => c.playerIndex === playerIndex)) {
|
|
1849
2004
|
_controllers = [..._controllers, { ...info, connected: info.connected ?? true }];
|
|
1850
2005
|
}
|
|
1851
|
-
|
|
1852
|
-
onControllerJoinCallback(playerIndex, info);
|
|
1853
|
-
}
|
|
2006
|
+
_onControllerJoinCallbacks.forEach((cb) => cb(playerIndex, info));
|
|
1854
2007
|
},
|
|
1855
2008
|
/**
|
|
1856
2009
|
* Simulate a player leaving the room (fully removed).
|
|
1857
|
-
*
|
|
1858
|
-
* @example
|
|
1859
|
-
* ```ts
|
|
1860
|
-
* controller.simulatePlayerLeave(1);
|
|
1861
|
-
* expect(controller.controllers.some(c => c.playerIndex === 1)).toBe(false);
|
|
1862
|
-
* ```
|
|
1863
2010
|
*/
|
|
1864
2011
|
simulatePlayerLeave(playerIndex) {
|
|
1865
2012
|
_controllers = _controllers.filter((c) => c.playerIndex !== playerIndex);
|
|
1866
|
-
|
|
1867
|
-
onControllerLeaveCallback(playerIndex);
|
|
1868
|
-
}
|
|
2013
|
+
_onControllerLeaveCallbacks.forEach((cb) => cb(playerIndex));
|
|
1869
2014
|
},
|
|
1870
2015
|
/**
|
|
1871
2016
|
* Simulate a player network disconnect (player still in room but unreachable).
|
|
1872
|
-
*
|
|
1873
|
-
* @example
|
|
1874
|
-
* ```ts
|
|
1875
|
-
* controller.simulatePlayerDisconnect(1);
|
|
1876
|
-
* expect(controller.controllers.find(c => c.playerIndex === 1)?.connected).toBe(false);
|
|
1877
|
-
* ```
|
|
1878
2017
|
*/
|
|
1879
2018
|
simulatePlayerDisconnect(playerIndex) {
|
|
1880
2019
|
_controllers = _controllers.map(
|
|
1881
2020
|
(c) => c.playerIndex === playerIndex ? { ...c, connected: false } : c
|
|
1882
2021
|
);
|
|
1883
|
-
|
|
1884
|
-
onControllerDisconnectCallback(playerIndex);
|
|
1885
|
-
}
|
|
2022
|
+
_onControllerDisconnectCallbacks.forEach((cb) => cb(playerIndex));
|
|
1886
2023
|
},
|
|
1887
2024
|
/**
|
|
1888
2025
|
* Simulate a player network reconnect after disconnect.
|
|
1889
|
-
*
|
|
1890
|
-
* @example
|
|
1891
|
-
* ```ts
|
|
1892
|
-
* controller.simulatePlayerDisconnect(1);
|
|
1893
|
-
* controller.simulatePlayerReconnect(1, { playerIndex: 1, nickname: 'Bob', connected: true });
|
|
1894
|
-
* expect(controller.controllers.find(c => c.playerIndex === 1)?.connected).toBe(true);
|
|
1895
|
-
* ```
|
|
1896
2026
|
*/
|
|
1897
2027
|
simulatePlayerReconnect(playerIndex, info) {
|
|
1898
2028
|
_controllers = _controllers.map(
|
|
1899
2029
|
(c) => c.playerIndex === playerIndex ? { ...info, connected: true } : c
|
|
1900
2030
|
);
|
|
1901
|
-
|
|
1902
|
-
onControllerReconnectCallback(playerIndex, info);
|
|
1903
|
-
}
|
|
2031
|
+
_onControllerReconnectCallbacks.forEach((cb) => cb(playerIndex, info));
|
|
1904
2032
|
},
|
|
1905
2033
|
simulateCharacterUpdate(playerIndex, appearance) {
|
|
1906
|
-
const
|
|
1907
|
-
if (!
|
|
2034
|
+
const ctrl = _controllers.find((c) => c.playerIndex === playerIndex);
|
|
2035
|
+
if (!ctrl) {
|
|
1908
2036
|
throw new Error(`Controller ${playerIndex} not found`);
|
|
1909
2037
|
}
|
|
1910
2038
|
_controllers = _controllers.map(
|
|
1911
2039
|
(c) => c.playerIndex === playerIndex ? { ...c, appearance } : c
|
|
1912
2040
|
);
|
|
1913
|
-
|
|
1914
|
-
onCharacterUpdatedCallback(playerIndex, appearance);
|
|
1915
|
-
}
|
|
2041
|
+
_onCharacterUpdatedCallbacks.forEach((cb) => cb(playerIndex, appearance));
|
|
1916
2042
|
},
|
|
1917
2043
|
simulateRateLimited(event) {
|
|
1918
|
-
|
|
1919
|
-
onRateLimitedCallback(event);
|
|
1920
|
-
}
|
|
2044
|
+
_onRateLimitedCallbacks.forEach((cb) => cb(event));
|
|
1921
2045
|
},
|
|
1922
2046
|
simulateAllReady() {
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
}
|
|
2047
|
+
_allReadyFired = true;
|
|
2048
|
+
_onAllReadyCallbacks.forEach((cb) => cb());
|
|
1926
2049
|
},
|
|
1927
2050
|
simulateError(error) {
|
|
1928
|
-
|
|
1929
|
-
onErrorCallback(error);
|
|
1930
|
-
}
|
|
2051
|
+
_onErrorCallbacks.forEach((cb) => cb(error));
|
|
1931
2052
|
}
|
|
1932
2053
|
};
|
|
1933
2054
|
if (autoReady) {
|