@smartico/public-api 0.0.384 → 0.0.386

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.
@@ -80,15 +80,8 @@ gems, diamonds, store items, or bonuses. The first element of
80
80
  the array is the prize for place 1, the second for place 2, and
81
81
  so on. The array length is the number of paid places.
82
82
 
83
- **Cache TTL — single shared key (known limitation)**
84
- The SDK caches the response for 30 seconds under a SINGLE shared
85
- key — calling `getLeaderBoard(DAILY)` followed by
86
- `getLeaderBoard(WEEKLY)` within the same 30 s window returns the
87
- cached DAILY result for the WEEKLY call. Same applies to
88
- switching `getPreviousPeriod`. To force a fresh fetch when
89
- switching periods or current/previous, call
90
- `_smartico.api.clearCaches()` before the second call, or simply
91
- wait out the TTL.
83
+ **Cache TTL**
84
+ The SDK caches each response for 30 seconds.
92
85
 
93
86
  **Period boundaries**
94
87
  The server finalizes each period on a server-configurable
@@ -145,8 +138,7 @@ if (board.me) {
145
138
  }
146
139
  }
147
140
 
148
- // Switch to previous period view — but bust the cache first to avoid the shared-key collision.
149
- await window._smartico.api.clearCaches();
141
+ // Switch to previous period view — cached separately from the current period.
150
142
  const prev = await window._smartico.api.getLeaderBoard(LeaderBoardPeriodType.WEEKLY, true);
151
143
  console.log('[smartico] previous-week standings — render with greyed-out "ended" treatment:', prev?.users.length, 'finalists');
152
144
 
@@ -205,8 +205,17 @@ metadata and group badges by category.
205
205
  **Idempotency / Side effects**: fetch-only; safe to call repeatedly.
206
206
  The cache layer deduplicates concurrent calls within the TTL window.
207
207
 
208
+ **Visitor mode**: not supported. Badges are an authenticated-only
209
+ feature; calling this on the visitor-mode handle (`_smartico.vapi`)
210
+ throws synchronously rather than silently returning an empty list.
211
+
208
212
  **UI guidance**: see [UI Guide — `getBadges`](../../ui/missions/UIGuide_getBadges.md).
209
213
 
214
+ #### Throws
215
+
216
+ `Error("getBadges is not available in visitor mode")` when
217
+ called from a visitor-mode session.
218
+
210
219
  #### Example
211
220
 
212
221
  ```ts
@@ -401,12 +401,25 @@ source values directly from [getUserProfile](#getuserprofile)'s
401
401
  `ach_points_ever` and [getLevels](#getlevels) would also work, but
402
402
  `progress` saves the math.
403
403
 
404
- **Cache TTL**: the SDK caches the response for 30 seconds. There is
405
- no push event that refreshes this cache; a server-side level change
406
- (visible immediately on [getUserProfile](#getuserprofile)'s `ach_level_current_id`
407
- via the user-properties update channel) becomes visible on this
408
- method only after the cache TTL expires, or after
409
- `_smartico.api.clearCaches()` is called.
404
+ **Cache TTL & freshness**
405
+ The response is cached for 30 seconds, but the SDK automatically
406
+ drops that cache whenever a user-properties update arrives — the
407
+ same live channel that backs [getUserProfile](#getuserprofile). The practical
408
+ effect depends on the label's leveling logic:
409
+ - **Points-based or operator-manual leveling**: a points or level
410
+ change flows through the user-properties channel, so the cache is
411
+ busted at that moment and the next `getCurrentLevel()` call
412
+ returns up-to-date data without waiting out the TTL.
413
+ - **Counter-based leveling** (level driven by sliding-window
414
+ counters such as deposit or wagering totals over a recent window —
415
+ see [getUserLevelExtraCounters](#getuserlevelextracounters)): ordinary progress changes
416
+ are recomputed by a periodic server job and do NOT emit a
417
+ user-properties update, so they become visible on this method only
418
+ after the 30 s TTL expires (or after `_smartico.api.clearCaches()`).
419
+
420
+ Avoid calling `clearCaches()` on the points/manual path — it's
421
+ unnecessary there (the cache is already refreshed on the property
422
+ update) and forces a slower extra round-trip.
410
423
 
411
424
  **Leveling logic**
412
425
  The semantics of how a user advances depends on the label's
@@ -432,10 +445,10 @@ const level = await window._smartico.api.getCurrentLevel();
432
445
  console.log('[smartico] render level badge — name:', level.name,
433
446
  'progress:', Math.round(level.progress), '%');
434
447
 
435
- // Detect level-up via getUserProfile's push channel.
448
+ // Detect level-up via getUserProfile's push channel. No clearCaches()
449
+ // needed — the SDK already drops the level cache on this update.
436
450
  window._smartico.on('props_change', async (changed) => {
437
451
  if ('ach_level_current_id' in changed) {
438
- await window._smartico.api.clearCaches(); // bust the 30 s cache
439
452
  const fresh = await window._smartico.api.getCurrentLevel();
440
453
  console.log('[smartico] user levelled up — animate badge to new level:', fresh.name);
441
454
  }
@@ -481,14 +494,12 @@ stored under the translation keys `levelsCounter1Name` and
481
494
  `levelsCounter2Name`.
482
495
 
483
496
  **Refresh cadence**
484
- Sliding-window counters are recomputed by a server-side job that
485
- runs roughly every 60 seconds against the operator's BigQuery
486
- dataset; level transitions driven by counter changes have a
487
- latency of up to several minutes after the underlying activity
488
- (e.g. a deposit). The SDK caches the response for 30 seconds; no
489
- push event refreshes this cache. After a level change visible on
490
- [getUserProfile](#getuserprofile)'s `ach_level_current_id`, call
491
- `_smartico.api.clearCaches()` to force a fresh fetch.
497
+ Sliding-window counters are recomputed by a periodic server-side
498
+ job. Its rebuild cadence is operator-configured per brand —
499
+ commonly daily, though it can be set more frequent — so level
500
+ transitions driven by counter changes surface with a latency tied
501
+ to that rebuild schedule after the underlying activity (e.g. a
502
+ deposit), not in real time.
492
503
 
493
504
  **Idempotency**: safe. Read-only. Repeated calls within the cache
494
505
  window return a deep-cloned cached value without a network
@@ -1,73 +1,118 @@
1
1
  # Enumeration: TournamentRegistrationError
2
2
 
3
+ Error codes returned in `err_code` by the `registerInTournament` method.
4
+
5
+ These are terse value definitions for lookup. The full per-code narrative —
6
+ when each fires and how the consumer's UI should react — lives in the
7
+ `registerInTournament` method TSDoc, which owns the error table.
8
+
9
+ Note the numbering break: the gems / diamonds codes are 6-digit
10
+ (`300010` / `300011`) while the rest of the tournament block is 5-digit;
11
+ they were appended after the original `30001`–`30009` range was occupied.
12
+ Branch on the exact numeric value, not on digit count.
13
+
3
14
  ## Enumeration Members
4
15
 
5
16
  ### NO\_ERROR
6
17
 
7
18
  > **NO\_ERROR**: `0`
8
19
 
20
+ Success — registration persisted; buy-in (if any) deducted.
21
+
9
22
  ***
10
23
 
11
24
  ### TOURNAMENT\_USER\_CANNOT\_JOIN\_WITHOUT\_CLAN
12
25
 
13
26
  > **TOURNAMENT\_USER\_CANNOT\_JOIN\_WITHOUT\_CLAN**: `1010`
14
27
 
28
+ Clan-based tournament and the user does not belong to a clan yet.
29
+
15
30
  ***
16
31
 
17
32
  ### TOURNAMENT\_INSTANCE\_NOT\_FOUND
18
33
 
19
34
  > **TOURNAMENT\_INSTANCE\_NOT\_FOUND**: `30001`
20
35
 
36
+ Instance id is invalid or the tournament was deleted.
37
+
21
38
  ***
22
39
 
23
40
  ### TOURNAMENT\_REGISTRATION\_NOT\_ENOUGH\_POINTS
24
41
 
25
42
  > **TOURNAMENT\_REGISTRATION\_NOT\_ENOUGH\_POINTS**: `30002`
26
43
 
44
+ Insufficient points balance for a points-priced buy-in.
45
+
27
46
  ***
28
47
 
29
48
  ### TOURNAMENT\_INSTANCE\_NOT\_IN\_STATE
30
49
 
31
50
  > **TOURNAMENT\_INSTANCE\_NOT\_IN\_STATE**: `30003`
32
51
 
52
+ Tournament is no longer in a registerable state (finished, cancelled, finalizing, or not yet open).
53
+
33
54
  ***
34
55
 
35
56
  ### TOURNAMENT\_ALREADY\_REGISTERED
36
57
 
37
58
  > **TOURNAMENT\_ALREADY\_REGISTERED**: `30004`
38
59
 
60
+ User is already registered; treat as an idempotent success.
61
+
39
62
  ***
40
63
 
41
64
  ### TOURNAMENT\_USER\_DONT\_MATCH\_CONDITIONS
42
65
 
43
66
  > **TOURNAMENT\_USER\_DONT\_MATCH\_CONDITIONS**: `30005`
44
67
 
68
+ User does not satisfy the tournament's segment / entry conditions.
69
+
45
70
  ***
46
71
 
47
72
  ### TOURNAMENT\_USER\_NOT\_REGISTERED
48
73
 
49
74
  > **TOURNAMENT\_USER\_NOT\_REGISTERED**: `30006`
50
75
 
76
+ User is not registered; anomalous on a register call (usually a server-side race).
77
+
51
78
  ***
52
79
 
53
80
  ### TOURNAMENT\_CANT\_CHANGE\_REGISTRATION\_STATUS
54
81
 
55
82
  > **TOURNAMENT\_CANT\_CHANGE\_REGISTRATION\_STATUS**: `30007`
56
83
 
84
+ Registration status transition is not allowed (e.g. re-registering a finished tournament).
85
+
57
86
  ***
58
87
 
59
88
  ### TOURNAMENT\_MAX\_REGISTRATIONS\_REACHED
60
89
 
61
90
  > **TOURNAMENT\_MAX\_REGISTRATIONS\_REACHED**: `30008`
62
91
 
92
+ Tournament filled up — maximum registrations reached.
93
+
94
+ ***
95
+
96
+ ### TOURNAMENT\_INVALID\_USER\_CURRENCY
97
+
98
+ > **TOURNAMENT\_INVALID\_USER\_CURRENCY**: `30009`
99
+
100
+ The user's wallet currency could not be resolved. Emitted server-side
101
+ during tournament score processing, NOT during registration — it is
102
+ never returned by `registerInTournament`. Included for completeness.
103
+
63
104
  ***
64
105
 
65
106
  ### TOURNAMENT\_REGISTRATION\_NOT\_ENOUGH\_GEMS
66
107
 
67
- > **TOURNAMENT\_REGISTRATION\_NOT\_ENOUGH\_GEMS**: `30010`
108
+ > **TOURNAMENT\_REGISTRATION\_NOT\_ENOUGH\_GEMS**: `300010`
109
+
110
+ Insufficient gems balance for a gems-priced buy-in.
68
111
 
69
112
  ***
70
113
 
71
114
  ### TOURNAMENT\_REGISTRATION\_NOT\_ENOUGH\_DIAMONDS
72
115
 
73
- > **TOURNAMENT\_REGISTRATION\_NOT\_ENOUGH\_DIAMONDS**: `30011`
116
+ > **TOURNAMENT\_REGISTRATION\_NOT\_ENOUGH\_DIAMONDS**: `300011`
117
+
118
+ Insufficient diamonds balance for a diamonds-priced buy-in.
@@ -76,8 +76,7 @@ table of codes; UI handling per code:
76
76
  | `1003` (insufficient funds) | Show insufficient-balance UI naming the currency (`['points','gems','diamonds'][entry_fee_currency_type_id]`) and the deficit (`entry_fee_amount`). Optional: deep-link to a currency-earning surface. |
77
77
  | `1004` (segment mismatch) | Show a gating message — operator-supplied copy if the clan exposes one; otherwise a generic "Not eligible for this clan". Don't retry. |
78
78
  | `1005` | `joinClan` does NOT return `1005`. If you see it in this method's response, treat as a generic error. |
79
- | `1006` (cooldown active) | Show "Cooldown until {cooldown_until local-formatted}". Disable the Join CTA across ALL clans until cooldown expires. |
80
- | `1007` (archived) | Defined in the public enum but the server emits `1001` instead. Handle as a synonym for `1001` defensively. |
79
+ | `1006` (cooldown active) | Show "Cooldown until {cooldown_until local-formatted}". Disable the Join CTA across ALL clans until cooldown expires. Only fires when the user is already in a *different* clan (a switch); a first-time join never returns it. |
81
80
  | `1011` | `joinClan` does NOT return `1011`. If you see it in this method's response, treat as a generic error. |
82
81
  | Other | Generic error toast with `errMsg` if any. |
83
82
 
@@ -4,8 +4,8 @@
4
4
  - Returns the top-20 ranked entries for one period, the current user's
5
5
  own rank, and the configured prize table.
6
6
  - One call per (period type, current-or-previous) — the SDK caches
7
- under a single key, so switching periods or current/previous within
8
- 30 s returns stale cached data unless `clearCaches()` is invoked.
7
+ each combination independently for 30 s, so switching periods or
8
+ current/previous fetches the correct board (no cross-tab collision).
9
9
  - No subscription, no push refresh. Poll manually if the consumer
10
10
  needs live updates during an in-progress period.
11
11
 
@@ -92,15 +92,13 @@ content.
92
92
 
93
93
  When the user switches between current and previous period:
94
94
 
95
- 1. Call `clearCaches()` to bust the shared OCache key
96
- (otherwise the second call returns stale data within 30 s).
97
- 2. Call `getLeaderBoard(periodType, getPreviousPeriod)` with the new
98
- `getPreviousPeriod` value.
99
- 3. Replace the rendered data.
95
+ 1. Call `getLeaderBoard(periodType, getPreviousPeriod)` with the new
96
+ `getPreviousPeriod` value — current and previous are cached under
97
+ distinct keys, so no manual cache-busting is needed.
98
+ 2. Replace the rendered data.
100
99
 
101
100
  ```ts
102
101
  const onTogglePrevious = async (showPrevious: boolean) => {
103
- await window._smartico.api.clearCaches();
104
102
  const board = await window._smartico.api.getLeaderBoard(
105
103
  activePeriodType,
106
104
  showPrevious,
@@ -109,8 +107,11 @@ const onTogglePrevious = async (showPrevious: boolean) => {
109
107
  };
110
108
  ```
111
109
 
112
- Same pattern applies when switching between period types
113
- (`DAILY` → `WEEKLY` → `MONTHLY`).
110
+ The same holds when switching between period types
111
+ (`DAILY` → `WEEKLY` → `MONTHLY`): each is cached separately. To force
112
+ a fresh fetch within the TTL (e.g. a manual refresh button), call
113
+ `clearCaches()` first — it clears the whole SDK cache, not just this
114
+ board.
114
115
 
115
116
  ## Status-specific visual treatments
116
117
 
@@ -155,10 +156,10 @@ Same pattern applies when switching between period types
155
156
 
156
157
  ## Performance
157
158
 
158
- - The 30 s cache deduplicates rapid refetches, but the single-key
159
- design means switching periods within the window returns stale
160
- data. `clearCaches()` is the only way to force a fresh fetch
161
- before the TTL expires.
159
+ - The 30 s cache deduplicates rapid refetches and is keyed per
160
+ `(periodType, getPreviousPeriod)`, so switching tabs returns the
161
+ correct board. `clearCaches()` forces a fresh fetch before the TTL
162
+ expires (it clears the entire SDK cache).
162
163
  - For live polling during an in-progress period, ~30 s cadence is
163
164
  appropriate (matching the cache TTL — additional polls within
164
165
  the window are free).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartico/public-api",
3
- "version": "0.0.384",
3
+ "version": "0.0.386",
4
4
  "description": "Smartico public API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,26 +1,32 @@
1
- /*
2
- * Possible error codes returned in JOIN_CLAN_RESPONSE.errCode (cid: 575).
3
- * Clan-protocol-specific numeric codes defined in the handler spec.
1
+ /**
2
+ * Error codes returned in `errCode` by the `joinClan` method (also carried
3
+ * on `TClanJoinResult`). These are terse value definitions for lookup; the
4
+ * full per-code narrative and UI handling lives in the `joinClan` method
5
+ * TSDoc, which owns the error table.
4
6
  */
5
7
  export enum JoinClanErrorCode {
6
- /** No error, join completed */
8
+ /** Success — join completed; entry fee (if any) deducted. */
7
9
  JOIN_CLAN_OK = 0,
8
- /** Invalid parameters */
10
+ /** Request was malformed (missing or invalid parameters). */
9
11
  JOIN_CLAN_INVALID_PARAMETERS = 1000,
10
- /** clan_id doesn't exist for this label */
12
+ /** Clan doesn't exist for this label. Archived clans are also reported as `1001`. */
11
13
  JOIN_CLAN_NOT_FOUND = 1001,
12
- /** members_count >= capacity_limit */
14
+ /** Clan is full — member capacity reached. */
13
15
  JOIN_CLAN_FULL = 1002,
14
- /** Not enough Points / Gems / Diamonds for entry fee */
16
+ /** Insufficient balance for the entry fee (points / gems / diamonds). */
15
17
  JOIN_CLAN_INSUFFICIENT_FUNDS = 1003,
16
- /** User doesn't satisfy entry_conditions / entry_segment_id */
18
+ /** User doesn't meet the clan's entry segment or conditions. */
17
19
  JOIN_CLAN_SEGMENT_MISMATCH = 1004,
18
- /** User is not in a clan */
20
+ /**
21
+ * User is not in a clan. Note: a `registerInTournament` response code
22
+ * that shares this enum — never returned by `joinClan`.
23
+ */
19
24
  JOIN_CLAN_USER_IS_NOT_IN_CLAN = 1005,
20
- /** User is within the CLAN_COOLDOWN_DAYS window */
25
+ /** User is within the clan-switch cooldown window (switch only). */
21
26
  JOIN_CLAN_COOLDOWN_ACTIVE = 1006,
22
- /** clan_status_id != 1 (clan is archived) */
23
- JOIN_CLAN_ARCHIVED = 1007,
24
- /** Clan joined after tournament start */
27
+ /**
28
+ * Clan joined after tournament start. Note: a `registerInTournament`
29
+ * response code that shares this enum — never returned by `joinClan`.
30
+ */
25
31
  JOIN_CLAN_JOINED_AFTER_TOURNAMENT_START = 1011,
26
32
  }
@@ -53,6 +53,9 @@ export interface InboxMessage {
53
53
  }
54
54
 
55
55
  export const InboxMessageBodyTransform = (item: InboxMessageBody): TInboxMessageBody => {
56
+ if (!item) {
57
+ return null;
58
+ }
56
59
  const x: TInboxMessageBody = {
57
60
  action: item.action,
58
61
  icon: item.image,
@@ -76,7 +76,7 @@ export interface Tournament {
76
76
 
77
77
  export const TournamentItemsTransform = (items: Tournament[]): TTournament[] => {
78
78
  return items
79
- .filter((r) => r.tournamentId >= 1)
79
+ .filter((r) => r != null && r.tournamentId >= 1)
80
80
  .map((r) => {
81
81
  const x: TTournament = {
82
82
  instance_id: r.tournamentInstanceId,
@@ -1,16 +1,44 @@
1
+ /**
2
+ * Error codes returned in `err_code` by the `registerInTournament` method.
3
+ *
4
+ * These are terse value definitions for lookup. The full per-code narrative —
5
+ * when each fires and how the consumer's UI should react — lives in the
6
+ * `registerInTournament` method TSDoc, which owns the error table.
7
+ *
8
+ * Note the numbering break: the gems / diamonds codes are 6-digit
9
+ * (`300010` / `300011`) while the rest of the tournament block is 5-digit;
10
+ * they were appended after the original `30001`–`30009` range was occupied.
11
+ * Branch on the exact numeric value, not on digit count.
12
+ */
1
13
  export enum TournamentRegistrationError {
14
+ /** Success — registration persisted; buy-in (if any) deducted. */
2
15
  NO_ERROR = 0,
16
+ /** Clan-based tournament and the user does not belong to a clan yet. */
3
17
  TOURNAMENT_USER_CANNOT_JOIN_WITHOUT_CLAN = 1010,
18
+ /** Instance id is invalid or the tournament was deleted. */
4
19
  TOURNAMENT_INSTANCE_NOT_FOUND = 30001,
20
+ /** Insufficient points balance for a points-priced buy-in. */
5
21
  TOURNAMENT_REGISTRATION_NOT_ENOUGH_POINTS = 30002,
22
+ /** Tournament is no longer in a registerable state (finished, cancelled, finalizing, or not yet open). */
6
23
  TOURNAMENT_INSTANCE_NOT_IN_STATE = 30003,
24
+ /** User is already registered; treat as an idempotent success. */
7
25
  TOURNAMENT_ALREADY_REGISTERED = 30004,
26
+ /** User does not satisfy the tournament's segment / entry conditions. */
8
27
  TOURNAMENT_USER_DONT_MATCH_CONDITIONS = 30005,
28
+ /** User is not registered; anomalous on a register call (usually a server-side race). */
9
29
  TOURNAMENT_USER_NOT_REGISTERED = 30006,
30
+ /** Registration status transition is not allowed (e.g. re-registering a finished tournament). */
10
31
  TOURNAMENT_CANT_CHANGE_REGISTRATION_STATUS = 30007,
32
+ /** Tournament filled up — maximum registrations reached. */
11
33
  TOURNAMENT_MAX_REGISTRATIONS_REACHED = 30008,
12
- TOURNAMENT_REGISTRATION_NOT_ENOUGH_GEMS = 30010,
13
- TOURNAMENT_REGISTRATION_NOT_ENOUGH_DIAMONDS = 30011,
14
-
15
-
34
+ /**
35
+ * The user's wallet currency could not be resolved. Emitted server-side
36
+ * during tournament score processing, NOT during registration — it is
37
+ * never returned by `registerInTournament`. Included for completeness.
38
+ */
39
+ TOURNAMENT_INVALID_USER_CURRENCY = 30009,
40
+ /** Insufficient gems balance for a gems-priced buy-in. */
41
+ TOURNAMENT_REGISTRATION_NOT_ENOUGH_GEMS = 300010,
42
+ /** Insufficient diamonds balance for a diamonds-priced buy-in. */
43
+ TOURNAMENT_REGISTRATION_NOT_ENOUGH_DIAMONDS = 300011,
16
44
  }
@@ -5,6 +5,7 @@ import { SmarticoAPI } from '../SmarticoAPI';
5
5
  import { JackpotWinPush, JackpotsOptinResponse, JackpotsOptoutRequest } from '../Jackpots';
6
6
  import { onUpdateContextKey } from './WSAPIBase';
7
7
  import { WSAPIInbox } from './WSAPIInbox';
8
+ import { TUserProfile } from './WSAPITypes';
8
9
 
9
10
  /**
10
11
  * Public surface of the Smartico WebSocket API. The consumer reaches every
@@ -64,13 +65,14 @@ export class WSAPI extends WSAPIInbox {
64
65
  this.updateInboxUnreadCount(res.unread_count);
65
66
  }
66
67
  });
67
- on(ClassId.CLIENT_PUBLIC_PROPERTIES_CHANGED_EVENT, (data: { props: { core_inbox_unread_count?: number; ach_points_balance?: number; ach_gems_balance?: number; ach_diamonds_balance?: number } }) => {
68
+ on(ClassId.CLIENT_PUBLIC_PROPERTIES_CHANGED_EVENT, (data: { props: TUserProfile }) => {
68
69
  if (data?.props?.core_inbox_unread_count !== undefined && data?.props?.core_inbox_unread_count !== null) {
69
70
  this.updateInboxUnreadCount(data.props.core_inbox_unread_count);
70
71
  }
71
72
  if (data?.props?.ach_points_balance !== undefined || data?.props?.ach_gems_balance !== undefined || data?.props?.ach_diamonds_balance !== undefined) {
72
73
  this.notifyActivityLogUpdate();
73
74
  }
75
+ OCache.clear(ECacheContext.WSAPI, onUpdateContextKey.CurrentLevel);
74
76
  });
75
77
  on(ClassId.RAF_OPTIN_RESPONSE, () => this.updateRaffles());
76
78
  }
@@ -303,12 +303,10 @@ export class WSAPIClans extends WSAPITournaments {
303
303
  * tournament requires the user to be in a clan first.
304
304
  * - `1006` (`JOIN_CLAN_COOLDOWN_ACTIVE`) — user is inside the
305
305
  * switch-cooldown window from a previous clan join (default
306
- * 7 days, configurable per label). Surface the
306
+ * 7 days, configurable per label). Only reachable when the user is
307
+ * already in a *different* clan (this call is then a clan switch);
308
+ * a first-time join never returns `1006`. Surface the
307
309
  * `TClans.cooldown_until` expiry in the disabled CTA.
308
- * - `1007` (`JOIN_CLAN_ARCHIVED`) — defined on the public enum but
309
- * the server does NOT emit this code in practice (archived clans
310
- * are reported as `1001`). Handle it as a synonym for `1001`
311
- * defensively, but do not rely on it.
312
310
  * - `1011` (`JOIN_CLAN_JOINED_AFTER_TOURNAMENT_START`) — this
313
311
  * method does NOT return `1011` directly. The code fires on
314
312
  * {@link registerInTournament} responses when the user joined
@@ -121,6 +121,9 @@ export class WSAPIJackpots extends WSAPIClans {
121
121
  ECacheContext.WSAPI,
122
122
  async () => {
123
123
  const _jackpots = await this.api.jackpotGet(this.userExtId, filter);
124
+ if (!_jackpots?.items) {
125
+ return [];
126
+ }
124
127
  const _pots = _jackpots.items.map((jp) => jp.pot);
125
128
 
126
129
  _jackpots.items.forEach((jp) => {
@@ -56,15 +56,8 @@ export class WSAPILeaderBoard extends WSAPIGamePick {
56
56
  * the array is the prize for place 1, the second for place 2, and
57
57
  * so on. The array length is the number of paid places.
58
58
  *
59
- * **Cache TTL — single shared key (known limitation)**
60
- * The SDK caches the response for 30 seconds under a SINGLE shared
61
- * key — calling `getLeaderBoard(DAILY)` followed by
62
- * `getLeaderBoard(WEEKLY)` within the same 30 s window returns the
63
- * cached DAILY result for the WEEKLY call. Same applies to
64
- * switching `getPreviousPeriod`. To force a fresh fetch when
65
- * switching periods or current/previous, call
66
- * `_smartico.api.clearCaches()` before the second call, or simply
67
- * wait out the TTL.
59
+ * **Cache TTL**
60
+ * The SDK caches each response for 30 seconds.
68
61
  *
69
62
  * **Period boundaries**
70
63
  * The server finalizes each period on a server-configurable
@@ -134,8 +127,7 @@ export class WSAPILeaderBoard extends WSAPIGamePick {
134
127
  * }
135
128
  * }
136
129
  *
137
- * // Switch to previous period view — but bust the cache first to avoid the shared-key collision.
138
- * await window._smartico.api.clearCaches();
130
+ * // Switch to previous period view — cached separately from the current period.
139
131
  * const prev = await window._smartico.api.getLeaderBoard(LeaderBoardPeriodType.WEEKLY, true);
140
132
  * console.log('[smartico] previous-week standings — render with greyed-out "ended" treatment:', prev?.users.length, 'finalists');
141
133
  *
@@ -144,8 +136,11 @@ export class WSAPILeaderBoard extends WSAPIGamePick {
144
136
  * ```
145
137
  */
146
138
  public async getLeaderBoard(periodType: LeaderBoardPeriodType, getPreviousPeriod?: boolean): Promise<LeaderBoardDetailsT> {
139
+ // Cache per (periodType, current/previous) so switching tabs or
140
+ // current↔previous within the TTL doesn't return a colliding board.
141
+ const cacheKey = `${onUpdateContextKey.LeaderBoards}:${periodType}:${getPreviousPeriod ? 1 : 0}`;
147
142
  return OCache.use(
148
- onUpdateContextKey.LeaderBoards,
143
+ cacheKey,
149
144
  ECacheContext.WSAPI,
150
145
  () => this.api.leaderboardsGetT(this.userExtId, periodType, getPreviousPeriod),
151
146
  CACHE_DATA_SEC,
@@ -586,6 +586,9 @@ export class WSAPIMiniGames extends WSAPIRaffles {
586
586
  CACHE_DATA_SEC,
587
587
  );
588
588
  const index = templates.findIndex((t) => t.id === data.saw_template_id);
589
+ if (index === -1) {
590
+ return;
591
+ }
589
592
  templates[index].spin_count = data.spin_count;
590
593
  this.updateEntity(onUpdateContextKey.Saw, templates);
591
594
  }
@@ -198,8 +198,14 @@ export class WSAPIMissions extends WSAPIGeneral {
198
198
  * **Idempotency / Side effects**: fetch-only; safe to call repeatedly.
199
199
  * The cache layer deduplicates concurrent calls within the TTL window.
200
200
  *
201
+ * **Visitor mode**: not supported. Badges are an authenticated-only
202
+ * feature; calling this on the visitor-mode handle (`_smartico.vapi`)
203
+ * throws synchronously rather than silently returning an empty list.
204
+ *
201
205
  * **UI guidance**: see [UI Guide — `getBadges`](../../docs/ui/missions/UIGuide_getBadges.md).
202
206
  *
207
+ * @throws `Error("getBadges is not available in visitor mode")` when
208
+ * called from a visitor-mode session.
203
209
  * @returns Promise resolving to `TMissionOrBadge[]`. Every item has
204
210
  * `type === 'badge'`. Cached for 30 s under the `Badges` key.
205
211
  *
@@ -241,6 +247,12 @@ export class WSAPIMissions extends WSAPIGeneral {
241
247
  * ```
242
248
  */
243
249
  public async getBadges(): Promise<TMissionOrBadge[]> {
250
+ // Badges are an authenticated-only feature. The visitor-mode WSAPI
251
+ // instance (`_smartico.vapi`) is constructed without a tracker, so the
252
+ // server would silently return nothing; throw a clear error instead.
253
+ if (!this.api.tracker) {
254
+ throw new Error('getBadges is not available in visitor mode');
255
+ }
244
256
  return OCache.use(onUpdateContextKey.Badges, ECacheContext.WSAPI, () => this.api.badgetsGetItemsT(this.userExtId), CACHE_DATA_SEC);
245
257
  }
246
258
 
@@ -371,12 +371,25 @@ export class WSAPIUser extends WSAPIBase {
371
371
  * `ach_points_ever` and {@link getLevels} would also work, but
372
372
  * `progress` saves the math.
373
373
  *
374
- * **Cache TTL**: the SDK caches the response for 30 seconds. There is
375
- * no push event that refreshes this cache; a server-side level change
376
- * (visible immediately on {@link getUserProfile}'s `ach_level_current_id`
377
- * via the user-properties update channel) becomes visible on this
378
- * method only after the cache TTL expires, or after
379
- * `_smartico.api.clearCaches()` is called.
374
+ * **Cache TTL & freshness**
375
+ * The response is cached for 30 seconds, but the SDK automatically
376
+ * drops that cache whenever a user-properties update arrives — the
377
+ * same live channel that backs {@link getUserProfile}. The practical
378
+ * effect depends on the label's leveling logic:
379
+ * - **Points-based or operator-manual leveling**: a points or level
380
+ * change flows through the user-properties channel, so the cache is
381
+ * busted at that moment and the next `getCurrentLevel()` call
382
+ * returns up-to-date data without waiting out the TTL.
383
+ * - **Counter-based leveling** (level driven by sliding-window
384
+ * counters such as deposit or wagering totals over a recent window —
385
+ * see {@link getUserLevelExtraCounters}): ordinary progress changes
386
+ * are recomputed by a periodic server job and do NOT emit a
387
+ * user-properties update, so they become visible on this method only
388
+ * after the 30 s TTL expires (or after `_smartico.api.clearCaches()`).
389
+ *
390
+ * Avoid calling `clearCaches()` on the points/manual path — it's
391
+ * unnecessary there (the cache is already refreshed on the property
392
+ * update) and forces a slower extra round-trip.
380
393
  *
381
394
  * **Leveling logic**
382
395
  * The semantics of how a user advances depends on the label's
@@ -404,10 +417,10 @@ export class WSAPIUser extends WSAPIBase {
404
417
  * console.log('[smartico] render level badge — name:', level.name,
405
418
  * 'progress:', Math.round(level.progress), '%');
406
419
  *
407
- * // Detect level-up via getUserProfile's push channel.
420
+ * // Detect level-up via getUserProfile's push channel. No clearCaches()
421
+ * // needed — the SDK already drops the level cache on this update.
408
422
  * window._smartico.on('props_change', async (changed) => {
409
423
  * if ('ach_level_current_id' in changed) {
410
- * await window._smartico.api.clearCaches(); // bust the 30 s cache
411
424
  * const fresh = await window._smartico.api.getCurrentLevel();
412
425
  * console.log('[smartico] user levelled up — animate badge to new level:', fresh.name);
413
426
  * }
@@ -443,14 +456,12 @@ export class WSAPIUser extends WSAPIBase {
443
456
  * `levelsCounter2Name`.
444
457
  *
445
458
  * **Refresh cadence**
446
- * Sliding-window counters are recomputed by a server-side job that
447
- * runs roughly every 60 seconds against the operator's BigQuery
448
- * dataset; level transitions driven by counter changes have a
449
- * latency of up to several minutes after the underlying activity
450
- * (e.g. a deposit). The SDK caches the response for 30 seconds; no
451
- * push event refreshes this cache. After a level change visible on
452
- * {@link getUserProfile}'s `ach_level_current_id`, call
453
- * `_smartico.api.clearCaches()` to force a fresh fetch.
459
+ * Sliding-window counters are recomputed by a periodic server-side
460
+ * job. Its rebuild cadence is operator-configured per brand —
461
+ * commonly daily, though it can be set more frequent — so level
462
+ * transitions driven by counter changes surface with a latency tied
463
+ * to that rebuild schedule after the underlying activity (e.g. a
464
+ * deposit), not in real time.
454
465
  *
455
466
  * **Idempotency**: safe. Read-only. Repeated calls within the cache
456
467
  * window return a deep-cloned cached value without a network