@remix-gg/sdk 0.4.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 ADDED
@@ -0,0 +1,423 @@
1
+ # Remix SDK
2
+
3
+ The Remix SDK enables game developers to integrate their HTML5 games with the Remix platform through a simple messaging interface.
4
+
5
+ ## Installation
6
+
7
+ Install the SDK using your preferred package manager:
8
+
9
+ ```sh
10
+ npm install @remix-gg/sdk
11
+ # or
12
+ bun install @remix-gg/sdk
13
+ # or
14
+ yarn add @remix-gg/sdk
15
+ # or
16
+ pnpm add @remix-gg/sdk
17
+ ```
18
+
19
+ When you upload game code to Remix, a script tag to reference the SDK will automatically be added to your game code. The SDK will be accessible from `window.RemixSDK`.
20
+
21
+ ## Example Implementation
22
+
23
+ ```typescript
24
+ import { sdk } from '@remix-gg/sdk'
25
+ import type { GameState, Player } from '@remix-gg/sdk'
26
+
27
+ class MyGame {
28
+ sdk = sdk
29
+ player: Player
30
+ gameState: GameState | null = null
31
+ isMuted: boolean = true
32
+
33
+ // some example values that correlate to items purchased by boosting
34
+ canSaveGame: boolean = false
35
+ hasSuperSkin: boolean = false
36
+
37
+ constructor() {
38
+ // attach the sdk to the Game class to we can reference it easily
39
+ this.sdk = window.RemixSDK
40
+
41
+ // Setup event listeners
42
+ this.setupEventListeners()
43
+
44
+ // Initialize your game
45
+ this.initialize()
46
+ }
47
+
48
+ private async setupEventListeners() {
49
+ // Listen for play again events
50
+ this.sdk.onPlayAgain(() => {
51
+ this.resetGame()
52
+ })
53
+
54
+ // Listen for mute state changes
55
+ this.sdk.onToggleMute((data) => {
56
+ this.isMuted = data.isMuted
57
+ })
58
+
59
+ // Listen for purchase completions (optional)
60
+ this.sdk.onPurchaseComplete(() => {
61
+ this.checkPurchasedItems()
62
+ })
63
+ }
64
+
65
+ private async initialize() {
66
+ // wait for the sdk to get game information from Remix
67
+ await this.sdk.ready()
68
+
69
+ // get information the game uses from the SDK
70
+ this.player = this.sdk.player
71
+ this.gameState = this.sdk.gameState
72
+
73
+ // check which items the user has already purchased
74
+ this.checkPurchasedItems()
75
+
76
+ //... start the game logic
77
+ }
78
+
79
+ private checkPurchasedItems() {
80
+ // check the identifier for each item to see if the user has purchased it
81
+
82
+ if (this.sdk.hasItem('save-game')) {
83
+ this.canSaveGame = true
84
+ }
85
+
86
+ if (this.sdk.hasItem('super-skin')) {
87
+ this.hasSuperSkin = true
88
+ }
89
+ }
90
+
91
+ private async purchaseItem(itemId: string) {
92
+ // trigger the boost UI and then check for new items after
93
+ await this.sdk.purchase({ item: itemId })
94
+ this.checkPurchasedItems()
95
+ }
96
+
97
+ private gameOver(finalScore: number) {
98
+ //
99
+ this.sdk.singlePlayer.actions.gameOver({ score: finalScore })
100
+ }
101
+
102
+ private saveGame() {
103
+ // return unless the user has purchased the 'save-game' item
104
+ // see checkPurchasedItems
105
+ if (!this.canSaveGame) return
106
+
107
+ // push game state updates to save progress for the user
108
+ const gameState: GameState = this.getCurrentGameState();
109
+ this.sdk.singlePlayer.actions.saveGameState({ gameState })
110
+ }
111
+
112
+ private triggerHapticFeedback() {
113
+ this.sdk.hapticFeedback()
114
+ }
115
+ }
116
+ ```
117
+
118
+
119
+ ## API Reference
120
+
121
+ ### Properties and Getters
122
+
123
+ #### `sdk.ready(): Promise<GameInfo>`
124
+
125
+ 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)
126
+
127
+ #### `sdk.isReady: boolean`
128
+
129
+ 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.
130
+
131
+ #### `sdk.player: Player | undefined`
132
+
133
+ Get the current player object.
134
+
135
+ #### `sdk.players: Player[] | undefined`
136
+
137
+ Get the list of all players in the game. This is useful for multiplayer games. In multiplayer games, it is essential that you use the `players` array so you can report scores with the associated player ids and notify players it is there turn.
138
+
139
+ #### `sdk.gameState: GameState | null | undefined`
140
+
141
+ Get the current game state. This will be `null` when there is no existing GameState for this game and user.
142
+
143
+ #### `sdk.purchasedItems: string[]`
144
+
145
+ Get the list of item IDs that the current player has purchased. This is a read-only property that reflects the `purchasedItems` array from the current player's `GameInfo`.
146
+
147
+ #### `sdk.storeItems: StoreItem[]`
148
+
149
+ Get the list of purchasable store items available for the current game session.
150
+
151
+ #### `sdk.hasItem(item: string): boolean`
152
+
153
+ Check if the current player has purchased a specific item.
154
+
155
+ Parameters:
156
+ - `item`: The item identifier to check (string)
157
+
158
+ Returns:
159
+ - `true` if the player has purchased the item, `false` otherwise
160
+
161
+ #### `sdk.getStoreItem(slug: string): StoreItem | undefined`
162
+
163
+ Get a specific store item by slug.
164
+
165
+ Parameters:
166
+ - `slug`: The store item slug to look up (string)
167
+
168
+ Returns:
169
+ - The matching `StoreItem` object when found, otherwise `undefined`
170
+
171
+ #### `sdk.gameInfo: GameInfo | undefined`
172
+
173
+ Get the current `GameInfo` object. This is all the data passed into the game from Remix. It is recommended that you use the other functions to access the specific data you need instead of pulling data out of this large object.
174
+
175
+ #### `sdk.hapticFeedback()`
176
+
177
+ Call this method to trigger haptic feedback on supported devices. Use this for important game events like collisions, achievements, or other significant player interactions.
178
+
179
+ #### Best Practices for Haptic Feedback
180
+
181
+ - Use haptic feedback sparingly to avoid overwhelming the player
182
+ - Reserve haptic feedback for meaningful interactions and achievements
183
+ - Consider using it for:
184
+ - Collisions with obstacles
185
+ - Collecting power-ups
186
+ - Completing levels
187
+ - Achieving high scores
188
+ - Important game events
189
+
190
+ #### `sdk.purchase({ item: string }) => Promise<{ success: boolean }>`
191
+
192
+ Call this function to initiate a purchase for an in-game item. This method sends a purchase request to the Remix platform and returns a promise that resolves with the purchase result. Make sure the `item` identifier matches an item you have added to your game using the [Remix](https://remix.gg) web app.
193
+
194
+ Parameters:
195
+ - `item`: The identifier of the item to purchase (string)
196
+
197
+ Returns:
198
+ - A promise that resolves with `{ success: boolean }` indicating whether the purchase was successful
199
+
200
+ The purchase flow works as follows:
201
+ 1. Call `purchase({ item: 'item-id' })` to initiate the purchase
202
+ 2. The platform handles the purchase transaction (payment processing, etc.)
203
+ 3. The promise resolves with `{ success: true }` if the purchase succeeds, or `{ success: false }` if it fails
204
+ 4. You can also listen for `purchase_complete` events using `sdk.onPurchaseComplete()` for additional handling
205
+
206
+ **Important**: The `purchasedItems` array in `GameInfo` will be automatically updated after a successful purchase, and you can check item ownership using `sdk.hasItem(item)` or `sdk.purchasedItems`.
207
+
208
+
209
+ ### Single Player Actions
210
+
211
+ #### `sdk.singlePlayer.actions.gameOver({ score: number })`
212
+
213
+ Call this method when the game is over to report the final score.
214
+
215
+ Parameters:
216
+
217
+ - `score`: The player's final score (number)
218
+
219
+ #### `sdk.singlePlayer.actions.saveGameState({ gameState: Record<string, unknown> })`
220
+
221
+ Call this method when you wish to persist game state for the user. This will enable users to save their progress in a
222
+ game between play sessions.
223
+
224
+
225
+ ### Multi-Player Actions
226
+
227
+ Muliplayer has the same actions as singlePlayer with 2 updates and 1 addition. It is important to note that YOU
228
+ MUST use the `gameInfo` data from the response to `ready()` in order to accurately report scores with player ids when the game ends.
229
+
230
+ #### `sdk.multiplayer.actions.gameOver({ scores: { playerId: string; score: number }[] })`
231
+
232
+ Call this method with the game is over to report the final scores.
233
+
234
+ Parameters:
235
+
236
+ - `scores`: The playerId and score for each player in the game.
237
+
238
+ #### `sdk.multiplayer.actions.saveGameState({ gameState: Record<string, unknown>; alertUserIds: string[] })`
239
+
240
+ Call this method when a player action should update the shared game state for all players. If your game has a concept of turns as most do, you must include the `alertUserIds` list with the id(s) of the player(s) that should be alerted it is their turn.
241
+
242
+ Parameters:
243
+
244
+ - `gameState`: A Record object containing your game state. The shape of the game state is open to the game creator's discretion.
245
+ - `alertUserIds`: A list of player ids to alert on this game state update. Typically this will be a list of length 1 containing the id of the player who is next to act, but different games may need to alert multiple players.
246
+
247
+ #### `sdk.multiplayer.actions.refuteGameState({ gameStateId: string })`
248
+
249
+ Call this method when your game client has determined an incoming game state is invalid. It is not strictly required that your game validates game state updates, but it should. Putting game state validation logic in your game client means that bad actors attempting to push unfair updates are invalidated by good actors running valid implementations of your game.
250
+
251
+ Parameters:
252
+
253
+ - `gameStateId`: The id from the `game_state_updated` event that the game client is refutting.
254
+
255
+ #### `sdk.multiplayer.actions.purchase({ item: string }) => Promise<{ success: boolean }>`
256
+
257
+ Same as the single player `purchase` method. Call this method to initiate a purchase for an in-game item in multiplayer games. See the single player purchase documentation above for details.
258
+
259
+
260
+ Here is an example function that handles update and refute game state for a Chess game:
261
+
262
+ ```javascript
263
+ handleGameStateUpdate({ gameState }) {
264
+ if (!gameState) return;
265
+
266
+ const { id, gameState: { moves } } = gameState;
267
+
268
+ try {
269
+ this.chess.reset();
270
+ if (moves?.length > 0) {
271
+ for (const move of moves) {
272
+ this.chess.move(move);
273
+ }
274
+ }
275
+ this.renderBoard();
276
+ this.updateStatus();
277
+ this.updateMoveHistory();
278
+ this.updateCapturedPieces();
279
+ } catch (error) {
280
+ // game state is invalid, refuting this game state update
281
+ this.sdk.multiplayer.actions.refuteGameState({ gameStateId: id });
282
+ }
283
+ }
284
+ ```
285
+
286
+ ### Events
287
+
288
+ The SDK provides both generic event listeners and specific typed listener methods. We recommend using the specific listener methods for better type safety and clarity.
289
+
290
+ #### Event Listeners
291
+
292
+ ##### `sdk.onPlayAgain(callback: () => void)`
293
+
294
+ Register a callback function that is called when the player wants to play again.
295
+
296
+ ##### `sdk.onToggleMute(callback: (data: { isMuted: boolean }) => void)`
297
+
298
+ Register a callback function that is called when receiving mute state changes.
299
+
300
+ Parameters:
301
+ - `callback`: Function that receives `{ isMuted: boolean }` indicating the current mute state
302
+
303
+ ##### `sdk.onGameStateUpdated(callback: (data: { id: string, gameState: Record<string, unknown> } | null) => void)`
304
+
305
+ Register a callback function that is called when there is an update to the game state. This is essential for multiplayer games to receive state updates from other players.
306
+
307
+ Parameters:
308
+ - `callback`: Function that receives the game state update data, or `null` if the game state is cleared
309
+
310
+ ##### `sdk.onGameInfo(callback: (data: GameInfo) => void)`
311
+
312
+ Register a callback function that is called when game information is received. This is typically called automatically when the game initializes, but you can listen for updates.
313
+
314
+ Parameters:
315
+ - `callback`: Function that receives the `GameInfo` object
316
+
317
+ ##### `sdk.onPurchaseComplete(callback: (data: { success: boolean }) => void)`
318
+
319
+ Register a callback function that is called when a purchase completes. This can be used for additional handling beyond the promise returned by `purchase()`. It returns the same value as awaiting the call to `purchase()`, but sometimes users may boost your game without your game triggering the `purchase` function.
320
+
321
+ Parameters:
322
+ - `callback`: Function that receives `{ success: boolean }` indicating whether the purchase succeeded
323
+
324
+ ## TypeScript Support
325
+
326
+ The SDK is written in TypeScript and includes full type definitions.
327
+
328
+
329
+ ## Example Multiplayer Implementation
330
+
331
+ Multiplayer game development is not yet available to all users, but the SDK supports it. Watch the Remix Discord for announcements about
332
+ multiplayer game support.
333
+
334
+ ```typescript
335
+ import { sdk } from '@remix-gg/sdk'
336
+ import type { GameState, Player } from '@remix-gg/sdk'
337
+
338
+ class Game {
339
+ sdk = sdk
340
+ gameState: GameState
341
+ player: Player
342
+ players: Player[]
343
+
344
+ constructor() {
345
+ this.sdk = window.RemixSDK
346
+
347
+ // Initialize your game
348
+ this.initialize()
349
+
350
+ // Setup event listeners
351
+ this.setupEventListeners()
352
+ }
353
+
354
+ private setupEventListeners() {
355
+ // Listen for play again events
356
+ this.sdk.onPlayAgain(() => {
357
+ this.resetGame()
358
+ })
359
+
360
+ // Listen for mute state changes
361
+ this.sdk.onToggleMute((data) => {
362
+ this.setMuted(data.isMuted)
363
+ })
364
+
365
+ // Listen for game state updates (essential for multiplayer)
366
+ this.sdk.onGameStateUpdated((data) => {
367
+ const { id, gameState } = data
368
+ const newGameStateIsValid = this.validateGameState(gameState);
369
+
370
+ if (newGameStateIsValid) {
371
+ this.gameState = gameState
372
+ } else {
373
+ sdk.multiplayer.actions.refuteGameState({ gameStateId: id });
374
+ }
375
+ })
376
+
377
+ // Listen for purchase completions (optional)
378
+ sdk.onPurchaseComplete((data) => {
379
+ if (data.success) {
380
+ this.handlePurchaseSuccess()
381
+ }
382
+ })
383
+ }
384
+
385
+ private async initialize() {
386
+ await this.sdk.ready()
387
+
388
+ this.gameState = this.sdk.gameState
389
+ this.player = this.sdk.player
390
+ this.players = this.sdk.players
391
+ }
392
+
393
+ private saveGameState(gameState: Record<string, unknown>, nextTurnPlayerId: string) {
394
+ sdk.multiplayer.actions.saveGameState({
395
+ gameState,
396
+ alertUserIds: [nextTurnPlayerId],
397
+ })
398
+ }
399
+
400
+ private gameOver(scores: { playerId: string, score: number }[]) {
401
+ sdk.multiplayer.actions.gameOver({ scores })
402
+ }
403
+
404
+ private async purchaseItem(itemId: string) {
405
+ try {
406
+ const result = await sdk.multiplayer.actions.purchase({ item: itemId })
407
+ if (result.success) {
408
+ console.log(`Successfully purchased ${itemId}`)
409
+ // Item is now available via sdk.hasItem(itemId) or sdk.purchasedItems
410
+ // The purchasedItems array is automatically updated for all players
411
+ } else {
412
+ console.log(`Purchase failed for ${itemId}`)
413
+ }
414
+ } catch (error) {
415
+ console.error('Purchase error:', error)
416
+ }
417
+ }
418
+ }
419
+ ```
420
+
421
+ ## License
422
+
423
+ MIT
@@ -0,0 +1,192 @@
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 GameState = Record<string, unknown>;
9
+ type Player = {
10
+ id: string;
11
+ name: string;
12
+ purchasedItems: string[];
13
+ imageUrl?: string;
14
+ };
15
+ type StoreItem = {
16
+ slug: string;
17
+ name: string;
18
+ itemType?: string;
19
+ bitsCost?: number | null;
20
+ description?: string | null;
21
+ iconUrl?: string | null;
22
+ tier?: number | null;
23
+ };
24
+ type GameInfo = {
25
+ players: Player[];
26
+ player: Player;
27
+ storeItems?: StoreItem[];
28
+ viewContext: ViewContext;
29
+ initialGameState: {
30
+ id: string;
31
+ gameState: GameState;
32
+ } | null;
33
+ };
34
+ type ReadyEvent = {
35
+ type: 'ready';
36
+ data: undefined;
37
+ };
38
+ type PlayAgainEvent = {
39
+ type: 'play_again';
40
+ data: undefined;
41
+ };
42
+ type SinglePlayerGameOverEvent = {
43
+ type: 'game_over';
44
+ data: {
45
+ score: number;
46
+ };
47
+ };
48
+ type MultiplayerGameOverEvent = {
49
+ type: 'multiplayer_game_over';
50
+ data: {
51
+ scores: {
52
+ playerId: string;
53
+ score: number;
54
+ }[];
55
+ };
56
+ };
57
+ type HapticFeedbackEvent = {
58
+ type: 'haptic_feedback';
59
+ data: undefined;
60
+ };
61
+ type ToggleMuteEvent = {
62
+ type: 'toggle_mute';
63
+ data: {
64
+ isMuted: boolean;
65
+ };
66
+ };
67
+ type GameErrorEvent = {
68
+ type: 'error';
69
+ data: {
70
+ message: string;
71
+ source?: string;
72
+ lineno?: number;
73
+ colno?: number;
74
+ error?: Error;
75
+ };
76
+ };
77
+ type GameInfoEvent = {
78
+ type: 'game_info';
79
+ data: GameInfo;
80
+ };
81
+ type MultiplayerSaveGameStateEvent = {
82
+ type: 'multiplayer_save_game_state';
83
+ data: {
84
+ gameState: GameState;
85
+ alertUserIds?: string[];
86
+ };
87
+ };
88
+ type GameStateUpdatedEvent = {
89
+ type: 'game_state_updated';
90
+ data: {
91
+ id: string;
92
+ gameState: GameState;
93
+ } | null;
94
+ };
95
+ type RefuteGameStateEvent = {
96
+ type: 'refute_game_state';
97
+ data: {
98
+ gameStateId: string;
99
+ };
100
+ };
101
+ type SaveGameStateEvent = {
102
+ type: 'save_game_state';
103
+ data: {
104
+ gameState: GameState;
105
+ };
106
+ };
107
+ type PurchaseEvent = {
108
+ type: 'purchase';
109
+ data: {
110
+ item: string;
111
+ };
112
+ };
113
+ type PurchaseCompleteEvent = {
114
+ type: 'purchase_complete';
115
+ data: {
116
+ success: boolean;
117
+ item?: string;
118
+ };
119
+ };
120
+ type GameEvent = PlayAgainEvent | SinglePlayerGameOverEvent | ReadyEvent | HapticFeedbackEvent | ToggleMuteEvent | GameErrorEvent | SaveGameStateEvent | RefuteGameStateEvent | GameInfoEvent | GameStateUpdatedEvent | MultiplayerGameOverEvent | MultiplayerSaveGameStateEvent | PurchaseEvent | PurchaseCompleteEvent;
121
+ type GameEventMessage<T extends GameEvent['type']> = {
122
+ type: 'game_event';
123
+ event: Extract<GameEvent, {
124
+ type: T;
125
+ }>;
126
+ };
127
+ /**
128
+ * Messages from the game host to the game client
129
+ */
130
+ type IncomingGameEvent = GameEventMessage<'play_again' | 'toggle_mute' | 'game_info' | 'game_state_updated' | 'purchase_complete'>;
131
+ type OutgoingGameEvent = GameEventMessage<'game_over' | 'ready' | 'haptic_feedback' | 'error' | 'save_game_state' | 'refute_game_state' | 'multiplayer_game_over' | 'multiplayer_save_game_state' | 'purchase'>;
132
+ type EventCallback = (data: unknown) => void;
133
+ declare class FarcadeSDK {
134
+ private isClient;
135
+ private target;
136
+ private eventListeners;
137
+ private readyPromiseResolve;
138
+ private purchasePromiseResolve;
139
+ private _gameInfo?;
140
+ private _gameState?;
141
+ constructor();
142
+ on(eventType: IncomingGameEvent['event']['type'], callback: EventCallback): void;
143
+ off(eventType: IncomingGameEvent['event']['type'], callback: EventCallback): void;
144
+ onPlayAgain(callback: () => void): void;
145
+ onToggleMute(callback: (data: ToggleMuteEvent['data']) => void): void;
146
+ onGameStateUpdated(callback: (data: GameStateUpdatedEvent['data']) => void): void;
147
+ onGameInfo(callback: (data: GameInfoEvent['data']) => void): void;
148
+ onPurchaseComplete(callback: (data: PurchaseCompleteEvent['data']) => void): void;
149
+ setTarget(target: Window): void;
150
+ get purchasedItems(): string[];
151
+ get storeItems(): StoreItem[];
152
+ get gameInfo(): GameInfo | undefined;
153
+ get gameState(): GameState | null | undefined;
154
+ get players(): Player[] | undefined;
155
+ get player(): Player | undefined;
156
+ get isReady(): boolean;
157
+ ready: () => Promise<GameInfo>;
158
+ purchase: (data: PurchaseEvent["data"]) => Promise<PurchaseCompleteEvent["data"]>;
159
+ reportError: (data: GameErrorEvent["data"]) => void;
160
+ hapticFeedback: () => void;
161
+ hasItem: (item: string) => boolean;
162
+ getStoreItem: (slug: string) => StoreItem | undefined;
163
+ singlePlayer: {
164
+ actions: {
165
+ ready: () => Promise<GameInfo>;
166
+ hapticFeedback: () => void;
167
+ reportError: (data: GameErrorEvent["data"]) => void;
168
+ purchase: (data: PurchaseEvent["data"]) => Promise<PurchaseCompleteEvent["data"]>;
169
+ gameOver: (data: SinglePlayerGameOverEvent["data"]) => void;
170
+ saveGameState: (data: SaveGameStateEvent["data"]) => void;
171
+ };
172
+ };
173
+ multiplayer: {
174
+ actions: {
175
+ gameOver: (data: MultiplayerGameOverEvent["data"]) => void;
176
+ refuteGameState: (data: RefuteGameStateEvent["data"]) => void;
177
+ saveGameState: (data: MultiplayerSaveGameStateEvent["data"]) => void;
178
+ ready: () => Promise<GameInfo>;
179
+ hapticFeedback: () => void;
180
+ reportError: (data: GameErrorEvent["data"]) => void;
181
+ purchase: (data: PurchaseEvent["data"]) => Promise<PurchaseCompleteEvent["data"]>;
182
+ };
183
+ };
184
+ private emit;
185
+ private handleMessage;
186
+ private sendMessage;
187
+ private handleGlobalError;
188
+ private handleUnhandledRejection;
189
+ }
190
+ declare const sdk: FarcadeSDK;
191
+
192
+ export { FarcadeSDK, type GameErrorEvent, type GameEvent, type GameEventMessage, type GameInfo, type GameInfoEvent, type GameState, type GameStateUpdatedEvent, type HapticFeedbackEvent, type IncomingGameEvent, type MultiplayerGameOverEvent, type MultiplayerSaveGameStateEvent, type OutgoingGameEvent, type PlayAgainEvent, type Player, type PurchaseCompleteEvent, type PurchaseEvent, type ReadyEvent, type RefuteGameStateEvent, type SaveGameStateEvent, type SinglePlayerGameOverEvent, type StoreItem, type ToggleMuteEvent, type ViewContext, sdk };