@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/README.md +36 -0
- package/package.json +11 -4
- package/src/browser.js +560 -122
- package/src/modules/core.js +17 -1
- package/src/modules/errors.js +75 -0
- package/src/modules/game-core.js +105 -19
- package/src/modules/game-direct.js +12 -16
- package/src/modules/game-methods.js +161 -21
- package/src/modules/game-proxy.js +46 -17
- package/src/modules/game-socket.js +43 -47
- package/src/modules/index.js +6 -0
- package/src/modules/misc.js +20 -0
- package/src/modules/notify.js +70 -0
- package/src/modules/wallet.js +7 -1
- package/types/index.d.ts +166 -20
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):
|
|
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
|
|
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):
|
|
230
|
-
onPlayerJoined(callback: (data: PlayerJoinedData) => void):
|
|
231
|
-
onPlayerLeft(callback: (data: PlayerLeftData) => void):
|
|
232
|
-
onStateUpdate(callback: (data: StateUpdateData) => void):
|
|
233
|
-
onSync(callback: (data: SyncData) => void):
|
|
234
|
-
onAction(callback: (data: GameActionData) => void):
|
|
235
|
-
onRealtime(callback: (data: Record<string, any>) => void):
|
|
236
|
-
onGameFinished(callback: (data: GameFinishedData) => void):
|
|
237
|
-
onGameRestarted(callback: (data: Record<string, any>) => void):
|
|
238
|
-
onError(callback: (data: { message: string; code?: string }) => void):
|
|
239
|
-
onRematchRequest(callback: (data: RematchData) => void):
|
|
240
|
-
onDisconnect(callback: (reason: string) => void):
|
|
241
|
-
onReconnect(callback: (attemptNumber: number) => void):
|
|
242
|
-
onConnectionError(callback: (error: Error) => void):
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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):
|
|
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 };
|