@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
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
import { ECacheContext, OCache } from '../OCache';
|
|
2
|
+
import {
|
|
3
|
+
TAchCategory,
|
|
4
|
+
TMissionClaimRewardResult,
|
|
5
|
+
TMissionOptInResult,
|
|
6
|
+
TMissionOrBadge,
|
|
7
|
+
} from './WSAPITypes';
|
|
8
|
+
import {
|
|
9
|
+
CACHE_DATA_SEC,
|
|
10
|
+
onUpdateContextKey,
|
|
11
|
+
} from './WSAPIBase';
|
|
12
|
+
import { WSAPIGeneral } from './WSAPIGeneral';
|
|
13
|
+
|
|
14
|
+
/** @group Missions */
|
|
15
|
+
export class WSAPIMissions extends WSAPIGeneral {
|
|
16
|
+
/**
|
|
17
|
+
* Returns all missions visible to the current user, scoped server-side
|
|
18
|
+
* to what the user is qualified to see. Optionally subscribes to live
|
|
19
|
+
* updates via `onUpdate`: the callback is invoked with the full, refreshed
|
|
20
|
+
* mission array whenever the server pushes a mission-state-changing event.
|
|
21
|
+
*
|
|
22
|
+
* The returned list is the canonical source of truth for mission state
|
|
23
|
+
* (`is_opted_in`, `progress`, `is_completed`, `completion_count`, etc.).
|
|
24
|
+
* Consumers should NEVER mutate it optimistically — always wait for the
|
|
25
|
+
* `onUpdate` callback (or a fresh `getMissions()` call after the 30s cache
|
|
26
|
+
* window expires) to observe the new state.
|
|
27
|
+
*
|
|
28
|
+
* @remarks
|
|
29
|
+
* **Subscription model (`onUpdate`)**
|
|
30
|
+
* The callback receives the FULL refreshed mission array (never a
|
|
31
|
+
* diff/patch). Each subsequent call to `getMissions({ onUpdate })`
|
|
32
|
+
* REPLACES the prior callback — only one active subscriber at a time.
|
|
33
|
+
* Pass `onUpdate: undefined` (or omit it) to keep the prior callback in
|
|
34
|
+
* place; the callback is never auto-cleared.
|
|
35
|
+
*
|
|
36
|
+
* **Update triggers** — the callback fires when:
|
|
37
|
+
* 1. **Mutation responses**: `requestMissionOptIn(...)` or
|
|
38
|
+
* `requestMissionClaimReward(...)` resolves (any `err_code`).
|
|
39
|
+
* 2. **Asynchronous server pushes**: a user activity (deposit, bet,
|
|
40
|
+
* login, etc.) advances task progress on a task configured to push
|
|
41
|
+
* updates, or completes a mission.
|
|
42
|
+
* 3. **Visibility changes**: the user's level changes, or other
|
|
43
|
+
* gamification visibility tags change.
|
|
44
|
+
*
|
|
45
|
+
* Operator-side BO mission-config edits do NOT push to connected
|
|
46
|
+
* clients — those changes surface only on the next cache miss (after
|
|
47
|
+
* the 30 s TTL) or alongside the next trigger from (1)–(3).
|
|
48
|
+
*
|
|
49
|
+
* **What's in the returned list / what's filtered server-side**
|
|
50
|
+
* The response is pre-filtered by the server. Missions EXCLUDED before
|
|
51
|
+
* reaching the SDK:
|
|
52
|
+
* - Missions in `DRAFT` or `ARCHIVED` status (see {@link AchievementStatus}).
|
|
53
|
+
* - Missions where the user fails visibility conditions. Exception:
|
|
54
|
+
* completed missions may remain visible after the user no longer
|
|
55
|
+
* qualifies (configurable per-brand).
|
|
56
|
+
* - `FEATURED_MANUALLY` missions that are still locked — they appear
|
|
57
|
+
* only after the user unlocks them.
|
|
58
|
+
* - Locked missions configured to hide while locked — UNLESS the
|
|
59
|
+
* mission is configured to expose immediately on unlock (then the
|
|
60
|
+
* locked mission is included as a teaser).
|
|
61
|
+
* - `RECURRING` missions between cycles, until the next cycle starts.
|
|
62
|
+
* - `RECURRING_QUANTITY` missions outside their `active_from_ts` /
|
|
63
|
+
* `active_till_ts` window.
|
|
64
|
+
*
|
|
65
|
+
* Client-side filtering you may still want: missions with
|
|
66
|
+
* `only_in_custom_section: true` are intended for their custom-section
|
|
67
|
+
* view only and should be hidden from the main mission list.
|
|
68
|
+
*
|
|
69
|
+
* **Reading state from the returned mission**
|
|
70
|
+
* Drive availability chips ("missed", "coming soon", "needs opt-in",
|
|
71
|
+
* "in progress", "expired") from `availability_status` (enum
|
|
72
|
+
* {@link AchievementAvailabilityStatus}) — it's the canonical derived
|
|
73
|
+
* state and reflects all server-side timing/visibility rules in one
|
|
74
|
+
* field. For time-limited and recurring missions, `dt_start` doubles
|
|
75
|
+
* as the opt-in timestamp (for opt-in missions) or unlock timestamp
|
|
76
|
+
* (for previously-locked missions); the expiration of a time-limited
|
|
77
|
+
* mission is `dt_start + time_limit_ms`. `next_recurrence_date_ts`
|
|
78
|
+
* is populated for `RECURRING` / `RECURRING_QUANTITY` missions but
|
|
79
|
+
* is no longer meaningful once the mission's `active_till_ts` has
|
|
80
|
+
* passed.
|
|
81
|
+
*
|
|
82
|
+
* **Refresh after a mutation**
|
|
83
|
+
* After `requestMissionOptIn(...)` or `requestMissionClaimReward(...)`
|
|
84
|
+
* resolves, the SDK auto-refreshes the mission cache; the `onUpdate`
|
|
85
|
+
* callback fires with the new array shortly after. `is_opted_in`,
|
|
86
|
+
* `progress`, and `is_completed` flip on the refreshed mission — do
|
|
87
|
+
* NOT mutate them optimistically while the round-trip is pending.
|
|
88
|
+
* Render any pending state from your own loading flag.
|
|
89
|
+
*
|
|
90
|
+
* **UI guidance**: see [UI Guide — `getMissions`](../../docs/ui/missions/UIGuide_getMissions.md).
|
|
91
|
+
*
|
|
92
|
+
* @param params Optional. Omit to fetch without subscribing.
|
|
93
|
+
* @param params.onUpdate Callback invoked with the full refreshed
|
|
94
|
+
* mission array on every server-pushed update
|
|
95
|
+
* (after opt-in / claim-reward calls, or when
|
|
96
|
+
* the server pushes an asynchronous
|
|
97
|
+
* mission-state change). Each call to
|
|
98
|
+
* `getMissions` overwrites the prior callback.
|
|
99
|
+
* Never fires in visitor mode.
|
|
100
|
+
* @returns Promise resolving to the current list of
|
|
101
|
+
* missions visible to the user. Empty array if
|
|
102
|
+
* the user has no missions (or if the visitor
|
|
103
|
+
* proxy user has none configured).
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* // 1. Initial fetch + subscribe to live updates
|
|
108
|
+
* const missions = await window._smartico.api.getMissions({
|
|
109
|
+
* onUpdate: (refreshed) => {
|
|
110
|
+
* console.log('[smartico] mission list refreshed — re-render the entire mission list UI from this array, do not merge with prior state:', refreshed);
|
|
111
|
+
*
|
|
112
|
+
* // 2. onUpdate fires for: opt-in/claim responses, task-progress
|
|
113
|
+
* // pushes that complete a mission or move a "pushToClient" task,
|
|
114
|
+
* // and user-level changes. Treat each call as authoritative.
|
|
115
|
+
* const claimable = refreshed.filter(m =>
|
|
116
|
+
* m.is_completed && m.requires_prize_claim && !m.prize_claimed_date_ts,
|
|
117
|
+
* );
|
|
118
|
+
* if (claimable.length > 0) {
|
|
119
|
+
* console.log('[smartico] there are unclaimed mission prizes — surface a "Claim" CTA on each of these missions:', claimable.map(m => m.id));
|
|
120
|
+
* }
|
|
121
|
+
* },
|
|
122
|
+
* });
|
|
123
|
+
*
|
|
124
|
+
* // 3. Filter/render from the returned list. The server already excludes
|
|
125
|
+
* // DRAFT/ARCHIVED missions and ones the user can't see. You may need
|
|
126
|
+
* // client-side filters for view-specific concerns (custom sections,
|
|
127
|
+
* // completed/missed tabs).
|
|
128
|
+
* const generalView = missions
|
|
129
|
+
* .filter(m => !m.only_in_custom_section)
|
|
130
|
+
* .sort((a, b) => (a.position ?? 0) - (b.position ?? 0));
|
|
131
|
+
*
|
|
132
|
+
* console.log('[smartico] initial mission list ready — render', generalView.length, 'missions sorted by position');
|
|
133
|
+
*
|
|
134
|
+
* // 4. Visitor-mode equivalent: onUpdate is accepted but never fires.
|
|
135
|
+
* // Re-poll if you need fresh data.
|
|
136
|
+
* // const visitorMissions = await window._smartico.vapi('EN').getMissions();
|
|
137
|
+
* // console.log('[smartico] visitor missions are the brand proxy user\'s — per-user fields are not meaningful');
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
public async getMissions({ onUpdate }: { onUpdate?: (data: TMissionOrBadge[]) => void } = {}): Promise<TMissionOrBadge[]> {
|
|
141
|
+
if (onUpdate) {
|
|
142
|
+
this.onUpdateCallback.set(onUpdateContextKey.Missions, onUpdate);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return OCache.use(
|
|
146
|
+
onUpdateContextKey.Missions,
|
|
147
|
+
ECacheContext.WSAPI,
|
|
148
|
+
() => this.api.missionsGetItemsT(this.userExtId),
|
|
149
|
+
CACHE_DATA_SEC,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Returns all the badges available to the current user.
|
|
155
|
+
*
|
|
156
|
+
* Badges are completion-tracked achievements that auto-unlock once the user
|
|
157
|
+
* meets their criteria — there is no opt-in step, no claim step, and no
|
|
158
|
+
* task-description reveal. Contrast with {@link getMissions}, which returns
|
|
159
|
+
* the same `TMissionOrBadge[]` shape but represents explicit opt-in flows.
|
|
160
|
+
*
|
|
161
|
+
* @remarks
|
|
162
|
+
* **Why no `onUpdate` callback?**
|
|
163
|
+
* Unlike `getMissions({ onUpdate })`, this method does NOT accept an
|
|
164
|
+
* `onUpdate` subscription. Internally the SDK never refreshes the `Badges`
|
|
165
|
+
* cache from server pushes: the achievement-related push events
|
|
166
|
+
* (mission opt-in / claim / reload) refresh the missions cache, NOT
|
|
167
|
+
* the badges cache. The badges cache is flushed only by:
|
|
168
|
+
* - its 30-second TTL
|
|
169
|
+
* - a full SDK cache wipe at login / logout
|
|
170
|
+
*
|
|
171
|
+
* **How to refresh the badge list**
|
|
172
|
+
* If you need near-live badge state, poll manually after the TTL expires.
|
|
173
|
+
* The first call inside the 30 s window returns the cached payload; the
|
|
174
|
+
* first call after the TTL triggers a fresh server round-trip. Example:
|
|
175
|
+
* ```ts
|
|
176
|
+
* setInterval(async () => {
|
|
177
|
+
* const badges = await window._smartico.api.getBadges();
|
|
178
|
+
* console.log('[smartico] badge list polled — re-render badge grid from this array', badges);
|
|
179
|
+
* }, 30_000);
|
|
180
|
+
* ```
|
|
181
|
+
* Alternatively, re-fetch on a domain event your app already handles
|
|
182
|
+
* (e.g. after the user finishes a game round or claims a bonus).
|
|
183
|
+
*
|
|
184
|
+
* **Differences from missions** (same `TMissionOrBadge` shape, different
|
|
185
|
+
* feature)
|
|
186
|
+
* The opt-in, claim, recurring-cycle, and unlock-description fields on
|
|
187
|
+
* the returned objects are not populated for badges; treat their values
|
|
188
|
+
* as undefined / default. Time windows are absolute calendar timestamps
|
|
189
|
+
* (`active_from_ts` / `active_till_ts`), not opt-in-relative durations —
|
|
190
|
+
* drive availability chips from the SDK-computed `badgeTimeLimitState`
|
|
191
|
+
* (enum {@link BadgesTimeLimitStates}), not `time_limit_ms`. Locking for
|
|
192
|
+
* badges is purely time-based: `is_locked` is `true` only when
|
|
193
|
+
* `badgeTimeLimitState === BadgesTimeLimitStates.BeforeStartDate` (the
|
|
194
|
+
* time window hasn't started yet). The primary navigation field is
|
|
195
|
+
* `category_ids` — call {@link getAchCategories} to resolve category
|
|
196
|
+
* metadata and group badges by category.
|
|
197
|
+
*
|
|
198
|
+
* **Idempotency / Side effects**: fetch-only; safe to call repeatedly.
|
|
199
|
+
* The cache layer deduplicates concurrent calls within the TTL window.
|
|
200
|
+
*
|
|
201
|
+
* **UI guidance**: see [UI Guide — `getBadges`](../../docs/ui/missions/UIGuide_getBadges.md).
|
|
202
|
+
*
|
|
203
|
+
* @returns Promise resolving to `TMissionOrBadge[]`. Every item has
|
|
204
|
+
* `type === 'badge'`. Cached for 30 s under the `Badges` key.
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```ts
|
|
208
|
+
* const [badges, categories] = await Promise.all([
|
|
209
|
+
* window._smartico.api.getBadges(),
|
|
210
|
+
* window._smartico.api.getAchCategories(),
|
|
211
|
+
* ]);
|
|
212
|
+
*
|
|
213
|
+
* console.log('[smartico] badges fetched — render grouped grid with', badges.length, 'badges across', categories.length, 'categories');
|
|
214
|
+
*
|
|
215
|
+
* for (const category of categories) {
|
|
216
|
+
* const inCategory = badges.filter(b => b.category_ids?.includes(category.id));
|
|
217
|
+
* const completed = inCategory.filter(b => b.is_completed).length;
|
|
218
|
+
* console.log(`[smartico] render category section "${category.name}" header showing ${completed}/${inCategory.length}`);
|
|
219
|
+
*
|
|
220
|
+
* for (const badge of inCategory) {
|
|
221
|
+
* // Use badgeTimeLimitState (not time_limit_ms) for availability chip
|
|
222
|
+
* if (badge.badgeTimeLimitState === 0) { // BadgesTimeLimitStates.BeforeStartDate
|
|
223
|
+
* console.log('[smartico] badge not yet started — render grayscaled card with "Starts on" chip for badge', badge.id);
|
|
224
|
+
* } else if (badge.is_completed) {
|
|
225
|
+
* console.log('[smartico] badge completed — render with completed styling and check-mark for badge', badge.id);
|
|
226
|
+
* } else {
|
|
227
|
+
* console.log('[smartico] badge in progress — render with stage counter (completed_tasks / total) for badge', badge.id);
|
|
228
|
+
* }
|
|
229
|
+
* }
|
|
230
|
+
* }
|
|
231
|
+
*
|
|
232
|
+
* // No onUpdate available — poll if live state matters
|
|
233
|
+
* setInterval(async () => {
|
|
234
|
+
* try {
|
|
235
|
+
* const fresh = await window._smartico.api.getBadges();
|
|
236
|
+
* console.log('[smartico] badge poll tick — diff against previous list and re-render any changed badge cards', fresh);
|
|
237
|
+
* } catch (e) {
|
|
238
|
+
* console.error('[smartico] badge poll failed — keep showing last known state, retry on next tick:', e);
|
|
239
|
+
* }
|
|
240
|
+
* }, 30_000);
|
|
241
|
+
* ```
|
|
242
|
+
*/
|
|
243
|
+
public async getBadges(): Promise<TMissionOrBadge[]> {
|
|
244
|
+
return OCache.use(onUpdateContextKey.Badges, ECacheContext.WSAPI, () => this.api.badgetsGetItemsT(this.userExtId), CACHE_DATA_SEC);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Returns the list of active mission/badge categories configured for the current label.
|
|
249
|
+
* Categories are operator-defined groupings used to organize missions and badges into
|
|
250
|
+
* sections, tabs, or filter chips in the gamification UI. The same category list serves
|
|
251
|
+
* BOTH missions and badges — there is no separate "mission categories" vs "badge
|
|
252
|
+
* categories" split on the server.
|
|
253
|
+
*
|
|
254
|
+
* @remarks
|
|
255
|
+
* **What categories are**
|
|
256
|
+
* - Operator-defined groupings, configured per-label by the brand operator.
|
|
257
|
+
* Only active categories are returned; inactive/deleted ones are filtered out.
|
|
258
|
+
* - Each `TAchCategory` exposes `{ id, name, order }`. `name` is the display label;
|
|
259
|
+
* `order` is the relative position (lower = appears first).
|
|
260
|
+
* - The same array covers BOTH missions and badges — categories are not filtered by
|
|
261
|
+
* entity type on the server.
|
|
262
|
+
*
|
|
263
|
+
* **Localization / translation**
|
|
264
|
+
* - `name` is **pre-translated server-side** to the authenticated user's stored language.
|
|
265
|
+
* In visitor mode, the language passed to `_smartico.vapi(lang)` drives the
|
|
266
|
+
* translation. Consumers never need to translate `name` themselves.
|
|
267
|
+
* - Fallback if no translation exists for the user's language: the brand's EN value
|
|
268
|
+
* is returned. The field is never null.
|
|
269
|
+
*
|
|
270
|
+
* **Sort order**
|
|
271
|
+
* The server does NOT pre-sort. Sort client-side before rendering:
|
|
272
|
+
* `categories.sort((a, b) => a.order - b.order)`.
|
|
273
|
+
*
|
|
274
|
+
* **How missions/badges join to categories**
|
|
275
|
+
* Each `TMissionOrBadge` exposes `category_ids: number[]` (zero-or-more,
|
|
276
|
+
* many-to-many). A single mission/badge can belong to multiple categories
|
|
277
|
+
* simultaneously, or to none. To render a "category → items" view:
|
|
278
|
+
* `missions.filter(m => m.category_ids.includes(cat.id))`.
|
|
279
|
+
*
|
|
280
|
+
* **Uncategorized items**
|
|
281
|
+
* Items with `category_ids: []` are NOT represented by a synthetic "Other" entry.
|
|
282
|
+
* If your UI needs one, generate it client-side by checking
|
|
283
|
+
* `item.category_ids.length === 0`.
|
|
284
|
+
*
|
|
285
|
+
* **Empty categories**
|
|
286
|
+
* A category may be returned even if zero missions/badges reference it. Filter
|
|
287
|
+
* out empty categories on the consumer side if you don't want to render section
|
|
288
|
+
* headers for them.
|
|
289
|
+
*
|
|
290
|
+
* **Refresh cadence / cache**
|
|
291
|
+
* - The SDK caches the response for 30 seconds. Subsequent calls within that
|
|
292
|
+
* window return the cached array without a network round-trip.
|
|
293
|
+
* - The cache is NOT invalidated by mission/badge push events — operator-driven
|
|
294
|
+
* category changes propagate at the next 30 s cache miss.
|
|
295
|
+
* - Cache is fully cleared on login / logout.
|
|
296
|
+
* - In **visitor mode** additional server-side caching applies on top of the
|
|
297
|
+
* SDK cache; worst-case staleness for an edited/added category is on the order
|
|
298
|
+
* of a few minutes for anonymous viewers.
|
|
299
|
+
*
|
|
300
|
+
* **Idempotency**: safe. Read-only metadata fetch. Cache de-duplicates concurrent calls.
|
|
301
|
+
*
|
|
302
|
+
* **Side effects**: none — pure metadata read.
|
|
303
|
+
*
|
|
304
|
+
* **UI guidance**: see [UI Guide — `getAchCategories`](../../docs/ui/missions/UIGuide_getAchCategories.md).
|
|
305
|
+
*
|
|
306
|
+
* @returns Array of `{ id, name, order }`. Empty array if no active categories are
|
|
307
|
+
* configured for the label. `name` is pre-translated. Order is NOT applied — caller
|
|
308
|
+
* must sort.
|
|
309
|
+
*
|
|
310
|
+
* @example
|
|
311
|
+
* ```ts
|
|
312
|
+
* const categories = await window._smartico.api.getAchCategories();
|
|
313
|
+
*
|
|
314
|
+
* if (categories.length === 0) {
|
|
315
|
+
* console.log('[smartico] no categories configured — render missions/badges as a flat list without section headers');
|
|
316
|
+
* } else {
|
|
317
|
+
* const sorted = [...categories].sort((a, b) => a.order - b.order);
|
|
318
|
+
* console.log('[smartico] categories loaded — render one tab/section per entry in this order:', sorted.map(c => c.name));
|
|
319
|
+
*
|
|
320
|
+
* // Join to missions: render each category as a section
|
|
321
|
+
* const missions = await window._smartico.api.getMissions();
|
|
322
|
+
* for (const cat of sorted) {
|
|
323
|
+
* const items = missions.filter(m => m.category_ids.includes(cat.id));
|
|
324
|
+
* if (items.length === 0) {
|
|
325
|
+
* console.log('[smartico] category has no missions — skip rendering its section header:', cat.name);
|
|
326
|
+
* continue;
|
|
327
|
+
* }
|
|
328
|
+
* console.log(`[smartico] render section "${cat.name}" with ${items.length} mission(s)`);
|
|
329
|
+
* }
|
|
330
|
+
*
|
|
331
|
+
* // Uncategorized items: surface in a caller-provided "Other" bucket if needed
|
|
332
|
+
* const uncategorized = missions.filter(m => m.category_ids.length === 0);
|
|
333
|
+
* if (uncategorized.length > 0) {
|
|
334
|
+
* console.log('[smartico] uncategorized missions exist — render a caller-provided "Other" section; server does NOT return id:-1:', uncategorized.length);
|
|
335
|
+
* }
|
|
336
|
+
* }
|
|
337
|
+
*
|
|
338
|
+
* // Visitor mode — same shape, language driven by vapi(lang) argument
|
|
339
|
+
* const visitorCats = await window._smartico.vapi('DE').getAchCategories();
|
|
340
|
+
* console.log('[smartico] visitor-mode categories (names translated to DE):', visitorCats);
|
|
341
|
+
* ```
|
|
342
|
+
*/
|
|
343
|
+
public async getAchCategories(): Promise<TAchCategory[]> {
|
|
344
|
+
return OCache.use(
|
|
345
|
+
onUpdateContextKey.AchCategories,
|
|
346
|
+
ECacheContext.WSAPI,
|
|
347
|
+
() => this.api.achGetCategoriesT(this.userExtId),
|
|
348
|
+
CACHE_DATA_SEC,
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Opt the current user in to a mission. The mission must have
|
|
354
|
+
* `is_requires_optin: true` (from `getMissions()`) and must not already
|
|
355
|
+
* be opted-in or locked. After a successful opt-in, the user's task
|
|
356
|
+
* progress starts counting toward this mission — events fired BEFORE
|
|
357
|
+
* opt-in do not count retroactively (except `UNLOCK_ACHIEVEMENT` tasks,
|
|
358
|
+
* which are exempt and can fire pre-opt-in).
|
|
359
|
+
*
|
|
360
|
+
* @remarks
|
|
361
|
+
* **Preconditions**
|
|
362
|
+
* Only call when the mission has `is_requires_optin === true`,
|
|
363
|
+
* `is_opted_in === false`, and `is_locked === false`. Calling on a
|
|
364
|
+
* mission that does not require opt-in is wasted; calling twice returns
|
|
365
|
+
* `40010`.
|
|
366
|
+
*
|
|
367
|
+
* **Error codes** (in `err_code`)
|
|
368
|
+
* - `0` — success; task progress counting begins
|
|
369
|
+
* - `105` — wrong mission id (different label) OR visibility conditions
|
|
370
|
+
* not met. Same code covers both; use `err_message` to disambiguate.
|
|
371
|
+
* - `40010` — already opted-in. Safe to treat as "no-op success"; usually
|
|
372
|
+
* means local mission state was stale (e.g. user opted in from another
|
|
373
|
+
* session/tab).
|
|
374
|
+
* - `40013` — mission not opt-in-able: missing `requires_optin`, in
|
|
375
|
+
* DRAFT/ARCHIVED state, or outside its `active_from_ts` /
|
|
376
|
+
* `active_till_ts` window.
|
|
377
|
+
* - `40014` — mission is locked; user has unlock-tasks remaining.
|
|
378
|
+
* Surface the mission's `unlock_mission_description` to guide them.
|
|
379
|
+
* - other non-zero — generic server error. Surface `err_message` if any.
|
|
380
|
+
*
|
|
381
|
+
* **Time-limited missions**
|
|
382
|
+
* For missions with `time_limit_ms > 0`, the countdown begins at opt-in
|
|
383
|
+
* time, not at mission creation. Server records expiration as
|
|
384
|
+
* `optin_date + time_limit_ms`.
|
|
385
|
+
*
|
|
386
|
+
* **Refresh after success**
|
|
387
|
+
* The SDK refreshes its mission cache automatically on opt-in response;
|
|
388
|
+
* any `onUpdate` callback passed to a prior `getMissions({ onUpdate })`
|
|
389
|
+
* fires with the new state shortly after. No manual re-fetch needed.
|
|
390
|
+
*
|
|
391
|
+
* **Idempotency**: not idempotent. A second call returns `40010`. Guard
|
|
392
|
+
* the call site against double-clicks.
|
|
393
|
+
*
|
|
394
|
+
* **Side effects**: does NOT award points / badges / levels directly.
|
|
395
|
+
* Rewards come from task completion, which is itself gated on opt-in.
|
|
396
|
+
* May affect visibility of other missions whose visibility conditions
|
|
397
|
+
* depend on this opt-in.
|
|
398
|
+
*
|
|
399
|
+
* **UI guidance**: see [UI Guide — `requestMissionOptIn`](../../docs/ui/missions/UIGuide_requestMissionOptIn.md).
|
|
400
|
+
*
|
|
401
|
+
* **Visitor mode: not supported**
|
|
402
|
+
*
|
|
403
|
+
* @param mission_id The mission `id` (from `getMissions()`)
|
|
404
|
+
* @returns `{ err_code, err_message }`; success when `err_code === 0`
|
|
405
|
+
*
|
|
406
|
+
* @example
|
|
407
|
+
* ```ts
|
|
408
|
+
* const missions = await window._smartico.api.getMissions({
|
|
409
|
+
* onUpdate: (m) => console.log('[smartico] mission list updated — re-render mission UI from this new array', m),
|
|
410
|
+
* });
|
|
411
|
+
* const mission = missions.find(m => m.id === missionId);
|
|
412
|
+
*
|
|
413
|
+
* if (!mission || !mission.is_requires_optin || mission.is_opted_in || mission.is_locked) {
|
|
414
|
+
* console.log('[smartico] opt-in not applicable — keep UI as-is');
|
|
415
|
+
* return;
|
|
416
|
+
* }
|
|
417
|
+
*
|
|
418
|
+
* console.log('[smartico] opt-in starting — show loading spinner on opt-in button and disable it to prevent double-clicks');
|
|
419
|
+
* const r = await window._smartico.api.requestMissionOptIn(mission.id);
|
|
420
|
+
* console.log('[smartico] opt-in response received — clear loading spinner');
|
|
421
|
+
*
|
|
422
|
+
* if (r.err_code === 0 || r.err_code === 40010) {
|
|
423
|
+
* console.log('[smartico] opt-in succeeded (or was already done) — onUpdate above will fire with refreshed mission state; no manual re-fetch needed');
|
|
424
|
+
* } else if (r.err_code === 40014) {
|
|
425
|
+
* console.error('[smartico] mission is locked — surface the unlock description as guidance:', mission.unlock_mission_description);
|
|
426
|
+
* } else {
|
|
427
|
+
* console.error('[smartico] opt-in failed — show an error toast with this message to the user:', r.err_message);
|
|
428
|
+
* }
|
|
429
|
+
* ```
|
|
430
|
+
*/
|
|
431
|
+
public async requestMissionOptIn(mission_id: number): Promise<TMissionOptInResult> {
|
|
432
|
+
const r = await this.api.missionOptIn(this.userExtId, mission_id);
|
|
433
|
+
|
|
434
|
+
const o: TMissionOptInResult = {
|
|
435
|
+
err_code: r.errCode,
|
|
436
|
+
err_message: r.errMsg,
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
return o;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Claim the prize for a completed mission. This is the step that runs
|
|
444
|
+
* AFTER `requestMissionOptIn()` (when applicable) and AFTER the user has
|
|
445
|
+
* completed all of the mission's tasks. Only call when the mission has
|
|
446
|
+
* `requires_prize_claim === true`, `is_completed === true`, and the prize
|
|
447
|
+
* has not already been claimed.
|
|
448
|
+
*
|
|
449
|
+
* The points / bonuses / badge grants associated with the mission are
|
|
450
|
+
* awarded server-side synchronously inside this call (before the response
|
|
451
|
+
* is returned), so a successful response (`err_code === 0`) means the
|
|
452
|
+
* reward has already landed in the user's account.
|
|
453
|
+
*
|
|
454
|
+
* @remarks
|
|
455
|
+
* **Preconditions**
|
|
456
|
+
* Only call when, on the mission object from `getMissions()`:
|
|
457
|
+
* - `is_completed === true`
|
|
458
|
+
* - `requires_prize_claim === true`
|
|
459
|
+
* - `prize_claimed_date_ts == null` (i.e. not already claimed)
|
|
460
|
+
* - `prize_claim_expiration_date` is `null` OR still in the future
|
|
461
|
+
*
|
|
462
|
+
* The `ach_completed_id` argument must be passed straight from the same
|
|
463
|
+
* mission object (`mission.ach_completed_id`). It is a server-side primary
|
|
464
|
+
* key identifying the specific completion row; the SDK consumer should
|
|
465
|
+
* never synthesise or cache it across sessions — always re-read it from a
|
|
466
|
+
* fresh `getMissions()` result.
|
|
467
|
+
*
|
|
468
|
+
* **Error codes** (in `err_code`)
|
|
469
|
+
* - `0` — success; the prize was claimed and rewards have been credited.
|
|
470
|
+
* - `1` — generic server error. Covers several distinct underlying
|
|
471
|
+
* conditions, all collapsed into the same code: stale or wrong
|
|
472
|
+
* `ach_completed_id` (does not match user / mission), the mission no
|
|
473
|
+
* longer requiring a prize claim, the mission being archived/draft,
|
|
474
|
+
* label mismatch, or the completion being too old (server enforces a
|
|
475
|
+
* freshness window of several months). Recovery: re-fetch missions
|
|
476
|
+
* with `getMissions()`; if the mission still appears claimable,
|
|
477
|
+
* surface a generic error.
|
|
478
|
+
* - `40015` — claim window expired (`prize_claim_expiration_date` has
|
|
479
|
+
* passed). Show a "claim period ended" message; do not retry.
|
|
480
|
+
* - `40016` — mission is not completed yet. Indicates a race or a stale
|
|
481
|
+
* local cache. Re-fetch via `getMissions()`; if `is_completed` is still
|
|
482
|
+
* false, the UI should hide the claim button.
|
|
483
|
+
* - `40017` — prize already claimed. Treat as idempotent success: refresh
|
|
484
|
+
* the mission list (auto-refresh will fire) and hide the claim button.
|
|
485
|
+
* Usually means another tab/session already claimed it.
|
|
486
|
+
* - other non-zero — generic server error. Surface `err_message` if any.
|
|
487
|
+
*
|
|
488
|
+
* **Recurring missions**
|
|
489
|
+
* The server transparently handles both one-shot and recurring mission
|
|
490
|
+
* completions. The SDK consumer passes a single `ach_completed_id`
|
|
491
|
+
* regardless of mission type; the server resolves the correct completion.
|
|
492
|
+
*
|
|
493
|
+
* **Refresh after success**
|
|
494
|
+
* On any claim response (success OR failure), the SDK automatically
|
|
495
|
+
* re-fetches the mission list and fires any `onUpdate` callback
|
|
496
|
+
* registered via `getMissions({ onUpdate })`. No manual re-fetch needed.
|
|
497
|
+
* Note: points balance, badges, and level state arrive through their
|
|
498
|
+
* own push channels — subscribe to them separately if your UI shows
|
|
499
|
+
* those values.
|
|
500
|
+
*
|
|
501
|
+
* **Idempotency**: not idempotent at the row level. A second call on the
|
|
502
|
+
* same `ach_completed_id` returns `40017` (already claimed). For a stale
|
|
503
|
+
* `ach_completed_id` the second call returns `1`. Guard the call site
|
|
504
|
+
* against double-clicks; the SDK does not enforce its own in-flight lock.
|
|
505
|
+
*
|
|
506
|
+
* **Side effects** (consumer-observable effects of a successful claim):
|
|
507
|
+
* - Points credited to the user's balance.
|
|
508
|
+
* - CRM-rule activities executed (may grant bonuses, badges; may unlock
|
|
509
|
+
* follow-up missions, which arrive as a later mission auto-refresh).
|
|
510
|
+
* - Analytics / automation events fired downstream; not directly observable
|
|
511
|
+
* in the response, but visible via the mission auto-refresh and any
|
|
512
|
+
* subsequent bonus / badge updates.
|
|
513
|
+
*
|
|
514
|
+
* **UI guidance**: see [UI Guide — `requestMissionClaimReward`](../../docs/ui/missions/UIGuide_requestMissionClaimReward.md).
|
|
515
|
+
*
|
|
516
|
+
* @param mission_id The mission `ach_id` (from `getMissions()`).
|
|
517
|
+
* @param ach_completed_id The completion-row identifier, read from
|
|
518
|
+
* `mission.ach_completed_id` on the same mission
|
|
519
|
+
* object fetched via `getMissions()`. Never
|
|
520
|
+
* fabricate or cache across sessions.
|
|
521
|
+
* @returns `{ err_code, err_message }`; success when `err_code === 0`
|
|
522
|
+
* (or `40017` when treated as idempotent no-op).
|
|
523
|
+
*
|
|
524
|
+
* @example
|
|
525
|
+
* ```ts
|
|
526
|
+
* const missions = await window._smartico.api.getMissions({
|
|
527
|
+
* onUpdate: (m) => console.log('[smartico] mission list updated — re-render mission UI from this new array', m),
|
|
528
|
+
* });
|
|
529
|
+
* const mission = missions.find(m => m.ach_id === missionId);
|
|
530
|
+
*
|
|
531
|
+
* if (
|
|
532
|
+
* !mission ||
|
|
533
|
+
* !mission.is_completed ||
|
|
534
|
+
* !mission.requires_prize_claim ||
|
|
535
|
+
* mission.prize_claimed_date_ts ||
|
|
536
|
+
* (mission.prize_claim_expiration_date && Date.now() >= mission.prize_claim_expiration_date)
|
|
537
|
+
* ) {
|
|
538
|
+
* console.log('[smartico] claim not applicable — keep claim button hidden');
|
|
539
|
+
* return;
|
|
540
|
+
* }
|
|
541
|
+
*
|
|
542
|
+
* console.log('[smartico] claim starting — show loading spinner on claim button and disable it to prevent double-clicks');
|
|
543
|
+
* const r = await window._smartico.api.requestMissionClaimReward(mission.ach_id, mission.ach_completed_id);
|
|
544
|
+
* console.log('[smartico] claim response received — clear loading spinner');
|
|
545
|
+
*
|
|
546
|
+
* if (r.err_code === 0 || r.err_code === 40017) {
|
|
547
|
+
* 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');
|
|
548
|
+
* } else if (r.err_code === 40015) {
|
|
549
|
+
* console.error('[smartico] claim window expired — show a "claim period ended" message and remove the claim button:', mission.prize_claim_expiration_date);
|
|
550
|
+
* } else if (r.err_code === 40016) {
|
|
551
|
+
* console.error('[smartico] mission reported as not completed by server — local state was stale; auto-refresh will reconcile, do not retry');
|
|
552
|
+
* } else {
|
|
553
|
+
* 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);
|
|
554
|
+
* }
|
|
555
|
+
* ```
|
|
556
|
+
*/
|
|
557
|
+
public async requestMissionClaimReward(mission_id: number, ach_completed_id: number): Promise<TMissionClaimRewardResult> {
|
|
558
|
+
const r = await this.api.missionClaimPrize(this.userExtId, mission_id, ach_completed_id);
|
|
559
|
+
|
|
560
|
+
const o: TMissionClaimRewardResult = {
|
|
561
|
+
err_code: r.errCode,
|
|
562
|
+
err_message: r.errMsg,
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
return o;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
protected async updateMissions() {
|
|
569
|
+
const payload = await this.api.missionsGetItemsT(this.userExtId);
|
|
570
|
+
this.updateEntity(onUpdateContextKey.Missions, payload);
|
|
571
|
+
}
|
|
572
|
+
}
|