@smartico/public-api 0.0.386 → 0.0.388
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 +2 -2
- package/dist/Avatars/AvatarCustomizeErrorCode.d.ts +7 -1
- package/dist/Avatars/AvatarCustomizeResponse.d.ts +16 -0
- package/dist/Avatars/index.d.ts +1 -0
- package/dist/CustomSections/AchCustomSection.d.ts +2 -1
- package/dist/Missions/UserAchievementTask.d.ts +4 -0
- package/dist/SmarticoAPI.d.ts +15 -2
- package/dist/WSAPI/WSAPIAvatars.d.ts +103 -0
- package/dist/WSAPI/WSAPILeaderBoard.d.ts +76 -10
- package/dist/WSAPI/WSAPITypes.d.ts +22 -5
- package/dist/index.js +441 -149
- package/dist/index.js.map +1 -1
- package/dist/index.modern.mjs +280 -29
- package/dist/index.modern.mjs.map +1 -1
- package/docs/SmarticoObject.md +15 -0
- package/docs/api/README.md +2 -0
- package/docs/api/classes/WSAPIAvatars.md +135 -0
- package/docs/api/classes/WSAPIClans.md +3 -5
- package/docs/api/classes/WSAPILeaderBoard.md +86 -10
- package/docs/api/documents/SmarticoObject.md +15 -0
- package/docs/api/enumerations/AvatarCustomizeErrorCode.md +21 -0
- package/docs/api/enumerations/JoinClanErrorCode.md +16 -17
- package/docs/api/interfaces/AvatarCustomizeResponse.md +31 -0
- package/docs/api/interfaces/LeaderBoardDetailsT.md +19 -0
- package/docs/api/interfaces/LeaderBoardUserT.md +14 -2
- package/docs/api/interfaces/TAvatarPrompt.md +2 -2
- package/docs/api/interfaces/TMissionOrBadgeTask.md +16 -0
- package/docs/api/interfaces/UserAchievementTask.md +16 -0
- package/docs/native/PROTOCOL.md +37 -7
- package/docs/ui/avatars/UIGuide_avatarsCustomize.md +69 -0
- package/docs/ui/leaderboard/UIGuide_getLeaderBoard.md +15 -0
- package/package.json +1 -1
- package/src/Avatars/AvatarCustomizeErrorCode.ts +7 -5
- package/src/Avatars/AvatarCustomizeResponse.ts +17 -0
- package/src/Avatars/index.ts +1 -0
- package/src/CustomSections/AchCustomSection.ts +1 -0
- package/src/Leaderboard/LeaderBoards.ts +9 -12
- package/src/Missions/UserAchievement.ts +2 -0
- package/src/Missions/UserAchievementTask.ts +4 -0
- package/src/SmarticoAPI.ts +84 -3
- package/src/WSAPI/WSAPIAvatars.ts +121 -0
- package/src/WSAPI/WSAPILeaderBoard.ts +91 -14
- package/src/WSAPI/WSAPITypes.ts +22 -5
- package/tsconfig.json +3 -1
package/docs/native/PROTOCOL.md
CHANGED
|
@@ -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
|
|
|
@@ -1870,7 +1872,8 @@ No method-specific fields. Send only the common fields (see [Common Message Fiel
|
|
|
1870
1872
|
|
|
1871
1873
|
### getLeaderBoard
|
|
1872
1874
|
|
|
1873
|
-
Get leaderboard
|
|
1875
|
+
Get one period's leaderboard with its ranked players, or the list of all
|
|
1876
|
+
configured boards without players.
|
|
1874
1877
|
|
|
1875
1878
|
#### Request
|
|
1876
1879
|
|
|
@@ -1878,9 +1881,14 @@ Get leaderboard for a specific period type.
|
|
|
1878
1881
|
|
|
1879
1882
|
| Field | Type | Description |
|
|
1880
1883
|
|-------|------|-------------|
|
|
1881
|
-
| `period_type_id` | `number` | Period type: `1` = Daily, `2` = Weekly, `3` = Monthly
|
|
1884
|
+
| `period_type_id` | `number` | Period type: `1` = Daily, `2` = Weekly, `3` = Monthly. Optional — if omitted, the response `map` contains **every** configured board. |
|
|
1882
1885
|
| `snapshot_offset` | `number` | `0` = current period, `1` = previous period, `2` = period before previous, etc. |
|
|
1883
|
-
| `include_users` | `boolean` |
|
|
1886
|
+
| `include_users` | `boolean` | `true` → each board includes its `positions` (and `userPosition`). `false` (or omitted) → board metadata only, no players — the lightweight "list available boards" mode. |
|
|
1887
|
+
|
|
1888
|
+
> **Two modes from one request.** Send `period_type_id` + `include_users: true`
|
|
1889
|
+
> to load a single board's standings. Omit `period_type_id` and send
|
|
1890
|
+
> `include_users: false` to enumerate all configured boards (metadata only) —
|
|
1891
|
+
> useful to build the board/tab selector before loading any standings.
|
|
1884
1892
|
|
|
1885
1893
|
#### Response
|
|
1886
1894
|
|
|
@@ -1888,9 +1896,31 @@ Get leaderboard for a specific period type.
|
|
|
1888
1896
|
|
|
1889
1897
|
| Field | Type | Description |
|
|
1890
1898
|
|-------|------|-------------|
|
|
1891
|
-
| `
|
|
1892
|
-
|
|
1893
|
-
|
|
1899
|
+
| `map` | `object` | Keyed by `period_type_id` (as a string: `"1"` / `"2"` / `"3"`), one entry per board. With `period_type_id` set in the request only that key is present; omitted → every configured board. |
|
|
1900
|
+
|
|
1901
|
+
Each value of `map` is a leaderboard object:
|
|
1902
|
+
|
|
1903
|
+
| Field | Type | Description |
|
|
1904
|
+
|-------|------|-------------|
|
|
1905
|
+
| `board_id` | `number` | Stable board id |
|
|
1906
|
+
| `period_type_id` | `number` | `1` = Daily, `2` = Weekly, `3` = Monthly |
|
|
1907
|
+
| `create_date` | `number` | `0` for the current period; the snapshot timestamp for a previous period |
|
|
1908
|
+
| `versiod_id` | `number` | Snapshot version; `0` for the current period (the field is spelled `versiod_id` on the wire) |
|
|
1909
|
+
| `reward_points` | `number[]` | Per-place prize points; index `0` = place 1 |
|
|
1910
|
+
| `board_public_meta` | `object` | `{ name, description, rules }` display strings (HTML allowed in `description` / `rules`) |
|
|
1911
|
+
| `positions` | `LeaderBoardPosition[]` | Ranked entries, top 20, ordered by rank. **Absent when `include_users` is `false`.** |
|
|
1912
|
+
| `userPosition` | `LeaderBoardPosition` | The requesting user's own entry. Absent when `include_users` is `false`, or when the user is unranked. |
|
|
1913
|
+
|
|
1914
|
+
Each `LeaderBoardPosition`:
|
|
1915
|
+
|
|
1916
|
+
| Field | Type | Description |
|
|
1917
|
+
|-------|------|-------------|
|
|
1918
|
+
| `position_in_board` | `number` | Rank (DENSE_RANK); `-1` on `userPosition` = unranked / outside the window |
|
|
1919
|
+
| `points_accumulated` | `number` | Participant's points for the period |
|
|
1920
|
+
| `public_username` | `string` | Display name (falls back to `user_alt_name`) |
|
|
1921
|
+
| `is_me` | `boolean` | `true` for the requesting user's row inside `positions` |
|
|
1922
|
+
| `avatar_id` | `string` | Raw avatar id — build the image URL client-side |
|
|
1923
|
+
| `level_id` | `number` | The participant's level (factor it into avatar resolution to match the default Smartico UI) |
|
|
1894
1924
|
|
|
1895
1925
|
---
|
|
1896
1926
|
|
|
@@ -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.
|
|
@@ -9,6 +9,15 @@
|
|
|
9
9
|
- No subscription, no push refresh. Poll manually if the consumer
|
|
10
10
|
needs live updates during an in-progress period.
|
|
11
11
|
|
|
12
|
+
## Discovering boards (`getLeaderBoards`)
|
|
13
|
+
Before loading any standings, call `getLeaderBoards()` to discover which
|
|
14
|
+
boards the operator configured (DAILY / WEEKLY / MONTHLY, or none). It
|
|
15
|
+
returns the board list as **metadata only** — `name`, `period_type_id`,
|
|
16
|
+
`rewards`, etc., with `users` empty and `me` undefined — in one lightweight
|
|
17
|
+
round-trip. Build the board/tab selector from that list, then call
|
|
18
|
+
`getLeaderBoard(period_type_id)` to load a tab's standings on demand. If it
|
|
19
|
+
returns an empty array, hide the leaderboard surface entirely.
|
|
20
|
+
|
|
12
21
|
## List view organization
|
|
13
22
|
|
|
14
23
|
The default Smartico UI renders the leaderboard with three top-level
|
|
@@ -113,6 +122,12 @@ a fresh fetch within the TTL (e.g. a manual refresh button), call
|
|
|
113
122
|
`clearCaches()` first — it clears the whole SDK cache, not just this
|
|
114
123
|
board.
|
|
115
124
|
|
|
125
|
+
To page back through **older** snapshots (not just the immediately
|
|
126
|
+
previous one), pass a number instead of a boolean: `getLeaderBoard(periodType, 2)`
|
|
127
|
+
fetches the period before last, `3` the one before that, etc. Label each
|
|
128
|
+
historical board with its `create_date` / `version_id` so the user knows
|
|
129
|
+
which run they're viewing.
|
|
130
|
+
|
|
116
131
|
## Status-specific visual treatments
|
|
117
132
|
|
|
118
133
|
- **Current period**: full-color rendering; live point counts.
|
package/package.json
CHANGED
|
@@ -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
|
+
}
|
package/src/Avatars/index.ts
CHANGED
|
@@ -13,19 +13,16 @@ const pointsRewardTransform = (reward_points: number[]): LeaderBoardsRewardsT[]
|
|
|
13
13
|
|
|
14
14
|
const getLeaderBoardPlayerTransformed = (user: LeaderBoardPosition, isMe?: boolean): LeaderBoardUserT => {
|
|
15
15
|
if (user) {
|
|
16
|
-
|
|
16
|
+
return {
|
|
17
17
|
public_username: user?.public_username || user.user_alt_name,
|
|
18
18
|
avatar_url: user.avatar_url,
|
|
19
|
+
level_id: user.level_id,
|
|
19
20
|
position: user.position_in_board,
|
|
20
21
|
points: user.points_accumulated,
|
|
21
|
-
|
|
22
|
+
// The `me` entry is the current user by definition, so force `true`
|
|
23
|
+
// there; list rows carry the server-provided flag.
|
|
24
|
+
is_me: isMe ? true : user.is_me,
|
|
22
25
|
};
|
|
23
|
-
|
|
24
|
-
if (isMe) {
|
|
25
|
-
delete x.is_me;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return x;
|
|
29
26
|
}
|
|
30
27
|
|
|
31
28
|
return null;
|
|
@@ -33,18 +30,18 @@ const getLeaderBoardPlayerTransformed = (user: LeaderBoardPosition, isMe?: boole
|
|
|
33
30
|
|
|
34
31
|
export const getLeaderBoardTransform = (board: LeaderBoardDetails): LeaderBoardDetailsT => {
|
|
35
32
|
if (board) {
|
|
36
|
-
|
|
33
|
+
return {
|
|
37
34
|
board_id: board.board_id,
|
|
38
35
|
name: board.board_public_meta.name,
|
|
39
36
|
description: board.board_public_meta.description,
|
|
40
37
|
rules: board.board_public_meta.rules,
|
|
41
38
|
period_type_id: board.period_type_id,
|
|
39
|
+
version_id: board.versiod_id,
|
|
40
|
+
create_date: board.create_date,
|
|
42
41
|
rewards: pointsRewardTransform(board.reward_points),
|
|
43
|
-
users: board.positions.map((p) => getLeaderBoardPlayerTransformed(p)),
|
|
42
|
+
users: (board.positions || []).map((p) => getLeaderBoardPlayerTransformed(p)),
|
|
44
43
|
me: getLeaderBoardPlayerTransformed(board.userPosition, true),
|
|
45
44
|
};
|
|
46
|
-
|
|
47
|
-
return x;
|
|
48
45
|
}
|
|
49
46
|
|
|
50
47
|
return null;
|
|
@@ -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
|
package/src/SmarticoAPI.ts
CHANGED
|
@@ -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);
|
|
@@ -1137,15 +1144,18 @@ class SmarticoAPI {
|
|
|
1137
1144
|
public async leaderboardGet(
|
|
1138
1145
|
user_ext_id: string,
|
|
1139
1146
|
period_type_id?: LeaderBoardPeriodType,
|
|
1140
|
-
prevPeriod: boolean = false,
|
|
1147
|
+
prevPeriod: boolean | number = false,
|
|
1141
1148
|
force_language?: string,
|
|
1142
1149
|
): Promise<LeaderBoardDetails> {
|
|
1150
|
+
// boolean keeps back-compat (false → current, true → previous);
|
|
1151
|
+
// a number selects an arbitrary snapshot offset (1 = previous, 2 = the one before, …).
|
|
1152
|
+
const snapshot_offset = typeof prevPeriod === 'number' ? prevPeriod : prevPeriod ? 1 : 0;
|
|
1143
1153
|
const message = this.buildMessage<GetLeaderBoardsRequest, GetLeaderBoardsResponse>(
|
|
1144
1154
|
user_ext_id,
|
|
1145
1155
|
ClassId.GET_LEADERS_BOARD_REQUEST,
|
|
1146
1156
|
{
|
|
1147
1157
|
period_type_id,
|
|
1148
|
-
snapshot_offset
|
|
1158
|
+
snapshot_offset,
|
|
1149
1159
|
include_users: true,
|
|
1150
1160
|
},
|
|
1151
1161
|
);
|
|
@@ -1177,11 +1187,36 @@ class SmarticoAPI {
|
|
|
1177
1187
|
public async leaderboardsGetT(
|
|
1178
1188
|
user_ext_id: string,
|
|
1179
1189
|
period_type_id: LeaderBoardPeriodType = LeaderBoardPeriodType.DAILY,
|
|
1180
|
-
prevPeriod: boolean = false,
|
|
1190
|
+
prevPeriod: boolean | number = false,
|
|
1181
1191
|
): Promise<LeaderBoardDetailsT> {
|
|
1182
1192
|
return getLeaderBoardTransform(await this.leaderboardGet(user_ext_id, period_type_id, prevPeriod));
|
|
1183
1193
|
}
|
|
1184
1194
|
|
|
1195
|
+
public async leaderboardsGetList(
|
|
1196
|
+
user_ext_id: string,
|
|
1197
|
+
force_language?: string,
|
|
1198
|
+
): Promise<LeaderBoardDetails[]> {
|
|
1199
|
+
const message = this.buildMessage<GetLeaderBoardsRequest, GetLeaderBoardsResponse>(
|
|
1200
|
+
user_ext_id,
|
|
1201
|
+
ClassId.GET_LEADERS_BOARD_REQUEST,
|
|
1202
|
+
{
|
|
1203
|
+
// No period_type_id → the server returns every configured board.
|
|
1204
|
+
// include_users: false → board metadata only (no positions / userPosition).
|
|
1205
|
+
snapshot_offset: 0,
|
|
1206
|
+
include_users: false,
|
|
1207
|
+
},
|
|
1208
|
+
);
|
|
1209
|
+
|
|
1210
|
+
const response = await this.send<GetLeaderBoardsResponse>(message, ClassId.GET_LEADERS_BOARD_RESPONSE, force_language);
|
|
1211
|
+
|
|
1212
|
+
// map is keyed by period_type_id; integer-like keys iterate in ascending order.
|
|
1213
|
+
return Object.values(response.map || {});
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
public async leaderboardsGetListT(user_ext_id: string): Promise<LeaderBoardDetailsT[]> {
|
|
1217
|
+
return (await this.leaderboardsGetList(user_ext_id)).map(getLeaderBoardTransform);
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1185
1220
|
public async levelsGet(user_ext_id: string, force_language?: string): Promise<GetLevelMapResponse> {
|
|
1186
1221
|
const message = this.buildMessage<any, GetLevelMapResponse>(user_ext_id, ClassId.GET_LEVEL_MAP_REQUEST);
|
|
1187
1222
|
return await this.send<GetLevelMapResponse>(message, ClassId.GET_LEVEL_MAP_RESPONSE, force_language);
|
|
@@ -1438,6 +1473,31 @@ class SmarticoAPI {
|
|
|
1438
1473
|
}
|
|
1439
1474
|
}
|
|
1440
1475
|
|
|
1476
|
+
private async sendAvatarApi<T>({ method, body }: { method: string, body: Record<string, any> }): Promise<T> {
|
|
1477
|
+
if (!this.tracker) {
|
|
1478
|
+
throw new Error(`Avatar APIs are not available in the visitor mode`);
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
const url = `${this.avatarOriginDomain}/${method}`;
|
|
1482
|
+
|
|
1483
|
+
try {
|
|
1484
|
+
const response = await fetch(url, {
|
|
1485
|
+
method: 'POST',
|
|
1486
|
+
headers: {
|
|
1487
|
+
'Accept': 'application/json',
|
|
1488
|
+
'Content-Type': 'application/json',
|
|
1489
|
+
},
|
|
1490
|
+
body: JSON.stringify(body),
|
|
1491
|
+
});
|
|
1492
|
+
|
|
1493
|
+
const data = await response.json();
|
|
1494
|
+
return SmarticoAPI.replaceSmrDomainsWithCloudfront<T>(data);
|
|
1495
|
+
} catch (e) {
|
|
1496
|
+
this.logger.error(`Failed to call avatar API: ${method}`, { url, error: e.message });
|
|
1497
|
+
throw new Error(`Failed to call avatar API: ${method}. ${e.message}`);
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1441
1501
|
public async gpGetActiveRounds(saw_template_id: number): Promise<GamesApiResponse<GamePickRound[]>> {
|
|
1442
1502
|
const params = {
|
|
1443
1503
|
ext_game_id: saw_template_id,
|
|
@@ -1539,6 +1599,27 @@ class SmarticoAPI {
|
|
|
1539
1599
|
return await this.send<SetAvatarResponse>(message, ClassId.CLIENT_SET_AVATAR_RESPONSE);
|
|
1540
1600
|
}
|
|
1541
1601
|
|
|
1602
|
+
public async avatarsCustomize(params: {
|
|
1603
|
+
label_id: number;
|
|
1604
|
+
user_id: number;
|
|
1605
|
+
prompt_id: number;
|
|
1606
|
+
avatar_url: string;
|
|
1607
|
+
avatar_real_id: number;
|
|
1608
|
+
}): Promise<AvatarCustomizeResponse> {
|
|
1609
|
+
return this.sendAvatarApi<AvatarCustomizeResponse>({
|
|
1610
|
+
method: 'avatar-customize',
|
|
1611
|
+
body: {
|
|
1612
|
+
label_id: params.label_id,
|
|
1613
|
+
user_id: params.user_id,
|
|
1614
|
+
user_ext_id: this.tracker?.getExtUserId(),
|
|
1615
|
+
brand_key: this.brand_api_key || this.tracker?.params?.brand_key,
|
|
1616
|
+
prompt_id: params.prompt_id,
|
|
1617
|
+
avatar_url: params.avatar_url,
|
|
1618
|
+
avatar_real_id: params.avatar_real_id,
|
|
1619
|
+
},
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1542
1623
|
public async avatarsGetListT(user_ext_id: string): Promise<TAvatarDefinition[]> {
|
|
1543
1624
|
return avatarDefinitionTransform((await this.avatarsGetList(user_ext_id)).avatars ?? [], this.avatarDomain);
|
|
1544
1625
|
}
|
|
@@ -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
|
}
|