import {
takeEvery, put, call, select, takeLeading, takeLatest,
} from 'redux-saga/effects';
/**
* @namespace sagas/round
*/
import Constants, { GAME_TYPE } from '../constants';
import { requestGet } from '../utils/request';
import {
delay,
roundSetupTimers,
transformStandingsPerTeam,
transformStatisticsPerTeamLast5,
filterSelectedOdds,
} from '../utils/common';
import {
GET_ROUND_SELECTIONS,
REPEAT_CALL_SUCCESS,
FETCH_ROUND_SUCCESS,
SET_ROUND_LOADER,
REPEAT_CALL,
GET_ROUND_SUCCESS,
FETCH_ROUND,
GET_ROUND,
SHOW_ERROR_MODAL,
SET_NUM_OF_BETS,
GET_PLACED_BETS,
GET_TURBO_ROUND_SELECTIONS,
WAKE_UP_APP,
FORCE_BALANCE_UPDATE,
GET_BALANCE,
SET_LAST_BET_ROUND,
CLEAR_SELECTIONS,
CLOSE_BETSLIP,
FETCH_STATISTICS,
SET_STATISTICS,
LIVE_FINISHED,
CLEAR_STATISTICS,
SET_FILTERED_SELECTED_ODDS,
} from '../reducers';
import {
getUser,
getCurrentPage,
getLostFocus,
getPlacebetBackupLogin,
} from '../store/common';
import { getNumberOfBets } from '../store/tickets';
import {
getRepeatRoundCall, getNextRound, getInitialBetsLoaded,
getNoBettingTimeCountdown, getRoundEnd,
getSelectedLeague, getStatistics, getGameType, getTurboRoundId, getRoundId,
} from '../store/bets';
import {
getLastBetRound, getSelectedOdds,
} from '../store/placebet';
/**
* Repeat Round API every time when receive RESULTS_NOT_READY code or old data.
* Repeat this 5 time maximum.
*
* @memberof sagas/round
* @param {object} action
* @property {object} action
* @property {object} action.payload
* @property {boolean} action.payload.showNoBetTime
*
* @yields {select} getRepeatRoundCall
* @yields {put} REPEAT_CALL_SUCCESS
* @yields {call} delay 1000
* @yields {put} GET_ROUND
* @yields {put} SHOW_ERROR_MODAL
* @returns {void}
*/
function* repeatCall(action) {
const { payload } = action;
try {
let repeatRoundCall = yield select(getRepeatRoundCall);
repeatRoundCall += 1;
payload.repeatRoundCall = repeatRoundCall;
yield put({
type: REPEAT_CALL_SUCCESS,
payload,
});
if (repeatRoundCall < 5) {
yield call(delay, 1000);
yield put({
type: GET_ROUND,
});
} else {
// showGeneralError();
}
} catch (error) {
yield put({
type: SHOW_ERROR_MODAL,
payload: {
activeModal: error.modal || Constants.MODALS.GENERAL_ERROR,
message: error.message,
},
});
}
}
/**
* Call when Live start, setup new data from round call
*
* @memberof sagas/round
* @param {object} action
* @property {object} action
* @property {object} action.payload
* @property {boolean} action.payload.isNoBetTime
* @property {number} action.payload.leftTime
*
* @yields {call} delay leftTime
* @yields {select} getNextRound
* @yields {select} getCurrentPage
* @yields {select} getNumberOfBets
* @yields {put} FETCH_ROUND_SUCCESS
* @yields {put} SET_NUM_OF_BETS
* @yields {put} SHOW_ERROR_MODAL
* @returns {void}
*/
function* resetFetchingResults(action) {
try {
if (action.payload.isNoBetTime) {
// One second before to setup live
yield call(delay, (action.payload.leftTime - 1) * 1000);
const placebetBackupLogin = yield select(getPlacebetBackupLogin);
const nextRound = yield select(getNextRound);
const currentPage = yield select(getCurrentPage);
const numberOfBets = placebetBackupLogin ? 0 : yield select(getNumberOfBets);
yield put({
type: FETCH_ROUND_SUCCESS,
payload: {
roundId: nextRound && nextRound.roundId,
roundEnd: nextRound && nextRound.roundEnd,
currentPage:
currentPage === Constants.PAGES.BETS || currentPage === Constants.PAGES.BETSLIP
? Constants.PAGES.LIVE
: currentPage,
haveLiveMatches: true,
},
});
yield put({
type: SET_NUM_OF_BETS,
payload: {
numberOfBetsLive: numberOfBets,
},
});
}
} catch (error) {
yield put({
type: SHOW_ERROR_MODAL,
payload: {
activeModal: error.modal || Constants.MODALS.GENERAL_ERROR,
message: error.message,
},
});
}
}
/**
* @memberof sagas/round
* @typedef "getRound/response.data"
* @type {object}
* @property {Round} round Round for betting
* @property {Live} roundResults Round in play (LIVE)
*/
/**
* @memberof sagas/round
* @typedef Round
* @type {object}
* @property {number} roundId Round ID
* @property {Array} competitions Sorted IDs of leagues
* @property {Array} seasons Sorted IDs of Seasons
* @property {Array} weeks Sorted IDs of weeks
* @property {Time} startDate Round start time
* @property {Time} endDate Round end time
* @property {Array} matches Names of competitors in matches per leagues
* @property {string} selections IDs of all selections included in round separated with "-"
* @property {Array} odds Odds per league (Matches separated with "|" and selections with "-")
* @property {Time} noBetDate Not in use
* @property {object} stake Not in use
* @property {number} bonusOddsThreshold Config parameter for SystemMenager library
* @property {string} bonusPercentages Config parameter for SystemMenager library
* @property {number} winCap Maximum winning payout
*/
/**
* @memberof sagas/round
* @typedef Live
* @type {object}
* @property {number} roundId Live round ID
* @property {Array} competitions Sorted IDs of leagues
* @property {Array} seasons Sorted IDs of Seasons
* @property {Array} weeks Sorted IDs of weeks
* @property {Time} liveStartDate Live round start time
* @property {Time} liveEndDate Live round end time
* @property {Array} matches Names of competitors in matches per leagues
* @property {Array} timeline Parsed timeline per league.
* (minute:competitorIndex which get +1 score separated with "-")
*/
/**
* Call in the time of No Betting Time to get data for next round and to get timeline for Live
*
* @memberof sagas/round
* @param {object} action
* @property {object} action
* @async
* @yields {select} getInitialBetsLoaded
* @yields {put} SET_ROUND_LOADER
* @yields {call} /game/round
* @yields {put} REPEAT_CALL
* @yields {select} getNoBettingTimeCountdown
* @yields {select} getLiveMatchDuration
* @yields {put} REPEAT_CALL
* @yields {put} GET_PLACED_BETS
* @yields {select} getCurrentPage
* @yields {put} GET_ROUND_SUCCESS
* @yields {put} FETCH_ROUND
* @yields {put} SHOW_ERROR_MODAL
*/
function* getRound(action) {
let response;
try {
const lostFocus = yield select(getLostFocus);
if (lostFocus) {
const now = new Date();
const dif = now - lostFocus;
const mins = parseInt(dif / (1000 * 60));
if (mins >= 15) {
return;
}
}
const initialBetsLoaded = yield select(getInitialBetsLoaded);
yield put({
type: SET_ROUND_LOADER,
payload: {
roundLoading: true,
},
});
response = yield call(requestGet, Constants.API_URLS.ROUND, true);
if (response.status === -1) {
if (response.error.code === Constants.ERROR.RESULTS_NOT_READY
|| response.error.code === Constants.ERROR.NEW_ROUND_NOT_READY) {
yield put({
type: REPEAT_CALL,
payload: {
showNoBetTime: !initialBetsLoaded,
},
});
} else {
throw new Error(response.error.message);
}
} else {
const noBettingTimeCountdown = yield select(getNoBettingTimeCountdown);
const liveMatchDuration = response?.data?.roundResults?.liveRoundDuration;
const {
oldData, haveLiveMatches, isNoBetTime, liveStart, roundEnd, leftTime,
} = roundSetupTimers(
response.data.round.endDate,
response.data.roundResults.liveStartDate,
liveMatchDuration,
noBettingTimeCountdown,
);
if (oldData) {
yield put({
type: REPEAT_CALL,
payload: {
showNoBetTime: !initialBetsLoaded,
},
});
return;
}
if (!initialBetsLoaded) {
yield put({
type: GET_PLACED_BETS,
payload: {
haveLiveMatches,
isNoBetTime,
liveId: response.data.roundResults.roundId,
roundId: response.data.round.roundId,
turbo: false,
},
});
}
let currentPage = yield select(getCurrentPage);
if (currentPage !== Constants.PAGES.MY_ACCOUNT_MENU) {
currentPage = haveLiveMatches
? Constants.PAGES.LIVE
: yield select(getCurrentPage);
}
// go to live page if user come in the middle of the live
// check for open bets
const lastBetRound = yield select(getLastBetRound);
if (lastBetRound && parseInt(lastBetRound) !== parseInt(response.data.round.roundId)) {
if (parseInt(lastBetRound) === parseInt(response.data?.roundResults?.roundId)) {
const liveEndDate = new Date(response.data.roundResults.liveEndDate);
const currentDate = new Date();
const timeDiff = parseInt((liveEndDate - currentDate) / 1000);
const waitTime = timeDiff < 0 ? 0 : timeDiff + Math.floor(Math.random() * 5) + 5;
yield put({
type: FORCE_BALANCE_UPDATE,
payload: waitTime,
});
} else {
yield put({
type: FORCE_BALANCE_UPDATE,
payload: 0,
});
}
}
const liveEndDate = new Date(response.data.roundResults.liveEndDate);
const currentDate = new Date();
const timeDiff = parseInt((liveEndDate - currentDate) / 1000);
let isThereLiveATM = haveLiveMatches;
if (timeDiff > 0) {
isThereLiveATM = true;
}
if (haveLiveMatches) {
currentPage = Constants.PAGES.MY_LIVE;
}
const selectedOdds = yield select(getSelectedOdds);
const roundId = response.data?.round?.roundId;
const filteredSelectedOdds = filterSelectedOdds(selectedOdds, roundId);
yield put({
type: SET_FILTERED_SELECTED_ODDS,
payload: {
selectedOdds: filteredSelectedOdds,
},
});
yield put({
type: GET_ROUND_SUCCESS,
payload: {
haveLiveMatches: isThereLiveATM,
round: response.data.round,
roundResults: response.data.roundResults,
winCap: response.data.round.winCap,
initialBetsLoaded,
isNoBetTime,
liveStart,
roundEnd,
bonusOddsThreshold: response.data.round.bonusOddsThreshold,
bonusPercentages: response.data.round.bonusPercentages.split('-'),
currentPage,
placebetBackupLogin: action.payload?.placebetBackupLogin || false,
isSelectLeague: action.payload?.leagueSelection || 0,
},
});
if (!action.payload?.leagueSelection || action.payload?.haveGameTypeChanged) {
yield put({
type: CLEAR_SELECTIONS,
});
yield put({
type: CLOSE_BETSLIP,
});
}
yield put({
type: FETCH_ROUND,
payload: {
isNoBetTime,
leftTime,
},
});
}
} catch (error) {
yield put({
type: SHOW_ERROR_MODAL,
payload: {
activeModal: error.modal || Constants.MODALS.GENERAL_ERROR,
message: error.message,
},
});
}
}
/**
* Get placed bets for MY_LIVE page
*
* @memberof sagas/round
* @async
* @param {object} action
* @property {object} action
* @property {object} action.payload
* @property {boolean} action.payload.haveLiveMatches
* @property {boolean} action.payload.isNoBetTime
* @property {number} action.payload.liveId
* @property {number} action.payload.roundId
*
* @yields {select} getUser
* @yields {put} GET_ROUND_SELECTIONS
* @returns {void}
*/
function* getPlacedBets(action) {
const user = yield select(getUser);
if (user && action.payload.roundId) {
if (action.payload.haveLiveMatches || action.payload.isNoBetTime) {
yield put({
type: action.payload.turbo ? GET_TURBO_ROUND_SELECTIONS : GET_ROUND_SELECTIONS,
payload: {
roundId: action.payload.liveId,
live: true,
},
});
} else {
yield put({
type: action.payload.turbo ? GET_TURBO_ROUND_SELECTIONS : GET_ROUND_SELECTIONS,
payload: {
roundId: action.payload.roundId,
},
});
}
}
}
/**
* Check if app needs to be awoken
*
* @memberof sagas/round
* @async
* @returns {void}
*/
function* checkIfWakeUpNeeded() {
const roundEnd = yield select(getRoundEnd);
if (roundEnd && roundEnd < new Date()) {
yield put({
type: GET_ROUND,
payload: {},
});
}
}
/**
* Call balance update after action.payload seconds
*
* @memberof sagas/round
* @param {object} action
* @async
* @returns {void}
*/
function* forceUpdateBalance(action) {
if (action.payload) {
yield call(delay, 1000 * action.payload);
}
yield put({
type: GET_BALANCE,
payload: {},
});
yield put({
type: SET_LAST_BET_ROUND,
payload: null,
});
}
/**
* Fetch league statistics
*
* @memberof sagas/round
* @param {object} action
* @async
* @returns {void}
*/
function* fetchStatistics(action) {
try {
const apiUrl = action.payload.gameType === 'Turbo' ? Constants.API_URLS.STATISTICS_TURBO
: Constants.API_URLS.STATISTICS;
const { leagueId, roundId } = action.payload;
const response = yield call(requestGet, apiUrl + leagueId, true);
if (response.status === -1) {
throw new Error(response.error.message);
} else {
const { standings, statistics } = response.data;
const newStandings = transformStandingsPerTeam(standings[0].value);
const last5Matches = transformStatisticsPerTeamLast5(statistics);
yield put({
type: SET_STATISTICS,
payload: {
roundId,
leagueId,
statistics: { standings: newStandings, last5: last5Matches },
clearOther: action.payload.clearOther,
},
});
}
} catch (error) {
yield put({
type: SHOW_ERROR_MODAL,
payload: {
activeModal: error.modal || Constants.MODALS.GENERAL_ERROR,
message: error.message,
},
});
}
}
/**
* Fetch league statistics
*
* @memberof sagas/round
* @async
* @returns {void}
*/
function* tryUpdateStatistics() {
const currentPage = yield select(getCurrentPage);
if (currentPage === Constants.PAGES.BETS) {
const gameType = yield select(getGameType);
const currentLeague = yield select(getSelectedLeague);
const currentLeagueId = currentLeague.id;
const currentStatistics = yield select(getStatistics);
const roundId = gameType === GAME_TYPE.CLASSIC
? yield select(getRoundId) : yield select(getTurboRoundId);
const shouldGetNewStatistics = currentStatistics[roundId]
&& currentStatistics[roundId][currentLeagueId] !== undefined;
if (shouldGetNewStatistics) {
yield put({
type: FETCH_STATISTICS,
payload: {
gameType,
roundId,
leagueId: currentLeagueId,
clearOther: true,
},
});
} else {
yield put({ type: CLEAR_STATISTICS });
}
} else {
yield put({ type: CLEAR_STATISTICS });
}
}
/**
* @memberof sagas/round
* @async
* @returns {void}
*/
export default function* roundSaga() {
yield takeEvery(REPEAT_CALL, repeatCall);
yield takeEvery(FETCH_ROUND, resetFetchingResults);
yield takeEvery(GET_ROUND, getRound);
yield takeEvery(GET_PLACED_BETS, getPlacedBets);
yield takeEvery(WAKE_UP_APP, checkIfWakeUpNeeded);
yield takeLatest(FORCE_BALANCE_UPDATE, forceUpdateBalance);
yield takeLeading(FETCH_STATISTICS, fetchStatistics);
yield takeLatest(LIVE_FINISHED, tryUpdateStatistics);
}