using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR; using xCommons.Extensions; using xExceptions.Constants; using xModels.Dtos; using xPushService.Constants; using xPushService.Extensions; using xPushService.Interfaces; using xPushService.Models; namespace xPushService.Base { public abstract class XBaseWebRTCHub : XBaseHub { // #region Props ... protected readonly IXWebRTCConnectionStore webRTCConnectionStore; #endregion // #region Constructor ... public XBaseWebRTCHub ( IXPushGroupStore groupStore, IXPushConnectionStore connectionStore, IXWebRTCConnectionStore webRTCConnectionStore ) : base (groupStore, connectionStore) { this.webRTCConnectionStore = webRTCConnectionStore; } #endregion // #region Overrides ... public override Task OnConnectedAsync () { return base.OnConnectedAsync (); } public override Task OnDisconnectedAsync (Exception exception) { // // // var connectionId = GetConnectionId (); // // // // Find Connections Active Calls ... // var activeConnections = webRTCConnectionStore // .FindMany (c => c.CallerConnectionId // .ToNormalString () == connectionId // .ToNormalString () || // c.IsInCallees (connectionId) // ) // .RunTask (); // // // if (activeConnections.HasChild ()) { // activeConnections // .ToList () // .ForEach (c => { // // // CancelCall ( // cancellerConnectionId: connectionId, // reason: XCallEndReason.Disconnected, // request: c // ) // .RunTask (); // }); // } // return base.OnDisconnectedAsync (exception); } #endregion // #region Actions ... /// /// Send PreOffer Call Request ... /// /// /// /// /// public Task RequestCall ( XCallType type, string connectionId, XDeviceDto device ) { // // Validate Args ... if (type == XCallType.None || connectionId.IsNullOrEmpty ()) { return Task.FromResult (false); } // // Check Connection Exists ... var isConnectionExists = connectionStore .IsExistsConnectionId (connectionId) .RunTask (); if (!isConnectionExists) { return Task.FromResult (false); } // // retrieve connection object ... var request = GetWebRTCConnection ( calleeDevice: device, connectionType: type, calleeConnectionId: connectionId ) .RunTask (); // // Add Connection to Store ... AddOrUpdateWebRTCConnection (request) .RunTask (); // try { // Clients .Clients ( request .GetReceivers () ) .SendAsync ( XWebRTCAction.RequestCall.GetStringValue (), request ) .RunTask (); // return Task.FromResult (true); } catch { } // return Task.FromResult (false); } /// /// Send PreOffer Call Request ... /// /// /// /// /// public Task RequestUserCall ( XCallType type, string user, XDeviceDto device ) { // // Validate Args ... if (type == XCallType.None || user.IsNullOrEmpty ()) { return Task.FromResult (false); } // // Check Connection Exists ... var isConnectionExists = connectionStore .IsExistsUser (user) .RunTask (); if (!isConnectionExists) { return Task.FromResult (false); } // // retrieve connection object ... var request = GetUserWebRTCConnection ( calleeDevice: device, connectionType: type, calleeUser: user ) .RunTask (); // // Add Connection to Store ... AddOrUpdateWebRTCConnection (request) .RunTask (); // try { // Clients .Clients ( request .GetReceivers () ) .SendAsync ( XWebRTCAction.RequestCall.GetStringValue (), request ) .RunTask (); // return Task.FromResult (true); } catch { } // return Task.FromResult (false); } /// /// Cancel or End Requested Call ... /// /// public Task CancelCall ( string cancellerConnectionId, XCallEndReason reason, XWebRTCConnectionDto request) { // // Validate Args ... var isValid = IsWebRTCConnectionsExists (request) .RunTask (); if (!isValid) { return Task.FromResult (false); } // // Check if i am Caller ... var isCancellerCaller = request .IsCaller (cancellerConnectionId); var isCancellerInCallees = request .IsInCallees (cancellerConnectionId); // if (isCancellerCaller) { // request.Callees.ToList ().ForEach (callee => { // callee.EndReason = reason; callee.EndTime = DateTime.UtcNow; }); // // Remove Connection From Store ... webRTCConnectionStore .Remove (request) .RunTask (); } else if (isCancellerInCallees) { // var canceller = request.GetCallee (cancellerConnectionId); if (canceller.IsNull ()) { return Task.FromResult (false); } // var updatedCanceller = canceller; updatedCanceller.EndReason = reason; updatedCanceller.EndTime = DateTime.UtcNow; // request.Callees = request.Callees .Update (canceller, updatedCanceller); // // Remove connection from Store if all callees end calls ... var canRemoveConnection = request.Callees.All (c => !c.EndTime.IsNull () && !c.EndReason.IsNull ()); if (canRemoveConnection) { webRTCConnectionStore .Remove (request) .RunTask (); } else { webRTCConnectionStore .Update (request) .RunTask (); } } // try { // Clients .Clients ( request.GetReceivers () ) .SendAsync ( XWebRTCAction.CancelCall.GetStringValue (), request ); // return Task.FromResult (true); } catch { return Task.FromResult (false); } } /// /// end a call request ... /// /// /// public Task RejectCall ( string rejecterConnectionId, XWebRTCConnectionDto request ) { // // Validate Args ... var isValid = IsWebRTCConnectionsExists (request) .RunTask (); var isRejecterInCallees = request .IsInCallees (rejecterConnectionId); if (!isValid || !isRejecterInCallees || rejecterConnectionId.IsNullOrEmpty () ) { return Task.FromResult (false); } // // Find Rejecter and Push Reject to it ... var rejecter = request .GetCallee (rejecterConnectionId); if (rejecter.IsNull ()) { return Task.FromResult (false); } // // Update request Callees ... var updatedRejecter = rejecter; updatedRejecter.EndTime = DateTime.UtcNow; updatedRejecter.EndReason = XCallEndReason.Reject; request.Callees = request.Callees .Update (rejecter, updatedRejecter); // // Check if Only one Callee and he Reject, Remove connection From Store, // otherwise Update connection ... var canRemoveConnection = request .Callees.Count () == 1 && request.Callees .All (c => c.ConnectionId .ToNormalString () == rejecterConnectionId .ToNormalString () ); if (canRemoveConnection) { webRTCConnectionStore .Remove (request) .RunTask (); } // // Prepare Receivers ... var receivers = new List () { request.CallerConnectionId, rejecterConnectionId } .AsReadOnly (); // try { // Clients .Clients (receivers) .SendAsync ( XWebRTCAction.RejectCall.GetStringValue (), request ) .RunTask (); // return Task.FromResult (true); } catch { } // return Task.FromResult (false); } /// /// answer a call request ... /// /// /// public Task AcceptCall ( string accepterConnectionId, XWebRTCConnectionDto request ) { // // Validate Args ... var isValid = IsWebRTCConnectionsExists (request) .RunTask (); var isAccepterInCallees = request .IsInCallees (accepterConnectionId); if (!isValid || !isAccepterInCallees || accepterConnectionId.IsNullOrEmpty () ) { return Task.FromResult (false); } // // Add Or Update ... AddOrUpdateWebRTCConnection (request) .RunTask (); // // prepare recievers ... var receivers = new List () { request.CallerConnectionId, accepterConnectionId, } .AsReadOnly (); // try { // Clients .Clients (receivers) .SendAsync ( XWebRTCAction.AcceptCall.GetStringValue (), request ) .RunTask (); // return Task.FromResult (true); } catch { return Task.FromResult (false); } } #endregion // #region WebRTC Signaling ... /// /// Send WebRTC Offer ... /// /// /// /// public Task WebRTCOffer ( XWebRTCSignalDto signal, XWebRTCConnectionDto request ) { // if ( signal.IsNull () || signal.Offer.IsNull () || !IsValidWebRTCConnection (request) ) { return Task.FromResult (false); } // var connectionId = GetConnectionId (); var offerRecievers = request .GetReceivers () .Where (r => r.ToNormalString () != connectionId.ToNormalString ()) .ToList () .AsReadOnly (); // try { // Clients .Clients (offerRecievers) .SendAsync ( XWebRTCAction.WebRTCOffer.GetStringValue (), signal, request ) .RunTask (); // return Task.FromResult (true); } catch { return Task.FromResult (false); } } /// /// Send WebRTC Answer ... /// /// /// /// public Task WebRTCAnswer ( XWebRTCSignalDto signal, XWebRTCConnectionDto request ) { // if ( signal.IsNull () || signal.Offer.IsNull () || signal.Answer.IsNull () || !IsValidWebRTCConnection (request) ) { return Task.FromResult (false); } // var connectionId = GetConnectionId (); var answerRecievers = request .GetReceivers () .Where (r => r.ToNormalString () != connectionId.ToNormalString ()) .ToList () .AsReadOnly (); // try { // Clients .Clients (answerRecievers) .SendAsync ( XWebRTCAction.WebRTCAnswer.GetStringValue (), signal, request ) .RunTask (); // return Task.FromResult (true); } catch { return Task.FromResult (false); } } /// /// Send WebRTC ICE Candidates ... /// /// /// /// public Task WebRTCICECandidate ( XWebRTCSignalDto signal, XWebRTCConnectionDto request ) { // if ( signal.IsNull () || signal.Offer.IsNull () || signal.Answer.IsNull () || !signal.Cadidates.HasChild () || !IsValidWebRTCConnection (request) ) { return Task.FromResult (false); } // var connectionId = GetConnectionId (); var candidatesRecievers = request .GetReceivers () .Where (r => r.ToNormalString () != connectionId.ToNormalString ()) .ToList () .AsReadOnly (); // try { // Clients .Clients (candidatesRecievers) .SendAsync ( XWebRTCAction.WebRTCICECandidate.GetStringValue (), signal, request ) .RunTask (); // return Task.FromResult (true); } catch { return Task.FromResult (false); } } #endregion // #region Protected ... /// /// create first time WebRTC connection by user name ... /// /// /// /// /// protected Task GetUserWebRTCConnection ( XCallType connectionType, string calleeUser, XDeviceDto calleeDevice ) { // // Validate Args ... if ( calleeDevice.IsNull () || connectionType == XCallType.None || calleeUser.IsNullOrEmpty () ) { throw XException.InvalidArgs.ToException (); } // var id = Guid.NewGuid ().ToString (); // var httpContext = Context.GetHttpContext (); var deviceJson = httpContext.Request.Query["Option"].ToString (); // // Retrieve Caller Info ... var callerConnectionId = Context.ConnectionId; var callerUserName = Context?.User?.Identity?.Name; var callerDevice = deviceJson.FromJSON (); // // Retrieve Connection ... var calleeConnection = connectionStore .FindOne (c => c.User.ToNormalString () == calleeUser.ToNormalString ()) .RunTask (); // var result = new XWebRTCConnectionDto { // Id = id, // Payload = null, Type = connectionType, // RequestTime = DateTime.UtcNow, // CallerUser = callerUserName, CallerDevice = callerDevice, CallerConnectionId = callerConnectionId, // Callees = new List { new XWebRTCCalleeDto { Device = calleeDevice, User = calleeConnection.User, ConnectionId = calleeConnection.Id, } } }; // return Task.FromResult (result); } /// /// create first time WebRTC connection ... /// /// /// /// /// protected Task GetWebRTCConnection ( XCallType connectionType, string calleeConnectionId, XDeviceDto calleeDevice ) { // // Validate Args ... if ( calleeDevice.IsNull () || connectionType == XCallType.None || calleeConnectionId.IsNullOrEmpty () ) { throw XException.InvalidArgs.ToException (); } // var id = Guid.NewGuid ().ToString (); // var httpContext = Context.GetHttpContext (); var deviceJson = httpContext.Request.Query["Option"].ToString (); // // Retrieve Caller Info ... var callerConnectionId = Context.ConnectionId; var callerUserName = Context?.User?.Identity?.Name; var callerDevice = deviceJson.FromJSON (); // // Retrieve Connection ... var calleeConnection = connectionStore .GetByConnectionId (calleeConnectionId) .RunTask (); // var result = new XWebRTCConnectionDto { // Id = id, // Payload = null, Type = connectionType, // RequestTime = DateTime.UtcNow, // CallerUser = callerUserName, CallerDevice = callerDevice, CallerConnectionId = callerConnectionId, // Callees = new List { new XWebRTCCalleeDto { Device = calleeDevice, User = calleeConnection.User, ConnectionId = calleeConnectionId, } } }; // return Task.FromResult (result); } /// /// Check i am caller or not ... /// /// /// protected bool AmICaller (XWebRTCConnectionDto request) { // // Validate Args ... if (!request.IsValid () || !IsValidWebRTCConnection (request) ) { throw XException.InvalidArgs.ToException (); } // var result = request.IsCaller (GetConnectionId ()); return result; } /// /// Check i am callee or not ... /// /// /// protected bool AmICallee (XWebRTCConnectionDto request) { // // Validate Args ... if (!request.IsValid () || !IsValidWebRTCConnection (request)) { throw XException.InvalidArgs.ToException (); } // var result = request.IsInCallees (GetConnectionId ()); return result; } /// /// validate a web rtc connection ... /// /// /// protected bool IsValidWebRTCConnection (XWebRTCConnectionDto request) { // // Validate Args ... if ( request.IsNull () || !request.IsValid () || !request.Callees.HasChild () || request.Type == XCallType.None || request.CallerConnectionId.IsNullOrEmpty () ) { return false; } // return true; } /// /// check bot Caller and Callee Connections Exists ... /// /// /// protected Task IsWebRTCConnectionsExists (XWebRTCConnectionDto request) { // // Validate Args ... if (!IsValidWebRTCConnection (request)) { return Task.FromResult (false); } // var isCallerConnectionExists = connectionStore .IsExistsConnectionId (request.CallerConnectionId) .RunTask (); // var isCalleesConnectionExists = !request.Callees.HasChild () ? true : Task.WhenAll (request.Callees.Select (callee => connectionStore .IsExistsConnectionId (callee.ConnectionId))) .ContinueWith (allTasks => { // var allResults = allTasks .RunTask (); // return allResults.All (r => r == true); }) .RunTask (); // if (!isCallerConnectionExists || !isCalleesConnectionExists) { return Task.FromResult (false); } // return Task.FromResult (true); } /// /// Add or Update a WebRTCConnection To/In Store ... /// /// /// protected Task AddOrUpdateWebRTCConnection (XWebRTCConnectionDto request) { // if (!request.IsValid () || request.Id.IsNullOrEmpty () ) { return Task.FromResult (false); } // // Check Exists or not ... var isWebRTCConnectionExists = webRTCConnectionStore .IsExistsByKey (request.Id) .RunTask (); // // Act based on existance ... if (!isWebRTCConnectionExists) { return webRTCConnectionStore.Add (request); } else { // var updated = webRTCConnectionStore.Update (request) .RunTask (); var isUpdate = !updated.IsNull (); // return Task.FromResult (isUpdate); } } #endregion } }