#10 329 - Group messages interference bugs fixing

Sapludināts
bronjaermin sapludināja 1 revīzijas no feature/329_group_messages_interference uz development pirms 3 gadiem

+ 1
- 1
Backend/Diligent.WebAPI.Business/Services/CustomerService.cs Parādīt failu

@@ -80,7 +80,7 @@ namespace Diligent.WebAPI.Business.Services

if (notification == null)
{
return false;
return true;
}

user.Notifications.Remove(notification);

+ 7
- 1
Backend/Diligent.WebAPI.Host/Controllers/NotificationController.cs Parādīt failu

@@ -1,4 +1,6 @@
using Diligent.WebAPI.Host.Mediator.Notifications.Queries;
using Diligent.WebAPI.Host.DTOs.Notification;
using Diligent.WebAPI.Host.Mediator.Notifications.Commands;
using Diligent.WebAPI.Host.Mediator.Notifications.Queries;
using MediatR;
using Microsoft.AspNetCore.Mvc;

@@ -19,5 +21,9 @@ namespace Diligent.WebAPI.Host.Controllers
[HttpGet("{id}")]
public async Task<IActionResult> GetNotifications([FromRoute] string id) =>
Ok(await _mediator.Send(new GetNotificationsQuery(id)));

[HttpPost]
public async Task<IActionResult> ReadNotifications([FromBody] NotificationDeleteDTO userConnection) =>
Ok(await _mediator.Send(new DeleteNotificationCommand(userConnection)));
}
}

+ 6
- 18
Backend/Diligent.WebAPI.Host/Hubs/ChatHub.cs Parādīt failu

@@ -22,17 +22,6 @@ namespace Diligent.WebAPI.Host.Hubs
_mediator = mediator;
}

//public override Task OnDisconnectedAsync(Exception? exception)
//{
// if (_connections.TryGetValue(Context.ConnectionId, out UserConnection room))
// {
// _connections.Remove(Context.ConnectionId);
// Clients.Group(room.RoomId).ReceiveMessage(new ChatMessage { User = room.UserId, Message = $"{room.UserId} has left room" });
// }

// return base.OnDisconnectedAsync(exception);
//}

[HubMethodName("SendMessageToGroup")]
[AuthorizationHubFilter(Roles = "Customer,Support")]
public async Task SendMessageToGroup(ChatMessage message)
@@ -41,8 +30,7 @@ namespace Diligent.WebAPI.Host.Hubs
// All other users will receive notification
if (_connections.TryGetValue(message.ConnId, out UserConnection room))
{
await Clients.Group(room.RoomId).ReceiveMessage(new ChatMessage { User = room.UserId, Message = message.Message });
await Clients.OthersInGroup(room.RoomId).ReceiveNotifications(room.UserId, room.RoomId);
await Clients.Group(room.RoomId).ReceiveMessage(new ChatMessage { UserId = room.UserId, User = room.UserId, Message = message.Message, RoomId = room.RoomId });
await _mediator.Send(new AddMessageCommand(room.RoomId, new Message { Content = message.Message, SenderId = message.UserId, Username = room.Username }));

// Find other users in room and save notification in database
@@ -53,6 +41,8 @@ namespace Diligent.WebAPI.Host.Hubs
await _mediator.Send(new AddNotificationCommand(new NotificationSaveDTO { RoomId = room.RoomId, ReceiverId = conn.Value.UserId }));
}
}

await Clients.OthersInGroup(room.RoomId).ReceiveNotifications(room.UserId, room.RoomId);
}
}

@@ -69,7 +59,7 @@ namespace Diligent.WebAPI.Host.Hubs
{
await Groups.AddToGroupAsync(Context.ConnectionId, userConnection.RoomId);
_connections[Context.ConnectionId] = userConnection;
await Clients.Group(userConnection.RoomId).ReceiveMessage(new ChatMessage { User = userConnection.UserId, Message = $"{userConnection.Username} has joined room", ConnId = Context.ConnectionId });
await Clients.Group(userConnection.RoomId).ReceiveMessage(new ChatMessage { UserId = userConnection.UserId, User = userConnection.UserId, RoomId = userConnection.RoomId, Message = $"{userConnection.Username} has joined room", ConnId = Context.ConnectionId });
}
else
{
@@ -83,8 +73,9 @@ namespace Diligent.WebAPI.Host.Hubs
{
if (_connections.TryGetValue(connection.ConnId, out UserConnection room))
{
// Find user connection in connections dictionary and delete it
_connections.Remove(Context.ConnectionId);
await Clients.OthersInGroup(room.RoomId).ReceiveMessage(new ChatMessage { User = room.UserId, Message = $"{room.Username} has left room" });
await Clients.OthersInGroup(room.RoomId).ReceiveMessage(new ChatMessage { UserId = room.UserId, User = room.UserId, Message = $"{room.Username} has left room", RoomId = room.RoomId });
await _mediator.Send(new RemoveUserFromGroupCommand(room.RoomId, room.UserId));
}
}
@@ -93,12 +84,9 @@ namespace Diligent.WebAPI.Host.Hubs
public async Task SeeWhoIsTyping(TypingInvokeMessage message)
{
// Find user's room and send message to everyone
//if (_connections.TryGetValue(Context.ConnectionId, out UserConnection room))
if (_connections.TryGetValue(message.ConnId, out UserConnection room))
{
// send the typing info to all in group except the caller
// viewtyping(username + "is typing") // message, roomId
//await Clients.GroupExcept(message.RoomId, Context.ConnectionId).ViewTyping(new TypingMessage { RoomId = room.RoomId, Message = message.Message });
await Clients.GroupExcept(message.RoomId, message.ConnId).ViewTyping(new TypingMessage { RoomId = room.RoomId, Message = message.Message });
}
}

+ 1
- 0
Backend/Diligent.WebAPI.Host/Hubs/ChatMessage.cs Parādīt failu

@@ -10,5 +10,6 @@

// Context.ConnectionId generated by SignalR
public string ConnId { get; set; }
public string RoomId { get; set; }
}
}

+ 0
- 8
Backend/Diligent.WebAPI.Host/Hubs/ConnectionHub.cs Parādīt failu

@@ -30,15 +30,7 @@ namespace Diligent.WebAPI.Host.Hubs
{
IDs.Remove(s.ConnId);
var msg = new StatusMessage { Id = s.Id, M = "unsubscription" };
//breakpoint here !
await Clients.Others.SendAsync("Notify", msg);
}
//public override async Task OnDisconnectedAsync(Exception exception, string id)
//{
// IDs.Remove(id);
// var msg = new StatusMessage { Id = id, M = "unsubscription" };
// await Clients.Others.SendAsync("Notify", msg);
// await base();
//}
}
}

+ 77
- 35
Frontend/src/components/ChatList.js Parādīt failu

@@ -7,22 +7,24 @@ import { FaSignInAlt } from "react-icons/fa";
import { GiSandsOfTime } from "react-icons/gi";
import { CgEnter } from "react-icons/cg";
import Dialog from "./UI/Dialog";
import { chatActions, fetchChatRoomsAsync,fetchSupportRoomsAsync } from "../store/chat-slice";
import {
chatActions,
fetchChatRoomsAsync,
fetchSupportRoomsAsync,
} from "../store/chat-slice";
import { createChatRoomAsync } from "../store/chat-slice";
import { createJoinRequestAsync, requestActions } from "../store/request-slice";
import { fetchRequestsAsync } from "../store/request-slice";
import { HubConnectionBuilder } from "@microsoft/signalr";
import { loadNotifications } from "../store/chat-slice";
import { readNotificationsAsync } from "../store/chat-slice";

// Ovde ce biti dostupne grupe i razgovori
const ChatList = () => {
const [createChat, setCreateChat] = useState(false);
// naziv chata koji ce biti dodan
const [chatName, setChatName] = useState("");
// grupe kojima je korisnik poslao zahtev za ulazak
const [requestedRooms, setRequestedRooms] = useState([]);
// chats from redux
const { rooms, status, error, notifications } = useSelector(
const { rooms, status: chatStatus, error, notifications, activeRoom } = useSelector(
(state) => state.chat
);
const {
@@ -30,12 +32,12 @@ const ChatList = () => {
status: requestsStatus,
requests,
} = useSelector((state) => state.requests);
// show modal
const [showModal, setShowModal] = useState(false);
const [loadedNotification, setLoadedNotification] = useState(false);
const { user } = useContext(UserContext);
// const [connection, setConnection] = useState();
// const [messages, setMessages] = useState([]);
const [myConnection, setMyConnection] = useState(null);
const [chatMessage, setChatMessage] = useState(null);
const [notificationRoom, setNotificationRoom] = useState(null);
const dispatch = useDispatch();

useEffect(() => {
@@ -45,11 +47,13 @@ const ChatList = () => {
}
}, [user, dispatch, loadedNotification]);

// Maybe don't work
useEffect(() => {
(user !== null && user.roles.includes('Support')) ? dispatch(fetchSupportRoomsAsync(user.id)):
(user !== null && dispatch(fetchChatRoomsAsync(user.id)))
user !== null && user.roles.includes("Support")
? dispatch(fetchSupportRoomsAsync(user.id))
: user !== null && dispatch(fetchChatRoomsAsync(user.id));
dispatch(fetchRequestsAsync());
}, [dispatch]);
}, [dispatch, user]);

useEffect(() => {
if (requests && rooms) {
@@ -77,7 +81,9 @@ const ChatList = () => {
const joinRoom = async (n) => {
try {
const connection = new HubConnectionBuilder()
.withUrl("http://localhost:5116/chatHub",{accessTokenFactory:() => user.token})
.withUrl("http://localhost:5116/chatHub", {
accessTokenFactory: () => user.token,
})
.withAutomaticReconnect()
.build();

@@ -87,34 +93,25 @@ const ChatList = () => {
dispatch(
chatActions.saveContextId({ connId: data.connId, userId: user.id })
);
setChatMessage(data);
}
// Every new received message will be stored in redux
dispatch(
chatActions.newMessage({
content: data.message,
createdAtUtc: new Date(),
deletedAtUtc: null,
id: null,
senderId: user.id,
updatedAtUtc: null,
username: user.username,
})
);
});

connection.on("ViewTyping", (data) =>{
connection.on("ViewTyping", (data) => {
dispatch(chatActions.addTyping(data));
})
});

connection.on("ReceiveNotifications", (userId, roomId) => {
if (user.id !== userId) dispatch(chatActions.addNotification(roomId));
if (user.id !== userId) {
dispatch(chatActions.addNotification(roomId));
setNotificationRoom(roomId);
}
});
// When user changed room, array with messages from previous room will be deleted from redux
dispatch(chatActions.newMessage({ changedRoom: true }));

connection.onclose((e) => {
// setConnection();
// setMessages([]);
// On close connection
});

await connection.start();
@@ -125,12 +122,57 @@ const ChatList = () => {
});
dispatch(chatActions.setRoom(n));
dispatch(chatActions.setConnection(connection));
// setConnection({ connection });
setMyConnection(connection);
} catch (e) {
console.log(e);
}
};

// Maybe don't work
useEffect(() => {
if (myConnection) {
myConnection.on("ReceiveMessage", (data) => {
// When user enter room first time after login, generated Context.ConnectionId will be saved in redux
if (data.connId) {
dispatch(
chatActions.saveContextId({ connId: data.connId, userId: user.id })
);
}
setChatMessage(data);
});
}
}, [myConnection, dispatch, user]);

// Maybe don't work
useEffect(() => {
if (chatMessage && activeRoom.id === chatMessage.roomId) {
dispatch(
chatActions.newMessage({
content: chatMessage.message,
createdAtUtc: new Date(),
deletedAtUtc: null,
id: null,
senderId: user.id,
updatedAtUtc: null,
username: user.username,
})
);
}
}, [chatMessage, dispatch, user, activeRoom]);

// Maybe don't work
useEffect(() => {
if (notificationRoom && activeRoom) {
if (notificationRoom === activeRoom.id) {
dispatch(chatActions.readNotifications(activeRoom.id));
dispatch(
readNotificationsAsync({ userId: user.id, roomId: activeRoom.id })
);
}
}
setNotificationRoom(null);
}, [notificationRoom, activeRoom, dispatch, user]);

const openModal = (n) => {
setShowModal(true);
dispatch(requestActions.chooseRoom(n));
@@ -178,8 +220,7 @@ const ChatList = () => {
}
}
}
return (
user !== null && user.roles.includes('Support') ?
return user !== null && user.roles.includes("Support") ? (
<div>
{rooms.map((room, index) => (
<div
@@ -195,7 +236,8 @@ const ChatList = () => {
</button>
</div>
))}
</div>:
</div>
) : (
<div>
{acceptedRequests.length > 0 && (
<div>
@@ -309,8 +351,8 @@ const ChatList = () => {
)}

{/* ovo ce biti zamenjeno konkretnim podacima */}
{(!error && requestedRooms && status === "pendingFetchRooms") ||
status === "pendingAddRoom" ? (
{(!error && requestedRooms && chatStatus === "pendingFetchRooms") ||
chatStatus === "pendingAddRoom" ? (
<p>Loading</p>
) : (
getView()

+ 10
- 23
Frontend/src/components/ChatWindow.js Parādīt failu

@@ -3,20 +3,15 @@ import { Button, Form, FormControl, InputGroup } from "react-bootstrap";
import { UserContext } from "../contexts/userContext";
import { useSelector, useDispatch } from "react-redux";
import { chatActions } from "../store/chat-slice";
import { BsCircleFill } from 'react-icons/bs'
import { BsCircleFill } from "react-icons/bs";
import TypingBar from "./TypingBar";

const ChatWindow = ({ room }) => {
const messagesEndRef = useRef(null);

const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
};

const [message, setMessage] = useState("");
const { user } = useContext(UserContext);
const connection = useSelector((state) => state.chat.connection);
const { typings } = useSelector((s) => s.chat)
const connections = useSelector((state) => state.chat.connections);
const activeRoom = useSelector((state) => state.chat.activeRoom);
const messages = useSelector((state) => state.chat.messages);
@@ -26,17 +21,11 @@ const ChatWindow = ({ room }) => {
dispatch(chatActions.setMessages(room.messages));
}, [dispatch, room.messages]);

useEffect(() => {
// scrollToBottom();
}, []);

const leftRoomHandler = async () => {
const userToFetch = connections.filter(
(conn) => conn.userId === user.id && conn.roomId === activeRoom.id
);

console.log(userToFetch[0].connId, user.id, activeRoom.id);

await connection.invoke("LeaveRoom", {
connId: userToFetch[0].connId,
});
@@ -62,23 +51,19 @@ const ChatWindow = ({ room }) => {
const status = useSelector((s) => s.status);
const { activeUsers } = status;

const messageOnChangeHandler = async (e) =>{
setMessage(e.target.value)
const messageOnChangeHandler = async (e) => {
setMessage(e.target.value);

const userToFetch = connections.filter(
(conn) => conn.userId === user.id && conn.roomId === activeRoom.id
);

// console.log(userToFetch[0].connId)
// console.log(room.id)
await connection.send("SeeWhoIsTyping", {
message: `${user.username} is typing...`,
connId: userToFetch[0].connId,
roomId: activeRoom.id
roomId: activeRoom.id,
});
}

};

return (
<div className="px-3 bg-light-transparent rounded h-100 d-flex flex-column">
@@ -100,7 +85,6 @@ const ChatWindow = ({ room }) => {

<div className="messages p-3 border d-flex flex-column-reverse">
{/* mapirane poruke */}

{messages
.map((n, index) => (
<div
@@ -139,9 +123,12 @@ const ChatWindow = ({ room }) => {
<div ref={messagesEndRef} />
</div>

<TypingBar id={activeRoom.id}/>
<TypingBar id={activeRoom.id} />

<Form style={{ height: "80px" }} className="d-flex align-items-center">
<Form
style={{ height: "80px" }}
className="d-flex align-items-center"
>
<InputGroup className="mb-3">
<FormControl
placeholder="Enter your messagge..."

+ 0
- 22
Frontend/src/components/CustomerRequest/CustomerRequest.js Parādīt failu

@@ -2,7 +2,6 @@ import React from "react";
import { FiCheckCircle, FiXCircle } from "react-icons/fi";
import styles from "./CustomerRequest.module.css";
import requestService from "../../services/requestService";
// import { HubConnectionBuilder } from "@microsoft/signalr";

export default function CustomerRequest({ customer, requestHandled }) {
const onAcceptRequestHandler = () => {
@@ -13,31 +12,10 @@ export default function CustomerRequest({ customer, requestHandled }) {
})
.then((res) => {
requestHandled(res);

// Joining in room
// joinRoom({ userId: customer.senderId, roomId: customer.roomId });
})
.catch((e) => console.log(e));
};

// const joinRoom = async (joiningCredentials) => {
// try {
// const connection = new HubConnectionBuilder()
// .withUrl("http://localhost:5116/chatHub")
// .withAutomaticReconnect()
// .build();

// connection.on("ReceiveMessage", (data) => {
// console.log("CUSTOMER REQUEST", data);
// });

// await connection.start();
// await connection.invoke("JoinRoom", joiningCredentials);
// } catch (e) {
// console.log(e);
// }
// };

const onRejectRequestHandler = () => {
requestService
.rejectCustomerRequest({

+ 3
- 11
Frontend/src/components/MiddleContainer.js Parādīt failu

@@ -2,17 +2,13 @@ import React from "react";
import Activity from "./Activity";
import ChatList from "./ChatList";
import Requests from "./Requests/Requests";
import { useContext, useEffect, useState } from "react";
import { useContext, useEffect } from "react";
import { HubConnectionBuilder } from "@microsoft/signalr";
import { UserContext } from "../contexts/userContext";
import { useDispatch, useSelector } from "react-redux";
import { useDispatch } from "react-redux";
import { statusActions } from "../store/status-slice";

const MiddleContainer = ({ showTerm }) => {
// const [conn, setConn] = useState('');
// window.addEventListener('beforeunload', ()=>{
// })

const { user } = useContext(UserContext);

const dispatch = useDispatch();
@@ -24,11 +20,6 @@ const MiddleContainer = ({ showTerm }) => {
.build();

dispatch(statusActions.setStatusConn(connection));
// setConn(connection)

// connection.onclose(() => {
// connection.send("Unsubscribe", user.id);
// });

connection.on("Notify", (data) => {
if (data.m === "subscription") {
@@ -51,6 +42,7 @@ const MiddleContainer = ({ showTerm }) => {
connection.start().then(fulfilled, rejected);
}

// Maybe don't work
useEffect(() => {
connect();
}, []);

+ 0
- 1
Frontend/src/components/RegisterForm.js Parādīt failu

@@ -28,7 +28,6 @@ const RegisterForm = () => {
navigate("/main");
})
.catch((err) => {
// console.log(err)
setError(err.response.data.message.split("."));
});
};

+ 0
- 5
Frontend/src/components/SideNavbar.js Parādīt failu

@@ -12,8 +12,6 @@ const SideNavbar = () => {

const logOutHandler = () => {
if (window.confirm("Are you sure?")) {
// ovde ce ici logika za logovanje
// uklanjanje iz state-a i slicno
dispatch(chatActions.deleteActiveRoom());
logOut();
}
@@ -24,7 +22,6 @@ const SideNavbar = () => {
<Link
to={'/main'}
className="btn btn-white w-100 button-block button-block-flex-column"
// onClick={() => onClickHandler("notifications")}
>
<FiBell className="icon-fs" />
<h5 className="small text-muted text-center fw-light">feed</h5>
@@ -33,7 +30,6 @@ const SideNavbar = () => {
<Link
to={'/chats'}
className="btn btn-white button-block mt-4 w-100 button-block-flex-column"
// onClick={() => onClickHandler("chats")}
>
<FiMessageSquare className="icon-fs" />
<h5 className="small text-muted text-center fw-light">chat</h5>
@@ -43,7 +39,6 @@ const SideNavbar = () => {
<Link
to={'/requests'}
className="btn btn-white button-block mt-4 w-100 button-block-flex-column"
// onClick={() => onClickHandler("requests")}
>
<FaArrowAltCircleRight className="icon-fs" />
<h5 className="small text-muted text-center fw-light">Requests</h5>

+ 1
- 1
Frontend/src/components/TypingBar.js Parādīt failu

@@ -16,7 +16,7 @@ const TypingBar = ({id}) => {
return () => clearTimeout(timeout)
}, 2500)
}, [rerender]);
}, [rerender, dispatch]);

return (
<div className='text-dark'>

+ 0
- 1
Frontend/src/components/UI/CustomAccordition.js Parādīt failu

@@ -1,5 +1,4 @@
import React, { useState } from "react";
// import { useSelector, useDispatch } from "react-redux";

import Accordion from "@mui/material/Accordion";
import AccordionSummary from "@mui/material/AccordionSummary";

+ 0
- 2
Frontend/src/contexts/userContext.js Parādīt failu

@@ -15,7 +15,6 @@ export const UserProvider = (props) => {
window.addEventListener("beforeunload", () => {
// that means the user probably shut the browser down without loging out
if (localStorage.getItem("activeOnes")) {
// status.connection.stop();
disconnect();
}
});
@@ -43,7 +42,6 @@ export const UserProvider = (props) => {
connId: status.connection.connectionId,
})
.then(() => console.log("bye"));
// connection.stop();
};

const rejected = () => {

+ 2
- 0
Frontend/src/services/notificationService.js Parādīt failu

@@ -8,6 +8,8 @@ const responseBody = (response) => response.data;
const methods = {
loadNotifications: (userId) =>
axios.get(`/Notification/${userId}`).then(responseBody),
readNotifications: (payload) =>
axios.post("/Notification", payload).then(responseBody),
};

export default methods;

+ 43
- 16
Frontend/src/store/chat-slice.js Parādīt failu

@@ -7,15 +7,11 @@ const initialState = {
rooms: [],
activeRoom: null,
error: null,
// Hub connection
connection: null,
// List of room messages
messages: [],
// All active user connections to rooms
connections: [],
// Notifications
notifications: [],
typings: []
typings: [],
};

export const fetchChatRoomsAsync = createAsyncThunk(
@@ -31,14 +27,14 @@ export const fetchChatRoomsAsync = createAsyncThunk(

export const fetchSupportRoomsAsync = createAsyncThunk(
"chat/fetchSupportRooms",
async (payload,thunkAPI) => {
try{
async (payload, thunkAPI) => {
try {
return await chatService.getSupportRooms(payload);
}catch(error){
return thunkAPI.rejectWithValue({error})
} catch (error) {
return thunkAPI.rejectWithValue({ error });
}
}
)
);

export const createChatRoomAsync = createAsyncThunk(
"chat/createChatRoomAsync",
@@ -62,6 +58,17 @@ export const loadNotifications = createAsyncThunk(
}
);

export const readNotificationsAsync = createAsyncThunk(
"chat/deleteNotificationsAsync",
async (payload, thunkAPI) => {
try {
return await notificationService.readNotifications(payload);
} catch (error) {
return thunkAPI.rejectWithValue({ error });
}
}
);

const chatSlice = createSlice({
name: "chat",
initialState,
@@ -157,17 +164,23 @@ const chatSlice = createSlice({
);
},
addTyping: (state, action) => {
if(!state.typings.some(n => n.message === action.payload.message && n.roomId === action.payload.roomId))
if (
!state.typings.some(
(n) =>
n.message === action.payload.message &&
n.roomId === action.payload.roomId
)
)
state.typings = [...state.typings, action.payload];
else console.log('ima vec')
else console.log("ima vec");
},
removeTyping: (state, action) => {
if(state.typings.length >= 1){
let f = state.typings[0]
state.typings = state.typings.filter(n => n.message !== f.message);
}
if (state.typings.length >= 1) {
let f = state.typings[0];
state.typings = state.typings.filter((n) => n.message !== f.message);
}
},
},
extraReducers: (builder) => {
// Fetch chat rooms
builder.addCase(fetchChatRoomsAsync.pending, (state) => {
@@ -233,6 +246,20 @@ const chatSlice = createSlice({
state.status = "idle";
state.error = action.payload;
});

// Read notifications
builder.addCase(readNotificationsAsync.pending, (state) => {
state.status = "pendingReadingNotifications";
state.error = null;
});
builder.addCase(readNotificationsAsync.fulfilled, (state) => {
state.status = "idle";
state.error = null;
});
builder.addCase(readNotificationsAsync.rejected, (state, action) => {
state.status = "idle";
state.error = action.payload;
});
},
});


+ 0
- 1
Frontend/src/store/status-slice.js Parādīt failu

@@ -4,7 +4,6 @@ const initialState = {
activeUsers: localStorage.getItem("activeOnes")
? JSON.parse(localStorage.getItem("activeOnes"))
: [],
//maybe needed later
connection: ''
};


Notiek ielāde…
Atcelt
Saglabāt