using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using LeanCloud;
using System.Reflection;
using LeanCloud.Realtime.Internal;
using LeanCloud.Storage.Internal;
using System.Threading;
#if UNITY
using UnityEngine;
#endif
namespace LeanCloud.Realtime
{
///
/// 实时消息的框架类
/// 包含了 WebSocket 连接以及事件通知的管理
///
public class AVRealtime
{
internal static IDictionary clients = null;
private static readonly object mutex = new object();
private string _wss;
private string _secondaryWss;
private string _sesstionToken;
private long _sesstionTokenExpire;
private string _clientId;
private string _deviceId;
private bool _secure;
private string _tag;
private string subprotocolPrefix = "lc.json.";
static readonly int RECONNECT_DELAY = 5 * 1000;
static readonly int RECONNECT_USE_SECONDARY_TIMES = 6;
static readonly int RECONNECT_FROM_APP_ROUTER = 12;
int reconnectTimes;
public bool IsSesstionTokenExpired
{
get
{
return DateTime.Now.ToUnixTimeStamp() > _sesstionTokenExpire;
}
}
private IAVIMCommandRunner avIMCommandRunner;
///
///
///
public IAVIMCommandRunner AVIMCommandRunner
{
get
{
lock (mutex)
{
avIMCommandRunner = avIMCommandRunner ?? new AVIMCommandRunner(this.AVWebSocketClient);
return avIMCommandRunner;
}
}
}
private IWebSocketClient webSocketController;
internal IWebSocketClient AVWebSocketClient
{
get
{
lock (mutex)
{
webSocketController = webSocketController ?? new DefaultWebSocketClient();
return webSocketController;
}
}
set
{
lock (mutex)
{
webSocketController = value;
}
}
}
internal static IAVRouterController RouterController
{
get
{
return AVIMCorePlugins.Instance.RouterController;
}
}
internal static IFreeStyleMessageClassingController FreeStyleMessageClassingController
{
get
{
return AVIMCorePlugins.Instance.FreeStyleClassingController;
}
}
///
///
///
public event EventHandler OnOfflineMessageReceived;
///
/// 与云端通讯的状态
///
public enum Status
{
///
/// 未初始化
///
None = -1,
///
/// 正在连接
///
Connecting = 0,
///
/// 已连接
///
Online = 1,
///
/// 连接已断开
///
Offline = 2,
///
/// 正在重连
///
Reconnecting = 3,
///
/// websocket 连接已被打开
///
Opened = 98,
///
/// 已主动关闭
///
Closed = 99,
}
private AVRealtime.Status state = Status.None;
public AVRealtime.Status State
{
get
{
return state;
}
private set
{
state = value;
}
}
private struct NetworkStateOptions
{
public bool Available { get; set; }
}
private NetworkStateOptions NetworkState { get; set; }
private struct WebSocketStateOptions
{
public int ClosedCode { get; set; }
}
private WebSocketStateOptions WebSocketState { get; set; }
///
///
///
public struct AVIMReconnectOptions
{
///
/// 重连的时间间隔,单位是秒
///
public long Interval { get; set; }
///
/// 重连的次数
///
public int Retry { get; set; }
}
internal string Subprotocol
{
get
{
return subprotocolPrefix + (int)CurrentConfiguration.OfflineMessageStrategy;
}
}
///
/// 重连选项
///
public AVIMReconnectOptions ReconnectOptions { get; set; }
private ISignatureFactory _signatureFactory;
///
/// 签名接口
///
public ISignatureFactory SignatureFactory
{
get
{
_signatureFactory = _signatureFactory ?? new DefaulSiganatureFactory();
return _signatureFactory;
}
set
{
_signatureFactory = value;
}
}
private bool useLeanEngineSignaturFactory;
///
/// 启用 LeanEngine 云函数签名
///
public void UseLeanEngineSignatureFactory()
{
useLeanEngineSignaturFactory = true;
this.SignatureFactory = new LeanEngineSignatureFactory();
}
private EventHandler m_OnDisconnected;
///
/// 连接断开触发的事件
/// 如果其他客户端使用了相同的 Tag 登录,就会导致当前用户被服务端断开
///
public event EventHandler OnDisconnected
{
add
{
m_OnDisconnected += value;
}
remove
{
m_OnDisconnected -= value;
}
}
private EventHandler m_OnReconnecting;
///
/// 正在重连时触发的事件
///
public event EventHandler OnReconnecting
{
add
{
m_OnReconnecting += value;
}
remove
{
m_OnReconnecting -= value;
}
}
private EventHandler m_OnReconnected;
///
/// 重连之后触发的事件
///
public event EventHandler OnReconnected
{
add
{
m_OnReconnected += value;
}
remove
{
m_OnReconnected -= value;
}
}
private EventHandler m_OnReconnectFailed;
///
/// 重连失败之后触发的事件
///
public event EventHandler OnReconnectFailed
{
add
{
m_OnReconnectFailed += value;
}
remove
{
m_OnReconnectFailed -= value;
}
}
///
/// Invokes the state of the network.
///
/// If set to true broken.
internal void InvokeNetworkState(bool available = false)
{
if (this.NetworkState.Available == available) return;
SetNetworkState(available);
PrintLog(string.Format("network connectivity is {0} now", available));
// 如果断线产生的原因是客户端掉线而不是服务端踢下线,则应该开始自动重连
var reasonShouldReconnect = new int[] { 0, 1006, 4107 };
if (this.NetworkState.Available && reasonShouldReconnect.Contains(this.WebSocketState.ClosedCode))
{
StartAutoReconnect();
}
}
internal void SetNetworkState(bool available = true)
{
this.NetworkState = new NetworkStateOptions()
{
Available = available
};
}
private EventHandler m_NoticeReceived;
public event EventHandler NoticeReceived
{
add
{
m_NoticeReceived += value;
}
remove
{
m_NoticeReceived -= value;
}
}
private void WebSocketClient_OnMessage(string obj)
{
try
{
var estimatedData = Json.Parse(obj) as IDictionary;
var validator = AVIMNotice.IsValidLeanCloudProtocol(estimatedData);
if (validator)
{
var notice = new AVIMNotice(estimatedData);
m_NoticeReceived?.Invoke(this, notice);
}
}
catch (Exception ex)
{
if (ex.InnerException != null)
{
PrintLog(ex.InnerException.Source);
}
if (ex.Source != null)
{
PrintLog(ex.Source);
}
PrintLog(ex.Message);
}
}
///
/// 设定监听者
///
///
///
public void SubscribeNoticeReceived(IAVIMListener listener, Func runtimeHook = null)
{
this.NoticeReceived += new EventHandler((sender, notice) =>
{
var approved = runtimeHook == null ? listener.ProtocolHook(notice) : runtimeHook(notice) && listener.ProtocolHook(notice);
if (approved)
{
listener.OnNoticeReceived(notice);
}
});
}
///
/// 初始化配置项
///
public struct Configuration
{
///
/// 签名工厂
///
public ISignatureFactory SignatureFactory { get; set; }
///
/// 自定义 WebSocket 客户端
///
public IWebSocketClient WebSocketClient { get; set; }
///
/// LeanCloud App Id
///
public string ApplicationId { get; set; }
///
/// LeanCloud App Key
///
public string ApplicationKey { get; set; }
///
/// 登录的时候告知服务器,本次登录所使用的离线消息策略
///
public OfflineMessageStrategy OfflineMessageStrategy { get; set; }
}
///
///登录时的离线消息下发策略
///
public enum OfflineMessageStrategy
{
///
/// 服务器将所有离线消息一次性在登录之后马上下发下来
///
Default = 1,
///
/// 不再下发未读消息,而是下发对话的未读通知,告知客户端有哪些对话处于未读状态
///
[Obsolete("该策略已被废弃,请使用 UnreadAck")]
UnreadNotice = 2,
///
/// ack 和 read 分离, ack 不会清理未读消息
///
UnreadAck = 3
}
///
/// 当前配置
///
public Configuration CurrentConfiguration { get; internal set; }
///
/// 初始化实时消息客户端
///
///
public AVRealtime(Configuration config)
{
lock (mutex)
{
if ((int)config.OfflineMessageStrategy == 0)
{
config.OfflineMessageStrategy = OfflineMessageStrategy.UnreadAck;
}
CurrentConfiguration = config;
if (CurrentConfiguration.WebSocketClient != null)
{
webSocketController = CurrentConfiguration.WebSocketClient;
}
if (CurrentConfiguration.SignatureFactory != null)
{
this.SignatureFactory = CurrentConfiguration.SignatureFactory;
}
ReconnectOptions = new AVIMReconnectOptions()
{
Interval = 5,
Retry = 120
};
RegisterMessageType();
RegisterMessageType();
RegisterMessageType();
RegisterMessageType();
RegisterMessageType();
RegisterMessageType();
RegisterMessageType();
RegisterMessageType();
RegisterMessageType();
// 注册服务端 goaway 指令
var goAwayListener = new GoAwayListener();
goAwayListener.OnGoAway += () => {
RouterController.ClearCache().ContinueWith(_ => {
reborn = true;
// 关闭 WebSocket
AVWebSocketClient.Disconnect();
});
};
SubscribeNoticeReceived(goAwayListener);
reconnectTimes = 0;
}
}
///
/// 初始化实时消息客户端
///
///
///
public AVRealtime(string applicationId, string applicationKey)
: this(new Configuration()
{
ApplicationId = applicationId,
ApplicationKey = applicationKey,
OfflineMessageStrategy = OfflineMessageStrategy.UnreadAck
})
{
}
#region websocket log
internal static Action LogTracker { get; private set; }
///
/// 打开 WebSocket 日志
///
///
public static void WebSocketLog(Action trace)
{
LogTracker = trace;
}
public static void PrintLog(string log)
{
if (AVRealtime.LogTracker != null)
{
AVRealtime.LogTracker(log);
}
}
#endregion
///
/// 创建 Client
///
///
///
/// 设备唯一的 Id。如果是 iOS 设备,需要将 iOS 推送使用的 DeviceToken 作为 deviceId 传入
/// 是否强制加密 wss 链接
///
///
public Task 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, true, cancellationToken).OnSuccess(t =>
{
if (!t.Result)
{
return Task.FromResult(null);
}
AVRealtime.PrintLog("websocket server connected, begin to open sesstion.");
SetNetworkState();
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;
if (cmd == null)
{
return Task.FromResult>>(null);
}
return this.RunCommandAsync(cmd);
}).Unwrap().OnSuccess(s =>
{
if (s.Result == null)
{
return null;
}
AVRealtime.PrintLog("sesstion opened.");
state = Status.Online;
ToggleHeartBeating(true);
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.ToUnixTimeStamp() + stTtl * 1000;
}
AfterLogIn(client);
return client;
});
}
}
///
/// Creates the client async.
///
/// The client async.
/// User.
/// Tag.
/// Device identifier.
/// If set to true secure.
public Task CreateClientAsync(AVUser user = null,
string tag = null,
string deviceId = null,
bool secure = true,
CancellationToken cancellationToken = default(CancellationToken))
{
AVIMClient client = null;
AVRealtime.PrintLog("begin OpenAsync.");
return OpenAsync(secure, Subprotocol, true, cancellationToken).OnSuccess(openTask =>
{
AVRealtime.PrintLog("OpenAsync OnSuccess. begin send open sesstion cmd.");
var userTask = Task.FromResult(user);
if (user == null)
userTask = AVUser.GetCurrentUserAsync();
return userTask;
}).Unwrap().OnSuccess(u =>
{
var theUser = u.Result;
return AVCloud.RequestRealtimeSignatureAsync(theUser);
}).Unwrap().OnSuccess(signTask =>
{
var signResult = signTask.Result;
var clientId = signResult.ClientId;
var nonce = signResult.Nonce;
var singnature = signResult.Signature;
var ts = signResult.Timestamp;
client = PreLogIn(clientId, tag, deviceId);
ToggleNotification(true);
return this.OpenSessionAsync(clientId, tag, deviceId, nonce, ts, singnature, secure);
}).Unwrap().OnSuccess(s =>
{
ToggleHeartBeating(true);
AfterLogIn(client);
return client;
});
}
#region pre-login
internal AVIMClient PreLogIn(string clientId,
string tag = null,
string deviceId = null)
{
var client = new AVIMClient(clientId, tag, this);
if (this.OnOfflineMessageReceived != null)
{
client.OnOfflineMessageReceived += this.OnOfflineMessageReceived;
}
_clientId = clientId;
_tag = tag;
_deviceId = deviceId;
if (_tag != null)
{
if (deviceId == null)
throw new ArgumentNullException(deviceId, "当 tag 不为空时,必须传入当前设备不变的唯一 id(deviceId)");
}
if (string.IsNullOrEmpty(clientId)) throw new Exception("当前 ClientId 为空,无法登录服务器。");
return client;
}
internal void AfterLogIn(AVIMClient client)
{
if (clients == null) clients = new Dictionary();
client.OnSessionClosed += (sender, e) => {
string clientId = (sender as AVIMClient).ClientId;
clients.Remove(clientId);
if (clients.Count == 0) {
LogOut();
}
};
clients[client.ClientId] = client;
}
#endregion
#region after-login
#endregion
///
/// 创建 Client
///
///
///
/// 设备唯一的 Id。如果是 iOS 设备,需要将 iOS 推送使用的 DeviceToken 作为 deviceId 传入
/// 是否强制加密 wss 链接
///
///
[Obsolete("CreateClient is deprecated, please use CreateClientAsync instead.")]
public Task CreateClient(
string clientId,
string tag = null,
string deviceId = null,
bool secure = true,
CancellationToken cancellationToken = default(CancellationToken))
{
return this.CreateClientAsync(clientId, tag, deviceId, secure, cancellationToken);
}
private bool _listening = false;
///
/// websocket 事件的监听的开关
///
/// 是否打开
public void ToggleNotification(bool toggle)
{
AVRealtime.PrintLog("ToggleNotification| toggle:" + toggle + "|listening: " + _listening);
if (toggle && !_listening)
{
AVWebSocketClient.OnClosed += WebsocketClient_OnClosed;
AVWebSocketClient.OnMessage += WebSocketClient_OnMessage;
_listening = true;
}
else if (!toggle && _listening)
{
AVWebSocketClient.OnClosed -= WebsocketClient_OnClosed;
AVWebSocketClient.OnMessage -= WebSocketClient_OnMessage;
_listening = false;
}
}
//public void ToggleOfflineNotification(bool toggle)
//{
// if (toggle)
// {
// PCLWebsocketClient.OnMessage += WebSocketClient_OnMessage_On_Session_Opening;
// }
// else
// {
// PCLWebsocketClient.OnMessage -= WebSocketClient_OnMessage_On_Session_Opening;
// }
//}
//private void WebSocketClient_OnMessage_On_Session_Opening(string obj)
//{
// AVRealtime.PrintLog("offline<=" + obj);
//}
string _beatPacket = "{}";
bool _heartBeatingToggle = true;
IAVTimer _heartBeatingTimer;
///
/// 主动发送心跳包
///
/// 是否开启
/// 时间间隔
/// 心跳包的内容,默认是个空的 {}
public void ToggleHeartBeating(bool toggle = true, double interval = 60000, string beatPacket = "{}")
{
this._heartBeatingToggle = toggle;
if (!string.Equals(_beatPacket, beatPacket)) _beatPacket = beatPacket;
if (_heartBeatingTimer == null && this._heartBeatingToggle)
{
_heartBeatingTimer = new AVTimer();
_heartBeatingTimer.Elapsed += SendHeartBeatingPacket;
_heartBeatingTimer.Interval = interval;
_heartBeatingTimer.Start();
PrintLog("auto heart beating started.");
}
if (!this._heartBeatingToggle && _heartBeatingTimer != null)
{
_heartBeatingTimer.Stop();
_heartBeatingTimer = null;
}
}
void SendHeartBeatingPacket(object sender, TimerEventArgs e)
{
PrintLog("auto heart beating ticked by timer.");
#if MONO || UNITY
Dispatcher.Instance.Post(() =>
{
KeepAlive();
});
#else
KeepAlive();
#endif
}
///
/// Keeps the alive.
///
public void KeepAlive()
{
try
{
var cmd = new AVIMCommand();
RunCommandAsync(cmd).ContinueWith(t =>
{
if (t.IsCanceled || t.IsFaulted || t.Exception != null)
{
InvokeNetworkState();
}
});
}
catch (Exception)
{
InvokeNetworkState();
}
}
internal bool sessionConflict = false;
internal bool loggedOut = false;
internal bool CanReconnect
{
get
{
return !sessionConflict && !loggedOut && state == Status.Offline;
}
}
///
/// 开始自动重连
///
public void StartAutoReconnect()
{
}
internal bool useSecondary = false;
internal bool reborn = false;
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 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.ToUnixTimeStamp() + stTtl * 1000;
}
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 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.ToUnixTimeStamp() + stTtl * 1000;
}
return t.Result;
});
}
///
/// 自动重连
///
///
Task AutoReconnect()
{
AVRealtime.PrintLog("AutoReconnect started.");
var reconnectingArgs = new AVIMReconnectingEventArgs()
{
ClientId = _clientId,
IsAuto = true,
SessionToken = _sesstionToken
};
m_OnReconnecting?.Invoke(this, reconnectingArgs);
var tcs = new TaskCompletionSource();
Task task;
if (reborn)
{
AVRealtime.PrintLog("both preferred and secondary websockets are expired, so try to request RTM router to get a new pair");
task = OpenAsync(this._secure, Subprotocol, true);
} else {
var websocketServer = _wss;
if (useSecondary) {
AVRealtime.PrintLog(string.Format("preferred websocket server ({0}) network broken, take secondary server({1}) :", _wss, _secondaryWss));
websocketServer = _secondaryWss;
}
task = OpenAsync(websocketServer, Subprotocol, true);
}
task.ContinueWith(t =>
{
if (t.IsFaulted || t.IsCanceled) {
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;
tcs.SetException(t.Exception);
throw t.Exception;
} else {
state = Status.Opened;
SetNetworkState();
void onClose(int code, string reason, string detail) {
AVRealtime.PrintLog("disconnect when open session");
var ex = new Exception("connection is closed");
tcs.SetException(ex);
AVWebSocketClient.OnClosed -= onClose;
throw ex;
};
AVWebSocketClient.OnClosed += onClose;
if (this.IsSesstionTokenExpired) {
AVRealtime.PrintLog("session is expired, auto relogin with clientId :" + _clientId);
return this.LogInAsync(_clientId, this._tag, this._deviceId, this._secure).ContinueWith(o => {
AVWebSocketClient.OnClosed -= onClose;
return !o.IsFaulted;
});
} 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 session with session token :" + _sesstionToken);
return RunCommandAsync(cmd).ContinueWith(o => {
AVWebSocketClient.OnClosed -= onClose;
return !o.IsFaulted;
});
}
}
}).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;
tcs.SetException(s.Exception);
}
else
{
var reconnectedArgs = new AVIMReconnectedEventArgs() {
ClientId = _clientId,
IsAuto = true,
SessionToken = _sesstionToken,
};
state = Status.Online;
m_OnReconnected?.Invoke(this, reconnectedArgs);
ToggleNotification(true);
ToggleHeartBeating(true);
tcs.SetResult(true);
}
});
return tcs.Task;
}
#region register IAVIMMessage
///
/// Registers the subtype of the message.
///
/// The 1st type parameter.
public void RegisterMessageType() where T : IAVIMMessage
{
AVIMCorePlugins.Instance.FreeStyleClassingController.RegisterSubclass(typeof(T));
}
#endregion
///
/// open websocket with default configurations.
///
///
public Task OpenAsync(bool secure = true)
{
return this.OpenAsync(secure, null);
}
///
/// Open websocket connection.
///
/// The async.
/// If set to true secure.
/// Subprotocol.
/// If set to true enforce.
/// Cancellation token.
public Task OpenAsync(bool secure, string subprotocol = null, bool enforce = false, CancellationToken cancellationToken = default(CancellationToken))
{
_secure = secure;
if (state == Status.Online && !enforce)
{
AVRealtime.PrintLog("state is Status.Online.");
return Task.FromResult(true);
}
if (AVClient.CurrentConfiguration.RealtimeServer != null)
{
_wss = AVClient.CurrentConfiguration.RealtimeServer;
AVRealtime.PrintLog("use configuration realtime server with url: " + _wss);
return OpenAsync(_wss, subprotocol, enforce);
}
var routerUrl = AVClient.CurrentConfiguration.RTMServer;
return RouterController.GetAsync(routerUrl, secure, cancellationToken).OnSuccess(r =>
{
var routerState = r.Result;
if (routerState == null)
{
return Task.FromResult(false);
}
_wss = routerState.server;
_secondaryWss = routerState.secondary;
state = Status.Connecting;
AVRealtime.PrintLog("push router give a url :" + _wss);
return OpenAsync(routerState.server, subprotocol, enforce);
}).Unwrap();
}
///
/// open webcoket connection with cloud.
///
/// wss address
/// subprotocol for websocket
///
///
public Task OpenAsync(string url, string subprotocol = null, bool enforce = false, CancellationToken cancellationToken = default(CancellationToken))
{
if (AVWebSocketClient.IsOpen && !enforce)
{
AVRealtime.PrintLog(url + "is already connectd.");
return Task.FromResult(true);
}
AVRealtime.PrintLog("websocket try to connect url :" + url + " with subprotocol: " + subprotocol);
AVRealtime.PrintLog(url + " \tconnecting...");
return AVWebSocketClient.Connect(url, subprotocol);
}
///
/// send websocket command to Realtime server.
///
///
///
public Task>> RunCommandAsync(AVIMCommand command)
{
command.AppId(AVClient.CurrentConfiguration.ApplicationId);
return this.AVIMCommandRunner.RunCommandAsync(command);
}
///
///
///
///
public void RunCommand(AVIMCommand command)
{
command.AppId(AVClient.CurrentConfiguration.ApplicationId);
this.AVIMCommandRunner.RunCommand(command);
}
internal Task AttachSignature(AVIMCommand command, Task SignatureTask)
{
AVRealtime.PrintLog("begin to attach singature.");
var tcs = new TaskCompletionSource();
if (SignatureTask == null)
{
tcs.SetResult(command);
return tcs.Task;
}
return SignatureTask.OnSuccess(_ =>
{
if (_.Result != null)
{
var signature = _.Result;
command.Argument("t", signature.Timestamp);
command.Argument("n", signature.Nonce);
command.Argument("s", signature.SignatureContent);
AVRealtime.PrintLog("AttachSignature ended.t:" + signature.Timestamp + ";n:" + signature.Nonce + ";s:" + signature.SignatureContent);
}
return command;
});
}
#region log out and clean event subscribtion
private void WebsocketClient_OnClosed(int errorCode, string reason, string detail)
{
PrintLog(string.Format("websocket closed with code is {0},reason is {1} and detail is {2}", errorCode, reason, detail));
state = Status.Offline;
ToggleNotification(false);
ToggleHeartBeating(false);
var disconnectEventArgs = new AVIMDisconnectEventArgs(errorCode, reason, detail);
m_OnDisconnected?.Invoke(this, disconnectEventArgs);
this.WebSocketState = new WebSocketStateOptions()
{
ClosedCode = errorCode
};
PrepareReconnect();
}
private void WebsocketClient_OnError(string obj)
{
PrintLog("error:" + obj);
// 如果遇到 WebSocket 错误之后,先关闭,再按断线处理
AVWebSocketClient.Close();
WebsocketClient_OnClosed(0, obj, string.Empty);
}
void PrepareReconnect() {
AVRealtime.PrintLog("Prepare Reconnect");
Task.Delay(RECONNECT_DELAY).ContinueWith(_ => {
// 开启重连
AutoReconnect().ContinueWith(t => {
if (t.IsFaulted) {
// 重连失败,延迟再次重连
reconnectTimes++;
AVRealtime.PrintLog(String.Format("reconnect {0} times", reconnectTimes));
if (reconnectTimes >= RECONNECT_FROM_APP_ROUTER) {
// 如果大于当前服务地址的最大重连次数,则清空 Router 后重新重连
RouterController.ClearCache().ContinueWith(__ => {
reborn = true;
PrepareReconnect();
});
} else if (reconnectTimes >= RECONNECT_USE_SECONDARY_TIMES) {
// 如果大于单台 IM 服务器的重连次数,则启用备用服务器
useSecondary = true;
PrepareReconnect();
} else {
PrepareReconnect();
}
} else {
// 重连成功
reconnectTimes = 0;
reborn = false;
useSecondary = false;
}
});
});
}
internal void LogOut()
{
State = Status.Closed;
loggedOut = true;
Dispose();
AVWebSocketClient.Close();
}
internal void Dispose()
{
var toggle = false;
ToggleNotification(toggle);
ToggleHeartBeating(toggle);
if (m_NoticeReceived != null)
{
foreach (Delegate d in m_NoticeReceived.GetInvocationList())
{
m_NoticeReceived -= (EventHandler)d;
}
}
if (m_OnDisconnected != null)
{
foreach (Delegate d in m_OnDisconnected.GetInvocationList())
{
m_OnDisconnected -= (EventHandler)d;
}
}
}
#endregion
static AVRealtime()
{
#if MONO || UNITY
versionString = "net-unity/" + Version;
#else
versionString = "net-universal/" + Version;
#endif
}
private static readonly string versionString;
internal static string VersionString
{
get
{
return versionString;
}
}
internal static System.Version Version
{
get
{
AssemblyName assemblyName = new AssemblyName(typeof(AVRealtime).GetTypeInfo().Assembly.FullName);
return assemblyName.Version;
}
}
}
}