@remix-gg/sdk 0.6.0 → 0.7.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 -7
- package/dist/index.d.mts +284 -0
- package/dist/index.d.ts +93 -16
- package/dist/index.js +316 -195
- package/dist/index.js.map +3 -3
- package/dist/index.min.js +116 -32
- package/dist/index.min.js.map +3 -3
- package/dist/index.mjs +255 -129
- package/dist/index.mjs.map +3 -3
- package/package.json +16 -5
package/README.md
CHANGED
|
@@ -45,8 +45,8 @@ class MyGame {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
private async setupEventListeners() {
|
|
48
|
-
// Listen for play
|
|
49
|
-
this.sdk.
|
|
48
|
+
// Listen for play (replay or host-directed level start)
|
|
49
|
+
this.sdk.onPlay(() => {
|
|
50
50
|
this.resetGame()
|
|
51
51
|
})
|
|
52
52
|
|
|
@@ -123,6 +123,8 @@ class MyGame {
|
|
|
123
123
|
|
|
124
124
|
This function will return when the SDK has recieved all the game information from Remix. You should await the call to this function before getting info from the SDK (sdk.player, sdk.gameState, etc)
|
|
125
125
|
|
|
126
|
+
The SDK announces itself to the host automatically when it loads — games never send the `ready` handshake themselves. `sdk.ready()` only waits for host data (`gameInfo`, `gameState`, level progression) to arrive before you read it; a game that reads nothing from the host does not need to call it.
|
|
127
|
+
|
|
126
128
|
#### `sdk.isReady: boolean`
|
|
127
129
|
|
|
128
130
|
Check if the SDK has received game info and is ready. More often, you will just await the call to `sdk.ready()`, but you may use this value if you prefer.
|
|
@@ -222,13 +224,34 @@ The purchase flow works as follows:
|
|
|
222
224
|
|
|
223
225
|
### Single Player Actions
|
|
224
226
|
|
|
225
|
-
#### `sdk.singlePlayer.actions.gameOver({ score: number })`
|
|
227
|
+
#### `sdk.singlePlayer.actions.gameOver({ score: number, levelAttempt?: LevelAttempt })`
|
|
226
228
|
|
|
227
229
|
Call this method when the game is over to report the final score.
|
|
228
230
|
|
|
229
231
|
Parameters:
|
|
230
232
|
|
|
231
233
|
- `score`: The player's final score (number)
|
|
234
|
+
- `levelAttempt`: For level-based games only — the result of the attempt that just ended (see Level-Based Games below). `score` is ignored for level-based games; pass `0`.
|
|
235
|
+
|
|
236
|
+
### Level-Based Games
|
|
237
|
+
|
|
238
|
+
When the host configures a fixed level count, `gameInfo.levelBased` is set and the game runs as a level progression game:
|
|
239
|
+
|
|
240
|
+
- `gameInfo.levelBased.levelCount` is the total number of levels.
|
|
241
|
+
- `gameInfo.levelBased.progress` is the platform-owned, score-derived progression (`LevelProgressState`). Read it through the getters `sdk.isLevelBased`, `sdk.levelCount`, `sdk.currentLevelIndex`, `sdk.levelStars`, `sdk.highestUnlockedLevel`, and `sdk.totalStars`. Never store progression in `gameState`.
|
|
242
|
+
- When an attempt ends, report only what happened:
|
|
243
|
+
|
|
244
|
+
```js
|
|
245
|
+
sdk.singlePlayer.actions.gameOver({
|
|
246
|
+
score: 0, // ignored for level-based games
|
|
247
|
+
levelAttempt: {
|
|
248
|
+
levelIndex, // 1-based level that was just played
|
|
249
|
+
stars, // 0 = failed, 1 | 2 | 3 earned on completion
|
|
250
|
+
},
|
|
251
|
+
})
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
The platform merges attempts into cumulative progression — games never compute or report star maps, unlock state, or "all levels complete" (the host derives world completion from `levelIndex` and its own `levelCount`). Listen for host-directed level starts with `sdk.onPlay((data) => ...)`; the host passes the target level as `data.levelIndex`.
|
|
232
255
|
|
|
233
256
|
#### `sdk.singlePlayer.actions.saveGameState({ gameState: Record<string, unknown> })`
|
|
234
257
|
|
|
@@ -303,9 +326,15 @@ The SDK provides both generic event listeners and specific typed listener method
|
|
|
303
326
|
|
|
304
327
|
#### Event Listeners
|
|
305
328
|
|
|
306
|
-
##### `sdk.
|
|
329
|
+
##### `sdk.onPlay(callback: (data?: { levelIndex?: number }) => void)`
|
|
330
|
+
|
|
331
|
+
Register a callback that is called when the host starts play — a replay after
|
|
332
|
+
game over, or (for level-based games) a host-directed level via `data.levelIndex`.
|
|
333
|
+
`data` may be omitted for a plain restart.
|
|
334
|
+
|
|
335
|
+
##### `sdk.onPlayAgain(callback: (data?: { levelIndex?: number }) => void)`
|
|
307
336
|
|
|
308
|
-
|
|
337
|
+
Alias of `sdk.onPlay`.
|
|
309
338
|
|
|
310
339
|
##### `sdk.onToggleMute(callback: (data: { isMuted: boolean }) => void)`
|
|
311
340
|
|
|
@@ -365,8 +394,8 @@ class Game {
|
|
|
365
394
|
}
|
|
366
395
|
|
|
367
396
|
private setupEventListeners() {
|
|
368
|
-
// Listen for play
|
|
369
|
-
this.sdk.
|
|
397
|
+
// Listen for play (replay or host-directed level start)
|
|
398
|
+
this.sdk.onPlay(() => {
|
|
370
399
|
this.resetGame()
|
|
371
400
|
})
|
|
372
401
|
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
interface Window {
|
|
3
|
+
FarcadeSDK: typeof sdk;
|
|
4
|
+
RemixSDK: typeof sdk;
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
type ViewContext = 'feed' | 'full_screen' | 'challenge' | 'tournament';
|
|
8
|
+
type SafeAreaInset = {
|
|
9
|
+
top: number;
|
|
10
|
+
right: number;
|
|
11
|
+
bottom: number;
|
|
12
|
+
left: number;
|
|
13
|
+
};
|
|
14
|
+
declare const ZERO_SAFE_AREA_INSET: SafeAreaInset;
|
|
15
|
+
type GameState = Record<string, unknown>;
|
|
16
|
+
type Player = {
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
purchasedItems: string[];
|
|
20
|
+
imageUrl?: string;
|
|
21
|
+
};
|
|
22
|
+
type InventoryItem = {
|
|
23
|
+
slug: string;
|
|
24
|
+
quantity: number;
|
|
25
|
+
};
|
|
26
|
+
type ShopItem = {
|
|
27
|
+
slug: string;
|
|
28
|
+
name: string;
|
|
29
|
+
itemType?: string;
|
|
30
|
+
bitsCost?: number | null;
|
|
31
|
+
description?: string | null;
|
|
32
|
+
iconUrl?: string | null;
|
|
33
|
+
tier?: number | null;
|
|
34
|
+
};
|
|
35
|
+
type GameInfo = {
|
|
36
|
+
players: Player[];
|
|
37
|
+
player: Player;
|
|
38
|
+
shopItems?: ShopItem[];
|
|
39
|
+
viewContext: ViewContext;
|
|
40
|
+
contentSafeAreaInset: SafeAreaInset;
|
|
41
|
+
initialGameState: {
|
|
42
|
+
id: string;
|
|
43
|
+
gameState: GameState;
|
|
44
|
+
} | null;
|
|
45
|
+
levelBased?: {
|
|
46
|
+
levelCount: number;
|
|
47
|
+
/**
|
|
48
|
+
* Score-derived level progression for this player. Platform-owned and kept
|
|
49
|
+
* fully separate from `gameState` — never read progression out of gameState.
|
|
50
|
+
*/
|
|
51
|
+
progress: LevelProgressState;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
/** Score-derived, platform-owned progression for a level-based game. */
|
|
55
|
+
type LevelProgressState = {
|
|
56
|
+
currentLevelIndex: number;
|
|
57
|
+
highestUnlockedLevel: number;
|
|
58
|
+
levelStars: Record<string, LevelStars>;
|
|
59
|
+
};
|
|
60
|
+
type ReadyEvent = {
|
|
61
|
+
type: 'ready';
|
|
62
|
+
data: {
|
|
63
|
+
instanceId: string;
|
|
64
|
+
} | undefined;
|
|
65
|
+
};
|
|
66
|
+
type PlayAgainEvent = {
|
|
67
|
+
type: 'play_again';
|
|
68
|
+
data?: {
|
|
69
|
+
levelIndex?: number;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
type LevelStars = 1 | 2 | 3;
|
|
73
|
+
type LevelOutcome = 'level_complete' | 'level_failed' | 'all_complete';
|
|
74
|
+
/**
|
|
75
|
+
* One level attempt reported with game_over. Games report only what happened
|
|
76
|
+
* in the attempt; the host owns and derives all cumulative progression
|
|
77
|
+
* (star maps, unlock state) — see `LevelProgressState` for the host→game snapshot.
|
|
78
|
+
*/
|
|
79
|
+
type LevelAttempt = {
|
|
80
|
+
/** 1-based index of the level that was just played. */
|
|
81
|
+
levelIndex: number;
|
|
82
|
+
/** Stars earned this attempt. Omit for star-less games; a completion counts as 1. */
|
|
83
|
+
stars?: LevelStars;
|
|
84
|
+
outcome: LevelOutcome;
|
|
85
|
+
};
|
|
86
|
+
type SinglePlayerGameOverEvent = {
|
|
87
|
+
type: 'game_over';
|
|
88
|
+
data: {
|
|
89
|
+
score: number;
|
|
90
|
+
levelAttempt?: LevelAttempt;
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
type MultiplayerGameOverEvent = {
|
|
94
|
+
type: 'multiplayer_game_over';
|
|
95
|
+
data: {
|
|
96
|
+
scores: {
|
|
97
|
+
playerId: string;
|
|
98
|
+
score: number;
|
|
99
|
+
}[];
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
type HapticFeedbackType = 'light' | 'medium' | 'hard' | 'success' | 'error';
|
|
103
|
+
type HapticFeedbackEvent = {
|
|
104
|
+
type: 'haptic_feedback';
|
|
105
|
+
data?: {
|
|
106
|
+
type?: HapticFeedbackType;
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
type ToggleMuteEvent = {
|
|
110
|
+
type: 'toggle_mute';
|
|
111
|
+
data: {
|
|
112
|
+
isMuted: boolean;
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
type GameErrorEvent = {
|
|
116
|
+
type: 'error';
|
|
117
|
+
data: {
|
|
118
|
+
message: string;
|
|
119
|
+
source?: string;
|
|
120
|
+
lineno?: number;
|
|
121
|
+
colno?: number;
|
|
122
|
+
error?: Error;
|
|
123
|
+
stack?: string;
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
type GameInfoEvent = {
|
|
127
|
+
type: 'game_info';
|
|
128
|
+
data: GameInfo;
|
|
129
|
+
};
|
|
130
|
+
type MultiplayerSaveGameStateEvent = {
|
|
131
|
+
type: 'multiplayer_save_game_state';
|
|
132
|
+
data: {
|
|
133
|
+
gameState: GameState;
|
|
134
|
+
alertUserIds?: string[];
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
type GameStateUpdatedEvent = {
|
|
138
|
+
type: 'game_state_updated';
|
|
139
|
+
data: {
|
|
140
|
+
id: string;
|
|
141
|
+
gameState: GameState;
|
|
142
|
+
} | null;
|
|
143
|
+
};
|
|
144
|
+
type RefuteGameStateEvent = {
|
|
145
|
+
type: 'refute_game_state';
|
|
146
|
+
data: {
|
|
147
|
+
gameStateId: string;
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
type SaveGameStateEvent = {
|
|
151
|
+
type: 'save_game_state';
|
|
152
|
+
data: {
|
|
153
|
+
gameState: GameState;
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
type PurchaseEvent = {
|
|
157
|
+
type: 'purchase';
|
|
158
|
+
data: {
|
|
159
|
+
item: string;
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
type PurchaseCompleteEvent = {
|
|
163
|
+
type: 'purchase_complete';
|
|
164
|
+
data: {
|
|
165
|
+
success: boolean;
|
|
166
|
+
item?: string;
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
type GameEvent = PlayAgainEvent | SinglePlayerGameOverEvent | ReadyEvent | HapticFeedbackEvent | ToggleMuteEvent | GameErrorEvent | SaveGameStateEvent | RefuteGameStateEvent | GameInfoEvent | GameStateUpdatedEvent | MultiplayerGameOverEvent | MultiplayerSaveGameStateEvent | PurchaseEvent | PurchaseCompleteEvent;
|
|
170
|
+
type GameEventMessage<T extends GameEvent['type']> = {
|
|
171
|
+
type: 'game_event';
|
|
172
|
+
event: Extract<GameEvent, {
|
|
173
|
+
type: T;
|
|
174
|
+
}>;
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* Build a game_event envelope for sending over postMessage. Hosts and the SDK
|
|
178
|
+
* share this so the wire format is defined in one place.
|
|
179
|
+
*/
|
|
180
|
+
declare const createGameEventMessage: <T extends GameEvent["type"]>(type: T, data: Extract<GameEvent, {
|
|
181
|
+
type: T;
|
|
182
|
+
}>["data"]) => GameEventMessage<T>;
|
|
183
|
+
/**
|
|
184
|
+
* Parse an unknown postMessage payload into a GameEvent, or null when the
|
|
185
|
+
* payload is not a well-formed game_event envelope.
|
|
186
|
+
*/
|
|
187
|
+
declare const parseGameEventMessage: (value: unknown) => GameEvent | null;
|
|
188
|
+
/**
|
|
189
|
+
* Messages from the game host to the game client
|
|
190
|
+
*/
|
|
191
|
+
type IncomingGameEvent = GameEventMessage<'play_again' | 'toggle_mute' | 'game_info' | 'game_state_updated' | 'purchase_complete'>;
|
|
192
|
+
type OutgoingGameEvent = GameEventMessage<'game_over' | 'ready' | 'haptic_feedback' | 'error' | 'save_game_state' | 'refute_game_state' | 'multiplayer_game_over' | 'multiplayer_save_game_state' | 'purchase'>;
|
|
193
|
+
type IncomingGameEventType = IncomingGameEvent['event']['type'];
|
|
194
|
+
type IncomingGameEventData<T extends IncomingGameEventType> = Extract<GameEvent, {
|
|
195
|
+
type: T;
|
|
196
|
+
}>['data'];
|
|
197
|
+
declare class RemixSDK {
|
|
198
|
+
private isClient;
|
|
199
|
+
private target;
|
|
200
|
+
private eventListeners;
|
|
201
|
+
private readyPromise;
|
|
202
|
+
private readyPromiseResolve;
|
|
203
|
+
private purchasePromiseResolvers;
|
|
204
|
+
private readyResendTimer;
|
|
205
|
+
private readonly instanceId;
|
|
206
|
+
private _gameInfo?;
|
|
207
|
+
private _gameState?;
|
|
208
|
+
private _levelStars;
|
|
209
|
+
constructor();
|
|
210
|
+
private scheduleReadyResend;
|
|
211
|
+
private cancelReadyResend;
|
|
212
|
+
on<T extends IncomingGameEventType>(eventType: T, callback: (data: IncomingGameEventData<T>) => void): () => void;
|
|
213
|
+
off<T extends IncomingGameEventType>(eventType: T, callback: (data: IncomingGameEventData<T>) => void): void;
|
|
214
|
+
/**
|
|
215
|
+
* Listen for the host starting play: a replay after game over, or — for level
|
|
216
|
+
* games — a host-directed level via `data.levelIndex` (Studio continue/jump or
|
|
217
|
+
* the production next-level button). `data` may be omitted for a plain restart.
|
|
218
|
+
*/
|
|
219
|
+
onPlay(callback: (data?: PlayAgainEvent['data']) => void): () => void;
|
|
220
|
+
/** @deprecated Use {@link onPlay}. Retained for backward compatibility. */
|
|
221
|
+
onPlayAgain(callback: (data?: PlayAgainEvent['data']) => void): () => void;
|
|
222
|
+
onToggleMute(callback: (data: ToggleMuteEvent['data']) => void): () => void;
|
|
223
|
+
onGameStateUpdated(callback: (data: GameStateUpdatedEvent['data']) => void): () => void;
|
|
224
|
+
onGameInfo(callback: (data: GameInfoEvent['data']) => void): () => void;
|
|
225
|
+
onPurchaseComplete(callback: (data: PurchaseCompleteEvent['data']) => void): () => void;
|
|
226
|
+
setTarget(target: Window): void;
|
|
227
|
+
get purchasedItems(): string[];
|
|
228
|
+
get inventory(): InventoryItem[];
|
|
229
|
+
get shopItems(): ShopItem[];
|
|
230
|
+
get gameInfo(): GameInfo | undefined;
|
|
231
|
+
get gameState(): GameState | null | undefined;
|
|
232
|
+
get players(): Player[] | undefined;
|
|
233
|
+
get player(): Player | undefined;
|
|
234
|
+
get isReady(): boolean;
|
|
235
|
+
/** True when the host configured this game with a fixed level count. */
|
|
236
|
+
get isLevelBased(): boolean;
|
|
237
|
+
/** Total levels for level-based games (from host metadata). */
|
|
238
|
+
get levelCount(): number | undefined;
|
|
239
|
+
/** 1-based level index from score-derived progression, defaulting to 1. */
|
|
240
|
+
get currentLevelIndex(): number;
|
|
241
|
+
/** Best stars earned per level from score-derived progression. Keys are 1-based level indices. */
|
|
242
|
+
get levelStars(): Record<string, LevelStars>;
|
|
243
|
+
/** Highest unlocked level from score-derived progression, defaulting to 1. */
|
|
244
|
+
get highestUnlockedLevel(): number;
|
|
245
|
+
/** Sum of best stars across all completed levels. */
|
|
246
|
+
get totalStars(): number;
|
|
247
|
+
ready: () => Promise<GameInfo>;
|
|
248
|
+
purchase: (data: PurchaseEvent["data"]) => Promise<PurchaseCompleteEvent["data"]>;
|
|
249
|
+
reportError: (data: GameErrorEvent["data"]) => void;
|
|
250
|
+
hapticFeedback: (type?: HapticFeedbackType) => void;
|
|
251
|
+
hasItem: (item: string) => boolean;
|
|
252
|
+
getItemPurchaseCount: (item: string) => number;
|
|
253
|
+
getShopItem: (slug: string) => ShopItem | undefined;
|
|
254
|
+
singlePlayer: {
|
|
255
|
+
actions: {
|
|
256
|
+
ready: () => Promise<GameInfo>;
|
|
257
|
+
hapticFeedback: (type?: HapticFeedbackType) => void;
|
|
258
|
+
reportError: (data: GameErrorEvent["data"]) => void;
|
|
259
|
+
purchase: (data: PurchaseEvent["data"]) => Promise<PurchaseCompleteEvent["data"]>;
|
|
260
|
+
gameOver: (data: SinglePlayerGameOverEvent["data"]) => void;
|
|
261
|
+
saveGameState: (data: SaveGameStateEvent["data"]) => void;
|
|
262
|
+
};
|
|
263
|
+
};
|
|
264
|
+
multiplayer: {
|
|
265
|
+
actions: {
|
|
266
|
+
gameOver: (data: MultiplayerGameOverEvent["data"]) => void;
|
|
267
|
+
refuteGameState: (data: RefuteGameStateEvent["data"]) => void;
|
|
268
|
+
saveGameState: (data: MultiplayerSaveGameStateEvent["data"]) => void;
|
|
269
|
+
ready: () => Promise<GameInfo>;
|
|
270
|
+
hapticFeedback: (type?: HapticFeedbackType) => void;
|
|
271
|
+
reportError: (data: GameErrorEvent["data"]) => void;
|
|
272
|
+
purchase: (data: PurchaseEvent["data"]) => Promise<PurchaseCompleteEvent["data"]>;
|
|
273
|
+
};
|
|
274
|
+
};
|
|
275
|
+
private emit;
|
|
276
|
+
private handleMessage;
|
|
277
|
+
private sendMessage;
|
|
278
|
+
private handleGlobalError;
|
|
279
|
+
private handleUnhandledRejection;
|
|
280
|
+
private applyPurchaseToCurrentPlayer;
|
|
281
|
+
}
|
|
282
|
+
declare const sdk: RemixSDK;
|
|
283
|
+
|
|
284
|
+
export { type GameErrorEvent, type GameEvent, type GameEventMessage, type GameInfo, type GameInfoEvent, type GameState, type GameStateUpdatedEvent, type HapticFeedbackEvent, type HapticFeedbackType, type IncomingGameEvent, type IncomingGameEventData, type IncomingGameEventType, type InventoryItem, type LevelAttempt, type LevelOutcome, 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, createGameEventMessage, parseGameEventMessage, sdk };
|
package/dist/index.d.ts
CHANGED
|
@@ -42,19 +42,52 @@ type GameInfo = {
|
|
|
42
42
|
id: string;
|
|
43
43
|
gameState: GameState;
|
|
44
44
|
} | null;
|
|
45
|
+
levelBased?: {
|
|
46
|
+
levelCount: number;
|
|
47
|
+
/**
|
|
48
|
+
* Score-derived level progression for this player. Platform-owned and kept
|
|
49
|
+
* fully separate from `gameState` — never read progression out of gameState.
|
|
50
|
+
*/
|
|
51
|
+
progress: LevelProgressState;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
/** Score-derived, platform-owned progression for a level-based game. */
|
|
55
|
+
type LevelProgressState = {
|
|
56
|
+
currentLevelIndex: number;
|
|
57
|
+
highestUnlockedLevel: number;
|
|
58
|
+
levelStars: Record<string, LevelStars>;
|
|
45
59
|
};
|
|
46
60
|
type ReadyEvent = {
|
|
47
61
|
type: 'ready';
|
|
48
|
-
data:
|
|
62
|
+
data: {
|
|
63
|
+
instanceId: string;
|
|
64
|
+
} | undefined;
|
|
49
65
|
};
|
|
50
66
|
type PlayAgainEvent = {
|
|
51
67
|
type: 'play_again';
|
|
52
|
-
data
|
|
68
|
+
data?: {
|
|
69
|
+
levelIndex?: number;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
type LevelStars = 1 | 2 | 3;
|
|
73
|
+
type LevelOutcome = 'level_complete' | 'level_failed' | 'all_complete';
|
|
74
|
+
/**
|
|
75
|
+
* One level attempt reported with game_over. Games report only what happened
|
|
76
|
+
* in the attempt; the host owns and derives all cumulative progression
|
|
77
|
+
* (star maps, unlock state) — see `LevelProgressState` for the host→game snapshot.
|
|
78
|
+
*/
|
|
79
|
+
type LevelAttempt = {
|
|
80
|
+
/** 1-based index of the level that was just played. */
|
|
81
|
+
levelIndex: number;
|
|
82
|
+
/** Stars earned this attempt. Omit for star-less games; a completion counts as 1. */
|
|
83
|
+
stars?: LevelStars;
|
|
84
|
+
outcome: LevelOutcome;
|
|
53
85
|
};
|
|
54
86
|
type SinglePlayerGameOverEvent = {
|
|
55
87
|
type: 'game_over';
|
|
56
88
|
data: {
|
|
57
89
|
score: number;
|
|
90
|
+
levelAttempt?: LevelAttempt;
|
|
58
91
|
};
|
|
59
92
|
};
|
|
60
93
|
type MultiplayerGameOverEvent = {
|
|
@@ -66,9 +99,12 @@ type MultiplayerGameOverEvent = {
|
|
|
66
99
|
}[];
|
|
67
100
|
};
|
|
68
101
|
};
|
|
102
|
+
type HapticFeedbackType = 'light' | 'medium' | 'hard' | 'success' | 'error';
|
|
69
103
|
type HapticFeedbackEvent = {
|
|
70
104
|
type: 'haptic_feedback';
|
|
71
|
-
data
|
|
105
|
+
data?: {
|
|
106
|
+
type?: HapticFeedbackType;
|
|
107
|
+
};
|
|
72
108
|
};
|
|
73
109
|
type ToggleMuteEvent = {
|
|
74
110
|
type: 'toggle_mute';
|
|
@@ -84,6 +120,7 @@ type GameErrorEvent = {
|
|
|
84
120
|
lineno?: number;
|
|
85
121
|
colno?: number;
|
|
86
122
|
error?: Error;
|
|
123
|
+
stack?: string;
|
|
87
124
|
};
|
|
88
125
|
};
|
|
89
126
|
type GameInfoEvent = {
|
|
@@ -136,28 +173,56 @@ type GameEventMessage<T extends GameEvent['type']> = {
|
|
|
136
173
|
type: T;
|
|
137
174
|
}>;
|
|
138
175
|
};
|
|
176
|
+
/**
|
|
177
|
+
* Build a game_event envelope for sending over postMessage. Hosts and the SDK
|
|
178
|
+
* share this so the wire format is defined in one place.
|
|
179
|
+
*/
|
|
180
|
+
declare const createGameEventMessage: <T extends GameEvent["type"]>(type: T, data: Extract<GameEvent, {
|
|
181
|
+
type: T;
|
|
182
|
+
}>["data"]) => GameEventMessage<T>;
|
|
183
|
+
/**
|
|
184
|
+
* Parse an unknown postMessage payload into a GameEvent, or null when the
|
|
185
|
+
* payload is not a well-formed game_event envelope.
|
|
186
|
+
*/
|
|
187
|
+
declare const parseGameEventMessage: (value: unknown) => GameEvent | null;
|
|
139
188
|
/**
|
|
140
189
|
* Messages from the game host to the game client
|
|
141
190
|
*/
|
|
142
191
|
type IncomingGameEvent = GameEventMessage<'play_again' | 'toggle_mute' | 'game_info' | 'game_state_updated' | 'purchase_complete'>;
|
|
143
192
|
type OutgoingGameEvent = GameEventMessage<'game_over' | 'ready' | 'haptic_feedback' | 'error' | 'save_game_state' | 'refute_game_state' | 'multiplayer_game_over' | 'multiplayer_save_game_state' | 'purchase'>;
|
|
144
|
-
type
|
|
193
|
+
type IncomingGameEventType = IncomingGameEvent['event']['type'];
|
|
194
|
+
type IncomingGameEventData<T extends IncomingGameEventType> = Extract<GameEvent, {
|
|
195
|
+
type: T;
|
|
196
|
+
}>['data'];
|
|
145
197
|
declare class RemixSDK {
|
|
146
198
|
private isClient;
|
|
147
199
|
private target;
|
|
148
200
|
private eventListeners;
|
|
201
|
+
private readyPromise;
|
|
149
202
|
private readyPromiseResolve;
|
|
150
|
-
private
|
|
203
|
+
private purchasePromiseResolvers;
|
|
204
|
+
private readyResendTimer;
|
|
205
|
+
private readonly instanceId;
|
|
151
206
|
private _gameInfo?;
|
|
152
207
|
private _gameState?;
|
|
208
|
+
private _levelStars;
|
|
153
209
|
constructor();
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
210
|
+
private scheduleReadyResend;
|
|
211
|
+
private cancelReadyResend;
|
|
212
|
+
on<T extends IncomingGameEventType>(eventType: T, callback: (data: IncomingGameEventData<T>) => void): () => void;
|
|
213
|
+
off<T extends IncomingGameEventType>(eventType: T, callback: (data: IncomingGameEventData<T>) => void): void;
|
|
214
|
+
/**
|
|
215
|
+
* Listen for the host starting play: a replay after game over, or — for level
|
|
216
|
+
* games — a host-directed level via `data.levelIndex` (Studio continue/jump or
|
|
217
|
+
* the production next-level button). `data` may be omitted for a plain restart.
|
|
218
|
+
*/
|
|
219
|
+
onPlay(callback: (data?: PlayAgainEvent['data']) => void): () => void;
|
|
220
|
+
/** @deprecated Use {@link onPlay}. Retained for backward compatibility. */
|
|
221
|
+
onPlayAgain(callback: (data?: PlayAgainEvent['data']) => void): () => void;
|
|
222
|
+
onToggleMute(callback: (data: ToggleMuteEvent['data']) => void): () => void;
|
|
223
|
+
onGameStateUpdated(callback: (data: GameStateUpdatedEvent['data']) => void): () => void;
|
|
224
|
+
onGameInfo(callback: (data: GameInfoEvent['data']) => void): () => void;
|
|
225
|
+
onPurchaseComplete(callback: (data: PurchaseCompleteEvent['data']) => void): () => void;
|
|
161
226
|
setTarget(target: Window): void;
|
|
162
227
|
get purchasedItems(): string[];
|
|
163
228
|
get inventory(): InventoryItem[];
|
|
@@ -167,17 +232,29 @@ declare class RemixSDK {
|
|
|
167
232
|
get players(): Player[] | undefined;
|
|
168
233
|
get player(): Player | undefined;
|
|
169
234
|
get isReady(): boolean;
|
|
235
|
+
/** True when the host configured this game with a fixed level count. */
|
|
236
|
+
get isLevelBased(): boolean;
|
|
237
|
+
/** Total levels for level-based games (from host metadata). */
|
|
238
|
+
get levelCount(): number | undefined;
|
|
239
|
+
/** 1-based level index from score-derived progression, defaulting to 1. */
|
|
240
|
+
get currentLevelIndex(): number;
|
|
241
|
+
/** Best stars earned per level from score-derived progression. Keys are 1-based level indices. */
|
|
242
|
+
get levelStars(): Record<string, LevelStars>;
|
|
243
|
+
/** Highest unlocked level from score-derived progression, defaulting to 1. */
|
|
244
|
+
get highestUnlockedLevel(): number;
|
|
245
|
+
/** Sum of best stars across all completed levels. */
|
|
246
|
+
get totalStars(): number;
|
|
170
247
|
ready: () => Promise<GameInfo>;
|
|
171
248
|
purchase: (data: PurchaseEvent["data"]) => Promise<PurchaseCompleteEvent["data"]>;
|
|
172
249
|
reportError: (data: GameErrorEvent["data"]) => void;
|
|
173
|
-
hapticFeedback: () => void;
|
|
250
|
+
hapticFeedback: (type?: HapticFeedbackType) => void;
|
|
174
251
|
hasItem: (item: string) => boolean;
|
|
175
252
|
getItemPurchaseCount: (item: string) => number;
|
|
176
253
|
getShopItem: (slug: string) => ShopItem | undefined;
|
|
177
254
|
singlePlayer: {
|
|
178
255
|
actions: {
|
|
179
256
|
ready: () => Promise<GameInfo>;
|
|
180
|
-
hapticFeedback: () => void;
|
|
257
|
+
hapticFeedback: (type?: HapticFeedbackType) => void;
|
|
181
258
|
reportError: (data: GameErrorEvent["data"]) => void;
|
|
182
259
|
purchase: (data: PurchaseEvent["data"]) => Promise<PurchaseCompleteEvent["data"]>;
|
|
183
260
|
gameOver: (data: SinglePlayerGameOverEvent["data"]) => void;
|
|
@@ -190,7 +267,7 @@ declare class RemixSDK {
|
|
|
190
267
|
refuteGameState: (data: RefuteGameStateEvent["data"]) => void;
|
|
191
268
|
saveGameState: (data: MultiplayerSaveGameStateEvent["data"]) => void;
|
|
192
269
|
ready: () => Promise<GameInfo>;
|
|
193
|
-
hapticFeedback: () => void;
|
|
270
|
+
hapticFeedback: (type?: HapticFeedbackType) => void;
|
|
194
271
|
reportError: (data: GameErrorEvent["data"]) => void;
|
|
195
272
|
purchase: (data: PurchaseEvent["data"]) => Promise<PurchaseCompleteEvent["data"]>;
|
|
196
273
|
};
|
|
@@ -204,4 +281,4 @@ declare class RemixSDK {
|
|
|
204
281
|
}
|
|
205
282
|
declare const sdk: RemixSDK;
|
|
206
283
|
|
|
207
|
-
export { type GameErrorEvent, type GameEvent, type GameEventMessage, type GameInfo, type GameInfoEvent, type GameState, type GameStateUpdatedEvent, type HapticFeedbackEvent, type IncomingGameEvent, type InventoryItem, 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 };
|
|
284
|
+
export { type GameErrorEvent, type GameEvent, type GameEventMessage, type GameInfo, type GameInfoEvent, type GameState, type GameStateUpdatedEvent, type HapticFeedbackEvent, type HapticFeedbackType, type IncomingGameEvent, type IncomingGameEventData, type IncomingGameEventType, type InventoryItem, type LevelAttempt, type LevelOutcome, 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, createGameEventMessage, parseGameEventMessage, sdk };
|