import { call, put, takeEvery, takeLatest, select, delay } from 'redux-saga/effects'; import { AUTH_USER_ACTIONS, EMPTY_USER, USER_ERRORS, USER_ROLES } from '~/redux/auth/constants'; import { authSetToken, userSetLoginError, authSetUser, userSendLoginRequest, gotAuthPostMessage, authOpenProfile, authSetProfile, authGetMessages, authSendMessage, authSetUpdates, authLoggedIn, authSetLastSeenMessages, authPatchUser, authShowRestoreModal, authSetRestore, authRequestRestoreCode, authRestorePassword, } from '~/redux/auth/actions'; import { apiUserLogin, apiAuthGetUser, apiAuthGetUserProfile, apiAuthGetUserMessages, apiAuthSendMessage, apiAuthGetUpdates, apiUpdateUser, apiRequestRestoreCode, apiCheckRestoreCode, apiRestoreCode, } from '~/redux/auth/api'; import { modalSetShown, modalShowDialog } from '~/redux/modal/actions'; import { selectToken, selectAuthProfile, selectAuthUser, selectAuthUpdates, selectAuthRestore, } from './selectors'; import { IResultWithStatus, INotification, IMessageNotification } from '../types'; import { IUser, IAuthState } from './types'; import { REHYDRATE, RehydrateAction } from 'redux-persist'; import { selectModal } from '~/redux/modal/selectors'; import { IModalState } from '~/redux/modal/reducer'; import { DIALOGS } from '~/redux/modal/constants'; import { ERRORS } from '~/constants/errors'; export function* reqWrapper(requestAction, props = {}): ReturnType<typeof requestAction> { const access = yield select(selectToken); const result = yield call(requestAction, { access, ...props }); if (result && result.status === 401) { return { error: USER_ERRORS.UNAUTHORIZED, data: {} }; } return result; } function* sendLoginRequestSaga({ username, password }: ReturnType<typeof userSendLoginRequest>) { if (!username || !password) return; const { error, data: { token, user }, }: IResultWithStatus<{ token: string; user: IUser }> = yield call(apiUserLogin, { username, password, }); if (error) { yield put(userSetLoginError(error)); return; } yield put(authSetToken(token)); yield put(authSetUser({ ...user, is_user: true })); yield put(authLoggedIn()); yield put(modalSetShown(false)); } function* refreshUser() { const { error, data: { user }, }: IResultWithStatus<{ user: IUser }> = yield call(reqWrapper, apiAuthGetUser); if (error) { yield put( authSetUser({ ...EMPTY_USER, is_user: false, }) ); return; } yield put(authSetUser({ ...user, is_user: true })); } function* checkUserSaga({ key }: RehydrateAction) { if (key !== 'auth') return; yield call(refreshUser); // yield put(authOpenProfile("gvorcek", "settings")); } function* gotPostMessageSaga({ token }: ReturnType<typeof gotAuthPostMessage>) { yield put(authSetToken(token)); yield call(refreshUser); const { is_shown, dialog }: IModalState = yield select(selectModal); if (is_shown && dialog === DIALOGS.LOGIN) yield put(modalSetShown(false)); } function* logoutSaga() { yield put(authSetToken(null)); yield put(authSetUser({ ...EMPTY_USER })); yield put( authSetUpdates({ last: null, notifications: [], }) ); } function* openProfile({ username, tab = 'profile' }: ReturnType<typeof authOpenProfile>) { yield put(modalShowDialog(DIALOGS.PROFILE)); yield put(authSetProfile({ is_loading: true, tab })); const { error, data: { user }, } = yield call(reqWrapper, apiAuthGetUserProfile, { username }); if (error || !user) { return yield put(modalSetShown(false)); } yield put(authSetProfile({ is_loading: false, user, messages: [] })); } function* getMessages({ username }: ReturnType<typeof authGetMessages>) { // yield put(modalShowDialog(DIALOGS.PROFILE)); const { messages } = yield select(selectAuthProfile); yield put( authSetProfile({ is_loading_messages: true, messages: messages && messages.length > 0 && (messages[0].to.username === username || messages[0].from.username === username) ? messages : [], }) ); const { error, data, // data: { messages }, } = yield call(reqWrapper, apiAuthGetUserMessages, { username }); if (error || !data.messages) { return yield put( authSetProfile({ is_loading_messages: false, messages_error: ERRORS.EMPTY_RESPONSE, }) ); } yield put(authSetProfile({ is_loading_messages: false, messages: data.messages })); const { notifications } = yield select(selectAuthUpdates); // clear viewed message from notifcation list const filtered = notifications.filter( notification => notification.type !== 'message' || (notification as IMessageNotification).content.from.username !== username ); if (filtered.length !== notifications.length) { yield put(authSetUpdates({ notifications: filtered })); } } function* sendMessage({ message, onSuccess }: ReturnType<typeof authSendMessage>) { const { user: { username }, } = yield select(selectAuthProfile); if (!username) return; yield put(authSetProfile({ is_sending_messages: true, messages_error: null })); const { error, data } = yield call(reqWrapper, apiAuthSendMessage, { username, message, }); console.log({ error, data }); if (error || !data.message) { return yield put( authSetProfile({ is_sending_messages: false, messages_error: error || ERRORS.EMPTY_RESPONSE, }) ); } const { user, messages } = yield select(selectAuthProfile); if (user.username !== username) { return yield put(authSetProfile({ is_sending_messages: false })); } yield put( authSetProfile({ is_sending_messages: false, messages: [data.message, ...messages], }) ); onSuccess(); } function* getUpdates() { const user = yield select(selectAuthUser); if (!user || !user.is_user || user.role === USER_ROLES.GUEST || !user.id) return; const modal: IModalState = yield select(selectModal); const profile: IAuthState['profile'] = yield select(selectAuthProfile); const { last }: IAuthState['updates'] = yield select(selectAuthUpdates); const exclude_dialogs = modal.is_shown && modal.dialog === DIALOGS.PROFILE && profile.user.id ? profile.user.id : null; const { error, data }: IResultWithStatus<{ notifications: INotification[] }> = yield call( reqWrapper, apiAuthGetUpdates, { exclude_dialogs, last: last || user.last_seen_messages } ); if (error || !data || !data.notifications || !data.notifications.length) return; const { notifications } = data; yield put( authSetUpdates({ last: notifications[0].created_at, notifications, }) ); } function* startPollingSaga() { while (true) { yield call(getUpdates); yield delay(60000); } } function* setLastSeenMessages({ last_seen_messages }: ReturnType<typeof authSetLastSeenMessages>) { if (!Date.parse(last_seen_messages)) return; yield call(reqWrapper, apiUpdateUser, { user: { last_seen_messages } }); } function* patchUser({ user }: ReturnType<typeof authPatchUser>) { const me = yield select(selectAuthUser); const { error, data } = yield call(reqWrapper, apiUpdateUser, { user }); if (error || !data.user || data.errors) { return yield put(authSetProfile({ patch_errors: data.errors })); } yield put(authSetUser({ ...me, ...data.user })); yield put(authSetProfile({ user: { ...me, ...data.user }, tab: 'profile' })); } function* requestRestoreCode({ field }: ReturnType<typeof authRequestRestoreCode>) { if (!field) return; yield put(authSetRestore({ error: null, is_loading: true })); const { error, data } = yield call(apiRequestRestoreCode, { field }); if (data.error || error) { return yield put(authSetRestore({ is_loading: false, error: data.error || error })); } yield put(authSetRestore({ is_loading: false, is_succesfull: true })); } function* showRestoreModal({ code }: ReturnType<typeof authShowRestoreModal>) { if (!code && !code.length) { return yield put(authSetRestore({ error: ERRORS.CODE_IS_INVALID, is_loading: false })); } yield put(authSetRestore({ user: null, is_loading: true })); const { error, data } = yield call(apiCheckRestoreCode, { code }); if (data.error || error || !data.user) { yield put( authSetRestore({ is_loading: false, error: data.error || error || ERRORS.CODE_IS_INVALID }) ); return yield put(modalShowDialog(DIALOGS.RESTORE_PASSWORD)); } yield put(authSetRestore({ user: data.user, code, is_loading: false })); yield put(modalShowDialog(DIALOGS.RESTORE_PASSWORD)); } function* restorePassword({ password }: ReturnType<typeof authRestorePassword>) { if (!password) return; yield put(authSetRestore({ is_loading: true })); const { code } = yield select(selectAuthRestore); if (!code) { return yield put(authSetRestore({ error: ERRORS.CODE_IS_INVALID, is_loading: false })); } const { error, data } = yield call(apiRestoreCode, { code, password }); if (data.error || error || !data.user || !data.token) { return yield put( authSetRestore({ is_loading: false, error: data.error || error || ERRORS.CODE_IS_INVALID }) ); } yield put(authSetToken(data.token)); yield put(authSetUser(data.user)); yield put(authSetRestore({ is_loading: false, is_succesfull: true, error: null })); yield call(refreshUser); } function* authSaga() { yield takeEvery(REHYDRATE, checkUserSaga); yield takeLatest([REHYDRATE, AUTH_USER_ACTIONS.LOGGED_IN], startPollingSaga); yield takeLatest(AUTH_USER_ACTIONS.LOGOUT, logoutSaga); yield takeLatest(AUTH_USER_ACTIONS.SEND_LOGIN_REQUEST, sendLoginRequestSaga); yield takeLatest(AUTH_USER_ACTIONS.GOT_AUTH_POST_MESSAGE, gotPostMessageSaga); yield takeLatest(AUTH_USER_ACTIONS.OPEN_PROFILE, openProfile); yield takeLatest(AUTH_USER_ACTIONS.GET_MESSAGES, getMessages); yield takeLatest(AUTH_USER_ACTIONS.SEND_MESSAGE, sendMessage); yield takeLatest(AUTH_USER_ACTIONS.SET_LAST_SEEN_MESSAGES, setLastSeenMessages); yield takeLatest(AUTH_USER_ACTIONS.PATCH_USER, patchUser); yield takeLatest(AUTH_USER_ACTIONS.REQUEST_RESTORE_CODE, requestRestoreCode); yield takeLatest(AUTH_USER_ACTIONS.SHOW_RESTORE_MODAL, showRestoreModal); yield takeLatest(AUTH_USER_ACTIONS.RESTORE_PASSWORD, restorePassword); } export default authSaga;