@smartico/public-api 0.0.379 → 0.0.380

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 (99) hide show
  1. package/.nvmrc +1 -1
  2. package/README.md +31 -2
  3. package/dist/Store/BuyStoreItemErrorCode.d.ts +9 -1
  4. package/dist/Tournaments/TournamentRegistrationError.d.ts +3 -1
  5. package/dist/WSAPI/WSAPI.d.ts +13 -1235
  6. package/dist/WSAPI/WSAPIAvatars.d.ts +78 -0
  7. package/dist/WSAPI/WSAPIBase.d.ts +71 -0
  8. package/dist/WSAPI/WSAPIBonuses.d.ts +26 -0
  9. package/dist/WSAPI/WSAPIClans.d.ts +388 -0
  10. package/dist/WSAPI/WSAPIGamePick.d.ts +308 -0
  11. package/dist/WSAPI/WSAPIGeneral.d.ts +30 -0
  12. package/dist/WSAPI/WSAPIInbox.d.ts +126 -0
  13. package/dist/WSAPI/WSAPIJackpots.d.ts +109 -0
  14. package/dist/WSAPI/WSAPILeaderBoard.d.ts +25 -0
  15. package/dist/WSAPI/WSAPIMiniGames.d.ts +94 -0
  16. package/dist/WSAPI/WSAPIMissions.d.ts +517 -0
  17. package/dist/WSAPI/WSAPIRaffles.d.ts +119 -0
  18. package/dist/WSAPI/WSAPIStore.d.ts +656 -0
  19. package/dist/WSAPI/WSAPITournaments.d.ts +501 -0
  20. package/dist/WSAPI/WSAPITypes.d.ts +145 -85
  21. package/dist/WSAPI/WSAPIUser.d.ts +383 -0
  22. package/dist/index.js +3818 -1528
  23. package/dist/index.js.map +1 -1
  24. package/dist/index.modern.mjs +3180 -981
  25. package/dist/index.modern.mjs.map +1 -1
  26. package/docs/api/README.md +56 -2
  27. package/docs/api/classes/WSAPIAvatars.md +116 -0
  28. package/docs/api/classes/WSAPIBonuses.md +50 -0
  29. package/docs/api/classes/WSAPIClans.md +66 -0
  30. package/docs/api/classes/WSAPIGamePick.md +181 -0
  31. package/docs/api/classes/WSAPIGeneral.md +63 -0
  32. package/docs/api/classes/WSAPIInbox.md +277 -0
  33. package/docs/api/classes/WSAPIJackpots.md +185 -0
  34. package/docs/api/classes/WSAPILeaderBoard.md +39 -0
  35. package/docs/api/classes/WSAPIMiniGames.md +175 -0
  36. package/docs/api/classes/WSAPIMissions.md +611 -0
  37. package/docs/api/classes/WSAPIRaffles.md +195 -0
  38. package/docs/api/classes/WSAPIStore.md +744 -0
  39. package/docs/api/classes/WSAPITournaments.md +104 -0
  40. package/docs/api/classes/WSAPIUser.md +200 -0
  41. package/docs/api/enumerations/AchievementClaimPeriodTypeId.md +13 -0
  42. package/docs/api/enumerations/BuyStoreItemErrorCode.md +32 -0
  43. package/docs/api/interfaces/ClanPrizePublicMeta.md +41 -0
  44. package/docs/api/interfaces/ClanPrizeStructureEntry.md +7 -1
  45. package/docs/api/interfaces/ClanPublicMeta.md +4 -0
  46. package/docs/api/interfaces/SAWTemplateUI.md +10 -0
  47. package/docs/api/interfaces/TAchCategory.md +19 -4
  48. package/docs/api/interfaces/TBuyStoreItemResult.md +26 -8
  49. package/docs/api/interfaces/TMissionClaimRewardResult.md +14 -3
  50. package/docs/api/interfaces/TMissionOptInResult.md +10 -3
  51. package/docs/api/interfaces/TMissionOrBadge.md +6 -2
  52. package/docs/api/interfaces/TStoreItem.md +21 -1
  53. package/docs/api/interfaces/TTournamentDetailed.md +8 -0
  54. package/docs/api/interfaces/UserAchievement.md +1 -1
  55. package/docs/ui/clans/UIGuide_getClanInfo.md +153 -0
  56. package/docs/ui/clans/UIGuide_getClans.md +172 -0
  57. package/docs/ui/clans/UIGuide_joinClan.md +196 -0
  58. package/docs/ui/missions/UIGuide_getAchCategories.md +17 -0
  59. package/docs/ui/missions/UIGuide_getBadges.md +237 -0
  60. package/docs/ui/missions/UIGuide_getMissions.md +268 -0
  61. package/docs/ui/missions/UIGuide_requestMissionClaimReward.md +21 -0
  62. package/docs/ui/missions/UIGuide_requestMissionOptIn.md +16 -0
  63. package/docs/ui/store/UIGuide_buyStoreItem.md +133 -0
  64. package/docs/ui/store/UIGuide_getStoreCategories.md +19 -0
  65. package/docs/ui/store/UIGuide_getStoreItems.md +264 -0
  66. package/docs/ui/store/UIGuide_getStorePurchasedItems.md +122 -0
  67. package/docs/ui/tournaments/UIGuide_getClanTournamentPlayers.md +140 -0
  68. package/docs/ui/tournaments/UIGuide_getTournamentInstanceInfo.md +265 -0
  69. package/docs/ui/tournaments/UIGuide_getTournamentsList.md +236 -0
  70. package/docs/ui/tournaments/UIGuide_registerInTournament.md +156 -0
  71. package/docs/ui/user/UIGuide_getLevels.md +162 -0
  72. package/docs/ui/user/UIGuide_getUserProfile.md +213 -0
  73. package/package.json +3 -3
  74. package/scripts/postdoc-cleanup.js +195 -0
  75. package/src/Missions/MissionsUtils.ts +5 -2
  76. package/src/Store/BuyStoreItemErrorCode.ts +8 -0
  77. package/src/Store/StoreItem.ts +1 -0
  78. package/src/Store/StoreItemPurchased.ts +1 -0
  79. package/src/Store/StoreItemType.ts +1 -1
  80. package/src/Tournaments/TournamentRegistrationError.ts +4 -0
  81. package/src/WSAPI/WSAPI.ts +27 -2007
  82. package/src/WSAPI/WSAPIAvatars.ts +127 -0
  83. package/src/WSAPI/WSAPIBase.ts +93 -0
  84. package/src/WSAPI/WSAPIBonuses.ts +54 -0
  85. package/src/WSAPI/WSAPIClans.ts +417 -0
  86. package/src/WSAPI/WSAPIGamePick.ts +382 -0
  87. package/src/WSAPI/WSAPIGeneral.ts +57 -0
  88. package/src/WSAPI/WSAPIInbox.ts +222 -0
  89. package/src/WSAPI/WSAPIJackpots.ts +212 -0
  90. package/src/WSAPI/WSAPILeaderBoard.ts +40 -0
  91. package/src/WSAPI/WSAPIMiniGames.ts +181 -0
  92. package/src/WSAPI/WSAPIMissions.ts +572 -0
  93. package/src/WSAPI/WSAPIRaffles.ts +175 -0
  94. package/src/WSAPI/WSAPIStore.ts +732 -0
  95. package/src/WSAPI/WSAPITournaments.ts +541 -0
  96. package/src/WSAPI/WSAPITypes.ts +145 -87
  97. package/src/WSAPI/WSAPIUser.ts +456 -0
  98. package/tsconfig.json +17 -1
  99. package/docs/api/classes/WSAPI.md +0 -1927
@@ -0,0 +1,541 @@
1
+ import { ECacheContext, OCache } from '../OCache';
2
+ import {
3
+ TTournament,
4
+ TTournamentDetailed,
5
+ TTournamentRegistrationResult,
6
+ TClanTournamentPlayers,
7
+ } from './WSAPITypes';
8
+ import {
9
+ CACHE_DATA_SEC,
10
+ onUpdateContextKey,
11
+ } from './WSAPIBase';
12
+ import { WSAPIStore } from './WSAPIStore';
13
+
14
+ /** @group Tournaments */
15
+ export class WSAPITournaments extends WSAPIStore {
16
+ /**
17
+ * Returns the list of tournaments currently visible to the user, scoped
18
+ * server-side to what the user's segments qualify them to see. Optionally
19
+ * subscribes to live updates via `onUpdate`: the callback is invoked with
20
+ * the full refreshed list whenever the user registers in a tournament.
21
+ *
22
+ * The returned list is the canonical source of truth for tournament
23
+ * lobby state (`is_user_registered`, `registration_status`,
24
+ * `is_in_progress`, `is_can_register`, etc.). Consumers should NOT
25
+ * mutate it optimistically — always wait for the `onUpdate` callback
26
+ * (or a fresh fetch after the 30 s cache window) to observe new state.
27
+ *
28
+ * @remarks
29
+ * **Subscription model (`onUpdate`)**
30
+ * The callback receives the FULL refreshed list (never a diff/patch).
31
+ * Each subsequent call to `getTournamentsList({ onUpdate })` REPLACES
32
+ * the prior callback — only one active subscriber at a time. Pass
33
+ * `onUpdate: undefined` (or omit it) to keep the prior callback in
34
+ * place; the callback is never auto-cleared.
35
+ *
36
+ * **Update triggers** — the callback fires when:
37
+ *
38
+ * 1. `registerInTournament(...)` resolves on this connection (any
39
+ * `err_code`). The refreshed list reflects the new
40
+ * `is_user_registered` / `registration_status` /
41
+ * `registration_count` on the affected tournament.
42
+ *
43
+ * Does NOT fire for: tournament lifecycle transitions (start, finish,
44
+ * cancellation), score updates, other users' registrations, or
45
+ * operator-side BO edits. Those changes surface only on the next
46
+ * cache miss (after the 30 s TTL) — poll manually if your UI needs
47
+ * sub-30s lifecycle freshness.
48
+ *
49
+ * **Server-side filtering** (what's excluded before the SDK sees it)
50
+ * The server filters by visibility segment and entry segment, removes
51
+ * tournaments past their post-finish display window, and excludes
52
+ * tournaments not yet published. SDK consumers receive only items
53
+ * the user is eligible to see; no client-side gating is required.
54
+ *
55
+ * **Reading state from the returned item**
56
+ * Drive list bucketing and CTA labels from the SDK-computed booleans
57
+ * (`is_active`, `is_can_register`, `is_cancelled`, `is_finished`,
58
+ * `is_in_progress`, `is_upcoming`), NOT from raw `start_time` /
59
+ * `end_time` comparisons — the booleans encode the canonical
60
+ * lifecycle states and already reflect server-side rules around
61
+ * qualifying scores and late registration. `is_user_registered` is
62
+ * independent of `is_can_register` — a registered user has
63
+ * `is_user_registered: true` and `is_can_register: false`. The `me`
64
+ * block (if present) carries the user's own leaderboard position and
65
+ * score; it's undefined for unregistered users, users without any
66
+ * recorded score, and visitor-mode sessions. The `prize_pool_short`
67
+ * field carries the operator-supplied summary string ("$10,000",
68
+ * "Mixed prizes") for compact card rendering; the full per-place
69
+ * `prizes[]` breakdown is also populated here but is typically
70
+ * rendered only in the detail view via
71
+ * {@link getTournamentInstanceInfo}.
72
+ *
73
+ * **Cross-references**
74
+ * Tournaments with `is_clan_based: true` group participants by clan —
75
+ * see {@link getClanTournamentPlayers} for fetching the players list
76
+ * of a specific clan within a clan tournament. The detailed lobby
77
+ * view (with full player leaderboard and prize structure) comes from
78
+ * {@link getTournamentInstanceInfo}. Tournaments with
79
+ * `custom_section_id` belong to an operator-defined custom section
80
+ * (resolve metadata via `getCustomSections()`).
81
+ *
82
+ * **Cache TTL**: the SDK caches the response for 30 seconds. Cache is
83
+ * fully cleared on login / logout.
84
+ *
85
+ * **Idempotency**: safe. Read-only. Repeated calls within the cache
86
+ * window return a deep-cloned cached array without a network
87
+ * round-trip.
88
+ *
89
+ * **Side effects**: none — pure metadata read.
90
+ *
91
+ * **UI guidance**: see [UI Guide — `getTournamentsList`](../../docs/ui/tournaments/UIGuide_getTournamentsList.md).
92
+ *
93
+ * **Visitor mode**: supported. The same shape is returned, scoped to
94
+ * the brand's public tournament list. Per-user fields (`me`,
95
+ * `is_user_registered`, `registration_status`) are not meaningful.
96
+ * The `onUpdate` callback is accepted but never fires (registration
97
+ * mutations are not supported in visitor mode).
98
+ *
99
+ * @param params Optional. Omit to fetch without subscribing.
100
+ * @param params.onUpdate Callback invoked with the full refreshed
101
+ * tournament list after every
102
+ * {@link registerInTournament} round-trip on
103
+ * this connection. Each call overwrites the
104
+ * prior callback. Never fires in visitor mode.
105
+ * @returns Promise resolving to the array of
106
+ * tournaments visible to the user. Empty
107
+ * array if no tournaments are visible.
108
+ *
109
+ * @example
110
+ * ```ts
111
+ * const tournaments = await window._smartico.api.getTournamentsList({
112
+ * onUpdate: (refreshed) => {
113
+ * console.log('[smartico] tournament list refreshed — re-render the lobby UI from this array, do not merge with prior state:', refreshed);
114
+ * const justRegistered = refreshed.filter(t => t.is_user_registered);
115
+ * console.log('[smartico] user is now registered in these tournaments:', justRegistered.map(t => t.name));
116
+ * },
117
+ * });
118
+ *
119
+ * // Bucket items by lifecycle state for a tabbed UI.
120
+ * const live = tournaments.filter(t => t.is_in_progress);
121
+ * const upcoming = tournaments
122
+ * .filter(t => t.is_upcoming && !t.is_user_registered)
123
+ * .sort((a, b) => a.start_time - b.start_time);
124
+ * const mine = tournaments.filter(t => t.is_user_registered);
125
+ * const finished = tournaments.filter(t => t.is_finished);
126
+ *
127
+ * console.log('[smartico] render lobby tabs with these counts: live=', live.length,
128
+ * 'upcoming=', upcoming.length, 'mine=', mine.length, 'finished=', finished.length);
129
+ *
130
+ * // Featured tournament: pin to position 0 of the Overview/Top tabs.
131
+ * const featured = tournaments.find(t => t.is_featured && !t.is_cancelled);
132
+ * if (featured) {
133
+ * console.log('[smartico] featured tournament — pin to position 0 of the Overview tab:', featured.name);
134
+ * }
135
+ *
136
+ * // Visitor mode: onUpdate accepted but never fires; re-poll if needed.
137
+ * // const visitorList = await window._smartico.vapi('EN').getTournamentsList();
138
+ * ```
139
+ */
140
+ public async getTournamentsList({ onUpdate }: { onUpdate?: (data: TTournament[]) => void } = {}): Promise<TTournament[]> {
141
+ if (onUpdate) {
142
+ this.onUpdateCallback.set(onUpdateContextKey.TournamentList, onUpdate);
143
+ }
144
+
145
+ return OCache.use(
146
+ onUpdateContextKey.TournamentList,
147
+ ECacheContext.WSAPI,
148
+ () => this.api.tournamentsGetLobbyT(this.userExtId),
149
+ CACHE_DATA_SEC,
150
+ );
151
+ }
152
+
153
+ /**
154
+ * Returns the full detail of a single tournament instance — adds the
155
+ * player leaderboard, the full prize structure, and (for clan
156
+ * tournaments) the clan leaderboard and per-clan prize structure on
157
+ * top of everything already in `TTournament`. Use this to power a
158
+ * tournament detail / lobby screen after the user picks an item from
159
+ * {@link getTournamentsList}.
160
+ *
161
+ * Live leaderboard updates require manual re-fetching — there is no
162
+ * subscription model, no push event, and no client-side cache (each
163
+ * call hits the server). The default Smartico UI polls every 3 seconds
164
+ * while the detail view is open.
165
+ *
166
+ * @remarks
167
+ * **Preconditions**
168
+ * Pass a valid `tournamentInstanceId` read from `TTournament.instance_id`
169
+ * on an item returned by {@link getTournamentsList}. The method works
170
+ * standalone — calling `getTournamentsList()` first is not required by
171
+ * the SDK, but is the only stable source of valid IDs.
172
+ *
173
+ * **Refresh model**
174
+ * - **No subscription.** This is a one-shot promise; call again to
175
+ * refresh.
176
+ * - **No client cache.** Every call sends a network request and
177
+ * returns the latest server snapshot. Safe to poll on an interval —
178
+ * the default Smartico UI uses 3 s while the detail view is visible.
179
+ * - **No push event** invalidates or refreshes detail state. Score
180
+ * changes, registration count changes, lifecycle transitions — all
181
+ * require a fresh `getTournamentInstanceInfo` call.
182
+ *
183
+ * **Returned shape — beyond `TTournament`**
184
+ * `TTournamentDetailed` extends `TTournament` with the following
185
+ * additional data: a `players[]` leaderboard sorted server-side by
186
+ * score (only members with at least one recorded score); the user's
187
+ * own `me` block carrying their rank and score (undefined when not
188
+ * registered or when the user has no score yet); a full per-place
189
+ * `prizes[]` array (place range, type, points / gems / diamonds
190
+ * amounts, image, name); and `related_games[]` if the operator has
191
+ * associated games with this tournament. For clan tournaments
192
+ * (`is_clan_based === true`), the response also carries
193
+ * `clan_leaderboard[]` (ranked clans with `total_score` and
194
+ * `contributing_members`), `clan_prize_structure[]` (per-clan-place
195
+ * prize tiers — Fixed vs Dynamic, with ScoreWeighted /
196
+ * EqualSplit distribution for Dynamic prizes), `user_clan_id` (the
197
+ * user's own clan), and the advisory `user_position_in_clan` /
198
+ * `user_score_in_clan` fields.
199
+ *
200
+ * **Idempotency**: safe. Read-only. Each call returns the latest
201
+ * server snapshot.
202
+ *
203
+ * **Side effects**: none — pure metadata read.
204
+ *
205
+ * **UI guidance**: see [UI Guide — `getTournamentInstanceInfo`](../../docs/ui/tournaments/UIGuide_getTournamentInstanceInfo.md).
206
+ *
207
+ * **Visitor mode**: not supported. Use {@link getTournamentsList} for
208
+ * the public lobby list in visitor mode; the detail endpoint requires
209
+ * an authenticated session.
210
+ *
211
+ * @param tournamentInstanceId The `instance_id` from a `TTournament`
212
+ * returned by {@link getTournamentsList}.
213
+ * @returns Promise resolving to `TTournamentDetailed`.
214
+ * Rejects if the instance ID is invalid
215
+ * or the user lacks visibility for it.
216
+ *
217
+ * @example
218
+ * ```ts
219
+ * const tournaments = await window._smartico.api.getTournamentsList();
220
+ * const tournament = tournaments[0];
221
+ *
222
+ * console.log('[smartico] loading detail view for tournament', tournament.instance_id);
223
+ * const detail = await window._smartico.api.getTournamentInstanceInfo(tournament.instance_id);
224
+ *
225
+ * console.log('[smartico] render detail with', detail.players?.length ?? 0,
226
+ * 'players,', detail.prizes?.length ?? 0, 'prize tiers');
227
+ *
228
+ * if (detail.is_clan_based && detail.clan_leaderboard) {
229
+ * console.log('[smartico] clan tournament — render clan leaderboard tab with',
230
+ * detail.clan_leaderboard.length, 'clans');
231
+ * if (detail.user_clan_id != null) {
232
+ * console.log('[smartico] user belongs to clan', detail.user_clan_id,
233
+ * '— highlight that row in the clan leaderboard');
234
+ * }
235
+ * }
236
+ *
237
+ * // Live leaderboard updates — poll every 3 seconds while the detail view is open.
238
+ * const pollId = setInterval(async () => {
239
+ * try {
240
+ * const fresh = await window._smartico.api.getTournamentInstanceInfo(tournament.instance_id);
241
+ * console.log('[smartico] detail refreshed — re-render player + clan leaderboards from this new snapshot:', fresh);
242
+ * } catch (e) {
243
+ * console.error('[smartico] tournament detail poll failed — keep showing last snapshot, retry on next tick:', e);
244
+ * }
245
+ * }, 3_000);
246
+ * // clearInterval(pollId) when the detail view closes.
247
+ *
248
+ * if (detail.me) {
249
+ * console.log('[smartico] current user is rank', detail.me.position,
250
+ * 'with', detail.me.scores, 'points — render the sticky "me" panel below the leaderboard');
251
+ * }
252
+ * ```
253
+ */
254
+ public async getTournamentInstanceInfo(tournamentInstanceId: number): Promise<TTournamentDetailed> {
255
+ return this.api.tournamentsGetInfoT(this.userExtId, tournamentInstanceId);
256
+ }
257
+
258
+ /**
259
+ * Returns the ranked players of a specific clan within a clan-based
260
+ * tournament instance. Used to drill down from the clan-leaderboard
261
+ * tab on the tournament detail view into a single clan's member
262
+ * roster.
263
+ *
264
+ * The server returns up to roughly 20 players (top-N by score
265
+ * descending). Server-side ordering is already by `scores` DESC
266
+ * (equivalent to `position` ASC) — no client-side re-sort is
267
+ * required.
268
+ *
269
+ * @remarks
270
+ * **Preconditions**
271
+ * Pass a valid `tournamentInstanceId` (read from `TTournament.instance_id`
272
+ * returned by {@link getTournamentsList}) and a `clanId` (read from
273
+ * `TTournamentDetailed.clan_leaderboard[].clan_id` returned by
274
+ * {@link getTournamentInstanceInfo}). The tournament should be
275
+ * clan-based (`is_clan_based === true`); calling this against a
276
+ * non-clan tournament typically returns an empty `players` array.
277
+ *
278
+ * **Refresh model**
279
+ * - **No subscription.** This is a one-shot promise.
280
+ * - **No client cache.** Every call hits the server.
281
+ * - **No push event** refreshes the response. Live updates to clan
282
+ * member rankings require a fresh call.
283
+ * - The default Smartico UI fetches this once when the user opens the
284
+ * clan-drill-down modal and does NOT refresh while the modal is
285
+ * open. If your UI needs live updates, re-call on an interval; the
286
+ * clan leaderboard at the parent level (via
287
+ * {@link getTournamentInstanceInfo}) does refresh via its own
288
+ * polling cadence.
289
+ *
290
+ * **Returned shape**
291
+ * `TClanTournamentPlayers` carries the requested clan's identity
292
+ * (`clan_id`, `clan_public_meta.name`, `clan_public_meta.image_url`)
293
+ * alongside the `players[]` array — each entry has `position`,
294
+ * `scores`, `public_username`, `clean_ext_user_id`, `avatar_id`,
295
+ * `avatar_real_id`, the resolved `avatar_url`, `user_id`, and the
296
+ * `is_me` flag identifying the current user's row. The `clan_id` and
297
+ * `clan_public_meta` on the response echo the input clan — the
298
+ * default Smartico UI ignores them and renders the header from the
299
+ * caller-side `ClanLeaderboardEntry` object, but they're available
300
+ * for consumers that don't already have that context.
301
+ *
302
+ * **Username display**: prefer `clean_ext_user_id` when set, falling
303
+ * back to `public_username`. The default Smartico UI uses
304
+ * `clean_ext_user_id || public_username || ''`.
305
+ *
306
+ * **Idempotency**: safe. Read-only. Each call returns the latest
307
+ * server snapshot.
308
+ *
309
+ * **Side effects**: none — pure metadata read.
310
+ *
311
+ * **UI guidance**: see [UI Guide — `getClanTournamentPlayers`](../../docs/ui/tournaments/UIGuide_getClanTournamentPlayers.md).
312
+ *
313
+ * **Visitor mode**: not supported.
314
+ *
315
+ * @param tournamentInstanceId The tournament `instance_id` from a
316
+ * clan-based `TTournament`.
317
+ * @param clanId The clan ID from
318
+ * `TTournamentDetailed.clan_leaderboard[].clan_id`.
319
+ * @returns Promise resolving to
320
+ * `TClanTournamentPlayers`. An invalid
321
+ * ID combination typically yields an
322
+ * empty `players` array; the SDK does
323
+ * not distinguish "no scores yet" from
324
+ * "invalid IDs" — both manifest as an
325
+ * empty list.
326
+ *
327
+ * @example
328
+ * ```ts
329
+ * const detail = await window._smartico.api.getTournamentInstanceInfo(tournamentInstanceId);
330
+ * if (!detail.is_clan_based || !detail.clan_leaderboard) {
331
+ * console.log('[smartico] not a clan tournament — skip clan drill-down');
332
+ * return;
333
+ * }
334
+ *
335
+ * // User clicked a clan row in the leaderboard.
336
+ * const clickedClan = detail.clan_leaderboard[0];
337
+ *
338
+ * console.log('[smartico] opening clan drill-down — show modal skeleton while loading');
339
+ * const result = await window._smartico.api.getClanTournamentPlayers(
340
+ * tournamentInstanceId,
341
+ * clickedClan.clan_id,
342
+ * );
343
+ *
344
+ * if (result.players.length === 0) {
345
+ * console.log('[smartico] clan has no ranked players yet — render empty state ("No leaders yet")');
346
+ * } else {
347
+ * console.log('[smartico] render clan member rows with .is-me highlight on the current user;',
348
+ * result.players.length, 'players ranked');
349
+ * const me = result.players.find(p => p.is_me);
350
+ * if (me) {
351
+ * console.log('[smartico] current user rank in this clan:', me.position, 'score:', me.scores);
352
+ * }
353
+ * }
354
+ * ```
355
+ */
356
+ public async getClanTournamentPlayers(tournamentInstanceId: number, clanId: number): Promise<TClanTournamentPlayers> {
357
+ return this.api.clanTournamentGetPlayers(this.userExtId, tournamentInstanceId, clanId);
358
+ }
359
+
360
+ /**
361
+ * Registers the authenticated user in the specified tournament instance.
362
+ * For tournaments with a buy-in cost, the user's balance is debited
363
+ * synchronously before the response is returned; for free tournaments
364
+ * the call is a simple membership add.
365
+ *
366
+ * A successful response (`err_code === 0`) means the registration row
367
+ * exists server-side, the buy-in (if any) has been deducted, and the
368
+ * SDK has already triggered a refresh of the tournament lobby cache.
369
+ *
370
+ * @remarks
371
+ * **Preconditions**
372
+ * Read the candidate from {@link getTournamentsList} and gate the call
373
+ * on `is_can_register === true`. This composite flag already encodes:
374
+ * not already registered, slot available
375
+ * (`registration_count < players_max_count`), tournament status is
376
+ * REGISTER (or STARTED with late-registration enabled), and the
377
+ * registration type is not AUTO. The SDK forwards the call
378
+ * unconditionally — calling without satisfying these will return one
379
+ * of the error codes below.
380
+ *
381
+ * For clan tournaments (`is_clan_based === true`), the user must
382
+ * already belong to a clan before calling. If they don't, prompt them
383
+ * to pick a clan first via {@link joinClan}, then call
384
+ * `registerInTournament` after the clan-join resolves. See "Error
385
+ * codes" `1010` below for the server-side safety net.
386
+ *
387
+ * **Error codes** (in `err_code`, typed as {@link TournamentRegistrationError})
388
+ * - `0` — success; the registration is persisted, buy-in deducted,
389
+ * tournament lobby refresh fires.
390
+ * - `1010` — `TOURNAMENT_USER_CANNOT_JOIN_WITHOUT_CLAN`: clan-based
391
+ * tournament and the user has no clan. Surface the clan-pick flow
392
+ * (e.g. open a clan-pick modal driven by {@link getClans} +
393
+ * {@link joinClan}), then retry registration.
394
+ * - `30001` — `TOURNAMENT_INSTANCE_NOT_FOUND`: instance ID is invalid
395
+ * or the tournament was deleted. Refresh the lobby via
396
+ * `getTournamentsList()` and hide the entry from the UI.
397
+ * - `30002` — `TOURNAMENT_REGISTRATION_NOT_ENOUGH_POINTS`: insufficient
398
+ * points balance for a points-cost tournament. Show an
399
+ * insufficient-balance UI naming the deficit.
400
+ * - `30003` — `TOURNAMENT_INSTANCE_NOT_IN_STATE`: tournament is no
401
+ * longer in a registerable state (finished, cancelled, finalizing,
402
+ * or not yet published with late registration disabled). Refresh
403
+ * the lobby; the button should hide.
404
+ * - `30004` — `TOURNAMENT_ALREADY_REGISTERED`: user is already
405
+ * registered. Treat as idempotent success — hide the Register button
406
+ * and refresh the lobby. Usually indicates a stale local state
407
+ * (e.g. registered from another tab).
408
+ * - `30005` — `TOURNAMENT_USER_DONT_MATCH_CONDITIONS`: user fails the
409
+ * tournament's segment / entry conditions. Prefer
410
+ * `tournament.segment_dont_match_message` (operator-supplied) over
411
+ * `err_message` when both are available.
412
+ * - `30006` — `TOURNAMENT_USER_NOT_REGISTERED`: anomalous on a fresh
413
+ * register call; usually indicates a server-side race. Treat as a
414
+ * generic transient error.
415
+ * - `30007` — `TOURNAMENT_CANT_CHANGE_REGISTRATION_STATUS`: status
416
+ * transition not allowed (e.g. re-registering after a finished /
417
+ * cancelled tournament). Show a generic error; do not retry.
418
+ * - `30008` — `TOURNAMENT_MAX_REGISTRATIONS_REACHED`: the tournament
419
+ * filled up between the lobby fetch and the registration click.
420
+ * Refresh the lobby — the button will hide on the refreshed item.
421
+ * - `300010` — insufficient **gems** balance (gems-cost tournament).
422
+ * Same UI handling as `30002` but for the gems currency.
423
+ * - `300011` — insufficient **diamonds** balance (diamonds-cost
424
+ * tournament). Same as `300010` but for diamonds.
425
+ * - other non-zero — generic server error. Surface `err_message` if any.
426
+ *
427
+ * Note: clan-tournament registration can occasionally surface error
428
+ * codes from the clan-join domain (e.g. `1011` — joined-after-start)
429
+ * via the same response path. These are members of `JoinClanErrorCode`,
430
+ * not `TournamentRegistrationError`. Branch on the numeric value.
431
+ *
432
+ * **Idempotency**: NOT idempotent. A second call on the same instance
433
+ * returns `30004` once the first call has succeeded. The SDK does NOT
434
+ * enforce an in-flight lock. The consumer MUST guard the call site
435
+ * against double-clicks (set a local "registering" flag on click, clear
436
+ * it on response).
437
+ *
438
+ * **Refresh after success (and after failure)**
439
+ * The SDK automatically refreshes the tournament lobby cache on every
440
+ * response (success OR error) and fires any `onUpdate` callback
441
+ * registered via `getTournamentsList({ onUpdate })`. After
442
+ * `err_code === 0`, the affected item's `is_user_registered` /
443
+ * `registration_status` / `registration_count` reflect the new
444
+ * registered state on the refreshed array. Note that
445
+ * {@link getTournamentInstanceInfo} is NOT auto-refreshed — if the
446
+ * detail screen is open, call it again manually.
447
+ *
448
+ * The user's balance (points / gems / diamonds) is exposed on the
449
+ * user-properties channel and updates independently of this call's
450
+ * response — subscribe to user-property updates separately if your UI
451
+ * shows the balance.
452
+ *
453
+ * **Side effects** (consumer-observable on success)
454
+ * - Balance debited synchronously by the buy-in amount in the
455
+ * tournament's currency (`registration_cost_points`,
456
+ * `registration_cost_gems`, or `registration_cost_diamonds`). For
457
+ * free tournaments no deduction occurs.
458
+ * - Server-side analytics events fire downstream
459
+ * (`tournament_user_registered` for immediate-registration types;
460
+ * `tournament_user_registration_pending` or
461
+ * `tournament_registered_pending_qualification` for
462
+ * manual-approval / qualification-based types).
463
+ * - Any operator-configured on-join prizes are distributed (e.g.
464
+ * welcome bonus). These surface via the relevant follow-up channels
465
+ * (e.g. {@link getBonuses}).
466
+ *
467
+ * **UI guidance**: see [UI Guide — `registerInTournament`](../../docs/ui/tournaments/UIGuide_registerInTournament.md).
468
+ *
469
+ * **Visitor mode**: not supported. Calling from a visitor session
470
+ * returns a generic error, not a typed `TournamentRegistrationError`.
471
+ *
472
+ * @param tournamentInstanceId The `instance_id` from `TTournament`
473
+ * (NOT the template `tournament_id`).
474
+ * @returns `{ err_code, err_message }`; success when `err_code === 0`
475
+ * (or `30004` when treated as idempotent no-op).
476
+ *
477
+ * @example
478
+ * ```ts
479
+ * const tournaments = await window._smartico.api.getTournamentsList({
480
+ * onUpdate: (refreshed) => console.log('[smartico] tournament list updated — re-render lobby from this array', refreshed),
481
+ * });
482
+ * const tournament = tournaments.find(t => t.instance_id === instanceId);
483
+ *
484
+ * if (!tournament) {
485
+ * console.log('[smartico] tournament not in current lobby — refresh getTournamentsList and retry');
486
+ * return;
487
+ * }
488
+ * if (!tournament.is_can_register) {
489
+ * console.log('[smartico] tournament not currently registerable — keep CTA disabled');
490
+ * return;
491
+ * }
492
+ *
493
+ * // Clan tournament + user has no clan → resolve clan first.
494
+ * if (tournament.is_clan_based) {
495
+ * const userProfile = window._smartico.api.getUserProfile();
496
+ * const userClanId = userProfile?.clan_id;
497
+ * if (userClanId == null) {
498
+ * console.log('[smartico] clan tournament — open the clan-pick modal first; after joinClan resolves, call registerInTournament again');
499
+ * return;
500
+ * }
501
+ * }
502
+ *
503
+ * console.log('[smartico] registration starting — set in-flight flag, show loading dots on the Join button, keep modal open');
504
+ * const r = await window._smartico.api.registerInTournament(tournament.instance_id);
505
+ * console.log('[smartico] registration response received — clear in-flight flag');
506
+ *
507
+ * if (r.err_code === 0 || r.err_code === 30004) {
508
+ * console.log('[smartico] registered (or was already) — close any modal, show a success toast; getTournamentsList onUpdate above will fire with the refreshed array');
509
+ * } else if (r.err_code === 1010) {
510
+ * console.log('[smartico] clan required — open the clan-pick modal again (this is the server-side safety net for a clan-leave race)');
511
+ * } else if (r.err_code === 30002) {
512
+ * console.error('[smartico] insufficient points — show insufficient-balance UI; deficit:', tournament.registration_cost_points);
513
+ * } else if (r.err_code === 300010) {
514
+ * console.error('[smartico] insufficient gems — show insufficient-balance UI; deficit:', tournament.registration_cost_gems);
515
+ * } else if (r.err_code === 300011) {
516
+ * console.error('[smartico] insufficient diamonds — show insufficient-balance UI; deficit:', tournament.registration_cost_diamonds);
517
+ * } else if (r.err_code === 30005) {
518
+ * console.error('[smartico] segment mismatch — prefer the operator-supplied message:', tournament.segment_dont_match_message || r.err_message);
519
+ * } else if (r.err_code === 30008) {
520
+ * console.error('[smartico] tournament full — auto-refresh will hide the button on the refreshed lobby item');
521
+ * } else {
522
+ * console.error('[smartico] registration failed — show a generic error toast with this message:', r.err_message);
523
+ * }
524
+ * ```
525
+ */
526
+ public async registerInTournament(tournamentInstanceId: number): Promise<TTournamentRegistrationResult> {
527
+ const r = await this.api.registerInTournament(this.userExtId, tournamentInstanceId);
528
+
529
+ const o: TTournamentRegistrationResult = {
530
+ err_code: r.errCode,
531
+ err_message: r.errMsg,
532
+ };
533
+
534
+ return o;
535
+ }
536
+
537
+ protected async updateTournaments() {
538
+ const payload = await this.api.tournamentsGetLobbyT(this.userExtId);
539
+ this.updateEntity(onUpdateContextKey.TournamentList, payload);
540
+ }
541
+ }