@smartico/public-api 0.0.385 → 0.0.387

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.
Files changed (54) hide show
  1. package/README.md +1 -1
  2. package/dist/Avatars/AvatarCustomizeErrorCode.d.ts +7 -1
  3. package/dist/Avatars/AvatarCustomizeResponse.d.ts +16 -0
  4. package/dist/Avatars/index.d.ts +1 -0
  5. package/dist/Clans/JoinClanErrorCode.d.ts +21 -11
  6. package/dist/Missions/UserAchievementTask.d.ts +4 -0
  7. package/dist/SmarticoAPI.d.ts +11 -0
  8. package/dist/Tournaments/TournamentRegistrationError.d.ts +32 -2
  9. package/dist/WSAPI/WSAPIAvatars.d.ts +103 -0
  10. package/dist/WSAPI/WSAPIClans.d.ts +3 -5
  11. package/dist/WSAPI/WSAPILeaderBoard.d.ts +3 -11
  12. package/dist/WSAPI/WSAPIMissions.d.ts +6 -0
  13. package/dist/WSAPI/WSAPITypes.d.ts +6 -2
  14. package/dist/WSAPI/WSAPIUser.d.ts +27 -16
  15. package/dist/index.js +362 -127
  16. package/dist/index.js.map +1 -1
  17. package/dist/index.modern.mjs +270 -55
  18. package/dist/index.modern.mjs.map +1 -1
  19. package/docs/SmarticoObject.md +15 -0
  20. package/docs/api/README.md +2 -0
  21. package/docs/api/classes/WSAPIAvatars.md +135 -0
  22. package/docs/api/classes/WSAPIClans.md +3 -5
  23. package/docs/api/classes/WSAPILeaderBoard.md +3 -11
  24. package/docs/api/classes/WSAPIMissions.md +9 -0
  25. package/docs/api/classes/WSAPIUser.md +27 -16
  26. package/docs/api/documents/SmarticoObject.md +15 -0
  27. package/docs/api/enumerations/AvatarCustomizeErrorCode.md +21 -0
  28. package/docs/api/enumerations/JoinClanErrorCode.md +16 -17
  29. package/docs/api/enumerations/TournamentRegistrationError.md +47 -2
  30. package/docs/api/interfaces/AvatarCustomizeResponse.md +31 -0
  31. package/docs/api/interfaces/TAvatarPrompt.md +2 -2
  32. package/docs/api/interfaces/TMissionOrBadgeTask.md +16 -0
  33. package/docs/api/interfaces/UserAchievementTask.md +16 -0
  34. package/docs/native/PROTOCOL.md +3 -1
  35. package/docs/ui/avatars/UIGuide_avatarsCustomize.md +69 -0
  36. package/docs/ui/clans/UIGuide_joinClan.md +1 -2
  37. package/docs/ui/leaderboard/UIGuide_getLeaderBoard.md +15 -14
  38. package/package.json +1 -1
  39. package/src/Avatars/AvatarCustomizeErrorCode.ts +7 -5
  40. package/src/Avatars/AvatarCustomizeResponse.ts +17 -0
  41. package/src/Avatars/index.ts +1 -0
  42. package/src/Clans/JoinClanErrorCode.ts +20 -14
  43. package/src/Missions/UserAchievement.ts +2 -0
  44. package/src/Missions/UserAchievementTask.ts +4 -0
  45. package/src/SmarticoAPI.ts +53 -0
  46. package/src/Tournaments/TournamentRegistrationError.ts +32 -4
  47. package/src/WSAPI/WSAPI.ts +3 -1
  48. package/src/WSAPI/WSAPIAvatars.ts +121 -0
  49. package/src/WSAPI/WSAPIClans.ts +3 -5
  50. package/src/WSAPI/WSAPILeaderBoard.ts +7 -12
  51. package/src/WSAPI/WSAPIMissions.ts +12 -0
  52. package/src/WSAPI/WSAPITypes.ts +6 -2
  53. package/src/WSAPI/WSAPIUser.ts +27 -16
  54. package/tsconfig.json +3 -1
@@ -1802,6 +1802,8 @@ Join (or switch to) a clan. On labels with no cooldown, switching clans re-charg
1802
1802
 
1803
1803
  Avatar selection (`setAvatar`) is documented under [User](#setavatar). The methods below read the avatar catalog and the user's AI-customized avatars.
1804
1804
 
1805
+ > **Note:** Generating a new AI-customized variant (`avatarsCustomize` in the JS SDK) is a separate **HTTP POST** to the avatar server, not part of this WebSocket protocol. It returns a CDN URL that you then activate via `setAvatar`. Native clients must call that HTTP endpoint directly; it is out of scope for this spec.
1806
+
1805
1807
  ### getAvatarsList
1806
1808
 
1807
1809
  Get the avatar catalog available to the current user.
@@ -1862,7 +1864,7 @@ No method-specific fields. Send only the common fields (see [Common Message Fiel
1862
1864
  |-------|------|-------------|
1863
1865
  | `prompts` | [`AvatarPrompt[]`](../api/interfaces/AvatarPrompt.md) | Available AI style prompts |
1864
1866
 
1865
- > **Note:** Prompt name/icon are nested at `public_meta.name` / `public_meta.icon_url`. `cost_currency_type_id`: `0` = points, `1` = gems, `2` = diamonds; `cost_value` is the amount.
1867
+ > **Note:** Prompt name/icon are nested at `public_meta.name` / `public_meta.icon_url`. `cost_currency_type_id`: `0` = points, `1` = gems, `2` = diamonds, `3` = free; `cost_value` is the amount (a `cost_value` of `0` is also free).
1866
1868
 
1867
1869
  ---
1868
1870
 
@@ -0,0 +1,69 @@
1
+ # UI Guide — `avatarsCustomize`
2
+
3
+ ## Overview
4
+ - A **paid, generate-only** AI step: it applies an operator-defined style
5
+ prompt to a base avatar and returns a `cdn_url` for the generated image.
6
+ - It does **not** change the user's active avatar. Activation is a second,
7
+ explicit step — call `setAvatar({ avatar_url: cdn_url, avatar_real_id })`.
8
+ - Synchronous and slow (~5–20 s). No SDK cache, no push.
9
+
10
+ ## Flow (the default Smartico UI)
11
+ 1. **Pick a base avatar** from the avatar catalogue (`getAvatarsList()` /
12
+ `getAvatarsCustomized()`), then open the "Customize" panel.
13
+ 2. **Pick a style prompt** from `getAvatarPrompts()`. Each prompt carries its
14
+ cost (`cost_currency_type_id` + `cost_value`); render the price on the
15
+ prompt tile and pre-disable prompts the user can't afford (compare against
16
+ the balances on `getUserProfile()`).
17
+ 3. **Generate.** On tap, call `avatarsCustomize(...)`. Immediately enter a
18
+ loading state (see below) and lock the prompt list + Generate button.
19
+ 4. **Preview.** On success, render `cdn_url` as a preview. The user can keep
20
+ browsing previously generated variants (they live in the customization
21
+ history surfaced by `getAvatarsCustomized()`).
22
+ 5. **Apply (explicit).** Only when the user taps "Apply" do you call
23
+ `setAvatar(...)` with the currently-selected URL (which may be the new
24
+ `cdn_url`, a history item, or the untouched base avatar).
25
+
26
+ ## Loading state
27
+ - The generation blocks for several seconds. Show a spinner/skeleton in the
28
+ preview slot for the full duration; keep the modal open.
29
+ - Disable the Generate trigger and the prompt list while in flight — there is
30
+ no SDK-side debounce, so a second tap fires a second (charged) generation.
31
+
32
+ ## Cost gating
33
+ - Read `cost_currency_type_id` (`0` points · `1` gems · `2` diamonds · `3`
34
+ free) and `cost_value` from the chosen `getAvatarPrompts()` entry.
35
+ - `cost_value === 0` (or type `3`) is free.
36
+ - Gate before calling: if the user can't afford the prompt, disable the tile
37
+ and show the price; don't rely on the server to be the only guard
38
+ (insufficient balance comes back as the generic `-1`).
39
+
40
+ ## Error / limit handling
41
+ | Result | UI |
42
+ |---|---|
43
+ | `cdn_url` set | Success. Render the preview; enable "Apply". |
44
+ | `errCode === 12001` | User hit their **monthly per-user cap**. Show e.g. "You've used all your custom avatars this month." Nothing was charged. |
45
+ | `errCode === 12002` | The **brand's monthly pool** is exhausted. Show "Custom avatars are unavailable right now — try again later." Nothing was charged. |
46
+ | `errCode === -1` | Generic failure (insufficient balance, unavailable prompt, generation error). Show a retryable error; nothing was charged. `errMessage` is fixed text — don't surface it as the reason. |
47
+
48
+ Both monthly caps are operator-configured and reset at the start of each
49
+ calendar month.
50
+
51
+ ## Important behavioral caveats
52
+ - **Generation is irreversible and counts toward the monthly caps on success
53
+ — even if the user never applies it.** Don't auto-generate on hover/scroll;
54
+ require an explicit tap.
55
+ - **Apply uses the currently-selected image**, which may be the new variant, a
56
+ history item, or the base avatar. Track selection explicitly so "Apply"
57
+ sends the right URL.
58
+ - After a successful generation the SDK refreshes the
59
+ `getAvatarsCustomized()` cache, so the new variant appears in the history
60
+ list on the next call.
61
+
62
+ ## Empty / unavailable states
63
+ - No prompts returned by `getAvatarPrompts()` → hide the Customize entry point.
64
+ - `12002` for the whole brand → consider hiding/greying the Customize entry
65
+ point until next month rather than letting every tap fail.
66
+
67
+ ## Visitor mode
68
+ - Not supported — `avatarsCustomize` throws in visitor mode. Hide the
69
+ customization surface for unauthenticated sessions.
@@ -76,8 +76,7 @@ table of codes; UI handling per code:
76
76
  | `1003` (insufficient funds) | Show insufficient-balance UI naming the currency (`['points','gems','diamonds'][entry_fee_currency_type_id]`) and the deficit (`entry_fee_amount`). Optional: deep-link to a currency-earning surface. |
77
77
  | `1004` (segment mismatch) | Show a gating message — operator-supplied copy if the clan exposes one; otherwise a generic "Not eligible for this clan". Don't retry. |
78
78
  | `1005` | `joinClan` does NOT return `1005`. If you see it in this method's response, treat as a generic error. |
79
- | `1006` (cooldown active) | Show "Cooldown until {cooldown_until local-formatted}". Disable the Join CTA across ALL clans until cooldown expires. |
80
- | `1007` (archived) | Defined in the public enum but the server emits `1001` instead. Handle as a synonym for `1001` defensively. |
79
+ | `1006` (cooldown active) | Show "Cooldown until {cooldown_until local-formatted}". Disable the Join CTA across ALL clans until cooldown expires. Only fires when the user is already in a *different* clan (a switch); a first-time join never returns it. |
81
80
  | `1011` | `joinClan` does NOT return `1011`. If you see it in this method's response, treat as a generic error. |
82
81
  | Other | Generic error toast with `errMsg` if any. |
83
82
 
@@ -4,8 +4,8 @@
4
4
  - Returns the top-20 ranked entries for one period, the current user's
5
5
  own rank, and the configured prize table.
6
6
  - One call per (period type, current-or-previous) — the SDK caches
7
- under a single key, so switching periods or current/previous within
8
- 30 s returns stale cached data unless `clearCaches()` is invoked.
7
+ each combination independently for 30 s, so switching periods or
8
+ current/previous fetches the correct board (no cross-tab collision).
9
9
  - No subscription, no push refresh. Poll manually if the consumer
10
10
  needs live updates during an in-progress period.
11
11
 
@@ -92,15 +92,13 @@ content.
92
92
 
93
93
  When the user switches between current and previous period:
94
94
 
95
- 1. Call `clearCaches()` to bust the shared OCache key
96
- (otherwise the second call returns stale data within 30 s).
97
- 2. Call `getLeaderBoard(periodType, getPreviousPeriod)` with the new
98
- `getPreviousPeriod` value.
99
- 3. Replace the rendered data.
95
+ 1. Call `getLeaderBoard(periodType, getPreviousPeriod)` with the new
96
+ `getPreviousPeriod` value — current and previous are cached under
97
+ distinct keys, so no manual cache-busting is needed.
98
+ 2. Replace the rendered data.
100
99
 
101
100
  ```ts
102
101
  const onTogglePrevious = async (showPrevious: boolean) => {
103
- await window._smartico.api.clearCaches();
104
102
  const board = await window._smartico.api.getLeaderBoard(
105
103
  activePeriodType,
106
104
  showPrevious,
@@ -109,8 +107,11 @@ const onTogglePrevious = async (showPrevious: boolean) => {
109
107
  };
110
108
  ```
111
109
 
112
- Same pattern applies when switching between period types
113
- (`DAILY` → `WEEKLY` → `MONTHLY`).
110
+ The same holds when switching between period types
111
+ (`DAILY` → `WEEKLY` → `MONTHLY`): each is cached separately. To force
112
+ a fresh fetch within the TTL (e.g. a manual refresh button), call
113
+ `clearCaches()` first — it clears the whole SDK cache, not just this
114
+ board.
114
115
 
115
116
  ## Status-specific visual treatments
116
117
 
@@ -155,10 +156,10 @@ Same pattern applies when switching between period types
155
156
 
156
157
  ## Performance
157
158
 
158
- - The 30 s cache deduplicates rapid refetches, but the single-key
159
- design means switching periods within the window returns stale
160
- data. `clearCaches()` is the only way to force a fresh fetch
161
- before the TTL expires.
159
+ - The 30 s cache deduplicates rapid refetches and is keyed per
160
+ `(periodType, getPreviousPeriod)`, so switching tabs returns the
161
+ correct board. `clearCaches()` forces a fresh fetch before the TTL
162
+ expires (it clears the entire SDK cache).
162
163
  - For live polling during an in-progress period, ~30 s cadence is
163
164
  appropriate (matching the cache TTL — additional polls within
164
165
  the window are free).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartico/public-api",
3
- "version": "0.0.385",
3
+ "version": "0.0.387",
4
4
  "description": "Smartico public API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,9 +1,11 @@
1
+ /**
2
+ * Limit error codes returned in `errCode` by the `avatarsCustomize` method.
3
+ * See the `avatarsCustomize` TSDoc for the full table (including the generic
4
+ * `-1` failure, which has no named member here).
5
+ */
1
6
  export enum AvatarCustomizeErrorCode {
7
+ /** The user has reached the operator-configured per-user monthly cap on custom-avatar generations. */
2
8
  AVATAR_USER_LIMIT = 12001,
9
+ /** The brand's operator-configured shared monthly pool of custom-avatar generations is exhausted. */
3
10
  AVATAR_LABEL_LIMIT = 12002,
4
11
  }
5
-
6
- export const AvatarCustomizeErrorMessage: Record<AvatarCustomizeErrorCode, string> = {
7
- [AvatarCustomizeErrorCode.AVATAR_USER_LIMIT]: 'You have exceeded number of custom avatars creation, please try later',
8
- [AvatarCustomizeErrorCode.AVATAR_LABEL_LIMIT]: 'The custom avatars are not available, please try again later',
9
- };
@@ -0,0 +1,17 @@
1
+ import { AvatarCustomizeErrorCode } from './AvatarCustomizeErrorCode';
2
+
3
+ /**
4
+ * Result of `_smartico.api.avatarsCustomize()`. On success only `cdn_url` is
5
+ * set; on failure only `errCode` / `errMessage`.
6
+ */
7
+ export interface AvatarCustomizeResponse {
8
+ /** CDN URL of the generated avatar variant. Present on success. */
9
+ cdn_url?: string;
10
+ /** Error code. Present on failure. Typed values are members of
11
+ * {@link AvatarCustomizeErrorCode}; `-1` is a generic failure. See the
12
+ * `avatarsCustomize` TSDoc for the full table. */
13
+ errCode?: AvatarCustomizeErrorCode | number;
14
+ /** Optional error message. Present on failure; the generic (`-1`) message
15
+ * is fixed text, so branch on `errCode`, not this string. */
16
+ errMessage?: string;
17
+ }
@@ -1,4 +1,5 @@
1
1
  export * from './AvatarCustomizeErrorCode';
2
+ export * from './AvatarCustomizeResponse';
2
3
  export * from './AvatarDefinition';
3
4
  export * from './AvatarCustomized';
4
5
  export * from './AvatarPrompt';
@@ -1,26 +1,32 @@
1
- /*
2
- * Possible error codes returned in JOIN_CLAN_RESPONSE.errCode (cid: 575).
3
- * Clan-protocol-specific numeric codes defined in the handler spec.
1
+ /**
2
+ * Error codes returned in `errCode` by the `joinClan` method (also carried
3
+ * on `TClanJoinResult`). These are terse value definitions for lookup; the
4
+ * full per-code narrative and UI handling lives in the `joinClan` method
5
+ * TSDoc, which owns the error table.
4
6
  */
5
7
  export enum JoinClanErrorCode {
6
- /** No error, join completed */
8
+ /** Success — join completed; entry fee (if any) deducted. */
7
9
  JOIN_CLAN_OK = 0,
8
- /** Invalid parameters */
10
+ /** Request was malformed (missing or invalid parameters). */
9
11
  JOIN_CLAN_INVALID_PARAMETERS = 1000,
10
- /** clan_id doesn't exist for this label */
12
+ /** Clan doesn't exist for this label. Archived clans are also reported as `1001`. */
11
13
  JOIN_CLAN_NOT_FOUND = 1001,
12
- /** members_count >= capacity_limit */
14
+ /** Clan is full — member capacity reached. */
13
15
  JOIN_CLAN_FULL = 1002,
14
- /** Not enough Points / Gems / Diamonds for entry fee */
16
+ /** Insufficient balance for the entry fee (points / gems / diamonds). */
15
17
  JOIN_CLAN_INSUFFICIENT_FUNDS = 1003,
16
- /** User doesn't satisfy entry_conditions / entry_segment_id */
18
+ /** User doesn't meet the clan's entry segment or conditions. */
17
19
  JOIN_CLAN_SEGMENT_MISMATCH = 1004,
18
- /** User is not in a clan */
20
+ /**
21
+ * User is not in a clan. Note: a `registerInTournament` response code
22
+ * that shares this enum — never returned by `joinClan`.
23
+ */
19
24
  JOIN_CLAN_USER_IS_NOT_IN_CLAN = 1005,
20
- /** User is within the CLAN_COOLDOWN_DAYS window */
25
+ /** User is within the clan-switch cooldown window (switch only). */
21
26
  JOIN_CLAN_COOLDOWN_ACTIVE = 1006,
22
- /** clan_status_id != 1 (clan is archived) */
23
- JOIN_CLAN_ARCHIVED = 1007,
24
- /** Clan joined after tournament start */
27
+ /**
28
+ * Clan joined after tournament start. Note: a `registerInTournament`
29
+ * response code that shares this enum — never returned by `joinClan`.
30
+ */
25
31
  JOIN_CLAN_JOINED_AFTER_TOURNAMENT_START = 1011,
26
32
  }
@@ -123,6 +123,8 @@ export const UserAchievementTransform = (items: UserAchievement[]): TMissionOrBa
123
123
  id: t.task_id,
124
124
  name: t.task_public_meta?.name,
125
125
  points_reward: t.points_reward,
126
+ gems_reward: t.gems_reward,
127
+ diamonds_reward: t.diamonds_reward,
126
128
  is_completed: t.isCompleted,
127
129
  progress: t.userProgress,
128
130
  execution_count_expected: t.executionCount,
@@ -6,6 +6,10 @@ export interface UserAchievementTask {
6
6
  task_id?: number;
7
7
  task_public_meta?: AchievementTaskPublicMeta;
8
8
  points_reward?: number;
9
+ /** Gems awarded when this task is completed. */
10
+ gems_reward?: number;
11
+ /** Diamonds awarded when this task is completed. */
12
+ diamonds_reward?: number;
9
13
  task_type_id: AchievementTaskType;
10
14
  isCompleted?: boolean;
11
15
  userExecutedCount?: number; // This is the number of times the user has executed 'activity' of the task. e.g. he bet 5 times out of 100. here will be 5
@@ -144,6 +144,7 @@ import { GetAvatarsListResponse, avatarDefinitionTransform } from './Avatars/Get
144
144
  import { GetAvatarsCustomizedResponse, avatarCustomizedTransform } from './Avatars/GetAvatarsCustomizedResponse';
145
145
  import { GetAvatarPromptsResponse, avatarPromptTransform } from './Avatars/GetAvatarPromptsResponse';
146
146
  import { SetAvatarRequest, SetAvatarResponse } from './Avatars';
147
+ import { AvatarCustomizeResponse } from './Avatars/AvatarCustomizeResponse';
147
148
  import { GetJackpotWinnersResponse, GetJackpotWinnersResponseTransform, JackpotWinnerHistory } from './Jackpots/GetJackpotWinnersResponse';
148
149
  import { GetJackpotWinnersRequest } from './Jackpots/GetJackpotWinnersRequest';
149
150
  import { GetJackpotEligibleGamesRequest } from './Jackpots/GetJackpotEligibleGamesRequest';
@@ -183,6 +184,7 @@ class SmarticoAPI {
183
184
  private gamesApiUrl: string;
184
185
  private baseRgApiParams: Record<string, string>;
185
186
  public avatarDomain: string;
187
+ private avatarOriginDomain: string;
186
188
  private envId: number;
187
189
 
188
190
  private logger: ILogger;
@@ -220,6 +222,7 @@ class SmarticoAPI {
220
222
  }
221
223
 
222
224
  this.avatarDomain = SmarticoAPI.getAvatarUrl(label_api_key || options.tracker?.label_api_key);
225
+ this.avatarOriginDomain = SmarticoAPI.getAvatarOriginUrl(label_api_key || options.tracker?.label_api_key);
223
226
 
224
227
  this.label_api_key = SmarticoAPI.getCleanLabelApiKey(label_api_key);
225
228
  }
@@ -292,6 +295,10 @@ class SmarticoAPI {
292
295
  return SmarticoAPI.replaceSmrDomainsWithCloudfront(avatarUrl);
293
296
  }
294
297
 
298
+ public static getAvatarOriginUrl(label_api_key: string): string {
299
+ return AVATAR_DOMAIN.replace('{ENV_ID}', SmarticoAPI.getEnvDnsSuffix(label_api_key));
300
+ }
301
+
295
302
  private async send<T>(message: any, expectCID?: ClassId, force_language?: string): Promise<T> {
296
303
  if (this.logCIDs.includes(message.cid)) {
297
304
  this.logger.info('REQ', message);
@@ -1438,6 +1445,31 @@ class SmarticoAPI {
1438
1445
  }
1439
1446
  }
1440
1447
 
1448
+ private async sendAvatarApi<T>({ method, body }: { method: string, body: Record<string, any> }): Promise<T> {
1449
+ if (!this.tracker) {
1450
+ throw new Error(`Avatar APIs are not available in the visitor mode`);
1451
+ }
1452
+
1453
+ const url = `${this.avatarOriginDomain}/${method}`;
1454
+
1455
+ try {
1456
+ const response = await fetch(url, {
1457
+ method: 'POST',
1458
+ headers: {
1459
+ 'Accept': 'application/json',
1460
+ 'Content-Type': 'application/json',
1461
+ },
1462
+ body: JSON.stringify(body),
1463
+ });
1464
+
1465
+ const data = await response.json();
1466
+ return SmarticoAPI.replaceSmrDomainsWithCloudfront<T>(data);
1467
+ } catch (e) {
1468
+ this.logger.error(`Failed to call avatar API: ${method}`, { url, error: e.message });
1469
+ throw new Error(`Failed to call avatar API: ${method}. ${e.message}`);
1470
+ }
1471
+ }
1472
+
1441
1473
  public async gpGetActiveRounds(saw_template_id: number): Promise<GamesApiResponse<GamePickRound[]>> {
1442
1474
  const params = {
1443
1475
  ext_game_id: saw_template_id,
@@ -1539,6 +1571,27 @@ class SmarticoAPI {
1539
1571
  return await this.send<SetAvatarResponse>(message, ClassId.CLIENT_SET_AVATAR_RESPONSE);
1540
1572
  }
1541
1573
 
1574
+ public async avatarsCustomize(params: {
1575
+ label_id: number;
1576
+ user_id: number;
1577
+ prompt_id: number;
1578
+ avatar_url: string;
1579
+ avatar_real_id: number;
1580
+ }): Promise<AvatarCustomizeResponse> {
1581
+ return this.sendAvatarApi<AvatarCustomizeResponse>({
1582
+ method: 'avatar-customize',
1583
+ body: {
1584
+ label_id: params.label_id,
1585
+ user_id: params.user_id,
1586
+ user_ext_id: this.tracker?.getExtUserId(),
1587
+ brand_key: this.brand_api_key || this.tracker?.params?.brand_key,
1588
+ prompt_id: params.prompt_id,
1589
+ avatar_url: params.avatar_url,
1590
+ avatar_real_id: params.avatar_real_id,
1591
+ },
1592
+ });
1593
+ }
1594
+
1542
1595
  public async avatarsGetListT(user_ext_id: string): Promise<TAvatarDefinition[]> {
1543
1596
  return avatarDefinitionTransform((await this.avatarsGetList(user_ext_id)).avatars ?? [], this.avatarDomain);
1544
1597
  }
@@ -1,16 +1,44 @@
1
+ /**
2
+ * Error codes returned in `err_code` by the `registerInTournament` method.
3
+ *
4
+ * These are terse value definitions for lookup. The full per-code narrative —
5
+ * when each fires and how the consumer's UI should react — lives in the
6
+ * `registerInTournament` method TSDoc, which owns the error table.
7
+ *
8
+ * Note the numbering break: the gems / diamonds codes are 6-digit
9
+ * (`300010` / `300011`) while the rest of the tournament block is 5-digit;
10
+ * they were appended after the original `30001`–`30009` range was occupied.
11
+ * Branch on the exact numeric value, not on digit count.
12
+ */
1
13
  export enum TournamentRegistrationError {
14
+ /** Success — registration persisted; buy-in (if any) deducted. */
2
15
  NO_ERROR = 0,
16
+ /** Clan-based tournament and the user does not belong to a clan yet. */
3
17
  TOURNAMENT_USER_CANNOT_JOIN_WITHOUT_CLAN = 1010,
18
+ /** Instance id is invalid or the tournament was deleted. */
4
19
  TOURNAMENT_INSTANCE_NOT_FOUND = 30001,
20
+ /** Insufficient points balance for a points-priced buy-in. */
5
21
  TOURNAMENT_REGISTRATION_NOT_ENOUGH_POINTS = 30002,
22
+ /** Tournament is no longer in a registerable state (finished, cancelled, finalizing, or not yet open). */
6
23
  TOURNAMENT_INSTANCE_NOT_IN_STATE = 30003,
24
+ /** User is already registered; treat as an idempotent success. */
7
25
  TOURNAMENT_ALREADY_REGISTERED = 30004,
26
+ /** User does not satisfy the tournament's segment / entry conditions. */
8
27
  TOURNAMENT_USER_DONT_MATCH_CONDITIONS = 30005,
28
+ /** User is not registered; anomalous on a register call (usually a server-side race). */
9
29
  TOURNAMENT_USER_NOT_REGISTERED = 30006,
30
+ /** Registration status transition is not allowed (e.g. re-registering a finished tournament). */
10
31
  TOURNAMENT_CANT_CHANGE_REGISTRATION_STATUS = 30007,
32
+ /** Tournament filled up — maximum registrations reached. */
11
33
  TOURNAMENT_MAX_REGISTRATIONS_REACHED = 30008,
12
- TOURNAMENT_REGISTRATION_NOT_ENOUGH_GEMS = 30010,
13
- TOURNAMENT_REGISTRATION_NOT_ENOUGH_DIAMONDS = 30011,
14
-
15
-
34
+ /**
35
+ * The user's wallet currency could not be resolved. Emitted server-side
36
+ * during tournament score processing, NOT during registration — it is
37
+ * never returned by `registerInTournament`. Included for completeness.
38
+ */
39
+ TOURNAMENT_INVALID_USER_CURRENCY = 30009,
40
+ /** Insufficient gems balance for a gems-priced buy-in. */
41
+ TOURNAMENT_REGISTRATION_NOT_ENOUGH_GEMS = 300010,
42
+ /** Insufficient diamonds balance for a diamonds-priced buy-in. */
43
+ TOURNAMENT_REGISTRATION_NOT_ENOUGH_DIAMONDS = 300011,
16
44
  }
@@ -5,6 +5,7 @@ import { SmarticoAPI } from '../SmarticoAPI';
5
5
  import { JackpotWinPush, JackpotsOptinResponse, JackpotsOptoutRequest } from '../Jackpots';
6
6
  import { onUpdateContextKey } from './WSAPIBase';
7
7
  import { WSAPIInbox } from './WSAPIInbox';
8
+ import { TUserProfile } from './WSAPITypes';
8
9
 
9
10
  /**
10
11
  * Public surface of the Smartico WebSocket API. The consumer reaches every
@@ -64,13 +65,14 @@ export class WSAPI extends WSAPIInbox {
64
65
  this.updateInboxUnreadCount(res.unread_count);
65
66
  }
66
67
  });
67
- on(ClassId.CLIENT_PUBLIC_PROPERTIES_CHANGED_EVENT, (data: { props: { core_inbox_unread_count?: number; ach_points_balance?: number; ach_gems_balance?: number; ach_diamonds_balance?: number } }) => {
68
+ on(ClassId.CLIENT_PUBLIC_PROPERTIES_CHANGED_EVENT, (data: { props: TUserProfile }) => {
68
69
  if (data?.props?.core_inbox_unread_count !== undefined && data?.props?.core_inbox_unread_count !== null) {
69
70
  this.updateInboxUnreadCount(data.props.core_inbox_unread_count);
70
71
  }
71
72
  if (data?.props?.ach_points_balance !== undefined || data?.props?.ach_gems_balance !== undefined || data?.props?.ach_diamonds_balance !== undefined) {
72
73
  this.notifyActivityLogUpdate();
73
74
  }
75
+ OCache.clear(ECacheContext.WSAPI, onUpdateContextKey.CurrentLevel);
74
76
  });
75
77
  on(ClassId.RAF_OPTIN_RESPONSE, () => this.updateRaffles());
76
78
  }
@@ -10,6 +10,7 @@ import {
10
10
  onUpdateContextKey,
11
11
  } from './WSAPIBase';
12
12
  import { WSAPIMiniGames } from './WSAPIMiniGames';
13
+ import { AvatarCustomizeResponse } from '../Avatars/AvatarCustomizeResponse';
13
14
 
14
15
  /** @group Avatars */
15
16
  export class WSAPIAvatars extends WSAPIMiniGames {
@@ -342,4 +343,124 @@ export class WSAPIAvatars extends WSAPIMiniGames {
342
343
  err_message: r.errMsg,
343
344
  };
344
345
  }
346
+
347
+ /**
348
+ * Generates an AI-customized variant of a base avatar by applying an
349
+ * operator-defined style prompt, returning the CDN URL of the result.
350
+ * This is a paid, generate-only step: it does NOT change the user's
351
+ * active avatar — pass the returned `cdn_url` to {@link setAvatar} to
352
+ * activate it.
353
+ *
354
+ * @remarks
355
+ * **Preconditions**
356
+ * - Authenticated session required; not available in visitor mode (throws).
357
+ * - All five fields are mandatory — the SDK throws synchronously
358
+ * (`Error("<field> is required")`) if any is missing or zero.
359
+ * - `prompt_id` must be one of the prompts from {@link getAvatarPrompts};
360
+ * `avatar_url` / `avatar_real_id` identify the base avatar to style.
361
+ *
362
+ * **Cost**
363
+ * Each generation costs the currency the operator configured on the
364
+ * chosen prompt (points, gems, or diamonds — some prompts are free; read
365
+ * `cost_currency_type_id` / `cost_value` from the {@link getAvatarPrompts}
366
+ * entry to gate the UI before calling). The cost is charged only on
367
+ * success; if the user can't afford it the call fails with `errCode -1`
368
+ * and nothing is generated or charged.
369
+ *
370
+ * **Latency**
371
+ * Generation runs server-side AI image synthesis and is synchronous from
372
+ * the caller's side — expect several seconds (roughly 5–20 s) before the
373
+ * promise resolves. There is no SDK cache and no push/subscription; show
374
+ * a loading state for the full duration and disable the trigger to
375
+ * prevent duplicate submissions.
376
+ *
377
+ * **Result & error codes**
378
+ * On success the result has `cdn_url` set; on failure it has `errCode` /
379
+ * `errMessage` set and no `cdn_url`. Branch on `errCode` — the generic
380
+ * message is fixed text, not cause-specific:
381
+ * - `12001` (`AvatarCustomizeErrorCode.AVATAR_USER_LIMIT`) — the user hit
382
+ * their monthly per-user generation cap. Nothing is generated or charged.
383
+ * - `12002` (`AvatarCustomizeErrorCode.AVATAR_LABEL_LIMIT`) — the brand's
384
+ * shared monthly pool is exhausted. Nothing is generated or charged;
385
+ * surface a "try again later" message.
386
+ * - `-1` — generic failure: insufficient balance, an unavailable prompt,
387
+ * or a generation error. Nothing is charged. Allow the user to retry.
388
+ *
389
+ * Both monthly caps are operator-configured and reset at the start of
390
+ * each calendar month.
391
+ *
392
+ * **Side effects (on success)**
393
+ * The generated variant is persisted to the user's customization history
394
+ * and counts toward the monthly caps **whether or not** it is later
395
+ * activated — generation is not reversible. The SDK clears the
396
+ * {@link getAvatarsCustomized} cache so the next call surfaces the new
397
+ * variant. The user's active avatar is unchanged until you call
398
+ * {@link setAvatar} with the returned `cdn_url`.
399
+ *
400
+ * **Idempotency**: not idempotent — each successful call generates (and
401
+ * charges for) a new variant. Guard the call site against double-clicks.
402
+ *
403
+ * **UI guidance**: see [UI Guide — `avatarsCustomize`](../../docs/ui/avatars/UIGuide_avatarsCustomize.md).
404
+ *
405
+ * **Visitor mode**: not supported (throws).
406
+ *
407
+ * @param props.label_id Numeric Smartico label (brand) ID the user belongs to.
408
+ * @param props.user_id Numeric Smartico user ID.
409
+ * @param props.prompt_id Style-prompt ID (`prompt_id`) from {@link getAvatarPrompts}.
410
+ * @param props.avatar_url CDN URL of the base avatar to customize.
411
+ * @param props.avatar_real_id `avatar_real_id` of the base avatar.
412
+ * @returns {@link AvatarCustomizeResponse} — `cdn_url` set on success;
413
+ * `errCode` / `errMessage` set on failure.
414
+ *
415
+ * @example
416
+ * ```ts
417
+ * // label_id / user_id identify the Smartico user being customized.
418
+ * const prompts = await window._smartico.api.getAvatarPrompts();
419
+ * const prompt = prompts[0];
420
+ *
421
+ * console.log('[smartico] generating avatar — show a spinner for ~5-20s and disable the Generate button');
422
+ * const result = await window._smartico.api.avatarsCustomize({
423
+ * label_id,
424
+ * user_id,
425
+ * prompt_id: prompt.prompt_id,
426
+ * avatar_url: baseAvatar.avatar_url,
427
+ * avatar_real_id: baseAvatar.avatar_real_id,
428
+ * });
429
+ *
430
+ * if (result.cdn_url) {
431
+ * console.log('[smartico] generation succeeded — preview this URL, then call setAvatar to apply it:', result.cdn_url);
432
+ * // await window._smartico.api.setAvatar({ avatar_url: result.cdn_url, avatar_real_id: baseAvatar.avatar_real_id });
433
+ * } else if (result.errCode === 12001) {
434
+ * console.error('[smartico] user monthly limit reached — show "you have used all your custom avatars this month"');
435
+ * } else if (result.errCode === 12002) {
436
+ * console.error('[smartico] brand monthly pool exhausted — show "custom avatars are unavailable right now, try later"');
437
+ * } else {
438
+ * console.error('[smartico] generation failed (e.g. not enough balance) — let the user retry:', result.errMessage);
439
+ * }
440
+ * ```
441
+ */
442
+ public async avatarsCustomize(props: {
443
+ label_id: number;
444
+ user_id: number;
445
+ prompt_id: number;
446
+ avatar_url: string;
447
+ avatar_real_id: number;
448
+ }): Promise<AvatarCustomizeResponse> {
449
+ const required: (keyof typeof props)[] = ['label_id', 'user_id', 'prompt_id', 'avatar_url', 'avatar_real_id'];
450
+ for (const field of required) {
451
+ if (!props[field]) {
452
+ throw new Error(`${field} is required`);
453
+ }
454
+ }
455
+
456
+ const r = await this.api.avatarsCustomize(props);
457
+
458
+ // On success a new variant is persisted server-side — drop the cached
459
+ // list so the next getAvatarsCustomized() surfaces it.
460
+ if (r.cdn_url) {
461
+ OCache.clear(ECacheContext.WSAPI, onUpdateContextKey.AvatarsCustomized);
462
+ }
463
+
464
+ return r;
465
+ }
345
466
  }
@@ -303,12 +303,10 @@ export class WSAPIClans extends WSAPITournaments {
303
303
  * tournament requires the user to be in a clan first.
304
304
  * - `1006` (`JOIN_CLAN_COOLDOWN_ACTIVE`) — user is inside the
305
305
  * switch-cooldown window from a previous clan join (default
306
- * 7 days, configurable per label). Surface the
306
+ * 7 days, configurable per label). Only reachable when the user is
307
+ * already in a *different* clan (this call is then a clan switch);
308
+ * a first-time join never returns `1006`. Surface the
307
309
  * `TClans.cooldown_until` expiry in the disabled CTA.
308
- * - `1007` (`JOIN_CLAN_ARCHIVED`) — defined on the public enum but
309
- * the server does NOT emit this code in practice (archived clans
310
- * are reported as `1001`). Handle it as a synonym for `1001`
311
- * defensively, but do not rely on it.
312
310
  * - `1011` (`JOIN_CLAN_JOINED_AFTER_TOURNAMENT_START`) — this
313
311
  * method does NOT return `1011` directly. The code fires on
314
312
  * {@link registerInTournament} responses when the user joined
@@ -56,15 +56,8 @@ export class WSAPILeaderBoard extends WSAPIGamePick {
56
56
  * the array is the prize for place 1, the second for place 2, and
57
57
  * so on. The array length is the number of paid places.
58
58
  *
59
- * **Cache TTL — single shared key (known limitation)**
60
- * The SDK caches the response for 30 seconds under a SINGLE shared
61
- * key — calling `getLeaderBoard(DAILY)` followed by
62
- * `getLeaderBoard(WEEKLY)` within the same 30 s window returns the
63
- * cached DAILY result for the WEEKLY call. Same applies to
64
- * switching `getPreviousPeriod`. To force a fresh fetch when
65
- * switching periods or current/previous, call
66
- * `_smartico.api.clearCaches()` before the second call, or simply
67
- * wait out the TTL.
59
+ * **Cache TTL**
60
+ * The SDK caches each response for 30 seconds.
68
61
  *
69
62
  * **Period boundaries**
70
63
  * The server finalizes each period on a server-configurable
@@ -134,8 +127,7 @@ export class WSAPILeaderBoard extends WSAPIGamePick {
134
127
  * }
135
128
  * }
136
129
  *
137
- * // Switch to previous period view — but bust the cache first to avoid the shared-key collision.
138
- * await window._smartico.api.clearCaches();
130
+ * // Switch to previous period view — cached separately from the current period.
139
131
  * const prev = await window._smartico.api.getLeaderBoard(LeaderBoardPeriodType.WEEKLY, true);
140
132
  * console.log('[smartico] previous-week standings — render with greyed-out "ended" treatment:', prev?.users.length, 'finalists');
141
133
  *
@@ -144,8 +136,11 @@ export class WSAPILeaderBoard extends WSAPIGamePick {
144
136
  * ```
145
137
  */
146
138
  public async getLeaderBoard(periodType: LeaderBoardPeriodType, getPreviousPeriod?: boolean): Promise<LeaderBoardDetailsT> {
139
+ // Cache per (periodType, current/previous) so switching tabs or
140
+ // current↔previous within the TTL doesn't return a colliding board.
141
+ const cacheKey = `${onUpdateContextKey.LeaderBoards}:${periodType}:${getPreviousPeriod ? 1 : 0}`;
147
142
  return OCache.use(
148
- onUpdateContextKey.LeaderBoards,
143
+ cacheKey,
149
144
  ECacheContext.WSAPI,
150
145
  () => this.api.leaderboardsGetT(this.userExtId, periodType, getPreviousPeriod),
151
146
  CACHE_DATA_SEC,