public void Events() { var validUser = new User { Id = AliceUserId, Status = new UsersStatus { Id = UserStatuses.Valid } }; var sgClientMock = new Mock <IEmailSender>(); var controller = new TextChatController(/*sgClientMock.Object*/); var events = new Queue <string>(); var aliceChatUser = new TextChatUser { FirstName = AliceName, LastName = "A.", Knows = AliceKnows, Learns = AliceLearns, Id = AliceUserId }; var message = new TextChatMessage { UserId = AliceUserId, FirstName = AliceName, RoomId = EnglishRoomId, Text = AliceMessageText, Visibility = MessageVisibility.Nobody }; UserId aliceUserId = null; controller.OnUserJoinedRoom += (roomId, userId) => { aliceUserId = userId; events.Enqueue($"{userId} joined {roomId}"); }; controller.OnPostedMessage += (msg) => { events.Enqueue(msg.Text); }; controller.JoinChat(AliceUserId, aliceChatUser); controller.JoinRoom(AliceUserId, EnglishRoomId); controller.PostTo(validUser, message).Wait(); Assert.AreEqual($"{aliceUserId} joined {EnglishRoomId}", events.Dequeue()); Assert.AreEqual(AliceMessageText, events.Dequeue()); Assert.AreEqual(0, events.Count); }
private async Task PersistVoiceCallInfo(string roomId) { if (RoomsConnectionsDictionary[roomId].Count > 1) { // Second user joined voice chat room - call is accepted if (RoomsVoiceCallsDictionary.ContainsKey(roomId)) { var currentVoiceCall = RoomsVoiceCallsDictionary[roomId]; currentVoiceCall.Accepted = DateTime.Now; currentVoiceCall.CalleeId = TextChatController.PartnerInPrivateRoom(roomId, currentVoiceCall.CallerId); ((DbContext)_db).Entry(currentVoiceCall).State = EntityState.Modified; Log.SignalR(LogTag.SavingAcceptedCallToDb, new { roomId }, Context); } else { // Something wrong have happened and we don't have a VoiceCall object in dict yet Log.SignalRError(LogTag.AcceptedVoiceCallRoomIdNotFoundDictionary, new { roomId }, Context); } } else { if (RoomsVoiceCallsDictionary.ContainsKey(roomId)) { Log.SignalRError(LogTag.VoiceCallRoomAlreadyExistsOnInitiatingCall, new { roomId }, Context); return; } // One user is here - call initiated var userId = Context.User.Identity.GetClaims().Id; var vc = new VoiceCall { Created = DateTime.Now, CallerId = userId, CalleeId = TextChatController.PartnerInPrivateRoom(roomId, userId), Platform = 1, // Formerly obtained from VoicePlatforms table (table abandonned when VoiceOut got removed) Source = Enumerables.SourceFeatures.PrivateTextChat }; RoomsVoiceCallsDictionary.Add(roomId, vc); _db.VoiceCalls.Add(vc); Log.SignalR(LogTag.SavingInitiatedCallToDb, new { roomId }, Context); } await _db.SaveChangesAsync(); }
//========== Constructor ====================================================================================== public TextChatHubCtrl(IHubConnectionContext <ITextChatHubClient> clients, TextChatController chatController) { Clients = clients; RoomsConnections = new RoomsConnectionsManager(); UsersConnections = new OneToManyMapper <UserId, ConnectionId>(); ChatCtrl = chatController; LastSeenConnections = new Dictionary <ConnectionId, DateTime>(); // Handle Chat Controller Events ChatCtrl.OnUserJoined += (user) => Send(UsersConnections.Values, new AddUserInvoker(user)); ChatCtrl.OnUserLeft += (userId) => Send(UsersConnections.Values, new RemoveUserInvoker(userId)); ChatCtrl.OnUserJoinedRoom += (roomId, userId) => Send(RoomsConnections.GetConnections(roomId), new AddUserToInvoker(roomId, userId)); ChatCtrl.OnUserLeftRoom += (roomId, userId) => Send(RoomsConnections.GetConnections(roomId), new RemoveUserFromInvoker(roomId, userId)); ChatCtrl.OnCountOfUsersUpdated += (roomId, count) => Send(UsersConnections.Values, new UpdateCountOfUsersInvoker(roomId, count)); ChatCtrl.OnUserStartedTyping += (roomId, userId) => Send(RoomsConnections.GetConnections(roomId), new MarkUserAsTypingInvoker(roomId, userId)); ChatCtrl.OnUserStoppedTyping += (roomId, userId) => Send(RoomsConnections.GetConnections(roomId), new UnmarkUserAsTypingInvoker(userId)); ChatCtrl.OnUserRequestedAudioCall += (roomId, userId) => Send(GetPartnerConnections(roomId, userId), new RequestAudioCallInvoker(roomId)); ChatCtrl.OnUserCancelledAudioCall += (roomId, userId) => Send(GetPartnerConnections(roomId, userId), new CancelAudioCallInvoker(roomId, userId)); ChatCtrl.OnUserDeclinedAudioCall += (roomId, reason, connId) => Send(GetRelatedConnections(roomId, connId), new DeclineAudioCallInvoker(roomId, reason)); ChatCtrl.OnUserHangoutedAudioCall += (roomId, userId) => Send(GetRelatedConnections(roomId), new HangoutAudioCallInvoker(roomId, userId)); ChatCtrl.OnAudioCallConnected += (roomId, userId) => Send(GetRelatedConnections(roomId), new AudioCallConnectedInvoker(roomId, userId)); ChatCtrl.OnPostedMessage += (msg) => { Send(msg.RoomId.IsPrivate() ? GetPartnerAndActiveConnections(msg.RoomId, msg.UserId, msg.ConnectionId) : RoomsConnections.GetConnections(msg.RoomId, msg.ConnectionId), new AddMessageInvoker(msg)); }; ChatCtrl.OnUserIdle += (userId) => Send(UsersConnections.GetFromKey(userId), new SetUserIdleInvoker(userId)); // Start connection heartbeat check var heartBeatTimer = new BetterTimer(OnLogHeartbeatError) { AutoReset = true }; heartBeatTimer.Start(null, HeartbeatCheck, HeartbeatCheckInterval); // Start connection heartbeat check var healthReportTimer = new BetterTimer(OnLogHealthReportError) { AutoReset = true }; healthReportTimer.Start(null, PublishHealthReport, HealthReportInterval); }
public void JoinAndLeavePrivateRoomTest() { var sgClientMock = new Mock <IEmailSender>(); var controller = new TextChatController(/*sgClientMock.Object*/); controller.JoinChat(AliceUserId, AliceTextChatUser); controller.JoinChat(BobUserId, BobTextChatUser); controller.JoinRoom(AliceUserId, EnglishRoomId); controller.JoinRoom(BobUserId, EnglishRoomId); Assert.AreEqual <int>(2, controller.GetState().Rooms[EnglishRoomId].Count, "Chat Room should contain 2 users."); //User join to private room RoomId privateRoomId = $"{AliceUserId}-{BobUserId}"; controller.JoinRoom(AliceUserId, privateRoomId); Assert.AreEqual <int>(1, controller.GetState().Rooms [privateRoomId].Count, "Private chat Room must contain 1 user."); //Single user leave private room controller.LeaveRoom(AliceUserId, privateRoomId); Assert.AreEqual <int>(1, controller.GetState().Rooms.Count, "Chat Model must contain 1 room (English)."); Assert.IsFalse(controller.GetState().Rooms.Keys.Contains(privateRoomId), $"Private room {privateRoomId} must not exist."); //Add new private room and 2 users joined to room controller.JoinRoom(AliceUserId, privateRoomId); Assert.AreEqual <int>(1, controller.GetState().Rooms [privateRoomId].Count, "Private chat Room must contain 1 user."); controller.JoinRoom(BobUserId, privateRoomId); Assert.AreEqual <int>(2, controller.GetState().Rooms [privateRoomId].Count, "Private chat Room must contain 2 users."); //First user leave private room controller.LeaveRoom(AliceUserId, privateRoomId); Assert.AreEqual <int>(2, controller.GetState().Rooms.Count, "Chat Model must contain 2 rooms."); Assert.IsTrue(controller.GetState().Rooms.Keys.Contains(privateRoomId), $"Private room {privateRoomId} must exist."); Assert.AreEqual <int>(1, controller.GetState().Rooms[privateRoomId].Count, "Private chat Room must contain 1 user."); //Second user leave private room controller.LeaveRoom(BobUserId, privateRoomId); Assert.AreEqual <int>(1, controller.GetState().Rooms.Count, "Chat Model must contain 1 room (English)."); Assert.IsFalse(controller.GetState().Rooms.Keys.Contains(privateRoomId), $"Private room {privateRoomId} must not exist."); //User created, joined private room and leave chat controller.JoinRoom(AliceUserId, privateRoomId); controller.LeaveChat(AliceUserId); Assert.AreEqual <int>(1, controller.GetState().Rooms.Count, "Chat Model must contain 1 room (English)."); Assert.IsFalse(controller.GetState().Rooms.Keys.Contains(privateRoomId), $"Private room {privateRoomId} must not exist."); }
public void LeaveRoomTest() { var sgClientMock = new Mock <IEmailSender>(); var controller = new TextChatController(/*sgClientMock.Object*/); controller.JoinChat(AliceUserId, AliceTextChatUser); controller.JoinChat(BobUserId, BobTextChatUser); controller.JoinChat(CarolUserId, CarolTextChatUser); controller.JoinRoom(AliceUserId, EnglishRoomId); controller.JoinRoom(BobUserId, EnglishRoomId); controller.JoinRoom(CarolUserId, EnglishRoomId); Assert.AreEqual <int>(3, controller.GetState().Rooms[EnglishRoomId].Count, "Chat Room should contain 3 users."); controller.LeaveRoom(AliceUserId, EnglishRoomId); Assert.AreEqual <int>(2, controller.GetState().Rooms[EnglishRoomId].Count, "Chat Room should contain 2 users."); controller.LeaveRoom(BobUserId, EnglishRoomId); controller.LeaveRoom(CarolUserId, EnglishRoomId); Assert.IsFalse(controller.GetState().Rooms.ContainsKey(EnglishRoomId), "Chat Room must not exist."); }
public void ConsecutiveJoinAndLeave() { var sgClientMock = new Mock <IEmailSender>(); var controller = new TextChatController(/*sgClientMock.Object*/); var events = new Queue <string>(); var aliceChatUser = new TextChatUser() { FirstName = AliceName, LastName = "A.", Knows = AliceKnows, Learns = AliceLearns, Id = AliceUserId }; UserId aliceUserId = null; // Listen to events controller.OnUserJoinedRoom += (roomId, userId) => { events.Enqueue($"{userId} joined {roomId}"); aliceUserId = userId; }; controller.OnUserLeftRoom += (roomId, userId) => { events.Enqueue($"{userId} left {roomId}"); }; controller.OnUserLeft += (userId) => { events.Enqueue($"{userId} left"); }; // Alice's actions controller.JoinChat(AliceUserId, aliceChatUser); controller.JoinRoom(AliceUserId, EnglishRoomId); controller.LeaveChat(AliceUserId); // Check results Assert.AreEqual($"{aliceUserId} joined {EnglishRoomId}", events.Dequeue()); Assert.AreEqual($"{aliceUserId} left {EnglishRoomId}", events.Dequeue()); Assert.AreEqual($"{aliceUserId} left", events.Dequeue()); // Alice's actions again controller.JoinChat(AliceUserId, aliceChatUser); controller.JoinRoom(AliceUserId, EnglishRoomId); controller.LeaveChat(AliceUserId); // Check results Assert.AreEqual($"{aliceUserId} joined {EnglishRoomId}", events.Dequeue()); Assert.AreEqual($"{aliceUserId} left {EnglishRoomId}", events.Dequeue()); Assert.AreEqual($"{aliceUserId} left", events.Dequeue()); Assert.AreEqual(0, events.Count); }
public void JoinChat() { var sgClientMock = new Mock <IEmailSender>(); var controller = new TextChatController(/*sgClientMock.Object*/); // Alice's actions controller.JoinChat(AliceUserId, AliceTextChatUser); controller.JoinRoom(AliceUserId, EnglishRoomId); // Bob's actions controller.JoinChat(BobUserId, BobTextChatUser); controller.JoinRoom(BobUserId, EnglishRoomId); // Carol's actions controller.JoinChat(CarolUserId, CarolTextChatUser); var tuple = controller.JoinRoom(CarolUserId, EnglishRoomId); var listOfUsersGuid = tuple.Item1; var listOfMessages = tuple.Item2; // Check results Assert.AreEqual(2, listOfUsersGuid.Count); // 2 because Carol was excluded from the list Assert.IsNotNull(listOfMessages); Assert.IsNull(listOfMessages.FirstOrDefault(a => a.RoomId != EnglishRoomId)); }
// Get the users' connections related to a specific private room private IEnumerable <ConnectionId> GetRelatedConnections(RoomId roomId, ConnectionId exceptConnection = null) { var userIds = TextChatController.UserIdsInPrivateRoom(roomId); return(UsersConnections.All.Where(r => userIds.Contains(r.Key)).SelectMany(v => v.Value).Where(c => c != exceptConnection)); }
// Get the partner's connections related to a specific private room private IEnumerable <ConnectionId> GetPartnerConnections(RoomId roomId, UserId thisUserId) { return(GetUserConnections(TextChatController.PartnerInPrivateRoom(roomId, thisUserId))); }
public async Task CallFinished(string roomId, string reasonString) { if (string.IsNullOrEmpty(roomId)) { return; } Log.SignalR(LogTag.ReceivedCallFinished, new { roomId, reason = reasonString }, Context); VoiceCall vc; var reason = CallFinishedReason.Parse(reasonString); RoomsVoiceCallsDictionary.TryGetValue(roomId, out vc); var userId = Context.User.Identity.GetClaims().Id; if (vc == null) { if (reason.Reason == "unsupported" && reason.Src != CallFinishedReason.Reporter.Peer) { // Special case #2. unsupported_join stands for voice call attempt from the same user that was already joined to the same room // We're not finishing established call in this case if (reason.Detail == "join") { return; } // Special case! Reporting about call finish and failed to initiate it // so we don't have anything for it in RoomsVoiceCallsDictionary but have to save something to DB vc = new VoiceCall { Created = DateTime.Now, CallerId = userId, CalleeId = TextChatController.PartnerInPrivateRoom(roomId, userId), Platform = 1, // Formerly obtained from VoicePlatforms table (table abandonned when VoiceOut got removed) Source = Enumerables.SourceFeatures.PrivateTextChat }; RoomsVoiceCallsDictionary.Add(roomId, vc); _db.VoiceCalls.Add(vc); } else { return; } } else { ((DbContext)_db).Entry(vc).State = EntityState.Modified; } RoomsVoiceCallsDictionary.Remove(roomId); vc.Ended = DateTime.Now; var isCallerEvent = (userId == vc.CallerId && reason.Src != CallFinishedReason.Reporter.Peer) || (userId != vc.CallerId && reason.Src == CallFinishedReason.Reporter.Peer); byte outcome; switch (reason.Reason) { case "cancelled": outcome = Enumerables.VoiceCallOutcomes.Cancel; break; case "declined": outcome = Enumerables.VoiceCallOutcomes.Decline; break; case "unsupported": if (reason.Detail == "browser") { outcome = isCallerEvent ? Enumerables.VoiceCallOutcomes.FailOnCallerUnsupportedBrowser : Enumerables.VoiceCallOutcomes.FailOnCalleeUnsupportedBrowser; } else { outcome = isCallerEvent ? Enumerables.VoiceCallOutcomes.FailOnCallerMicNotFound : Enumerables.VoiceCallOutcomes.FailOnCalleeMicNotFound; } break; case "hangout": outcome = isCallerEvent ? Enumerables.VoiceCallOutcomes.CallerHangout : Enumerables.VoiceCallOutcomes.CalleeHangout; break; case "leftRoom": outcome = isCallerEvent ? Enumerables.VoiceCallOutcomes.CallerLeft : Enumerables.VoiceCallOutcomes.CalleeLeft; break; case "disconnected": outcome = isCallerEvent ? Enumerables.VoiceCallOutcomes.LostCaller : Enumerables.VoiceCallOutcomes.LostCallee; break; default: outcome = Enumerables.VoiceCallOutcomes.Error; break; } vc.Outcome = outcome; Log.SignalR(LogTag.SavingCallOutcome, new { roomId }, Context); await _db.SaveChangesAsync(); }