@remix-gg/sdk 0.8.2 → 0.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/dist/index.d.mts +197 -4
- package/dist/index.d.ts +197 -4
- package/dist/index.js +864 -1
- package/dist/index.js.map +10 -0
- package/dist/index.min.js +806 -3
- package/dist/index.min.js.map +6 -4
- package/dist/index.mjs +861 -1
- package/dist/index.mjs.map +10 -0
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,9 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sdk.realtime` — realtime multiplayer rooms for Remix Desktop games.
|
|
3
|
+
*
|
|
4
|
+
* The game sees rooms, peers, and messages; everything else is deliberately
|
|
5
|
+
* invisible. The platform's room broker (Remix Desktop main) owns identity,
|
|
6
|
+
* room admission, signaling, and ICE minting; this controller rides the
|
|
7
|
+
* `multiplayer_*` game events both ways and drives the data-channel mesh
|
|
8
|
+
* (`./mesh.ts`) under the surface. A game never touches `RTCPeerConnection`,
|
|
9
|
+
* SDP, or ICE — which is what lets the transport change under a published
|
|
10
|
+
* game without the game updating.
|
|
11
|
+
*
|
|
12
|
+
* Membership truth is the SERVER ROSTER the broker relays
|
|
13
|
+
* (`multiplayer_peers`), never the mesh: a rebuilt slot re-fires its
|
|
14
|
+
* transport callbacks and a repeated bye can announce the same departure
|
|
15
|
+
* twice, so peer join/leave here is a roster diff and nothing else. The
|
|
16
|
+
* mesh's own callbacks are diagnostics.
|
|
17
|
+
*/
|
|
18
|
+
type RealtimePeer = {
|
|
19
|
+
userId: string;
|
|
20
|
+
username: string;
|
|
21
|
+
pfp: string | null;
|
|
22
|
+
/** ISO seat stamp; seat order (oldest first) is the deterministic tiebreak. */
|
|
23
|
+
joinedAt: string | null;
|
|
24
|
+
};
|
|
25
|
+
type RealtimeEndReason = 'left' | 'expired' | 'offline' | 'closed';
|
|
26
|
+
type RealtimeErrorCode = 'room_not_found' | 'room_full' | 'game_mismatch' | 'no_game_id' | 'join_failed';
|
|
27
|
+
declare class RealtimeRoomError extends Error {
|
|
28
|
+
readonly code: RealtimeErrorCode;
|
|
29
|
+
constructor(code: RealtimeErrorCode, message: string);
|
|
30
|
+
}
|
|
31
|
+
type RealtimeSendOptions = {
|
|
32
|
+
/**
|
|
33
|
+
* Default true: ordered, retransmitted — lobby, countdowns, results.
|
|
34
|
+
* `reliable: false` is the per-frame state stream (unordered, never
|
|
35
|
+
* retransmitted): a packet that arrives late is worth less than nothing,
|
|
36
|
+
* and one that never arrives is replaced by the next frame anyway.
|
|
37
|
+
*/
|
|
38
|
+
reliable?: boolean;
|
|
39
|
+
};
|
|
40
|
+
interface RealtimeRoom {
|
|
41
|
+
readonly roomId: string;
|
|
42
|
+
/** The join code — what the host shares with friends. */
|
|
43
|
+
readonly code: string;
|
|
44
|
+
readonly selfId: string;
|
|
45
|
+
readonly hostUserId: string;
|
|
46
|
+
/** Whether this player created the room. */
|
|
47
|
+
readonly isHost: boolean;
|
|
48
|
+
/** The live roster, self excluded, in seat order. */
|
|
49
|
+
readonly peers: RealtimePeer[];
|
|
50
|
+
readonly ended: boolean;
|
|
51
|
+
/** Broadcast to every peer. Data must be JSON-serializable. */
|
|
52
|
+
send(data: unknown, options?: RealtimeSendOptions): void;
|
|
53
|
+
sendTo(userId: string, data: unknown, options?: RealtimeSendOptions): void;
|
|
54
|
+
onMessage(callback: (fromUserId: string, data: unknown) => void): () => void;
|
|
55
|
+
onPeerJoin(callback: (peer: RealtimePeer) => void): () => void;
|
|
56
|
+
onPeerLeave(callback: (peer: RealtimePeer) => void): () => void;
|
|
57
|
+
onEnded(callback: (reason: RealtimeEndReason) => void): () => void;
|
|
58
|
+
/** Transport trouble worth telling the player about; the room keeps trying. */
|
|
59
|
+
onError(callback: (message: string) => void): () => void;
|
|
60
|
+
/**
|
|
61
|
+
* Open the platform's invite share flow — the friend picker that sends the
|
|
62
|
+
* room's join code into DMs and groups. The game never sees the friend
|
|
63
|
+
* list; the platform owns the whole exchange. A game may also just show
|
|
64
|
+
* `room.code` for players to share by hand.
|
|
65
|
+
*/
|
|
66
|
+
invite(): void;
|
|
67
|
+
leave(): void;
|
|
68
|
+
}
|
|
69
|
+
interface RealtimeNamespace {
|
|
70
|
+
/** Create a room for this game and take the first seat. One live room at a time. */
|
|
71
|
+
createRoom(): Promise<RealtimeRoom>;
|
|
72
|
+
/** Join a friend's room by its invite code. */
|
|
73
|
+
joinRoom(code: string): Promise<RealtimeRoom>;
|
|
74
|
+
/** The live room, or null. */
|
|
75
|
+
readonly room: RealtimeRoom | null;
|
|
76
|
+
/**
|
|
77
|
+
* Every room that becomes live — including one the PLATFORM joined for the
|
|
78
|
+
* player (accepting an invite boots the game already seated). A lobby
|
|
79
|
+
* screen should mount from here, not only from its own create/join call.
|
|
80
|
+
*/
|
|
81
|
+
onRoom(callback: (room: RealtimeRoom) => void): () => void;
|
|
82
|
+
/**
|
|
83
|
+
* A room the platform tried to join for the player (an accepted invite)
|
|
84
|
+
* that failed — full, expired, or the wrong game. There is no pending
|
|
85
|
+
* `createRoom`/`joinRoom` call to reject, so this is where the failure
|
|
86
|
+
* surfaces; a lobby should tell the player rather than sit solo as if
|
|
87
|
+
* nothing happened. Requested joins keep rejecting their own promise.
|
|
88
|
+
*/
|
|
89
|
+
onRoomError(callback: (error: RealtimeRoomError) => void): () => void;
|
|
90
|
+
}
|
|
91
|
+
|
|
1
92
|
declare global {
|
|
2
93
|
interface Window {
|
|
3
94
|
FarcadeSDK: typeof sdk;
|
|
4
95
|
RemixSDK: typeof sdk;
|
|
5
96
|
}
|
|
6
97
|
}
|
|
98
|
+
|
|
7
99
|
type ViewContext = 'feed' | 'full_screen' | 'challenge' | 'tournament';
|
|
8
100
|
type SafeAreaInset = {
|
|
9
101
|
top: number;
|
|
@@ -42,6 +134,16 @@ type GameInfo = {
|
|
|
42
134
|
id: string;
|
|
43
135
|
gameState: GameState;
|
|
44
136
|
} | null;
|
|
137
|
+
/**
|
|
138
|
+
* Explicit turn owner at session start for turn-based multiplayer sessions:
|
|
139
|
+
* the id of the player the platform is waiting on (the challenge creator
|
|
140
|
+
* until they make their first move). Games should trust this over inferring
|
|
141
|
+
* the boot turn from their own state — the platform rejects state saves from
|
|
142
|
+
* anyone else. It reflects the moment game_info was sent; after the local
|
|
143
|
+
* player submits a move, the turn is theirs no longer. Absent outside
|
|
144
|
+
* challenge sessions and on hosts that predate the field.
|
|
145
|
+
*/
|
|
146
|
+
usersTurnId?: string | null;
|
|
45
147
|
/**
|
|
46
148
|
* Present only when the host has level progression enabled for this session.
|
|
47
149
|
* Games must key level mode off this block's presence, not their own level
|
|
@@ -165,7 +267,89 @@ type PurchaseCompleteEvent = {
|
|
|
165
267
|
item?: string;
|
|
166
268
|
};
|
|
167
269
|
};
|
|
168
|
-
type
|
|
270
|
+
type RealtimeSignalKind = 'offer' | 'answer' | 'ice' | 'bye';
|
|
271
|
+
type MultiplayerCreateRoomEvent = {
|
|
272
|
+
type: 'multiplayer_create_room';
|
|
273
|
+
data: undefined;
|
|
274
|
+
};
|
|
275
|
+
type MultiplayerJoinRoomEvent = {
|
|
276
|
+
type: 'multiplayer_join_room';
|
|
277
|
+
data: {
|
|
278
|
+
code: string;
|
|
279
|
+
};
|
|
280
|
+
};
|
|
281
|
+
type MultiplayerLeaveRoomEvent = {
|
|
282
|
+
type: 'multiplayer_leave_room';
|
|
283
|
+
data: undefined;
|
|
284
|
+
};
|
|
285
|
+
type MultiplayerSignalEvent = {
|
|
286
|
+
type: 'multiplayer_signal';
|
|
287
|
+
data: {
|
|
288
|
+
toUserId: string;
|
|
289
|
+
kind: RealtimeSignalKind;
|
|
290
|
+
payload: string;
|
|
291
|
+
};
|
|
292
|
+
};
|
|
293
|
+
type MultiplayerRefreshIceEvent = {
|
|
294
|
+
type: 'multiplayer_refresh_ice';
|
|
295
|
+
data: undefined;
|
|
296
|
+
};
|
|
297
|
+
/**
|
|
298
|
+
* Open the platform's invite share flow for the live room. Carries nothing:
|
|
299
|
+
* the platform already knows the room's game and code.
|
|
300
|
+
*/
|
|
301
|
+
type MultiplayerRequestInviteEvent = {
|
|
302
|
+
type: 'multiplayer_request_invite';
|
|
303
|
+
data: undefined;
|
|
304
|
+
};
|
|
305
|
+
type MultiplayerSessionEvent = {
|
|
306
|
+
type: 'multiplayer_session';
|
|
307
|
+
data: {
|
|
308
|
+
roomId: string;
|
|
309
|
+
code: string;
|
|
310
|
+
gameId: string;
|
|
311
|
+
selfId: string;
|
|
312
|
+
hostUserId: string;
|
|
313
|
+
peers: RealtimePeer[];
|
|
314
|
+
iceServers: RTCIceServer[];
|
|
315
|
+
};
|
|
316
|
+
};
|
|
317
|
+
type MultiplayerPeersEvent = {
|
|
318
|
+
type: 'multiplayer_peers';
|
|
319
|
+
data: {
|
|
320
|
+
peers: RealtimePeer[];
|
|
321
|
+
};
|
|
322
|
+
};
|
|
323
|
+
type MultiplayerSignalsEvent = {
|
|
324
|
+
type: 'multiplayer_signals';
|
|
325
|
+
data: {
|
|
326
|
+
signals: Array<{
|
|
327
|
+
fromUserId: string;
|
|
328
|
+
kind: string;
|
|
329
|
+
payload: string;
|
|
330
|
+
}>;
|
|
331
|
+
};
|
|
332
|
+
};
|
|
333
|
+
type MultiplayerIceServersEvent = {
|
|
334
|
+
type: 'multiplayer_ice_servers';
|
|
335
|
+
data: {
|
|
336
|
+
iceServers: RTCIceServer[];
|
|
337
|
+
};
|
|
338
|
+
};
|
|
339
|
+
type MultiplayerSessionEndedEvent = {
|
|
340
|
+
type: 'multiplayer_session_ended';
|
|
341
|
+
data: {
|
|
342
|
+
reason: RealtimeEndReason;
|
|
343
|
+
};
|
|
344
|
+
};
|
|
345
|
+
type MultiplayerErrorEvent = {
|
|
346
|
+
type: 'multiplayer_error';
|
|
347
|
+
data: {
|
|
348
|
+
code: RealtimeErrorCode;
|
|
349
|
+
message: string;
|
|
350
|
+
};
|
|
351
|
+
};
|
|
352
|
+
type GameEvent = PlayAgainEvent | SinglePlayerGameOverEvent | ReadyEvent | HapticFeedbackEvent | ToggleMuteEvent | GameErrorEvent | SaveGameStateEvent | RefuteGameStateEvent | GameInfoEvent | GameStateUpdatedEvent | MultiplayerGameOverEvent | MultiplayerSaveGameStateEvent | PurchaseEvent | PurchaseCompleteEvent | MultiplayerCreateRoomEvent | MultiplayerJoinRoomEvent | MultiplayerLeaveRoomEvent | MultiplayerSignalEvent | MultiplayerRefreshIceEvent | MultiplayerRequestInviteEvent | MultiplayerSessionEvent | MultiplayerPeersEvent | MultiplayerSignalsEvent | MultiplayerIceServersEvent | MultiplayerSessionEndedEvent | MultiplayerErrorEvent;
|
|
169
353
|
type GameEventMessage<T extends GameEvent['type']> = {
|
|
170
354
|
type: 'game_event';
|
|
171
355
|
event: Extract<GameEvent, {
|
|
@@ -175,8 +359,8 @@ type GameEventMessage<T extends GameEvent['type']> = {
|
|
|
175
359
|
/**
|
|
176
360
|
* Messages from the game host to the game client
|
|
177
361
|
*/
|
|
178
|
-
type IncomingGameEvent = GameEventMessage<'play_again' | 'toggle_mute' | 'game_info' | 'game_state_updated' | 'purchase_complete'>;
|
|
179
|
-
type OutgoingGameEvent = GameEventMessage<'game_over' | 'ready' | 'haptic_feedback' | 'error' | 'save_game_state' | 'refute_game_state' | 'multiplayer_game_over' | 'multiplayer_save_game_state' | 'purchase'>;
|
|
362
|
+
type IncomingGameEvent = GameEventMessage<'play_again' | 'toggle_mute' | 'game_info' | 'game_state_updated' | 'purchase_complete' | 'multiplayer_session' | 'multiplayer_peers' | 'multiplayer_signals' | 'multiplayer_ice_servers' | 'multiplayer_session_ended' | 'multiplayer_error'>;
|
|
363
|
+
type OutgoingGameEvent = GameEventMessage<'game_over' | 'ready' | 'haptic_feedback' | 'error' | 'save_game_state' | 'refute_game_state' | 'multiplayer_game_over' | 'multiplayer_save_game_state' | 'purchase' | 'multiplayer_create_room' | 'multiplayer_join_room' | 'multiplayer_leave_room' | 'multiplayer_signal' | 'multiplayer_refresh_ice' | 'multiplayer_request_invite'>;
|
|
180
364
|
type EventCallback = (data: unknown) => void;
|
|
181
365
|
declare class RemixSDK {
|
|
182
366
|
/**
|
|
@@ -256,6 +440,15 @@ declare class RemixSDK {
|
|
|
256
440
|
purchase: (data: PurchaseEvent["data"]) => Promise<PurchaseCompleteEvent["data"]>;
|
|
257
441
|
};
|
|
258
442
|
};
|
|
443
|
+
/**
|
|
444
|
+
* Realtime multiplayer rooms (Remix Desktop): create or join a room, then
|
|
445
|
+
* talk to peers over `room.send` / `room.onMessage`. Distinct from
|
|
446
|
+
* `sdk.multiplayer`, which is the platform-arbitrated turn-based flow.
|
|
447
|
+
* The transport underneath is the platform's business; games see rooms,
|
|
448
|
+
* peers, and messages, and nothing else.
|
|
449
|
+
*/
|
|
450
|
+
private realtimeController;
|
|
451
|
+
realtime: RealtimeNamespace;
|
|
259
452
|
private emit;
|
|
260
453
|
private handleMessage;
|
|
261
454
|
private sendMessage;
|
|
@@ -265,4 +458,4 @@ declare class RemixSDK {
|
|
|
265
458
|
}
|
|
266
459
|
declare const sdk: RemixSDK;
|
|
267
460
|
|
|
268
|
-
export { type GameErrorEvent, type GameEvent, type GameEventMessage, type GameInfo, type GameInfoEvent, type GameState, type GameStateUpdatedEvent, type HapticFeedbackEvent, type HapticFeedbackType, type IncomingGameEvent, type InventoryItem, type LevelAttempt, type LevelAttemptStars, type LevelProgressState, type LevelStars, type MultiplayerGameOverEvent, type MultiplayerSaveGameStateEvent, type OutgoingGameEvent, type PlayAgainEvent, type Player, type PurchaseCompleteEvent, type PurchaseEvent, type ReadyEvent, type RefuteGameStateEvent, RemixSDK, type SafeAreaInset, type SaveGameStateEvent, type ShopItem, type SinglePlayerGameOverEvent, type ToggleMuteEvent, type ViewContext, ZERO_SAFE_AREA_INSET, sdk };
|
|
461
|
+
export { type GameErrorEvent, type GameEvent, type GameEventMessage, type GameInfo, type GameInfoEvent, type GameState, type GameStateUpdatedEvent, type HapticFeedbackEvent, type HapticFeedbackType, type IncomingGameEvent, type InventoryItem, type LevelAttempt, type LevelAttemptStars, type LevelProgressState, type LevelStars, type MultiplayerCreateRoomEvent, type MultiplayerErrorEvent, type MultiplayerGameOverEvent, type MultiplayerIceServersEvent, type MultiplayerJoinRoomEvent, type MultiplayerLeaveRoomEvent, type MultiplayerPeersEvent, type MultiplayerRefreshIceEvent, type MultiplayerRequestInviteEvent, type MultiplayerSaveGameStateEvent, type MultiplayerSessionEndedEvent, type MultiplayerSessionEvent, type MultiplayerSignalEvent, type MultiplayerSignalsEvent, type OutgoingGameEvent, type PlayAgainEvent, type Player, type PurchaseCompleteEvent, type PurchaseEvent, type ReadyEvent, type RealtimeEndReason, type RealtimeErrorCode, type RealtimeNamespace, type RealtimePeer, type RealtimeRoom, RealtimeRoomError, type RealtimeSendOptions, type RealtimeSignalKind, type RefuteGameStateEvent, RemixSDK, type SafeAreaInset, type SaveGameStateEvent, type ShopItem, type SinglePlayerGameOverEvent, type ToggleMuteEvent, type ViewContext, ZERO_SAFE_AREA_INSET, sdk };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sdk.realtime` — realtime multiplayer rooms for Remix Desktop games.
|
|
3
|
+
*
|
|
4
|
+
* The game sees rooms, peers, and messages; everything else is deliberately
|
|
5
|
+
* invisible. The platform's room broker (Remix Desktop main) owns identity,
|
|
6
|
+
* room admission, signaling, and ICE minting; this controller rides the
|
|
7
|
+
* `multiplayer_*` game events both ways and drives the data-channel mesh
|
|
8
|
+
* (`./mesh.ts`) under the surface. A game never touches `RTCPeerConnection`,
|
|
9
|
+
* SDP, or ICE — which is what lets the transport change under a published
|
|
10
|
+
* game without the game updating.
|
|
11
|
+
*
|
|
12
|
+
* Membership truth is the SERVER ROSTER the broker relays
|
|
13
|
+
* (`multiplayer_peers`), never the mesh: a rebuilt slot re-fires its
|
|
14
|
+
* transport callbacks and a repeated bye can announce the same departure
|
|
15
|
+
* twice, so peer join/leave here is a roster diff and nothing else. The
|
|
16
|
+
* mesh's own callbacks are diagnostics.
|
|
17
|
+
*/
|
|
18
|
+
type RealtimePeer = {
|
|
19
|
+
userId: string;
|
|
20
|
+
username: string;
|
|
21
|
+
pfp: string | null;
|
|
22
|
+
/** ISO seat stamp; seat order (oldest first) is the deterministic tiebreak. */
|
|
23
|
+
joinedAt: string | null;
|
|
24
|
+
};
|
|
25
|
+
type RealtimeEndReason = 'left' | 'expired' | 'offline' | 'closed';
|
|
26
|
+
type RealtimeErrorCode = 'room_not_found' | 'room_full' | 'game_mismatch' | 'no_game_id' | 'join_failed';
|
|
27
|
+
declare class RealtimeRoomError extends Error {
|
|
28
|
+
readonly code: RealtimeErrorCode;
|
|
29
|
+
constructor(code: RealtimeErrorCode, message: string);
|
|
30
|
+
}
|
|
31
|
+
type RealtimeSendOptions = {
|
|
32
|
+
/**
|
|
33
|
+
* Default true: ordered, retransmitted — lobby, countdowns, results.
|
|
34
|
+
* `reliable: false` is the per-frame state stream (unordered, never
|
|
35
|
+
* retransmitted): a packet that arrives late is worth less than nothing,
|
|
36
|
+
* and one that never arrives is replaced by the next frame anyway.
|
|
37
|
+
*/
|
|
38
|
+
reliable?: boolean;
|
|
39
|
+
};
|
|
40
|
+
interface RealtimeRoom {
|
|
41
|
+
readonly roomId: string;
|
|
42
|
+
/** The join code — what the host shares with friends. */
|
|
43
|
+
readonly code: string;
|
|
44
|
+
readonly selfId: string;
|
|
45
|
+
readonly hostUserId: string;
|
|
46
|
+
/** Whether this player created the room. */
|
|
47
|
+
readonly isHost: boolean;
|
|
48
|
+
/** The live roster, self excluded, in seat order. */
|
|
49
|
+
readonly peers: RealtimePeer[];
|
|
50
|
+
readonly ended: boolean;
|
|
51
|
+
/** Broadcast to every peer. Data must be JSON-serializable. */
|
|
52
|
+
send(data: unknown, options?: RealtimeSendOptions): void;
|
|
53
|
+
sendTo(userId: string, data: unknown, options?: RealtimeSendOptions): void;
|
|
54
|
+
onMessage(callback: (fromUserId: string, data: unknown) => void): () => void;
|
|
55
|
+
onPeerJoin(callback: (peer: RealtimePeer) => void): () => void;
|
|
56
|
+
onPeerLeave(callback: (peer: RealtimePeer) => void): () => void;
|
|
57
|
+
onEnded(callback: (reason: RealtimeEndReason) => void): () => void;
|
|
58
|
+
/** Transport trouble worth telling the player about; the room keeps trying. */
|
|
59
|
+
onError(callback: (message: string) => void): () => void;
|
|
60
|
+
/**
|
|
61
|
+
* Open the platform's invite share flow — the friend picker that sends the
|
|
62
|
+
* room's join code into DMs and groups. The game never sees the friend
|
|
63
|
+
* list; the platform owns the whole exchange. A game may also just show
|
|
64
|
+
* `room.code` for players to share by hand.
|
|
65
|
+
*/
|
|
66
|
+
invite(): void;
|
|
67
|
+
leave(): void;
|
|
68
|
+
}
|
|
69
|
+
interface RealtimeNamespace {
|
|
70
|
+
/** Create a room for this game and take the first seat. One live room at a time. */
|
|
71
|
+
createRoom(): Promise<RealtimeRoom>;
|
|
72
|
+
/** Join a friend's room by its invite code. */
|
|
73
|
+
joinRoom(code: string): Promise<RealtimeRoom>;
|
|
74
|
+
/** The live room, or null. */
|
|
75
|
+
readonly room: RealtimeRoom | null;
|
|
76
|
+
/**
|
|
77
|
+
* Every room that becomes live — including one the PLATFORM joined for the
|
|
78
|
+
* player (accepting an invite boots the game already seated). A lobby
|
|
79
|
+
* screen should mount from here, not only from its own create/join call.
|
|
80
|
+
*/
|
|
81
|
+
onRoom(callback: (room: RealtimeRoom) => void): () => void;
|
|
82
|
+
/**
|
|
83
|
+
* A room the platform tried to join for the player (an accepted invite)
|
|
84
|
+
* that failed — full, expired, or the wrong game. There is no pending
|
|
85
|
+
* `createRoom`/`joinRoom` call to reject, so this is where the failure
|
|
86
|
+
* surfaces; a lobby should tell the player rather than sit solo as if
|
|
87
|
+
* nothing happened. Requested joins keep rejecting their own promise.
|
|
88
|
+
*/
|
|
89
|
+
onRoomError(callback: (error: RealtimeRoomError) => void): () => void;
|
|
90
|
+
}
|
|
91
|
+
|
|
1
92
|
declare global {
|
|
2
93
|
interface Window {
|
|
3
94
|
FarcadeSDK: typeof sdk;
|
|
4
95
|
RemixSDK: typeof sdk;
|
|
5
96
|
}
|
|
6
97
|
}
|
|
98
|
+
|
|
7
99
|
type ViewContext = 'feed' | 'full_screen' | 'challenge' | 'tournament';
|
|
8
100
|
type SafeAreaInset = {
|
|
9
101
|
top: number;
|
|
@@ -42,6 +134,16 @@ type GameInfo = {
|
|
|
42
134
|
id: string;
|
|
43
135
|
gameState: GameState;
|
|
44
136
|
} | null;
|
|
137
|
+
/**
|
|
138
|
+
* Explicit turn owner at session start for turn-based multiplayer sessions:
|
|
139
|
+
* the id of the player the platform is waiting on (the challenge creator
|
|
140
|
+
* until they make their first move). Games should trust this over inferring
|
|
141
|
+
* the boot turn from their own state — the platform rejects state saves from
|
|
142
|
+
* anyone else. It reflects the moment game_info was sent; after the local
|
|
143
|
+
* player submits a move, the turn is theirs no longer. Absent outside
|
|
144
|
+
* challenge sessions and on hosts that predate the field.
|
|
145
|
+
*/
|
|
146
|
+
usersTurnId?: string | null;
|
|
45
147
|
/**
|
|
46
148
|
* Present only when the host has level progression enabled for this session.
|
|
47
149
|
* Games must key level mode off this block's presence, not their own level
|
|
@@ -165,7 +267,89 @@ type PurchaseCompleteEvent = {
|
|
|
165
267
|
item?: string;
|
|
166
268
|
};
|
|
167
269
|
};
|
|
168
|
-
type
|
|
270
|
+
type RealtimeSignalKind = 'offer' | 'answer' | 'ice' | 'bye';
|
|
271
|
+
type MultiplayerCreateRoomEvent = {
|
|
272
|
+
type: 'multiplayer_create_room';
|
|
273
|
+
data: undefined;
|
|
274
|
+
};
|
|
275
|
+
type MultiplayerJoinRoomEvent = {
|
|
276
|
+
type: 'multiplayer_join_room';
|
|
277
|
+
data: {
|
|
278
|
+
code: string;
|
|
279
|
+
};
|
|
280
|
+
};
|
|
281
|
+
type MultiplayerLeaveRoomEvent = {
|
|
282
|
+
type: 'multiplayer_leave_room';
|
|
283
|
+
data: undefined;
|
|
284
|
+
};
|
|
285
|
+
type MultiplayerSignalEvent = {
|
|
286
|
+
type: 'multiplayer_signal';
|
|
287
|
+
data: {
|
|
288
|
+
toUserId: string;
|
|
289
|
+
kind: RealtimeSignalKind;
|
|
290
|
+
payload: string;
|
|
291
|
+
};
|
|
292
|
+
};
|
|
293
|
+
type MultiplayerRefreshIceEvent = {
|
|
294
|
+
type: 'multiplayer_refresh_ice';
|
|
295
|
+
data: undefined;
|
|
296
|
+
};
|
|
297
|
+
/**
|
|
298
|
+
* Open the platform's invite share flow for the live room. Carries nothing:
|
|
299
|
+
* the platform already knows the room's game and code.
|
|
300
|
+
*/
|
|
301
|
+
type MultiplayerRequestInviteEvent = {
|
|
302
|
+
type: 'multiplayer_request_invite';
|
|
303
|
+
data: undefined;
|
|
304
|
+
};
|
|
305
|
+
type MultiplayerSessionEvent = {
|
|
306
|
+
type: 'multiplayer_session';
|
|
307
|
+
data: {
|
|
308
|
+
roomId: string;
|
|
309
|
+
code: string;
|
|
310
|
+
gameId: string;
|
|
311
|
+
selfId: string;
|
|
312
|
+
hostUserId: string;
|
|
313
|
+
peers: RealtimePeer[];
|
|
314
|
+
iceServers: RTCIceServer[];
|
|
315
|
+
};
|
|
316
|
+
};
|
|
317
|
+
type MultiplayerPeersEvent = {
|
|
318
|
+
type: 'multiplayer_peers';
|
|
319
|
+
data: {
|
|
320
|
+
peers: RealtimePeer[];
|
|
321
|
+
};
|
|
322
|
+
};
|
|
323
|
+
type MultiplayerSignalsEvent = {
|
|
324
|
+
type: 'multiplayer_signals';
|
|
325
|
+
data: {
|
|
326
|
+
signals: Array<{
|
|
327
|
+
fromUserId: string;
|
|
328
|
+
kind: string;
|
|
329
|
+
payload: string;
|
|
330
|
+
}>;
|
|
331
|
+
};
|
|
332
|
+
};
|
|
333
|
+
type MultiplayerIceServersEvent = {
|
|
334
|
+
type: 'multiplayer_ice_servers';
|
|
335
|
+
data: {
|
|
336
|
+
iceServers: RTCIceServer[];
|
|
337
|
+
};
|
|
338
|
+
};
|
|
339
|
+
type MultiplayerSessionEndedEvent = {
|
|
340
|
+
type: 'multiplayer_session_ended';
|
|
341
|
+
data: {
|
|
342
|
+
reason: RealtimeEndReason;
|
|
343
|
+
};
|
|
344
|
+
};
|
|
345
|
+
type MultiplayerErrorEvent = {
|
|
346
|
+
type: 'multiplayer_error';
|
|
347
|
+
data: {
|
|
348
|
+
code: RealtimeErrorCode;
|
|
349
|
+
message: string;
|
|
350
|
+
};
|
|
351
|
+
};
|
|
352
|
+
type GameEvent = PlayAgainEvent | SinglePlayerGameOverEvent | ReadyEvent | HapticFeedbackEvent | ToggleMuteEvent | GameErrorEvent | SaveGameStateEvent | RefuteGameStateEvent | GameInfoEvent | GameStateUpdatedEvent | MultiplayerGameOverEvent | MultiplayerSaveGameStateEvent | PurchaseEvent | PurchaseCompleteEvent | MultiplayerCreateRoomEvent | MultiplayerJoinRoomEvent | MultiplayerLeaveRoomEvent | MultiplayerSignalEvent | MultiplayerRefreshIceEvent | MultiplayerRequestInviteEvent | MultiplayerSessionEvent | MultiplayerPeersEvent | MultiplayerSignalsEvent | MultiplayerIceServersEvent | MultiplayerSessionEndedEvent | MultiplayerErrorEvent;
|
|
169
353
|
type GameEventMessage<T extends GameEvent['type']> = {
|
|
170
354
|
type: 'game_event';
|
|
171
355
|
event: Extract<GameEvent, {
|
|
@@ -175,8 +359,8 @@ type GameEventMessage<T extends GameEvent['type']> = {
|
|
|
175
359
|
/**
|
|
176
360
|
* Messages from the game host to the game client
|
|
177
361
|
*/
|
|
178
|
-
type IncomingGameEvent = GameEventMessage<'play_again' | 'toggle_mute' | 'game_info' | 'game_state_updated' | 'purchase_complete'>;
|
|
179
|
-
type OutgoingGameEvent = GameEventMessage<'game_over' | 'ready' | 'haptic_feedback' | 'error' | 'save_game_state' | 'refute_game_state' | 'multiplayer_game_over' | 'multiplayer_save_game_state' | 'purchase'>;
|
|
362
|
+
type IncomingGameEvent = GameEventMessage<'play_again' | 'toggle_mute' | 'game_info' | 'game_state_updated' | 'purchase_complete' | 'multiplayer_session' | 'multiplayer_peers' | 'multiplayer_signals' | 'multiplayer_ice_servers' | 'multiplayer_session_ended' | 'multiplayer_error'>;
|
|
363
|
+
type OutgoingGameEvent = GameEventMessage<'game_over' | 'ready' | 'haptic_feedback' | 'error' | 'save_game_state' | 'refute_game_state' | 'multiplayer_game_over' | 'multiplayer_save_game_state' | 'purchase' | 'multiplayer_create_room' | 'multiplayer_join_room' | 'multiplayer_leave_room' | 'multiplayer_signal' | 'multiplayer_refresh_ice' | 'multiplayer_request_invite'>;
|
|
180
364
|
type EventCallback = (data: unknown) => void;
|
|
181
365
|
declare class RemixSDK {
|
|
182
366
|
/**
|
|
@@ -256,6 +440,15 @@ declare class RemixSDK {
|
|
|
256
440
|
purchase: (data: PurchaseEvent["data"]) => Promise<PurchaseCompleteEvent["data"]>;
|
|
257
441
|
};
|
|
258
442
|
};
|
|
443
|
+
/**
|
|
444
|
+
* Realtime multiplayer rooms (Remix Desktop): create or join a room, then
|
|
445
|
+
* talk to peers over `room.send` / `room.onMessage`. Distinct from
|
|
446
|
+
* `sdk.multiplayer`, which is the platform-arbitrated turn-based flow.
|
|
447
|
+
* The transport underneath is the platform's business; games see rooms,
|
|
448
|
+
* peers, and messages, and nothing else.
|
|
449
|
+
*/
|
|
450
|
+
private realtimeController;
|
|
451
|
+
realtime: RealtimeNamespace;
|
|
259
452
|
private emit;
|
|
260
453
|
private handleMessage;
|
|
261
454
|
private sendMessage;
|
|
@@ -265,4 +458,4 @@ declare class RemixSDK {
|
|
|
265
458
|
}
|
|
266
459
|
declare const sdk: RemixSDK;
|
|
267
460
|
|
|
268
|
-
export { type GameErrorEvent, type GameEvent, type GameEventMessage, type GameInfo, type GameInfoEvent, type GameState, type GameStateUpdatedEvent, type HapticFeedbackEvent, type HapticFeedbackType, type IncomingGameEvent, type InventoryItem, type LevelAttempt, type LevelAttemptStars, type LevelProgressState, type LevelStars, type MultiplayerGameOverEvent, type MultiplayerSaveGameStateEvent, type OutgoingGameEvent, type PlayAgainEvent, type Player, type PurchaseCompleteEvent, type PurchaseEvent, type ReadyEvent, type RefuteGameStateEvent, RemixSDK, type SafeAreaInset, type SaveGameStateEvent, type ShopItem, type SinglePlayerGameOverEvent, type ToggleMuteEvent, type ViewContext, ZERO_SAFE_AREA_INSET, sdk };
|
|
461
|
+
export { type GameErrorEvent, type GameEvent, type GameEventMessage, type GameInfo, type GameInfoEvent, type GameState, type GameStateUpdatedEvent, type HapticFeedbackEvent, type HapticFeedbackType, type IncomingGameEvent, type InventoryItem, type LevelAttempt, type LevelAttemptStars, type LevelProgressState, type LevelStars, type MultiplayerCreateRoomEvent, type MultiplayerErrorEvent, type MultiplayerGameOverEvent, type MultiplayerIceServersEvent, type MultiplayerJoinRoomEvent, type MultiplayerLeaveRoomEvent, type MultiplayerPeersEvent, type MultiplayerRefreshIceEvent, type MultiplayerRequestInviteEvent, type MultiplayerSaveGameStateEvent, type MultiplayerSessionEndedEvent, type MultiplayerSessionEvent, type MultiplayerSignalEvent, type MultiplayerSignalsEvent, type OutgoingGameEvent, type PlayAgainEvent, type Player, type PurchaseCompleteEvent, type PurchaseEvent, type ReadyEvent, type RealtimeEndReason, type RealtimeErrorCode, type RealtimeNamespace, type RealtimePeer, type RealtimeRoom, RealtimeRoomError, type RealtimeSendOptions, type RealtimeSignalKind, type RefuteGameStateEvent, RemixSDK, type SafeAreaInset, type SaveGameStateEvent, type ShopItem, type SinglePlayerGameOverEvent, type ToggleMuteEvent, type ViewContext, ZERO_SAFE_AREA_INSET, sdk };
|