/// <summary>
        /// 自动重连
        /// </summary>
        /// <returns></returns>
        public Task AutoReconnect()
        {
            return(OpenAsync(_wss).ContinueWith(t =>
            {
                state = Status.Reconnecting;
                var cmd = new SessionCommand()
                          .UA(VersionString)
                          .Tag(_tag)
                          .R(1)
                          .SessionToken(this._sesstionToken)
                          .Option("open")
                          .PeerId(_clientId);

                return AttachSignature(cmd, this.SignatureFactory.CreateConnectSignature(_clientId)).OnSuccess(_ =>
                {
                    return AVIMCommandRunner.RunCommandAsync(cmd);
                }).Unwrap();
            }).Unwrap().OnSuccess(s =>
            {
                var result = s.Result;
                if (result.Item1 == 0)
                {
                    state = Status.Online;
                }
                else
                {
                    state = Status.Offline;
                }
            }));
        }
        /// <summary>
        /// 创建 Client
        /// </summary>
        /// <param name="clientId"></param>
        /// <param name="tag"></param>
        /// <param name="deviceId">设备唯一的 Id。如果是 iOS 设备,需要将 iOS 推送使用的 DeviceToken 作为 deviceId 传入</param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public Task <AVIMClient> CreateClient(
            string clientId,
            string tag      = null,
            string deviceId = null,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            _clientId = clientId;
            _tag      = tag;
            if (_tag != null)
            {
                if (deviceId == null)
                {
                    throw new ArgumentNullException(deviceId, "当 tag 不为空时,必须传入当前设备不变的唯一 id(deviceId)");
                }
            }

            if (string.IsNullOrEmpty(clientId))
            {
                throw new Exception("当前 ClientId 为空,无法登录服务器。");
            }
            return(OpenAsync(cancellationToken).OnSuccess(t =>
            {
                ToggleNotification(true);

                var cmd = new SessionCommand()
                          .UA(VersionString)
                          .Tag(tag)
                          .Argument("deviceId", deviceId)
                          .Option("open")
                          .PeerId(clientId);

                return AttachSignature(cmd, this.SignatureFactory.CreateConnectSignature(clientId)).OnSuccess(_ =>
                {
                    return AVIMCommandRunner.RunCommandAsync(cmd);
                }).Unwrap();
            }).Unwrap().OnSuccess(s =>
            {
                if (s.Exception != null)
                {
                    var imException = s.Exception.InnerException as AVIMException;
                }
                state = Status.Online;
                var response = s.Result.Item2;
                if (response.ContainsKey("st"))
                {
                    _sesstionToken = response["st"] as string;
                }
                if (response.ContainsKey("stTtl"))
                {
                    var stTtl = long.Parse(response["stTtl"].ToString());
                    _sesstionTokenExpire = DateTime.Now.UnixTimeStampSeconds() + stTtl;
                }
                var client = new AVIMClient(clientId, tag, this);
                return client;
            }));
        }
        /// <summary>
        /// 创建 Client
        /// </summary>
        /// <param name="clientId"></param>
        /// <param name="tag"></param>
        /// <param name="deviceId">设备唯一的 Id。如果是 iOS 设备,需要将 iOS 推送使用的 DeviceToken 作为 deviceId 传入</param>
        /// <param name="secure">是否强制加密 wss 链接</param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public Task <AVIMClient> CreateClientAsync(string clientId,
                                                   string tag      = null,
                                                   string deviceId = null,
                                                   bool secure     = true,
                                                   CancellationToken cancellationToken = default(CancellationToken))
        {
            lock (mutex)
            {
                var client = PreLogIn(clientId, tag, deviceId);

                AVRealtime.PrintLog("begin OpenAsync.");
                return(OpenAsync(secure, Subprotocol, cancellationToken).OnSuccess(t =>
                {
                    AVRealtime.PrintLog("OpenAsync OnSuccess. begin send open sesstion cmd.");

                    var cmd = new SessionCommand()
                              .UA(VersionString)
                              .Tag(tag)
                              .DeviceId(deviceId)
                              .Option("open")
                              .PeerId(clientId);

                    ToggleNotification(true);
                    return AttachSignature(cmd, this.SignatureFactory.CreateConnectSignature(clientId));
                }).Unwrap().OnSuccess(x =>
                {
                    var cmd = x.Result;
                    return AVIMCommandRunner.RunCommandAsync(cmd);
                }).Unwrap().OnSuccess(s =>
                {
                    AVRealtime.PrintLog("sesstion opened.");
                    state = Status.Online;
                    ToggleHeartBeating(_heartBeatingToggle);
                    var response = s.Result.Item2;
                    if (response.ContainsKey("st"))
                    {
                        _sesstionToken = response["st"] as string;
                    }
                    if (response.ContainsKey("stTtl"))
                    {
                        var stTtl = long.Parse(response["stTtl"].ToString());
                        _sesstionTokenExpire = DateTime.Now.UnixTimeStampSeconds() + stTtl;
                    }
                    return client;
                }));
            }
        }
        internal Task LogInAsync(string clientId,
                                 string tag      = null,
                                 string deviceId = null,
                                 bool secure     = true,
                                 CancellationToken cancellationToken = default(CancellationToken))
        {
            lock (mutex)
            {
                var cmd = new SessionCommand()
                          .UA(VersionString)
                          .Tag(tag)
                          .DeviceId(deviceId)
                          .Option("open")
                          .PeerId(clientId);

                var result = AttachSignature(cmd, this.SignatureFactory.CreateConnectSignature(clientId)).OnSuccess(_ =>
                {
                    return(AVIMCommandRunner.RunCommandAsync(cmd));
                }).Unwrap().OnSuccess(t =>
                {
                    AVRealtime.PrintLog("sesstion opened.");
                    if (t.Exception != null)
                    {
                        var imException = t.Exception.InnerException as AVIMException;
                        throw imException;
                    }
                    state        = Status.Online;
                    var response = t.Result.Item2;
                    if (response.ContainsKey("st"))
                    {
                        _sesstionToken = response["st"] as string;
                    }
                    if (response.ContainsKey("stTtl"))
                    {
                        var stTtl            = long.Parse(response["stTtl"].ToString());
                        _sesstionTokenExpire = DateTime.Now.UnixTimeStampSeconds() + stTtl;
                    }
                    return(t.Result);
                });

                return(result);
            }
        }
        internal Task OpenSessionAsync(string clientId,
                                       string tag       = null,
                                       string deviceId  = null,
                                       string nonce     = null,
                                       long timestamp   = 0,
                                       string signature = null,
                                       bool secure      = true)
        {
            var cmd = new SessionCommand()
                      .UA(VersionString)
                      .Tag(tag)
                      .DeviceId(deviceId)
                      .Option("open")
                      .PeerId(clientId)
                      .Argument("n", nonce)
                      .Argument("t", timestamp)
                      .Argument("s", signature);

            return(AVIMCommandRunner.RunCommandAsync(cmd).OnSuccess(t =>
            {
                AVRealtime.PrintLog("sesstion opened.");
                if (t.Exception != null)
                {
                    var imException = t.Exception.InnerException as AVIMException;
                    throw imException;
                }
                state = Status.Online;
                var response = t.Result.Item2;
                if (response.ContainsKey("st"))
                {
                    _sesstionToken = response["st"] as string;
                }
                if (response.ContainsKey("stTtl"))
                {
                    var stTtl = long.Parse(response["stTtl"].ToString());
                    _sesstionTokenExpire = DateTime.Now.UnixTimeStampSeconds() + stTtl;
                }
                return t.Result;
            }));
        }
        /// <summary>
        /// 自动重连
        /// </summary>
        /// <returns></returns>
        public Task AutoReconnect()
        {
            var reconnectingArgs = new AVIMReconnectingEventArgs()
            {
                ClientId     = _clientId,
                IsAuto       = true,
                SessionToken = _sesstionToken
            };

            m_OnReconnecting?.Invoke(this, reconnectingArgs);

            return(OpenAsync(_wss, Subprotocol).ContinueWith(t =>
            {
                if (t.IsFaulted || t.Exception != null)
                {
                    state = Status.Reconnecting;
                    var reconnectFailedArgs = new AVIMReconnectFailedArgs()
                    {
                        ClientId = _clientId,
                        IsAuto = true,
                        SessionToken = _sesstionToken,
                        FailedCode = 0  // network broken.
                    };
                    m_OnReconnectFailed?.Invoke(this, reconnectFailedArgs);
                    state = Status.Offline;
                    return Task.FromResult(false);
                }
                else
                {
                    if (t.Result)
                    {
                        state = Status.Opened;

                        if (this.IsSesstionTokenExpired)
                        {
                            AVRealtime.PrintLog("sesstion is expired, auto relogin with clientId :" + _clientId);
                            return this.LogInAsync(_clientId, this._tag, this._deviceId, this._secure).OnSuccess(o =>
                            {
                                ClearReconnectTimer();
                                return true;
                            });
                        }
                        else
                        {
                            var sessionCMD = new SessionCommand().UA(VersionString).R(1);

                            if (string.IsNullOrEmpty(_tag))
                            {
                                sessionCMD = sessionCMD.Tag(_tag).SessionToken(this._sesstionToken);
                            }

                            var cmd = sessionCMD.Option("open")
                                      .PeerId(_clientId);

                            AVRealtime.PrintLog("reopen sesstion with sesstion token :" + _sesstionToken);
                            return AVIMCommandRunner.RunCommandAsync(cmd).OnSuccess(c =>
                            {
                                ClearReconnectTimer();
                                return true;
                            });
                        }
                    }
                    else
                    {
                        return Task.FromResult(false);
                    }
                }
            }).Unwrap().ContinueWith(s =>
            {
                if (s.IsFaulted || s.Exception != null)
                {
                    var reconnectFailedArgs = new AVIMReconnectFailedArgs()
                    {
                        ClientId = _clientId,
                        IsAuto = true,
                        SessionToken = _sesstionToken,
                        FailedCode = 1
                    };
                    m_OnReconnectFailed?.Invoke(this, reconnectFailedArgs);
                    state = Status.Offline;
                }
                else
                {
                    if (s.Result)
                    {
                        autoReconnectionStarted = false;
                        reconnectTimer = null;
                        var reconnectedArgs = new AVIMReconnectedEventArgs()
                        {
                            ClientId = _clientId,
                            IsAuto = true,
                            SessionToken = _sesstionToken,
                        };
                        state = Status.Online;
                        m_OnReconnected?.Invoke(this, reconnectedArgs);
                    }
                }
            }));
        }