@usions/sdk 2.20.0 → 2.20.2

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/README.md CHANGED
@@ -139,10 +139,11 @@ follow four rules so both screens always agree:
139
139
  actions automatically. Use `onPlayerConnection` for the opponent's status;
140
140
  don't end the match on `'reconnecting'` (a ~15s grace window applies),
141
141
  treat `'gone'` as the opponent having left.
142
- 4. **Authority checkpoints with `setState`** — the room authority
143
- (`player_ids[0]`) should checkpoint at meaningful transitions so a
144
- rejoining client restores instantly (delivered as `game_state` in the
145
- join result and `onSync`; max 64 KB):
142
+ 4. **Checkpoint with `setState`** — any participant (not just the host)
143
+ should checkpoint at meaningful transitions so a rejoining client restores
144
+ instantly (delivered as `game_state` in the join result and `onSync`; max
145
+ 64 KB). Letting whoever made the move persist state keeps the snapshot
146
+ fresh even while the host is backgrounded:
146
147
  ```javascript
147
148
  await Usion.game.setState(fullGameState)
148
149
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usions/sdk",
3
- "version": "2.20.0",
3
+ "version": "2.20.2",
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.20.0', // injected from package.json at build
72
+ version: '2.20.2', // injected from package.json at build
73
73
  config: {},
74
74
  _initialized: false,
75
75
  _initCallback: null,
@@ -2220,7 +2220,10 @@ var Usion = (function () {
2220
2220
  * in the join ack and in game:sync — recovery becomes "load checkpoint,
2221
2221
  * replay the tail" instead of replaying every action from zero.
2222
2222
  *
2223
- * Only the room authority (player_ids[0] / host) may call this. The
2223
+ * Any participant in the room may call this (not just the host) the
2224
+ * server only requires that you're in player_ids. This keeps the snapshot
2225
+ * fresh even while the host is backgrounded; the host-authority pattern
2226
+ * (only the host calls setState) still works for games that prefer it. The
2224
2227
  * serialized state is capped at 64 KB.
2225
2228
  *
2226
2229
  * @param {*} state - JSON-serializable authoritative game state
@@ -4851,6 +4854,24 @@ var Usion = (function () {
4851
4854
  applyGameMethods(game, Usion);
4852
4855
  applyGameNetcode(game, Usion);
4853
4856
 
4857
+ // Foreground catch-up safety net (generic across every transport).
4858
+ //
4859
+ // When a page/iframe is backgrounded its JS is suspended: timers freeze and
4860
+ // relayed actions can be missed (postMessage to a frozen WebView). A brief
4861
+ // app-switch may also never trip a socket disconnect/reconnect, so the game's
4862
+ // onReconnect handler never fires and it silently misses moves made while it
4863
+ // was away — leaving turn state behind and the table deadlocked. On every
4864
+ // return to visibility, if we're in a room, request a sync so the game
4865
+ // deterministically catches up. Per the action reliability contract, onSync
4866
+ // is deduped by sequence, so an unnecessary resync is a no-op.
4867
+ if (typeof document !== 'undefined' && typeof document.addEventListener === 'function') {
4868
+ document.addEventListener('visibilitychange', function() {
4869
+ if (document.visibilityState !== 'visible') return;
4870
+ if (!game.roomId) return;
4871
+ try { game.requestSync(); } catch (e) { /* non-fatal */ }
4872
+ });
4873
+ }
4874
+
4854
4875
  return game;
4855
4876
  }
4856
4877
 
@@ -242,5 +242,23 @@ export function createGameModule(Usion) {
242
242
  applyGameMethods(game, Usion);
243
243
  applyGameNetcode(game, Usion);
244
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
+
245
263
  return game;
246
264
  }
@@ -248,7 +248,10 @@ export function applyGameMethods(game, Usion) {
248
248
  * in the join ack and in game:sync — recovery becomes "load checkpoint,
249
249
  * replay the tail" instead of replaying every action from zero.
250
250
  *
251
- * Only the room authority (player_ids[0] / host) may call this. The
251
+ * Any participant in the room may call this (not just the host) the
252
+ * server only requires that you're in player_ids. This keeps the snapshot
253
+ * fresh even while the host is backgrounded; the host-authority pattern
254
+ * (only the host calls setState) still works for games that prefer it. The
252
255
  * serialized state is capped at 64 KB.
253
256
  *
254
257
  * @param {*} state - JSON-serializable authoritative game state
package/types/index.d.ts CHANGED
@@ -300,8 +300,9 @@ export interface GameModule {
300
300
  // pause input and show a "reconnecting" indicator.
301
301
  // - For turn-based games, pass { nextTurn } on each move and trust
302
302
  // current_turn from join/sync instead of deriving the turn locally.
303
- // - The room authority (player_ids[0]) should checkpoint via setState()
304
- // at meaningful transitions so rejoining clients can restore instantly.
303
+ // - Any participant should checkpoint via setState() at meaningful
304
+ // transitions so rejoining clients can restore instantly (the host
305
+ // need not be the one to call it).
305
306
  join(roomId?: string): Promise<GameJoinResult>;
306
307
  leave(): void;
307
308
  action(actionType: string, actionData?: Record<string, any>, opts?: GameActionOptions): Promise<ActionResult>;
@@ -322,9 +323,11 @@ export interface GameModule {
322
323
  invite(opts?: GameInviteOptions): Promise<GameInviteResult>;
323
324
 
324
325
  /**
325
- * Checkpoint authoritative game state on the server (authority only
326
- * player_ids[0]/host). (Re)joining clients receive the latest checkpoint
327
- * as game_state in the join ack and in game:sync. Max 64 KB serialized.
326
+ * Checkpoint authoritative game state on the server. Any participant in the
327
+ * room may call this (not just player_ids[0]/host), so the snapshot stays
328
+ * fresh even while the host is backgrounded. (Re)joining clients receive the
329
+ * latest checkpoint as game_state in the join ack and in game:sync. Max 64 KB
330
+ * serialized.
328
331
  */
329
332
  setState(state: Record<string, any>): Promise<{ success: boolean; error?: string; code?: UsionErrorCode }>;
330
333