404 lines
11 KiB
JavaScript
404 lines
11 KiB
JavaScript
import FingerprintJS from "@fingerprintjs/fingerprintjs";
|
|
import * as Sentry from "@sentry/browser";
|
|
import { notification } from "antd";
|
|
import axios from "axios";
|
|
import { setUserId, setUserProperties } from "firebase/analytics";
|
|
import {
|
|
checkActionCode,
|
|
confirmPasswordReset,
|
|
sendPasswordResetEmail,
|
|
signInWithEmailAndPassword,
|
|
signOut
|
|
} from "firebase/auth";
|
|
import { arrayUnion, doc, getDoc, setDoc, updateDoc } from "firebase/firestore";
|
|
import { getToken } from "firebase/messaging";
|
|
import i18next from "i18next";
|
|
import LogRocket from "logrocket";
|
|
import { all, call, delay, put, select, takeLatest } from "redux-saga/effects";
|
|
import { Userpilot } from "userpilot";
|
|
import { factory } from "../../App/App.container";
|
|
import {
|
|
analytics,
|
|
auth,
|
|
firestore,
|
|
getCurrentUser,
|
|
logImEXEvent,
|
|
messaging,
|
|
updateCurrentUser
|
|
} from "../../firebase/firebase.utils";
|
|
import { QUERY_EULA } from "../../graphql/bodyshop.queries";
|
|
import client from "../../utils/GraphQLClient";
|
|
import dayjs from "../../utils/day";
|
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
|
import {
|
|
checkInstanceId,
|
|
sendPasswordResetFailure,
|
|
sendPasswordResetSuccess,
|
|
setAuthlevel,
|
|
setInstanceConflict,
|
|
setInstanceId,
|
|
setLocalFingerprint,
|
|
signInFailure,
|
|
signInSuccess,
|
|
signOutFailure,
|
|
signOutSuccess,
|
|
unauthorizedUser,
|
|
updateUserDetailsSuccess,
|
|
validatePasswordResetFailure,
|
|
validatePasswordResetSuccess
|
|
} from "./user.actions";
|
|
import UserActionTypes from "./user.types";
|
|
import cleanAxios from "../../utils/CleanAxios";
|
|
|
|
const fpPromise = FingerprintJS.load();
|
|
|
|
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 signInWithEmailAndPassword(auth, 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);
|
|
|
|
const eulaQuery = yield client.query({
|
|
query: QUERY_EULA,
|
|
variables: {
|
|
now: dayjs()
|
|
}
|
|
});
|
|
|
|
const eulaIsAccepted = eulaQuery.data.eulas.length > 0 && eulaQuery.data.eulas[0].eula_acceptances.length > 0;
|
|
|
|
yield put(
|
|
signInSuccess({
|
|
uid: user.uid,
|
|
email: user.email,
|
|
displayName: user.displayName,
|
|
photoURL: user.photoURL,
|
|
authorized: true,
|
|
eulaIsAccepted,
|
|
currentEula: eulaIsAccepted ? null : eulaQuery.data.eulas[0]
|
|
})
|
|
);
|
|
} catch (error) {
|
|
yield put(signInFailure(error));
|
|
}
|
|
}
|
|
|
|
export function* onSignOutStart() {
|
|
yield takeLatest(UserActionTypes.SIGN_OUT_START, signOutStart);
|
|
}
|
|
|
|
export function* signOutStart() {
|
|
try {
|
|
logImEXEvent("redux_sign_out");
|
|
|
|
const state = yield select();
|
|
|
|
//unsub from topic.
|
|
|
|
try {
|
|
const fcm_tokens = yield getToken(messaging);
|
|
yield call(axios.post, "/notifications/unsubscribe", {
|
|
fcm_tokens,
|
|
imexshopid: state.user.bodyshop.imexshopid,
|
|
type: "messaging"
|
|
});
|
|
} catch (error) {
|
|
console.log("No FCM token. Skipping unsubscribe.");
|
|
}
|
|
|
|
yield signOut(auth);
|
|
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 {
|
|
const updatedDetails = yield updateCurrentUser(userDetails.payload);
|
|
|
|
yield put(updateUserDetailsSuccess(updatedDetails));
|
|
notification.open({
|
|
type: "success",
|
|
message: i18next.t("profile.successes.updated")
|
|
});
|
|
} catch (error) {
|
|
//yield put(signOutFailure(error.message));
|
|
}
|
|
}
|
|
|
|
export function* onSetInstanceId() {
|
|
yield takeLatest(UserActionTypes.SET_INSTANCE_ID, setInstanceIdSaga);
|
|
}
|
|
|
|
export function* setInstanceIdSaga({ payload: uid }) {
|
|
try {
|
|
const userInstanceRef = doc(firestore, `userInstance/${uid}`);
|
|
|
|
// Get the visitor identifier when you need it.
|
|
const fp = yield fpPromise;
|
|
const result = yield fp.get();
|
|
const res = yield cleanAxios.get("https://api.ipify.org/?format=json");
|
|
const udoc = yield getDoc(userInstanceRef);
|
|
|
|
if (!udoc.data()) {
|
|
yield setDoc(userInstanceRef, {
|
|
timestamp: new Date(),
|
|
fingerprint: result.visitorId,
|
|
//totalFingerprint: result,
|
|
ip: [res.data.ip]
|
|
});
|
|
} else {
|
|
yield updateDoc(userInstanceRef, {
|
|
timestamp: new Date(),
|
|
fingerprint: result.visitorId,
|
|
//totalFingerprint: result,
|
|
ip: arrayUnion(res.data.ip)
|
|
});
|
|
}
|
|
|
|
yield put(setLocalFingerprint(result.visitorId));
|
|
yield delay(5 * 60 * 1000);
|
|
if (import.meta.env.PROD) yield put(checkInstanceId(uid));
|
|
} catch (error) {
|
|
console.log("error", error);
|
|
}
|
|
}
|
|
|
|
export function* onCheckInstanceId() {
|
|
yield takeLatest(UserActionTypes.CHECK_INSTANCE_ID, checkInstanceIdSaga);
|
|
}
|
|
|
|
export function* checkInstanceIdSaga({ payload: uid }) {
|
|
try {
|
|
const snapshot = yield getDoc(doc(firestore, `userInstance/${uid}`));
|
|
let fingerprint = yield select((state) => state.user.fingerprint);
|
|
yield put(setInstanceConflict());
|
|
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);
|
|
}
|
|
}
|
|
|
|
export function* onSignInSuccess() {
|
|
yield takeLatest(UserActionTypes.SIGN_IN_SUCCESS, signInSuccessSaga);
|
|
}
|
|
|
|
export function* signInSuccessSaga({ payload }) {
|
|
LogRocket.identify(payload.email);
|
|
|
|
try {
|
|
InstanceRenderManager({
|
|
executeFunction: true,
|
|
args: [],
|
|
imex: () => {
|
|
window.$crisp.push(["set", "user:nickname", [payload.displayName || payload.email]]);
|
|
window.$crisp.push(["set", "session:segments", [["user"]]]);
|
|
},
|
|
rome: () => {
|
|
window.$zoho.salesiq.visitor.name(payload.displayName || payload.email);
|
|
window.$zoho.salesiq.visitor.email(payload.email);
|
|
},
|
|
promanager: () => {
|
|
Userpilot.identify(payload.email, {
|
|
email: payload.email
|
|
});
|
|
console.log("*** Userpilot identified.");
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.log("Error updating Crisp settings.", error);
|
|
}
|
|
|
|
try {
|
|
Sentry.setUser({
|
|
email: payload.email,
|
|
username: payload.displayName || payload.email
|
|
});
|
|
} catch (error) {
|
|
console.log("Error setting Sentry user.", error);
|
|
}
|
|
|
|
setUserId(analytics, payload.email);
|
|
setUserProperties(analytics, payload);
|
|
yield logImEXEvent("redux_sign_in_success");
|
|
}
|
|
|
|
export function* onSendPasswordResetStart() {
|
|
yield takeLatest(UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START, sendPasswordResetEmailSaga);
|
|
yield takeLatest(UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START_AGAIN, sendPasswordResetEmailSaga);
|
|
}
|
|
|
|
export function* sendPasswordResetEmailSaga({ payload }) {
|
|
try {
|
|
yield sendPasswordResetEmail(auth, payload, {
|
|
url: InstanceRenderManager({
|
|
imex: "https://imex.online/passwordreset",
|
|
rome: "https://romeonline.io/passwordreset",
|
|
promanager: "https:promanager.web-est.com/passwordreset"
|
|
})
|
|
});
|
|
|
|
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 {
|
|
checkActionCode(auth, code);
|
|
yield confirmPasswordReset(auth, code, password);
|
|
yield put(validatePasswordResetSuccess());
|
|
} catch (error) {
|
|
yield put(validatePasswordResetFailure(error.message));
|
|
}
|
|
}
|
|
|
|
export function* onSetShopDetails() {
|
|
yield takeLatest(UserActionTypes.SET_SHOP_DETAILS, SetAuthLevelFromShopDetails);
|
|
}
|
|
|
|
export function* SetAuthLevelFromShopDetails({ payload }) {
|
|
try {
|
|
const userEmail = yield select((state) => state.user.currentUser.email);
|
|
try {
|
|
dayjs.tz.setDefault(payload.timezone);
|
|
} catch (error) {
|
|
console.log(error);
|
|
}
|
|
|
|
factory.client(payload.imexshopid);
|
|
|
|
const authRecord = payload.associations.filter((a) => a.useremail.toLowerCase() === userEmail.toLowerCase());
|
|
|
|
yield put(setAuthlevel(authRecord[0] ? authRecord[0].authlevel : 0));
|
|
yield put(
|
|
updateUserDetailsSuccess(authRecord[0] ? { validemail: authRecord[0].user.validemail } : { validemail: false })
|
|
);
|
|
|
|
const user = yield select((state) => state.user.currentUser);
|
|
if (payload.features.singleDeviceOnly) {
|
|
if (
|
|
!(
|
|
user.email.includes("@imex.") ||
|
|
user.email.includes("@rome.") ||
|
|
user.email.includes("@rometech.") ||
|
|
user.email.includes("@promanager.")
|
|
)
|
|
)
|
|
yield put(setInstanceId(user.uid));
|
|
}
|
|
|
|
//For Rome, check to make sure it's not a PM shop.
|
|
try {
|
|
InstanceRenderManager({
|
|
executeFunction: true,
|
|
args: [],
|
|
rome: () => {
|
|
if (
|
|
payload.imexshopid.toLowerCase().startsWith("pm_") &&
|
|
!(
|
|
user.email.includes("@imex.") ||
|
|
user.email.includes("@rome.") ||
|
|
user.email.includes("@rometech.") ||
|
|
user.email.includes("@promanager.")
|
|
)
|
|
) {
|
|
throw new Error("You are not authorized to use this application.");
|
|
}
|
|
},
|
|
promanager: () => {}
|
|
});
|
|
} catch (error) {
|
|
yield put(setInstanceConflict());
|
|
}
|
|
|
|
try {
|
|
InstanceRenderManager({
|
|
executeFunction: true,
|
|
args: [],
|
|
imex: () => {
|
|
window.$crisp.push(["set", "user:company", [payload.shopname]]);
|
|
if (authRecord[0] && authRecord[0].user.validemail) {
|
|
window.$crisp.push(["set", "user:email", [authRecord[0].user.email]]);
|
|
}
|
|
},
|
|
rome: () => {
|
|
window.$zoho.salesiq.visitor.info({ "Shop Name": payload.shopname });
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error("Couldnt find $crisp.");
|
|
}
|
|
} catch (error) {
|
|
yield put(signInFailure(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),
|
|
call(onSetShopDetails)
|
|
]);
|
|
}
|