feature/IO-3000-Migrate-MSG-to-Sockets - Add Conversation, merge master, packages

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-11-18 10:11:41 -08:00
parent 27de849be7
commit ae0bfad89a
7 changed files with 1283 additions and 2297 deletions

View File

@@ -76,6 +76,11 @@ const useSocket = (bodyshop) => {
dispatch({ type: "ADD_MESSAGE", payload: message }); dispatch({ type: "ADD_MESSAGE", payload: message });
}; };
const handleNewConversation = (data) => {
dispatch({ type: "ADD_CONVERSATION", payload: data.conversation });
dispatch({ type: "ADD_MESSAGE", payload: data.message });
};
const handleReadUpdated = ({ conversationId }) => { const handleReadUpdated = ({ conversationId }) => {
dispatch({ type: "UPDATE_UNREAD_COUNT", payload: conversationId }); dispatch({ type: "UPDATE_UNREAD_COUNT", payload: conversationId });
}; };
@@ -88,6 +93,7 @@ const useSocket = (bodyshop) => {
socketInstance.on("connect_error", handleConnectionError); socketInstance.on("connect_error", handleConnectionError);
socketInstance.on("disconnect", handleDisconnect); socketInstance.on("disconnect", handleDisconnect);
socketInstance.on("bodyshop-message", handleBodyshopMessage); socketInstance.on("bodyshop-message", handleBodyshopMessage);
socketInstance.on("new-conversation", handleNewConversation);
} }
} else { } else {
// User is not authenticated // User is not authenticated

View File

@@ -50,6 +50,12 @@ export const addMessage = (message) => ({
payload: message payload: message
}); });
// Add a Conversation to the list of conversations
export const addConversation = (conversation) => ({
type: "ADD_CONVERSATION",
payload: conversation
});
// Update unread count for a conversation (e.g., after marking messages as read) // Update unread count for a conversation (e.g., after marking messages as read)
export const updateUnreadCount = (conversationId) => ({ export const updateUnreadCount = (conversationId) => ({
type: MessagingActionTypes.UPDATE_UNREAD_COUNT, type: MessagingActionTypes.UPDATE_UNREAD_COUNT,

View File

@@ -71,18 +71,23 @@ const messagingReducer = (state = INITIAL_STATE, action) => {
messages: [...state.messages, action.payload] messages: [...state.messages, action.payload]
}; };
case MessagingActionTypes.UPDATE_UNREAD_COUNT: case MessagingActionTypes.ADD_CONVERSATION:
console.log("SKL");
console.dir({ action, state });
return { return {
...state, ...state,
conversations: Array.isArray(state.conversations) conversations: [...state.conversations, action.payload]
? state.conversations.map((conversation) => };
conversation.id === action.payload
? { ...conversation, unreadCount: 0 } // Reset unread count for the selected conversation case MessagingActionTypes.UPDATE_UNREAD_COUNT:
: conversation return {
) ...state,
: state.conversations, conversations: {
...state.conversations,
conversations: state.conversations.conversations.map((conversation) =>
conversation.id === action.payload
? { ...conversation, unreadcnt: 0 } // Reset unread count for the selected conversation
: conversation
)
},
unreadCount: Math.max(state.unreadCount - 1, 0) // Ensure unreadCount does not go below zero unreadCount: Math.max(state.unreadCount - 1, 0) // Ensure unreadCount does not go below zero
}; };

View File

@@ -8,6 +8,7 @@ const MessagingActionTypes = {
SET_MESSAGE: "SET_MESSAGE", SET_MESSAGE: "SET_MESSAGE",
ADD_MESSAGE: "ADD_MESSAGE", ADD_MESSAGE: "ADD_MESSAGE",
UPDATE_UNREAD_COUNT: "UPDATE_UNREAD_COUNT", UPDATE_UNREAD_COUNT: "UPDATE_UNREAD_COUNT",
SET_CONVERSATIONS: "SET_CONVERSATIONS" SET_CONVERSATIONS: "SET_CONVERSATIONS",
ADD_CONVERSATION: "ADD_CONVERSATION"
}; };
export default MessagingActionTypes; export default MessagingActionTypes;

3234
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -19,13 +19,13 @@
"makeitpretty": "prettier --write \"**/*.{css,js,json,jsx,scss}\"" "makeitpretty": "prettier --write \"**/*.{css,js,json,jsx,scss}\""
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-cloudwatch-logs": "^3.679.0", "@aws-sdk/client-cloudwatch-logs": "^3.693.0",
"@aws-sdk/client-elasticache": "^3.675.0", "@aws-sdk/client-elasticache": "^3.693.0",
"@aws-sdk/client-s3": "^3.689.0", "@aws-sdk/client-s3": "^3.693.0",
"@aws-sdk/client-secrets-manager": "^3.675.0", "@aws-sdk/client-secrets-manager": "^3.693.0",
"@aws-sdk/client-ses": "^3.675.0", "@aws-sdk/client-ses": "^3.693.0",
"@aws-sdk/credential-provider-node": "^3.675.0", "@aws-sdk/credential-provider-node": "^3.693.0",
"@opensearch-project/opensearch": "^2.12.0", "@opensearch-project/opensearch": "^2.13.0",
"@socket.io/admin-ui": "^0.5.1", "@socket.io/admin-ui": "^0.5.1",
"@socket.io/redis-adapter": "^8.3.0", "@socket.io/redis-adapter": "^8.3.0",
"aws4": "^1.13.2", "aws4": "^1.13.2",
@@ -34,20 +34,20 @@
"bluebird": "^3.7.2", "bluebird": "^3.7.2",
"body-parser": "^1.20.3", "body-parser": "^1.20.3",
"canvas": "^2.11.2", "canvas": "^2.11.2",
"chart.js": "^4.4.5", "chart.js": "^4.4.6",
"cloudinary": "^2.5.1", "cloudinary": "^2.5.1",
"compression": "^1.7.4", "compression": "^1.7.5",
"cookie-parser": "^1.4.7", "cookie-parser": "^1.4.7",
"cors": "2.8.5", "cors": "2.8.5",
"csrf": "^3.1.0", "csrf": "^3.1.0",
"dinero.js": "^1.9.1", "dinero.js": "^1.9.1",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"express": "^4.21.1", "express": "^4.21.1",
"firebase-admin": "^12.6.0", "firebase-admin": "^13.0.0",
"graphql": "^16.9.0", "graphql": "^16.9.0",
"graphql-request": "^6.1.0", "graphql-request": "^6.1.0",
"inline-css": "^4.0.2", "inline-css": "^4.0.2",
"intuit-oauth": "^4.1.2", "intuit-oauth": "^4.1.3",
"ioredis": "^5.4.1", "ioredis": "^5.4.1",
"json-2-csv": "^5.5.6", "json-2-csv": "^5.5.6",
"lodash": "^4.17.21", "lodash": "^4.17.21",
@@ -56,18 +56,18 @@
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"node-mailjet": "^6.0.6", "node-mailjet": "^6.0.6",
"node-persist": "^4.0.3", "node-persist": "^4.0.3",
"nodemailer": "^6.9.15", "nodemailer": "^6.9.16",
"phone": "^3.1.51", "phone": "^3.1.53",
"recursive-diff": "^1.0.9", "recursive-diff": "^1.0.9",
"redis": "^4.7.0", "redis": "^4.7.0",
"rimraf": "^6.0.1", "rimraf": "^6.0.1",
"soap": "^1.1.5", "soap": "^1.1.6",
"socket.io": "^4.8.0", "socket.io": "^4.8.1",
"socket.io-adapter": "^2.5.5", "socket.io-adapter": "^2.5.5",
"ssh2-sftp-client": "^10.0.3", "ssh2-sftp-client": "^10.0.3",
"twilio": "^4.23.0", "twilio": "^4.23.0",
"uuid": "^10.0.0", "uuid": "^10.0.0",
"winston": "^3.15.0", "winston": "^3.17.0",
"winston-cloudwatch": "^6.3.0", "winston-cloudwatch": "^6.3.0",
"xml2js": "^0.6.2", "xml2js": "^0.6.2",
"xmlbuilder2": "^3.1.1" "xmlbuilder2": "^3.1.1"

View File

@@ -11,8 +11,11 @@ const logger = require("../utils/logger");
const InstanceManager = require("../utils/instanceMgr").default; const InstanceManager = require("../utils/instanceMgr").default;
exports.receive = async (req, res) => { exports.receive = async (req, res) => {
//Perform request validation // Perform request validation
const { ioRedis } = req; const {
ioRedis,
ioHelpers: { getBodyshopRoom }
} = req;
logger.log("sms-inbound", "DEBUG", "api", null, { logger.log("sms-inbound", "DEBUG", "api", null, {
msid: req.body.SmsMessageSid, msid: req.body.SmsMessageSid,
@@ -21,7 +24,7 @@ exports.receive = async (req, res) => {
image_path: generateMediaArray(req.body) image_path: generateMediaArray(req.body)
}); });
if (!!!req.body || !!!req.body.MessagingServiceSid || !!!req.body.SmsMessageSid) { if (!req.body || !req.body.MessagingServiceSid || !req.body.SmsMessageSid) {
logger.log("sms-inbound-error", "ERROR", "api", null, { logger.log("sms-inbound-error", "ERROR", "api", null, {
msid: req.body.SmsMessageSid, msid: req.body.SmsMessageSid,
text: req.body.Body, text: req.body.Body,
@@ -29,175 +32,158 @@ exports.receive = async (req, res) => {
image_path: generateMediaArray(req.body), image_path: generateMediaArray(req.body),
type: "malformed-request" type: "malformed-request"
}); });
res.status(400); res.status(400).json({ success: false, error: "Malformed Request" });
res.json({ success: false, error: "Malformed Request" }); return;
} else { }
try {
const response = await client.request(queries.FIND_BODYSHOP_BY_MESSAGING_SERVICE_SID, {
mssid: req.body.MessagingServiceSid,
phone: phone(req.body.From).phoneNumber
});
let newMessage = { try {
msid: req.body.SmsMessageSid, const response = await client.request(queries.FIND_BODYSHOP_BY_MESSAGING_SERVICE_SID, {
text: req.body.Body, mssid: req.body.MessagingServiceSid,
image: !!req.body.MediaUrl0, phone: phone(req.body.From).phoneNumber
image_path: generateMediaArray(req.body) });
};
if (response.bodyshops[0]) { let newMessage = {
//Found a bodyshop - should always happen. msid: req.body.SmsMessageSid,
if (response.bodyshops[0].conversations.length === 0) { text: req.body.Body,
//No conversation Found, create one. image: !!req.body.MediaUrl0,
//console.log("[SMS Receive] No conversation found. Creating one."); image_path: generateMediaArray(req.body)
newMessage.conversation = { };
data: {
bodyshopid: response.bodyshops[0].id, if (response.bodyshops[0]) {
phone_num: phone(req.body.From).phoneNumber if (response.bodyshops[0].conversations.length === 0) {
} // No conversation found, create one
}; newMessage.conversation = {
} else if (response.bodyshops[0].conversations.length === 1) { data: {
//Just add it to the conversation bodyshopid: response.bodyshops[0].id,
//console.log("[SMS Receive] Conversation found. Added ID."); phone_num: phone(req.body.From).phoneNumber
newMessage.conversationid = response.bodyshops[0].conversations[0].id;
} else {
//We should never get here.
logger.log("sms-inbound-error", "ERROR", "api", null, {
msid: req.body.SmsMessageSid,
text: req.body.Body,
image: !!req.body.MediaUrl0,
image_path: generateMediaArray(req.body),
messagingServiceSid: req.body.MessagingServiceSid,
type: "duplicate-phone"
});
}
try {
let insertresp;
if (response.bodyshops[0].conversations[0]) {
insertresp = await client.request(queries.INSERT_MESSAGE, {
msg: newMessage,
conversationid: response.bodyshops[0].conversations[0] && response.bodyshops[0].conversations[0].id
});
} else {
insertresp = await client.request(queries.RECEIVE_MESSAGE, {
msg: newMessage
});
} }
const message = insertresp.insert_messages.returning[0]; };
const data = {
type: "messaging-inbound",
conversationid: message.conversationid || "",
text: message.text || "",
messageid: message.id || "",
phone_num: message.conversation.phone_num || ""
};
const fcmresp = await admin.messaging().send({ try {
topic: `${message.conversation.bodyshop.imexshopid}-messaging`, // Insert new conversation and message
notification: { const insertresp = await client.request(queries.RECEIVE_MESSAGE, { msg: newMessage });
title: InstanceManager({
imex: `ImEX Online Message - ${data.phone_num}`, const createdConversation = insertresp.insert_conversations.returning[0];
rome: `Rome Online Message - ${data.phone_num}`, const message = insertresp.insert_messages.returning[0];
promanager: `ProManager Message - ${data.phone_num}`
}), // Emit new conversation event
body: message.image_path ? `Image ${message.text}` : message.text ioRedis.to(getBodyshopRoom(response.bodyshops[0].id)).emit("new-conversation", {
//imageUrl: "https://thinkimex.com/img/io-fcm.png", //TODO:AIO Resolve addresses for other instances conversation: createdConversation,
}, message: {
data ...message,
text: message.text || "",
image: message.image || false,
image_path: message.image_path || []
}
}); });
logger.log("sms-inbound-success", "DEBUG", "api", null, { logger.log("sms-inbound-success", "DEBUG", "api", null, {
newMessage, newMessage,
fcmresp createdConversation
}); });
// Broadcast new message to the conversation room
const room = `conversation-${newMessage.conversationid}`;
ioRedis.to(room).emit("new-message", newMessage);
res.status(200).send(""); res.status(200).send("");
} catch (e2) { return;
} catch (e) {
logger.log("sms-inbound-error", "ERROR", "api", null, { logger.log("sms-inbound-error", "ERROR", "api", null, {
msid: req.body.SmsMessageSid, msid: req.body.SmsMessageSid,
text: req.body.Body, text: req.body.Body,
image: !!req.body.MediaUrl0, image: !!req.body.MediaUrl0,
image_path: generateMediaArray(req.body), image_path: generateMediaArray(req.body),
messagingServiceSid: req.body.MessagingServiceSid, messagingServiceSid: req.body.MessagingServiceSid,
error: e2 error: e
}); });
res.sendStatus(500).json(e2); res.status(500).json(e);
return;
} }
} else if (response.bodyshops[0].conversations.length === 1) {
// Add to the existing conversation
newMessage.conversationid = response.bodyshops[0].conversations[0].id;
} else {
// Duplicate phone error
logger.log("sms-inbound-error", "ERROR", "api", null, {
msid: req.body.SmsMessageSid,
text: req.body.Body,
image: !!req.body.MediaUrl0,
image_path: generateMediaArray(req.body),
messagingServiceSid: req.body.MessagingServiceSid,
type: "duplicate-phone"
});
res.status(400).json({ success: false, error: "Duplicate phone number" });
return;
}
try {
// Insert message into an existing conversation
const insertresp = await client.request(queries.INSERT_MESSAGE, {
msg: newMessage,
conversationid: newMessage.conversationid
});
const message = insertresp.insert_messages.returning[0];
const data = {
type: "messaging-inbound",
conversationid: message.conversationid || "",
text: message.text || "",
messageid: message.id || "",
phone_num: message.conversation.phone_num || ""
};
const fcmresp = await admin.messaging().send({
topic: `${message.conversation.bodyshop.imexshopid}-messaging`,
notification: {
title: InstanceManager({
imex: `ImEX Online Message - ${data.phone_num}`,
rome: `Rome Online Message - ${data.phone_num}`,
promanager: `ProManager Message - ${data.phone_num}`
}),
body: message.image_path ? `Image ${message.text}` : message.text
},
data
});
logger.log("sms-inbound-success", "DEBUG", "api", null, {
newMessage,
fcmresp
});
// Broadcast new message to the conversation room
const room = `conversation-${newMessage.conversationid}`;
ioRedis.to(room).emit("new-message", newMessage);
res.status(200).send("");
} catch (e) {
logger.log("sms-inbound-error", "ERROR", "api", null, {
msid: req.body.SmsMessageSid,
text: req.body.Body,
image: !!req.body.MediaUrl0,
image_path: generateMediaArray(req.body),
messagingServiceSid: req.body.MessagingServiceSid,
error: e
});
res.status(500).json(e);
} }
} catch (e1) {
logger.log("sms-inbound-error", "ERROR", "api", null, {
msid: req.body.SmsMessageSid,
text: req.body.Body,
image: !!req.body.MediaUrl0,
image_path: generateMediaArray(req.body),
messagingServiceSid: req.body.MessagingServiceSid,
error: e1
});
res.sendStatus(500).json(e1);
} }
} catch (e) {
logger.log("sms-inbound-error", "ERROR", "api", null, {
msid: req.body.SmsMessageSid,
text: req.body.Body,
image: !!req.body.MediaUrl0,
image_path: generateMediaArray(req.body),
messagingServiceSid: req.body.MessagingServiceSid,
error: e
});
res.status(500).json(e);
} }
}; };
// const sampleMessage: {
// "ToCountry": "CA",
// "ToState": "BC",
// "SmsMessageSid": "SMad7bddaf3454c0904999d6018b1e8f49",
// "NumMedia": "0",
// "ToCity": "Vancouver",
// "FromZip": "",
// "SmsSid": "SMad7bddaf3454c0904999d6018b1e8f49",
// "FromState": "BC",
// "SmsStatus": "received",
// "FromCity": "VANCOUVER",
// "Body": "Hi",
// "FromCountry": "CA",
// "To": "+16043301606",
// "MessagingServiceSid": "MG6e259e2add04ffa0d0aa355038670ee1",
// "ToZip": "",
// "NumSegments": "1",
// "MessageSid": "SMad7bddaf3454c0904999d6018b1e8f49",
// "AccountSid": "AC6c09d337d6b9c68ab6488c2052bd457c",
// "From": "+16049992002",
// "ApiVersion": "2010-04-01"
// }
// ] req.body {
// [0] ToCountry: 'CA',
// [0] MediaContentType0: 'image/jpeg',
// [0] ToState: 'BC',
// [0] SmsMessageSid: 'MM14fa2851ba26e0dc2b62073f8e7cdf27',
// [0] NumMedia: '1',
// [0] ToCity: 'Vancouver',
// [0] FromZip: '',
// [0] SmsSid: 'MM14fa2851ba26e0dc2b62073f8e7cdf27',
// [0] FromState: 'BC',
// [0] SmsStatus: 'received',
// [0] FromCity: 'VANCOUVER',
// [0] Body: '',
// [0] FromCountry: 'CA',
// [0] To: '+16043301606',
// [0] MessagingServiceSid: 'MG6e259e2add04ffa0d0aa355038670ee1',
// [0] ToZip: '',
// [0] NumSegments: '1',
// [0] MessageSid: 'MM14fa2851ba26e0dc2b62073f8e7cdf27',
// [0] AccountSid: 'AC6c09d337d6b9c68ab6488c2052bd457c',
// [0] From: '+16049992002',
// [0] MediaUrl0: 'https://api.twilio.com/2010-04-01/Accounts/AC6c09d337d6b9c68ab6488c2052bd457c/Messages/MM14fa2851ba26e0dc2b62073f8e7cdf27/Media/MEf129dd37979852f395eb29ffb126e19e',
// [0] ApiVersion: '2010-04-01'
// [0] }
// [0] MediaContentType0: 'image/jpeg',
// MediaContentType0: 'video/3gpp',
const generateMediaArray = (body) => { const generateMediaArray = (body) => {
const { NumMedia } = body; const { NumMedia } = body;
if (parseInt(NumMedia) > 0) { if (parseInt(NumMedia) > 0) {
//stuff
const ret = []; const ret = [];
for (var i = 0; i < parseInt(NumMedia); i++) { for (let i = 0; i < parseInt(NumMedia); i++) {
ret.push(body[`MediaUrl${i}`]); ret.push(body[`MediaUrl${i}`]);
} }
return ret; return ret;