private Channel CreateChannel(string channelName)
        {
            ChannelTypes type = Channel.GetChannelType(channelName);
            Channel      result;

            switch (type)
            {
            case ChannelTypes.Private:
            case ChannelTypes.PrivateEncrypted:
                AuthEndpointCheck(channelName);
                result = new PrivateChannel(channelName, this);
                break;

            case ChannelTypes.Presence:
                AuthEndpointCheck(channelName);
                result = new PresenceChannel(channelName, this);
                break;

            default:
                result = new Channel(channelName, this);
                break;
            }

            if (!Channels.TryAdd(channelName, result))
            {
                result = Channels[channelName];
            }

            return(result);
        }
        /// <summary>
        /// Subscribes to a typed member info presence channel.
        /// </summary>
        /// <typeparam name="MemberT">The type used to deserialize channel member info.</typeparam>
        /// <param name="channelName">The name of the channel to subsribe to.</param>
        /// <param name="subscribedEventHandler">An optional <see cref="SubscriptionEventHandler"/>. Alternatively, use <c>Pusher.Subscribed</c>.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="channelName"/> is <c>null</c> or whitespace.</exception>
        /// <exception cref="ChannelUnauthorizedException">If authorization fails.</exception>
        /// <exception cref="ChannelAuthorizationFailureException">f an HTTP call to the authorization URL fails; that is, the HTTP status code is outside of the range 200-299.</exception>
        /// <exception cref="ChannelException">If the client receives an error (pusher:error) from the Pusher cluster while trying to subscribe.</exception>
        /// <exception cref="OperationTimeoutException">
        /// If the client times out waiting for the Pusher server to confirm the subscription. The timeout is defind in the <see cref="PusherOptions"/>.
        /// </exception>
        /// <returns>A GenericPresenceChannel<MemberT> channel identified by <paramref name="channelName"/>.</returns>
        /// <remarks>
        /// If Pusher is connected when calling this method, the channel will be subscribed when this method returns;
        /// that is <c>channel.IsSubscribed == true</c>.
        /// If Pusher is not connected when calling this method, <c>channel.IsSubscribed == false</c> and
        /// the channel will only be subscribed after calling <c>Pusher.ConnectAsync</c>.
        /// </remarks>
        public async Task <GenericPresenceChannel <MemberT> > SubscribePresenceAsync <MemberT>(
            string channelName,
            SubscriptionEventHandler subscribedEventHandler = null)
        {
            Guard.ChannelName(channelName);

            var channelType = Channel.GetChannelType(channelName);

            if (channelType != ChannelTypes.Presence)
            {
                throw new ArgumentException($"The channel name '{channelName}' is not that of a presence channel. Expecting the name to start with '{Constants.PRESENCE_CHANNEL}'.", nameof(channelName));
            }

            GenericPresenceChannel <MemberT> result;

            if (Channels.TryGetValue(channelName, out Channel channel))
            {
                if (!(channel is GenericPresenceChannel <MemberT> presenceChannel))
                {
                    string errorMsg;
                    if (channel is PresenceChannel)
                    {
                        errorMsg = $"The presence channel '{channelName}' has already been created as a {nameof(PresenceChannel)} : {nameof(PresenceChannel)}<dynamic>.";
                    }
                    else
                    {
                        errorMsg = $"The presence channel '{channelName}' has already been created but with a different type: {channel.GetType()}";
                    }

                    throw new ChannelException(errorMsg, ErrorCodes.PresenceChannelAlreadyDefined, channelName, SocketID);
                }

                if (presenceChannel.IsSubscribed)
                {
                    result = presenceChannel;
                }
                else
                {
                    result = (await SubscribeAsync(channelName, presenceChannel, subscribedEventHandler).ConfigureAwait(false)) as GenericPresenceChannel <MemberT>;
                }
            }
            else
            {
                AuthEndpointCheck(channelName);
                result = new GenericPresenceChannel <MemberT>(channelName, this);
                if (Channels.TryAdd(channelName, result))
                {
                    result = (await SubscribeAsync(channelName, result, subscribedEventHandler).ConfigureAwait(false)) as GenericPresenceChannel <MemberT>;
                }
            }

            return(result);
        }
        private void ValidateTriggerInput(string channelName, string eventName)
        {
            if (Channel.GetChannelType(channelName) == ChannelTypes.Public)
            {
                string errorMsg = $"Failed to trigger event '{eventName}'. Client events are only supported on private (non-encrypted) and presence channels.";
                throw new TriggerEventException(errorMsg, ErrorCodes.TriggerEventPublicChannelError);
            }

            if (Channel.GetChannelType(channelName) == ChannelTypes.PrivateEncrypted)
            {
                string errorMsg = $"Failed to trigger event '{eventName}'. Client events are not supported on private encrypted channels.";
                throw new TriggerEventException(errorMsg, ErrorCodes.TriggerEventPrivateEncryptedChannelError);
            }

            string token = "client-";

            if (eventName.IndexOf(token, StringComparison.OrdinalIgnoreCase) != 0)
            {
                string errorMsg = $"Failed to trigger event '{eventName}'. Client events must start with '{token}'.";
                throw new TriggerEventException(errorMsg, ErrorCodes.TriggerEventNameInvalidError);
            }

            if (State != ConnectionState.Connected)
            {
                string errorMsg = $"Failed to trigger event '{eventName}'. Client needs to be connected. Current connection state is {State}.";
                throw new TriggerEventException(errorMsg, ErrorCodes.TriggerEventNotConnectedError);
            }

            bool subscribed = false;

            if (Channels.TryGetValue(channelName, out Channel channel))
            {
                subscribed = channel.IsSubscribed;
            }

            if (!subscribed)
            {
                string errorMsg = $"Failed to trigger event '{eventName}'. Channel '{channelName}' needs to be subscribed.";
                throw new TriggerEventException(errorMsg, ErrorCodes.TriggerEventNotSubscribedError);
            }
        }