@smartico/public-api 0.0.387 → 0.0.389

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.
@@ -13,7 +13,9 @@ monthly tabs.
13
13
 
14
14
  The leaderboard for each period type is configured separately by
15
15
  the operator. A label may have any combination of DAILY, WEEKLY,
16
- and MONTHLY boards configured (or none).
16
+ and MONTHLY boards configured (or none). To discover which boards
17
+ exist before loading any standings, call [getLeaderBoards](#getleaderboards)
18
+ (metadata only) and drive the tab selector from its `period_type_id`s.
17
19
 
18
20
  #### Parameters
19
21
 
@@ -29,12 +31,13 @@ The board's period type
29
31
 
30
32
  ##### getPreviousPeriod?
31
33
 
32
- `boolean`
34
+ `number` \| `boolean`
33
35
 
34
- When `true`, returns the most recent
35
- finalized snapshot for that period type
36
- (e.g. last week's results). Defaults to
37
- `false` (current in-progress period).
36
+ Which period to fetch. `false` (default)
37
+ = current in-progress period; `true` =
38
+ the most recent finalized snapshot; a
39
+ number `n` = the n-th previous snapshot
40
+ (`1` = previous, `2` = the one before, …).
38
41
 
39
42
  #### Returns
40
43
 
@@ -56,10 +59,13 @@ against that case in consumer code.
56
59
  Pass a `periodType` from [LeaderBoardPeriodType](../enumerations/LeaderBoardPeriodType.md)
57
60
  (`DAILY = 1`, `WEEKLY = 2`, `MONTHLY = 3`). Pass
58
61
  `getPreviousPeriod: true` to fetch the most-recently-ended
59
- period's snapshot (yesterday's results, last week's results,
60
- last month's results) instead of the current live period. The
61
- SDK only exposes one period back — older snapshots are not
62
- reachable through this method.
62
+ period's snapshot (yesterday's / last week's / last month's
63
+ results) instead of the current live period. To reach older
64
+ snapshots, pass a number instead of a boolean: `1` = previous
65
+ period, `2` = the one before that, and so on (`0` / `false` =
66
+ current). Use the snapshot's `create_date` / `version_id` to label
67
+ which historical run a previous-period board is. How far back
68
+ snapshots are retained is operator-configured.
63
69
 
64
70
  **Top-20 cap**
65
71
  The server hard-caps `users[]` at 20 entries, ordered by
@@ -147,3 +153,73 @@ console.log('[smartico] previous-week standings — render with greyed-out "ende
147
153
  ```
148
154
 
149
155
  ***
156
+
157
+ ### getLeaderBoards()
158
+
159
+ > **getLeaderBoards**(): `Promise`\<[`LeaderBoardDetailsT`](../interfaces/LeaderBoardDetailsT.md)[]\>
160
+
161
+ Returns the list of leaderboards the operator has configured for this
162
+ label — **metadata only, without participants**. Use it to discover
163
+ which boards exist (and their period types) so you can render the
164
+ board/tab selector, then load a specific board's standings on demand
165
+ with [getLeaderBoard](#getleaderboard).
166
+
167
+ This is the lightweight discovery counterpart to [getLeaderBoard](#getleaderboard):
168
+ it issues one round-trip that returns every board's identity, name,
169
+ description, rules, `period_type_id`, and prize table, but **not** the
170
+ ranked players. Fetching the full standings for one board (the top-20
171
+ `users[]` and the caller's own `me` entry) is what [getLeaderBoard](#getleaderboard)
172
+ does per period.
173
+
174
+ #### Returns
175
+
176
+ `Promise`\<[`LeaderBoardDetailsT`](../interfaces/LeaderBoardDetailsT.md)[]\>
177
+
178
+ Promise resolving to the array of configured boards
179
+ (metadata only). Empty array when none are configured.
180
+
181
+ #### Remarks
182
+
183
+ **Returned shape**
184
+ An array of `LeaderBoardDetailsT`, one per configured board, ordered by
185
+ `period_type_id` ascending (DAILY, WEEKLY, MONTHLY, …). On every entry
186
+ `users` is an empty array and `me` is `undefined` — this call never
187
+ carries participant data. Read `period_type_id` from each entry and pass
188
+ it to [getLeaderBoard](#getleaderboard) to load that board's standings.
189
+
190
+ **Empty result**
191
+ Resolves to an empty array when the label has no leaderboards configured
192
+ — treat that as "hide the leaderboard surface".
193
+
194
+ **Cache TTL**
195
+ Cached for 30 seconds under a dedicated key (independent of
196
+ [getLeaderBoard](#getleaderboard)'s per-period cache). Repeated calls within the
197
+ window return the cached list without a round-trip.
198
+
199
+ **Refresh model**: one-shot fetch — no subscription, no push. Board
200
+ configuration changes surface on the next cache miss.
201
+
202
+ **Idempotency / Side effects**: safe. Read-only.
203
+
204
+ **Visitor mode**: supported. Returns the brand's public board list.
205
+
206
+ #### Example
207
+
208
+ ```ts
209
+ const boards = await window._smartico.api.getLeaderBoards();
210
+
211
+ if (boards.length === 0) {
212
+ console.log('[smartico] no leaderboards configured — hide the leaderboard surface');
213
+ return;
214
+ }
215
+
216
+ // Render one tab per board from the lightweight list (no players yet).
217
+ console.log('[smartico] render leaderboard tabs:', boards.map(b => b.name));
218
+
219
+ // When the user opens a tab, load that board's standings on demand.
220
+ const first = boards[0];
221
+ const full = await window._smartico.api.getLeaderBoard(first.period_type_id);
222
+ console.log('[smartico] loaded standings for', first.name, '—', full?.users.length ?? 0, 'players');
223
+ ```
224
+
225
+ ***
@@ -46,6 +46,24 @@ Period type this board is bound to ([LeaderBoardPeriodType](../enumerations/Lead
46
46
 
47
47
  ***
48
48
 
49
+ ### version\_id
50
+
51
+ > **version\_id**: `number`
52
+
53
+ Snapshot version. `0` for the live current period; a positive value
54
+ identifies a finalized previous-period snapshot (see `getPreviousPeriod`).
55
+
56
+ ***
57
+
58
+ ### create\_date
59
+
60
+ > **create\_date**: `number`
61
+
62
+ Snapshot creation timestamp (Unix ms). `0` for the live current period;
63
+ the finalization time for a previous-period snapshot.
64
+
65
+ ***
66
+
49
67
  ### rewards
50
68
 
51
69
  > **rewards**: [`LeaderBoardsRewardsT`](LeaderBoardsRewardsT.md)[]
@@ -59,6 +77,7 @@ Per-place prize table; the array length is the number of paid places.
59
77
  > **users**: [`LeaderBoardUserT`](LeaderBoardUserT.md)[]
60
78
 
61
79
  Top-20 ranked entries (server-capped), sorted by `position` ASC.
80
+ Empty when fetched via `getLeaderBoards()` (metadata-only list).
62
81
 
63
82
  ***
64
83
 
@@ -16,7 +16,18 @@ Display username (operator-defined alias).
16
16
 
17
17
  > **avatar\_url**: `string`
18
18
 
19
- Resolved CDN URL for the participant's avatar.
19
+ Resolved CDN URL for the participant's avatar. May be empty when the
20
+ participant has no custom avatar — fall back to a level-based default
21
+ using `level_id`.
22
+
23
+ ***
24
+
25
+ ### level\_id
26
+
27
+ > **level\_id**: `number`
28
+
29
+ The participant's level id — use it to resolve a level-based default
30
+ avatar when `avatar_url` is empty.
20
31
 
21
32
  ***
22
33
 
@@ -41,4 +52,5 @@ Participant's points for this period.
41
52
 
42
53
  > **is\_me**: `boolean`
43
54
 
44
- `true` when this row is the current authenticated user.
55
+ `true` when this row is the current authenticated user. Always `true`
56
+ on the `me` entry.
@@ -1872,7 +1872,8 @@ No method-specific fields. Send only the common fields (see [Common Message Fiel
1872
1872
 
1873
1873
  ### getLeaderBoard
1874
1874
 
1875
- Get leaderboard for a specific period type.
1875
+ Get one period's leaderboard with its ranked players, or the list of all
1876
+ configured boards without players.
1876
1877
 
1877
1878
  #### Request
1878
1879
 
@@ -1880,9 +1881,14 @@ Get leaderboard for a specific period type.
1880
1881
 
1881
1882
  | Field | Type | Description |
1882
1883
  |-------|------|-------------|
1883
- | `period_type_id` | `number` | Period type: `1` = Daily, `2` = Weekly, `3` = Monthly (optional, if not set returns all boards) |
1884
+ | `period_type_id` | `number` | Period type: `1` = Daily, `2` = Weekly, `3` = Monthly. Optional — if omitted, the response `map` contains **every** configured board. |
1884
1885
  | `snapshot_offset` | `number` | `0` = current period, `1` = previous period, `2` = period before previous, etc. |
1885
- | `include_users` | `boolean` | Whether to include user details (optional) |
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.
1886
1892
 
1887
1893
  #### Response
1888
1894
 
@@ -1890,9 +1896,31 @@ Get leaderboard for a specific period type.
1890
1896
 
1891
1897
  | Field | Type | Description |
1892
1898
  |-------|------|-------------|
1893
- | `leaders` | [`LeaderBoardUserT[]`](../api/interfaces/LeaderBoardUserT.md) | Array of leaderboard entries |
1894
- | `user_position` | `number` | Current user's position |
1895
- | `user_points` | `number` | Current user's points |
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) |
1896
1924
 
1897
1925
  ---
1898
1926
 
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartico/public-api",
3
- "version": "0.0.387",
3
+ "version": "0.0.389",
4
4
  "description": "Smartico public API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -48,4 +48,5 @@ export enum LiquidEntityData {
48
48
  Tournament = 10,
49
49
  Raffles = 11,
50
50
  SingleRaffle = 12,
51
+ Clans = 13,
51
52
  }
@@ -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
- const x: LeaderBoardUserT = {
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
- is_me: user.is_me,
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
- const x: LeaderBoardDetailsT = {
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;
@@ -1144,15 +1144,18 @@ class SmarticoAPI {
1144
1144
  public async leaderboardGet(
1145
1145
  user_ext_id: string,
1146
1146
  period_type_id?: LeaderBoardPeriodType,
1147
- prevPeriod: boolean = false,
1147
+ prevPeriod: boolean | number = false,
1148
1148
  force_language?: string,
1149
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;
1150
1153
  const message = this.buildMessage<GetLeaderBoardsRequest, GetLeaderBoardsResponse>(
1151
1154
  user_ext_id,
1152
1155
  ClassId.GET_LEADERS_BOARD_REQUEST,
1153
1156
  {
1154
1157
  period_type_id,
1155
- snapshot_offset: prevPeriod ? 1 : 0,
1158
+ snapshot_offset,
1156
1159
  include_users: true,
1157
1160
  },
1158
1161
  );
@@ -1184,11 +1187,36 @@ class SmarticoAPI {
1184
1187
  public async leaderboardsGetT(
1185
1188
  user_ext_id: string,
1186
1189
  period_type_id: LeaderBoardPeriodType = LeaderBoardPeriodType.DAILY,
1187
- prevPeriod: boolean = false,
1190
+ prevPeriod: boolean | number = false,
1188
1191
  ): Promise<LeaderBoardDetailsT> {
1189
1192
  return getLeaderBoardTransform(await this.leaderboardGet(user_ext_id, period_type_id, prevPeriod));
1190
1193
  }
1191
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
+
1192
1220
  public async levelsGet(user_ext_id: string, force_language?: string): Promise<GetLevelMapResponse> {
1193
1221
  const message = this.buildMessage<any, GetLevelMapResponse>(user_ext_id, ClassId.GET_LEVEL_MAP_REQUEST);
1194
1222
  return await this.send<GetLevelMapResponse>(message, ClassId.GET_LEVEL_MAP_RESPONSE, force_language);
@@ -1572,7 +1600,6 @@ class SmarticoAPI {
1572
1600
  }
1573
1601
 
1574
1602
  public async avatarsCustomize(params: {
1575
- label_id: number;
1576
1603
  user_id: number;
1577
1604
  prompt_id: number;
1578
1605
  avatar_url: string;
@@ -1581,7 +1608,8 @@ class SmarticoAPI {
1581
1608
  return this.sendAvatarApi<AvatarCustomizeResponse>({
1582
1609
  method: 'avatar-customize',
1583
1610
  body: {
1584
- label_id: params.label_id,
1611
+ // The server resolves label_id from label_api_key.
1612
+ label_api_key: this.label_api_key,
1585
1613
  user_id: params.user_id,
1586
1614
  user_ext_id: this.tracker?.getExtUserId(),
1587
1615
  brand_key: this.brand_api_key || this.tracker?.params?.brand_key,
@@ -404,7 +404,6 @@ export class WSAPIAvatars extends WSAPIMiniGames {
404
404
  *
405
405
  * **Visitor mode**: not supported (throws).
406
406
  *
407
- * @param props.label_id Numeric Smartico label (brand) ID the user belongs to.
408
407
  * @param props.user_id Numeric Smartico user ID.
409
408
  * @param props.prompt_id Style-prompt ID (`prompt_id`) from {@link getAvatarPrompts}.
410
409
  * @param props.avatar_url CDN URL of the base avatar to customize.
@@ -414,13 +413,12 @@ export class WSAPIAvatars extends WSAPIMiniGames {
414
413
  *
415
414
  * @example
416
415
  * ```ts
417
- * // label_id / user_id identify the Smartico user being customized.
416
+ * // user_id identifies the Smartico user being customized.
418
417
  * const prompts = await window._smartico.api.getAvatarPrompts();
419
418
  * const prompt = prompts[0];
420
419
  *
421
420
  * console.log('[smartico] generating avatar — show a spinner for ~5-20s and disable the Generate button');
422
421
  * const result = await window._smartico.api.avatarsCustomize({
423
- * label_id,
424
422
  * user_id,
425
423
  * prompt_id: prompt.prompt_id,
426
424
  * avatar_url: baseAvatar.avatar_url,
@@ -440,13 +438,12 @@ export class WSAPIAvatars extends WSAPIMiniGames {
440
438
  * ```
441
439
  */
442
440
  public async avatarsCustomize(props: {
443
- label_id: number;
444
441
  user_id: number;
445
442
  prompt_id: number;
446
443
  avatar_url: string;
447
444
  avatar_real_id: number;
448
445
  }): Promise<AvatarCustomizeResponse> {
449
- const required: (keyof typeof props)[] = ['label_id', 'user_id', 'prompt_id', 'avatar_url', 'avatar_real_id'];
446
+ const required: (keyof typeof props)[] = ['user_id', 'prompt_id', 'avatar_url', 'avatar_real_id'];
450
447
  for (const field of required) {
451
448
  if (!props[field]) {
452
449
  throw new Error(`${field} is required`);
@@ -20,7 +20,9 @@ export class WSAPILeaderBoard extends WSAPIGamePick {
20
20
  *
21
21
  * The leaderboard for each period type is configured separately by
22
22
  * the operator. A label may have any combination of DAILY, WEEKLY,
23
- * and MONTHLY boards configured (or none).
23
+ * and MONTHLY boards configured (or none). To discover which boards
24
+ * exist before loading any standings, call {@link getLeaderBoards}
25
+ * (metadata only) and drive the tab selector from its `period_type_id`s.
24
26
  *
25
27
  * @remarks
26
28
  * **Return value note** — the runtime may return `undefined` if no
@@ -32,10 +34,13 @@ export class WSAPILeaderBoard extends WSAPIGamePick {
32
34
  * Pass a `periodType` from {@link LeaderBoardPeriodType}
33
35
  * (`DAILY = 1`, `WEEKLY = 2`, `MONTHLY = 3`). Pass
34
36
  * `getPreviousPeriod: true` to fetch the most-recently-ended
35
- * period's snapshot (yesterday's results, last week's results,
36
- * last month's results) instead of the current live period. The
37
- * SDK only exposes one period back — older snapshots are not
38
- * reachable through this method.
37
+ * period's snapshot (yesterday's / last week's / last month's
38
+ * results) instead of the current live period. To reach older
39
+ * snapshots, pass a number instead of a boolean: `1` = previous
40
+ * period, `2` = the one before that, and so on (`0` / `false` =
41
+ * current). Use the snapshot's `create_date` / `version_id` to label
42
+ * which historical run a previous-period board is. How far back
43
+ * snapshots are retained is operator-configured.
39
44
  *
40
45
  * **Top-20 cap**
41
46
  * The server hard-caps `users[]` at 20 entries, ordered by
@@ -89,10 +94,11 @@ export class WSAPILeaderBoard extends WSAPIGamePick {
89
94
  * Selects which pre-configured board to
90
95
  * fetch; the board itself is bound to one
91
96
  * period by the operator.
92
- * @param getPreviousPeriod When `true`, returns the most recent
93
- * finalized snapshot for that period type
94
- * (e.g. last week's results). Defaults to
95
- * `false` (current in-progress period).
97
+ * @param getPreviousPeriod Which period to fetch. `false` (default)
98
+ * = current in-progress period; `true` =
99
+ * the most recent finalized snapshot; a
100
+ * number `n` = the n-th previous snapshot
101
+ * (`1` = previous, `2` = the one before, …).
96
102
  * @returns Promise resolving to `LeaderBoardDetailsT`.
97
103
  * **At runtime, may resolve to `undefined`**
98
104
  * when no board is configured for the
@@ -135,14 +141,85 @@ export class WSAPILeaderBoard extends WSAPIGamePick {
135
141
  * // const visitorBoard = await window._smartico.vapi('EN').getLeaderBoard(LeaderBoardPeriodType.DAILY);
136
142
  * ```
137
143
  */
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}`;
144
+ public async getLeaderBoard(periodType: LeaderBoardPeriodType, getPreviousPeriod?: boolean | number): Promise<LeaderBoardDetailsT> {
145
+ // Resolve the snapshot offset: false/omitted → 0 (current), true → 1
146
+ // (previous), or an explicit number for older snapshots.
147
+ const offset = typeof getPreviousPeriod === 'number' ? getPreviousPeriod : getPreviousPeriod ? 1 : 0;
148
+ // Cache per (periodType, offset) so switching tabs or period within the
149
+ // TTL doesn't return a colliding board.
150
+ const cacheKey = `${onUpdateContextKey.LeaderBoards}:${periodType}:${offset}`;
142
151
  return OCache.use(
143
152
  cacheKey,
144
153
  ECacheContext.WSAPI,
145
- () => this.api.leaderboardsGetT(this.userExtId, periodType, getPreviousPeriod),
154
+ () => this.api.leaderboardsGetT(this.userExtId, periodType, offset),
155
+ CACHE_DATA_SEC,
156
+ );
157
+ }
158
+
159
+ /**
160
+ * Returns the list of leaderboards the operator has configured for this
161
+ * label — **metadata only, without participants**. Use it to discover
162
+ * which boards exist (and their period types) so you can render the
163
+ * board/tab selector, then load a specific board's standings on demand
164
+ * with {@link getLeaderBoard}.
165
+ *
166
+ * This is the lightweight discovery counterpart to {@link getLeaderBoard}:
167
+ * it issues one round-trip that returns every board's identity, name,
168
+ * description, rules, `period_type_id`, and prize table, but **not** the
169
+ * ranked players. Fetching the full standings for one board (the top-20
170
+ * `users[]` and the caller's own `me` entry) is what {@link getLeaderBoard}
171
+ * does per period.
172
+ *
173
+ * @remarks
174
+ * **Returned shape**
175
+ * An array of `LeaderBoardDetailsT`, one per configured board, ordered by
176
+ * `period_type_id` ascending (DAILY, WEEKLY, MONTHLY, …). On every entry
177
+ * `users` is an empty array and `me` is `undefined` — this call never
178
+ * carries participant data. Read `period_type_id` from each entry and pass
179
+ * it to {@link getLeaderBoard} to load that board's standings.
180
+ *
181
+ * **Empty result**
182
+ * Resolves to an empty array when the label has no leaderboards configured
183
+ * — treat that as "hide the leaderboard surface".
184
+ *
185
+ * **Cache TTL**
186
+ * Cached for 30 seconds under a dedicated key (independent of
187
+ * {@link getLeaderBoard}'s per-period cache). Repeated calls within the
188
+ * window return the cached list without a round-trip.
189
+ *
190
+ * **Refresh model**: one-shot fetch — no subscription, no push. Board
191
+ * configuration changes surface on the next cache miss.
192
+ *
193
+ * **Idempotency / Side effects**: safe. Read-only.
194
+ *
195
+ * **Visitor mode**: supported. Returns the brand's public board list.
196
+ *
197
+ * @returns Promise resolving to the array of configured boards
198
+ * (metadata only). Empty array when none are configured.
199
+ *
200
+ * @example
201
+ * ```ts
202
+ * const boards = await window._smartico.api.getLeaderBoards();
203
+ *
204
+ * if (boards.length === 0) {
205
+ * console.log('[smartico] no leaderboards configured — hide the leaderboard surface');
206
+ * return;
207
+ * }
208
+ *
209
+ * // Render one tab per board from the lightweight list (no players yet).
210
+ * console.log('[smartico] render leaderboard tabs:', boards.map(b => b.name));
211
+ *
212
+ * // When the user opens a tab, load that board's standings on demand.
213
+ * const first = boards[0];
214
+ * const full = await window._smartico.api.getLeaderBoard(first.period_type_id);
215
+ * console.log('[smartico] loaded standings for', first.name, '—', full?.users.length ?? 0, 'players');
216
+ * ```
217
+ */
218
+ public async getLeaderBoards(): Promise<LeaderBoardDetailsT[]> {
219
+ return OCache.use(
220
+ `${onUpdateContextKey.LeaderBoards}:list`,
221
+ ECacheContext.WSAPI,
222
+ () => this.api.leaderboardsGetListT(this.userExtId),
146
223
  CACHE_DATA_SEC,
147
224
  );
148
225
  }
@@ -1105,9 +1105,16 @@ export interface LeaderBoardDetailsT {
1105
1105
  rules: string;
1106
1106
  /** Period type this board is bound to ({@link LeaderBoardPeriodType}). */
1107
1107
  period_type_id: LeaderBoardPeriodType;
1108
+ /** Snapshot version. `0` for the live current period; a positive value
1109
+ * identifies a finalized previous-period snapshot (see `getPreviousPeriod`). */
1110
+ version_id: number;
1111
+ /** Snapshot creation timestamp (Unix ms). `0` for the live current period;
1112
+ * the finalization time for a previous-period snapshot. */
1113
+ create_date: number;
1108
1114
  /** Per-place prize table; the array length is the number of paid places. */
1109
1115
  rewards: LeaderBoardsRewardsT[];
1110
- /** Top-20 ranked entries (server-capped), sorted by `position` ASC. */
1116
+ /** Top-20 ranked entries (server-capped), sorted by `position` ASC.
1117
+ * Empty when fetched via `getLeaderBoards()` (metadata-only list). */
1111
1118
  users: LeaderBoardUserT[];
1112
1119
  /** Current user's own entry. `undefined` for visitor sessions.
1113
1120
  * For authenticated users, `position === -1` means the user is
@@ -1132,14 +1139,20 @@ export interface LeaderBoardsRewardsT {
1132
1139
  export interface LeaderBoardUserT {
1133
1140
  /** Display username (operator-defined alias). */
1134
1141
  public_username: string;
1135
- /** Resolved CDN URL for the participant's avatar. */
1142
+ /** Resolved CDN URL for the participant's avatar. May be empty when the
1143
+ * participant has no custom avatar — fall back to a level-based default
1144
+ * using `level_id`. */
1136
1145
  avatar_url: string;
1146
+ /** The participant's level id — use it to resolve a level-based default
1147
+ * avatar when `avatar_url` is empty. */
1148
+ level_id: number;
1137
1149
  /** Rank in the leaderboard (DENSE_RANK over all participants).
1138
1150
  * `-1` on the `me` entry signals "unranked / outside the window". */
1139
1151
  position: number;
1140
1152
  /** Participant's points for this period. */
1141
1153
  points: number;
1142
- /** `true` when this row is the current authenticated user. */
1154
+ /** `true` when this row is the current authenticated user. Always `true`
1155
+ * on the `me` entry. */
1143
1156
  is_me: boolean;
1144
1157
  }
1145
1158