@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/screen.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
|
function validatePlayerIndex(playerIndex, controllers) {
|
|
@@ -36,17 +36,16 @@ class ScreenImpl {
|
|
|
36
36
|
handlerToTransport = /* @__PURE__ */ new Map();
|
|
37
37
|
// Pending handlers registered via on() before transport is ready
|
|
38
38
|
_pendingHandlers = [];
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
_onControllerDisconnectCallbacks = /* @__PURE__ */ new Set();
|
|
44
|
-
_onControllerReconnectCallbacks = /* @__PURE__ */ new Set();
|
|
45
|
-
_onCharacterUpdatedCallbacks = /* @__PURE__ */ new Set();
|
|
46
|
-
_onRateLimitedCallbacks = /* @__PURE__ */ new Set();
|
|
47
|
-
_onErrorCallbacks = /* @__PURE__ */ new Set();
|
|
39
|
+
// Unified lifecycle listener map (supports both onXxx() and on('$xxx') patterns)
|
|
40
|
+
_lifecycleListeners = /* @__PURE__ */ new Map();
|
|
41
|
+
// Outbound message buffer (messages sent before ready)
|
|
42
|
+
_outboundBuffer = [];
|
|
48
43
|
// Whether all-ready has fired
|
|
49
44
|
_allReadyFired = false;
|
|
45
|
+
// Self-connection awareness
|
|
46
|
+
_isConnected = false;
|
|
47
|
+
// Protocol versioning
|
|
48
|
+
_protocolVersion = protocol.PROTOCOL_VERSION;
|
|
50
49
|
// Ready promise
|
|
51
50
|
_readyResolve;
|
|
52
51
|
_readyReject;
|
|
@@ -108,8 +107,17 @@ class ScreenImpl {
|
|
|
108
107
|
this._readyReject(error);
|
|
109
108
|
return;
|
|
110
109
|
}
|
|
111
|
-
this.transport = new PostMessageTransport.PostMessageTransport(parentOrigin);
|
|
110
|
+
this.transport = this.config.transport ?? new PostMessageTransport.PostMessageTransport(parentOrigin);
|
|
112
111
|
this._roomCode = initData.roomCode;
|
|
112
|
+
const serverProtocolVersion = initData.protocolVersion;
|
|
113
|
+
if (serverProtocolVersion !== void 0) {
|
|
114
|
+
this._protocolVersion = serverProtocolVersion;
|
|
115
|
+
if (serverProtocolVersion !== protocol.PROTOCOL_VERSION) {
|
|
116
|
+
this.logger.warn(
|
|
117
|
+
`Protocol version mismatch: SDK v${protocol.PROTOCOL_VERSION}, server v${serverProtocolVersion}. Some features may not work correctly.`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
113
121
|
this._controllers = this.mapControllersFromInit(initData.players);
|
|
114
122
|
if (this._controllers.length === 0) {
|
|
115
123
|
this.logger.warn("Screen initialized with zero controllers");
|
|
@@ -119,13 +127,28 @@ class ScreenImpl {
|
|
|
119
127
|
this.setupUserEventHandler(event, handler);
|
|
120
128
|
}
|
|
121
129
|
this._pendingHandlers = [];
|
|
130
|
+
this._isConnected = true;
|
|
122
131
|
this._isReady = true;
|
|
132
|
+
for (const buffered of this._outboundBuffer) {
|
|
133
|
+
try {
|
|
134
|
+
switch (buffered.method) {
|
|
135
|
+
case "broadcast":
|
|
136
|
+
this.broadcast(buffered.args[0], buffered.args[1]);
|
|
137
|
+
break;
|
|
138
|
+
case "sendToController":
|
|
139
|
+
this.sendToController(buffered.args[0], buffered.args[1], buffered.args[2]);
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
} catch (err) {
|
|
143
|
+
this.handleError(err instanceof errors.SmoreSDKError ? err : new errors.SmoreSDKError("UNKNOWN", "Failed to flush buffered message"));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
this._outboundBuffer = [];
|
|
123
147
|
this.logger.lifecycle("Screen ready", {
|
|
124
148
|
roomCode: this._roomCode,
|
|
125
149
|
controllers: this._controllers.length
|
|
126
150
|
});
|
|
127
|
-
|
|
128
|
-
if (autoReady) {
|
|
151
|
+
if (this.config.autoReady !== false) {
|
|
129
152
|
this.logger.lifecycle("Auto-signaling ready (autoReady enabled)");
|
|
130
153
|
this.signalReady();
|
|
131
154
|
}
|
|
@@ -143,13 +166,13 @@ class ScreenImpl {
|
|
|
143
166
|
for (const nc of newControllers) {
|
|
144
167
|
if (!oldControllers.some((oc) => oc.playerIndex === nc.playerIndex)) {
|
|
145
168
|
this.logger.lifecycle("Controller joined (via update)", { playerIndex: nc.playerIndex });
|
|
146
|
-
this.
|
|
169
|
+
this._emitLifecycle("$controller-join", nc.playerIndex, nc);
|
|
147
170
|
}
|
|
148
171
|
}
|
|
149
172
|
for (const oc of oldControllers) {
|
|
150
173
|
if (!newControllers.some((nc) => nc.playerIndex === oc.playerIndex)) {
|
|
151
174
|
this.logger.lifecycle("Controller left (via update)", { playerIndex: oc.playerIndex });
|
|
152
|
-
this.
|
|
175
|
+
this._emitLifecycle("$controller-leave", oc.playerIndex);
|
|
153
176
|
}
|
|
154
177
|
}
|
|
155
178
|
}
|
|
@@ -159,18 +182,11 @@ class ScreenImpl {
|
|
|
159
182
|
}
|
|
160
183
|
};
|
|
161
184
|
window.addEventListener("message", this.boundMessageHandler);
|
|
162
|
-
window.parent.postMessage({ type: "_bridge:ready" }, parentOrigin);
|
|
185
|
+
window.parent.postMessage({ type: "_bridge:ready", protocolVersion: protocol.PROTOCOL_VERSION }, parentOrigin);
|
|
163
186
|
this.logger.lifecycle("Sent _bridge:ready to parent");
|
|
164
187
|
}
|
|
165
188
|
mapControllersFromInit(players) {
|
|
166
|
-
return players.map((p, index) => (
|
|
167
|
-
playerIndex: p.playerIndex ?? index,
|
|
168
|
-
// Fallback to `nickname` for defensive compatibility (server currently always sends `name`)
|
|
169
|
-
nickname: p.nickname || p.name || `Player ${index + 1}`,
|
|
170
|
-
connected: p.connected !== false,
|
|
171
|
-
// Fallback to `appearance` for defensive compatibility (server currently always sends `character`)
|
|
172
|
-
appearance: p.appearance ?? p.character
|
|
173
|
-
}));
|
|
189
|
+
return players.map((p, index) => shared.mapPlayerDTO(p, index));
|
|
174
190
|
}
|
|
175
191
|
setupEventHandlers() {
|
|
176
192
|
if (!this.transport) return;
|
|
@@ -178,16 +194,11 @@ class ScreenImpl {
|
|
|
178
194
|
const payload = data;
|
|
179
195
|
const playerData = payload?.player;
|
|
180
196
|
if (playerData && typeof playerData.playerIndex === "number") {
|
|
181
|
-
const controllerInfo =
|
|
182
|
-
playerIndex: playerData.playerIndex,
|
|
183
|
-
nickname: playerData.nickname || playerData.name || `Player ${playerData.playerIndex + 1}`,
|
|
184
|
-
connected: playerData.connected !== false,
|
|
185
|
-
appearance: playerData.appearance ?? playerData.character
|
|
186
|
-
};
|
|
197
|
+
const controllerInfo = shared.mapPlayerDTO(playerData, playerData.playerIndex);
|
|
187
198
|
if (this._controllers.some((c) => c.playerIndex === controllerInfo.playerIndex)) return;
|
|
188
199
|
this._controllers = [...this._controllers, controllerInfo];
|
|
189
200
|
this.logger.lifecycle("Controller joined", { playerIndex: controllerInfo.playerIndex });
|
|
190
|
-
this.
|
|
201
|
+
this._emitLifecycle("$controller-join", controllerInfo.playerIndex, controllerInfo);
|
|
191
202
|
}
|
|
192
203
|
});
|
|
193
204
|
this.registerTransportHandler(events.SMORE_EVENTS.PLAYER_LEFT, (data) => {
|
|
@@ -197,7 +208,7 @@ class ScreenImpl {
|
|
|
197
208
|
if (!this._controllers.some((c) => c.playerIndex === playerIndex)) return;
|
|
198
209
|
this._controllers = this._controllers.filter((c) => c.playerIndex !== playerIndex);
|
|
199
210
|
this.logger.lifecycle("Controller left", { playerIndex });
|
|
200
|
-
this.
|
|
211
|
+
this._emitLifecycle("$controller-leave", playerIndex);
|
|
201
212
|
}
|
|
202
213
|
});
|
|
203
214
|
this.registerTransportHandler(events.SMORE_EVENTS.PLAYER_DISCONNECTED, (data) => {
|
|
@@ -208,24 +219,19 @@ class ScreenImpl {
|
|
|
208
219
|
(c) => c.playerIndex === playerIndex ? { ...c, connected: false } : c
|
|
209
220
|
);
|
|
210
221
|
this.logger.lifecycle("Controller disconnected", { playerIndex });
|
|
211
|
-
this.
|
|
222
|
+
this._emitLifecycle("$controller-disconnect", playerIndex);
|
|
212
223
|
}
|
|
213
224
|
});
|
|
214
225
|
this.registerTransportHandler(events.SMORE_EVENTS.PLAYER_RECONNECTED, (data) => {
|
|
215
226
|
const payload = data;
|
|
216
227
|
const playerData = payload?.player;
|
|
217
228
|
if (playerData && typeof playerData.playerIndex === "number") {
|
|
218
|
-
const controllerInfo =
|
|
219
|
-
playerIndex: playerData.playerIndex,
|
|
220
|
-
nickname: playerData.nickname || playerData.name || `Player ${playerData.playerIndex + 1}`,
|
|
221
|
-
connected: true,
|
|
222
|
-
appearance: playerData.appearance ?? playerData.character
|
|
223
|
-
};
|
|
229
|
+
const controllerInfo = shared.mapPlayerDTO(playerData, playerData.playerIndex);
|
|
224
230
|
this._controllers = this._controllers.map(
|
|
225
231
|
(c) => c.playerIndex === controllerInfo.playerIndex ? controllerInfo : c
|
|
226
232
|
);
|
|
227
233
|
this.logger.lifecycle("Controller reconnected", { playerIndex: controllerInfo.playerIndex });
|
|
228
|
-
this.
|
|
234
|
+
this._emitLifecycle("$controller-reconnect", controllerInfo.playerIndex, controllerInfo);
|
|
229
235
|
}
|
|
230
236
|
});
|
|
231
237
|
this.registerTransportHandler(events.SMORE_EVENTS.PLAYER_CHARACTER_UPDATED, (data) => {
|
|
@@ -238,19 +244,32 @@ class ScreenImpl {
|
|
|
238
244
|
(c) => c.playerIndex === pi ? { ...c, appearance } : c
|
|
239
245
|
);
|
|
240
246
|
this.logger.lifecycle("Player character updated", { playerIndex: pi });
|
|
241
|
-
this.
|
|
247
|
+
this._emitLifecycle("$character-updated", pi, appearance ?? null);
|
|
242
248
|
}
|
|
243
249
|
});
|
|
244
250
|
this.registerTransportHandler(events.SMORE_EVENTS.RATE_LIMITED, (data) => {
|
|
245
251
|
const payload = data;
|
|
246
|
-
const
|
|
247
|
-
this.
|
|
248
|
-
|
|
252
|
+
const eventName = payload?.event ?? "unknown";
|
|
253
|
+
this.handleError(
|
|
254
|
+
new errors.SmoreSDKError("RATE_LIMITED", `Server rate-limited event: ${eventName}`, {
|
|
255
|
+
details: { event: eventName }
|
|
256
|
+
})
|
|
257
|
+
);
|
|
249
258
|
});
|
|
250
259
|
this.registerTransportHandler(events.SMORE_EVENTS.ALL_READY, () => {
|
|
251
260
|
this.logger.lifecycle("All participants ready");
|
|
252
261
|
this._allReadyFired = true;
|
|
253
|
-
this.
|
|
262
|
+
this._emitLifecycle("$all-ready");
|
|
263
|
+
});
|
|
264
|
+
this.registerTransportHandler(events.SMORE_EVENTS.SELF_DISCONNECTED, () => {
|
|
265
|
+
this._isConnected = false;
|
|
266
|
+
this.logger.lifecycle("Connection lost");
|
|
267
|
+
this._emitLifecycle("$connection-change", false);
|
|
268
|
+
});
|
|
269
|
+
this.registerTransportHandler(events.SMORE_EVENTS.SELF_RECONNECTED, () => {
|
|
270
|
+
this._isConnected = true;
|
|
271
|
+
this.logger.lifecycle("Connection restored");
|
|
272
|
+
this._emitLifecycle("$connection-change", true);
|
|
254
273
|
});
|
|
255
274
|
}
|
|
256
275
|
/**
|
|
@@ -319,6 +338,45 @@ class ScreenImpl {
|
|
|
319
338
|
get isDestroyed() {
|
|
320
339
|
return this._isDestroyed;
|
|
321
340
|
}
|
|
341
|
+
get isConnected() {
|
|
342
|
+
return this._isConnected;
|
|
343
|
+
}
|
|
344
|
+
get protocolVersion() {
|
|
345
|
+
return this._protocolVersion;
|
|
346
|
+
}
|
|
347
|
+
// ---------------------------------------------------------------------------
|
|
348
|
+
// Lifecycle Listener Helpers
|
|
349
|
+
// ---------------------------------------------------------------------------
|
|
350
|
+
_addLifecycleListener(event, listener) {
|
|
351
|
+
let set = this._lifecycleListeners.get(event);
|
|
352
|
+
if (!set) {
|
|
353
|
+
set = /* @__PURE__ */ new Set();
|
|
354
|
+
this._lifecycleListeners.set(event, set);
|
|
355
|
+
}
|
|
356
|
+
set.add(listener);
|
|
357
|
+
return () => {
|
|
358
|
+
set.delete(listener);
|
|
359
|
+
if (set.size === 0) this._lifecycleListeners.delete(event);
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
_emitLifecycle(event, ...args) {
|
|
363
|
+
this._lifecycleListeners.get(event)?.forEach((cb) => {
|
|
364
|
+
try {
|
|
365
|
+
cb(...args);
|
|
366
|
+
} catch (err) {
|
|
367
|
+
this.handleError(
|
|
368
|
+
new errors.SmoreSDKError("UNKNOWN", `Error in lifecycle handler for "${event}"`, {
|
|
369
|
+
cause: err instanceof Error ? err : void 0,
|
|
370
|
+
details: { event }
|
|
371
|
+
})
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
_hasLifecycleListeners(event) {
|
|
377
|
+
const set = this._lifecycleListeners.get(event);
|
|
378
|
+
return set !== void 0 && set.size > 0;
|
|
379
|
+
}
|
|
322
380
|
// ---------------------------------------------------------------------------
|
|
323
381
|
// Lifecycle Methods
|
|
324
382
|
// ---------------------------------------------------------------------------
|
|
@@ -326,52 +384,28 @@ class ScreenImpl {
|
|
|
326
384
|
if (this._allReadyFired) {
|
|
327
385
|
callback();
|
|
328
386
|
}
|
|
329
|
-
this.
|
|
330
|
-
return () => {
|
|
331
|
-
this._onAllReadyCallbacks.delete(callback);
|
|
332
|
-
};
|
|
387
|
+
return this._addLifecycleListener("$all-ready", callback);
|
|
333
388
|
}
|
|
334
389
|
onControllerJoin(callback) {
|
|
335
|
-
this.
|
|
336
|
-
return () => {
|
|
337
|
-
this._onControllerJoinCallbacks.delete(callback);
|
|
338
|
-
};
|
|
390
|
+
return this._addLifecycleListener("$controller-join", callback);
|
|
339
391
|
}
|
|
340
392
|
onControllerLeave(callback) {
|
|
341
|
-
this.
|
|
342
|
-
return () => {
|
|
343
|
-
this._onControllerLeaveCallbacks.delete(callback);
|
|
344
|
-
};
|
|
393
|
+
return this._addLifecycleListener("$controller-leave", callback);
|
|
345
394
|
}
|
|
346
395
|
onControllerDisconnect(callback) {
|
|
347
|
-
this.
|
|
348
|
-
return () => {
|
|
349
|
-
this._onControllerDisconnectCallbacks.delete(callback);
|
|
350
|
-
};
|
|
396
|
+
return this._addLifecycleListener("$controller-disconnect", callback);
|
|
351
397
|
}
|
|
352
398
|
onControllerReconnect(callback) {
|
|
353
|
-
this.
|
|
354
|
-
return () => {
|
|
355
|
-
this._onControllerReconnectCallbacks.delete(callback);
|
|
356
|
-
};
|
|
399
|
+
return this._addLifecycleListener("$controller-reconnect", callback);
|
|
357
400
|
}
|
|
358
401
|
onCharacterUpdated(callback) {
|
|
359
|
-
this.
|
|
360
|
-
return () => {
|
|
361
|
-
this._onCharacterUpdatedCallbacks.delete(callback);
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
onRateLimited(callback) {
|
|
365
|
-
this._onRateLimitedCallbacks.add(callback);
|
|
366
|
-
return () => {
|
|
367
|
-
this._onRateLimitedCallbacks.delete(callback);
|
|
368
|
-
};
|
|
402
|
+
return this._addLifecycleListener("$character-updated", callback);
|
|
369
403
|
}
|
|
370
404
|
onError(callback) {
|
|
371
|
-
this.
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
405
|
+
return this._addLifecycleListener("$error", callback);
|
|
406
|
+
}
|
|
407
|
+
onConnectionChange(callback) {
|
|
408
|
+
return this._addLifecycleListener("$connection-change", callback);
|
|
375
409
|
}
|
|
376
410
|
// ---------------------------------------------------------------------------
|
|
377
411
|
// Communication Methods
|
|
@@ -380,7 +414,6 @@ class ScreenImpl {
|
|
|
380
414
|
* Send type-safe events to all controllers.
|
|
381
415
|
*
|
|
382
416
|
* Uses EventMap generic for compile-time type checking of event names and data payloads.
|
|
383
|
-
* Runtime behavior is identical to broadcastRaw - both call validateEventName at runtime.
|
|
384
417
|
*
|
|
385
418
|
* @note Data should be an object. Primitive values will be wrapped as `{ data: value }` by the relay server.
|
|
386
419
|
* @note Maximum payload size is 64KB. Data exceeding this limit will be silently dropped by the server.
|
|
@@ -389,31 +422,18 @@ class ScreenImpl {
|
|
|
389
422
|
*
|
|
390
423
|
* Warning: Avoid sending primitive values directly (string, number, boolean).
|
|
391
424
|
* Wrap in an object: broadcast('event', { value: 42 }) instead of broadcast('event', 42)
|
|
392
|
-
*
|
|
393
|
-
* @see broadcastRaw for bypassing TypeScript type checking (runtime behavior identical)
|
|
394
425
|
*/
|
|
395
426
|
broadcast(event, data) {
|
|
396
|
-
this.
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
this.transport
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
* Bypasses EventMap generic type checks at compile time.
|
|
405
|
-
* Runtime behavior is identical to broadcast - both call validateEventName at runtime.
|
|
406
|
-
*
|
|
407
|
-
* Use this when you need dynamic event names or when working without a predefined EventMap.
|
|
408
|
-
*
|
|
409
|
-
* @note Data should be an object. Primitive values will be wrapped as `{ data: value }` by the relay server.
|
|
410
|
-
* @note Maximum payload size is 64KB. Data exceeding this limit will be silently dropped by the server.
|
|
411
|
-
*
|
|
412
|
-
* @see broadcast for type-safe version using EventMap generic
|
|
413
|
-
*/
|
|
414
|
-
broadcastRaw(event, data) {
|
|
415
|
-
this.ensureReady("broadcastRaw");
|
|
427
|
+
if (this._isDestroyed) {
|
|
428
|
+
throw new errors.SmoreSDKError("DESTROYED", "Cannot call broadcast() after destroy()");
|
|
429
|
+
}
|
|
430
|
+
if (!this._isReady || !this.transport) {
|
|
431
|
+
this._outboundBuffer.push({ method: "broadcast", args: [event, data] });
|
|
432
|
+
this.logger.debug(`Buffered broadcast "${event}" (screen not ready yet)`);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
416
435
|
events.validateEventName(event);
|
|
436
|
+
shared.validatePayloadSize(data);
|
|
417
437
|
this.logger.send(event, data);
|
|
418
438
|
this.transport.emit(event, data);
|
|
419
439
|
}
|
|
@@ -432,24 +452,17 @@ class ScreenImpl {
|
|
|
432
452
|
* @param data - Event data payload
|
|
433
453
|
*/
|
|
434
454
|
sendToController(playerIndex, event, data) {
|
|
435
|
-
this.
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
if (
|
|
439
|
-
this.
|
|
440
|
-
|
|
441
|
-
|
|
455
|
+
if (this._isDestroyed) {
|
|
456
|
+
throw new errors.SmoreSDKError("DESTROYED", "Cannot call sendToController() after destroy()");
|
|
457
|
+
}
|
|
458
|
+
if (!this._isReady || !this.transport) {
|
|
459
|
+
this._outboundBuffer.push({ method: "sendToController", args: [playerIndex, event, data] });
|
|
460
|
+
this.logger.debug(`Buffered sendToController "${event}" -> Player ${playerIndex} (screen not ready yet)`);
|
|
461
|
+
return;
|
|
442
462
|
}
|
|
443
|
-
this.logger.send(`${event} -> Player ${playerIndex}`, data);
|
|
444
|
-
this.transport.emit(event, {
|
|
445
|
-
targetPlayerIndex: playerIndex,
|
|
446
|
-
...data && typeof data === "object" ? data : { data }
|
|
447
|
-
});
|
|
448
|
-
}
|
|
449
|
-
sendToControllerRaw(playerIndex, event, data) {
|
|
450
|
-
this.ensureReady("sendToControllerRaw");
|
|
451
463
|
events.validateEventName(event);
|
|
452
464
|
validatePlayerIndex(playerIndex, this._controllers);
|
|
465
|
+
shared.validatePayloadSize(data);
|
|
453
466
|
if (data && typeof data === "object" && "targetPlayerIndex" in data) {
|
|
454
467
|
this.logger.warn(
|
|
455
468
|
`Event "${event}" data contains reserved field "targetPlayerIndex" which will be overwritten for routing.`
|
|
@@ -484,6 +497,16 @@ class ScreenImpl {
|
|
|
484
497
|
* are queued and activated when the transport becomes available.
|
|
485
498
|
*/
|
|
486
499
|
on(event, handler) {
|
|
500
|
+
if (typeof event === "string" && event.startsWith("$")) {
|
|
501
|
+
const validEvents = events.SCREEN_LIFECYCLE_EVENTS;
|
|
502
|
+
if (!validEvents.has(event)) {
|
|
503
|
+
throw new errors.SmoreSDKError("INVALID_EVENT", `Unknown lifecycle event: "${event}". Valid lifecycle events: ${Array.from(validEvents).join(", ")}`);
|
|
504
|
+
}
|
|
505
|
+
if (event === "$all-ready" && this._allReadyFired) {
|
|
506
|
+
handler();
|
|
507
|
+
}
|
|
508
|
+
return this._addLifecycleListener(event, handler);
|
|
509
|
+
}
|
|
487
510
|
events.validateEventName(event);
|
|
488
511
|
let handlers = this.eventHandlers.get(event);
|
|
489
512
|
if (!handlers) {
|
|
@@ -546,6 +569,23 @@ class ScreenImpl {
|
|
|
546
569
|
* @returns Unsubscribe function to remove the handler before it fires
|
|
547
570
|
*/
|
|
548
571
|
once(event, handler) {
|
|
572
|
+
if (typeof event === "string" && event.startsWith("$")) {
|
|
573
|
+
const validEvents = events.SCREEN_LIFECYCLE_EVENTS;
|
|
574
|
+
if (!validEvents.has(event)) {
|
|
575
|
+
throw new errors.SmoreSDKError("INVALID_EVENT", `Unknown lifecycle event: "${event}"`);
|
|
576
|
+
}
|
|
577
|
+
if (event === "$all-ready" && this._allReadyFired) {
|
|
578
|
+
handler();
|
|
579
|
+
return () => {
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
const wrapper = (...args) => {
|
|
583
|
+
unsub();
|
|
584
|
+
handler(...args);
|
|
585
|
+
};
|
|
586
|
+
const unsub = this._addLifecycleListener(event, wrapper);
|
|
587
|
+
return unsub;
|
|
588
|
+
}
|
|
549
589
|
const wrappedHandler = (playerIndex, data) => {
|
|
550
590
|
unsubscribe();
|
|
551
591
|
handler(playerIndex, data);
|
|
@@ -554,6 +594,14 @@ class ScreenImpl {
|
|
|
554
594
|
return unsubscribe;
|
|
555
595
|
}
|
|
556
596
|
off(event, handler) {
|
|
597
|
+
if (typeof event === "string" && event.startsWith("$")) {
|
|
598
|
+
if (!handler) {
|
|
599
|
+
this._lifecycleListeners.delete(event);
|
|
600
|
+
} else {
|
|
601
|
+
this._lifecycleListeners.get(event)?.delete(handler);
|
|
602
|
+
}
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
557
605
|
if (!handler) {
|
|
558
606
|
this.eventHandlers.delete(event);
|
|
559
607
|
this.transport?.off(event);
|
|
@@ -581,6 +629,21 @@ class ScreenImpl {
|
|
|
581
629
|
);
|
|
582
630
|
}
|
|
583
631
|
}
|
|
632
|
+
removeAllListeners(event) {
|
|
633
|
+
if (event) {
|
|
634
|
+
this.eventHandlers.delete(event);
|
|
635
|
+
this.transport?.off(event);
|
|
636
|
+
this.registeredTransportHandlers = this.registeredTransportHandlers.filter((h) => h.event !== event);
|
|
637
|
+
for (const [key, val] of this.handlerToTransport) {
|
|
638
|
+
if (val.event === event) this.handlerToTransport.delete(key);
|
|
639
|
+
}
|
|
640
|
+
this._pendingHandlers = this._pendingHandlers.filter((p) => p.event !== event);
|
|
641
|
+
} else {
|
|
642
|
+
for (const evt of [...this.eventHandlers.keys()]) {
|
|
643
|
+
this.removeAllListeners(evt);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
584
647
|
// ---------------------------------------------------------------------------
|
|
585
648
|
// Utilities
|
|
586
649
|
// ---------------------------------------------------------------------------
|
|
@@ -590,9 +653,6 @@ class ScreenImpl {
|
|
|
590
653
|
getControllerCount() {
|
|
591
654
|
return this._controllers.filter((c) => c.connected).length;
|
|
592
655
|
}
|
|
593
|
-
hasAnyConnectedControllers() {
|
|
594
|
-
return this._controllers.some((c) => c.connected);
|
|
595
|
-
}
|
|
596
656
|
// ---------------------------------------------------------------------------
|
|
597
657
|
// Cleanup
|
|
598
658
|
// ---------------------------------------------------------------------------
|
|
@@ -616,14 +676,9 @@ class ScreenImpl {
|
|
|
616
676
|
this.eventHandlers.clear();
|
|
617
677
|
this.handlerToTransport.clear();
|
|
618
678
|
this._pendingHandlers = [];
|
|
619
|
-
this.
|
|
620
|
-
this.
|
|
621
|
-
this.
|
|
622
|
-
this._onControllerDisconnectCallbacks.clear();
|
|
623
|
-
this._onControllerReconnectCallbacks.clear();
|
|
624
|
-
this._onCharacterUpdatedCallbacks.clear();
|
|
625
|
-
this._onRateLimitedCallbacks.clear();
|
|
626
|
-
this._onErrorCallbacks.clear();
|
|
679
|
+
this._lifecycleListeners.clear();
|
|
680
|
+
this._isConnected = false;
|
|
681
|
+
this._outboundBuffer = [];
|
|
627
682
|
if (this.transport instanceof PostMessageTransport.PostMessageTransport) {
|
|
628
683
|
this.transport.destroy();
|
|
629
684
|
}
|
|
@@ -639,8 +694,8 @@ class ScreenImpl {
|
|
|
639
694
|
handleError(error) {
|
|
640
695
|
this.logger.warn(`Error in handler: ${error.message}`);
|
|
641
696
|
const smoreError = error.toSmoreError();
|
|
642
|
-
if (this.
|
|
643
|
-
this.
|
|
697
|
+
if (this._hasLifecycleListeners("$error")) {
|
|
698
|
+
this._emitLifecycle("$error", smoreError);
|
|
644
699
|
} else {
|
|
645
700
|
this.logger.error(error.message, error.details);
|
|
646
701
|
}
|