using PE.Plugins.PubnubChat.Models; using PubnubApi; using System; namespace PE.Plugins.PubnubChat { public enum ChatState { None, Waiting, Typing, InCall } public class ChatService : IChatService, IDisposable { #region Events public event InitializationChangedEventHandler InitializedChanged; public event EventHandler ConnectedChanged; public event EventHandler<MessageEventArgs> MessageReceived; public event EventHandler<PresenceEventArgs> ChannelJoined; public event EventHandler<PresenceEventArgs> ChannelLeft; public event EventHandler<PresenceEventArgs> ChannelTimeout; public event EventHandler<PresenceEventArgs> ChannelState; public event EventHandler<MessageEventArgs> PublishComplete; #endregion Events #region Fields private Pubnub _Pubnub; private readonly string _PublishKey = string.Empty; private readonly string _SubscribeKey = string.Empty; private SubscribeCallbackExt _ListenerSubscribeCallack; private string _UserId = string.Empty; private string _AuthKey = string.Empty; private bool _Disposed = false; private int _FailCount = 0; private string _PushData = string.Empty; #endregion Fields #region Constructors public ChatService(ChatConfiguration configuration) { _PublishKey = configuration.PublishKey; _SubscribeKey = configuration.SubscribeKey; } ~ChatService() { Dispose(); } #endregion Constructors #region Properties public bool Connected { get; private set; } public bool Initialized { get; private set; } = false; #endregion Properties #region Init public void Initialize(string userId, string authKey = null, bool reset = true, string push = "") { System.Diagnostics.Debug.WriteLine($"*** {GetType().Name}.Uninitialize - Initializing Chat for user {_UserId}, Reset: {reset}"); if (reset) _FailCount = 0; // we can only initialize if the user is registered if (string.IsNullOrEmpty(userId)) return; _UserId = userId; _AuthKey = authKey; _PushData = push; PNConfiguration config = new PNConfiguration(); config.PublishKey = _PublishKey; config.SubscribeKey = _SubscribeKey; config.Uuid = _UserId; config.Secure = true; config.HeartbeatNotificationOption = PNHeartbeatNotificationOption.All; if (!string.IsNullOrEmpty(authKey)) config.AuthKey = authKey; _Pubnub = new Pubnub(config); _ListenerSubscribeCallack = new SubscribeCallbackExt( (pubnub, message) => OnMessageReceived(pubnub, message), (pubnub, presence) => OnPresenceReceived(pubnub, presence), (pubnub, status) => OnStatusReceived(pubnub, status)); _Pubnub.AddListener(_ListenerSubscribeCallack); // create and subscribe to the lobby channel _Pubnub .Subscribe<string>() .Channels(new string[] { _UserId }) .WithPresence() .Execute(); // add push if necessary if (!string.IsNullOrEmpty(_PushData)) { var data = _PushData.Split(new char[] { '|' }); if (data.Length != 2) throw new ArgumentException("Push initialization data."); var type = (PNPushType)Enum.Parse(typeof(PNPushType), data[0]); _Pubnub.AddPushNotificationsOnChannels() .PushType(type) .Channels(new string[] { _UserId }) //.DeviceId(data[1]) .DeviceId(data[1]) .Async(new AddPushChannelCallback()); } Initialized = true; InitializedChanged?.Invoke(Initialized); } public void Uninitialize() { // this can be called before Initialize if (_Pubnub == null) return; try { System.Diagnostics.Debug.WriteLine($"*** {GetType().Name}.Uninitialize - Uninitializing Chat for user {_UserId}"); _Pubnub.RemoveListener(_ListenerSubscribeCallack); _Pubnub.Unsubscribe<string>().Channels(new string[] { _UserId }).Execute(); // TODO: consider removing push channel association here Initialized = false; InitializedChanged?.Invoke(Initialized); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"{ GetType().Name}.Uninitialize - Exception: { ex}"); } } #endregion Init #region Callbacks private void OnMessageReceived(Pubnub pubnub, PNMessageResult<object> result) { try { MessageReceived?.Invoke(this, new MessageEventArgs(result.Message.ToString())); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"*** {GetType().Name}.MessageReceived - Unable to deserialize message: { result.Message}\r\n\tException: { ex}"); } } private void OnPresenceReceived(Pubnub pubnub, PNPresenceEventResult result) { System.Diagnostics.Debug.WriteLine($"*** { GetType().Name}.OnPresenceReceived\r\n -> Event: { result.Event}"); { // handle incoming presence data if (result.Event.Equals("join")) { RaiseChannelJoined(result.Channel, result.Uuid); } else if (result.Event.Equals("leave")) { RaiseChannelLeft(result.Channel, result.Uuid); } else if (result.Event.Equals("state-change")) { // listen for status events - eg: typing, etc if ((result.State == null) || (result.State.Count == 0)) return; foreach (var key in result.State.Keys) { var state = (ChatState)Enum.Parse(typeof(ChatState), result.State[key].ToString()); RaiseChannelState(result.Channel, result.Uuid, state); } } else if (result.Event.Equals("timeout")) { } else if (result.Event.Equals("interval")) { // find the ids that have joined if ((result.Join != null) && (result.Join.Length > 0)) { foreach (var uuid in result.Join) RaiseChannelJoined(result.Channel, uuid); } if ((result.Leave != null) && (result.Leave.Length > 0)) { foreach (var uuid in result.Leave) RaiseChannelJoined(result.Channel, uuid); } } else if (result.HereNowRefresh) { // TODO: request state for channels //GetState(); } } } private void OnStatusReceived(Pubnub pubnub, PNStatus status) { { System.Diagnostics.Debug.WriteLine($"*** {GetType().Name}.OnStatusReceived\r\n -> Operation: { status.Operation}\r\n -> Category: { status.Category}\r\n -> Error: { status.Error}\r\n -> Data: { status.ErrorData}\r\n -> Status Code: { status.StatusCode}"); if (status.Operation == PNOperationType.PNHeartbeatOperation) { Connected = !status.Error; ConnectedChanged?.Invoke(this, new EventArgs()); } if ((status.Operation == PNOperationType.PNSubscribeOperation) && (status.Category == PNStatusCategory.PNUnknownCategory) && (status.StatusCode == 404)) { try { _FailCount++; if (_FailCount > 3)
} Initialized = true; InitializedChanged?.Invoke(Initialized); } public void Uninitialize() { // this can be called before Initialize if (_Pubnub == null) return; try { System.Diagnostics.Debug.WriteLine($"*** {GetType().Name}.Uninitialize - Uninitializing Chat for user {_UserId}"); _Pubnub.RemoveListener(_ListenerSubscribeCallack); _Pubnub.Unsubscribe<string>().Channels(new string[] { _UserId }).Execute(); // TODO: consider removing push channel association here Initialized = false; InitializedChanged?.Invoke(Initialized); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"{ GetType().Name}.Uninitialize - Exception: { ex}"); } } #endregion Init #region Callbacks private void OnMessageReceived(Pubnub pubnub, PNMessageResult<object> result) { try { MessageReceived?.Invoke(this, new MessageEventArgs(result.Message.ToString())); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"*** {GetType().Name}.MessageReceived - Unable to deserialize message: { result.Message}\r\n\tException: { ex}"); } } private void OnPresenceReceived(Pubnub pubnub, PNPresenceEventResult result) { System.Diagnostics.Debug.WriteLine($"*** { GetType().Name}.OnPresenceReceived\r\n -> Event: { result.Event}"); { // handle incoming presence data if (result.Event.Equals("join")) { RaiseChannelJoined(result.Channel, result.Uuid); } else if (result.Event.Equals("leave")) { RaiseChannelLeft(result.Channel, result.Uuid); } else if (result.Event.Equals("state-change")) { // listen for status events - eg: typing, etc if ((result.State == null) || (result.State.Count == 0)) return; foreach (var key in result.State.Keys) { var state = (ChatState)Enum.Parse(typeof(ChatState), result.State[key].ToString()); RaiseChannelState(result.Channel, result.Uuid, state); } } else if (result.Event.Equals("timeout")) { } else if (result.Event.Equals("interval")) { // find the ids that have joined if ((result.Join != null) && (result.Join.Length > 0)) { foreach (var uuid in result.Join) RaiseChannelJoined(result.Channel, uuid); } if ((result.Leave != null) && (result.Leave.Length > 0)) { foreach (var uuid in result.Leave) RaiseChannelJoined(result.Channel, uuid); } } else if (result.HereNowRefresh) { // TODO: request state for channels //GetState(); } } } private void OnStatusReceived(Pubnub pubnub, PNStatus status) { { System.Diagnostics.Debug.WriteLine($"*** {GetType().Name}.OnStatusReceived\r\n -> Operation: { status.Operation}\r\n -> Category: { status.Category}\r\n -> Error: { status.Error}\r\n -> Data: { status.ErrorData}\r\n -> Status Code: { status.StatusCode}"); if (status.Operation == PNOperationType.PNHeartbeatOperation) { Connected = !status.Error; ConnectedChanged?.Invoke(this, new EventArgs()); } if ((status.Operation == PNOperationType.PNSubscribeOperation) && (status.Category == PNStatusCategory.PNUnknownCategory) && (status.StatusCode == 404)) { try { _FailCount++; if (_FailCount > 3)
public void Initialize(string userId, long lastActivity = 0) { try { // we can only initialize if the user is registered if (string.IsNullOrEmpty(userId)) { return; } _UserId = userId; PNConfiguration config = new PNConfiguration(); config.PublishKey = _PublishKey; config.SubscribeKey = _SubscribeKey; config.Uuid = _UserId; config.Secure = true; _Pubnub = new Pubnub(config); SubscribeCallbackExt listenerSubscribeCallack = new SubscribeCallbackExt((pubnubObj, message) => { try { // get the message base to determine type BaseMessage m = Serializer.Deserialize <BaseMessage>(message.Message.ToString()); // deserialize to actual type m = (BaseMessage)Serializer.Deserialize(GetType().Assembly.GetType(m.Type), message.Message.ToString()); // let listeners know MessageReceived?.Invoke(this, new MessageEventArgs <BaseMessage>(m)); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(string.Format("*** ChatService.MessageReceived - Unable to deserialize message: {0}", message.Message.ToString())); } }, (pubnubObj, presence) => { // handle incoming presence data if (presence.Event.Equals("join")) { RaiseChannelJoined(presence.Channel, presence.Uuid); } else if (presence.Event.Equals("leave")) { RaiseChannelLeft(presence.Channel, presence.Uuid); } else if (presence.Event.Equals("state-change")) { // listen for status events - eg: typing, etc if ((presence.State == null) || (presence.State.Count == 0)) { return; } foreach (var key in presence.State.Keys) { var state = (ChatState)Enum.Parse(typeof(ChatState), presence.State[key].ToString()); RaiseChannelState(presence.Channel, presence.Uuid, state); } } else if (presence.Event.Equals("timeout")) { } else if (presence.Event.Equals("interval")) { // find the ids that have joined if ((presence.Join != null) && (presence.Join.Length > 0)) { foreach (var uuid in presence.Join) { RaiseChannelJoined(presence.Channel, uuid); } } if ((presence.Leave != null) && (presence.Leave.Length > 0)) { foreach (var uuid in presence.Leave) { RaiseChannelJoined(presence.Channel, uuid); } } } else if (presence.HereNowRefresh) { // TODO: request state for channels //GetState(); } }, (pubnubObj, status) => { if (status.Operation == PNOperationType.PNHeartbeatOperation) { Connected = !status.Error; ConnectedChanged?.Invoke(this, new EventArgs()); } else if ((status.Operation != PNOperationType.PNSubscribeOperation) && (status.Operation != PNOperationType.PNUnsubscribeOperation)) { return; } if (status.Category == PNStatusCategory.PNConnectedCategory) { // this is expected for a subscribe, this means there is no error or issue whatsoever } else if (status.Category == PNStatusCategory.PNReconnectedCategory) { // this usually occurs if subscribe temporarily fails but reconnects. This means // there was an error but there is no longer any issue } else if (status.Category == PNStatusCategory.PNDisconnectedCategory) { // this is the expected category for an unsubscribe. This means there // was no error in unsubscribing from everything } else if (status.Category == PNStatusCategory.PNUnexpectedDisconnectCategory) { // this is usually an issue with the internet connection, this is an error, handle appropriately } else if (status.Category == PNStatusCategory.PNAccessDeniedCategory) { // this means that PAM does allow this client to subscribe to this // channel and channel group configuration. This is another explicit error } }); _Pubnub.AddListener(listenerSubscribeCallack); // create and subscribe to the lobby channel _Pubnub .Subscribe <string>() .Channels(new string[] { _UserId }) .WithPresence() .Execute(); Initialized = true; InitializedChanged?.Invoke(this, new EventArgs()); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(string.Format("*** ChatService.Initialize - Exception: {0}", ex)); } }
protected virtual void OnInitializedChanged(bool state) { InitializedChanged?.Invoke(this, state); }