@usions/sdk 2.19.0 → 2.20.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usions/sdk",
3
- "version": "2.19.0",
3
+ "version": "2.20.1",
4
4
  "description": "Usion Mini App SDK for iframe games and services",
5
5
  "type": "module",
6
6
  "main": "src/modules/index.js",
package/src/browser.js CHANGED
@@ -69,7 +69,7 @@ var Usion = (function () {
69
69
  * Core Usion object with init, _post, _request
70
70
  */
71
71
  const core = {
72
- version: '2.19.0', // injected from package.json at build
72
+ version: '2.20.1', // injected from package.json at build
73
73
  config: {},
74
74
  _initialized: false,
75
75
  _initCallback: null,
@@ -190,6 +190,27 @@ var Usion = (function () {
190
190
  const h = self._backendHandlers[data.event];
191
191
  if (h) h(data.data);
192
192
  }
193
+
194
+ // Handle a room assigned by the host AFTER launch — the solo→host
195
+ // promotion path. The user opened the game solo from Explore (launch
196
+ // mode 'single'), then shared it; the host created a room and tells us
197
+ // to adopt it so invitees connect to the SAME room and the host starts
198
+ // receiving onPlayerJoined. No-op when there is no game module.
199
+ if (data.type === 'GAME_ROOM_ASSIGNED' && data.roomId && self.game) {
200
+ var _assignedRoom = data.roomId;
201
+ // Reflect the new room in launch config so getLaunchParams().roomId,
202
+ // .mode and game.isMultiplayer() all report multiplayer from here on.
203
+ self.config.roomId = _assignedRoom;
204
+ self.config.mode = 'multiplayer';
205
+ // Let a solo game flip to its multiplayer UI before peers arrive.
206
+ self.game._dispatch('roomAssigned', { roomId: _assignedRoom });
207
+ // Connect + join so the host is actually in the room. Idempotent when
208
+ // already connected/joined; non-fatal on failure (the game can retry).
209
+ Promise.resolve()
210
+ .then(function() { return self.game.connect(); })
211
+ .then(function() { return self.game.join(_assignedRoom); })
212
+ .catch(function() { /* non-fatal */ });
213
+ }
193
214
  });
194
215
 
195
216
  // Report the user's first real interaction to the host (see below).
@@ -4615,6 +4636,7 @@ var Usion = (function () {
4615
4636
  disconnect: 'disconnect',
4616
4637
  reconnect: 'reconnect',
4617
4638
  connection_error: 'connectionError',
4639
+ room_assigned: 'roomAssigned',
4618
4640
  };
4619
4641
 
4620
4642
  function _normalizeEventName(event) {
@@ -4760,6 +4782,11 @@ var Usion = (function () {
4760
4782
  onReconnect: function(callback) { return this._setHandler('reconnect', callback); },
4761
4783
  onConnectionError: function(callback) { return this._setHandler('connectionError', callback); },
4762
4784
  onPlayerConnection: function(callback) { return this._setHandler('playerConnection', callback); },
4785
+ // Fired when the host assigns this app a room AFTER launch (the user opened
4786
+ // the game solo, then shared it — see GAME_ROOM_ASSIGNED). The SDK has
4787
+ // already updated config.roomId and is connecting+joining; use this to flip
4788
+ // a solo game into its multiplayer/hosting UI. onJoined fires right after.
4789
+ onRoomAssigned: function(callback) { return this._setHandler('roomAssigned', callback); },
4763
4790
 
4764
4791
  /** @private Set the single legacy handler; returns an unsubscribe fn. */
4765
4792
  _setHandler: function(name, callback) {
@@ -4824,6 +4851,24 @@ var Usion = (function () {
4824
4851
  applyGameMethods(game, Usion);
4825
4852
  applyGameNetcode(game, Usion);
4826
4853
 
4854
+ // Foreground catch-up safety net (generic across every transport).
4855
+ //
4856
+ // When a page/iframe is backgrounded its JS is suspended: timers freeze and
4857
+ // relayed actions can be missed (postMessage to a frozen WebView). A brief
4858
+ // app-switch may also never trip a socket disconnect/reconnect, so the game's
4859
+ // onReconnect handler never fires and it silently misses moves made while it
4860
+ // was away — leaving turn state behind and the table deadlocked. On every
4861
+ // return to visibility, if we're in a room, request a sync so the game
4862
+ // deterministically catches up. Per the action reliability contract, onSync
4863
+ // is deduped by sequence, so an unnecessary resync is a no-op.
4864
+ if (typeof document !== 'undefined' && typeof document.addEventListener === 'function') {
4865
+ document.addEventListener('visibilitychange', function() {
4866
+ if (document.visibilityState !== 'visible') return;
4867
+ if (!game.roomId) return;
4868
+ try { game.requestSync(); } catch (e) { /* non-fatal */ }
4869
+ });
4870
+ }
4871
+
4827
4872
  return game;
4828
4873
  }
4829
4874
 
@@ -187,6 +187,27 @@ export const core = {
187
187
  const h = self._backendHandlers[data.event];
188
188
  if (h) h(data.data);
189
189
  }
190
+
191
+ // Handle a room assigned by the host AFTER launch — the solo→host
192
+ // promotion path. The user opened the game solo from Explore (launch
193
+ // mode 'single'), then shared it; the host created a room and tells us
194
+ // to adopt it so invitees connect to the SAME room and the host starts
195
+ // receiving onPlayerJoined. No-op when there is no game module.
196
+ if (data.type === 'GAME_ROOM_ASSIGNED' && data.roomId && self.game) {
197
+ var _assignedRoom = data.roomId;
198
+ // Reflect the new room in launch config so getLaunchParams().roomId,
199
+ // .mode and game.isMultiplayer() all report multiplayer from here on.
200
+ self.config.roomId = _assignedRoom;
201
+ self.config.mode = 'multiplayer';
202
+ // Let a solo game flip to its multiplayer UI before peers arrive.
203
+ self.game._dispatch('roomAssigned', { roomId: _assignedRoom });
204
+ // Connect + join so the host is actually in the room. Idempotent when
205
+ // already connected/joined; non-fatal on failure (the game can retry).
206
+ Promise.resolve()
207
+ .then(function() { return self.game.connect(); })
208
+ .then(function() { return self.game.join(_assignedRoom); })
209
+ .catch(function() { /* non-fatal */ });
210
+ }
190
211
  });
191
212
 
192
213
  // Report the user's first real interaction to the host (see below).
@@ -27,6 +27,7 @@ const _EVENT_ALIASES = {
27
27
  disconnect: 'disconnect',
28
28
  reconnect: 'reconnect',
29
29
  connection_error: 'connectionError',
30
+ room_assigned: 'roomAssigned',
30
31
  };
31
32
 
32
33
  function _normalizeEventName(event) {
@@ -172,6 +173,11 @@ export function createGameModule(Usion) {
172
173
  onReconnect: function(callback) { return this._setHandler('reconnect', callback); },
173
174
  onConnectionError: function(callback) { return this._setHandler('connectionError', callback); },
174
175
  onPlayerConnection: function(callback) { return this._setHandler('playerConnection', callback); },
176
+ // Fired when the host assigns this app a room AFTER launch (the user opened
177
+ // the game solo, then shared it — see GAME_ROOM_ASSIGNED). The SDK has
178
+ // already updated config.roomId and is connecting+joining; use this to flip
179
+ // a solo game into its multiplayer/hosting UI. onJoined fires right after.
180
+ onRoomAssigned: function(callback) { return this._setHandler('roomAssigned', callback); },
175
181
 
176
182
  /** @private Set the single legacy handler; returns an unsubscribe fn. */
177
183
  _setHandler: function(name, callback) {
@@ -236,5 +242,23 @@ export function createGameModule(Usion) {
236
242
  applyGameMethods(game, Usion);
237
243
  applyGameNetcode(game, Usion);
238
244
 
245
+ // Foreground catch-up safety net (generic across every transport).
246
+ //
247
+ // When a page/iframe is backgrounded its JS is suspended: timers freeze and
248
+ // relayed actions can be missed (postMessage to a frozen WebView). A brief
249
+ // app-switch may also never trip a socket disconnect/reconnect, so the game's
250
+ // onReconnect handler never fires and it silently misses moves made while it
251
+ // was away — leaving turn state behind and the table deadlocked. On every
252
+ // return to visibility, if we're in a room, request a sync so the game
253
+ // deterministically catches up. Per the action reliability contract, onSync
254
+ // is deduped by sequence, so an unnecessary resync is a no-op.
255
+ if (typeof document !== 'undefined' && typeof document.addEventListener === 'function') {
256
+ document.addEventListener('visibilitychange', function() {
257
+ if (document.visibilityState !== 'visible') return;
258
+ if (!game.roomId) return;
259
+ try { game.requestSync(); } catch (e) { /* non-fatal */ }
260
+ });
261
+ }
262
+
239
263
  return game;
240
264
  }
package/types/index.d.ts CHANGED
@@ -355,6 +355,15 @@ export interface GameModule {
355
355
  onConnectionError(callback: (error: Error) => void): UnsubscribeFn;
356
356
  /** Peer connection lifecycle: connected / reconnecting (grace) / gone. */
357
357
  onPlayerConnection(callback: (data: PlayerConnectionData) => void): UnsubscribeFn;
358
+ /**
359
+ * Fired when the host assigns this app a room AFTER launch — the solo→host
360
+ * promotion path. The user opened the game solo (launch mode 'single'), then
361
+ * shared it from the host's top-bar Share button; the host created a room and
362
+ * the SDK has already updated `getLaunchParams().roomId`/`.mode` and is
363
+ * connecting+joining. Use this to flip a solo game into its multiplayer /
364
+ * hosting UI; `onJoined` fires right after the join completes.
365
+ */
366
+ onRoomAssigned(callback: (data: { roomId: string }) => void): UnsubscribeFn;
358
367
 
359
368
  /**
360
369
  * Register an ADDITIONAL event listener. Unlike the onX methods this