@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
package/dist/cjs/controller.cjs
CHANGED
|
@@ -5,7 +5,7 @@ var protocol = require('./transport/protocol.cjs');
|
|
|
5
5
|
var errors = require('./errors.cjs');
|
|
6
6
|
var events = require('./events.cjs');
|
|
7
7
|
var logger = require('./logger.cjs');
|
|
8
|
-
var
|
|
8
|
+
var shared = require('./shared.cjs');
|
|
9
9
|
|
|
10
10
|
const DEFAULT_TIMEOUT = 1e4;
|
|
11
11
|
class ControllerImpl {
|
|
@@ -13,7 +13,7 @@ class ControllerImpl {
|
|
|
13
13
|
config;
|
|
14
14
|
logger;
|
|
15
15
|
_roomCode = "";
|
|
16
|
-
|
|
16
|
+
_myPlayerIndex = -1;
|
|
17
17
|
_isReady = false;
|
|
18
18
|
_isDestroyed = false;
|
|
19
19
|
_initTimeoutId = null;
|
|
@@ -25,17 +25,16 @@ class ControllerImpl {
|
|
|
25
25
|
_controllers = [];
|
|
26
26
|
// Pending handlers registered via on() before transport is ready
|
|
27
27
|
_pendingHandlers = [];
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
_onControllerDisconnectCallbacks = /* @__PURE__ */ new Set();
|
|
33
|
-
_onControllerReconnectCallbacks = /* @__PURE__ */ new Set();
|
|
34
|
-
_onCharacterUpdatedCallbacks = /* @__PURE__ */ new Set();
|
|
35
|
-
_onRateLimitedCallbacks = /* @__PURE__ */ new Set();
|
|
36
|
-
_onErrorCallbacks = /* @__PURE__ */ new Set();
|
|
28
|
+
// Unified lifecycle listener map (supports both onXxx() and on('$xxx') patterns)
|
|
29
|
+
_lifecycleListeners = /* @__PURE__ */ new Map();
|
|
30
|
+
// Outbound message buffer (messages sent before ready)
|
|
31
|
+
_outboundBuffer = [];
|
|
37
32
|
// Whether all-ready has fired
|
|
38
33
|
_allReadyFired = false;
|
|
34
|
+
// Self-connection awareness
|
|
35
|
+
_isConnected = false;
|
|
36
|
+
// Protocol versioning
|
|
37
|
+
_protocolVersion = protocol.PROTOCOL_VERSION;
|
|
39
38
|
// Ready promise
|
|
40
39
|
_readyResolve;
|
|
41
40
|
_readyReject;
|
|
@@ -52,8 +51,8 @@ class ControllerImpl {
|
|
|
52
51
|
// ---------------------------------------------------------------------------
|
|
53
52
|
// Properties (readonly)
|
|
54
53
|
// ---------------------------------------------------------------------------
|
|
55
|
-
get
|
|
56
|
-
return this.
|
|
54
|
+
get myPlayerIndex() {
|
|
55
|
+
return this._myPlayerIndex;
|
|
57
56
|
}
|
|
58
57
|
get roomCode() {
|
|
59
58
|
return this._roomCode;
|
|
@@ -64,6 +63,12 @@ class ControllerImpl {
|
|
|
64
63
|
get isDestroyed() {
|
|
65
64
|
return this._isDestroyed;
|
|
66
65
|
}
|
|
66
|
+
get isConnected() {
|
|
67
|
+
return this._isConnected;
|
|
68
|
+
}
|
|
69
|
+
get protocolVersion() {
|
|
70
|
+
return this._protocolVersion;
|
|
71
|
+
}
|
|
67
72
|
/**
|
|
68
73
|
* Read-only list of all known controllers (players) in the room.
|
|
69
74
|
* Returns full ControllerInfo including playerIndex, nickname, connected status, and appearance.
|
|
@@ -80,6 +85,9 @@ class ControllerImpl {
|
|
|
80
85
|
getControllerCount() {
|
|
81
86
|
return this._controllers.filter((c) => c.connected).length;
|
|
82
87
|
}
|
|
88
|
+
getController(playerIndex) {
|
|
89
|
+
return this._controllers.find((c) => c.playerIndex === playerIndex);
|
|
90
|
+
}
|
|
83
91
|
// ---------------------------------------------------------------------------
|
|
84
92
|
// Initialization
|
|
85
93
|
// ---------------------------------------------------------------------------
|
|
@@ -110,7 +118,7 @@ class ControllerImpl {
|
|
|
110
118
|
};
|
|
111
119
|
window.addEventListener("message", this.boundMessageHandler);
|
|
112
120
|
this.logger.lifecycle("Sending _bridge:ready to parent");
|
|
113
|
-
window.parent.postMessage({ type: "_bridge:ready" }, parentOrigin);
|
|
121
|
+
window.parent.postMessage({ type: "_bridge:ready", protocolVersion: protocol.PROTOCOL_VERSION }, parentOrigin);
|
|
114
122
|
}
|
|
115
123
|
handleInit(msg, parentOrigin) {
|
|
116
124
|
const initPayload = msg.payload;
|
|
@@ -149,28 +157,44 @@ class ControllerImpl {
|
|
|
149
157
|
this._readyReject(error);
|
|
150
158
|
return;
|
|
151
159
|
}
|
|
152
|
-
this.transport = new PostMessageTransport.PostMessageTransport(parentOrigin);
|
|
160
|
+
this.transport = this.config.transport ?? new PostMessageTransport.PostMessageTransport(parentOrigin);
|
|
153
161
|
this._roomCode = initData.roomCode;
|
|
154
|
-
|
|
162
|
+
const serverProtocolVersion = initData.protocolVersion;
|
|
163
|
+
if (serverProtocolVersion !== void 0) {
|
|
164
|
+
this._protocolVersion = serverProtocolVersion;
|
|
165
|
+
if (serverProtocolVersion !== protocol.PROTOCOL_VERSION) {
|
|
166
|
+
this.logger.warn(
|
|
167
|
+
`Protocol version mismatch: SDK v${protocol.PROTOCOL_VERSION}, server v${serverProtocolVersion}. Some features may not work correctly.`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
this._myPlayerIndex = initData.myIndex;
|
|
155
172
|
const initPlayers = initData.players;
|
|
156
|
-
this._controllers = initPlayers.filter((p) => typeof p.playerIndex === "number").map((p) => (
|
|
157
|
-
playerIndex: p.playerIndex,
|
|
158
|
-
nickname: p.nickname || p.name || `Player ${p.playerIndex + 1}`,
|
|
159
|
-
connected: p.connected !== false,
|
|
160
|
-
appearance: p.appearance ?? p.character
|
|
161
|
-
}));
|
|
173
|
+
this._controllers = initPlayers.filter((p) => typeof p.playerIndex === "number").map((p, index) => shared.mapPlayerDTO(p, index));
|
|
162
174
|
this.setupEventHandlers();
|
|
163
175
|
for (const { event, handler } of this._pendingHandlers) {
|
|
164
176
|
this.setupUserEventHandler(event, handler);
|
|
165
177
|
}
|
|
166
178
|
this._pendingHandlers = [];
|
|
179
|
+
this._isConnected = true;
|
|
167
180
|
this._isReady = true;
|
|
181
|
+
for (const buffered of this._outboundBuffer) {
|
|
182
|
+
try {
|
|
183
|
+
switch (buffered.method) {
|
|
184
|
+
case "send":
|
|
185
|
+
this.send(buffered.args[0], buffered.args[1]);
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
} catch (err) {
|
|
189
|
+
this.handleError(err instanceof errors.SmoreSDKError ? err : new errors.SmoreSDKError("UNKNOWN", "Failed to flush buffered message"));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
this._outboundBuffer = [];
|
|
168
193
|
this.logger.lifecycle("Controller ready", {
|
|
169
194
|
roomCode: this._roomCode,
|
|
170
|
-
myIndex: this.
|
|
195
|
+
myIndex: this._myPlayerIndex
|
|
171
196
|
});
|
|
172
|
-
|
|
173
|
-
if (autoReady) {
|
|
197
|
+
if (this.config.autoReady !== false) {
|
|
174
198
|
this.logger.lifecycle("Auto-signaling ready (autoReady enabled)");
|
|
175
199
|
this.signalReady();
|
|
176
200
|
}
|
|
@@ -185,21 +209,16 @@ class ControllerImpl {
|
|
|
185
209
|
this.logger.debug("Received _bridge:update", updateData);
|
|
186
210
|
if (updateData.players && Array.isArray(updateData.players)) {
|
|
187
211
|
const players = updateData.players;
|
|
188
|
-
const newControllers = players.filter((p) => typeof p.playerIndex === "number").map((p) => (
|
|
189
|
-
playerIndex: p.playerIndex,
|
|
190
|
-
nickname: p.nickname || p.name || `Player ${p.playerIndex + 1}`,
|
|
191
|
-
connected: p.connected !== false,
|
|
192
|
-
appearance: p.appearance ?? p.character
|
|
193
|
-
}));
|
|
212
|
+
const newControllers = players.filter((p) => typeof p.playerIndex === "number").map((p, index) => shared.mapPlayerDTO(p, index));
|
|
194
213
|
const oldControllers = this._controllers;
|
|
195
214
|
for (const nc of newControllers) {
|
|
196
215
|
if (!oldControllers.some((oc) => oc.playerIndex === nc.playerIndex)) {
|
|
197
|
-
this.
|
|
216
|
+
this._emitLifecycle("$controller-join", nc.playerIndex, nc);
|
|
198
217
|
}
|
|
199
218
|
}
|
|
200
219
|
for (const oc of oldControllers) {
|
|
201
220
|
if (!newControllers.some((nc) => nc.playerIndex === oc.playerIndex)) {
|
|
202
|
-
this.
|
|
221
|
+
this._emitLifecycle("$controller-leave", oc.playerIndex);
|
|
203
222
|
}
|
|
204
223
|
}
|
|
205
224
|
for (const nc of newControllers) {
|
|
@@ -207,11 +226,11 @@ class ControllerImpl {
|
|
|
207
226
|
if (oc) {
|
|
208
227
|
if (oc.connected && !nc.connected) {
|
|
209
228
|
this.logger.debug("Player disconnected (via update)", { playerIndex: nc.playerIndex });
|
|
210
|
-
this.
|
|
229
|
+
this._emitLifecycle("$controller-disconnect", nc.playerIndex);
|
|
211
230
|
}
|
|
212
231
|
if (!oc.connected && nc.connected) {
|
|
213
232
|
this.logger.debug("Player reconnected (via update)", { playerIndex: nc.playerIndex });
|
|
214
|
-
this.
|
|
233
|
+
this._emitLifecycle("$controller-reconnect", nc.playerIndex, nc);
|
|
215
234
|
}
|
|
216
235
|
}
|
|
217
236
|
}
|
|
@@ -226,19 +245,10 @@ class ControllerImpl {
|
|
|
226
245
|
const playerIndex = playerInfo?.playerIndex ?? data.playerIndex;
|
|
227
246
|
if (playerIndex !== void 0) {
|
|
228
247
|
if (this._controllers.some((c) => c.playerIndex === playerIndex)) return;
|
|
229
|
-
const controllerInfo = playerInfo ? {
|
|
230
|
-
playerIndex,
|
|
231
|
-
nickname: playerInfo.nickname || playerInfo.name || `Player ${playerIndex + 1}`,
|
|
232
|
-
connected: playerInfo.connected !== false,
|
|
233
|
-
appearance: playerInfo.appearance ?? playerInfo.character
|
|
234
|
-
} : {
|
|
235
|
-
playerIndex,
|
|
236
|
-
nickname: `Player ${playerIndex + 1}`,
|
|
237
|
-
connected: true
|
|
238
|
-
};
|
|
248
|
+
const controllerInfo = playerInfo ? shared.mapPlayerDTO(playerInfo, playerIndex) : shared.mapPlayerDTO({ playerIndex, connected: true }, playerIndex);
|
|
239
249
|
this._controllers = [...this._controllers, controllerInfo];
|
|
240
250
|
this.logger.debug("Player joined", { playerIndex });
|
|
241
|
-
this.
|
|
251
|
+
this._emitLifecycle("$controller-join", playerIndex, controllerInfo);
|
|
242
252
|
}
|
|
243
253
|
});
|
|
244
254
|
this.registerHandler(events.SMORE_EVENTS.PLAYER_LEFT, (raw) => {
|
|
@@ -248,7 +258,7 @@ class ControllerImpl {
|
|
|
248
258
|
if (!this._controllers.some((c) => c.playerIndex === playerIndex)) return;
|
|
249
259
|
this._controllers = this._controllers.filter((c) => c.playerIndex !== playerIndex);
|
|
250
260
|
this.logger.debug("Player left", { playerIndex });
|
|
251
|
-
this.
|
|
261
|
+
this._emitLifecycle("$controller-leave", playerIndex);
|
|
252
262
|
}
|
|
253
263
|
});
|
|
254
264
|
this.registerHandler(events.SMORE_EVENTS.PLAYER_DISCONNECTED, (raw) => {
|
|
@@ -260,7 +270,7 @@ class ControllerImpl {
|
|
|
260
270
|
(c) => c.playerIndex === playerIndex ? { ...c, connected: false } : c
|
|
261
271
|
);
|
|
262
272
|
this.logger.debug("Player disconnected", { playerIndex });
|
|
263
|
-
this.
|
|
273
|
+
this._emitLifecycle("$controller-disconnect", playerIndex);
|
|
264
274
|
}
|
|
265
275
|
});
|
|
266
276
|
this.registerHandler(events.SMORE_EVENTS.PLAYER_RECONNECTED, (raw) => {
|
|
@@ -268,21 +278,12 @@ class ControllerImpl {
|
|
|
268
278
|
const playerData = data.player;
|
|
269
279
|
const playerIndex = playerData?.playerIndex ?? data.playerIndex;
|
|
270
280
|
if (playerIndex !== void 0) {
|
|
271
|
-
const controllerInfo = playerData ? {
|
|
272
|
-
playerIndex,
|
|
273
|
-
nickname: playerData.nickname || playerData.name || `Player ${playerIndex + 1}`,
|
|
274
|
-
connected: true,
|
|
275
|
-
appearance: playerData.appearance ?? playerData.character
|
|
276
|
-
} : {
|
|
277
|
-
playerIndex,
|
|
278
|
-
nickname: `Player ${playerIndex + 1}`,
|
|
279
|
-
connected: true
|
|
280
|
-
};
|
|
281
|
+
const controllerInfo = playerData ? shared.mapPlayerDTO(playerData, playerIndex) : shared.mapPlayerDTO({ playerIndex, connected: true }, playerIndex);
|
|
281
282
|
this._controllers = this._controllers.map(
|
|
282
283
|
(c) => c.playerIndex === playerIndex ? controllerInfo : c
|
|
283
284
|
);
|
|
284
285
|
this.logger.debug("Player reconnected", { playerIndex });
|
|
285
|
-
this.
|
|
286
|
+
this._emitLifecycle("$controller-reconnect", playerIndex, controllerInfo);
|
|
286
287
|
}
|
|
287
288
|
});
|
|
288
289
|
this.registerHandler(events.SMORE_EVENTS.PLAYER_CHARACTER_UPDATED, (raw) => {
|
|
@@ -295,19 +296,37 @@ class ControllerImpl {
|
|
|
295
296
|
(c) => c.playerIndex === pi ? { ...c, appearance } : c
|
|
296
297
|
);
|
|
297
298
|
this.logger.debug("Player character updated", { playerIndex: pi });
|
|
298
|
-
this.
|
|
299
|
+
this._emitLifecycle("$character-updated", pi, appearance ?? null);
|
|
299
300
|
}
|
|
300
301
|
});
|
|
301
302
|
this.registerHandler(events.SMORE_EVENTS.RATE_LIMITED, (raw) => {
|
|
302
303
|
const data = raw;
|
|
303
|
-
const
|
|
304
|
-
this.
|
|
305
|
-
|
|
304
|
+
const eventName = data?.event ?? "unknown";
|
|
305
|
+
this.handleError(
|
|
306
|
+
new errors.SmoreSDKError("RATE_LIMITED", `Server rate-limited event: ${eventName}`, {
|
|
307
|
+
details: { event: eventName }
|
|
308
|
+
})
|
|
309
|
+
);
|
|
310
|
+
});
|
|
311
|
+
this.registerHandler(events.SMORE_EVENTS.GAME_OVER, (raw) => {
|
|
312
|
+
const data = raw;
|
|
313
|
+
this.logger.lifecycle("Game over", data?.results);
|
|
314
|
+
this._emitLifecycle("$game-over", data?.results);
|
|
306
315
|
});
|
|
307
316
|
this.registerHandler(events.SMORE_EVENTS.ALL_READY, () => {
|
|
308
317
|
this.logger.lifecycle("All participants ready");
|
|
309
318
|
this._allReadyFired = true;
|
|
310
|
-
this.
|
|
319
|
+
this._emitLifecycle("$all-ready");
|
|
320
|
+
});
|
|
321
|
+
this.registerHandler(events.SMORE_EVENTS.SELF_DISCONNECTED, () => {
|
|
322
|
+
this._isConnected = false;
|
|
323
|
+
this.logger.lifecycle("Connection lost");
|
|
324
|
+
this._emitLifecycle("$connection-change", false);
|
|
325
|
+
});
|
|
326
|
+
this.registerHandler(events.SMORE_EVENTS.SELF_RECONNECTED, () => {
|
|
327
|
+
this._isConnected = true;
|
|
328
|
+
this.logger.lifecycle("Connection restored");
|
|
329
|
+
this._emitLifecycle("$connection-change", true);
|
|
311
330
|
});
|
|
312
331
|
}
|
|
313
332
|
/**
|
|
@@ -346,58 +365,70 @@ class ControllerImpl {
|
|
|
346
365
|
this.registeredHandlers.push({ event, handler });
|
|
347
366
|
}
|
|
348
367
|
// ---------------------------------------------------------------------------
|
|
368
|
+
// Lifecycle Listener Helpers
|
|
369
|
+
// ---------------------------------------------------------------------------
|
|
370
|
+
_addLifecycleListener(event, listener) {
|
|
371
|
+
let set = this._lifecycleListeners.get(event);
|
|
372
|
+
if (!set) {
|
|
373
|
+
set = /* @__PURE__ */ new Set();
|
|
374
|
+
this._lifecycleListeners.set(event, set);
|
|
375
|
+
}
|
|
376
|
+
set.add(listener);
|
|
377
|
+
return () => {
|
|
378
|
+
set.delete(listener);
|
|
379
|
+
if (set.size === 0) this._lifecycleListeners.delete(event);
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
_emitLifecycle(event, ...args) {
|
|
383
|
+
this._lifecycleListeners.get(event)?.forEach((cb) => {
|
|
384
|
+
try {
|
|
385
|
+
cb(...args);
|
|
386
|
+
} catch (err) {
|
|
387
|
+
this.handleError(
|
|
388
|
+
new errors.SmoreSDKError("UNKNOWN", `Error in lifecycle handler for "${event}"`, {
|
|
389
|
+
cause: err instanceof Error ? err : void 0,
|
|
390
|
+
details: { event }
|
|
391
|
+
})
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
_hasLifecycleListeners(event) {
|
|
397
|
+
const set = this._lifecycleListeners.get(event);
|
|
398
|
+
return set !== void 0 && set.size > 0;
|
|
399
|
+
}
|
|
400
|
+
// ---------------------------------------------------------------------------
|
|
349
401
|
// Lifecycle Methods
|
|
350
402
|
// ---------------------------------------------------------------------------
|
|
351
403
|
onAllReady(callback) {
|
|
352
404
|
if (this._allReadyFired) {
|
|
353
405
|
callback();
|
|
354
406
|
}
|
|
355
|
-
this.
|
|
356
|
-
return () => {
|
|
357
|
-
this._onAllReadyCallbacks.delete(callback);
|
|
358
|
-
};
|
|
407
|
+
return this._addLifecycleListener("$all-ready", callback);
|
|
359
408
|
}
|
|
360
409
|
onControllerJoin(callback) {
|
|
361
|
-
this.
|
|
362
|
-
return () => {
|
|
363
|
-
this._onControllerJoinCallbacks.delete(callback);
|
|
364
|
-
};
|
|
410
|
+
return this._addLifecycleListener("$controller-join", callback);
|
|
365
411
|
}
|
|
366
412
|
onControllerLeave(callback) {
|
|
367
|
-
this.
|
|
368
|
-
return () => {
|
|
369
|
-
this._onControllerLeaveCallbacks.delete(callback);
|
|
370
|
-
};
|
|
413
|
+
return this._addLifecycleListener("$controller-leave", callback);
|
|
371
414
|
}
|
|
372
415
|
onControllerDisconnect(callback) {
|
|
373
|
-
this.
|
|
374
|
-
return () => {
|
|
375
|
-
this._onControllerDisconnectCallbacks.delete(callback);
|
|
376
|
-
};
|
|
416
|
+
return this._addLifecycleListener("$controller-disconnect", callback);
|
|
377
417
|
}
|
|
378
418
|
onControllerReconnect(callback) {
|
|
379
|
-
this.
|
|
380
|
-
return () => {
|
|
381
|
-
this._onControllerReconnectCallbacks.delete(callback);
|
|
382
|
-
};
|
|
419
|
+
return this._addLifecycleListener("$controller-reconnect", callback);
|
|
383
420
|
}
|
|
384
421
|
onCharacterUpdated(callback) {
|
|
385
|
-
this.
|
|
386
|
-
return () => {
|
|
387
|
-
this._onCharacterUpdatedCallbacks.delete(callback);
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
onRateLimited(callback) {
|
|
391
|
-
this._onRateLimitedCallbacks.add(callback);
|
|
392
|
-
return () => {
|
|
393
|
-
this._onRateLimitedCallbacks.delete(callback);
|
|
394
|
-
};
|
|
422
|
+
return this._addLifecycleListener("$character-updated", callback);
|
|
395
423
|
}
|
|
396
424
|
onError(callback) {
|
|
397
|
-
this.
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
425
|
+
return this._addLifecycleListener("$error", callback);
|
|
426
|
+
}
|
|
427
|
+
onConnectionChange(callback) {
|
|
428
|
+
return this._addLifecycleListener("$connection-change", callback);
|
|
429
|
+
}
|
|
430
|
+
onGameOver(callback) {
|
|
431
|
+
return this._addLifecycleListener("$game-over", callback);
|
|
401
432
|
}
|
|
402
433
|
// ---------------------------------------------------------------------------
|
|
403
434
|
// Communication Methods
|
|
@@ -413,8 +444,16 @@ class ControllerImpl {
|
|
|
413
444
|
* Use the onError callback or smore:rate-limited event to detect rate limiting.
|
|
414
445
|
*/
|
|
415
446
|
send(event, data) {
|
|
416
|
-
this.
|
|
447
|
+
if (this._isDestroyed) {
|
|
448
|
+
throw new errors.SmoreSDKError("DESTROYED", "Cannot call send() after destroy()");
|
|
449
|
+
}
|
|
450
|
+
if (!this._isReady || !this.transport) {
|
|
451
|
+
this._outboundBuffer.push({ method: "send", args: [event, data] });
|
|
452
|
+
this.logger.debug(`Buffered send "${event}" (controller not ready yet)`);
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
417
455
|
events.validateEventName(event);
|
|
456
|
+
shared.validatePayloadSize(data);
|
|
418
457
|
if (typeof data !== "object" || data === null) {
|
|
419
458
|
this.logger.warn(
|
|
420
459
|
'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).'
|
|
@@ -423,12 +462,6 @@ class ControllerImpl {
|
|
|
423
462
|
this.logSend(event, data);
|
|
424
463
|
this.transport.emit(event, data);
|
|
425
464
|
}
|
|
426
|
-
sendRaw(event, data) {
|
|
427
|
-
this.ensureReady("sendRaw");
|
|
428
|
-
events.validateEventName(event);
|
|
429
|
-
this.logSend(event, data);
|
|
430
|
-
this.transport.emit(event, data);
|
|
431
|
-
}
|
|
432
465
|
signalReady() {
|
|
433
466
|
this.ensureReady("signalReady");
|
|
434
467
|
this.logSend(events.SMORE_EVENTS.GAME_READY, {});
|
|
@@ -450,6 +483,16 @@ class ControllerImpl {
|
|
|
450
483
|
* handler receives `(data)` -- targeted to this specific controller.
|
|
451
484
|
*/
|
|
452
485
|
on(event, handler) {
|
|
486
|
+
if (typeof event === "string" && event.startsWith("$")) {
|
|
487
|
+
const validEvents = events.CONTROLLER_LIFECYCLE_EVENTS;
|
|
488
|
+
if (!validEvents.has(event)) {
|
|
489
|
+
throw new errors.SmoreSDKError("INVALID_EVENT", `Unknown lifecycle event: "${event}". Valid lifecycle events: ${Array.from(validEvents).join(", ")}`);
|
|
490
|
+
}
|
|
491
|
+
if (event === "$all-ready" && this._allReadyFired) {
|
|
492
|
+
handler();
|
|
493
|
+
}
|
|
494
|
+
return this._addLifecycleListener(event, handler);
|
|
495
|
+
}
|
|
453
496
|
events.validateEventName(event);
|
|
454
497
|
let listeners = this.eventListeners.get(event);
|
|
455
498
|
if (!listeners) {
|
|
@@ -502,6 +545,23 @@ class ControllerImpl {
|
|
|
502
545
|
* `off(event, originalHandler)`. Use the returned unsubscribe function instead.
|
|
503
546
|
*/
|
|
504
547
|
once(event, handler) {
|
|
548
|
+
if (typeof event === "string" && event.startsWith("$")) {
|
|
549
|
+
const validEvents = events.CONTROLLER_LIFECYCLE_EVENTS;
|
|
550
|
+
if (!validEvents.has(event)) {
|
|
551
|
+
throw new errors.SmoreSDKError("INVALID_EVENT", `Unknown lifecycle event: "${event}"`);
|
|
552
|
+
}
|
|
553
|
+
if (event === "$all-ready" && this._allReadyFired) {
|
|
554
|
+
handler();
|
|
555
|
+
return () => {
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
const wrapper = (...args) => {
|
|
559
|
+
unsub();
|
|
560
|
+
handler(...args);
|
|
561
|
+
};
|
|
562
|
+
const unsub = this._addLifecycleListener(event, wrapper);
|
|
563
|
+
return unsub;
|
|
564
|
+
}
|
|
505
565
|
const unsubscribe = this.on(event, ((data) => {
|
|
506
566
|
unsubscribe();
|
|
507
567
|
handler(data);
|
|
@@ -509,6 +569,14 @@ class ControllerImpl {
|
|
|
509
569
|
return unsubscribe;
|
|
510
570
|
}
|
|
511
571
|
off(event, handler) {
|
|
572
|
+
if (typeof event === "string" && event.startsWith("$")) {
|
|
573
|
+
if (!handler) {
|
|
574
|
+
this._lifecycleListeners.delete(event);
|
|
575
|
+
} else {
|
|
576
|
+
this._lifecycleListeners.get(event)?.delete(handler);
|
|
577
|
+
}
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
512
580
|
if (!handler) {
|
|
513
581
|
this.eventListeners.delete(event);
|
|
514
582
|
this.transport?.off(event);
|
|
@@ -536,6 +604,21 @@ class ControllerImpl {
|
|
|
536
604
|
);
|
|
537
605
|
}
|
|
538
606
|
}
|
|
607
|
+
removeAllListeners(event) {
|
|
608
|
+
if (event) {
|
|
609
|
+
this.eventListeners.delete(event);
|
|
610
|
+
this.transport?.off(event);
|
|
611
|
+
this.registeredHandlers = this.registeredHandlers.filter((h) => h.event !== event);
|
|
612
|
+
for (const [key, val] of this.handlerToTransport) {
|
|
613
|
+
if (val.event === event) this.handlerToTransport.delete(key);
|
|
614
|
+
}
|
|
615
|
+
this._pendingHandlers = this._pendingHandlers.filter((p) => p.event !== event);
|
|
616
|
+
} else {
|
|
617
|
+
for (const evt of [...this.eventListeners.keys()]) {
|
|
618
|
+
this.removeAllListeners(evt);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
539
622
|
// ---------------------------------------------------------------------------
|
|
540
623
|
// Cleanup
|
|
541
624
|
// ---------------------------------------------------------------------------
|
|
@@ -559,14 +642,9 @@ class ControllerImpl {
|
|
|
559
642
|
this.eventListeners.clear();
|
|
560
643
|
this.handlerToTransport.clear();
|
|
561
644
|
this._pendingHandlers = [];
|
|
562
|
-
this.
|
|
563
|
-
this.
|
|
564
|
-
this.
|
|
565
|
-
this._onControllerDisconnectCallbacks.clear();
|
|
566
|
-
this._onControllerReconnectCallbacks.clear();
|
|
567
|
-
this._onCharacterUpdatedCallbacks.clear();
|
|
568
|
-
this._onRateLimitedCallbacks.clear();
|
|
569
|
-
this._onErrorCallbacks.clear();
|
|
645
|
+
this._lifecycleListeners.clear();
|
|
646
|
+
this._isConnected = false;
|
|
647
|
+
this._outboundBuffer = [];
|
|
570
648
|
if (this.transport) {
|
|
571
649
|
this.transport.destroy();
|
|
572
650
|
this.transport = null;
|
|
@@ -598,8 +676,8 @@ class ControllerImpl {
|
|
|
598
676
|
handleError(error) {
|
|
599
677
|
this.logger.warn(`Error in handler: ${error.message}`);
|
|
600
678
|
const smoreError = error.toSmoreError();
|
|
601
|
-
if (this.
|
|
602
|
-
this.
|
|
679
|
+
if (this._hasLifecycleListeners("$error")) {
|
|
680
|
+
this._emitLifecycle("$error", smoreError);
|
|
603
681
|
} else {
|
|
604
682
|
this.logger.error(error.message, error.details);
|
|
605
683
|
}
|