diff --git a/backend/src/controllers/WhatsAppController.ts b/backend/src/controllers/WhatsAppController.ts index f08cbf5..531bfe7 100644 --- a/backend/src/controllers/WhatsAppController.ts +++ b/backend/src/controllers/WhatsAppController.ts @@ -2,8 +2,6 @@ import { Request, Response } from "express"; import { getIO } from "../libs/socket"; import { removeWbot } from "../libs/wbot"; import { StartWhatsAppSession } from "../services/WbotServices/StartWhatsAppSession"; -import wbotMessageListener from "../services/WbotServices/wbotMessageListener"; -import wbotMonitor from "../services/WbotServices/wbotMonitor"; import CreateWhatsAppService from "../services/WhatsappService/CreateWhatsAppService"; import DeleteWhatsAppService from "../services/WhatsappService/DeleteWhatsAppService"; diff --git a/backend/src/controllers/WhatsAppSessionController.ts b/backend/src/controllers/WhatsAppSessionController.ts index 46d3b6a..8a03645 100644 --- a/backend/src/controllers/WhatsAppSessionController.ts +++ b/backend/src/controllers/WhatsAppSessionController.ts @@ -1,10 +1,7 @@ import { Request, Response } from "express"; -// import Whatsapp from "../models/Whatsapp"; -// import { getIO } from "../libs/socket"; import { getWbot } from "../libs/wbot"; import ShowWhatsAppService from "../services/WhatsappService/ShowWhatsAppService"; import { StartWhatsAppSession } from "../services/WbotServices/StartWhatsAppSession"; -// import wbotMonitor from "../services/wbotMonitor"; const store = async (req: Request, res: Response): Promise => { const { whatsappId } = req.params; @@ -15,6 +12,17 @@ const store = async (req: Request, res: Response): Promise => { return res.status(200).json({ message: "Starting session." }); }; +const update = async (req: Request, res: Response): Promise => { + const { whatsappId } = req.params; + const whatsapp = await ShowWhatsAppService(whatsappId); + + await whatsapp.update({ session: "" }); + + StartWhatsAppSession(whatsapp); + + return res.status(200).json({ message: "Starting session." }); +}; + const remove = async (req: Request, res: Response): Promise => { const { whatsappId } = req.params; const whatsapp = await ShowWhatsAppService(whatsappId); @@ -26,4 +34,4 @@ const remove = async (req: Request, res: Response): Promise => { return res.status(200).json({ message: "Session disconnected." }); }; -export default { store, remove }; +export default { store, remove, update }; diff --git a/backend/src/libs/wbot.ts b/backend/src/libs/wbot.ts index 25a145a..ce8ba81 100644 --- a/backend/src/libs/wbot.ts +++ b/backend/src/libs/wbot.ts @@ -3,7 +3,6 @@ import { Client } from "whatsapp-web.js"; import { getIO } from "./socket"; import Whatsapp from "../models/Whatsapp"; import AppError from "../errors/AppError"; -import { StartWhatsAppSession } from "../services/WbotServices/StartWhatsAppSession"; interface Session extends Client { id?: number; @@ -22,10 +21,10 @@ export const initWbot = async (whatsapp: Whatsapp): Promise => { sessionCfg = JSON.parse(whatsapp.session); } - const sessionIndex = sessions.findIndex(s => s.id === whatsapp.id); - if (sessionIndex !== -1) { - sessions[sessionIndex].destroy(); - sessions.splice(sessionIndex, 1); + const currentSessionIndex = sessions.findIndex(s => s.id === whatsapp.id); + if (currentSessionIndex !== -1) { + sessions[currentSessionIndex].destroy(); + sessions.splice(currentSessionIndex, 1); } const wbot: Session = new Client({ @@ -37,7 +36,13 @@ export const initWbot = async (whatsapp: Whatsapp): Promise => { wbot.on("qr", async qr => { console.log("Session:", sessionName); qrCode.generate(qr, { small: true }); - await whatsapp.update({ qrcode: qr, status: "qrcode" }); + await whatsapp.update({ qrcode: qr, status: "qrcode", retries: 0 }); + + const sessionIndex = sessions.findIndex(s => s.id === whatsapp.id); + if (sessionIndex === -1) { + wbot.id = whatsapp.id; + sessions.push(wbot); + } io.emit("whatsappSession", { action: "update", @@ -48,20 +53,14 @@ export const initWbot = async (whatsapp: Whatsapp): Promise => { wbot.on("authenticated", async session => { console.log("Session:", sessionName, "AUTHENTICATED"); await whatsapp.update({ - session: JSON.stringify(session), - status: "authenticated" - }); - - io.emit("whatsappSession", { - action: "update", - session: whatsapp + session: JSON.stringify(session) }); }); wbot.on("auth_failure", async msg => { console.error("Session:", sessionName, "AUTHENTICATION FAILURE", msg); - if (whatsapp.retries > 2) { + if (whatsapp.retries > 1) { await whatsapp.update({ session: "", retries: 0 }); } @@ -76,7 +75,6 @@ export const initWbot = async (whatsapp: Whatsapp): Promise => { session: whatsapp }); - StartWhatsAppSession(whatsapp); reject(new Error("Error starting whatsapp session.")); }); @@ -95,8 +93,13 @@ export const initWbot = async (whatsapp: Whatsapp): Promise => { }); wbot.sendPresenceAvailable(); - wbot.id = whatsapp.id; - sessions.push(wbot); + + const sessionIndex = sessions.findIndex(s => s.id === whatsapp.id); + if (sessionIndex === -1) { + wbot.id = whatsapp.id; + sessions.push(wbot); + } + resolve(wbot); }); } catch (err) { diff --git a/backend/src/routes/whatsappSessionRoutes.ts b/backend/src/routes/whatsappSessionRoutes.ts index fc94d73..731d847 100644 --- a/backend/src/routes/whatsappSessionRoutes.ts +++ b/backend/src/routes/whatsappSessionRoutes.ts @@ -11,6 +11,12 @@ whatsappSessionRoutes.post( WhatsAppSessionController.store ); +whatsappSessionRoutes.put( + "/whatsappsession/:whatsappId", + isAuth, + WhatsAppSessionController.update +); + whatsappSessionRoutes.delete( "/whatsappsession/:whatsappId", isAuth, diff --git a/backend/src/services/WbotServices/StartWhatsAppSession.ts b/backend/src/services/WbotServices/StartWhatsAppSession.ts index 4ea8fcc..b5f508e 100644 --- a/backend/src/services/WbotServices/StartWhatsAppSession.ts +++ b/backend/src/services/WbotServices/StartWhatsAppSession.ts @@ -1,11 +1,20 @@ import { initWbot } from "../../libs/wbot"; import Whatsapp from "../../models/Whatsapp"; import wbotMessageListener from "./wbotMessageListener"; +import { getIO } from "../../libs/socket"; import wbotMonitor from "./wbotMonitor"; export const StartWhatsAppSession = async ( whatsapp: Whatsapp ): Promise => { + await whatsapp.update({ status: "OPENING" }); + + const io = getIO(); + io.emit("whatsappSession", { + action: "update", + session: whatsapp + }); + try { const wbot = await initWbot(whatsapp); wbotMessageListener(wbot); diff --git a/backend/src/services/WbotServices/wbotMonitor.ts b/backend/src/services/WbotServices/wbotMonitor.ts index ce62117..8316d13 100644 --- a/backend/src/services/WbotServices/wbotMonitor.ts +++ b/backend/src/services/WbotServices/wbotMonitor.ts @@ -54,7 +54,7 @@ const wbotMonitor = async ( wbot.on("disconnected", async reason => { console.log("Disconnected session:", sessionName, reason); try { - await whatsapp.update({ status: "DISCONNECTED", session: "" }); + await whatsapp.update({ status: "OPENING", session: "" }); } catch (err) { Sentry.captureException(err); console.log(err); diff --git a/backend/src/services/WhatsappService/CreateWhatsAppService.ts b/backend/src/services/WhatsappService/CreateWhatsAppService.ts index 20f14d8..118343e 100644 --- a/backend/src/services/WhatsappService/CreateWhatsAppService.ts +++ b/backend/src/services/WhatsappService/CreateWhatsAppService.ts @@ -16,7 +16,7 @@ interface Response { const CreateWhatsAppService = async ({ name, - status = "INITIALIZING", + status = "OPENING", isDefault = false }: Request): Promise => { const schema = Yup.object().shape({ diff --git a/frontend/src/pages/Connections/index.js b/frontend/src/pages/Connections/index.js index b82a31d..ffbd0f9 100644 --- a/frontend/src/pages/Connections/index.js +++ b/frontend/src/pages/Connections/index.js @@ -14,8 +14,19 @@ import { Table, TableHead, Paper, + Tooltip, + Typography, } from "@material-ui/core"; -import { Edit, DeleteOutline, CheckCircle } from "@material-ui/icons"; +import { + Edit, + CheckCircle, + SignalCellularConnectedNoInternet2Bar, + SignalCellularConnectedNoInternet0Bar, + Schedule, + SignalCellular4Bar, + CropFree, + DeleteOutline, +} from "@material-ui/icons"; import MainContainer from "../../components/MainContainer"; import MainHeader from "../../components/MainHeader"; @@ -26,6 +37,7 @@ 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"; @@ -56,6 +68,7 @@ const reducer = (state, action) => { 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]; @@ -84,8 +97,47 @@ const useStyles = makeStyles(theme => ({ overflowY: "scroll", ...theme.scrollbarStyles, }, + customTableCell: { + display: "flex", + alignItems: "center", + justifyContent: "center", + }, + tooltip: { + backgroundColor: "#f5f5f9", + color: "rgba(0, 0, 0, 0.87)", + fontSize: theme.typography.pxToRem(14), + border: "1px solid #dadde9", + maxWidth: 450, + }, + tooltipPopper: { + textAlign: "center", + }, })); +const CustomToolTip = ({ title, content, children }) => { + const classes = useStyles(); + + return ( + + + {title} + + {content && {content}} + + } + > + {children} + + ); +}; + const Connections = () => { const classes = useStyles(); @@ -94,15 +146,28 @@ const Connections = () => { 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 [deletingWhatsApp, setDeletingWhatsApp] = useState(null); + const confirmationModalInitialState = { + action: "", + title: "", + message: "", + whatsAppId: "", + open: false, + }; + const [confirmModalInfo, setConfirmModalInfo] = useState( + 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}`)) { @@ -144,19 +209,27 @@ const Connections = () => { }; }, []); - // const handleDisconnectSession = async whatsAppId => { - // try { - // await api.put(`/whatsapp/whatsApp/${whatsAppId}`, { - // whatsApp: "", - // status: "pending", - // }); - // } catch (err) { - // console.log(err); - // if (err.response && err.response.data && err.response.data.error) { - // toast.error(err.response.data.error); - // } - // } - // }; + 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 handleRequestNewQrCode = async whatsAppId => { + 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 handleOpenWhatsAppModal = () => { setSelectedWhatsApp(null); @@ -183,51 +256,187 @@ const Connections = () => { setWhatsAppModalOpen(true); }; - const handleDeleteWhatsApp = async whatsAppId => { - try { - await api.delete(`/whatsapp/${whatsAppId}`); - toast.success(i18n.t("connections.toasts.deleted")); - } catch (err) { - const errorMsg = err.response?.data?.error; - if (errorMsg) { - if (i18n.exists(`backendErrors.${errorMsg}`)) { - toast.error(i18n.t(`backendErrors.${errorMsg}`)); + 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({ + action: action, + title: i18n.t("connections.confirmationModal.disconnectTitle"), + message: i18n.t("connections.confirmationModal.disconnectMessage"), + whatsAppId: whatsAppId, + }); + } + + if (action === "delete") { + setConfirmModalInfo({ + action: action, + title: i18n.t("connections.confirmationModal.deleteTitle"), + message: i18n.t("connections.confirmationModal.deleteMessage"), + whatsAppId: whatsAppId, + }); + } + setConfirmModalOpen(true); + }; + + const handleSubmitConfirmationModal = async () => { + if (confirmModalInfo.action === "disconnect") { + try { + await api.delete(`/whatsappsession/${confirmModalInfo.whatsAppId}`); + } catch (err) { + 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(err.response.data.error); + toast.error("Unknown error"); } - } else { - toast.error("Unknown error"); } } - setDeletingWhatsApp(null); + + if (confirmModalInfo.action === "delete") { + try { + await api.delete(`/whatsapp/${confirmModalInfo.whatsAppId}`); + toast.success(i18n.t("connections.toasts.deleted")); + } catch (err) { + 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"); + } + } + } + + setConfirmModalInfo(confirmationModalInitialState); }; return ( handleDeleteWhatsApp(deletingWhatsApp.id)} + onConfirm={handleSubmitConfirmationModal} > - {i18n.t("connections.confirmationModal.deleteMessage")} + {confirmModalInfo.message} {i18n.t("connections.title")} @@ -251,6 +460,9 @@ const Connections = () => { {i18n.t("connections.table.status")} + + {i18n.t("connections.table.session")} + {i18n.t("connections.table.lastUpdate")} @@ -263,35 +475,28 @@ const Connections = () => { - {false ? ( + {loading ? ( ) : ( <> - {whatsApps && - whatsApps.length > 0 && - whatsApps.map((whatsApp, index) => ( + {whatsApps?.length > 0 && + whatsApps.map(whatsApp => ( {whatsApp.name} - {whatsApp.status === "qrcode" ? ( - - ) : ( - whatsApp.status - )} + {renderStatusToolTips(whatsApp)} + + + {renderActionButtons(whatsApp)} {format(parseISO(whatsApp.updatedAt), "dd/MM/yy HH:mm")} {whatsApp.isDefault && ( - +
+ +
)}
@@ -305,8 +510,7 @@ const Connections = () => { { - setConfirmModalOpen(true); - setDeletingWhatsApp(whatsApp); + handleOpenConfirmationModal("delete", whatsApp.id); }} > diff --git a/frontend/src/translate/languages/en.js b/frontend/src/translate/languages/en.js index da0690b..09ea331 100644 --- a/frontend/src/translate/languages/en.js +++ b/frontend/src/translate/languages/en.js @@ -48,9 +48,40 @@ const messages = { confirmationModal: { deleteTitle: "Delete", deleteMessage: "Are you sure? It cannot be reverted.", + disconnectTitle: "Disconnect", + disconnectMessage: + "You will need to read QR Code again to start receiving messages.", }, buttons: { add: "Add WhatsApp", + disconnect: "Disconnect", + tryAgain: "Try Again", + qrcode: "QR CODE", + newQr: "New QR CODE", + connecting: "Connectiing", + }, + toolTips: { + disconnected: { + title: "Failed to start WhatsApp session", + 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: + "Click on 'QR CODE' button and read the QR Code with your cell phone to start session", + }, + connected: { + title: "Connection established", + }, + timeout: { + title: "Connection with cell phone has been lost", + content: + "Make sure your cell phone is connected to the internet and WhatsApp is open, or click on 'Disconnect' button to get a new QRcode", + }, }, table: { name: "Name", @@ -58,6 +89,7 @@ const messages = { lastUpdate: "Last Update", default: "Default", actions: "Actions", + session: "Session", }, }, whatsappModal: { diff --git a/frontend/src/translate/languages/es.js b/frontend/src/translate/languages/es.js index 2cc3dd2..ebca7e5 100644 --- a/frontend/src/translate/languages/es.js +++ b/frontend/src/translate/languages/es.js @@ -50,9 +50,40 @@ const messages = { confirmationModal: { deleteTitle: "Borrar", deleteMessage: "¿Estás seguro? Este proceso no puede ser revertido.", + disconnectTitle: "Desconectar", + disconnectMessage: + "Você precisará ler o QR code novamente para começar a receber mensagens.", }, buttons: { add: "Agrega WhatsApp", + disconnect: "Desconectar", + tryAgain: "Inténtalo de nuevo", + qrcode: "QR CODE", + newQr: "Nuevo QR CODE", + connecting: "Conectando", + }, + toolTips: { + disconnected: { + title: "No se pudo iniciar la sesión de WhatsApp", + 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: + "Haga clic en el botón 'CÓDIGO QR' y lea el Código QR con su teléfono celular para iniciar la sesión", + }, + connected: { + title: "Conexión establecida", + }, + timeout: { + title: "Se perdió la conexión con el teléfono celular", + content: + "Asegúrese de que su teléfono celular esté conectado a Internet y que WhatsApp esté abierto, o haga clic en el botón 'Desconectar' para obtener un nuevo código QR", + }, }, table: { name: "Nombre", @@ -60,6 +91,7 @@ const messages = { lastUpdate: "Última Actualización", default: "Por Defecto", actions: "Acciones", + session: "Sesión", }, }, whatsappModal: { diff --git a/frontend/src/translate/languages/pt.js b/frontend/src/translate/languages/pt.js index b92c905..f4bbc28 100644 --- a/frontend/src/translate/languages/pt.js +++ b/frontend/src/translate/languages/pt.js @@ -48,9 +48,40 @@ const messages = { confirmationModal: { deleteTitle: "Deletar", deleteMessage: "Você tem certeza? Essa ação não pode ser revertida.", + disconnectTitle: "Desconectar", + disconnectMessage: + "Você precisará ler o código QR novamente para começar a receber mensagens.", }, buttons: { add: "Adicionar WhatsApp", + disconnect: "desconectar", + tryAgain: "Tentar novamente", + qrcode: "QR CODE", + newQr: "Novo QR CODE", + connecting: "Conectando", + }, + toolTips: { + disconnected: { + title: "Falha ao iniciar sessão do WhatsApp", + 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: + "Clique no botão 'QR CODE' e leia o QR Code com o seu celular para iniciar a sessão", + }, + connected: { + title: "Conexão estabelecida!", + }, + timeout: { + title: "A conexão com o celular foi perdida", + content: + "Certifique-se de que seu celular esteja conectado à internet e o WhatsApp esteja aberto, ou clique no botão 'Desconectar' para obter um novo QR Code", + }, }, table: { name: "Nome", @@ -58,6 +89,7 @@ const messages = { lastUpdate: "Última atualização", default: "Padrão", actions: "Ações", + session: "Sessão", }, }, whatsappModal: {