IO-1551 Refactor messaging.

This commit is contained in:
Patrick Fic
2021-12-08 12:38:26 -08:00
parent 26d22388c0
commit 1f7b53ee22
30 changed files with 614 additions and 302 deletions

View File

@@ -1,4 +1,4 @@
<babeledit_project version="1.2" be_version="2.7.1"> <babeledit_project be_version="2.7.1" version="1.2">
<!-- <!--
BabelEdit project file BabelEdit project file
@@ -14199,6 +14199,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>tryagain</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>view</name> <name>view</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -14246,6 +14267,27 @@
<folder_node> <folder_node>
<name>errors</name> <name>errors</name>
<children> <children>
<concept_node>
<name>fcm</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>notfound</name> <name>notfound</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -14634,6 +14676,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>help</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>hours</name> <name>hours</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

View File

@@ -1,53 +1,39 @@
importScripts("https://www.gstatic.com/firebasejs/7.14.2/firebase-app.js"); // Scripts for firebase and firebase messaging
importScripts( importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-app.js");
"https://www.gstatic.com/firebasejs/7.14.2/firebase-messaging.js" importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-messaging.js");
);
firebase.initializeApp({ // Initialize the Firebase app in the service worker by passing the generated config
apiKey: "AIzaSyDSezy-jGJreo7ulgpLdlpOwAOrgcaEkhU", const firebaseConfig = {
authDomain: "imex-prod.firebaseapp.com", apiKey: "AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc",
databaseURL: "https://imex-prod.firebaseio.com", authDomain: "imex-dev.firebaseapp.com",
projectId: "imex-prod", databaseURL: "https://imex-dev.firebaseio.com",
storageBucket: "imex-prod.appspot.com", projectId: "imex-dev",
messagingSenderId: "253497221485", storageBucket: "imex-dev.appspot.com",
appId: "1:253497221485:web:3c81c483b94db84b227a64", messagingSenderId: "759548147434",
measurementId: "G-NTWBKG2L0M", appId: "1:759548147434:web:e8239868a48ceb36700993",
}); measurementId: "G-K5XRBVVB4S",
};
// const firebaseConfig = {
// apiKey: "AIzaSyDSezy-jGJreo7ulgpLdlpOwAOrgcaEkhU",
// authDomain: "imex-prod.firebaseapp.com",
// databaseURL: "https://imex-prod.firebaseio.com",
// projectId: "imex-prod",
// storageBucket: "imex-prod.appspot.com",
// messagingSenderId: "253497221485",
// appId: "1:253497221485:web:3c81c483b94db84b227a64",
// measurementId: "G-NTWBKG2L0M",
// }
// firebase.initializeApp({ firebase.initializeApp(firebaseConfig);
// apiKey: "AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc",
// authDomain: "imex-dev.firebaseapp.com",
// databaseURL: "https://imex-dev.firebaseio.com",
// projectId: "imex-dev",
// storageBucket: "imex-dev.appspot.com",
// messagingSenderId: "759548147434",
// appId: "1:759548147434:web:e8239868a48ceb36700993",
// measurementId: "G-K5XRBVVB4S",
// });
// Retrieve firebase messaging
const messaging = firebase.messaging(); const messaging = firebase.messaging();
self.addEventListener("fetch", (fetch) => { messaging.onBackgroundMessage(function (payload) {
//required for installation as a PWA. Can ignore for now. console.log("FCM BG MSG", payload);
//console.log("fetch", fetch); // Customize notification here
}); const channel = new BroadcastChannel("imex-sw-messages");
channel.postMessage(payload);
messaging.setBackgroundMessageHandler(function (payload) { //self.registration.showNotification(notificationTitle, notificationOptions);
return self.registration.showNotification(
"[SW]" + payload.notification.title,
payload.notification
);
});
//Handles the notification getting clicked.
self.addEventListener("notificationclick", function (event) {
console.log("SW notificationclick", event);
// event.notification.close();
if (event.action === "archive") {
// Archive action was clicked
archiveEmail();
} else {
// Main body of notification was clicked
clients.openWindow("/inbox");
}
}); });

View File

@@ -1,43 +0,0 @@
import { MessageOutlined } from "@ant-design/icons";
import { Badge, Card } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { toggleChatVisible } from "../../redux/messaging/messaging.actions";
import { selectChatVisible } from "../../redux/messaging/messaging.selectors";
import ChatPopupComponent from '../chat-popup/chat-popup.component'
const mapStateToProps = createStructuredSelector({
chatVisible: selectChatVisible,
});
const mapDispatchToProps = (dispatch) => ({
toggleChatVisible: () => dispatch(toggleChatVisible()),
});
export function ChatAffixComponent({
chatVisible,
toggleChatVisible,
conversationList,
unreadCount,
}) {
const { t } = useTranslation();
return (
<Badge count={unreadCount}>
<Card size='small'>
{chatVisible ? (
<ChatPopupComponent conversationList={conversationList} />
) : (
<div
onClick={() => toggleChatVisible()}
style={{ cursor: "pointer" }}>
<MessageOutlined />
<strong>{t("messaging.labels.messaging")}</strong>
</div>
)}
</Card>
</Badge>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(ChatAffixComponent);

View File

@@ -1,13 +1,16 @@
import { useSubscription } from "@apollo/client"; import { useApolloClient } from "@apollo/client";
import React from "react"; import { getToken, onMessage } from "@firebase/messaging";
import { Button, notification, Space } from "antd";
import axios from "axios";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { CONVERSATION_LIST_SUBSCRIPTION } from "../../graphql/conversations.queries"; import { messaging, requestForToken } from "../../firebase/firebase.utils";
import { selectChatVisible } from "../../redux/messaging/messaging.selectors"; import { selectChatVisible } from "../../redux/messaging/messaging.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component"; import FcmHandler from "../../utils/fcm-handler";
import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import ChatPopupComponent from "../chat-popup/chat-popup.component";
import ChatAffixComponent from "./chat-affix.component";
import "./chat-affix.styles.scss"; import "./chat-affix.styles.scss";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -16,32 +19,85 @@ const mapStateToProps = createStructuredSelector({
}); });
export function ChatAffixContainer({ bodyshop, chatVisible }) { export function ChatAffixContainer({ bodyshop, chatVisible }) {
const { loading, error, data } = useSubscription( const { t } = useTranslation();
CONVERSATION_LIST_SUBSCRIPTION, const client = useApolloClient();
{ useEffect(() => {
skip: !bodyshop || (bodyshop && !bodyshop.messagingservicesid), if (!bodyshop || !bodyshop.messagingservicesid) return;
}
);
if (loading) return <LoadingSpinner />; async function SubscribeToTopic() {
if (error) return <AlertComponent message={error.message} type="error" />; try {
const r = await axios.post("/notifications/subscribe", {
fcm_tokens: await getToken(messaging, {
vapidKey: process.env.REACT_APP_FIREBASE_PUBLIC_VAPID_KEY,
}),
type: "messaging",
imexshopid: bodyshop.imexshopid,
});
console.log("FCM Topic Subscription", r.data);
} catch (error) {
console.log(
"Error attempting to subscribe to messaging topic: ",
error
);
notification.open({
type: "warning",
message: t("general.errors.fcm"),
btn: (
<Space>
<Button
onClick={async () => {
const resp = await requestForToken();
console.log(
"🚀 ~ file: chat-affix.container.jsx ~ line 44 ~ resp",
resp
);
SubscribeToTopic();
}}
>
{t("general.actions.tryagain")}
</Button>
<Button
onClick={() => {
const win = window.open(
"https://help.imex.online/en/article/enabling-notifications-o978xi/",
"_blank"
);
win.focus();
}}
>
{t("general.labels.help")}
</Button>
</Space>
),
});
}
}
SubscribeToTopic();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [bodyshop]);
useEffect(() => {
function handleMessage(payload) {
FcmHandler({
client,
payload: (payload && payload.data && payload.data.data) || payload.data,
});
}
const stopMessageListenr = onMessage(messaging, handleMessage);
const channel = new BroadcastChannel("imex-sw-messages");
channel.addEventListener("message", handleMessage);
return () => {
stopMessageListenr();
channel.removeEventListener("message", handleMessage);
};
}, [client]);
if (!bodyshop || !bodyshop.messagingservicesid) return <></>; if (!bodyshop || !bodyshop.messagingservicesid) return <></>;
return ( return (
<div className={`chat-affix ${chatVisible ? "chat-affix-open" : ""}`}> <div className={`chat-affix ${chatVisible ? "chat-affix-open" : ""}`}>
{bodyshop && bodyshop.messagingservicesid ? ( {bodyshop && bodyshop.messagingservicesid ? <ChatPopupComponent /> : null}
<ChatAffixComponent
conversationList={(data && data.conversations) || []}
unreadCount={
(data &&
data.conversations.reduce((acc, val) => {
return (acc = acc + val.messages_aggregate.aggregate.count);
}, 0)) ||
0
}
/>
) : null}
</div> </div>
); );
} }

View File

@@ -1,14 +1,12 @@
import { Badge, List, Tag, Tooltip } from "antd"; import { Badge, List, Tag } from "antd";
import { AlertFilled } from "@ant-design/icons";
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
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";
import { TimeAgoFormatter } from "../../utils/DateFormatter";
import PhoneFormatter from "../../utils/PhoneFormatter"; import PhoneFormatter from "../../utils/PhoneFormatter";
import "./chat-conversation-list.styles.scss"; import "./chat-conversation-list.styles.scss";
import { useTranslation } from "react-i18next";
import { TimeAgoFormatter } from "../../utils/DateFormatter";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
selectedConversation: selectSelectedConversation, selectedConversation: selectSelectedConversation,
@@ -24,8 +22,6 @@ export function ChatConversationListComponent({
selectedConversation, selectedConversation,
setSelectedConversation, setSelectedConversation,
}) { }) {
const { t } = useTranslation();
return ( return (
<div className="chat-list-container"> <div className="chat-list-container">
<List <List
@@ -33,6 +29,7 @@ export function ChatConversationListComponent({
dataSource={conversationList} dataSource={conversationList}
renderItem={(item) => ( renderItem={(item) => (
<List.Item <List.Item
key={item.id}
onClick={() => setSelectedConversation(item.id)} onClick={() => setSelectedConversation(item.id)}
className={`chat-list-item ${ className={`chat-list-item ${
item.id === selectedConversation item.id === selectedConversation
@@ -43,19 +40,9 @@ export function ChatConversationListComponent({
{item.job_conversations.length > 0 ? ( {item.job_conversations.length > 0 ? (
<div className="chat-name"> <div className="chat-name">
{item.job_conversations.map((j, idx) => ( {item.job_conversations.map((j, idx) => (
<div key={idx} style={{ display: "flex" }}> <div key={idx}>{`${j.job.ownr_fn || ""} ${
{j.job.owner && !j.job.owner.allow_text_message && ( j.job.ownr_ln || ""
<Tooltip title={t("messaging.labels.noallowtxt")}> } ${j.job.ownr_co_nm || ""} `}</div>
<AlertFilled
className="production-alert"
style={{ marginRight: ".3rem", alignItems: "center" }}
/>
</Tooltip>
)}
<div>{`${j.job.ownr_fn || ""} ${j.job.ownr_ln || ""} ${
j.job.ownr_co_nm || ""
} `}</div>
</div>
))} ))}
</div> </div>
) : ( ) : (

View File

@@ -9,6 +9,7 @@ import "./chat-conversation.styles.scss";
export default function ChatConversationComponent({ export default function ChatConversationComponent({
subState, subState,
conversation, conversation,
messages,
handleMarkConversationAsRead, handleMarkConversationAsRead,
}) { }) {
const [loading, error] = subState; const [loading, error] = subState;
@@ -16,8 +17,6 @@ export default function ChatConversationComponent({
if (loading) return <LoadingSkeleton />; if (loading) return <LoadingSkeleton />;
if (error) return <AlertComponent message={error.message} type="error" />; if (error) return <AlertComponent message={error.message} type="error" />;
const messages = (conversation && conversation.messages) || [];
return ( return (
<div <div
className="chat-conversation" className="chat-conversation"

View File

@@ -1,18 +1,32 @@
import { useMutation, useSubscription } from "@apollo/client"; import { useMutation, useQuery, useSubscription } from "@apollo/client";
import React, { useState } from "react"; import React, { useState } from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { CONVERSATION_SUBSCRIPTION_BY_PK } from "../../graphql/conversations.queries"; import {
CONVERSATION_SUBSCRIPTION_BY_PK,
GET_CONVERSATION_DETAILS,
} from "../../graphql/conversations.queries";
import { MARK_MESSAGES_AS_READ_BY_CONVERSATION } from "../../graphql/messages.queries"; import { MARK_MESSAGES_AS_READ_BY_CONVERSATION } from "../../graphql/messages.queries";
import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors"; import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors";
import ChatConversationComponent from "./chat-conversation.component"; import ChatConversationComponent from "./chat-conversation.component";
import axios from "axios";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
selectedConversation: selectSelectedConversation, selectedConversation: selectSelectedConversation,
bodyshop: selectBodyshop,
}); });
export default connect(mapStateToProps, null)(ChatConversationContainer); export default connect(mapStateToProps, null)(ChatConversationContainer);
export function ChatConversationContainer({ selectedConversation }) { export function ChatConversationContainer({ bodyshop, selectedConversation }) {
const {
loading: convoLoading,
error: convoError,
data: convoData,
} = useQuery(GET_CONVERSATION_DETAILS, {
variables: { conversationId: selectedConversation },
});
const { loading, error, data } = useSubscription( const { loading, error, data } = useSubscription(
CONVERSATION_SUBSCRIPTION_BY_PK, CONVERSATION_SUBSCRIPTION_BY_PK,
{ {
@@ -26,30 +40,46 @@ export function ChatConversationContainer({ selectedConversation }) {
MARK_MESSAGES_AS_READ_BY_CONVERSATION, MARK_MESSAGES_AS_READ_BY_CONVERSATION,
{ {
variables: { conversationId: selectedConversation }, variables: { conversationId: selectedConversation },
update(cache) {
cache.modify({
id: cache.identify({
__typename: "conversations",
id: selectedConversation,
}),
fields: {
messages_aggregate(cached) {
return { aggregate: { count: 0 } };
},
},
});
},
} }
); );
const unreadCount = const unreadCount =
(data && data &&
data.conversations_by_pk && data.messages &&
data.conversations_by_pk && data.messages.reduce((acc, val) => {
data.conversations_by_pk.messages_aggregate && return !val.read && !val.isoutbound ? acc + 1 : acc;
data.conversations_by_pk.messages_aggregate.aggregate && }, 0);
data.conversations_by_pk.messages_aggregate.aggregate.count) ||
0;
const handleMarkConversationAsRead = async () => { const handleMarkConversationAsRead = async () => {
if (unreadCount > 0 && !!selectedConversation && !markingAsReadInProgress) { if (unreadCount > 0 && !!selectedConversation && !markingAsReadInProgress) {
setMarkingAsReadInProgress(true); setMarkingAsReadInProgress(true);
await markConversationRead(); await markConversationRead({});
await axios.post("/sms/markConversationRead", {
conversationid: selectedConversation,
imexshopid: bodyshop.imexshopid,
});
setMarkingAsReadInProgress(false); setMarkingAsReadInProgress(false);
} }
}; };
return ( return (
<ChatConversationComponent <ChatConversationComponent
subState={[loading, error]} subState={[loading || convoLoading, error || convoError]}
conversation={data ? data.conversations_by_pk : {}} conversation={convoData ? convoData.conversations_by_pk : {}}
messages={data ? data.messages : []}
handleMarkConversationAsRead={handleMarkConversationAsRead} handleMarkConversationAsRead={handleMarkConversationAsRead}
/> />
); );

View File

@@ -46,7 +46,7 @@ export function ChatNewConversation({ openChatByPhone }) {
return ( return (
<Popover trigger="click" content={popContent}> <Popover trigger="click" content={popContent}>
<PlusCircleFilled style={{ margin: "1rem" }} /> <PlusCircleFilled />
</Popover> </Popover>
); );
} }

View File

@@ -1,54 +1,108 @@
import { ShrinkOutlined, InfoCircleOutlined } from "@ant-design/icons"; import {
import { Col, Row, Tooltip, Typography } from "antd"; ShrinkOutlined,
import React from "react"; InfoCircleOutlined,
SyncOutlined,
} from "@ant-design/icons";
import { Col, Row, Tooltip, Space, Typography, Badge, Card } from "antd";
import React, { useEffect } 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 { toggleChatVisible } from "../../redux/messaging/messaging.actions"; import { toggleChatVisible } from "../../redux/messaging/messaging.actions";
import ChatConversationListComponent from "../chat-conversation-list/chat-conversation-list.component"; import ChatConversationListComponent from "../chat-conversation-list/chat-conversation-list.component";
import ChatConversationContainer from "../chat-conversation/chat-conversation.container"; import ChatConversationContainer from "../chat-conversation/chat-conversation.container";
import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors"; import {
selectChatVisible,
selectSelectedConversation,
} from "../../redux/messaging/messaging.selectors";
import "./chat-popup.styles.scss"; import "./chat-popup.styles.scss";
import ChatNewConversation from "../chat-new-conversation/chat-new-conversation.component"; import ChatNewConversation from "../chat-new-conversation/chat-new-conversation.component";
import { CONVERSATION_LIST_QUERY } from "../../graphql/conversations.queries";
import { useQuery } from "@apollo/client";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import { MessageOutlined } from "@ant-design/icons";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
selectedConversation: selectSelectedConversation, selectedConversation: selectSelectedConversation,
chatVisible: selectChatVisible,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
toggleChatVisible: () => dispatch(toggleChatVisible()), toggleChatVisible: () => dispatch(toggleChatVisible()),
}); });
export function ChatPopupComponent({ export function ChatPopupComponent({
conversationList, chatVisible,
selectedConversation, selectedConversation,
toggleChatVisible, toggleChatVisible,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
return (
<div className="chat-popup">
<div style={{ display: "flex", alignItems: "center" }}>
<Typography.Title level={4}>
{t("messaging.labels.messaging")}
</Typography.Title>
<ChatNewConversation />
<Tooltip title={t("messaging.labels.recentonly")}>
<InfoCircleOutlined />
</Tooltip>
</div>
<ShrinkOutlined
onClick={() => toggleChatVisible()}
style={{ position: "absolute", right: ".5rem", top: ".5rem" }}
/>
<Row gutter={[8, 8]} className="chat-popup-content"> const { loading, data, refetch, called } = useQuery(
<Col span={8}> CONVERSATION_LIST_QUERY,
<ChatConversationListComponent conversationList={conversationList} /> {}
</Col> );
<Col span={16}>
{selectedConversation ? <ChatConversationContainer /> : null} useEffect(() => {
</Col> if (called && chatVisible) refetch();
</Row> }, [chatVisible, called, refetch]);
</div>
const unreadCount = data
? data.conversations.reduce(
(acc, val) => val.messages_aggregate.aggregate.count + acc,
0
)
: 0;
return (
<Badge count={unreadCount}>
<Card size="small">
{chatVisible ? (
<div className="chat-popup">
<Space align="center">
<Typography.Title level={4}>
{t("messaging.labels.messaging")}
</Typography.Title>
<ChatNewConversation />
<Tooltip title={t("messaging.labels.recentonly")}>
<InfoCircleOutlined />
</Tooltip>
<SyncOutlined
style={{ cursor: "pointer" }}
onClick={() => refetch()}
/>
</Space>
<ShrinkOutlined
onClick={() => toggleChatVisible()}
style={{ position: "absolute", right: ".5rem", top: ".5rem" }}
/>
<Row gutter={[8, 8]} className="chat-popup-content">
<Col span={8}>
{loading ? (
<LoadingSpinner />
) : (
<ChatConversationListComponent
conversationList={data ? data.conversations : []}
/>
)}
</Col>
<Col span={16}>
{selectedConversation ? <ChatConversationContainer /> : null}
</Col>
</Row>
</div>
) : (
<div
onClick={() => toggleChatVisible()}
style={{ cursor: "pointer" }}
>
<MessageOutlined />
<strong>{t("messaging.labels.messaging")}</strong>
</div>
)}
</Card>
</Badge>
); );
} }
export default connect(mapStateToProps, mapDispatchToProps)(ChatPopupComponent); export default connect(mapStateToProps, mapDispatchToProps)(ChatPopupComponent);

View File

@@ -2,8 +2,7 @@ import { getAnalytics, logEvent } from "firebase/analytics";
import { initializeApp } from "firebase/app"; import { initializeApp } from "firebase/app";
import { getAuth, updatePassword, updateProfile } from "firebase/auth"; import { getAuth, updatePassword, updateProfile } from "firebase/auth";
import { getFirestore } from "firebase/firestore"; import { getFirestore } from "firebase/firestore";
import { tracker } from "../App/App.container"; import { getMessaging, getToken, onMessage } from "firebase/messaging";
//import { getMessaging } from "firebase/messaging";
import { store } from "../redux/store"; import { store } from "../redux/store";
const config = JSON.parse(process.env.REACT_APP_FIREBASE_CONFIG); const config = JSON.parse(process.env.REACT_APP_FIREBASE_CONFIG);
@@ -40,17 +39,34 @@ export const updateCurrentPassword = async (password) => {
return updatePassword(currentUser, password); return updatePassword(currentUser, password);
}; };
//let messaging; export const messaging = getMessaging();
// try {
// messaging = getMessaging();
// // Project Settings => Cloud Messaging => Web Push certificates
// messaging.usePublicVapidKey(process.env.REACT_APP_FIREBASE_PUBLIC_VAPID_KEY);
// console.log("[FCM UTIL] FCM initialized successfully.");
// } catch {
// console.log("[FCM UTIL] Firebase Messaging is likely unsupported.");
// }
// export { messaging }; export const requestForToken = () => {
return getToken(messaging, {
vapidKey: process.env.REACT_APP_FIREBASE_PUBLIC_VAPID_KEY,
})
.then((currentToken) => {
if (currentToken) {
console.log("current token for client: ", currentToken);
// Perform any other necessary action with the token
} else {
// Show permission request UI
console.log(
"No registration token available. Request permission to generate one."
);
}
})
.catch((err) => {
console.log("An error occurred while retrieving token. ", err);
});
};
export const onMessageListener = () =>
new Promise((resolve) => {
onMessage(messaging, (payload) => {
console.log("Inbound FCM Message", payload);
resolve(payload);
});
});
export const logImEXEvent = (eventName, additionalParams, stateProp = null) => { export const logImEXEvent = (eventName, additionalParams, stateProp = null) => {
const state = stateProp || store.getState(); const state = stateProp || store.getState();
@@ -70,9 +86,6 @@ export const logImEXEvent = (eventName, additionalParams, stateProp = null) => {
// eventParams // eventParams
// ); // );
logEvent(analytics, eventName, eventParams); logEvent(analytics, eventName, eventParams);
//Log event to OpenReplay server.
tracker.event(eventName, eventParams);
}; };
// if (messaging) { // if (messaging) {

View File

@@ -1,15 +1,54 @@
import { gql } from "@apollo/client"; import { gql } from "@apollo/client";
export const CONVERSATION_LIST_SUBSCRIPTION = gql` // export const CONVERSATION_LIST_SUBSCRIPTION = gql`
subscription CONVERSATION_LIST_SUBSCRIPTION { // subscription CONVERSATION_LIST_SUBSCRIPTION {
// conversations(
// order_by: { updated_at: desc }
// limit: 50
// where: { archived: { _eq: false } }
// ) {
// phone_num
// id
// updated_at
// unreadcnt
// messages_aggregate(
// where: { read: { _eq: false }, isoutbound: { _eq: false } }
// ) {
// aggregate {
// count
// }
// }
// job_conversations {
// job {
// id
// ro_number
// ownr_fn
// ownr_ln
// ownr_co_nm
// }
// }
// }
// }
// `;
export const CONVERSATION_LIST_QUERY = gql`
query CONVERSATION_LIST_QUERY {
conversations( conversations(
order_by: { updated_at: desc } order_by: { updated_at: desc }
limit: 100 limit: 50
where: { archived: { _eq: false } } where: { archived: { _eq: false } }
) { ) {
phone_num phone_num
id id
updated_at updated_at
unreadcnt
messages_aggregate(
where: { read: { _eq: false }, isoutbound: { _eq: false } }
) {
aggregate {
count
}
}
job_conversations { job_conversations {
job { job {
id id
@@ -17,17 +56,6 @@ export const CONVERSATION_LIST_SUBSCRIPTION = gql`
ownr_fn ownr_fn
ownr_ln ownr_ln
ownr_co_nm ownr_co_nm
owner {
id
allow_text_message
}
}
}
messages_aggregate(
where: { read: { _eq: false }, isoutbound: { _eq: false } }
) {
aggregate {
count
} }
} }
} }
@@ -36,24 +64,26 @@ export const CONVERSATION_LIST_SUBSCRIPTION = gql`
export const CONVERSATION_SUBSCRIPTION_BY_PK = gql` export const CONVERSATION_SUBSCRIPTION_BY_PK = gql`
subscription CONVERSATION_SUBSCRIPTION_BY_PK($conversationId: uuid!) { subscription CONVERSATION_SUBSCRIPTION_BY_PK($conversationId: uuid!) {
messages(
order_by: { created_at: asc_nulls_first }
where: { conversationid: { _eq: $conversationId } }
) {
id
status
text
isoutbound
image
image_path
userid
created_at
read
}
}
`;
export const GET_CONVERSATION_DETAILS = gql`
query GET_CONVERSATION_DETAILS($conversationId: uuid!) {
conversations_by_pk(id: $conversationId) { conversations_by_pk(id: $conversationId) {
messages(order_by: { created_at: asc_nulls_first }) {
id
status
text
isoutbound
image
image_path
userid
created_at
}
messages_aggregate(
where: { read: { _eq: false }, isoutbound: { _eq: false } }
) {
aggregate {
count
}
}
id id
phone_num phone_num
archived archived

View File

@@ -8,6 +8,8 @@ export const MARK_MESSAGES_AS_READ_BY_CONVERSATION = gql`
) { ) {
returning { returning {
id id
read
isoutbound
} }
} }
} }

View File

@@ -5,13 +5,12 @@ import preval from "preval.macro";
import React, { lazy, Suspense, useEffect } from "react"; import React, { lazy, Suspense, useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import ErrorBoundary from "../../components/error-boundary/error-boundary.component";
import { Link, Route, Switch } from "react-router-dom"; import { Link, Route, Switch } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import BreadCrumbs from "../../components/breadcrumbs/breadcrumbs.component"; import BreadCrumbs from "../../components/breadcrumbs/breadcrumbs.component";
import ChatAffixContainer from "../../components/chat-affix/chat-affix.container"; import ChatAffixContainer from "../../components/chat-affix/chat-affix.container";
import ConflictComponent from "../../components/conflict/conflict.component"; import ConflictComponent from "../../components/conflict/conflict.component";
import FcmNotification from "../../components/fcm-notification/fcm-notification.component"; import ErrorBoundary from "../../components/error-boundary/error-boundary.component";
//import FooterComponent from "../../components/footer/footer.component"; //import FooterComponent from "../../components/footer/footer.component";
//Component Imports //Component Imports
import HeaderContainer from "../../components/header/header.container"; import HeaderContainer from "../../components/header/header.container";
@@ -20,12 +19,11 @@ import PartnerPingComponent from "../../components/partner-ping/partner-ping.com
import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container"; import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container";
import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component"; import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component";
import TestComponent from "../../components/_test/test.component"; import TestComponent from "../../components/_test/test.component";
import { QUERY_STRIPE_ID } from "../../graphql/bodyshop.queries"; import { requestForToken } from "../../firebase/firebase.utils";
import { import {
selectBodyshop, selectBodyshop,
selectInstanceConflict, selectInstanceConflict,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import client from "../../utils/GraphQLClient";
import "./manage.page.styles.scss"; import "./manage.page.styles.scss";
const ManageRootPage = lazy(() => const ManageRootPage = lazy(() =>
@@ -166,16 +164,15 @@ const Dms = lazy(() => import("../dms/dms.container"));
const { Content, Footer } = Layout; const { Content, Footer } = Layout;
const stripePromise = new Promise((resolve, reject) => { const stripePromise = new Promise((resolve, reject) => {
client.query({ query: QUERY_STRIPE_ID }).then((resp) => { // client.query({ query: QUERY_STRIPE_ID }).then((resp) => {
if (resp.data.bodyshops[0]) // if (resp.data.bodyshops[0])
resolve( resolve(
loadStripe(process.env.REACT_APP_STRIPE_PUBLIC_KEY, { loadStripe(process.env.REACT_APP_STRIPE_PUBLIC_KEY, {
stripeAccount: stripeAccount: "No Stripe Id Resolve",
resp.data.bodyshops[0].stripe_acct_id || "No Stripe Id Resolve", })
}) );
); // reject();
reject(); // });
});
}); });
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -188,7 +185,9 @@ export function Manage({ match, conflict, bodyshop }) {
useEffect(() => { useEffect(() => {
const widgetId = "IABVNO4scRKY11XBQkNr"; const widgetId = "IABVNO4scRKY11XBQkNr";
window.noticeable.render("widget", widgetId); window.noticeable.render("widget", widgetId);
requestForToken();
}, []); }, []);
useEffect(() => { useEffect(() => {
document.title = t("titles.app"); document.title = t("titles.app");
}, [t]); }, [t]);
@@ -395,7 +394,6 @@ export function Manage({ match, conflict, bodyshop }) {
<HeaderContainer /> <HeaderContainer />
<Content className="content-container"> <Content className="content-container">
<FcmNotification />
<PartnerPingComponent /> <PartnerPingComponent />
<ErrorBoundary>{PageContent}</ErrorBoundary> <ErrorBoundary>{PageContent}</ErrorBoundary>
<BackTop /> <BackTop />

View File

@@ -5,7 +5,7 @@ import { connect } from "react-redux";
import { Redirect, Route, Switch } from "react-router-dom"; import { Redirect, Route, Switch } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import ErrorBoundary from "../../components/error-boundary/error-boundary.component"; import ErrorBoundary from "../../components/error-boundary/error-boundary.component";
import FcmNotification from "../../components/fcm-notification/fcm-notification.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import TechHeader from "../../components/tech-header/tech-header.component"; import TechHeader from "../../components/tech-header/tech-header.component";
import TechSider from "../../components/tech-sider/tech-sider.component"; import TechSider from "../../components/tech-sider/tech-sider.component";
@@ -58,7 +58,6 @@ export function TechPage({ technician, match }) {
{technician ? null : <Redirect to={`${match.path}/login`} />} {technician ? null : <Redirect to={`${match.path}/login`} />}
<TechHeader /> <TechHeader />
<Content className="tech-content-container"> <Content className="tech-content-container">
<FcmNotification />
<ErrorBoundary> <ErrorBoundary>
<Suspense <Suspense
fallback={ fallback={

View File

@@ -899,10 +899,12 @@
"selectall": "Select All", "selectall": "Select All",
"senderrortosupport": "Send Error to Support", "senderrortosupport": "Send Error to Support",
"submit": "Submit", "submit": "Submit",
"tryagain": "Try Again",
"view": "View", "view": "View",
"viewreleasenotes": "See What's Changed" "viewreleasenotes": "See What's Changed"
}, },
"errors": { "errors": {
"fcm": "You must allow notification permissions to have real time messaging. Click to try again.",
"notfound": "No record was found." "notfound": "No record was found."
}, },
"itemtypes": { "itemtypes": {
@@ -925,6 +927,7 @@
"exceptiontitle": "An error has occurred.", "exceptiontitle": "An error has occurred.",
"friday": "Friday", "friday": "Friday",
"globalsearch": "Global Search", "globalsearch": "Global Search",
"help": "Help",
"hours": "hrs", "hours": "hrs",
"in": "In", "in": "In",
"instanceconflictext": "Your $t(titles.app) account can only be used on one device at any given time. Refresh your session to take control.", "instanceconflictext": "Your $t(titles.app) account can only be used on one device at any given time. Refresh your session to take control.",

View File

@@ -899,10 +899,12 @@
"selectall": "", "selectall": "",
"senderrortosupport": "", "senderrortosupport": "",
"submit": "", "submit": "",
"tryagain": "",
"view": "", "view": "",
"viewreleasenotes": "" "viewreleasenotes": ""
}, },
"errors": { "errors": {
"fcm": "",
"notfound": "" "notfound": ""
}, },
"itemtypes": { "itemtypes": {
@@ -925,6 +927,7 @@
"exceptiontitle": "", "exceptiontitle": "",
"friday": "", "friday": "",
"globalsearch": "", "globalsearch": "",
"help": "",
"hours": "", "hours": "",
"in": "en", "in": "en",
"instanceconflictext": "", "instanceconflictext": "",

View File

@@ -899,10 +899,12 @@
"selectall": "", "selectall": "",
"senderrortosupport": "", "senderrortosupport": "",
"submit": "", "submit": "",
"tryagain": "",
"view": "", "view": "",
"viewreleasenotes": "" "viewreleasenotes": ""
}, },
"errors": { "errors": {
"fcm": "",
"notfound": "" "notfound": ""
}, },
"itemtypes": { "itemtypes": {
@@ -925,6 +927,7 @@
"exceptiontitle": "", "exceptiontitle": "",
"friday": "", "friday": "",
"globalsearch": "", "globalsearch": "",
"help": "",
"hours": "", "hours": "",
"in": "dans", "in": "dans",
"instanceconflictext": "", "instanceconflictext": "",

View File

@@ -91,13 +91,11 @@ export default function CiecaSelect(parts = true, labor = true) {
} }
export function GetPartTypeName(part_type) { export function GetPartTypeName(part_type) {
console.log(part_type);
if (!part_type) return null; if (!part_type) return null;
return i18n.t(`joblines.fields.part_types.${part_type.toUpperCase()}`); return i18n.t(`joblines.fields.part_types.${part_type.toUpperCase()}`);
} }
export function Get(part_type) { export function Get(part_type) {
console.log(part_type);
if (!part_type) return null; if (!part_type) return null;
return i18n.t(`joblines.fields.part_types.${part_type.toUpperCase()}`); return i18n.t(`joblines.fields.part_types.${part_type.toUpperCase()}`);
} }

View File

@@ -50,13 +50,18 @@ const roundTripLink = new ApolloLink((operation, forward) => {
const TrackExecutionTime = async (operationName, time) => { const TrackExecutionTime = async (operationName, time) => {
const rdxStore = store.getState(); const rdxStore = store.getState();
try { try {
console.log("trying");
axios.post("/ioevent", { axios.post("/ioevent", {
operationName, operationName,
time, time,
dbevent: true, dbevent: true,
user: rdxStore.user.currentUser.email, user:
imexshopid: rdxStore.user.bodyshop.imexshopid, rdxStore.user &&
rdxStore.user.currentUser &&
rdxStore.user.currentUser.email,
imexshopid:
rdxStore.user &&
rdxStore.user.bodyshop &&
rdxStore.user.bodyshop.imexshopid,
}); });
} catch (error) { } catch (error) {
console.log("IOEvent Error", error); console.log("IOEvent Error", error);

View File

@@ -148,8 +148,6 @@ export async function RenderTemplates(
}, },
}; };
console.log("reportRequest", reportRequest);
try { try {
const render = await jsreport.renderAsync(reportRequest); const render = await jsreport.renderAsync(reportRequest);
if (!renderAsHtml) { if (!renderAsHtml) {

View File

@@ -0,0 +1,34 @@
export default async function FcmHandler({ client, payload }) {
console.log("Handling payload type", payload);
switch (payload.type) {
case "messaging-inbound":
client.cache.modify({
id: client.cache.identify({
__typename: "conversations",
id: payload.conversationid,
}),
fields: {
messages_aggregate(cached) {
return { aggregate: { count: cached.aggregate.count + 1 } };
},
},
});
break;
case "messaging-mark-conversation-read":
client.cache.modify({
id: client.cache.identify({
__typename: "conversations",
id: payload.conversationid,
}),
fields: {
messages_aggregate(cached) {
return { aggregate: { count: 0 } };
},
},
});
break;
default:
console.log("No payload type set.");
break;
}
}

View File

@@ -1172,11 +1172,12 @@
- active: - active:
_eq: true _eq: true
columns: columns:
- id
- created_at
- updated_at
- bodyshopid - bodyshopid
- created_at
- id
- phone_num - phone_num
- unreadcnt
- updated_at
select_permissions: select_permissions:
- role: user - role: user
permission: permission:
@@ -1186,6 +1187,7 @@
- created_at - created_at
- id - id
- phone_num - phone_num
- unreadcnt
- updated_at - updated_at
filter: filter:
bodyshop: bodyshop:
@@ -1206,6 +1208,7 @@
- created_at - created_at
- id - id
- phone_num - phone_num
- unreadcnt
- updated_at - updated_at
filter: filter:
bodyshop: bodyshop:

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."conversations" add column "unreadcnt" numeric
-- not null default '0';

View File

@@ -0,0 +1,2 @@
alter table "public"."conversations" add column "unreadcnt" numeric
not null default '0';

View File

@@ -66,7 +66,6 @@ app.get("/test", async function (req, res) {
"git rev-parse --short HEAD" "git rev-parse --short HEAD"
); );
logger.log("test-api-status", "DEBUG", "api", { commit }); logger.log("test-api-status", "DEBUG", "api", { commit });
res.status(200).send(`OK - ${commit}`); res.status(200).send(`OK - ${commit}`);
}); });
@@ -114,6 +113,7 @@ app.post(
twilio.webhook({ validate: process.env.NODE_ENV === "PRODUCTION" }), twilio.webhook({ validate: process.env.NODE_ENV === "PRODUCTION" }),
smsStatus.status smsStatus.status
); );
app.post("/sms/markConversationRead", smsStatus.markConversationRead);
var job = require("./server/job/job"); var job = require("./server/job/job");
app.post("/job/totals", fb.validateFirebaseIdToken, job.totals); app.post("/job/totals", fb.validateFirebaseIdToken, job.totals);
@@ -133,9 +133,10 @@ app.post("/render/inlinecss", fb.validateFirebaseIdToken, inlineCss.inlinecss);
app.post( app.post(
"/notifications/send", "/notifications/send",
fb.validateFirebaseIdToken,
fb.sendNotification fb.sendNotification
); );
app.post("/notifications/subscribe", fb.validateFirebaseIdToken, fb.subscribe);
app.post("/adm/updateuser", fb.validateFirebaseIdToken, fb.updateUser); app.post("/adm/updateuser", fb.validateFirebaseIdToken, fb.updateUser);
//Stripe Processing //Stripe Processing

View File

@@ -74,31 +74,48 @@ exports.updateUser = (req, res) => {
}); });
}; };
exports.sendNotification = (req, res) => { exports.sendNotification = async (req, res) => {
var registrationToken = setTimeout(() => {
"fqIWg8ENDFyrRrMWJ1sItR:APA91bHirdZ05Zo66flMlvala97SMXoiQGwP4oCvMwd-vVrSauD_WoNim3kXHGqyP-bzENjkXwA5icyUAReFbeHn6dIaPcbpcsXuY73-eJAXvZiu1gIsrd1BOsnj3dEMT7Q4F6mTPth1"; // Send a message to the device corresponding to the provided
var message = { // registration token.
notification: { title: "The Title", body: "The Body" }, admin
data: { .messaging()
jobid: "1234", .send({
}, topic: "PRD_PATRICK-messaging",
token: registrationToken, notification: {
}; title: `ImEX Online Message - +16049992002"`,
body: "Test Noti.",
imageUrl: "https://thinkimex.com/img/logo512.png",
},
data: {
type: "messaging-inbound",
conversationid: "e0eb17c3-3a78-4e3f-b932-55ef35aa2297",
text: "Hello. ",
image_path: "",
phone_num: "+16049992002",
},
})
.then((response) => {
// Response is a message ID string.
console.log("Successfully sent message:", response);
})
.catch((error) => {
console.log("Error sending message:", error);
});
// Send a message to the device corresponding to the provided res.sendStatus(200);
// registration token. }, 500);
admin };
exports.subscribe = async (req, res) => {
const result = await admin
.messaging() .messaging()
.send(message) .subscribeToTopic(
.then((response) => { req.body.fcm_tokens,
// Response is a message ID string. `${req.body.imexshopid}-${req.body.type}`
console.log("Successfully sent message:", response); );
})
.catch((error) => {
console.log("Error sending message:", error);
});
res.sendStatus(200); res.json(result);
}; };
exports.validateFirebaseIdToken = async (req, res, next) => { exports.validateFirebaseIdToken = async (req, res, next) => {

View File

@@ -22,48 +22,74 @@ mutation UNARCHIVE_CONVERSATION($id: uuid!) {
exports.RECEIVE_MESSAGE = ` exports.RECEIVE_MESSAGE = `
mutation RECEIVE_MESSAGE($msg: [messages_insert_input!]!) { mutation RECEIVE_MESSAGE($msg: [messages_insert_input!]!) {
insert_messages(objects: $msg) {
insert_messages(objects: $msg) {
returning { returning {
conversation { conversation {
id id
archived archived
bodyshop { bodyshop{
associations(where: {active: {_eq: true}}) { imexshopid
user {
fcmtokens
}
}
} }
created_at
updated_at
unreadcnt
phone_num
} }
conversationid
created_at
id
image_path
image
isoutbound
msid
read
text
updated_at
status
userid
} }
} }
} }
`; `;
exports.INSERT_MESSAGE = ` exports.INSERT_MESSAGE = `
mutation INSERT_MESSAGE($msg: [messages_insert_input!]!, $conversationid: uuid!) { mutation INSERT_MESSAGE($msg: [messages_insert_input!]!, $conversationid: uuid!) {
update_conversations_by_pk(pk_columns: {id: $conversationid}, _set: {archived: false}) { update_conversations_by_pk(pk_columns: {id: $conversationid}, _set: {archived: false}) {
id id
archived
} }
insert_messages(objects: $msg) { insert_messages(objects: $msg) {
returning { returning {
conversation { conversation {
id id
archived archived
bodyshop { bodyshop{
associations(where: {active: {_eq: true}}) { imexshopid
user {
fcmtokens
}
}
} }
created_at
updated_at
unreadcnt
phone_num
} }
conversationid
created_at
id
image_path
image
isoutbound
msid
read
text
updated_at
status
userid
} }
} }
} }
`; `;
exports.UPDATE_MESSAGE_STATUS = ` exports.UPDATE_MESSAGE_STATUS = `

View File

@@ -9,7 +9,7 @@ require("dotenv").config({
const client = require("../graphql-client/graphql-client").client; const client = require("../graphql-client/graphql-client").client;
const queries = require("../graphql-client/queries"); const queries = require("../graphql-client/queries");
const { phone } = require("phone"); const { phone } = require("phone");
const admin = require("../firebase/firebase-handler").admin; const { admin } = require("../firebase/firebase-handler");
const logger = require("../utils/logger"); const logger = require("../utils/logger");
exports.receive = async (req, res) => { exports.receive = async (req, res) => {
//Perform request validation //Perform request validation
@@ -78,21 +78,43 @@ exports.receive = async (req, res) => {
}); });
} }
try { try {
let insertresp;
if (response.bodyshops[0].conversations[0]) { if (response.bodyshops[0].conversations[0]) {
const r3 = await client.request(queries.INSERT_MESSAGE, { insertresp = await client.request(queries.INSERT_MESSAGE, {
msg: newMessage, msg: newMessage,
conversationid: conversationid:
response.bodyshops[0].conversations[0] && response.bodyshops[0].conversations[0] &&
response.bodyshops[0].conversations[0].id, response.bodyshops[0].conversations[0].id,
}); });
} else { } else {
const r2 = await client.request(queries.RECEIVE_MESSAGE, { insertresp = await client.request(queries.RECEIVE_MESSAGE, {
msg: newMessage, msg: newMessage,
}); });
} }
const message = insertresp.insert_messages.returning[0];
const data = {
type: "messaging-inbound",
conversationid: message.conversationid || "",
text: message.text || "",
image_path: message.image_path || "",
image: (message.image && message.image.toString()) || "",
messageid: message.id || "",
phone_num: message.conversation.phone_num || "",
};
const fcmresp = await admin.messaging().send({
topic: `${message.conversation.bodyshop.imexshopid}-messaging`,
notification: {
title: `ImEX Online Message - ${data.phone_num}`,
body: message.image_path ? `Image ${message.text}` : message.text,
imageUrl: "https://thinkimex.com/img/logo512.png",
},
data,
});
logger.log("sms-inbound-success", "DEBUG", "api", null, { logger.log("sms-inbound-success", "DEBUG", "api", null, {
newMessage, newMessage,
fcmresp,
}); });
res.status(200).send(""); res.status(200).send("");
} catch (e2) { } catch (e2) {

View File

@@ -10,6 +10,7 @@ const client = require("../graphql-client/graphql-client").client;
const queries = require("../graphql-client/queries"); const queries = require("../graphql-client/queries");
const { phone } = require("phone"); const { phone } = require("phone");
const logger = require("../utils/logger"); const logger = require("../utils/logger");
const { admin } = require("../firebase/firebase-handler");
exports.status = (req, res) => { exports.status = (req, res) => {
const { SmsSid, SmsStatus } = req.body; const { SmsSid, SmsStatus } = req.body;
@@ -34,6 +35,23 @@ exports.status = (req, res) => {
res.sendStatus(200); res.sendStatus(200);
}; };
exports.markConversationRead = async (req, res) => {
const { conversationid, imexshopid } = req.body;
admin.messaging().send({
topic: `${imexshopid}-messaging`,
// notification: {
// title: `ImEX Online Message - ${data.phone_num}`,
// body: message.image_path ? `Image ${message.text}` : message.text,
// imageUrl: "https://thinkimex.com/img/logo512.png",
// },
data: {
type: "messaging-mark-conversation-read",
conversationid: conversationid || "",
},
});
res.send(200);
};
// Inbound Sample // Inbound Sample
// { // {
// "SmsSid": "SM5205ea340e06437799d9345e7283457c", // "SmsSid": "SM5205ea340e06437799d9345e7283457c",

View File

@@ -5,13 +5,14 @@ const logger = new graylog2.graylog({
}); });
function log(message, type, user, record, object) { function log(message, type, user, record, object) {
console.log(message, { if (type !== "ioevent")
type, console.log(message, {
env: process.env.NODE_ENV || "development", type,
user, env: process.env.NODE_ENV || "development",
record, user,
...object, record,
}); ...object,
});
logger.log(message, { logger.log(message, {
type, type,
env: process.env.NODE_ENV || "development", env: process.env.NODE_ENV || "development",