Initial database structure and migrations, login flow, refactor main page.

This commit is contained in:
Patrick Fic
2020-10-13 17:45:36 -07:00
parent abfb91d55f
commit 030f0b7a90
71 changed files with 5331 additions and 387 deletions

View File

@@ -1,55 +1,41 @@
import { Button, Layout } from "antd";
import { ApolloProvider } from "@apollo/client";
import { ConfigProvider, Spin } from "antd";
import enLocale from "antd/es/locale/en_US";
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import ipcTypes from "../ipc.types";
import RoutesPage from "../components/pages/routes/routes.page";
import SignInPage from "../components/pages/sign-in/sign-in.page";
import client from "../graphql/GraphQLClient";
import { checkUserSession } from "../redux/user/user.actions";
import { selectCurrentUser } from "../redux/user/user.selectors";
const { ipcRenderer } = window.require("electron");
const settings = window.require("electron-settings");
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
});
const mapDispatchToProps = (dispatch) => ({
checkUserSession: () => dispatch(checkUserSession()),
});
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({});
export function App() {
export function App({ currentUser, checkUserSession }) {
useEffect(() => {
ipcRenderer.on("test-success", (event, obj) => {
console.log("Test Success", obj);
});
ipcRenderer.on(ipcTypes.default.filewatcher.startSuccess, (event, obj) => {
console.log(ipcTypes.default.filewatcher.startSuccess, obj);
});
// Cleanup the listener events so that memory leaks are avoided.
return function cleanup() {
ipcRenderer.removeAllListeners(
"test-success",
ipcTypes.default.filewatcher.startSuccess
);
};
}, []);
checkUserSession();
}, [checkUserSession]);
if (currentUser.authorized === null) {
return <Spin />;
}
return (
<Layout>
<Layout.Header>
<div> Header</div>
</Layout.Header>
<Layout.Content>
<div>Welcome to your new react app. asdas sd</div>
<Button
onClick={() => {
ipcRenderer.send("test", { test: true });
}}
>
TEST Generic IPC
</Button>
<Button
onClick={() => {
ipcRenderer.send(ipcTypes.default.filewatcher.start);
}}
>
Start Watcher
</Button>
</Layout.Content>
</Layout>
<ApolloProvider client={client}>
<ConfigProvider
componentSize="small"
input={{ autoComplete: "new-password" }}
locale={enLocale}
>
<div>{currentUser.authorized ? <RoutesPage /> : <SignInPage />}</div>
</ConfigProvider>
</ApolloProvider>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(App);

BIN
src/assets/ImEX Logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
src/assets/logo1024.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

BIN
src/assets/logo512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1,17 @@
import { Menu } from "antd";
import React from "react";
import { connect } from "react-redux";
import { signOutStart } from "../../../redux/user/user.actions";
const mapDispatchToProps = (dispatch) => ({
signOutStart: () => dispatch(signOutStart()),
});
export function SiderSignOut({ signOutStart, ...restProps }) {
return (
<Menu.Item {...restProps} onClick={() => signOutStart()}>
Sign Out
</Menu.Item>
);
}
export default connect(null, mapDispatchToProps)(SiderSignOut);

View File

@@ -0,0 +1,37 @@
import {
DesktopOutlined,
FileOutlined,
PieChartOutlined,
TeamOutlined,
UserOutlined,
} from "@ant-design/icons";
import { Menu } from "antd";
import React from "react";
import { Link } from "react-router-dom";
import SiderSignOut from "../../molecules/sider-sign-out/sider-sign-out.molecule";
const { SubMenu } = Menu;
export default function SiderMenuOrganism() {
return (
<Menu defaultSelectedKeys={["1"]} mode="inline">
<Menu.Item key="1" icon={<PieChartOutlined />}>
<Link to="/">Jobs</Link>
</Menu.Item>
<Menu.Item key="2" icon={<DesktopOutlined />}>
<Link to="/nope">Jobs Not Working</Link>
</Menu.Item>
<SubMenu key="sub1" icon={<UserOutlined />} title="User">
<Menu.Item key="3">Tom</Menu.Item>
<Menu.Item key="4">Bill</Menu.Item>
<Menu.Item key="5">Alex</Menu.Item>
</SubMenu>
<SubMenu key="sub2" icon={<TeamOutlined />} title="Team">
<Menu.Item key="6">Team 1</Menu.Item>
<Menu.Item key="8">Team 2</Menu.Item>
</SubMenu>
<Menu.Item key="9" icon={<FileOutlined />} />
<SiderSignOut />
</Menu>
);
}

View File

@@ -0,0 +1,50 @@
import { Button } from "antd";
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import ipcTypes from "../../../ipc.types";
const { ipcRenderer } = window.require("electron");
//const settings = window.require("electron-settings");
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({});
export function JobsPage() {
useEffect(() => {
ipcRenderer.on("test-success", (event, obj) => {
console.log("Test Success", obj);
});
ipcRenderer.on(ipcTypes.default.filewatcher.startSuccess, (event, obj) => {
console.log(ipcTypes.default.filewatcher.startSuccess, obj);
});
// Cleanup the listener events so that memory leaks are avoided.
return function cleanup() {
ipcRenderer.removeAllListeners(
"test-success",
ipcTypes.default.filewatcher.startSuccess
);
};
}, []);
return (
<div>
<div>Welcome to your new react app. asdas sd</div>
<Button
onClick={() => {
ipcRenderer.send("test", { test: true });
}}
>
TEST Generic IPC
</Button>
<Button
onClick={() => {
ipcRenderer.send(ipcTypes.default.filewatcher.start);
}}
>
Start Watcher
</Button>
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(JobsPage);

View File

@@ -0,0 +1,28 @@
import { Layout } from "antd";
import React from "react";
import { connect } from "react-redux";
import { Route, Switch } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import SiderMenuOrganism from "../../organisms/sider-menu/sider-menu.organism";
import Jobs from "../jobs/jobs.page";
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({});
export function RoutesPage() {
return (
<Layout style={{ background: "#fff" }} hasSider>
<Layout.Sider style={{ background: "#fff" }} collapsible>
<SiderMenuOrganism />
</Layout.Sider>
<Layout style={{ background: "#fff" }}>
<Layout.Content style={{ margin: "1rem" }}>
<Switch>
<Route exact path="/" component={Jobs} />
</Switch>
</Layout.Content>
</Layout>
</Layout>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(RoutesPage);

View File

@@ -0,0 +1,59 @@
import { LockOutlined, UserOutlined } from "@ant-design/icons";
import { Alert, Button, Form, Input, Typography } from "antd";
import React from "react";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import ImEXOnlineLogo from "../../../assets/logo192.png";
import { emailSignInStart } from "../../../redux/user/user.actions";
import { selectSignInError } from "../../../redux/user/user.selectors";
import "./sign-in.page.styles.scss";
const mapStateToProps = createStructuredSelector({
signInError: selectSignInError,
});
const mapDispatchToProps = (dispatch) => ({
emailSignInStart: (email, password) =>
dispatch(emailSignInStart({ email, password })),
});
export function SignInPage({ emailSignInStart, signInError }) {
const handleFinish = (values) => {
const { email, password } = values;
emailSignInStart(email, password);
};
const [form] = Form.useForm();
return (
<div className="login-container">
<div className="login-logo-container">
<img src={ImEXOnlineLogo} height="100" width="100" alt="ImEX Online" />
<Typography.Title>ImEX RPS</Typography.Title>
</div>
<Form onFinish={handleFinish} form={form} size="large">
<Form.Item name="email" rules={[{ required: true }]}>
<Input prefix={<UserOutlined />} placeholder="Email" />
</Form.Item>
<Form.Item name="password" rules={[{ required: true }]}>
<Input
prefix={<LockOutlined />}
type="password"
placeholder="Password"
/>
</Form.Item>
{signInError ? (
<Alert type="error" message={signInError.message} />
) : null}
<Button className="login-btn" type="primary" htmlType="submit">
Login
</Button>
</Form>
<Link to={"/resetpassword"}>
<Button>Reset Password</Button>
</Link>
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(SignInPage);

View File

@@ -0,0 +1,29 @@
.login-container {
display: flex;
align-items: center;
flex-direction: column;
padding: 2rem;
form {
width: 75vw;
max-width: 20rem;
}
}
.login-logo-container {
display: flex;
align-items: center;
margin-bottom: 2rem;
h1 {
text-align: center;
margin: 1rem;
}
}
//Required as it is position inside form.
.login-btn {
margin: 1.5rem 0rem;
position: relative;
left: 50%;
transform: translate(-50%, 0);
}

View File

@@ -0,0 +1,64 @@
import { Button, Col, Collapse, Result, Row, Space } from "antd";
import React from "react";
class ErrorBoundary extends React.Component {
constructor() {
super();
this.state = {
hasErrored: false,
error: null,
info: null,
};
}
static getDerivedStateFromError(error) {
console.log("ErrorBoundary -> getDerivedStateFromError -> error", error);
return { hasErrored: true, error: error };
}
componentDidCatch(error, info) {
console.log("Exception Caught by Error Boundary.", error, info);
this.setState({ ...this.state, error, info });
}
render() {
if (this.state.hasErrored === true) {
return (
<div>
<Result
status="500"
title="Error!"
subTitle="Error subtitle"
extra={
<Space>
<Button
type="primary"
onClick={() => {
window.location.reload();
}}
>
Refresh
</Button>
</Space>
}
/>
<Row>
<Col offset={6} span={12}>
<Collapse bordered={false}>
<Collapse.Panel header="Error Details">
<div>
<strong>{this.state.error.message}</strong>
</div>
<div>{this.state.error.stack}</div>
</Collapse.Panel>
</Collapse>
</Col>
</Row>
</div>
);
} else {
return this.props.children;
}
}
}
export default ErrorBoundary;

55
src/components/test.jsx Normal file
View File

@@ -0,0 +1,55 @@
import { Button, Layout } from "antd";
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import ipcTypes from "../ipc.types";
const { ipcRenderer } = window.require("electron");
//const settings = window.require("electron-settings");
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({});
export function App() {
useEffect(() => {
ipcRenderer.on("test-success", (event, obj) => {
console.log("Test Success", obj);
});
ipcRenderer.on(ipcTypes.default.filewatcher.startSuccess, (event, obj) => {
console.log(ipcTypes.default.filewatcher.startSuccess, obj);
});
// Cleanup the listener events so that memory leaks are avoided.
return function cleanup() {
ipcRenderer.removeAllListeners(
"test-success",
ipcTypes.default.filewatcher.startSuccess
);
};
}, []);
return (
<Layout>
<Layout.Header>
<div> Header</div>
</Layout.Header>
<Layout.Content>
<div>Welcome to your new react app. asdas sd</div>
<Button
onClick={() => {
ipcRenderer.send("test", { test: true });
}}
>
TEST Generic IPC
</Button>
<Button
onClick={() => {
ipcRenderer.send(ipcTypes.default.filewatcher.start);
}}
>
Start Watcher
</Button>
</Layout.Content>
</Layout>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(App);

View File

@@ -1,18 +1,14 @@
import firebase from "firebase/app";
import "firebase/firestore";
import "firebase/analytics";
import "firebase/auth";
import "firebase/database";
import "firebase/analytics";
import "firebase/messaging";
import { store } from "../redux/store";
import firebase from "firebase/app";
const config = JSON.parse(process.env.REACT_APP_FIREBASE_CONFIG);
firebase.initializeApp(config);
export const auth = firebase.auth();
export const firestore = firebase.firestore();
export const analytics = firebase.analytics();
export default firebase;
export const getCurrentUser = () => {
@@ -34,83 +30,3 @@ export const updateCurrentUser = (userDetails) => {
}, reject);
});
};
let messaging;
try {
messaging = firebase.messaging();
// Project Settings => Cloud Messaging => Web Push certificates
messaging.usePublicVapidKey(process.env.REACT_APP_FIREBASE_PUBLIC_VAPID_KEY);
console.log("[FCM UTIL] FCM initialized successfully.");
} catch {
console.log("[FCM UTIL] Firebase Messaging is likely unsupported.");
}
export { messaging };
export const logImEXEvent = (eventName, additionalParams, stateProp = null) => {
const state = stateProp || store.getState();
const eventParams = {
shop:
(state.user && state.user.bodyshop && state.user.bodyshop.shopname) ||
null,
user:
(state.user && state.user.currentUser && state.user.currentUser.email) ||
null,
...additionalParams,
};
analytics.logEvent(eventName, eventParams);
};
if (messaging) {
messaging.onMessage(async (payload) => {
console.log("[FCM] UTILS Message received. ", payload);
navigator.serviceWorker.getRegistration().then((registration) => {
return registration.showNotification(
"[UTIL]" + payload.notification.title,
payload.notification
);
});
// if (!payload.clientId) return;
// // Get the client.
// const client = await clients.get(payload.clientId);
// // Exit early if we don't get the client.
// // Eg, if it closed.
// if (!client) return;
// // Send a message to the client.
// console.log("Posting to client.");
// client.postMessage({
// msg: "Hey I just got a fetch from you!",
// url: payload.request.url,
// });
// [START_EXCLUDE]
// Update the UI to include the received message.
//appendMessage(payload);
// [END_EXCLUDE]
});
messaging.onTokenRefresh(() => {
messaging
.getToken()
.then((refreshedToken) => {
console.log("[FCM] Token refreshed.");
// Indicate that the new Instance ID token has not yet been sent to the
// app server.
// setTokenSentToServer(false);
// // Send Instance ID token to app server.
// sendTokenToServer(refreshedToken);
// // [START_EXCLUDE]
// // Display new Instance ID token and clear UI of all previous messages.
// resetUI();
// [END_EXCLUDE]
})
.catch((err) => {
console.log("[FCM] Unable to retrieve refreshed token ", err);
// showToken("Unable to retrieve refreshed token ", err);
});
});
}

View File

@@ -0,0 +1,130 @@
import { ApolloClient, ApolloLink, InMemoryCache } from "@apollo/client";
import { setContext } from "@apollo/client/link/context";
import { onError } from "@apollo/client/link/error";
import { HttpLink } from "@apollo/client/link/http"; //"apollo-link-http";
import { RetryLink } from "@apollo/client/link/retry";
import { WebSocketLink } from "@apollo/client/link/ws";
import { getMainDefinition } from "@apollo/client/utilities";
import apolloLogger from "apollo-link-logger";
import { auth } from "../firebase/firebase.utils";
const errorLink = onError(
({ graphQLErrors, networkError, operation, forward }) => {
if (graphQLErrors)
graphQLErrors.forEach(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
)
);
if (networkError)
console.log(`[Network error]: ${JSON.stringify(networkError)}`);
console.log(operation.getContext());
}
);
const httpLink = new HttpLink({
uri: process.env.REACT_APP_GRAPHQL_ENDPOINT,
});
const wsLink = new WebSocketLink({
uri: process.env.REACT_APP_GRAPHQL_ENDPOINT_WS,
options: {
lazy: true,
reconnect: true,
connectionParams: async () => {
const token =
auth.currentUser && (await auth.currentUser.getIdToken(true));
if (token) {
return {
headers: {
authorization: token ? `Bearer ${token}` : "",
},
};
}
},
},
});
const subscriptionMiddleware = {
applyMiddleware: async (options, next) => {
options.authToken =
auth.currentUser && (await auth.currentUser.getIdToken(true));
next();
},
};
wsLink.subscriptionClient.use([subscriptionMiddleware]);
const link = new HttpLink.split(
// split based on operation type
({ query }) => {
const definition = getMainDefinition(query);
// console.log(
// "##Intercepted GQL Transaction : " +
// definition.operation +
// "|" +
// definition.name.value +
// "##",
// query
// );
return (
definition.kind === "OperationDefinition" &&
definition.operation === "subscription"
);
},
wsLink,
httpLink
);
const authLink = setContext((_, { headers }) => {
return (
auth.currentUser &&
auth.currentUser.getIdToken().then((token) => {
if (token) {
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
},
};
} else {
console.log("We have no authorization header.");
return { headers };
}
})
);
});
const retryLink = new RetryLink({
delay: {
initial: 500,
max: 5,
jitter: true,
},
attempts: {
max: 5,
retryIf: (error, _operation) => !!error,
},
});
const middlewares = [];
if (process.env.NODE_ENV === "development") {
middlewares.push(apolloLogger);
}
middlewares.push(retryLink.concat(errorLink.concat(authLink.concat(link))));
const cache = new InMemoryCache({});
export default new ApolloClient({
link: ApolloLink.from(middlewares),
cache,
connectToDevTools: process.env.NODE_ENV !== "production",
defaultOptions: {
query: {
fetchPolicy: "network-only",
},
watchQuery: {
fetchPolicy: "network-only",
},
},
});

View File

@@ -0,0 +1,14 @@
import gql from "graphql-tag";
export const UPSERT_USER = gql`
mutation UPSERT_USER($authEmail: String!, $authToken: String!) {
insert_users(
objects: [{ email: $authEmail, authid: $authToken }]
on_conflict: { constraint: users_pkey, update_columns: [authid] }
) {
returning {
authid
}
}
}
`;

View File

@@ -1,6 +1,7 @@
import { all, call } from "redux-saga/effects";
import { applicationSagas } from "./application/application.sagas";
import { userSagas } from "./user/user.sagas";
export default function* rootSaga() {
yield all([call(applicationSagas)]);
yield all([call(applicationSagas), call(userSagas)]);
}

View File

@@ -1,29 +1,22 @@
import Fingerprint2 from "fingerprintjs2";
import LogRocket from "logrocket";
import { all, call, delay, put, select, takeLatest } from "redux-saga/effects";
import { all, call, put, takeLatest } from "redux-saga/effects";
import {
auth,
firestore,
getCurrentUser,
logImEXEvent,
updateCurrentUser,
} from "../../firebase/firebase.utils";
import client from "../../graphql/GraphQLClient";
import { UPSERT_USER } from "../../graphql/user.queries";
import {
checkInstanceId,
setInstanceConflict,
setInstanceId,
setLocalFingerprint,
sendPasswordResetFailure,
sendPasswordResetSuccess,
signInFailure,
signInSuccess,
signOutFailure,
signOutSuccess,
unauthorizedUser,
updateUserDetailsSuccess,
sendPasswordResetFailure,
sendPasswordResetSuccess,
validatePasswordResetSuccess,
validatePasswordResetFailure,
setAuthlevel,
validatePasswordResetSuccess,
} from "./user.actions";
import UserActionTypes from "./user.types";
@@ -32,9 +25,14 @@ export function* onEmailSignInStart() {
}
export function* signInWithEmail({ payload: { email, password } }) {
try {
logImEXEvent("redux_sign_in_attempt", { user: email });
const { user } = yield auth.signInWithEmailAndPassword(email, password);
console.log("function*signInWithEmail -> user", user)
const result = yield client.mutate({
mutation: UPSERT_USER,
variables: { authEmail: user.email, authToken: user.uid },
});
console.log("function*signInWithEmail -> result", result);
yield put(
signInSuccess({
@@ -47,7 +45,6 @@ export function* signInWithEmail({ payload: { email, password } }) {
);
} catch (error) {
yield put(signInFailure(error));
logImEXEvent("redux_sign_in_failure", { user: email, error });
}
}
@@ -56,15 +53,12 @@ export function* onCheckUserSession() {
}
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,
@@ -83,8 +77,6 @@ export function* onSignOutStart() {
}
export function* signOutStart() {
try {
logImEXEvent("redux_sign_out");
yield auth.signOut();
yield put(signOutSuccess());
localStorage.removeItem("token");
@@ -105,66 +97,15 @@ export function* updateUserDetails(userDetails) {
//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);
if (process.env.NODE_ENV === "production") 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);
if (!payload.email.includes("@imex.")) yield put(setInstanceId(payload.uid));
yield logImEXEvent("redux_sign_in_success");
// LogRocket.identify(payload.email);
// if (!payload.email.includes("@imex.")) yield put(setInstanceId(payload.uid));
// yield logImEXEvent("redux_sign_in_success");
}
export function* onSendPasswordResetStart() {
@@ -178,7 +119,7 @@ export function* sendPasswordResetEmail({ payload }) {
yield auth.sendPasswordResetEmail(payload, {
url: "https://imex.online/passwordreset",
});
console.log("Good should send.");
yield put(sendPasswordResetSuccess());
} catch (error) {
yield put(sendPasswordResetFailure(error.message));
@@ -201,37 +142,14 @@ export function* validatePasswordResetStart({ payload: { password, code } }) {
}
}
export function* onSetShopDetails() {
yield takeLatest(
UserActionTypes.SET_SHOP_DETAILS,
SetAuthLevelFromShopDetails
);
}
export function* SetAuthLevelFromShopDetails({ payload }) {
try {
const userEmail = yield select((state) => state.user.currentUser.email);
const authRecord = payload.associations.filter(
(a) => a.useremail === userEmail
);
yield put(setAuthlevel(authRecord[0] ? authRecord[0].authlevel : 0));
} 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),
]);
}