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);
        }
Exemplo n.º 2
0
        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)));
 }
Exemplo n.º 10
0
        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();
        }