@usions/sdk 2.11.1 → 2.13.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/types/index.d.ts CHANGED
@@ -24,6 +24,13 @@ export interface UsionConfig {
24
24
  serviceName?: string;
25
25
  apiUrl?: string;
26
26
  connectionMode?: 'platform' | 'direct';
27
+ /**
28
+ * Internal path the host opened this app at — set when the user taps a
29
+ * notification carrying a `path`. Read via `Usion.getLaunchParams().path`.
30
+ */
31
+ launchPath?: string;
32
+ /** Referral code the app was opened with, if any. */
33
+ ref?: string;
27
34
  }
28
35
 
29
36
  // ─── Payment ─────────────────────────────────────────────────────
@@ -76,7 +83,7 @@ export interface WalletModule {
76
83
  getBalance(): Promise<number>;
77
84
  hasCredits(amount: number): Promise<boolean>;
78
85
  requestPayment(amount: number, reason: string, data?: Record<string, any>): Promise<PaymentResponse>;
79
- onBalanceChange(callback: (balance: number) => void): void;
86
+ onBalanceChange(callback: (balance: number) => void): UnsubscribeFn;
80
87
  }
81
88
 
82
89
  // ─── Session ─────────────────────────────────────────────────────
@@ -178,6 +185,38 @@ export interface GameActionData {
178
185
  action_type: string;
179
186
  action_data: Record<string, any>;
180
187
  sequence?: number;
188
+ /** Present when the sender passed { nextTurn } — the server-remembered turn. */
189
+ current_turn?: string;
190
+ }
191
+
192
+ /** Returned by event registrations; call to remove the handler. */
193
+ export type UnsubscribeFn = () => void;
194
+
195
+ export interface GameActionOptions {
196
+ /**
197
+ * Player ID whose turn comes after this action. The server stores it (no
198
+ * game logic — a relay that remembers) and returns it as current_turn in
199
+ * join acks and game:sync, so turn state survives reconnects.
200
+ */
201
+ nextTurn?: string;
202
+ /**
203
+ * Hold this action while disconnected and send it (in order) once the
204
+ * connection recovers, instead of rejecting with NOT_CONNECTED — "your
205
+ * move is saved and sends when you're back". For turn-based games only;
206
+ * never queue realtime-style inputs. Queue cap 20 (then QUEUE_FULL).
207
+ */
208
+ queueOffline?: boolean;
209
+ }
210
+
211
+ export interface PlayerConnectionData {
212
+ room_id: string;
213
+ player_id: string;
214
+ /**
215
+ * connected — player (re)joined the room.
216
+ * reconnecting — player's connection dropped; grace window running (~15s).
217
+ * gone — player did not return before the grace/heartbeat expiry.
218
+ */
219
+ state: 'connected' | 'reconnecting' | 'gone';
181
220
  }
182
221
 
183
222
  export interface GameFinishedData {
@@ -191,6 +230,7 @@ export interface SyncData {
191
230
  room_id: string;
192
231
  actions: GameActionData[];
193
232
  game_state: Record<string, any>;
233
+ current_turn?: string | null;
194
234
  sequence: number;
195
235
  }
196
236
 
@@ -207,14 +247,35 @@ export interface GameModule {
207
247
  isConnected(): boolean;
208
248
 
209
249
  // Room & actions
250
+ //
251
+ // RELIABILITY CONTRACT (what the platform guarantees / what your game must do):
252
+ // - Every action is echoed back to the sender with the authoritative
253
+ // sequence number. Apply game state ONLY in onAction — never
254
+ // optimistically on send — so all clients apply identical actions in
255
+ // identical order. The SDK deduplicates by sequence, so each action is
256
+ // delivered exactly once even across reconnect replays.
257
+ // - On a connection blip the host/SDK rejoins the room and replays missed
258
+ // actions via onSync automatically. Use onDisconnect/onReconnect to
259
+ // pause input and show a "reconnecting" indicator.
260
+ // - For turn-based games, pass { nextTurn } on each move and trust
261
+ // current_turn from join/sync instead of deriving the turn locally.
262
+ // - The room authority (player_ids[0]) should checkpoint via setState()
263
+ // at meaningful transitions so rejoining clients can restore instantly.
210
264
  join(roomId?: string): Promise<GameJoinResult>;
211
265
  leave(): void;
212
- action(actionType: string, actionData?: Record<string, any>): Promise<ActionResult>;
266
+ action(actionType: string, actionData?: Record<string, any>, opts?: GameActionOptions): Promise<ActionResult>;
213
267
  realtime(actionType: string, actionData?: Record<string, any>): void;
214
268
  requestSync(lastSequence?: number): void;
215
269
  requestRematch(): void;
216
270
  forfeit(): Promise<{ success: boolean; error?: string }>;
217
271
 
272
+ /**
273
+ * Checkpoint authoritative game state on the server (authority only —
274
+ * player_ids[0]/host). (Re)joining clients receive the latest checkpoint
275
+ * as game_state in the join ack and in game:sync. Max 64 KB serialized.
276
+ */
277
+ setState(state: Record<string, any>): Promise<{ success: boolean; error?: string; code?: UsionErrorCode }>;
278
+
218
279
  // Persisted state — survives iframe unmount/remount.
219
280
  // Keyed by (player_id, room_id) in localStorage. Schema is up to the game.
220
281
  saveState<T = any>(state: T): boolean;
@@ -226,23 +287,31 @@ export interface GameModule {
226
287
  debug(payload: Record<string, any>): void;
227
288
 
228
289
  // Event handlers
229
- onJoined(callback: (data: GameJoinResult) => void): void;
230
- onPlayerJoined(callback: (data: PlayerJoinedData) => void): void;
231
- onPlayerLeft(callback: (data: PlayerLeftData) => void): void;
232
- onStateUpdate(callback: (data: StateUpdateData) => void): void;
233
- onSync(callback: (data: SyncData) => void): void;
234
- onAction(callback: (data: GameActionData) => void): void;
235
- onRealtime(callback: (data: Record<string, any>) => void): void;
236
- onGameFinished(callback: (data: GameFinishedData) => void): void;
237
- onGameRestarted(callback: (data: Record<string, any>) => void): void;
238
- onError(callback: (data: { message: string; code?: string }) => void): void;
239
- onRematchRequest(callback: (data: RematchData) => void): void;
240
- onDisconnect(callback: (reason: string) => void): void;
241
- onReconnect(callback: (attemptNumber: number) => void): void;
242
- onConnectionError(callback: (error: Error) => void): void;
243
-
244
- // Generic event listener
245
- on(event: string, callback: (data: any) => void): void;
290
+ onJoined(callback: (data: GameJoinResult) => void): UnsubscribeFn;
291
+ onPlayerJoined(callback: (data: PlayerJoinedData) => void): UnsubscribeFn;
292
+ onPlayerLeft(callback: (data: PlayerLeftData) => void): UnsubscribeFn;
293
+ onStateUpdate(callback: (data: StateUpdateData) => void): UnsubscribeFn;
294
+ onSync(callback: (data: SyncData) => void): UnsubscribeFn;
295
+ onAction(callback: (data: GameActionData) => void): UnsubscribeFn;
296
+ onRealtime(callback: (data: Record<string, any>) => void): UnsubscribeFn;
297
+ onGameFinished(callback: (data: GameFinishedData) => void): UnsubscribeFn;
298
+ onGameRestarted(callback: (data: Record<string, any>) => void): UnsubscribeFn;
299
+ onError(callback: (data: { message: string; code?: string }) => void): UnsubscribeFn;
300
+ onRematchRequest(callback: (data: RematchData) => void): UnsubscribeFn;
301
+ onDisconnect(callback: (reason: string) => void): UnsubscribeFn;
302
+ onReconnect(callback: (attemptNumber: number) => void): UnsubscribeFn;
303
+ onConnectionError(callback: (error: Error) => void): UnsubscribeFn;
304
+ /** Peer connection lifecycle: connected / reconnecting (grace) / gone. */
305
+ onPlayerConnection(callback: (data: PlayerConnectionData) => void): UnsubscribeFn;
306
+
307
+ /**
308
+ * Register an ADDITIONAL event listener. Unlike the onX methods this
309
+ * supports multiple listeners per event, can be called before connect(),
310
+ * and works in every transport. Accepts internal names ('action'), wire
311
+ * names ('game:action'), or snake_case ('player_joined').
312
+ * @returns Unsubscribe function.
313
+ */
314
+ on(event: string, callback: (data: any) => void): UnsubscribeFn;
246
315
 
247
316
  // ─── Netcode helpers (low-latency realtime) ──────────────────
248
317
  // JSON state delta compression.
@@ -667,7 +736,7 @@ export interface BotModule {
667
736
  sendMessage(text: string): void;
668
737
  updateContext(ctx: Record<string, any>): void;
669
738
  close(result?: any): void;
670
- onMessage(callback: (message: BotMessage) => void): void;
739
+ onMessage(callback: (message: BotMessage) => void): UnsubscribeFn;
671
740
  }
672
741
 
673
742
  // ─── Selection Grid ──────────────────────────────────────────────
@@ -691,6 +760,13 @@ export interface UsionSDK {
691
760
  getTheme(): 'light' | 'dark';
692
761
  getLanguage(): string;
693
762
 
763
+ /**
764
+ * Launch parameters the host opened this app with. `path` is the deep-link
765
+ * target (e.g. from a tapped `Usion.notify` notification); route to it after
766
+ * init so the user lands on the right screen.
767
+ */
768
+ getLaunchParams(): { path: string | null; ref: string | null; roomId: string | null };
769
+
694
770
  // Payments
695
771
  requestPayment(amount: number, reason: string, data?: Record<string, any>): Promise<PaymentResponse>;
696
772
 
@@ -733,6 +809,29 @@ export interface UsionSDK {
733
809
  onChange: (selected: string, item: HTMLElement) => void
734
810
  ): SelectionGrid;
735
811
 
812
+ /** SDK version (injected from package.json at build time). */
813
+ version: string;
814
+
815
+ /**
816
+ * Live diagnostics snapshot: version, transport, connection + sequence
817
+ * state. game.debug() attaches this automatically as payload._diag.
818
+ */
819
+ diagnostics(): {
820
+ version: string;
821
+ transport: 'socket' | 'proxy' | 'direct' | 'none';
822
+ connected: boolean;
823
+ joined: boolean;
824
+ roomId: string | null;
825
+ playerId: string | null;
826
+ lastSequence: number;
827
+ lastActionApplied: number;
828
+ rejoining: boolean;
829
+ };
830
+
831
+ // Errors — branch on err.code (stable), never on message text.
832
+ UsionError: typeof UsionError;
833
+ ERROR_CODES: typeof ERROR_CODES;
834
+
736
835
  // Modules
737
836
  user: UserModule;
738
837
  storage: StorageModule;
@@ -746,9 +845,24 @@ export interface UsionSDK {
746
845
  leaderboard: LeaderboardModule;
747
846
  matchmaking: MatchmakingModule;
748
847
  cloud: CloudModule;
848
+ notify: NotifyModule;
749
849
  netcode: NetcodeModule;
750
850
  }
751
851
 
852
+ /**
853
+ * Send notifications to the CURRENT user that reopen this app (in-app banner
854
+ * when online, OS push when offline). `path` deep-links to a screen, read back
855
+ * via `Usion.getLaunchParams().path`. Rate-limited per user per service.
856
+ */
857
+ export interface NotifyModule {
858
+ /** Notify the current user. */
859
+ send(opts: { title: string; body: string; path?: string; serviceId?: string }): Promise<{ success: boolean; delivered?: string }>;
860
+ /** Mute/unmute notifications from this app for the current user. */
861
+ setMuted(muted: boolean, opts?: { serviceId?: string }): Promise<{ success: boolean; muted: boolean }>;
862
+ /** Whether the current user has muted this app's notifications. */
863
+ isMuted(opts?: { serviceId?: string }): Promise<boolean>;
864
+ }
865
+
752
866
  /** Scoped server-persisted KV operations (per-user or shared). */
753
867
  export interface CloudScope {
754
868
  /** Get a value; resolves to null when the key doesn't exist. */
@@ -822,6 +936,38 @@ export interface LobbyModule {
822
936
  queue(serviceId: string, opts?: { conversationId?: string }): Promise<any>;
823
937
  }
824
938
 
939
+ /**
940
+ * Stable machine-readable error codes. Part of the public API: codes are
941
+ * never removed within a major version. Messages may change — branch on
942
+ * `err.code`, never on message text.
943
+ */
944
+ export declare const ERROR_CODES: {
945
+ readonly NOT_CONNECTED: 'NOT_CONNECTED';
946
+ readonly NO_ROOM: 'NO_ROOM';
947
+ readonly ROOM_NOT_FOUND: 'ROOM_NOT_FOUND';
948
+ readonly NOT_PARTICIPANT: 'NOT_PARTICIPANT';
949
+ readonly NOT_AUTHORITY: 'NOT_AUTHORITY';
950
+ readonly NOT_AUTHENTICATED: 'NOT_AUTHENTICATED';
951
+ readonly JOIN_TIMEOUT: 'JOIN_TIMEOUT';
952
+ readonly CONNECT_TIMEOUT: 'CONNECT_TIMEOUT';
953
+ readonly STATE_TOO_LARGE: 'STATE_TOO_LARGE';
954
+ readonly INVALID_STATE: 'INVALID_STATE';
955
+ readonly INVALID_NEXT_TURN: 'INVALID_NEXT_TURN';
956
+ readonly RATE_LIMITED: 'RATE_LIMITED';
957
+ readonly REQUEST_TIMEOUT: 'REQUEST_TIMEOUT';
958
+ readonly QUEUE_FULL: 'QUEUE_FULL';
959
+ readonly UNSUPPORTED: 'UNSUPPORTED';
960
+ readonly UNKNOWN: 'UNKNOWN';
961
+ };
962
+
963
+ export type UsionErrorCode = keyof typeof ERROR_CODES;
964
+
965
+ export declare class UsionError extends Error {
966
+ readonly name: 'UsionError';
967
+ readonly code: UsionErrorCode;
968
+ constructor(code: UsionErrorCode | string, message?: string);
969
+ }
970
+
825
971
  declare const Usion: UsionSDK;
826
972
 
827
973
  export { Usion };