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