· 4 years ago · May 28, 2021, 12:44 AM
1/*
2DATABASE
3 | /\
4 | |
5 \/ |
6mainService <---> facebookService, emailService, etc <---> third party APIs
7 | /\ (these dont talk to DB)
8 | |
9 \/ |
10Frontend
11
12(front end only ever talks to mainService)
13
14*/
15
16
17
18// METRICS
19
20// my top metric here is the ability for this system to scale with new services being added.
21// the main goal is that in 5 years when the amount of channels to chat through has been tripled,
22// the core architechture here should only have been added onto.
23// also the man hours needed to add new channels
24
25// backend scalabity is also key since the amount of active threads could reach millions
26
27// the key metric from a customers perspective is that this app should reduce
28// time/effort to assign/resolve issues
29
30
31// DEFINITIONS
32
33// AGENT: a customer representative who chats with visitors to their website
34// PORTAL: a SMB's account with agents and threads and contacts
35
36
37//DATA STRUCTURES
38
39channelObj: {
40 contactInfo: {},
41 channelInfo: {
42 channelType: ['FACEBOOK', 'EMAIL', 'etc']
43 }
44}
45
46Thread: {
47 channel: channelObj,
48 inbox: inboxObj,
49 assignedAgent,
50 mostRecentMessage: Message,
51 timeWaitingForResponse
52}
53
54Message: {
55 senderId,
56 timestamp,
57 text,
58 richContent: {
59 link
60 attachment
61 }
62}
63
64Portal {
65 agents: [],
66 threads: [],
67 contacts
68}
69
70 agent {
71 fullName,
72 email,
73 etc
74}
75
76
77// SERVICES
78
79mainService {
80 threads = []
81 portals = []
82 agents = []
83
84 // this will (by default) return the unassigned threadlist
85 GET getUserAndThreadListAndPortalData() {
86 returns {
87 agent,
88 threadList,
89 portalInformation
90 }
91 }
92
93 POST makeNewThread()
94 GET getThread()
95
96 POST sendMessage(thread, message){
97 // dynamic dispatch to more specific service
98 thread.channel.publishAgentMessage(message)
99 }
100
101 GET getMessages(thread) {
102 // dynamic dispatch to more specific service
103 thread.channel.getMessages(message)
104 }
105
106 GET openRealtimeConnection() {
107 // returns data needed for realtime connection
108 }
109}
110
111interface channelService {
112 GET getMessages(thread)
113
114 POST publishAgentMessage()
115}
116
117facebookService {
118 GET getMessages(thread) {
119 //calls to facebook api
120 }
121
122 POST publishAgentMessage(){
123 // this methods parses out rich content from each message into facebook-understandable data
124 // and then calls to facebook api
125 }
126
127 // each custom service also implements more specific methods such as a webhook
128 WEBHOOK() {
129 // responds to facebook actions and makes the proper calls
130 // to our main service (close thread, give data for contacts, update most recent message on thread)
131
132 // this is also where automation would happen and where the service would send stuff like bot messages
133 mainService.getTheAutomatedResponse()
134 }
135}
136
137emailChannel {
138 GET getMessages(thread) {
139 //calls to email api
140 }
141
142 POST publishAgentMessage(){
143 //sends email
144 }
145
146 POST receiveEmail() { //(webhook?)
147 // does things to thread if need be, similar to FB webhook
148 }
149}
150
151// more services here
152
153
154
155
156//frontend
157
158
159// APP LOAD FLOW
160// STEP 1: fetch user data from token
161// STEP 2a-d: load stores with data and open realtime connection
162// STEP 3: fetch the currently open thread
163// app is ready
164
165openThreadStore.js {
166 // only has one open thread
167 OpenThreadObject = {
168 asyncStatus: oneOf [UNINITIALIZED, STARTED, FAILED, SUCCEEDED],
169 messages: [],
170 contacts: [],
171 threadId: null
172 }
173
174 // staged messages are messages that have been sent but not recieved by the backend yet,
175 // this is here so we can render instantly
176 stagedMessages = []
177
178 fetchOpenThreadSucceeded(data) {
179 OpenThreadObject = data
180 }
181}
182
183threadListStore {
184
185 // each thread is an id along with any other information needed in the threadlist (unread message count etc)
186 threads = []
187
188 // by default only show threads that are unassigned
189 sortingParams = {}
190
191 fetchNewThreadList() {
192 // fetch a new thread list based on what agents are assigned or time left without a response, etc
193
194 res = await fetch(sortingParams)
195 threads = res.json()
196
197 // maybe dont replace the open thread here? it might be bad UX
198 }
199
200 // STEP 2a: save list of threads into thread list store
201 onUserPortalAndThreadDataFetched() {
202 //save list of threads into this store
203 }
204}
205
206
207
208userStore {
209 // has current user data
210 activeUser = {
211 userId,
212 token,
213 portalId
214 }
215
216 // STEP 2b: save list of threads into thread list store
217 onUserPortalAndThreadDataFetched() {
218 //save user data into this store
219 }
220}
221
222PortalStore {
223 portal = {
224 portalId
225 }
226
227 //STEP 2c: fetch portal data
228 onUserPortalAndThreadDataFetched() {
229 // save portal data into this store
230 }
231}
232
233
234
235// only has one open realtime connection, which should get called for anything related to the user
236// each message should come in with the data needed to determine which thread its on
237realtimeStore.js {
238 RealtimeConnection = {
239 realtimeData,
240
241 onGetMessage() {
242 // the thread should always exist here
243
244 // add new message to thread
245 // remove message from stagedMessages
246
247 // if the thread is not the opened thread, add the message to thread list, thread.mostRecentMessage.
248 },
249
250 onSendMessage(message) {
251 // add to stagedMessages so that we can render them instantly,
252 // then as the messages come in from the realtime we can remove them from stagedMessages
253 }
254 }
255
256 //STEP 2d: open realtime connection
257 onUserPortalAndThreadDataFetched(data) {
258 // initialize the realtime connection
259
260 // once this is done, fetch main thread data
261 }
262}
263
264appStore.js {
265 // this handles UI things
266
267}
268
269ThreadSortComponent {
270 // contains all the sorting selections for the threadlist (unassigned, mythreads, etc)
271 onClick={fetchNewThreadList}
272}
273
274
275threadComponent {
276 // rendering is BLOCKED until fetchUserAndPortalData is done and the realtime connection is opened
277
278 // STEP 3: fetch open thread data
279 fetchOpenedThread()
280}
281
282
283ThreadListComponent {
284 // one of these is rendered for each thread in the threadStore
285 // onclick fetches a new OpenThreadObject and replaces value in the store
286}
287
288App.js {
289 // STEP 1: fetch user data from token
290 // this will get caught in the realtime store, the user store, the portal store, and the threadlist store
291 fetchUserAndPortalData()
292}
293
294
295// dont trigger requests from reducers, only do it within the render of components
296