feature/IO-3000-Migrate-MSG-to-Sockets - Progress Checkpoint

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-11-18 19:51:10 -08:00
parent ae0bfad89a
commit 6e6c44f2b9
9 changed files with 238 additions and 183 deletions

View File

@@ -1,7 +1,7 @@
import { Badge, Card, List, Space, Tag } from "antd"; import { Badge, Card, List, Space, Tag } from "antd";
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { AutoSizer, CellMeasurer, CellMeasurerCache, List as VirtualizedList } from "react-virtualized"; import { Virtuoso } from "react-virtuoso";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { setSelectedConversation } from "../../redux/messaging/messaging.actions"; import { setSelectedConversation } from "../../redux/messaging/messaging.actions";
import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors"; import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors";
@@ -25,12 +25,7 @@ function ChatConversationListComponent({
setSelectedConversation, setSelectedConversation,
loadMoreConversations loadMoreConversations
}) { }) {
const cache = new CellMeasurerCache({ const renderConversation = (index) => {
fixedWidth: true,
defaultHeight: 60
});
const rowRenderer = ({ index, key, style, parent }) => {
const item = conversationList[index]; const item = conversationList[index];
const cardContentRight = <TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>; const cardContentRight = <TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>;
const cardContentLeft = const cardContentLeft =
@@ -52,6 +47,7 @@ function ChatConversationListComponent({
)} )}
</> </>
); );
const cardExtra = <Badge count={item.messages_aggregate.aggregate.count || 0} />; const cardExtra = <Badge count={item.messages_aggregate.aggregate.count || 0} />;
const getCardStyle = () => const getCardStyle = () =>
@@ -60,40 +56,27 @@ function ChatConversationListComponent({
: { backgroundColor: index % 2 === 0 ? "#f0f2f5" : "#ffffff" }; : { backgroundColor: index % 2 === 0 ? "#f0f2f5" : "#ffffff" };
return ( return (
<CellMeasurer key={key} cache={cache} parent={parent} columnIndex={0} rowIndex={index}> <List.Item
<List.Item key={item.id}
onClick={() => setSelectedConversation(item.id)} onClick={() => setSelectedConversation(item.id)}
style={style} className={`chat-list-item ${item.id === selectedConversation ? "chat-list-selected-conversation" : ""}`}
className={`chat-list-item >
${item.id === selectedConversation ? "chat-list-selected-conversation" : null}`} <Card style={getCardStyle()} bordered={false} size="small" extra={cardExtra} title={cardTitle}>
> <div style={{ display: "inline-block", width: "70%", textAlign: "left" }}>{cardContentLeft}</div>
<Card style={getCardStyle()} bordered={false} size="small" extra={cardExtra} title={cardTitle}> <div style={{ display: "inline-block", width: "30%", textAlign: "right" }}>{cardContentRight}</div>
<div style={{ display: "inline-block", width: "70%", textAlign: "left" }}>{cardContentLeft}</div> </Card>
<div style={{ display: "inline-block", width: "30%", textAlign: "right" }}>{cardContentRight}</div> </List.Item>
</Card>
</List.Item>
</CellMeasurer>
); );
}; };
return ( return (
<div className="chat-list-container"> <div className="chat-list-container">
<AutoSizer> <Virtuoso
{({ height, width }) => ( data={conversationList}
<VirtualizedList itemContent={(index) => renderConversation(index)}
height={height} style={{ height: "100%", width: "100%" }}
width={width} endReached={loadMoreConversations} // Calls loadMoreConversations when scrolled to the bottom
rowCount={conversationList.length} />
rowHeight={cache.rowHeight}
rowRenderer={rowRenderer}
onScroll={({ scrollTop, scrollHeight, clientHeight }) => {
if (scrollTop + clientHeight === scrollHeight) {
loadMoreConversations();
}
}}
/>
)}
</AutoSizer>
</div> </div>
); );
} }

View File

@@ -1,7 +1,7 @@
.chat-list-container { .chat-list-container {
overflow: hidden; height: 100%; /* Ensure it takes up the full available height */
height: 100%;
border: 1px solid gainsboro; border: 1px solid gainsboro;
overflow: auto; /* Allow scrolling for the Virtuoso component */
} }
.chat-list-item { .chat-list-item {
@@ -14,3 +14,24 @@
color: #ff7a00; color: #ff7a00;
} }
} }
/* Virtuoso item container adjustments */
.chat-list-container > div {
height: 100%; /* Ensure Virtuoso takes full height */
display: flex;
flex-direction: column;
}
/* Add spacing and better alignment for items */
.chat-list-item {
padding: 0.5rem 0; /* Add spacing between list items */
.ant-card {
border-radius: 8px; /* Slight rounding for card edges */
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); /* Subtle shadow for better definition */
}
&:hover .ant-card {
border-color: #ff7a00; /* Highlight border on hover */
}
}

View File

@@ -13,12 +13,12 @@ const mapStateToProps = createStructuredSelector({
}); });
export function ChatConversationContainer({ bodyshop, selectedConversation }) { export function ChatConversationContainer({ bodyshop, selectedConversation }) {
const { socket, clientId } = useContext(SocketContext); const { socket } = useContext(SocketContext);
const [conversationDetails, setConversationDetails] = useState({}); const [conversationDetails, setConversationDetails] = useState({});
const [messages, setMessages] = useState([]); const [messages, setMessages] = useState([]);
const [markingAsReadInProgress, setMarkingAsReadInProgress] = useState(false); const [markingAsReadInProgress, setMarkingAsReadInProgress] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState(null); const [error] = useState(null);
// Fetch conversation details and messages when a conversation is selected // Fetch conversation details and messages when a conversation is selected
useEffect(() => { useEffect(() => {
@@ -28,8 +28,6 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) {
socket.on("conversation-details", (data) => { socket.on("conversation-details", (data) => {
setConversationDetails(data.conversation); setConversationDetails(data.conversation);
console.log("HIT HIT HIT");
console.dir(data);
setMessages(data.messages); setMessages(data.messages);
setLoading(false); setLoading(false);
}); });

View File

@@ -2,105 +2,90 @@ import Icon from "@ant-design/icons";
import { Tooltip } from "antd"; import { Tooltip } from "antd";
import i18n from "i18next"; import i18n from "i18next";
import dayjs from "../../utils/day"; import dayjs from "../../utils/day";
import React, { useEffect, useRef } from "react"; import React, { useRef, useEffect } from "react";
import { MdDone, MdDoneAll } from "react-icons/md"; import { MdDone, MdDoneAll } from "react-icons/md";
import { AutoSizer, CellMeasurer, CellMeasurerCache, List } from "react-virtualized"; import { Virtuoso } from "react-virtuoso";
import { DateTimeFormatter } from "../../utils/DateFormatter"; import { DateTimeFormatter } from "../../utils/DateFormatter";
import "./chat-message-list.styles.scss"; import "./chat-message-list.styles.scss";
export default function ChatMessageListComponent({ messages }) { export default function ChatMessageListComponent({ messages }) {
const virtualizedListRef = useRef(null); const virtuosoRef = useRef(null);
const _cache = new CellMeasurerCache({ // Scroll to the bottom after a short delay when the component mounts
fixedWidth: true, useEffect(() => {
// minHeight: 50, const timer = setTimeout(() => {
defaultHeight: 100 if (virtuosoRef.current) {
}); virtuosoRef.current.scrollToIndex({
index: messages.length - 1,
behavior: "auto" // Instantly scroll to the bottom
});
}
}, 100); // Delay of 100ms to allow rendering
return () => clearTimeout(timer); // Cleanup the timer on unmount
}, [messages.length]); // Run only once on component mount
const scrollToBottom = (renderedrows) => { // Scroll to the bottom after the new messages are rendered
//console.log("Scrolling to", messages.length); useEffect(() => {
// !!virtualizedListRef.current && if (virtuosoRef.current) {
// virtualizedListRef.current.scrollToRow(messages.length); // Allow the DOM and Virtuoso to fully render the new data
// Outstanding isue on virtualization: https://github.com/bvaughn/react-virtualized/issues/1179 setTimeout(() => {
//Scrolling does not work on this version of React. virtuosoRef.current.scrollToIndex({
}; index: messages.length - 1,
align: "end", // Ensure the last message is fully visible
behavior: "smooth" // Smooth scrolling
});
}, 50); // Slight delay to ensure layout recalculates
}
}, [messages]); // Triggered when new messages are added
useEffect(scrollToBottom, [messages]); const renderMessage = (index) => {
const message = messages[index];
const _rowRenderer = ({ index, key, parent, style }) => {
return ( return (
<CellMeasurer cache={_cache} key={key} rowIndex={index} parent={parent}> <div key={index} className={`${message.isoutbound ? "mine messages" : "yours messages"}`}>
{({ measure, registerChild }) => ( <div className="message msgmargin">
<div <Tooltip title={DateTimeFormatter({ children: message.created_at })}>
ref={registerChild} <div>
onLoad={measure} {message.image_path &&
style={style} message.image_path.map((i, idx) => (
className={`${messages[index].isoutbound ? "mine messages" : "yours messages"}`} <div key={idx} style={{ display: "flex", justifyContent: "center" }}>
> <a href={i} target="__blank" rel="noopener noreferrer">
<div className="message msgmargin"> <img alt="Received" className="message-img" src={i} />
{MessageRender(messages[index])} </a>
{StatusRender(messages[index].status)} </div>
))}
<div>{message.text}</div>
</div> </div>
{messages[index].isoutbound && ( </Tooltip>
<div style={{ fontSize: 10 }}> {message.status && (
{i18n.t("messaging.labels.sentby", { <div className="message-status">
by: messages[index].userid, <Icon
time: dayjs(messages[index].created_at).format("MM/DD/YYYY @ hh:mm a") component={message.status === "sent" ? MdDone : message.status === "delivered" ? MdDoneAll : null}
})} className="message-icon"
</div> />
)} </div>
)}
</div>
{message.isoutbound && (
<div style={{ fontSize: 10 }}>
{i18n.t("messaging.labels.sentby", {
by: message.userid,
time: dayjs(message.created_at).format("MM/DD/YYYY @ hh:mm a")
})}
</div> </div>
)} )}
</CellMeasurer> </div>
); );
}; };
return ( return (
<div className="chat"> <div className="chat">
<AutoSizer> <Virtuoso
{({ height, width }) => ( ref={virtuosoRef}
<List data={messages}
ref={virtualizedListRef} itemContent={(index) => renderMessage(index)}
width={width} followOutput="smooth" // Ensure smooth scrolling when new data is appended
height={height} style={{ height: "100%", width: "100%" }}
rowHeight={_cache.rowHeight} />
rowRenderer={_rowRenderer}
rowCount={messages.length}
overscanRowCount={10}
estimatedRowSize={150}
scrollToIndex={messages.length}
/>
)}
</AutoSizer>
</div> </div>
); );
} }
const MessageRender = (message) => {
return (
<Tooltip title={DateTimeFormatter({ children: message.created_at })}>
<div>
{message.image_path &&
message.image_path.map((i, idx) => (
<div key={idx} style={{ display: "flex", justifyContent: "center" }}>
<a href={i} target="__blank">
<img alt="Received" className="message-img" src={i} />
</a>
</div>
))}
<div>{message.text}</div>
</div>
</Tooltip>
);
};
const StatusRender = (status) => {
switch (status) {
case "sent":
return <Icon component={MdDone} className="message-icon" />;
case "delivered":
return <Icon component={MdDoneAll} className="message-icon" />;
default:
return null;
}
};

View File

@@ -1,37 +1,30 @@
.message-icon { .message-icon {
//position: absolute;
// bottom: 0rem;
color: whitesmoke; color: whitesmoke;
border: #000000; border: #000000;
position: absolute; position: absolute;
margin: 0 0.1rem; margin: 0 0.1rem;
bottom: 0.1rem; bottom: 0.1rem;
right: 0.3rem; right: 0.3rem;
z-index: 5; z-index: 5;
} }
.chat { .chat {
flex: 1; flex: 1;
//width: 300px;
//border: solid 1px #eee;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin: 0.8rem 0rem; margin: 0.8rem 0rem;
overflow: hidden; // Ensure the content scrolls correctly
} }
.messages { .messages {
//margin-top: 30px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 0.5rem; // Add padding to avoid edge clipping
} }
.message { .message {
border-radius: 20px; border-radius: 20px;
padding: 0.25rem 0.8rem; padding: 0.25rem 0.8rem;
//margin-top: 5px;
// margin-bottom: 5px;
//display: inline-block;
.message-img { .message-img {
max-width: 10rem; max-width: 10rem;
@@ -56,7 +49,7 @@
position: relative; position: relative;
} }
.yours .message.last:before { .yours .message:last-child:before {
content: ""; content: "";
position: absolute; position: absolute;
z-index: 0; z-index: 0;
@@ -68,7 +61,7 @@
border-bottom-right-radius: 15px; border-bottom-right-radius: 15px;
} }
.yours .message.last:after { .yours .message:last-child:after {
content: ""; content: "";
position: absolute; position: absolute;
z-index: 1; z-index: 1;
@@ -88,12 +81,11 @@
color: white; color: white;
margin-left: 25%; margin-left: 25%;
background: linear-gradient(to bottom, #00d0ea 0%, #0085d1 100%); background: linear-gradient(to bottom, #00d0ea 0%, #0085d1 100%);
background-attachment: fixed;
position: relative; position: relative;
padding-bottom: 0.6rem; padding-bottom: 0.6rem;
} }
.mine .message.last:before { .mine .message:last-child:before {
content: ""; content: "";
position: absolute; position: absolute;
z-index: 0; z-index: 0;
@@ -102,11 +94,10 @@
height: 20px; height: 20px;
width: 20px; width: 20px;
background: linear-gradient(to bottom, #00d0ea 0%, #0085d1 100%); background: linear-gradient(to bottom, #00d0ea 0%, #0085d1 100%);
background-attachment: fixed;
border-bottom-left-radius: 15px; border-bottom-left-radius: 15px;
} }
.mine .message.last:after { .mine .message:last-child:after {
content: ""; content: "";
position: absolute; position: absolute;
z-index: 1; z-index: 1;

View File

@@ -39,7 +39,7 @@ export function ChatPopupComponent({
unreadCount unreadCount
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { socket, clientId } = useContext(SocketContext); const { socket } = useContext(SocketContext);
// Emit event to open messaging when chat becomes visible // Emit event to open messaging when chat becomes visible
useEffect(() => { useEffect(() => {

View File

@@ -1,55 +1,66 @@
import { LoadingOutlined, SendOutlined } from "@ant-design/icons"; import { LoadingOutlined, SendOutlined } from "@ant-design/icons";
import { Input, Spin } from "antd"; import { Input, Spin } from "antd";
import React, { useEffect, useRef, useState } from "react"; import React, { useContext, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { sendMessage, setMessage } from "../../redux/messaging/messaging.actions"; import { setMessage } from "../../redux/messaging/messaging.actions";
import { selectIsSending, selectMessage } from "../../redux/messaging/messaging.selectors"; import { selectIsSending, selectMessage } from "../../redux/messaging/messaging.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import ChatMediaSelector from "../chat-media-selector/chat-media-selector.component"; import ChatMediaSelector from "../chat-media-selector/chat-media-selector.component";
import ChatPresetsComponent from "../chat-presets/chat-presets.component"; import ChatPresetsComponent from "../chat-presets/chat-presets.component";
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
isSending: selectIsSending, isSending: selectIsSending,
message: selectMessage message: selectMessage,
user: selectCurrentUser
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
sendMessage: (message) => dispatch(sendMessage(message)),
setMessage: (message) => dispatch(setMessage(message)) setMessage: (message) => dispatch(setMessage(message))
}); });
function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSending, message, setMessage }) { function ChatSendMessageComponent({ conversation, bodyshop, isSending, message, setMessage, user }) {
const { socket } = useContext(SocketContext); // Access WebSocket instance
const inputArea = useRef(null); const inputArea = useRef(null);
const [selectedMedia, setSelectedMedia] = useState([]); const [selectedMedia, setSelectedMedia] = useState([]);
const { t } = useTranslation();
useEffect(() => { useEffect(() => {
inputArea.current.focus(); inputArea.current.focus();
}, [isSending, setMessage]); }, [isSending, setMessage]);
const { t } = useTranslation();
const handleEnter = () => { const handleEnter = () => {
const selectedImages = selectedMedia.filter((i) => i.isSelected); const selectedImages = selectedMedia.filter((i) => i.isSelected);
if ((message === "" || !message) && selectedImages.length === 0) return; if ((message === "" || !message) && selectedImages.length === 0) return;
logImEXEvent("messaging_send_message"); logImEXEvent("messaging_send_message");
if (selectedImages.length < 11) { if (selectedImages.length < 11) {
sendMessage({ const messageData = {
user,
to: conversation.phone_num, to: conversation.phone_num,
body: message || "", body: message || "",
messagingServiceSid: bodyshop.messagingservicesid, messagingServiceSid: bodyshop.messagingservicesid,
conversationid: conversation.id, conversationid: conversation.id,
selectedMedia: selectedImages, selectedMedia: selectedImages,
imexshopid: bodyshop.imexshopid imexshopid: bodyshop.imexshopid
}); };
// Emit the send-message event via WebSocket
socket.emit("send-message", messageData);
setSelectedMedia( setSelectedMedia(
selectedMedia.map((i) => { selectedMedia.map((i) => {
return { ...i, isSelected: false }; return { ...i, isSelected: false };
}) })
); );
// Optionally clear the input message
setMessage("");
} }
}; };
@@ -74,15 +85,11 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSendi
onChange={(e) => setMessage(e.target.value)} onChange={(e) => setMessage(e.target.value)}
onPressEnter={(event) => { onPressEnter={(event) => {
event.preventDefault(); event.preventDefault();
if (!!!event.shiftKey) handleEnter(); if (!event.shiftKey) handleEnter();
}} }}
/> />
</span> </span>
<SendOutlined <SendOutlined className="imex-flex-row__margin" onClick={handleEnter} />
className="imex-flex-row__margin"
// disabled={message === "" || !message}
onClick={handleEnter}
/>
<Spin <Spin
style={{ display: `${isSending ? "" : "none"}` }} style={{ display: `${isSending ? "" : "none"}` }}
indicator={ indicator={

View File

@@ -1,8 +1,5 @@
import react from "@vitejs/plugin-react"; import react from "@vitejs/plugin-react";
import { promises as fsPromises } from "fs"; import { promises as fsPromises } from "fs";
import { createRequire } from "module";
import * as path from "path";
import * as url from "url";
import { createLogger, defineConfig } from "vite"; import { createLogger, defineConfig } from "vite";
import { ViteEjsPlugin } from "vite-plugin-ejs"; import { ViteEjsPlugin } from "vite-plugin-ejs";
import eslint from "vite-plugin-eslint"; import eslint from "vite-plugin-eslint";
@@ -18,28 +15,6 @@ process.env.VITE_APP_GIT_SHA_DATE = new Date().toLocaleString("en-US", {
const getFormattedTimestamp = () => const getFormattedTimestamp = () =>
new Date().toLocaleTimeString("en-US", { hour12: true }).replace("AM", "a.m.").replace("PM", "p.m."); new Date().toLocaleTimeString("en-US", { hour12: true }).replace("AM", "a.m.").replace("PM", "p.m.");
/** This is a hack around react-virtualized, should be removed when switching to react-virtuoso */
const WRONG_CODE = `import { bpfrpt_proptype_WindowScroller } from "../WindowScroller.js";`;
function reactVirtualizedFix() {
return {
name: "flat:react-virtualized",
configResolved: async () => {
const require = createRequire(import.meta.url);
const reactVirtualizedPath = require.resolve("react-virtualized");
const { pathname: reactVirtualizedFilePath } = new url.URL(reactVirtualizedPath, import.meta.url);
const file = reactVirtualizedFilePath.replace(
path.join("dist", "commonjs", "index.js"),
path.join("dist", "es", "WindowScroller", "utils", "onScroll.js")
);
const code = await fsPromises.readFile(file, "utf-8");
const modified = code.replace(WRONG_CODE, "");
await fsPromises.writeFile(file, modified);
}
};
}
/** End of hack */
export const logger = createLogger("info", { export const logger = createLogger("info", {
allowClearScreen: false allowClearScreen: false
}); });
@@ -108,7 +83,6 @@ export default defineConfig({
gcm_sender_id: "103953800507" gcm_sender_id: "103953800507"
} }
}), }),
reactVirtualizedFix(),
react(), react(),
eslint() eslint()
], ],

View File

@@ -1,7 +1,14 @@
const { admin } = require("../firebase/firebase-handler"); const { admin } = require("../firebase/firebase-handler");
const { MARK_MESSAGES_AS_READ, GET_CONVERSATIONS, GET_CONVERSATION_DETAILS } = require("../graphql-client/queries"); const { MARK_MESSAGES_AS_READ, GET_CONVERSATIONS, GET_CONVERSATION_DETAILS } = require("../graphql-client/queries");
const logger = require("../utils/logger");
const { phone } = require("phone");
const { client: gqlClient } = require("../graphql-client/graphql-client");
const queries = require("../graphql-client/queries");
const twilio = require("twilio");
const client = require("../graphql-client/graphql-client").client; const client = require("../graphql-client/graphql-client").client;
const twilioClient = twilio(process.env.TWILIO_AUTH_TOKEN, process.env.TWILIO_AUTH_KEY);
const redisSocketEvents = ({ const redisSocketEvents = ({
io, io,
redisHelpers: { setSessionData, clearSessionData }, // Note: Used if we persist user to Redis redisHelpers: { setSessionData, clearSessionData }, // Note: Used if we persist user to Redis
@@ -173,7 +180,96 @@ const redisSocketEvents = ({
socket.emit("error", { message: "Failed to mark messages as read" }); socket.emit("error", { message: "Failed to mark messages as read" });
} }
}; };
// Mark Messages as Read
const sendMessage = (data) => {
const { to, messagingServiceSid, body, conversationid, selectedMedia, imexshopid, user } = data;
console.dir({ data });
logger.log("sms-outbound", "DEBUG", user.email, null, {
messagingServiceSid: messagingServiceSid,
to: phone(to).phoneNumber,
mediaUrl: selectedMedia.map((i) => i.src),
text: body,
conversationid,
isoutbound: true,
userid: user.email,
image: selectedMedia?.length > 0,
image_path: selectedMedia?.length > 0 ? selectedMedia.map((i) => i.src) : []
});
if (!!to && !!messagingServiceSid && (!!body || !!selectedMedia?.length > 0) && !!conversationid) {
twilioClient.messages
.create({
body: body,
messagingServiceSid: messagingServiceSid,
to: phone(to).phoneNumber,
mediaUrl: selectedMedia.map((i) => i.src)
})
.then((message) => {
let newMessage = {
msid: message.sid,
text: body,
conversationid,
isoutbound: true,
userid: user.email,
image: selectedMedia?.length > 0,
image_path: selectedMedia?.length > 0 ? selectedMedia.map((i) => i.src) : []
};
gqlClient
.request(queries.INSERT_MESSAGE, { msg: newMessage, conversationid })
.then((r2) => {
//console.log("Responding GQL Message ID", JSON.stringify(r2));
logger.log("sms-outbound-success", "DEBUG", user.email, null, {
msid: message.sid,
conversationid
});
const data = {
type: "messaging-outbound",
conversationid: newMessage.conversationid || ""
};
// TODO Verify
// const messageData = response.insert_messages.returning[0];
// Broadcast new message to conversation room
const room = `conversation-${conversationid}`;
io.to(room).emit("new-message", newMessage);
admin.messaging().send({
topic: `${imexshopid}-messaging`,
data
});
})
.catch((e2) => {
logger.log("sms-outbound-error", "ERROR", user.email, null, {
msid: message.sid,
conversationid,
error: e2
});
});
})
.catch((e1) => {
logger.log("sms-outbound-error", "ERROR", user.email, null, {
conversationid,
error: e1
});
});
} else {
logger.log("sms-outbound-error", "ERROR", user.email, null, {
type: "missing-parameters",
messagingServiceSid: messagingServiceSid,
to: phone(to).phoneNumber,
text: body,
conversationid,
isoutbound: true,
userid: user.email,
image: selectedMedia?.length > 0,
image_path: selectedMedia?.length > 0 ? selectedMedia.map((i) => i.src) : []
});
}
};
socket.on("send-message", sendMessage);
socket.on("mark-as-read", markAsRead); socket.on("mark-as-read", markAsRead);
socket.on("join-conversation", joinConversation); socket.on("join-conversation", joinConversation);
socket.on("open-messaging", openMessaging); socket.on("open-messaging", openMessaging);