@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.
- package/.nvmrc +1 -1
- package/README.md +31 -2
- package/dist/Store/BuyStoreItemErrorCode.d.ts +9 -1
- package/dist/Tournaments/TournamentRegistrationError.d.ts +3 -1
- package/dist/WSAPI/WSAPI.d.ts +13 -1235
- package/dist/WSAPI/WSAPIAvatars.d.ts +78 -0
- package/dist/WSAPI/WSAPIBase.d.ts +71 -0
- package/dist/WSAPI/WSAPIBonuses.d.ts +26 -0
- package/dist/WSAPI/WSAPIClans.d.ts +388 -0
- package/dist/WSAPI/WSAPIGamePick.d.ts +308 -0
- package/dist/WSAPI/WSAPIGeneral.d.ts +30 -0
- package/dist/WSAPI/WSAPIInbox.d.ts +126 -0
- package/dist/WSAPI/WSAPIJackpots.d.ts +109 -0
- package/dist/WSAPI/WSAPILeaderBoard.d.ts +25 -0
- package/dist/WSAPI/WSAPIMiniGames.d.ts +94 -0
- package/dist/WSAPI/WSAPIMissions.d.ts +517 -0
- package/dist/WSAPI/WSAPIRaffles.d.ts +119 -0
- package/dist/WSAPI/WSAPIStore.d.ts +656 -0
- package/dist/WSAPI/WSAPITournaments.d.ts +501 -0
- package/dist/WSAPI/WSAPITypes.d.ts +145 -85
- package/dist/WSAPI/WSAPIUser.d.ts +383 -0
- package/dist/index.js +3818 -1528
- package/dist/index.js.map +1 -1
- package/dist/index.modern.mjs +3180 -981
- package/dist/index.modern.mjs.map +1 -1
- package/docs/api/README.md +56 -2
- package/docs/api/classes/WSAPIAvatars.md +116 -0
- package/docs/api/classes/WSAPIBonuses.md +50 -0
- package/docs/api/classes/WSAPIClans.md +66 -0
- package/docs/api/classes/WSAPIGamePick.md +181 -0
- package/docs/api/classes/WSAPIGeneral.md +63 -0
- package/docs/api/classes/WSAPIInbox.md +277 -0
- package/docs/api/classes/WSAPIJackpots.md +185 -0
- package/docs/api/classes/WSAPILeaderBoard.md +39 -0
- package/docs/api/classes/WSAPIMiniGames.md +175 -0
- package/docs/api/classes/WSAPIMissions.md +611 -0
- package/docs/api/classes/WSAPIRaffles.md +195 -0
- package/docs/api/classes/WSAPIStore.md +744 -0
- package/docs/api/classes/WSAPITournaments.md +104 -0
- package/docs/api/classes/WSAPIUser.md +200 -0
- package/docs/api/enumerations/AchievementClaimPeriodTypeId.md +13 -0
- package/docs/api/enumerations/BuyStoreItemErrorCode.md +32 -0
- package/docs/api/interfaces/ClanPrizePublicMeta.md +41 -0
- package/docs/api/interfaces/ClanPrizeStructureEntry.md +7 -1
- package/docs/api/interfaces/ClanPublicMeta.md +4 -0
- package/docs/api/interfaces/SAWTemplateUI.md +10 -0
- package/docs/api/interfaces/TAchCategory.md +19 -4
- package/docs/api/interfaces/TBuyStoreItemResult.md +26 -8
- package/docs/api/interfaces/TMissionClaimRewardResult.md +14 -3
- package/docs/api/interfaces/TMissionOptInResult.md +10 -3
- package/docs/api/interfaces/TMissionOrBadge.md +6 -2
- package/docs/api/interfaces/TStoreItem.md +21 -1
- package/docs/api/interfaces/TTournamentDetailed.md +8 -0
- package/docs/api/interfaces/UserAchievement.md +1 -1
- package/docs/ui/clans/UIGuide_getClanInfo.md +153 -0
- package/docs/ui/clans/UIGuide_getClans.md +172 -0
- package/docs/ui/clans/UIGuide_joinClan.md +196 -0
- package/docs/ui/missions/UIGuide_getAchCategories.md +17 -0
- package/docs/ui/missions/UIGuide_getBadges.md +237 -0
- package/docs/ui/missions/UIGuide_getMissions.md +268 -0
- package/docs/ui/missions/UIGuide_requestMissionClaimReward.md +21 -0
- package/docs/ui/missions/UIGuide_requestMissionOptIn.md +16 -0
- package/docs/ui/store/UIGuide_buyStoreItem.md +133 -0
- package/docs/ui/store/UIGuide_getStoreCategories.md +19 -0
- package/docs/ui/store/UIGuide_getStoreItems.md +264 -0
- package/docs/ui/store/UIGuide_getStorePurchasedItems.md +122 -0
- package/docs/ui/tournaments/UIGuide_getClanTournamentPlayers.md +140 -0
- package/docs/ui/tournaments/UIGuide_getTournamentInstanceInfo.md +265 -0
- package/docs/ui/tournaments/UIGuide_getTournamentsList.md +236 -0
- package/docs/ui/tournaments/UIGuide_registerInTournament.md +156 -0
- package/docs/ui/user/UIGuide_getLevels.md +162 -0
- package/docs/ui/user/UIGuide_getUserProfile.md +213 -0
- package/package.json +3 -3
- package/scripts/postdoc-cleanup.js +195 -0
- package/src/Missions/MissionsUtils.ts +5 -2
- package/src/Store/BuyStoreItemErrorCode.ts +8 -0
- package/src/Store/StoreItem.ts +1 -0
- package/src/Store/StoreItemPurchased.ts +1 -0
- package/src/Store/StoreItemType.ts +1 -1
- package/src/Tournaments/TournamentRegistrationError.ts +4 -0
- package/src/WSAPI/WSAPI.ts +27 -2007
- package/src/WSAPI/WSAPIAvatars.ts +127 -0
- package/src/WSAPI/WSAPIBase.ts +93 -0
- package/src/WSAPI/WSAPIBonuses.ts +54 -0
- package/src/WSAPI/WSAPIClans.ts +417 -0
- package/src/WSAPI/WSAPIGamePick.ts +382 -0
- package/src/WSAPI/WSAPIGeneral.ts +57 -0
- package/src/WSAPI/WSAPIInbox.ts +222 -0
- package/src/WSAPI/WSAPIJackpots.ts +212 -0
- package/src/WSAPI/WSAPILeaderBoard.ts +40 -0
- package/src/WSAPI/WSAPIMiniGames.ts +181 -0
- package/src/WSAPI/WSAPIMissions.ts +572 -0
- package/src/WSAPI/WSAPIRaffles.ts +175 -0
- package/src/WSAPI/WSAPIStore.ts +732 -0
- package/src/WSAPI/WSAPITournaments.ts +541 -0
- package/src/WSAPI/WSAPITypes.ts +145 -87
- package/src/WSAPI/WSAPIUser.ts +456 -0
- package/tsconfig.json +17 -1
- package/docs/api/classes/WSAPI.md +0 -1927
package/dist/index.modern.mjs
CHANGED
|
@@ -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.
|
|
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'
|
|
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
|
-
/** @
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
*
|
|
2394
|
-
*
|
|
2395
|
-
*
|
|
2396
|
-
*
|
|
2397
|
-
*
|
|
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
|
-
|
|
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
|
-
/**
|
|
2441
|
-
*
|
|
2442
|
-
*
|
|
2443
|
-
*
|
|
2444
|
-
*
|
|
2445
|
-
*
|
|
2446
|
-
*
|
|
2447
|
-
*
|
|
2448
|
-
*
|
|
2449
|
-
*
|
|
2450
|
-
*
|
|
2451
|
-
*
|
|
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
|
|
2460
|
-
*
|
|
2461
|
-
*
|
|
2462
|
-
*
|
|
2463
|
-
*
|
|
2464
|
-
*
|
|
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
|
-
/**
|
|
2474
|
-
*
|
|
2475
|
-
*
|
|
2476
|
-
*
|
|
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
|
-
*
|
|
2481
|
-
*
|
|
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
|
-
*
|
|
2486
|
-
*
|
|
2487
|
-
*
|
|
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
|
|
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.
|
|
2789
|
+
this.onUpdateCallback.set(onUpdateContextKey.ActivityLog, onUpdate);
|
|
2497
2790
|
}
|
|
2498
|
-
return OCache.use(onUpdateContextKey.
|
|
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
|
-
*
|
|
2502
|
-
|
|
2503
|
-
|
|
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
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
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
|
-
*
|
|
2561
|
-
*
|
|
2562
|
-
*
|
|
2563
|
-
*
|
|
2564
|
-
*
|
|
2565
|
-
*
|
|
2566
|
-
*
|
|
2567
|
-
*
|
|
2568
|
-
*
|
|
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
|
-
*
|
|
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
|
-
/**
|
|
2590
|
-
*
|
|
2591
|
-
*
|
|
2592
|
-
*
|
|
2593
|
-
*
|
|
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
|
-
*
|
|
2610
|
-
*
|
|
2611
|
-
*
|
|
2612
|
-
*
|
|
2613
|
-
*
|
|
2614
|
-
*
|
|
2615
|
-
*
|
|
2616
|
-
*
|
|
2617
|
-
*
|
|
2618
|
-
*
|
|
2619
|
-
*
|
|
2620
|
-
*
|
|
2621
|
-
*
|
|
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
|
-
*
|
|
2630
|
-
*
|
|
2631
|
-
*
|
|
2632
|
-
*
|
|
2633
|
-
*
|
|
2634
|
-
*
|
|
2635
|
-
*
|
|
2636
|
-
*
|
|
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
|
|
2657
|
-
*
|
|
2658
|
-
*
|
|
2659
|
-
*
|
|
2660
|
-
*
|
|
2661
|
-
*
|
|
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
|
-
*
|
|
2666
|
-
*
|
|
2667
|
-
*
|
|
2668
|
-
*
|
|
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
|
-
|
|
2674
|
-
|
|
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
|
|
2678
|
-
*
|
|
2679
|
-
*
|
|
2680
|
-
*
|
|
2681
|
-
*
|
|
2682
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
2689
|
-
|
|
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
|
-
|
|
2695
|
-
|
|
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
|
|
2699
|
-
*
|
|
2700
|
-
*
|
|
2701
|
-
*
|
|
2702
|
-
*
|
|
2703
|
-
*
|
|
2704
|
-
*
|
|
2705
|
-
*
|
|
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
|
-
*
|
|
2710
|
-
*
|
|
2711
|
-
*
|
|
2712
|
-
*
|
|
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
|
|
4764
|
+
async getClans({
|
|
2718
4765
|
onUpdate
|
|
2719
4766
|
} = {}) {
|
|
2720
4767
|
if (onUpdate) {
|
|
2721
|
-
this.onUpdateCallback.set(onUpdateContextKey.
|
|
4768
|
+
this.onUpdateCallback.set(onUpdateContextKey.Clans, onUpdate);
|
|
2722
4769
|
}
|
|
2723
|
-
return OCache.use(onUpdateContextKey.
|
|
4770
|
+
return OCache.use(onUpdateContextKey.Clans, ECacheContext.WSAPI, () => this.api.clansGetListT(this.userExtId), CACHE_DATA_SEC);
|
|
2724
4771
|
}
|
|
2725
4772
|
/**
|
|
2726
|
-
* Returns the
|
|
2727
|
-
*
|
|
2728
|
-
*
|
|
2729
|
-
*
|
|
2730
|
-
*
|
|
2731
|
-
*
|
|
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
|
-
|
|
2734
|
-
|
|
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
|
|
2741
|
-
|
|
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
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
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.
|
|
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
|
|
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
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
if (
|
|
2765
|
-
this.
|
|
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
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
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
|
-
*
|
|
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.
|
|
2781
|
-
* console.log(
|
|
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
|
|
2786
|
-
|
|
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
|
-
*
|
|
2790
|
-
*
|
|
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.
|
|
2796
|
-
* console.log(
|
|
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
|
|
2802
|
-
|
|
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
|
|
2808
|
-
|
|
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
|
-
*
|
|
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
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
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
|
-
*
|
|
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
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
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
|
-
/**
|
|
2846
|
-
*
|
|
2847
|
-
* The 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.
|
|
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').
|
|
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
|
|
5228
|
+
async getRaffles({
|
|
2879
5229
|
onUpdate
|
|
2880
5230
|
} = {}) {
|
|
2881
5231
|
if (onUpdate) {
|
|
2882
|
-
this.onUpdateCallback.set(onUpdateContextKey.
|
|
5232
|
+
this.onUpdateCallback.set(onUpdateContextKey.Raffles, onUpdate);
|
|
2883
5233
|
}
|
|
2884
|
-
return OCache.use(onUpdateContextKey.
|
|
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
|
-
*
|
|
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
|
-
*
|
|
2908
|
-
*
|
|
2909
|
-
*
|
|
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
|
-
*
|
|
2919
|
-
*
|
|
2920
|
-
*
|
|
2921
|
-
*
|
|
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
|
|
2928
|
-
|
|
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
|
|
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
|
-
*
|
|
2958
|
-
*
|
|
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
|
-
*
|
|
2965
|
-
*
|
|
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
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
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
|
|
5291
|
+
return drawRunHistoryTransform(res);
|
|
2998
5292
|
}
|
|
2999
5293
|
/**
|
|
3000
|
-
* Returns
|
|
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
|
-
* **
|
|
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
|
-
*
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
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
|
|
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
|
-
*
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
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
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
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
|
|
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
|
|
3075
|
-
|
|
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
|
-
*
|
|
3083
|
-
|
|
3084
|
-
|
|
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.
|
|
3098
|
-
*
|
|
3099
|
-
* activityType: 31 // Inbox
|
|
5355
|
+
* _smartico.api.getMiniGames().then((result) => {
|
|
5356
|
+
* console.log(result);
|
|
3100
5357
|
* });
|
|
3101
5358
|
* ```
|
|
3102
5359
|
*
|
|
3103
|
-
* **Visitor mode
|
|
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
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
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
|
-
*
|
|
3117
|
-
*
|
|
3118
|
-
*
|
|
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.
|
|
3124
|
-
*
|
|
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
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
5391
|
+
async getMiniGamesHistory({
|
|
5392
|
+
limit,
|
|
5393
|
+
offset,
|
|
5394
|
+
saw_template_id
|
|
3141
5395
|
}) {
|
|
3142
|
-
this.api.
|
|
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
|
-
*
|
|
3146
|
-
* The
|
|
3147
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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.
|
|
5416
|
+
this.onUpdateCallback.set(onUpdateContextKey.Saw, onUpdate);
|
|
3185
5417
|
}
|
|
3186
|
-
|
|
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
|
|
3552
|
-
*
|
|
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.
|
|
3559
|
-
*
|
|
5951
|
+
* _smartico.api.getLeaderBoard(1).then((result) => {
|
|
5952
|
+
* console.log(result);
|
|
3560
5953
|
* });
|
|
3561
5954
|
* ```
|
|
3562
5955
|
*
|
|
3563
|
-
* **Visitor mode
|
|
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.
|
|
3576
|
-
*
|
|
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
|
|
3583
|
-
return OCache.use(onUpdateContextKey.
|
|
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
|
-
*
|
|
3587
|
-
*
|
|
3588
|
-
*
|
|
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.
|
|
3593
|
-
*
|
|
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
|
-
|
|
3600
|
-
|
|
5989
|
+
reportImpressionEvent({
|
|
5990
|
+
engagement_uid,
|
|
5991
|
+
activityType
|
|
5992
|
+
}) {
|
|
5993
|
+
this.api.reportEngagementImpression(this.userExtId, engagement_uid, activityType);
|
|
3601
5994
|
}
|
|
3602
5995
|
/**
|
|
3603
|
-
*
|
|
3604
|
-
*
|
|
3605
|
-
* and
|
|
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.
|
|
3611
|
-
*
|
|
3612
|
-
*
|
|
3613
|
-
*
|
|
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
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
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
|
-
*
|
|
3779
|
-
*
|
|
3780
|
-
*
|
|
3781
|
-
*
|
|
3782
|
-
*
|
|
3783
|
-
*
|
|
3784
|
-
*
|
|
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
|
|
3792
|
-
|
|
3793
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
3840
|
-
jp_template_id,
|
|
6056
|
+
async getInboxUnreadCount({
|
|
3841
6057
|
onUpdate
|
|
3842
|
-
}) {
|
|
6058
|
+
} = {}) {
|
|
3843
6059
|
if (onUpdate) {
|
|
3844
|
-
this.onUpdateCallback.set(onUpdateContextKey.
|
|
6060
|
+
this.onUpdateCallback.set(onUpdateContextKey.InboxUnreadCount, onUpdate);
|
|
3845
6061
|
}
|
|
3846
|
-
return OCache.use(onUpdateContextKey.
|
|
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
|
|
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
|
-
* **
|
|
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
|
|
3869
|
-
|
|
3870
|
-
return result;
|
|
6069
|
+
async getInboxMessageBody(messageGuid) {
|
|
6070
|
+
return await this.api.getInboxMessageBodyT(messageGuid);
|
|
3871
6071
|
}
|
|
3872
6072
|
/**
|
|
3873
|
-
*
|
|
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
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
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
|
-
*
|
|
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
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
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
|
-
*
|
|
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
|
|
3953
|
-
const
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
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
|
-
*
|
|
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
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
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
|
|
6121
|
+
* Requests to delete all inbox messages
|
|
3991
6122
|
*
|
|
3992
6123
|
* **Visitor mode: not supported**
|
|
3993
6124
|
*/
|
|
3994
|
-
async
|
|
3995
|
-
|
|
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;
|