@smoregg/sdk 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/config.cjs.map +1 -1
- package/dist/cjs/controller.cjs +215 -145
- package/dist/cjs/controller.cjs.map +1 -1
- package/dist/cjs/screen.cjs +220 -178
- package/dist/cjs/screen.cjs.map +1 -1
- package/dist/cjs/testing.cjs +160 -151
- package/dist/cjs/testing.cjs.map +1 -1
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/controller.js +216 -146
- package/dist/esm/controller.js.map +1 -1
- package/dist/esm/screen.js +221 -179
- package/dist/esm/screen.js.map +1 -1
- package/dist/esm/testing.js +160 -151
- package/dist/esm/testing.js.map +1 -1
- package/dist/types/config.d.ts +1 -2
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/controller.d.ts +22 -43
- package/dist/types/controller.d.ts.map +1 -1
- package/dist/types/index.d.ts +14 -14
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/screen.d.ts +26 -37
- package/dist/types/screen.d.ts.map +1 -1
- package/dist/types/testing.d.ts +16 -0
- package/dist/types/testing.d.ts.map +1 -1
- package/dist/types/types.d.ts +244 -338
- package/dist/types/types.d.ts.map +1 -1
- package/dist/umd/smore-sdk.umd.js +595 -474
- package/dist/umd/smore-sdk.umd.js.map +1 -1
- package/dist/umd/smore-sdk.umd.min.js +1 -1
- package/dist/umd/smore-sdk.umd.min.js.map +1 -1
- package/package.json +1 -1
- package/dist/cjs/SmoreHost.cjs +0 -306
- package/dist/cjs/SmoreHost.cjs.map +0 -1
- package/dist/cjs/SmorePlayer.cjs +0 -229
- package/dist/cjs/SmorePlayer.cjs.map +0 -1
- package/dist/cjs/components/DirectionPad.cjs +0 -68
- package/dist/cjs/components/DirectionPad.cjs.map +0 -1
- package/dist/cjs/components/DirectionPad.module.css.cjs +0 -12
- package/dist/cjs/components/DirectionPad.module.css.cjs.map +0 -1
- package/dist/cjs/components/HoldButton.cjs +0 -57
- package/dist/cjs/components/HoldButton.cjs.map +0 -1
- package/dist/cjs/components/HoldButton.module.css.cjs +0 -12
- package/dist/cjs/components/HoldButton.module.css.cjs.map +0 -1
- package/dist/cjs/components/IframeGameBridge.cjs +0 -115
- package/dist/cjs/components/IframeGameBridge.cjs.map +0 -1
- package/dist/cjs/components/SwipeArea.cjs +0 -58
- package/dist/cjs/components/SwipeArea.cjs.map +0 -1
- package/dist/cjs/components/SwipeArea.module.css.cjs +0 -12
- package/dist/cjs/components/SwipeArea.module.css.cjs.map +0 -1
- package/dist/cjs/components/TapButton.cjs +0 -58
- package/dist/cjs/components/TapButton.cjs.map +0 -1
- package/dist/cjs/components/TapButton.module.css.cjs +0 -12
- package/dist/cjs/components/TapButton.module.css.cjs.map +0 -1
- package/dist/cjs/context/RoomProvider.cjs +0 -118
- package/dist/cjs/context/RoomProvider.cjs.map +0 -1
- package/dist/cjs/hooks/useExternalGames.cjs +0 -49
- package/dist/cjs/hooks/useExternalGames.cjs.map +0 -1
- package/dist/cjs/hooks/useGameHost.cjs +0 -206
- package/dist/cjs/hooks/useGameHost.cjs.map +0 -1
- package/dist/cjs/hooks/useGamePlayer.cjs +0 -134
- package/dist/cjs/hooks/useGamePlayer.cjs.map +0 -1
- package/dist/cjs/iframe/index.cjs +0 -260
- package/dist/cjs/iframe/index.cjs.map +0 -1
- package/dist/cjs/node_modules/.pnpm/style-inject@0.3.0/node_modules/style-inject/dist/style-inject.es.cjs +0 -33
- package/dist/cjs/node_modules/.pnpm/style-inject@0.3.0/node_modules/style-inject/dist/style-inject.es.cjs.map +0 -1
- package/dist/cjs/server/index.cjs +0 -45
- package/dist/cjs/server/index.cjs.map +0 -1
- package/dist/cjs/transport/DirectTransport.cjs +0 -23
- package/dist/cjs/transport/DirectTransport.cjs.map +0 -1
- package/dist/cjs/utils/connectionMonitor.cjs +0 -77
- package/dist/cjs/utils/connectionMonitor.cjs.map +0 -1
- package/dist/cjs/utils/preloadAssets.cjs +0 -66
- package/dist/cjs/utils/preloadAssets.cjs.map +0 -1
- package/dist/cjs/utils/serverTime.cjs +0 -43
- package/dist/cjs/utils/serverTime.cjs.map +0 -1
- package/dist/esm/SmoreHost.js +0 -304
- package/dist/esm/SmoreHost.js.map +0 -1
- package/dist/esm/SmorePlayer.js +0 -227
- package/dist/esm/SmorePlayer.js.map +0 -1
- package/dist/esm/components/DirectionPad.js +0 -66
- package/dist/esm/components/DirectionPad.js.map +0 -1
- package/dist/esm/components/DirectionPad.module.css.js +0 -8
- package/dist/esm/components/DirectionPad.module.css.js.map +0 -1
- package/dist/esm/components/HoldButton.js +0 -55
- package/dist/esm/components/HoldButton.js.map +0 -1
- package/dist/esm/components/HoldButton.module.css.js +0 -8
- package/dist/esm/components/HoldButton.module.css.js.map +0 -1
- package/dist/esm/components/IframeGameBridge.js +0 -113
- package/dist/esm/components/IframeGameBridge.js.map +0 -1
- package/dist/esm/components/SwipeArea.js +0 -56
- package/dist/esm/components/SwipeArea.js.map +0 -1
- package/dist/esm/components/SwipeArea.module.css.js +0 -8
- package/dist/esm/components/SwipeArea.module.css.js.map +0 -1
- package/dist/esm/components/TapButton.js +0 -56
- package/dist/esm/components/TapButton.js.map +0 -1
- package/dist/esm/components/TapButton.module.css.js +0 -8
- package/dist/esm/components/TapButton.module.css.js.map +0 -1
- package/dist/esm/context/RoomProvider.js +0 -109
- package/dist/esm/context/RoomProvider.js.map +0 -1
- package/dist/esm/hooks/useExternalGames.js +0 -47
- package/dist/esm/hooks/useExternalGames.js.map +0 -1
- package/dist/esm/hooks/useGameHost.js +0 -204
- package/dist/esm/hooks/useGameHost.js.map +0 -1
- package/dist/esm/hooks/useGamePlayer.js +0 -132
- package/dist/esm/hooks/useGamePlayer.js.map +0 -1
- package/dist/esm/iframe/index.js +0 -257
- package/dist/esm/iframe/index.js.map +0 -1
- package/dist/esm/node_modules/.pnpm/style-inject@0.3.0/node_modules/style-inject/dist/style-inject.es.js +0 -29
- package/dist/esm/node_modules/.pnpm/style-inject@0.3.0/node_modules/style-inject/dist/style-inject.es.js.map +0 -1
- package/dist/esm/server/index.js +0 -43
- package/dist/esm/server/index.js.map +0 -1
- package/dist/esm/transport/DirectTransport.js +0 -21
- package/dist/esm/transport/DirectTransport.js.map +0 -1
- package/dist/esm/utils/connectionMonitor.js +0 -75
- package/dist/esm/utils/connectionMonitor.js.map +0 -1
- package/dist/esm/utils/preloadAssets.js +0 -63
- package/dist/esm/utils/preloadAssets.js.map +0 -1
- package/dist/esm/utils/serverTime.js +0 -41
- package/dist/esm/utils/serverTime.js.map +0 -1
- package/dist/types/SmoreHost.d.ts +0 -187
- package/dist/types/SmoreHost.d.ts.map +0 -1
- package/dist/types/SmorePlayer.d.ts +0 -146
- package/dist/types/SmorePlayer.d.ts.map +0 -1
- package/dist/types/components/DirectionPad.d.ts +0 -21
- package/dist/types/components/DirectionPad.d.ts.map +0 -1
- package/dist/types/components/HoldButton.d.ts +0 -22
- package/dist/types/components/HoldButton.d.ts.map +0 -1
- package/dist/types/components/IframeGameBridge.d.ts +0 -38
- package/dist/types/components/IframeGameBridge.d.ts.map +0 -1
- package/dist/types/components/SwipeArea.d.ts +0 -19
- package/dist/types/components/SwipeArea.d.ts.map +0 -1
- package/dist/types/components/TapButton.d.ts +0 -19
- package/dist/types/components/TapButton.d.ts.map +0 -1
- package/dist/types/components/index.d.ts +0 -6
- package/dist/types/components/index.d.ts.map +0 -1
- package/dist/types/context/RoomProvider.d.ts +0 -69
- package/dist/types/context/RoomProvider.d.ts.map +0 -1
- package/dist/types/context/index.d.ts +0 -3
- package/dist/types/context/index.d.ts.map +0 -1
- package/dist/types/dev/DevSimulator.d.ts +0 -31
- package/dist/types/dev/DevSimulator.d.ts.map +0 -1
- package/dist/types/dev/index.d.ts +0 -2
- package/dist/types/dev/index.d.ts.map +0 -1
- package/dist/types/hooks/index.d.ts +0 -7
- package/dist/types/hooks/index.d.ts.map +0 -1
- package/dist/types/hooks/useExternalGames.d.ts +0 -32
- package/dist/types/hooks/useExternalGames.d.ts.map +0 -1
- package/dist/types/hooks/useGameHost.d.ts +0 -67
- package/dist/types/hooks/useGameHost.d.ts.map +0 -1
- package/dist/types/hooks/useGamePlayer.d.ts +0 -55
- package/dist/types/hooks/useGamePlayer.d.ts.map +0 -1
- package/dist/types/iframe/IframeRoomProvider.d.ts +0 -31
- package/dist/types/iframe/IframeRoomProvider.d.ts.map +0 -1
- package/dist/types/iframe/index.d.ts +0 -18
- package/dist/types/iframe/index.d.ts.map +0 -1
- package/dist/types/iframe/vanilla-entry.d.ts +0 -7
- package/dist/types/iframe/vanilla-entry.d.ts.map +0 -1
- package/dist/types/iframe/vanilla.d.ts +0 -49
- package/dist/types/iframe/vanilla.d.ts.map +0 -1
- package/dist/types/server/createGameRelay.d.ts +0 -26
- package/dist/types/server/createGameRelay.d.ts.map +0 -1
- package/dist/types/server/index.d.ts +0 -3
- package/dist/types/server/index.d.ts.map +0 -1
- package/dist/types/utils/connectionMonitor.d.ts +0 -57
- package/dist/types/utils/connectionMonitor.d.ts.map +0 -1
- package/dist/types/utils/index.d.ts +0 -7
- package/dist/types/utils/index.d.ts.map +0 -1
- package/dist/types/utils/preloadAssets.d.ts +0 -29
- package/dist/types/utils/preloadAssets.d.ts.map +0 -1
- package/dist/types/utils/serverTime.d.ts +0 -28
- package/dist/types/utils/serverTime.d.ts.map +0 -1
- package/dist/umd/smore-sdk-iframe.umd.js +0 -266
- package/dist/umd/smore-sdk-iframe.umd.js.map +0 -1
- package/dist/umd/smore-sdk-iframe.umd.min.js +0 -2
- package/dist/umd/smore-sdk-iframe.umd.min.js.map +0 -1
- package/dist/umd/smore-sdk-vanilla.umd.js +0 -1275
- package/dist/umd/smore-sdk-vanilla.umd.js.map +0 -1
- package/dist/umd/smore-sdk-vanilla.umd.min.js +0 -2
- package/dist/umd/smore-sdk-vanilla.umd.min.js.map +0 -1
package/dist/esm/screen.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { PostMessageTransport } from './transport/PostMessageTransport.js';
|
|
2
2
|
import { isBridgeMessage, validateInitPayload } from './transport/protocol.js';
|
|
3
3
|
import { SmoreSDKError } from './errors.js';
|
|
4
|
-
import {
|
|
4
|
+
import { SMORE_EVENTS, validateEventName } from './events.js';
|
|
5
5
|
import { DebugLogger } from './logger.js';
|
|
6
6
|
import { getGlobalConfig } from './config.js';
|
|
7
7
|
|
|
@@ -26,122 +26,139 @@ class ScreenImpl {
|
|
|
26
26
|
_roomCode = "";
|
|
27
27
|
_isReady = false;
|
|
28
28
|
_isDestroyed = false;
|
|
29
|
+
_initTimeoutId = null;
|
|
29
30
|
eventHandlers = /* @__PURE__ */ new Map();
|
|
30
31
|
registeredTransportHandlers = [];
|
|
31
32
|
boundMessageHandler = null;
|
|
32
|
-
// Maps user-facing handler
|
|
33
|
+
// Maps user-facing handler -> transport wrappedHandler for proper cleanup in on()/off()
|
|
33
34
|
handlerToTransport = /* @__PURE__ */ new Map();
|
|
34
|
-
//
|
|
35
|
-
|
|
35
|
+
// Pending handlers registered via on() before transport is ready
|
|
36
|
+
_pendingHandlers = [];
|
|
37
|
+
// Lifecycle callback arrays
|
|
38
|
+
_onAllReadyCallbacks = /* @__PURE__ */ new Set();
|
|
39
|
+
_onControllerJoinCallbacks = /* @__PURE__ */ new Set();
|
|
40
|
+
_onControllerLeaveCallbacks = /* @__PURE__ */ new Set();
|
|
41
|
+
_onControllerDisconnectCallbacks = /* @__PURE__ */ new Set();
|
|
42
|
+
_onControllerReconnectCallbacks = /* @__PURE__ */ new Set();
|
|
43
|
+
_onCharacterUpdatedCallbacks = /* @__PURE__ */ new Set();
|
|
44
|
+
_onRateLimitedCallbacks = /* @__PURE__ */ new Set();
|
|
45
|
+
_onErrorCallbacks = /* @__PURE__ */ new Set();
|
|
46
|
+
// Whether all-ready has fired
|
|
47
|
+
_allReadyFired = false;
|
|
48
|
+
// Ready promise
|
|
49
|
+
_readyResolve;
|
|
50
|
+
_readyReject;
|
|
51
|
+
ready;
|
|
36
52
|
constructor(config = {}) {
|
|
37
53
|
this.config = config;
|
|
38
54
|
this.logger = new DebugLogger(config.debug, "[SmoreScreen]");
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
55
|
+
this.ready = new Promise((resolve, reject) => {
|
|
56
|
+
this._readyResolve = resolve;
|
|
57
|
+
this._readyReject = reject;
|
|
58
|
+
});
|
|
59
|
+
this.startInitialization();
|
|
44
60
|
}
|
|
45
61
|
// ---------------------------------------------------------------------------
|
|
46
|
-
// Initialization (called
|
|
62
|
+
// Initialization (called in constructor)
|
|
47
63
|
// ---------------------------------------------------------------------------
|
|
48
|
-
|
|
64
|
+
startInitialization() {
|
|
49
65
|
this.logger.lifecycle("Initializing screen...");
|
|
50
66
|
const parentOrigin = this.config.parentOrigin ?? "*";
|
|
51
67
|
const timeout = this.config.timeout ?? DEFAULT_TIMEOUT;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
this.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
this.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
68
|
+
this._initTimeoutId = setTimeout(() => {
|
|
69
|
+
this.cleanup();
|
|
70
|
+
const error = new SmoreSDKError(
|
|
71
|
+
"TIMEOUT",
|
|
72
|
+
`Screen initialization timed out after ${timeout}ms. Make sure the parent frame sends _bridge:init. Check that the iframe has correct sandbox attributes (allow-scripts required) and same-origin/cross-origin settings. Create a new Screen instance to retry (this instance has been cleaned up).`,
|
|
73
|
+
{ details: { timeout } }
|
|
74
|
+
);
|
|
75
|
+
this.handleError(error);
|
|
76
|
+
this._readyReject(error);
|
|
77
|
+
}, timeout);
|
|
78
|
+
this.boundMessageHandler = (e) => {
|
|
79
|
+
if (parentOrigin !== "*" && e.origin !== parentOrigin) return;
|
|
80
|
+
const msg = e.data;
|
|
81
|
+
if (!isBridgeMessage(msg)) return;
|
|
82
|
+
if (msg.type === "_bridge:init") {
|
|
83
|
+
clearTimeout(this._initTimeoutId);
|
|
84
|
+
const initPayload = msg.payload;
|
|
85
|
+
try {
|
|
86
|
+
validateInitPayload(initPayload);
|
|
87
|
+
} catch (err) {
|
|
88
|
+
const error = new SmoreSDKError(
|
|
89
|
+
"INIT_FAILED",
|
|
90
|
+
`Invalid _bridge:init payload: ${err instanceof Error ? err.message : String(err)}`,
|
|
91
|
+
{ details: { payload: initPayload } }
|
|
92
|
+
);
|
|
93
|
+
this.logger.warn("_bridge:init validation failed", error);
|
|
94
|
+
this.handleError(error);
|
|
95
|
+
this._readyReject(error);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const initData = initPayload;
|
|
99
|
+
if (initData.side !== "host") {
|
|
100
|
+
const error = new SmoreSDKError(
|
|
101
|
+
"INIT_FAILED",
|
|
102
|
+
`Received init for wrong side: ${initData.side}. Expected "host".`,
|
|
103
|
+
{ details: { side: initData.side } }
|
|
104
|
+
);
|
|
105
|
+
this.handleError(error);
|
|
106
|
+
this._readyReject(error);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
this.transport = new PostMessageTransport(parentOrigin);
|
|
110
|
+
this._roomCode = initData.roomCode;
|
|
111
|
+
this._controllers = this.mapControllersFromInit(initData.players);
|
|
112
|
+
if (this._controllers.length === 0) {
|
|
113
|
+
this.logger.warn("Screen initialized with zero controllers");
|
|
114
|
+
}
|
|
115
|
+
this.setupEventHandlers();
|
|
116
|
+
for (const { event, handler } of this._pendingHandlers) {
|
|
117
|
+
this.setupUserEventHandler(event, handler);
|
|
118
|
+
}
|
|
119
|
+
this._pendingHandlers = [];
|
|
120
|
+
this._isReady = true;
|
|
121
|
+
this.logger.lifecycle("Screen ready", {
|
|
122
|
+
roomCode: this._roomCode,
|
|
123
|
+
controllers: this._controllers.length
|
|
124
|
+
});
|
|
125
|
+
const autoReady = getGlobalConfig().autoReady ?? true;
|
|
126
|
+
if (autoReady) {
|
|
127
|
+
this.logger.lifecycle("Auto-signaling ready (autoReady enabled)");
|
|
128
|
+
this.signalReady();
|
|
129
|
+
}
|
|
130
|
+
this._readyResolve();
|
|
131
|
+
} else if (msg.type === "_bridge:update") {
|
|
132
|
+
if (!this._isReady) {
|
|
133
|
+
this.logger.debug("Ignoring _bridge:update before init completes");
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const updateData = msg.payload;
|
|
137
|
+
if (updateData.players && Array.isArray(updateData.players)) {
|
|
138
|
+
const oldControllers = this._controllers;
|
|
139
|
+
const newControllers = this.mapControllersFromInit(updateData.players);
|
|
140
|
+
this._controllers = newControllers;
|
|
141
|
+
for (const nc of newControllers) {
|
|
142
|
+
if (!oldControllers.some((oc) => oc.playerIndex === nc.playerIndex)) {
|
|
143
|
+
this.logger.lifecycle("Controller joined (via update)", { playerIndex: nc.playerIndex });
|
|
144
|
+
this._onControllerJoinCallbacks.forEach((cb) => cb(nc.playerIndex, nc));
|
|
128
145
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
146
|
+
}
|
|
147
|
+
for (const oc of oldControllers) {
|
|
148
|
+
if (!newControllers.some((nc) => nc.playerIndex === oc.playerIndex)) {
|
|
149
|
+
this.logger.lifecycle("Controller left (via update)", { playerIndex: oc.playerIndex });
|
|
150
|
+
this._onControllerLeaveCallbacks.forEach((cb) => cb(oc.playerIndex));
|
|
134
151
|
}
|
|
135
152
|
}
|
|
136
|
-
this.logger.lifecycle("Room updated", {
|
|
137
|
-
controllers: this._controllers.length
|
|
138
|
-
});
|
|
139
153
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
154
|
+
this.logger.lifecycle("Room updated", {
|
|
155
|
+
controllers: this._controllers.length
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
window.addEventListener("message", this.boundMessageHandler);
|
|
160
|
+
window.parent.postMessage({ type: "_bridge:ready" }, parentOrigin);
|
|
161
|
+
this.logger.lifecycle("Sent _bridge:ready to parent");
|
|
145
162
|
}
|
|
146
163
|
mapControllersFromInit(players) {
|
|
147
164
|
return players.map((p, index) => ({
|
|
@@ -168,7 +185,7 @@ class ScreenImpl {
|
|
|
168
185
|
if (this._controllers.some((c) => c.playerIndex === controllerInfo.playerIndex)) return;
|
|
169
186
|
this._controllers = [...this._controllers, controllerInfo];
|
|
170
187
|
this.logger.lifecycle("Controller joined", { playerIndex: controllerInfo.playerIndex });
|
|
171
|
-
this.
|
|
188
|
+
this._onControllerJoinCallbacks.forEach((cb) => cb(controllerInfo.playerIndex, controllerInfo));
|
|
172
189
|
}
|
|
173
190
|
});
|
|
174
191
|
this.registerTransportHandler(SMORE_EVENTS.PLAYER_LEFT, (data) => {
|
|
@@ -178,7 +195,7 @@ class ScreenImpl {
|
|
|
178
195
|
if (!this._controllers.some((c) => c.playerIndex === playerIndex)) return;
|
|
179
196
|
this._controllers = this._controllers.filter((c) => c.playerIndex !== playerIndex);
|
|
180
197
|
this.logger.lifecycle("Controller left", { playerIndex });
|
|
181
|
-
this.
|
|
198
|
+
this._onControllerLeaveCallbacks.forEach((cb) => cb(playerIndex));
|
|
182
199
|
}
|
|
183
200
|
});
|
|
184
201
|
this.registerTransportHandler(SMORE_EVENTS.PLAYER_DISCONNECTED, (data) => {
|
|
@@ -189,7 +206,7 @@ class ScreenImpl {
|
|
|
189
206
|
(c) => c.playerIndex === playerIndex ? { ...c, connected: false } : c
|
|
190
207
|
);
|
|
191
208
|
this.logger.lifecycle("Controller disconnected", { playerIndex });
|
|
192
|
-
this.
|
|
209
|
+
this._onControllerDisconnectCallbacks.forEach((cb) => cb(playerIndex));
|
|
193
210
|
}
|
|
194
211
|
});
|
|
195
212
|
this.registerTransportHandler(SMORE_EVENTS.PLAYER_RECONNECTED, (data) => {
|
|
@@ -206,38 +223,33 @@ class ScreenImpl {
|
|
|
206
223
|
(c) => c.playerIndex === controllerInfo.playerIndex ? controllerInfo : c
|
|
207
224
|
);
|
|
208
225
|
this.logger.lifecycle("Controller reconnected", { playerIndex: controllerInfo.playerIndex });
|
|
209
|
-
this.
|
|
226
|
+
this._onControllerReconnectCallbacks.forEach((cb) => cb(controllerInfo.playerIndex, controllerInfo));
|
|
210
227
|
}
|
|
211
228
|
});
|
|
212
229
|
this.registerTransportHandler(SMORE_EVENTS.PLAYER_CHARACTER_UPDATED, (data) => {
|
|
213
230
|
const payload = data;
|
|
214
231
|
const playerData = payload?.player;
|
|
215
232
|
if (playerData && typeof playerData.playerIndex === "number") {
|
|
233
|
+
const pi = playerData.playerIndex;
|
|
216
234
|
const appearance = playerData.character ?? null;
|
|
217
235
|
this._controllers = this._controllers.map(
|
|
218
|
-
(c) => c.playerIndex ===
|
|
236
|
+
(c) => c.playerIndex === pi ? { ...c, appearance } : c
|
|
219
237
|
);
|
|
220
|
-
this.logger.lifecycle("Player character updated", { playerIndex:
|
|
221
|
-
this.
|
|
238
|
+
this.logger.lifecycle("Player character updated", { playerIndex: pi });
|
|
239
|
+
this._onCharacterUpdatedCallbacks.forEach((cb) => cb(pi, appearance ?? null));
|
|
222
240
|
}
|
|
223
241
|
});
|
|
224
242
|
this.registerTransportHandler(SMORE_EVENTS.RATE_LIMITED, (data) => {
|
|
225
243
|
const payload = data;
|
|
226
244
|
const event = payload?.event ?? "unknown";
|
|
227
245
|
this.logger.warn(`Rate limited: ${event}`);
|
|
228
|
-
this.
|
|
246
|
+
this._onRateLimitedCallbacks.forEach((cb) => cb(event));
|
|
229
247
|
});
|
|
230
248
|
this.registerTransportHandler(SMORE_EVENTS.ALL_READY, () => {
|
|
231
249
|
this.logger.lifecycle("All participants ready");
|
|
232
|
-
this.
|
|
250
|
+
this._allReadyFired = true;
|
|
251
|
+
this._onAllReadyCallbacks.forEach((cb) => cb());
|
|
233
252
|
});
|
|
234
|
-
if (this.config.listeners) {
|
|
235
|
-
for (const [event, handler] of Object.entries(this.config.listeners)) {
|
|
236
|
-
if (!handler) continue;
|
|
237
|
-
this._configListenerEvents.add(event);
|
|
238
|
-
this.setupUserEventHandler(event, handler);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
253
|
}
|
|
242
254
|
/**
|
|
243
255
|
* Sets up a user event handler with playerIndex extraction.
|
|
@@ -279,6 +291,7 @@ class ScreenImpl {
|
|
|
279
291
|
this.eventHandlers.set(event, handlers);
|
|
280
292
|
}
|
|
281
293
|
handlers.add(handler);
|
|
294
|
+
this.handlerToTransport.set(handler, { event, transportHandler: wrappedHandler });
|
|
282
295
|
}
|
|
283
296
|
registerTransportHandler(event, handler) {
|
|
284
297
|
if (!this.transport) return;
|
|
@@ -305,6 +318,60 @@ class ScreenImpl {
|
|
|
305
318
|
return this._isDestroyed;
|
|
306
319
|
}
|
|
307
320
|
// ---------------------------------------------------------------------------
|
|
321
|
+
// Lifecycle Methods
|
|
322
|
+
// ---------------------------------------------------------------------------
|
|
323
|
+
onAllReady(callback) {
|
|
324
|
+
if (this._allReadyFired) {
|
|
325
|
+
callback();
|
|
326
|
+
}
|
|
327
|
+
this._onAllReadyCallbacks.add(callback);
|
|
328
|
+
return () => {
|
|
329
|
+
this._onAllReadyCallbacks.delete(callback);
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
onControllerJoin(callback) {
|
|
333
|
+
this._onControllerJoinCallbacks.add(callback);
|
|
334
|
+
return () => {
|
|
335
|
+
this._onControllerJoinCallbacks.delete(callback);
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
onControllerLeave(callback) {
|
|
339
|
+
this._onControllerLeaveCallbacks.add(callback);
|
|
340
|
+
return () => {
|
|
341
|
+
this._onControllerLeaveCallbacks.delete(callback);
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
onControllerDisconnect(callback) {
|
|
345
|
+
this._onControllerDisconnectCallbacks.add(callback);
|
|
346
|
+
return () => {
|
|
347
|
+
this._onControllerDisconnectCallbacks.delete(callback);
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
onControllerReconnect(callback) {
|
|
351
|
+
this._onControllerReconnectCallbacks.add(callback);
|
|
352
|
+
return () => {
|
|
353
|
+
this._onControllerReconnectCallbacks.delete(callback);
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
onCharacterUpdated(callback) {
|
|
357
|
+
this._onCharacterUpdatedCallbacks.add(callback);
|
|
358
|
+
return () => {
|
|
359
|
+
this._onCharacterUpdatedCallbacks.delete(callback);
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
onRateLimited(callback) {
|
|
363
|
+
this._onRateLimitedCallbacks.add(callback);
|
|
364
|
+
return () => {
|
|
365
|
+
this._onRateLimitedCallbacks.delete(callback);
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
onError(callback) {
|
|
369
|
+
this._onErrorCallbacks.add(callback);
|
|
370
|
+
return () => {
|
|
371
|
+
this._onErrorCallbacks.delete(callback);
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
// ---------------------------------------------------------------------------
|
|
308
375
|
// Communication Methods
|
|
309
376
|
// ---------------------------------------------------------------------------
|
|
310
377
|
/**
|
|
@@ -411,11 +478,8 @@ class ScreenImpl {
|
|
|
411
478
|
/**
|
|
412
479
|
* Register an event handler for messages from controllers.
|
|
413
480
|
*
|
|
414
|
-
*
|
|
415
|
-
*
|
|
416
|
-
* will NOT be registered with the transport layer. This means the handler will never
|
|
417
|
-
* fire for events received during the pre-ready window. Always call `on()` after
|
|
418
|
-
* initialization completes, or use `config.listeners` for handlers needed from the start.
|
|
481
|
+
* Can be called before the Screen is ready. Handlers registered before ready
|
|
482
|
+
* are queued and activated when the transport becomes available.
|
|
419
483
|
*/
|
|
420
484
|
on(event, handler) {
|
|
421
485
|
validateEventName(event);
|
|
@@ -425,9 +489,8 @@ class ScreenImpl {
|
|
|
425
489
|
this.eventHandlers.set(event, handlers);
|
|
426
490
|
}
|
|
427
491
|
handlers.add(handler);
|
|
428
|
-
let wrappedHandler = null;
|
|
429
492
|
if (this.transport) {
|
|
430
|
-
wrappedHandler = (data) => {
|
|
493
|
+
const wrappedHandler = (data) => {
|
|
431
494
|
this.logger.receive(event, data);
|
|
432
495
|
const payload = data;
|
|
433
496
|
const { playerIndex, ...rest } = payload;
|
|
@@ -447,16 +510,22 @@ class ScreenImpl {
|
|
|
447
510
|
};
|
|
448
511
|
this.registerTransportHandler(event, wrappedHandler);
|
|
449
512
|
this.handlerToTransport.set(handler, { event, transportHandler: wrappedHandler });
|
|
513
|
+
} else {
|
|
514
|
+
this._pendingHandlers.push({ event, handler });
|
|
450
515
|
}
|
|
451
516
|
return () => {
|
|
452
517
|
handlers?.delete(handler);
|
|
453
518
|
if (handlers?.size === 0) {
|
|
454
519
|
this.eventHandlers.delete(event);
|
|
455
520
|
}
|
|
456
|
-
|
|
457
|
-
|
|
521
|
+
this._pendingHandlers = this._pendingHandlers.filter(
|
|
522
|
+
(p) => !(p.event === event && p.handler === handler)
|
|
523
|
+
);
|
|
524
|
+
const entry = this.handlerToTransport.get(handler);
|
|
525
|
+
if (entry) {
|
|
526
|
+
this.transport?.off(event, entry.transportHandler);
|
|
458
527
|
this.registeredTransportHandlers = this.registeredTransportHandlers.filter(
|
|
459
|
-
(h) => h.handler !==
|
|
528
|
+
(h) => h.handler !== entry.transportHandler
|
|
460
529
|
);
|
|
461
530
|
this.handlerToTransport.delete(handler);
|
|
462
531
|
}
|
|
@@ -473,16 +542,6 @@ class ScreenImpl {
|
|
|
473
542
|
* @param event - Event name to listen for
|
|
474
543
|
* @param handler - Handler function to call once
|
|
475
544
|
* @returns Unsubscribe function to remove the handler before it fires
|
|
476
|
-
*
|
|
477
|
-
* @example
|
|
478
|
-
* ```ts
|
|
479
|
-
* const unsubscribe = screen.once('ready', (playerIndex, data) => {
|
|
480
|
-
* console.log('Ready event received');
|
|
481
|
-
* });
|
|
482
|
-
*
|
|
483
|
-
* // To remove before the event fires:
|
|
484
|
-
* unsubscribe();
|
|
485
|
-
* ```
|
|
486
545
|
*/
|
|
487
546
|
once(event, handler) {
|
|
488
547
|
const wrappedHandler = (playerIndex, data) => {
|
|
@@ -494,24 +553,13 @@ class ScreenImpl {
|
|
|
494
553
|
}
|
|
495
554
|
off(event, handler) {
|
|
496
555
|
if (!handler) {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
(h) => h.handler !== val.transportHandler
|
|
503
|
-
);
|
|
504
|
-
this.handlerToTransport.delete(key);
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
} else {
|
|
508
|
-
this.eventHandlers.delete(event);
|
|
509
|
-
this.transport?.off(event);
|
|
510
|
-
this.registeredTransportHandlers = this.registeredTransportHandlers.filter((h) => h.event !== event);
|
|
511
|
-
for (const [key, val] of this.handlerToTransport) {
|
|
512
|
-
if (val.event === event) this.handlerToTransport.delete(key);
|
|
513
|
-
}
|
|
556
|
+
this.eventHandlers.delete(event);
|
|
557
|
+
this.transport?.off(event);
|
|
558
|
+
this.registeredTransportHandlers = this.registeredTransportHandlers.filter((h) => h.event !== event);
|
|
559
|
+
for (const [key, val] of this.handlerToTransport) {
|
|
560
|
+
if (val.event === event) this.handlerToTransport.delete(key);
|
|
514
561
|
}
|
|
562
|
+
this._pendingHandlers = this._pendingHandlers.filter((p) => p.event !== event);
|
|
515
563
|
} else {
|
|
516
564
|
const handlers = this.eventHandlers.get(event);
|
|
517
565
|
handlers?.delete(handler);
|
|
@@ -526,6 +574,9 @@ class ScreenImpl {
|
|
|
526
574
|
);
|
|
527
575
|
this.handlerToTransport.delete(handler);
|
|
528
576
|
}
|
|
577
|
+
this._pendingHandlers = this._pendingHandlers.filter(
|
|
578
|
+
(p) => !(p.event === event && p.handler === handler)
|
|
579
|
+
);
|
|
529
580
|
}
|
|
530
581
|
}
|
|
531
582
|
// ---------------------------------------------------------------------------
|
|
@@ -537,25 +588,6 @@ class ScreenImpl {
|
|
|
537
588
|
getControllerCount() {
|
|
538
589
|
return this._controllers.filter((c) => c.connected).length;
|
|
539
590
|
}
|
|
540
|
-
/**
|
|
541
|
-
* Check if there is at least one connected controller.
|
|
542
|
-
* Useful for detecting when all players have disconnected
|
|
543
|
-
* (e.g., to pause the game or show a waiting screen).
|
|
544
|
-
*
|
|
545
|
-
* Use this in onControllerDisconnect callback to detect when all controllers have disconnected.
|
|
546
|
-
*
|
|
547
|
-
* @example
|
|
548
|
-
* ```ts
|
|
549
|
-
* const screen = await createScreen<MyEvents>({
|
|
550
|
-
* onControllerDisconnect: (playerIndex) => {
|
|
551
|
-
* if (!screen.hasAnyConnectedControllers()) {
|
|
552
|
-
* console.log('All controllers disconnected!');
|
|
553
|
-
* screen.broadcast('waiting-for-players', {});
|
|
554
|
-
* }
|
|
555
|
-
* },
|
|
556
|
-
* });
|
|
557
|
-
* ```
|
|
558
|
-
*/
|
|
559
591
|
hasAnyConnectedControllers() {
|
|
560
592
|
return this._controllers.some((c) => c.connected);
|
|
561
593
|
}
|
|
@@ -571,12 +603,25 @@ class ScreenImpl {
|
|
|
571
603
|
this.logger.lifecycle("Screen destroyed");
|
|
572
604
|
}
|
|
573
605
|
cleanup() {
|
|
606
|
+
if (this._initTimeoutId) {
|
|
607
|
+
clearTimeout(this._initTimeoutId);
|
|
608
|
+
this._initTimeoutId = null;
|
|
609
|
+
}
|
|
574
610
|
for (const { event, handler } of this.registeredTransportHandlers) {
|
|
575
611
|
this.transport?.off(event, handler);
|
|
576
612
|
}
|
|
577
613
|
this.registeredTransportHandlers = [];
|
|
578
614
|
this.eventHandlers.clear();
|
|
579
615
|
this.handlerToTransport.clear();
|
|
616
|
+
this._pendingHandlers = [];
|
|
617
|
+
this._onAllReadyCallbacks.clear();
|
|
618
|
+
this._onControllerJoinCallbacks.clear();
|
|
619
|
+
this._onControllerLeaveCallbacks.clear();
|
|
620
|
+
this._onControllerDisconnectCallbacks.clear();
|
|
621
|
+
this._onControllerReconnectCallbacks.clear();
|
|
622
|
+
this._onCharacterUpdatedCallbacks.clear();
|
|
623
|
+
this._onRateLimitedCallbacks.clear();
|
|
624
|
+
this._onErrorCallbacks.clear();
|
|
580
625
|
if (this.transport instanceof PostMessageTransport) {
|
|
581
626
|
this.transport.destroy();
|
|
582
627
|
}
|
|
@@ -592,8 +637,8 @@ class ScreenImpl {
|
|
|
592
637
|
handleError(error) {
|
|
593
638
|
this.logger.warn(`Error in handler: ${error.message}`);
|
|
594
639
|
const smoreError = error.toSmoreError();
|
|
595
|
-
if (this.
|
|
596
|
-
this.
|
|
640
|
+
if (this._onErrorCallbacks.size > 0) {
|
|
641
|
+
this._onErrorCallbacks.forEach((cb) => cb(smoreError));
|
|
597
642
|
} else {
|
|
598
643
|
this.logger.error(error.message, error.details);
|
|
599
644
|
}
|
|
@@ -609,17 +654,14 @@ class ScreenImpl {
|
|
|
609
654
|
if (!this._isReady || !this.transport) {
|
|
610
655
|
throw new SmoreSDKError(
|
|
611
656
|
"NOT_READY",
|
|
612
|
-
`Cannot call ${method}() before screen is ready. Use await
|
|
657
|
+
`Cannot call ${method}() before screen is ready. Use await screen.ready.`,
|
|
613
658
|
{ details: { method } }
|
|
614
659
|
);
|
|
615
660
|
}
|
|
616
661
|
}
|
|
617
662
|
}
|
|
618
663
|
function createScreen(config) {
|
|
619
|
-
|
|
620
|
-
const promise = screen.initialize().then(() => screen);
|
|
621
|
-
promise.instance = screen;
|
|
622
|
-
return promise;
|
|
664
|
+
return new ScreenImpl(config);
|
|
623
665
|
}
|
|
624
666
|
|
|
625
667
|
export { createScreen };
|