@usions/sdk 2.1.5 → 2.10.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
@@ -16,6 +16,8 @@ export interface UsionConfig {
16
16
  theme?: 'light' | 'dark';
17
17
  language?: string;
18
18
  socketUrl?: string;
19
+ /** HTTP/3 WebTransport endpoint for direct-mode games (lowest latency). */
20
+ webTransportUrl?: string;
19
21
  roomId?: string;
20
22
  playerIds?: string[];
21
23
  serviceId?: string;
@@ -213,6 +215,16 @@ export interface GameModule {
213
215
  requestRematch(): void;
214
216
  forfeit(): Promise<{ success: boolean; error?: string }>;
215
217
 
218
+ // Persisted state — survives iframe unmount/remount.
219
+ // Keyed by (player_id, room_id) in localStorage. Schema is up to the game.
220
+ saveState<T = any>(state: T): boolean;
221
+ loadState<T = any>(): T | null;
222
+ clearState(): void;
223
+
224
+ // Forward a debug snapshot to the parent. Rendered as a top-right
225
+ // overlay over the iframe when the host page is opened with `?debug=1`.
226
+ debug(payload: Record<string, any>): void;
227
+
216
228
  // Event handlers
217
229
  onJoined(callback: (data: GameJoinResult) => void): void;
218
230
  onPlayerJoined(callback: (data: PlayerJoinedData) => void): void;
@@ -231,6 +243,400 @@ export interface GameModule {
231
243
 
232
244
  // Generic event listener
233
245
  on(event: string, callback: (data: any) => void): void;
246
+
247
+ // ─── Netcode helpers (low-latency realtime) ──────────────────
248
+ // JSON state delta compression.
249
+ diff(prev: any, next: any): any | undefined;
250
+ patch(base: any, delta: any): any;
251
+ /** Round numeric fields to a fixed precision (snapshot compression). */
252
+ quantize<T = any>(value: T, precision?: number): T;
253
+ /** Compact binary codec (use on direct/mesh transports). */
254
+ encode(value: any): Uint8Array;
255
+ decode(buf: ArrayBuffer | Uint8Array): any;
256
+
257
+ /** Snapshot interpolation buffer (adaptive buffer + capped extrapolation). */
258
+ createInterpolation(opts?: InterpolationOptions): SnapshotInterpolation;
259
+ /** Client-side prediction + reconciliation + error smoothing. */
260
+ createPredictor<S = any, I = any>(opts: PredictorOptions<S, I>): Predictor<S, I>;
261
+ /** Fixed-rate outbound coalescer (defaults to sending via game.realtime). */
262
+ createSender(opts?: SenderOptions): Coalescer;
263
+ /** Sequence-guarded, delta-compressed snapshot sender. Pair with createSnapshotReceiver. */
264
+ createSnapshotSender(opts?: SnapshotSenderOptions): SnapshotSender;
265
+ /** Receiver for createSnapshotSender (drops stale/out-of-order frames). */
266
+ createSnapshotReceiver(opts?: SnapshotReceiverOptions): SnapshotReceiver;
267
+ /** One-line WebRTC P2P (2-peer) setup; signaling over realtime. */
268
+ createMesh(opts: MeshOptions): MeshConnection;
269
+ /** N-peer full mesh; signaling routed per-peer over realtime. */
270
+ createMeshNetwork(opts?: MeshNetworkOptions): MeshNetwork;
271
+ /** WebTransport (HTTP/3) connection — lowest-latency client-server datagrams. */
272
+ createWebTransport(opts?: WebTransportOptions): WebTransportConnection;
273
+ /** Declarative replication (host): mutate the object, it auto-syncs. */
274
+ replicate<T = any>(obj: T, opts?: ReplicateOptions): Replicated<T>;
275
+ /** Declarative replication (client): receive + (optionally) interpolate. */
276
+ replica<S = any>(opts?: ReplicaOptions): Replica<S>;
277
+ /** Server-side lag compensation (history + rewind) for fair hit-resolution. */
278
+ createLagCompensator(opts?: LagCompensatorOptions): LagCompensator;
279
+ /** Deterministic lockstep (inputs-only sim + free replays). */
280
+ createLockstep(opts: LockstepOptions): Lockstep;
281
+ /** Inject latency/jitter/loss locally to test under bad networks (null = off). */
282
+ simulateNetwork(opts: NetworkSimOptions | null): NetworkSim | void;
283
+
284
+ /** Measure round-trip time once (single outstanding probe); ms or null. */
285
+ ping(): Promise<number | null>;
286
+ /** Latest smoothed round-trip time in ms (null until first ping). */
287
+ getRtt(): number | null;
288
+ }
289
+
290
+ // ─── Netcode toolkit ──────────────────────────────────────────────
291
+
292
+ export interface NetcodeEntity {
293
+ id: string | number;
294
+ [key: string]: any;
295
+ }
296
+
297
+ export interface Snapshot {
298
+ id?: string;
299
+ time?: number;
300
+ state: NetcodeEntity[] | { [group: string]: NetcodeEntity[] };
301
+ }
302
+
303
+ export interface InterpolationOptions {
304
+ /** Expected snapshot rate; sets the default buffer (default 20). */
305
+ serverFps?: number;
306
+ /** Fixed interpolation delay in ms (default ≈ 3 server frames). */
307
+ bufferMs?: number;
308
+ /** Grow the buffer with measured jitter (Source cl_interp_ratio style). */
309
+ adaptive?: boolean;
310
+ /** Adaptive buffer clamps. */
311
+ minBufferMs?: number;
312
+ maxBufferMs?: number;
313
+ /** Max forward extrapolation on buffer underrun, ms (0 = off; Source caps ~250). */
314
+ extrapolationMs?: number;
315
+ /** Render against snapshot.time (server clock domain) instead of arrival time. */
316
+ serverTime?: boolean;
317
+ /** Snapshot history depth (default 120). */
318
+ maxSize?: number;
319
+ /** Clock source override (default Date.now). */
320
+ now?: () => number;
321
+ }
322
+
323
+ export class SnapshotInterpolation {
324
+ constructor(opts?: InterpolationOptions);
325
+ getBufferMs(): number;
326
+ setBufferMs(ms: number): void;
327
+ getJitter(): number;
328
+ add(snapshot: Snapshot | NetcodeEntity[] | { [group: string]: NetcodeEntity[] }): void;
329
+ /** Interpolated entities for the render instant, or null if no data yet. */
330
+ calc(keys: string, group?: string): NetcodeEntity[] | null;
331
+ vault: Vault;
332
+ }
333
+
334
+ export class Vault {
335
+ constructor(maxSize?: number);
336
+ add(snapshot: { time: number; state: any }): void;
337
+ readonly size: number;
338
+ setMaxSize(n: number): void;
339
+ latest(): { time: number; state: any } | null;
340
+ clear(): void;
341
+ straddle(time: number): [any, any];
342
+ }
343
+
344
+ export interface PredictorSmoothOptions {
345
+ keys?: string | string[];
346
+ rate?: number;
347
+ snapTo?: number;
348
+ }
349
+
350
+ export interface PredictorOptions<S = any, I = any> {
351
+ apply: (state: S, input: I) => S;
352
+ initialState?: S;
353
+ /** Smoothly blend correction errors instead of snapping (Overwatch style). */
354
+ smooth?: PredictorSmoothOptions | string;
355
+ }
356
+
357
+ export class Predictor<S = any, I = any> {
358
+ constructor(opts: PredictorOptions<S, I>);
359
+ readonly state: S;
360
+ readonly pending: Array<{ seq: number; input: I }>;
361
+ readonly lastSeq: number;
362
+ predict(input: I): { seq: number; state: S };
363
+ reconcile(serverState: S, ackedSeq: number): S;
364
+ /** Render state: corrected + decaying error offset. Call once per frame. */
365
+ view(rate?: number): S;
366
+ reset(state?: S): void;
367
+ }
368
+
369
+ export interface CoalescerOptions {
370
+ hz?: number;
371
+ onFlush: (entries: Array<{ type: string; data: any }>) => void;
372
+ autoStart?: boolean;
373
+ setInterval?: (fn: () => void, ms: number) => any;
374
+ clearInterval?: (handle: any) => void;
375
+ }
376
+
377
+ export class Coalescer {
378
+ constructor(opts: CoalescerOptions);
379
+ readonly running: boolean;
380
+ /** Latest-wins: only the newest data for `type` is sent next flush. */
381
+ queue(type: string, data: any): void;
382
+ /** Buffered: every value for `type` is kept and sent next flush. */
383
+ append(type: string, data: any): void;
384
+ drain(): Array<{ type: string; data: any }>;
385
+ flush(): void;
386
+ start(): void;
387
+ stop(): void;
388
+ }
389
+
390
+ export interface SenderOptions {
391
+ hz?: number;
392
+ autoStart?: boolean;
393
+ send?: (type: string, data: any) => void;
394
+ }
395
+
396
+ export interface SnapshotSenderOptions extends SenderOptions {
397
+ /** realtime action_type to send under (default 'state'). */
398
+ channel?: string;
399
+ /** Use delta compression with periodic keyframes (default true). */
400
+ delta?: boolean;
401
+ /** Send a full keyframe every N deltas so lost deltas self-heal (default 30). */
402
+ keyframeEvery?: number;
403
+ /** Quantize numeric fields to this many decimals before diffing. */
404
+ precision?: number;
405
+ /** Binary-encode the wire payload: true (built-in) or a custom encoder. */
406
+ encode?: boolean | ((payload: any) => any);
407
+ /** Auto-read state each tick from this getter (basis for game.replicate). */
408
+ source?: () => any;
409
+ }
410
+
411
+ export interface ReplicateOptions extends SnapshotSenderOptions {}
412
+
413
+ export interface Replicated<T = any> {
414
+ state: T;
415
+ flush(): void;
416
+ start(): void;
417
+ stop(): void;
418
+ }
419
+
420
+ export interface ReplicaOptions {
421
+ channel?: string;
422
+ decode?: boolean | ((buf: any) => any);
423
+ /** Interpolation keys ('x y') or an InterpolationOptions object to smooth entities. */
424
+ interpolate?: string | InterpolationOptions;
425
+ keys?: string;
426
+ group?: string;
427
+ }
428
+
429
+ export interface Replica<S = any> {
430
+ readonly state: S;
431
+ view(): any;
432
+ onChange(cb: (state: S) => void): void;
433
+ stop(): void;
434
+ }
435
+
436
+ export interface NetworkSimOptions {
437
+ latencyMs?: number;
438
+ jitterMs?: number;
439
+ lossPct?: number;
440
+ dupPct?: number;
441
+ setTimeout?: (fn: () => void, ms: number) => any;
442
+ clearTimeout?: (handle: any) => void;
443
+ random?: () => number;
444
+ }
445
+
446
+ export class NetworkSim {
447
+ constructor(opts?: NetworkSimOptions);
448
+ latencyMs: number; jitterMs: number; lossPct: number; dupPct: number;
449
+ set(opts: NetworkSimOptions): NetworkSim;
450
+ wrap<F extends (...args: any[]) => any>(fn: F): F;
451
+ flush(): void;
452
+ }
453
+
454
+ export interface LockstepOptions {
455
+ playerId: string;
456
+ players: string[];
457
+ step: (frame: number, inputs: Record<string, any>) => void;
458
+ send?: (msg: { frame: number; playerId: string; input: any }) => void;
459
+ inputDelay?: number;
460
+ idleInput?: any;
461
+ }
462
+
463
+ export class Lockstep {
464
+ constructor(opts: LockstepOptions);
465
+ readonly frame: number;
466
+ readonly players: string[];
467
+ submit(input: any): number;
468
+ receive(msg: { frame: number; playerId: string; input: any }): void;
469
+ tick(): number;
470
+ isStalled(): boolean;
471
+ getReplay(): Array<{ frame: number; inputs: Record<string, any> }>;
472
+ static replay(log: Array<{ frame: number; inputs: Record<string, any> }>, step: (frame: number, inputs: Record<string, any>) => void): void;
473
+ }
474
+
475
+ export interface LagCompensatorOptions {
476
+ historyMs?: number;
477
+ maxSize?: number;
478
+ now?: () => number;
479
+ }
480
+
481
+ export class LagCompensator {
482
+ constructor(opts?: LagCompensatorOptions);
483
+ readonly size: number;
484
+ record(entities: Array<{ id: string | number; [k: string]: any }>, time?: number): void;
485
+ rewind(time: number, keys?: string[]): Record<string, any>;
486
+ rewindForClient(rttMs: number, interpBufferMs: number, opts?: { maxRewindMs?: number; keys?: string[] }): Record<string, any>;
487
+ clear(): void;
488
+ }
489
+
490
+ export interface SnapshotSender {
491
+ send(state: any): void;
492
+ flush(): void;
493
+ start(): void;
494
+ stop(): void;
495
+ reset(): void;
496
+ }
497
+
498
+ export interface SnapshotReceiverOptions {
499
+ /** Binary-decode incoming payloads: true (built-in) or a custom decoder. */
500
+ decode?: boolean | ((buf: any) => any);
501
+ }
502
+
503
+ export interface SnapshotReceiver {
504
+ /** Apply a message from the sender; returns the reconstructed state. */
505
+ receive<S = any>(msg: any): S;
506
+ readonly state: any;
507
+ readonly stats: { appliedSeq: number; baseSeq: number; dropped: number };
508
+ reset(): void;
509
+ }
510
+
511
+ export interface PingMeterOptions {
512
+ alpha?: number;
513
+ now?: () => number;
514
+ }
515
+
516
+ export class PingMeter {
517
+ constructor(opts?: PingMeterOptions);
518
+ readonly rtt: number | null;
519
+ readonly latency: number | null;
520
+ readonly jitter: number;
521
+ readonly last: number | null;
522
+ begin(): number;
523
+ end(id: number): number | null;
524
+ sample(rttMs: number): number | null;
525
+ reset(): void;
526
+ }
527
+
528
+ export interface IceServerConfig {
529
+ stun?: string;
530
+ turn?: string;
531
+ turnUsername?: string;
532
+ turnCredential?: string;
533
+ }
534
+
535
+ export interface MeshOptions {
536
+ role: 'host' | 'guest';
537
+ /** realtime action_type used for signaling (default 'signal'). */
538
+ signalChannel?: string;
539
+ iceServers?: RTCIceServer[];
540
+ /** Drop stale/out-of-order frames on the unreliable channel (default true). */
541
+ sequenced?: boolean;
542
+ /** Recover via ICE restart on connection failure (host; default true). */
543
+ autoReconnect?: boolean;
544
+ maxRestarts?: number;
545
+ RTCPeerConnection?: typeof RTCPeerConnection;
546
+ setTimeout?: (fn: () => void, ms: number) => any;
547
+ }
548
+
549
+ export class MeshConnection {
550
+ constructor(opts: MeshOptions & { sendSignal: (payload: any) => void });
551
+ /** Build an iceServers array with optional TURN relay. */
552
+ static iceServers(cfg?: IceServerConfig): RTCIceServer[];
553
+ readonly connected: boolean;
554
+ readonly role: 'host' | 'guest';
555
+ onOpen: (() => void) | null;
556
+ onMessage: ((data: any, channel: 'unreliable' | 'reliable') => void) | null;
557
+ onClose: (() => void) | null;
558
+ onError: ((err: any) => void) | null;
559
+ onStateChange: ((state: string) => void) | null;
560
+ start(): Promise<void>;
561
+ handleSignal(payload: any): Promise<void>;
562
+ send(data: any): boolean;
563
+ sendReliable(data: any): boolean;
564
+ close(): void;
565
+ }
566
+
567
+ export interface MeshNetworkOptions {
568
+ selfId?: string;
569
+ signalChannel?: string;
570
+ iceServers?: RTCIceServer[];
571
+ sequenced?: boolean;
572
+ autoReconnect?: boolean;
573
+ RTCPeerConnection?: typeof RTCPeerConnection;
574
+ }
575
+
576
+ export class MeshNetwork {
577
+ constructor(opts: MeshNetworkOptions & { selfId: string; sendSignal: (toPeerId: string, payload: any) => void });
578
+ readonly selfId: string;
579
+ readonly peerIds: string[];
580
+ readonly connectedCount: number;
581
+ peer(peerId: string): MeshConnection | null;
582
+ addPeer(peerId: string): Promise<MeshConnection | null>;
583
+ setRoster(peerIds: string[]): Promise<void>;
584
+ removePeer(peerId: string): void;
585
+ handleSignal(fromPeerId: string, payload: any): Promise<void>;
586
+ send(peerId: string, data: any): boolean;
587
+ sendReliable(peerId: string, data: any): boolean;
588
+ broadcast(data: any): void;
589
+ broadcastReliable(data: any): void;
590
+ onPeerOpen: ((peerId: string) => void) | null;
591
+ onPeerClose: ((peerId: string) => void) | null;
592
+ onMessage: ((peerId: string, data: any, channel: 'unreliable' | 'reliable') => void) | null;
593
+ onError: ((peerId: string, err: any) => void) | null;
594
+ close(): void;
595
+ }
596
+
597
+ export interface WebTransportOptions {
598
+ /** https:// HTTP/3 endpoint (defaults to Usion.config.webTransportUrl). */
599
+ url?: string;
600
+ /** For self-signed dev certificates. */
601
+ serverCertificateHashes?: Array<{ algorithm: string; value: BufferSource }>;
602
+ /** Drop stale/out-of-order datagrams (default true). */
603
+ sequenced?: boolean;
604
+ /** Injectable WebTransport constructor (tests). */
605
+ WebTransport?: any;
606
+ }
607
+
608
+ export class WebTransportConnection {
609
+ constructor(opts: WebTransportOptions & { url: string });
610
+ readonly connected: boolean;
611
+ onOpen: (() => void) | null;
612
+ onMessage: ((data: any, channel: 'datagram' | 'reliable') => void) | null;
613
+ onClose: (() => void) | null;
614
+ onError: ((err: any) => void) | null;
615
+ connect(): Promise<void>;
616
+ /** Send over the unreliable datagram channel (sequenced). */
617
+ send(data: any): boolean;
618
+ /** Send over the reliable ordered stream. */
619
+ sendReliable(data: any): boolean;
620
+ close(): void;
621
+ }
622
+
623
+ export interface NetcodeModule {
624
+ SnapshotInterpolation: typeof SnapshotInterpolation;
625
+ Vault: typeof Vault;
626
+ Predictor: typeof Predictor;
627
+ Coalescer: typeof Coalescer;
628
+ PingMeter: typeof PingMeter;
629
+ MeshConnection: typeof MeshConnection;
630
+ MeshNetwork: typeof MeshNetwork;
631
+ WebTransportConnection: typeof WebTransportConnection;
632
+ NetworkSim: typeof NetworkSim;
633
+ Lockstep: typeof Lockstep;
634
+ LagCompensator: typeof LagCompensator;
635
+ diff(prev: any, next: any): any | undefined;
636
+ patch(base: any, delta: any): any;
637
+ quantize<T = any>(value: T, precision?: number): T;
638
+ encode(value: any): Uint8Array;
639
+ decode(buf: ArrayBuffer | Uint8Array): any;
234
640
  }
235
641
 
236
642
  // ─── Results ────────────────────────────────────────────────────
@@ -306,6 +712,10 @@ export interface UsionSDK {
306
712
 
307
713
  // Sharing
308
714
  share(contentType: 'audio' | 'image' | 'video' | 'text' | 'mixed', data: ShareData): void;
715
+ shareToFeed(contentType: 'text' | 'image' | 'video' | 'audio' | 'mixed', data: {
716
+ text?: string;
717
+ media?: Array<{ type: 'image' | 'video' | 'audio'; url: string; thumbnailUrl?: string; width?: number; height?: number; duration?: number }>;
718
+ }): Promise<{ success: boolean; postId?: string; shareUrl?: string }>;
309
719
 
310
720
  // Logging
311
721
  log(msg: string): void;
@@ -332,6 +742,59 @@ export interface UsionSDK {
332
742
  chat: ChatModule;
333
743
  bot: BotModule;
334
744
  game: GameModule;
745
+ lobby: LobbyModule;
746
+ leaderboard: LeaderboardModule;
747
+ matchmaking: MatchmakingModule;
748
+ netcode: NetcodeModule;
749
+ }
750
+
751
+ export interface Match { roomId: string; players: string[]; serviceId?: string; }
752
+
753
+ export interface MatchmakingModule {
754
+ /** Join the quick-match queue; resolves when paired with online strangers. */
755
+ find(serviceId?: string, opts?: { size?: number }): Promise<Match>;
756
+ /** Leave the queue / stop waiting. */
757
+ cancel(): Promise<any>;
758
+ /** Called whenever a match is found. */
759
+ onMatch(cb: (match: Match) => void): void;
760
+ }
761
+
762
+ export interface LeaderboardEntry {
763
+ user_id: string;
764
+ name?: string | null;
765
+ avatar?: string | null;
766
+ score: number;
767
+ rank: number;
768
+ is_me: boolean;
769
+ metadata?: any;
770
+ }
771
+
772
+ export interface LeaderboardModule {
773
+ /** Submit a score (best is kept per the service's order config). Opt-in per game. */
774
+ submit(score: number, metadata?: any, opts?: { serviceId?: string }): Promise<{ success: boolean; score: number; best: number; rank: number; updated: boolean }>;
775
+ /** Leaderboard of people you've messaged (plus you). */
776
+ friends(opts?: { serviceId?: string; limit?: number }): Promise<LeaderboardEntry[]>;
777
+ /** Global top N. */
778
+ top(opts?: { serviceId?: string; limit?: number }): Promise<LeaderboardEntry[]>;
779
+ /** Your own score + global rank. */
780
+ me(opts?: { serviceId?: string }): Promise<{ score: number | null; rank: number | null; total: number; metadata?: any }>;
781
+ }
782
+
783
+ export interface LobbyMember { id: string; name?: string; ready: boolean; }
784
+ export interface LobbyState { code: string | null; host: string | null; status: string | null; members: LobbyMember[]; }
785
+
786
+ export interface LobbyModule {
787
+ readonly state: LobbyState;
788
+ onUpdate(cb: (state: { code: string; host: string; status: string; members: LobbyMember[] }) => void): void;
789
+ onStarted(cb: (data: { room_id: string; by: string; player_ids?: string[] }) => void): void;
790
+ create(opts?: { maxPlayers?: number; public?: boolean }): Promise<{ code: string }>;
791
+ join(code: string): Promise<{ code: string }>;
792
+ leave(): Promise<void>;
793
+ setReady(ready?: boolean): Promise<any>;
794
+ allReady(): boolean;
795
+ isHost(): boolean;
796
+ start(roomId: string): Promise<any>;
797
+ queue(serviceId: string, opts?: { conversationId?: string }): Promise<any>;
335
798
  }
336
799
 
337
800
  declare const Usion: UsionSDK;