@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
@@ -949,6 +949,10 @@ var InboxReadStatus;
949
949
 
950
950
  var BuyStoreItemErrorCode;
951
951
  (function (BuyStoreItemErrorCode) {
952
+ /** Generic server error / control-group rejection */
953
+ BuyStoreItemErrorCode[BuyStoreItemErrorCode["SHOP_FAILED_GENERIC"] = 1] = "SHOP_FAILED_GENERIC";
954
+ /** Label mismatch detected before purchase transaction */
955
+ BuyStoreItemErrorCode[BuyStoreItemErrorCode["SHOP_WRONG_LABEL_PRECHECK"] = 106] = "SHOP_WRONG_LABEL_PRECHECK";
952
956
  /** User don't have enough points on balance */
953
957
  BuyStoreItemErrorCode[BuyStoreItemErrorCode["SHOP_NO_BALANCE"] = 11000] = "SHOP_NO_BALANCE";
954
958
  /** Wrong shop item id */
@@ -963,6 +967,8 @@ var BuyStoreItemErrorCode;
963
967
  BuyStoreItemErrorCode[BuyStoreItemErrorCode["SHOP_FAILED_TO_BUY_MATCHING_BONUS"] = 11005] = "SHOP_FAILED_TO_BUY_MATCHING_BONUS";
964
968
  /** Failed to buy item because of limit of max items is reached */
965
969
  BuyStoreItemErrorCode[BuyStoreItemErrorCode["SHOP_FAILED_MAX_BOUGHT_ITEMS_REACHED"] = 11006] = "SHOP_FAILED_MAX_BOUGHT_ITEMS_REACHED";
970
+ /** Bonus was issued but a follow-up redemption step failed */
971
+ BuyStoreItemErrorCode[BuyStoreItemErrorCode["SHOP_FAILED_BONUS_REDEMPTION"] = 11007] = "SHOP_FAILED_BONUS_REDEMPTION";
966
972
  /** Failed to buy item because no more items are available */
967
973
  BuyStoreItemErrorCode[BuyStoreItemErrorCode["SHOP_FAILED_POOL_EMPTY"] = 11009] = "SHOP_FAILED_POOL_EMPTY";
968
974
  /** User doesn't have enough gems on balance */
@@ -971,6 +977,8 @@ var BuyStoreItemErrorCode;
971
977
  BuyStoreItemErrorCode[BuyStoreItemErrorCode["SHOP_NO_BALANCE_DIAMONDS"] = 11012] = "SHOP_NO_BALANCE_DIAMONDS";
972
978
  /** Failed to buy item because of purchase limitation */
973
979
  BuyStoreItemErrorCode[BuyStoreItemErrorCode["SHOP_FAILED_PURCHASE_LIMITATION"] = 11014] = "SHOP_FAILED_PURCHASE_LIMITATION";
980
+ /** Uncaught server exception */
981
+ BuyStoreItemErrorCode[BuyStoreItemErrorCode["SHOP_UNEXPECTED_EXCEPTION"] = 9999] = "SHOP_UNEXPECTED_EXCEPTION";
974
982
  })(BuyStoreItemErrorCode || (BuyStoreItemErrorCode = {}));
975
983
 
976
984
  const StoreCategoryTransform = items => {
@@ -1014,7 +1022,7 @@ var StoreItemTypeName;
1014
1022
  const StoreItemTypeNamed = type => {
1015
1023
  return {
1016
1024
  [StoreItemType.Bonus]: StoreItemTypeName.Bonus,
1017
- [StoreItemType.Tangible]: StoreItemTypeName.Bonus,
1025
+ [StoreItemType.Tangible]: StoreItemTypeName.Tangible,
1018
1026
  [StoreItemType.MiniGameSpin]: StoreItemTypeName.MiniGameSpin,
1019
1027
  [StoreItemType.ChangeLevel]: StoreItemTypeName.ChangeLevel,
1020
1028
  [StoreItemType.PrizeDrop]: StoreItemTypeName.PrizeDrop,
@@ -1054,6 +1062,7 @@ const StoreItemTransform = items => {
1054
1062
  pool: r.shopPool,
1055
1063
  custom_data: IntUtils.JsonOrText(r.itemPublicMeta.custom_data),
1056
1064
  active_till_date: r.activeTillDate,
1065
+ show_timer: r.itemPublicMeta.show_timer,
1057
1066
  discounted_price: r.itemPublicMeta.discount_prize,
1058
1067
  discount_price_ribbon: r.itemPublicMeta.discount_prize_ribbon,
1059
1068
  custom_ribbon_image: r.itemPublicMeta.custom_ribbon_image,
@@ -1120,6 +1129,7 @@ const StoreItemPurchasedTransform = items => {
1120
1129
  purchased_this_week: purchasedThisWeek,
1121
1130
  purchased_this_month: purchasedThisMonth,
1122
1131
  custom_data: r.itemPublicMeta.custom_data,
1132
+ show_timer: r.itemPublicMeta.show_timer,
1123
1133
  related_games: (((_r$relatedGames = r.relatedGames) == null ? void 0 : _r$relatedGames.filter(g => g.game_public_meta.enabled)) || []).map((g, i) => ({
1124
1134
  ext_game_id: g.ext_game_id,
1125
1135
  game_public_meta: {
@@ -1243,7 +1253,7 @@ var BadgesTimeLimitStates;
1243
1253
 
1244
1254
  var _MissionUtils;
1245
1255
  const USER_STATE_PARAMS_KEYS_GAMES = ['core_fav_game_top3', 'core_fav_game_type_top3', 'core_fav_game_provider_top3'];
1246
- const USER_STATE_PARAMS_KEYS_BET_AMOUNT = ['core_recommended_deposit_amount', 'core_recommended_casino_bet_amount', 'casino_last_bet_amount', 'casino_last_bet_amount_real', 'casino_last_bet_amount_bonus', 'acc_last_deposit_amount'];
1256
+ const USER_STATE_PARAMS_KEYS_BET_AMOUNT = ['core_recommended_deposit_amount', 'core_recommended_casino_bet_amount'];
1247
1257
  const MINOR_WORDS = new Set(['of', 'at', 'in', 'on', 'to', 'up', 'as', 'by', 'for', 'from', 'into', 'onto', 'with', 'upon', 'via', 'and', 'but', 'or', 'nor', 'so', 'yet', 'vs', 'v', 'a', 'an', 'the']);
1248
1258
  function toGameTitleCase(raw) {
1249
1259
  if (!raw) return raw;
@@ -2003,6 +2013,8 @@ var TournamentRegistrationError;
2003
2013
  TournamentRegistrationError[TournamentRegistrationError["TOURNAMENT_USER_NOT_REGISTERED"] = 30006] = "TOURNAMENT_USER_NOT_REGISTERED";
2004
2014
  TournamentRegistrationError[TournamentRegistrationError["TOURNAMENT_CANT_CHANGE_REGISTRATION_STATUS"] = 30007] = "TOURNAMENT_CANT_CHANGE_REGISTRATION_STATUS";
2005
2015
  TournamentRegistrationError[TournamentRegistrationError["TOURNAMENT_MAX_REGISTRATIONS_REACHED"] = 30008] = "TOURNAMENT_MAX_REGISTRATIONS_REACHED";
2016
+ TournamentRegistrationError[TournamentRegistrationError["TOURNAMENT_REGISTRATION_NOT_ENOUGH_GEMS"] = 30010] = "TOURNAMENT_REGISTRATION_NOT_ENOUGH_GEMS";
2017
+ TournamentRegistrationError[TournamentRegistrationError["TOURNAMENT_REGISTRATION_NOT_ENOUGH_DIAMONDS"] = 30011] = "TOURNAMENT_REGISTRATION_NOT_ENOUGH_DIAMONDS";
2006
2018
  })(TournamentRegistrationError || (TournamentRegistrationError = {}));
2007
2019
 
2008
2020
  var TournamentType;
@@ -2085,6 +2097,92 @@ const GetLevelMapResponseTransform = levels => {
2085
2097
  }));
2086
2098
  };
2087
2099
 
2100
+ /** @hidden */
2101
+ const CACHE_DATA_SEC = 30;
2102
+ /** @hidden */
2103
+ const JACKPOT_TEMPLATE_CACHE_SEC = 30;
2104
+ /** @hidden */
2105
+ const JACKPOT_POT_CACHE_SEC = 1;
2106
+ /** @hidden */
2107
+ const JACKPOT_WINNERS_CACHE_SEC = 30;
2108
+ /** @hidden */
2109
+ const JACKPOT_ELIGIBLE_GAMES_CACHE_SEC = 30;
2110
+ /** @hidden */
2111
+ var onUpdateContextKey;
2112
+ (function (onUpdateContextKey) {
2113
+ onUpdateContextKey["Saw"] = "saw";
2114
+ onUpdateContextKey["Missions"] = "missions";
2115
+ onUpdateContextKey["TournamentList"] = "tournamentList";
2116
+ onUpdateContextKey["InboxMessages"] = "inboxMessages";
2117
+ onUpdateContextKey["Badges"] = "badges";
2118
+ onUpdateContextKey["Levels"] = "levels";
2119
+ onUpdateContextKey["StoreItems"] = "storeItems";
2120
+ onUpdateContextKey["StoreCategories"] = "storeCategories";
2121
+ onUpdateContextKey["AchCategories"] = "achCategories";
2122
+ onUpdateContextKey["LeaderBoards"] = "leaderBoards";
2123
+ onUpdateContextKey["LevelExtraCounters"] = "levelExtraCounters";
2124
+ onUpdateContextKey["Segments"] = "segments";
2125
+ onUpdateContextKey["StoreHistory"] = "storeHistory";
2126
+ onUpdateContextKey["Jackpots"] = "jackpots";
2127
+ onUpdateContextKey["Pots"] = "Pots";
2128
+ onUpdateContextKey["CustomSections"] = "customSections";
2129
+ onUpdateContextKey["Bonuses"] = "bonuses";
2130
+ onUpdateContextKey["Clans"] = "clans";
2131
+ onUpdateContextKey["SAWHistory"] = "sawHistory";
2132
+ onUpdateContextKey["JackpotWinners"] = "jackpotWinners";
2133
+ onUpdateContextKey["Raffles"] = "raffles";
2134
+ onUpdateContextKey["JackpotEligibleGames"] = "jackpotEligibleGames";
2135
+ onUpdateContextKey["CurrentLevel"] = "currentLevel";
2136
+ onUpdateContextKey["InboxUnreadCount"] = "inboxUnreadCount";
2137
+ onUpdateContextKey["ActivityLog"] = "activityLog";
2138
+ onUpdateContextKey["AvatarsList"] = "avatarsList";
2139
+ onUpdateContextKey["AvatarsCustomized"] = "avatarsCustomized";
2140
+ onUpdateContextKey["AvatarPrompts"] = "avatarPrompts";
2141
+ })(onUpdateContextKey || (onUpdateContextKey = {}));
2142
+ /**
2143
+ * Abstract base of the SDK's public surface. Holds the shared state
2144
+ * (`api`, `userExtId`, `onUpdateCallback` map) and the generic cache /
2145
+ * subscription helper (`updateEntity`). Domain-specific methods live on
2146
+ * subclasses; the terminator `WSAPI` class composes the whole chain.
2147
+ *
2148
+ * **Consumers should not instantiate this directly** — the SDK constructs
2149
+ * the concrete `WSAPI` via `_smartico.api`.
2150
+ *
2151
+ * @hidden
2152
+ */
2153
+ class WSAPIBase {
2154
+ /** @private */
2155
+ constructor(api, userExtId = null) {
2156
+ this.api = void 0;
2157
+ /** @hidden */
2158
+ this.onUpdateCallback = new Map();
2159
+ /** @hidden */
2160
+ this.userExtId = null;
2161
+ this.api = api;
2162
+ this.userExtId = userExtId;
2163
+ OCache.clearAll();
2164
+ }
2165
+ /** @private */
2166
+ // AA: this method is used from the _smartico script to clear cache when the context is changed,
2167
+ // e.g. when user is changed or language is changed
2168
+ clearCaches() {
2169
+ OCache.clearAll();
2170
+ }
2171
+ /**
2172
+ * Generic cache-update + subscription-notify helper. Used by domain
2173
+ * classes' `update*` methods to refresh a context and fire any
2174
+ * registered `onUpdate` callback.
2175
+ * @hidden
2176
+ */
2177
+ async updateEntity(contextKey, payload) {
2178
+ OCache.set(contextKey, payload, ECacheContext.WSAPI);
2179
+ const onUpdate = this.onUpdateCallback.get(contextKey);
2180
+ if (onUpdate) {
2181
+ onUpdate(payload);
2182
+ }
2183
+ }
2184
+ }
2185
+
2088
2186
  /** @hidden */
2089
2187
  const ticketsTransform = items => {
2090
2188
  return items.map(item => {
@@ -2285,119 +2383,120 @@ const raffleClaimPrizeResponseTransform = info => {
2285
2383
  };
2286
2384
  };
2287
2385
 
2288
- /** @hidden */
2289
- const CACHE_DATA_SEC = 30;
2290
- const JACKPOT_TEMPLATE_CACHE_SEC = 30;
2291
- const JACKPOT_POT_CACHE_SEC = 1;
2292
- const JACKPOT_WINNERS_CACHE_SEC = 30;
2293
- const JACKPOT_ELIGIBLE_GAMES_CACHE_SEC = 30;
2294
- /** @hidden */
2295
- var onUpdateContextKey;
2296
- (function (onUpdateContextKey) {
2297
- onUpdateContextKey["Saw"] = "saw";
2298
- onUpdateContextKey["Missions"] = "missions";
2299
- onUpdateContextKey["TournamentList"] = "tournamentList";
2300
- onUpdateContextKey["InboxMessages"] = "inboxMessages";
2301
- onUpdateContextKey["Badges"] = "badges";
2302
- onUpdateContextKey["Levels"] = "levels";
2303
- onUpdateContextKey["StoreItems"] = "storeItems";
2304
- onUpdateContextKey["StoreCategories"] = "storeCategories";
2305
- onUpdateContextKey["AchCategories"] = "achCategories";
2306
- onUpdateContextKey["LeaderBoards"] = "leaderBoards";
2307
- onUpdateContextKey["LevelExtraCounters"] = "levelExtraCounters";
2308
- onUpdateContextKey["Segments"] = "segments";
2309
- onUpdateContextKey["StoreHistory"] = "storeHistory";
2310
- onUpdateContextKey["Jackpots"] = "jackpots";
2311
- onUpdateContextKey["Pots"] = "Pots";
2312
- onUpdateContextKey["CustomSections"] = "customSections";
2313
- onUpdateContextKey["Bonuses"] = "bonuses";
2314
- onUpdateContextKey["Clans"] = "clans";
2315
- onUpdateContextKey["SAWHistory"] = "sawHistory";
2316
- onUpdateContextKey["JackpotWinners"] = "jackpotWinners";
2317
- onUpdateContextKey["Raffles"] = "raffles";
2318
- onUpdateContextKey["JackpotEligibleGames"] = "jackpotEligibleGames";
2319
- onUpdateContextKey["CurrentLevel"] = "currentLevel";
2320
- onUpdateContextKey["InboxUnreadCount"] = "inboxUnreadCount";
2321
- onUpdateContextKey["ActivityLog"] = "activityLog";
2322
- onUpdateContextKey["AvatarsList"] = "avatarsList";
2323
- onUpdateContextKey["AvatarsCustomized"] = "avatarsCustomized";
2324
- onUpdateContextKey["AvatarPrompts"] = "avatarPrompts";
2325
- })(onUpdateContextKey || (onUpdateContextKey = {}));
2326
- /** @group General API */
2327
- class WSAPI {
2328
- /** @private */
2329
- constructor(api, userExtId = null) {
2330
- this.api = void 0;
2331
- this.onUpdateCallback = new Map();
2332
- this.jackpotGetSignature = '';
2333
- this.userExtId = null;
2334
- this.api = api;
2335
- this.userExtId = userExtId;
2336
- OCache.clearAll();
2337
- if (this.api.tracker) {
2338
- const on = this.api.tracker.on;
2339
- on(ClassId.SAW_SPINS_COUNT_PUSH, data => this.updateOnSpin(data));
2340
- on(ClassId.SAW_SHOW_SPIN_PUSH, () => this.reloadMiniGameTemplate());
2341
- on(ClassId.SAW_AKNOWLEDGE_RESPONSE, () => {
2342
- this.reloadMiniGameTemplate();
2343
- OCache.clear(ECacheContext.WSAPI, onUpdateContextKey.SAWHistory);
2344
- });
2345
- on(ClassId.SAW_DO_SPIN_RESPONSE, () => {
2346
- this.reloadMiniGameTemplate();
2347
- OCache.clear(ECacheContext.WSAPI, onUpdateContextKey.SAWHistory);
2348
- });
2349
- on(ClassId.MISSION_OPTIN_RESPONSE, () => this.updateMissions());
2350
- on(ClassId.ACHIEVEMENT_CLAIM_PRIZE_RESPONSE, () => this.updateMissions());
2351
- on(ClassId.RELOAD_ACHIEVEMENTS_EVENT, () => this.updateMissions());
2352
- on(ClassId.TOURNAMENT_REGISTER_RESPONSE, () => this.updateTournaments());
2353
- on(ClassId.BUY_SHOP_ITEM_RESPONSE, () => {
2354
- this.updateStorePurchasedItems();
2355
- this.updateStoreItems();
2356
- });
2357
- on(ClassId.CLIENT_ENGAGEMENT_EVENT_NEW, () => this.updateInboxMessages());
2358
- on(ClassId.LOGOUT_RESPONSE, () => OCache.clearContext(ECacheContext.WSAPI));
2359
- on(ClassId.IDENTIFY_RESPONSE, () => OCache.clearContext(ECacheContext.WSAPI));
2360
- on(ClassId.JP_WIN_PUSH, data => this.jackpotClearCache());
2361
- on(ClassId.JP_OPTOUT_RESPONSE, data => this.jackpotClearCache());
2362
- on(ClassId.JP_OPTIN_RESPONSE, data => this.jackpotClearCache());
2363
- on(ClassId.CLAIM_BONUS_RESPONSE, () => this.updateBonuses());
2364
- on(ClassId.SAW_DO_SPIN_BATCH_RESPONSE, () => {
2365
- this.reloadMiniGameTemplate();
2366
- OCache.clear(ECacheContext.WSAPI, onUpdateContextKey.SAWHistory);
2367
- });
2368
- on(ClassId.RAF_CLAIM_PRIZE_RESPONSE, () => this.updateRaffles());
2369
- on(ClassId.GET_INBOX_MESSAGES_RESPONSE, res => {
2370
- if (res.unread_count !== undefined && res.unread_count !== null) {
2371
- this.updateInboxUnreadCount(res.unread_count);
2372
- }
2373
- });
2374
- on(ClassId.CLIENT_PUBLIC_PROPERTIES_CHANGED_EVENT, data => {
2375
- var _data$props, _data$props2, _data$props3, _data$props4, _data$props5;
2376
- if ((data == null || (_data$props = data.props) == null ? void 0 : _data$props.core_inbox_unread_count) !== undefined && (data == null || (_data$props2 = data.props) == null ? void 0 : _data$props2.core_inbox_unread_count) !== null) {
2377
- this.updateInboxUnreadCount(data.props.core_inbox_unread_count);
2378
- }
2379
- if ((data == null || (_data$props3 = data.props) == null ? void 0 : _data$props3.ach_points_balance) !== undefined || (data == null || (_data$props4 = data.props) == null ? void 0 : _data$props4.ach_gems_balance) !== undefined || (data == null || (_data$props5 = data.props) == null ? void 0 : _data$props5.ach_diamonds_balance) !== undefined) {
2380
- this.notifyActivityLogUpdate();
2381
- }
2382
- });
2383
- on(ClassId.RAF_OPTIN_RESPONSE, () => this.updateRaffles());
2384
- }
2385
- }
2386
- /** @private */
2387
- // AA: this method is used from the _smartico script to clear cache when the context is changed,
2388
- // e.g. when user is changed or language is changed
2389
- clearCaches() {
2390
- OCache.clearAll();
2391
- }
2392
- /** Returns information about current user
2393
- * Pay attention that this method is synchronous and returns the user profile object immediately, not a promise.
2394
- * **Example**:
2395
- * ```
2396
- * var p = _smartico.api.getUserProfile();
2397
- * console.log(p);
2386
+ /** @group User */
2387
+ class WSAPIUser extends WSAPIBase {
2388
+ /**
2389
+ * Returns the current user's public profile — the source of truth for
2390
+ * balances (points / gems / diamonds), the current level ID, the
2391
+ * display username + avatar, language, public tags, and the inbox
2392
+ * unread count. **This method is synchronous** (returns `TUserProfile`,
2393
+ * not a Promise): the SDK keeps a local snapshot of the user's public
2394
+ * properties that is initialised at identify time and kept live by a
2395
+ * server-driven update channel — every call returns the latest cached
2396
+ * values without a network round-trip.
2397
+ *
2398
+ * Subscribe to changes by registering a callback on `_smartico.on('props_change', ...)`
2399
+ * — the callback fires with the full snapshot at identify and with
2400
+ * partial-key updates on every subsequent server push (typically
2401
+ * under 1 second after the underlying state change).
2402
+ *
2403
+ * @remarks
2404
+ * **Preconditions**
2405
+ * The tracker must be initialised — calling this before the identify
2406
+ * round-trip completes throws (`"Tracker is not initialized, cannot
2407
+ * getUserProfile"`). Safe ways to wait for readiness:
2408
+ * - register `_smartico.on('identify', (errCode, props) => ...)` —
2409
+ * fires once after a successful identify with the full props
2410
+ * - register `_smartico.on('props_change', () => ...)` — fires first
2411
+ * at identify with the full snapshot, then on every push update
2412
+ *
2413
+ * **What's kept live (push-updated)**
2414
+ * Balance fields (`ach_points_balance`, `ach_gems_balance`,
2415
+ * `ach_diamonds_balance`, `ach_points_ever`), `ach_level_current_id`,
2416
+ * `core_public_tags`, `core_inbox_unread_count`, `core_user_language`,
2417
+ * `avatar_url`, `public_username`, and the AI-driven recommended-amount
2418
+ * fields all flow over the SDK's user-properties update channel as
2419
+ * partial updates — the snapshot is patched in place when any of them
2420
+ * change server-side. Re-call `getUserProfile()` (or watch
2421
+ * `props_change`) to observe new values.
2422
+ *
2423
+ * **Reading state from the returned profile**
2424
+ * Balances (`ach_points_balance`, `ach_gems_balance`,
2425
+ * `ach_diamonds_balance`) are the canonical source for affordability
2426
+ * checks across the SDK — compare against `entry_fee_amount` (clans),
2427
+ * `registration_cost_points` / `_gems` / `_diamonds` (tournaments), and
2428
+ * `price` / `discounted_price` + `purchase_type` (store items).
2429
+ * `ach_points_ever` is monotonic — store purchases deduct from
2430
+ * `ach_points_balance` but NEVER from `ach_points_ever`, so it remains
2431
+ * useful for level-progress math (`required_points` on the next
2432
+ * {@link getLevels} entry minus `ach_points_ever`). `ach_level_current_id`
2433
+ * is the FK into the level ladder — resolve metadata via
2434
+ * {@link getCurrentLevel} (richer, includes progress %) or
2435
+ * {@link getLevels} (full ladder lookup table).
2436
+ *
2437
+ * **Inbox unread count** — `core_inbox_unread_count` on this profile
2438
+ * is push-updated in real time (under 1 second). For an inbox badge,
2439
+ * prefer this field over {@link getInboxUnreadCount} (which is cached
2440
+ * for 30 s) — same value, fresher signal.
2441
+ *
2442
+ * **Language**: `core_user_language` reflects the server's stored
2443
+ * language. If the consumer just called `_smartico.changeLanguage(...)`,
2444
+ * the field may briefly lag the local intent until the server push
2445
+ * arrives. Use `_smartico.getPublicProps()` for the
2446
+ * client-fallback-applied version if instant accuracy matters.
2447
+ *
2448
+ * **Idempotency / Side effects**: trivially safe — no network call,
2449
+ * no state mutation.
2450
+ *
2451
+ * **UI guidance**: see [UI Guide — `getUserProfile`](../../docs/ui/user/UIGuide_getUserProfile.md).
2452
+ *
2453
+ * **Visitor mode**: not supported — throws. Visitor sessions do not
2454
+ * have a public-profile snapshot.
2455
+ *
2456
+ * @returns The current cached `TUserProfile` snapshot. A shallow copy —
2457
+ * mutating it does not affect the SDK's internal state.
2458
+ * @throws `Error("Tracker is not initialized, cannot getUserProfile")`
2459
+ * if called before identify completes or from a visitor session.
2460
+ *
2461
+ * @example
2462
+ * ```ts
2463
+ * // Wait for identify before calling.
2464
+ * window._smartico.on('identify', (errCode) => {
2465
+ * if (errCode !== 0) return;
2466
+ * const profile = window._smartico.api.getUserProfile();
2467
+ * console.log('[smartico] initial profile loaded — render the user widget:', profile);
2468
+ * });
2469
+ *
2470
+ * // Stay in sync with live updates.
2471
+ * window._smartico.on('props_change', (changed) => {
2472
+ * // `changed` is the partial keys for this push (full snapshot at identify).
2473
+ * const profile = window._smartico.api.getUserProfile();
2474
+ * console.log('[smartico] profile updated — re-render any widgets bound to:', Object.keys(changed),
2475
+ * '— balances now:', profile.ach_points_balance, profile.ach_gems_balance, profile.ach_diamonds_balance);
2476
+ *
2477
+ * // React to specific fields.
2478
+ * if ('ach_level_current_id' in changed) {
2479
+ * console.log('[smartico] level changed — call getCurrentLevel() for richer detail, animate the level badge');
2480
+ * }
2481
+ * if ('core_inbox_unread_count' in changed) {
2482
+ * console.log('[smartico] inbox unread count changed — update the badge to:', profile.core_inbox_unread_count);
2483
+ * }
2484
+ * });
2485
+ *
2486
+ * // Affordability gating example — used inside a clan / tournament / store CTA handler.
2487
+ * const profile = window._smartico.api.getUserProfile();
2488
+ * const item = await window._smartico.api.getStoreItems().then(items => items[0]);
2489
+ * const price = item.discounted_price ?? item.price;
2490
+ * const balance = {
2491
+ * points: profile.ach_points_balance,
2492
+ * gems: profile.ach_gems_balance,
2493
+ * diamonds: profile.ach_diamonds_balance,
2494
+ * }[item.purchase_type];
2495
+ * if (balance < price) {
2496
+ * console.log('[smartico] insufficient', item.purchase_type, '— disable Buy and show deficit', price - balance);
2497
+ * }
2398
2498
  * ```
2399
- * **Visitor mode: not supported**
2400
- * */
2499
+ */
2401
2500
  getUserProfile() {
2402
2501
  if (this.api.tracker) {
2403
2502
  const o = Object.assign({}, this.api.tracker.userPublicProps);
@@ -2437,74 +2536,850 @@ class WSAPI {
2437
2536
  async checkSegmentListMatch(segment_ids) {
2438
2537
  return await this.api.coreCheckSegments(this.userExtId, Array.isArray(segment_ids) ? segment_ids : [segment_ids]);
2439
2538
  }
2440
- /** Returns all the levels available to the current user
2441
- * **Example**:
2442
- * ```
2443
- * _smartico.api.getLevels().then((result) => {
2444
- * console.log(result);
2445
- * });
2446
- * ```
2447
- *
2448
- * **Example in the Visitor mode**:
2449
- * ```
2450
- * _smartico.vapi('EN').getLevels().then((result) => {
2451
- * console.log(result);
2452
- * });
2539
+ /**
2540
+ * Returns the full level ladder configured for the current label —
2541
+ * one `TLevel` per active level, server-sorted by `required_points`
2542
+ * ASC (lowest first; `ordinal_position` is a 1-based index into the
2543
+ * returned array). Use this to render a level map / progression
2544
+ * screen, or as a lookup table to resolve `ach_level_current_id` from
2545
+ * {@link getUserProfile} into a richer level object.
2546
+ *
2547
+ * @remarks
2548
+ * **Server-side filtering**
2549
+ * The server returns ALL active levels for the label. There is NO
2550
+ * per-user filtering on visibility, level-status, or anything else —
2551
+ * the response is identical across users of the same label. Apply
2552
+ * `visibility_points` filtering client-side if you want to hide
2553
+ * not-yet-reached levels (compare against
2554
+ * `getUserProfile().ach_points_ever` and exclude levels where
2555
+ * `visibility_points > ach_points_ever`).
2556
+ *
2557
+ * **Sort order**
2558
+ * The returned array is already sorted by `required_points` ASC, and
2559
+ * `ordinal_position` is a 1-based index into that same sort order —
2560
+ * client-side re-sorting is not required for the default rendering.
2561
+ * If your label uses sliding-window leveling logic
2562
+ * (see {@link getUserLevelExtraCounters}) and you want to sort by
2563
+ * a multi-criteria key (`required_points`, then
2564
+ * `required_level_counter_1`, then `required_level_counter_2`), do
2565
+ * that client-side.
2566
+ *
2567
+ * **Cache TTL**: the SDK caches the response for 30 seconds. Cache
2568
+ * is fully cleared on login / logout. Operator-driven level edits
2569
+ * surface on the next cache miss.
2570
+ *
2571
+ * **Idempotency / Side effects**: safe. Read-only metadata. Calls
2572
+ * within the cache window de-duplicate to the same cached array.
2573
+ *
2574
+ * **UI guidance**: see [UI Guide — `getLevels`](../../docs/ui/user/UIGuide_getLevels.md).
2575
+ *
2576
+ * **Visitor mode**: supported. Use `_smartico.vapi(lang).getLevels()`
2577
+ * to fetch the ladder for anonymous viewers; the level configuration
2578
+ * is label-scoped static data so the response is meaningful even
2579
+ * without an authenticated user. Per-user fields (the implicit
2580
+ * "current level" via {@link getCurrentLevel}, or the user's progress)
2581
+ * are not available in visitor mode.
2582
+ *
2583
+ * @returns Promise resolving to the ordered `TLevel[]` ladder. Empty
2584
+ * if no levels are configured for the label.
2585
+ *
2586
+ * @example
2587
+ * ```ts
2588
+ * const [levels, profile] = await Promise.all([
2589
+ * window._smartico.api.getLevels(),
2590
+ * Promise.resolve(window._smartico.api.getUserProfile()),
2591
+ * ]);
2592
+ *
2593
+ * // Resolve the user's current level by ID.
2594
+ * const current = levels.find(l => l.id === profile.ach_level_current_id);
2595
+ * console.log('[smartico] render header badge — current level:', current?.name,
2596
+ * '(position', current?.ordinal_position, 'of', levels.length, ')');
2597
+ *
2598
+ * // Filter to levels the user has unlocked enough to see.
2599
+ * const visible = levels.filter(l =>
2600
+ * l.visibility_points == null || l.visibility_points <= profile.ach_points_ever
2601
+ * );
2602
+ * console.log('[smartico] render the level map with', visible.length, 'visible tiles');
2603
+ *
2604
+ * // Visitor-mode equivalent: returns the same ladder; no per-user state.
2605
+ * // const visitorLevels = await window._smartico.vapi('EN').getLevels();
2453
2606
  * ```
2454
2607
  */
2455
2608
  async getLevels() {
2456
2609
  return OCache.use(onUpdateContextKey.Levels, ECacheContext.WSAPI, () => this.api.levelsGetT(this.userExtId), CACHE_DATA_SEC);
2457
2610
  }
2458
2611
  /**
2459
- * Returns the current level of the user with extended information including ordinal position and progress.
2460
- *
2461
- * **Example**:
2462
- * ```
2463
- * _smartico.api.getCurrentLevel().then((result) => {
2464
- * console.log(result);
2612
+ * Returns the user's current level — every `TLevel` field plus a
2613
+ * computed `progress` percentage toward the next level. Use this to
2614
+ * power a level badge with a progress bar.
2615
+ *
2616
+ * @remarks
2617
+ * **How `progress` is computed**
2618
+ * The SDK derives `progress` as the *delta-from-current-level-floor*:
2619
+ * `(ach_points_ever − current.required_points) / (next.required_points
2620
+ * − current.required_points) × 100`, clamped to `[0, 100]`. At the
2621
+ * highest level (no next level), `progress` is `100`. Reading the
2622
+ * source values directly from {@link getUserProfile}'s
2623
+ * `ach_points_ever` and {@link getLevels} would also work, but
2624
+ * `progress` saves the math.
2625
+ *
2626
+ * **Cache TTL**: the SDK caches the response for 30 seconds. There is
2627
+ * no push event that refreshes this cache; a server-side level change
2628
+ * (visible immediately on {@link getUserProfile}'s `ach_level_current_id`
2629
+ * via the user-properties update channel) becomes visible on this
2630
+ * method only after the cache TTL expires, or after
2631
+ * `_smartico.api.clearCaches()` is called.
2632
+ *
2633
+ * **Leveling logic**
2634
+ * The semantics of how a user advances depends on the label's
2635
+ * leveling logic (configured per brand): points-only (the default),
2636
+ * sliding-window with extra counters (uses
2637
+ * {@link getUserLevelExtraCounters}), or fully-manual operator
2638
+ * assignment. The `progress` percentage on this method assumes the
2639
+ * points-only model — it's not meaningful for manual-only labels
2640
+ * where level changes are operator-driven rather than points-driven.
2641
+ *
2642
+ * **Idempotency**: safe. Read-only. Repeated calls within the cache
2643
+ * window return a deep-cloned cached value without a network
2644
+ * round-trip.
2645
+ *
2646
+ * **Side effects**: none — pure read.
2647
+ *
2648
+ * **Visitor mode**: not supported.
2649
+ *
2650
+ * @returns Promise resolving to `TLevelCurrent` — the current level
2651
+ * plus `progress: number` (0–100).
2652
+ *
2653
+ * @example
2654
+ * ```ts
2655
+ * const level = await window._smartico.api.getCurrentLevel();
2656
+ * console.log('[smartico] render level badge — name:', level.name,
2657
+ * 'progress:', Math.round(level.progress), '%');
2658
+ *
2659
+ * // Detect level-up via getUserProfile's push channel.
2660
+ * window._smartico.on('props_change', async (changed) => {
2661
+ * if ('ach_level_current_id' in changed) {
2662
+ * await window._smartico.api.clearCaches(); // bust the 30 s cache
2663
+ * const fresh = await window._smartico.api.getCurrentLevel();
2664
+ * console.log('[smartico] user levelled up — animate badge to new level:', fresh.name);
2665
+ * }
2465
2666
  * });
2466
2667
  * ```
2467
- *
2468
- * **Visitor mode: not supported**
2469
2668
  */
2470
2669
  async getCurrentLevel() {
2471
2670
  return OCache.use(onUpdateContextKey.CurrentLevel, ECacheContext.WSAPI, () => this.api.getLevelCurrent(this.userExtId), CACHE_DATA_SEC);
2472
2671
  }
2473
- /** Returns all the missions configured for the current user (server-side scoped, not filtered by Widget visibility).
2474
- * The returned missions are cached for 30 seconds. But you can pass the onUpdate callback as a parameter.
2475
- * Note that each time you call getMissions with a new onUpdate callback, the old one will be overwritten by the new one.
2476
- * The onUpdate callback will be called on mission OptIn and the updated missions will be passed to it.
2672
+ /**
2673
+ * Returns the user's current values for the two label-defined "extra
2674
+ * counters" used by sliding-window leveling logic. Operators
2675
+ * configure what each counter means per label (e.g. `level_counter_1`
2676
+ * = total deposits in the last 30 days, `level_counter_2` = total
2677
+ * wagering in the last 30 days). Compare against
2678
+ * `required_level_counter_1` / `_2` on the next level from
2679
+ * {@link getLevels} to know how close the user is to advancing.
2680
+ *
2681
+ * @remarks
2682
+ * **When this matters**
2683
+ * Only labels configured for sliding-window leveling populate the
2684
+ * counter fields. On the default points-only leveling model, both
2685
+ * `level_counter_1` and `level_counter_2` are `undefined`. Detect
2686
+ * a points-only label by either counter being `undefined`.
2687
+ *
2688
+ * **Counter semantics — operator-defined**
2689
+ * The SDK exposes raw numeric values; the meaning of each counter
2690
+ * (deposit amount, wager amount, lifetime spend, etc.) is fully
2691
+ * operator-defined per label. Resolve labels for display via
2692
+ * {@link getTranslations} — the operator's display strings are
2693
+ * stored under the translation keys `levelsCounter1Name` and
2694
+ * `levelsCounter2Name`.
2695
+ *
2696
+ * **Refresh cadence**
2697
+ * Sliding-window counters are recomputed by a server-side job that
2698
+ * runs roughly every 60 seconds against the operator's BigQuery
2699
+ * dataset; level transitions driven by counter changes have a
2700
+ * latency of up to several minutes after the underlying activity
2701
+ * (e.g. a deposit). The SDK caches the response for 30 seconds; no
2702
+ * push event refreshes this cache. After a level change visible on
2703
+ * {@link getUserProfile}'s `ach_level_current_id`, call
2704
+ * `_smartico.api.clearCaches()` to force a fresh fetch.
2705
+ *
2706
+ * **Idempotency**: safe. Read-only. Repeated calls within the cache
2707
+ * window return a deep-cloned cached value without a network
2708
+ * round-trip.
2709
+ *
2710
+ * **Side effects**: none — pure read.
2711
+ *
2712
+ * **Visitor mode**: not supported.
2713
+ *
2714
+ * @returns Promise resolving to `UserLevelExtraCountersT`. Both
2715
+ * fields are `undefined` on labels not using sliding-window
2716
+ * leveling.
2717
+ *
2718
+ * @example
2719
+ * ```ts
2720
+ * const [counters, levels, profile] = await Promise.all([
2721
+ * window._smartico.api.getUserLevelExtraCounters(),
2722
+ * window._smartico.api.getLevels(),
2723
+ * Promise.resolve(window._smartico.api.getUserProfile()),
2724
+ * ]);
2725
+ *
2726
+ * // Quick detection: undefined means this label doesn't use sliding-window leveling.
2727
+ * if (counters.level_counter_1 === undefined) {
2728
+ * console.log('[smartico] points-only label — skip counter UI; show points progress only');
2729
+ * return;
2730
+ * }
2731
+ *
2732
+ * // Find the next level and render a 3-bar progress block.
2733
+ * const currentIdx = levels.findIndex(l => l.id === profile.ach_level_current_id);
2734
+ * const next = levels[currentIdx + 1];
2735
+ * if (next) {
2736
+ * console.log('[smartico] render points bar:', profile.ach_points_ever, '/', next.required_points);
2737
+ * if (next.required_level_counter_1) {
2738
+ * console.log('[smartico] render counter 1 bar:', counters.level_counter_1, '/', next.required_level_counter_1);
2739
+ * }
2740
+ * if (next.required_level_counter_2) {
2741
+ * console.log('[smartico] render counter 2 bar:', counters.level_counter_2, '/', next.required_level_counter_2);
2742
+ * }
2743
+ * }
2744
+ * ```
2745
+ */
2746
+ async getUserLevelExtraCounters() {
2747
+ return OCache.use(onUpdateContextKey.LevelExtraCounters, ECacheContext.WSAPI, () => this.api.getUserGamificationInfoT(this.userExtId), CACHE_DATA_SEC);
2748
+ }
2749
+ /**
2750
+ * Returns the activity log for a user within a specified time range.
2751
+ * The response includes both points changes and gems/diamonds changes.
2752
+ * Each log entry contains information about the change amount, balance, and source.
2753
+ * The returned list is cached for 30 seconds.
2754
+ * You can pass the onUpdate callback as a parameter, it will be called every time the activity log is updated and will provide the updated list of activity logs for the last 10 minutes.
2477
2755
  *
2478
2756
  * **Example**:
2479
2757
  * ```
2480
- * _smartico.api.getMissions().then((result) => {
2481
- * console.log(result);
2482
- * });
2483
- * ```
2758
+ * const startTime = Math.floor(Date.now() / 1000) - 86400 * 30; // 30 days ago
2759
+ * const endTime = Math.floor(Date.now() / 1000); // now
2484
2760
  *
2485
- * **Example in the Visitor mode**:
2486
- * ```
2487
- * _smartico.vapi('EN').getMissions().then((result) => {
2761
+ * _smartico.api.getActivityLog({
2762
+ * startTimeSeconds: startTime,
2763
+ * endTimeSeconds: endTime,
2764
+ * from: 0,
2765
+ * to: 50,
2766
+ * onUpdate: (data) => console.log('Updated:', data)
2767
+ * }).then((result) => {
2488
2768
  * console.log(result);
2489
2769
  * });
2490
2770
  * ```
2771
+ *
2772
+ * **Visitor mode: not supported**
2773
+ *
2774
+ * @param params - Activity log parameters
2775
+ * @param params.startTimeSeconds - Start time in seconds (epoch timestamp)
2776
+ * @param params.endTimeSeconds - End time in seconds (epoch timestamp)
2777
+ * @param params.from - Start index of records to return
2778
+ * @param params.to - End index of records to return
2779
+ * @param params.onUpdate - Optional callback function that will be called when the activity log is updated
2491
2780
  */
2492
- async getMissions({
2781
+ async getActivityLog({
2782
+ startTimeSeconds,
2783
+ endTimeSeconds,
2784
+ from,
2785
+ to,
2493
2786
  onUpdate
2494
- } = {}) {
2787
+ }) {
2495
2788
  if (onUpdate) {
2496
- this.onUpdateCallback.set(onUpdateContextKey.Missions, onUpdate);
2789
+ this.onUpdateCallback.set(onUpdateContextKey.ActivityLog, onUpdate);
2497
2790
  }
2498
- return OCache.use(onUpdateContextKey.Missions, ECacheContext.WSAPI, () => this.api.missionsGetItemsT(this.userExtId), CACHE_DATA_SEC);
2791
+ return await OCache.use(onUpdateContextKey.ActivityLog, ECacheContext.WSAPI, () => this.api.getActivityLogT(this.userExtId, startTimeSeconds, endTimeSeconds, from, to), CACHE_DATA_SEC);
2792
+ }
2793
+ async notifyActivityLogUpdate() {
2794
+ const startSeconds = Date.now() / 1000 - 600;
2795
+ const endSeconds = Date.now() / 1000;
2796
+ const payload = await this.api.getActivityLogT(this.userExtId, startSeconds, endSeconds, 0, 50);
2797
+ this.updateEntity(onUpdateContextKey.ActivityLog, payload);
2798
+ }
2799
+ }
2800
+
2801
+ /** @group General */
2802
+ class WSAPIGeneral extends WSAPIUser {
2803
+ async getCustomSections() {
2804
+ return OCache.use(onUpdateContextKey.CustomSections, ECacheContext.WSAPI, () => this.api.customSectionsGetT(this.userExtId), CACHE_DATA_SEC);
2499
2805
  }
2500
2806
  /**
2501
- * Returns all the badges available to the current user
2502
- *
2503
- * **Visitor mode: not supported**
2807
+ * Requests translations for the given language. Returns the object including translation key/translation value pairs. All possible translation keys defined in the back office.
2808
+ */
2809
+ async getTranslations(lang_code) {
2810
+ const r = await this.api.getTranslationsT(this.userExtId, lang_code, []);
2811
+ return {
2812
+ translations: r.translations
2813
+ };
2814
+ }
2815
+ /**
2816
+ * Returns all the related tournaments and missions for the provided game id for the current user
2817
+ * The provided Game ID should correspond to the ID from the Games Catalog - https://help.smartico.ai/welcome/technical-guides/games-catalog-api
2818
+ *
2819
+ * **Example**:
2820
+ * ```
2821
+ * _smartico.api.getRelatedItemsForGame('gold-slot2').then((result) => {
2822
+ * console.log(result);
2823
+ * });
2824
+ * ```
2825
+ *
2826
+ * **Example in the Visitor mode**:
2827
+ * ```
2828
+ * _smartico.vapi('EN').getRelatedItemsForGame('gold-slot2').then((result) => {
2829
+ * console.log(result);
2830
+ * });
2831
+ * ```
2832
+ */
2833
+ async getRelatedItemsForGame(related_game_id) {
2834
+ const result = await this.api.getRelatedItemsForGame(this.userExtId, related_game_id);
2835
+ return result;
2836
+ }
2837
+ }
2838
+
2839
+ /** @group Missions */
2840
+ class WSAPIMissions extends WSAPIGeneral {
2841
+ /**
2842
+ * Returns all missions visible to the current user, scoped server-side
2843
+ * to what the user is qualified to see. Optionally subscribes to live
2844
+ * updates via `onUpdate`: the callback is invoked with the full, refreshed
2845
+ * mission array whenever the server pushes a mission-state-changing event.
2846
+ *
2847
+ * The returned list is the canonical source of truth for mission state
2848
+ * (`is_opted_in`, `progress`, `is_completed`, `completion_count`, etc.).
2849
+ * Consumers should NEVER mutate it optimistically — always wait for the
2850
+ * `onUpdate` callback (or a fresh `getMissions()` call after the 30s cache
2851
+ * window expires) to observe the new state.
2852
+ *
2853
+ * @remarks
2854
+ * **Subscription model (`onUpdate`)**
2855
+ * The callback receives the FULL refreshed mission array (never a
2856
+ * diff/patch). Each subsequent call to `getMissions({ onUpdate })`
2857
+ * REPLACES the prior callback — only one active subscriber at a time.
2858
+ * Pass `onUpdate: undefined` (or omit it) to keep the prior callback in
2859
+ * place; the callback is never auto-cleared.
2860
+ *
2861
+ * **Update triggers** — the callback fires when:
2862
+ * 1. **Mutation responses**: `requestMissionOptIn(...)` or
2863
+ * `requestMissionClaimReward(...)` resolves (any `err_code`).
2864
+ * 2. **Asynchronous server pushes**: a user activity (deposit, bet,
2865
+ * login, etc.) advances task progress on a task configured to push
2866
+ * updates, or completes a mission.
2867
+ * 3. **Visibility changes**: the user's level changes, or other
2868
+ * gamification visibility tags change.
2869
+ *
2870
+ * Operator-side BO mission-config edits do NOT push to connected
2871
+ * clients — those changes surface only on the next cache miss (after
2872
+ * the 30 s TTL) or alongside the next trigger from (1)–(3).
2873
+ *
2874
+ * **What's in the returned list / what's filtered server-side**
2875
+ * The response is pre-filtered by the server. Missions EXCLUDED before
2876
+ * reaching the SDK:
2877
+ * - Missions in `DRAFT` or `ARCHIVED` status (see {@link AchievementStatus}).
2878
+ * - Missions where the user fails visibility conditions. Exception:
2879
+ * completed missions may remain visible after the user no longer
2880
+ * qualifies (configurable per-brand).
2881
+ * - `FEATURED_MANUALLY` missions that are still locked — they appear
2882
+ * only after the user unlocks them.
2883
+ * - Locked missions configured to hide while locked — UNLESS the
2884
+ * mission is configured to expose immediately on unlock (then the
2885
+ * locked mission is included as a teaser).
2886
+ * - `RECURRING` missions between cycles, until the next cycle starts.
2887
+ * - `RECURRING_QUANTITY` missions outside their `active_from_ts` /
2888
+ * `active_till_ts` window.
2889
+ *
2890
+ * Client-side filtering you may still want: missions with
2891
+ * `only_in_custom_section: true` are intended for their custom-section
2892
+ * view only and should be hidden from the main mission list.
2893
+ *
2894
+ * **Reading state from the returned mission**
2895
+ * Drive availability chips ("missed", "coming soon", "needs opt-in",
2896
+ * "in progress", "expired") from `availability_status` (enum
2897
+ * {@link AchievementAvailabilityStatus}) — it's the canonical derived
2898
+ * state and reflects all server-side timing/visibility rules in one
2899
+ * field. For time-limited and recurring missions, `dt_start` doubles
2900
+ * as the opt-in timestamp (for opt-in missions) or unlock timestamp
2901
+ * (for previously-locked missions); the expiration of a time-limited
2902
+ * mission is `dt_start + time_limit_ms`. `next_recurrence_date_ts`
2903
+ * is populated for `RECURRING` / `RECURRING_QUANTITY` missions but
2904
+ * is no longer meaningful once the mission's `active_till_ts` has
2905
+ * passed.
2906
+ *
2907
+ * **Refresh after a mutation**
2908
+ * After `requestMissionOptIn(...)` or `requestMissionClaimReward(...)`
2909
+ * resolves, the SDK auto-refreshes the mission cache; the `onUpdate`
2910
+ * callback fires with the new array shortly after. `is_opted_in`,
2911
+ * `progress`, and `is_completed` flip on the refreshed mission — do
2912
+ * NOT mutate them optimistically while the round-trip is pending.
2913
+ * Render any pending state from your own loading flag.
2914
+ *
2915
+ * **UI guidance**: see [UI Guide — `getMissions`](../../docs/ui/missions/UIGuide_getMissions.md).
2916
+ *
2917
+ * @param params Optional. Omit to fetch without subscribing.
2918
+ * @param params.onUpdate Callback invoked with the full refreshed
2919
+ * mission array on every server-pushed update
2920
+ * (after opt-in / claim-reward calls, or when
2921
+ * the server pushes an asynchronous
2922
+ * mission-state change). Each call to
2923
+ * `getMissions` overwrites the prior callback.
2924
+ * Never fires in visitor mode.
2925
+ * @returns Promise resolving to the current list of
2926
+ * missions visible to the user. Empty array if
2927
+ * the user has no missions (or if the visitor
2928
+ * proxy user has none configured).
2929
+ *
2930
+ * @example
2931
+ * ```ts
2932
+ * // 1. Initial fetch + subscribe to live updates
2933
+ * const missions = await window._smartico.api.getMissions({
2934
+ * onUpdate: (refreshed) => {
2935
+ * console.log('[smartico] mission list refreshed — re-render the entire mission list UI from this array, do not merge with prior state:', refreshed);
2936
+ *
2937
+ * // 2. onUpdate fires for: opt-in/claim responses, task-progress
2938
+ * // pushes that complete a mission or move a "pushToClient" task,
2939
+ * // and user-level changes. Treat each call as authoritative.
2940
+ * const claimable = refreshed.filter(m =>
2941
+ * m.is_completed && m.requires_prize_claim && !m.prize_claimed_date_ts,
2942
+ * );
2943
+ * if (claimable.length > 0) {
2944
+ * console.log('[smartico] there are unclaimed mission prizes — surface a "Claim" CTA on each of these missions:', claimable.map(m => m.id));
2945
+ * }
2946
+ * },
2947
+ * });
2948
+ *
2949
+ * // 3. Filter/render from the returned list. The server already excludes
2950
+ * // DRAFT/ARCHIVED missions and ones the user can't see. You may need
2951
+ * // client-side filters for view-specific concerns (custom sections,
2952
+ * // completed/missed tabs).
2953
+ * const generalView = missions
2954
+ * .filter(m => !m.only_in_custom_section)
2955
+ * .sort((a, b) => (a.position ?? 0) - (b.position ?? 0));
2956
+ *
2957
+ * console.log('[smartico] initial mission list ready — render', generalView.length, 'missions sorted by position');
2958
+ *
2959
+ * // 4. Visitor-mode equivalent: onUpdate is accepted but never fires.
2960
+ * // Re-poll if you need fresh data.
2961
+ * // const visitorMissions = await window._smartico.vapi('EN').getMissions();
2962
+ * // console.log('[smartico] visitor missions are the brand proxy user\'s — per-user fields are not meaningful');
2963
+ * ```
2964
+ */
2965
+ async getMissions({
2966
+ onUpdate
2967
+ } = {}) {
2968
+ if (onUpdate) {
2969
+ this.onUpdateCallback.set(onUpdateContextKey.Missions, onUpdate);
2970
+ }
2971
+ return OCache.use(onUpdateContextKey.Missions, ECacheContext.WSAPI, () => this.api.missionsGetItemsT(this.userExtId), CACHE_DATA_SEC);
2972
+ }
2973
+ /**
2974
+ * Returns all the badges available to the current user.
2975
+ *
2976
+ * Badges are completion-tracked achievements that auto-unlock once the user
2977
+ * meets their criteria — there is no opt-in step, no claim step, and no
2978
+ * task-description reveal. Contrast with {@link getMissions}, which returns
2979
+ * the same `TMissionOrBadge[]` shape but represents explicit opt-in flows.
2980
+ *
2981
+ * @remarks
2982
+ * **Why no `onUpdate` callback?**
2983
+ * Unlike `getMissions({ onUpdate })`, this method does NOT accept an
2984
+ * `onUpdate` subscription. Internally the SDK never refreshes the `Badges`
2985
+ * cache from server pushes: the achievement-related push events
2986
+ * (mission opt-in / claim / reload) refresh the missions cache, NOT
2987
+ * the badges cache. The badges cache is flushed only by:
2988
+ * - its 30-second TTL
2989
+ * - a full SDK cache wipe at login / logout
2990
+ *
2991
+ * **How to refresh the badge list**
2992
+ * If you need near-live badge state, poll manually after the TTL expires.
2993
+ * The first call inside the 30 s window returns the cached payload; the
2994
+ * first call after the TTL triggers a fresh server round-trip. Example:
2995
+ * ```ts
2996
+ * setInterval(async () => {
2997
+ * const badges = await window._smartico.api.getBadges();
2998
+ * console.log('[smartico] badge list polled — re-render badge grid from this array', badges);
2999
+ * }, 30_000);
3000
+ * ```
3001
+ * Alternatively, re-fetch on a domain event your app already handles
3002
+ * (e.g. after the user finishes a game round or claims a bonus).
3003
+ *
3004
+ * **Differences from missions** (same `TMissionOrBadge` shape, different
3005
+ * feature)
3006
+ * The opt-in, claim, recurring-cycle, and unlock-description fields on
3007
+ * the returned objects are not populated for badges; treat their values
3008
+ * as undefined / default. Time windows are absolute calendar timestamps
3009
+ * (`active_from_ts` / `active_till_ts`), not opt-in-relative durations —
3010
+ * drive availability chips from the SDK-computed `badgeTimeLimitState`
3011
+ * (enum {@link BadgesTimeLimitStates}), not `time_limit_ms`. Locking for
3012
+ * badges is purely time-based: `is_locked` is `true` only when
3013
+ * `badgeTimeLimitState === BadgesTimeLimitStates.BeforeStartDate` (the
3014
+ * time window hasn't started yet). The primary navigation field is
3015
+ * `category_ids` — call {@link getAchCategories} to resolve category
3016
+ * metadata and group badges by category.
3017
+ *
3018
+ * **Idempotency / Side effects**: fetch-only; safe to call repeatedly.
3019
+ * The cache layer deduplicates concurrent calls within the TTL window.
3020
+ *
3021
+ * **UI guidance**: see [UI Guide — `getBadges`](../../docs/ui/missions/UIGuide_getBadges.md).
3022
+ *
3023
+ * @returns Promise resolving to `TMissionOrBadge[]`. Every item has
3024
+ * `type === 'badge'`. Cached for 30 s under the `Badges` key.
3025
+ *
3026
+ * @example
3027
+ * ```ts
3028
+ * const [badges, categories] = await Promise.all([
3029
+ * window._smartico.api.getBadges(),
3030
+ * window._smartico.api.getAchCategories(),
3031
+ * ]);
3032
+ *
3033
+ * console.log('[smartico] badges fetched — render grouped grid with', badges.length, 'badges across', categories.length, 'categories');
3034
+ *
3035
+ * for (const category of categories) {
3036
+ * const inCategory = badges.filter(b => b.category_ids?.includes(category.id));
3037
+ * const completed = inCategory.filter(b => b.is_completed).length;
3038
+ * console.log(`[smartico] render category section "${category.name}" header showing ${completed}/${inCategory.length}`);
3039
+ *
3040
+ * for (const badge of inCategory) {
3041
+ * // Use badgeTimeLimitState (not time_limit_ms) for availability chip
3042
+ * if (badge.badgeTimeLimitState === 0) { // BadgesTimeLimitStates.BeforeStartDate
3043
+ * console.log('[smartico] badge not yet started — render grayscaled card with "Starts on" chip for badge', badge.id);
3044
+ * } else if (badge.is_completed) {
3045
+ * console.log('[smartico] badge completed — render with completed styling and check-mark for badge', badge.id);
3046
+ * } else {
3047
+ * console.log('[smartico] badge in progress — render with stage counter (completed_tasks / total) for badge', badge.id);
3048
+ * }
3049
+ * }
3050
+ * }
3051
+ *
3052
+ * // No onUpdate available — poll if live state matters
3053
+ * setInterval(async () => {
3054
+ * try {
3055
+ * const fresh = await window._smartico.api.getBadges();
3056
+ * console.log('[smartico] badge poll tick — diff against previous list and re-render any changed badge cards', fresh);
3057
+ * } catch (e) {
3058
+ * console.error('[smartico] badge poll failed — keep showing last known state, retry on next tick:', e);
3059
+ * }
3060
+ * }, 30_000);
3061
+ * ```
2504
3062
  */
2505
3063
  async getBadges() {
2506
3064
  return OCache.use(onUpdateContextKey.Badges, ECacheContext.WSAPI, () => this.api.badgetsGetItemsT(this.userExtId), CACHE_DATA_SEC);
2507
3065
  }
3066
+ /**
3067
+ * Returns the list of active mission/badge categories configured for the current label.
3068
+ * Categories are operator-defined groupings used to organize missions and badges into
3069
+ * sections, tabs, or filter chips in the gamification UI. The same category list serves
3070
+ * BOTH missions and badges — there is no separate "mission categories" vs "badge
3071
+ * categories" split on the server.
3072
+ *
3073
+ * @remarks
3074
+ * **What categories are**
3075
+ * - Operator-defined groupings, configured per-label by the brand operator.
3076
+ * Only active categories are returned; inactive/deleted ones are filtered out.
3077
+ * - Each `TAchCategory` exposes `{ id, name, order }`. `name` is the display label;
3078
+ * `order` is the relative position (lower = appears first).
3079
+ * - The same array covers BOTH missions and badges — categories are not filtered by
3080
+ * entity type on the server.
3081
+ *
3082
+ * **Localization / translation**
3083
+ * - `name` is **pre-translated server-side** to the authenticated user's stored language.
3084
+ * In visitor mode, the language passed to `_smartico.vapi(lang)` drives the
3085
+ * translation. Consumers never need to translate `name` themselves.
3086
+ * - Fallback if no translation exists for the user's language: the brand's EN value
3087
+ * is returned. The field is never null.
3088
+ *
3089
+ * **Sort order**
3090
+ * The server does NOT pre-sort. Sort client-side before rendering:
3091
+ * `categories.sort((a, b) => a.order - b.order)`.
3092
+ *
3093
+ * **How missions/badges join to categories**
3094
+ * Each `TMissionOrBadge` exposes `category_ids: number[]` (zero-or-more,
3095
+ * many-to-many). A single mission/badge can belong to multiple categories
3096
+ * simultaneously, or to none. To render a "category → items" view:
3097
+ * `missions.filter(m => m.category_ids.includes(cat.id))`.
3098
+ *
3099
+ * **Uncategorized items**
3100
+ * Items with `category_ids: []` are NOT represented by a synthetic "Other" entry.
3101
+ * If your UI needs one, generate it client-side by checking
3102
+ * `item.category_ids.length === 0`.
3103
+ *
3104
+ * **Empty categories**
3105
+ * A category may be returned even if zero missions/badges reference it. Filter
3106
+ * out empty categories on the consumer side if you don't want to render section
3107
+ * headers for them.
3108
+ *
3109
+ * **Refresh cadence / cache**
3110
+ * - The SDK caches the response for 30 seconds. Subsequent calls within that
3111
+ * window return the cached array without a network round-trip.
3112
+ * - The cache is NOT invalidated by mission/badge push events — operator-driven
3113
+ * category changes propagate at the next 30 s cache miss.
3114
+ * - Cache is fully cleared on login / logout.
3115
+ * - In **visitor mode** additional server-side caching applies on top of the
3116
+ * SDK cache; worst-case staleness for an edited/added category is on the order
3117
+ * of a few minutes for anonymous viewers.
3118
+ *
3119
+ * **Idempotency**: safe. Read-only metadata fetch. Cache de-duplicates concurrent calls.
3120
+ *
3121
+ * **Side effects**: none — pure metadata read.
3122
+ *
3123
+ * **UI guidance**: see [UI Guide — `getAchCategories`](../../docs/ui/missions/UIGuide_getAchCategories.md).
3124
+ *
3125
+ * @returns Array of `{ id, name, order }`. Empty array if no active categories are
3126
+ * configured for the label. `name` is pre-translated. Order is NOT applied — caller
3127
+ * must sort.
3128
+ *
3129
+ * @example
3130
+ * ```ts
3131
+ * const categories = await window._smartico.api.getAchCategories();
3132
+ *
3133
+ * if (categories.length === 0) {
3134
+ * console.log('[smartico] no categories configured — render missions/badges as a flat list without section headers');
3135
+ * } else {
3136
+ * const sorted = [...categories].sort((a, b) => a.order - b.order);
3137
+ * console.log('[smartico] categories loaded — render one tab/section per entry in this order:', sorted.map(c => c.name));
3138
+ *
3139
+ * // Join to missions: render each category as a section
3140
+ * const missions = await window._smartico.api.getMissions();
3141
+ * for (const cat of sorted) {
3142
+ * const items = missions.filter(m => m.category_ids.includes(cat.id));
3143
+ * if (items.length === 0) {
3144
+ * console.log('[smartico] category has no missions — skip rendering its section header:', cat.name);
3145
+ * continue;
3146
+ * }
3147
+ * console.log(`[smartico] render section "${cat.name}" with ${items.length} mission(s)`);
3148
+ * }
3149
+ *
3150
+ * // Uncategorized items: surface in a caller-provided "Other" bucket if needed
3151
+ * const uncategorized = missions.filter(m => m.category_ids.length === 0);
3152
+ * if (uncategorized.length > 0) {
3153
+ * console.log('[smartico] uncategorized missions exist — render a caller-provided "Other" section; server does NOT return id:-1:', uncategorized.length);
3154
+ * }
3155
+ * }
3156
+ *
3157
+ * // Visitor mode — same shape, language driven by vapi(lang) argument
3158
+ * const visitorCats = await window._smartico.vapi('DE').getAchCategories();
3159
+ * console.log('[smartico] visitor-mode categories (names translated to DE):', visitorCats);
3160
+ * ```
3161
+ */
3162
+ async getAchCategories() {
3163
+ return OCache.use(onUpdateContextKey.AchCategories, ECacheContext.WSAPI, () => this.api.achGetCategoriesT(this.userExtId), CACHE_DATA_SEC);
3164
+ }
3165
+ /**
3166
+ * Opt the current user in to a mission. The mission must have
3167
+ * `is_requires_optin: true` (from `getMissions()`) and must not already
3168
+ * be opted-in or locked. After a successful opt-in, the user's task
3169
+ * progress starts counting toward this mission — events fired BEFORE
3170
+ * opt-in do not count retroactively (except `UNLOCK_ACHIEVEMENT` tasks,
3171
+ * which are exempt and can fire pre-opt-in).
3172
+ *
3173
+ * @remarks
3174
+ * **Preconditions**
3175
+ * Only call when the mission has `is_requires_optin === true`,
3176
+ * `is_opted_in === false`, and `is_locked === false`. Calling on a
3177
+ * mission that does not require opt-in is wasted; calling twice returns
3178
+ * `40010`.
3179
+ *
3180
+ * **Error codes** (in `err_code`)
3181
+ * - `0` — success; task progress counting begins
3182
+ * - `105` — wrong mission id (different label) OR visibility conditions
3183
+ * not met. Same code covers both; use `err_message` to disambiguate.
3184
+ * - `40010` — already opted-in. Safe to treat as "no-op success"; usually
3185
+ * means local mission state was stale (e.g. user opted in from another
3186
+ * session/tab).
3187
+ * - `40013` — mission not opt-in-able: missing `requires_optin`, in
3188
+ * DRAFT/ARCHIVED state, or outside its `active_from_ts` /
3189
+ * `active_till_ts` window.
3190
+ * - `40014` — mission is locked; user has unlock-tasks remaining.
3191
+ * Surface the mission's `unlock_mission_description` to guide them.
3192
+ * - other non-zero — generic server error. Surface `err_message` if any.
3193
+ *
3194
+ * **Time-limited missions**
3195
+ * For missions with `time_limit_ms > 0`, the countdown begins at opt-in
3196
+ * time, not at mission creation. Server records expiration as
3197
+ * `optin_date + time_limit_ms`.
3198
+ *
3199
+ * **Refresh after success**
3200
+ * The SDK refreshes its mission cache automatically on opt-in response;
3201
+ * any `onUpdate` callback passed to a prior `getMissions({ onUpdate })`
3202
+ * fires with the new state shortly after. No manual re-fetch needed.
3203
+ *
3204
+ * **Idempotency**: not idempotent. A second call returns `40010`. Guard
3205
+ * the call site against double-clicks.
3206
+ *
3207
+ * **Side effects**: does NOT award points / badges / levels directly.
3208
+ * Rewards come from task completion, which is itself gated on opt-in.
3209
+ * May affect visibility of other missions whose visibility conditions
3210
+ * depend on this opt-in.
3211
+ *
3212
+ * **UI guidance**: see [UI Guide — `requestMissionOptIn`](../../docs/ui/missions/UIGuide_requestMissionOptIn.md).
3213
+ *
3214
+ * **Visitor mode: not supported**
3215
+ *
3216
+ * @param mission_id The mission `id` (from `getMissions()`)
3217
+ * @returns `{ err_code, err_message }`; success when `err_code === 0`
3218
+ *
3219
+ * @example
3220
+ * ```ts
3221
+ * const missions = await window._smartico.api.getMissions({
3222
+ * onUpdate: (m) => console.log('[smartico] mission list updated — re-render mission UI from this new array', m),
3223
+ * });
3224
+ * const mission = missions.find(m => m.id === missionId);
3225
+ *
3226
+ * if (!mission || !mission.is_requires_optin || mission.is_opted_in || mission.is_locked) {
3227
+ * console.log('[smartico] opt-in not applicable — keep UI as-is');
3228
+ * return;
3229
+ * }
3230
+ *
3231
+ * console.log('[smartico] opt-in starting — show loading spinner on opt-in button and disable it to prevent double-clicks');
3232
+ * const r = await window._smartico.api.requestMissionOptIn(mission.id);
3233
+ * console.log('[smartico] opt-in response received — clear loading spinner');
3234
+ *
3235
+ * if (r.err_code === 0 || r.err_code === 40010) {
3236
+ * console.log('[smartico] opt-in succeeded (or was already done) — onUpdate above will fire with refreshed mission state; no manual re-fetch needed');
3237
+ * } else if (r.err_code === 40014) {
3238
+ * console.error('[smartico] mission is locked — surface the unlock description as guidance:', mission.unlock_mission_description);
3239
+ * } else {
3240
+ * console.error('[smartico] opt-in failed — show an error toast with this message to the user:', r.err_message);
3241
+ * }
3242
+ * ```
3243
+ */
3244
+ async requestMissionOptIn(mission_id) {
3245
+ const r = await this.api.missionOptIn(this.userExtId, mission_id);
3246
+ const o = {
3247
+ err_code: r.errCode,
3248
+ err_message: r.errMsg
3249
+ };
3250
+ return o;
3251
+ }
3252
+ /**
3253
+ * Claim the prize for a completed mission. This is the step that runs
3254
+ * AFTER `requestMissionOptIn()` (when applicable) and AFTER the user has
3255
+ * completed all of the mission's tasks. Only call when the mission has
3256
+ * `requires_prize_claim === true`, `is_completed === true`, and the prize
3257
+ * has not already been claimed.
3258
+ *
3259
+ * The points / bonuses / badge grants associated with the mission are
3260
+ * awarded server-side synchronously inside this call (before the response
3261
+ * is returned), so a successful response (`err_code === 0`) means the
3262
+ * reward has already landed in the user's account.
3263
+ *
3264
+ * @remarks
3265
+ * **Preconditions**
3266
+ * Only call when, on the mission object from `getMissions()`:
3267
+ * - `is_completed === true`
3268
+ * - `requires_prize_claim === true`
3269
+ * - `prize_claimed_date_ts == null` (i.e. not already claimed)
3270
+ * - `prize_claim_expiration_date` is `null` OR still in the future
3271
+ *
3272
+ * The `ach_completed_id` argument must be passed straight from the same
3273
+ * mission object (`mission.ach_completed_id`). It is a server-side primary
3274
+ * key identifying the specific completion row; the SDK consumer should
3275
+ * never synthesise or cache it across sessions — always re-read it from a
3276
+ * fresh `getMissions()` result.
3277
+ *
3278
+ * **Error codes** (in `err_code`)
3279
+ * - `0` — success; the prize was claimed and rewards have been credited.
3280
+ * - `1` — generic server error. Covers several distinct underlying
3281
+ * conditions, all collapsed into the same code: stale or wrong
3282
+ * `ach_completed_id` (does not match user / mission), the mission no
3283
+ * longer requiring a prize claim, the mission being archived/draft,
3284
+ * label mismatch, or the completion being too old (server enforces a
3285
+ * freshness window of several months). Recovery: re-fetch missions
3286
+ * with `getMissions()`; if the mission still appears claimable,
3287
+ * surface a generic error.
3288
+ * - `40015` — claim window expired (`prize_claim_expiration_date` has
3289
+ * passed). Show a "claim period ended" message; do not retry.
3290
+ * - `40016` — mission is not completed yet. Indicates a race or a stale
3291
+ * local cache. Re-fetch via `getMissions()`; if `is_completed` is still
3292
+ * false, the UI should hide the claim button.
3293
+ * - `40017` — prize already claimed. Treat as idempotent success: refresh
3294
+ * the mission list (auto-refresh will fire) and hide the claim button.
3295
+ * Usually means another tab/session already claimed it.
3296
+ * - other non-zero — generic server error. Surface `err_message` if any.
3297
+ *
3298
+ * **Recurring missions**
3299
+ * The server transparently handles both one-shot and recurring mission
3300
+ * completions. The SDK consumer passes a single `ach_completed_id`
3301
+ * regardless of mission type; the server resolves the correct completion.
3302
+ *
3303
+ * **Refresh after success**
3304
+ * On any claim response (success OR failure), the SDK automatically
3305
+ * re-fetches the mission list and fires any `onUpdate` callback
3306
+ * registered via `getMissions({ onUpdate })`. No manual re-fetch needed.
3307
+ * Note: points balance, badges, and level state arrive through their
3308
+ * own push channels — subscribe to them separately if your UI shows
3309
+ * those values.
3310
+ *
3311
+ * **Idempotency**: not idempotent at the row level. A second call on the
3312
+ * same `ach_completed_id` returns `40017` (already claimed). For a stale
3313
+ * `ach_completed_id` the second call returns `1`. Guard the call site
3314
+ * against double-clicks; the SDK does not enforce its own in-flight lock.
3315
+ *
3316
+ * **Side effects** (consumer-observable effects of a successful claim):
3317
+ * - Points credited to the user's balance.
3318
+ * - CRM-rule activities executed (may grant bonuses, badges; may unlock
3319
+ * follow-up missions, which arrive as a later mission auto-refresh).
3320
+ * - Analytics / automation events fired downstream; not directly observable
3321
+ * in the response, but visible via the mission auto-refresh and any
3322
+ * subsequent bonus / badge updates.
3323
+ *
3324
+ * **UI guidance**: see [UI Guide — `requestMissionClaimReward`](../../docs/ui/missions/UIGuide_requestMissionClaimReward.md).
3325
+ *
3326
+ * @param mission_id The mission `ach_id` (from `getMissions()`).
3327
+ * @param ach_completed_id The completion-row identifier, read from
3328
+ * `mission.ach_completed_id` on the same mission
3329
+ * object fetched via `getMissions()`. Never
3330
+ * fabricate or cache across sessions.
3331
+ * @returns `{ err_code, err_message }`; success when `err_code === 0`
3332
+ * (or `40017` when treated as idempotent no-op).
3333
+ *
3334
+ * @example
3335
+ * ```ts
3336
+ * const missions = await window._smartico.api.getMissions({
3337
+ * onUpdate: (m) => console.log('[smartico] mission list updated — re-render mission UI from this new array', m),
3338
+ * });
3339
+ * const mission = missions.find(m => m.ach_id === missionId);
3340
+ *
3341
+ * if (
3342
+ * !mission ||
3343
+ * !mission.is_completed ||
3344
+ * !mission.requires_prize_claim ||
3345
+ * mission.prize_claimed_date_ts ||
3346
+ * (mission.prize_claim_expiration_date && Date.now() >= mission.prize_claim_expiration_date)
3347
+ * ) {
3348
+ * console.log('[smartico] claim not applicable — keep claim button hidden');
3349
+ * return;
3350
+ * }
3351
+ *
3352
+ * console.log('[smartico] claim starting — show loading spinner on claim button and disable it to prevent double-clicks');
3353
+ * const r = await window._smartico.api.requestMissionClaimReward(mission.ach_id, mission.ach_completed_id);
3354
+ * console.log('[smartico] claim response received — clear loading spinner');
3355
+ *
3356
+ * if (r.err_code === 0 || r.err_code === 40017) {
3357
+ * console.log('[smartico] claim succeeded (or was already claimed) — points/bonuses are credited; mission list auto-refresh will fire via the onUpdate above; hide the claim button');
3358
+ * } else if (r.err_code === 40015) {
3359
+ * console.error('[smartico] claim window expired — show a "claim period ended" message and remove the claim button:', mission.prize_claim_expiration_date);
3360
+ * } else if (r.err_code === 40016) {
3361
+ * console.error('[smartico] mission reported as not completed by server — local state was stale; auto-refresh will reconcile, do not retry');
3362
+ * } else {
3363
+ * console.error('[smartico] claim failed — show an error toast with this message to the user (and re-fetch missions to recover from a stale ach_completed_id):', r.err_message);
3364
+ * }
3365
+ * ```
3366
+ */
3367
+ async requestMissionClaimReward(mission_id, ach_completed_id) {
3368
+ const r = await this.api.missionClaimPrize(this.userExtId, mission_id, ach_completed_id);
3369
+ const o = {
3370
+ err_code: r.errCode,
3371
+ err_message: r.errMsg
3372
+ };
3373
+ return o;
3374
+ }
3375
+ async updateMissions() {
3376
+ const payload = await this.api.missionsGetItemsT(this.userExtId);
3377
+ this.updateEntity(onUpdateContextKey.Missions, payload);
3378
+ }
3379
+ }
3380
+
3381
+ /** @group Bonuses */
3382
+ class WSAPIBonuses extends WSAPIMissions {
2508
3383
  /**
2509
3384
  * Returns all the bonuses for the current user
2510
3385
  * The returned bonuses are cached for 30 seconds. But you can pass the onUpdate callback as a parameter.
@@ -2538,45 +3413,198 @@ class WSAPI {
2538
3413
  };
2539
3414
  return o;
2540
3415
  }
2541
- /**
2542
- * Returns the extra counters for the current user level.
2543
- * These are counters that are configured for each Smartico client separately by request.
2544
- * For example 1st counter could be total wagering amount, 2nd counter could be total deposit amount, etc.
2545
- *
2546
- * **Example**:
2547
- * ```
2548
- * _smartico.api.getUserLevelExtraCounters().then((result) => {
2549
- * console.log(result);
2550
- * });
2551
- * ```
2552
- *
2553
- * **Visitor mode: not supported**
2554
- */
2555
- async getUserLevelExtraCounters() {
2556
- return OCache.use(onUpdateContextKey.LevelExtraCounters, ECacheContext.WSAPI, () => this.api.getUserGamificationInfoT(this.userExtId), CACHE_DATA_SEC);
3416
+ async updateBonuses() {
3417
+ const payload = await this.api.bonusesGetItemsT(this.userExtId);
3418
+ this.updateEntity(onUpdateContextKey.Bonuses, payload);
3419
+ }
3420
+ }
3421
+
3422
+ /** @group Store */
3423
+ class WSAPIStore extends WSAPIBonuses {
3424
+ constructor(...args) {
3425
+ super(...args);
3426
+ this.storeHistoryOnUpdateParams = {
3427
+ limit: 20,
3428
+ offset: 0
3429
+ };
3430
+ }
3431
+ storeHistoryCacheKey(limit, offset) {
3432
+ return `${onUpdateContextKey.StoreHistory}:${limit}:${offset}`;
2557
3433
  }
2558
3434
  /**
2559
- *
2560
- * Returns all the store items available to the current user
2561
- * The returned store items are cached for 30 seconds. But you can pass the onUpdate callback as a parameter.
2562
- * Note that each time you call getStoreItems with a new onUpdate callback, the old one will be overwritten by the new one.
2563
- * The onUpdate callback will be called on purchase of the store item.
2564
- *
2565
- * **Example**:
2566
- * ```
2567
- * _smartico.api.getStoreItems().then((result) => {
2568
- * console.log(result);
3435
+ * Returns the catalog of buyable store items visible to the current user,
3436
+ * scoped server-side to what the user is qualified to see. Optionally
3437
+ * subscribes to live updates via `onUpdate`: the callback is invoked with
3438
+ * the full refreshed catalog after every purchase the user makes from this
3439
+ * device.
3440
+ *
3441
+ * The returned list is the canonical source of truth for store state
3442
+ * (`can_buy`, `pool`, `discounted_price`, `active_till_date`, etc.).
3443
+ * Consumers should NEVER mutate it optimistically — always re-fetch (or
3444
+ * wait for the `onUpdate` callback) to observe the new state.
3445
+ *
3446
+ * @remarks
3447
+ * **Preconditions**
3448
+ * The catalog is per-user. Visitor mode is supported with caveats (see
3449
+ * "Visitor mode" below). For the authenticated path, the user's balances
3450
+ * (`ach_points_balance`, `ach_gems_balance`, `ach_diamonds_balance` from
3451
+ * {@link getUserProfile}) must be tracked client-side to drive the
3452
+ * affordability state of the Buy button — the SDK does not pre-compute
3453
+ * affordability into `can_buy`.
3454
+ *
3455
+ * **Subscription model (`onUpdate`)**
3456
+ * The callback receives the FULL refreshed catalog (never a diff/patch).
3457
+ * Each subsequent call to `getStoreItems({ onUpdate })` REPLACES the prior
3458
+ * callback — only one active subscriber at a time. Pass
3459
+ * `onUpdate: undefined` (or omit it) to keep the prior callback in place;
3460
+ * the callback is never auto-cleared.
3461
+ *
3462
+ * **Update triggers**
3463
+ *
3464
+ * Fires after: any `buyStoreItem(...)` call resolves on this connection
3465
+ * (success OR failure). This is the ONLY trigger. The refreshed array
3466
+ * reflects pool decrement on the just-bought item, per-period purchase
3467
+ * cap re-evaluation on `can_buy`, and any `purchased_today` /
3468
+ * `purchased_this_week` / `purchased_this_month` flips visible via
3469
+ * {@link getStorePurchasedItems}.
3470
+ *
3471
+ * Does NOT fire for:
3472
+ * - Operator-side BO catalog edits (price, ribbons, pool seed, segment
3473
+ * conditions, new/removed items) — surface only on the next cache miss.
3474
+ * - Concurrent purchases by OTHER users — the current user's view of
3475
+ * `pool` may be stale until they themselves refresh.
3476
+ * - User balance changes from gameplay — the catalog is independent of
3477
+ * balance; the consumer drives Buy-button affordability from a
3478
+ * separately-tracked balance source.
3479
+ * - Items going past their `active_till_date` while the page is open —
3480
+ * the item simply disappears on the next refresh.
3481
+ *
3482
+ * **Server-side filtering** (what's already excluded before the SDK sees it)
3483
+ * - Items outside the `[active_from_date, active_till_date]` window —
3484
+ * past the till-date, items are ALWAYS filtered out server-side.
3485
+ * - Items the user fails visibility-segment / visibility-condition
3486
+ * checks for.
3487
+ * - Items configured to hide-when-empty whose pool has reached 0
3488
+ * (per-item operator flag). Items without that flag are still returned
3489
+ * with `pool === 0` so a "Sold out" state can be rendered.
3490
+ * - Items where the per-period bought-count cap is reached (and the
3491
+ * operator chose to hide rather than just disable).
3492
+ *
3493
+ * **Reading state from the returned item**
3494
+ * The catalog row carries everything a Buy button needs except the user's
3495
+ * balance: `can_buy` already reflects segment / visibility / per-period
3496
+ * cap checks, but the SDK does NOT pre-compute balance affordability —
3497
+ * compare `discounted_price ?? price` against the user's balance for
3498
+ * `purchase_type` (`ach_points_balance` / `ach_gems_balance` /
3499
+ * `ach_diamonds_balance` from {@link getUserProfile}). `purchase_type` is
3500
+ * per-item (`'points'` / `'gems'` / `'diamonds'`); operators can mix
3501
+ * currencies within a single catalog. For sold-out display, `pool === 0`
3502
+ * means the operator chose to keep the item visible — render an "Out of
3503
+ * stock" overlay. When `discounted_price` is set, render `price` with a
3504
+ * strikethrough and `discounted_price` as the active price; the
3505
+ * accompanying `discount_price_ribbon` is either a percentage preset
3506
+ * (`'10'`, `'15'`, `'20'`, `'25'`, `'50'`) or the literal `'custom'`
3507
+ * (in which case `custom_ribbon_image` is the artwork at 250×300 px).
3508
+ * A live countdown for time-limited items is rendered only when the
3509
+ * operator has enabled the per-item `show_timer` flag.
3510
+ *
3511
+ * **Cross-references**
3512
+ * `category_ids` joins to `TStoreCategory.id` from
3513
+ * {@link getStoreCategories} (many-to-many — an item can belong to
3514
+ * multiple categories). Items with `only_in_custom_section: true` are
3515
+ * returned in the catalog but intended for their custom-section view
3516
+ * only — filter them client-side for the main store view. The
3517
+ * history-only fields (`purchase_ts`, `purchase_points_amount`, the
3518
+ * `purchased_*` booleans) are populated only by
3519
+ * {@link getStorePurchasedItems}, not by this method.
3520
+ *
3521
+ * **Idempotency**: yes. `getStoreItems` is a pure read. Calling it
3522
+ * repeatedly within the 30-second cache window resolves from the cache
3523
+ * without a network round-trip.
3524
+ *
3525
+ * **Side effects** (of a successful purchase that triggers `onUpdate`)
3526
+ * Documented here because the consumer's `onUpdate` handler will observe
3527
+ * the resulting state changes on the next refreshed array:
3528
+ * - Points / gems / diamonds balance deducted per `purchase_type`. The
3529
+ * new balance becomes visible on the user's gamification profile via
3530
+ * the SDK's user-properties update channel (`ach_points_balance` /
3531
+ * `ach_gems_balance` / `ach_diamonds_balance`).
3532
+ * - `pool` decremented for limited-stock items.
3533
+ * - `type = 'bonus'` → a CRM bonus is granted; surfaces via
3534
+ * {@link getBonuses}.
3535
+ * - `type = 'raffleticket'` → raffle tickets granted; surfaces via
3536
+ * `getRaffles`.
3537
+ * - `type = 'changelevel'` → user's level adjusted; surfaces via
3538
+ * {@link getCurrentLevel}.
3539
+ * - `type = 'minigamespin'` → SAW spin attempts granted; visible via the
3540
+ * mini-game APIs.
3541
+ * - `type = 'prizedrop'` → one prize-drop attempt granted.
3542
+ * - `type = 'tangible'` → no automatic gameplay effect; the purchase is
3543
+ * recorded for the operator's manual fulfillment (e.g. physical merch).
3544
+ *
3545
+ * **UI guidance**: see [UI Guide — `getStoreItems`](../../docs/ui/store/UIGuide_getStoreItems.md).
3546
+ *
3547
+ * @param params Optional. Omit to fetch without subscribing.
3548
+ * @param params.onUpdate Callback invoked with the full refreshed
3549
+ * catalog after every {@link buyStoreItem}
3550
+ * round-trip on this connection. Each call to
3551
+ * `getStoreItems` overwrites the prior
3552
+ * callback. Never fires in visitor mode.
3553
+ * @returns Promise resolving to the catalog of store
3554
+ * items visible to the user. Empty array if
3555
+ * the user has no items (or if the visitor
3556
+ * proxy user has none configured).
3557
+ *
3558
+ * @example
3559
+ * ```ts
3560
+ * // 1. Initial fetch + subscribe to live updates
3561
+ * const items = await window._smartico.api.getStoreItems({
3562
+ * onUpdate: (refreshed) => {
3563
+ * console.log('[smartico] store catalog refreshed after a purchase — re-render the entire store UI from this array, do not merge with prior state:', refreshed);
3564
+ *
3565
+ * // 2. onUpdate fires only after the current user's own purchase
3566
+ * // round-trip. Pool counts and can_buy flags are now current
3567
+ * // for this user.
3568
+ * const soldOut = refreshed.filter(i => i.pool === 0);
3569
+ * if (soldOut.length > 0) {
3570
+ * console.log('[smartico] these items are now sold out — render an "Out of stock" overlay on each:', soldOut.map(i => i.id));
3571
+ * }
3572
+ * },
2569
3573
  * });
2570
- * ```
2571
3574
  *
2572
- * **Example in the Visitor mode**:
3575
+ * // 3. Live affordability per item. Compare each item's price against
3576
+ * // the user's balance for its purchase_type (read from
3577
+ * // getUserProfile(), kept live by the SDK's user-properties channel).
3578
+ * const profile = window._smartico.api.getUserProfile();
3579
+ * const balances = {
3580
+ * points: profile?.ach_points_balance ?? 0,
3581
+ * gems: profile?.ach_gems_balance ?? 0,
3582
+ * diamonds: profile?.ach_diamonds_balance ?? 0,
3583
+ * };
3584
+ *
3585
+ * // 4. Filter out items that are custom-section-only for the main view.
3586
+ * const mainViewItems = items.filter(i => !i.only_in_custom_section);
3587
+ *
3588
+ * // 5. Bucket each item's button state for rendering.
3589
+ * for (const i of mainViewItems) {
3590
+ * const activePrice = i.discounted_price ?? i.price;
3591
+ * const canAfford = balances[i.purchase_type] >= activePrice;
3592
+ * if (i.pool === 0) {
3593
+ * console.log('[smartico] item', i.id, 'is sold out — render disabled Buy with "Out of stock" overlay');
3594
+ * } else if (i.can_buy === false) {
3595
+ * console.log('[smartico] item', i.id, 'is not eligible — render disabled Buy and surface cant_buy_message on the popup:', i.cant_buy_message);
3596
+ * } else if (!canAfford) {
3597
+ * console.log('[smartico] item', i.id, 'is unaffordable — render disabled Buy and show "short by" line on popup; deficit =', activePrice - balances[i.purchase_type], i.purchase_type);
3598
+ * } else {
3599
+ * console.log('[smartico] item', i.id, 'is buyable — render enabled Buy at price', activePrice, i.purchase_type);
3600
+ * }
3601
+ * }
3602
+ *
3603
+ * // 6. Visitor-mode equivalent: onUpdate is accepted but never fires.
3604
+ * // Re-poll if you need fresh data; buyStoreItem is not supported.
3605
+ * // const visitorItems = await window._smartico.vapi('EN').getStoreItems();
3606
+ * // console.log('[smartico] visitor catalog is the brand proxy user\'s — per-user fields are not meaningful');
2573
3607
  * ```
2574
- * _smartico.vapi('EN').getStoreItems().then((result) => {
2575
- * console.log(result);
2576
- * });
2577
- * ```
2578
- * @param params
2579
- * @param params.onUpdate - callback function that will be called when the store items are updated
2580
3608
  */
2581
3609
  async getStoreItems({
2582
3610
  onUpdate
@@ -2586,15 +3614,168 @@ class WSAPI {
2586
3614
  }
2587
3615
  return OCache.use(onUpdateContextKey.StoreItems, ECacheContext.WSAPI, () => this.api.storeGetItemsT(this.userExtId), CACHE_DATA_SEC);
2588
3616
  }
2589
- /** Buy the specific shop item by item_id. Returns the err_code in case of success or error.
2590
- * **Example**:
2591
- * ```
2592
- * _smartico.api.buyStoreItem(1).then((result) => {
2593
- * console.log(result);
3617
+ /**
3618
+ * Purchase a specific store item on behalf of the current user. Spends
3619
+ * the user's points / gems / diamonds (depending on the item's
3620
+ * `purchase_type`) and triggers reward delivery (bonus issuance, mini-game
3621
+ * spin grant, level change, prize-drop, raffle ticket, or tangible item).
3622
+ *
3623
+ * A successful response (`err_code === 0`) means the funds have already
3624
+ * been debited and the reward has been queued / granted server-side. The
3625
+ * user's balance change arrives over a separate push channel, not in this
3626
+ * response — see "Refresh after success" below.
3627
+ *
3628
+ * @remarks
3629
+ * **Preconditions**
3630
+ * Read the candidate item from `getStoreItems()` first and gate the call
3631
+ * on these fields:
3632
+ * - `can_buy === true` — server-derived; combines segment / visibility
3633
+ * conditions and any operator-set restrictions.
3634
+ * - `pool == null || pool > 0` — `null` means unlimited stock; a positive
3635
+ * number means N units remain.
3636
+ * - User's balance for the item's `purchase_type`
3637
+ * (`'points'` / `'gems'` / `'diamonds'`) is `>= price` (or
3638
+ * `>= discounted_price` if set). Balances are not on `TStoreItem` —
3639
+ * read them from the user properties returned by `getUserState()`.
3640
+ * - If `active_till_date` is set, it is in the future.
3641
+ *
3642
+ * Calling without satisfying these will most likely return a non-zero
3643
+ * `err_code` (one of the codes below). The SDK does not pre-validate
3644
+ * — it forwards the request to the server unconditionally.
3645
+ *
3646
+ * **Error codes** (in `err_code`)
3647
+ * - `0` — success; funds debited and reward delivered.
3648
+ * - `1` — generic server error / control-group rejection. Treat as a
3649
+ * transient failure; show a "try again later" message.
3650
+ * - `106` — label mismatch detected before the purchase transaction
3651
+ * (e.g. cross-label item id). From a consumer perspective: the item is
3652
+ * not purchasable by this user.
3653
+ * - `11000` (`SHOP_NO_BALANCE`) — insufficient **points** balance.
3654
+ * Show an insufficient-balance message that names how many points are
3655
+ * missing (`price - current_points_balance`). The Buy button on the
3656
+ * item should already be disabled when this is the case; this code
3657
+ * normally fires only on a stale local balance.
3658
+ * - `11001` (`SHOP_WRONG_LABEL`) — user-vs-item label mismatch detected
3659
+ * inside the purchase transaction. Distinct from `106`; both mean
3660
+ * "not purchasable by this user" from the consumer's perspective.
3661
+ * Surface as a generic "item not available" message.
3662
+ * - `11002` (`SHOP_FAILED_TO_BUY_BONUS`) — the bonus configured on the
3663
+ * store item couldn't be issued (bad / disabled bonus config on the
3664
+ * operator side). Funds are NOT debited. Surface a generic error and
3665
+ * advise the user to contact support if it persists.
3666
+ * - `11003` (`SHOP_FAILED_TO_BUY_SHOP_ITEM_CONDITION`) — the item's
3667
+ * **visibility** segment condition is not satisfied. Shouldn't be
3668
+ * reachable through a correctly-rendered UI (the item shouldn't be
3669
+ * shown in the first place); usually indicates a stale local store
3670
+ * list. Re-fetch via `getStoreItems()`.
3671
+ * - `11004` (`SHOP_FAILED_TO_BUY_SHOP_ITEM_CONDITION_PURSHASE`) — the
3672
+ * item's **purchase** segment condition is not satisfied (the item is
3673
+ * visible but the user is not in the buyable segment). Surface a
3674
+ * localized "try again later" / "you're not eligible" message; do not
3675
+ * retry automatically.
3676
+ * - `11005` (`SHOP_FAILED_TO_BUY_MATCHING_BONUS`) — matching-bonus
3677
+ * coupon issuance failed (variant of `11002` for matching-deposit
3678
+ * bonuses). Funds are NOT debited. Surface a generic error.
3679
+ * - `11006` (`SHOP_FAILED_MAX_BOUGHT_ITEMS_REACHED`) — per-user purchase
3680
+ * cap reached (e.g. "max 1 per day"). Surface the item's
3681
+ * `limit_message` if set, otherwise `err_message`. Do not retry.
3682
+ * - `11007` — bonus was issued but a follow-up redemption step failed
3683
+ * server-side. Funds **are** debited and the purchase event is recorded;
3684
+ * the consumer's UI should refresh the store list (auto-refresh will
3685
+ * fire) and surface a soft warning rather than a hard error.
3686
+ * - `11009` (`SHOP_FAILED_POOL_EMPTY`) — stock pool is empty / sold out.
3687
+ * Show a "sold out" message; auto-refresh will mark the item as such
3688
+ * in subsequent `getStoreItems()` updates.
3689
+ * - `11011` (`SHOP_NO_BALANCE_GEMS`) — insufficient **gems** balance.
3690
+ * Same handling as `11000` but for the gems currency.
3691
+ * - `11012` (`SHOP_NO_BALANCE_DIAMONDS`) — insufficient **diamonds**
3692
+ * balance. Same handling as `11000` but for the diamonds currency.
3693
+ * - `11014` (`SHOP_FAILED_PURCHASE_LIMITATION`) — outside the configured
3694
+ * purchase window (purchase weekday / time-of-day / window). Surface
3695
+ * the item's `purchase_limit_message` if set, otherwise `err_message`.
3696
+ * - `9999` — uncaught server exception. Treat as a transient failure;
3697
+ * surface a generic error and allow retry.
3698
+ * - other non-zero — generic server error. Surface `err_message` if any.
3699
+ *
3700
+ * **Idempotency**: NOT idempotent. The server performs no de-duplication —
3701
+ * a second successful call deducts funds again and grants the reward
3702
+ * again (subject to per-user purchase caps). The SDK itself does **not**
3703
+ * enforce a single in-flight call. The consumer MUST guard the call site
3704
+ * against double-clicks (set a local "buying" flag on click, clear it on
3705
+ * response).
3706
+ *
3707
+ * **Refresh after success (and after failure)**
3708
+ * The SDK automatically re-fetches the store item list and the
3709
+ * purchased-items history on every response (success or error). Any
3710
+ * `onUpdate` callback registered via `getStoreItems({ onUpdate })` (or
3711
+ * the purchased-items equivalent) fires shortly after with the updated
3712
+ * arrays — reflecting the new `pool`, `can_buy`, and any `purchased_*`
3713
+ * flags. The user's balance (points / gems / diamonds) is exposed on the
3714
+ * user-properties channel and updates independently; if the UI displays
3715
+ * the balance, subscribe to user-property updates separately (it does
3716
+ * NOT flow through `getStoreItems`).
3717
+ *
3718
+ * **Side effects** (consumer-observable on success)
3719
+ * - User's points / gems / diamonds balance decreases by `price`
3720
+ * (or `discounted_price` if set).
3721
+ * - Item's `pool` decrements by 1 (when `pool != null`).
3722
+ * - Per-user purchase counters update — `purchased_today`,
3723
+ * `purchased_this_week`, `purchased_this_month` on the next
3724
+ * `getStoreItems()` refresh.
3725
+ * - The reward is delivered: bonus issuance, mini-game spin grant,
3726
+ * level change, prize drop, raffle ticket, or tangible-item record.
3727
+ * Each reward type pushes its own follow-up update (e.g. a new
3728
+ * bonus appears in `getBonuses()`, the spin shows up in
3729
+ * `getMiniGames()`). Subscribe to each relevant channel separately.
3730
+ * - A purchase row is appended to the user's purchase history,
3731
+ * surfaced via the purchased-items list.
3732
+ *
3733
+ * **UI guidance**: see [UI Guide — `buyStoreItem`](../../docs/ui/store/UIGuide_buyStoreItem.md).
3734
+ *
3735
+ * @param item_id The store item `id` (from `getStoreItems()`).
3736
+ * @returns `{ err_code, err_message }`; success when `err_code === 0`.
3737
+ *
3738
+ * @example
3739
+ * ```ts
3740
+ * const items = await window._smartico.api.getStoreItems({
3741
+ * onUpdate: (next) => console.log('[smartico] store items updated — re-render store grid with this fresh array', next),
2594
3742
  * });
3743
+ * const item = items.find(i => i.id === itemId);
3744
+ *
3745
+ * if (!item) {
3746
+ * console.log('[smartico] item not in current store list — keep UI as-is, possibly refresh getStoreItems');
3747
+ * return;
3748
+ * }
3749
+ *
3750
+ * // Local pre-checks: keep these aligned with the Buy-button decision matrix above.
3751
+ * const effectivePrice = item.discounted_price ?? item.price;
3752
+ * const inStock = item.pool == null || item.pool > 0;
3753
+ * const inWindow = !item.active_till_date || Date.now() < item.active_till_date;
3754
+ * if (!item.can_buy || !inStock || !inWindow) {
3755
+ * console.log('[smartico] buy not applicable — show disabled state with operator copy if any:', item.cant_buy_message);
3756
+ * return;
3757
+ * }
3758
+ *
3759
+ * console.log('[smartico] buy starting — set in-flight flag, show loading dots on Buy button, keep detail modal open');
3760
+ * const r = await window._smartico.api.buyStoreItem(item.id);
3761
+ * console.log('[smartico] buy response received — clear in-flight flag');
3762
+ *
3763
+ * if (r.err_code === 0) {
3764
+ * console.log('[smartico] purchase successful — close the detail modal, show a success toast; getStoreItems onUpdate above will fire with the refreshed list (do NOT manually re-fetch)');
3765
+ * } else if (r.err_code === 11000 || r.err_code === 11011 || r.err_code === 11012) {
3766
+ * console.error('[smartico] insufficient balance for this purchase_type — show insufficient-balance UI and how many units are missing:', item.purchase_type, effectivePrice);
3767
+ * } else if (r.err_code === 11009) {
3768
+ * console.error('[smartico] item sold out — show a dedicated "Sold out" message; the auto-refresh will mark the card as such shortly');
3769
+ * } else if (r.err_code === 11006) {
3770
+ * console.error('[smartico] per-user purchase cap reached — surface the operator-defined limit message:', item.limit_message || r.err_message);
3771
+ * } else if (r.err_code === 11014) {
3772
+ * console.error('[smartico] outside purchase window — surface the operator-defined purchase-limit message:', item.purchase_limit_message || r.err_message);
3773
+ * } else if (r.err_code === 11004 || r.err_code === 11003) {
3774
+ * console.error('[smartico] user not eligible / stale visibility — show a localized "try again later" message and let auto-refresh reconcile');
3775
+ * } else {
3776
+ * console.error('[smartico] buy failed — show a generic error toast/modal with this message:', r.err_message);
3777
+ * }
2595
3778
  * ```
2596
- *
2597
- * **Visitor mode: not supported**
2598
3779
  */
2599
3780
  async buyStoreItem(item_id) {
2600
3781
  const r = await this.api.buyStoreItem(this.userExtId, item_id);
@@ -2605,586 +3786,1796 @@ class WSAPI {
2605
3786
  return o;
2606
3787
  }
2607
3788
  /**
2608
- *
2609
- * Returns store categories
2610
- *
2611
- * **Example**:
2612
- * ```
2613
- * _smartico.api.getStoreCategories().then((result) => {
2614
- * console.log(result);
2615
- * });
2616
- * ```
2617
- *
2618
- * **Example in the Visitor mode**:
2619
- * ```
2620
- * _smartico.vapi('EN').getStoreCategories().then((result) => {
2621
- * console.log(result);
2622
- * });
3789
+ * Returns the list of active store categories configured for the current label.
3790
+ * Categories are operator-defined groupings used to organize store items into tabs,
3791
+ * sections, or filter chips in the gamification store UI.
3792
+ *
3793
+ * @remarks
3794
+ * **What categories are**
3795
+ * - Operator-defined groupings, configured per-label by the brand operator.
3796
+ * Only active categories are returned; inactive / archived / deleted ones are
3797
+ * filtered out server-side.
3798
+ * - Each `TStoreCategory` exposes `{ id, name, order }`. `name` is the display label;
3799
+ * `order` is the relative position (lower = appears first).
3800
+ * - The same set of categories is returned to every user of the label — there is no
3801
+ * per-segment / per-currency / per-device filtering on the category list. Item-level
3802
+ * segment visibility happens on {@link getStoreItems}, not here.
3803
+ *
3804
+ * **Localization / translation**
3805
+ * - `name` is **pre-translated server-side** to the authenticated user's stored language.
3806
+ * In visitor mode, the language passed to `_smartico.vapi(lang)` drives the translation.
3807
+ * Consumers never need to translate `name` themselves.
3808
+ * - Fallback if no translation exists for the user's language: the operator-set default
3809
+ * (typically the brand's English) value is returned. The field is never null for an
3810
+ * active category.
3811
+ *
3812
+ * **Sort order**
3813
+ * The server does NOT pre-sort. Sort client-side before rendering:
3814
+ * `categories.sort((a, b) => a.order - b.order)`.
3815
+ *
3816
+ * **How store items join to categories**
3817
+ * Each `TStoreItem` exposes `category_ids: number[]` (zero-or-more, many-to-many).
3818
+ * A single store item can belong to multiple categories, or to none. To render a
3819
+ * "category → items" view:
3820
+ * `items.filter(i => i.category_ids.includes(cat.id))`.
3821
+ *
3822
+ * **Uncategorized items**
3823
+ * Items with `category_ids: []` are NOT represented by a synthetic "Other" entry
3824
+ * in the response. The convention used by the reference store UI is to synthesize
3825
+ * a client-side bucket (e.g. labelled "General") for items that have no category, OR
3826
+ * for items whose `category_ids` reference a category that is no longer returned by
3827
+ * this method (e.g. archived since the item was tagged). If you choose to render
3828
+ * such a synthetic bucket, place it first (the reference UI uses an `order: -1`
3829
+ * convention so it precedes all operator-defined categories).
3830
+ *
3831
+ * **Empty categories**
3832
+ * A category may be returned even if zero items currently reference it (since this
3833
+ * method does not join against items). The reference store UI hides such empty
3834
+ * categories from its tab strip — replicate that filter if you don't want to render
3835
+ * tabs that lead to empty content panels.
3836
+ *
3837
+ * **Refresh cadence / cache**
3838
+ * - The SDK caches the response for 30 seconds. Subsequent calls within that window
3839
+ * return the cached array without a network round-trip.
3840
+ * - The cache is NOT invalidated by store-item push events or purchases — operator-driven
3841
+ * category changes propagate at the next 30 s cache miss.
3842
+ * - Cache is fully cleared on login / logout.
3843
+ * - In **visitor mode**, additional server-side caching applies on top of the SDK cache;
3844
+ * worst-case staleness for an edited/added category is on the order of a few minutes
3845
+ * for anonymous viewers.
3846
+ *
3847
+ * **Idempotency**: safe. Read-only metadata fetch. Cache de-duplicates concurrent calls.
3848
+ *
3849
+ * **Side effects**: none — pure metadata read.
3850
+ *
3851
+ * **UI guidance**: see [UI Guide — `getStoreCategories`](../../docs/ui/store/UIGuide_getStoreCategories.md).
3852
+ *
3853
+ * @returns Array of `{ id, name, order }`. Empty array if no active categories are
3854
+ * configured for the label. `name` is pre-translated. Order is NOT applied — caller
3855
+ * must sort.
3856
+ *
3857
+ * @example
3858
+ * ```ts
3859
+ * const [categories, items] = await Promise.all([
3860
+ * window._smartico.api.getStoreCategories(),
3861
+ * window._smartico.api.getStoreItems(),
3862
+ * ]);
3863
+ *
3864
+ * if (categories.length === 0) {
3865
+ * console.log('[smartico] no store categories configured — render items as a flat grid without tabs:', items.length);
3866
+ * } else {
3867
+ * // Sort categories; build per-category item buckets; hide empty categories
3868
+ * const sorted = [...categories].sort((a, b) => a.order - b.order);
3869
+ * const tabs = sorted
3870
+ * .map(cat => ({ cat, items: items.filter(i => i.category_ids?.includes(cat.id)) }))
3871
+ * .filter(t => t.items.length > 0);
3872
+ *
3873
+ * // Optional: synthesize an "Other" bucket for items with no (or unknown) category
3874
+ * const knownIds = new Set(categories.map(c => c.id));
3875
+ * const orphans = items.filter(i => !i.category_ids?.length || !i.category_ids.some(id => knownIds.has(id)));
3876
+ * if (orphans.length > 0) {
3877
+ * console.log('[smartico] uncategorized store items — render a client-synthesized "Other" tab at the front of the tab strip:', orphans.length);
3878
+ * }
3879
+ *
3880
+ * console.log('[smartico] render store tabs in this order with these counts:', tabs.map(t => `${t.cat.name} (${t.items.length})`));
3881
+ * }
3882
+ *
3883
+ * // Visitor mode — same shape, language driven by vapi(lang) argument
3884
+ * const visitorCats = await window._smartico.vapi('DE').getStoreCategories();
3885
+ * console.log('[smartico] visitor-mode store categories (names translated to DE):', visitorCats);
2623
3886
  * ```
2624
3887
  */
2625
3888
  async getStoreCategories() {
2626
3889
  return OCache.use(onUpdateContextKey.StoreCategories, ECacheContext.WSAPI, () => this.api.storeGetCategoriesT(this.userExtId), CACHE_DATA_SEC);
2627
3890
  }
2628
3891
  /**
2629
- * Returns purchased items based on the provided parameters. "Limit" and "offset" indicate the range of items to be fetched.
2630
- * The maximum number of items per request is limited to 20.
2631
- * You can leave this params empty and by default it will return list of purchased items ranging from 0 to 20.
2632
- * The returned store items are cached for 30 seconds. But you can pass the onUpdate callback as a parameter.
2633
- * Note that each time you call getStorePurchasedItems with a new onUpdate callback, the old one will be overwritten by the new one.
2634
- * The onUpdate callback will be called on purchase of the store item and the last 20 items will be passed to it.
2635
- *
2636
- * **Example**:
3892
+ * Fetches the authenticated user's **store purchase history** — a paginated,
3893
+ * newest-first list of past store purchases (one row per redemption).
3894
+ *
3895
+ * Designed for a "My Purchases" / "Order History" screen. Each entry carries
3896
+ * the same descriptive fields as the catalog card plus three history-only
3897
+ * fields: `purchase_ts` (when the purchase happened), `purchase_points_amount`
3898
+ * (the amount actually paid at that time — a price snapshot, not the
3899
+ * current `price`), and `purchase_type` (`'points' | 'gems' | 'diamonds'`).
3900
+ *
3901
+ * @remarks
3902
+ * **Preconditions**
3903
+ * - User must be authenticated. Visitor mode is not supported (the SDK only
3904
+ * exposes this method on the authenticated `_smartico.api`, not on
3905
+ * `_smartico.vapi`).
3906
+ * - No prerequisite calls — calling without first fetching
3907
+ * {@link getStoreItems} or {@link getStoreCategories} is fine.
3908
+ *
3909
+ * **Pagination semantics**
3910
+ * - **Offset-based**, newest-first. The server orders rows by purchase
3911
+ * timestamp **descending** and the SDK does NOT re-sort — items arrive
3912
+ * ready to render in display order.
3913
+ * - `limit` — page size. **The SDK default is 20.** There is no server-side
3914
+ * hard cap, but 20 matches what the default Smartico UI uses end-to-end; the
3915
+ * protocol and storage layers were sized for this page size. Treat `20`
3916
+ * as the conventional/recommended value; passing a much larger limit will
3917
+ * return more rows but is not the supported integration pattern.
3918
+ * Avoid `limit <= 0` — the server peeks `limit + 1` rows internally, so
3919
+ * `limit = 0` returns 1 row with a misleading "has more" signal, and
3920
+ * negative values produce undefined results.
3921
+ * - `offset` — number of items to skip from the start. The SDK default is
3922
+ * `0` (first page). For load-more pagination, pass
3923
+ * `offset = (previously-loaded items).length` on each subsequent call.
3924
+ * - **No `hasMore` on the returned array.** `TStoreItem[]` is a flat list;
3925
+ * the underlying protocol response carries `hasMore` but it is not
3926
+ * surfaced through this method. Two ways for consumers to know they
3927
+ * reached the end:
3928
+ * 1. The returned array is shorter than `limit` (definitive end), or
3929
+ * 2. A subsequent call at `offset = previous.length` returns `[]`.
3930
+ * Either is reliable.
3931
+ * - No date-range / category / status filter parameters — pagination is the
3932
+ * only server-side slicing. Apply any further filtering client-side.
3933
+ *
3934
+ * **No per-item fulfillment status**
3935
+ * - History entries do NOT carry a `delivered` / `pending` / `cancelled` /
3936
+ * `refunded` field. The list contains rows for committed purchase
3937
+ * attempts; the server query does not filter by transaction-status, so in
3938
+ * practice the rows that appear are completed purchases (failed
3939
+ * transactions roll back and don't write a row). Treat every item as a
3940
+ * completed transaction unless a future SDK release adds an explicit
3941
+ * status field.
3942
+ *
3943
+ * **Bonus-type entries**
3944
+ * - When the user redeemed a bonus item, the entry's `type` field surfaces
3945
+ * as `'bonus'`. **Known limitation:** the SDK transform currently maps
3946
+ * both `Bonus` and `Tangible` server-side item types to the same string
3947
+ * `'bonus'` — so the `type` field on history rows cannot distinguish a
3948
+ * bonus redemption from a tangible-prize redemption. The actual bonus
3949
+ * payload (wallet credit, free spins, etc.) is dispatched by the server
3950
+ * asynchronously and is NOT embedded in the history entry — consumers
3951
+ * that need bonus details should subscribe to bonus push events
3952
+ * separately rather than reading them from this list.
3953
+ * - History rows render identically across types — there is no deep-link to
3954
+ * the bonus wallet or re-claim affordance on the row itself.
3955
+ *
3956
+ * **Per-row data**
3957
+ * Each row uses the `TStoreItem` shape but populates four history-only
3958
+ * fields not present on `getStoreItems` output. `purchase_ts` is the
3959
+ * Unix-ms timestamp of the purchase — format with
3960
+ * `new Date(purchase_ts).toLocaleString()` in the user's local timezone;
3961
+ * no relative labels ("Today" / "Yesterday") are produced by the SDK.
3962
+ * `purchase_points_amount` is the amount actually paid at the time of
3963
+ * purchase — a snapshot of the historical price, which may differ from
3964
+ * the item's current `price`. `purchase_type` is the currency actually
3965
+ * charged (`'points' | 'gems' | 'diamonds'`); render the matching
3966
+ * balance icon next to the amount. The convenience booleans
3967
+ * `purchased_today` / `purchased_this_week` / `purchased_this_month` are
3968
+ * computed client-side from the consumer's local clock — useful for
3969
+ * grouping rows under "Today" / "This Week" headers.
3970
+ *
3971
+ * **Catalog fields on a history row**
3972
+ * The promotional fields (`ribbon`, `discounted_price`,
3973
+ * `discount_price_ribbon`, `custom_ribbon_image`, `active_till_date`)
3974
+ * reflect the CURRENT catalog state of the item, NOT its state at
3975
+ * purchase time — the default Smartico UI hides them on history rows to
3976
+ * avoid implying that the historical purchase carried the current
3977
+ * promotional banner. Descriptive marketing fields (`description`,
3978
+ * `hint_text`, `related_games`) are typically suppressed on a compact
3979
+ * history row. The current-purchasability fields `can_buy` and `pool`
3980
+ * reflect now, not then — useful only if you render a "Buy Again"
3981
+ * affordance on the row. One implementation gotcha: unlike
3982
+ * `getStoreItems`, this method's transform does NOT auto-parse JSON
3983
+ * strings in `custom_data` — call `JSON.parse()` yourself if you store
3984
+ * JSON there.
3985
+ *
3986
+ * **Cache & refresh**
3987
+ * - The SDK caches each `(limit, offset)` page separately for 30 seconds.
3988
+ * Repeated calls within that window resolve from cache without a
3989
+ * network round-trip.
3990
+ * - The cache is invalidated and `onUpdate` (when registered) is invoked
3991
+ * automatically after a successful {@link buyStoreItem} call — the SDK
3992
+ * re-fetches the same page parameters that were registered together with
3993
+ * the current `onUpdate` callback and pushes the fresh array.
3994
+ * - Multi-tab caveat: if the user has the same brand open in multiple
3995
+ * tabs, **all open tabs** receive the `onUpdate` event on a purchase,
3996
+ * not only the tab that initiated the buy. The event is scoped to the
3997
+ * current authenticated user — a different user's purchase never fires
3998
+ * this callback.
3999
+ * - Only ONE `onUpdate` callback can be registered at a time. Each call to
4000
+ * `getStorePurchasedItems({ onUpdate })` overwrites the previous handler.
4001
+ *
4002
+ * **Idempotency**: safe. Pure read.
4003
+ *
4004
+ * **Side effects**: none — read-only.
4005
+ *
4006
+ * **UI guidance**: see [UI Guide — `getStorePurchasedItems`](../../docs/ui/store/UIGuide_getStorePurchasedItems.md).
4007
+ *
4008
+ * @param params - Optional pagination + callback bag.
4009
+ * @param params.limit - Page size. Defaults to `20`. Use `20` as the
4010
+ * conventional value (matches the default Smartico UI end-to-end). Avoid
4011
+ * non-positive values.
4012
+ * @param params.offset - Number of rows to skip from the newest end.
4013
+ * Defaults to `0`. For load-more pagination pass the current array
4014
+ * length.
4015
+ * @param params.onUpdate - Optional callback invoked after a successful
4016
+ * {@link buyStoreItem} elsewhere in the session. Receives the refreshed
4017
+ * page for the same `(limit, offset)` used on the call that registered
4018
+ * this callback. Only ONE callback can be registered; each new call
4019
+ * overwrites the previous one. Pass a stable handler or
4020
+ * re-register intentionally on screen entry.
4021
+ *
4022
+ * @returns Promise resolving to an array of `TStoreItem` history entries
4023
+ * in newest-first order. Empty array if the user has no purchases (or
4024
+ * the requested `offset` is beyond the end). Each entry carries
4025
+ * `purchase_ts`, `purchase_points_amount`, `purchase_type`, and the
4026
+ * `purchased_today/this_week/this_month` convenience flags in addition
4027
+ * to the standard item fields.
4028
+ *
4029
+ * @example
4030
+ * ```ts
4031
+ * // Initial load + subscribe to refresh-on-purchase.
4032
+ * const handleHistoryUpdate = (items: TStoreItem[]) => {
4033
+ * console.log(
4034
+ * '[smartico] purchase history refreshed — re-render the history list with these',
4035
+ * items.length,
4036
+ * 'items (newest first):',
4037
+ * items,
4038
+ * );
4039
+ * };
4040
+ *
4041
+ * try {
4042
+ * const firstPage = await _smartico.api.getStorePurchasedItems({
4043
+ * limit: 20,
4044
+ * offset: 0,
4045
+ * onUpdate: handleHistoryUpdate,
4046
+ * });
4047
+ *
4048
+ * if (firstPage.length === 0) {
4049
+ * console.log('[smartico] no purchase history — render the empty state ("No purchases yet")');
4050
+ * } else {
4051
+ * console.log(
4052
+ * '[smartico] render',
4053
+ * firstPage.length,
4054
+ * 'history rows (newest first). Show "Load more" button if length === 20:',
4055
+ * firstPage,
4056
+ * );
4057
+ *
4058
+ * // Inspect the first entry's history-only fields.
4059
+ * const first = firstPage[0];
4060
+ * console.log(
4061
+ * '[smartico] most-recent purchase: name=', first.name,
4062
+ * 'paid', first.purchase_points_amount, first.purchase_type,
4063
+ * 'at', new Date(first.purchase_ts ?? 0).toLocaleString(),
4064
+ * '— format with toLocaleString (user-local time) on the row.',
4065
+ * );
4066
+ * }
4067
+ *
4068
+ * // Load-more pattern: fetch page 2 once the user scrolls / clicks "Load more".
4069
+ * if (firstPage.length === 20) {
4070
+ * const nextPage = await _smartico.api.getStorePurchasedItems({
4071
+ * limit: 20,
4072
+ * offset: firstPage.length,
4073
+ * });
4074
+ * const isLastPage = nextPage.length < 20;
4075
+ * console.log(
4076
+ * '[smartico] appended page 2 —',
4077
+ * nextPage.length,
4078
+ * 'rows.',
4079
+ * isLastPage ? 'Hide "Load more" — end reached.' : 'Keep "Load more" visible.',
4080
+ * );
4081
+ * }
4082
+ * } catch (e) {
4083
+ * console.error(
4084
+ * '[smartico] failed to load purchase history — show a generic error placeholder or retry with backoff:',
4085
+ * e,
4086
+ * );
4087
+ * }
2637
4088
  * ```
2638
- * _smartico.api.getStorePurchasedItems().then((result) => {
2639
- * console.log(result);
2640
- * });
2641
- * ```
2642
- *
2643
- * **Visitor mode: not supported**
2644
4089
  */
2645
4090
  async getStorePurchasedItems({
2646
4091
  limit,
2647
4092
  offset,
2648
4093
  onUpdate
2649
4094
  } = {}) {
4095
+ const requestLimit = limit === undefined ? 20 : limit;
4096
+ const requestOffset = offset === undefined ? 0 : offset;
4097
+ const cacheKey = this.storeHistoryCacheKey(requestLimit, requestOffset);
2650
4098
  if (onUpdate) {
2651
4099
  this.onUpdateCallback.set(onUpdateContextKey.StoreHistory, onUpdate);
4100
+ this.storeHistoryOnUpdateParams = {
4101
+ limit: requestLimit,
4102
+ offset: requestOffset
4103
+ };
4104
+ }
4105
+ return OCache.use(cacheKey, ECacheContext.WSAPI, () => this.api.storeGetPurchasedItemsT(this.userExtId, requestLimit, requestOffset), CACHE_DATA_SEC);
4106
+ }
4107
+ async updateStorePurchasedItems() {
4108
+ const payload = await this.api.storeGetPurchasedItemsT(this.userExtId, this.storeHistoryOnUpdateParams.limit, this.storeHistoryOnUpdateParams.offset);
4109
+ const cacheKey = this.storeHistoryCacheKey(this.storeHistoryOnUpdateParams.limit, this.storeHistoryOnUpdateParams.offset);
4110
+ OCache.set(cacheKey, payload, ECacheContext.WSAPI);
4111
+ const onUpdate = this.onUpdateCallback.get(onUpdateContextKey.StoreHistory);
4112
+ if (onUpdate) {
4113
+ onUpdate(payload);
2652
4114
  }
2653
- return OCache.use(onUpdateContextKey.StoreHistory, ECacheContext.WSAPI, () => this.api.storeGetPurchasedItemsT(this.userExtId, limit, offset), CACHE_DATA_SEC);
2654
4115
  }
4116
+ async updateStoreItems() {
4117
+ const payload = await this.api.storeGetItemsT(this.userExtId);
4118
+ this.updateEntity(onUpdateContextKey.StoreItems, payload);
4119
+ }
4120
+ }
4121
+
4122
+ /** @group Tournaments */
4123
+ class WSAPITournaments extends WSAPIStore {
2655
4124
  /**
2656
- * Returns missions & badges categories
2657
- *
2658
- * **Example**:
2659
- * ```
2660
- * _smartico.api.getAchCategories().then((result) => {
2661
- * console.log(result);
4125
+ * Returns the list of tournaments currently visible to the user, scoped
4126
+ * server-side to what the user's segments qualify them to see. Optionally
4127
+ * subscribes to live updates via `onUpdate`: the callback is invoked with
4128
+ * the full refreshed list whenever the user registers in a tournament.
4129
+ *
4130
+ * The returned list is the canonical source of truth for tournament
4131
+ * lobby state (`is_user_registered`, `registration_status`,
4132
+ * `is_in_progress`, `is_can_register`, etc.). Consumers should NOT
4133
+ * mutate it optimistically — always wait for the `onUpdate` callback
4134
+ * (or a fresh fetch after the 30 s cache window) to observe new state.
4135
+ *
4136
+ * @remarks
4137
+ * **Subscription model (`onUpdate`)**
4138
+ * The callback receives the FULL refreshed list (never a diff/patch).
4139
+ * Each subsequent call to `getTournamentsList({ onUpdate })` REPLACES
4140
+ * the prior callback — only one active subscriber at a time. Pass
4141
+ * `onUpdate: undefined` (or omit it) to keep the prior callback in
4142
+ * place; the callback is never auto-cleared.
4143
+ *
4144
+ * **Update triggers** — the callback fires when:
4145
+ *
4146
+ * 1. `registerInTournament(...)` resolves on this connection (any
4147
+ * `err_code`). The refreshed list reflects the new
4148
+ * `is_user_registered` / `registration_status` /
4149
+ * `registration_count` on the affected tournament.
4150
+ *
4151
+ * Does NOT fire for: tournament lifecycle transitions (start, finish,
4152
+ * cancellation), score updates, other users' registrations, or
4153
+ * operator-side BO edits. Those changes surface only on the next
4154
+ * cache miss (after the 30 s TTL) — poll manually if your UI needs
4155
+ * sub-30s lifecycle freshness.
4156
+ *
4157
+ * **Server-side filtering** (what's excluded before the SDK sees it)
4158
+ * The server filters by visibility segment and entry segment, removes
4159
+ * tournaments past their post-finish display window, and excludes
4160
+ * tournaments not yet published. SDK consumers receive only items
4161
+ * the user is eligible to see; no client-side gating is required.
4162
+ *
4163
+ * **Reading state from the returned item**
4164
+ * Drive list bucketing and CTA labels from the SDK-computed booleans
4165
+ * (`is_active`, `is_can_register`, `is_cancelled`, `is_finished`,
4166
+ * `is_in_progress`, `is_upcoming`), NOT from raw `start_time` /
4167
+ * `end_time` comparisons — the booleans encode the canonical
4168
+ * lifecycle states and already reflect server-side rules around
4169
+ * qualifying scores and late registration. `is_user_registered` is
4170
+ * independent of `is_can_register` — a registered user has
4171
+ * `is_user_registered: true` and `is_can_register: false`. The `me`
4172
+ * block (if present) carries the user's own leaderboard position and
4173
+ * score; it's undefined for unregistered users, users without any
4174
+ * recorded score, and visitor-mode sessions. The `prize_pool_short`
4175
+ * field carries the operator-supplied summary string ("$10,000",
4176
+ * "Mixed prizes") for compact card rendering; the full per-place
4177
+ * `prizes[]` breakdown is also populated here but is typically
4178
+ * rendered only in the detail view via
4179
+ * {@link getTournamentInstanceInfo}.
4180
+ *
4181
+ * **Cross-references**
4182
+ * Tournaments with `is_clan_based: true` group participants by clan —
4183
+ * see {@link getClanTournamentPlayers} for fetching the players list
4184
+ * of a specific clan within a clan tournament. The detailed lobby
4185
+ * view (with full player leaderboard and prize structure) comes from
4186
+ * {@link getTournamentInstanceInfo}. Tournaments with
4187
+ * `custom_section_id` belong to an operator-defined custom section
4188
+ * (resolve metadata via `getCustomSections()`).
4189
+ *
4190
+ * **Cache TTL**: the SDK caches the response for 30 seconds. Cache is
4191
+ * fully cleared on login / logout.
4192
+ *
4193
+ * **Idempotency**: safe. Read-only. Repeated calls within the cache
4194
+ * window return a deep-cloned cached array without a network
4195
+ * round-trip.
4196
+ *
4197
+ * **Side effects**: none — pure metadata read.
4198
+ *
4199
+ * **UI guidance**: see [UI Guide — `getTournamentsList`](../../docs/ui/tournaments/UIGuide_getTournamentsList.md).
4200
+ *
4201
+ * **Visitor mode**: supported. The same shape is returned, scoped to
4202
+ * the brand's public tournament list. Per-user fields (`me`,
4203
+ * `is_user_registered`, `registration_status`) are not meaningful.
4204
+ * The `onUpdate` callback is accepted but never fires (registration
4205
+ * mutations are not supported in visitor mode).
4206
+ *
4207
+ * @param params Optional. Omit to fetch without subscribing.
4208
+ * @param params.onUpdate Callback invoked with the full refreshed
4209
+ * tournament list after every
4210
+ * {@link registerInTournament} round-trip on
4211
+ * this connection. Each call overwrites the
4212
+ * prior callback. Never fires in visitor mode.
4213
+ * @returns Promise resolving to the array of
4214
+ * tournaments visible to the user. Empty
4215
+ * array if no tournaments are visible.
4216
+ *
4217
+ * @example
4218
+ * ```ts
4219
+ * const tournaments = await window._smartico.api.getTournamentsList({
4220
+ * onUpdate: (refreshed) => {
4221
+ * console.log('[smartico] tournament list refreshed — re-render the lobby UI from this array, do not merge with prior state:', refreshed);
4222
+ * const justRegistered = refreshed.filter(t => t.is_user_registered);
4223
+ * console.log('[smartico] user is now registered in these tournaments:', justRegistered.map(t => t.name));
4224
+ * },
2662
4225
  * });
2663
- * ```
2664
4226
  *
2665
- * **Example in the Visitor mode**:
2666
- * ```
2667
- * _smartico.vapi('EN').getAchCategories().then((result) => {
2668
- * console.log(result);
2669
- * });
4227
+ * // Bucket items by lifecycle state for a tabbed UI.
4228
+ * const live = tournaments.filter(t => t.is_in_progress);
4229
+ * const upcoming = tournaments
4230
+ * .filter(t => t.is_upcoming && !t.is_user_registered)
4231
+ * .sort((a, b) => a.start_time - b.start_time);
4232
+ * const mine = tournaments.filter(t => t.is_user_registered);
4233
+ * const finished = tournaments.filter(t => t.is_finished);
4234
+ *
4235
+ * console.log('[smartico] render lobby tabs with these counts: live=', live.length,
4236
+ * 'upcoming=', upcoming.length, 'mine=', mine.length, 'finished=', finished.length);
4237
+ *
4238
+ * // Featured tournament: pin to position 0 of the Overview/Top tabs.
4239
+ * const featured = tournaments.find(t => t.is_featured && !t.is_cancelled);
4240
+ * if (featured) {
4241
+ * console.log('[smartico] featured tournament — pin to position 0 of the Overview tab:', featured.name);
4242
+ * }
4243
+ *
4244
+ * // Visitor mode: onUpdate accepted but never fires; re-poll if needed.
4245
+ * // const visitorList = await window._smartico.vapi('EN').getTournamentsList();
2670
4246
  * ```
2671
- *
2672
- * */
2673
- async getAchCategories() {
2674
- return OCache.use(onUpdateContextKey.AchCategories, ECacheContext.WSAPI, () => this.api.achGetCategoriesT(this.userExtId), CACHE_DATA_SEC);
4247
+ */
4248
+ async getTournamentsList({
4249
+ onUpdate
4250
+ } = {}) {
4251
+ if (onUpdate) {
4252
+ this.onUpdateCallback.set(onUpdateContextKey.TournamentList, onUpdate);
4253
+ }
4254
+ return OCache.use(onUpdateContextKey.TournamentList, ECacheContext.WSAPI, () => this.api.tournamentsGetLobbyT(this.userExtId), CACHE_DATA_SEC);
2675
4255
  }
2676
4256
  /**
2677
- * Returns list of custom sections
2678
- *
2679
- * **Example**:
2680
- * ```
2681
- * _smartico.api.getCustomSections().then((result) => {
2682
- * console.log(result);
2683
- * });
4257
+ * Returns the full detail of a single tournament instance — adds the
4258
+ * player leaderboard, the full prize structure, and (for clan
4259
+ * tournaments) the clan leaderboard and per-clan prize structure on
4260
+ * top of everything already in `TTournament`. Use this to power a
4261
+ * tournament detail / lobby screen after the user picks an item from
4262
+ * {@link getTournamentsList}.
4263
+ *
4264
+ * Live leaderboard updates require manual re-fetching — there is no
4265
+ * subscription model, no push event, and no client-side cache (each
4266
+ * call hits the server). The default Smartico UI polls every 3 seconds
4267
+ * while the detail view is open.
4268
+ *
4269
+ * @remarks
4270
+ * **Preconditions**
4271
+ * Pass a valid `tournamentInstanceId` read from `TTournament.instance_id`
4272
+ * on an item returned by {@link getTournamentsList}. The method works
4273
+ * standalone — calling `getTournamentsList()` first is not required by
4274
+ * the SDK, but is the only stable source of valid IDs.
4275
+ *
4276
+ * **Refresh model**
4277
+ * - **No subscription.** This is a one-shot promise; call again to
4278
+ * refresh.
4279
+ * - **No client cache.** Every call sends a network request and
4280
+ * returns the latest server snapshot. Safe to poll on an interval —
4281
+ * the default Smartico UI uses 3 s while the detail view is visible.
4282
+ * - **No push event** invalidates or refreshes detail state. Score
4283
+ * changes, registration count changes, lifecycle transitions — all
4284
+ * require a fresh `getTournamentInstanceInfo` call.
4285
+ *
4286
+ * **Returned shape — beyond `TTournament`**
4287
+ * `TTournamentDetailed` extends `TTournament` with the following
4288
+ * additional data: a `players[]` leaderboard sorted server-side by
4289
+ * score (only members with at least one recorded score); the user's
4290
+ * own `me` block carrying their rank and score (undefined when not
4291
+ * registered or when the user has no score yet); a full per-place
4292
+ * `prizes[]` array (place range, type, points / gems / diamonds
4293
+ * amounts, image, name); and `related_games[]` if the operator has
4294
+ * associated games with this tournament. For clan tournaments
4295
+ * (`is_clan_based === true`), the response also carries
4296
+ * `clan_leaderboard[]` (ranked clans with `total_score` and
4297
+ * `contributing_members`), `clan_prize_structure[]` (per-clan-place
4298
+ * prize tiers — Fixed vs Dynamic, with ScoreWeighted /
4299
+ * EqualSplit distribution for Dynamic prizes), `user_clan_id` (the
4300
+ * user's own clan), and the advisory `user_position_in_clan` /
4301
+ * `user_score_in_clan` fields.
4302
+ *
4303
+ * **Idempotency**: safe. Read-only. Each call returns the latest
4304
+ * server snapshot.
4305
+ *
4306
+ * **Side effects**: none — pure metadata read.
4307
+ *
4308
+ * **UI guidance**: see [UI Guide — `getTournamentInstanceInfo`](../../docs/ui/tournaments/UIGuide_getTournamentInstanceInfo.md).
4309
+ *
4310
+ * **Visitor mode**: not supported. Use {@link getTournamentsList} for
4311
+ * the public lobby list in visitor mode; the detail endpoint requires
4312
+ * an authenticated session.
4313
+ *
4314
+ * @param tournamentInstanceId The `instance_id` from a `TTournament`
4315
+ * returned by {@link getTournamentsList}.
4316
+ * @returns Promise resolving to `TTournamentDetailed`.
4317
+ * Rejects if the instance ID is invalid
4318
+ * or the user lacks visibility for it.
4319
+ *
4320
+ * @example
4321
+ * ```ts
4322
+ * const tournaments = await window._smartico.api.getTournamentsList();
4323
+ * const tournament = tournaments[0];
4324
+ *
4325
+ * console.log('[smartico] loading detail view for tournament', tournament.instance_id);
4326
+ * const detail = await window._smartico.api.getTournamentInstanceInfo(tournament.instance_id);
4327
+ *
4328
+ * console.log('[smartico] render detail with', detail.players?.length ?? 0,
4329
+ * 'players,', detail.prizes?.length ?? 0, 'prize tiers');
4330
+ *
4331
+ * if (detail.is_clan_based && detail.clan_leaderboard) {
4332
+ * console.log('[smartico] clan tournament — render clan leaderboard tab with',
4333
+ * detail.clan_leaderboard.length, 'clans');
4334
+ * if (detail.user_clan_id != null) {
4335
+ * console.log('[smartico] user belongs to clan', detail.user_clan_id,
4336
+ * '— highlight that row in the clan leaderboard');
4337
+ * }
4338
+ * }
4339
+ *
4340
+ * // Live leaderboard updates — poll every 3 seconds while the detail view is open.
4341
+ * const pollId = setInterval(async () => {
4342
+ * try {
4343
+ * const fresh = await window._smartico.api.getTournamentInstanceInfo(tournament.instance_id);
4344
+ * console.log('[smartico] detail refreshed — re-render player + clan leaderboards from this new snapshot:', fresh);
4345
+ * } catch (e) {
4346
+ * console.error('[smartico] tournament detail poll failed — keep showing last snapshot, retry on next tick:', e);
4347
+ * }
4348
+ * }, 3_000);
4349
+ * // clearInterval(pollId) when the detail view closes.
4350
+ *
4351
+ * if (detail.me) {
4352
+ * console.log('[smartico] current user is rank', detail.me.position,
4353
+ * 'with', detail.me.scores, 'points — render the sticky "me" panel below the leaderboard');
4354
+ * }
2684
4355
  * ```
2685
- *
2686
- * **Example in the Visitor mode**:
4356
+ */
4357
+ async getTournamentInstanceInfo(tournamentInstanceId) {
4358
+ return this.api.tournamentsGetInfoT(this.userExtId, tournamentInstanceId);
4359
+ }
4360
+ /**
4361
+ * Returns the ranked players of a specific clan within a clan-based
4362
+ * tournament instance. Used to drill down from the clan-leaderboard
4363
+ * tab on the tournament detail view into a single clan's member
4364
+ * roster.
4365
+ *
4366
+ * The server returns up to roughly 20 players (top-N by score
4367
+ * descending). Server-side ordering is already by `scores` DESC
4368
+ * (equivalent to `position` ASC) — no client-side re-sort is
4369
+ * required.
4370
+ *
4371
+ * @remarks
4372
+ * **Preconditions**
4373
+ * Pass a valid `tournamentInstanceId` (read from `TTournament.instance_id`
4374
+ * returned by {@link getTournamentsList}) and a `clanId` (read from
4375
+ * `TTournamentDetailed.clan_leaderboard[].clan_id` returned by
4376
+ * {@link getTournamentInstanceInfo}). The tournament should be
4377
+ * clan-based (`is_clan_based === true`); calling this against a
4378
+ * non-clan tournament typically returns an empty `players` array.
4379
+ *
4380
+ * **Refresh model**
4381
+ * - **No subscription.** This is a one-shot promise.
4382
+ * - **No client cache.** Every call hits the server.
4383
+ * - **No push event** refreshes the response. Live updates to clan
4384
+ * member rankings require a fresh call.
4385
+ * - The default Smartico UI fetches this once when the user opens the
4386
+ * clan-drill-down modal and does NOT refresh while the modal is
4387
+ * open. If your UI needs live updates, re-call on an interval; the
4388
+ * clan leaderboard at the parent level (via
4389
+ * {@link getTournamentInstanceInfo}) does refresh via its own
4390
+ * polling cadence.
4391
+ *
4392
+ * **Returned shape**
4393
+ * `TClanTournamentPlayers` carries the requested clan's identity
4394
+ * (`clan_id`, `clan_public_meta.name`, `clan_public_meta.image_url`)
4395
+ * alongside the `players[]` array — each entry has `position`,
4396
+ * `scores`, `public_username`, `clean_ext_user_id`, `avatar_id`,
4397
+ * `avatar_real_id`, the resolved `avatar_url`, `user_id`, and the
4398
+ * `is_me` flag identifying the current user's row. The `clan_id` and
4399
+ * `clan_public_meta` on the response echo the input clan — the
4400
+ * default Smartico UI ignores them and renders the header from the
4401
+ * caller-side `ClanLeaderboardEntry` object, but they're available
4402
+ * for consumers that don't already have that context.
4403
+ *
4404
+ * **Username display**: prefer `clean_ext_user_id` when set, falling
4405
+ * back to `public_username`. The default Smartico UI uses
4406
+ * `clean_ext_user_id || public_username || ''`.
4407
+ *
4408
+ * **Idempotency**: safe. Read-only. Each call returns the latest
4409
+ * server snapshot.
4410
+ *
4411
+ * **Side effects**: none — pure metadata read.
4412
+ *
4413
+ * **UI guidance**: see [UI Guide — `getClanTournamentPlayers`](../../docs/ui/tournaments/UIGuide_getClanTournamentPlayers.md).
4414
+ *
4415
+ * **Visitor mode**: not supported.
4416
+ *
4417
+ * @param tournamentInstanceId The tournament `instance_id` from a
4418
+ * clan-based `TTournament`.
4419
+ * @param clanId The clan ID from
4420
+ * `TTournamentDetailed.clan_leaderboard[].clan_id`.
4421
+ * @returns Promise resolving to
4422
+ * `TClanTournamentPlayers`. An invalid
4423
+ * ID combination typically yields an
4424
+ * empty `players` array; the SDK does
4425
+ * not distinguish "no scores yet" from
4426
+ * "invalid IDs" — both manifest as an
4427
+ * empty list.
4428
+ *
4429
+ * @example
4430
+ * ```ts
4431
+ * const detail = await window._smartico.api.getTournamentInstanceInfo(tournamentInstanceId);
4432
+ * if (!detail.is_clan_based || !detail.clan_leaderboard) {
4433
+ * console.log('[smartico] not a clan tournament — skip clan drill-down');
4434
+ * return;
4435
+ * }
4436
+ *
4437
+ * // User clicked a clan row in the leaderboard.
4438
+ * const clickedClan = detail.clan_leaderboard[0];
4439
+ *
4440
+ * console.log('[smartico] opening clan drill-down — show modal skeleton while loading');
4441
+ * const result = await window._smartico.api.getClanTournamentPlayers(
4442
+ * tournamentInstanceId,
4443
+ * clickedClan.clan_id,
4444
+ * );
4445
+ *
4446
+ * if (result.players.length === 0) {
4447
+ * console.log('[smartico] clan has no ranked players yet — render empty state ("No leaders yet")');
4448
+ * } else {
4449
+ * console.log('[smartico] render clan member rows with .is-me highlight on the current user;',
4450
+ * result.players.length, 'players ranked');
4451
+ * const me = result.players.find(p => p.is_me);
4452
+ * if (me) {
4453
+ * console.log('[smartico] current user rank in this clan:', me.position, 'score:', me.scores);
4454
+ * }
4455
+ * }
2687
4456
  * ```
2688
- * _smartico.vapi('EN').getCustomSections().then((result) => {
2689
- * console.log(result);
4457
+ */
4458
+ async getClanTournamentPlayers(tournamentInstanceId, clanId) {
4459
+ return this.api.clanTournamentGetPlayers(this.userExtId, tournamentInstanceId, clanId);
4460
+ }
4461
+ /**
4462
+ * Registers the authenticated user in the specified tournament instance.
4463
+ * For tournaments with a buy-in cost, the user's balance is debited
4464
+ * synchronously before the response is returned; for free tournaments
4465
+ * the call is a simple membership add.
4466
+ *
4467
+ * A successful response (`err_code === 0`) means the registration row
4468
+ * exists server-side, the buy-in (if any) has been deducted, and the
4469
+ * SDK has already triggered a refresh of the tournament lobby cache.
4470
+ *
4471
+ * @remarks
4472
+ * **Preconditions**
4473
+ * Read the candidate from {@link getTournamentsList} and gate the call
4474
+ * on `is_can_register === true`. This composite flag already encodes:
4475
+ * not already registered, slot available
4476
+ * (`registration_count < players_max_count`), tournament status is
4477
+ * REGISTER (or STARTED with late-registration enabled), and the
4478
+ * registration type is not AUTO. The SDK forwards the call
4479
+ * unconditionally — calling without satisfying these will return one
4480
+ * of the error codes below.
4481
+ *
4482
+ * For clan tournaments (`is_clan_based === true`), the user must
4483
+ * already belong to a clan before calling. If they don't, prompt them
4484
+ * to pick a clan first via {@link joinClan}, then call
4485
+ * `registerInTournament` after the clan-join resolves. See "Error
4486
+ * codes" `1010` below for the server-side safety net.
4487
+ *
4488
+ * **Error codes** (in `err_code`, typed as {@link TournamentRegistrationError})
4489
+ * - `0` — success; the registration is persisted, buy-in deducted,
4490
+ * tournament lobby refresh fires.
4491
+ * - `1010` — `TOURNAMENT_USER_CANNOT_JOIN_WITHOUT_CLAN`: clan-based
4492
+ * tournament and the user has no clan. Surface the clan-pick flow
4493
+ * (e.g. open a clan-pick modal driven by {@link getClans} +
4494
+ * {@link joinClan}), then retry registration.
4495
+ * - `30001` — `TOURNAMENT_INSTANCE_NOT_FOUND`: instance ID is invalid
4496
+ * or the tournament was deleted. Refresh the lobby via
4497
+ * `getTournamentsList()` and hide the entry from the UI.
4498
+ * - `30002` — `TOURNAMENT_REGISTRATION_NOT_ENOUGH_POINTS`: insufficient
4499
+ * points balance for a points-cost tournament. Show an
4500
+ * insufficient-balance UI naming the deficit.
4501
+ * - `30003` — `TOURNAMENT_INSTANCE_NOT_IN_STATE`: tournament is no
4502
+ * longer in a registerable state (finished, cancelled, finalizing,
4503
+ * or not yet published with late registration disabled). Refresh
4504
+ * the lobby; the button should hide.
4505
+ * - `30004` — `TOURNAMENT_ALREADY_REGISTERED`: user is already
4506
+ * registered. Treat as idempotent success — hide the Register button
4507
+ * and refresh the lobby. Usually indicates a stale local state
4508
+ * (e.g. registered from another tab).
4509
+ * - `30005` — `TOURNAMENT_USER_DONT_MATCH_CONDITIONS`: user fails the
4510
+ * tournament's segment / entry conditions. Prefer
4511
+ * `tournament.segment_dont_match_message` (operator-supplied) over
4512
+ * `err_message` when both are available.
4513
+ * - `30006` — `TOURNAMENT_USER_NOT_REGISTERED`: anomalous on a fresh
4514
+ * register call; usually indicates a server-side race. Treat as a
4515
+ * generic transient error.
4516
+ * - `30007` — `TOURNAMENT_CANT_CHANGE_REGISTRATION_STATUS`: status
4517
+ * transition not allowed (e.g. re-registering after a finished /
4518
+ * cancelled tournament). Show a generic error; do not retry.
4519
+ * - `30008` — `TOURNAMENT_MAX_REGISTRATIONS_REACHED`: the tournament
4520
+ * filled up between the lobby fetch and the registration click.
4521
+ * Refresh the lobby — the button will hide on the refreshed item.
4522
+ * - `300010` — insufficient **gems** balance (gems-cost tournament).
4523
+ * Same UI handling as `30002` but for the gems currency.
4524
+ * - `300011` — insufficient **diamonds** balance (diamonds-cost
4525
+ * tournament). Same as `300010` but for diamonds.
4526
+ * - other non-zero — generic server error. Surface `err_message` if any.
4527
+ *
4528
+ * Note: clan-tournament registration can occasionally surface error
4529
+ * codes from the clan-join domain (e.g. `1011` — joined-after-start)
4530
+ * via the same response path. These are members of `JoinClanErrorCode`,
4531
+ * not `TournamentRegistrationError`. Branch on the numeric value.
4532
+ *
4533
+ * **Idempotency**: NOT idempotent. A second call on the same instance
4534
+ * returns `30004` once the first call has succeeded. The SDK does NOT
4535
+ * enforce an in-flight lock. The consumer MUST guard the call site
4536
+ * against double-clicks (set a local "registering" flag on click, clear
4537
+ * it on response).
4538
+ *
4539
+ * **Refresh after success (and after failure)**
4540
+ * The SDK automatically refreshes the tournament lobby cache on every
4541
+ * response (success OR error) and fires any `onUpdate` callback
4542
+ * registered via `getTournamentsList({ onUpdate })`. After
4543
+ * `err_code === 0`, the affected item's `is_user_registered` /
4544
+ * `registration_status` / `registration_count` reflect the new
4545
+ * registered state on the refreshed array. Note that
4546
+ * {@link getTournamentInstanceInfo} is NOT auto-refreshed — if the
4547
+ * detail screen is open, call it again manually.
4548
+ *
4549
+ * The user's balance (points / gems / diamonds) is exposed on the
4550
+ * user-properties channel and updates independently of this call's
4551
+ * response — subscribe to user-property updates separately if your UI
4552
+ * shows the balance.
4553
+ *
4554
+ * **Side effects** (consumer-observable on success)
4555
+ * - Balance debited synchronously by the buy-in amount in the
4556
+ * tournament's currency (`registration_cost_points`,
4557
+ * `registration_cost_gems`, or `registration_cost_diamonds`). For
4558
+ * free tournaments no deduction occurs.
4559
+ * - Server-side analytics events fire downstream
4560
+ * (`tournament_user_registered` for immediate-registration types;
4561
+ * `tournament_user_registration_pending` or
4562
+ * `tournament_registered_pending_qualification` for
4563
+ * manual-approval / qualification-based types).
4564
+ * - Any operator-configured on-join prizes are distributed (e.g.
4565
+ * welcome bonus). These surface via the relevant follow-up channels
4566
+ * (e.g. {@link getBonuses}).
4567
+ *
4568
+ * **UI guidance**: see [UI Guide — `registerInTournament`](../../docs/ui/tournaments/UIGuide_registerInTournament.md).
4569
+ *
4570
+ * **Visitor mode**: not supported. Calling from a visitor session
4571
+ * returns a generic error, not a typed `TournamentRegistrationError`.
4572
+ *
4573
+ * @param tournamentInstanceId The `instance_id` from `TTournament`
4574
+ * (NOT the template `tournament_id`).
4575
+ * @returns `{ err_code, err_message }`; success when `err_code === 0`
4576
+ * (or `30004` when treated as idempotent no-op).
4577
+ *
4578
+ * @example
4579
+ * ```ts
4580
+ * const tournaments = await window._smartico.api.getTournamentsList({
4581
+ * onUpdate: (refreshed) => console.log('[smartico] tournament list updated — re-render lobby from this array', refreshed),
2690
4582
  * });
4583
+ * const tournament = tournaments.find(t => t.instance_id === instanceId);
4584
+ *
4585
+ * if (!tournament) {
4586
+ * console.log('[smartico] tournament not in current lobby — refresh getTournamentsList and retry');
4587
+ * return;
4588
+ * }
4589
+ * if (!tournament.is_can_register) {
4590
+ * console.log('[smartico] tournament not currently registerable — keep CTA disabled');
4591
+ * return;
4592
+ * }
4593
+ *
4594
+ * // Clan tournament + user has no clan → resolve clan first.
4595
+ * if (tournament.is_clan_based) {
4596
+ * const userProfile = window._smartico.api.getUserProfile();
4597
+ * const userClanId = userProfile?.clan_id;
4598
+ * if (userClanId == null) {
4599
+ * console.log('[smartico] clan tournament — open the clan-pick modal first; after joinClan resolves, call registerInTournament again');
4600
+ * return;
4601
+ * }
4602
+ * }
4603
+ *
4604
+ * console.log('[smartico] registration starting — set in-flight flag, show loading dots on the Join button, keep modal open');
4605
+ * const r = await window._smartico.api.registerInTournament(tournament.instance_id);
4606
+ * console.log('[smartico] registration response received — clear in-flight flag');
4607
+ *
4608
+ * if (r.err_code === 0 || r.err_code === 30004) {
4609
+ * console.log('[smartico] registered (or was already) — close any modal, show a success toast; getTournamentsList onUpdate above will fire with the refreshed array');
4610
+ * } else if (r.err_code === 1010) {
4611
+ * console.log('[smartico] clan required — open the clan-pick modal again (this is the server-side safety net for a clan-leave race)');
4612
+ * } else if (r.err_code === 30002) {
4613
+ * console.error('[smartico] insufficient points — show insufficient-balance UI; deficit:', tournament.registration_cost_points);
4614
+ * } else if (r.err_code === 300010) {
4615
+ * console.error('[smartico] insufficient gems — show insufficient-balance UI; deficit:', tournament.registration_cost_gems);
4616
+ * } else if (r.err_code === 300011) {
4617
+ * console.error('[smartico] insufficient diamonds — show insufficient-balance UI; deficit:', tournament.registration_cost_diamonds);
4618
+ * } else if (r.err_code === 30005) {
4619
+ * console.error('[smartico] segment mismatch — prefer the operator-supplied message:', tournament.segment_dont_match_message || r.err_message);
4620
+ * } else if (r.err_code === 30008) {
4621
+ * console.error('[smartico] tournament full — auto-refresh will hide the button on the refreshed lobby item');
4622
+ * } else {
4623
+ * console.error('[smartico] registration failed — show a generic error toast with this message:', r.err_message);
4624
+ * }
2691
4625
  * ```
2692
- *
2693
- * */
2694
- async getCustomSections() {
2695
- return OCache.use(onUpdateContextKey.CustomSections, ECacheContext.WSAPI, () => this.api.customSectionsGetT(this.userExtId), CACHE_DATA_SEC);
4626
+ */
4627
+ async registerInTournament(tournamentInstanceId) {
4628
+ const r = await this.api.registerInTournament(this.userExtId, tournamentInstanceId);
4629
+ const o = {
4630
+ err_code: r.errCode,
4631
+ err_message: r.errMsg
4632
+ };
4633
+ return o;
4634
+ }
4635
+ async updateTournaments() {
4636
+ const payload = await this.api.tournamentsGetLobbyT(this.userExtId);
4637
+ this.updateEntity(onUpdateContextKey.TournamentList, payload);
2696
4638
  }
4639
+ }
4640
+
4641
+ /** @group Clans */
4642
+ class WSAPIClans extends WSAPITournaments {
2697
4643
  /**
2698
- * Returns the list of mini-games configured for the current user (not filtered by spin availability or Widget visibility).
2699
- * The returned list of mini-games is cached for 30 seconds. But you can pass the onUpdate callback as a parameter. Note that each time you call getMiniGames with a new onUpdate callback, the old one will be overwritten by the new one.
2700
- * The onUpdate callback will be called on available spin count change, if mini-game has increasing jackpot per spin or won prize is spin/jackpot and if max count of the available user spins equals one, also if the spins were issued to the user manually in the BO. Updated templates will be passed to onUpdate callback.
2701
- *
2702
- * **Example**:
2703
- * ```
2704
- * _smartico.api.getMiniGames().then((result) => {
2705
- * console.log(result);
4644
+ * Returns the active clans visible to the current user along with the
4645
+ * user's own membership state (`user_clan_id`, `cooldown_until`,
4646
+ * `join_date`). Use this to power a clan-picker / clan-browser screen.
4647
+ *
4648
+ * The returned `clans[]` is server-sorted by `rating_position` ASC
4649
+ * (1 = highest-ranked). The server has already filtered out archived
4650
+ * clans, clans the user fails segment conditions for, and (when any
4651
+ * clan has a non-zero score) clans with a zero rating score.
4652
+ *
4653
+ * @remarks
4654
+ * **Subscription model (`onUpdate`)**
4655
+ * The callback receives the FULL refreshed `TClans` payload (never a
4656
+ * diff/patch). Each subsequent call to `getClans({ onUpdate })`
4657
+ * REPLACES the prior callback. Pass `onUpdate: undefined` (or omit
4658
+ * it) to keep the prior callback in place; the callback is never
4659
+ * auto-cleared.
4660
+ *
4661
+ * **Update triggers** — note that, unlike most other subscription
4662
+ * methods in the SDK, this callback is **poll-driven only**. There
4663
+ * is NO server push that refreshes the clans list. The callback
4664
+ * fires when:
4665
+ *
4666
+ * 1. The 30-second cache TTL expires and a subsequent `getClans()`
4667
+ * call (from the consumer) triggers a fresh fetch.
4668
+ *
4669
+ * Consequence: clan-membership changes (other users joining your
4670
+ * clan, an operator-driven kick, a clan being archived) surface
4671
+ * only on the next consumer-driven fetch. If your UI needs
4672
+ * near-live state, poll `getClans()` on an interval; or re-call
4673
+ * after a {@link joinClan} resolves to pick up the new
4674
+ * `user_clan_id` immediately.
4675
+ *
4676
+ * **Reading state from the returned payload**
4677
+ * Drive list rendering from `clans[]` (already sorted). Identify
4678
+ * the user's own clan via `clan.clan_id === user_clan_id`
4679
+ * (`null` for clanless users). Detect an active switch-cooldown
4680
+ * with `cooldown_until !== null` — when set, the user cannot join
4681
+ * any clan until the cooldown expires, even clans they could
4682
+ * normally join. The cooldown is **user-level**, not per-clan.
4683
+ *
4684
+ * `cooldown_until` is an ISO 8601 UTC datetime string with no
4685
+ * timezone suffix (`"YYYY-MM-DDTHH:MM:SS"`). Parse as UTC:
4686
+ * `new Date(cooldown_until + 'Z')` or `moment.utc(cooldown_until)`.
4687
+ * Display in the user's local time. The default cooldown period
4688
+ * is configured per label by the operator (typically 7 days).
4689
+ *
4690
+ * `rating_position` is a global rank computed server-side across
4691
+ * all active clans in the label (1 = highest-rated). Because some
4692
+ * clans may be hidden by per-user segment visibility, positions
4693
+ * the user sees may skip (e.g. 1, 3, 7) — don't assume the array
4694
+ * spans a contiguous range.
4695
+ *
4696
+ * The `entry_fee_currency_type_id` enum on each clan uses values
4697
+ * `0` = points, `1` = gems, `2` = diamonds, `3` = free. Compare
4698
+ * `entry_fee_amount` against the user's matching balance (from
4699
+ * {@link getUserProfile}) to drive Join button affordability.
4700
+ *
4701
+ * **Cache TTL**: the SDK caches the response for 30 seconds. Cache
4702
+ * is fully cleared on login / logout.
4703
+ *
4704
+ * **Idempotency**: safe. Read-only. Repeated calls within the
4705
+ * cache window return a deep-cloned cached payload without a
4706
+ * network round-trip.
4707
+ *
4708
+ * **Side effects**: none — pure metadata read.
4709
+ *
4710
+ * **UI guidance**: see [UI Guide — `getClans`](../../docs/ui/clans/UIGuide_getClans.md).
4711
+ *
4712
+ * **Visitor mode**: not supported.
4713
+ *
4714
+ * @param params Optional. Omit to fetch without subscribing.
4715
+ * @param params.onUpdate Callback invoked with the full refreshed
4716
+ * `TClans` payload whenever the 30 s cache
4717
+ * expires and a subsequent fetch lands.
4718
+ * Each call to `getClans` overwrites the
4719
+ * prior callback.
4720
+ * @returns Promise resolving to `TClans`. Empty
4721
+ * `clans` array if no clans are visible.
4722
+ *
4723
+ * @example
4724
+ * ```ts
4725
+ * const result = await window._smartico.api.getClans({
4726
+ * onUpdate: (refreshed) => {
4727
+ * console.log('[smartico] clans payload refreshed (poll-driven; ~30 s cadence) — re-render the clan list from this payload:', refreshed);
4728
+ * },
2706
4729
  * });
2707
- * ```
2708
4730
  *
2709
- * **Example in the Visitor mode**:
2710
- * ```
2711
- * _smartico.vapi('EN').getMiniGames().then((result) => {
2712
- * console.log(result);
2713
- * });
4731
+ * console.log('[smartico] user is', result.user_clan_id == null ? 'clanless' : 'in clan ' + result.user_clan_id);
4732
+ *
4733
+ * // Cooldown handling — affects ALL clans, not a specific one.
4734
+ * if (result.cooldown_until) {
4735
+ * const cooldownEndsUtc = new Date(result.cooldown_until + 'Z');
4736
+ * console.log('[smartico] switch-cooldown active — disable all Join buttons until', cooldownEndsUtc.toLocaleString(),
4737
+ * '(local). Render the cooldown end on the disabled button label.');
4738
+ * }
4739
+ *
4740
+ * // Render clans — already sorted by rating_position ASC.
4741
+ * for (const clan of result.clans) {
4742
+ * const isMine = clan.clan_id === result.user_clan_id;
4743
+ * const isFull = clan.member_count >= clan.capacity_limit;
4744
+ *
4745
+ * if (isMine) {
4746
+ * console.log('[smartico] highlight clan', clan.clan_id, 'as "Your clan"; CTA label "Your clan", no-op');
4747
+ * } else if (result.cooldown_until) {
4748
+ * console.log('[smartico] clan', clan.clan_id, '— Join blocked by user cooldown; show disabled button with cooldown end date');
4749
+ * } else if (isFull) {
4750
+ * console.log('[smartico] clan', clan.clan_id, '— Clan full; show disabled "Clan is full" button');
4751
+ * } else {
4752
+ * const label = clan.entry_fee_amount === 0 || clan.entry_fee_currency_type_id === 3
4753
+ * ? 'Join free'
4754
+ * : `Join (${clan.entry_fee_amount} ${['Points','Gems','Diamonds'][clan.entry_fee_currency_type_id]})`;
4755
+ * console.log('[smartico] clan', clan.clan_id, '— render enabled Join button labelled', label);
4756
+ * }
4757
+ * }
4758
+ *
4759
+ * // After a successful joinClan(), re-call getClans manually — no push refresh.
4760
+ * // const r = await window._smartico.api.joinClan(targetClanId);
4761
+ * // if (r.errCode === 0) await window._smartico.api.getClans();
2714
4762
  * ```
2715
- *
2716
4763
  */
2717
- async getMiniGames({
4764
+ async getClans({
2718
4765
  onUpdate
2719
4766
  } = {}) {
2720
4767
  if (onUpdate) {
2721
- this.onUpdateCallback.set(onUpdateContextKey.Saw, onUpdate);
4768
+ this.onUpdateCallback.set(onUpdateContextKey.Clans, onUpdate);
2722
4769
  }
2723
- return OCache.use(onUpdateContextKey.Saw, ECacheContext.WSAPI, () => this.api.sawGetTemplatesT(this.userExtId), CACHE_DATA_SEC);
4770
+ return OCache.use(onUpdateContextKey.Clans, ECacheContext.WSAPI, () => this.api.clansGetListT(this.userExtId), CACHE_DATA_SEC);
2724
4771
  }
2725
4772
  /**
2726
- * Returns the list of mini-games based on the provided parameters. "Limit" and "offset" indicate the range of items to be fetched.
2727
- * The maximum number of items per request is limited to 20.
2728
- * You can leave this params empty and by default it will return list of mini-games ranging from 0 to 20.
2729
- * The returned list of mini-games history is cached for 30 seconds.
2730
- *
2731
- * **Example**:
4773
+ * Returns the full detail of a single clan — adds the ranked
4774
+ * `members[]` roster and a fresh `cooldown_until` on top of the same
4775
+ * fields exposed on a `TClan` list entry. Use this to power a clan
4776
+ * detail screen / popup after the user picks an item from
4777
+ * {@link getClans}.
4778
+ *
4779
+ * Each call is a fresh server round-trip — there is no client cache
4780
+ * and no push event refreshes detail state. Re-call to refresh.
4781
+ *
4782
+ * @remarks
4783
+ * **Preconditions**
4784
+ * Pass a valid `clanId` (typically read from
4785
+ * `TClans.clans[i].clan_id` returned by {@link getClans}). The
4786
+ * method works standalone — `getClans()` is not required first —
4787
+ * but is the only stable source of valid IDs.
4788
+ *
4789
+ * **Refresh model**
4790
+ * - **No subscription.** One-shot promise.
4791
+ * - **No client cache.** Every call sends a network request.
4792
+ * - **No push event** refreshes the detail. Member-join, kick, or
4793
+ * contribution-score changes require a fresh `getClanInfo` call.
4794
+ *
4795
+ * **Returned shape — beyond `TClan`**
4796
+ * `TClanInfo` adds two things on top of the same identity / capacity
4797
+ * / fee / rating fields you'd see on a `TClan` list entry:
4798
+ * 1. A ranked `members[]` array — each entry carries `user_id`,
4799
+ * `public_username`, `avatar_id` / `avatar_real_id`, the resolved
4800
+ * `avatar_url`, the member's `position` (rank within this clan),
4801
+ * `contribution_score`, the `is_me` flag identifying the current
4802
+ * user's row, and the optional `clean_ext_user_id`. The server
4803
+ * orders members by score DESC (i.e. `position` ASC); no
4804
+ * client-side re-sort is required.
4805
+ * 2. A fresh `cooldown_until` on the clan info itself. This is the
4806
+ * SAME user-level cooldown as `TClans.cooldown_until` (the
4807
+ * cooldown is global to the user, not per clan) — but `getClans`
4808
+ * returns a value cached for up to 30 s while `getClanInfo`
4809
+ * always returns the current value. If the list-cached cooldown
4810
+ * has just expired, this detail call will reflect it first.
4811
+ *
4812
+ * **Username display**: in the default Smartico UI, member rows use
4813
+ * `public_username`. Some surfaces (e.g. the tournament clan
4814
+ * drill-down) prefer `clean_ext_user_id` as the primary display
4815
+ * with `public_username` as fallback — pick the convention that
4816
+ * matches your product's identity model.
4817
+ *
4818
+ * **Idempotency**: safe. Read-only.
4819
+ *
4820
+ * **Side effects**: none — pure metadata read.
4821
+ *
4822
+ * **UI guidance**: see [UI Guide — `getClanInfo`](../../docs/ui/clans/UIGuide_getClanInfo.md).
4823
+ *
4824
+ * **Visitor mode**: not supported.
4825
+ *
4826
+ * @param clanId The clan ID from `TClans.clans[i].clan_id`.
4827
+ * @returns Promise resolving to `TClanInfo`. Rejects if the
4828
+ * clan ID is invalid or the user lacks visibility
4829
+ * for it.
4830
+ *
4831
+ * @example
4832
+ * ```ts
4833
+ * const result = await window._smartico.api.getClans();
4834
+ * const clan = result.clans[0];
4835
+ *
4836
+ * console.log('[smartico] loading detail for clan', clan.clan_id);
4837
+ * const detail = await window._smartico.api.getClanInfo(clan.clan_id);
4838
+ *
4839
+ * console.log('[smartico] render detail with', detail.members.length, 'members');
4840
+ *
4841
+ * // Find current user's row to power a sticky "me" footer.
4842
+ * const meRow = detail.members.find(m => m.is_me);
4843
+ * if (meRow) {
4844
+ * console.log('[smartico] current user rank in this clan:', meRow.position,
4845
+ * 'contribution:', meRow.contribution_score, '— render sticky my-member footer');
4846
+ * }
4847
+ *
4848
+ * // Resolve Join CTA state using the detail's cooldown (always fresh).
4849
+ * const isMyClan = detail.clan_id === result.user_clan_id;
4850
+ * const inCooldown = detail.cooldown_until != null;
4851
+ * const isFull = detail.member_count >= detail.capacity_limit;
4852
+ * if (isMyClan) {
4853
+ * console.log('[smartico] this is the user\'s clan — render disabled "Your clan" button');
4854
+ * } else if (inCooldown) {
4855
+ * const ends = new Date(detail.cooldown_until + 'Z');
4856
+ * console.log('[smartico] user is in clan-switch cooldown until', ends.toLocaleString(),
4857
+ * '— render disabled cooldown button');
4858
+ * } else if (isFull) {
4859
+ * console.log('[smartico] clan is full — render disabled "Clan is full" button');
4860
+ * } else {
4861
+ * console.log('[smartico] joinable — render enabled Join button');
4862
+ * }
2732
4863
  * ```
2733
- * _smartico.api.getMiniGamesHistory().then((result) => {
2734
- * console.log(result);
2735
- * });
4864
+ */
4865
+ async getClanInfo(clanId) {
4866
+ return this.api.clansGetInfoT(this.userExtId, clanId);
4867
+ }
4868
+ /**
4869
+ * Joins (or switches into) a clan on behalf of the current user. For
4870
+ * paid clans (`entry_fee_amount > 0`), the user's balance for the
4871
+ * matching currency is debited synchronously before the response
4872
+ * returns. If the user is already in a clan, this is treated as an
4873
+ * atomic clan-switch (deactivate old membership + insert new + debit
4874
+ * fee, all in one transaction — the user is never observably
4875
+ * clanless).
4876
+ *
4877
+ * A successful response (`errCode === 0`) means: membership is
4878
+ * persisted, the fee (if any) has been debited, the user's
4879
+ * switch-cooldown has restarted, and a clan-joined CRM event has
4880
+ * fired server-side.
4881
+ *
4882
+ * @remarks
4883
+ * **Result shape note**: `TClanJoinResult` uses `errCode` / `errMsg`
4884
+ * (camelCase) — different from most other SDK result types in this
4885
+ * library which use `err_code` / `err_message` (snake_case). Branch
4886
+ * on the camelCase keys when reading this method's result.
4887
+ *
4888
+ * **Preconditions**
4889
+ * Read the candidate from {@link getClans} and gate the call on
4890
+ * `clan.member_count < clan.capacity_limit`, `cooldown_until == null`
4891
+ * (on the `TClans` payload — the cooldown is user-level, not
4892
+ * per-clan), and the user's balance for `entry_fee_currency_type_id`
4893
+ * being at least `entry_fee_amount` (free clans bypass this check).
4894
+ * The SDK forwards the call unconditionally — calling without
4895
+ * satisfying these will most likely return one of the error codes
4896
+ * below.
4897
+ *
4898
+ * **Error codes** (in `errCode`, typed as {@link JoinClanErrorCode})
4899
+ * - `0` (`JOIN_CLAN_OK`) — success; membership persisted, fee
4900
+ * debited, cooldown restarted.
4901
+ * - `1000` (`JOIN_CLAN_INVALID_PARAMETERS`) — request body missing
4902
+ * `clan_id`. Should not occur via the SDK; treat as a programming
4903
+ * error.
4904
+ * - `1001` (`JOIN_CLAN_NOT_FOUND`) — clan doesn't exist for this
4905
+ * label, OR the clan is archived / in draft state. The server
4906
+ * merges "missing" and "archived" into this single code. Refresh
4907
+ * `getClans()` and remove the entry from the UI.
4908
+ * - `1002` (`JOIN_CLAN_FULL`) — capacity reached between the list
4909
+ * fetch and the join click (`member_count >= capacity_limit` at
4910
+ * the server's atomic check). Refresh the list — the button will
4911
+ * become "Clan is full".
4912
+ * - `1003` (`JOIN_CLAN_INSUFFICIENT_FUNDS`) — insufficient balance
4913
+ * for the clan's entry fee. A single code covers all three
4914
+ * currencies (points / gems / diamonds); read
4915
+ * `clan.entry_fee_currency_type_id` to identify which currency
4916
+ * the user is short on. Show an insufficient-balance UI naming
4917
+ * the deficit.
4918
+ * - `1004` (`JOIN_CLAN_SEGMENT_MISMATCH`) — user does not meet the
4919
+ * clan's entry segment or numeric conditions. There is no
4920
+ * client-side pre-guard for this — surface a gating message.
4921
+ * - `1005` (`JOIN_CLAN_USER_IS_NOT_IN_CLAN`) — this method NEVER
4922
+ * returns `1005`. The code is shared with the tournament
4923
+ * registration error space and fires only on
4924
+ * {@link registerInTournament} responses when a clan-based
4925
+ * tournament requires the user to be in a clan first.
4926
+ * - `1006` (`JOIN_CLAN_COOLDOWN_ACTIVE`) — user is inside the
4927
+ * switch-cooldown window from a previous clan join (default
4928
+ * 7 days, configurable per label). Surface the
4929
+ * `TClans.cooldown_until` expiry in the disabled CTA.
4930
+ * - `1007` (`JOIN_CLAN_ARCHIVED`) — defined on the public enum but
4931
+ * the server does NOT emit this code in practice (archived clans
4932
+ * are reported as `1001`). Handle it as a synonym for `1001`
4933
+ * defensively, but do not rely on it.
4934
+ * - `1011` (`JOIN_CLAN_JOINED_AFTER_TOURNAMENT_START`) — this
4935
+ * method does NOT return `1011` directly. The code fires on
4936
+ * {@link registerInTournament} responses when the user joined
4937
+ * their current clan after the tournament started AND already
4938
+ * has tournament scores.
4939
+ * - other non-zero — generic server error. Surface `errMsg` if any.
4940
+ *
4941
+ * **Idempotency**: NOT safe — and notably dangerous. Calling
4942
+ * `joinClan` with the user's CURRENT `clanId` does NOT short-circuit
4943
+ * server-side; the call is treated as a clan-switch to the same
4944
+ * clan. If the label's cooldown is non-zero (the default), the
4945
+ * second call returns `1006`. If the cooldown is configured to
4946
+ * zero, the second call will deduct the entry fee AGAIN, deactivate
4947
+ * the membership, and re-insert it. The SDK does NOT enforce an
4948
+ * in-flight lock. Consumers MUST guard the call site against
4949
+ * double-clicks (set a local "joining" flag on click, clear it on
4950
+ * response) AND short-circuit when the user is already in the
4951
+ * target clan.
4952
+ *
4953
+ * **Refresh after success (and after failure)**
4954
+ * The SDK does NOT automatically refresh the {@link getClans} cache
4955
+ * after a join. Manually re-call `getClans()` on success to pick up
4956
+ * the new `user_clan_id`, refreshed `member_count`, and the new
4957
+ * `cooldown_until` value. Until you do, the cached `TClans` payload
4958
+ * is stale.
4959
+ *
4960
+ * **Side effects** (consumer-observable on success)
4961
+ * - Entry-fee currency balance decreases by `entry_fee_amount`
4962
+ * (visible on the next {@link getUserProfile} refresh / via the
4963
+ * user-properties update channel).
4964
+ * - The user's clan membership row is created (or replaced, on a
4965
+ * switch). `member_count` on the new clan increments by 1; on a
4966
+ * switch, the old clan's `member_count` decrements by 1 in the
4967
+ * same transaction.
4968
+ * - The user's switch-cooldown restarts at the join time (the
4969
+ * `TClans.cooldown_until` field on the next list fetch reflects
4970
+ * the new expiry).
4971
+ * - Server-side CRM analytics fire (clan-joined event, currency-
4972
+ * deducted event for paid clans). These surface only as
4973
+ * server-side traces; the SDK consumer observes them indirectly
4974
+ * via balance / membership refresh.
4975
+ *
4976
+ * **UI guidance**: see [UI Guide — `joinClan`](../../docs/ui/clans/UIGuide_joinClan.md).
4977
+ *
4978
+ * **Visitor mode**: not supported. The server rejects the request
4979
+ * for unauthenticated sessions.
4980
+ *
4981
+ * @param clanId The clan ID from `TClans.clans[i].clan_id` (or
4982
+ * `TClanInfo.clan_id`).
4983
+ * @returns `{ errCode, errMsg }`; success when `errCode === 0`.
4984
+ * Note camelCase keys (see "Result shape note" above).
4985
+ *
4986
+ * @example
4987
+ * ```ts
4988
+ * const result = await window._smartico.api.getClans();
4989
+ * const target = result.clans.find(c => c.clan_id === clanId);
4990
+ *
4991
+ * if (!target) {
4992
+ * console.log('[smartico] clan not in current list — refresh getClans and retry');
4993
+ * return;
4994
+ * }
4995
+ * if (target.clan_id === result.user_clan_id) {
4996
+ * console.log('[smartico] user is already in this clan — short-circuit; do NOT call joinClan again, the server will treat it as a switch and may re-deduct the fee');
4997
+ * return;
4998
+ * }
4999
+ * if (result.cooldown_until) {
5000
+ * console.log('[smartico] user is in cooldown — disable Join until', new Date(result.cooldown_until + 'Z').toLocaleString());
5001
+ * return;
5002
+ * }
5003
+ * if (target.member_count >= target.capacity_limit) {
5004
+ * console.log('[smartico] clan is full — disable Join');
5005
+ * return;
5006
+ * }
5007
+ *
5008
+ * console.log('[smartico] join starting — set in-flight flag, show loading dots on Join button, keep modal open');
5009
+ * const r = await window._smartico.api.joinClan(target.clan_id);
5010
+ * console.log('[smartico] join response received — clear in-flight flag');
5011
+ *
5012
+ * if (r.errCode === 0) {
5013
+ * console.log('[smartico] joined successfully — refresh getClans to pick up the new user_clan_id, member_count, and cooldown_until');
5014
+ * await window._smartico.api.getClans();
5015
+ * } else if (r.errCode === 1002) {
5016
+ * console.error('[smartico] clan is full — refresh list, hide the Join CTA on the now-full clan');
5017
+ * } else if (r.errCode === 1003) {
5018
+ * const currencyLabel = ['points', 'gems', 'diamonds'][target.entry_fee_currency_type_id] ?? 'currency';
5019
+ * console.error('[smartico] insufficient', currencyLabel, '— need', target.entry_fee_amount);
5020
+ * } else if (r.errCode === 1004) {
5021
+ * console.error('[smartico] user does not meet segment / entry conditions — show a gating message');
5022
+ * } else if (r.errCode === 1006) {
5023
+ * console.error('[smartico] cooldown active — surface the cooldown_until from getClans:', result.cooldown_until);
5024
+ * } else if (r.errCode === 1001) {
5025
+ * console.error('[smartico] clan not found or archived — refresh list and remove from UI');
5026
+ * } else {
5027
+ * console.error('[smartico] join failed — show a generic error toast with this message:', r.errMsg);
5028
+ * }
2736
5029
  * ```
2737
- *
2738
- * **Visitor mode: not supported**
2739
5030
  */
2740
- async getMiniGamesHistory({
2741
- limit,
2742
- offset,
2743
- saw_template_id
2744
- }) {
2745
- return OCache.use(onUpdateContextKey.SAWHistory, ECacheContext.WSAPI, () => this.api.getSawWinningHistoryT(this.userExtId, limit, offset, saw_template_id), CACHE_DATA_SEC);
5031
+ async joinClan(clanId) {
5032
+ return this.api.clanJoin(this.userExtId, clanId);
2746
5033
  }
2747
- /**
2748
- * Plays the specified by template_id mini-game on behalf of user and returns prize_id or err_code
2749
- * After playMiniGame is called, you can call getMiniGames to get the list of mini-games.The returned list of mini-games is cached for 30 seconds. But you can pass the onUpdate callback as a parameter. Note that each time you call playMiniGame with a new onUpdate callback, the old one will be overwritten by the new one.
2750
- * The onUpdate callback will be called on available spin count change, if mini-game has increasing jackpot per spin or won prize is spin/jackpot and if max count of the available user spins equals one, also if the spins were issued to the user manually in the BO. Updated templates will be passed to onUpdate callback.
5034
+ async updateClans() {
5035
+ const payload = await this.api.clansGetListT(this.userExtId);
5036
+ this.updateEntity(onUpdateContextKey.Clans, payload);
5037
+ }
5038
+ }
5039
+
5040
+ /** @group Jackpots */
5041
+ class WSAPIJackpots extends WSAPIClans {
5042
+ constructor(...args) {
5043
+ super(...args);
5044
+ /** @hidden */
5045
+ this.jackpotGetSignature = '';
5046
+ }
5047
+ async jackpotClearCache() {
5048
+ OCache.clear(ECacheContext.WSAPI, onUpdateContextKey.Jackpots);
5049
+ OCache.clear(ECacheContext.WSAPI, onUpdateContextKey.Pots);
5050
+ OCache.clear(ECacheContext.WSAPI, onUpdateContextKey.JackpotWinners);
5051
+ }
5052
+ /** Returns list of Jackpots that are active in the system and matching to the filter definition.
5053
+ * If filter is not provided, all active jackpots will be returned.
5054
+ * Filter can be used to get jackpots related to specific game or specific jackpot template.
5055
+ * You can call this method every second in order to get up to date information about current value of the jackpot(s) and present them to the end-users
2751
5056
  *
2752
5057
  * **Example**:
2753
5058
  * ```
2754
- * _smartico.api.playMiniGame(55).then((result) => {
5059
+ * _smartico.api.jackpotGet({ related_game_id: 'wooko-slot' }).then((result) => {
2755
5060
  * console.log(result);
2756
5061
  * });
2757
5062
  * ```
2758
5063
  *
2759
- * **Visitor mode: not supported**
5064
+ * **Example in the Visitor mode**:
5065
+ * ```
5066
+ * _smartico.vapi('EN').jackpotGet({ related_game_id: 'wooko-slot' }).then((result) => {
5067
+ * console.log(result);
5068
+ * });
5069
+ * ```
2760
5070
  */
2761
- async playMiniGame(template_id, {
2762
- onUpdate
2763
- } = {}) {
2764
- if (onUpdate) {
2765
- this.onUpdateCallback.set(onUpdateContextKey.Saw, onUpdate);
5071
+ async jackpotGet(filter) {
5072
+ var _this = this;
5073
+ const signature = `${filter == null ? void 0 : filter.jp_template_id}:${filter == null ? void 0 : filter.related_game_id}`;
5074
+ if (signature !== this.jackpotGetSignature) {
5075
+ this.jackpotGetSignature = signature;
5076
+ this.jackpotClearCache();
2766
5077
  }
2767
- const r = await this.api.sawSpinRequest(this.userExtId, template_id);
2768
- this.api.doAcknowledgeRequest(this.userExtId, r.request_id);
2769
- const o = {
2770
- err_code: r.errCode,
2771
- err_message: r.errMsg,
2772
- prize_id: r.saw_prize_id
2773
- };
2774
- return o;
5078
+ let jackpots = [];
5079
+ let pots = [];
5080
+ jackpots = await OCache.use(onUpdateContextKey.Jackpots, ECacheContext.WSAPI, async function () {
5081
+ const _jackpots = await _this.api.jackpotGet(_this.userExtId, filter);
5082
+ const _pots = _jackpots.items.map(jp => jp.pot);
5083
+ _jackpots.items.forEach(jp => {
5084
+ jp.jp_public_meta.custom_data = IntUtils.JsonOrText(jp.jp_public_meta.custom_data);
5085
+ });
5086
+ OCache.set(onUpdateContextKey.Pots, _pots, ECacheContext.WSAPI, JACKPOT_POT_CACHE_SEC);
5087
+ return _jackpots.items;
5088
+ }, JACKPOT_TEMPLATE_CACHE_SEC);
5089
+ if (jackpots.length > 0) {
5090
+ pots = await OCache.use(onUpdateContextKey.Pots, ECacheContext.WSAPI, async function () {
5091
+ const jp_template_ids = jackpots.map(jp => jp.jp_template_id);
5092
+ return (await _this.api.potGet(_this.userExtId, {
5093
+ jp_template_ids
5094
+ })).items;
5095
+ }, JACKPOT_POT_CACHE_SEC);
5096
+ }
5097
+ return jackpots.map(jp => {
5098
+ let _jp = _extends({}, jp, {
5099
+ pot: pots.find(p => p.jp_template_id === jp.jp_template_id)
5100
+ });
5101
+ return _jp;
5102
+ });
2775
5103
  }
2776
5104
  /**
2777
- * Sends the acknowledge request with specific client_request_id from minigame history in order to claim prize
5105
+ * Opt-in currently logged in user to the jackpot with the specified jp_template_id.
5106
+ * You may call jackpotGet method after doing optin to see that user is opted in to the jackpot.
5107
+ *
2778
5108
  * **Example**:
2779
5109
  * ```
2780
- * _smartico.api.miniGameWinAcknowledgeRequest('2a189322-31bb-4119-b943-bx7868ff8dc3').then((result) => {
2781
- * console.log(result);
5110
+ * _smartico.api.jackpotOptIn({ jp_template_id: 123 }).then((result) => {
5111
+ * console.log('Opted in to the jackpot');
2782
5112
  * });
2783
5113
  * ```
5114
+ *
5115
+ * **Visitor mode: not supported**
5116
+ *
2784
5117
  */
2785
- async miniGameWinAcknowledgeRequest(request_id) {
2786
- return this.api.doAcknowledgeRequest(this.userExtId, request_id);
5118
+ async jackpotOptIn(filter) {
5119
+ if (!filter.jp_template_id) {
5120
+ throw new Error('jp_template_id is required in jackpotOptIn');
5121
+ }
5122
+ const result = await this.api.jackpotOptIn(this.userExtId, filter);
5123
+ return result;
2787
5124
  }
2788
5125
  /**
2789
- * Plays the specified by template_id mini-game on behalf of user spin_count times and returns array of the prizes
2790
- * After playMiniGameBatch is called, you can call getMiniGames to get the list of mini-games. The returned list of mini-games is cached for 30 seconds. But you can pass the onUpdate callback as a parameter. Note that each time you call playMiniGameBatch with a new onUpdate callback, the old one will be overwritten by the new one.
2791
- * The onUpdate callback will be called on available spin count change, if mini-game has increasing jackpot per spin or won prize is spin/jackpot and if max count of the available user spins equals one, also if the spins were issued to the user manually in the BO. Updated templates will be passed to onUpdate callback.
5126
+ * Opt-out currently logged in user from the jackpot with the specified jp_template_id.
5127
+ * You may call jackpotGet method after doing optout to see that user is not opted in to the jackpot.
2792
5128
  *
2793
5129
  * **Example**:
2794
5130
  * ```
2795
- * _smartico.api.playMiniGameBatch(55, 10).then((result) => {
2796
- * console.log(result);
5131
+ * _smartico.api.jackpotOptOut({ jp_template_id: 123 }).then((result) => {
5132
+ * console.log('Opted out from the jackpot');
2797
5133
  * });
2798
5134
  * ```
5135
+ *
2799
5136
  * **Visitor mode: not supported**
5137
+ *
2800
5138
  */
2801
- async playMiniGameBatch(template_id, spin_count, {
2802
- onUpdate
2803
- } = {}) {
2804
- if (onUpdate) {
2805
- this.onUpdateCallback.set(onUpdateContextKey.Saw, onUpdate);
5139
+ async jackpotOptOut(filter) {
5140
+ if (!filter.jp_template_id) {
5141
+ throw new Error('jp_template_id is required in jackpotOptOut');
2806
5142
  }
2807
- const response = await this.api.sawSpinBatchRequest(this.userExtId, template_id, spin_count);
2808
- const request_ids = response.results.map(result => result.request_id);
2809
- this.api.doAcknowledgeBatchRequest(this.userExtId, request_ids);
2810
- const o = response.results.map(result => ({
2811
- errCode: result.errCode,
2812
- errMessage: result.errMsg,
2813
- saw_prize_id: result.saw_prize_id,
2814
- jackpot_amount: result.jackpot_amount,
2815
- first_spin_in_period: result.first_spin_in_period
2816
- }));
2817
- return o;
5143
+ const result = await this.api.jackpotOptOut(this.userExtId, filter);
5144
+ return result;
2818
5145
  }
2819
5146
  /**
2820
- * Requests an opt-in for the specified mission_id. Returns the err_code.
5147
+ * Returns jackpot winners for the given `jp_template_id` (paginated on the server).
5148
+ * Default page size on the wire is 20; use `limit`, `offset`, and repeated calls to load more.
5149
+ * The full protocol response also includes `has_more`; this method returns only the `winners` array.
5150
+ *
5151
+ * **Example**:
5152
+ * ```
5153
+ * _smartico.api.getJackpotWinners({
5154
+ * jp_template_id: 123,
5155
+ * }).then((result) => {
5156
+ * console.log(result);
5157
+ * });
5158
+ * ```
2821
5159
  *
2822
5160
  * **Visitor mode: not supported**
5161
+ *
5162
+ * @param params - Jackpot winners parameters
5163
+ * @param params.jp_template_id - Jackpot template id (required; throws if missing)
5164
+ * @param params.limit - Page size (server default 20 when omitted)
5165
+ * @param params.offset - Offset into the winner list
2823
5166
  */
2824
- async requestMissionOptIn(mission_id) {
2825
- const r = await this.api.missionOptIn(this.userExtId, mission_id);
2826
- const o = {
2827
- err_code: r.errCode,
2828
- err_message: r.errMsg
2829
- };
2830
- return o;
5167
+ async getJackpotWinners({
5168
+ limit,
5169
+ offset,
5170
+ jp_template_id
5171
+ }) {
5172
+ return OCache.use(onUpdateContextKey.JackpotWinners + jp_template_id, ECacheContext.WSAPI, () => this.api.getJackpotWinnersT(this.userExtId, limit, offset, jp_template_id), JACKPOT_WINNERS_CACHE_SEC);
2831
5173
  }
2832
5174
  /**
2833
- * Request for claim reward for the specified mission id. Returns the err_code.
5175
+ * Returns the eligible games for the jackpot with the specified jp_template_id.
5176
+ *
5177
+ * **Example**:
5178
+ * ```
5179
+ * _smartico.api.getJackpotEligibleGames({ jp_template_id: 123 }).then((result) => {
5180
+ * console.log(result);
5181
+ * });
5182
+ * ```
2834
5183
  *
2835
5184
  * **Visitor mode: not supported**
5185
+ *
2836
5186
  */
2837
- async requestMissionClaimReward(mission_id, ach_completed_id) {
2838
- const r = await this.api.missionClaimPrize(this.userExtId, mission_id, ach_completed_id);
2839
- const o = {
2840
- err_code: r.errCode,
2841
- err_message: r.errMsg
2842
- };
2843
- return o;
5187
+ async getJackpotEligibleGames({
5188
+ jp_template_id,
5189
+ onUpdate
5190
+ }) {
5191
+ if (onUpdate) {
5192
+ this.onUpdateCallback.set(onUpdateContextKey.JackpotEligibleGames, onUpdate);
5193
+ }
5194
+ return OCache.use(onUpdateContextKey.JackpotEligibleGames + jp_template_id, ECacheContext.WSAPI, () => this.api.getJackpotEligibleGamesT(this.userExtId, {
5195
+ jp_template_id
5196
+ }), JACKPOT_ELIGIBLE_GAMES_CACHE_SEC);
5197
+ }
5198
+ }
5199
+
5200
+ /** @group Raffles */
5201
+ class WSAPIRaffles extends WSAPIJackpots {
5202
+ async updateRaffles() {
5203
+ const payload = await this.api.getRafflesT(this.userExtId);
5204
+ this.updateEntity(onUpdateContextKey.Raffles, payload);
2844
5205
  }
2845
- /** Returns all the active instances of tournaments
2846
- * The returned list is cached for 30 seconds. But you can pass the onUpdate callback as a parameter. Note that each time you call getTournamentsList with a new onUpdate callback, the old one will be overwritten by the new one.
2847
- * The onUpdate callback will be called when the user has registered in a tournament. Updated list will be passed to onUpdate callback.
5206
+ /**
5207
+ * Returns the list of Raffles available for user
5208
+ * The returned list of Raffles is cached for 30 seconds. But you can pass the onUpdate callback as a parameter. Note that each time you call getRaffles with a new onUpdate callback, the old one will be overwritten by the new one.
5209
+ * The onUpdate callback will be called on claiming prize. Updated Raffles will be passed to onUpdate callback.
2848
5210
  *
2849
5211
  * **Example**:
5212
+ *
2850
5213
  * ```
2851
- * _smartico.api.getTournamentsList().then((result) => {
5214
+ * _smartico.api.getRaffles().then((result) => {
2852
5215
  * console.log(result);
2853
5216
  * });
2854
5217
  * ```
2855
5218
  *
2856
5219
  * **Example in the Visitor mode**:
5220
+ *
2857
5221
  * ```
2858
- * _smartico.vapi('EN').getTournamentsList().then((result) => {
5222
+ * _smartico.vapi('EN').getRaffles().then((result) => {
2859
5223
  * console.log(result);
2860
5224
  * });
2861
5225
  * ```
2862
- * */
2863
- async getTournamentsList({
2864
- onUpdate
2865
- } = {}) {
2866
- if (onUpdate) {
2867
- this.onUpdateCallback.set(onUpdateContextKey.TournamentList, onUpdate);
2868
- }
2869
- return OCache.use(onUpdateContextKey.TournamentList, ECacheContext.WSAPI, () => this.api.tournamentsGetLobbyT(this.userExtId), CACHE_DATA_SEC);
2870
- }
2871
- /**
2872
- * Returns clans list payload for the current user.
2873
- * The returned payload is cached for 30 seconds.
2874
- * If onUpdate is passed, it will be called when clans response is received.
2875
5226
  *
2876
- * **Visitor mode: not supported**
2877
5227
  */
2878
- async getClans({
5228
+ async getRaffles({
2879
5229
  onUpdate
2880
5230
  } = {}) {
2881
5231
  if (onUpdate) {
2882
- this.onUpdateCallback.set(onUpdateContextKey.Clans, onUpdate);
5232
+ this.onUpdateCallback.set(onUpdateContextKey.Raffles, onUpdate);
2883
5233
  }
2884
- return OCache.use(onUpdateContextKey.Clans, ECacheContext.WSAPI, () => this.api.clansGetListT(this.userExtId), CACHE_DATA_SEC);
2885
- }
2886
- /**
2887
- * Returns detailed information for a specific clan including its members.
2888
- *
2889
- * **Visitor mode: not supported**
2890
- */
2891
- async getClanInfo(clanId) {
2892
- return this.api.clansGetInfoT(this.userExtId, clanId);
5234
+ return OCache.use(onUpdateContextKey.Raffles, ECacheContext.WSAPI, () => this.api.getRafflesT(this.userExtId), CACHE_DATA_SEC);
2893
5235
  }
2894
5236
  /**
2895
- * Joins a clan on behalf of the current user.
5237
+ * Returns draw run for provided raffle_id and run_id.
5238
+ * You can pass winners_from and winners_to parameters to get a specific range of winners. Default is 0-20.
2896
5239
  *
2897
- * **Visitor mode: not supported**
2898
- */
2899
- async joinClan(clanId) {
2900
- return this.api.clanJoin(this.userExtId, clanId);
2901
- }
2902
- /**
2903
- * Returns detailed information for a specific tournament instance; the response includes tournament info and the leaderboard of players
2904
5240
  *
2905
5241
  * **Example**:
2906
- * ```
2907
- * _smartico.api.getTournamentsList().then((result) => {
2908
- * if (result.length > 0) {
2909
- * _smartico.api.getTournamentInstanceInfo(result[0].instance_id).then((result) => {
2910
- * console.log(result);
2911
- * });
2912
- * }
5242
+ *
5243
+ * ```javascript
5244
+ * _smartico.api.getRaffleDrawRun({raffle_id: 156, run_id: 145}).then((result) => {
5245
+ * console.log(result);
2913
5246
  * });
2914
5247
  * ```
2915
5248
  *
2916
5249
  * **Example in the Visitor mode**:
2917
- * ```
2918
- * _smartico.vapi('EN').getTournamentsList().then((result) => {
2919
- * if (result.length > 0) {
2920
- * _smartico.vapi('EN').getTournamentInstanceInfo(result[0].instance_id).then((result) => {
2921
- * console.log(result);
2922
- * });
2923
- * }
5250
+ *
5251
+ *
5252
+ * ```javascript
5253
+ * _smartico.vapi('EN').getRaffleDrawRun({ raffle_id: 156, run_id: 145 }).then((result) => {
5254
+ * console.log(result);
2924
5255
  * });
2925
5256
  * ```
5257
+ *
2926
5258
  */
2927
- async getTournamentInstanceInfo(tournamentInstanceId) {
2928
- return this.api.tournamentsGetInfoT(this.userExtId, tournamentInstanceId);
5259
+ async getRaffleDrawRun(props) {
5260
+ if (!props.raffle_id || !props.run_id) {
5261
+ throw new Error('both raffle_id and run_id are required');
5262
+ }
5263
+ return await this.api.getRaffleDrawRunT(this.userExtId, props.raffle_id, props.run_id, props.winners_from, props.winners_to);
2929
5264
  }
2930
5265
  /**
2931
- * Returns the players of a specific clan in a clan-based tournament.
5266
+ * Returns history of draw runs for the provided raffle_id and draw_id, if the draw_id is not provided will return history of all the draws for the provided raffle_id
2932
5267
  *
2933
- * **Visitor mode: not supported**
2934
- */
2935
- async getClanTournamentPlayers(tournamentInstanceId, clanId) {
2936
- return this.api.clanTournamentGetPlayers(this.userExtId, tournamentInstanceId, clanId);
2937
- }
2938
- /**
2939
- * Requests registration for the specified tournament instance. Returns the err_code.
2940
- *
2941
- * **Visitor mode: not supported**
2942
- */
2943
- async registerInTournament(tournamentInstanceId) {
2944
- const r = await this.api.registerInTournament(this.userExtId, tournamentInstanceId);
2945
- const o = {
2946
- err_code: r.errCode,
2947
- err_message: r.errMsg
2948
- };
2949
- return o;
2950
- }
2951
- /**
2952
- * Returns the leaderboard for the current type (default is Daily). If getPreviousPeriod is passed as true, a leaderboard for the previous period for the current type will be returned.
2953
- * For example, if the type is Weekly and getPreviousPeriod is true, a leaderboard for the previous week will be returned.
2954
5268
  *
2955
5269
  * **Example**:
2956
- * ```
2957
- * _smartico.api.getLeaderBoard(1).then((result) => {
2958
- * console.log(result);
5270
+ *
5271
+ * ```javascript
5272
+ * _smartico.api.getRaffleDrawRunsHistory({ raffle_id: 156, draw_id: 432 }).then((result) => {
5273
+ * console.log(result);
2959
5274
  * });
2960
5275
  * ```
2961
5276
  *
2962
5277
  * **Example in the Visitor mode**:
2963
- * ```
2964
- * _smartico.vapi('EN').getLeaderBoard(1).then((result) => {
2965
- * console.log(result);
5278
+ *
5279
+ * ```javascript
5280
+ * _smartico.vapi('EN').getRaffleDrawRunsHistory({ raffle_id: 156, draw_id: 432 }).then((result) => {
5281
+ * console.log(result);
2966
5282
  * });
2967
5283
  * ```
2968
- */
2969
- async getLeaderBoard(periodType, getPreviousPeriod) {
2970
- return OCache.use(onUpdateContextKey.LeaderBoards, ECacheContext.WSAPI, () => this.api.leaderboardsGetT(this.userExtId, periodType, getPreviousPeriod), CACHE_DATA_SEC);
2971
- }
2972
- /** Returns inbox messages based on the provided parameters. "From" and "to" indicate the range of messages to be fetched.
2973
- * The maximum number of messages per request is limited to 20.
2974
- * An indicator "onlyFavorite" can be passed to get only messages marked as favorites.
2975
- * An indicator "read_status" can be passed to get only messages marked as read or unread.
2976
- * You can leave this params empty and by default it will return list of messages ranging from 0 to 20.
2977
- * This function returns a list of messages without the body of each message.
2978
- * To get the body of the message you need to call getInboxMessageBody function and pass the message guid contained in each message of this request.
2979
- * All other action like mark as read, favorite, delete, etc. can be done using this message GUID.
2980
- * The "onUpdate" callback will be triggered when the user receives a new message. It will provide an updated list of messages, ranging from 0 to 20, to the onUpdate callback function.
2981
5284
  *
2982
- * **Visitor mode: not supported**
2983
- *
2984
- * @param params
2985
5285
  */
2986
- async getInboxMessages({
2987
- from,
2988
- to,
2989
- onlyFavorite,
2990
- categoryId,
2991
- read_status,
2992
- onUpdate
2993
- } = {}) {
2994
- if (onUpdate) {
2995
- this.onUpdateCallback.set(onUpdateContextKey.InboxMessages, onUpdate);
5286
+ async getRaffleDrawRunsHistory(props) {
5287
+ const res = await this.api.getRaffleDrawRunsHistory(this.userExtId, props);
5288
+ if (!props.raffle_id) {
5289
+ throw new Error('raffle_id is required');
2996
5290
  }
2997
- return await this.api.getInboxMessagesT(this.userExtId, from, to, onlyFavorite, categoryId, read_status);
5291
+ return drawRunHistoryTransform(res);
2998
5292
  }
2999
5293
  /**
3000
- * Returns inbox unread count.
5294
+ * Returns `err_code` and `err_message` after the call; `err_code` 0 means the request succeeded.
3001
5295
  *
3002
- * **Visitor mode: not supported**
3003
- * @param params
3004
- */
3005
- async getInboxUnreadCount({
3006
- onUpdate
3007
- } = {}) {
3008
- if (onUpdate) {
3009
- this.onUpdateCallback.set(onUpdateContextKey.InboxUnreadCount, onUpdate);
3010
- }
3011
- return OCache.use(onUpdateContextKey.InboxUnreadCount, ECacheContext.WSAPI, () => this.api.getInboxUnreadCountT(this.userExtId), CACHE_DATA_SEC);
3012
- }
3013
- /**
3014
- * Returns the message body of the specified message guid.
3015
5296
  *
3016
- * **Visitor mode: not supported**
3017
- */
3018
- async getInboxMessageBody(messageGuid) {
3019
- return await this.api.getInboxMessageBodyT(messageGuid);
3020
- }
3021
- /**
3022
- * Requests to mark inbox message with specified guid as read
5297
+ * **Example**:
3023
5298
  *
3024
- * **Visitor mode: not supported**
3025
- */
3026
- async markInboxMessageAsRead(messageGuid) {
3027
- const r = await this.api.markInboxMessageRead(this.userExtId, messageGuid);
3028
- return {
3029
- err_code: r.errCode,
3030
- err_message: r.errMsg
3031
- };
3032
- }
3033
- /**
3034
- * Requests to mark all inbox messages as read
5299
+ * ```javascript
5300
+ * _smartico.api.claimRafflePrize({won_id:251}).then((result) => {
5301
+ * console.log(result);
5302
+ * });
5303
+ * ```
3035
5304
  *
3036
- * **Visitor mode: not supported**
3037
- */
3038
- async markAllInboxMessagesAsRead() {
3039
- const r = await this.api.markAllInboxMessageRead(this.userExtId);
3040
- return {
3041
- err_code: r.errCode,
3042
- err_message: r.errMsg
3043
- };
3044
- }
3045
- /**
3046
- * Requests to mark inbox message with specified guid as favorite. Pass mark true to add message to favorite and false to remove.
5305
+ * **Example in the Visitor mode**:
3047
5306
  *
3048
- * **Visitor mode: not supported**
3049
- */
3050
- async markUnmarkInboxMessageAsFavorite(messageGuid, mark) {
3051
- const r = await this.api.markUnmarkInboxMessageAsFavorite(this.userExtId, messageGuid, mark);
3052
- return {
3053
- err_code: r.errCode,
3054
- err_message: r.errMsg
3055
- };
3056
- }
3057
- /**
3058
- * Requests to delete inbox message
5307
+ * ```javascript
5308
+ * _smartico.vapi('EN').claimRafflePrize({ won_id: 251 }).then((result) => {
5309
+ * console.log(result);
5310
+ * });
5311
+ * ```
3059
5312
  *
3060
- * **Visitor mode: not supported**
3061
5313
  */
3062
- async deleteInboxMessage(messageGuid) {
3063
- const r = await this.api.deleteInboxMessage(this.userExtId, messageGuid);
3064
- return {
3065
- err_code: r.errCode,
3066
- err_message: r.errMsg
3067
- };
5314
+ async claimRafflePrize(props) {
5315
+ if (!props.won_id) {
5316
+ throw new Error('won_id is required');
5317
+ }
5318
+ const res = await this.api.claimRafflePrize(this.userExtId, {
5319
+ won_id: props.won_id
5320
+ });
5321
+ return raffleClaimPrizeResponseTransform(res);
3068
5322
  }
3069
5323
  /**
3070
- * Requests to delete all inbox messages
5324
+ * Requests an opt-in for the specified raffle. Returns the err_code.
3071
5325
  *
3072
5326
  * **Visitor mode: not supported**
3073
5327
  */
3074
- async deleteAllInboxMessages() {
3075
- const r = await this.api.deleteAllInboxMessages(this.userExtId);
5328
+ async requestRaffleOptin(props) {
5329
+ if (!props.raffle_id) {
5330
+ throw new Error('raffle_id is required');
5331
+ }
5332
+ if (!props.draw_id) {
5333
+ throw new Error('draw_id is required');
5334
+ }
5335
+ if (!props.raffle_run_id) {
5336
+ throw new Error('raffle_run_id is required');
5337
+ }
5338
+ const r = await this.api.raffleOptin(this.userExtId, props);
3076
5339
  return {
3077
5340
  err_code: r.errCode,
3078
5341
  err_message: r.errMsg
3079
5342
  };
3080
5343
  }
5344
+ }
5345
+
5346
+ /** @group MiniGames */
5347
+ class WSAPIMiniGames extends WSAPIRaffles {
3081
5348
  /**
3082
- * Requests translations for the given language. Returns the object including translation key/translation value pairs. All possible translation keys defined in the back office.
3083
- */
3084
- async getTranslations(lang_code) {
3085
- const r = await this.api.getTranslationsT(this.userExtId, lang_code, []);
3086
- return {
3087
- translations: r.translations
3088
- };
3089
- }
3090
- /**
3091
- * Reports an impression event for an engagement (when engagement content is displayed to the user).
3092
- * Use this method to track when users view engagement content such as inbox messages, popups.
3093
- * When using for Inbox cases, you need to use message guid as engagement_uid, and pass 31 as activityType.
5349
+ * Returns the list of mini-games configured for the current user (not filtered by spin availability or Widget visibility).
5350
+ * The returned list of mini-games is cached for 30 seconds. But you can pass the onUpdate callback as a parameter. Note that each time you call getMiniGames with a new onUpdate callback, the old one will be overwritten by the new one.
5351
+ * The onUpdate callback will be called on available spin count change, if mini-game has increasing jackpot per spin or won prize is spin/jackpot and if max count of the available user spins equals one, also if the spins were issued to the user manually in the BO. Updated templates will be passed to onUpdate callback.
3094
5352
  *
3095
5353
  * **Example**:
3096
5354
  * ```
3097
- * _smartico.api.reportImpressionEvent({
3098
- * engagement_uid: 'abc123-def456',
3099
- * activityType: 31 // Inbox
5355
+ * _smartico.api.getMiniGames().then((result) => {
5356
+ * console.log(result);
3100
5357
  * });
3101
5358
  * ```
3102
5359
  *
3103
- * **Visitor mode: not supported**
5360
+ * **Example in the Visitor mode**:
5361
+ * ```
5362
+ * _smartico.vapi('EN').getMiniGames().then((result) => {
5363
+ * console.log(result);
5364
+ * });
5365
+ * ```
3104
5366
  *
3105
- * @param params - Event parameters
3106
- * @param params.engagement_uid - Unique identifier for the engagement
3107
- * @param params.activityType - Type of engagement activity (Popup=30, Inbox=31)
3108
5367
  */
3109
- reportImpressionEvent({
3110
- engagement_uid,
3111
- activityType
3112
- }) {
3113
- this.api.reportEngagementImpression(this.userExtId, engagement_uid, activityType);
5368
+ async getMiniGames({
5369
+ onUpdate
5370
+ } = {}) {
5371
+ if (onUpdate) {
5372
+ this.onUpdateCallback.set(onUpdateContextKey.Saw, onUpdate);
5373
+ }
5374
+ return OCache.use(onUpdateContextKey.Saw, ECacheContext.WSAPI, () => this.api.sawGetTemplatesT(this.userExtId), CACHE_DATA_SEC);
3114
5375
  }
3115
5376
  /**
3116
- * Reports a click/action event for an engagement (when user interacts with engagement content).
3117
- * Use this method to track when users click on or interact with engagement content such as inbox messages, popups.
3118
- * When using for Inbox cases, you need to use message guid as engagement_uid, and pass 31 as activityType, and pass the action/deeplink that was triggered by the user interaction as action.
3119
- *
5377
+ * Returns the list of mini-games based on the provided parameters. "Limit" and "offset" indicate the range of items to be fetched.
5378
+ * The maximum number of items per request is limited to 20.
5379
+ * You can leave this params empty and by default it will return list of mini-games ranging from 0 to 20.
5380
+ * The returned list of mini-games history is cached for 30 seconds.
3120
5381
  *
3121
5382
  * **Example**:
3122
5383
  * ```
3123
- * _smartico.api.reportClickEvent({
3124
- * engagement_uid: 'abc123-def456',
3125
- * activityType: 31 // Inbox
3126
- * action: 'dp:gf_missions'
5384
+ * _smartico.api.getMiniGamesHistory().then((result) => {
5385
+ * console.log(result);
3127
5386
  * });
3128
5387
  * ```
3129
5388
  *
3130
5389
  * **Visitor mode: not supported**
3131
- *
3132
- * @param params - Event parameters
3133
- * @param params.engagement_uid - Unique identifier for the engagement
3134
- * @param params.activityType - Type of engagement activity (Popup=30, Inbox=31)
3135
- * @param params.action - Optional action/deeplink that was triggered by the user interaction
3136
5390
  */
3137
- reportClickEvent({
3138
- engagement_uid,
3139
- activityType,
3140
- action
5391
+ async getMiniGamesHistory({
5392
+ limit,
5393
+ offset,
5394
+ saw_template_id
3141
5395
  }) {
3142
- this.api.reportEngagementAction(this.userExtId, engagement_uid, activityType, action);
5396
+ return OCache.use(onUpdateContextKey.SAWHistory, ECacheContext.WSAPI, () => this.api.getSawWinningHistoryT(this.userExtId, limit, offset, saw_template_id), CACHE_DATA_SEC);
3143
5397
  }
3144
5398
  /**
3145
- * Returns the activity log for a user within a specified time range.
3146
- * The response includes both points changes and gems/diamonds changes.
3147
- * Each log entry contains information about the change amount, balance, and source.
3148
- * The returned list is cached for 30 seconds.
3149
- * You can pass the onUpdate callback as a parameter, it will be called every time the activity log is updated and will provide the updated list of activity logs for the last 10 minutes.
5399
+ * Plays the specified by template_id mini-game on behalf of user and returns prize_id or err_code
5400
+ * After playMiniGame is called, you can call getMiniGames to get the list of mini-games.The returned list of mini-games is cached for 30 seconds. But you can pass the onUpdate callback as a parameter. Note that each time you call playMiniGame with a new onUpdate callback, the old one will be overwritten by the new one.
5401
+ * The onUpdate callback will be called on available spin count change, if mini-game has increasing jackpot per spin or won prize is spin/jackpot and if max count of the available user spins equals one, also if the spins were issued to the user manually in the BO. Updated templates will be passed to onUpdate callback.
3150
5402
  *
3151
5403
  * **Example**:
3152
5404
  * ```
3153
- * const startTime = Math.floor(Date.now() / 1000) - 86400 * 30; // 30 days ago
3154
- * const endTime = Math.floor(Date.now() / 1000); // now
3155
- *
3156
- * _smartico.api.getActivityLog({
3157
- * startTimeSeconds: startTime,
3158
- * endTimeSeconds: endTime,
3159
- * from: 0,
3160
- * to: 50,
3161
- * onUpdate: (data) => console.log('Updated:', data)
3162
- * }).then((result) => {
5405
+ * _smartico.api.playMiniGame(55).then((result) => {
3163
5406
  * console.log(result);
3164
5407
  * });
3165
5408
  * ```
3166
5409
  *
3167
5410
  * **Visitor mode: not supported**
3168
- *
3169
- * @param params - Activity log parameters
3170
- * @param params.startTimeSeconds - Start time in seconds (epoch timestamp)
3171
- * @param params.endTimeSeconds - End time in seconds (epoch timestamp)
3172
- * @param params.from - Start index of records to return
3173
- * @param params.to - End index of records to return
3174
- * @param params.onUpdate - Optional callback function that will be called when the activity log is updated
3175
5411
  */
3176
- async getActivityLog({
3177
- startTimeSeconds,
3178
- endTimeSeconds,
3179
- from,
3180
- to,
5412
+ async playMiniGame(template_id, {
3181
5413
  onUpdate
3182
- }) {
5414
+ } = {}) {
3183
5415
  if (onUpdate) {
3184
- this.onUpdateCallback.set(onUpdateContextKey.ActivityLog, onUpdate);
5416
+ this.onUpdateCallback.set(onUpdateContextKey.Saw, onUpdate);
3185
5417
  }
3186
- return await OCache.use(onUpdateContextKey.ActivityLog, ECacheContext.WSAPI, () => this.api.getActivityLogT(this.userExtId, startTimeSeconds, endTimeSeconds, from, to), CACHE_DATA_SEC);
5418
+ const r = await this.api.sawSpinRequest(this.userExtId, template_id);
5419
+ this.api.doAcknowledgeRequest(this.userExtId, r.request_id);
5420
+ const o = {
5421
+ err_code: r.errCode,
5422
+ err_message: r.errMsg,
5423
+ prize_id: r.saw_prize_id
5424
+ };
5425
+ return o;
5426
+ }
5427
+ /**
5428
+ * Sends the acknowledge request with specific client_request_id from minigame history in order to claim prize
5429
+ * **Example**:
5430
+ * ```
5431
+ * _smartico.api.miniGameWinAcknowledgeRequest('2a189322-31bb-4119-b943-bx7868ff8dc3').then((result) => {
5432
+ * console.log(result);
5433
+ * });
5434
+ * ```
5435
+ */
5436
+ async miniGameWinAcknowledgeRequest(request_id) {
5437
+ return this.api.doAcknowledgeRequest(this.userExtId, request_id);
5438
+ }
5439
+ /**
5440
+ * Plays the specified by template_id mini-game on behalf of user spin_count times and returns array of the prizes
5441
+ * After playMiniGameBatch is called, you can call getMiniGames to get the list of mini-games. The returned list of mini-games is cached for 30 seconds. But you can pass the onUpdate callback as a parameter. Note that each time you call playMiniGameBatch with a new onUpdate callback, the old one will be overwritten by the new one.
5442
+ * The onUpdate callback will be called on available spin count change, if mini-game has increasing jackpot per spin or won prize is spin/jackpot and if max count of the available user spins equals one, also if the spins were issued to the user manually in the BO. Updated templates will be passed to onUpdate callback.
5443
+ *
5444
+ * **Example**:
5445
+ * ```
5446
+ * _smartico.api.playMiniGameBatch(55, 10).then((result) => {
5447
+ * console.log(result);
5448
+ * });
5449
+ * ```
5450
+ * **Visitor mode: not supported**
5451
+ */
5452
+ async playMiniGameBatch(template_id, spin_count, {
5453
+ onUpdate
5454
+ } = {}) {
5455
+ if (onUpdate) {
5456
+ this.onUpdateCallback.set(onUpdateContextKey.Saw, onUpdate);
5457
+ }
5458
+ const response = await this.api.sawSpinBatchRequest(this.userExtId, template_id, spin_count);
5459
+ const request_ids = response.results.map(result => result.request_id);
5460
+ this.api.doAcknowledgeBatchRequest(this.userExtId, request_ids);
5461
+ const o = response.results.map(result => ({
5462
+ errCode: result.errCode,
5463
+ errMessage: result.errMsg,
5464
+ saw_prize_id: result.saw_prize_id,
5465
+ jackpot_amount: result.jackpot_amount,
5466
+ first_spin_in_period: result.first_spin_in_period
5467
+ }));
5468
+ return o;
5469
+ }
5470
+ async updateOnSpin(data) {
5471
+ const templates = await OCache.use(onUpdateContextKey.Saw, ECacheContext.WSAPI, () => this.api.sawGetTemplatesT(this.userExtId), CACHE_DATA_SEC);
5472
+ const index = templates.findIndex(t => t.id === data.saw_template_id);
5473
+ templates[index].spin_count = data.spin_count;
5474
+ this.updateEntity(onUpdateContextKey.Saw, templates);
5475
+ }
5476
+ async reloadMiniGameTemplate() {
5477
+ const updatedTemplates = await this.api.sawGetTemplatesT(this.userExtId);
5478
+ this.updateEntity(onUpdateContextKey.Saw, updatedTemplates);
5479
+ }
5480
+ }
5481
+
5482
+ /** @group Avatars */
5483
+ class WSAPIAvatars extends WSAPIMiniGames {
5484
+ /**
5485
+ * Returns the list of avatars available in the catalog for the current user.
5486
+ * The response includes both free avatars and earned/purchased ones.
5487
+ * Avatars with `hide_until_achieved = true` and `is_given = false` should be hidden from the user.
5488
+ * The result is cached for 30 seconds.
5489
+ *
5490
+ * **Example**:
5491
+ * ```
5492
+ * _smartico.api.getAvatarsList().then((result) => {
5493
+ * console.log(result);
5494
+ * });
5495
+ * ```
5496
+ *
5497
+ * **Visitor mode: not supported**
5498
+ */
5499
+ async getAvatarsList() {
5500
+ return OCache.use(onUpdateContextKey.AvatarsList, ECacheContext.WSAPI, () => this.api.avatarsGetListT(this.userExtId), CACHE_DATA_SEC);
5501
+ }
5502
+ /**
5503
+ * Returns the list of AI-customized avatars for the current user.
5504
+ * Each entry represents a previously generated AI customization for a specific base avatar.
5505
+ * The result is cached for 30 seconds.
5506
+ *
5507
+ * **Example**:
5508
+ * ```
5509
+ * _smartico.api.getAvatarsCustomized().then((result) => {
5510
+ * console.log(result);
5511
+ * });
5512
+ * ```
5513
+ *
5514
+ * **Visitor mode: not supported**
5515
+ */
5516
+ async getAvatarsCustomized() {
5517
+ return OCache.use(onUpdateContextKey.AvatarsCustomized, ECacheContext.WSAPI, () => this.api.avatarsGetCustomizedT(this.userExtId), CACHE_DATA_SEC);
5518
+ }
5519
+ /**
5520
+ * Returns the list of AI customization prompts (styles) available for avatar customization.
5521
+ * Each prompt represents a visual style that can be applied to a base avatar.
5522
+ * The result is cached for 30 seconds.
5523
+ *
5524
+ * **Example**:
5525
+ * ```
5526
+ * _smartico.api.getAvatarPrompts().then((result) => {
5527
+ * console.log(result);
5528
+ * });
5529
+ * ```
5530
+ *
5531
+ * **Visitor mode: not supported**
5532
+ */
5533
+ async getAvatarPrompts() {
5534
+ return OCache.use(onUpdateContextKey.AvatarPrompts, ECacheContext.WSAPI, () => this.api.avatarsGetPromptsT(this.userExtId), CACHE_DATA_SEC);
5535
+ }
5536
+ /**
5537
+ * Sets the specified avatar as the active avatar for the current user.
5538
+ * Pass `avatar_url` (the `TAvatarDefinition.url` path or a CDN URL for AI-customized avatars)
5539
+ * and `avatar_real_id` from the avatar catalog.
5540
+ * After a successful call, the avatar list cache is cleared so the next `getAvatarsList()` call reflects `is_in_use`.
5541
+ *
5542
+ * **Example**:
5543
+ * ```
5544
+ * _smartico.api.getAvatarsList().then((avatars) => {
5545
+ * const avatar = avatars.find((a) => !a.hide_until_achieved || a.is_given);
5546
+ * if (avatar) {
5547
+ * _smartico.api.setAvatar({
5548
+ * avatar_url: avatar.url,
5549
+ * avatar_real_id: avatar.avatar_real_id,
5550
+ * }).then((result) => {
5551
+ * console.log(result);
5552
+ * });
5553
+ * }
5554
+ * });
5555
+ * ```
5556
+ *
5557
+ * **Visitor mode: not supported**
5558
+ */
5559
+ async setAvatar(props) {
5560
+ var _r$errCode;
5561
+ if (!props.avatar_url) {
5562
+ throw new Error('avatar_url is required');
5563
+ }
5564
+ if (!props.avatar_real_id) {
5565
+ throw new Error('avatar_real_id is required');
5566
+ }
5567
+ const r = await this.api.avatarSetAvatar(this.userExtId, props.avatar_url, props.avatar_real_id);
5568
+ OCache.clear(ECacheContext.WSAPI, onUpdateContextKey.AvatarsList);
5569
+ OCache.clear(ECacheContext.WSAPI, onUpdateContextKey.AvatarsCustomized);
5570
+ return {
5571
+ err_code: (_r$errCode = r.errCode) != null ? _r$errCode : 0,
5572
+ err_message: r.errMsg
5573
+ };
3187
5574
  }
5575
+ }
5576
+
5577
+ /** @group GamePick */
5578
+ class WSAPIGamePick extends WSAPIAvatars {
3188
5579
  /**
3189
5580
  * Returns the active rounds for the specified MatchX or Quiz game.
3190
5581
  * Each round includes its events (matches/questions) along with the current user's selections and scores.
@@ -3547,466 +5938,274 @@ class WSAPI {
3547
5938
  }
3548
5939
  return this.api.gpGetRoundInfoForUser(props.saw_template_id, props.round_id, props.int_user_id);
3549
5940
  }
5941
+ }
5942
+
5943
+ /** @group LeaderBoard */
5944
+ class WSAPILeaderBoard extends WSAPIGamePick {
3550
5945
  /**
3551
- * Returns the list of avatars available in the catalog for the current user.
3552
- * The response includes both free avatars and earned/purchased ones.
3553
- * Avatars with `hide_until_achieved = true` and `is_given = false` should be hidden from the user.
3554
- * The result is cached for 30 seconds.
5946
+ * Returns the leaderboard for the current type (default is Daily). If getPreviousPeriod is passed as true, a leaderboard for the previous period for the current type will be returned.
5947
+ * For example, if the type is Weekly and getPreviousPeriod is true, a leaderboard for the previous week will be returned.
3555
5948
  *
3556
5949
  * **Example**:
3557
5950
  * ```
3558
- * _smartico.api.getAvatarsList().then((result) => {
3559
- * console.log(result);
5951
+ * _smartico.api.getLeaderBoard(1).then((result) => {
5952
+ * console.log(result);
3560
5953
  * });
3561
5954
  * ```
3562
5955
  *
3563
- * **Visitor mode: not supported**
3564
- */
3565
- async getAvatarsList() {
3566
- return OCache.use(onUpdateContextKey.AvatarsList, ECacheContext.WSAPI, () => this.api.avatarsGetListT(this.userExtId), CACHE_DATA_SEC);
3567
- }
3568
- /**
3569
- * Returns the list of AI-customized avatars for the current user.
3570
- * Each entry represents a previously generated AI customization for a specific base avatar.
3571
- * The result is cached for 30 seconds.
3572
- *
3573
- * **Example**:
5956
+ * **Example in the Visitor mode**:
3574
5957
  * ```
3575
- * _smartico.api.getAvatarsCustomized().then((result) => {
3576
- * console.log(result);
5958
+ * _smartico.vapi('EN').getLeaderBoard(1).then((result) => {
5959
+ * console.log(result);
3577
5960
  * });
3578
5961
  * ```
3579
- *
3580
- * **Visitor mode: not supported**
3581
5962
  */
3582
- async getAvatarsCustomized() {
3583
- return OCache.use(onUpdateContextKey.AvatarsCustomized, ECacheContext.WSAPI, () => this.api.avatarsGetCustomizedT(this.userExtId), CACHE_DATA_SEC);
5963
+ async getLeaderBoard(periodType, getPreviousPeriod) {
5964
+ return OCache.use(onUpdateContextKey.LeaderBoards, ECacheContext.WSAPI, () => this.api.leaderboardsGetT(this.userExtId, periodType, getPreviousPeriod), CACHE_DATA_SEC);
3584
5965
  }
5966
+ }
5967
+
5968
+ /** @group Inbox */
5969
+ class WSAPIInbox extends WSAPILeaderBoard {
3585
5970
  /**
3586
- * Returns the list of AI customization prompts (styles) available for avatar customization.
3587
- * Each prompt represents a visual style that can be applied to a base avatar.
3588
- * The result is cached for 30 seconds.
5971
+ * Reports an impression event for an engagement (when engagement content is displayed to the user).
5972
+ * Use this method to track when users view engagement content such as inbox messages, popups.
5973
+ * When using for Inbox cases, you need to use message guid as engagement_uid, and pass 31 as activityType.
3589
5974
  *
3590
5975
  * **Example**:
3591
5976
  * ```
3592
- * _smartico.api.getAvatarPrompts().then((result) => {
3593
- * console.log(result);
5977
+ * _smartico.api.reportImpressionEvent({
5978
+ * engagement_uid: 'abc123-def456',
5979
+ * activityType: 31 // Inbox
3594
5980
  * });
3595
5981
  * ```
3596
5982
  *
3597
5983
  * **Visitor mode: not supported**
5984
+ *
5985
+ * @param params - Event parameters
5986
+ * @param params.engagement_uid - Unique identifier for the engagement
5987
+ * @param params.activityType - Type of engagement activity (Popup=30, Inbox=31)
3598
5988
  */
3599
- async getAvatarPrompts() {
3600
- return OCache.use(onUpdateContextKey.AvatarPrompts, ECacheContext.WSAPI, () => this.api.avatarsGetPromptsT(this.userExtId), CACHE_DATA_SEC);
5989
+ reportImpressionEvent({
5990
+ engagement_uid,
5991
+ activityType
5992
+ }) {
5993
+ this.api.reportEngagementImpression(this.userExtId, engagement_uid, activityType);
3601
5994
  }
3602
5995
  /**
3603
- * Sets the specified avatar as the active avatar for the current user.
3604
- * Pass `avatar_url` (the `TAvatarDefinition.url` path or a CDN URL for AI-customized avatars)
3605
- * and `avatar_real_id` from the avatar catalog.
3606
- * After a successful call, the avatar list cache is cleared so the next `getAvatarsList()` call reflects `is_in_use`.
5996
+ * Reports a click/action event for an engagement (when user interacts with engagement content).
5997
+ * Use this method to track when users click on or interact with engagement content such as inbox messages, popups.
5998
+ * When using for Inbox cases, you need to use message guid as engagement_uid, and pass 31 as activityType, and pass the action/deeplink that was triggered by the user interaction as action.
3607
5999
  *
3608
6000
  * **Example**:
3609
6001
  * ```
3610
- * _smartico.api.getAvatarsList().then((avatars) => {
3611
- * const avatar = avatars.find((a) => !a.hide_until_achieved || a.is_given);
3612
- * if (avatar) {
3613
- * _smartico.api.setAvatar({
3614
- * avatar_url: avatar.url,
3615
- * avatar_real_id: avatar.avatar_real_id,
3616
- * }).then((result) => {
3617
- * console.log(result);
3618
- * });
3619
- * }
6002
+ * _smartico.api.reportClickEvent({
6003
+ * engagement_uid: 'abc123-def456',
6004
+ * activityType: 31 // Inbox
6005
+ * action: 'dp:gf_missions'
3620
6006
  * });
3621
6007
  * ```
3622
6008
  *
3623
6009
  * **Visitor mode: not supported**
6010
+ *
6011
+ * @param params - Event parameters
6012
+ * @param params.engagement_uid - Unique identifier for the engagement
6013
+ * @param params.activityType - Type of engagement activity (Popup=30, Inbox=31)
6014
+ * @param params.action - Optional action/deeplink that was triggered by the user interaction
3624
6015
  */
3625
- async setAvatar(props) {
3626
- var _r$errCode;
3627
- if (!props.avatar_url) {
3628
- throw new Error('avatar_url is required');
3629
- }
3630
- if (!props.avatar_real_id) {
3631
- throw new Error('avatar_real_id is required');
3632
- }
3633
- const r = await this.api.avatarSetAvatar(this.userExtId, props.avatar_url, props.avatar_real_id);
3634
- OCache.clear(ECacheContext.WSAPI, onUpdateContextKey.AvatarsList);
3635
- OCache.clear(ECacheContext.WSAPI, onUpdateContextKey.AvatarsCustomized);
3636
- return {
3637
- err_code: (_r$errCode = r.errCode) != null ? _r$errCode : 0,
3638
- err_message: r.errMsg
3639
- };
3640
- }
3641
- async updateOnSpin(data) {
3642
- const templates = await OCache.use(onUpdateContextKey.Saw, ECacheContext.WSAPI, () => this.api.sawGetTemplatesT(this.userExtId), CACHE_DATA_SEC);
3643
- const index = templates.findIndex(t => t.id === data.saw_template_id);
3644
- templates[index].spin_count = data.spin_count;
3645
- this.updateEntity(onUpdateContextKey.Saw, templates);
3646
- }
3647
- async reloadMiniGameTemplate() {
3648
- const updatedTemplates = await this.api.sawGetTemplatesT(this.userExtId);
3649
- this.updateEntity(onUpdateContextKey.Saw, updatedTemplates);
3650
- }
3651
- async updateMissions() {
3652
- const payload = await this.api.missionsGetItemsT(this.userExtId);
3653
- this.updateEntity(onUpdateContextKey.Missions, payload);
3654
- }
3655
- async updateBonuses() {
3656
- const payload = await this.api.bonusesGetItemsT(this.userExtId);
3657
- this.updateEntity(onUpdateContextKey.Bonuses, payload);
3658
- }
3659
- async updateTournaments() {
3660
- const payload = await this.api.tournamentsGetLobbyT(this.userExtId);
3661
- this.updateEntity(onUpdateContextKey.TournamentList, payload);
3662
- }
3663
- async updateClans() {
3664
- const payload = await this.api.clansGetListT(this.userExtId);
3665
- this.updateEntity(onUpdateContextKey.Clans, payload);
3666
- }
3667
- async updateStorePurchasedItems() {
3668
- const payload = await this.api.storeGetPurchasedItemsT(this.userExtId, 20, 0);
3669
- this.updateEntity(onUpdateContextKey.StoreHistory, payload);
3670
- }
3671
- async updateStoreItems() {
3672
- const payload = await this.api.storeGetItemsT(this.userExtId);
3673
- this.updateEntity(onUpdateContextKey.StoreItems, payload);
3674
- }
3675
- async updateInboxUnreadCount(count) {
3676
- this.updateEntity(onUpdateContextKey.InboxUnreadCount, count);
3677
- }
3678
- async updateInboxMessages() {
3679
- const payload = await this.api.getInboxMessagesT(this.userExtId);
3680
- this.updateEntity(onUpdateContextKey.InboxMessages, payload);
3681
- }
3682
- async updateRaffles() {
3683
- const payload = await this.api.getRafflesT(this.userExtId);
3684
- this.updateEntity(onUpdateContextKey.Raffles, payload);
3685
- }
3686
- async notifyActivityLogUpdate() {
3687
- const startSeconds = Date.now() / 1000 - 600;
3688
- const endSeconds = Date.now() / 1000;
3689
- const payload = await this.api.getActivityLogT(this.userExtId, startSeconds, endSeconds, 0, 50);
3690
- this.updateEntity(onUpdateContextKey.ActivityLog, payload);
3691
- }
3692
- async updateEntity(contextKey, payload) {
3693
- OCache.set(contextKey, payload, ECacheContext.WSAPI);
3694
- const onUpdate = this.onUpdateCallback.get(contextKey);
3695
- if (onUpdate) {
3696
- onUpdate(payload);
3697
- }
3698
- }
3699
- async jackpotClearCache() {
3700
- OCache.clear(ECacheContext.WSAPI, onUpdateContextKey.Jackpots);
3701
- OCache.clear(ECacheContext.WSAPI, onUpdateContextKey.Pots);
3702
- OCache.clear(ECacheContext.WSAPI, onUpdateContextKey.JackpotWinners);
3703
- }
3704
- /** Returns list of Jackpots that are active in the system and matching to the filter definition.
3705
- * If filter is not provided, all active jackpots will be returned.
3706
- * Filter can be used to get jackpots related to specific game or specific jackpot template.
3707
- * You can call this method every second in order to get up to date information about current value of the jackpot(s) and present them to the end-users
3708
- *
3709
- * **Example**:
3710
- * ```
3711
- * _smartico.api.jackpotGet({ related_game_id: 'wooko-slot' }).then((result) => {
3712
- * console.log(result);
3713
- * });
3714
- * ```
3715
- *
3716
- * **Example in the Visitor mode**:
3717
- * ```
3718
- * _smartico.vapi('EN').jackpotGet({ related_game_id: 'wooko-slot' }).then((result) => {
3719
- * console.log(result);
3720
- * });
3721
- * ```
3722
- */
3723
- async jackpotGet(filter) {
3724
- var _this = this;
3725
- const signature = `${filter == null ? void 0 : filter.jp_template_id}:${filter == null ? void 0 : filter.related_game_id}`;
3726
- if (signature !== this.jackpotGetSignature) {
3727
- this.jackpotGetSignature = signature;
3728
- this.jackpotClearCache();
3729
- }
3730
- let jackpots = [];
3731
- let pots = [];
3732
- jackpots = await OCache.use(onUpdateContextKey.Jackpots, ECacheContext.WSAPI, async function () {
3733
- const _jackpots = await _this.api.jackpotGet(_this.userExtId, filter);
3734
- const _pots = _jackpots.items.map(jp => jp.pot);
3735
- _jackpots.items.forEach(jp => {
3736
- jp.jp_public_meta.custom_data = IntUtils.JsonOrText(jp.jp_public_meta.custom_data);
3737
- });
3738
- OCache.set(onUpdateContextKey.Pots, _pots, ECacheContext.WSAPI, JACKPOT_POT_CACHE_SEC);
3739
- return _jackpots.items;
3740
- }, JACKPOT_TEMPLATE_CACHE_SEC);
3741
- if (jackpots.length > 0) {
3742
- pots = await OCache.use(onUpdateContextKey.Pots, ECacheContext.WSAPI, async function () {
3743
- const jp_template_ids = jackpots.map(jp => jp.jp_template_id);
3744
- return (await _this.api.potGet(_this.userExtId, {
3745
- jp_template_ids
3746
- })).items;
3747
- }, JACKPOT_POT_CACHE_SEC);
3748
- }
3749
- return jackpots.map(jp => {
3750
- let _jp = _extends({}, jp, {
3751
- pot: pots.find(p => p.jp_template_id === jp.jp_template_id)
3752
- });
3753
- return _jp;
3754
- });
3755
- }
3756
- /**
3757
- * Opt-in currently logged in user to the jackpot with the specified jp_template_id.
3758
- * You may call jackpotGet method after doing optin to see that user is opted in to the jackpot.
3759
- *
3760
- * **Example**:
3761
- * ```
3762
- * _smartico.api.jackpotOptIn({ jp_template_id: 123 }).then((result) => {
3763
- * console.log('Opted in to the jackpot');
3764
- * });
3765
- * ```
3766
- *
3767
- * **Visitor mode: not supported**
3768
- *
3769
- */
3770
- async jackpotOptIn(filter) {
3771
- if (!filter.jp_template_id) {
3772
- throw new Error('jp_template_id is required in jackpotOptIn');
3773
- }
3774
- const result = await this.api.jackpotOptIn(this.userExtId, filter);
3775
- return result;
6016
+ reportClickEvent({
6017
+ engagement_uid,
6018
+ activityType,
6019
+ action
6020
+ }) {
6021
+ this.api.reportEngagementAction(this.userExtId, engagement_uid, activityType, action);
3776
6022
  }
3777
- /**
3778
- * Opt-out currently logged in user from the jackpot with the specified jp_template_id.
3779
- * You may call jackpotGet method after doing optout to see that user is not opted in to the jackpot.
3780
- *
3781
- * **Example**:
3782
- * ```
3783
- * _smartico.api.jackpotOptOut({ jp_template_id: 123 }).then((result) => {
3784
- * console.log('Opted out from the jackpot');
3785
- * });
3786
- * ```
6023
+ /** Returns inbox messages based on the provided parameters. "From" and "to" indicate the range of messages to be fetched.
6024
+ * The maximum number of messages per request is limited to 20.
6025
+ * An indicator "onlyFavorite" can be passed to get only messages marked as favorites.
6026
+ * An indicator "read_status" can be passed to get only messages marked as read or unread.
6027
+ * You can leave this params empty and by default it will return list of messages ranging from 0 to 20.
6028
+ * This function returns a list of messages without the body of each message.
6029
+ * To get the body of the message you need to call getInboxMessageBody function and pass the message guid contained in each message of this request.
6030
+ * All other action like mark as read, favorite, delete, etc. can be done using this message GUID.
6031
+ * The "onUpdate" callback will be triggered when the user receives a new message. It will provide an updated list of messages, ranging from 0 to 20, to the onUpdate callback function.
3787
6032
  *
3788
6033
  * **Visitor mode: not supported**
3789
6034
  *
6035
+ * @param params
3790
6036
  */
3791
- async jackpotOptOut(filter) {
3792
- if (!filter.jp_template_id) {
3793
- throw new Error('jp_template_id is required in jackpotOptOut');
6037
+ async getInboxMessages({
6038
+ from,
6039
+ to,
6040
+ onlyFavorite,
6041
+ categoryId,
6042
+ read_status,
6043
+ onUpdate
6044
+ } = {}) {
6045
+ if (onUpdate) {
6046
+ this.onUpdateCallback.set(onUpdateContextKey.InboxMessages, onUpdate);
3794
6047
  }
3795
- const result = await this.api.jackpotOptOut(this.userExtId, filter);
3796
- return result;
3797
- }
3798
- /**
3799
- * Returns jackpot winners for the given `jp_template_id` (paginated on the server).
3800
- * Default page size on the wire is 20; use `limit`, `offset`, and repeated calls to load more.
3801
- * The full protocol response also includes `has_more`; this method returns only the `winners` array.
3802
- *
3803
- * **Example**:
3804
- * ```
3805
- * _smartico.api.getJackpotWinners({
3806
- * jp_template_id: 123,
3807
- * }).then((result) => {
3808
- * console.log(result);
3809
- * });
3810
- * ```
3811
- *
3812
- * **Visitor mode: not supported**
3813
- *
3814
- * @param params - Jackpot winners parameters
3815
- * @param params.jp_template_id - Jackpot template id (required; throws if missing)
3816
- * @param params.limit - Page size (server default 20 when omitted)
3817
- * @param params.offset - Offset into the winner list
3818
- */
3819
- async getJackpotWinners({
3820
- limit,
3821
- offset,
3822
- jp_template_id
3823
- }) {
3824
- return OCache.use(onUpdateContextKey.JackpotWinners + jp_template_id, ECacheContext.WSAPI, () => this.api.getJackpotWinnersT(this.userExtId, limit, offset, jp_template_id), JACKPOT_WINNERS_CACHE_SEC);
6048
+ return await this.api.getInboxMessagesT(this.userExtId, from, to, onlyFavorite, categoryId, read_status);
3825
6049
  }
3826
6050
  /**
3827
- * Returns the eligible games for the jackpot with the specified jp_template_id.
3828
- *
3829
- * **Example**:
3830
- * ```
3831
- * _smartico.api.getJackpotEligibleGames({ jp_template_id: 123 }).then((result) => {
3832
- * console.log(result);
3833
- * });
3834
- * ```
6051
+ * Returns inbox unread count.
3835
6052
  *
3836
6053
  * **Visitor mode: not supported**
3837
- *
6054
+ * @param params
3838
6055
  */
3839
- async getJackpotEligibleGames({
3840
- jp_template_id,
6056
+ async getInboxUnreadCount({
3841
6057
  onUpdate
3842
- }) {
6058
+ } = {}) {
3843
6059
  if (onUpdate) {
3844
- this.onUpdateCallback.set(onUpdateContextKey.JackpotEligibleGames, onUpdate);
6060
+ this.onUpdateCallback.set(onUpdateContextKey.InboxUnreadCount, onUpdate);
3845
6061
  }
3846
- return OCache.use(onUpdateContextKey.JackpotEligibleGames + jp_template_id, ECacheContext.WSAPI, () => this.api.getJackpotEligibleGamesT(this.userExtId, {
3847
- jp_template_id
3848
- }), JACKPOT_ELIGIBLE_GAMES_CACHE_SEC);
6062
+ return OCache.use(onUpdateContextKey.InboxUnreadCount, ECacheContext.WSAPI, () => this.api.getInboxUnreadCountT(this.userExtId), CACHE_DATA_SEC);
3849
6063
  }
3850
6064
  /**
3851
- * Returns all the related tournaments and missions for the provided game id for the current user
3852
- * The provided Game ID should correspond to the ID from the Games Catalog - https://help.smartico.ai/welcome/technical-guides/games-catalog-api
3853
- *
3854
- * **Example**:
3855
- * ```
3856
- * _smartico.api.getRelatedItemsForGame('gold-slot2').then((result) => {
3857
- * console.log(result);
3858
- * });
3859
- * ```
6065
+ * Returns the message body of the specified message guid.
3860
6066
  *
3861
- * **Example in the Visitor mode**:
3862
- * ```
3863
- * _smartico.vapi('EN').getRelatedItemsForGame('gold-slot2').then((result) => {
3864
- * console.log(result);
3865
- * });
3866
- * ```
6067
+ * **Visitor mode: not supported**
3867
6068
  */
3868
- async getRelatedItemsForGame(related_game_id) {
3869
- const result = await this.api.getRelatedItemsForGame(this.userExtId, related_game_id);
3870
- return result;
6069
+ async getInboxMessageBody(messageGuid) {
6070
+ return await this.api.getInboxMessageBodyT(messageGuid);
3871
6071
  }
3872
6072
  /**
3873
- * Returns the list of Raffles available for user
3874
- * The returned list of Raffles is cached for 30 seconds. But you can pass the onUpdate callback as a parameter. Note that each time you call getRaffles with a new onUpdate callback, the old one will be overwritten by the new one.
3875
- * The onUpdate callback will be called on claiming prize. Updated Raffles will be passed to onUpdate callback.
3876
- *
3877
- * **Example**:
3878
- *
3879
- * ```
3880
- * _smartico.api.getRaffles().then((result) => {
3881
- * console.log(result);
3882
- * });
3883
- * ```
3884
- *
3885
- * **Example in the Visitor mode**:
3886
- *
3887
- * ```
3888
- * _smartico.vapi('EN').getRaffles().then((result) => {
3889
- * console.log(result);
3890
- * });
3891
- * ```
6073
+ * Requests to mark inbox message with specified guid as read
3892
6074
  *
6075
+ * **Visitor mode: not supported**
3893
6076
  */
3894
- async getRaffles({
3895
- onUpdate
3896
- } = {}) {
3897
- if (onUpdate) {
3898
- this.onUpdateCallback.set(onUpdateContextKey.Raffles, onUpdate);
3899
- }
3900
- return OCache.use(onUpdateContextKey.Raffles, ECacheContext.WSAPI, () => this.api.getRafflesT(this.userExtId), CACHE_DATA_SEC);
6077
+ async markInboxMessageAsRead(messageGuid) {
6078
+ const r = await this.api.markInboxMessageRead(this.userExtId, messageGuid);
6079
+ return {
6080
+ err_code: r.errCode,
6081
+ err_message: r.errMsg
6082
+ };
3901
6083
  }
3902
6084
  /**
3903
- * Returns draw run for provided raffle_id and run_id.
3904
- * You can pass winners_from and winners_to parameters to get a specific range of winners. Default is 0-20.
3905
- *
3906
- *
3907
- * **Example**:
3908
- *
3909
- * ```javascript
3910
- * _smartico.api.getRaffleDrawRun({raffle_id: 156, run_id: 145}).then((result) => {
3911
- * console.log(result);
3912
- * });
3913
- * ```
3914
- *
3915
- * **Example in the Visitor mode**:
3916
- *
3917
- *
3918
- * ```javascript
3919
- * _smartico.vapi('EN').getRaffleDrawRun({ raffle_id: 156, run_id: 145 }).then((result) => {
3920
- * console.log(result);
3921
- * });
3922
- * ```
6085
+ * Requests to mark all inbox messages as read
3923
6086
  *
6087
+ * **Visitor mode: not supported**
3924
6088
  */
3925
- async getRaffleDrawRun(props) {
3926
- if (!props.raffle_id || !props.run_id) {
3927
- throw new Error('both raffle_id and run_id are required');
3928
- }
3929
- return await this.api.getRaffleDrawRunT(this.userExtId, props.raffle_id, props.run_id, props.winners_from, props.winners_to);
6089
+ async markAllInboxMessagesAsRead() {
6090
+ const r = await this.api.markAllInboxMessageRead(this.userExtId);
6091
+ return {
6092
+ err_code: r.errCode,
6093
+ err_message: r.errMsg
6094
+ };
3930
6095
  }
3931
6096
  /**
3932
- * Returns history of draw runs for the provided raffle_id and draw_id, if the draw_id is not provided will return history of all the draws for the provided raffle_id
3933
- *
3934
- *
3935
- * **Example**:
3936
- *
3937
- * ```javascript
3938
- * _smartico.api.getRaffleDrawRunsHistory({ raffle_id: 156, draw_id: 432 }).then((result) => {
3939
- * console.log(result);
3940
- * });
3941
- * ```
3942
- *
3943
- * **Example in the Visitor mode**:
3944
- *
3945
- * ```javascript
3946
- * _smartico.vapi('EN').getRaffleDrawRunsHistory({ raffle_id: 156, draw_id: 432 }).then((result) => {
3947
- * console.log(result);
3948
- * });
3949
- * ```
6097
+ * Requests to mark inbox message with specified guid as favorite. Pass mark true to add message to favorite and false to remove.
3950
6098
  *
6099
+ * **Visitor mode: not supported**
3951
6100
  */
3952
- async getRaffleDrawRunsHistory(props) {
3953
- const res = await this.api.getRaffleDrawRunsHistory(this.userExtId, props);
3954
- if (!props.raffle_id) {
3955
- throw new Error('raffle_id is required');
3956
- }
3957
- return drawRunHistoryTransform(res);
6101
+ async markUnmarkInboxMessageAsFavorite(messageGuid, mark) {
6102
+ const r = await this.api.markUnmarkInboxMessageAsFavorite(this.userExtId, messageGuid, mark);
6103
+ return {
6104
+ err_code: r.errCode,
6105
+ err_message: r.errMsg
6106
+ };
3958
6107
  }
3959
6108
  /**
3960
- * Returns `err_code` and `err_message` after the call; `err_code` 0 means the request succeeded.
3961
- *
3962
- *
3963
- * **Example**:
3964
- *
3965
- * ```javascript
3966
- * _smartico.api.claimRafflePrize({won_id:251}).then((result) => {
3967
- * console.log(result);
3968
- * });
3969
- * ```
3970
- *
3971
- * **Example in the Visitor mode**:
3972
- *
3973
- * ```javascript
3974
- * _smartico.vapi('EN').claimRafflePrize({ won_id: 251 }).then((result) => {
3975
- * console.log(result);
3976
- * });
3977
- * ```
6109
+ * Requests to delete inbox message
3978
6110
  *
6111
+ * **Visitor mode: not supported**
3979
6112
  */
3980
- async claimRafflePrize(props) {
3981
- if (!props.won_id) {
3982
- throw new Error('won_id is required');
3983
- }
3984
- const res = await this.api.claimRafflePrize(this.userExtId, {
3985
- won_id: props.won_id
3986
- });
3987
- return raffleClaimPrizeResponseTransform(res);
6113
+ async deleteInboxMessage(messageGuid) {
6114
+ const r = await this.api.deleteInboxMessage(this.userExtId, messageGuid);
6115
+ return {
6116
+ err_code: r.errCode,
6117
+ err_message: r.errMsg
6118
+ };
3988
6119
  }
3989
6120
  /**
3990
- * Requests an opt-in for the specified raffle. Returns the err_code.
6121
+ * Requests to delete all inbox messages
3991
6122
  *
3992
6123
  * **Visitor mode: not supported**
3993
6124
  */
3994
- async requestRaffleOptin(props) {
3995
- if (!props.raffle_id) {
3996
- throw new Error('raffle_id is required');
3997
- }
3998
- if (!props.draw_id) {
3999
- throw new Error('draw_id is required');
4000
- }
4001
- if (!props.raffle_run_id) {
4002
- throw new Error('raffle_run_id is required');
4003
- }
4004
- const r = await this.api.raffleOptin(this.userExtId, props);
6125
+ async deleteAllInboxMessages() {
6126
+ const r = await this.api.deleteAllInboxMessages(this.userExtId);
4005
6127
  return {
4006
6128
  err_code: r.errCode,
4007
6129
  err_message: r.errMsg
4008
6130
  };
4009
6131
  }
6132
+ async updateInboxUnreadCount(count) {
6133
+ this.updateEntity(onUpdateContextKey.InboxUnreadCount, count);
6134
+ }
6135
+ async updateInboxMessages() {
6136
+ const payload = await this.api.getInboxMessagesT(this.userExtId);
6137
+ this.updateEntity(onUpdateContextKey.InboxMessages, payload);
6138
+ }
6139
+ }
6140
+
6141
+ /**
6142
+ * Public surface of the Smartico WebSocket API. The consumer reaches every
6143
+ * method on this class via `_smartico.api.X(...)`. The class is the
6144
+ * terminator of an inheritance chain that splits the surface into focused
6145
+ * domain classes (see `WSAPIBase` → `WSAPIUser` → ... → `WSAPIInbox` →
6146
+ * `WSAPI`); each domain class hosts its own subset of methods. Consumers
6147
+ * don't need to know about the chain — every method is reachable directly
6148
+ * on the `WSAPI` instance via prototype-chain lookup.
6149
+ *
6150
+ * @group General API
6151
+ */
6152
+ class WSAPI extends WSAPIInbox {
6153
+ /** @private */
6154
+ constructor(api, userExtId = null) {
6155
+ super(api, userExtId);
6156
+ // Push-subscription wiring. This block must live in the terminator
6157
+ // because it references `update*` helpers defined across multiple
6158
+ // domain classes; only the terminator has visibility into all of
6159
+ // them via the prototype chain.
6160
+ if (this.api.tracker) {
6161
+ const on = this.api.tracker.on;
6162
+ on(ClassId.SAW_SPINS_COUNT_PUSH, data => this.updateOnSpin(data));
6163
+ on(ClassId.SAW_SHOW_SPIN_PUSH, () => this.reloadMiniGameTemplate());
6164
+ on(ClassId.SAW_AKNOWLEDGE_RESPONSE, () => {
6165
+ this.reloadMiniGameTemplate();
6166
+ OCache.clear(ECacheContext.WSAPI, onUpdateContextKey.SAWHistory);
6167
+ });
6168
+ on(ClassId.SAW_DO_SPIN_RESPONSE, () => {
6169
+ this.reloadMiniGameTemplate();
6170
+ OCache.clear(ECacheContext.WSAPI, onUpdateContextKey.SAWHistory);
6171
+ });
6172
+ on(ClassId.MISSION_OPTIN_RESPONSE, () => this.updateMissions());
6173
+ on(ClassId.ACHIEVEMENT_CLAIM_PRIZE_RESPONSE, () => this.updateMissions());
6174
+ on(ClassId.RELOAD_ACHIEVEMENTS_EVENT, () => this.updateMissions());
6175
+ on(ClassId.TOURNAMENT_REGISTER_RESPONSE, () => this.updateTournaments());
6176
+ on(ClassId.BUY_SHOP_ITEM_RESPONSE, () => {
6177
+ this.updateStorePurchasedItems();
6178
+ this.updateStoreItems();
6179
+ });
6180
+ on(ClassId.CLIENT_ENGAGEMENT_EVENT_NEW, () => this.updateInboxMessages());
6181
+ on(ClassId.LOGOUT_RESPONSE, () => OCache.clearContext(ECacheContext.WSAPI));
6182
+ on(ClassId.IDENTIFY_RESPONSE, () => OCache.clearContext(ECacheContext.WSAPI));
6183
+ on(ClassId.JP_WIN_PUSH, _data => this.jackpotClearCache());
6184
+ on(ClassId.JP_OPTOUT_RESPONSE, _data => this.jackpotClearCache());
6185
+ on(ClassId.JP_OPTIN_RESPONSE, _data => this.jackpotClearCache());
6186
+ on(ClassId.CLAIM_BONUS_RESPONSE, () => this.updateBonuses());
6187
+ on(ClassId.SAW_DO_SPIN_BATCH_RESPONSE, () => {
6188
+ this.reloadMiniGameTemplate();
6189
+ OCache.clear(ECacheContext.WSAPI, onUpdateContextKey.SAWHistory);
6190
+ });
6191
+ on(ClassId.RAF_CLAIM_PRIZE_RESPONSE, () => this.updateRaffles());
6192
+ on(ClassId.GET_INBOX_MESSAGES_RESPONSE, res => {
6193
+ if (res.unread_count !== undefined && res.unread_count !== null) {
6194
+ this.updateInboxUnreadCount(res.unread_count);
6195
+ }
6196
+ });
6197
+ on(ClassId.CLIENT_PUBLIC_PROPERTIES_CHANGED_EVENT, data => {
6198
+ var _data$props, _data$props2, _data$props3, _data$props4, _data$props5;
6199
+ if ((data == null || (_data$props = data.props) == null ? void 0 : _data$props.core_inbox_unread_count) !== undefined && (data == null || (_data$props2 = data.props) == null ? void 0 : _data$props2.core_inbox_unread_count) !== null) {
6200
+ this.updateInboxUnreadCount(data.props.core_inbox_unread_count);
6201
+ }
6202
+ if ((data == null || (_data$props3 = data.props) == null ? void 0 : _data$props3.ach_points_balance) !== undefined || (data == null || (_data$props4 = data.props) == null ? void 0 : _data$props4.ach_gems_balance) !== undefined || (data == null || (_data$props5 = data.props) == null ? void 0 : _data$props5.ach_diamonds_balance) !== undefined) {
6203
+ this.notifyActivityLogUpdate();
6204
+ }
6205
+ });
6206
+ on(ClassId.RAF_OPTIN_RESPONSE, () => this.updateRaffles());
6207
+ }
6208
+ }
4010
6209
  }
4011
6210
 
4012
6211
  var AchCustomSectionType;