import Fingerprint2 from "fingerprintjs2"; import LogRocket from "logrocket"; import { all, call, delay, put, select, takeLatest } from "redux-saga/effects"; import { auth, firestore, getCurrentUser, logImEXEvent, updateCurrentUser, } from "../../firebase/firebase.utils"; import { checkInstanceId, setInstanceConflict, setInstanceId, setLocalFingerprint, signInFailure, signInSuccess, signOutFailure, signOutSuccess, unauthorizedUser, updateUserDetailsSuccess, sendPasswordResetFailure, sendPasswordResetSuccess, validatePasswordResetSuccess, validatePasswordResetFailure, } from "./user.actions"; import UserActionTypes from "./user.types"; export function* onEmailSignInStart() { yield takeLatest(UserActionTypes.EMAIL_SIGN_IN_START, signInWithEmail); } export function* signInWithEmail({ payload: { email, password } }) { try { logImEXEvent("redux_sign_in_attempt", { user: email }); const { user } = yield auth.signInWithEmailAndPassword(email, password); yield put( signInSuccess({ uid: user.uid, email: user.email, displayName: user.displayName, photoURL: user.photoURL, authorized: true, }) ); } catch (error) { yield put(signInFailure(error)); logImEXEvent("redux_sign_in_failure", { user: email, error }); } } export function* onCheckUserSession() { yield takeLatest(UserActionTypes.CHECK_USER_SESSION, isUserAuthenticated); } export function* isUserAuthenticated() { try { logImEXEvent("redux_auth_check"); const user = yield getCurrentUser(); if (!user) { yield put(unauthorizedUser()); return; } LogRocket.identify(user.email); yield put( signInSuccess({ uid: user.uid, email: user.email, displayName: user.displayName, photoURL: user.photoURL, authorized: true, }) ); } catch (error) { yield put(signInFailure(error)); } } export function* onSignOutStart() { yield takeLatest(UserActionTypes.SIGN_OUT_START, signOutStart); } export function* signOutStart() { try { logImEXEvent("redux_sign_out"); yield auth.signOut(); yield put(signOutSuccess()); localStorage.removeItem("token"); } catch (error) { yield put(signOutFailure(error.message)); } } export function* onUpdateUserDetails() { yield takeLatest(UserActionTypes.UPDATE_USER_DETAILS, updateUserDetails); } export function* updateUserDetails(userDetails) { try { yield updateCurrentUser(userDetails.payload); yield put(updateUserDetailsSuccess(userDetails.payload)); } catch (error) { //yield put(signOutFailure(error.message)); //TODO error handling } } export function* onSetInstanceId() { yield takeLatest(UserActionTypes.SET_INSTANCE_ID, setInstanceIdSaga); } export function* setInstanceIdSaga({ payload: uid }) { try { const userInstanceRef = firestore.doc(`userInstance/${uid}`); const fingerprint = Fingerprint2.x64hash128( (yield Fingerprint2.getPromise({})).map((c) => c.value).join(""), 31 ); yield userInstanceRef.set({ timestamp: new Date(), fingerprint, }); yield put(setLocalFingerprint(fingerprint)); yield delay(5 * 60 * 1000); yield put(checkInstanceId(uid)); } catch (error) { console.log("error", error); //yield put(signOutFailure(error.message)); //TODO error handling } } export function* onCheckInstanceId() { yield takeLatest(UserActionTypes.CHECK_INSTANCE_ID, checkInstanceIdSaga); } export function* checkInstanceIdSaga({ payload: uid }) { try { const userInstanceRef = firestore.doc(`userInstance/${uid}`); const snapshot = yield userInstanceRef.get(); let fingerprint = yield select((state) => state.user.fingerprint); if (snapshot.data().fingerprint === fingerprint) { yield delay(5 * 60 * 1000); yield put(checkInstanceId(uid)); } else { console.log("ERROR: Fingerprints do not match. Conflict detected."); logImEXEvent("instance_confict"); yield put(setInstanceConflict()); } } catch (error) { console.log("error", error); //TODO error handling } } export function* onSignInSuccess() { yield takeLatest(UserActionTypes.SIGN_IN_SUCCESS, signInSuccessSaga); } export function* signInSuccessSaga({ payload }) { LogRocket.identify(payload.email); yield put(setInstanceId(payload.uid)); yield logImEXEvent("redux_sign_in_success"); } export function* onSendPasswordResetStart() { yield takeLatest( UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START, sendPasswordResetEmail ); } export function* sendPasswordResetEmail({ payload }) { try { yield auth.sendPasswordResetEmail(payload, { url: "https://imex.online/passwordreset", }); console.log("Good should send."); yield put(sendPasswordResetSuccess()); } catch (error) { yield put(sendPasswordResetFailure(error.message)); } } export function* onValidatePasswordResetStart() { yield takeLatest( UserActionTypes.VALIDATE_PASSWORD_RESET_START, validatePasswordResetStart ); } export function* validatePasswordResetStart({ payload: { password, code } }) { try { yield auth.confirmPasswordReset(code, password); yield put(validatePasswordResetSuccess()); } catch (error) { console.log("function*validatePasswordResetStart -> error", error); yield put(validatePasswordResetFailure(error.message)); } } export function* userSagas() { yield all([ call(onEmailSignInStart), call(onCheckUserSession), call(onSignOutStart), call(onUpdateUserDetails), call(onSetInstanceId), call(onCheckInstanceId), call(onSignInSuccess), call(onSendPasswordResetStart), call(onValidatePasswordResetStart), ]); }