Service oriented architecture with two backends:
ChatServer
:- publishes user join requests in
Redis pub/sub
,users
channel - enables
User
andSupport
to exchange messages via chat usingWebSockets
- publishes user join requests in
Subscriber
:- subscribes to
users
channel inRedis pub/sub
and forwards user join requests to UI usingWebSockets
- subscribes to
Requirements for running the whole project:
- Docker engine
Instructions:
- clone the repo
- cd into repo
- start docker engine
- run:
docker-compose up
(it startsChatServer
,Subscriber
andRedis
in three different docker containers which are connected via docker network)
Protocol and flow description for building UI:
- Register
User
inChatServer
by sending HTTP request to:GET localhost:9000/user/join/{userName}
- Expect
JSON
response:
{ "userId": "9d6d2db0cdb84d8ba67105670d94d641", "username": "Nika", "chatId": "d2464b5386044daf9e36ffd414260f67" }
- Expect
- Initiate WebSocket connection for
User
inChatServer
by sending request to:GET localhost:9000/chat/{chatId}
- server immediately starts sending
ping
message for each second, UI must respond with:pong:user
(covers heartbeat functionality) - for requesting to join, send message:
{ "type": "Join", "args": { "userId": "9d6d2db0cdb84d8ba67105670d94d641", "username": "Nika", "from": "User" } }
- Expect
JSON
response:
{ "type": "UserJoined", "args": { "username": "Nika", "userId": "9d6d2db0cdb84d8ba67105670d94d641", "chatId": "d2464b5386044daf9e36ffd414260f67" } }
- server immediately starts sending
- Initiate WebSocket connection for
Support
inSubscriber
by sending request to:GET localhost:9001/users
- server immediately starts sending
ping
message for each second, UI must respond with:pong:support
(covers heartbeat functionality) - for loading pending users send this WS message, it also subscribes to
Redis PubSub
and reads newly joined users{ "type": "Load" }
- Expect one re more
JSON
WebSocket messages (TODO - consider sending one message with list of users instead of single message for each user): for pending usersfor newly joined users{ "username": "Nika", "userId": "9d6d2db0cdb84d8ba67105670d94d641", "chatId": "d2464b5386044daf9e36ffd414260f67" }
{ "type": "UserJoined", "args": { "username": "Dodo", "userId": "b646273c1f8840b1ad296b40ef62f2c5", "chatId": "2500f760be124d299f214c5f3aa0ecf2" } }
- once
Support
clicks to the special user request, UI must send theJoinUser
as websocket message so that it gets filtered out for otherSupport
agents, at the same time UI must send another request toChatServer
-step 4
(details instep 4
)JoinUser
looks like
as a response the backend sends following WebSocket message:{ "type": "JoinUser", "args": { "userId": "8e0a7c3e1b2f489f8ce129a5167f71b5", "chatId": "0765d196ec4e4d108b9e4b6fca7c8254", "username": "Zaza" } }
This message will be broadcasted to all support UI-s and Frontend will be able to drop this user from the list{ "type": "RemoveUser", "args": { "userId": "8e0a7c3e1b2f489f8ce129a5167f71b5" } }
- server immediately starts sending
- Initiate WebSocket connection for
Support
inChatServer
by sending request to:GET localhost:9000/chat/{chatId}
- attach request body:
{ "type": "Join", "args": { "userId" : "9d6d2db0cdb84d8ba67105670d94d641", "supportUserName": "Vika", "username": "Nika", "from": "Support" } }
- Expect
JSON
response:{ "type": "SupportJoinedUser", "args": { "supportId": "1939d1c378ea45809521c16668c21c10", "supportUserName": "Kiku", "username": "f3eb5276f29c4971827d31acd12327a7", "userId": "Gela", "chatId": "91a68f25d5e5444aaaa5dc47ebf7d893" } }
- Send chat message to
Support
fromUser
by sending the following WebSocket text message on the opened WS connection:{ "type": "ChatMessage", "args": { "userId": "9d6d2db0cdb84d8ba67105670d94d641", "supportId": "7c270542e64a4dfbb2bc1c7793746674", "content": "Hey, I want to transfer money offshore, how can I do it via internet bank?", "from": "User" } }
- Expect
JSON
response for bothUser
andSupport
WebSocket connections:
{ "type": "ChatMessage", "args": { "userId": "9d6d2db0cdb84d8ba67105670d94d641", "supportId": "7c270542e64a4dfbb2bc1c7793746674", "content": "hey", "timestamp": "2023-10-22T18:18:59.360855Z", "from": "User" } }
- Expect
- Send chat message to
User
fromSupport
by sending the following WebSocket text message on the opened WS connection:{ "type": "ChatMessage", "args": { "userId": "9d6d2db0cdb84d8ba67105670d94d641", "supportId": "7c270542e64a4dfbb2bc1c7793746674", "supportUserName": "Vika", "content": "Hello, please navigate to Transfer and then select Offshore :)", "from": "Support" } }
- Expect
JSON
response for bothUser
andSupport
WebSocket connections:
{ "type": "ChatMessage", "args": { "userId": "f49f3cc665af4cb38092af714a4c87fa", "supportId": "7c270542e64a4dfbb2bc1c7793746674", "content": "Hello, please navigate to Transfer and then select Offshore :)", "timestamp": "2023-10-22T18:19:19.119515Z", "from": "Support" } }
- Expect
Each chat expires after two minutes of inactivity. Also, WebSocket connection automatically closes after 120 seconds of inactivity.
In case User
or Support
refreshes browser backend either loads:
- conversation history (if the chat is still active)
- message about chat expiration (if the chat was expired)
-
In case
User
refreshes browser the UI must send newJoin
(re-join) WebSocket text message:{ "type": "Join", "args": { "userId": "8b5ee49aa3bb45e1a8719179e5e25c12", "supportId": "7c270542e64a4dfbb2bc1c7793746674", "username": "Nika", "from": "User" } }
- Expect
JSON
response:
{ "type": "ChatHistory", "args": { "messages": [ { "userId": "718b568c22f6460ca46a07cfc6aae3ba", "supportId": "35eda9304e554514915fdbb8f26b710d", "content": "Hey, I want to tranasfer money offshore, how can I do it via internet bank?", "timestamp": "2023-10-22T18:36:54.834408Z", "from": "User" }, { "userId": "718b568c22f6460ca46a07cfc6aae3ba", "supportId": "35eda9304e554514915fdbb8f26b710d", "content": "Hello, please navigate to Transfer and then select Offshore :)", "timestamp": "2023-10-22T18:37:10.219775Z", "from": "Support" } ] } }
- Expect
-
Same message is loaded for
Support
, however a bit differentJoin
(re-join) WebSocket text message must be sent:{ "type": "Join", "args": { "userId": "8b5ee49aa3bb45e1a8719179e5e25c12", "supportId": "7c270542e64a4dfbb2bc1c7793746674", "username": "Nika", "supportUserName": "Vika", "from": "Support" } }