feat!: balances and items replace the wallet in the player profile
CI / check (push) Failing after 10s
CI / publish (push) Has been skipped

This commit is contained in:
edmand46
2026-09-21 23:39:02 +03:00
parent 83e3f78823
commit 7e232a9000
6 changed files with 42 additions and 33 deletions
+7 -7
View File
@@ -1,7 +1,7 @@
# @rudder/sdk # @rudder/sdk
Rudder client SDK for web games: device and social auth, player state Rudder client SDK for web games: device and social auth, player state
(profile, wallets, counters), catalog currencies, player storage, and calls to (profile, balances, items), catalog currencies, player storage, and calls to
installed modules through generated typed clients. installed modules through generated typed clients.
## Install ## Install
@@ -34,8 +34,8 @@ const client = createClient({
await client.auth.loginWithDevice(); await client.auth.loginWithDevice();
const profile = client.player.value; const profile = client.player.value;
const gold = profile?.wallets?.find((w) => w.currency === 'gold')?.balance; const gold = profile?.balances?.find((b) => b.currency === 'gold')?.balance;
const wins = profile?.counters?.wins ?? 0; const swords = profile?.items?.find((i) => i.item === 'sword')?.quantity ?? 0;
await client.storage.save('progress', JSON.stringify({ level: 7 })); await client.storage.save('progress', JSON.stringify({ level: 7 }));
@@ -90,13 +90,13 @@ can also call directly:
| Area | Access | | Area | Access |
|---|---| |---|---|
| Auth | `client.auth.loginWithDevice()`, `loginWithGoogle({ idToken })`, `loginWithApple({ idToken })`, `loginWithCustom({ customData })`, `linkWithGoogle(idToken)`, `linkWithApple(idToken)`, `linkWithCustom({ customData })`, `unlinkIdentity(provider)`, `logout()`, `isAuthenticated`, `onAuthStateChange(cb)` | | Auth | `client.auth.loginWithDevice()`, `loginWithGoogle({ idToken })`, `loginWithApple({ idToken })`, `loginWithCustom({ customData })`, `linkWithGoogle(idToken)`, `linkWithApple(idToken)`, `linkWithCustom({ customData })`, `unlinkIdentity(provider)`, `logout()`, `isAuthenticated`, `onAuthStateChange(cb)` |
| Player | `client.player` state: `player`, `identities`, `wallets`, `counters` | | Player | `client.player` state: `player`, `identities`, `balances`, `items` |
| Catalog currencies | `client.catalog` state (`Map<slug, CatalogCurrency>`) | | Catalog currencies | `client.catalog` state (`Map<slug, CatalogCurrency>`) |
| Storage | `client.storage` state + `.save(type, data)` / `.delete(type)` | | Storage | `client.storage` state + `.save(type, data)` / `.delete(type)` |
| Modules | `client.modules.call<T>(module, fn, args?, { moduleVersion? })`, usually through a generated client | | Modules | `client.modules.call<T>(module, fn, args?, { moduleVersion? })`, usually through a generated client |
`counters` holds the values of the project's client-visible number counters `balances` carries one entry per released currency and `items` one per
by slug; currency balances are in `wallets`. inventory stack.
## Error handling ## Error handling
@@ -172,7 +172,7 @@ function useSynced<T>(state: { onChange(cb: () => void): () => void; value: T |
function Wallet() { function Wallet() {
const profile = useSynced(client.player); const profile = useSynced(client.player);
return <div>{profile?.wallets?.map((w) => `${w.currency}: ${w.balance}`).join(', ')}</div>; return <div>{profile?.balances?.map((b) => `${b.currency}: ${b.balance}`).join(', ')}</div>;
} }
``` ```
+4 -4
View File
@@ -1,6 +1,6 @@
--- ---
name: rudder-web-sdk name: rudder-web-sdk
description: Use when working with the Rudder TypeScript/JavaScript client SDK (@rudder/sdk) for web games. Covers createClient, device/google/apple/custom auth, identity linking, state objects (player with wallets and counters, catalog, storage), module calls through clients generated by `rudder client generate`, idempotent retries, and error handling. Load this whenever code imports from @rudder/sdk or a generated rudder.modules.ts. description: Use when working with the Rudder TypeScript/JavaScript client SDK (@rudder/sdk) for web games. Covers createClient, device/google/apple/custom auth, identity linking, state objects (player with balances and items, catalog, storage), module calls through clients generated by `rudder client generate`, idempotent retries, and error handling. Load this whenever code imports from @rudder/sdk or a generated rudder.modules.ts.
--- ---
# Rudder Web SDK (@rudder/sdk) # Rudder Web SDK (@rudder/sdk)
@@ -75,7 +75,7 @@ refresh clears tokens and emits `signed-out`. Details:
| Area | Access | Kind | | Area | Access | Kind |
|---|---|---| |---|---|---|
| Auth | `client.auth` | service | | Auth | `client.auth` | service |
| Player | `client.player` (`player`, `identities`, `wallets`, `counters`) | `SyncedState<PlayerProfile>` | | Player | `client.player` (`player`, `identities`, `balances`, `items`) | `SyncedState<PlayerProfile>` |
| Catalog currencies | `client.catalog` | `SyncedState<Map<string, CatalogCurrency>>` | | Catalog currencies | `client.catalog` | `SyncedState<Map<string, CatalogCurrency>>` |
| Player storage | `client.storage` + `.save(type, data)` / `.delete(type)` | `SyncedState<GetStorageResponse>` + mutations | | Player storage | `client.storage` + `.save(type, data)` / `.delete(type)` | `SyncedState<GetStorageResponse>` + mutations |
| Modules | `client.modules.call<T>(module, fn, args?, { moduleVersion? })` | resolves to `result` as `T` | | Modules | `client.modules.call<T>(module, fn, args?, { moduleVersion? })` | resolves to `result` as `T` |
@@ -116,7 +116,7 @@ and commit it when installed modules change. Details:
revision poll (`GET /sdk/v1/sync`, keys `profile`, `catalog`, `storage`) runs revision poll (`GET /sdk/v1/sync`, keys `profile`, `catalog`, `storage`) runs
every 30 s (±20% jitter), pauses while the tab is hidden, and reloads the every 30 s (±20% jitter), pauses while the tab is hidden, and reloads the
states whose revision grew. Storage mutations invalidate immediately. Call states whose revision grew. Storage mutations invalidate immediately. Call
`player.reload()` after a module call that changes wallets or counters when `player.reload()` after a module call that changes balances or items when
you need fresh values right away. you need fresh values right away.
## Errors ## Errors
@@ -137,6 +137,6 @@ timeout a 504, both without details.
## Reference files ## Reference files
- [reference/auth.md](reference/auth.md): login flows, options, token store, session lifecycle - [reference/auth.md](reference/auth.md): login flows, options, token store, session lifecycle
- [reference/player.md](reference/player.md): profile, wallets, counters - [reference/player.md](reference/player.md): profile, balances, items
- [reference/storage.md](reference/storage.md): player storage, limits - [reference/storage.md](reference/storage.md): player storage, limits
- [reference/modules.md](reference/modules.md): generated clients, `modules.call`, idempotency, retries, errors - [reference/modules.md](reference/modules.md): generated clients, `modules.call`, idempotency, retries, errors
+17 -13
View File
@@ -1,12 +1,11 @@
# Player — `client.player` # Player — `client.player`
`PlayerDomain` (source: `src/domains/PlayerDomain.ts`) — the observable player `PlayerDomain` (source: `src/domains/PlayerDomain.ts`) — the observable player
profile: player, linked identities, currency wallets and profile: player, linked identities, currency balances and inventory stacks.
number counters. Synced under revision key `profile`. Synced under revision key `profile`.
There is no separate wallet service in this SDK. Wallet balances are read from There is no separate ledger service in this SDK. Balances and items are read
the profile and are credited/debited server-side (admin wallet adjust, module from the profile and are written server-side (admin adjust, module functions).
functions).
## Surface ## Surface
@@ -24,10 +23,10 @@ await client.player.reload(); // force refetch
```ts ```ts
interface PlayerProfile { interface PlayerProfile {
counters?: { [slug: string]: number }; // client-visible number counters balances?: Balance[];
identities?: PlayerProfileIdentity[]; identities?: PlayerProfileIdentity[];
items?: InventoryStack[];
player?: Player; player?: Player;
wallets?: Wallet[];
} }
interface PlayerProfileIdentity { interface PlayerProfileIdentity {
@@ -45,20 +44,25 @@ interface Player {
region?: string; region?: string;
} }
interface Wallet { interface Balance {
balance?: number; balance?: number;
currency?: string; // currency code configured in the dashboard currency?: string; // currency slug configured in the dashboard
}
interface InventoryStack {
item?: string; // item slug
quantity?: number;
} }
``` ```
## Behavior notes ## Behavior notes
- `counters` holds the values of `number` counters whose definition is - `balances` carries one entry per currency in the latest release, zero-filled
`client_visible`; hidden counters are left out. Currency counters are in for a player who never received it; `items` carries one entry per non-empty
`wallets`, not in `counters`. Counter changes bump the `profile` revision. inventory stack. Both bump the `profile` revision when they change.
- Warmed automatically at login (with catalog). - Warmed automatically at login (with catalog).
- Kept fresh by the revision poll; call `reload()` after a module function - Kept fresh by the revision poll; call `reload()` after a module function
that changes wallets or counters. that changes balances or items.
- All fields are optional on the wire (`?`); code defensively. - All fields are optional on the wire (`?`); code defensively.
- React: `useSyncExternalStore` maps directly onto `onChange` (see the SDK - React: `useSyncExternalStore` maps directly onto `onChange` (see the SDK
README "React recipe"). README "React recipe").
+1 -1
View File
@@ -5,7 +5,7 @@ import type { PlayerProfile } from '../generated/player.js';
/** /**
* PlayerDomain — the observable player profile (player, identities, * PlayerDomain — the observable player profile (player, identities,
* wallets, counters). * balances, items).
* Synced under revision key `profile`. * Synced under revision key `profile`.
*/ */
export class PlayerDomain extends SyncedState<PlayerProfile> { export class PlayerDomain extends SyncedState<PlayerProfile> {
+12 -7
View File
@@ -1,5 +1,15 @@
// Code generated by apigen. DO NOT EDIT. // Code generated by apigen. DO NOT EDIT.
export interface Balance {
"balance"?: number;
"currency"?: string;
}
export interface InventoryStack {
"item"?: string;
"quantity"?: number;
}
export interface Player { export interface Player {
"avatarUrl"?: string; "avatarUrl"?: string;
"createdAt"?: string; "createdAt"?: string;
@@ -11,10 +21,10 @@ export interface Player {
} }
export interface PlayerProfile { export interface PlayerProfile {
"counters"?: { [key: string]: number }; "balances"?: Balance[];
"identities"?: PlayerProfileIdentity[]; "identities"?: PlayerProfileIdentity[];
"items"?: InventoryStack[];
"player"?: Player; "player"?: Player;
"wallets"?: Wallet[];
} }
export interface PlayerProfileIdentity { export interface PlayerProfileIdentity {
@@ -23,8 +33,3 @@ export interface PlayerProfileIdentity {
"subject": string; "subject": string;
} }
export interface Wallet {
"balance"?: number;
"currency"?: string;
}
+1 -1
View File
@@ -46,7 +46,7 @@ export type { StorageDomain } from './domains/StorageDomain.js';
// Generated wire types — the shapes returned by the observable state and // Generated wire types — the shapes returned by the observable state and
// handles, so consumers can name them instead of re-declaring mirrors. // handles, so consumers can name them instead of re-declaring mirrors.
export type { Player, PlayerProfile, PlayerProfileIdentity, Wallet } from './generated/player.js'; export type { Balance, InventoryStack, Player, PlayerProfile, PlayerProfileIdentity } from './generated/player.js';
export type { CatalogCurrency } from './generated/catalog.js'; export type { CatalogCurrency } from './generated/catalog.js';
export type { StorageItem, GetStorageResponse } from './generated/storage.js'; export type { StorageItem, GetStorageResponse } from './generated/storage.js';
export type { ModuleErrorResponse } from './generated/modules.js'; export type { ModuleErrorResponse } from './generated/modules.js';