diff --git a/backend/src/controllers/WhatsAppSessionController.ts b/backend/src/controllers/WhatsAppSessionController.ts index 8a03645..045aa20 100644 --- a/backend/src/controllers/WhatsAppSessionController.ts +++ b/backend/src/controllers/WhatsAppSessionController.ts @@ -2,6 +2,7 @@ import { Request, Response } from "express"; import { getWbot } from "../libs/wbot"; import ShowWhatsAppService from "../services/WhatsappService/ShowWhatsAppService"; import { StartWhatsAppSession } from "../services/WbotServices/StartWhatsAppSession"; +import UpdateWhatsAppService from "../services/WhatsappService/UpdateWhatsAppService"; const store = async (req: Request, res: Response): Promise => { const { whatsappId } = req.params; @@ -14,9 +15,11 @@ const store = async (req: Request, res: Response): Promise => { const update = async (req: Request, res: Response): Promise => { const { whatsappId } = req.params; - const whatsapp = await ShowWhatsAppService(whatsappId); - await whatsapp.update({ session: "" }); + const { whatsapp } = await UpdateWhatsAppService({ + whatsappId, + whatsappData: { session: "" } + }); StartWhatsAppSession(whatsapp); diff --git a/backend/src/libs/wbot.ts b/backend/src/libs/wbot.ts index ce8ba81..6ab64df 100644 --- a/backend/src/libs/wbot.ts +++ b/backend/src/libs/wbot.ts @@ -21,12 +21,6 @@ export const initWbot = async (whatsapp: Whatsapp): Promise => { sessionCfg = JSON.parse(whatsapp.session); } - const currentSessionIndex = sessions.findIndex(s => s.id === whatsapp.id); - if (currentSessionIndex !== -1) { - sessions[currentSessionIndex].destroy(); - sessions.splice(currentSessionIndex, 1); - } - const wbot: Session = new Client({ session: sessionCfg }); diff --git a/backend/src/services/WhatsappService/UpdateWhatsAppService.ts b/backend/src/services/WhatsappService/UpdateWhatsAppService.ts index f98818d..2fe79cc 100644 --- a/backend/src/services/WhatsappService/UpdateWhatsAppService.ts +++ b/backend/src/services/WhatsappService/UpdateWhatsAppService.ts @@ -7,6 +7,7 @@ import Whatsapp from "../../models/Whatsapp"; interface WhatsappData { name?: string; status?: string; + session?: string; isDefault?: boolean; } @@ -29,7 +30,7 @@ const UpdateWhatsAppService = async ({ isDefault: Yup.boolean() }); - const { name, status, isDefault } = whatsappData; + const { name, status, isDefault, session } = whatsappData; try { await schema.validate({ name, status, isDefault }); @@ -58,6 +59,7 @@ const UpdateWhatsAppService = async ({ await whatsapp.update({ name, status, + session, isDefault }); diff --git a/frontend/src/components/_layout/MainListItems.js b/frontend/src/components/_layout/MainListItems.js index ba1265c..2b1e4b4 100644 --- a/frontend/src/components/_layout/MainListItems.js +++ b/frontend/src/components/_layout/MainListItems.js @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useContext, useEffect, useState } from "react"; import { Link as RouterLink } from "react-router-dom"; import ListItem from "@material-ui/core/ListItem"; @@ -15,6 +15,9 @@ import GroupIcon from "@material-ui/icons/Group"; import ContactPhoneIcon from "@material-ui/icons/ContactPhone"; import { i18n } from "../../translate/i18n"; +import { Badge } from "@material-ui/core"; + +import { WhatsAppsContext } from "../../context/WhatsApp/WhatsAppsContext"; function ListItemLink(props) { const { icon, primary, to, className } = props; @@ -39,13 +42,44 @@ function ListItemLink(props) { const MainListItems = () => { const userProfile = localStorage.getItem("profile"); + const { whatsApps } = useContext(WhatsAppsContext); + const [connectionWarning, setConnectionWarning] = useState(false); + + useEffect(() => { + const delayDebounceFn = setTimeout(() => { + if (whatsApps.length > 0) { + const offlineWhats = whatsApps.filter(whats => { + if ( + whats.status === "qrcode" || + whats.status === "PAIRING" || + whats.status === "DISCONNECTED" || + whats.status === "TIMEOUT" || + whats.status === "OPENING" + ) + return true; + else return false; + }); + if (offlineWhats.length > 0) { + setConnectionWarning(true); + } else { + setConnectionWarning(false); + } + } + }, 2000); + return () => clearTimeout(delayDebounceFn); + }, [whatsApps]); + return (
} /> } + icon={ + + + + } /> ({ }, })); -const LoggedInLayout = ({ appTitle, children }) => { +const LoggedInLayout = ({ children }) => { const drawerState = localStorage.getItem("drawerOpen"); const userId = +localStorage.getItem("userId"); const classes = useStyles(); diff --git a/frontend/src/context/Auth/useAuth.js b/frontend/src/context/Auth/useAuth.js index 45bbb2b..b607954 100644 --- a/frontend/src/context/Auth/useAuth.js +++ b/frontend/src/context/Auth/useAuth.js @@ -11,6 +11,48 @@ const useAuth = () => { const [isAuth, setIsAuth] = useState(false); const [loading, setLoading] = useState(true); + api.interceptors.request.use( + config => { + const token = localStorage.getItem("token"); + if (token) { + config.headers["Authorization"] = `Bearer ${JSON.parse(token)}`; + setIsAuth(true); + } + return config; + }, + error => { + Promise.reject(error); + } + ); + + api.interceptors.response.use( + response => { + return response; + }, + async error => { + const originalRequest = error.config; + if (error?.response?.status === 403 && !originalRequest._retry) { + originalRequest._retry = true; + + const { data } = await api.post("/auth/refresh_token"); + if (data) { + localStorage.setItem("token", JSON.stringify(data.token)); + api.defaults.headers.Authorization = `Bearer ${data.token}`; + } + return api(originalRequest); + } + if (error?.response?.status === 401) { + localStorage.removeItem("token"); + localStorage.removeItem("username"); + localStorage.removeItem("profile"); + localStorage.removeItem("userId"); + api.defaults.headers.Authorization = undefined; + setIsAuth(false); + } + return Promise.reject(error); + } + ); + useEffect(() => { const token = localStorage.getItem("token"); if (token) { @@ -18,48 +60,6 @@ const useAuth = () => { setIsAuth(true); } setLoading(false); - - api.interceptors.request.use( - config => { - const token = localStorage.getItem("token"); - if (token) { - config.headers["Authorization"] = `Bearer ${JSON.parse(token)}`; - setIsAuth(true); - } - return config; - }, - error => { - Promise.reject(error); - } - ); - - api.interceptors.response.use( - response => { - return response; - }, - async error => { - const originalRequest = error.config; - if (error?.response?.status === 403 && !originalRequest._retry) { - originalRequest._retry = true; - - const { data } = await api.post("/auth/refresh_token"); - if (data) { - localStorage.setItem("token", JSON.stringify(data.token)); - api.defaults.headers.Authorization = `Bearer ${data.token}`; - } - return api(originalRequest); - } - if (error?.response?.status === 401) { - localStorage.removeItem("token"); - localStorage.removeItem("username"); - localStorage.removeItem("profile"); - localStorage.removeItem("userId"); - api.defaults.headers.Authorization = undefined; - setIsAuth(false); - } - return Promise.reject(error); - } - ); }, []); const handleLogin = async (e, user) => { @@ -106,7 +106,7 @@ const useAuth = () => { history.push("/login"); }; - return { isAuth, loading, handleLogin, handleLogout }; + return { isAuth, setIsAuth, loading, handleLogin, handleLogout }; }; export default useAuth; diff --git a/frontend/src/context/WhatsApp/WhatsAppsContext.js b/frontend/src/context/WhatsApp/WhatsAppsContext.js new file mode 100644 index 0000000..4b4daef --- /dev/null +++ b/frontend/src/context/WhatsApp/WhatsAppsContext.js @@ -0,0 +1,17 @@ +import React, { createContext } from "react"; + +import useWhatsApps from "./useWhatsApps"; + +const WhatsAppsContext = createContext(); + +const WhatsAppsProvider = ({ children }) => { + const { loading, whatsApps } = useWhatsApps(); + + return ( + + {children} + + ); +}; + +export { WhatsAppsContext, WhatsAppsProvider }; diff --git a/frontend/src/context/WhatsApp/useWhatsApps.js b/frontend/src/context/WhatsApp/useWhatsApps.js new file mode 100644 index 0000000..4a81951 --- /dev/null +++ b/frontend/src/context/WhatsApp/useWhatsApps.js @@ -0,0 +1,114 @@ +import { useState, useEffect, useReducer } from "react"; +import { toast } from "react-toastify"; +import openSocket from "socket.io-client"; + +import api from "../../services/api"; +import { i18n } from "../../translate/i18n"; + +const reducer = (state, action) => { + if (action.type === "LOAD_WHATSAPPS") { + const whatsApps = action.payload; + + return [...whatsApps]; + } + + if (action.type === "UPDATE_WHATSAPPS") { + const whatsApp = action.payload; + const whatsAppIndex = state.findIndex(s => s.id === whatsApp.id); + + if (whatsAppIndex !== -1) { + state[whatsAppIndex] = whatsApp; + return [...state]; + } else { + return [whatsApp, ...state]; + } + } + + if (action.type === "UPDATE_SESSION") { + const whatsApp = action.payload; + const whatsAppIndex = state.findIndex(s => s.id === whatsApp.id); + + if (whatsAppIndex !== -1) { + state[whatsAppIndex].status = whatsApp.status; + state[whatsAppIndex].updatedAt = whatsApp.updatedAt; + state[whatsAppIndex].qrcode = whatsApp.qrcode; + state[whatsAppIndex].retries = whatsApp.retries; + return [...state]; + } else { + return [...state]; + } + } + + if (action.type === "DELETE_WHATSAPPS") { + const whatsAppId = action.payload; + + const whatsAppIndex = state.findIndex(s => s.id === whatsAppId); + if (whatsAppIndex !== -1) { + state.splice(whatsAppIndex, 1); + } + return [...state]; + } + + if (action.type === "RESET") { + return []; + } +}; + +const useWhatsApps = () => { + const [whatsApps, dispatch] = useReducer(reducer, []); + const [loading, setLoading] = useState(true); + + useEffect(() => { + setLoading(true); + const fetchSession = async () => { + try { + const { data } = await api.get("/whatsapp/"); + dispatch({ type: "LOAD_WHATSAPPS", payload: data }); + setLoading(false); + } catch (err) { + setLoading(false); + const errorMsg = err.response?.data?.error; + if (errorMsg) { + if (i18n.exists(`backendErrors.${errorMsg}`)) { + toast.error(i18n.t(`backendErrors.${errorMsg}`)); + } else { + toast.error(err.response.data.error); + } + } else { + toast.error("Unknown error"); + } + } + }; + fetchSession(); + }, []); + + useEffect(() => { + const socket = openSocket(process.env.REACT_APP_BACKEND_URL); + + socket.on("whatsapp", data => { + if (data.action === "update") { + dispatch({ type: "UPDATE_WHATSAPPS", payload: data.whatsapp }); + } + }); + + socket.on("whatsapp", data => { + if (data.action === "delete") { + dispatch({ type: "DELETE_WHATSAPPS", payload: data.whatsappId }); + } + }); + + socket.on("whatsappSession", data => { + if (data.action === "update") { + dispatch({ type: "UPDATE_SESSION", payload: data.session }); + } + }); + + return () => { + socket.disconnect(); + }; + }, []); + + return { whatsApps, loading }; +}; + +export default useWhatsApps; diff --git a/frontend/src/pages/Connections/index.js b/frontend/src/pages/Connections/index.js index ffbd0f9..fc0004e 100644 --- a/frontend/src/pages/Connections/index.js +++ b/frontend/src/pages/Connections/index.js @@ -1,5 +1,4 @@ -import React, { useState, useEffect, useReducer, useCallback } from "react"; -import openSocket from "socket.io-client"; +import React, { useState, useCallback, useContext } from "react"; import { toast } from "react-toastify"; import { format, parseISO } from "date-fns"; @@ -16,13 +15,13 @@ import { Paper, Tooltip, Typography, + CircularProgress, } from "@material-ui/core"; import { Edit, CheckCircle, SignalCellularConnectedNoInternet2Bar, SignalCellularConnectedNoInternet0Bar, - Schedule, SignalCellular4Bar, CropFree, DeleteOutline, @@ -37,58 +36,9 @@ import TableRowSkeleton from "../../components/TableRowSkeleton"; import api from "../../services/api"; import WhatsAppModal from "../../components/WhatsAppModal"; import ConfirmationModal from "../../components/ConfirmationModal"; -import ButtonWithSpinner from "../../components/ButtonWithSpinner"; import QrcodeModal from "../../components/QrcodeModal"; import { i18n } from "../../translate/i18n"; - -const reducer = (state, action) => { - if (action.type === "LOAD_WHATSAPPS") { - const whatsApps = action.payload; - - return [...whatsApps]; - } - - if (action.type === "UPDATE_WHATSAPPS") { - const whatsApp = action.payload; - const whatsAppIndex = state.findIndex(s => s.id === whatsApp.id); - - if (whatsAppIndex !== -1) { - state[whatsAppIndex] = whatsApp; - return [...state]; - } else { - return [whatsApp, ...state]; - } - } - - if (action.type === "UPDATE_SESSION") { - const whatsApp = action.payload; - const whatsAppIndex = state.findIndex(s => s.id === whatsApp.id); - - if (whatsAppIndex !== -1) { - state[whatsAppIndex].status = whatsApp.status; - state[whatsAppIndex].updatedAt = whatsApp.updatedAt; - state[whatsAppIndex].qrcode = whatsApp.qrcode; - state[whatsAppIndex].retries = whatsApp.retries; - return [...state]; - } else { - return [...state]; - } - } - - if (action.type === "DELETE_WHATSAPPS") { - const whatsAppId = action.payload; - - const whatsAppIndex = state.findIndex(s => s.id === whatsAppId); - if (whatsAppIndex !== -1) { - state.splice(whatsAppIndex, 1); - } - return [...state]; - } - - if (action.type === "RESET") { - return []; - } -}; +import { WhatsAppsContext } from "../../context/WhatsApp/WhatsAppsContext"; const useStyles = makeStyles(theme => ({ mainPaper: { @@ -112,6 +62,9 @@ const useStyles = makeStyles(theme => ({ tooltipPopper: { textAlign: "center", }, + buttonProgress: { + color: green[500], + }, })); const CustomToolTip = ({ title, content, children }) => { @@ -141,12 +94,10 @@ const CustomToolTip = ({ title, content, children }) => { const Connections = () => { const classes = useStyles(); - const [whatsApps, dispatch] = useReducer(reducer, []); - + const { whatsApps, loading } = useContext(WhatsAppsContext); const [whatsAppModalOpen, setWhatsAppModalOpen] = useState(false); const [qrModalOpen, setQrModalOpen] = useState(false); const [selectedWhatsApp, setSelectedWhatsApp] = useState(null); - const [loading, setLoading] = useState(true); const [confirmModalOpen, setConfirmModalOpen] = useState(false); const confirmationModalInitialState = { action: "", @@ -159,63 +110,19 @@ const Connections = () => { confirmationModalInitialState ); - useEffect(() => { - setLoading(true); - const fetchSession = async () => { - try { - const { data } = await api.get("/whatsapp/"); - dispatch({ type: "LOAD_WHATSAPPS", payload: data }); - setLoading(false); - } catch (err) { - setLoading(false); - const errorMsg = err.response?.data?.error; - if (errorMsg) { - if (i18n.exists(`backendErrors.${errorMsg}`)) { - toast.error(i18n.t(`backendErrors.${errorMsg}`)); - } else { - toast.error(err.response.data.error); - } - } else { - toast.error("Unknown error"); - } - } - }; - fetchSession(); - }, []); - - useEffect(() => { - const socket = openSocket(process.env.REACT_APP_BACKEND_URL); - - socket.on("whatsapp", data => { - if (data.action === "update") { - dispatch({ type: "UPDATE_WHATSAPPS", payload: data.whatsapp }); - } - }); - - socket.on("whatsapp", data => { - if (data.action === "delete") { - dispatch({ type: "DELETE_WHATSAPPS", payload: data.whatsappId }); - } - }); - - socket.on("whatsappSession", data => { - if (data.action === "update") { - dispatch({ type: "UPDATE_SESSION", payload: data.session }); - } - }); - - return () => { - socket.disconnect(); - }; - }, []); - const handleStartWhatsAppSession = async whatsAppId => { try { await api.post(`/whatsappsession/${whatsAppId}`); } catch (err) { - console.log(err); - if (err.response && err.response.data && err.response.data.error) { - toast.error(err.response.data.error); + const errorMsg = err.response?.data?.error; + if (errorMsg) { + if (i18n.exists(`backendErrors.${errorMsg}`)) { + toast.error(i18n.t(`backendErrors.${errorMsg}`)); + } else { + toast.error(err.response.data.error); + } + } else { + toast.error("Unknown error"); } } }; @@ -224,9 +131,15 @@ const Connections = () => { try { await api.put(`/whatsappsession/${whatsAppId}`); } catch (err) { - console.log(err); - if (err.response && err.response.data && err.response.data.error) { - toast.error(err.response.data.error); + const errorMsg = err.response?.data?.error; + if (errorMsg) { + if (i18n.exists(`backendErrors.${errorMsg}`)) { + toast.error(i18n.t(`backendErrors.${errorMsg}`)); + } else { + toast.error(err.response.data.error); + } + } else { + toast.error("Unknown error"); } } }; @@ -256,108 +169,6 @@ const Connections = () => { setWhatsAppModalOpen(true); }; - const renderActionButtons = whatsApp => { - return ( - <> - {whatsApp.status === "qrcode" && ( - - )} - {whatsApp.status === "DISCONNECTED" && ( - <> - {" "} - - - )} - {(whatsApp.status === "CONNECTED" || - whatsApp.status === "PAIRING" || - whatsApp.status === "TIMEOUT") && ( - - )} - {whatsApp.status === "OPENING" && ( - - {i18n.t("connections.buttons.connecting")} - - )} - - ); - }; - - const renderStatusToolTips = whatsApp => { - return ( -
- {whatsApp.status === "DISCONNECTED" && ( - - - - )} - {whatsApp.status === "OPENING" && ( - - - - )} - {whatsApp.status === "qrcode" && ( - - - - )} - {whatsApp.status === "CONNECTED" && ( - - - - )} - {(whatsApp.status === "TIMEOUT" || whatsApp.status === "PAIRING") && ( - - - - )} -
- ); - }; - const handleOpenConfirmationModal = (action, whatsAppId) => { if (action === "disconnect") { setConfirmModalInfo({ @@ -418,6 +229,101 @@ const Connections = () => { setConfirmModalInfo(confirmationModalInitialState); }; + const renderActionButtons = whatsApp => { + return ( + <> + {whatsApp.status === "qrcode" && ( + + )} + {whatsApp.status === "DISCONNECTED" && ( + <> + {" "} + + + )} + {(whatsApp.status === "CONNECTED" || + whatsApp.status === "PAIRING" || + whatsApp.status === "TIMEOUT") && ( + + )} + {whatsApp.status === "OPENING" && ( + + )} + + ); + }; + + const renderStatusToolTips = whatsApp => { + return ( +
+ {whatsApp.status === "DISCONNECTED" && ( + + + + )} + {whatsApp.status === "OPENING" && ( + + )} + {whatsApp.status === "qrcode" && ( + + + + )} + {whatsApp.status === "CONNECTED" && ( + + + + )} + {(whatsApp.status === "TIMEOUT" || whatsApp.status === "PAIRING") && ( + + + + )} +
+ ); + }; + return ( { @@ -21,24 +22,26 @@ const Routes = () => { - - - - - - - - + + + + + + + + + + diff --git a/frontend/src/translate/languages/en.js b/frontend/src/translate/languages/en.js index 0946fdc..18f8f17 100644 --- a/frontend/src/translate/languages/en.js +++ b/frontend/src/translate/languages/en.js @@ -65,9 +65,6 @@ const messages = { content: "Make sure your cell phone is connected to the internet and try again, or request a new QR Code", }, - opening: { - title: "Starting session...", - }, qrcode: { title: "Waiting for QR Code read", content: diff --git a/frontend/src/translate/languages/es.js b/frontend/src/translate/languages/es.js index d05d55e..a487c63 100644 --- a/frontend/src/translate/languages/es.js +++ b/frontend/src/translate/languages/es.js @@ -67,9 +67,6 @@ const messages = { content: "Asegúrese de que su teléfono celular esté conectado a Internet y vuelva a intentarlo o solicite un nuevo código QR", }, - opening: { - title: "Iniciando sesión ...", - }, qrcode: { title: "Esperando la lectura del código QR", content: diff --git a/frontend/src/translate/languages/pt.js b/frontend/src/translate/languages/pt.js index 3e4bbd3..3f17fed 100644 --- a/frontend/src/translate/languages/pt.js +++ b/frontend/src/translate/languages/pt.js @@ -66,9 +66,6 @@ const messages = { content: "Certifique-se de que seu celular esteja conectado à internet e tente novamente, ou solicite um novo QR Code", }, - opening: { - title: "Iniciando a sessão...", - }, qrcode: { title: "Esperando leitura do QR Code", content: