Criação do Load Balance para usuarios que estejam na mesma fila. Criação de solução de para reaproveitar sessão

pull/1/head
adriano 2022-06-27 03:21:04 -03:00
parent 3d69fa84ef
commit c5c13f2b34
16 changed files with 367 additions and 64 deletions

View File

@ -25,6 +25,7 @@
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"express": "^4.17.1", "express": "^4.17.1",
"express-async-errors": "^3.1.1", "express-async-errors": "^3.1.1",
"fs-extra": "^10.1.0",
"http-graceful-shutdown": "^2.3.2", "http-graceful-shutdown": "^2.3.2",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"multer": "^1.4.2", "multer": "^1.4.2",

View File

@ -5,6 +5,8 @@ import ShowWhatsAppService from "../services/WhatsappService/ShowWhatsAppService
import { StartWhatsAppSession } from "../services/WbotServices/StartWhatsAppSession"; import { StartWhatsAppSession } from "../services/WbotServices/StartWhatsAppSession";
import UpdateWhatsAppService from "../services/WhatsappService/UpdateWhatsAppService"; import UpdateWhatsAppService from "../services/WhatsappService/UpdateWhatsAppService";
import { restartWhatsSession } from "../helpers/RestartWhatsSession";
import path from 'path'; import path from 'path';
// import { WWebJsw } from "../../WWebJS/session-bd_40" // import { WWebJsw } from "../../WWebJS/session-bd_40"
@ -31,6 +33,24 @@ const update = async (req: Request, res: Response): Promise<Response> => {
return res.status(200).json({ message: "Starting session." }); return res.status(200).json({ message: "Starting session." });
}; };
const restart = async (req: Request, res: Response): Promise<Response> => {
const { whatsappId } = req.params;
console.log('FROM REQUEST WHATSAPP ID: ', whatsappId)
const whatsapp = await ShowWhatsAppService(whatsappId);
await restartWhatsSession(whatsapp)
return res.status(200).json({ message: "Starting session." });
};
const remove = async (req: Request, res: Response): Promise<Response> => { const remove = async (req: Request, res: Response): Promise<Response> => {
const { whatsappId } = req.params; const { whatsappId } = req.params;
const whatsapp = await ShowWhatsAppService(whatsappId); const whatsapp = await ShowWhatsAppService(whatsappId);
@ -44,9 +64,9 @@ const remove = async (req: Request, res: Response): Promise<Response> => {
//removeDir(path.join(process.cwd(),'.wwebjs_auth', `session-bd_${whatsapp.id}`)) //removeDir(path.join(process.cwd(),'.wwebjs_auth', `session-bd_${whatsapp.id}`))
wbot.logout(); await wbot.logout();
return res.status(200).json({ message: "Session disconnected." }); return res.status(200).json({ message: "Session disconnected." });
}; };
export default { store, remove, update }; export default { store, remove, update, restart };

View File

@ -0,0 +1,15 @@
const fsPromises = require("fs/promises");
const fs = require('fs-extra')
// Delete a directory and its children
export const copyFolder = (sourcePath: string, destPath: string) => {
fs.copySync(sourcePath, destPath, { overwrite: true }, (err: any) => {
if (err) {
console.error(err);
} else {
console.log("Copy dir success!");
}
});
}

View File

@ -7,7 +7,7 @@ export const removeDir = async (dirPath:string) => {
if (fs.existsSync(dirPath)){ if (fs.existsSync(dirPath)){
try { try {
await fsPromises.rm(dirPath, { recursive: true }); await fsPromises.rm(dirPath, { recursive: true, force: true});
console.log("Directory removed!"); console.log("Directory removed!");
} }
catch (err) { catch (err) {

View File

@ -8,7 +8,11 @@ import { Op, where } from "sequelize";
import wbotByUserQueue from '../helpers/GetWbotByUserQueue' import wbotByUserQueue from '../helpers/GetWbotByUserQueue'
const GetDefaultWhatsApp = async (userId?: string | number ): Promise<Whatsapp> => { // import WhatsQueueIndex from "./WhatsQueueIndex";
import { WhatsIndex } from "./LoadBalanceWhatsSameQueue";
const GetDefaultWhatsApp = async (userId?: string | number): Promise<Whatsapp> => {
// test del // test del
let defaultWhatsapp = await Whatsapp.findOne({ let defaultWhatsapp = await Whatsapp.findOne({
@ -17,24 +21,32 @@ const GetDefaultWhatsApp = async (userId?: string | number ): Promise<Whatsapp>
if (!defaultWhatsapp) { if (!defaultWhatsapp) {
if(userId){
if (userId) {
let whatsapps = await wbotByUserQueue(userId) let whatsapps = await wbotByUserQueue(userId)
if(whatsapps.length > 0){ if (whatsapps.length > 0) {
defaultWhatsapp = whatsapps[0] if (whatsapps.length > 1) {
defaultWhatsapp = whatsapps[+WhatsIndex(whatsapps)]
}
else {
defaultWhatsapp = whatsapps[0]
}
}// Quando o usuário não está em nenhuma fila }// Quando o usuário não está em nenhuma fila
else{ else {
defaultWhatsapp = await Whatsapp.findOne({ where: { status: 'CONNECTED' } }); defaultWhatsapp = await Whatsapp.findOne({ where: { status: 'CONNECTED' } });
} }
} }
else{ else {
defaultWhatsapp = await Whatsapp.findOne({ where: { status: 'CONNECTED' } }); defaultWhatsapp = await Whatsapp.findOne({ where: { status: 'CONNECTED' } });
} }
@ -49,7 +61,7 @@ const GetDefaultWhatsApp = async (userId?: string | number ): Promise<Whatsapp>
// const defaultWhatsapp = await Whatsapp.findOne({ // const defaultWhatsapp = await Whatsapp.findOne({
// where: { isDefault: true } // where: { isDefault: true }
// }); // });

View File

@ -0,0 +1,16 @@
export const isPositiveInteger = (index: string ) => {
if (typeof index !== 'string') {
return false;
}
const num = Number(index);
if (Number.isInteger(num) && num >= 0) {
return true;
}
return false;
}

View File

@ -0,0 +1,25 @@
const fsPromises = require("fs/promises");
const fs = require('fs')
import WhatsQueueIndex from "./WhatsQueueIndex";
// Delete a directory and its children
export const WhatsIndex = (whatsapps: Object[]) => {
let index: number = 0
if (WhatsQueueIndex.getIndex() >= whatsapps.length) {
WhatsQueueIndex.setIndex(0)
}
// console.log('WhatsQueueIndex.getIndex(): ', WhatsQueueIndex.getIndex())
index = +WhatsQueueIndex.getIndex()
WhatsQueueIndex.setIndex(+WhatsQueueIndex.getIndex() + 1)
console.log('WhatsQueue Index: ', index)
return index
}

View File

@ -0,0 +1,39 @@
import path from "path";
import { number } from "yup";
import { removeWbot } from "../libs/wbot";
import Whatsapp from "../models/Whatsapp";
import { StartWhatsAppSession } from "../services/WbotServices/StartWhatsAppSession";
import { copyFolder } from "./CopyFolder";
import { removeDir } from "./DeleteDirectory";
const fsPromises = require("fs/promises");
const fs = require('fs')
// Restart session
export const restartWhatsSession = async (whatsapp: Whatsapp) => {
console.log('RESTARTING THE whatsapp.id: ', whatsapp.id)
const sourcePath = path.join(__dirname, `../../.wwebjs_auth/sessions/`, `session-bd_${whatsapp.id}`)
const destPath = path.join(__dirname, `../../.wwebjs_auth/`, `session-bd_${whatsapp.id}`)
console.log('================sourcePath: ', sourcePath)
console.log('================destPath: ', destPath)
removeWbot(whatsapp.id)
await removeDir(destPath)
if (fs.existsSync(sourcePath)) {
// copy the good session for restars the new session
copyFolder(sourcePath, destPath)
}
else {
console.log('Directory not found to copy: ', sourcePath)
}
console.log('RESTARTING SESSION...')
await StartWhatsAppSession(whatsapp);
}

View File

@ -0,0 +1,16 @@
class WhatsQueueIndex {
static staticIndex:Number = 0;
static setIndex(index:Number){
this.staticIndex = index
}
static getIndex(){
return this.staticIndex
}
}
export default WhatsQueueIndex;

View File

@ -7,12 +7,21 @@ import { logger } from "../utils/logger";
import { handleMessage } from "../services/WbotServices/wbotMessageListener"; import { handleMessage } from "../services/WbotServices/wbotMessageListener";
const fs = require('fs') const fs = require('fs')
import { copyFolder } from "../helpers/CopyFolder";
import path from "path";
import { number } from "yup";
interface Session extends Client { interface Session extends Client {
id?: number; id?: number;
} }
const sessions: Session[] = []; const sessions: Session[] = [];
let backupSession: any[] = []
const syncUnreadMessages = async (wbot: Session) => { const syncUnreadMessages = async (wbot: Session) => {
const chats = await wbot.getChats(); const chats = await wbot.getChats();
@ -49,7 +58,8 @@ export const initWbot = async (whatsapp: Whatsapp): Promise<Session> => {
//NOVA OPÇÃO MD //NOVA OPÇÃO MD
const wbot: Session = new Client({session: sessionCfg, authStrategy: new LocalAuth({clientId: 'bd_'+whatsapp.id}), const wbot: Session = new Client({
session: sessionCfg, authStrategy: new LocalAuth({ clientId: 'bd_' + whatsapp.id }),
puppeteer: { args: ['--no-sandbox', '--disable-setuid-sandbox'], executablePath: process.env.CHROME_BIN || undefined }, puppeteer: { args: ['--no-sandbox', '--disable-setuid-sandbox'], executablePath: process.env.CHROME_BIN || undefined },
}); });
@ -75,7 +85,12 @@ export const initWbot = async (whatsapp: Whatsapp): Promise<Session> => {
wbot.on("qr", async qr => { wbot.on("qr", async qr => {
console.log('************** whatsapp.id: ',whatsapp.id)
if (!backupSession.includes(whatsapp.id)) {
backupSession.push(whatsapp.id)
}
console.log('************** QRCODE whatsapp.id: ', whatsapp.id, ' | backupSession: ', backupSession)
logger.info("Session:", sessionName); logger.info("Session:", sessionName);
qrCode.generate(qr, { small: true }); qrCode.generate(qr, { small: true });
@ -95,9 +110,7 @@ export const initWbot = async (whatsapp: Whatsapp): Promise<Session> => {
wbot.on("authenticated", async session => { wbot.on("authenticated", async session => {
logger.info(`Session: ${sessionName} AUTHENTICATED`); logger.info(`Session: ${sessionName} AUTHENTICATED`);
await whatsapp.update({
session: JSON.stringify(session)
});
}); });
wbot.on("auth_failure", async msg => { wbot.on("auth_failure", async msg => {
@ -141,6 +154,7 @@ export const initWbot = async (whatsapp: Whatsapp): Promise<Session> => {
const sessionIndex = sessions.findIndex(s => s.id === whatsapp.id); const sessionIndex = sessions.findIndex(s => s.id === whatsapp.id);
if (sessionIndex === -1) { if (sessionIndex === -1) {
console.log('WBOT ADD ID: ', whatsapp.id)
wbot.id = whatsapp.id; wbot.id = whatsapp.id;
sessions.push(wbot); sessions.push(wbot);
} }
@ -149,6 +163,44 @@ export const initWbot = async (whatsapp: Whatsapp): Promise<Session> => {
await syncUnreadMessages(wbot); await syncUnreadMessages(wbot);
resolve(wbot); resolve(wbot);
console.log(`>>>>>>>>>>>>>>>>>>>>>>>>>.. BACKUP SESSION whatsapp.id ${whatsapp.id} | backupSession: ${backupSession}`)
const whatsIndex = backupSession.findIndex((id:number) => id === +whatsapp.id);
console.log(' whatsIndex: ', whatsIndex)
if (whatsIndex !== -1) {
backupSession.splice(whatsIndex, 1);
setTimeout(async () => {
const sourcePath = path.join(__dirname, `../../.wwebjs_auth/`, `session-bd_${whatsapp.id}`)
const destPath = path.join(__dirname, `../../.wwebjs_auth/sessions`, `session-bd_${whatsapp.id}`)
if (fs.existsSync(path.join(__dirname, `../../.wwebjs_auth/sessions`))) {
// copy the good session for backup dir
copyFolder(sourcePath, destPath)
}
else {
console.log('Directory not found to copy: ', destPath)
}
console.log(` COPIOU backup whatsapp.id ---------------------------------->${whatsapp.id}`)
}, 30000);
console.log(' PASSOU NO TIMEOUT!')
}
}); });
} catch (err) { } catch (err) {
logger.error(`${err}`); logger.error(`${err}`);
@ -169,6 +221,7 @@ export const removeWbot = (whatsappId: number): void => {
try { try {
const sessionIndex = sessions.findIndex(s => s.id === whatsappId); const sessionIndex = sessions.findIndex(s => s.id === whatsappId);
if (sessionIndex !== -1) { if (sessionIndex !== -1) {
console.log('WBOT REMOVED ID: ', whatsappId)
sessions[sessionIndex].destroy(); sessions[sessionIndex].destroy();
sessions.splice(sessionIndex, 1); sessions.splice(sessionIndex, 1);
} }

View File

@ -25,4 +25,10 @@ whatsappSessionRoutes.delete(
WhatsAppSessionController.remove WhatsAppSessionController.remove
); );
whatsappSessionRoutes.post(
"/restartwhatsappsession/:whatsappId",
isAuth,
WhatsAppSessionController.restart
);
export default whatsappSessionRoutes; export default whatsappSessionRoutes;

View File

@ -70,15 +70,15 @@ const ListTicketsService = async ({
status status
}; };
if (unlimited) { // if (unlimited) {
whereCondition = { // whereCondition = {
...whereCondition, // ...whereCondition,
createdAt: { // createdAt: {
[Op.gte]: dateToday.fullDate + ' 00:00:00.000000', // [Op.gte]: dateToday.fullDate + ' 00:00:00.000000',
[Op.lte]: dateToday.fullDate + ' 23:59:59.999999' // [Op.lte]: dateToday.fullDate + ' 23:59:59.999999'
} // }
} // }
} // }
} }

View File

@ -9,6 +9,7 @@ import Ticket from "../../models/Ticket";
import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService"; import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService";
import wbotByUserQueue from '../../helpers/GetWbotByUserQueue' import wbotByUserQueue from '../../helpers/GetWbotByUserQueue'
import { WhatsIndex } from "../../helpers/LoadBalanceWhatsSameQueue";
interface Request { interface Request {
@ -31,13 +32,22 @@ const SendWhatsAppMessage = async ({
const whatsapp = await ShowWhatsAppService(ticket.whatsappId); const whatsapp = await ShowWhatsAppService(ticket.whatsappId);
if(whatsapp.status!='CONNECTED'){ if (whatsapp.status != 'CONNECTED') {
let whatsapps = await wbotByUserQueue(ticket.userId) let whatsapps = await wbotByUserQueue(ticket.userId)
if(whatsapps.length > 0){ if (whatsapps.length > 0) {
await ticket.update({ whatsappId: whatsapps[0].id }); if (whatsapps.length > 1) {
await ticket.update({ whatsappId: whatsapps[+WhatsIndex(whatsapps)].id });
}
else {
await ticket.update({ whatsappId: whatsapps[0].id });
}
} }

View File

@ -5,9 +5,7 @@ import { getIO } from "../../libs/socket";
import wbotMonitor from "./wbotMonitor"; import wbotMonitor from "./wbotMonitor";
import { logger } from "../../utils/logger"; import { logger } from "../../utils/logger";
export const StartWhatsAppSession = async ( export const StartWhatsAppSession = async (whatsapp: Whatsapp): Promise<void> => {
whatsapp: Whatsapp
): Promise<void> => {
await whatsapp.update({ status: "OPENING" }); await whatsapp.update({ status: "OPENING" });
const io = getIO(); const io = getIO();

View File

@ -3,6 +3,10 @@ import { promisify } from "util";
import { writeFile } from "fs"; import { writeFile } from "fs";
import * as Sentry from "@sentry/node"; import * as Sentry from "@sentry/node";
import { copyFolder } from "../../helpers/CopyFolder";
import { removeDir } from "../../helpers/DeleteDirectory";
import path from 'path';
import { import {
Contact as WbotContact, Contact as WbotContact,
Message as WbotMessage, Message as WbotMessage,
@ -31,6 +35,10 @@ import Queue from "../../models/Queue";
import fs from 'fs'; import fs from 'fs';
import { StartWhatsAppSession } from "../../services/WbotServices/StartWhatsAppSession";
import { removeWbot } from '../../libs/wbot'
import { restartWhatsSession } from "../../helpers/RestartWhatsSession";
// test del // test del
import data_ura from './ura' import data_ura from './ura'
import msg_client_transfer from './ura_msg_transfer' import msg_client_transfer from './ura_msg_transfer'
@ -733,9 +741,32 @@ const handleMessage = async (
// //
// test del
// console.log('WBOT.id: ',wbot.id)
// const sourcePath = path.join(__dirname,`../../../.wwebjs_auth/sessions/`, `session-bd_${wbot.id}`)
// const destPath = path.join(__dirname,`../../../.wwebjs_auth/`, `session-bd_${wbot.id}`)
// console.log('================sourcePath: ', sourcePath)
// console.log('================sourcePath: ', destPath)
// removeWbot(33)
// await removeDir(destPath)
// copyFolder(sourcePath, destPath)
// await StartWhatsAppSession(whatsapp);
// console.log('RESTARTING SESSION...')
//
} catch (err) { } catch (err) {
Sentry.captureException(err); Sentry.captureException(err);
logger.error(`Error handling whatsapp message: Err: ${err}`); logger.error(`Error handling whatsapp message: Err: ${err}`);
} }
}; };

View File

@ -112,6 +112,8 @@ const Connections = () => {
const [confirmModalOpen, setConfirmModalOpen] = useState(false); const [confirmModalOpen, setConfirmModalOpen] = useState(false);
const confirmationModalInitialState = { const confirmationModalInitialState = {
action: "", action: "",
title: "", title: "",
@ -131,6 +133,16 @@ const Connections = () => {
} }
}; };
const handleRestartWhatsAppSession = async whatsAppId => {
try {
await api.post(`/restartwhatsappsession/${whatsAppId}`);
} catch (err) {
toastError(err);
}
};
const handleRequestNewQrCode = async whatsAppId => { const handleRequestNewQrCode = async whatsAppId => {
try { try {
await api.put(`/whatsappsession/${whatsAppId}`); await api.put(`/whatsappsession/${whatsAppId}`);
@ -319,6 +331,7 @@ const Connections = () => {
perform="connections-view:show" perform="connections-view:show"
yes={() => ( yes={() => (
<MainContainer> <MainContainer>
<ConfirmationModal <ConfirmationModal
title={confirmModalInfo.title} title={confirmModalInfo.title}
open={confirmModalOpen} open={confirmModalOpen}
@ -327,21 +340,23 @@ const Connections = () => {
> >
{confirmModalInfo.message} {confirmModalInfo.message}
</ConfirmationModal> </ConfirmationModal>
<QrcodeModal <QrcodeModal
open={qrModalOpen} open={qrModalOpen}
onClose={handleCloseQrModal} onClose={handleCloseQrModal}
whatsAppId={!whatsAppModalOpen && selectedWhatsApp?.id} whatsAppId={!whatsAppModalOpen && selectedWhatsApp?.id}
/> />
<WhatsAppModal <WhatsAppModal
open={whatsAppModalOpen} open={whatsAppModalOpen}
onClose={handleCloseWhatsAppModal} onClose={handleCloseWhatsAppModal}
whatsAppId={!qrModalOpen && selectedWhatsApp?.id} whatsAppId={!qrModalOpen && selectedWhatsApp?.id}
/> />
<MainHeader> <MainHeader>
<Title>{i18n.t("connections.title")}</Title> <Title>{i18n.t("connections.title")}</Title>
<MainHeaderButtonsWrapper> <MainHeaderButtonsWrapper>
<Can <Can
role={user.profile} role={user.profile}
perform="btn-add-whatsapp" perform="btn-add-whatsapp"
@ -354,23 +369,23 @@ const Connections = () => {
</Button> </Button>
)} )}
/> />
</MainHeaderButtonsWrapper> </MainHeaderButtonsWrapper>
</MainHeader> </MainHeader>
<Paper className={classes.mainPaper} variant="outlined"> <Paper className={classes.mainPaper} variant="outlined">
<Table size="small"> <Table size="small">
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell align="center"> <TableCell align="center">
{i18n.t("connections.table.name")} {i18n.t("connections.table.name")}
</TableCell> </TableCell>
<TableCell align="center"> <TableCell align="center">
{i18n.t("connections.table.status")} {i18n.t("connections.table.status")}
</TableCell> </TableCell>
<Can <Can
role={user.profile} role={user.profile}
perform="connection-button:show" perform="connection-button:show"
@ -383,6 +398,20 @@ const Connections = () => {
<Can
role={user.profile}
perform="connection-button:show"
yes={() => (
<TableCell align="center">
Restart
</TableCell>
)}
/>
<TableCell align="center"> <TableCell align="center">
{i18n.t("connections.table.lastUpdate")} {i18n.t("connections.table.lastUpdate")}
</TableCell> </TableCell>
@ -403,6 +432,7 @@ const Connections = () => {
whatsApps.map(whatsApp => ( whatsApps.map(whatsApp => (
<TableRow key={whatsApp.id}> <TableRow key={whatsApp.id}>
<TableCell align="center">{whatsApp.name}</TableCell> <TableCell align="center">{whatsApp.name}</TableCell>
<TableCell align="center"> <TableCell align="center">
{renderStatusToolTips(whatsApp)} {renderStatusToolTips(whatsApp)}
</TableCell> </TableCell>
@ -419,9 +449,38 @@ const Connections = () => {
<Can
role={user.profile}
perform="connection-button:show"
yes={() => (
<TableCell align="center">
<Button
size="small"
variant="contained"
color="primary"
onClick={() => handleRestartWhatsAppSession(whatsApp.id)}
>
Restart
</Button>
</TableCell>
)}
/>
<TableCell align="center"> <TableCell align="center">
{format(parseISO(whatsApp.updatedAt), "dd/MM/yy HH:mm")} {format(parseISO(whatsApp.updatedAt), "dd/MM/yy HH:mm")}
</TableCell> </TableCell>
<TableCell align="center"> <TableCell align="center">
{whatsApp.isDefault && ( {whatsApp.isDefault && (
<div className={classes.customTableCell}> <div className={classes.customTableCell}>
@ -429,6 +488,8 @@ const Connections = () => {
</div> </div>
)} )}
</TableCell> </TableCell>
<TableCell align="center"> <TableCell align="center">
<Can <Can