@smoregg/sdk 2.0.0 → 2.1.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 +193 -115
- package/dist/cjs/controller.cjs.map +1 -1
- package/dist/cjs/errors.cjs +1 -0
- package/dist/cjs/errors.cjs.map +1 -1
- package/dist/cjs/events.cjs +19 -2
- package/dist/cjs/events.cjs.map +1 -1
- package/dist/cjs/index.cjs +2 -7
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/screen.cjs +185 -130
- package/dist/cjs/screen.cjs.map +1 -1
- package/dist/cjs/shared.cjs +34 -0
- package/dist/cjs/shared.cjs.map +1 -0
- package/dist/cjs/testing.cjs +125 -74
- package/dist/cjs/testing.cjs.map +1 -1
- package/dist/cjs/transport/PostMessageTransport.cjs +12 -0
- package/dist/cjs/transport/PostMessageTransport.cjs.map +1 -1
- package/dist/cjs/transport/protocol.cjs +2 -0
- package/dist/cjs/transport/protocol.cjs.map +1 -1
- package/dist/cjs/types.cjs +16 -0
- package/dist/cjs/types.cjs.map +1 -0
- package/dist/esm/controller.js +195 -117
- package/dist/esm/controller.js.map +1 -1
- package/dist/esm/errors.js +1 -0
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/events.js +18 -3
- package/dist/esm/events.js.map +1 -1
- package/dist/esm/index.js +1 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/screen.js +187 -132
- package/dist/esm/screen.js.map +1 -1
- package/dist/esm/shared.js +30 -0
- package/dist/esm/shared.js.map +1 -0
- package/dist/esm/testing.js +125 -74
- package/dist/esm/testing.js.map +1 -1
- package/dist/esm/transport/PostMessageTransport.js +12 -0
- package/dist/esm/transport/PostMessageTransport.js.map +1 -1
- package/dist/esm/transport/protocol.js +2 -1
- package/dist/esm/transport/protocol.js.map +1 -1
- package/dist/esm/types.js +14 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/types/controller.d.ts +1 -1
- package/dist/types/controller.d.ts.map +1 -1
- package/dist/types/errors.d.ts.map +1 -1
- package/dist/types/events.d.ts +10 -1
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/index.d.ts +4 -8
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/screen.d.ts +3 -3
- package/dist/types/screen.d.ts.map +1 -1
- package/dist/types/shared.d.ts +21 -0
- package/dist/types/shared.d.ts.map +1 -0
- package/dist/types/testing.d.ts +63 -4
- package/dist/types/testing.d.ts.map +1 -1
- package/dist/types/transport/PostMessageTransport.d.ts +1 -0
- package/dist/types/transport/PostMessageTransport.d.ts.map +1 -1
- package/dist/types/transport/protocol.d.ts +4 -0
- package/dist/types/transport/protocol.d.ts.map +1 -1
- package/dist/types/types.d.ts +215 -347
- package/dist/types/types.d.ts.map +1 -1
- package/dist/umd/smore-sdk.umd.js +442 -787
- 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 +7 -1
- package/dist/cjs/config.cjs +0 -13
- package/dist/cjs/config.cjs.map +0 -1
- package/dist/esm/config.js +0 -10
- package/dist/esm/config.js.map +0 -1
- package/dist/types/config.d.ts +0 -35
- package/dist/types/config.d.ts.map +0 -1
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
})(this, (function (exports) { 'use strict';
|
|
6
6
|
|
|
7
7
|
const BRIDGE_MSG_PREFIX = "_bridge:";
|
|
8
|
+
const PROTOCOL_VERSION = 1;
|
|
8
9
|
function isBridgeMessage(data) {
|
|
9
10
|
return data !== null && typeof data === "object" && "type" in data && typeof data.type === "string" && data.type.startsWith(BRIDGE_MSG_PREFIX);
|
|
10
11
|
}
|
|
@@ -29,6 +30,8 @@
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
class PostMessageTransport {
|
|
33
|
+
static ACK_TIMEOUT = 3e4;
|
|
34
|
+
// 30 seconds
|
|
32
35
|
handlers = /* @__PURE__ */ new Map();
|
|
33
36
|
ackCallbacks = /* @__PURE__ */ new Map();
|
|
34
37
|
ackCounter = 0;
|
|
@@ -47,11 +50,21 @@
|
|
|
47
50
|
const callback = args[0];
|
|
48
51
|
ackId = `ack_${++this.ackCounter}`;
|
|
49
52
|
this.ackCallbacks.set(ackId, callback);
|
|
53
|
+
setTimeout(() => {
|
|
54
|
+
if (this.ackCallbacks.has(ackId)) {
|
|
55
|
+
this.ackCallbacks.delete(ackId);
|
|
56
|
+
}
|
|
57
|
+
}, PostMessageTransport.ACK_TIMEOUT);
|
|
50
58
|
} else if (args.length >= 2 && typeof args[args.length - 1] === "function") {
|
|
51
59
|
data = args[0];
|
|
52
60
|
const callback = args[args.length - 1];
|
|
53
61
|
ackId = `ack_${++this.ackCounter}`;
|
|
54
62
|
this.ackCallbacks.set(ackId, callback);
|
|
63
|
+
setTimeout(() => {
|
|
64
|
+
if (this.ackCallbacks.has(ackId)) {
|
|
65
|
+
this.ackCallbacks.delete(ackId);
|
|
66
|
+
}
|
|
67
|
+
}, PostMessageTransport.ACK_TIMEOUT);
|
|
55
68
|
}
|
|
56
69
|
window.parent.postMessage(
|
|
57
70
|
{ type: "_bridge:emit", payload: { event, data, ackId } },
|
|
@@ -115,6 +128,7 @@
|
|
|
115
128
|
super(message, options?.cause ? { cause: options.cause } : void 0);
|
|
116
129
|
this.name = "SmoreSDKError";
|
|
117
130
|
this.code = code;
|
|
131
|
+
this.cause = options?.cause;
|
|
118
132
|
this.details = options?.details;
|
|
119
133
|
const ErrorWithCapture = Error;
|
|
120
134
|
if (typeof ErrorWithCapture.captureStackTrace === "function") {
|
|
@@ -134,8 +148,6 @@
|
|
|
134
148
|
const SMORE_EVENTS = {
|
|
135
149
|
// Game lifecycle
|
|
136
150
|
GAME_OVER: "smore:game-over",
|
|
137
|
-
RETURN_TO_LOBBY: "smore:return-to-lobby",
|
|
138
|
-
// Used internally by platform, not handled by SDK
|
|
139
151
|
// Player management
|
|
140
152
|
PLAYER_JOINED: "smore:player-joined",
|
|
141
153
|
PLAYER_LEFT: "smore:player-left",
|
|
@@ -148,6 +160,9 @@
|
|
|
148
160
|
// Game ready sync
|
|
149
161
|
GAME_READY: "smore:game-ready",
|
|
150
162
|
ALL_READY: "smore:all-ready",
|
|
163
|
+
// Connection status (self)
|
|
164
|
+
SELF_DISCONNECTED: "smore:self-disconnected",
|
|
165
|
+
SELF_RECONNECTED: "smore:self-reconnected",
|
|
151
166
|
// Send to specific player (internal use)
|
|
152
167
|
SEND_TO_PLAYER: "smore:send-to-player"
|
|
153
168
|
// Used internally by platform, not handled by SDK
|
|
@@ -176,6 +191,20 @@
|
|
|
176
191
|
);
|
|
177
192
|
}
|
|
178
193
|
}
|
|
194
|
+
const SCREEN_LIFECYCLE_EVENTS = /* @__PURE__ */ new Set([
|
|
195
|
+
"$all-ready",
|
|
196
|
+
"$controller-join",
|
|
197
|
+
"$controller-leave",
|
|
198
|
+
"$controller-disconnect",
|
|
199
|
+
"$controller-reconnect",
|
|
200
|
+
"$character-updated",
|
|
201
|
+
"$error",
|
|
202
|
+
"$connection-change"
|
|
203
|
+
]);
|
|
204
|
+
const CONTROLLER_LIFECYCLE_EVENTS = /* @__PURE__ */ new Set([
|
|
205
|
+
...SCREEN_LIFECYCLE_EVENTS,
|
|
206
|
+
"$game-over"
|
|
207
|
+
]);
|
|
179
208
|
|
|
180
209
|
class DebugLogger {
|
|
181
210
|
enabled;
|
|
@@ -248,12 +277,30 @@
|
|
|
248
277
|
}
|
|
249
278
|
}
|
|
250
279
|
|
|
251
|
-
|
|
252
|
-
function
|
|
253
|
-
|
|
280
|
+
const MAX_PAYLOAD_SIZE = 65536;
|
|
281
|
+
function mapPlayerDTO(raw, fallbackIndex) {
|
|
282
|
+
return {
|
|
283
|
+
playerIndex: raw.playerIndex ?? fallbackIndex,
|
|
284
|
+
nickname: raw.nickname || raw.name || `Player ${(raw.playerIndex ?? fallbackIndex) + 1}`,
|
|
285
|
+
connected: raw.connected !== false,
|
|
286
|
+
appearance: raw.appearance ?? raw.character
|
|
287
|
+
};
|
|
254
288
|
}
|
|
255
|
-
function
|
|
256
|
-
return
|
|
289
|
+
function validatePayloadSize(data) {
|
|
290
|
+
if (data === void 0 || data === null) return;
|
|
291
|
+
try {
|
|
292
|
+
const serialized = JSON.stringify(data);
|
|
293
|
+
const byteLength = new TextEncoder().encode(serialized).byteLength;
|
|
294
|
+
if (byteLength > MAX_PAYLOAD_SIZE) {
|
|
295
|
+
throw new SmoreSDKError(
|
|
296
|
+
"PAYLOAD_TOO_LARGE",
|
|
297
|
+
`Event payload exceeds maximum size of ${MAX_PAYLOAD_SIZE} bytes (got ${byteLength} bytes). The server will silently drop this event.`,
|
|
298
|
+
{ details: { size: byteLength, limit: MAX_PAYLOAD_SIZE } }
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
} catch (err) {
|
|
302
|
+
if (err instanceof SmoreSDKError) throw err;
|
|
303
|
+
}
|
|
257
304
|
}
|
|
258
305
|
|
|
259
306
|
const DEFAULT_TIMEOUT$1 = 1e4;
|
|
@@ -285,17 +332,16 @@
|
|
|
285
332
|
handlerToTransport = /* @__PURE__ */ new Map();
|
|
286
333
|
// Pending handlers registered via on() before transport is ready
|
|
287
334
|
_pendingHandlers = [];
|
|
288
|
-
//
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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();
|
|
335
|
+
// Unified lifecycle listener map (supports both onXxx() and on('$xxx') patterns)
|
|
336
|
+
_lifecycleListeners = /* @__PURE__ */ new Map();
|
|
337
|
+
// Outbound message buffer (messages sent before ready)
|
|
338
|
+
_outboundBuffer = [];
|
|
297
339
|
// Whether all-ready has fired
|
|
298
340
|
_allReadyFired = false;
|
|
341
|
+
// Self-connection awareness
|
|
342
|
+
_isConnected = false;
|
|
343
|
+
// Protocol versioning
|
|
344
|
+
_protocolVersion = PROTOCOL_VERSION;
|
|
299
345
|
// Ready promise
|
|
300
346
|
_readyResolve;
|
|
301
347
|
_readyReject;
|
|
@@ -357,8 +403,17 @@
|
|
|
357
403
|
this._readyReject(error);
|
|
358
404
|
return;
|
|
359
405
|
}
|
|
360
|
-
this.transport = new PostMessageTransport(parentOrigin);
|
|
406
|
+
this.transport = this.config.transport ?? new PostMessageTransport(parentOrigin);
|
|
361
407
|
this._roomCode = initData.roomCode;
|
|
408
|
+
const serverProtocolVersion = initData.protocolVersion;
|
|
409
|
+
if (serverProtocolVersion !== void 0) {
|
|
410
|
+
this._protocolVersion = serverProtocolVersion;
|
|
411
|
+
if (serverProtocolVersion !== PROTOCOL_VERSION) {
|
|
412
|
+
this.logger.warn(
|
|
413
|
+
`Protocol version mismatch: SDK v${PROTOCOL_VERSION}, server v${serverProtocolVersion}. Some features may not work correctly.`
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
362
417
|
this._controllers = this.mapControllersFromInit(initData.players);
|
|
363
418
|
if (this._controllers.length === 0) {
|
|
364
419
|
this.logger.warn("Screen initialized with zero controllers");
|
|
@@ -368,13 +423,28 @@
|
|
|
368
423
|
this.setupUserEventHandler(event, handler);
|
|
369
424
|
}
|
|
370
425
|
this._pendingHandlers = [];
|
|
426
|
+
this._isConnected = true;
|
|
371
427
|
this._isReady = true;
|
|
428
|
+
for (const buffered of this._outboundBuffer) {
|
|
429
|
+
try {
|
|
430
|
+
switch (buffered.method) {
|
|
431
|
+
case "broadcast":
|
|
432
|
+
this.broadcast(buffered.args[0], buffered.args[1]);
|
|
433
|
+
break;
|
|
434
|
+
case "sendToController":
|
|
435
|
+
this.sendToController(buffered.args[0], buffered.args[1], buffered.args[2]);
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
} catch (err) {
|
|
439
|
+
this.handleError(err instanceof SmoreSDKError ? err : new SmoreSDKError("UNKNOWN", "Failed to flush buffered message"));
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
this._outboundBuffer = [];
|
|
372
443
|
this.logger.lifecycle("Screen ready", {
|
|
373
444
|
roomCode: this._roomCode,
|
|
374
445
|
controllers: this._controllers.length
|
|
375
446
|
});
|
|
376
|
-
|
|
377
|
-
if (autoReady) {
|
|
447
|
+
if (this.config.autoReady !== false) {
|
|
378
448
|
this.logger.lifecycle("Auto-signaling ready (autoReady enabled)");
|
|
379
449
|
this.signalReady();
|
|
380
450
|
}
|
|
@@ -392,13 +462,13 @@
|
|
|
392
462
|
for (const nc of newControllers) {
|
|
393
463
|
if (!oldControllers.some((oc) => oc.playerIndex === nc.playerIndex)) {
|
|
394
464
|
this.logger.lifecycle("Controller joined (via update)", { playerIndex: nc.playerIndex });
|
|
395
|
-
this.
|
|
465
|
+
this._emitLifecycle("$controller-join", nc.playerIndex, nc);
|
|
396
466
|
}
|
|
397
467
|
}
|
|
398
468
|
for (const oc of oldControllers) {
|
|
399
469
|
if (!newControllers.some((nc) => nc.playerIndex === oc.playerIndex)) {
|
|
400
470
|
this.logger.lifecycle("Controller left (via update)", { playerIndex: oc.playerIndex });
|
|
401
|
-
this.
|
|
471
|
+
this._emitLifecycle("$controller-leave", oc.playerIndex);
|
|
402
472
|
}
|
|
403
473
|
}
|
|
404
474
|
}
|
|
@@ -408,18 +478,11 @@
|
|
|
408
478
|
}
|
|
409
479
|
};
|
|
410
480
|
window.addEventListener("message", this.boundMessageHandler);
|
|
411
|
-
window.parent.postMessage({ type: "_bridge:ready" }, parentOrigin);
|
|
481
|
+
window.parent.postMessage({ type: "_bridge:ready", protocolVersion: PROTOCOL_VERSION }, parentOrigin);
|
|
412
482
|
this.logger.lifecycle("Sent _bridge:ready to parent");
|
|
413
483
|
}
|
|
414
484
|
mapControllersFromInit(players) {
|
|
415
|
-
return players.map((p, index) => (
|
|
416
|
-
playerIndex: p.playerIndex ?? index,
|
|
417
|
-
// Fallback to `nickname` for defensive compatibility (server currently always sends `name`)
|
|
418
|
-
nickname: p.nickname || p.name || `Player ${index + 1}`,
|
|
419
|
-
connected: p.connected !== false,
|
|
420
|
-
// Fallback to `appearance` for defensive compatibility (server currently always sends `character`)
|
|
421
|
-
appearance: p.appearance ?? p.character
|
|
422
|
-
}));
|
|
485
|
+
return players.map((p, index) => mapPlayerDTO(p, index));
|
|
423
486
|
}
|
|
424
487
|
setupEventHandlers() {
|
|
425
488
|
if (!this.transport) return;
|
|
@@ -427,16 +490,11 @@
|
|
|
427
490
|
const payload = data;
|
|
428
491
|
const playerData = payload?.player;
|
|
429
492
|
if (playerData && typeof playerData.playerIndex === "number") {
|
|
430
|
-
const controllerInfo =
|
|
431
|
-
playerIndex: playerData.playerIndex,
|
|
432
|
-
nickname: playerData.nickname || playerData.name || `Player ${playerData.playerIndex + 1}`,
|
|
433
|
-
connected: playerData.connected !== false,
|
|
434
|
-
appearance: playerData.appearance ?? playerData.character
|
|
435
|
-
};
|
|
493
|
+
const controllerInfo = mapPlayerDTO(playerData, playerData.playerIndex);
|
|
436
494
|
if (this._controllers.some((c) => c.playerIndex === controllerInfo.playerIndex)) return;
|
|
437
495
|
this._controllers = [...this._controllers, controllerInfo];
|
|
438
496
|
this.logger.lifecycle("Controller joined", { playerIndex: controllerInfo.playerIndex });
|
|
439
|
-
this.
|
|
497
|
+
this._emitLifecycle("$controller-join", controllerInfo.playerIndex, controllerInfo);
|
|
440
498
|
}
|
|
441
499
|
});
|
|
442
500
|
this.registerTransportHandler(SMORE_EVENTS.PLAYER_LEFT, (data) => {
|
|
@@ -446,7 +504,7 @@
|
|
|
446
504
|
if (!this._controllers.some((c) => c.playerIndex === playerIndex)) return;
|
|
447
505
|
this._controllers = this._controllers.filter((c) => c.playerIndex !== playerIndex);
|
|
448
506
|
this.logger.lifecycle("Controller left", { playerIndex });
|
|
449
|
-
this.
|
|
507
|
+
this._emitLifecycle("$controller-leave", playerIndex);
|
|
450
508
|
}
|
|
451
509
|
});
|
|
452
510
|
this.registerTransportHandler(SMORE_EVENTS.PLAYER_DISCONNECTED, (data) => {
|
|
@@ -457,24 +515,19 @@
|
|
|
457
515
|
(c) => c.playerIndex === playerIndex ? { ...c, connected: false } : c
|
|
458
516
|
);
|
|
459
517
|
this.logger.lifecycle("Controller disconnected", { playerIndex });
|
|
460
|
-
this.
|
|
518
|
+
this._emitLifecycle("$controller-disconnect", playerIndex);
|
|
461
519
|
}
|
|
462
520
|
});
|
|
463
521
|
this.registerTransportHandler(SMORE_EVENTS.PLAYER_RECONNECTED, (data) => {
|
|
464
522
|
const payload = data;
|
|
465
523
|
const playerData = payload?.player;
|
|
466
524
|
if (playerData && typeof playerData.playerIndex === "number") {
|
|
467
|
-
const controllerInfo =
|
|
468
|
-
playerIndex: playerData.playerIndex,
|
|
469
|
-
nickname: playerData.nickname || playerData.name || `Player ${playerData.playerIndex + 1}`,
|
|
470
|
-
connected: true,
|
|
471
|
-
appearance: playerData.appearance ?? playerData.character
|
|
472
|
-
};
|
|
525
|
+
const controllerInfo = mapPlayerDTO(playerData, playerData.playerIndex);
|
|
473
526
|
this._controllers = this._controllers.map(
|
|
474
527
|
(c) => c.playerIndex === controllerInfo.playerIndex ? controllerInfo : c
|
|
475
528
|
);
|
|
476
529
|
this.logger.lifecycle("Controller reconnected", { playerIndex: controllerInfo.playerIndex });
|
|
477
|
-
this.
|
|
530
|
+
this._emitLifecycle("$controller-reconnect", controllerInfo.playerIndex, controllerInfo);
|
|
478
531
|
}
|
|
479
532
|
});
|
|
480
533
|
this.registerTransportHandler(SMORE_EVENTS.PLAYER_CHARACTER_UPDATED, (data) => {
|
|
@@ -487,19 +540,32 @@
|
|
|
487
540
|
(c) => c.playerIndex === pi ? { ...c, appearance } : c
|
|
488
541
|
);
|
|
489
542
|
this.logger.lifecycle("Player character updated", { playerIndex: pi });
|
|
490
|
-
this.
|
|
543
|
+
this._emitLifecycle("$character-updated", pi, appearance ?? null);
|
|
491
544
|
}
|
|
492
545
|
});
|
|
493
546
|
this.registerTransportHandler(SMORE_EVENTS.RATE_LIMITED, (data) => {
|
|
494
547
|
const payload = data;
|
|
495
|
-
const
|
|
496
|
-
this.
|
|
497
|
-
|
|
548
|
+
const eventName = payload?.event ?? "unknown";
|
|
549
|
+
this.handleError(
|
|
550
|
+
new SmoreSDKError("RATE_LIMITED", `Server rate-limited event: ${eventName}`, {
|
|
551
|
+
details: { event: eventName }
|
|
552
|
+
})
|
|
553
|
+
);
|
|
498
554
|
});
|
|
499
555
|
this.registerTransportHandler(SMORE_EVENTS.ALL_READY, () => {
|
|
500
556
|
this.logger.lifecycle("All participants ready");
|
|
501
557
|
this._allReadyFired = true;
|
|
502
|
-
this.
|
|
558
|
+
this._emitLifecycle("$all-ready");
|
|
559
|
+
});
|
|
560
|
+
this.registerTransportHandler(SMORE_EVENTS.SELF_DISCONNECTED, () => {
|
|
561
|
+
this._isConnected = false;
|
|
562
|
+
this.logger.lifecycle("Connection lost");
|
|
563
|
+
this._emitLifecycle("$connection-change", false);
|
|
564
|
+
});
|
|
565
|
+
this.registerTransportHandler(SMORE_EVENTS.SELF_RECONNECTED, () => {
|
|
566
|
+
this._isConnected = true;
|
|
567
|
+
this.logger.lifecycle("Connection restored");
|
|
568
|
+
this._emitLifecycle("$connection-change", true);
|
|
503
569
|
});
|
|
504
570
|
}
|
|
505
571
|
/**
|
|
@@ -568,6 +634,45 @@
|
|
|
568
634
|
get isDestroyed() {
|
|
569
635
|
return this._isDestroyed;
|
|
570
636
|
}
|
|
637
|
+
get isConnected() {
|
|
638
|
+
return this._isConnected;
|
|
639
|
+
}
|
|
640
|
+
get protocolVersion() {
|
|
641
|
+
return this._protocolVersion;
|
|
642
|
+
}
|
|
643
|
+
// ---------------------------------------------------------------------------
|
|
644
|
+
// Lifecycle Listener Helpers
|
|
645
|
+
// ---------------------------------------------------------------------------
|
|
646
|
+
_addLifecycleListener(event, listener) {
|
|
647
|
+
let set = this._lifecycleListeners.get(event);
|
|
648
|
+
if (!set) {
|
|
649
|
+
set = /* @__PURE__ */ new Set();
|
|
650
|
+
this._lifecycleListeners.set(event, set);
|
|
651
|
+
}
|
|
652
|
+
set.add(listener);
|
|
653
|
+
return () => {
|
|
654
|
+
set.delete(listener);
|
|
655
|
+
if (set.size === 0) this._lifecycleListeners.delete(event);
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
_emitLifecycle(event, ...args) {
|
|
659
|
+
this._lifecycleListeners.get(event)?.forEach((cb) => {
|
|
660
|
+
try {
|
|
661
|
+
cb(...args);
|
|
662
|
+
} catch (err) {
|
|
663
|
+
this.handleError(
|
|
664
|
+
new SmoreSDKError("UNKNOWN", `Error in lifecycle handler for "${event}"`, {
|
|
665
|
+
cause: err instanceof Error ? err : void 0,
|
|
666
|
+
details: { event }
|
|
667
|
+
})
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
_hasLifecycleListeners(event) {
|
|
673
|
+
const set = this._lifecycleListeners.get(event);
|
|
674
|
+
return set !== void 0 && set.size > 0;
|
|
675
|
+
}
|
|
571
676
|
// ---------------------------------------------------------------------------
|
|
572
677
|
// Lifecycle Methods
|
|
573
678
|
// ---------------------------------------------------------------------------
|
|
@@ -575,52 +680,28 @@
|
|
|
575
680
|
if (this._allReadyFired) {
|
|
576
681
|
callback();
|
|
577
682
|
}
|
|
578
|
-
this.
|
|
579
|
-
return () => {
|
|
580
|
-
this._onAllReadyCallbacks.delete(callback);
|
|
581
|
-
};
|
|
683
|
+
return this._addLifecycleListener("$all-ready", callback);
|
|
582
684
|
}
|
|
583
685
|
onControllerJoin(callback) {
|
|
584
|
-
this.
|
|
585
|
-
return () => {
|
|
586
|
-
this._onControllerJoinCallbacks.delete(callback);
|
|
587
|
-
};
|
|
686
|
+
return this._addLifecycleListener("$controller-join", callback);
|
|
588
687
|
}
|
|
589
688
|
onControllerLeave(callback) {
|
|
590
|
-
this.
|
|
591
|
-
return () => {
|
|
592
|
-
this._onControllerLeaveCallbacks.delete(callback);
|
|
593
|
-
};
|
|
689
|
+
return this._addLifecycleListener("$controller-leave", callback);
|
|
594
690
|
}
|
|
595
691
|
onControllerDisconnect(callback) {
|
|
596
|
-
this.
|
|
597
|
-
return () => {
|
|
598
|
-
this._onControllerDisconnectCallbacks.delete(callback);
|
|
599
|
-
};
|
|
692
|
+
return this._addLifecycleListener("$controller-disconnect", callback);
|
|
600
693
|
}
|
|
601
694
|
onControllerReconnect(callback) {
|
|
602
|
-
this.
|
|
603
|
-
return () => {
|
|
604
|
-
this._onControllerReconnectCallbacks.delete(callback);
|
|
605
|
-
};
|
|
695
|
+
return this._addLifecycleListener("$controller-reconnect", callback);
|
|
606
696
|
}
|
|
607
697
|
onCharacterUpdated(callback) {
|
|
608
|
-
this.
|
|
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
|
-
};
|
|
698
|
+
return this._addLifecycleListener("$character-updated", callback);
|
|
618
699
|
}
|
|
619
700
|
onError(callback) {
|
|
620
|
-
this.
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
701
|
+
return this._addLifecycleListener("$error", callback);
|
|
702
|
+
}
|
|
703
|
+
onConnectionChange(callback) {
|
|
704
|
+
return this._addLifecycleListener("$connection-change", callback);
|
|
624
705
|
}
|
|
625
706
|
// ---------------------------------------------------------------------------
|
|
626
707
|
// Communication Methods
|
|
@@ -629,7 +710,6 @@
|
|
|
629
710
|
* Send type-safe events to all controllers.
|
|
630
711
|
*
|
|
631
712
|
* Uses EventMap generic for compile-time type checking of event names and data payloads.
|
|
632
|
-
* Runtime behavior is identical to broadcastRaw - both call validateEventName at runtime.
|
|
633
713
|
*
|
|
634
714
|
* @note Data should be an object. Primitive values will be wrapped as `{ data: value }` by the relay server.
|
|
635
715
|
* @note Maximum payload size is 64KB. Data exceeding this limit will be silently dropped by the server.
|
|
@@ -638,31 +718,18 @@
|
|
|
638
718
|
*
|
|
639
719
|
* Warning: Avoid sending primitive values directly (string, number, boolean).
|
|
640
720
|
* Wrap in an object: broadcast('event', { value: 42 }) instead of broadcast('event', 42)
|
|
641
|
-
*
|
|
642
|
-
* @see broadcastRaw for bypassing TypeScript type checking (runtime behavior identical)
|
|
643
721
|
*/
|
|
644
722
|
broadcast(event, data) {
|
|
645
|
-
this.
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
this.transport
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
* Bypasses EventMap generic type checks at compile time.
|
|
654
|
-
* Runtime behavior is identical to broadcast - both call validateEventName at runtime.
|
|
655
|
-
*
|
|
656
|
-
* Use this when you need dynamic event names or when working without a predefined EventMap.
|
|
657
|
-
*
|
|
658
|
-
* @note Data should be an object. Primitive values will be wrapped as `{ data: value }` by the relay server.
|
|
659
|
-
* @note Maximum payload size is 64KB. Data exceeding this limit will be silently dropped by the server.
|
|
660
|
-
*
|
|
661
|
-
* @see broadcast for type-safe version using EventMap generic
|
|
662
|
-
*/
|
|
663
|
-
broadcastRaw(event, data) {
|
|
664
|
-
this.ensureReady("broadcastRaw");
|
|
723
|
+
if (this._isDestroyed) {
|
|
724
|
+
throw new SmoreSDKError("DESTROYED", "Cannot call broadcast() after destroy()");
|
|
725
|
+
}
|
|
726
|
+
if (!this._isReady || !this.transport) {
|
|
727
|
+
this._outboundBuffer.push({ method: "broadcast", args: [event, data] });
|
|
728
|
+
this.logger.debug(`Buffered broadcast "${event}" (screen not ready yet)`);
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
665
731
|
validateEventName(event);
|
|
732
|
+
validatePayloadSize(data);
|
|
666
733
|
this.logger.send(event, data);
|
|
667
734
|
this.transport.emit(event, data);
|
|
668
735
|
}
|
|
@@ -681,24 +748,17 @@
|
|
|
681
748
|
* @param data - Event data payload
|
|
682
749
|
*/
|
|
683
750
|
sendToController(playerIndex, event, data) {
|
|
684
|
-
this.
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
if (
|
|
688
|
-
this.
|
|
689
|
-
|
|
690
|
-
|
|
751
|
+
if (this._isDestroyed) {
|
|
752
|
+
throw new SmoreSDKError("DESTROYED", "Cannot call sendToController() after destroy()");
|
|
753
|
+
}
|
|
754
|
+
if (!this._isReady || !this.transport) {
|
|
755
|
+
this._outboundBuffer.push({ method: "sendToController", args: [playerIndex, event, data] });
|
|
756
|
+
this.logger.debug(`Buffered sendToController "${event}" -> Player ${playerIndex} (screen not ready yet)`);
|
|
757
|
+
return;
|
|
691
758
|
}
|
|
692
|
-
this.logger.send(`${event} -> Player ${playerIndex}`, data);
|
|
693
|
-
this.transport.emit(event, {
|
|
694
|
-
targetPlayerIndex: playerIndex,
|
|
695
|
-
...data && typeof data === "object" ? data : { data }
|
|
696
|
-
});
|
|
697
|
-
}
|
|
698
|
-
sendToControllerRaw(playerIndex, event, data) {
|
|
699
|
-
this.ensureReady("sendToControllerRaw");
|
|
700
759
|
validateEventName(event);
|
|
701
760
|
validatePlayerIndex(playerIndex, this._controllers);
|
|
761
|
+
validatePayloadSize(data);
|
|
702
762
|
if (data && typeof data === "object" && "targetPlayerIndex" in data) {
|
|
703
763
|
this.logger.warn(
|
|
704
764
|
`Event "${event}" data contains reserved field "targetPlayerIndex" which will be overwritten for routing.`
|
|
@@ -733,6 +793,16 @@
|
|
|
733
793
|
* are queued and activated when the transport becomes available.
|
|
734
794
|
*/
|
|
735
795
|
on(event, handler) {
|
|
796
|
+
if (typeof event === "string" && event.startsWith("$")) {
|
|
797
|
+
const validEvents = SCREEN_LIFECYCLE_EVENTS;
|
|
798
|
+
if (!validEvents.has(event)) {
|
|
799
|
+
throw new SmoreSDKError("INVALID_EVENT", `Unknown lifecycle event: "${event}". Valid lifecycle events: ${Array.from(validEvents).join(", ")}`);
|
|
800
|
+
}
|
|
801
|
+
if (event === "$all-ready" && this._allReadyFired) {
|
|
802
|
+
handler();
|
|
803
|
+
}
|
|
804
|
+
return this._addLifecycleListener(event, handler);
|
|
805
|
+
}
|
|
736
806
|
validateEventName(event);
|
|
737
807
|
let handlers = this.eventHandlers.get(event);
|
|
738
808
|
if (!handlers) {
|
|
@@ -795,6 +865,23 @@
|
|
|
795
865
|
* @returns Unsubscribe function to remove the handler before it fires
|
|
796
866
|
*/
|
|
797
867
|
once(event, handler) {
|
|
868
|
+
if (typeof event === "string" && event.startsWith("$")) {
|
|
869
|
+
const validEvents = SCREEN_LIFECYCLE_EVENTS;
|
|
870
|
+
if (!validEvents.has(event)) {
|
|
871
|
+
throw new SmoreSDKError("INVALID_EVENT", `Unknown lifecycle event: "${event}"`);
|
|
872
|
+
}
|
|
873
|
+
if (event === "$all-ready" && this._allReadyFired) {
|
|
874
|
+
handler();
|
|
875
|
+
return () => {
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
const wrapper = (...args) => {
|
|
879
|
+
unsub();
|
|
880
|
+
handler(...args);
|
|
881
|
+
};
|
|
882
|
+
const unsub = this._addLifecycleListener(event, wrapper);
|
|
883
|
+
return unsub;
|
|
884
|
+
}
|
|
798
885
|
const wrappedHandler = (playerIndex, data) => {
|
|
799
886
|
unsubscribe();
|
|
800
887
|
handler(playerIndex, data);
|
|
@@ -803,6 +890,14 @@
|
|
|
803
890
|
return unsubscribe;
|
|
804
891
|
}
|
|
805
892
|
off(event, handler) {
|
|
893
|
+
if (typeof event === "string" && event.startsWith("$")) {
|
|
894
|
+
if (!handler) {
|
|
895
|
+
this._lifecycleListeners.delete(event);
|
|
896
|
+
} else {
|
|
897
|
+
this._lifecycleListeners.get(event)?.delete(handler);
|
|
898
|
+
}
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
806
901
|
if (!handler) {
|
|
807
902
|
this.eventHandlers.delete(event);
|
|
808
903
|
this.transport?.off(event);
|
|
@@ -830,6 +925,21 @@
|
|
|
830
925
|
);
|
|
831
926
|
}
|
|
832
927
|
}
|
|
928
|
+
removeAllListeners(event) {
|
|
929
|
+
if (event) {
|
|
930
|
+
this.eventHandlers.delete(event);
|
|
931
|
+
this.transport?.off(event);
|
|
932
|
+
this.registeredTransportHandlers = this.registeredTransportHandlers.filter((h) => h.event !== event);
|
|
933
|
+
for (const [key, val] of this.handlerToTransport) {
|
|
934
|
+
if (val.event === event) this.handlerToTransport.delete(key);
|
|
935
|
+
}
|
|
936
|
+
this._pendingHandlers = this._pendingHandlers.filter((p) => p.event !== event);
|
|
937
|
+
} else {
|
|
938
|
+
for (const evt of [...this.eventHandlers.keys()]) {
|
|
939
|
+
this.removeAllListeners(evt);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
}
|
|
833
943
|
// ---------------------------------------------------------------------------
|
|
834
944
|
// Utilities
|
|
835
945
|
// ---------------------------------------------------------------------------
|
|
@@ -839,9 +949,6 @@
|
|
|
839
949
|
getControllerCount() {
|
|
840
950
|
return this._controllers.filter((c) => c.connected).length;
|
|
841
951
|
}
|
|
842
|
-
hasAnyConnectedControllers() {
|
|
843
|
-
return this._controllers.some((c) => c.connected);
|
|
844
|
-
}
|
|
845
952
|
// ---------------------------------------------------------------------------
|
|
846
953
|
// Cleanup
|
|
847
954
|
// ---------------------------------------------------------------------------
|
|
@@ -865,14 +972,9 @@
|
|
|
865
972
|
this.eventHandlers.clear();
|
|
866
973
|
this.handlerToTransport.clear();
|
|
867
974
|
this._pendingHandlers = [];
|
|
868
|
-
this.
|
|
869
|
-
this.
|
|
870
|
-
this.
|
|
871
|
-
this._onControllerDisconnectCallbacks.clear();
|
|
872
|
-
this._onControllerReconnectCallbacks.clear();
|
|
873
|
-
this._onCharacterUpdatedCallbacks.clear();
|
|
874
|
-
this._onRateLimitedCallbacks.clear();
|
|
875
|
-
this._onErrorCallbacks.clear();
|
|
975
|
+
this._lifecycleListeners.clear();
|
|
976
|
+
this._isConnected = false;
|
|
977
|
+
this._outboundBuffer = [];
|
|
876
978
|
if (this.transport instanceof PostMessageTransport) {
|
|
877
979
|
this.transport.destroy();
|
|
878
980
|
}
|
|
@@ -888,8 +990,8 @@
|
|
|
888
990
|
handleError(error) {
|
|
889
991
|
this.logger.warn(`Error in handler: ${error.message}`);
|
|
890
992
|
const smoreError = error.toSmoreError();
|
|
891
|
-
if (this.
|
|
892
|
-
this.
|
|
993
|
+
if (this._hasLifecycleListeners("$error")) {
|
|
994
|
+
this._emitLifecycle("$error", smoreError);
|
|
893
995
|
} else {
|
|
894
996
|
this.logger.error(error.message, error.details);
|
|
895
997
|
}
|
|
@@ -921,7 +1023,7 @@
|
|
|
921
1023
|
config;
|
|
922
1024
|
logger;
|
|
923
1025
|
_roomCode = "";
|
|
924
|
-
|
|
1026
|
+
_myPlayerIndex = -1;
|
|
925
1027
|
_isReady = false;
|
|
926
1028
|
_isDestroyed = false;
|
|
927
1029
|
_initTimeoutId = null;
|
|
@@ -933,17 +1035,16 @@
|
|
|
933
1035
|
_controllers = [];
|
|
934
1036
|
// Pending handlers registered via on() before transport is ready
|
|
935
1037
|
_pendingHandlers = [];
|
|
936
|
-
//
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
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();
|
|
1038
|
+
// Unified lifecycle listener map (supports both onXxx() and on('$xxx') patterns)
|
|
1039
|
+
_lifecycleListeners = /* @__PURE__ */ new Map();
|
|
1040
|
+
// Outbound message buffer (messages sent before ready)
|
|
1041
|
+
_outboundBuffer = [];
|
|
945
1042
|
// Whether all-ready has fired
|
|
946
1043
|
_allReadyFired = false;
|
|
1044
|
+
// Self-connection awareness
|
|
1045
|
+
_isConnected = false;
|
|
1046
|
+
// Protocol versioning
|
|
1047
|
+
_protocolVersion = PROTOCOL_VERSION;
|
|
947
1048
|
// Ready promise
|
|
948
1049
|
_readyResolve;
|
|
949
1050
|
_readyReject;
|
|
@@ -960,8 +1061,8 @@
|
|
|
960
1061
|
// ---------------------------------------------------------------------------
|
|
961
1062
|
// Properties (readonly)
|
|
962
1063
|
// ---------------------------------------------------------------------------
|
|
963
|
-
get
|
|
964
|
-
return this.
|
|
1064
|
+
get myPlayerIndex() {
|
|
1065
|
+
return this._myPlayerIndex;
|
|
965
1066
|
}
|
|
966
1067
|
get roomCode() {
|
|
967
1068
|
return this._roomCode;
|
|
@@ -972,6 +1073,12 @@
|
|
|
972
1073
|
get isDestroyed() {
|
|
973
1074
|
return this._isDestroyed;
|
|
974
1075
|
}
|
|
1076
|
+
get isConnected() {
|
|
1077
|
+
return this._isConnected;
|
|
1078
|
+
}
|
|
1079
|
+
get protocolVersion() {
|
|
1080
|
+
return this._protocolVersion;
|
|
1081
|
+
}
|
|
975
1082
|
/**
|
|
976
1083
|
* Read-only list of all known controllers (players) in the room.
|
|
977
1084
|
* Returns full ControllerInfo including playerIndex, nickname, connected status, and appearance.
|
|
@@ -988,6 +1095,9 @@
|
|
|
988
1095
|
getControllerCount() {
|
|
989
1096
|
return this._controllers.filter((c) => c.connected).length;
|
|
990
1097
|
}
|
|
1098
|
+
getController(playerIndex) {
|
|
1099
|
+
return this._controllers.find((c) => c.playerIndex === playerIndex);
|
|
1100
|
+
}
|
|
991
1101
|
// ---------------------------------------------------------------------------
|
|
992
1102
|
// Initialization
|
|
993
1103
|
// ---------------------------------------------------------------------------
|
|
@@ -1018,7 +1128,7 @@
|
|
|
1018
1128
|
};
|
|
1019
1129
|
window.addEventListener("message", this.boundMessageHandler);
|
|
1020
1130
|
this.logger.lifecycle("Sending _bridge:ready to parent");
|
|
1021
|
-
window.parent.postMessage({ type: "_bridge:ready" }, parentOrigin);
|
|
1131
|
+
window.parent.postMessage({ type: "_bridge:ready", protocolVersion: PROTOCOL_VERSION }, parentOrigin);
|
|
1022
1132
|
}
|
|
1023
1133
|
handleInit(msg, parentOrigin) {
|
|
1024
1134
|
const initPayload = msg.payload;
|
|
@@ -1057,28 +1167,44 @@
|
|
|
1057
1167
|
this._readyReject(error);
|
|
1058
1168
|
return;
|
|
1059
1169
|
}
|
|
1060
|
-
this.transport = new PostMessageTransport(parentOrigin);
|
|
1170
|
+
this.transport = this.config.transport ?? new PostMessageTransport(parentOrigin);
|
|
1061
1171
|
this._roomCode = initData.roomCode;
|
|
1062
|
-
|
|
1172
|
+
const serverProtocolVersion = initData.protocolVersion;
|
|
1173
|
+
if (serverProtocolVersion !== void 0) {
|
|
1174
|
+
this._protocolVersion = serverProtocolVersion;
|
|
1175
|
+
if (serverProtocolVersion !== PROTOCOL_VERSION) {
|
|
1176
|
+
this.logger.warn(
|
|
1177
|
+
`Protocol version mismatch: SDK v${PROTOCOL_VERSION}, server v${serverProtocolVersion}. Some features may not work correctly.`
|
|
1178
|
+
);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
this._myPlayerIndex = initData.myIndex;
|
|
1063
1182
|
const initPlayers = initData.players;
|
|
1064
|
-
this._controllers = initPlayers.filter((p) => typeof p.playerIndex === "number").map((p) => (
|
|
1065
|
-
playerIndex: p.playerIndex,
|
|
1066
|
-
nickname: p.nickname || p.name || `Player ${p.playerIndex + 1}`,
|
|
1067
|
-
connected: p.connected !== false,
|
|
1068
|
-
appearance: p.appearance ?? p.character
|
|
1069
|
-
}));
|
|
1183
|
+
this._controllers = initPlayers.filter((p) => typeof p.playerIndex === "number").map((p, index) => mapPlayerDTO(p, index));
|
|
1070
1184
|
this.setupEventHandlers();
|
|
1071
1185
|
for (const { event, handler } of this._pendingHandlers) {
|
|
1072
1186
|
this.setupUserEventHandler(event, handler);
|
|
1073
1187
|
}
|
|
1074
1188
|
this._pendingHandlers = [];
|
|
1189
|
+
this._isConnected = true;
|
|
1075
1190
|
this._isReady = true;
|
|
1191
|
+
for (const buffered of this._outboundBuffer) {
|
|
1192
|
+
try {
|
|
1193
|
+
switch (buffered.method) {
|
|
1194
|
+
case "send":
|
|
1195
|
+
this.send(buffered.args[0], buffered.args[1]);
|
|
1196
|
+
break;
|
|
1197
|
+
}
|
|
1198
|
+
} catch (err) {
|
|
1199
|
+
this.handleError(err instanceof SmoreSDKError ? err : new SmoreSDKError("UNKNOWN", "Failed to flush buffered message"));
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
this._outboundBuffer = [];
|
|
1076
1203
|
this.logger.lifecycle("Controller ready", {
|
|
1077
1204
|
roomCode: this._roomCode,
|
|
1078
|
-
myIndex: this.
|
|
1205
|
+
myIndex: this._myPlayerIndex
|
|
1079
1206
|
});
|
|
1080
|
-
|
|
1081
|
-
if (autoReady) {
|
|
1207
|
+
if (this.config.autoReady !== false) {
|
|
1082
1208
|
this.logger.lifecycle("Auto-signaling ready (autoReady enabled)");
|
|
1083
1209
|
this.signalReady();
|
|
1084
1210
|
}
|
|
@@ -1093,21 +1219,16 @@
|
|
|
1093
1219
|
this.logger.debug("Received _bridge:update", updateData);
|
|
1094
1220
|
if (updateData.players && Array.isArray(updateData.players)) {
|
|
1095
1221
|
const players = updateData.players;
|
|
1096
|
-
const newControllers = players.filter((p) => typeof p.playerIndex === "number").map((p) => (
|
|
1097
|
-
playerIndex: p.playerIndex,
|
|
1098
|
-
nickname: p.nickname || p.name || `Player ${p.playerIndex + 1}`,
|
|
1099
|
-
connected: p.connected !== false,
|
|
1100
|
-
appearance: p.appearance ?? p.character
|
|
1101
|
-
}));
|
|
1222
|
+
const newControllers = players.filter((p) => typeof p.playerIndex === "number").map((p, index) => mapPlayerDTO(p, index));
|
|
1102
1223
|
const oldControllers = this._controllers;
|
|
1103
1224
|
for (const nc of newControllers) {
|
|
1104
1225
|
if (!oldControllers.some((oc) => oc.playerIndex === nc.playerIndex)) {
|
|
1105
|
-
this.
|
|
1226
|
+
this._emitLifecycle("$controller-join", nc.playerIndex, nc);
|
|
1106
1227
|
}
|
|
1107
1228
|
}
|
|
1108
1229
|
for (const oc of oldControllers) {
|
|
1109
1230
|
if (!newControllers.some((nc) => nc.playerIndex === oc.playerIndex)) {
|
|
1110
|
-
this.
|
|
1231
|
+
this._emitLifecycle("$controller-leave", oc.playerIndex);
|
|
1111
1232
|
}
|
|
1112
1233
|
}
|
|
1113
1234
|
for (const nc of newControllers) {
|
|
@@ -1115,11 +1236,11 @@
|
|
|
1115
1236
|
if (oc) {
|
|
1116
1237
|
if (oc.connected && !nc.connected) {
|
|
1117
1238
|
this.logger.debug("Player disconnected (via update)", { playerIndex: nc.playerIndex });
|
|
1118
|
-
this.
|
|
1239
|
+
this._emitLifecycle("$controller-disconnect", nc.playerIndex);
|
|
1119
1240
|
}
|
|
1120
1241
|
if (!oc.connected && nc.connected) {
|
|
1121
1242
|
this.logger.debug("Player reconnected (via update)", { playerIndex: nc.playerIndex });
|
|
1122
|
-
this.
|
|
1243
|
+
this._emitLifecycle("$controller-reconnect", nc.playerIndex, nc);
|
|
1123
1244
|
}
|
|
1124
1245
|
}
|
|
1125
1246
|
}
|
|
@@ -1134,19 +1255,10 @@
|
|
|
1134
1255
|
const playerIndex = playerInfo?.playerIndex ?? data.playerIndex;
|
|
1135
1256
|
if (playerIndex !== void 0) {
|
|
1136
1257
|
if (this._controllers.some((c) => c.playerIndex === playerIndex)) return;
|
|
1137
|
-
const controllerInfo = playerInfo ? {
|
|
1138
|
-
playerIndex,
|
|
1139
|
-
nickname: playerInfo.nickname || playerInfo.name || `Player ${playerIndex + 1}`,
|
|
1140
|
-
connected: playerInfo.connected !== false,
|
|
1141
|
-
appearance: playerInfo.appearance ?? playerInfo.character
|
|
1142
|
-
} : {
|
|
1143
|
-
playerIndex,
|
|
1144
|
-
nickname: `Player ${playerIndex + 1}`,
|
|
1145
|
-
connected: true
|
|
1146
|
-
};
|
|
1258
|
+
const controllerInfo = playerInfo ? mapPlayerDTO(playerInfo, playerIndex) : mapPlayerDTO({ playerIndex, connected: true }, playerIndex);
|
|
1147
1259
|
this._controllers = [...this._controllers, controllerInfo];
|
|
1148
1260
|
this.logger.debug("Player joined", { playerIndex });
|
|
1149
|
-
this.
|
|
1261
|
+
this._emitLifecycle("$controller-join", playerIndex, controllerInfo);
|
|
1150
1262
|
}
|
|
1151
1263
|
});
|
|
1152
1264
|
this.registerHandler(SMORE_EVENTS.PLAYER_LEFT, (raw) => {
|
|
@@ -1156,7 +1268,7 @@
|
|
|
1156
1268
|
if (!this._controllers.some((c) => c.playerIndex === playerIndex)) return;
|
|
1157
1269
|
this._controllers = this._controllers.filter((c) => c.playerIndex !== playerIndex);
|
|
1158
1270
|
this.logger.debug("Player left", { playerIndex });
|
|
1159
|
-
this.
|
|
1271
|
+
this._emitLifecycle("$controller-leave", playerIndex);
|
|
1160
1272
|
}
|
|
1161
1273
|
});
|
|
1162
1274
|
this.registerHandler(SMORE_EVENTS.PLAYER_DISCONNECTED, (raw) => {
|
|
@@ -1168,7 +1280,7 @@
|
|
|
1168
1280
|
(c) => c.playerIndex === playerIndex ? { ...c, connected: false } : c
|
|
1169
1281
|
);
|
|
1170
1282
|
this.logger.debug("Player disconnected", { playerIndex });
|
|
1171
|
-
this.
|
|
1283
|
+
this._emitLifecycle("$controller-disconnect", playerIndex);
|
|
1172
1284
|
}
|
|
1173
1285
|
});
|
|
1174
1286
|
this.registerHandler(SMORE_EVENTS.PLAYER_RECONNECTED, (raw) => {
|
|
@@ -1176,21 +1288,12 @@
|
|
|
1176
1288
|
const playerData = data.player;
|
|
1177
1289
|
const playerIndex = playerData?.playerIndex ?? data.playerIndex;
|
|
1178
1290
|
if (playerIndex !== void 0) {
|
|
1179
|
-
const controllerInfo = playerData ? {
|
|
1180
|
-
playerIndex,
|
|
1181
|
-
nickname: playerData.nickname || playerData.name || `Player ${playerIndex + 1}`,
|
|
1182
|
-
connected: true,
|
|
1183
|
-
appearance: playerData.appearance ?? playerData.character
|
|
1184
|
-
} : {
|
|
1185
|
-
playerIndex,
|
|
1186
|
-
nickname: `Player ${playerIndex + 1}`,
|
|
1187
|
-
connected: true
|
|
1188
|
-
};
|
|
1291
|
+
const controllerInfo = playerData ? mapPlayerDTO(playerData, playerIndex) : mapPlayerDTO({ playerIndex, connected: true }, playerIndex);
|
|
1189
1292
|
this._controllers = this._controllers.map(
|
|
1190
1293
|
(c) => c.playerIndex === playerIndex ? controllerInfo : c
|
|
1191
1294
|
);
|
|
1192
1295
|
this.logger.debug("Player reconnected", { playerIndex });
|
|
1193
|
-
this.
|
|
1296
|
+
this._emitLifecycle("$controller-reconnect", playerIndex, controllerInfo);
|
|
1194
1297
|
}
|
|
1195
1298
|
});
|
|
1196
1299
|
this.registerHandler(SMORE_EVENTS.PLAYER_CHARACTER_UPDATED, (raw) => {
|
|
@@ -1203,19 +1306,37 @@
|
|
|
1203
1306
|
(c) => c.playerIndex === pi ? { ...c, appearance } : c
|
|
1204
1307
|
);
|
|
1205
1308
|
this.logger.debug("Player character updated", { playerIndex: pi });
|
|
1206
|
-
this.
|
|
1309
|
+
this._emitLifecycle("$character-updated", pi, appearance ?? null);
|
|
1207
1310
|
}
|
|
1208
1311
|
});
|
|
1209
1312
|
this.registerHandler(SMORE_EVENTS.RATE_LIMITED, (raw) => {
|
|
1210
1313
|
const data = raw;
|
|
1211
|
-
const
|
|
1212
|
-
this.
|
|
1213
|
-
|
|
1314
|
+
const eventName = data?.event ?? "unknown";
|
|
1315
|
+
this.handleError(
|
|
1316
|
+
new SmoreSDKError("RATE_LIMITED", `Server rate-limited event: ${eventName}`, {
|
|
1317
|
+
details: { event: eventName }
|
|
1318
|
+
})
|
|
1319
|
+
);
|
|
1320
|
+
});
|
|
1321
|
+
this.registerHandler(SMORE_EVENTS.GAME_OVER, (raw) => {
|
|
1322
|
+
const data = raw;
|
|
1323
|
+
this.logger.lifecycle("Game over", data?.results);
|
|
1324
|
+
this._emitLifecycle("$game-over", data?.results);
|
|
1214
1325
|
});
|
|
1215
1326
|
this.registerHandler(SMORE_EVENTS.ALL_READY, () => {
|
|
1216
1327
|
this.logger.lifecycle("All participants ready");
|
|
1217
1328
|
this._allReadyFired = true;
|
|
1218
|
-
this.
|
|
1329
|
+
this._emitLifecycle("$all-ready");
|
|
1330
|
+
});
|
|
1331
|
+
this.registerHandler(SMORE_EVENTS.SELF_DISCONNECTED, () => {
|
|
1332
|
+
this._isConnected = false;
|
|
1333
|
+
this.logger.lifecycle("Connection lost");
|
|
1334
|
+
this._emitLifecycle("$connection-change", false);
|
|
1335
|
+
});
|
|
1336
|
+
this.registerHandler(SMORE_EVENTS.SELF_RECONNECTED, () => {
|
|
1337
|
+
this._isConnected = true;
|
|
1338
|
+
this.logger.lifecycle("Connection restored");
|
|
1339
|
+
this._emitLifecycle("$connection-change", true);
|
|
1219
1340
|
});
|
|
1220
1341
|
}
|
|
1221
1342
|
/**
|
|
@@ -1254,58 +1375,70 @@
|
|
|
1254
1375
|
this.registeredHandlers.push({ event, handler });
|
|
1255
1376
|
}
|
|
1256
1377
|
// ---------------------------------------------------------------------------
|
|
1378
|
+
// Lifecycle Listener Helpers
|
|
1379
|
+
// ---------------------------------------------------------------------------
|
|
1380
|
+
_addLifecycleListener(event, listener) {
|
|
1381
|
+
let set = this._lifecycleListeners.get(event);
|
|
1382
|
+
if (!set) {
|
|
1383
|
+
set = /* @__PURE__ */ new Set();
|
|
1384
|
+
this._lifecycleListeners.set(event, set);
|
|
1385
|
+
}
|
|
1386
|
+
set.add(listener);
|
|
1387
|
+
return () => {
|
|
1388
|
+
set.delete(listener);
|
|
1389
|
+
if (set.size === 0) this._lifecycleListeners.delete(event);
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
_emitLifecycle(event, ...args) {
|
|
1393
|
+
this._lifecycleListeners.get(event)?.forEach((cb) => {
|
|
1394
|
+
try {
|
|
1395
|
+
cb(...args);
|
|
1396
|
+
} catch (err) {
|
|
1397
|
+
this.handleError(
|
|
1398
|
+
new SmoreSDKError("UNKNOWN", `Error in lifecycle handler for "${event}"`, {
|
|
1399
|
+
cause: err instanceof Error ? err : void 0,
|
|
1400
|
+
details: { event }
|
|
1401
|
+
})
|
|
1402
|
+
);
|
|
1403
|
+
}
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1406
|
+
_hasLifecycleListeners(event) {
|
|
1407
|
+
const set = this._lifecycleListeners.get(event);
|
|
1408
|
+
return set !== void 0 && set.size > 0;
|
|
1409
|
+
}
|
|
1410
|
+
// ---------------------------------------------------------------------------
|
|
1257
1411
|
// Lifecycle Methods
|
|
1258
1412
|
// ---------------------------------------------------------------------------
|
|
1259
1413
|
onAllReady(callback) {
|
|
1260
1414
|
if (this._allReadyFired) {
|
|
1261
1415
|
callback();
|
|
1262
1416
|
}
|
|
1263
|
-
this.
|
|
1264
|
-
return () => {
|
|
1265
|
-
this._onAllReadyCallbacks.delete(callback);
|
|
1266
|
-
};
|
|
1417
|
+
return this._addLifecycleListener("$all-ready", callback);
|
|
1267
1418
|
}
|
|
1268
1419
|
onControllerJoin(callback) {
|
|
1269
|
-
this.
|
|
1270
|
-
return () => {
|
|
1271
|
-
this._onControllerJoinCallbacks.delete(callback);
|
|
1272
|
-
};
|
|
1420
|
+
return this._addLifecycleListener("$controller-join", callback);
|
|
1273
1421
|
}
|
|
1274
1422
|
onControllerLeave(callback) {
|
|
1275
|
-
this.
|
|
1276
|
-
return () => {
|
|
1277
|
-
this._onControllerLeaveCallbacks.delete(callback);
|
|
1278
|
-
};
|
|
1423
|
+
return this._addLifecycleListener("$controller-leave", callback);
|
|
1279
1424
|
}
|
|
1280
1425
|
onControllerDisconnect(callback) {
|
|
1281
|
-
this.
|
|
1282
|
-
return () => {
|
|
1283
|
-
this._onControllerDisconnectCallbacks.delete(callback);
|
|
1284
|
-
};
|
|
1426
|
+
return this._addLifecycleListener("$controller-disconnect", callback);
|
|
1285
1427
|
}
|
|
1286
1428
|
onControllerReconnect(callback) {
|
|
1287
|
-
this.
|
|
1288
|
-
return () => {
|
|
1289
|
-
this._onControllerReconnectCallbacks.delete(callback);
|
|
1290
|
-
};
|
|
1429
|
+
return this._addLifecycleListener("$controller-reconnect", callback);
|
|
1291
1430
|
}
|
|
1292
1431
|
onCharacterUpdated(callback) {
|
|
1293
|
-
this.
|
|
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
|
-
};
|
|
1432
|
+
return this._addLifecycleListener("$character-updated", callback);
|
|
1303
1433
|
}
|
|
1304
1434
|
onError(callback) {
|
|
1305
|
-
this.
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1435
|
+
return this._addLifecycleListener("$error", callback);
|
|
1436
|
+
}
|
|
1437
|
+
onConnectionChange(callback) {
|
|
1438
|
+
return this._addLifecycleListener("$connection-change", callback);
|
|
1439
|
+
}
|
|
1440
|
+
onGameOver(callback) {
|
|
1441
|
+
return this._addLifecycleListener("$game-over", callback);
|
|
1309
1442
|
}
|
|
1310
1443
|
// ---------------------------------------------------------------------------
|
|
1311
1444
|
// Communication Methods
|
|
@@ -1321,8 +1454,16 @@
|
|
|
1321
1454
|
* Use the onError callback or smore:rate-limited event to detect rate limiting.
|
|
1322
1455
|
*/
|
|
1323
1456
|
send(event, data) {
|
|
1324
|
-
this.
|
|
1457
|
+
if (this._isDestroyed) {
|
|
1458
|
+
throw new SmoreSDKError("DESTROYED", "Cannot call send() after destroy()");
|
|
1459
|
+
}
|
|
1460
|
+
if (!this._isReady || !this.transport) {
|
|
1461
|
+
this._outboundBuffer.push({ method: "send", args: [event, data] });
|
|
1462
|
+
this.logger.debug(`Buffered send "${event}" (controller not ready yet)`);
|
|
1463
|
+
return;
|
|
1464
|
+
}
|
|
1325
1465
|
validateEventName(event);
|
|
1466
|
+
validatePayloadSize(data);
|
|
1326
1467
|
if (typeof data !== "object" || data === null) {
|
|
1327
1468
|
this.logger.warn(
|
|
1328
1469
|
'Event data should be an object. Primitive values will be wrapped as { data: value } by the relay server. To avoid confusion, wrap explicitly: send("event", { value: 42 }) instead of send("event", 42).'
|
|
@@ -1331,12 +1472,6 @@
|
|
|
1331
1472
|
this.logSend(event, data);
|
|
1332
1473
|
this.transport.emit(event, data);
|
|
1333
1474
|
}
|
|
1334
|
-
sendRaw(event, data) {
|
|
1335
|
-
this.ensureReady("sendRaw");
|
|
1336
|
-
validateEventName(event);
|
|
1337
|
-
this.logSend(event, data);
|
|
1338
|
-
this.transport.emit(event, data);
|
|
1339
|
-
}
|
|
1340
1475
|
signalReady() {
|
|
1341
1476
|
this.ensureReady("signalReady");
|
|
1342
1477
|
this.logSend(SMORE_EVENTS.GAME_READY, {});
|
|
@@ -1358,6 +1493,16 @@
|
|
|
1358
1493
|
* handler receives `(data)` -- targeted to this specific controller.
|
|
1359
1494
|
*/
|
|
1360
1495
|
on(event, handler) {
|
|
1496
|
+
if (typeof event === "string" && event.startsWith("$")) {
|
|
1497
|
+
const validEvents = CONTROLLER_LIFECYCLE_EVENTS;
|
|
1498
|
+
if (!validEvents.has(event)) {
|
|
1499
|
+
throw new SmoreSDKError("INVALID_EVENT", `Unknown lifecycle event: "${event}". Valid lifecycle events: ${Array.from(validEvents).join(", ")}`);
|
|
1500
|
+
}
|
|
1501
|
+
if (event === "$all-ready" && this._allReadyFired) {
|
|
1502
|
+
handler();
|
|
1503
|
+
}
|
|
1504
|
+
return this._addLifecycleListener(event, handler);
|
|
1505
|
+
}
|
|
1361
1506
|
validateEventName(event);
|
|
1362
1507
|
let listeners = this.eventListeners.get(event);
|
|
1363
1508
|
if (!listeners) {
|
|
@@ -1410,6 +1555,23 @@
|
|
|
1410
1555
|
* `off(event, originalHandler)`. Use the returned unsubscribe function instead.
|
|
1411
1556
|
*/
|
|
1412
1557
|
once(event, handler) {
|
|
1558
|
+
if (typeof event === "string" && event.startsWith("$")) {
|
|
1559
|
+
const validEvents = CONTROLLER_LIFECYCLE_EVENTS;
|
|
1560
|
+
if (!validEvents.has(event)) {
|
|
1561
|
+
throw new SmoreSDKError("INVALID_EVENT", `Unknown lifecycle event: "${event}"`);
|
|
1562
|
+
}
|
|
1563
|
+
if (event === "$all-ready" && this._allReadyFired) {
|
|
1564
|
+
handler();
|
|
1565
|
+
return () => {
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
const wrapper = (...args) => {
|
|
1569
|
+
unsub();
|
|
1570
|
+
handler(...args);
|
|
1571
|
+
};
|
|
1572
|
+
const unsub = this._addLifecycleListener(event, wrapper);
|
|
1573
|
+
return unsub;
|
|
1574
|
+
}
|
|
1413
1575
|
const unsubscribe = this.on(event, ((data) => {
|
|
1414
1576
|
unsubscribe();
|
|
1415
1577
|
handler(data);
|
|
@@ -1417,6 +1579,14 @@
|
|
|
1417
1579
|
return unsubscribe;
|
|
1418
1580
|
}
|
|
1419
1581
|
off(event, handler) {
|
|
1582
|
+
if (typeof event === "string" && event.startsWith("$")) {
|
|
1583
|
+
if (!handler) {
|
|
1584
|
+
this._lifecycleListeners.delete(event);
|
|
1585
|
+
} else {
|
|
1586
|
+
this._lifecycleListeners.get(event)?.delete(handler);
|
|
1587
|
+
}
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1420
1590
|
if (!handler) {
|
|
1421
1591
|
this.eventListeners.delete(event);
|
|
1422
1592
|
this.transport?.off(event);
|
|
@@ -1444,6 +1614,21 @@
|
|
|
1444
1614
|
);
|
|
1445
1615
|
}
|
|
1446
1616
|
}
|
|
1617
|
+
removeAllListeners(event) {
|
|
1618
|
+
if (event) {
|
|
1619
|
+
this.eventListeners.delete(event);
|
|
1620
|
+
this.transport?.off(event);
|
|
1621
|
+
this.registeredHandlers = this.registeredHandlers.filter((h) => h.event !== event);
|
|
1622
|
+
for (const [key, val] of this.handlerToTransport) {
|
|
1623
|
+
if (val.event === event) this.handlerToTransport.delete(key);
|
|
1624
|
+
}
|
|
1625
|
+
this._pendingHandlers = this._pendingHandlers.filter((p) => p.event !== event);
|
|
1626
|
+
} else {
|
|
1627
|
+
for (const evt of [...this.eventListeners.keys()]) {
|
|
1628
|
+
this.removeAllListeners(evt);
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1447
1632
|
// ---------------------------------------------------------------------------
|
|
1448
1633
|
// Cleanup
|
|
1449
1634
|
// ---------------------------------------------------------------------------
|
|
@@ -1467,14 +1652,9 @@
|
|
|
1467
1652
|
this.eventListeners.clear();
|
|
1468
1653
|
this.handlerToTransport.clear();
|
|
1469
1654
|
this._pendingHandlers = [];
|
|
1470
|
-
this.
|
|
1471
|
-
this.
|
|
1472
|
-
this.
|
|
1473
|
-
this._onControllerDisconnectCallbacks.clear();
|
|
1474
|
-
this._onControllerReconnectCallbacks.clear();
|
|
1475
|
-
this._onCharacterUpdatedCallbacks.clear();
|
|
1476
|
-
this._onRateLimitedCallbacks.clear();
|
|
1477
|
-
this._onErrorCallbacks.clear();
|
|
1655
|
+
this._lifecycleListeners.clear();
|
|
1656
|
+
this._isConnected = false;
|
|
1657
|
+
this._outboundBuffer = [];
|
|
1478
1658
|
if (this.transport) {
|
|
1479
1659
|
this.transport.destroy();
|
|
1480
1660
|
this.transport = null;
|
|
@@ -1506,8 +1686,8 @@
|
|
|
1506
1686
|
handleError(error) {
|
|
1507
1687
|
this.logger.warn(`Error in handler: ${error.message}`);
|
|
1508
1688
|
const smoreError = error.toSmoreError();
|
|
1509
|
-
if (this.
|
|
1510
|
-
this.
|
|
1689
|
+
if (this._hasLifecycleListeners("$error")) {
|
|
1690
|
+
this._emitLifecycle("$error", smoreError);
|
|
1511
1691
|
} else {
|
|
1512
1692
|
this.logger.error(error.message, error.details);
|
|
1513
1693
|
}
|
|
@@ -1523,547 +1703,22 @@
|
|
|
1523
1703
|
return new ControllerImpl(config ?? {});
|
|
1524
1704
|
}
|
|
1525
1705
|
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
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
|
-
});
|
|
1549
|
-
const broadcasts = [];
|
|
1550
|
-
const sends = [];
|
|
1551
|
-
const screen = {
|
|
1552
|
-
// === Properties ===
|
|
1553
|
-
get controllers() {
|
|
1554
|
-
return [..._controllers];
|
|
1555
|
-
},
|
|
1556
|
-
get roomCode() {
|
|
1557
|
-
return roomCode;
|
|
1558
|
-
},
|
|
1559
|
-
get isReady() {
|
|
1560
|
-
return _isReady;
|
|
1561
|
-
},
|
|
1562
|
-
get isDestroyed() {
|
|
1563
|
-
return _isDestroyed;
|
|
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
|
-
},
|
|
1620
|
-
// === Communication Methods ===
|
|
1621
|
-
broadcast(event, data) {
|
|
1622
|
-
if (_isDestroyed) {
|
|
1623
|
-
throw new Error("Cannot broadcast: screen is destroyed");
|
|
1624
|
-
}
|
|
1625
|
-
if (!_isReady) {
|
|
1626
|
-
throw new Error("Cannot broadcast: screen is not ready");
|
|
1627
|
-
}
|
|
1628
|
-
validateEventName(event);
|
|
1629
|
-
broadcasts.push({ event, data });
|
|
1630
|
-
},
|
|
1631
|
-
broadcastRaw(event, data) {
|
|
1632
|
-
if (_isDestroyed) {
|
|
1633
|
-
throw new Error("Cannot broadcast: screen is destroyed");
|
|
1634
|
-
}
|
|
1635
|
-
if (!_isReady) {
|
|
1636
|
-
throw new Error("Cannot broadcast: screen is not ready");
|
|
1637
|
-
}
|
|
1638
|
-
validateEventName(event);
|
|
1639
|
-
broadcasts.push({ event, data });
|
|
1640
|
-
},
|
|
1641
|
-
sendToController(playerIndex, event, data) {
|
|
1642
|
-
if (_isDestroyed) {
|
|
1643
|
-
throw new Error("Cannot send: screen is destroyed");
|
|
1644
|
-
}
|
|
1645
|
-
if (!_isReady) {
|
|
1646
|
-
throw new Error("Cannot send: screen is not ready");
|
|
1647
|
-
}
|
|
1648
|
-
validateEventName(event);
|
|
1649
|
-
if (!_controllers.some((c) => c.playerIndex === playerIndex)) {
|
|
1650
|
-
throw new Error(`Invalid player index: ${playerIndex}`);
|
|
1651
|
-
}
|
|
1652
|
-
sends.push({ playerIndex, event, data });
|
|
1653
|
-
},
|
|
1654
|
-
sendToControllerRaw(playerIndex, event, data) {
|
|
1655
|
-
if (_isDestroyed) {
|
|
1656
|
-
throw new Error("Cannot send: screen is destroyed");
|
|
1657
|
-
}
|
|
1658
|
-
if (!_isReady) {
|
|
1659
|
-
throw new Error("Cannot send: screen is not ready");
|
|
1660
|
-
}
|
|
1661
|
-
validateEventName(event);
|
|
1662
|
-
if (!_controllers.some((c) => c.playerIndex === playerIndex)) {
|
|
1663
|
-
throw new Error(`Invalid player index: ${playerIndex}`);
|
|
1664
|
-
}
|
|
1665
|
-
sends.push({ playerIndex, event, data });
|
|
1666
|
-
},
|
|
1667
|
-
// === Game Lifecycle ===
|
|
1668
|
-
gameOver(results) {
|
|
1669
|
-
if (_isDestroyed) {
|
|
1670
|
-
throw new Error("Cannot call gameOver: screen is destroyed");
|
|
1671
|
-
}
|
|
1672
|
-
if (!_isReady) {
|
|
1673
|
-
throw new Error("Cannot call gameOver: screen is not ready");
|
|
1674
|
-
}
|
|
1675
|
-
broadcasts.push({ event: "smore:game-over", data: { results } });
|
|
1676
|
-
},
|
|
1677
|
-
signalReady() {
|
|
1678
|
-
if (_isDestroyed) {
|
|
1679
|
-
throw new Error("Cannot call signalReady: screen is destroyed");
|
|
1680
|
-
}
|
|
1681
|
-
if (!_isReady) {
|
|
1682
|
-
throw new Error("Cannot call signalReady: screen is not ready");
|
|
1683
|
-
}
|
|
1684
|
-
},
|
|
1685
|
-
// === Event Subscription ===
|
|
1686
|
-
on(event, handler) {
|
|
1687
|
-
validateEventName(event);
|
|
1688
|
-
const eventStr = event;
|
|
1689
|
-
if (!listeners.has(eventStr)) {
|
|
1690
|
-
listeners.set(eventStr, /* @__PURE__ */ new Set());
|
|
1691
|
-
}
|
|
1692
|
-
listeners.get(eventStr).add(handler);
|
|
1693
|
-
return () => {
|
|
1694
|
-
listeners.get(eventStr)?.delete(handler);
|
|
1695
|
-
};
|
|
1696
|
-
},
|
|
1697
|
-
once(event, handler) {
|
|
1698
|
-
validateEventName(event);
|
|
1699
|
-
const wrapper = (playerIndex, data) => {
|
|
1700
|
-
handler(playerIndex, data);
|
|
1701
|
-
screen.off(event, wrapper);
|
|
1702
|
-
};
|
|
1703
|
-
return screen.on(event, wrapper);
|
|
1704
|
-
},
|
|
1705
|
-
off(event, handler) {
|
|
1706
|
-
validateEventName(event);
|
|
1707
|
-
const eventStr = event;
|
|
1708
|
-
if (!handler) {
|
|
1709
|
-
listeners.delete(eventStr);
|
|
1710
|
-
} else {
|
|
1711
|
-
listeners.get(eventStr)?.delete(handler);
|
|
1712
|
-
}
|
|
1713
|
-
},
|
|
1714
|
-
// === Utilities ===
|
|
1715
|
-
getController(playerIndex) {
|
|
1716
|
-
return _controllers.find((c) => c.playerIndex === playerIndex);
|
|
1717
|
-
},
|
|
1718
|
-
getControllerCount() {
|
|
1719
|
-
return _controllers.filter((c) => c.connected).length;
|
|
1720
|
-
},
|
|
1721
|
-
hasAnyConnectedControllers() {
|
|
1722
|
-
return _controllers.some((c) => c.connected);
|
|
1723
|
-
},
|
|
1724
|
-
// === Cleanup ===
|
|
1725
|
-
/**
|
|
1726
|
-
* Note: destroy() clears recorded broadcast/event arrays. Call getBroadcasts() before destroy() if assertions are needed.
|
|
1727
|
-
*/
|
|
1728
|
-
destroy() {
|
|
1729
|
-
_isDestroyed = true;
|
|
1730
|
-
listeners.clear();
|
|
1731
|
-
broadcasts.length = 0;
|
|
1732
|
-
sends.length = 0;
|
|
1733
|
-
},
|
|
1734
|
-
// === Mock-specific methods ===
|
|
1735
|
-
simulateEvent(playerIndex, event, data) {
|
|
1736
|
-
validateEventName(event);
|
|
1737
|
-
const eventStr = event;
|
|
1738
|
-
const handlers = listeners.get(eventStr);
|
|
1739
|
-
if (handlers) {
|
|
1740
|
-
handlers.forEach((handler) => {
|
|
1741
|
-
handler(playerIndex, data);
|
|
1742
|
-
});
|
|
1743
|
-
}
|
|
1744
|
-
},
|
|
1745
|
-
simulateControllerJoin(info) {
|
|
1746
|
-
_controllers.push(info);
|
|
1747
|
-
_onControllerJoinCallbacks.forEach((cb) => cb(info.playerIndex, info));
|
|
1748
|
-
},
|
|
1749
|
-
simulateControllerLeave(playerIndex) {
|
|
1750
|
-
_controllers = _controllers.filter((c) => c.playerIndex !== playerIndex);
|
|
1751
|
-
_onControllerLeaveCallbacks.forEach((cb) => cb(playerIndex));
|
|
1752
|
-
},
|
|
1753
|
-
/**
|
|
1754
|
-
* Simulate a controller network disconnect (player still in room but unreachable).
|
|
1755
|
-
*/
|
|
1756
|
-
simulateControllerDisconnect(playerIndex) {
|
|
1757
|
-
const controller = _controllers.find((c) => c.playerIndex === playerIndex);
|
|
1758
|
-
if (!controller) {
|
|
1759
|
-
throw new Error(`Controller ${playerIndex} not found`);
|
|
1760
|
-
}
|
|
1761
|
-
_controllers = _controllers.map(
|
|
1762
|
-
(c) => c.playerIndex === playerIndex ? { ...c, connected: false } : c
|
|
1763
|
-
);
|
|
1764
|
-
_onControllerDisconnectCallbacks.forEach((cb) => cb(playerIndex));
|
|
1765
|
-
},
|
|
1766
|
-
/**
|
|
1767
|
-
* Simulate a controller network reconnect after disconnect.
|
|
1768
|
-
*/
|
|
1769
|
-
simulateControllerReconnect(playerIndex) {
|
|
1770
|
-
const controller = _controllers.find((c) => c.playerIndex === playerIndex);
|
|
1771
|
-
if (!controller) {
|
|
1772
|
-
throw new Error(`Controller ${playerIndex} not found`);
|
|
1773
|
-
}
|
|
1774
|
-
const reconnectedController = { ...controller, connected: true };
|
|
1775
|
-
_controllers = _controllers.map(
|
|
1776
|
-
(c) => c.playerIndex === playerIndex ? reconnectedController : c
|
|
1777
|
-
);
|
|
1778
|
-
_onControllerReconnectCallbacks.forEach((cb) => cb(playerIndex, reconnectedController));
|
|
1779
|
-
},
|
|
1780
|
-
simulateCharacterUpdate(playerIndex, appearance) {
|
|
1781
|
-
const controller = _controllers.find((c) => c.playerIndex === playerIndex);
|
|
1782
|
-
if (!controller) {
|
|
1783
|
-
throw new Error(`Controller ${playerIndex} not found`);
|
|
1784
|
-
}
|
|
1785
|
-
_controllers = _controllers.map(
|
|
1786
|
-
(c) => c.playerIndex === playerIndex ? { ...c, appearance } : c
|
|
1787
|
-
);
|
|
1788
|
-
_onCharacterUpdatedCallbacks.forEach((cb) => cb(playerIndex, appearance));
|
|
1789
|
-
},
|
|
1790
|
-
simulateRateLimited(event) {
|
|
1791
|
-
_onRateLimitedCallbacks.forEach((cb) => cb(event));
|
|
1792
|
-
},
|
|
1793
|
-
simulateAllReady() {
|
|
1794
|
-
_allReadyFired = true;
|
|
1795
|
-
_onAllReadyCallbacks.forEach((cb) => cb());
|
|
1796
|
-
},
|
|
1797
|
-
simulateError(error) {
|
|
1798
|
-
_onErrorCallbacks.forEach((cb) => cb(error));
|
|
1799
|
-
},
|
|
1800
|
-
getBroadcasts() {
|
|
1801
|
-
return [...broadcasts];
|
|
1802
|
-
},
|
|
1803
|
-
getSentToController(playerIndex) {
|
|
1804
|
-
return sends.filter((s) => s.playerIndex === playerIndex).map((s) => ({ event: s.event, data: s.data }));
|
|
1805
|
-
},
|
|
1806
|
-
clearRecordedEvents() {
|
|
1807
|
-
broadcasts.length = 0;
|
|
1808
|
-
sends.length = 0;
|
|
1809
|
-
},
|
|
1810
|
-
triggerReady() {
|
|
1811
|
-
_isReady = true;
|
|
1812
|
-
_readyResolve();
|
|
1813
|
-
}
|
|
1814
|
-
};
|
|
1815
|
-
if (autoReady) {
|
|
1816
|
-
setTimeout(() => screen.triggerReady(), 0);
|
|
1817
|
-
}
|
|
1818
|
-
return screen;
|
|
1819
|
-
}
|
|
1820
|
-
function createMockController(options = {}) {
|
|
1821
|
-
const {
|
|
1822
|
-
roomCode = "TEST",
|
|
1823
|
-
myIndex = 0,
|
|
1824
|
-
autoReady = true
|
|
1825
|
-
} = options;
|
|
1826
|
-
let _isReady = false;
|
|
1827
|
-
let _isDestroyed = false;
|
|
1828
|
-
let _controllers = options.controllers ?? [];
|
|
1829
|
-
const listeners = /* @__PURE__ */ new Map();
|
|
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
|
-
});
|
|
1843
|
-
const sentEvents = [];
|
|
1844
|
-
const controller = {
|
|
1845
|
-
// === Properties ===
|
|
1846
|
-
get myIndex() {
|
|
1847
|
-
return myIndex;
|
|
1848
|
-
},
|
|
1849
|
-
get roomCode() {
|
|
1850
|
-
return roomCode;
|
|
1851
|
-
},
|
|
1852
|
-
get isReady() {
|
|
1853
|
-
return _isReady;
|
|
1854
|
-
},
|
|
1855
|
-
get isDestroyed() {
|
|
1856
|
-
return _isDestroyed;
|
|
1857
|
-
},
|
|
1858
|
-
get controllers() {
|
|
1859
|
-
return [..._controllers];
|
|
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
|
-
},
|
|
1916
|
-
getControllerCount() {
|
|
1917
|
-
return _controllers.filter((c) => c.connected).length;
|
|
1918
|
-
},
|
|
1919
|
-
// === Communication Methods ===
|
|
1920
|
-
send(event, data) {
|
|
1921
|
-
if (_isDestroyed) {
|
|
1922
|
-
throw new Error("Cannot send: controller is destroyed");
|
|
1923
|
-
}
|
|
1924
|
-
validateEventName(event);
|
|
1925
|
-
sentEvents.push({ event, data });
|
|
1926
|
-
},
|
|
1927
|
-
sendRaw(event, data) {
|
|
1928
|
-
if (_isDestroyed) {
|
|
1929
|
-
throw new Error("Cannot send: controller is destroyed");
|
|
1930
|
-
}
|
|
1931
|
-
validateEventName(event);
|
|
1932
|
-
sentEvents.push({ event, data });
|
|
1933
|
-
},
|
|
1934
|
-
signalReady() {
|
|
1935
|
-
if (_isDestroyed) {
|
|
1936
|
-
throw new Error("Cannot call signalReady: controller is destroyed");
|
|
1937
|
-
}
|
|
1938
|
-
if (!_isReady) {
|
|
1939
|
-
throw new Error("Cannot call signalReady: controller is not ready");
|
|
1940
|
-
}
|
|
1941
|
-
},
|
|
1942
|
-
// === Event Subscription ===
|
|
1943
|
-
on(event, handler) {
|
|
1944
|
-
validateEventName(event);
|
|
1945
|
-
const eventStr = event;
|
|
1946
|
-
if (!listeners.has(eventStr)) {
|
|
1947
|
-
listeners.set(eventStr, /* @__PURE__ */ new Set());
|
|
1948
|
-
}
|
|
1949
|
-
listeners.get(eventStr).add(handler);
|
|
1950
|
-
return () => {
|
|
1951
|
-
listeners.get(eventStr)?.delete(handler);
|
|
1952
|
-
};
|
|
1953
|
-
},
|
|
1954
|
-
once(event, handler) {
|
|
1955
|
-
validateEventName(event);
|
|
1956
|
-
const wrapper = (data) => {
|
|
1957
|
-
handler(data);
|
|
1958
|
-
controller.off(event, wrapper);
|
|
1959
|
-
};
|
|
1960
|
-
return controller.on(event, wrapper);
|
|
1961
|
-
},
|
|
1962
|
-
off(event, handler) {
|
|
1963
|
-
validateEventName(event);
|
|
1964
|
-
const eventStr = event;
|
|
1965
|
-
if (!handler) {
|
|
1966
|
-
listeners.delete(eventStr);
|
|
1967
|
-
} else {
|
|
1968
|
-
listeners.get(eventStr)?.delete(handler);
|
|
1969
|
-
}
|
|
1970
|
-
},
|
|
1971
|
-
// === Cleanup ===
|
|
1972
|
-
destroy() {
|
|
1973
|
-
_isDestroyed = true;
|
|
1974
|
-
listeners.clear();
|
|
1975
|
-
sentEvents.length = 0;
|
|
1976
|
-
},
|
|
1977
|
-
// === Mock-specific methods ===
|
|
1978
|
-
simulateEvent(event, data) {
|
|
1979
|
-
validateEventName(event);
|
|
1980
|
-
const eventStr = event;
|
|
1981
|
-
const handlers = listeners.get(eventStr);
|
|
1982
|
-
if (handlers) {
|
|
1983
|
-
handlers.forEach((handler) => {
|
|
1984
|
-
handler(data);
|
|
1985
|
-
});
|
|
1986
|
-
}
|
|
1987
|
-
},
|
|
1988
|
-
getSentEvents() {
|
|
1989
|
-
return [...sentEvents];
|
|
1990
|
-
},
|
|
1991
|
-
clearRecordedEvents() {
|
|
1992
|
-
sentEvents.length = 0;
|
|
1993
|
-
},
|
|
1994
|
-
triggerReady() {
|
|
1995
|
-
_isReady = true;
|
|
1996
|
-
_readyResolve();
|
|
1997
|
-
},
|
|
1998
|
-
/**
|
|
1999
|
-
* Simulate a new player joining the room.
|
|
2000
|
-
* Stores full ControllerInfo (nickname, appearance, etc.) for later retrieval.
|
|
2001
|
-
*/
|
|
2002
|
-
simulatePlayerJoin(playerIndex, info) {
|
|
2003
|
-
if (!_controllers.some((c) => c.playerIndex === playerIndex)) {
|
|
2004
|
-
_controllers = [..._controllers, { ...info, connected: info.connected ?? true }];
|
|
2005
|
-
}
|
|
2006
|
-
_onControllerJoinCallbacks.forEach((cb) => cb(playerIndex, info));
|
|
2007
|
-
},
|
|
2008
|
-
/**
|
|
2009
|
-
* Simulate a player leaving the room (fully removed).
|
|
2010
|
-
*/
|
|
2011
|
-
simulatePlayerLeave(playerIndex) {
|
|
2012
|
-
_controllers = _controllers.filter((c) => c.playerIndex !== playerIndex);
|
|
2013
|
-
_onControllerLeaveCallbacks.forEach((cb) => cb(playerIndex));
|
|
2014
|
-
},
|
|
2015
|
-
/**
|
|
2016
|
-
* Simulate a player network disconnect (player still in room but unreachable).
|
|
2017
|
-
*/
|
|
2018
|
-
simulatePlayerDisconnect(playerIndex) {
|
|
2019
|
-
_controllers = _controllers.map(
|
|
2020
|
-
(c) => c.playerIndex === playerIndex ? { ...c, connected: false } : c
|
|
2021
|
-
);
|
|
2022
|
-
_onControllerDisconnectCallbacks.forEach((cb) => cb(playerIndex));
|
|
2023
|
-
},
|
|
2024
|
-
/**
|
|
2025
|
-
* Simulate a player network reconnect after disconnect.
|
|
2026
|
-
*/
|
|
2027
|
-
simulatePlayerReconnect(playerIndex, info) {
|
|
2028
|
-
_controllers = _controllers.map(
|
|
2029
|
-
(c) => c.playerIndex === playerIndex ? { ...info, connected: true } : c
|
|
2030
|
-
);
|
|
2031
|
-
_onControllerReconnectCallbacks.forEach((cb) => cb(playerIndex, info));
|
|
2032
|
-
},
|
|
2033
|
-
simulateCharacterUpdate(playerIndex, appearance) {
|
|
2034
|
-
const ctrl = _controllers.find((c) => c.playerIndex === playerIndex);
|
|
2035
|
-
if (!ctrl) {
|
|
2036
|
-
throw new Error(`Controller ${playerIndex} not found`);
|
|
2037
|
-
}
|
|
2038
|
-
_controllers = _controllers.map(
|
|
2039
|
-
(c) => c.playerIndex === playerIndex ? { ...c, appearance } : c
|
|
2040
|
-
);
|
|
2041
|
-
_onCharacterUpdatedCallbacks.forEach((cb) => cb(playerIndex, appearance));
|
|
2042
|
-
},
|
|
2043
|
-
simulateRateLimited(event) {
|
|
2044
|
-
_onRateLimitedCallbacks.forEach((cb) => cb(event));
|
|
2045
|
-
},
|
|
2046
|
-
simulateAllReady() {
|
|
2047
|
-
_allReadyFired = true;
|
|
2048
|
-
_onAllReadyCallbacks.forEach((cb) => cb());
|
|
2049
|
-
},
|
|
2050
|
-
simulateError(error) {
|
|
2051
|
-
_onErrorCallbacks.forEach((cb) => cb(error));
|
|
2052
|
-
}
|
|
2053
|
-
};
|
|
2054
|
-
if (autoReady) {
|
|
2055
|
-
setTimeout(() => controller.triggerReady(), 0);
|
|
2056
|
-
}
|
|
2057
|
-
return controller;
|
|
2058
|
-
}
|
|
1706
|
+
const LifecycleEvent = {
|
|
1707
|
+
ALL_READY: "$all-ready",
|
|
1708
|
+
CONTROLLER_JOIN: "$controller-join",
|
|
1709
|
+
CONTROLLER_LEAVE: "$controller-leave",
|
|
1710
|
+
CONTROLLER_DISCONNECT: "$controller-disconnect",
|
|
1711
|
+
CONTROLLER_RECONNECT: "$controller-reconnect",
|
|
1712
|
+
CHARACTER_UPDATED: "$character-updated",
|
|
1713
|
+
ERROR: "$error",
|
|
1714
|
+
GAME_OVER: "$game-over",
|
|
1715
|
+
CONNECTION_CHANGE: "$connection-change"
|
|
1716
|
+
};
|
|
2059
1717
|
|
|
1718
|
+
exports.LifecycleEvent = LifecycleEvent;
|
|
2060
1719
|
exports.SmoreSDKError = SmoreSDKError;
|
|
2061
|
-
exports.configure = configure;
|
|
2062
1720
|
exports.createController = createController;
|
|
2063
|
-
exports.createMockController = createMockController;
|
|
2064
|
-
exports.createMockScreen = createMockScreen;
|
|
2065
1721
|
exports.createScreen = createScreen;
|
|
2066
|
-
exports.validateEventName = validateEventName;
|
|
2067
1722
|
|
|
2068
1723
|
}));
|
|
2069
1724
|
//# sourceMappingURL=smore-sdk.umd.js.map
|