import { ClassId } from './Base/ClassId'; import { ProtocolRequest } from './Base/ProtocolRequest'; import { ProtocolResponse } from './Base/ProtocolResponse'; import { SAWGetTemplatesResponse } from './MiniGames/SAWGetTemplatesResponse'; import { SAWGetTemplatesRequest } from './MiniGames/SAWGetTemplatesRequest'; import { IntUtils } from './IntUtils'; import { ILogger } from './ILogger'; import { SAWDoAknowledgeRequest, SAWDoAknowledgeResponse, SAWDoSpinRequest, SAWDoSpinResponse, SAWHistoryTransform, SAWPrizesHistory, SAWSpinErrorCode, SAWTemplatesTransform, SAWWinningHistoryRequest, SAWWinningHistoryResponse, } from './MiniGames'; import { ECacheContext, OCache } from './OCache'; import { ActivityTypeLimited, CoreUtils, GetTranslationsRequest, GetTranslationsResponse, PublicLabelSettings, ResponseIdentify, TranslationArea, } from './Core'; import { GetLabelInfoResponse } from './Core/GetLabelInfoResponse'; import { GetLabelInfoRequest } from './Core/GetLabelInfoRequest'; import { GetInboxMessagesRequest, GetInboxMessagesResponse, InboxMessageBody, InboxMessageBodyTransform, InboxMessagesTransform, InboxReadStatus, MarkInboxMessageDeletedRequest, MarkInboxMessageDeletedResponse, MarkInboxMessageReadRequest, MarkInboxMessageReadResponse, MarkInboxMessageStarredRequest, MarkInboxMessageStarredResponse, } from './Inbox'; import { BuyStoreItemRequest, BuyStoreItemResponse, GetCategoriesStoreResponse, GetStoreHistoryRequest, GetStoreHistoryResponse, GetStoreItemsResponse, StoreCategoryTransform, StoreItemPurchasedTransform, StoreItemTransform, } from './Store'; import { AchCategoryTransform, AchClaimPrizeRequest, AchClaimPrizeResponse, AchievementOptinRequest, AchievementOptinResponse, AchievementType, GetAchCategoriesResponse, GetAchievementMapRequest, GetAchievementMapResponse, UserAchievementTransform, enrichUserAchievementsWithBadgeState, } from './Missions'; import { GetClanTournamentPlayersRequest, GetClanTournamentPlayersResponse, GetTournamentInfoRequest, GetTournamentInfoResponse, GetTournamentsRequest, GetTournamentsResponse, TournamentItemsTransform, TournamentRegisterRequest, TournamentRegisterResponse, clanTournamentPlayersTransform, tournamentInfoItemTransform, } from './Tournaments'; import { GetLeaderBoardsRequest, GetLeaderBoardsResponse, LeaderBoardDetails, LeaderBoardPeriodType } from './Leaderboard'; import { GetLevelMapResponse, GetLevelMapResponseTransform } from './Level'; import { WSAPI } from './WSAPI/WSAPI'; import { TInboxMessage, TInboxMessageBody, TLevel, TMiniGameTemplate, TMissionOrBadge, TStoreCategory, TAchCategory, TStoreItem, TTournament, TTournamentDetailed, LeaderBoardDetailsT, UserLevelExtraCountersT, TSegmentCheckResult, TUICustomSection, TBonus, TRaffle, TLevelCurrent, TActivityLog, TRaffleDraw, TAvatarDefinition, TAvatarCustomized, TAvatarPrompt, TClans, TClanInfo, TClanJoinResult, TClanTournamentPlayers, } from './WSAPI/WSAPITypes'; import { getLeaderBoardTransform } from './Leaderboard/LeaderBoards'; import { GetAchievementsUserInfoResponse } from './Core/GetAchievementsUserInfoResponse'; import { CheckSegmentMatchResponse } from './Core/CheckSegmentMatchResponse'; import { CheckSegmentMatchRequest } from './Core/CheckSegmentMatchRequest'; import { GetJackpotsPotsRequest, GetJackpotsPotsResponse, GetJackpotsRequest, GetJackpotsResponse, JackpotsOptinRequest, JackpotsOptinResponse, JackpotsOptoutRequest, JackpotsOptoutResponse, } from './Jackpots'; import { GetCustomSectionsRequest, GetCustomSectionsResponse, UICustomSectionTransform } from './CustomSections'; import { BonusItemsTransform, ClaimBonusRequest, ClaimBonusResponse, GetBonusesResponse } from './Bonuses'; import { GetBonusesRequest } from './Bonuses/GetBonusesRequest'; import { SAWDoSpinBatchResponse } from './MiniGames/SAWDoSpinBatchResponse'; import { SAWDoSpinBatchRequest } from './MiniGames/SAWDoSpinBatchRequest'; import { SAWDoAcknowledgeBatchRequest } from './MiniGames/SAWDoAcknowledgeBatchRequest'; import { SAWDoAcknowledgeBatchResponse } from './MiniGames/SAWDoAcknowledgeBatchResponse'; import { GetRelatedAchTourRequest } from './Missions/GetRelatedAchTourRequest'; import { GetRelatedAchTourResponse } from './Missions/GetRelatedAchTourResponse'; import { GetRafflesResponse, raffleTransform } from './Raffle/GetRafflesResponse'; import { GetRafflesRequest } from './Raffle/GetRafflesRequest'; import { GetActivityLogRequest, GetActivityLogResponse, ActivityLogTransform } from './ActivityLog'; import { GetClanListRequest, GetClanListResponse, GetClanInfoRequest, GetClanInfoResponse, JoinClanRequest, JoinClanResponse } from './Clans'; import { InboxCategories } from './Inbox/InboxCategories'; import { GetDrawRunRequest, GetDrawRunResponse, GetRaffleDrawRunsHistoryRequest, GetRaffleDrawRunsHistoryResponse, RaffleClaimPrizeRequest, RaffleClaimPrizeResponse, RaffleOptinRequest, RaffleOptinResponse, drawRunTransform, GetRaffleWonPrizesRequest, GetRaffleWonPrizesResponse } from './Raffle'; import { GetAvatarsListResponse, avatarDefinitionTransform } from './Avatars/GetAvatarsListResponse'; import { GetAvatarsCustomizedResponse, avatarCustomizedTransform } from './Avatars/GetAvatarsCustomizedResponse'; import { GetAvatarPromptsResponse, avatarPromptTransform } from './Avatars/GetAvatarPromptsResponse'; import { SetAvatarRequest, SetAvatarResponse } from './Avatars'; import { AvatarCustomizeResponse } from './Avatars/AvatarCustomizeResponse'; import { GetJackpotWinnersResponse, GetJackpotWinnersResponseTransform, JackpotWinnerHistory } from './Jackpots/GetJackpotWinnersResponse'; import { GetJackpotWinnersRequest } from './Jackpots/GetJackpotWinnersRequest'; import { GetJackpotEligibleGamesRequest } from './Jackpots/GetJackpotEligibleGamesRequest'; import { GetJackpotEligibleGamesResponse, GetJackpotEligibleGamesResponseTransform, JackpotEligibleGame, TGetJackpotEligibleGamesResponse } from './Jackpots/GetJackpotEligibleGamesResponse'; import { GamePickGameInfo, GamePickRound, GamePickRoundBoard, GamePickUserInfo, GamesApiResponse } from './GamePick'; const PUBLIC_API_URL = 'https://papi{ENV_ID}.smartico.ai/services/public'; const C_SOCKET_PROD = 'wss://api{ENV_ID}.smartico.ai/websocket/services'; const GAMES_API_URL = 'https://r-games-api.smr.vc'; const AVATAR_DOMAIN = 'https://img{ENV_ID}.smr.vc'; const DEFAULT_LANG_EN = 'EN'; const SMR_TO_CLOUDFRONT_DOMAINS: { [key: string]: string } = { 'img.smr.vc': 'd1am61onjxtys8.cloudfront.net', 'img3.smr.vc': 'd3dubbodzd2q05.cloudfront.net', 'img4.smr.vc': 'dvm0p9vsezqr2.cloudfront.net', 'img5.smr.vc': 'd3gen1ksvxhac8.cloudfront.net', 'img6.smr.vc': 'db1kmyg7iufeo.cloudfront.net', 'img7.smr.vc': 'd36om2g86xefo6.cloudfront.net', 'img8.smr.vc': 'd2zme31v54n5pb.cloudfront.net', 'static.smr.vc': 'dtt380pweilws.cloudfront.net', 'static3.smr.vc': 'd1qt8ake8g4imn.cloudfront.net', 'static4.smr.vc': 'd146b4m7rkvjkw.cloudfront.net', 'static5.smr.vc': 'd3l7suk1kl9rwh.cloudfront.net', 'static6.smr.vc': 'd121pfj16xdfcq.cloudfront.net', 'static7.smr.vc': 'd21deilz814qgl.cloudfront.net', 'static8.smr.vc': 'd1uffsroxjy2ku.cloudfront.net', }; // Single combined matcher built once, so a call is one pass instead of one per domain. const SMR_DOMAIN_REGEX = new RegExp( Object.keys(SMR_TO_CLOUDFRONT_DOMAINS) .map((d) => d.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) .join('|'), 'g', ); interface Tracker { label_api_key: string; userPublicProps: any; params?: { brand_key?: string }; on: (callBackKey: ClassId, func: (data: any) => void) => void; getLabelSetting: (key: PublicLabelSettings) => any; triggerExternalCallBack: (callBackKey: string, payload: any) => void; getExtUserId: () => string; } interface IOptions { logger?: ILogger; logCIDs?: ClassId[]; logHTTPTiming?: boolean; tracker?: Tracker; brand_api_key?: string; } type MessageSender = (message: any, publicApuUrl?: string, expectCID?: ClassId) => Promise; class SmarticoAPI { private publicUrl: string; private wsUrl: string; private inboxCdnUrl: string; private partnerUrl: string; private gamesApiUrl: string; private baseRgApiParams: Record; public avatarDomain: string; private avatarOriginDomain: string; private envId: number; private logger: ILogger; private logCIDs: ClassId[]; private logHTTPTiming: boolean; public tracker?: Tracker; public constructor( private label_api_key: string, private brand_api_key: string, private messageSender: MessageSender, options: IOptions = {}, ) { this.logger = options.logger || (console as any); if (this.logger.always === undefined) { this.logger.always = this.logger.info; } this.logCIDs = options.logCIDs || []; this.logHTTPTiming = options.logHTTPTiming || false; this.tracker = options.tracker; this.publicUrl = SmarticoAPI.getPublicUrl(label_api_key); this.wsUrl = SmarticoAPI.getPublicWsUrl(label_api_key); if (this.tracker) { // available only for real users this.baseRgApiParams = { env_id: String(SmarticoAPI.getEnvId(label_api_key)), label_api_key: label_api_key, brand_key: options.brand_api_key || this.tracker.params.brand_key, hash: IntUtils.uuid(), } } this.avatarDomain = SmarticoAPI.getAvatarUrl(label_api_key || options.tracker?.label_api_key); this.avatarOriginDomain = SmarticoAPI.getAvatarOriginUrl(label_api_key || options.tracker?.label_api_key); this.label_api_key = SmarticoAPI.getCleanLabelApiKey(label_api_key); } public static getEnvDnsSuffix(label_api_key: string): string { let ENV_ID = label_api_key.length === 38 ? label_api_key.substring(37, 38) : ''; if (ENV_ID === '1' || ENV_ID === '2') { ENV_ID = ''; } return ENV_ID; } public static replaceSmrDomainsWithCloudfront(value: string | { [key: string]: any }): T { if (!value) { return value as T; } const isString = typeof value === 'string'; const source = isString ? (value as string) : JSON.stringify(value); if (!source.includes('smr.vc')) return value as T; // ← skip even the regex const replaced = source.replace(SMR_DOMAIN_REGEX, (match) => SMR_TO_CLOUDFRONT_DOMAINS[match]); // No domain matched: skip the JSON re-parse and return the input untouched. if (replaced === source) { return value as T; } if (isString) { return replaced as T; } try { return JSON.parse(replaced); } catch (err) { return value as T; } } public static getEnvId(label_api_key: string): number { return label_api_key.length === 38 ? parseInt(label_api_key.substring(37, 38), 10) : 2; } public static getCleanLabelApiKey(label_api_key: string): string { return label_api_key.substring(0, 36); } public static getPublicUrl(label_api_key: string): string { return PUBLIC_API_URL.replace('{ENV_ID}', SmarticoAPI.getEnvDnsSuffix(label_api_key)); } public static getPublicWsUrl(label_api_key: string): string { return C_SOCKET_PROD.replace('{ENV_ID}', SmarticoAPI.getEnvDnsSuffix(label_api_key)); } public static getAvatarUrl(label_api_key: string): string { const avatarUrl = AVATAR_DOMAIN.replace('{ENV_ID}', SmarticoAPI.getEnvDnsSuffix(label_api_key)); return SmarticoAPI.replaceSmrDomainsWithCloudfront(avatarUrl); } public static getAvatarOriginUrl(label_api_key: string): string { return AVATAR_DOMAIN.replace('{ENV_ID}', SmarticoAPI.getEnvDnsSuffix(label_api_key)); } private async send(message: any, expectCID?: ClassId, force_language?: string): Promise { if (this.logCIDs.includes(message.cid)) { this.logger.info('REQ', message); } if (force_language) { message.force_language = force_language; } let result: any; try { const timeStart = new Date().getTime(); result = await this.messageSender(message, this.publicUrl, expectCID); if (result.errCode && result.errSetup) { throw new Error(result.errMessage); } result = SmarticoAPI.replaceSmrDomainsWithCloudfront(result); const timeEnd = new Date().getTime(); if (this.logHTTPTiming) { this.logger.always('HTTP time, ms:' + (timeEnd - timeStart)); } } catch (e) { this.logger.warn(`Failed to make request to smartico channel, L2. ${e.message}`, { url: this.publicUrl, request: message, error: e.message, }); throw new Error(`Failed to make request to smartico channel, L2 L1. ${e.message}`); } if (this.logCIDs.includes(message.cid)) { this.logger.info('RES', result); } if (expectCID) { if (Array.isArray(result)) { for (const str of result as string[]) { const obj: ProtocolResponse = JSON.parse(str); if (this.logCIDs.includes(obj.cid)) { this.logger.info('RES', result); } if (obj.cid === expectCID) { return obj as any; } } this.logger.error(`Cant find proper response in array, expected CID ${expectCID}`, { request: message, response: result, }); } else { return result; } } else { if (Array.isArray(result)) { if (result.length === 1) { const obj = JSON.parse(result[0]); if (this.logCIDs.includes(obj.cid)) { this.logger.info('RES', result); } return obj; } else { this.logger.error('Expected one response, but got array', { request: message, response: result }); } } return result; } } private buildMessage(user_ext_id: string, cid: ClassId, payload: Partial = {}): TResponse { const message: ProtocolRequest = { // AA: in fact we need api and brand keys and ext_user_id only in the context of HTTP // its not needed in WebSocket and can be deleted api_key: this.label_api_key, brand_key: this.brand_api_key, ext_user_id: user_ext_id, cid, uuid: IntUtils.uuid(), ts: new Date().getTime(), ...payload, }; if (message.ext_user_id === undefined || message.ext_user_id === null) { delete message.ext_user_id; } if (message.brand_key === undefined || message.brand_key === null) { delete message.brand_key; } return message as any; } public async coreReportCustomEvent(user_ext_id: string, eventType: string, payload: any = {}): Promise { const eventMessage = this.buildMessage(user_ext_id, ClassId.EVENT, { eventType, payload, }); const eventResponse = await this.send(eventMessage, ClassId.EVENT_RESPONSE); return eventResponse; } public async coreGetTranslations( user_ext_id: string, lang_code: string, areas: TranslationArea[], cacheSec: number = 60, ): Promise { if (lang_code === undefined || lang_code === null || (lang_code.trim && lang_code.trim() === '')) { lang_code = DEFAULT_LANG_EN; } const response = await OCache.use( `${lang_code}-${this.label_api_key}-${this.brand_api_key}`, ECacheContext.Translations, async () => { const tsBaseRQ = this.buildMessage( user_ext_id, ClassId.GET_TRANSLATIONS_REQUEST, { lang_code: DEFAULT_LANG_EN, hash_code: null, areas, }, ); const trBase = await this.send(tsBaseRQ, ClassId.GET_TRANSLATIONS_RESPONSE); if (lang_code !== DEFAULT_LANG_EN) { const trUserRQ = this.buildMessage( user_ext_id, ClassId.GET_TRANSLATIONS_REQUEST, { lang_code, hash_code: null, areas, }, ); const trUser = await this.send(trUserRQ, ClassId.GET_TRANSLATIONS_RESPONSE); Object.keys(trUser?.translations ?? {}).forEach((k) => { trBase.translations[k] = trUser.translations[k]; }); } return trBase; }, cacheSec, ); return response; } public async coreIdentifyLabel(user_ext_id: string, cacheSec: number = 60): Promise { return OCache.use( `${this.label_api_key} - ${this.brand_api_key}`, ECacheContext.LabelInfo, async () => { const message = this.buildMessage(user_ext_id, ClassId.INIT); return this.send(message, ClassId.INIT_RESPONSE); }, cacheSec, ); } public async coreIdentifyUser(user_ext_id: string): Promise { const message = this.buildMessage(user_ext_id, ClassId.IDENTIFY, { request_id: IntUtils.uuid(), // AA: do we need request_id? }); const r = await this.send(message, ClassId.IDENTIFY_RESPONSE); r.avatar_id = CoreUtils.avatarUrl(r.avatar_id, this.avatarDomain); return r; } public async coreChangeUsername( user_ext_id: string, public_username_custom: string, ): Promise<{ public_username_custom: string }> { const message = this.buildMessage(user_ext_id, ClassId.CLIENT_SET_CUSTOM_USERNAME_REQUEST, { public_username_custom, }); return await this.send(message, ClassId.CLIENT_SET_CUSTOM_USERNAME_RESPONSE); } public async coreCheckSegments(user_ext_id: string, segment_id: number[]): Promise { const message = this.buildMessage(user_ext_id, ClassId.CHECK_SEGMENT_MATCH_REQUEST, { segment_id, }); const results = await this.send(message, ClassId.CHECK_SEGMENT_MATCH_RESPONSE); return results.segments || []; } /** * Reports an engagement impression event (when engagement content is displayed to the user). */ public reportEngagementImpression( user_ext_id: string, engagement_uid: string, activityType: ActivityTypeLimited | number, ): void { const message = this.buildMessage(user_ext_id, ClassId.CLIENT_ENGAGEMENT_IMPRESSION_REQUEST, { engagement_uid, activityType, }); this.send(message).catch(() => { }); } /** * Reports an engagement action event (when user clicks/interacts with engagement content). */ public reportEngagementAction( user_ext_id: string, engagement_uid: string, activityType: ActivityTypeLimited | number, action?: string, ): void { const message = this.buildMessage(user_ext_id, ClassId.CLIENT_ENGAGEMENT_ACTION_REQUEST, { engagement_uid, activityType, ...(action && { action }), }); this.send(message).catch(() => { }); } public async jackpotGet( user_ext_id: string, filter?: { related_game_id?: string; jp_template_id?: number }, force_language?: string, ): Promise { const message = this.buildMessage( user_ext_id, ClassId.JP_GET_JACKPOTS_REQUEST, filter, ); return await this.send(message, ClassId.JP_GET_JACKPOTS_RESPONSE, force_language); } public async potGet(user_ext_id: string, filter: { jp_template_ids: number[] }): Promise { const message = this.buildMessage( user_ext_id, ClassId.JP_GET_LATEST_POTS_REQUEST, filter, ); return await this.send(message, ClassId.JP_GET_LATEST_POTS_RESPONSE); } public async jackpotOptIn(user_ext_id: string, payload: { jp_template_id: number }): Promise { const message = this.buildMessage( user_ext_id, ClassId.JP_OPTIN_REQUEST, payload, ); return await this.send(message, ClassId.JP_OPTIN_RESPONSE); } public async jackpotOptOut(user_ext_id: string, payload: { jp_template_id: number }): Promise { const message = this.buildMessage( user_ext_id, ClassId.JP_OPTOUT_REQUEST, payload, ); return await this.send(message, ClassId.JP_OPTOUT_RESPONSE); } public async getJackpotWinners(user_ext_id: string, limit: number = 20, offset: number = 0, jp_template_id: number): Promise { const message = this.buildMessage( user_ext_id, ClassId.JP_GET_WINNERS_REQUEST, { limit, offset, jp_template_id, }, ); return await this.send(message, ClassId.JP_GET_WINNERS_RESPONSE); } public async getJackpotWinnersT(user_ext_id: string, limit: number = 20, offset: number = 0, jp_template_id: number): Promise { return GetJackpotWinnersResponseTransform((await this.getJackpotWinners(user_ext_id, limit, offset, jp_template_id)).winners); } public async getJackpotEligibleGames(user_ext_id: string, { jp_template_id }: { jp_template_id: number }): Promise { const message = this.buildMessage( user_ext_id, ClassId.JP_GET_ELIGIBLE_GAMES_REQUEST, { jp_template_id }, ); return await this.send(message, ClassId.JP_GET_ELIGIBLE_GAMES_RESPONSE); } public async getJackpotEligibleGamesT(user_ext_id: string, { jp_template_id }: { jp_template_id: number }): Promise { return GetJackpotEligibleGamesResponseTransform(await this.getJackpotEligibleGames(user_ext_id, { jp_template_id })); } public async sawGetTemplates( user_ext_id: string, force_language?: string, is_visitor_mode: boolean = false, ): Promise { const message = this.buildMessage( user_ext_id, ClassId.SAW_GET_SPINS_REQUEST, { is_visitor_mode }, ); const response = await this.send(message, ClassId.SAW_GET_SPINS_RESPONSE, force_language); if (response && response.templates) { response.templates.forEach((t) => { if (t.jackpot_current !== undefined && t.jackpot_current !== null) { const jackpotValue = t.jackpot_current + (t.saw_template_ui_definition?.jackpot_symbol ? ' ' + t.saw_template_ui_definition?.jackpot_symbol : ''); t.saw_template_ui_definition.name = IntUtils.replaceAll( t.saw_template_ui_definition.name, '{{jackpot}}', jackpotValue, ); t.saw_template_ui_definition.description = IntUtils.replaceAll( t.saw_template_ui_definition.description, '{{jackpot}}', jackpotValue, ); t.saw_template_ui_definition.promo_text = IntUtils.replaceAll( t.saw_template_ui_definition.promo_text, '{{jackpot}}', jackpotValue, ); t.prizes.forEach((p) => { p.saw_prize_ui_definition.name = IntUtils.replaceAll( p.saw_prize_ui_definition.name, '{{jackpot}}', jackpotValue, ); p.saw_prize_ui_definition.aknowledge_message = IntUtils.replaceAll( p.saw_prize_ui_definition.aknowledge_message, '{{jackpot}}', jackpotValue, ); p.saw_prize_ui_definition.aknowledge_message_lose = IntUtils.replaceAll( p.saw_prize_ui_definition.aknowledge_message_lose, '{{jackpot}}', jackpotValue, ); }); } }); } return response; } public async sawGetTemplatesT(user_ext_id: string): Promise { return SAWTemplatesTransform((await this.sawGetTemplates(user_ext_id)).templates); } public async doAcknowledgeRequest(user_ext_id: string, request_id: string, lose?: boolean): Promise { const message = this.buildMessage( user_ext_id, ClassId.SAW_AKNOWLEDGE_REQUEST, { request_id, lose, }, ); return await this.send(message, ClassId.SAW_AKNOWLEDGE_RESPONSE); } public async sawSpinRequest(user_ext_id: string, saw_template_id: number, round_id?: number): Promise { if (!saw_template_id) { throw new Error('Missing template id'); } const request_id = IntUtils.uuid(); const message = this.buildMessage(user_ext_id, ClassId.SAW_DO_SPIN_REQUEST, { saw_template_id, request_id, }); const spinAttemptResponse = await this.send(message, ClassId.SAW_DO_SPIN_RESPONSE); // to simulate fail // response.errCode = SAWSpinErrorCode.SAW_NO_SPINS; const status: string = { [SAWSpinErrorCode.SAW_OK]: 'OK', [SAWSpinErrorCode.SAW_NO_SPINS]: 'NO SPINS AVAILABLE', [SAWSpinErrorCode.SAW_PRIZE_POOL_EMPTY]: 'PRIZE POOL IS EMPTY', [SAWSpinErrorCode.SAW_NOT_ENOUGH_POINTS]: 'NOT ENOUGH POINTS', [SAWSpinErrorCode.SAW_FAILED_MAX_SPINS_REACHED]: 'MAX SPIN ATTEMPTS REACHED', [SAWSpinErrorCode.SAW_TEMPLATE_NOT_ACTIVE]: "MINIGAME IS NOT IN ACTIVE PERIOD", [SAWSpinErrorCode.SAW_NOT_IN_SEGMENT]: "USER IS NOT IN SEGMENT", [SAWSpinErrorCode.SAW_NO_BALANCE_GEMS]: "NOT ENOUGH GEMS", [SAWSpinErrorCode.SAW_NO_BALANCE_DIAMONDS]: "NOT ENOUGH DIAMONDS", }[spinAttemptResponse.errCode] || 'OTHER'; await this.coreReportCustomEvent(user_ext_id, 'minigame_attempt', { saw_template_id, status, round_id, }); return { ...spinAttemptResponse, request_id }; } public async doAcknowledgeBatchRequest(user_ext_id: string, request_ids: string[]): Promise { const message = this.buildMessage( user_ext_id, ClassId.SAW_AKNOWLEDGE_REQUEST, { request_ids, }, ); return await this.send(message, ClassId.SAW_AKNOWLEDGE_BATCH_RESPONSE); } public async sawSpinBatchRequest(user_ext_id: string, saw_template_id: number, spins_count: number): Promise { const spins = []; for (let i = 0; i < spins_count; i++) { const request_id = IntUtils.uuid(); spins.push({ request_id, saw_template_id }) } const message = this.buildMessage(user_ext_id, ClassId.SAW_DO_SPIN_BATCH_REQUEST, { spins }); const spinAttemptResponse = await this.send(message, ClassId.SAW_DO_SPIN_BATCH_RESPONSE); // If one response is 'OK' we consider that whole result is 'OK' const result = spinAttemptResponse.results.find((res) => res.errCode === 0); let status = 'OK'; if (!result) { status = 'BATCH FAIL'; } await this.coreReportCustomEvent(user_ext_id, 'minigame_attempt', { saw_template_id, status, spins_count, }); return { ...spinAttemptResponse }; } public async getSawWinningHistory(user_ext_id: string, limit: number = 20, offset: number = 0, saw_template_id: number): Promise { const message = this.buildMessage(user_ext_id, ClassId.GET_SAW_HISTORY_REQUEST, { limit, offset, saw_template_id }) return await this.send(message, ClassId.GET_SAW_HISTORY_RESPONSE); } public async getSawWinningHistoryT(user_ext_id: string, limit?: number, offset?: number, saw_template_id?: number): Promise { if (IntUtils.isNotNull(limit) && IntUtils.isNotNull(offset) && limit > 50) { limit = 50; } return SAWHistoryTransform((await this.getSawWinningHistory(user_ext_id, limit, offset, saw_template_id)).prizes); } public async missionOptIn(user_ext_id: string, mission_id: number) { if (!mission_id) { throw new Error('Missing mission id'); } const message = this.buildMessage( user_ext_id, ClassId.MISSION_OPTIN_REQUEST, { achievementId: mission_id, }, ); const res = await this.send(message, ClassId.MISSION_OPTIN_RESPONSE); return res; } public async missionClaimPrize(user_ext_id: string, mission_id: number, ach_completed_id: number) { if (!mission_id) { throw new Error('Missing mission id'); } const message = this.buildMessage( user_ext_id, ClassId.ACHIEVEMENT_CLAIM_PRIZE_REQUEST, { ach_id: mission_id, ach_completed_id: ach_completed_id, }, ); const res = await this.send(message, ClassId.ACHIEVEMENT_CLAIM_PRIZE_RESPONSE); return res; } public async registerInTournament(user_ext_id: string, tournamentInstanceId: number) { if (!tournamentInstanceId) { throw new Error('Missing tournament instance id'); } const message = this.buildMessage( user_ext_id, ClassId.TOURNAMENT_REGISTER_REQUEST, { tournamentInstanceId, }, ); const res = await this.send(message, ClassId.TOURNAMENT_REGISTER_RESPONSE); return res; } public async buyStoreItem(user_ext_id: string, itemId: number) { if (!itemId) { throw new Error('Missing store item id'); } const message = this.buildMessage(user_ext_id, ClassId.BUY_SHOP_ITEM_REQUEST, { itemId, }); const res = await this.send(message, ClassId.BUY_SHOP_ITEM_RESPONSE); return res; } public async inboxGetMessages( user_ext_id: string, limit: number = 10, offset: number = 0, ): Promise { const message = this.buildMessage( user_ext_id, ClassId.GET_INBOX_MESSAGES_REQUEST, { limit, offset, }, ); return await this.send(message, ClassId.GET_INBOX_MESSAGES_RESPONSE); } public async storeGetItems(user_ext_id: string, force_language?: string): Promise { const message = this.buildMessage(user_ext_id, ClassId.GET_SHOP_ITEMS_REQUEST); return await this.send(message, ClassId.GET_SHOP_ITEMS_RESPONSE, force_language); } public async storeGetItemsT(user_ext_id: string): Promise { return StoreItemTransform((await this.storeGetItems(user_ext_id)).items); } public async storeGetCategories(user_ext_id: string, force_language?: string): Promise { const message = this.buildMessage(user_ext_id, ClassId.GET_SHOP_CATEGORIES_REQUEST); return await this.send(message, ClassId.GET_SHOP_CATEGORIES_RESPONSE, force_language); } public async storeGetCategoriesT(user_ext_id: string): Promise { return StoreCategoryTransform((await this.storeGetCategories(user_ext_id)).categories); } public async storeGetPurchasedItems( user_ext_id: string, limit: number = 20, offset: number = 0, ): Promise { const message = this.buildMessage( user_ext_id, ClassId.ACH_SHOP_ITEM_HISTORY_REQUEST, { limit, offset, }, ); return await this.send(message, ClassId.ACH_SHOP_ITEM_HISTORY_RESPONSE); } public async storeGetPurchasedItemsT(user_ext_id: string, limit?: number, offset?: number): Promise { if (IntUtils.isNotNull(limit) && IntUtils.isNotNull(offset) && limit > 50) { limit = 50; } return StoreItemPurchasedTransform((await this.storeGetPurchasedItems(user_ext_id, limit, offset)).items); } public async missionsGetItems(user_ext_id: string, force_language?: string): Promise { const message = this.buildMessage( user_ext_id, ClassId.GET_ACHIEVEMENT_MAP_REQUEST, ); const response = await this.send( message, ClassId.GET_ACHIEVEMENT_MAP_RESPONSE, force_language, ); // we need to clone response to avoid changing original object,for cases when its called together with badgesGetItems (e.g. in Promise.all) const responseClone = { ...response }; if (responseClone.achievements) { responseClone.achievements = responseClone.achievements.filter((a) => a.ach_type_id === AchievementType.Mission); } return responseClone; } public async missionsGetItemsT(user_ext_id: string): Promise { return UserAchievementTransform((await this.missionsGetItems(user_ext_id)).achievements); } public async getUserGamificationInfo(user_ext_id: string): Promise { const message = this.buildMessage( user_ext_id, ClassId.GET_ACHIEVEMENT_USER_REQUEST, ); return await this.send(message, ClassId.GET_ACHIEVEMENT_USER_RESPONSE); } public async getUserGamificationInfoT(user_ext_id: string): Promise { const response = await this.getUserGamificationInfo(user_ext_id); return { level_counter_1: response.level_counter_1, level_counter_2: response.level_counter_2, }; } public async achGetCategories(user_ext_id: string, force_language?: string): Promise { const message = this.buildMessage(user_ext_id, ClassId.GET_ACH_CATEGORIES_REQUEST); return await this.send(message, ClassId.GET_ACH_CATEGORIES_RESPONSE, force_language); } public async achGetCategoriesT(user_ext_id: string): Promise { return AchCategoryTransform((await this.achGetCategories(user_ext_id)).categories); } public async badgetsGetItems(user_ext_id: string, force_language?: string): Promise { const message = this.buildMessage( user_ext_id, ClassId.GET_ACHIEVEMENT_MAP_REQUEST, ); const response = await this.send( message, ClassId.GET_ACHIEVEMENT_MAP_RESPONSE, force_language, ); // we need to clone response to avoid changing original object,for cases when its called together with missionsGetItems (e.g. in Promise.all) const responseClone = { ...response }; if (responseClone.achievements) { responseClone.achievements = responseClone.achievements.filter((a) => a.ach_type_id === AchievementType.Badge); responseClone.achievements = enrichUserAchievementsWithBadgeState(responseClone.achievements); } return responseClone; } public async bonusesGetItems(user_ext_id: string, force_language?: string): Promise { const message = this.buildMessage( user_ext_id, ClassId.GET_BONUSES_REQUEST, ); const response = await this.send( message, ClassId.GET_BONUSES_RESPONSE, force_language, ); const responseClone = { ...response }; return responseClone; } public async bonusClaimItem(user_ext_id: string, bonus_id: number): Promise { const message = this.buildMessage( user_ext_id, ClassId.CLAIM_BONUS_REQUEST, { bonusId: bonus_id } ); const response = await this.send( message, ClassId.CLAIM_BONUS_RESPONSE, ); const responseClone = { ...response }; return responseClone; } public async bonusesGetItemsT(user_ext_id: string): Promise { return BonusItemsTransform((await this.bonusesGetItems(user_ext_id)).bonuses); } public async badgetsGetItemsT(user_ext_id: string): Promise { return UserAchievementTransform((await this.badgetsGetItems(user_ext_id)).achievements); } public async tournamentsGetLobby(user_ext_id: string, force_language?: string): Promise { const message = this.buildMessage( user_ext_id, ClassId.GET_TOURNAMENT_LOBBY_REQUEST, ); return await this.send(message, ClassId.GET_TOURNAMENT_LOBBY_RESPONSE, force_language); } public async tournamentsGetLobbyT(user_ext_id: string): Promise { return TournamentItemsTransform((await this.tournamentsGetLobby(user_ext_id)).tournaments); } public async clansGetList(user_ext_id: string, force_language?: string): Promise { const message = this.buildMessage( user_ext_id, ClassId.GET_CLAN_LIST_REQUEST, ); return await this.send(message, ClassId.GET_CLAN_LIST_RESPONSE, force_language); } public async clansGetListT(user_ext_id: string): Promise { const response = await this.clansGetList(user_ext_id); return { clans: response.clans, user_clan_id: response.user_clan_id, cooldown_until: response.cooldown_until, join_date: response.join_date ?? null, }; } public async clansGetInfo(user_ext_id: string, clanId: number, force_language?: string): Promise { const message = this.buildMessage( user_ext_id, ClassId.GET_CLAN_INFO_REQUEST, { clan_id: clanId }, ); return await this.send(message, ClassId.GET_CLAN_INFO_RESPONSE, force_language); } public async clansGetInfoT(user_ext_id: string, clanId: number, force_language?: string): Promise { const response = await this.clansGetInfo(user_ext_id, clanId, force_language); const c = response.clanInfo; return { clan_id: c.clan_id, public_meta: c.public_meta, member_count: c.member_count, capacity_limit: c.capacity_limit, entry_fee_currency_type_id: c.entry_fee_currency_type_id, entry_fee_amount: c.entry_fee_amount, rating_position: c.rating_position, rating_score: c.rating_score, cooldown_until: c.cooldown_until ?? null, members: (c.members || []).map((m) => ({ user_id: m.user_id, public_username: m.public_username, avatar_id: m.avatar_id, avatar_real_id: m.avatar_real_id, avatar_url: CoreUtils.avatarUrl(m.avatar_id, this.avatarDomain), position: m.position, contribution_score: m.contribution_score, is_me: m.is_me, clean_ext_user_id: m.clean_ext_user_id, })), }; } public async clanJoin(user_ext_id: string, clanId: number, joinSourceId: number = 0): Promise { const message = this.buildMessage( user_ext_id, ClassId.JOIN_CLAN_REQUEST, { clan_id: clanId, join_source_id: joinSourceId }, ); const response = await this.send(message, ClassId.JOIN_CLAN_RESPONSE); return { errCode: response.errCode ?? 0, errMsg: response.errMsg ?? '' }; } public async tournamentsGetInfo( user_ext_id: string, tournamentInstanceId: number, force_language?: string, ): Promise { const message = this.buildMessage( user_ext_id, ClassId.GET_TOURNAMENT_INFO_REQUEST, { tournamentInstanceId, }, ); const response = await this.send( message, ClassId.GET_TOURNAMENT_INFO_RESPONSE, force_language, ); if (response.userPosition?.avatar_id) { response.userPosition.avatar_url = CoreUtils.avatarUrl(response.userPosition.avatar_id, this.avatarDomain); } if (response.tournamentInfo?.players?.length) { response.tournamentInfo.players.forEach((p) => { p.avatar_url = CoreUtils.avatarUrl(p.avatar_id, this.avatarDomain); }); } return response; } public async tournamentsGetInfoT(user_ext_id: string, tournamentInstanceId: number): Promise { if (!tournamentInstanceId) { throw new Error('Missing tournament instance id'); } const response = await this.tournamentsGetInfo(user_ext_id, tournamentInstanceId); return tournamentInfoItemTransform(response); } public async clanTournamentGetPlayers( user_ext_id: string, tournament_instance_id: number, clan_id: number, force_language?: string, ): Promise { const message = this.buildMessage( user_ext_id, ClassId.GET_CLAN_TOURNAMENT_PLAYERS_REQUEST, { tournament_instance_id, clan_id, force_language }, ); const response = await this.send( message, ClassId.GET_CLAN_TOURNAMENT_PLAYERS_RESPONSE, force_language, ); if (response.players?.length) { response.players.forEach((p) => { p.avatar_url = CoreUtils.avatarUrl(p.avatar_id, this.avatarDomain); }); } return clanTournamentPlayersTransform(response); } public async leaderboardGet( user_ext_id: string, period_type_id?: LeaderBoardPeriodType, prevPeriod: boolean | number = false, force_language?: string, ): Promise { // boolean keeps back-compat (false → current, true → previous); // a number selects an arbitrary snapshot offset (1 = previous, 2 = the one before, …). const snapshot_offset = typeof prevPeriod === 'number' ? prevPeriod : prevPeriod ? 1 : 0; const message = this.buildMessage( user_ext_id, ClassId.GET_LEADERS_BOARD_REQUEST, { period_type_id, snapshot_offset, include_users: true, }, ); const response = await this.send(message, ClassId.GET_LEADERS_BOARD_RESPONSE, force_language); const boardKey = Object.keys(response.map || {}).find((k) => period_type_id === undefined || k === period_type_id?.toString()); if (boardKey === undefined) { return undefined; } if (response.map[boardKey]?.userPosition?.avatar_id) { response.map[boardKey].userPosition.avatar_url = CoreUtils.avatarUrl( response.map[boardKey].userPosition.avatar_id, this.avatarDomain, ); } if (response.map[boardKey]?.positions?.length) { response.map[boardKey].positions.forEach((p) => { p.avatar_url = CoreUtils.avatarUrl(p.avatar_id, this.avatarDomain); }); } return response.map[boardKey]; } public async leaderboardsGetT( user_ext_id: string, period_type_id: LeaderBoardPeriodType = LeaderBoardPeriodType.DAILY, prevPeriod: boolean | number = false, ): Promise { return getLeaderBoardTransform(await this.leaderboardGet(user_ext_id, period_type_id, prevPeriod)); } public async leaderboardsGetList( user_ext_id: string, force_language?: string, ): Promise { const message = this.buildMessage( user_ext_id, ClassId.GET_LEADERS_BOARD_REQUEST, { // No period_type_id → the server returns every configured board. // include_users: false → board metadata only (no positions / userPosition). snapshot_offset: 0, include_users: false, }, ); const response = await this.send(message, ClassId.GET_LEADERS_BOARD_RESPONSE, force_language); // map is keyed by period_type_id; integer-like keys iterate in ascending order. return Object.values(response.map || {}); } public async leaderboardsGetListT(user_ext_id: string): Promise { return (await this.leaderboardsGetList(user_ext_id)).map(getLeaderBoardTransform); } public async levelsGet(user_ext_id: string, force_language?: string): Promise { const message = this.buildMessage(user_ext_id, ClassId.GET_LEVEL_MAP_REQUEST); return await this.send(message, ClassId.GET_LEVEL_MAP_RESPONSE, force_language); } public async levelsGetT(user_ext_id: string): Promise { return GetLevelMapResponseTransform(await this.levelsGet(user_ext_id)); } public async getLevelCurrent(user_ext_id: string): Promise { const levels = await this.levelsGetT(user_ext_id); const userInfo = await this.getUserGamificationInfo(user_ext_id); if (!levels || levels.length === 0) return null; const sortedLevels = [...levels].sort((a, b) => a.required_points - b.required_points); let currentLevelIndex = sortedLevels.findIndex((level) => level.id === userInfo.current_level); if (currentLevelIndex === -1) { const userPointsEver = userInfo.points_ever ?? 0; currentLevelIndex = sortedLevels.findIndex((level, index) => { const nextLevel = sortedLevels[index + 1]; return userPointsEver >= level.required_points && (!nextLevel || userPointsEver < nextLevel.required_points); }); if (currentLevelIndex === -1) { currentLevelIndex = sortedLevels.length - 1; } } const currentLevel = sortedLevels[currentLevelIndex]; const nextLevel = sortedLevels[currentLevelIndex + 1]; const userPointsEver = userInfo.points_ever ?? 0; const progress = nextLevel ? ((userPointsEver - currentLevel.required_points) / (nextLevel.required_points - currentLevel.required_points)) * 100 : 100; return { ...currentLevel, progress: Math.min(Math.max(progress, 0), 100), }; } public async customSectionsGet(user_ext_id: string, force_language?: string): Promise { const message = this.buildMessage( user_ext_id, ClassId.GET_CUSTOM_SECTIONS_REQUEST, ); return await this.send(message, ClassId.GET_CUSTOM_SECTIONS_RESPONSE, force_language); } public async customSectionsGetT(user_ext_id: string): Promise { return UICustomSectionTransform(await this.customSectionsGet(user_ext_id)); } public async getTranslationsT( user_ext_id: string, lang_code: string, areas: TranslationArea[], cacheSec: number = 60, ): Promise { return await this.coreGetTranslations(user_ext_id, lang_code, areas, 30); } public async getInboxMessages( user_ext_id: string, limit: number = 20, offset: number = 0, starred_only: boolean, category_id?: InboxCategories, read_status?: InboxReadStatus, ): Promise { const message = this.buildMessage( user_ext_id, ClassId.GET_INBOX_MESSAGES_REQUEST, { limit, offset, starred_only, category_id, read_status, }, ); return await this.send(message, ClassId.GET_INBOX_MESSAGES_RESPONSE); } public async getInboxMessagesT( user_ext_id: string, from: number = 0, to: number = 20, favoriteOnly: boolean = false, categoryId?: InboxCategories, read_status?: InboxReadStatus, ): Promise { const limit = to - from > 20 ? 20 : to - from; const offset = from; return InboxMessagesTransform((await this.getInboxMessages(user_ext_id, limit, offset, favoriteOnly, categoryId, read_status)).log); } public async getInboxUnreadCountT( user_ext_id: string, ): Promise { const limit = 1; const offset = 0; return (await this.getInboxMessages(user_ext_id, limit, offset, false, null)).unread_count; } public async getInboxMessageBody(messageGuid: string): Promise { const getMessageBody = async (messageGuid: string): Promise => { const inboxCdnUrl = this.tracker.getLabelSetting(PublicLabelSettings.INBOX_PUBLIC_CDN); try { const url = `${inboxCdnUrl}${messageGuid}.json`; const response = await fetch(url, { method: 'GET', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', }, }); const data = await response.json(); return data || {}; } catch (error) { this.logger.error('Error fetching inbox message body:', error); return null; } }; return await getMessageBody(messageGuid); } public async getInboxMessageBodyT(messageGuid: string): Promise { const message = await this.getInboxMessageBody(messageGuid); return InboxMessageBodyTransform(message); } public async markInboxMessageRead(user_ext_id: string, messageGuid: string): Promise { const message = this.buildMessage( user_ext_id, ClassId.MARK_INBOX_READ_REQUEST, { engagement_uid: messageGuid, }, ); return await this.send(message, ClassId.MARK_INBOX_READ_RESPONSE); } public async markAllInboxMessageRead(user_ext_id: string): Promise { const message = this.buildMessage( user_ext_id, ClassId.MARK_INBOX_READ_REQUEST, { all_read: true, }, ); return await this.send(message, ClassId.MARK_INBOX_READ_RESPONSE); } public async markUnmarkInboxMessageAsFavorite( user_ext_id: string, messageGuid: string, mark: boolean, ): Promise { const message = this.buildMessage( user_ext_id, ClassId.MARK_INBOX_STARRED_REQUEST, { engagement_uid: messageGuid, is_starred: mark, }, ); return await this.send(message, ClassId.MARK_INBOX_STARRED_RESPONSE); } public async deleteInboxMessage(user_ext_id: string, messageGuid: string): Promise { const message = this.buildMessage( user_ext_id, ClassId.MARK_INBOX_DELETED_REQUEST, { engagement_uid: messageGuid, }, ); return await this.send(message, ClassId.MARK_INBOX_DELETED_RESPONSE); } public async deleteAllInboxMessages(user_ext_id: string): Promise { const message = this.buildMessage( user_ext_id, ClassId.MARK_INBOX_DELETED_REQUEST, { all_deleted: true, }, ); return await this.send(message, ClassId.MARK_INBOX_DELETED_RESPONSE); } private buildGamesApiParams(): Record { const params: Record = Object.assign({}, this.baseRgApiParams, { ext_user_id: this.tracker.userPublicProps?.user_id, smartico_ext_user_id: this.tracker.getExtUserId(), lang: this.tracker.userPublicProps?.core_user_language, }); return params; } private async sendGamesApi({ method, params, usePost = false }: { method: string, params: Record, usePost?: boolean }): Promise> { if (!this.tracker) { throw new Error(`MatchX and Quiz APIs are not available in the visitor mode`); } const baseParams = this.buildGamesApiParams(); const queryString = Object.entries(baseParams).map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join('&'); let url = `${GAMES_API_URL}/${method}?${queryString}`; let fetchOptions: RequestInit = { method: 'GET', headers: { 'Accept': 'application/json' }, }; if (usePost && params) { fetchOptions = { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify(params), }; } else if (params) { const extraQuery = Object.entries(params) .filter(([, v]) => v !== null && v !== undefined) .map(([k, v]) => `${k}=${encodeURIComponent(typeof v === 'object' ? JSON.stringify(v) : v)}`) .join('&'); url += `&${extraQuery}`; } try { const response = await fetch(url, fetchOptions); const data = await response.json(); return SmarticoAPI.replaceSmrDomainsWithCloudfront>(data); } catch (e) { this.logger.error(`Failed to call games API: ${method}`, { url, error: e.message }); throw new Error(`Failed to call games API: ${method}. ${e.message}`); } } private async sendAvatarApi({ method, body }: { method: string, body: Record }): Promise { if (!this.tracker) { throw new Error(`Avatar APIs are not available in the visitor mode`); } const url = `${this.avatarOriginDomain}/${method}`; try { const response = await fetch(url, { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify(body), }); const data = await response.json(); return SmarticoAPI.replaceSmrDomainsWithCloudfront(data); } catch (e) { this.logger.error(`Failed to call avatar API: ${method}`, { url, error: e.message }); throw new Error(`Failed to call avatar API: ${method}. ${e.message}`); } } public async gpGetActiveRounds(saw_template_id: number): Promise> { const params = { ext_game_id: saw_template_id, }; return this.sendGamesApi({ method: 'active-rounds', params }); } public async gpGetActiveRound(saw_template_id: number, round_id?: number): Promise> { const params = { ext_game_id: saw_template_id, round_id: round_id, }; return this.sendGamesApi({ method: 'active-round', params }); } public async gpGetGamesHistory(saw_template_id: number): Promise> { const params = { ext_game_id: saw_template_id, }; return this.sendGamesApi({ method: 'games-history', params }); } public async gpGetGameBoard(saw_template_id: number, round_id: number): Promise> { const params = { ext_game_id: saw_template_id, round_id: round_id, }; return this.sendGamesApi({ method: 'game-board', params }); } public async gpSubmitSelection(saw_template_id: number, round: any, isQuiz: boolean): Promise> { const method = isQuiz ? 'submit-selection-quiz' : 'submit-selection'; const params = { ext_game_id: saw_template_id, round: round, }; return this.sendGamesApi({ method, params, usePost: true }); } public async gpGetUserInfo(saw_template_id: number): Promise> { const params = { ext_game_id: saw_template_id, }; return this.sendGamesApi({ method: 'user-info', params }); } public async gpGetGameInfo(saw_template_id: number): Promise> { const params = { ext_game_id: saw_template_id, }; return this.sendGamesApi({ method: 'game-info', params }); } public async gpGetTranslations(saw_template_id: number): Promise> { const params = { ext_game_id: saw_template_id, }; return this.sendGamesApi({ method: 'translations', params }); } public async gpGetRoundInfoForUser(saw_template_id: number, round_id: number, int_user_id: number): Promise> { const params = { ext_game_id: saw_template_id, round_id: round_id, int_user_id: int_user_id, }; return this.sendGamesApi({ method: 'game-round-info-for-user', params }); } public async avatarsGetList(user_ext_id: string): Promise { const message = this.buildMessage(user_ext_id, ClassId.GET_AVATARS_LIST_REQUEST); return await this.send(message, ClassId.GET_AVATARS_LIST_RESPONSE); } public async avatarsGetCustomized(user_ext_id: string): Promise { const message = this.buildMessage(user_ext_id, ClassId.GET_AVATARS_CUSTOMIZED_REQUEST); return await this.send(message, ClassId.GET_AVATARS_CUSTOMIZED_RESPONSE); } public async avatarsGetPrompts(user_ext_id: string): Promise { const message = this.buildMessage(user_ext_id, ClassId.GET_AVATAR_PROMPTS_REQUEST); return await this.send(message, ClassId.GET_AVATAR_PROMPTS_RESPONSE); } public async avatarSetAvatar(user_ext_id: string, avatar_id: string, avatar_real_id: number): Promise { const message = this.buildMessage(user_ext_id, ClassId.CLIENT_SET_AVATAR_REQUEST, { avatar_id, avatar_real_id, }); return await this.send(message, ClassId.CLIENT_SET_AVATAR_RESPONSE); } public async avatarsCustomize(params: { user_id: number; prompt_id: number; avatar_url: string; avatar_real_id: number; }): Promise { return this.sendAvatarApi({ method: 'avatar-customize', body: { // The server resolves label_id from label_api_key. label_api_key: this.label_api_key, user_id: params.user_id, user_ext_id: this.tracker?.getExtUserId(), brand_key: this.brand_api_key || this.tracker?.params?.brand_key, prompt_id: params.prompt_id, avatar_url: params.avatar_url, avatar_real_id: params.avatar_real_id, }, }); } public async avatarsGetListT(user_ext_id: string): Promise { return avatarDefinitionTransform((await this.avatarsGetList(user_ext_id)).avatars ?? [], this.avatarDomain); } public async avatarsGetCustomizedT(user_ext_id: string): Promise { return avatarCustomizedTransform((await this.avatarsGetCustomized(user_ext_id)).avatars ?? [], this.avatarDomain); } public async avatarsGetPromptsT(user_ext_id: string): Promise { return avatarPromptTransform((await this.avatarsGetPrompts(user_ext_id)).prompts ?? [], this.avatarDomain); } public getWSCalls(): WSAPI { return new WSAPI(this); } public async getRelatedItemsForGame(user_ext_id: string, related_game_id: string,): Promise { const message = this.buildMessage( user_ext_id, ClassId.GET_RELATED_ACH_N_TOURNAMENTS_REQUEST, { related_game_id: related_game_id } ); return await this.send(message, ClassId.GET_RELATED_ACH_N_TOURNAMENTS_RESPONSE); } public async getRafflesT(user_ext_id: string): Promise { return raffleTransform((await this.getRaffles(user_ext_id)).items); } public async getRaffles(user_ext_id: string): Promise { const message = this.buildMessage(user_ext_id, ClassId.RAF_GET_RAFFLES_REQUEST); return await this.send(message, ClassId.RAF_GET_RAFFLES_RESPONSE); } public async getRaffleDrawRun(user_ext_id: string, payload: { raffle_id: number; run_id: number; winners_limit?: number; winners_offset?: number }): Promise { const message = this.buildMessage( user_ext_id, ClassId.RAF_GET_DRAW_RUN_REQUEST, payload, ); return await this.send(message, ClassId.RAF_GET_DRAW_RUN_RESPONSE); } public async getRaffleDrawRunT( user_ext_id: string, raffle_id: number, run_id: number, winners_from: number = 0, winners_to: number = 20, ): Promise { const winners_limit = winners_to - winners_from > 50 ? 50 : winners_to - winners_from; const winners_offset = winners_from; return drawRunTransform(await this.getRaffleDrawRun(user_ext_id, { raffle_id, run_id, winners_limit, winners_offset }),); } public async getRaffleDrawRunsHistory( user_ext_id: string, props: { raffle_id: number; draw_id?: number }, ): Promise { const message = this.buildMessage( user_ext_id, ClassId.RAF_GET_DRAW_HISTORY_REQUEST, props, ); return await this.send(message, ClassId.RAF_GET_DRAW_HISTORY_RESPONSE); } public async claimRafflePrize(user_ext_id: string, props: { won_id: number }): Promise { const message = this.buildMessage( user_ext_id, ClassId.RAF_CLAIM_PRIZE_REQUEST, props, ); return await this.send(message, ClassId.RAF_CLAIM_PRIZE_RESPONSE); } public async raffleOptin(user_ext_id: string, props: { raffle_id: number; draw_id: number; raffle_run_id: number }): Promise { const message = this.buildMessage( user_ext_id, ClassId.RAF_OPTIN_REQUEST, props, ); return await this.send(message, ClassId.RAF_OPTIN_RESPONSE); } public async getRaffleWonPrizes( user_ext_id: string, props: { raffle_id: number; offset: number; limit: number }, ): Promise { const message = this.buildMessage( user_ext_id, ClassId.RAF_GET_WON_PRIZES_REQUEST, props, ); return await this.send(message, ClassId.RAF_GET_WON_PRIZES_RESPONSE); } public async getActivityLog( user_ext_id: string, startTimeSeconds: number, endTimeSeconds: number, limit: number, offset: number, types?: number[], src_types?: number[], ): Promise { const message = this.buildMessage( user_ext_id, ClassId.GET_POINT_HISTORY_REQUEST, { startTimeSeconds: Math.floor(startTimeSeconds), endTimeSeconds: Math.floor(endTimeSeconds), limit, offset, ...(types ? { types } : {}), ...(src_types ? { src_types } : {}), }, ); return await this.send(message, ClassId.GET_POINT_HISTORY_RESPONSE); } public async getActivityLogT( user_ext_id: string, startTimeSeconds: number, endTimeSeconds: number, from: number = 0, to: number = 50, types?: number[], src_types?: number[], ): Promise { const limit = to - from > 50 ? 50 : to - from; const offset = from; return ActivityLogTransform( (await this.getActivityLog(user_ext_id, startTimeSeconds, endTimeSeconds, limit, offset, types, src_types)).logHistory, ); } } export { SmarticoAPI, MessageSender };