Merge pull request #52 from onerain88/realtime
Realtime
commit
cb98ff61fd
|
@ -1,15 +0,0 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
|
||||||
<ReleaseVersion>0.1.0</ReleaseVersion>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="Internal\" />
|
|
||||||
<Folder Include="Internal\AppRouter\" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
|
|
@ -5,14 +5,14 @@ using System.Collections.Generic;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace LeanCloud.Common {
|
namespace LeanCloud.Common {
|
||||||
public class AppRouter {
|
public class LCAppRouter {
|
||||||
private readonly string appId;
|
private readonly string appId;
|
||||||
|
|
||||||
private readonly string server;
|
private readonly string server;
|
||||||
|
|
||||||
private AppServer appServer;
|
private LCAppServer appServer;
|
||||||
|
|
||||||
public AppRouter(string appId, string server) {
|
public LCAppRouter(string appId, string server) {
|
||||||
if (!IsInternalApp(appId) && string.IsNullOrEmpty(server)) {
|
if (!IsInternalApp(appId) && string.IsNullOrEmpty(server)) {
|
||||||
// 国内节点必须配置自定义域名
|
// 国内节点必须配置自定义域名
|
||||||
throw new Exception("Please init with your server url.");
|
throw new Exception("Please init with your server url.");
|
||||||
|
@ -26,36 +26,49 @@ namespace LeanCloud.Common {
|
||||||
if (!string.IsNullOrEmpty(server)) {
|
if (!string.IsNullOrEmpty(server)) {
|
||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
|
LCAppServer appServ = await FetchAppServer();
|
||||||
|
return appServ.ApiServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetRealtimeServer() {
|
||||||
|
if (!string.IsNullOrEmpty(server)) {
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
LCAppServer appServ = await FetchAppServer();
|
||||||
|
return appServ.PushServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task<LCAppServer> FetchAppServer() {
|
||||||
// 判断节点地区
|
// 判断节点地区
|
||||||
if (!IsInternalApp(appId)) {
|
if (!IsInternalApp(appId)) {
|
||||||
// 国内节点必须配置自定义域名
|
// 国内节点必须配置自定义域名
|
||||||
throw new Exception("Please init with your server url.");
|
throw new Exception("Please init with your server url.");
|
||||||
}
|
}
|
||||||
// 向 App Router 请求地址
|
// 向 App Router 请求地址
|
||||||
if (appServer == null || appServer.IsExpired) {
|
if (appServer == null || !appServer.IsValid) {
|
||||||
try {
|
try {
|
||||||
HttpRequestMessage request = new HttpRequestMessage {
|
HttpRequestMessage request = new HttpRequestMessage {
|
||||||
RequestUri = new Uri($"https://app-router.com/2/route?appId={appId}"),
|
RequestUri = new Uri($"https://app-router.com/2/route?appId={appId}"),
|
||||||
Method = HttpMethod.Get
|
Method = HttpMethod.Get
|
||||||
};
|
};
|
||||||
HttpClient client = new HttpClient();
|
HttpClient client = new HttpClient();
|
||||||
HttpUtils.PrintRequest(client, request);
|
LCHttpUtils.PrintRequest(client, request);
|
||||||
HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
|
HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
|
||||||
request.Dispose();
|
request.Dispose();
|
||||||
|
|
||||||
string resultString = await response.Content.ReadAsStringAsync();
|
string resultString = await response.Content.ReadAsStringAsync();
|
||||||
response.Dispose();
|
response.Dispose();
|
||||||
HttpUtils.PrintResponse(response, resultString);
|
LCHttpUtils.PrintResponse(response, resultString);
|
||||||
|
|
||||||
Dictionary<string, object> data = JsonConvert.DeserializeObject<Dictionary<string, object>>(resultString);
|
Dictionary<string, object> data = JsonConvert.DeserializeObject<Dictionary<string, object>>(resultString);
|
||||||
appServer = new AppServer(data);
|
appServer = new LCAppServer(data);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.Error(e.Message);
|
LCLogger.Error(e);
|
||||||
// 拉取服务地址失败后,使用国际节点的默认服务地址
|
// 拉取服务地址失败后,使用国际节点的默认服务地址
|
||||||
appServer = AppServer.GetInternalFallbackAppServer(appId);
|
appServer = LCAppServer.GetInternalFallbackAppServer(appId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return appServer.ApiServer;
|
return appServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsInternalApp(string appId) {
|
private static bool IsInternalApp(string appId) {
|
|
@ -2,7 +2,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace LeanCloud.Common {
|
namespace LeanCloud.Common {
|
||||||
public class AppServer {
|
public class LCAppServer {
|
||||||
public string ApiServer {
|
public string ApiServer {
|
||||||
get; private set;
|
get; private set;
|
||||||
}
|
}
|
||||||
|
@ -19,9 +19,9 @@ namespace LeanCloud.Common {
|
||||||
get; private set;
|
get; private set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsExpired {
|
public bool IsValid {
|
||||||
get {
|
get {
|
||||||
return ttl != -1 && DateTime.Now > expiredAt;
|
return ttl != -1 || DateTime.Now < expiredAt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ namespace LeanCloud.Common {
|
||||||
|
|
||||||
private readonly int ttl;
|
private readonly int ttl;
|
||||||
|
|
||||||
public AppServer(Dictionary<string, object> data) {
|
public LCAppServer(Dictionary<string, object> data) {
|
||||||
ApiServer = GetUrlWithScheme(data["api_server"] as string);
|
ApiServer = GetUrlWithScheme(data["api_server"] as string);
|
||||||
PushServer = GetUrlWithScheme(data["push_server"] as string);
|
PushServer = GetUrlWithScheme(data["push_server"] as string);
|
||||||
EngineServer = GetUrlWithScheme(data["engine_server"] as string);
|
EngineServer = GetUrlWithScheme(data["engine_server"] as string);
|
||||||
|
@ -41,9 +41,9 @@ namespace LeanCloud.Common {
|
||||||
return url.StartsWith("https://") ? url : $"https://{url}";
|
return url.StartsWith("https://") ? url : $"https://{url}";
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static AppServer GetInternalFallbackAppServer(string appId) {
|
internal static LCAppServer GetInternalFallbackAppServer(string appId) {
|
||||||
string prefix = appId.Substring(0, 8).ToLower();
|
string prefix = appId.Substring(0, 8).ToLower();
|
||||||
return new AppServer(new Dictionary<string, object> {
|
return new LCAppServer(new Dictionary<string, object> {
|
||||||
{ "api_server", $"https://{prefix}.api.lncldglobal.com" },
|
{ "api_server", $"https://{prefix}.api.lncldglobal.com" },
|
||||||
{ "push_server", $"https://{prefix}.engine.lncldglobal.com" },
|
{ "push_server", $"https://{prefix}.engine.lncldglobal.com" },
|
||||||
{ "engine_server", $"https://{prefix}.push.lncldglobal.com" },
|
{ "engine_server", $"https://{prefix}.push.lncldglobal.com" },
|
|
@ -3,7 +3,7 @@ using System.Text;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
|
||||||
namespace LeanCloud.Common {
|
namespace LeanCloud.Common {
|
||||||
public static class HttpUtils {
|
public static class LCHttpUtils {
|
||||||
public static void PrintRequest(HttpClient client, HttpRequestMessage request, string content = null) {
|
public static void PrintRequest(HttpClient client, HttpRequestMessage request, string content = null) {
|
||||||
if (client == null) {
|
if (client == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -31,7 +31,7 @@ namespace LeanCloud.Common {
|
||||||
sb.AppendLine($"Content: {content}");
|
sb.AppendLine($"Content: {content}");
|
||||||
}
|
}
|
||||||
sb.AppendLine("=== HTTP Request End ===");
|
sb.AppendLine("=== HTTP Request End ===");
|
||||||
Logger.Debug(sb.ToString());
|
LCLogger.Debug(sb.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void PrintResponse(HttpResponseMessage response, string content = null) {
|
public static void PrintResponse(HttpResponseMessage response, string content = null) {
|
||||||
|
@ -43,7 +43,7 @@ namespace LeanCloud.Common {
|
||||||
sb.AppendLine($"Content: {content}");
|
sb.AppendLine($"Content: {content}");
|
||||||
}
|
}
|
||||||
sb.AppendLine("=== HTTP Response End ===");
|
sb.AppendLine("=== HTTP Response End ===");
|
||||||
Logger.Debug(sb.ToString());
|
LCLogger.Debug(sb.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -5,7 +5,7 @@ namespace LeanCloud.Common {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 为 Json 解析提供异步接口
|
/// 为 Json 解析提供异步接口
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class JsonUtils {
|
public static class LCJsonUtils {
|
||||||
public static async Task<string> SerializeObjectAsync(object obj) {
|
public static async Task<string> SerializeObjectAsync(object obj) {
|
||||||
string str = null;
|
string str = null;
|
||||||
await Task.Run(() => {
|
await Task.Run(() => {
|
|
@ -1,8 +1,8 @@
|
||||||
namespace LeanCloud.Common {
|
namespace LeanCloud {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 日志级别
|
/// 日志级别
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum LogLevel {
|
public enum LCLogLevel {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 调试级别
|
/// 调试级别
|
||||||
/// </summary>
|
/// </summary>
|
|
@ -0,0 +1,51 @@
|
||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LeanCloud {
|
||||||
|
/// <summary>
|
||||||
|
/// 日志类
|
||||||
|
/// </summary>
|
||||||
|
public static class LCLogger {
|
||||||
|
/// <summary>
|
||||||
|
/// 日志回调接口,方便开发者调试
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The log delegate.</value>
|
||||||
|
public static Action<LCLogLevel, string> LogDelegate {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Debug(string log) {
|
||||||
|
LogDelegate?.Invoke(LCLogLevel.Debug, log);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Debug(string format, params object[] args) {
|
||||||
|
LogDelegate?.Invoke(LCLogLevel.Debug, string.Format(format, args));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Warn(string log) {
|
||||||
|
LogDelegate?.Invoke(LCLogLevel.Warn, log);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Warn(string format, params object[] args) {
|
||||||
|
LogDelegate?.Invoke(LCLogLevel.Warn, string.Format(format, args));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Error(string log) {
|
||||||
|
LogDelegate?.Invoke(LCLogLevel.Error, log);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Error(string format, params object[] args) {
|
||||||
|
LogDelegate?.Invoke(LCLogLevel.Error, string.Format(format, args));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Error(Exception e) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.Append(e.GetType());
|
||||||
|
sb.Append("\n");
|
||||||
|
sb.Append(e.Message);
|
||||||
|
sb.Append("\n");
|
||||||
|
sb.Append(e.StackTrace);
|
||||||
|
Error(sb.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,40 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace LeanCloud.Common {
|
|
||||||
/// <summary>
|
|
||||||
/// 日志类
|
|
||||||
/// </summary>
|
|
||||||
public static class Logger {
|
|
||||||
/// <summary>
|
|
||||||
/// 日志回调接口,方便开发者调试
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The log delegate.</value>
|
|
||||||
public static Action<LogLevel, string> LogDelegate {
|
|
||||||
get; set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Debug(string log) {
|
|
||||||
LogDelegate?.Invoke(LogLevel.Debug, log);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Debug(string format, params object[] args) {
|
|
||||||
LogDelegate?.Invoke(LogLevel.Debug, string.Format(format, args));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Warn(string log) {
|
|
||||||
LogDelegate?.Invoke(LogLevel.Warn, log);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Warn(string format, params object[] args) {
|
|
||||||
LogDelegate?.Invoke(LogLevel.Warn, string.Format(format, args));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Error(string log) {
|
|
||||||
LogDelegate?.Invoke(LogLevel.Error, log);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Error(string format, params object[] args) {
|
|
||||||
LogDelegate?.Invoke(LogLevel.Error, string.Format(format, args));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -10,7 +10,7 @@ namespace LeanCloud.Common {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides helper methods that allow us to use terser code elsewhere.
|
/// Provides helper methods that allow us to use terser code elsewhere.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class TaskExtensions {
|
public static class LCTaskExtensions {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ensures a task (even null) is awaitable.
|
/// Ensures a task (even null) is awaitable.
|
||||||
/// </summary>
|
/// </summary>
|
|
@ -0,0 +1,43 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace LeanCloud.Common {
|
||||||
|
/// <summary>
|
||||||
|
/// 单线程环境,用于控制台应用 await 返回
|
||||||
|
/// </summary>
|
||||||
|
public class SingleThreadSynchronizationContext : SynchronizationContext {
|
||||||
|
private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> queue = new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>();
|
||||||
|
|
||||||
|
public override void Post(SendOrPostCallback d, object state) {
|
||||||
|
queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RunOnCurrentThread() {
|
||||||
|
while (queue.TryTake(out KeyValuePair<SendOrPostCallback, object> workItem, Timeout.Infinite)) {
|
||||||
|
workItem.Key(workItem.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Complete() {
|
||||||
|
queue.CompleteAdding();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Run(Func<Task> func) {
|
||||||
|
SynchronizationContext prevContext = Current;
|
||||||
|
try {
|
||||||
|
SingleThreadSynchronizationContext syncContext = new SingleThreadSynchronizationContext();
|
||||||
|
SetSynchronizationContext(syncContext);
|
||||||
|
|
||||||
|
Task t = func();
|
||||||
|
syncContext.RunOnCurrentThread();
|
||||||
|
|
||||||
|
t.GetAwaiter().GetResult();
|
||||||
|
} finally {
|
||||||
|
SetSynchronizationContext(prevContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,26 +0,0 @@
|
||||||
using System.Reflection;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
// Information about this assembly is defined by the following attributes.
|
|
||||||
// Change them to the values specific to your project.
|
|
||||||
|
|
||||||
[assembly: AssemblyTitle("RTM.PCL")]
|
|
||||||
[assembly: AssemblyDescription("")]
|
|
||||||
[assembly: AssemblyConfiguration("")]
|
|
||||||
[assembly: AssemblyCompany("")]
|
|
||||||
[assembly: AssemblyProduct("")]
|
|
||||||
[assembly: AssemblyCopyright("${AuthorCopyright}")]
|
|
||||||
[assembly: AssemblyTrademark("")]
|
|
||||||
[assembly: AssemblyCulture("")]
|
|
||||||
|
|
||||||
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
|
|
||||||
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
|
|
||||||
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
|
|
||||||
|
|
||||||
[assembly: AssemblyVersion("1.0.*")]
|
|
||||||
|
|
||||||
// The following attributes are used to specify the signing key for the assembly,
|
|
||||||
// if desired. See the Mono documentation for more information about signing.
|
|
||||||
|
|
||||||
//[assembly: AssemblyDelaySign(false)]
|
|
||||||
//[assembly: AssemblyKeyFile("")]
|
|
|
@ -1,210 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
||||||
<PropertyGroup>
|
|
||||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
|
||||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
|
||||||
<ProjectGuid>{92B2B40E-A3CD-4672-AC84-2E894E1A6CE5}</ProjectGuid>
|
|
||||||
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
|
||||||
<OutputType>Library</OutputType>
|
|
||||||
<RootNamespace>RTM.PCL</RootNamespace>
|
|
||||||
<AssemblyName>RTM.PCL</AssemblyName>
|
|
||||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
|
||||||
<TargetFrameworkProfile>Profile111</TargetFrameworkProfile>
|
|
||||||
<ReleaseVersion>0.1.0</ReleaseVersion>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
|
||||||
<DebugSymbols>true</DebugSymbols>
|
|
||||||
<DebugType>full</DebugType>
|
|
||||||
<Optimize>false</Optimize>
|
|
||||||
<OutputPath>bin\Debug</OutputPath>
|
|
||||||
<DefineConstants>DEBUG;</DefineConstants>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
|
||||||
<Optimize>true</Optimize>
|
|
||||||
<OutputPath>bin\Release</OutputPath>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
</PropertyGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
|
||||||
<Compile Include="..\Source\Internal\AVIMCorePlugins.cs">
|
|
||||||
<Link>Internal\AVIMCorePlugins.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\IAVIMPlatformHooks.cs">
|
|
||||||
<Link>Internal\IAVIMPlatformHooks.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Command\AVIMCommand.cs">
|
|
||||||
<Link>Internal\Command\AVIMCommand.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Command\AVIMCommandRunner.cs">
|
|
||||||
<Link>Internal\Command\AVIMCommandRunner.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Command\AckCommand.cs">
|
|
||||||
<Link>Internal\Command\AckCommand.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Command\ConversationCommand.cs">
|
|
||||||
<Link>Internal\Command\ConversationCommand.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Command\IAVIMCommandRunner.cs">
|
|
||||||
<Link>Internal\Command\IAVIMCommandRunner.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Command\MessageCommand.cs">
|
|
||||||
<Link>Internal\Command\MessageCommand.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Command\PatchCommand.cs">
|
|
||||||
<Link>Internal\Command\PatchCommand.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Command\ReadCommand.cs">
|
|
||||||
<Link>Internal\Command\ReadCommand.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Command\SessionCommand.cs">
|
|
||||||
<Link>Internal\Command\SessionCommand.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\DataEngine\Controller\DateTimeEngine.cs">
|
|
||||||
<Link>Internal\DataEngine\Controller\DateTimeEngine.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\DataEngine\Controller\DictionaryEngine.cs">
|
|
||||||
<Link>Internal\DataEngine\Controller\DictionaryEngine.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\DataEngine\Controller\StringEngine.cs">
|
|
||||||
<Link>Internal\DataEngine\Controller\StringEngine.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Message\Subclassing\FreeStyleMessageClassInfo.cs">
|
|
||||||
<Link>Internal\Message\Subclassing\FreeStyleMessageClassInfo.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Message\Subclassing\FreeStyleMessageClassingController.cs">
|
|
||||||
<Link>Internal\Message\Subclassing\FreeStyleMessageClassingController.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Message\Subclassing\IFreeStyleMessageClassingController.cs">
|
|
||||||
<Link>Internal\Message\Subclassing\IFreeStyleMessageClassingController.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Protocol\AVIMProtocol.cs">
|
|
||||||
<Link>Internal\Protocol\AVIMProtocol.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Router\AVRouterController.cs">
|
|
||||||
<Link>Internal\Router\AVRouterController.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Router\IAVRouterController.cs">
|
|
||||||
<Link>Internal\Router\IAVRouterController.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Router\State\RouterState.cs">
|
|
||||||
<Link>Internal\Router\State\RouterState.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Timer\IAVTimer.cs">
|
|
||||||
<Link>Internal\Timer\IAVTimer.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Timer\Portable\AVTimer.Portable.cs">
|
|
||||||
<Link>Internal\Timer\Portable\AVTimer.Portable.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\WebSocket\IWebSocketClient.cs">
|
|
||||||
<Link>Internal\WebSocket\IWebSocketClient.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\WebSocket\Portable\DefaultWebSocketClient.Portable.cs">
|
|
||||||
<Link>Internal\WebSocket\Portable\DefaultWebSocketClient.Portable.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMAudioMessage.cs">
|
|
||||||
<Link>Public\AVIMAudioMessage.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMBinaryMessage.cs">
|
|
||||||
<Link>Public\AVIMBinaryMessage.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMClient.cs">
|
|
||||||
<Link>Public\AVIMClient.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMConversation.cs">
|
|
||||||
<Link>Public\AVIMConversation.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMConversationQuery.cs">
|
|
||||||
<Link>Public\AVIMConversationQuery.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMEnumerator.cs">
|
|
||||||
<Link>Public\AVIMEnumerator.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMEventArgs.cs">
|
|
||||||
<Link>Public\AVIMEventArgs.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMException.cs">
|
|
||||||
<Link>Public\AVIMException.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMImageMessage.cs">
|
|
||||||
<Link>Public\AVIMImageMessage.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMMessage.cs">
|
|
||||||
<Link>Public\AVIMMessage.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMMessageClassNameAttribute.cs">
|
|
||||||
<Link>Public\AVIMMessageClassNameAttribute.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMMessageFieldNameAttribute.cs">
|
|
||||||
<Link>Public\AVIMMessageFieldNameAttribute.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMMessageListener.cs">
|
|
||||||
<Link>Public\AVIMMessageListener.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMNotice.cs">
|
|
||||||
<Link>Public\AVIMNotice.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMRecalledMessage.cs">
|
|
||||||
<Link>Public\AVIMRecalledMessage.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMSignature.cs">
|
|
||||||
<Link>Public\AVIMSignature.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMTemporaryConversation.cs">
|
|
||||||
<Link>Public\AVIMTemporaryConversation.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMTextMessage.cs">
|
|
||||||
<Link>Public\AVIMTextMessage.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMTypedMessage.cs">
|
|
||||||
<Link>Public\AVIMTypedMessage.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMTypedMessageTypeIntAttribute.cs">
|
|
||||||
<Link>Public\AVIMTypedMessageTypeIntAttribute.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVRealtime.cs">
|
|
||||||
<Link>Public\AVRealtime.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\IAVIMListener.cs">
|
|
||||||
<Link>Public\IAVIMListener.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\IAVIMMessage.cs">
|
|
||||||
<Link>Public\IAVIMMessage.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\ICacheEngine.cs">
|
|
||||||
<Link>Public\ICacheEngine.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\ISignatureFactory.cs">
|
|
||||||
<Link>Public\ISignatureFactory.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\Listener\AVIMConversationListener.cs">
|
|
||||||
<Link>Public\Listener\AVIMConversationListener.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\Listener\ConversationUnreadListener.cs">
|
|
||||||
<Link>Public\Listener\ConversationUnreadListener.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\Listener\GoAwayListener.cs">
|
|
||||||
<Link>Public\Listener\GoAwayListener.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\Listener\MessagePatchListener.cs">
|
|
||||||
<Link>Public\Listener\MessagePatchListener.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\Listener\OfflineMessageListener.cs">
|
|
||||||
<Link>Public\Listener\OfflineMessageListener.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\Listener\SessionListener.cs">
|
|
||||||
<Link>Public\Listener\SessionListener.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<None Include="packages.config" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Reference Include="WebSockets.PCL">
|
|
||||||
<HintPath>..\..\packages\Websockets.Pcl.1.1.9\lib\portable-net45+win+wpa81+wp80+MonoAndroid10+xamarinios10\WebSockets.PCL.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
|
||||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
|
|
||||||
</Project>
|
|
|
@ -1,4 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<packages>
|
|
||||||
<package id="Websockets.Pcl" version="1.1.9" targetFramework="portable45-net45+win8+wpa81" developmentDependency="true" />
|
|
||||||
</packages>
|
|
|
@ -1,41 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
||||||
<PropertyGroup>
|
|
||||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
|
||||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
|
||||||
<ProjectGuid>{A1BBD0B5-41C6-4579-B9A3-5EF778BE7F95}</ProjectGuid>
|
|
||||||
<OutputType>Library</OutputType>
|
|
||||||
<RootNamespace>RTM.Test</RootNamespace>
|
|
||||||
<AssemblyName>RTM.Test</AssemblyName>
|
|
||||||
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
|
|
||||||
<ReleaseVersion>0.1.0</ReleaseVersion>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
|
||||||
<DebugSymbols>true</DebugSymbols>
|
|
||||||
<DebugType>full</DebugType>
|
|
||||||
<Optimize>false</Optimize>
|
|
||||||
<OutputPath>bin\Debug</OutputPath>
|
|
||||||
<DefineConstants>DEBUG;</DefineConstants>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
|
||||||
<Optimize>true</Optimize>
|
|
||||||
<OutputPath>bin\Release</OutputPath>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
</PropertyGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Reference Include="System" />
|
|
||||||
<Reference Include="nunit.framework">
|
|
||||||
<HintPath>..\..\packages\NUnit.2.6.4\lib\nunit.framework.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Include="Test.cs" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<None Include="packages.config" />
|
|
||||||
</ItemGroup>
|
|
||||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
|
||||||
</Project>
|
|
|
@ -1,10 +0,0 @@
|
||||||
using NUnit.Framework;
|
|
||||||
using System;
|
|
||||||
namespace RTM.Test {
|
|
||||||
[TestFixture()]
|
|
||||||
public class Test {
|
|
||||||
[Test()]
|
|
||||||
public void TestCase() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<packages>
|
|
||||||
<package id="NUnit" version="2.6.4" targetFramework="net47" />
|
|
||||||
</packages>
|
|
|
@ -1,26 +0,0 @@
|
||||||
using System.Reflection;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
// Information about this assembly is defined by the following attributes.
|
|
||||||
// Change them to the values specific to your project.
|
|
||||||
|
|
||||||
[assembly: AssemblyTitle("RTM.Unity")]
|
|
||||||
[assembly: AssemblyDescription("")]
|
|
||||||
[assembly: AssemblyConfiguration("")]
|
|
||||||
[assembly: AssemblyCompany("")]
|
|
||||||
[assembly: AssemblyProduct("")]
|
|
||||||
[assembly: AssemblyCopyright("${AuthorCopyright}")]
|
|
||||||
[assembly: AssemblyTrademark("")]
|
|
||||||
[assembly: AssemblyCulture("")]
|
|
||||||
|
|
||||||
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
|
|
||||||
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
|
|
||||||
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
|
|
||||||
|
|
||||||
[assembly: AssemblyVersion("1.0.*")]
|
|
||||||
|
|
||||||
// The following attributes are used to specify the signing key for the assembly,
|
|
||||||
// if desired. See the Mono documentation for more information about signing.
|
|
||||||
|
|
||||||
//[assembly: AssemblyDelaySign(false)]
|
|
||||||
//[assembly: AssemblyKeyFile("")]
|
|
|
@ -1,219 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
||||||
<PropertyGroup>
|
|
||||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
|
||||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
|
||||||
<ProjectGuid>{1E608FCD-9039-4FF7-8EE7-BA8B00E15D1C}</ProjectGuid>
|
|
||||||
<OutputType>Library</OutputType>
|
|
||||||
<RootNamespace>RTM.Unity</RootNamespace>
|
|
||||||
<AssemblyName>RTM.Unity</AssemblyName>
|
|
||||||
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
|
|
||||||
<ReleaseVersion>0.1.0</ReleaseVersion>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
|
||||||
<DebugSymbols>true</DebugSymbols>
|
|
||||||
<DebugType>full</DebugType>
|
|
||||||
<Optimize>false</Optimize>
|
|
||||||
<OutputPath>bin\Debug</OutputPath>
|
|
||||||
<DefineConstants>DEBUG;</DefineConstants>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
<ConsolePause>false</ConsolePause>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
|
||||||
<Optimize>true</Optimize>
|
|
||||||
<OutputPath>bin\Release</OutputPath>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
<ConsolePause>false</ConsolePause>
|
|
||||||
</PropertyGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Reference Include="System" />
|
|
||||||
<Reference Include="UnityEngine">
|
|
||||||
<HintPath>..\..\Libs\UnityEngine.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="websocket-sharp">
|
|
||||||
<HintPath>..\..\Libs\websocket-sharp.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
|
||||||
<Compile Include="..\Source\Internal\AVIMCorePlugins.cs">
|
|
||||||
<Link>Internal\AVIMCorePlugins.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\IAVIMPlatformHooks.cs">
|
|
||||||
<Link>Internal\IAVIMPlatformHooks.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Command\AVIMCommand.cs">
|
|
||||||
<Link>Internal\Command\AVIMCommand.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Command\AVIMCommandRunner.cs">
|
|
||||||
<Link>Internal\Command\AVIMCommandRunner.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Command\AckCommand.cs">
|
|
||||||
<Link>Internal\Command\AckCommand.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Command\ConversationCommand.cs">
|
|
||||||
<Link>Internal\Command\ConversationCommand.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Command\IAVIMCommandRunner.cs">
|
|
||||||
<Link>Internal\Command\IAVIMCommandRunner.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Command\MessageCommand.cs">
|
|
||||||
<Link>Internal\Command\MessageCommand.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Command\PatchCommand.cs">
|
|
||||||
<Link>Internal\Command\PatchCommand.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Command\ReadCommand.cs">
|
|
||||||
<Link>Internal\Command\ReadCommand.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Command\SessionCommand.cs">
|
|
||||||
<Link>Internal\Command\SessionCommand.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\DataEngine\Controller\DateTimeEngine.cs">
|
|
||||||
<Link>Internal\DataEngine\Controller\DateTimeEngine.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\DataEngine\Controller\DictionaryEngine.cs">
|
|
||||||
<Link>Internal\DataEngine\Controller\DictionaryEngine.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\DataEngine\Controller\StringEngine.cs">
|
|
||||||
<Link>Internal\DataEngine\Controller\StringEngine.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Message\Subclassing\FreeStyleMessageClassInfo.cs">
|
|
||||||
<Link>Internal\Message\Subclassing\FreeStyleMessageClassInfo.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Message\Subclassing\FreeStyleMessageClassingController.cs">
|
|
||||||
<Link>Internal\Message\Subclassing\FreeStyleMessageClassingController.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Message\Subclassing\IFreeStyleMessageClassingController.cs">
|
|
||||||
<Link>Internal\Message\Subclassing\IFreeStyleMessageClassingController.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Protocol\AVIMProtocol.cs">
|
|
||||||
<Link>Internal\Protocol\AVIMProtocol.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Router\AVRouterController.cs">
|
|
||||||
<Link>Internal\Router\AVRouterController.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Router\IAVRouterController.cs">
|
|
||||||
<Link>Internal\Router\IAVRouterController.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Router\State\RouterState.cs">
|
|
||||||
<Link>Internal\Router\State\RouterState.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Timer\IAVTimer.cs">
|
|
||||||
<Link>Internal\Timer\IAVTimer.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\Timer\Unity\AVTimer.Unity.cs">
|
|
||||||
<Link>Internal\Timer\Unity\AVTimer.Unity.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\WebSocket\IWebSocketClient.cs">
|
|
||||||
<Link>Internal\WebSocket\IWebSocketClient.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Internal\WebSocket\Unity\DefaultWebSocketClient.Unity.cs">
|
|
||||||
<Link>Internal\WebSocket\Unity\DefaultWebSocketClient.Unity.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMAudioMessage.cs">
|
|
||||||
<Link>Public\AVIMAudioMessage.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMBinaryMessage.cs">
|
|
||||||
<Link>Public\AVIMBinaryMessage.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMClient.cs">
|
|
||||||
<Link>Public\AVIMClient.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMConversation.cs">
|
|
||||||
<Link>Public\AVIMConversation.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMConversationQuery.cs">
|
|
||||||
<Link>Public\AVIMConversationQuery.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMEnumerator.cs">
|
|
||||||
<Link>Public\AVIMEnumerator.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMEventArgs.cs">
|
|
||||||
<Link>Public\AVIMEventArgs.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMException.cs">
|
|
||||||
<Link>Public\AVIMException.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMImageMessage.cs">
|
|
||||||
<Link>Public\AVIMImageMessage.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMMessage.cs">
|
|
||||||
<Link>Public\AVIMMessage.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMMessageClassNameAttribute.cs">
|
|
||||||
<Link>Public\AVIMMessageClassNameAttribute.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMMessageFieldNameAttribute.cs">
|
|
||||||
<Link>Public\AVIMMessageFieldNameAttribute.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMMessageListener.cs">
|
|
||||||
<Link>Public\AVIMMessageListener.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMNotice.cs">
|
|
||||||
<Link>Public\AVIMNotice.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMRecalledMessage.cs">
|
|
||||||
<Link>Public\AVIMRecalledMessage.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMSignature.cs">
|
|
||||||
<Link>Public\AVIMSignature.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMTemporaryConversation.cs">
|
|
||||||
<Link>Public\AVIMTemporaryConversation.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMTextMessage.cs">
|
|
||||||
<Link>Public\AVIMTextMessage.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMTypedMessage.cs">
|
|
||||||
<Link>Public\AVIMTypedMessage.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVIMTypedMessageTypeIntAttribute.cs">
|
|
||||||
<Link>Public\AVIMTypedMessageTypeIntAttribute.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\AVRealtime.cs">
|
|
||||||
<Link>Public\AVRealtime.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\IAVIMListener.cs">
|
|
||||||
<Link>Public\IAVIMListener.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\IAVIMMessage.cs">
|
|
||||||
<Link>Public\IAVIMMessage.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\ICacheEngine.cs">
|
|
||||||
<Link>Public\ICacheEngine.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\ISignatureFactory.cs">
|
|
||||||
<Link>Public\ISignatureFactory.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\Listener\AVIMConversationListener.cs">
|
|
||||||
<Link>Public\Listener\AVIMConversationListener.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\Listener\ConversationUnreadListener.cs">
|
|
||||||
<Link>Public\Listener\ConversationUnreadListener.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\Listener\GoAwayListener.cs">
|
|
||||||
<Link>Public\Listener\GoAwayListener.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\Listener\MessagePatchListener.cs">
|
|
||||||
<Link>Public\Listener\MessagePatchListener.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\Listener\OfflineMessageListener.cs">
|
|
||||||
<Link>Public\Listener\OfflineMessageListener.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\Listener\SessionListener.cs">
|
|
||||||
<Link>Public\Listener\SessionListener.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Source\Public\Unity\AVRealtimeBehavior.cs">
|
|
||||||
<Link>Public\Unity\AVRealtimeBehavior.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<None Include="..\Source\Internal\WebSocket\Unity\websocket-sharp.dll">
|
|
||||||
<Link>Internal\WebSocket\Unity\websocket-sharp.dll</Link>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
|
||||||
</Project>
|
|
|
@ -1,56 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal
|
|
||||||
{
|
|
||||||
internal class AVIMCorePlugins
|
|
||||||
{
|
|
||||||
private static readonly AVIMCorePlugins instance = new AVIMCorePlugins();
|
|
||||||
public static AVIMCorePlugins Instance
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly object mutex = new object();
|
|
||||||
|
|
||||||
private IAVRouterController routerController;
|
|
||||||
public IAVRouterController RouterController
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
lock (mutex)
|
|
||||||
{
|
|
||||||
routerController = routerController ?? new AVRouterController();
|
|
||||||
return routerController;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
internal set
|
|
||||||
{
|
|
||||||
lock (mutex)
|
|
||||||
{
|
|
||||||
routerController = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private IFreeStyleMessageClassingController freeStyleClassingController;
|
|
||||||
public IFreeStyleMessageClassingController FreeStyleClassingController
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
lock (mutex)
|
|
||||||
{
|
|
||||||
freeStyleClassingController = freeStyleClassingController ?? new FreeStyleMessageClassingController();
|
|
||||||
return freeStyleClassingController;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,176 +0,0 @@
|
||||||
using LeanCloud;
|
|
||||||
using LeanCloud.Storage.Internal;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Command.
|
|
||||||
/// </summary>
|
|
||||||
public class AVIMCommand
|
|
||||||
{
|
|
||||||
protected readonly string cmd;
|
|
||||||
protected readonly string op;
|
|
||||||
protected string appId;
|
|
||||||
protected string peerId;
|
|
||||||
protected AVIMSignature signature;
|
|
||||||
protected readonly IDictionary<string, object> arguments;
|
|
||||||
|
|
||||||
public int TimeoutInSeconds { get; set; }
|
|
||||||
|
|
||||||
protected readonly IDictionary<string, object> estimatedData = new Dictionary<string, object>();
|
|
||||||
internal readonly object mutex = new object();
|
|
||||||
internal static readonly object Mutex = new object();
|
|
||||||
|
|
||||||
public AVIMCommand() :
|
|
||||||
this(arguments: new Dictionary<string, object>())
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
protected AVIMCommand(string cmd = null,
|
|
||||||
string op = null,
|
|
||||||
string appId = null,
|
|
||||||
string peerId = null,
|
|
||||||
AVIMSignature signature = null,
|
|
||||||
IDictionary<string, object> arguments = null)
|
|
||||||
{
|
|
||||||
this.cmd = cmd;
|
|
||||||
this.op = op;
|
|
||||||
this.arguments = arguments == null ? new Dictionary<string, object>() : arguments;
|
|
||||||
this.peerId = peerId;
|
|
||||||
this.signature = signature;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected AVIMCommand(AVIMCommand source,
|
|
||||||
string cmd = null,
|
|
||||||
string op = null,
|
|
||||||
string appId = null,
|
|
||||||
string peerId = null,
|
|
||||||
IDictionary<string, object> arguments = null,
|
|
||||||
AVIMSignature signature = null)
|
|
||||||
{
|
|
||||||
if (source == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException("source", "Source can not be null");
|
|
||||||
}
|
|
||||||
this.cmd = source.cmd;
|
|
||||||
this.op = source.op;
|
|
||||||
this.arguments = source.arguments;
|
|
||||||
this.peerId = source.peerId;
|
|
||||||
this.appId = source.appId;
|
|
||||||
this.signature = source.signature;
|
|
||||||
|
|
||||||
if (cmd != null)
|
|
||||||
{
|
|
||||||
this.cmd = cmd;
|
|
||||||
}
|
|
||||||
if (op != null)
|
|
||||||
{
|
|
||||||
this.op = op;
|
|
||||||
}
|
|
||||||
if (arguments != null)
|
|
||||||
{
|
|
||||||
this.arguments = arguments;
|
|
||||||
}
|
|
||||||
if (peerId != null)
|
|
||||||
{
|
|
||||||
this.peerId = peerId;
|
|
||||||
}
|
|
||||||
if (appId != null)
|
|
||||||
{
|
|
||||||
this.appId = appId;
|
|
||||||
}
|
|
||||||
if (signature != null)
|
|
||||||
{
|
|
||||||
this.signature = signature;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public AVIMCommand Command(string cmd)
|
|
||||||
{
|
|
||||||
return new AVIMCommand(this, cmd: cmd);
|
|
||||||
}
|
|
||||||
public AVIMCommand Option(string op)
|
|
||||||
{
|
|
||||||
return new AVIMCommand(this, op: op);
|
|
||||||
}
|
|
||||||
public AVIMCommand Argument(string key, object value)
|
|
||||||
{
|
|
||||||
lock (mutex)
|
|
||||||
{
|
|
||||||
this.arguments[key] = value;
|
|
||||||
return new AVIMCommand(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public AVIMCommand AppId(string appId)
|
|
||||||
{
|
|
||||||
this.appId = appId;
|
|
||||||
return new AVIMCommand(this, appId: appId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AVIMCommand PeerId(string peerId)
|
|
||||||
{
|
|
||||||
this.peerId = peerId;
|
|
||||||
return new AVIMCommand(this, peerId: peerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AVIMCommand IDlize()
|
|
||||||
{
|
|
||||||
this.Argument("i", AVIMCommand.NextCmdId);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual IDictionary<string, object> Encode()
|
|
||||||
{
|
|
||||||
lock (mutex)
|
|
||||||
{
|
|
||||||
estimatedData.Clear();
|
|
||||||
estimatedData.Merge(arguments);
|
|
||||||
estimatedData.Add("cmd", cmd);
|
|
||||||
estimatedData.Add("appId", this.appId);
|
|
||||||
if (!string.IsNullOrEmpty(op))
|
|
||||||
estimatedData.Add("op", op);
|
|
||||||
if (!string.IsNullOrEmpty(peerId))
|
|
||||||
estimatedData.Add("peerId", peerId);
|
|
||||||
|
|
||||||
return estimatedData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual string EncodeJsonString()
|
|
||||||
{
|
|
||||||
var json = this.Encode();
|
|
||||||
return Json.Encode(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsValid
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return !string.IsNullOrEmpty(this.cmd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Int32 lastCmdId = -65536;
|
|
||||||
internal static Int32 NextCmdId
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
lock (Mutex)
|
|
||||||
{
|
|
||||||
lastCmdId++;
|
|
||||||
|
|
||||||
if (lastCmdId > ushort.MaxValue)
|
|
||||||
{
|
|
||||||
lastCmdId = -65536;
|
|
||||||
}
|
|
||||||
return lastCmdId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,92 +0,0 @@
|
||||||
using LeanCloud;
|
|
||||||
using LeanCloud.Storage.Internal;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal
|
|
||||||
{
|
|
||||||
public class AVIMCommandRunner : IAVIMCommandRunner
|
|
||||||
{
|
|
||||||
private readonly IWebSocketClient webSocketClient;
|
|
||||||
public AVIMCommandRunner(IWebSocketClient webSocketClient)
|
|
||||||
{
|
|
||||||
this.webSocketClient = webSocketClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RunCommand(AVIMCommand command)
|
|
||||||
{
|
|
||||||
command.IDlize();
|
|
||||||
var requestString = command.EncodeJsonString();
|
|
||||||
AVRealtime.PrintLog("websocket=>" + requestString);
|
|
||||||
webSocketClient.Send(requestString);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="command"></param>
|
|
||||||
/// <param name="cancellationToken"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<Tuple<int, IDictionary<string, object>>> RunCommandAsync(AVIMCommand command, CancellationToken cancellationToken = default(CancellationToken))
|
|
||||||
{
|
|
||||||
var tcs = new TaskCompletionSource<Tuple<int, IDictionary<string, object>>>();
|
|
||||||
|
|
||||||
command.IDlize();
|
|
||||||
|
|
||||||
var requestString = command.EncodeJsonString();
|
|
||||||
if (!command.IsValid)
|
|
||||||
{
|
|
||||||
requestString = "{}";
|
|
||||||
}
|
|
||||||
AVRealtime.PrintLog("websocket=>" + requestString);
|
|
||||||
webSocketClient.Send(requestString);
|
|
||||||
var requestJson = command.Encode();
|
|
||||||
|
|
||||||
|
|
||||||
Action<string> onMessage = null;
|
|
||||||
onMessage = (response) =>
|
|
||||||
{
|
|
||||||
//AVRealtime.PrintLog("response<=" + response);
|
|
||||||
var responseJson = Json.Parse(response) as IDictionary<string, object>;
|
|
||||||
if (responseJson.Keys.Contains("i"))
|
|
||||||
{
|
|
||||||
if (requestJson["i"].ToString() == responseJson["i"].ToString())
|
|
||||||
{
|
|
||||||
var result = new Tuple<int, IDictionary<string, object>>(-1, responseJson);
|
|
||||||
if (responseJson.Keys.Contains("code"))
|
|
||||||
{
|
|
||||||
var errorCode = int.Parse(responseJson["code"].ToString());
|
|
||||||
var reason = string.Empty;
|
|
||||||
int appCode = 0;
|
|
||||||
|
|
||||||
if (responseJson.Keys.Contains("reason"))
|
|
||||||
{
|
|
||||||
reason = responseJson["reason"].ToString();
|
|
||||||
}
|
|
||||||
if (responseJson.Keys.Contains("appCode"))
|
|
||||||
{
|
|
||||||
appCode = int.Parse(responseJson["appCode"].ToString());
|
|
||||||
}
|
|
||||||
tcs.SetException(new AVIMException(errorCode, appCode, reason, null));
|
|
||||||
}
|
|
||||||
if (tcs.Task.Exception == null)
|
|
||||||
{
|
|
||||||
tcs.SetResult(result);
|
|
||||||
}
|
|
||||||
webSocketClient.OnMessage -= onMessage;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
webSocketClient.OnMessage += onMessage;
|
|
||||||
return tcs.Task;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal
|
|
||||||
{
|
|
||||||
internal class AckCommand : AVIMCommand
|
|
||||||
{
|
|
||||||
public AckCommand()
|
|
||||||
: base(cmd: "ack")
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public AckCommand(AVIMCommand source)
|
|
||||||
: base(source)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public AckCommand Message(IAVIMMessage message)
|
|
||||||
{
|
|
||||||
return new AckCommand()
|
|
||||||
.ConversationId(message.ConversationId)
|
|
||||||
.MessageId(message.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AckCommand MessageId(string messageId)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(messageId))
|
|
||||||
{
|
|
||||||
messageId = "";
|
|
||||||
}
|
|
||||||
return new AckCommand(this.Argument("mid", messageId));
|
|
||||||
}
|
|
||||||
|
|
||||||
public AckCommand ConversationId(string conversationId)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(conversationId))
|
|
||||||
{
|
|
||||||
conversationId = "";
|
|
||||||
}
|
|
||||||
return new AckCommand(this.Argument("cid", conversationId));
|
|
||||||
}
|
|
||||||
|
|
||||||
public AckCommand FromTimeStamp(long startTimeStamp)
|
|
||||||
{
|
|
||||||
return new AckCommand(this.Argument("fromts", startTimeStamp));
|
|
||||||
}
|
|
||||||
|
|
||||||
public AckCommand ToTimeStamp(long endTimeStamp)
|
|
||||||
{
|
|
||||||
return new AckCommand(this.Argument("tots", endTimeStamp));
|
|
||||||
}
|
|
||||||
|
|
||||||
public AckCommand ReadAck()
|
|
||||||
{
|
|
||||||
return new AckCommand(this.Argument("read", true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,129 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal
|
|
||||||
{
|
|
||||||
internal class ConversationCommand : AVIMCommand
|
|
||||||
{
|
|
||||||
protected IList<string> members;
|
|
||||||
public ConversationCommand()
|
|
||||||
: base(cmd: "conv")
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConversationCommand(AVIMCommand source)
|
|
||||||
: base(source: source)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConversationCommand Member(string clientId)
|
|
||||||
{
|
|
||||||
if (members == null)
|
|
||||||
{
|
|
||||||
members = new List<string>();
|
|
||||||
}
|
|
||||||
members.Add(clientId);
|
|
||||||
return Members(members);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConversationCommand Members(IEnumerable<string> members)
|
|
||||||
{
|
|
||||||
this.members = members.ToList();
|
|
||||||
return new ConversationCommand(this.Argument("m", members));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConversationCommand Transient(bool isTransient)
|
|
||||||
{
|
|
||||||
return new ConversationCommand(this.Argument("transient", isTransient));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConversationCommand Unique(bool isUnique)
|
|
||||||
{
|
|
||||||
return new ConversationCommand(this.Argument("unique", isUnique));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConversationCommand Temporary(bool isTemporary)
|
|
||||||
{
|
|
||||||
return new ConversationCommand(this.Argument("tempConv", isTemporary));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConversationCommand TempConvTTL(double tempConvTTL)
|
|
||||||
{
|
|
||||||
return new ConversationCommand(this.Argument("tempConvTTL", tempConvTTL));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConversationCommand Attr(IDictionary<string, object> attr)
|
|
||||||
{
|
|
||||||
return new ConversationCommand(this.Argument("attr", attr));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConversationCommand Set(string key, object value)
|
|
||||||
{
|
|
||||||
return new ConversationCommand(this.Argument(key, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConversationCommand ConversationId(string conversationId)
|
|
||||||
{
|
|
||||||
return new ConversationCommand(this.Argument("cid", conversationId));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConversationCommand Generate(AVIMConversation conversation)
|
|
||||||
{
|
|
||||||
var attr = conversation.EncodeAttributes();
|
|
||||||
var cmd = new ConversationCommand()
|
|
||||||
.ConversationId(conversation.ConversationId)
|
|
||||||
.Attr(attr)
|
|
||||||
.Members(conversation.MemberIds)
|
|
||||||
.Transient(conversation.IsTransient)
|
|
||||||
.Temporary(conversation.IsTemporary);
|
|
||||||
|
|
||||||
if (conversation.IsTemporary)
|
|
||||||
{
|
|
||||||
var ttl = (conversation.expiredAt.Value - DateTime.Now).TotalSeconds;
|
|
||||||
cmd = cmd.TempConvTTL(ttl);
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConversationCommand Where(object encodedQueryString)
|
|
||||||
{
|
|
||||||
return new ConversationCommand(this.Argument("where", encodedQueryString));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConversationCommand Limit(int limit)
|
|
||||||
{
|
|
||||||
return new ConversationCommand(this.Argument("limit", limit));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConversationCommand Skip(int skip)
|
|
||||||
{
|
|
||||||
return new ConversationCommand(this.Argument("skip", skip));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConversationCommand Count()
|
|
||||||
{
|
|
||||||
return new ConversationCommand(this.Argument("count", 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConversationCommand Sort(string sort)
|
|
||||||
{
|
|
||||||
return new ConversationCommand(this.Argument("sort", sort));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConversationCommand TargetClientId(string targetClientId)
|
|
||||||
{
|
|
||||||
return new ConversationCommand(this.Argument("targetClientId", targetClientId));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConversationCommand QueryAllMembers(bool queryAllMembers)
|
|
||||||
{
|
|
||||||
return new ConversationCommand(this.Argument("queryAllMembers", queryAllMembers));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal
|
|
||||||
{
|
|
||||||
public interface IAVIMCommandRunner
|
|
||||||
{
|
|
||||||
Task<Tuple<int, IDictionary<string, object>>> RunCommandAsync(AVIMCommand command,
|
|
||||||
CancellationToken cancellationToken = default(CancellationToken));
|
|
||||||
|
|
||||||
void RunCommand(AVIMCommand command);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,83 +0,0 @@
|
||||||
using LeanCloud.Storage.Internal;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal
|
|
||||||
{
|
|
||||||
internal class MessageCommand : AVIMCommand
|
|
||||||
{
|
|
||||||
public MessageCommand()
|
|
||||||
: base(cmd: "direct")
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public MessageCommand(AVIMCommand source)
|
|
||||||
: base(source: source)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public MessageCommand ConvId(string convId)
|
|
||||||
{
|
|
||||||
return new MessageCommand(this.Argument("cid", convId));
|
|
||||||
}
|
|
||||||
|
|
||||||
public MessageCommand Receipt(bool receipt)
|
|
||||||
{
|
|
||||||
return new MessageCommand(this.Argument("r", receipt));
|
|
||||||
}
|
|
||||||
|
|
||||||
public MessageCommand Transient(bool transient)
|
|
||||||
{
|
|
||||||
if (transient) return new MessageCommand(this.Argument("transient", transient));
|
|
||||||
return new MessageCommand(this);
|
|
||||||
}
|
|
||||||
public MessageCommand Priority(int priority)
|
|
||||||
{
|
|
||||||
if (priority > 1) return new MessageCommand(this.Argument("level", priority));
|
|
||||||
return new MessageCommand(this);
|
|
||||||
}
|
|
||||||
public MessageCommand Will(bool will)
|
|
||||||
{
|
|
||||||
if (will) return new MessageCommand(this.Argument("will", will));
|
|
||||||
return new MessageCommand(this);
|
|
||||||
}
|
|
||||||
public MessageCommand Distinct(string token)
|
|
||||||
{
|
|
||||||
return new MessageCommand(this.Argument("dt", token));
|
|
||||||
}
|
|
||||||
public MessageCommand Message(string msg)
|
|
||||||
{
|
|
||||||
return new MessageCommand(this.Argument("msg", msg));
|
|
||||||
}
|
|
||||||
public MessageCommand BinaryEncode(bool binaryEncode)
|
|
||||||
{
|
|
||||||
return new MessageCommand(this.Argument("bin", binaryEncode));
|
|
||||||
}
|
|
||||||
|
|
||||||
public MessageCommand PushData(IDictionary<string, object> pushData)
|
|
||||||
{
|
|
||||||
return new MessageCommand(this.Argument("pushData", Json.Encode(pushData)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public MessageCommand Mention(IEnumerable<string> clientIds)
|
|
||||||
{
|
|
||||||
var mentionedMembers = clientIds.ToList();
|
|
||||||
return new MessageCommand(this.Argument("mentionPids", mentionedMembers));
|
|
||||||
}
|
|
||||||
|
|
||||||
public MessageCommand MentionAll(bool mentionAll)
|
|
||||||
{
|
|
||||||
return new MessageCommand(this.Argument("mentionAll", mentionAll));
|
|
||||||
}
|
|
||||||
|
|
||||||
public MessageCommand Binary(byte[] data)
|
|
||||||
{
|
|
||||||
return new MessageCommand(this.Argument("binaryMsg", data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,98 +0,0 @@
|
||||||
using LeanCloud.Storage.Internal;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal
|
|
||||||
{
|
|
||||||
internal class PatchCommand : AVIMCommand
|
|
||||||
{
|
|
||||||
|
|
||||||
internal struct Patch
|
|
||||||
{
|
|
||||||
public string MessageId { get; set; }
|
|
||||||
public string ConvId { get; set; }
|
|
||||||
public string From { get; set; }
|
|
||||||
public long MetaTimestamp { get; set; }
|
|
||||||
public long PatchTimestamp { get; set; }
|
|
||||||
public string PatchData { get; set; }
|
|
||||||
public bool Recall { get; set; }
|
|
||||||
public byte[] BinaryData { get; set; }
|
|
||||||
public bool MentionAll { get; set; }
|
|
||||||
public IEnumerable<string> MentionIds { get; set; }
|
|
||||||
|
|
||||||
public IDictionary<string, object> Encode()
|
|
||||||
{
|
|
||||||
return new Dictionary<string, object>()
|
|
||||||
{
|
|
||||||
{ "cid",this.ConvId},
|
|
||||||
{ "mid",this.MessageId},
|
|
||||||
{ "from",this.From},
|
|
||||||
{ "timestamp",this.MetaTimestamp},
|
|
||||||
{ "recall",this.Recall},
|
|
||||||
{ "data",this.PatchData},
|
|
||||||
{ "patchTimestamp",this.PatchTimestamp},
|
|
||||||
{ "binaryMsg",this.BinaryData},
|
|
||||||
{ "mentionAll",this.MentionAll},
|
|
||||||
{ "meintonPids",this.MentionIds}
|
|
||||||
} as IDictionary<string, object>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public PatchCommand()
|
|
||||||
: base(cmd: "patch", op: "modify")
|
|
||||||
{
|
|
||||||
this.Patches = new List<Patch>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public PatchCommand(AVIMCommand source, ICollection<Patch> sourcePatchs)
|
|
||||||
: base(source: source)
|
|
||||||
{
|
|
||||||
this.Patches = sourcePatchs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ICollection<Patch> Patches { get; set; }
|
|
||||||
|
|
||||||
public IList<IDictionary<string, object>> EncodePatches()
|
|
||||||
{
|
|
||||||
return this.Patches.Select(p => p.Encode().Trim()).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public PatchCommand Recall(IAVIMMessage message)
|
|
||||||
{
|
|
||||||
var patch = new Patch()
|
|
||||||
{
|
|
||||||
ConvId = message.ConversationId,
|
|
||||||
From = message.FromClientId,
|
|
||||||
MessageId = message.Id,
|
|
||||||
MetaTimestamp = message.ServerTimestamp,
|
|
||||||
Recall = true,
|
|
||||||
PatchTimestamp = DateTime.Now.ToUnixTimeStamp()
|
|
||||||
};
|
|
||||||
|
|
||||||
this.Patches.Add(patch);
|
|
||||||
this.Argument("patches", this.EncodePatches());
|
|
||||||
return new PatchCommand(this, this.Patches);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PatchCommand Modify(IAVIMMessage oldMessage, IAVIMMessage newMessage)
|
|
||||||
{
|
|
||||||
var patch = new Patch()
|
|
||||||
{
|
|
||||||
ConvId = oldMessage.ConversationId,
|
|
||||||
From = oldMessage.FromClientId,
|
|
||||||
MessageId = oldMessage.Id,
|
|
||||||
MetaTimestamp = oldMessage.ServerTimestamp,
|
|
||||||
Recall = false,
|
|
||||||
PatchTimestamp = DateTime.Now.ToUnixTimeStamp(),
|
|
||||||
PatchData = newMessage.Serialize()
|
|
||||||
};
|
|
||||||
|
|
||||||
this.Patches.Add(patch);
|
|
||||||
this.Argument("patches", this.EncodePatches());
|
|
||||||
return new PatchCommand(this, this.Patches);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal
|
|
||||||
{
|
|
||||||
internal class ReadCommand : AVIMCommand
|
|
||||||
{
|
|
||||||
internal class ConvRead
|
|
||||||
{
|
|
||||||
internal string ConvId { get; set; }
|
|
||||||
internal string MessageId { get; set; }
|
|
||||||
internal long Timestamp { get; set; }
|
|
||||||
public override bool Equals(object obj)
|
|
||||||
{
|
|
||||||
ConvRead cr = obj as ConvRead;
|
|
||||||
return cr.ConvId == this.ConvId;
|
|
||||||
}
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return this.ConvId.GetHashCode() ^ this.MessageId.GetHashCode() ^ this.Timestamp.GetHashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadCommand()
|
|
||||||
: base(cmd: "read")
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadCommand(AVIMCommand source)
|
|
||||||
: base(source)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadCommand ConvId(string convId)
|
|
||||||
{
|
|
||||||
return new ReadCommand(this.Argument("cid", convId));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadCommand ConvIds(IEnumerable<string> convIds)
|
|
||||||
{
|
|
||||||
if (convIds != null)
|
|
||||||
{
|
|
||||||
if (convIds.Count() > 0)
|
|
||||||
{
|
|
||||||
return new ReadCommand(this.Argument("cids", convIds.ToList()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadCommand Conv(ConvRead conv)
|
|
||||||
{
|
|
||||||
return Convs(new ConvRead[] { conv });
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadCommand Convs(IEnumerable<ConvRead> convReads)
|
|
||||||
{
|
|
||||||
if (convReads != null)
|
|
||||||
{
|
|
||||||
if (convReads.Count() > 0)
|
|
||||||
{
|
|
||||||
IList<IDictionary<string, object>> payload = new List<IDictionary<string, object>>();
|
|
||||||
|
|
||||||
foreach (var convRead in convReads)
|
|
||||||
{
|
|
||||||
var convDic = new Dictionary<string, object>();
|
|
||||||
convDic.Add("cid", convRead.ConvId);
|
|
||||||
if (!string.IsNullOrEmpty(convRead.MessageId))
|
|
||||||
{
|
|
||||||
convDic.Add("mid", convRead.MessageId);
|
|
||||||
}
|
|
||||||
if (convRead.Timestamp != 0)
|
|
||||||
{
|
|
||||||
convDic.Add("timestamp", convRead.Timestamp);
|
|
||||||
}
|
|
||||||
payload.Add(convDic);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ReadCommand(this.Argument("convs", payload));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal
|
|
||||||
{
|
|
||||||
internal class SessionCommand : AVIMCommand
|
|
||||||
{
|
|
||||||
static readonly int MESSAGE_RECALL_AND_MODIFY = 0x1;
|
|
||||||
|
|
||||||
public SessionCommand()
|
|
||||||
: base(cmd: "session")
|
|
||||||
{
|
|
||||||
arguments.Add("configBitmap", MESSAGE_RECALL_AND_MODIFY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SessionCommand(AVIMCommand source)
|
|
||||||
:base(source: source)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public SessionCommand UA(string ua)
|
|
||||||
{
|
|
||||||
return new SessionCommand(this.Argument("ua", ua));
|
|
||||||
}
|
|
||||||
|
|
||||||
public SessionCommand Tag(string tag)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(tag)) return new SessionCommand(this);
|
|
||||||
return new SessionCommand(this.Argument("tag", tag));
|
|
||||||
}
|
|
||||||
|
|
||||||
public SessionCommand DeviceId(string deviceId)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(deviceId)) return new SessionCommand(this);
|
|
||||||
return new SessionCommand(this.Argument("deviceId", deviceId));
|
|
||||||
}
|
|
||||||
|
|
||||||
public SessionCommand R(int r)
|
|
||||||
{
|
|
||||||
return new SessionCommand(this.Argument("r", r));
|
|
||||||
}
|
|
||||||
|
|
||||||
public SessionCommand SessionToken(string st)
|
|
||||||
{
|
|
||||||
return new SessionCommand(this.Argument("st", st));
|
|
||||||
}
|
|
||||||
|
|
||||||
public SessionCommand SessionPeerIds(IEnumerable<string> sessionPeerIds)
|
|
||||||
{
|
|
||||||
return new SessionCommand(this.Argument("sessionPeerIds", sessionPeerIds.ToList()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal
|
|
||||||
{
|
|
||||||
internal enum UnixTimeStampUnit
|
|
||||||
{
|
|
||||||
Second = 1,
|
|
||||||
Milisecond = 1000,
|
|
||||||
}
|
|
||||||
internal static class DateTimeEngine
|
|
||||||
{
|
|
||||||
public static long ToUnixTimeStamp(this DateTime date, UnixTimeStampUnit unit = UnixTimeStampUnit.Milisecond)
|
|
||||||
{
|
|
||||||
long unixTimestamp = (long)(date.ToUniversalTime().Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
|
|
||||||
return (unixTimestamp * (int)unit);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DateTime ToDateTime(this long timestamp, UnixTimeStampUnit unit = UnixTimeStampUnit.Milisecond)
|
|
||||||
{
|
|
||||||
var timespan = timestamp * 1000 / (int)(unit);
|
|
||||||
DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
|
|
||||||
dtDateTime = dtDateTime.AddMilliseconds(timespan).ToLocalTime();
|
|
||||||
return dtDateTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal
|
|
||||||
{
|
|
||||||
internal static class DictionaryEngine
|
|
||||||
{
|
|
||||||
internal static IDictionary<string, object> Merge(this IDictionary<string, object> dataLeft, IDictionary<string, object> dataRight)
|
|
||||||
{
|
|
||||||
if (dataRight == null)
|
|
||||||
return dataLeft;
|
|
||||||
foreach (var kv in dataRight)
|
|
||||||
{
|
|
||||||
if (dataLeft.ContainsKey(kv.Key))
|
|
||||||
{
|
|
||||||
dataLeft[kv.Key] = kv.Value;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
dataLeft.Add(kv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dataLeft;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static object Grab(this IDictionary<string, object> data, string path)
|
|
||||||
{
|
|
||||||
var keys = path.Split('.').ToList<string>();
|
|
||||||
if (keys.Count == 1) return data[keys[0]];
|
|
||||||
|
|
||||||
var deep = data[keys[0]] as IDictionary<string, object>;
|
|
||||||
|
|
||||||
keys.RemoveAt(0);
|
|
||||||
string deepPath = string.Join(".", keys.ToArray());
|
|
||||||
|
|
||||||
return Grab(deep, deepPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static IDictionary<TKey, TValue> Trim<TKey, TValue>(this IDictionary<TKey, TValue> data)
|
|
||||||
{
|
|
||||||
return data.Where(kvp => kvp.Value != null).ToDictionary(k => k.Key, v => v.Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq.Expressions;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal
|
|
||||||
{
|
|
||||||
internal static class StringEngine
|
|
||||||
{
|
|
||||||
internal static string Random(this string str, int length)
|
|
||||||
{
|
|
||||||
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
|
|
||||||
var random = new Random();
|
|
||||||
return new string(Enumerable.Repeat(chars, length)
|
|
||||||
.Select(s => s[random.Next(s.Length)]).ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static string TempConvId<T>(this IEnumerable<T> objs)
|
|
||||||
{
|
|
||||||
var orderedBase64Strs = objs.Select(obj => Encoding.UTF8.ToBase64(obj.ToString())).OrderBy(a => a, StringComparer.Ordinal).ToArray();
|
|
||||||
return "_tmp:" + string.Join("$", orderedBase64Strs);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static string ToBase64(this System.Text.Encoding encoding, string text)
|
|
||||||
{
|
|
||||||
if (text == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] textAsBytes = encoding.GetBytes(text);
|
|
||||||
return Convert.ToBase64String(textAsBytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal
|
|
||||||
{
|
|
||||||
interface IAVIMPlatformHooks
|
|
||||||
{
|
|
||||||
IWebSocketClient WebSocketClient { get; }
|
|
||||||
|
|
||||||
string ua { get; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,75 +0,0 @@
|
||||||
using LeanCloud.Storage.Internal;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal
|
|
||||||
{
|
|
||||||
internal class FreeStyleMessageClassInfo
|
|
||||||
{
|
|
||||||
public TypeInfo TypeInfo { get; private set; }
|
|
||||||
public IDictionary<String, String> PropertyMappings { get; private set; }
|
|
||||||
private ConstructorInfo Constructor { get; set; }
|
|
||||||
//private MethodInfo ValidateMethod { get; set; }
|
|
||||||
|
|
||||||
public int TypeInt { get; set; }
|
|
||||||
|
|
||||||
public FreeStyleMessageClassInfo(Type type, ConstructorInfo constructor)
|
|
||||||
{
|
|
||||||
TypeInfo = type.GetTypeInfo();
|
|
||||||
Constructor = constructor;
|
|
||||||
PropertyMappings = ReflectionHelpers.GetProperties(type)
|
|
||||||
.Select(prop => Tuple.Create(prop, prop.GetCustomAttribute<AVIMMessageFieldNameAttribute>(true)))
|
|
||||||
.Where(t => t.Item2 != null)
|
|
||||||
.Select(t => Tuple.Create(t.Item1, t.Item2.FieldName))
|
|
||||||
.ToDictionary(t => t.Item1.Name, t => t.Item2);
|
|
||||||
}
|
|
||||||
public bool Validate(string msgStr)
|
|
||||||
{
|
|
||||||
var instance = Instantiate(msgStr);
|
|
||||||
if (instance is AVIMTypedMessage)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var msgDic = Json.Parse(msgStr) as IDictionary<string, object>;
|
|
||||||
if (msgDic != null)
|
|
||||||
{
|
|
||||||
if (msgDic.ContainsKey(AVIMProtocol.LCTYPE))
|
|
||||||
{
|
|
||||||
return msgDic[AVIMProtocol.LCTYPE].ToString() == TypeInt.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
if (ex is ArgumentException)
|
|
||||||
{
|
|
||||||
return instance.Validate(msgStr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return instance.Validate(msgStr);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IAVIMMessage Instantiate(string msgStr)
|
|
||||||
{
|
|
||||||
var rtn = (IAVIMMessage)Constructor.Invoke(null);
|
|
||||||
return rtn;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetMessageClassName(TypeInfo type)
|
|
||||||
{
|
|
||||||
var attribute = type.GetCustomAttribute<AVIMMessageClassNameAttribute>();
|
|
||||||
return attribute != null ? attribute.ClassName : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int GetTypedInteger(TypeInfo type)
|
|
||||||
{
|
|
||||||
var attribute = type.GetCustomAttribute<AVIMTypedMessageTypeIntAttribute>();
|
|
||||||
return attribute != null ? attribute.TypeInteger : 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,210 +0,0 @@
|
||||||
using LeanCloud.Storage.Internal;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal
|
|
||||||
{
|
|
||||||
internal class FreeStyleMessageClassingController : IFreeStyleMessageClassingController
|
|
||||||
{
|
|
||||||
private static readonly string messageClassName = "_AVIMMessage";
|
|
||||||
private readonly IDictionary<string, FreeStyleMessageClassInfo> registeredInterfaces;
|
|
||||||
private readonly ReaderWriterLockSlim mutex;
|
|
||||||
|
|
||||||
public FreeStyleMessageClassingController()
|
|
||||||
{
|
|
||||||
mutex = new ReaderWriterLockSlim();
|
|
||||||
registeredInterfaces = new Dictionary<string, FreeStyleMessageClassInfo>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Type GetType(IDictionary<string, object> msg)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IAVIMMessage Instantiate(string msgStr, IDictionary<string, object> buildInData)
|
|
||||||
{
|
|
||||||
FreeStyleMessageClassInfo info = null;
|
|
||||||
mutex.EnterReadLock();
|
|
||||||
bool bin = false;
|
|
||||||
if (buildInData.ContainsKey("bin"))
|
|
||||||
{
|
|
||||||
bool.TryParse(buildInData["bin"].ToString(), out bin);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bin)
|
|
||||||
{
|
|
||||||
var binMessage = new AVIMBinaryMessage();
|
|
||||||
this.DecodeProperties(binMessage, buildInData);
|
|
||||||
return binMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
var reverse = registeredInterfaces.Values.Reverse();
|
|
||||||
foreach (var subInterface in reverse)
|
|
||||||
{
|
|
||||||
if (subInterface.Validate(msgStr))
|
|
||||||
{
|
|
||||||
info = subInterface;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mutex.ExitReadLock();
|
|
||||||
|
|
||||||
var message = info != null ? info.Instantiate(msgStr) : new AVIMMessage();
|
|
||||||
|
|
||||||
this.DecodeProperties(message, buildInData);
|
|
||||||
|
|
||||||
message.Deserialize(msgStr);
|
|
||||||
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IAVIMMessage DecodeProperties(IAVIMMessage message, IDictionary<string, object> buildInData)
|
|
||||||
{
|
|
||||||
long timestamp;
|
|
||||||
if (buildInData.ContainsKey("timestamp"))
|
|
||||||
{
|
|
||||||
if (long.TryParse(buildInData["timestamp"].ToString(), out timestamp))
|
|
||||||
{
|
|
||||||
message.ServerTimestamp = timestamp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
long ackAt;
|
|
||||||
if (buildInData.ContainsKey("ackAt"))
|
|
||||||
{
|
|
||||||
if (long.TryParse(buildInData["ackAt"].ToString(), out ackAt))
|
|
||||||
{
|
|
||||||
message.RcpTimestamp = ackAt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buildInData.ContainsKey("from"))
|
|
||||||
{
|
|
||||||
message.FromClientId = buildInData["from"].ToString();
|
|
||||||
}
|
|
||||||
if (buildInData.ContainsKey("msgId"))
|
|
||||||
{
|
|
||||||
message.Id = buildInData["msgId"].ToString();
|
|
||||||
}
|
|
||||||
if (buildInData.ContainsKey("cid"))
|
|
||||||
{
|
|
||||||
message.ConversationId = buildInData["cid"].ToString();
|
|
||||||
}
|
|
||||||
if (buildInData.ContainsKey("fromPeerId"))
|
|
||||||
{
|
|
||||||
message.FromClientId = buildInData["fromPeerId"].ToString();
|
|
||||||
}
|
|
||||||
if (buildInData.ContainsKey("id"))
|
|
||||||
{
|
|
||||||
message.Id = buildInData["id"].ToString();
|
|
||||||
}
|
|
||||||
if (buildInData.ContainsKey("mid"))
|
|
||||||
{
|
|
||||||
message.Id = buildInData["mid"].ToString();
|
|
||||||
}
|
|
||||||
if (buildInData.ContainsKey("mentionPids"))
|
|
||||||
{
|
|
||||||
message.MentionList = AVDecoder.Instance.DecodeList<string>(buildInData["mentionPids"]);
|
|
||||||
}
|
|
||||||
if (buildInData.TryGetValue("patchTimestamp", out object patchTimestampObj)) {
|
|
||||||
if (long.TryParse(patchTimestampObj.ToString(), out long patchTimestamp)) {
|
|
||||||
message.UpdatedAt = patchTimestamp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool mentionAll;
|
|
||||||
if (buildInData.ContainsKey("mentionAll"))
|
|
||||||
{
|
|
||||||
if (bool.TryParse(buildInData["mentionAll"].ToString(), out mentionAll))
|
|
||||||
{
|
|
||||||
message.MentionAll = mentionAll;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IDictionary<string, object> EncodeProperties(IAVIMMessage subclass)
|
|
||||||
{
|
|
||||||
var type = subclass.GetType();
|
|
||||||
var result = new Dictionary<string, object>();
|
|
||||||
var className = GetClassName(type);
|
|
||||||
var typeInt = GetTypeInt(type);
|
|
||||||
var propertMappings = GetPropertyMappings(className);
|
|
||||||
foreach (var propertyPair in propertMappings)
|
|
||||||
{
|
|
||||||
var propertyInfo = ReflectionHelpers.GetProperty(type, propertyPair.Key);
|
|
||||||
var operation = propertyInfo.GetValue(subclass, null);
|
|
||||||
if (operation != null)
|
|
||||||
result[propertyPair.Value] = PointerOrLocalIdEncoder.Instance.Encode(operation);
|
|
||||||
}
|
|
||||||
if (typeInt != 0)
|
|
||||||
{
|
|
||||||
result[AVIMProtocol.LCTYPE] = typeInt;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsTypeValid(IDictionary<string, object> msg, Type type)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RegisterSubclass(Type type)
|
|
||||||
{
|
|
||||||
TypeInfo typeInfo = type.GetTypeInfo();
|
|
||||||
|
|
||||||
if (!typeof(IAVIMMessage).GetTypeInfo().IsAssignableFrom(typeInfo))
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Cannot register a type that is not a implementation of IAVIMMessage");
|
|
||||||
}
|
|
||||||
var className = GetClassName(type);
|
|
||||||
var typeInt = GetTypeInt(type);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
mutex.EnterWriteLock();
|
|
||||||
ConstructorInfo constructor = type.FindConstructor();
|
|
||||||
if (constructor == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Cannot register a type that does not implement the default constructor!");
|
|
||||||
}
|
|
||||||
var classInfo = new FreeStyleMessageClassInfo(type, constructor);
|
|
||||||
if (typeInt != 0)
|
|
||||||
{
|
|
||||||
classInfo.TypeInt = typeInt;
|
|
||||||
}
|
|
||||||
registeredInterfaces[className] = classInfo;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
mutex.ExitWriteLock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public String GetClassName(Type type)
|
|
||||||
{
|
|
||||||
return type == typeof(IAVIMMessage)
|
|
||||||
? messageClassName
|
|
||||||
: FreeStyleMessageClassInfo.GetMessageClassName(type.GetTypeInfo());
|
|
||||||
}
|
|
||||||
public int GetTypeInt(Type type)
|
|
||||||
{
|
|
||||||
return type == typeof(AVIMTypedMessage) ? 0 : FreeStyleMessageClassInfo.GetTypedInteger(type.GetTypeInfo());
|
|
||||||
}
|
|
||||||
public IDictionary<String, String> GetPropertyMappings(String className)
|
|
||||||
{
|
|
||||||
FreeStyleMessageClassInfo info = null;
|
|
||||||
mutex.EnterReadLock();
|
|
||||||
registeredInterfaces.TryGetValue(className, out info);
|
|
||||||
if (info == null)
|
|
||||||
{
|
|
||||||
registeredInterfaces.TryGetValue(messageClassName, out info);
|
|
||||||
}
|
|
||||||
mutex.ExitReadLock();
|
|
||||||
|
|
||||||
return info.PropertyMappings;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal
|
|
||||||
{
|
|
||||||
interface IFreeStyleMessageClassingController
|
|
||||||
{
|
|
||||||
bool IsTypeValid(IDictionary<string,object> msg, Type type);
|
|
||||||
void RegisterSubclass(Type t);
|
|
||||||
IAVIMMessage Instantiate(string msgStr,IDictionary<string,object> buildInData);
|
|
||||||
IDictionary<string, object> EncodeProperties(IAVIMMessage subclass);
|
|
||||||
Type GetType(IDictionary<string, object> msg);
|
|
||||||
String GetClassName(Type type);
|
|
||||||
IDictionary<String, String> GetPropertyMappings(String className);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal
|
|
||||||
{
|
|
||||||
internal class AVIMProtocol
|
|
||||||
{
|
|
||||||
#region msg format
|
|
||||||
static internal readonly string LCTYPE = "_lctype";
|
|
||||||
static internal readonly string LCFILE = "_lcfile";
|
|
||||||
static internal readonly string LCTEXT = "_lctext";
|
|
||||||
static internal readonly string LCATTRS = "_lcattrs";
|
|
||||||
static internal readonly string LCLOC = "_lcloc";
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,173 +0,0 @@
|
||||||
using LeanCloud.Storage.Internal;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal
|
|
||||||
{
|
|
||||||
internal class AVRouterController : IAVRouterController
|
|
||||||
{
|
|
||||||
const string routerUrl = "http://router.g0.push.leancloud.cn/v1/route?appId={0}";
|
|
||||||
const string routerKey = "LeanCloud_RouterState";
|
|
||||||
public Task<PushRouterState> GetAsync(string pushRouter = null, bool secure = true, CancellationToken cancellationToken = default(CancellationToken))
|
|
||||||
{
|
|
||||||
//return Task.FromResult(new PushRouterState()
|
|
||||||
//{
|
|
||||||
// server = "wss://rtm57.leancloud.cn/"
|
|
||||||
//});
|
|
||||||
return LoadAysnc(cancellationToken).OnSuccess(_ =>
|
|
||||||
{
|
|
||||||
var cache = _.Result;
|
|
||||||
var task = Task.FromResult<PushRouterState>(cache);
|
|
||||||
|
|
||||||
if (cache == null || cache.expire < DateTime.Now.ToUnixTimeStamp())
|
|
||||||
{
|
|
||||||
task = QueryAsync(pushRouter, secure, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
return task;
|
|
||||||
}).Unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 清理地址缓存
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The cache.</returns>
|
|
||||||
public Task ClearCache() {
|
|
||||||
var tcs = new TaskCompletionSource<bool>();
|
|
||||||
AVPlugins.Instance.StorageController.LoadAsync().ContinueWith(t => {
|
|
||||||
if (t.IsFaulted) {
|
|
||||||
tcs.SetResult(true);
|
|
||||||
} else {
|
|
||||||
var storage = t.Result;
|
|
||||||
if (storage.ContainsKey(routerKey)) {
|
|
||||||
storage.RemoveAsync(routerKey).ContinueWith(_ => tcs.SetResult(true));
|
|
||||||
} else {
|
|
||||||
tcs.SetResult(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return tcs.Task;
|
|
||||||
}
|
|
||||||
|
|
||||||
Task<PushRouterState> LoadAysnc(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return AVPlugins.Instance.StorageController.LoadAsync().OnSuccess(_ =>
|
|
||||||
{
|
|
||||||
var currentCache = _.Result;
|
|
||||||
object routeCacheStr = null;
|
|
||||||
if (currentCache.TryGetValue(routerKey, out routeCacheStr))
|
|
||||||
{
|
|
||||||
var routeCache = routeCacheStr as IDictionary<string, object>;
|
|
||||||
var routerState = new PushRouterState()
|
|
||||||
{
|
|
||||||
groupId = routeCache["groupId"] as string,
|
|
||||||
server = routeCache["server"] as string,
|
|
||||||
secondary = routeCache["secondary"] as string,
|
|
||||||
ttl = long.Parse(routeCache["ttl"].ToString()),
|
|
||||||
expire = long.Parse(routeCache["expire"].ToString()),
|
|
||||||
source = "localCache"
|
|
||||||
};
|
|
||||||
return routerState;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return Task.FromResult<PushRouterState>(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Task<PushRouterState> QueryAsync(string pushRouter, bool secure, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var routerHost = pushRouter;
|
|
||||||
if (routerHost == null) {
|
|
||||||
var appRouter = AVPlugins.Instance.AppRouterController.Get();
|
|
||||||
routerHost = string.Format("https://{0}/v1/route?appId={1}", appRouter.RealtimeRouterServer, AVClient.CurrentConfiguration.ApplicationId) ?? appRouter.RealtimeRouterServer ?? string.Format(routerUrl, AVClient.CurrentConfiguration.ApplicationId);
|
|
||||||
}
|
|
||||||
AVRealtime.PrintLog($"router: {routerHost}");
|
|
||||||
AVRealtime.PrintLog($"push: {pushRouter}");
|
|
||||||
if (!string.IsNullOrEmpty(pushRouter))
|
|
||||||
{
|
|
||||||
var rtmUri = new Uri(pushRouter);
|
|
||||||
if (!string.IsNullOrEmpty(rtmUri.Scheme))
|
|
||||||
{
|
|
||||||
var url = new Uri(rtmUri, "v1/route").ToString();
|
|
||||||
routerHost = string.Format("{0}?appId={1}", url, AVClient.CurrentConfiguration.ApplicationId);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
routerHost = string.Format("https://{0}/v1/route?appId={1}", pushRouter, AVClient.CurrentConfiguration.ApplicationId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (secure)
|
|
||||||
{
|
|
||||||
routerHost += "&secure=1";
|
|
||||||
}
|
|
||||||
|
|
||||||
AVRealtime.PrintLog("use push router url:" + routerHost);
|
|
||||||
|
|
||||||
return AVClient.RequestAsync(uri: new Uri(routerHost),
|
|
||||||
method: "GET",
|
|
||||||
headers: null,
|
|
||||||
data: null,
|
|
||||||
contentType: "application/json",
|
|
||||||
cancellationToken: CancellationToken.None).ContinueWith<PushRouterState>(t =>
|
|
||||||
{
|
|
||||||
if (t.Exception != null)
|
|
||||||
{
|
|
||||||
var innnerException = t.Exception.InnerException;
|
|
||||||
AVRealtime.PrintLog(innnerException.Message);
|
|
||||||
throw innnerException;
|
|
||||||
}
|
|
||||||
var httpStatus = (int)t.Result.Item1;
|
|
||||||
if (httpStatus != 200)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var result = t.Result.Item2;
|
|
||||||
|
|
||||||
var routerState = Json.Parse(result) as IDictionary<string, object>;
|
|
||||||
if (routerState.Keys.Count == 0)
|
|
||||||
{
|
|
||||||
throw new KeyNotFoundException("Can not get websocket url from server,please check the appId.");
|
|
||||||
}
|
|
||||||
var ttl = long.Parse(routerState["ttl"].ToString());
|
|
||||||
var expire = DateTime.Now.AddSeconds(ttl);
|
|
||||||
routerState["expire"] = expire.ToUnixTimeStamp();
|
|
||||||
|
|
||||||
//save to local cache async.
|
|
||||||
AVPlugins.Instance.StorageController.LoadAsync().OnSuccess(storage => storage.Result.AddAsync(routerKey, routerState));
|
|
||||||
var routerStateObj = new PushRouterState()
|
|
||||||
{
|
|
||||||
groupId = routerState["groupId"] as string,
|
|
||||||
server = routerState["server"] as string,
|
|
||||||
secondary = routerState["secondary"] as string,
|
|
||||||
ttl = long.Parse(routerState["ttl"].ToString()),
|
|
||||||
expire = expire.ToUnixTimeStamp(),
|
|
||||||
source = "online"
|
|
||||||
};
|
|
||||||
|
|
||||||
return routerStateObj;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
if (e is KeyNotFoundException)
|
|
||||||
{
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal
|
|
||||||
{
|
|
||||||
interface IAVRouterController
|
|
||||||
{
|
|
||||||
Task<PushRouterState> GetAsync(string pushRouter, bool secure, CancellationToken cancellationToken = default(CancellationToken));
|
|
||||||
Task ClearCache();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal
|
|
||||||
{
|
|
||||||
internal class PushRouterState
|
|
||||||
{
|
|
||||||
public string groupId { get; internal set; }
|
|
||||||
public string server { get; internal set; }
|
|
||||||
public long ttl { get; internal set; }
|
|
||||||
public long expire { get; internal set; }
|
|
||||||
public string secondary { get; internal set; }
|
|
||||||
public string groupUrl { get; internal set; }
|
|
||||||
|
|
||||||
public string source { get; internal set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
using System;
|
|
||||||
namespace LeanCloud.Realtime.Internal
|
|
||||||
{
|
|
||||||
public interface IAVTimer
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Start this timer.
|
|
||||||
/// </summary>
|
|
||||||
void Start();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stop this timer.
|
|
||||||
/// </summary>
|
|
||||||
void Stop();
|
|
||||||
|
|
||||||
bool Enabled { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The number of milliseconds between timer events.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The interval.</value>
|
|
||||||
double Interval { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 已经执行了多少次
|
|
||||||
/// </summary>
|
|
||||||
long Executed { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when elapsed.
|
|
||||||
/// </summary>
|
|
||||||
event EventHandler<TimerEventArgs> Elapsed;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Timer event arguments.
|
|
||||||
/// </summary>
|
|
||||||
public class TimerEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
public TimerEventArgs(DateTime signalTime)
|
|
||||||
{
|
|
||||||
SignalTime = signalTime;
|
|
||||||
}
|
|
||||||
public DateTime SignalTime
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
private set;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,107 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal
|
|
||||||
{
|
|
||||||
internal delegate void TimerCallback();
|
|
||||||
|
|
||||||
internal sealed class Timer : CancellationTokenSource, IDisposable
|
|
||||||
{
|
|
||||||
TimerCallback exe;
|
|
||||||
int Interval { get; set; }
|
|
||||||
|
|
||||||
internal Timer(TimerCallback callback, int interval, bool enable)
|
|
||||||
{
|
|
||||||
exe = callback;
|
|
||||||
Interval = interval;
|
|
||||||
|
|
||||||
Enabled = enable;
|
|
||||||
Execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
Task Execute()
|
|
||||||
{
|
|
||||||
if (Enabled)
|
|
||||||
return Task.Delay(Interval).ContinueWith(t =>
|
|
||||||
{
|
|
||||||
if (!Enabled)
|
|
||||||
return null;
|
|
||||||
exe();
|
|
||||||
return this.Execute();
|
|
||||||
});
|
|
||||||
else
|
|
||||||
return Task.FromResult(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
volatile bool enabled;
|
|
||||||
public bool Enabled
|
|
||||||
{
|
|
||||||
get {
|
|
||||||
return enabled;
|
|
||||||
} set {
|
|
||||||
enabled = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AVTimer : IAVTimer
|
|
||||||
{
|
|
||||||
public AVTimer()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer timer;
|
|
||||||
|
|
||||||
public bool Enabled
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return timer.Enabled;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
timer.Enabled = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public double Interval
|
|
||||||
{
|
|
||||||
get; set;
|
|
||||||
}
|
|
||||||
|
|
||||||
long executed;
|
|
||||||
|
|
||||||
public long Executed
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return executed;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal set
|
|
||||||
{
|
|
||||||
executed = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Start()
|
|
||||||
{
|
|
||||||
if (timer == null)
|
|
||||||
{
|
|
||||||
timer = new Timer(() =>
|
|
||||||
{
|
|
||||||
Elapsed(this, new TimerEventArgs(DateTime.Now));
|
|
||||||
}, (int)Interval, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Stop()
|
|
||||||
{
|
|
||||||
if (timer != null) timer.Enabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public event EventHandler<TimerEventArgs> Elapsed;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,82 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Timers;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal
|
|
||||||
{
|
|
||||||
public class AVTimer : IAVTimer
|
|
||||||
{
|
|
||||||
public AVTimer()
|
|
||||||
{
|
|
||||||
timer = new Timer();
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer timer;
|
|
||||||
|
|
||||||
public bool Enabled
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return timer.Enabled;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
timer.Enabled = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public double Interval
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return timer.Interval;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
timer.Interval = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
long executed;
|
|
||||||
|
|
||||||
public long Executed
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return executed;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal set
|
|
||||||
{
|
|
||||||
executed = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Start()
|
|
||||||
{
|
|
||||||
timer.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Stop()
|
|
||||||
{
|
|
||||||
timer.Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public event EventHandler<TimerEventArgs> Elapsed
|
|
||||||
{
|
|
||||||
add
|
|
||||||
{
|
|
||||||
timer.Elapsed += (object sender, ElapsedEventArgs e) =>
|
|
||||||
{
|
|
||||||
value(this, new TimerEventArgs(e.SignalTime));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
remove
|
|
||||||
{
|
|
||||||
timer.Elapsed -= (object sender, ElapsedEventArgs e) =>
|
|
||||||
{
|
|
||||||
value(this, new TimerEventArgs(e.SignalTime));
|
|
||||||
}; ;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// LeanCloud WebSocket 客户端接口
|
|
||||||
/// </summary>
|
|
||||||
public interface IWebSocketClient
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 客户端 WebSocket 长连接是否打开
|
|
||||||
/// </summary>
|
|
||||||
bool IsOpen { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// WebSocket 长连接关闭时触发的事件回调
|
|
||||||
/// </summary>
|
|
||||||
event Action<int, string, string> OnClosed;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 云端发送数据包给客户端,WebSocket 接受到时触发的事件回调
|
|
||||||
/// </summary>
|
|
||||||
event Action<string> OnMessage;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 客户端 WebSocket 长连接成功打开时,触发的事件回调
|
|
||||||
/// </summary>
|
|
||||||
event Action OnOpened;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 主动关闭连接
|
|
||||||
/// </summary>
|
|
||||||
void Close();
|
|
||||||
|
|
||||||
void Disconnect();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 打开连接
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="url">wss 地址</param>
|
|
||||||
/// <param name="protocol">子协议</param>
|
|
||||||
void Open(string url, string protocol = null);
|
|
||||||
/// <summary>
|
|
||||||
/// 发送数据包的接口
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="message"></param>
|
|
||||||
void Send(string message);
|
|
||||||
|
|
||||||
Task<bool> Connect(string url, string protocol = null);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,174 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Net.WebSockets;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal
|
|
||||||
{
|
|
||||||
public class DefaultWebSocketClient : IWebSocketClient
|
|
||||||
{
|
|
||||||
private const int ReceiveChunkSize = 1024;
|
|
||||||
private const int SendChunkSize = 1024;
|
|
||||||
|
|
||||||
private ClientWebSocket _ws;
|
|
||||||
private Uri _uri;
|
|
||||||
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
|
||||||
private readonly CancellationToken _cancellationToken;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when on closed.
|
|
||||||
/// </summary>
|
|
||||||
public event Action<int, string, string> OnClosed;
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when on error.
|
|
||||||
/// </summary>
|
|
||||||
public event Action<string> OnError;
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when on log.
|
|
||||||
/// </summary>
|
|
||||||
public event Action<string> OnLog;
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when on opened.
|
|
||||||
/// </summary>
|
|
||||||
public event Action OnOpened;
|
|
||||||
|
|
||||||
public bool IsOpen => _ws.State == WebSocketState.Open;
|
|
||||||
|
|
||||||
public DefaultWebSocketClient()
|
|
||||||
{
|
|
||||||
_ws = NewWebSocket();
|
|
||||||
_cancellationToken = _cancellationTokenSource.Token;
|
|
||||||
}
|
|
||||||
|
|
||||||
public event Action<string> OnMessage;
|
|
||||||
|
|
||||||
private async void StartListen()
|
|
||||||
{
|
|
||||||
var buffer = new byte[8192];
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
while (_ws.State == WebSocketState.Open)
|
|
||||||
{
|
|
||||||
var stringResult = new StringBuilder();
|
|
||||||
|
|
||||||
WebSocketReceiveResult result;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
result = await _ws.ReceiveAsync(new ArraySegment<byte>(buffer), _cancellationToken);
|
|
||||||
|
|
||||||
if (result.MessageType == WebSocketMessageType.Close)
|
|
||||||
{
|
|
||||||
await
|
|
||||||
_ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
|
|
||||||
CallOnDisconnected();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var str = Encoding.UTF8.GetString(buffer, 0, result.Count);
|
|
||||||
stringResult.Append(str);
|
|
||||||
}
|
|
||||||
|
|
||||||
} while (!result.EndOfMessage);
|
|
||||||
|
|
||||||
CallOnMessage(stringResult);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
CallOnDisconnected();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_ws.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CallOnMessage(StringBuilder stringResult)
|
|
||||||
{
|
|
||||||
if (OnMessage != null)
|
|
||||||
RunInTask(() => OnMessage(stringResult.ToString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CallOnDisconnected()
|
|
||||||
{
|
|
||||||
AVRealtime.PrintLog("PCL websocket closed without parameters.");
|
|
||||||
if (OnClosed != null)
|
|
||||||
RunInTask(() => this.OnClosed(0, "", ""));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CallOnConnected()
|
|
||||||
{
|
|
||||||
if (OnOpened != null)
|
|
||||||
RunInTask(() => this.OnOpened());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void RunInTask(Action action)
|
|
||||||
{
|
|
||||||
Task.Factory.StartNew(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async void Close()
|
|
||||||
{
|
|
||||||
if (_ws != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
|
|
||||||
CallOnDisconnected();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
CallOnDisconnected();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Disconnect() {
|
|
||||||
_ws?.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async void Open(string url, string protocol = null)
|
|
||||||
{
|
|
||||||
_uri = new Uri(url);
|
|
||||||
if (_ws.State == WebSocketState.Open || _ws.State == WebSocketState.Connecting)
|
|
||||||
{
|
|
||||||
_ws = NewWebSocket();
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _ws.ConnectAsync(_uri, _cancellationToken);
|
|
||||||
CallOnConnected();
|
|
||||||
StartListen();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
if (ex is ObjectDisposedException)
|
|
||||||
{
|
|
||||||
OnError($"can NOT connect server with url: {url}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async void Send(string message)
|
|
||||||
{
|
|
||||||
if (_ws.State != WebSocketState.Open)
|
|
||||||
{
|
|
||||||
throw new Exception("Connection is not open.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var encoded = Encoding.UTF8.GetBytes(message);
|
|
||||||
var buffer = new ArraySegment<Byte>(encoded, 0, encoded.Length);
|
|
||||||
await _ws.SendAsync(buffer, WebSocketMessageType.Text, true, _cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
ClientWebSocket NewWebSocket()
|
|
||||||
{
|
|
||||||
var result = new ClientWebSocket();
|
|
||||||
result.Options.KeepAliveInterval = TimeSpan.FromSeconds(20);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
using System;
|
|
||||||
using Websockets;
|
|
||||||
using System.Net.WebSockets;
|
|
||||||
using LeanCloud.Realtime.Internal;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime
|
|
||||||
{
|
|
||||||
public class WebSocketClient: IWebSocketClient
|
|
||||||
{
|
|
||||||
internal readonly IWebSocketConnection connection;
|
|
||||||
public WebSocketClient()
|
|
||||||
{
|
|
||||||
Websockets.Net.WebsocketConnection.Link();
|
|
||||||
connection = WebSocketFactory.Create();
|
|
||||||
}
|
|
||||||
|
|
||||||
public event Action OnClosed;
|
|
||||||
public event Action<string> OnError;
|
|
||||||
public event Action<string> OnLog;
|
|
||||||
|
|
||||||
public event Action OnOpened
|
|
||||||
{
|
|
||||||
add
|
|
||||||
{
|
|
||||||
connection.OnOpened += value;
|
|
||||||
}
|
|
||||||
remove
|
|
||||||
{
|
|
||||||
connection.OnOpened -= value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public event Action<string> OnMessage
|
|
||||||
{
|
|
||||||
add
|
|
||||||
{
|
|
||||||
connection.OnMessage += value;
|
|
||||||
}
|
|
||||||
remove
|
|
||||||
{
|
|
||||||
connection.OnMessage -= value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsOpen
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return connection.IsOpen;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Close()
|
|
||||||
{
|
|
||||||
if (connection != null)
|
|
||||||
{
|
|
||||||
connection.Close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Open(string url, string protocol = null)
|
|
||||||
{
|
|
||||||
if (connection != null)
|
|
||||||
{
|
|
||||||
connection.Open(url, protocol);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Send(string message)
|
|
||||||
{
|
|
||||||
if (connection != null)
|
|
||||||
{
|
|
||||||
if (this.IsOpen)
|
|
||||||
{
|
|
||||||
connection.Send(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,164 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Websockets;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// LeanCloud Realtime SDK for .NET Portable 内置默认的 WebSocketClient
|
|
||||||
/// </summary>
|
|
||||||
public class DefaultWebSocketClient : IWebSocketClient
|
|
||||||
{
|
|
||||||
internal IWebSocketConnection connection;
|
|
||||||
/// <summary>
|
|
||||||
/// LeanCluod .NET Realtime SDK 内置默认的 WebSocketClient
|
|
||||||
/// 开发者可以在初始化的时候指定自定义的 WebSocketClient
|
|
||||||
/// </summary>
|
|
||||||
public DefaultWebSocketClient()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public event Action<int, string, string> OnClosed;
|
|
||||||
public event Action OnOpened;
|
|
||||||
public event Action<string> OnMessage;
|
|
||||||
|
|
||||||
public bool IsOpen
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return connection != null && connection.IsOpen;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Close()
|
|
||||||
{
|
|
||||||
if (connection != null)
|
|
||||||
{
|
|
||||||
connection.OnOpened -= Connection_OnOpened;
|
|
||||||
connection.OnMessage -= Connection_OnMessage;
|
|
||||||
connection.OnClosed -= Connection_OnClosed;
|
|
||||||
connection.OnError -= Connection_OnError;
|
|
||||||
try {
|
|
||||||
connection.Close();
|
|
||||||
} catch (Exception e) {
|
|
||||||
AVRealtime.PrintLog(string.Format("close websocket error: {0}", e.Message));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Disconnect() {
|
|
||||||
connection?.Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Open(string url, string protocol = null)
|
|
||||||
{
|
|
||||||
// 在每次打开时,重新创建 WebSocket 对象
|
|
||||||
connection = WebSocketFactory.Create();
|
|
||||||
connection.OnOpened += Connection_OnOpened;
|
|
||||||
connection.OnMessage += Connection_OnMessage;
|
|
||||||
connection.OnClosed += Connection_OnClosed;
|
|
||||||
connection.OnError += Connection_OnError;
|
|
||||||
if (!string.IsNullOrEmpty(protocol))
|
|
||||||
{
|
|
||||||
url = string.Format("{0}?subprotocol={1}", url, protocol);
|
|
||||||
}
|
|
||||||
connection.Open(url, protocol);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Connection_OnOpened()
|
|
||||||
{
|
|
||||||
OnOpened?.Invoke();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Connection_OnMessage(string obj)
|
|
||||||
{
|
|
||||||
AVRealtime.PrintLog("websocket<=" + obj);
|
|
||||||
OnMessage?.Invoke(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Connection_OnClosed()
|
|
||||||
{
|
|
||||||
AVRealtime.PrintLog("PCL websocket closed without parameters..");
|
|
||||||
OnClosed?.Invoke(0, "", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Connection_OnError(string obj)
|
|
||||||
{
|
|
||||||
AVRealtime.PrintLog($"PCL websocket error: {obj}");
|
|
||||||
connection?.Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Send(string message)
|
|
||||||
{
|
|
||||||
if (connection != null)
|
|
||||||
{
|
|
||||||
if (this.IsOpen)
|
|
||||||
{
|
|
||||||
connection.Send(message);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var log = "Connection is NOT open when send message";
|
|
||||||
AVRealtime.PrintLog(log);
|
|
||||||
connection?.Close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
AVRealtime.PrintLog("Connection is NULL");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<bool> Connect(string url, string protocol = null) {
|
|
||||||
var tcs = new TaskCompletionSource<bool>();
|
|
||||||
Action onOpen = null;
|
|
||||||
Action onClose = null;
|
|
||||||
Action<string> onError = null;
|
|
||||||
onOpen = () => {
|
|
||||||
AVRealtime.PrintLog("PCL websocket opened");
|
|
||||||
connection.OnOpened -= onOpen;
|
|
||||||
connection.OnClosed -= onClose;
|
|
||||||
connection.OnError -= onError;
|
|
||||||
// 注册事件
|
|
||||||
connection.OnMessage += Connection_OnMessage;
|
|
||||||
connection.OnClosed += Connection_OnClosed;
|
|
||||||
connection.OnError += Connection_OnError;
|
|
||||||
tcs.SetResult(true);
|
|
||||||
};
|
|
||||||
onClose = () => {
|
|
||||||
connection.OnOpened -= onOpen;
|
|
||||||
connection.OnClosed -= onClose;
|
|
||||||
connection.OnError -= onError;
|
|
||||||
tcs.SetException(new Exception("连接关闭"));
|
|
||||||
};
|
|
||||||
onError = (err) => {
|
|
||||||
AVRealtime.PrintLog(string.Format("连接错误:{0}", err));
|
|
||||||
connection.OnOpened -= onOpen;
|
|
||||||
connection.OnClosed -= onClose;
|
|
||||||
connection.OnError -= onError;
|
|
||||||
try {
|
|
||||||
connection.Close();
|
|
||||||
} catch (Exception e) {
|
|
||||||
AVRealtime.PrintLog(string.Format("关闭错误的 WebSocket 异常:{0}", e.Message));
|
|
||||||
} finally {
|
|
||||||
tcs.SetException(new Exception(string.Format("连接错误:{0}", err)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 在每次打开时,重新创建 WebSocket 对象
|
|
||||||
connection = WebSocketFactory.Create();
|
|
||||||
connection.OnOpened += onOpen;
|
|
||||||
connection.OnClosed += onClose;
|
|
||||||
connection.OnError += onError;
|
|
||||||
//
|
|
||||||
if (!string.IsNullOrEmpty(protocol)) {
|
|
||||||
url = string.Format("{0}?subprotocol={1}", url, protocol);
|
|
||||||
}
|
|
||||||
connection.Open(url, protocol);
|
|
||||||
return tcs.Task;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,149 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using WebSocketSharp;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal
|
|
||||||
{
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// LeanCluod Unity Realtime SDK 内置默认的 WebSocketClient
|
|
||||||
/// 开发者可以在初始化的时候指定自定义的 WebSocketClient
|
|
||||||
/// </summary>
|
|
||||||
public class DefaultWebSocketClient : IWebSocketClient
|
|
||||||
{
|
|
||||||
WebSocket ws;
|
|
||||||
public bool IsOpen
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (ws == null) { return false; }
|
|
||||||
return ws.IsAlive;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public event Action<int, string, string> OnClosed;
|
|
||||||
public event Action<string> OnMessage
|
|
||||||
{
|
|
||||||
add
|
|
||||||
{
|
|
||||||
onMesssageCount++;
|
|
||||||
AVRealtime.PrintLog("DefaultWebSocketClient.OnMessage event add with " + onMesssageCount + " times");
|
|
||||||
m_OnMessage += value;
|
|
||||||
|
|
||||||
}
|
|
||||||
remove
|
|
||||||
{
|
|
||||||
onMesssageCount--;
|
|
||||||
AVRealtime.PrintLog("DefaultWebSocketClient.OnMessage event remove with " + onMesssageCount + " times");
|
|
||||||
m_OnMessage -= value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private Action<string> m_OnMessage;
|
|
||||||
private int onMesssageCount = 0;
|
|
||||||
public event Action OnOpened;
|
|
||||||
|
|
||||||
public void Close()
|
|
||||||
{
|
|
||||||
ws.CloseAsync();
|
|
||||||
ws.OnOpen -= OnOpen;
|
|
||||||
ws.OnMessage -= OnWebSokectMessage;
|
|
||||||
ws.OnClose -= OnClose;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Disconnect() {
|
|
||||||
ws.CloseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Open(string url, string protocol = null)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(protocol))
|
|
||||||
{
|
|
||||||
url = string.Format("{0}?subprotocol={1}", url, protocol);
|
|
||||||
}
|
|
||||||
ws = new WebSocket(url);
|
|
||||||
ws.OnOpen += OnOpen;
|
|
||||||
ws.OnMessage += OnWebSokectMessage;
|
|
||||||
ws.OnClose += OnClose;
|
|
||||||
ws.ConnectAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnWebSokectMessage(object sender, MessageEventArgs e)
|
|
||||||
{
|
|
||||||
AVRealtime.PrintLog("websocket<=" + e.Data);
|
|
||||||
m_OnMessage?.Invoke(e.Data);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnClose(object sender, CloseEventArgs e) {
|
|
||||||
AVRealtime.PrintLog(string.Format("Unity websocket closed with {0}, {1}", e.Code, e.Reason));
|
|
||||||
OnClosed?.Invoke(e.Code, e.Reason, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnWebSocketError(object sender, ErrorEventArgs e) {
|
|
||||||
AVRealtime.PrintLog($"PCL websocket error: {e.Message}");
|
|
||||||
ws?.Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnOpen(object sender, EventArgs e) {
|
|
||||||
OnOpened?.Invoke();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Send(string message)
|
|
||||||
{
|
|
||||||
ws.SendAsync(message, (b) =>
|
|
||||||
{
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<bool> Connect(string url, string protocol = null) {
|
|
||||||
var tcs = new TaskCompletionSource<bool>();
|
|
||||||
EventHandler onOpen = null;
|
|
||||||
EventHandler<CloseEventArgs> onClose = null;
|
|
||||||
EventHandler<ErrorEventArgs> onError = null;
|
|
||||||
onOpen = (sender, e) => {
|
|
||||||
AVRealtime.PrintLog("PCL websocket opened");
|
|
||||||
ws.OnOpen -= onOpen;
|
|
||||||
ws.OnClose -= onClose;
|
|
||||||
ws.OnError -= onError;
|
|
||||||
// 注册事件
|
|
||||||
ws.OnMessage += OnWebSokectMessage;
|
|
||||||
ws.OnClose += OnClose;
|
|
||||||
ws.OnError += OnWebSocketError;
|
|
||||||
tcs.SetResult(true);
|
|
||||||
};
|
|
||||||
onClose = (sender, e) => {
|
|
||||||
ws.OnOpen -= onOpen;
|
|
||||||
ws.OnClose -= onClose;
|
|
||||||
ws.OnError -= onError;
|
|
||||||
tcs.SetException(new Exception("连接关闭"));
|
|
||||||
};
|
|
||||||
onError = (sender, e) => {
|
|
||||||
AVRealtime.PrintLog(string.Format("连接错误:{0}", e.Message));
|
|
||||||
ws.OnOpen -= onOpen;
|
|
||||||
ws.OnClose -= onClose;
|
|
||||||
ws.OnError -= onError;
|
|
||||||
try {
|
|
||||||
ws.Close();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
AVRealtime.PrintLog(string.Format("关闭错误的 WebSocket 异常:{0}", ex.Message));
|
|
||||||
} finally {
|
|
||||||
tcs.SetException(new Exception(string.Format("连接错误:{0}", e.Message)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 在每次打开时,重新创建 WebSocket 对象
|
|
||||||
if (!string.IsNullOrEmpty(protocol)) {
|
|
||||||
url = string.Format("{0}?subprotocol={1}", url, protocol);
|
|
||||||
}
|
|
||||||
ws = new WebSocket(url);
|
|
||||||
ws.OnOpen += onOpen;
|
|
||||||
ws.OnClose += onClose;
|
|
||||||
ws.OnError += onError;
|
|
||||||
ws.ConnectAsync();
|
|
||||||
return tcs.Task;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Binary file not shown.
|
@ -1,28 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Audio message.
|
|
||||||
/// </summary>
|
|
||||||
[AVIMMessageClassName("_AVIMAudioMessage")]
|
|
||||||
[AVIMTypedMessageTypeInt(-3)]
|
|
||||||
public class AVIMAudioMessage : AVIMFileMessage
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Video message.
|
|
||||||
/// </summary>
|
|
||||||
[AVIMMessageClassName("_AVIMVideoMessage")]
|
|
||||||
[AVIMTypedMessageTypeInt(-4)]
|
|
||||||
public class AVIMVideoMessage: AVIMFileMessage
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
using System;
|
|
||||||
using LeanCloud.Realtime.Internal;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 基于二进制数据的消息类型,可以直接发送 Byte 数组
|
|
||||||
/// </summary>
|
|
||||||
[AVIMMessageClassName("_AVIMBinaryMessage")]
|
|
||||||
public class AVIMBinaryMessage : AVIMMessage
|
|
||||||
{
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="T:LeanCloud.Realtime.AVIMBinaryMessage"/> class.
|
|
||||||
/// </summary>
|
|
||||||
public AVIMBinaryMessage()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// create new instance of AVIMBinnaryMessage
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="data"></param>
|
|
||||||
public AVIMBinaryMessage(byte[] data)
|
|
||||||
{
|
|
||||||
this.BinaryData = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the binary data.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The binary data.</value>
|
|
||||||
public byte[] BinaryData { get; set; }
|
|
||||||
|
|
||||||
internal override MessageCommand BeforeSend(MessageCommand cmd)
|
|
||||||
{
|
|
||||||
var result = base.BeforeSend(cmd);
|
|
||||||
result = result.Binary(this.BinaryData);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,181 +0,0 @@
|
||||||
using LeanCloud.Realtime.Internal;
|
|
||||||
using LeanCloud.Storage.Internal;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 对话查询类
|
|
||||||
/// </summary>
|
|
||||||
public class AVIMConversationQuery : AVQueryPair<AVIMConversationQuery, AVIMConversation>, IAVQuery
|
|
||||||
{
|
|
||||||
internal AVIMClient CurrentClient { get; set; }
|
|
||||||
internal AVIMConversationQuery(AVIMClient _currentClient)
|
|
||||||
: base()
|
|
||||||
{
|
|
||||||
CurrentClient = _currentClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool compact;
|
|
||||||
bool withLastMessageRefreshed;
|
|
||||||
|
|
||||||
private AVIMConversationQuery(AVIMConversationQuery source,
|
|
||||||
IDictionary<string, object> where = null,
|
|
||||||
IEnumerable<string> replacementOrderBy = null,
|
|
||||||
IEnumerable<string> thenBy = null,
|
|
||||||
int? skip = null,
|
|
||||||
int? limit = null,
|
|
||||||
IEnumerable<string> includes = null,
|
|
||||||
IEnumerable<string> selectedKeys = null,
|
|
||||||
String redirectClassNameForKey = null)
|
|
||||||
: base(source, where, replacementOrderBy, thenBy, skip, limit, includes, selectedKeys, redirectClassNameForKey)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates the instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The instance.</returns>
|
|
||||||
/// <param name="where">Where.</param>
|
|
||||||
/// <param name="replacementOrderBy">Replacement order by.</param>
|
|
||||||
/// <param name="thenBy">Then by.</param>
|
|
||||||
/// <param name="skip">Skip.</param>
|
|
||||||
/// <param name="limit">Limit.</param>
|
|
||||||
/// <param name="includes">Includes.</param>
|
|
||||||
/// <param name="selectedKeys">Selected keys.</param>
|
|
||||||
/// <param name="redirectClassNameForKey">Redirect class name for key.</param>
|
|
||||||
public override AVIMConversationQuery CreateInstance(
|
|
||||||
IDictionary<string, object> where = null,
|
|
||||||
IEnumerable<string> replacementOrderBy = null,
|
|
||||||
IEnumerable<string> thenBy = null,
|
|
||||||
int? skip = null,
|
|
||||||
int? limit = null,
|
|
||||||
IEnumerable<string> includes = null,
|
|
||||||
IEnumerable<string> selectedKeys = null,
|
|
||||||
String redirectClassNameForKey = null)
|
|
||||||
{
|
|
||||||
var rtn = new AVIMConversationQuery(this, where, replacementOrderBy, thenBy, skip, limit, includes);
|
|
||||||
rtn.CurrentClient = this.CurrentClient;
|
|
||||||
rtn.compact = this.compact;
|
|
||||||
rtn.withLastMessageRefreshed = this.withLastMessageRefreshed;
|
|
||||||
return rtn;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Withs the last message refreshed.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The last message refreshed.</returns>
|
|
||||||
/// <param name="enabled">If set to <c>true</c> enabled.</param>
|
|
||||||
public AVIMConversationQuery WithLastMessageRefreshed(bool enabled)
|
|
||||||
{
|
|
||||||
this.withLastMessageRefreshed = enabled;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AVIMConversationQuery Compact(bool enabled)
|
|
||||||
{
|
|
||||||
this.compact = enabled;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
internal ConversationCommand GenerateQueryCommand()
|
|
||||||
{
|
|
||||||
var cmd = new ConversationCommand();
|
|
||||||
|
|
||||||
var queryParameters = this.BuildParameters(false);
|
|
||||||
if (queryParameters != null)
|
|
||||||
{
|
|
||||||
if (queryParameters.Keys.Contains("where"))
|
|
||||||
cmd.Where(queryParameters["where"]);
|
|
||||||
|
|
||||||
if (queryParameters.Keys.Contains("skip"))
|
|
||||||
cmd.Skip(int.Parse(queryParameters["skip"].ToString()));
|
|
||||||
|
|
||||||
if (queryParameters.Keys.Contains("limit"))
|
|
||||||
cmd.Limit(int.Parse(queryParameters["limit"].ToString()));
|
|
||||||
|
|
||||||
if (queryParameters.Keys.Contains("sort"))
|
|
||||||
cmd.Sort(queryParameters["order"].ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<int> CountAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var convCmd = this.GenerateQueryCommand();
|
|
||||||
convCmd.Count();
|
|
||||||
convCmd.Limit(0);
|
|
||||||
var cmd = convCmd.Option("query");
|
|
||||||
return CurrentClient.RunCommandAsync(convCmd).OnSuccess(t =>
|
|
||||||
{
|
|
||||||
var result = t.Result.Item2;
|
|
||||||
|
|
||||||
if (result.ContainsKey("count"))
|
|
||||||
{
|
|
||||||
return int.Parse(result["count"].ToString());
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查找符合条件的对话
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="cancellationToken"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public override Task<IEnumerable<AVIMConversation>> FindAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var convCmd = this.GenerateQueryCommand().Option("query");
|
|
||||||
return CurrentClient.RunCommandAsync(convCmd).OnSuccess(t =>
|
|
||||||
{
|
|
||||||
var result = t.Result.Item2;
|
|
||||||
|
|
||||||
IList<AVIMConversation> rtn = new List<AVIMConversation>();
|
|
||||||
var conList = result["results"] as IList<object>;
|
|
||||||
if (conList != null)
|
|
||||||
{
|
|
||||||
foreach (var c in conList)
|
|
||||||
{
|
|
||||||
var cData = c as IDictionary<string, object>;
|
|
||||||
if (cData != null)
|
|
||||||
{
|
|
||||||
var con = AVIMConversation.CreateWithData(cData, CurrentClient);
|
|
||||||
rtn.Add(con);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rtn.AsEnumerable();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<AVIMConversation> FirstAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return this.FirstOrDefaultAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<AVIMConversation> FirstOrDefaultAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var firstQuery = this.Limit(1);
|
|
||||||
return firstQuery.FindAsync().OnSuccess(t =>
|
|
||||||
{
|
|
||||||
return t.Result.FirstOrDefault();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<AVIMConversation> GetAsync(string objectId, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var idQuery = this.WhereEqualTo("objectId", objectId);
|
|
||||||
return idQuery.FirstAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,248 +0,0 @@
|
||||||
using LeanCloud.Storage.Internal;
|
|
||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using LeanCloud.Realtime.Internal;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime
|
|
||||||
{
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// AVIMM essage pager.
|
|
||||||
/// </summary>
|
|
||||||
public class AVIMMessagePager
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the query.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The query.</value>
|
|
||||||
public AVIMMessageQuery Query { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the size of the page.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The size of the page.</value>
|
|
||||||
public int PageSize
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return Query.Limit;
|
|
||||||
}
|
|
||||||
|
|
||||||
private set
|
|
||||||
{
|
|
||||||
Query.Limit = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the current message identifier flag.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The current message identifier flag.</value>
|
|
||||||
public string CurrentMessageIdFlag
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return Query.StartMessageId;
|
|
||||||
}
|
|
||||||
private set
|
|
||||||
{
|
|
||||||
Query.StartMessageId = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the current date time flag.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The current date time flag.</value>
|
|
||||||
public DateTime CurrentDateTimeFlag
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return Query.From;
|
|
||||||
}
|
|
||||||
private set
|
|
||||||
{
|
|
||||||
Query.From = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal AVIMMessagePager()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="T:LeanCloud.Realtime.AVIMMessagePager"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="conversation">Conversation.</param>
|
|
||||||
public AVIMMessagePager(AVIMConversation conversation)
|
|
||||||
: this()
|
|
||||||
{
|
|
||||||
Query = conversation.GetMessageQuery();
|
|
||||||
PageSize = 20;
|
|
||||||
CurrentDateTimeFlag = DateTime.Now;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the size of the page.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The page size.</returns>
|
|
||||||
/// <param name="pageSize">Page size.</param>
|
|
||||||
public AVIMMessagePager SetPageSize(int pageSize)
|
|
||||||
{
|
|
||||||
PageSize = pageSize;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Previouses the async.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The async.</returns>
|
|
||||||
public Task<IEnumerable<IAVIMMessage>> PreviousAsync()
|
|
||||||
{
|
|
||||||
return Query.FindAsync().OnSuccess(t =>
|
|
||||||
{
|
|
||||||
var headerMessage = t.Result.FirstOrDefault();
|
|
||||||
if (headerMessage != null)
|
|
||||||
{
|
|
||||||
CurrentMessageIdFlag = headerMessage.Id;
|
|
||||||
CurrentDateTimeFlag = headerMessage.ServerTimestamp.ToDateTime();
|
|
||||||
}
|
|
||||||
return t.Result;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// from previous to lastest.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<IEnumerable<IAVIMMessage>> NextAsync()
|
|
||||||
{
|
|
||||||
return Query.ReverseFindAsync().OnSuccess(t =>
|
|
||||||
{
|
|
||||||
var tailMessage = t.Result.LastOrDefault();
|
|
||||||
if (tailMessage != null)
|
|
||||||
{
|
|
||||||
CurrentMessageIdFlag = tailMessage.Id;
|
|
||||||
CurrentDateTimeFlag = tailMessage.ServerTimestamp.ToDateTime();
|
|
||||||
}
|
|
||||||
return t.Result;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// history message interator.
|
|
||||||
/// </summary>
|
|
||||||
public class AVIMMessageQuery
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the convsersation.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The convsersation.</value>
|
|
||||||
public AVIMConversation Convsersation { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the limit.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The limit.</value>
|
|
||||||
public int Limit { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets from.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>From.</value>
|
|
||||||
public DateTime From { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets to.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>To.</value>
|
|
||||||
public DateTime To { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the end message identifier.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The end message identifier.</value>
|
|
||||||
public string EndMessageId { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the start message identifier.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The start message identifier.</value>
|
|
||||||
public string StartMessageId { get; set; }
|
|
||||||
|
|
||||||
|
|
||||||
internal AVIMMessageQuery()
|
|
||||||
{
|
|
||||||
Limit = 20;
|
|
||||||
From = DateTime.Now;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="T:LeanCloud.Realtime.AVIMMessageQuery"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="conversation">Conversation.</param>
|
|
||||||
public AVIMMessageQuery(AVIMConversation conversation)
|
|
||||||
: this()
|
|
||||||
{
|
|
||||||
Convsersation = conversation;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the limit.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The limit.</returns>
|
|
||||||
/// <param name="limit">Limit.</param>
|
|
||||||
public AVIMMessageQuery SetLimit(int limit)
|
|
||||||
{
|
|
||||||
Limit = limit;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// from lastest to previous.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<IEnumerable<IAVIMMessage>> FindAsync()
|
|
||||||
{
|
|
||||||
return FindAsync<IAVIMMessage>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// from lastest to previous.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<IEnumerable<IAVIMMessage>> ReverseFindAsync()
|
|
||||||
{
|
|
||||||
return ReverseFindAsync<IAVIMMessage>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Finds the async.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The async.</returns>
|
|
||||||
/// <param name="reverse">set direction to reverse,it means query direct is from old to new.</param>
|
|
||||||
/// <typeparam name="T">The 1st type parameter.</typeparam>
|
|
||||||
public Task<IEnumerable<T>> FindAsync<T>(bool reverse = false)
|
|
||||||
where T : IAVIMMessage
|
|
||||||
{
|
|
||||||
return Convsersation.QueryMessageAsync<T>(
|
|
||||||
beforeTimeStampPoint: From,
|
|
||||||
afterTimeStampPoint: To,
|
|
||||||
limit: Limit,
|
|
||||||
afterMessageId: EndMessageId,
|
|
||||||
beforeMessageId: StartMessageId,
|
|
||||||
direction: reverse ? 0 : 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// from previous to lastest.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<IEnumerable<T>> ReverseFindAsync<T>()
|
|
||||||
where T : IAVIMMessage
|
|
||||||
{
|
|
||||||
return FindAsync<T>(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,251 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public class AVIMEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
public AVIMEventArgs()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
public AVIMException.ErrorCode ErrorCode { get; internal set; }
|
|
||||||
/// <summary>
|
|
||||||
/// LeanCloud 服务端发往客户端消息通知
|
|
||||||
/// </summary>
|
|
||||||
public string Message { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AVIMDisconnectEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
public int Code { get; private set; }
|
|
||||||
|
|
||||||
public string Reason { get; private set; }
|
|
||||||
|
|
||||||
public string Detail { get; private set; }
|
|
||||||
|
|
||||||
public AVIMDisconnectEventArgs()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
public AVIMDisconnectEventArgs(int _code, string _reason, string _detail)
|
|
||||||
{
|
|
||||||
this.Code = _code;
|
|
||||||
this.Reason = _reason;
|
|
||||||
this.Detail = _detail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 开始重连之后触发正在重连的事件通知,提供给监听者的事件参数
|
|
||||||
/// </summary>
|
|
||||||
public class AVIMReconnectingEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 是否由 SDK 内部机制启动的自动重连
|
|
||||||
/// </summary>
|
|
||||||
public bool IsAuto { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 重连的 client Id
|
|
||||||
/// </summary>
|
|
||||||
public string ClientId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 重连时使用的 SessionToken
|
|
||||||
/// </summary>
|
|
||||||
public string SessionToken { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 重连成功之后的事件回调
|
|
||||||
/// </summary>
|
|
||||||
public class AVIMReconnectedEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 是否由 SDK 内部机制启动的自动重连
|
|
||||||
/// </summary>
|
|
||||||
public bool IsAuto { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 重连的 client Id
|
|
||||||
/// </summary>
|
|
||||||
public string ClientId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 重连时使用的 SessionToken
|
|
||||||
/// </summary>
|
|
||||||
public string SessionToken { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 重连失败之后的事件回调参数
|
|
||||||
/// </summary>
|
|
||||||
public class AVIMReconnectFailedArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 是否由 SDK 内部机制启动的自动重连
|
|
||||||
/// </summary>
|
|
||||||
public bool IsAuto { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 重连的 client Id
|
|
||||||
/// </summary>
|
|
||||||
public string ClientId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 重连时使用的 SessionToken
|
|
||||||
/// </summary>
|
|
||||||
public string SessionToken { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 失败的原因
|
|
||||||
/// 0. 客户端网络断开
|
|
||||||
/// 1. sessionToken 错误或者失效,需要重新创建 client
|
|
||||||
/// </summary>
|
|
||||||
public int FailedCode { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// AVIMM essage event arguments.
|
|
||||||
/// </summary>
|
|
||||||
public class AVIMMessageEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="T:LeanCloud.Realtime.AVIMMessageEventArgs"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="iMessage">I message.</param>
|
|
||||||
public AVIMMessageEventArgs(IAVIMMessage iMessage)
|
|
||||||
{
|
|
||||||
Message = iMessage;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the message.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The message.</value>
|
|
||||||
public IAVIMMessage Message { get; internal set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// AVIMMessage event arguments.
|
|
||||||
/// </summary>
|
|
||||||
public class AVIMMessagePatchEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
public AVIMMessagePatchEventArgs(IAVIMMessage message)
|
|
||||||
{
|
|
||||||
Message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the message.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The message.</value>
|
|
||||||
public IAVIMMessage Message { get; internal set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// AVIMT ext message event arguments.
|
|
||||||
/// </summary>
|
|
||||||
public class AVIMTextMessageEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="T:LeanCloud.Realtime.AVIMTextMessageEventArgs"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="raw">Raw.</param>
|
|
||||||
public AVIMTextMessageEventArgs(AVIMTextMessage raw)
|
|
||||||
{
|
|
||||||
TextMessage = raw;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the text message.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The text message.</value>
|
|
||||||
public AVIMTextMessage TextMessage { get; internal set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 当对话中有人加入时,触发 <seealso cref="AVIMMembersJoinListener.OnMembersJoined"/> 时所携带的事件参数
|
|
||||||
/// </summary>
|
|
||||||
public class AVIMOnMembersJoinedEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 加入到对话的 Client Id(s)
|
|
||||||
/// </summary>
|
|
||||||
public IEnumerable<string> JoinedMembers { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 邀请的操作人
|
|
||||||
/// </summary>
|
|
||||||
public string InvitedBy { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 此次操作针对的对话 Id
|
|
||||||
/// </summary>
|
|
||||||
public string ConversationId { get; internal set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 当对话中有人加入时,触发 AVIMMembersJoinListener<seealso cref="AVIMMembersLeftListener.OnMembersLeft"/> 时所携带的事件参数
|
|
||||||
/// </summary>
|
|
||||||
public class AVIMOnMembersLeftEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 离开对话的 Client Id(s)
|
|
||||||
/// </summary>
|
|
||||||
public IEnumerable<string> LeftMembers { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 踢出的操作人
|
|
||||||
/// </summary>
|
|
||||||
public string KickedBy { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 此次操作针对的对话 Id
|
|
||||||
/// </summary>
|
|
||||||
public string ConversationId { get; internal set; }
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// 当前用户被邀请加入到对话
|
|
||||||
/// </summary>
|
|
||||||
public class AVIMOnInvitedEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 邀请的操作人
|
|
||||||
/// </summary>
|
|
||||||
public string InvitedBy { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 此次操作针对的对话 Id
|
|
||||||
/// </summary>
|
|
||||||
public string ConversationId { get; internal set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 当前用户被他人从对话中踢出
|
|
||||||
/// </summary>
|
|
||||||
public class AVIMOnKickedEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 踢出的操作人
|
|
||||||
/// </summary>
|
|
||||||
public string KickedBy { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 此次操作针对的对话 Id
|
|
||||||
/// </summary>
|
|
||||||
public string ConversationId { get; internal set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AVIMSessionClosedEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
public int Code { get; internal set; }
|
|
||||||
|
|
||||||
public string Reason { get; internal set; }
|
|
||||||
|
|
||||||
public string Detail { get; internal set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,235 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 实时通信的异常
|
|
||||||
/// </summary>
|
|
||||||
public class AVIMException : Exception
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 错误代码
|
|
||||||
/// </summary>
|
|
||||||
public enum ErrorCode
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Error code indicating that an unknown error or an error unrelated to LeanCloud
|
|
||||||
/// occurred.
|
|
||||||
/// </summary>
|
|
||||||
OtherCause = -1,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 服务端错误
|
|
||||||
/// </summary>
|
|
||||||
FromServer = 4000,
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// websocket 连接非正常关闭,通常见于路由器配置对长连接限制的情况。SDK 会自动重连,无需人工干预。
|
|
||||||
/// </summary>
|
|
||||||
UnknownError = 1006,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 应用不存在或应用禁用了实时通信服务
|
|
||||||
/// </summary>
|
|
||||||
APP_NOT_AVAILABLE = 4100,
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 登录签名验证失败
|
|
||||||
/// </summary>
|
|
||||||
SIGNATURE_FAILED = 4102,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Client ClientId 格式错误,超过 64 个字符。
|
|
||||||
/// </summary>
|
|
||||||
INVALID_LOGIN = 4103,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Session 没有打开就发送消息,或执行其他操作。常见的错误场景是调用 open session 后直接发送消息,正确的用法是在 Session 打开的回调里执行。
|
|
||||||
/// </summary>
|
|
||||||
SESSION_REQUIRED = 4105,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 读超时,服务器端长时间没有收到客户端的数据,切断连接。SDK 包装了心跳包的机制,出现此错误通常是网络问题。SDK 会自动重连,无需人工干预。
|
|
||||||
/// </summary>
|
|
||||||
READ_TIMEOUT = 4107,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 登录超时,连接后长时间没有完成 session open。通常是登录被拒绝等原因,出现此问题可能是使用方式有误,可以 创建工单,由我们技术顾问来给出建议。
|
|
||||||
/// </summary>
|
|
||||||
LOGIN_TIMEOUT = 4108,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 包过长。消息大小超过 5 KB,请缩短消息或者拆分消息。
|
|
||||||
/// </summary>
|
|
||||||
FRAME_TOO_LONG = 4109,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置安全域名后,当前登录的域名与安全域名不符合。
|
|
||||||
/// </summary>
|
|
||||||
INVALID_ORIGIN = 4110,
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置单设备登录后,客户端被其他设备挤下线。
|
|
||||||
/// </summary>
|
|
||||||
SESSION_CONFLICT = 4111,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 应用容量超限,当天登录用户数已经超过应用设定的最大值。
|
|
||||||
/// </summary>
|
|
||||||
APP_QUOTA_EXCEEDED = 4113,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 客户端发送的序列化数据服务器端无法解析。
|
|
||||||
/// </summary>
|
|
||||||
UNPARSEABLE_RAW_MESSAGE = 4114,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 客户端被 REST API 管理接口强制下线。
|
|
||||||
/// </summary>
|
|
||||||
KICKED_BY_APP = 4115,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 应用单位时间内发送消息量超过限制,消息被拒绝。
|
|
||||||
/// </summary>
|
|
||||||
MESSAGE_SENT_QUOTA_EXCEEDED = 4116,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 服务器内部错误,如果反复出现请收集相关线索并 创建工单,我们会尽快解决。
|
|
||||||
/// </summary>
|
|
||||||
INTERNAL_ERROR = 4200,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 通过 API 发送消息超时
|
|
||||||
/// </summary>
|
|
||||||
SEND_MESSAGE_TIMEOUT = 4201,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 上游 API 调用异常,请查看报错信息了解错误详情
|
|
||||||
/// </summary>
|
|
||||||
CONVERSATION_API_FAILED = 4301,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 对话相关操作签名错误
|
|
||||||
/// </summary>
|
|
||||||
CONVERSATION_SIGNATURE_FAILED = 4302,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 发送消息,或邀请等操作对应的对话不存在。
|
|
||||||
/// </summary>
|
|
||||||
CONVERSATION_NOT_FOUND = 4303,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 对话成员已满,不能再添加。
|
|
||||||
/// </summary>
|
|
||||||
CONVERSATION_FULL = 4304,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 对话操作被应用的云引擎 Hook 拒绝
|
|
||||||
/// </summary>
|
|
||||||
CONVERSATION_REJECTED_BY_APP = 4305,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 更新对话操作失败
|
|
||||||
/// </summary>
|
|
||||||
CONVERSATION_UPDATE_FAILED = 4306,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 该对话为只读,不能更新或增删成员。
|
|
||||||
/// </summary>
|
|
||||||
CONVERSATION_READ_ONLY = 4307,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 该对话禁止当前用户发送消息
|
|
||||||
/// </summary>
|
|
||||||
CONVERSATION_NOT_ALLOWED = 4308,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 更新对话的请求被拒绝,当前用户不在对话中
|
|
||||||
/// </summary>
|
|
||||||
CONVERSATION_UPDATE_REJECT = 4309,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查询对话失败,常见于慢查询导致的超时或受其他慢查询导致的数据库响应慢
|
|
||||||
/// </summary>
|
|
||||||
CONVERSATION_QUERY_FAILED = 4310,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 拉取对话消息记录失败,常见与超时的情况
|
|
||||||
/// </summary>
|
|
||||||
CONVERSATION_LOG_FAILED = 4311,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 拉去对话消息记录被拒绝,当前用户不再对话中
|
|
||||||
/// </summary>
|
|
||||||
CONVERSATION_LOG_REJECT = 4312,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 该功能仅对系统对话有效
|
|
||||||
/// </summary>
|
|
||||||
SYSTEM_CONVERSATION_REQUIRED = 4313,
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 该功能仅对普通对话有效。
|
|
||||||
/// </summary>
|
|
||||||
NORMAL_CONVERSATION_REQUIRED = 4314,
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 当前用户被加入此对话的黑名单,无法发送消息。
|
|
||||||
/// </summary>
|
|
||||||
CONVERSATION_BLACKLISTED = 4315,
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 该功能仅对暂态对话有效。
|
|
||||||
/// </summary>
|
|
||||||
TRANSIENT_CONVERSATION_REQUIRED = 4316,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 发送消息的对话不存在,或当前用户不在对话中
|
|
||||||
/// </summary>
|
|
||||||
INVALID_MESSAGING_TARGET = 4401,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 发送的消息被应用的云引擎 Hook 拒绝
|
|
||||||
/// </summary>
|
|
||||||
MESSAGE_REJECTED_BY_APP = 4402,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 客户端无法通过 WebSocket 发送数据包
|
|
||||||
/// </summary>
|
|
||||||
CAN_NOT_EXCUTE_COMMAND = 1002,
|
|
||||||
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// 用户云代码返回的错误码
|
|
||||||
/// </summary>
|
|
||||||
public int AppCode { get; private set; }
|
|
||||||
|
|
||||||
|
|
||||||
internal AVIMException(ErrorCode code, string message, Exception cause = null)
|
|
||||||
: base(message, cause)
|
|
||||||
{
|
|
||||||
this.Code = code;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal AVIMException(int code, int appCode, string message, Exception cause = null)
|
|
||||||
: this((ErrorCode)code, message, cause)
|
|
||||||
{
|
|
||||||
this.AppCode = appCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The LeanCloud error code associated with the exception.
|
|
||||||
/// </summary>
|
|
||||||
public ErrorCode Code { get; private set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,246 +0,0 @@
|
||||||
using LeanCloud;
|
|
||||||
using LeanCloud.Storage.Internal;
|
|
||||||
using LeanCloud.Realtime.Internal;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 图像消息
|
|
||||||
/// </summary>
|
|
||||||
[AVIMMessageClassName("_AVIMImageMessage")]
|
|
||||||
[AVIMTypedMessageTypeInt(-2)]
|
|
||||||
public class AVIMImageMessage : AVIMFileMessage
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// File message.
|
|
||||||
/// </summary>
|
|
||||||
[AVIMMessageClassName("_AVIMFileMessage")]
|
|
||||||
[AVIMTypedMessageTypeInt(-6)]
|
|
||||||
public class AVIMFileMessage : AVIMMessageDecorator
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="T:LeanCloud.Realtime.AVIMFileMessage"/> class.
|
|
||||||
/// </summary>
|
|
||||||
public AVIMFileMessage()
|
|
||||||
: base(new AVIMTypedMessage())
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="T:LeanCloud.Realtime.AVIMFileMessage"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="message">Message.</param>
|
|
||||||
public AVIMFileMessage(AVIMTypedMessage message)
|
|
||||||
: base(message)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the file.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The file.</value>
|
|
||||||
public AVFile File { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Froms the URL.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The URL.</returns>
|
|
||||||
/// <param name="url">URL.</param>
|
|
||||||
/// <typeparam name="T">The 1st type parameter.</typeparam>
|
|
||||||
public static T FromUrl<T>(string url) where T : AVIMFileMessage, new()
|
|
||||||
{
|
|
||||||
return FromUrl<T>(string.Empty.Random(8), url, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// From the URL.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The URL.</returns>
|
|
||||||
/// <param name="fileName">File name.</param>
|
|
||||||
/// <param name="externalUrl">External URL.</param>
|
|
||||||
/// <param name="textTitle">Text title.</param>
|
|
||||||
/// <param name="customAttributes">Custom attributes.</param>
|
|
||||||
/// <typeparam name="T">The 1st type parameter.</typeparam>
|
|
||||||
public static T FromUrl<T>(string fileName, string externalUrl, string textTitle, IDictionary<string, object> customAttributes = null) where T : AVIMFileMessage, new()
|
|
||||||
{
|
|
||||||
T message = new T();
|
|
||||||
message.File = new AVFile(fileName, externalUrl);
|
|
||||||
message.TextContent = textTitle;
|
|
||||||
message.MergeCustomAttributes(customAttributes);
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// From the stream.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The stream.</returns>
|
|
||||||
/// <param name="fileName">File name.</param>
|
|
||||||
/// <param name="data">Data.</param>
|
|
||||||
/// <param name="mimeType">MIME type.</param>
|
|
||||||
/// <param name="textTitle">Text title.</param>
|
|
||||||
/// <param name="metaData">Meta data.</param>
|
|
||||||
/// <param name="customAttributes">Custom attributes.</param>
|
|
||||||
/// <typeparam name="T">The 1st type parameter.</typeparam>
|
|
||||||
public static T FromStream<T>(string fileName, Stream data, string mimeType, string textTitle, IDictionary<string, object> metaData, IDictionary<string, object> customAttributes = null) where T : AVIMFileMessage, new()
|
|
||||||
{
|
|
||||||
T message = new T();
|
|
||||||
message.File = new AVFile(fileName, data, mimeType, metaData);
|
|
||||||
message.TextContent = textTitle;
|
|
||||||
message.MergeCustomAttributes(customAttributes);
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Encodes the decorator.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The decorator.</returns>
|
|
||||||
public override IDictionary<string, object> EncodeDecorator()
|
|
||||||
{
|
|
||||||
if (File.Url == null) throw new InvalidOperationException("File.Url can not be null before it can be sent.");
|
|
||||||
File.MetaData["name"] = File.Name;
|
|
||||||
File.MetaData["format"] = File.MimeType;
|
|
||||||
var fileData = new Dictionary<string, object>
|
|
||||||
{
|
|
||||||
{ "url", File.Url.ToString()},
|
|
||||||
{ "objId", File.ObjectId },
|
|
||||||
{ "metaData", File.MetaData }
|
|
||||||
};
|
|
||||||
|
|
||||||
return new Dictionary<string, object>
|
|
||||||
{
|
|
||||||
{ AVIMProtocol.LCFILE, fileData }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Deserialize the specified msgStr.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The deserialize.</returns>
|
|
||||||
/// <param name="msgStr">Message string.</param>
|
|
||||||
public override IAVIMMessage Deserialize(string msgStr)
|
|
||||||
{
|
|
||||||
var msg = Json.Parse(msgStr) as IDictionary<string, object>;
|
|
||||||
var fileData = msg[AVIMProtocol.LCFILE] as IDictionary<string, object>;
|
|
||||||
string mimeType = null;
|
|
||||||
string url = null;
|
|
||||||
string name = null;
|
|
||||||
string objId = null;
|
|
||||||
IDictionary<string, object> metaData = null;
|
|
||||||
if (fileData != null)
|
|
||||||
{
|
|
||||||
object metaDataObj = null;
|
|
||||||
|
|
||||||
if (fileData.TryGetValue("metaData", out metaDataObj))
|
|
||||||
{
|
|
||||||
metaData = metaDataObj as IDictionary<string, object>;
|
|
||||||
object fileNameObj = null;
|
|
||||||
if (metaData != null)
|
|
||||||
{
|
|
||||||
if (metaData.TryGetValue("name", out fileNameObj))
|
|
||||||
{
|
|
||||||
name = fileNameObj.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
object mimeTypeObj = null;
|
|
||||||
if (metaData != null)
|
|
||||||
{
|
|
||||||
if (metaData.TryGetValue("format", out mimeTypeObj))
|
|
||||||
{
|
|
||||||
if (mimeTypeObj != null)
|
|
||||||
mimeType = mimeTypeObj.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object objIdObj = null;
|
|
||||||
if (fileData.TryGetValue("objId", out objIdObj))
|
|
||||||
{
|
|
||||||
if (objIdObj != null)
|
|
||||||
objId = objIdObj.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
object urlObj = null;
|
|
||||||
if (fileData.TryGetValue("url", out urlObj))
|
|
||||||
{
|
|
||||||
url = urlObj.ToString();
|
|
||||||
}
|
|
||||||
File = AVFile.CreateWithData(objId, name, url, metaData);
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.Deserialize(msgStr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Location message.
|
|
||||||
/// </summary>
|
|
||||||
[AVIMMessageClassName("_AVIMMessageClassName")]
|
|
||||||
[AVIMTypedMessageTypeInt(-5)]
|
|
||||||
public class AVIMLocationMessage : AVIMMessageDecorator
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="T:LeanCloud.Realtime.AVIMLocationMessage"/> class.
|
|
||||||
/// </summary>
|
|
||||||
public AVIMLocationMessage()
|
|
||||||
: base(new AVIMTypedMessage())
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the location.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The location.</value>
|
|
||||||
public AVGeoPoint Location { get; set; }
|
|
||||||
|
|
||||||
public AVIMLocationMessage(AVGeoPoint location)
|
|
||||||
: this()
|
|
||||||
{
|
|
||||||
Location = location;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Encodes the decorator.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The decorator.</returns>
|
|
||||||
public override IDictionary<string, object> EncodeDecorator()
|
|
||||||
{
|
|
||||||
var locationData = new Dictionary<string, object>
|
|
||||||
{
|
|
||||||
{ "longitude", Location.Longitude},
|
|
||||||
{ "latitude", Location.Latitude }
|
|
||||||
};
|
|
||||||
return new Dictionary<string, object>
|
|
||||||
{
|
|
||||||
{ AVIMProtocol.LCLOC, locationData }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Deserialize the specified msgStr.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The deserialize.</returns>
|
|
||||||
/// <param name="msgStr">Message string.</param>
|
|
||||||
public override IAVIMMessage Deserialize(string msgStr)
|
|
||||||
{
|
|
||||||
var msg = Json.Parse(msgStr) as IDictionary<string, object>;
|
|
||||||
var locationData = msg[AVIMProtocol.LCLOC] as IDictionary<string, object>;
|
|
||||||
if (locationData != null)
|
|
||||||
{
|
|
||||||
Location = new AVGeoPoint(double.Parse(locationData["latitude"].ToString()), double.Parse(locationData["longitude"].ToString()));
|
|
||||||
}
|
|
||||||
return base.Deserialize(msgStr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,162 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using LeanCloud;
|
|
||||||
using System.Reflection;
|
|
||||||
using LeanCloud.Storage.Internal;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Collections;
|
|
||||||
using LeanCloud.Realtime.Internal;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 实时消息的核心基类,它是 Json schema 消息的父类
|
|
||||||
/// </summary>
|
|
||||||
[AVIMMessageClassName("_AVIMMessage")]
|
|
||||||
public class AVIMMessage : IAVIMMessage
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 默认的构造函数
|
|
||||||
/// </summary>
|
|
||||||
public AVIMMessage()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
internal readonly object mutex = new object();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 对话的Id
|
|
||||||
/// </summary>
|
|
||||||
public string ConversationId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 发送消息的 ClientId
|
|
||||||
/// </summary>
|
|
||||||
public string FromClientId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 消息在全局的唯一标识Id
|
|
||||||
/// </summary>
|
|
||||||
public string Id { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 服务器端的时间戳
|
|
||||||
/// </summary>
|
|
||||||
public long ServerTimestamp { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the content.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The content.</value>
|
|
||||||
public string Content { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 对方收到消息的时间戳,如果是多人聊天,那以最早收到消息的人回发的 ACK 为准
|
|
||||||
/// </summary>
|
|
||||||
public long RcpTimestamp { get; set; }
|
|
||||||
|
|
||||||
public long UpdatedAt { get; set; }
|
|
||||||
|
|
||||||
internal string cmdId { get; set; }
|
|
||||||
|
|
||||||
#region
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether this <see cref="T:LeanCloud.Realtime.IAVIMMessage"/> mention all.
|
|
||||||
/// </summary>
|
|
||||||
/// <value><c>true</c> if mention all; otherwise, <c>false</c>.</value>
|
|
||||||
public bool MentionAll { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the mention list.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The mention list.</value>
|
|
||||||
public IEnumerable<string> MentionList { get; set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region register convertor for custom typed message
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Serialize this message.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The serialize.</returns>
|
|
||||||
public virtual string Serialize()
|
|
||||||
{
|
|
||||||
return Content;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Validate the specified msgStr.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The validate.</returns>
|
|
||||||
/// <param name="msgStr">Message string.</param>
|
|
||||||
public virtual bool Validate(string msgStr)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Deserialize the specified msgStr to message subclass instance
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The deserialize.</returns>
|
|
||||||
/// <param name="msgStr">Message string.</param>
|
|
||||||
public virtual IAVIMMessage Deserialize(string msgStr)
|
|
||||||
{
|
|
||||||
Content = msgStr;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal virtual MessageCommand BeforeSend(MessageCommand cmd)
|
|
||||||
{
|
|
||||||
return cmd;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static IAVIMMessage CopyMetaData(IAVIMMessage srcMsg, IAVIMMessage desMsg) {
|
|
||||||
if (srcMsg == null)
|
|
||||||
return desMsg;
|
|
||||||
|
|
||||||
desMsg.ConversationId = srcMsg.ConversationId;
|
|
||||||
desMsg.FromClientId = srcMsg.FromClientId;
|
|
||||||
desMsg.Id = srcMsg.Id;
|
|
||||||
desMsg.ServerTimestamp = srcMsg.ServerTimestamp;
|
|
||||||
desMsg.RcpTimestamp = srcMsg.RcpTimestamp;
|
|
||||||
desMsg.UpdatedAt = srcMsg.UpdatedAt;
|
|
||||||
return desMsg;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 消息的发送选项
|
|
||||||
/// </summary>
|
|
||||||
public struct AVIMSendOptions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 是否需要送达回执
|
|
||||||
/// </summary>
|
|
||||||
public bool Receipt;
|
|
||||||
/// <summary>
|
|
||||||
/// 是否是暂态消息,暂态消息不返回送达回执(ack),不保留离线消息,不触发离线推送
|
|
||||||
/// </summary>
|
|
||||||
public bool Transient;
|
|
||||||
/// <summary>
|
|
||||||
/// 消息的优先级,默认是1,可选值还有 2|3
|
|
||||||
/// </summary>
|
|
||||||
public int Priority;
|
|
||||||
/// <summary>
|
|
||||||
/// 是否为 Will 类型的消息,这条消息会被缓存在服务端,一旦当前客户端下线,这条消息会被发送到对话内的其他成员
|
|
||||||
/// </summary>
|
|
||||||
public bool Will;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 如果消息的接收者已经下线了,这个字段的内容就会被离线推送到接收者
|
|
||||||
///<remarks>例如,一张图片消息的离线消息内容可以类似于:[您收到一条图片消息,点击查看] 这样的推送内容,参照微信的做法</remarks>
|
|
||||||
/// </summary>
|
|
||||||
public IDictionary<string, object> PushData;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime
|
|
||||||
{
|
|
||||||
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
|
|
||||||
public sealed class AVIMMessageClassNameAttribute: Attribute
|
|
||||||
{
|
|
||||||
public AVIMMessageClassNameAttribute(string className)
|
|
||||||
{
|
|
||||||
this.ClassName = className;
|
|
||||||
}
|
|
||||||
public string ClassName { get; private set; }
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime
|
|
||||||
{
|
|
||||||
[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
|
|
||||||
public sealed class AVIMMessageFieldNameAttribute: Attribute
|
|
||||||
{
|
|
||||||
public AVIMMessageFieldNameAttribute(string fieldName)
|
|
||||||
{
|
|
||||||
FieldName = fieldName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string FieldName { get; private set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,143 +0,0 @@
|
||||||
using LeanCloud.Realtime.Internal;
|
|
||||||
using LeanCloud.Storage.Internal;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 默认的消息监听器,它主要承担的指责是回执的发送与用户自定义的监听器不冲突
|
|
||||||
/// </summary>
|
|
||||||
public class AVIMMessageListener : IAVIMListener
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 默认的 AVIMMessageListener 只会监听 direct 协议,但是并不会触发针对消息类型的判断的监听器
|
|
||||||
/// </summary>
|
|
||||||
public AVIMMessageListener()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Protocols the hook.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns><c>true</c>, if hook was protocoled, <c>false</c> otherwise.</returns>
|
|
||||||
/// <param name="notice">Notice.</param>
|
|
||||||
public virtual bool ProtocolHook(AVIMNotice notice)
|
|
||||||
{
|
|
||||||
if (notice.CommandName != "direct") return false;
|
|
||||||
if (notice.RawData.ContainsKey("offline")) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private EventHandler<AVIMMessageEventArgs> m_OnMessageReceived;
|
|
||||||
/// <summary>
|
|
||||||
/// 接收到聊天消息的事件通知
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<AVIMMessageEventArgs> OnMessageReceived
|
|
||||||
{
|
|
||||||
add
|
|
||||||
{
|
|
||||||
m_OnMessageReceived += value;
|
|
||||||
}
|
|
||||||
remove
|
|
||||||
{
|
|
||||||
m_OnMessageReceived -= value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
internal virtual void OnMessage(AVIMNotice notice)
|
|
||||||
{
|
|
||||||
if (m_OnMessageReceived != null)
|
|
||||||
{
|
|
||||||
var msgStr = notice.RawData["msg"].ToString();
|
|
||||||
var iMessage = AVRealtime.FreeStyleMessageClassingController.Instantiate(msgStr, notice.RawData);
|
|
||||||
//var messageNotice = new AVIMMessageNotice(notice.RawData);
|
|
||||||
//var messaegObj = AVIMMessage.Create(messageNotice);
|
|
||||||
var args = new AVIMMessageEventArgs(iMessage);
|
|
||||||
m_OnMessageReceived.Invoke(this, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Ons the notice received.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="notice">Notice.</param>
|
|
||||||
public virtual void OnNoticeReceived(AVIMNotice notice)
|
|
||||||
{
|
|
||||||
this.OnMessage(notice);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 文本消息监听器
|
|
||||||
/// </summary>
|
|
||||||
public class AVIMTextMessageListener : IAVIMListener
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 构建默认的文本消息监听器
|
|
||||||
/// </summary>
|
|
||||||
public AVIMTextMessageListener()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 构建文本消息监听者
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="textMessageReceived"></param>
|
|
||||||
public AVIMTextMessageListener(Action<AVIMTextMessage> textMessageReceived)
|
|
||||||
{
|
|
||||||
OnTextMessageReceived += (sender, textMessage) =>
|
|
||||||
{
|
|
||||||
textMessageReceived(textMessage.TextMessage);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private EventHandler<AVIMTextMessageEventArgs> m_OnTextMessageReceived;
|
|
||||||
public event EventHandler<AVIMTextMessageEventArgs> OnTextMessageReceived
|
|
||||||
{
|
|
||||||
add
|
|
||||||
{
|
|
||||||
m_OnTextMessageReceived += value;
|
|
||||||
}
|
|
||||||
remove
|
|
||||||
{
|
|
||||||
m_OnTextMessageReceived -= value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual bool ProtocolHook(AVIMNotice notice)
|
|
||||||
{
|
|
||||||
if (notice.CommandName != "direct") return false;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var msg = Json.Parse(notice.RawData["msg"].ToString()) as IDictionary<string, object>;
|
|
||||||
if (!msg.Keys.Contains(AVIMProtocol.LCTYPE)) return false;
|
|
||||||
var typInt = 0;
|
|
||||||
int.TryParse(msg[AVIMProtocol.LCTYPE].ToString(), out typInt);
|
|
||||||
if (typInt != -1) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch(ArgumentException)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void OnNoticeReceived(AVIMNotice notice)
|
|
||||||
{
|
|
||||||
if (m_OnTextMessageReceived != null)
|
|
||||||
{
|
|
||||||
var textMessage = new AVIMTextMessage();
|
|
||||||
textMessage.Deserialize(notice.RawData["msg"].ToString());
|
|
||||||
m_OnTextMessageReceived(this, new AVIMTextMessageEventArgs(textMessage));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
using LeanCloud;
|
|
||||||
using LeanCloud.Realtime.Internal;
|
|
||||||
using LeanCloud.Storage.Internal;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public interface IAVIMNotice
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="estimatedData"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
AVIMNotice Restore(IDictionary<string, object> estimatedData);
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// 从服务端接受到的通知
|
|
||||||
/// <para>通知泛指消息,对话信息变更(例如加人和被踢等),服务器的 ACK,消息回执等</para>
|
|
||||||
/// </summary>
|
|
||||||
public class AVIMNotice : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="T:LeanCloud.Realtime.AVIMNotice"/> class.
|
|
||||||
/// </summary>
|
|
||||||
public AVIMNotice()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The name of the command.
|
|
||||||
/// </summary>
|
|
||||||
public readonly string CommandName;
|
|
||||||
public readonly IDictionary<string, object> RawData;
|
|
||||||
public AVIMNotice(IDictionary<string, object> estimatedData)
|
|
||||||
{
|
|
||||||
this.RawData = estimatedData;
|
|
||||||
this.CommandName = estimatedData["cmd"].ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsValidLeanCloudProtocol(IDictionary<string, object> estimatedData)
|
|
||||||
{
|
|
||||||
if (estimatedData == null) return false;
|
|
||||||
if (estimatedData.Count == 0) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime {
|
|
||||||
/// <summary>
|
|
||||||
/// 撤回消息
|
|
||||||
/// </summary>
|
|
||||||
[AVIMMessageClassName("_AVIMRecalledMessagee")]
|
|
||||||
[AVIMTypedMessageTypeInt(-127)]
|
|
||||||
public class AVIMRecalledMessage : AVIMTypedMessage {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 签名
|
|
||||||
/// </summary>
|
|
||||||
public class AVIMSignature
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 经过 SHA1 以及相关操作参数计算出来的加密字符串
|
|
||||||
/// </summary>
|
|
||||||
public string SignatureContent { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 服务端时间戳
|
|
||||||
/// </summary>
|
|
||||||
public long Timestamp { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 随机字符串
|
|
||||||
/// </summary>
|
|
||||||
public string Nonce { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 构造一个签名
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="s"></param>
|
|
||||||
/// <param name="t"></param>
|
|
||||||
/// <param name="n"></param>
|
|
||||||
public AVIMSignature(string s,long t,string n)
|
|
||||||
{
|
|
||||||
this.Nonce = n;
|
|
||||||
this.SignatureContent = s;
|
|
||||||
this.Timestamp = t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Temporary conversation.
|
|
||||||
/// </summary>
|
|
||||||
public class AVIMTemporaryConversation : AVIMConversation
|
|
||||||
{
|
|
||||||
public DateTime ExpiredAt
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (expiredAt == null)
|
|
||||||
return DateTime.Now.AddDays(1);
|
|
||||||
return expiredAt.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
set
|
|
||||||
{
|
|
||||||
expiredAt = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal AVIMTemporaryConversation(long ttl)
|
|
||||||
: base(isTemporary: true)
|
|
||||||
{
|
|
||||||
this.expiredAt = DateTime.Now.AddDays(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
using LeanCloud.Realtime.Internal;
|
|
||||||
using LeanCloud.Storage.Internal;
|
|
||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 纯文本信息
|
|
||||||
/// </summary>
|
|
||||||
[AVIMMessageClassName("_AVIMTextMessage")]
|
|
||||||
[AVIMTypedMessageTypeInt(-1)]
|
|
||||||
public class AVIMTextMessage : AVIMTypedMessage
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 构建一个文本信息 <see cref="AVIMTextMessage"/> class.
|
|
||||||
/// </summary>
|
|
||||||
public AVIMTextMessage()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 文本类型标记
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("LCType is deprecated, please use AVIMTypedMessageTypeInt instead.")]
|
|
||||||
[AVIMMessageFieldName("_lctype")]
|
|
||||||
public int LCType
|
|
||||||
{
|
|
||||||
get; set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 构造一个纯文本信息
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="textContent"></param>
|
|
||||||
public AVIMTextMessage(string textContent)
|
|
||||||
: this()
|
|
||||||
{
|
|
||||||
TextContent = textContent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,205 +0,0 @@
|
||||||
using LeanCloud.Storage.Internal;
|
|
||||||
using LeanCloud.Realtime.Internal;
|
|
||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
[AVIMMessageClassName("_AVIMTypedMessage")]
|
|
||||||
[AVIMTypedMessageTypeInt(0)]
|
|
||||||
public class AVIMTypedMessage : AVIMMessage, IEnumerable<KeyValuePair<string, object>>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="T:LeanCloud.Realtime.AVIMTypedMessage"/> class.
|
|
||||||
/// </summary>
|
|
||||||
public AVIMTypedMessage()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 文本内容
|
|
||||||
/// </summary>
|
|
||||||
[AVIMMessageFieldName("_lctext")]
|
|
||||||
public string TextContent
|
|
||||||
{
|
|
||||||
get; set;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IDictionary<string, object> estimatedData = new Dictionary<string, object>();
|
|
||||||
/// <summary>
|
|
||||||
/// Serialize this instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The serialize.</returns>
|
|
||||||
public override string Serialize()
|
|
||||||
{
|
|
||||||
var result = Encode();
|
|
||||||
var resultStr = Json.Encode(result);
|
|
||||||
this.Content = resultStr;
|
|
||||||
return resultStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Encode this instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The encode.</returns>
|
|
||||||
public virtual IDictionary<string, object> Encode()
|
|
||||||
{
|
|
||||||
var result = AVRealtime.FreeStyleMessageClassingController.EncodeProperties(this);
|
|
||||||
var encodedAttrs = PointerOrLocalIdEncoder.Instance.Encode(estimatedData);
|
|
||||||
result[AVIMProtocol.LCATTRS] = estimatedData;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Validate the specified msgStr.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The validate.</returns>
|
|
||||||
/// <param name="msgStr">Message string.</param>
|
|
||||||
public override bool Validate(string msgStr)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var msg = Json.Parse(msgStr) as IDictionary<string, object>;
|
|
||||||
return msg.ContainsKey(AVIMProtocol.LCTYPE);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Deserialize the specified msgStr.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The deserialize.</returns>
|
|
||||||
/// <param name="msgStr">Message string.</param>
|
|
||||||
public override IAVIMMessage Deserialize(string msgStr)
|
|
||||||
{
|
|
||||||
var msg = Json.Parse(msgStr) as IDictionary<string, object>;
|
|
||||||
var className = AVRealtime.FreeStyleMessageClassingController.GetClassName(this.GetType());
|
|
||||||
var PropertyMappings = AVRealtime.FreeStyleMessageClassingController.GetPropertyMappings(className);
|
|
||||||
var messageFieldProperties = PropertyMappings.Where(prop => msg.ContainsKey(prop.Value))
|
|
||||||
.Select(prop => Tuple.Create(ReflectionHelpers.GetProperty(this.GetType(), prop.Key), msg[prop.Value]));
|
|
||||||
|
|
||||||
foreach (var property in messageFieldProperties)
|
|
||||||
{
|
|
||||||
property.Item1.SetValue(this, property.Item2, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.ContainsKey(AVIMProtocol.LCATTRS))
|
|
||||||
{
|
|
||||||
object attrs = msg[AVIMProtocol.LCATTRS];
|
|
||||||
this.estimatedData = AVDecoder.Instance.Decode(attrs) as Dictionary<string, object>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.Deserialize(msgStr);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the <see cref="T:LeanCloud.Realtime.AVIMTypedMessage"/> with the specified key.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key">Key.</param>
|
|
||||||
public virtual object this[string key]
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (estimatedData.TryGetValue(key, out object value)) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
estimatedData[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Merges the custom attributes.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="customAttributes">Custom attributes.</param>
|
|
||||||
public void MergeCustomAttributes(IDictionary<string, object> customAttributes)
|
|
||||||
{
|
|
||||||
this.estimatedData = this.estimatedData.Merge(customAttributes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the enumerator.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The enumerator.</returns>
|
|
||||||
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
|
|
||||||
{
|
|
||||||
return estimatedData.GetEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
|
||||||
{
|
|
||||||
return ((IEnumerable<KeyValuePair<string, object>>)this).GetEnumerator();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// AVIMMessage decorator.
|
|
||||||
/// </summary>
|
|
||||||
public abstract class AVIMMessageDecorator : AVIMTypedMessage
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the message.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The message.</value>
|
|
||||||
public AVIMTypedMessage Message { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="T:LeanCloud.Realtime.AVIMMessageDecorator"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="message">Message.</param>
|
|
||||||
protected AVIMMessageDecorator(AVIMTypedMessage message)
|
|
||||||
{
|
|
||||||
this.Message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the content of the message.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The content of the message.</value>
|
|
||||||
public virtual IDictionary<string, object> MessageContent { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Encodes the decorated.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The decorated.</returns>
|
|
||||||
public virtual IDictionary<string, object> EncodeDecorated()
|
|
||||||
{
|
|
||||||
return Message.Encode();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Encode this instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The encode.</returns>
|
|
||||||
public override IDictionary<string, object> Encode()
|
|
||||||
{
|
|
||||||
var decoratedMessageEncoded = EncodeDecorated();
|
|
||||||
var selfEncoded = base.Encode();
|
|
||||||
var decoratoEncoded = this.EncodeDecorator();
|
|
||||||
var resultEncoed = decoratedMessageEncoded.Merge(selfEncoded).Merge(decoratoEncoded);
|
|
||||||
return resultEncoed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Encodes the decorator.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The decorator.</returns>
|
|
||||||
public abstract IDictionary<string, object> EncodeDecorator();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
using System;
|
|
||||||
namespace LeanCloud.Realtime
|
|
||||||
{
|
|
||||||
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
|
|
||||||
public sealed class AVIMTypedMessageTypeIntAttribute : Attribute
|
|
||||||
{
|
|
||||||
public AVIMTypedMessageTypeIntAttribute(int typeInt)
|
|
||||||
{
|
|
||||||
this.TypeInteger = typeInt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int TypeInteger { get; private set; }
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,33 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// WebSocket 监听服务端事件通知的接口
|
|
||||||
/// 所有基于协议层的事件监听都需要实现这个接口,然后自定义监听协议。
|
|
||||||
/// </summary>
|
|
||||||
public interface IAVIMListener
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 监听的协议 Hook
|
|
||||||
/// 例如,消息的协议是 direct 命令,因此消息监听需要判断 <see cref="AVIMNotice.CommandName"/> == "direct" 才可以调用
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="notice"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
bool ProtocolHook(AVIMNotice notice);
|
|
||||||
|
|
||||||
///// <summary>
|
|
||||||
///// 如果 <see cref="IAVIMListener.HookFilter"/> 返回 true,则会启动 NoticeAction 里面的回调逻辑
|
|
||||||
///// </summary>
|
|
||||||
//Action<AVIMNotice> NoticeAction { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 如果 <see cref="IAVIMListener.OnNoticeReceived(AVIMNotice)"/> 返回 true,则会启动 NoticeAction 里面的回调逻辑
|
|
||||||
/// </summary>
|
|
||||||
void OnNoticeReceived(AVIMNotice notice);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,83 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 消息接口
|
|
||||||
/// <para>所有消息必须实现这个接口</para>
|
|
||||||
/// </summary>
|
|
||||||
public interface IAVIMMessage
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Serialize this instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The serialize.</returns>
|
|
||||||
string Serialize();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Validate the specified msgStr.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The validate.</returns>
|
|
||||||
/// <param name="msgStr">Message string.</param>
|
|
||||||
bool Validate(string msgStr);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Deserialize the specified msgStr.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The deserialize.</returns>
|
|
||||||
/// <param name="msgStr">Message string.</param>
|
|
||||||
IAVIMMessage Deserialize(string msgStr);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the conversation identifier.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The conversation identifier.</value>
|
|
||||||
string ConversationId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets from client identifier.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>From client identifier.</value>
|
|
||||||
string FromClientId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the identifier.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The identifier.</value>
|
|
||||||
string Id { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the server timestamp.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The server timestamp.</value>
|
|
||||||
long ServerTimestamp { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the rcp timestamp.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The rcp timestamp.</value>
|
|
||||||
long RcpTimestamp { get; set; }
|
|
||||||
|
|
||||||
long UpdatedAt { get; set; }
|
|
||||||
|
|
||||||
|
|
||||||
#region mention features.
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether this <see cref="T:LeanCloud.Realtime.IAVIMMessage"/> mention all.
|
|
||||||
/// </summary>
|
|
||||||
/// <value><c>true</c> if mention all; otherwise, <c>false</c>.</value>
|
|
||||||
bool MentionAll { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the mention list.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The mention list.</value>
|
|
||||||
IEnumerable<string> MentionList { get; set; }
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime
|
|
||||||
{
|
|
||||||
public interface ISQLStorage
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,131 +0,0 @@
|
||||||
using LeanCloud;
|
|
||||||
using LeanCloud.Storage.Internal;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 对话操作的签名类型,比如讲一个 client id 加入到对话中
|
|
||||||
/// <see cref="https://leancloud.cn/docs/realtime_v2.html#群组功能的签名"/>
|
|
||||||
/// </summary>
|
|
||||||
public enum ConversationSignatureAction
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// add 加入对话和邀请对方加入对话
|
|
||||||
/// </summary>
|
|
||||||
Add,
|
|
||||||
/// <summary>
|
|
||||||
/// remove 当前 client Id 离开对话和将其他人踢出对话
|
|
||||||
/// </summary>
|
|
||||||
Remove
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// <see cref="https://leancloud.cn/docs/realtime_v2.html#群组功能的签名"/>
|
|
||||||
/// </summary>
|
|
||||||
public interface ISignatureFactory
|
|
||||||
{
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 构建登录签名
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="clientId">需要登录到云端服务器的 client Id</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
Task<AVIMSignature> CreateConnectSignature(string clientId);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="clientId"></param>
|
|
||||||
/// <param name="targetIds"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
Task<AVIMSignature> CreateStartConversationSignature(string clientId, IEnumerable<string> targetIds);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="conversationId"></param>
|
|
||||||
/// <param name="clientId"></param>
|
|
||||||
/// <param name="targetIds"></param>
|
|
||||||
/// <param name="action">需要签名的操作</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
Task<AVIMSignature> CreateConversationSignature(string conversationId, string clientId, IEnumerable<string> targetIds, ConversationSignatureAction action);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class DefaulSiganatureFactory : ISignatureFactory
|
|
||||||
{
|
|
||||||
Task<AVIMSignature> ISignatureFactory.CreateConnectSignature(string clientId)
|
|
||||||
{
|
|
||||||
return Task.FromResult<AVIMSignature>(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
Task<AVIMSignature> ISignatureFactory.CreateConversationSignature(string conversationId, string clientId, IEnumerable<string> targetIds, ConversationSignatureAction action)
|
|
||||||
{
|
|
||||||
return Task.FromResult<AVIMSignature>(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
Task<AVIMSignature> ISignatureFactory.CreateStartConversationSignature(string clientId, IEnumerable<string> targetIds)
|
|
||||||
{
|
|
||||||
return Task.FromResult<AVIMSignature>(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class LeanEngineSignatureFactory : ISignatureFactory
|
|
||||||
{
|
|
||||||
public Task<AVIMSignature> CreateConnectSignature(string clientId)
|
|
||||||
{
|
|
||||||
var data = new Dictionary<string, object>();
|
|
||||||
data.Add("client_id", clientId);
|
|
||||||
return AVCloud.CallFunctionAsync<IDictionary<string, object>>("connect", data).OnSuccess(_ =>
|
|
||||||
{
|
|
||||||
var jsonData = _.Result;
|
|
||||||
var s = jsonData["signature"].ToString();
|
|
||||||
var n = jsonData["nonce"].ToString();
|
|
||||||
var t = long.Parse(jsonData["timestamp"].ToString());
|
|
||||||
var signature = new AVIMSignature(s, t, n);
|
|
||||||
return signature;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<AVIMSignature> CreateStartConversationSignature(string clientId, IEnumerable<string> targetIds)
|
|
||||||
{
|
|
||||||
var data = new Dictionary<string, object>();
|
|
||||||
data.Add("client_id", clientId);
|
|
||||||
data.Add("members", targetIds.ToList());
|
|
||||||
return AVCloud.CallFunctionAsync<IDictionary<string, object>>("startConversation", data).OnSuccess(_ =>
|
|
||||||
{
|
|
||||||
var jsonData = _.Result;
|
|
||||||
var s = jsonData["signature"].ToString();
|
|
||||||
var n = jsonData["nonce"].ToString();
|
|
||||||
var t = long.Parse(jsonData["timestamp"].ToString());
|
|
||||||
var signature = new AVIMSignature(s, t, n);
|
|
||||||
return signature;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<AVIMSignature> CreateConversationSignature(string conversationId, string clientId, IEnumerable<string> targetIds, ConversationSignatureAction action)
|
|
||||||
{
|
|
||||||
var actionList = new string[] { "invite", "kick" };
|
|
||||||
var data = new Dictionary<string, object>();
|
|
||||||
data.Add("client_id", clientId);
|
|
||||||
data.Add("conv_id", conversationId);
|
|
||||||
data.Add("members", targetIds.ToList());
|
|
||||||
data.Add("action", actionList[(int)action]);
|
|
||||||
return AVCloud.CallFunctionAsync<IDictionary<string, object>>("oprateConversation", data).OnSuccess(_ =>
|
|
||||||
{
|
|
||||||
var jsonData = _.Result;
|
|
||||||
var s = jsonData["signature"].ToString();
|
|
||||||
var n = jsonData["nonce"].ToString();
|
|
||||||
var t = long.Parse(jsonData["timestamp"].ToString());
|
|
||||||
var signature = new AVIMSignature(s, t, n);
|
|
||||||
return signature;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,256 +0,0 @@
|
||||||
using LeanCloud.Storage.Internal;
|
|
||||||
using LeanCloud.Realtime.Internal;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 对话中成员变动的事件参数,它提供被操作的对话(Conversation),操作类型(AffectedType)
|
|
||||||
/// 受影响的成员列表(AffectedMembers)
|
|
||||||
/// </summary>
|
|
||||||
public class AVIMOnMembersChangedEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 本次成员变动中被操作的具体对话(AVIMConversation)的对象
|
|
||||||
/// </summary>
|
|
||||||
public AVIMConversation Conversation { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 变动的类型
|
|
||||||
/// </summary>
|
|
||||||
public AVIMConversationEventType AffectedType { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 受影响的成员的 Client Ids
|
|
||||||
/// </summary>
|
|
||||||
public IList<string> AffectedMembers { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 操作人的 Client ClientId
|
|
||||||
/// </summary>
|
|
||||||
public string Oprator { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 操作的时间,已转化为本地时间
|
|
||||||
/// </summary>
|
|
||||||
public DateTime OpratedTime { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 变动的类型,目前支持如下:
|
|
||||||
/// 1、Joined:当前 Client 主动加入,案例:当 A 主动加入到对话,A 将收到 Joined 事件响应,其余的成员收到 MembersJoined 事件响应
|
|
||||||
/// 2、Left:当前 Client 主动退出,案例:当 A 从对话中退出,A 将收到 Left 事件响应,其余的成员收到 MembersLeft 事件响应
|
|
||||||
/// 3、MembersJoined:某个成员加入(区别于Joined和Kicked),案例:当 A 把 B 加入到对话中,C 将收到 MembersJoined 事件响应
|
|
||||||
/// 4、MembersLeft:某个成员加入(区别于Joined和Kicked),案例:当 A 把 B 从对话中剔除,C 将收到 MembersLeft 事件响应
|
|
||||||
/// 5、Invited:当前 Client 被邀请加入,案例:当 A 被 B 邀请加入到对话中,A 将收到 Invited 事件响应,B 将收到 Joined ,其余的成员收到 MembersJoined 事件响应
|
|
||||||
/// 6、Kicked:当前 Client 被剔除,案例:当 A 被 B 从对话中剔除,A 将收到 Kicked 事件响应,B 将收到 Left,其余的成员收到 MembersLeft 事件响应
|
|
||||||
/// </summary>
|
|
||||||
public enum AVIMConversationEventType
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 自身主动加入
|
|
||||||
/// </summary>
|
|
||||||
Joined = 1,
|
|
||||||
/// <summary>
|
|
||||||
/// 自身主动离开
|
|
||||||
/// </summary>
|
|
||||||
Left,
|
|
||||||
/// <summary>
|
|
||||||
/// 他人加入
|
|
||||||
/// </summary>
|
|
||||||
MembersJoined,
|
|
||||||
/// <summary>
|
|
||||||
/// 他人离开
|
|
||||||
/// </summary>
|
|
||||||
MembersLeft,
|
|
||||||
/// <summary>
|
|
||||||
/// 自身被邀请加入
|
|
||||||
/// </summary>
|
|
||||||
Invited,
|
|
||||||
/// <summary>
|
|
||||||
/// 自身被他人剔除
|
|
||||||
/// </summary>
|
|
||||||
Kicked
|
|
||||||
}
|
|
||||||
|
|
||||||
#region AVIMMembersJoinListener
|
|
||||||
//when Members joined or invited by member,this listener will invoke AVIMOnMembersJoinedEventArgs event.
|
|
||||||
/// <summary>
|
|
||||||
/// 对话中有成员加入的时候,在改对话中的其他成员都会触发 <see cref="AVIMMembersJoinListener.OnMembersJoined"/> 事件
|
|
||||||
/// </summary>
|
|
||||||
public class AVIMMembersJoinListener : IAVIMListener
|
|
||||||
{
|
|
||||||
|
|
||||||
private EventHandler<AVIMOnMembersJoinedEventArgs> m_OnMembersJoined;
|
|
||||||
/// <summary>
|
|
||||||
/// 有成员加入到对话时,触发的事件
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<AVIMOnMembersJoinedEventArgs> OnMembersJoined
|
|
||||||
{
|
|
||||||
add
|
|
||||||
{
|
|
||||||
m_OnMembersJoined += value;
|
|
||||||
}
|
|
||||||
remove
|
|
||||||
{
|
|
||||||
m_OnMembersJoined -= value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void OnNoticeReceived(AVIMNotice notice)
|
|
||||||
{
|
|
||||||
if (m_OnMembersJoined != null)
|
|
||||||
{
|
|
||||||
var joinedMembers = AVDecoder.Instance.DecodeList<string>(notice.RawData["m"]);
|
|
||||||
var ivitedBy = notice.RawData["initBy"].ToString();
|
|
||||||
var conersationId = notice.RawData["cid"].ToString();
|
|
||||||
var args = new AVIMOnMembersJoinedEventArgs()
|
|
||||||
{
|
|
||||||
ConversationId = conersationId,
|
|
||||||
InvitedBy = ivitedBy,
|
|
||||||
JoinedMembers = joinedMembers
|
|
||||||
};
|
|
||||||
m_OnMembersJoined.Invoke(this, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual bool ProtocolHook(AVIMNotice notice)
|
|
||||||
{
|
|
||||||
if (notice.CommandName != "conv") return false;
|
|
||||||
if (!notice.RawData.ContainsKey("op")) return false;
|
|
||||||
var op = notice.RawData["op"].ToString();
|
|
||||||
if (!op.Equals("members-joined")) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region AVIMMembersLeftListener
|
|
||||||
// when Members left or kicked by member,this listener will invoke AVIMOnMembersJoinedEventArgs event.
|
|
||||||
/// <summary>
|
|
||||||
/// 对话中有成员加入的时候,在改对话中的其他成员都会触发 <seealso cref="AVIMMembersLeftListener.OnMembersLeft"/>OnMembersJoined 事件
|
|
||||||
/// </summary>
|
|
||||||
public class AVIMMembersLeftListener : IAVIMListener
|
|
||||||
{
|
|
||||||
private EventHandler<AVIMOnMembersLeftEventArgs> m_OnMembersLeft;
|
|
||||||
/// <summary>
|
|
||||||
/// 有成员加入到对话时,触发的事件
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<AVIMOnMembersLeftEventArgs> OnMembersLeft
|
|
||||||
{
|
|
||||||
add
|
|
||||||
{
|
|
||||||
m_OnMembersLeft += value;
|
|
||||||
}
|
|
||||||
remove
|
|
||||||
{
|
|
||||||
m_OnMembersLeft -= value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public virtual void OnNoticeReceived(AVIMNotice notice)
|
|
||||||
{
|
|
||||||
if (m_OnMembersLeft != null)
|
|
||||||
{
|
|
||||||
var leftMembers = AVDecoder.Instance.DecodeList<string>(notice.RawData["m"]);
|
|
||||||
var kickedBy = notice.RawData["initBy"].ToString();
|
|
||||||
var conersationId = notice.RawData["cid"].ToString();
|
|
||||||
var args = new AVIMOnMembersLeftEventArgs()
|
|
||||||
{
|
|
||||||
ConversationId = conersationId,
|
|
||||||
KickedBy = kickedBy,
|
|
||||||
LeftMembers = leftMembers
|
|
||||||
};
|
|
||||||
m_OnMembersLeft.Invoke(this, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual bool ProtocolHook(AVIMNotice notice)
|
|
||||||
{
|
|
||||||
if (notice.CommandName != "conv") return false;
|
|
||||||
if (!notice.RawData.ContainsKey("op")) return false;
|
|
||||||
var op = notice.RawData["op"].ToString();
|
|
||||||
if (!op.Equals("members-left")) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region AVIMInvitedListener
|
|
||||||
public class AVIMInvitedListener : IAVIMListener
|
|
||||||
{
|
|
||||||
private EventHandler<AVIMOnInvitedEventArgs> m_OnInvited;
|
|
||||||
public event EventHandler<AVIMOnInvitedEventArgs> OnInvited {
|
|
||||||
add {
|
|
||||||
m_OnInvited += value;
|
|
||||||
} remove {
|
|
||||||
m_OnInvited -= value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public void OnNoticeReceived(AVIMNotice notice)
|
|
||||||
{
|
|
||||||
if (m_OnInvited != null)
|
|
||||||
{
|
|
||||||
var ivitedBy = notice.RawData["initBy"].ToString();
|
|
||||||
var conersationId = notice.RawData["cid"].ToString();
|
|
||||||
var args = new AVIMOnInvitedEventArgs()
|
|
||||||
{
|
|
||||||
ConversationId = conersationId,
|
|
||||||
InvitedBy = ivitedBy,
|
|
||||||
};
|
|
||||||
m_OnInvited.Invoke(this, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ProtocolHook(AVIMNotice notice)
|
|
||||||
{
|
|
||||||
if (notice.CommandName != "conv") return false;
|
|
||||||
if (!notice.RawData.ContainsKey("op")) return false;
|
|
||||||
var op = notice.RawData["op"].ToString();
|
|
||||||
if (!op.Equals("joined")) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region AVIMKickedListener
|
|
||||||
public class AVIMKickedListener : IAVIMListener
|
|
||||||
{
|
|
||||||
private EventHandler<AVIMOnKickedEventArgs> m_OnKicked;
|
|
||||||
public event EventHandler<AVIMOnKickedEventArgs> OnKicked {
|
|
||||||
add {
|
|
||||||
m_OnKicked += value;
|
|
||||||
} remove {
|
|
||||||
m_OnKicked -= value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public void OnNoticeReceived(AVIMNotice notice)
|
|
||||||
{
|
|
||||||
if (m_OnKicked != null)
|
|
||||||
{
|
|
||||||
var kickcdBy = notice.RawData["initBy"].ToString();
|
|
||||||
var conersationId = notice.RawData["cid"].ToString();
|
|
||||||
var args = new AVIMOnKickedEventArgs()
|
|
||||||
{
|
|
||||||
ConversationId = conersationId,
|
|
||||||
KickedBy = kickcdBy,
|
|
||||||
};
|
|
||||||
m_OnKicked.Invoke(this, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ProtocolHook(AVIMNotice notice)
|
|
||||||
{
|
|
||||||
if (notice.CommandName != "conv") return false;
|
|
||||||
if (!notice.RawData.ContainsKey("op")) return false;
|
|
||||||
var op = notice.RawData["op"].ToString();
|
|
||||||
if (!op.Equals("left")) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,145 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using LeanCloud.Realtime.Internal;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime
|
|
||||||
{
|
|
||||||
internal class ConversationUnreadListener : IAVIMListener
|
|
||||||
{
|
|
||||||
internal class UnreadConversationNotice : IEqualityComparer<UnreadConversationNotice>
|
|
||||||
{
|
|
||||||
internal readonly object mutex = new object();
|
|
||||||
internal IAVIMMessage LastUnreadMessage { get; set; }
|
|
||||||
internal string ConvId { get; set; }
|
|
||||||
internal int UnreadCount { get; set; }
|
|
||||||
|
|
||||||
public bool Equals(UnreadConversationNotice x, UnreadConversationNotice y)
|
|
||||||
{
|
|
||||||
return x.ConvId == y.ConvId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetHashCode(UnreadConversationNotice obj)
|
|
||||||
{
|
|
||||||
return obj.ConvId.GetHashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void AutomicIncrement()
|
|
||||||
{
|
|
||||||
lock (mutex)
|
|
||||||
{
|
|
||||||
UnreadCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
internal static readonly object sMutex = new object();
|
|
||||||
internal static long NotifTime;
|
|
||||||
internal static HashSet<UnreadConversationNotice> UnreadConversations;
|
|
||||||
static ConversationUnreadListener()
|
|
||||||
{
|
|
||||||
UnreadConversations = new HashSet<UnreadConversationNotice>(new UnreadConversationNotice());
|
|
||||||
NotifTime = DateTime.Now.ToUnixTimeStamp();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static void UpdateNotice(IAVIMMessage message)
|
|
||||||
{
|
|
||||||
lock (sMutex)
|
|
||||||
{
|
|
||||||
var convValidators = UnreadConversations.Where(c => c.ConvId == message.ConversationId);
|
|
||||||
if (convValidators != null)
|
|
||||||
{
|
|
||||||
if (convValidators.Count() > 0)
|
|
||||||
{
|
|
||||||
var currentNotice = convValidators.FirstOrDefault();
|
|
||||||
currentNotice.AutomicIncrement();
|
|
||||||
currentNotice.LastUnreadMessage = message;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var currentThread = new UnreadConversationNotice();
|
|
||||||
currentThread.ConvId = message.ConversationId;
|
|
||||||
currentThread.LastUnreadMessage = message;
|
|
||||||
currentThread.AutomicIncrement();
|
|
||||||
UnreadConversations.Add(currentThread);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
internal static void ClearUnread(string convId)
|
|
||||||
{
|
|
||||||
UnreadConversations.Remove(Get(convId));
|
|
||||||
}
|
|
||||||
internal static IEnumerable<string> FindAllConvIds()
|
|
||||||
{
|
|
||||||
lock (sMutex)
|
|
||||||
{
|
|
||||||
return ConversationUnreadListener.UnreadConversations.Select(c => c.ConvId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static UnreadConversationNotice Get(string convId)
|
|
||||||
{
|
|
||||||
lock (sMutex)
|
|
||||||
{
|
|
||||||
var unreadValidator = ConversationUnreadListener.UnreadConversations.Where(c => c.ConvId == convId);
|
|
||||||
if (unreadValidator != null)
|
|
||||||
{
|
|
||||||
if (unreadValidator.Count() > 0)
|
|
||||||
{
|
|
||||||
var notice = unreadValidator.FirstOrDefault();
|
|
||||||
return notice;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnNoticeReceived(AVIMNotice notice)
|
|
||||||
{
|
|
||||||
lock (sMutex)
|
|
||||||
{
|
|
||||||
if (notice.RawData.ContainsKey("convs"))
|
|
||||||
{
|
|
||||||
var unreadRawData = notice.RawData["convs"] as List<object>;
|
|
||||||
if (notice.RawData.ContainsKey("notifTime"))
|
|
||||||
{
|
|
||||||
long.TryParse(notice.RawData["notifTime"].ToString(), out NotifTime);
|
|
||||||
}
|
|
||||||
foreach (var data in unreadRawData)
|
|
||||||
{
|
|
||||||
var dataMap = data as IDictionary<string, object>;
|
|
||||||
if (dataMap != null)
|
|
||||||
{
|
|
||||||
var convId = dataMap["cid"].ToString();
|
|
||||||
var ucn = Get(convId);
|
|
||||||
if (ucn == null) ucn = new UnreadConversationNotice();
|
|
||||||
|
|
||||||
ucn.ConvId = convId;
|
|
||||||
var unreadCount = 0;
|
|
||||||
Int32.TryParse(dataMap["unread"].ToString(), out unreadCount);
|
|
||||||
ucn.UnreadCount = unreadCount;
|
|
||||||
|
|
||||||
#region restore last message for the conversation
|
|
||||||
if (dataMap.ContainsKey("data"))
|
|
||||||
{
|
|
||||||
var msgStr = dataMap["data"].ToString();
|
|
||||||
var messageObj = AVRealtime.FreeStyleMessageClassingController.Instantiate(msgStr, dataMap);
|
|
||||||
ucn.LastUnreadMessage = messageObj;
|
|
||||||
}
|
|
||||||
|
|
||||||
UnreadConversations.Add(ucn);
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ProtocolHook(AVIMNotice notice)
|
|
||||||
{
|
|
||||||
return notice.CommandName == "unread";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime {
|
|
||||||
/// <summary>
|
|
||||||
/// 强制被踢下线处理
|
|
||||||
/// </summary>
|
|
||||||
internal class GoAwayListener : IAVIMListener {
|
|
||||||
Action onGoAway;
|
|
||||||
|
|
||||||
public event Action OnGoAway {
|
|
||||||
add {
|
|
||||||
onGoAway += value;
|
|
||||||
}
|
|
||||||
remove {
|
|
||||||
onGoAway -= value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnNoticeReceived(AVIMNotice notice) {
|
|
||||||
// TODO 退出并清理路由缓存
|
|
||||||
onGoAway?.Invoke();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ProtocolHook(AVIMNotice notice) {
|
|
||||||
return notice.CommandName == "goaway";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime
|
|
||||||
{
|
|
||||||
internal delegate void OnMessagePatch(IEnumerable<IAVIMMessage> messages);
|
|
||||||
internal class MessagePatchListener : IAVIMListener
|
|
||||||
{
|
|
||||||
public OnMessagePatch OnReceived { get; set; }
|
|
||||||
|
|
||||||
public void OnNoticeReceived(AVIMNotice notice)
|
|
||||||
{
|
|
||||||
ICollection<IAVIMMessage> patchedMessages = new List<IAVIMMessage>();
|
|
||||||
var msgObjs = notice.RawData["patches"] as IList<object>;
|
|
||||||
if (msgObjs != null)
|
|
||||||
{
|
|
||||||
foreach (var msgObj in msgObjs)
|
|
||||||
{
|
|
||||||
var msgData = msgObj as IDictionary<string, object>;
|
|
||||||
if (msgData != null)
|
|
||||||
{
|
|
||||||
var msgStr = msgData["data"] as string;
|
|
||||||
var message = AVRealtime.FreeStyleMessageClassingController.Instantiate(msgStr, msgData);
|
|
||||||
patchedMessages.Add(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (OnReceived != null)
|
|
||||||
{
|
|
||||||
if (patchedMessages.Count > 0)
|
|
||||||
{
|
|
||||||
this.OnReceived(patchedMessages);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ProtocolHook(AVIMNotice notice)
|
|
||||||
{
|
|
||||||
if (notice.CommandName != "patch") return false;
|
|
||||||
if (!notice.RawData.ContainsKey("op")) return false;
|
|
||||||
if (notice.RawData["op"].ToString() != "modify") return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
using LeanCloud.Storage.Internal;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime
|
|
||||||
{
|
|
||||||
internal class OfflineMessageListener : IAVIMListener
|
|
||||||
{
|
|
||||||
private EventHandler<AVIMMessageEventArgs> m_OnOfflineMessageReceived;
|
|
||||||
public event EventHandler<AVIMMessageEventArgs> OnOfflineMessageReceived
|
|
||||||
{
|
|
||||||
add
|
|
||||||
{
|
|
||||||
m_OnOfflineMessageReceived += value;
|
|
||||||
}
|
|
||||||
remove
|
|
||||||
{
|
|
||||||
m_OnOfflineMessageReceived -= value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public void OnNoticeReceived(AVIMNotice notice)
|
|
||||||
{
|
|
||||||
if (m_OnOfflineMessageReceived != null)
|
|
||||||
{
|
|
||||||
var msgStr = notice.RawData["msg"].ToString();
|
|
||||||
var iMessage = AVRealtime.FreeStyleMessageClassingController.Instantiate(msgStr, notice.RawData);
|
|
||||||
var args = new AVIMMessageEventArgs(iMessage);
|
|
||||||
m_OnOfflineMessageReceived.Invoke(this, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ProtocolHook(AVIMNotice notice)
|
|
||||||
{
|
|
||||||
if (notice.CommandName != "direct") return false;
|
|
||||||
if (!notice.RawData.ContainsKey("offline")) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime
|
|
||||||
{
|
|
||||||
internal class SessionListener : IAVIMListener
|
|
||||||
{
|
|
||||||
private Action<int, string, string> _onSessionClosed;
|
|
||||||
public event Action<int, string, string> OnSessionClosed
|
|
||||||
{
|
|
||||||
add
|
|
||||||
{
|
|
||||||
_onSessionClosed += value;
|
|
||||||
}
|
|
||||||
remove
|
|
||||||
{
|
|
||||||
_onSessionClosed -= value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public void OnNoticeReceived(AVIMNotice notice)
|
|
||||||
{
|
|
||||||
var code = 0;
|
|
||||||
if (notice.RawData.ContainsKey("code"))
|
|
||||||
{
|
|
||||||
int.TryParse(notice.RawData["code"].ToString(), out code);
|
|
||||||
}
|
|
||||||
|
|
||||||
var reason = "";
|
|
||||||
if (notice.RawData.ContainsKey("reason"))
|
|
||||||
{
|
|
||||||
reason = notice.RawData["reason"].ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
var detail = "";
|
|
||||||
if (notice.RawData.ContainsKey("detail"))
|
|
||||||
{
|
|
||||||
detail = notice.RawData["detail"].ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_onSessionClosed != null)
|
|
||||||
{
|
|
||||||
_onSessionClosed(code, reason, detail);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ProtocolHook(AVIMNotice notice)
|
|
||||||
{
|
|
||||||
if (notice.CommandName != "session") return false;
|
|
||||||
if (!notice.RawData.ContainsKey("op")) return false;
|
|
||||||
if (notice.RawData.ContainsKey("i")) return false;
|
|
||||||
if (notice.RawData["op"].ToString() != "closed") return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,101 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using LeanCloud.Realtime.Internal;
|
|
||||||
using LeanCloud.Storage.Internal;
|
|
||||||
using UnityEngine;
|
|
||||||
using UnityEngine.Networking;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// AVRealtime initialize behavior.
|
|
||||||
/// </summary>
|
|
||||||
public class AVRealtimeBehavior : AVInitializeBehaviour
|
|
||||||
{
|
|
||||||
public string RTMRouter = null;
|
|
||||||
|
|
||||||
//void OnApplicationQuit()
|
|
||||||
//{
|
|
||||||
// if (AVRealtime.clients != null)
|
|
||||||
// {
|
|
||||||
// foreach (var item in AVRealtime.clients)
|
|
||||||
// {
|
|
||||||
// item.Value.LinkedRealtime.LogOut();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
//private void Update()
|
|
||||||
//{
|
|
||||||
// var available = Application.internetReachability != NetworkReachability.NotReachable;
|
|
||||||
// if (AVRealtime.clients != null)
|
|
||||||
// foreach (var item in AVRealtime.clients)
|
|
||||||
// {
|
|
||||||
// if (item.Value != null)
|
|
||||||
// if (item.Value.LinkedRealtime != null)
|
|
||||||
// item.Value.LinkedRealtime.InvokeNetworkState(available);
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
//public override void Awake()
|
|
||||||
//{
|
|
||||||
// base.Awake();
|
|
||||||
// StartCoroutine(InitializeRealtime());
|
|
||||||
// gameObject.name = "AVRealtimeInitializeBehavior";
|
|
||||||
//}
|
|
||||||
|
|
||||||
//public IEnumerator InitializeRealtime()
|
|
||||||
//{
|
|
||||||
// if (isRealtimeInitialized)
|
|
||||||
// {
|
|
||||||
// yield break;
|
|
||||||
// }
|
|
||||||
// isRealtimeInitialized = true;
|
|
||||||
// yield return FetchRouter();
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
|
||||||
//[SerializeField]
|
|
||||||
//public bool secure;
|
|
||||||
//private static bool isRealtimeInitialized = false;
|
|
||||||
//public string Server;
|
|
||||||
//private IDictionary<string, object> routerState;
|
|
||||||
|
|
||||||
//public IEnumerator FetchRouter()
|
|
||||||
//{
|
|
||||||
// var router = RTMRouter;
|
|
||||||
// if (string.IsNullOrEmpty(router)) {
|
|
||||||
// var state = AVPlugins.Instance.AppRouterController.Get();
|
|
||||||
// router = state.RealtimeRouterServer;
|
|
||||||
// }
|
|
||||||
// var url = string.Format("https://{0}/v1/route?appId={1}", router, applicationID);
|
|
||||||
// if (secure)
|
|
||||||
// {
|
|
||||||
// url += "&secure=1";
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var request = new UnityWebRequest(url);
|
|
||||||
// request.downloadHandler = new DownloadHandlerBuffer();
|
|
||||||
// yield return request.Send();
|
|
||||||
|
|
||||||
// if (request.isError)
|
|
||||||
// {
|
|
||||||
// throw new AVException(AVException.ErrorCode.ConnectionFailed, "can not reach router.", null);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var result = request.downloadHandler.text;
|
|
||||||
// routerState = Json.Parse(result) as IDictionary<string, object>;
|
|
||||||
// if (routerState.Keys.Count == 0)
|
|
||||||
// {
|
|
||||||
// throw new KeyNotFoundException("Can not get websocket url from server,please check the appId.");
|
|
||||||
// }
|
|
||||||
// var ttl = long.Parse(routerState["ttl"].ToString());
|
|
||||||
// var expire = DateTime.Now.AddSeconds(ttl);
|
|
||||||
// routerState["expire"] = expire.ToUnixTimeStamp(UnixTimeStampUnit.Second);
|
|
||||||
// Server = routerState["server"].ToString();
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace LeanCloud.Realtime {
|
||||||
|
/// <summary>
|
||||||
|
/// 聊天室
|
||||||
|
/// </summary>
|
||||||
|
public class LCIMChatRoom : LCIMConversation {
|
||||||
|
public LCIMChatRoom(LCIMClient client) :
|
||||||
|
base(client) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取在线用户数量
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<int> GetOnlineMembersCount() {
|
||||||
|
return await GetMembersCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取在线用户
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="limit"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<ReadOnlyCollection<string>> GetOnlineMembers(int limit = 50) {
|
||||||
|
return await Client.ConversationController.GetOnlineMembers(Id, limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<LCIMPartiallySuccessResult> AddMembers(IEnumerable<string> clientIds) {
|
||||||
|
throw new Exception("Add members is not allowed in chat room.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,517 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using LeanCloud.Storage;
|
||||||
|
|
||||||
|
namespace LeanCloud.Realtime {
|
||||||
|
/// <summary>
|
||||||
|
/// 普通对话
|
||||||
|
/// </summary>
|
||||||
|
public class LCIMConversation {
|
||||||
|
/// <summary>
|
||||||
|
/// 对话 Id
|
||||||
|
/// </summary>
|
||||||
|
public string Id {
|
||||||
|
get; internal set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否唯一
|
||||||
|
/// </summary>
|
||||||
|
public bool Unique {
|
||||||
|
get; internal set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 唯一 Id
|
||||||
|
/// </summary>
|
||||||
|
public string UniqueId {
|
||||||
|
get; internal set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 对话名称
|
||||||
|
/// </summary>
|
||||||
|
public string Name {
|
||||||
|
get {
|
||||||
|
return this["name"] as string;
|
||||||
|
}
|
||||||
|
internal set {
|
||||||
|
this["name"] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建者 Id
|
||||||
|
/// </summary>
|
||||||
|
public string CreatorId {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 成员 Id
|
||||||
|
/// </summary>
|
||||||
|
public ReadOnlyCollection<string> MemberIds {
|
||||||
|
get {
|
||||||
|
return new ReadOnlyCollection<string>(ids.ToList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 静音成员 Id
|
||||||
|
/// </summary>
|
||||||
|
public ReadOnlyCollection<string> MutedMemberIds {
|
||||||
|
get {
|
||||||
|
return new ReadOnlyCollection<string>(mutedIds.ToList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 未读消息数量
|
||||||
|
/// </summary>
|
||||||
|
public int Unread {
|
||||||
|
get; internal set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 最新的一条消息
|
||||||
|
/// </summary>
|
||||||
|
public LCIMMessage LastMessage {
|
||||||
|
get; internal set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime CreatedAt {
|
||||||
|
get; internal set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime UpdatedAt {
|
||||||
|
get; internal set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 最新送达消息时间戳
|
||||||
|
/// </summary>
|
||||||
|
public long LastDeliveredTimestamp {
|
||||||
|
get; internal set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 最新送达消息时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime LastDeliveredAt {
|
||||||
|
get {
|
||||||
|
DateTimeOffset dateTimeOffset = DateTimeOffset.FromUnixTimeMilliseconds(LastDeliveredTimestamp);
|
||||||
|
return dateTimeOffset.DateTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 最新已读消息时间戳
|
||||||
|
/// </summary>
|
||||||
|
public long LastReadTimestamp {
|
||||||
|
get; internal set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 最新已读消息时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime LastReadAt {
|
||||||
|
get {
|
||||||
|
DateTimeOffset dateTimeOffset = DateTimeOffset.FromUnixTimeMilliseconds(LastReadTimestamp);
|
||||||
|
return dateTimeOffset.DateTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置/获取对话属性
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public object this[string key] {
|
||||||
|
get {
|
||||||
|
return customProperties[key];
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
customProperties[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否已静音
|
||||||
|
/// </summary>
|
||||||
|
public bool IsMute {
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected LCIMClient Client {
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Dictionary<string, object> customProperties;
|
||||||
|
|
||||||
|
internal HashSet<string> ids;
|
||||||
|
|
||||||
|
internal HashSet<string> mutedIds;
|
||||||
|
|
||||||
|
internal LCIMConversation(LCIMClient client) {
|
||||||
|
Client = client;
|
||||||
|
customProperties = new Dictionary<string, object>();
|
||||||
|
ids = new HashSet<string>();
|
||||||
|
mutedIds = new HashSet<string>();
|
||||||
|
customProperties = new Dictionary<string, object>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取对话人数,或暂态对话的在线人数
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<int> GetMembersCount() {
|
||||||
|
return await Client.ConversationController.GetMembersCount(Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将该会话标记为已读
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task Read() {
|
||||||
|
if (LastMessage == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await Client.MessageController.Read(Id, LastMessage);
|
||||||
|
Unread = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 修改对话属性
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="attributes"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task UpdateInfo(Dictionary<string, object> attributes) {
|
||||||
|
if (attributes == null || attributes.Count == 0) {
|
||||||
|
throw new ArgumentNullException(nameof(attributes));
|
||||||
|
}
|
||||||
|
Dictionary<string, object> updatedAttr = await Client.ConversationController.UpdateInfo(Id, attributes);
|
||||||
|
if (updatedAttr != null) {
|
||||||
|
MergeInfo(updatedAttr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 添加用户到对话
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientIds">用户 Id</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual async Task<LCIMPartiallySuccessResult> AddMembers(IEnumerable<string> clientIds) {
|
||||||
|
if (clientIds == null || clientIds.Count() == 0) {
|
||||||
|
throw new ArgumentNullException(nameof(clientIds));
|
||||||
|
}
|
||||||
|
LCIMPartiallySuccessResult result = await Client.ConversationController.AddMembers(Id, clientIds);
|
||||||
|
ids.UnionWith(result.SuccessfulClientIdList);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 删除用户
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="removeIds">用户 Id</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<LCIMPartiallySuccessResult> RemoveMembers(IEnumerable<string> removeIds) {
|
||||||
|
if (removeIds == null || removeIds.Count() == 0) {
|
||||||
|
throw new ArgumentNullException(nameof(removeIds));
|
||||||
|
}
|
||||||
|
LCIMPartiallySuccessResult result = await Client.ConversationController.RemoveMembers(Id, removeIds);
|
||||||
|
ids.RemoveWhere(id => result.SuccessfulClientIdList.Contains(id));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 加入对话
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task Join() {
|
||||||
|
LCIMPartiallySuccessResult result = await Client.ConversationController.AddMembers(Id,
|
||||||
|
new string[] { Client.Id });
|
||||||
|
if (result.IsSuccess) {
|
||||||
|
ids.UnionWith(result.SuccessfulClientIdList);
|
||||||
|
} else {
|
||||||
|
LCIMOperationFailure error = result.FailureList[0];
|
||||||
|
throw new LCException(error.Code, error.Reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 离开对话
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task Quit() {
|
||||||
|
LCIMPartiallySuccessResult result = await RemoveMembers(new string[] { Client.Id });
|
||||||
|
if (!result.IsSuccess) {
|
||||||
|
LCIMOperationFailure error = result.FailureList[0];
|
||||||
|
throw new LCException(error.Code, error.Reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送消息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<LCIMMessage> Send(LCIMMessage message,
|
||||||
|
LCIMMessageSendOptions options = null) {
|
||||||
|
if (message == null) {
|
||||||
|
throw new ArgumentNullException(nameof(message));
|
||||||
|
}
|
||||||
|
if (options == null) {
|
||||||
|
options = LCIMMessageSendOptions.Default;
|
||||||
|
}
|
||||||
|
await Client.MessageController.Send(Id, message, options);
|
||||||
|
LastMessage = message;
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 静音
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task Mute() {
|
||||||
|
await Client.ConversationController.Mute(Id);
|
||||||
|
IsMute = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 取消静音
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task Unmute() {
|
||||||
|
await Client.ConversationController.Unmute(Id);
|
||||||
|
IsMute = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 禁言
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientIds"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<LCIMPartiallySuccessResult> MuteMembers(IEnumerable<string> clientIds) {
|
||||||
|
if (clientIds == null || clientIds.Count() == 0) {
|
||||||
|
throw new ArgumentNullException(nameof(clientIds));
|
||||||
|
}
|
||||||
|
LCIMPartiallySuccessResult result = await Client.ConversationController.MuteMembers(Id, clientIds);
|
||||||
|
if (result.SuccessfulClientIdList != null) {
|
||||||
|
mutedIds.UnionWith(result.SuccessfulClientIdList);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 取消禁言
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientIdList"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<LCIMPartiallySuccessResult> UnmuteMembers(IEnumerable<string> clientIds) {
|
||||||
|
if (clientIds == null || clientIds.Count() == 0) {
|
||||||
|
throw new ArgumentNullException(nameof(clientIds));
|
||||||
|
}
|
||||||
|
LCIMPartiallySuccessResult result = await Client.ConversationController.UnmuteMembers(Id, clientIds);
|
||||||
|
if (result.SuccessfulClientIdList != null) {
|
||||||
|
mutedIds.RemoveWhere(id => result.SuccessfulClientIdList.Contains(id));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将用户加入黑名单
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientIds"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<LCIMPartiallySuccessResult> BlockMembers(IEnumerable<string> clientIds) {
|
||||||
|
if (clientIds == null || clientIds.Count() == 0) {
|
||||||
|
throw new ArgumentNullException(nameof(clientIds));
|
||||||
|
}
|
||||||
|
return await Client.ConversationController.BlockMembers(Id, clientIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将用户移除黑名单
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientIds"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<LCIMPartiallySuccessResult> UnblockMembers(IEnumerable<string> clientIds) {
|
||||||
|
if (clientIds == null || clientIds.Count() == 0) {
|
||||||
|
throw new ArgumentNullException(nameof(clientIds));
|
||||||
|
}
|
||||||
|
return await Client.ConversationController.UnblockMembers(Id, clientIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 撤回消息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task RecallMessage(LCIMMessage message) {
|
||||||
|
if (message == null) {
|
||||||
|
throw new ArgumentNullException(nameof(message));
|
||||||
|
}
|
||||||
|
await Client.MessageController.RecallMessage(Id, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 修改消息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="oldMessage"></param>
|
||||||
|
/// <param name="newMessage"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task UpdateMessage(LCIMMessage oldMessage, LCIMMessage newMessage) {
|
||||||
|
if (oldMessage == null) {
|
||||||
|
throw new ArgumentNullException(nameof(oldMessage));
|
||||||
|
}
|
||||||
|
if (newMessage == null) {
|
||||||
|
throw new ArgumentNullException(nameof(newMessage));
|
||||||
|
}
|
||||||
|
await Client.MessageController.UpdateMessage(Id, oldMessage, newMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新对话中成员的角色
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="memberId"></param>
|
||||||
|
/// <param name="role"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task UpdateMemberRole(string memberId, string role) {
|
||||||
|
if (string.IsNullOrEmpty(memberId)) {
|
||||||
|
throw new ArgumentNullException(nameof(memberId));
|
||||||
|
}
|
||||||
|
if (role != LCIMConversationMemberInfo.Manager && role != LCIMConversationMemberInfo.Member) {
|
||||||
|
throw new ArgumentException("role MUST be Manager Or Memebr");
|
||||||
|
}
|
||||||
|
await Client.ConversationController.UpdateMemberRole(Id, memberId, role);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取对话中成员的角色(只返回管理员)
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<ReadOnlyCollection<LCIMConversationMemberInfo>> GetAllMemberInfo() {
|
||||||
|
return await Client.ConversationController.GetAllMemberInfo(Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取对话中指定成员的角色
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="memberId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<LCIMConversationMemberInfo> GetMemberInfo(string memberId) {
|
||||||
|
if (string.IsNullOrEmpty(memberId)) {
|
||||||
|
throw new ArgumentNullException(nameof(memberId));
|
||||||
|
}
|
||||||
|
ReadOnlyCollection<LCIMConversationMemberInfo> members = await GetAllMemberInfo();
|
||||||
|
foreach (LCIMConversationMemberInfo member in members) {
|
||||||
|
if (member.MemberId == memberId) {
|
||||||
|
return member;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询禁言用户
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="limit"></param>
|
||||||
|
/// <param name="next"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<LCIMPageResult> QueryMutedMembers(int limit = 10,
|
||||||
|
string next = null) {
|
||||||
|
return await Client.ConversationController.QueryMutedMembers(Id, limit, next);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询黑名单用户
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="limit">限制</param>
|
||||||
|
/// <param name="next">其实用户 Id</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<LCIMPageResult> QueryBlockedMembers(int limit = 10,
|
||||||
|
string next = null) {
|
||||||
|
return await Client.ConversationController.QueryBlockedMembers(Id, limit, next);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询聊天记录
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="start">起点</param>
|
||||||
|
/// <param name="end">终点</param>
|
||||||
|
/// <param name="direction">查找方向</param>
|
||||||
|
/// <param name="limit">限制</param>
|
||||||
|
/// <param name="messageType">消息类型</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<ReadOnlyCollection<LCIMMessage>> QueryMessages(LCIMMessageQueryEndpoint start = null,
|
||||||
|
LCIMMessageQueryEndpoint end = null,
|
||||||
|
LCIMMessageQueryDirection direction = LCIMMessageQueryDirection.NewToOld,
|
||||||
|
int limit = 20,
|
||||||
|
int messageType = 0) {
|
||||||
|
return await Client.MessageController.QueryMessages(Id, start, end, direction, limit, messageType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取会话已收/已读时间戳
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task FetchReciptTimestamps() {
|
||||||
|
await Client.ConversationController.FetchReciptTimestamp(Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool IsTemporayConversation(string convId) {
|
||||||
|
return convId.StartsWith("_tmp:");
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void MergeFrom(Dictionary<string, object> conv) {
|
||||||
|
if (conv.TryGetValue("objectId", out object idObj)) {
|
||||||
|
Id = idObj as string;
|
||||||
|
}
|
||||||
|
if (conv.TryGetValue("unique", out object uniqueObj)) {
|
||||||
|
Unique = (bool)uniqueObj;
|
||||||
|
}
|
||||||
|
if (conv.TryGetValue("uniqueId", out object uniqueIdObj)) {
|
||||||
|
UniqueId = uniqueIdObj as string;
|
||||||
|
}
|
||||||
|
if (conv.TryGetValue("createdAt", out object createdAtObj)) {
|
||||||
|
CreatedAt = DateTime.Parse(createdAtObj.ToString());
|
||||||
|
}
|
||||||
|
if (conv.TryGetValue("updatedAt", out object updatedAtObj)) {
|
||||||
|
UpdatedAt = DateTime.Parse(updatedAtObj.ToString());
|
||||||
|
}
|
||||||
|
if (conv.TryGetValue("c", out object co)) {
|
||||||
|
CreatorId = co as string;
|
||||||
|
}
|
||||||
|
if (conv.TryGetValue("m", out object mo)) {
|
||||||
|
IEnumerable<string> ids = (mo as IList<object>).Cast<string>();
|
||||||
|
this.ids = new HashSet<string>(ids);
|
||||||
|
}
|
||||||
|
if (conv.TryGetValue("mu", out object muo)) {
|
||||||
|
IEnumerable<string> ids = (muo as IList<object>).Cast<string>();
|
||||||
|
mutedIds = new HashSet<string>(ids);
|
||||||
|
}
|
||||||
|
//if (conv.TryGetValue("lm", out object lmo)) {
|
||||||
|
// LastMessageAt = (DateTime)LCDecoder.Decode(lmo);
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void MergeInfo(Dictionary<string, object> attr) {
|
||||||
|
if (attr == null || attr.Count == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach (KeyValuePair<string, object> kv in attr) {
|
||||||
|
customProperties[kv.Key] = kv.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
namespace LeanCloud.Realtime {
|
||||||
|
public class LCIMConversationMemberInfo {
|
||||||
|
/// <summary>
|
||||||
|
/// 群主
|
||||||
|
/// </summary>
|
||||||
|
public const string Owner = "Owner";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 管理员
|
||||||
|
/// </summary>
|
||||||
|
public const string Manager = "Manager";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 成员
|
||||||
|
/// </summary>
|
||||||
|
public const string Member = "Member";
|
||||||
|
|
||||||
|
public string ConversationId {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string MemberId {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Role {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsOwner {
|
||||||
|
get {
|
||||||
|
return Role == Owner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsManager {
|
||||||
|
get {
|
||||||
|
return Role == Manager;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,253 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using LeanCloud.Storage.Internal.Query;
|
||||||
|
|
||||||
|
namespace LeanCloud.Realtime {
|
||||||
|
public class LCIMConversationQuery {
|
||||||
|
internal LCCompositionalCondition Condition {
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly LCIMClient client;
|
||||||
|
|
||||||
|
public LCIMConversationQuery(LCIMClient client) {
|
||||||
|
Condition = new LCCompositionalCondition();
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 等于
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public LCIMConversationQuery WhereEqualTo(string key,
|
||||||
|
object value) {
|
||||||
|
Condition.WhereEqualTo(key, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 不等于
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public LCIMConversationQuery WhereNotEqualTo(string key,
|
||||||
|
object value) {
|
||||||
|
Condition.WhereNotEqualTo(key, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 包含
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="values"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public LCIMConversationQuery WhereContainedIn(string key,
|
||||||
|
IEnumerable values) {
|
||||||
|
Condition.WhereContainedIn(key, values);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 包含全部
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="values"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public LCIMConversationQuery WhereContainsAll(string key,
|
||||||
|
IEnumerable values) {
|
||||||
|
Condition.WhereContainsAll(key, values);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 存在
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public LCIMConversationQuery WhereExists(string key) {
|
||||||
|
Condition.WhereExists(key);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 不存在
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public LCIMConversationQuery WhereDoesNotExist(string key) {
|
||||||
|
Condition.WhereDoesNotExist(key);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 长度等于
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="size"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public LCIMConversationQuery WhereSizeEqualTo(string key,
|
||||||
|
int size) {
|
||||||
|
Condition.WhereSizeEqualTo(key, size);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 大于
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public LCIMConversationQuery WhereGreaterThan(string key,
|
||||||
|
object value) {
|
||||||
|
Condition.WhereGreaterThan(key, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 大于等于
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public LCIMConversationQuery WhereGreaterThanOrEqualTo(string key,
|
||||||
|
object value) {
|
||||||
|
Condition.WhereGreaterThanOrEqualTo(key, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 小于
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public LCIMConversationQuery WhereLessThan(string key,
|
||||||
|
object value) {
|
||||||
|
Condition.WhereLessThan(key, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 小于等于
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public LCIMConversationQuery WhereLessThanOrEqualTo(string key,
|
||||||
|
object value) {
|
||||||
|
Condition.WhereLessThanOrEqualTo(key, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 前缀
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="prefix"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public LCIMConversationQuery WhereStartsWith(string key,
|
||||||
|
string prefix) {
|
||||||
|
Condition.WhereStartsWith(key, prefix);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 后缀
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="suffix"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public LCIMConversationQuery WhereEndsWith(string key, string suffix) {
|
||||||
|
Condition.WhereEndsWith(key, suffix);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 字符串包含
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="subString"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public LCIMConversationQuery WhereContains(string key, string subString) {
|
||||||
|
Condition.WhereContains(key, subString);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 按 key 升序
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public LCIMConversationQuery OrderBy(string key) {
|
||||||
|
Condition.OrderBy(key);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 按 key 降序
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public LCIMConversationQuery OrderByDescending(string key) {
|
||||||
|
Condition.OrderByDescending(key);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 拉取 key 的完整对象
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public LCIMConversationQuery Include(string key) {
|
||||||
|
Condition.Include(key);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 包含 key
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public LCIMConversationQuery Select(string key) {
|
||||||
|
Condition.Select(key);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 跳过
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public LCIMConversationQuery Skip(int value) {
|
||||||
|
Condition.Skip = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 限制数量
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public LCIMConversationQuery Limit(int value) {
|
||||||
|
Condition.Limit = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool WithLastMessageRefreshed {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<ReadOnlyCollection<LCIMConversation>> Find() {
|
||||||
|
return await client.ConversationController.Find(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace LeanCloud.Realtime {
|
||||||
|
public class LCIMMessageQueryEndpoint {
|
||||||
|
public string MessageId {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long SentTimestamp {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsClosed {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LCIMMessageQueryEndpoint() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum LCIMMessageQueryDirection {
|
||||||
|
NewToOld,
|
||||||
|
OldToNew
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace LeanCloud.Realtime {
|
||||||
|
/// <summary>
|
||||||
|
/// 系统对话
|
||||||
|
/// </summary>
|
||||||
|
public class LCIMServiceConversation : LCIMConversation {
|
||||||
|
public LCIMServiceConversation(LCIMClient client) : base(client) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Subscribe() {
|
||||||
|
await Join();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Unsubscribe() {
|
||||||
|
await Quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> CheckSubscription() {
|
||||||
|
return await Client.ConversationController.CheckSubscription(Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace LeanCloud.Realtime {
|
||||||
|
/// <summary>
|
||||||
|
/// 临时对话
|
||||||
|
/// </summary>
|
||||||
|
public class LCIMTemporaryConversation : LCIMConversation {
|
||||||
|
/// <summary>
|
||||||
|
/// 过期时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime ExpiredAt {
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否过期
|
||||||
|
/// </summary>
|
||||||
|
public bool IsExpired {
|
||||||
|
get {
|
||||||
|
return DateTime.Now > ExpiredAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LCIMTemporaryConversation(LCIMClient client) : base(client) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,239 @@
|
||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Google.Protobuf;
|
||||||
|
using LeanCloud.Realtime.Internal.Router;
|
||||||
|
using LeanCloud.Realtime.Internal.WebSocket;
|
||||||
|
using LeanCloud.Realtime.Internal.Protocol;
|
||||||
|
using LeanCloud.Storage;
|
||||||
|
|
||||||
|
namespace LeanCloud.Realtime.Internal.Connection {
|
||||||
|
/// <summary>
|
||||||
|
/// 连接层,只与数据协议相关
|
||||||
|
/// </summary>
|
||||||
|
internal class LCConnection {
|
||||||
|
/// <summary>
|
||||||
|
/// 发送超时
|
||||||
|
/// </summary>
|
||||||
|
private const int SEND_TIMEOUT = 10000;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 最大重连次数,超过后重置 Router 缓存后再次尝试重连
|
||||||
|
/// </summary>
|
||||||
|
private const int MAX_RECONNECT_TIMES = 10;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 重连间隔
|
||||||
|
/// </summary>
|
||||||
|
private const int RECONNECT_INTERVAL = 10000;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 心跳间隔
|
||||||
|
/// </summary>
|
||||||
|
private const int HEART_BEAT_INTERVAL = 30000;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 通知事件
|
||||||
|
/// </summary>
|
||||||
|
internal Action<GenericCommand> OnNotification;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 断线事件
|
||||||
|
/// </summary>
|
||||||
|
internal Action OnDisconnect;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 重连成功事件
|
||||||
|
/// </summary>
|
||||||
|
internal Action OnReconnected;
|
||||||
|
|
||||||
|
internal string id;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 请求回调缓存
|
||||||
|
/// </summary>
|
||||||
|
private readonly Dictionary<int, TaskCompletionSource<GenericCommand>> responses;
|
||||||
|
|
||||||
|
private int requestI = 1;
|
||||||
|
|
||||||
|
private LCRTMRouter router;
|
||||||
|
|
||||||
|
private LCHeartBeat heartBeat;
|
||||||
|
|
||||||
|
private LCWebSocketClient client;
|
||||||
|
|
||||||
|
internal LCConnection(string id) {
|
||||||
|
this.id = id;
|
||||||
|
responses = new Dictionary<int, TaskCompletionSource<GenericCommand>>();
|
||||||
|
heartBeat = new LCHeartBeat(this, HEART_BEAT_INTERVAL, HEART_BEAT_INTERVAL);
|
||||||
|
router = new LCRTMRouter();
|
||||||
|
client = new LCWebSocketClient {
|
||||||
|
OnMessage = OnClientMessage,
|
||||||
|
OnClose = OnClientDisconnect
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task Connect() {
|
||||||
|
try {
|
||||||
|
LCRTMServer rtmServer = await router.GetServer();
|
||||||
|
try {
|
||||||
|
LCLogger.Debug($"Primary Server");
|
||||||
|
await client.Connect(rtmServer.Primary);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LCLogger.Error(e);
|
||||||
|
LCLogger.Debug($"Secondary Server");
|
||||||
|
await client.Connect(rtmServer.Secondary);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 重置连接
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task Reset() {
|
||||||
|
// 关闭就连接
|
||||||
|
await client.Close();
|
||||||
|
// 重新创建连接组件
|
||||||
|
heartBeat = new LCHeartBeat(this, HEART_BEAT_INTERVAL, HEART_BEAT_INTERVAL);
|
||||||
|
router = new LCRTMRouter();
|
||||||
|
client = new LCWebSocketClient {
|
||||||
|
OnMessage = OnClientMessage,
|
||||||
|
OnClose = OnClientDisconnect
|
||||||
|
};
|
||||||
|
await Reconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送请求,会在收到应答后返回
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task<GenericCommand> SendRequest(GenericCommand request) {
|
||||||
|
TaskCompletionSource<GenericCommand> tcs = new TaskCompletionSource<GenericCommand>();
|
||||||
|
request.I = requestI++;
|
||||||
|
responses.Add(request.I, tcs);
|
||||||
|
try {
|
||||||
|
await SendCommand(request);
|
||||||
|
} catch (Exception e) {
|
||||||
|
tcs.TrySetException(e);
|
||||||
|
}
|
||||||
|
return await tcs.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送命令
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="command"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task SendCommand(GenericCommand command) {
|
||||||
|
LCLogger.Debug($"{id} => {FormatCommand(command)}");
|
||||||
|
byte[] bytes = command.ToByteArray();
|
||||||
|
Task sendTask = client.Send(bytes);
|
||||||
|
if (await Task.WhenAny(sendTask, Task.Delay(SEND_TIMEOUT)) == sendTask) {
|
||||||
|
await sendTask;
|
||||||
|
} else {
|
||||||
|
throw new TimeoutException("Send request");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 关闭连接
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task Close() {
|
||||||
|
OnNotification = null;
|
||||||
|
OnDisconnect = null;
|
||||||
|
OnReconnected = null;
|
||||||
|
heartBeat.Stop();
|
||||||
|
await client.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnClientMessage(byte[] bytes) {
|
||||||
|
_ = heartBeat.Refresh(OnPingTimeout);
|
||||||
|
try {
|
||||||
|
GenericCommand command = GenericCommand.Parser.ParseFrom(bytes);
|
||||||
|
LCLogger.Debug($"{id} <= {FormatCommand(command)}");
|
||||||
|
if (command.HasI) {
|
||||||
|
// 应答
|
||||||
|
int requestIndex = command.I;
|
||||||
|
if (responses.TryGetValue(requestIndex, out TaskCompletionSource<GenericCommand> tcs)) {
|
||||||
|
if (command.HasErrorMessage) {
|
||||||
|
// 错误
|
||||||
|
ErrorCommand error = command.ErrorMessage;
|
||||||
|
int code = error.Code;
|
||||||
|
string detail = error.Detail;
|
||||||
|
// 包装成异常抛出
|
||||||
|
LCException exception = new LCException(code, detail);
|
||||||
|
tcs.TrySetException(exception);
|
||||||
|
} else {
|
||||||
|
tcs.TrySetResult(command);
|
||||||
|
}
|
||||||
|
responses.Remove(requestIndex);
|
||||||
|
} else {
|
||||||
|
LCLogger.Error($"No request for {requestIndex}");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 通知
|
||||||
|
OnNotification?.Invoke(command);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LCLogger.Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnClientDisconnect() {
|
||||||
|
heartBeat.Stop();
|
||||||
|
OnDisconnect?.Invoke();
|
||||||
|
// 重连
|
||||||
|
_ = Reconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnPingTimeout() {
|
||||||
|
await client.Close();
|
||||||
|
OnClientDisconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Reconnect() {
|
||||||
|
while (true) {
|
||||||
|
int reconnectCount = 0;
|
||||||
|
// 重连策略
|
||||||
|
while (reconnectCount < MAX_RECONNECT_TIMES) {
|
||||||
|
try {
|
||||||
|
LCLogger.Debug($"Reconnecting... {reconnectCount}");
|
||||||
|
await Connect();
|
||||||
|
break;
|
||||||
|
} catch (Exception e) {
|
||||||
|
reconnectCount++;
|
||||||
|
LCLogger.Error(e);
|
||||||
|
LCLogger.Debug($"Reconnect after {RECONNECT_INTERVAL}ms");
|
||||||
|
await Task.Delay(RECONNECT_INTERVAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (reconnectCount < MAX_RECONNECT_TIMES) {
|
||||||
|
// 重连成功
|
||||||
|
LCLogger.Debug("Reconnected");
|
||||||
|
client.OnMessage = OnClientMessage;
|
||||||
|
client.OnClose = OnClientDisconnect;
|
||||||
|
OnReconnected?.Invoke();
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// 重置 Router,继续尝试重连
|
||||||
|
router = new LCRTMRouter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatCommand(GenericCommand command) {
|
||||||
|
StringBuilder sb = new StringBuilder($"{command.Cmd}");
|
||||||
|
if (command.HasOp) {
|
||||||
|
sb.Append($"/{command.Op}");
|
||||||
|
}
|
||||||
|
sb.Append($"\n{command}");
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using LeanCloud.Realtime.Internal.Protocol;
|
||||||
|
|
||||||
|
namespace LeanCloud.Realtime.Internal.Connection {
|
||||||
|
/// <summary>
|
||||||
|
/// 心跳控制器,由于 .Net Standard 2.0 不支持发送 ping frame,所以需要发送逻辑心跳
|
||||||
|
/// 1. 每次接收到消息后开始监听,如果在 pingInterval 时间内没有再次接收到消息,则发送 ping 请求;
|
||||||
|
/// 2. 发送后等待 pongInterval 时间,如果在此时间内接收到了任何消息,则取消并重新开始监听 1;
|
||||||
|
/// 3. 如果没收到消息,则认为超时并回调,连接层接收回调后放弃当前连接,以断线逻辑处理
|
||||||
|
/// </summary>
|
||||||
|
internal class LCHeartBeat {
|
||||||
|
private readonly LCConnection connection;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ping 间隔
|
||||||
|
/// </summary>
|
||||||
|
private readonly int pingInterval;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// pong 间隔
|
||||||
|
/// </summary>
|
||||||
|
private readonly int pongInterval;
|
||||||
|
|
||||||
|
private CancellationTokenSource pingCTS;
|
||||||
|
private CancellationTokenSource pongCTS;
|
||||||
|
|
||||||
|
internal LCHeartBeat(LCConnection connection,
|
||||||
|
int pingInterval,
|
||||||
|
int pongInterval) {
|
||||||
|
this.connection = connection;
|
||||||
|
this.pingInterval = pingInterval;
|
||||||
|
this.pongInterval = pongInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新心跳监听
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task Refresh(Action onTimeout) {
|
||||||
|
LCLogger.Debug("HeartBeat refresh");
|
||||||
|
pingCTS?.Cancel();
|
||||||
|
pongCTS?.Cancel();
|
||||||
|
|
||||||
|
// 计时准备 ping
|
||||||
|
pingCTS = new CancellationTokenSource();
|
||||||
|
Task delayTask = Task.Delay(pingInterval, pingCTS.Token);
|
||||||
|
await delayTask;
|
||||||
|
if (delayTask.IsCanceled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 发送 ping 包
|
||||||
|
LCLogger.Debug("Ping ~~~");
|
||||||
|
GenericCommand command = new GenericCommand {
|
||||||
|
Cmd = CommandType.Echo,
|
||||||
|
AppId = LCApplication.AppId,
|
||||||
|
PeerId = connection.id
|
||||||
|
};
|
||||||
|
_ = connection.SendRequest(command);
|
||||||
|
pongCTS = new CancellationTokenSource();
|
||||||
|
Task timeoutTask = Task.Delay(pongInterval, pongCTS.Token);
|
||||||
|
await timeoutTask;
|
||||||
|
if (timeoutTask.IsCanceled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// timeout
|
||||||
|
LCLogger.Error("Ping timeout");
|
||||||
|
onTimeout.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 停止心跳监听
|
||||||
|
/// </summary>
|
||||||
|
internal void Stop() {
|
||||||
|
pingCTS?.Cancel();
|
||||||
|
pongCTS?.Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using LeanCloud.Realtime.Internal.Protocol;
|
||||||
|
using LeanCloud.Realtime.Internal.Connection;
|
||||||
|
|
||||||
|
namespace LeanCloud.Realtime.Internal.Controller {
|
||||||
|
internal abstract class LCIMController {
|
||||||
|
protected LCIMClient Client {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal LCIMController(LCIMClient client) {
|
||||||
|
Client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal abstract Task OnNotification(GenericCommand notification);
|
||||||
|
|
||||||
|
protected LCConnection Connection {
|
||||||
|
get {
|
||||||
|
return Client.Connection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected GenericCommand NewCommand(CommandType cmd, OpType op) {
|
||||||
|
GenericCommand command = NewCommand(cmd);
|
||||||
|
command.Op = op;
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected GenericCommand NewCommand(CommandType cmd) {
|
||||||
|
return new GenericCommand {
|
||||||
|
Cmd = cmd,
|
||||||
|
AppId = LCApplication.AppId,
|
||||||
|
PeerId = Client.Id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,824 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using LeanCloud.Realtime.Internal.Protocol;
|
||||||
|
using LeanCloud.Storage.Internal;
|
||||||
|
using LeanCloud.Storage.Internal.Codec;
|
||||||
|
using LeanCloud.Common;
|
||||||
|
|
||||||
|
namespace LeanCloud.Realtime.Internal.Controller {
|
||||||
|
internal class LCIMConversationController : LCIMController {
|
||||||
|
internal LCIMConversationController(LCIMClient client) : base(client) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 内部接口
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建对话
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="members"></param>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="transient"></param>
|
||||||
|
/// <param name="unique"></param>
|
||||||
|
/// <param name="temporary"></param>
|
||||||
|
/// <param name="temporaryTtl"></param>
|
||||||
|
/// <param name="properties"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task<LCIMConversation> CreateConv(
|
||||||
|
IEnumerable<string> members = null,
|
||||||
|
string name = null,
|
||||||
|
bool transient = false,
|
||||||
|
bool unique = true,
|
||||||
|
bool temporary = false,
|
||||||
|
int temporaryTtl = 86400,
|
||||||
|
Dictionary<string, object> properties = null) {
|
||||||
|
GenericCommand request = NewCommand(CommandType.Conv, OpType.Start);
|
||||||
|
ConvCommand conv = new ConvCommand {
|
||||||
|
Transient = transient,
|
||||||
|
Unique = unique,
|
||||||
|
};
|
||||||
|
if (members != null) {
|
||||||
|
conv.M.AddRange(members);
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(name)) {
|
||||||
|
conv.N = name;
|
||||||
|
}
|
||||||
|
if (temporary) {
|
||||||
|
conv.TempConv = temporary;
|
||||||
|
conv.TempConvTTL = temporaryTtl;
|
||||||
|
}
|
||||||
|
if (properties != null) {
|
||||||
|
conv.Attr = new JsonObjectMessage {
|
||||||
|
Data = JsonConvert.SerializeObject(LCEncoder.Encode(properties))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (Client.SignatureFactory != null) {
|
||||||
|
LCIMSignature signature = await Client.SignatureFactory.CreateStartConversationSignature(Client.Id, members);
|
||||||
|
conv.S = signature.Signature;
|
||||||
|
conv.T = signature.Timestamp;
|
||||||
|
conv.N = signature.Nonce;
|
||||||
|
}
|
||||||
|
request.ConvMessage = conv;
|
||||||
|
GenericCommand response = await Connection.SendRequest(request);
|
||||||
|
string convId = response.ConvMessage.Cid;
|
||||||
|
if (!Client.ConversationDict.TryGetValue(convId, out LCIMConversation conversation)) {
|
||||||
|
if (transient) {
|
||||||
|
conversation = new LCIMChatRoom(Client);
|
||||||
|
} else if (temporary) {
|
||||||
|
conversation = new LCIMTemporaryConversation(Client);
|
||||||
|
} else if (properties != null && properties.ContainsKey("system")) {
|
||||||
|
conversation = new LCIMServiceConversation(Client);
|
||||||
|
} else {
|
||||||
|
conversation = new LCIMConversation(Client);
|
||||||
|
}
|
||||||
|
Client.ConversationDict[convId] = conversation;
|
||||||
|
}
|
||||||
|
// 合并请求数据
|
||||||
|
conversation.Id = convId;
|
||||||
|
conversation.Unique = unique;
|
||||||
|
conversation.UniqueId = response.ConvMessage.UniqueId;
|
||||||
|
conversation.Name = name;
|
||||||
|
conversation.CreatorId = Client.Id;
|
||||||
|
conversation.ids = members != null ?
|
||||||
|
new HashSet<string>(members) : new HashSet<string>();
|
||||||
|
conversation.CreatedAt = DateTime.Parse(response.ConvMessage.Cdate);
|
||||||
|
conversation.UpdatedAt = conversation.CreatedAt;
|
||||||
|
return conversation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询成员数量
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task<int> GetMembersCount(string convId) {
|
||||||
|
ConvCommand conv = new ConvCommand {
|
||||||
|
Cid = convId,
|
||||||
|
};
|
||||||
|
GenericCommand command = NewCommand(CommandType.Conv, OpType.Count);
|
||||||
|
command.ConvMessage = conv;
|
||||||
|
GenericCommand response = await Connection.SendRequest(command);
|
||||||
|
return response.ConvMessage.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新对话属性
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convId"></param>
|
||||||
|
/// <param name="attributes"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task<Dictionary<string, object>> UpdateInfo(string convId,
|
||||||
|
Dictionary<string, object> attributes) {
|
||||||
|
ConvCommand conv = new ConvCommand {
|
||||||
|
Cid = convId,
|
||||||
|
};
|
||||||
|
conv.Attr = new JsonObjectMessage {
|
||||||
|
Data = JsonConvert.SerializeObject(attributes)
|
||||||
|
};
|
||||||
|
GenericCommand request = NewCommand(CommandType.Conv, OpType.Update);
|
||||||
|
request.ConvMessage = conv;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
JsonObjectMessage attr = response.ConvMessage.AttrModified;
|
||||||
|
// 更新自定义属性
|
||||||
|
if (attr != null) {
|
||||||
|
Dictionary<string, object> updatedAttr = JsonConvert.DeserializeObject<Dictionary<string, object>>(attr.Data);
|
||||||
|
return updatedAttr;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 增加成员
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convId"></param>
|
||||||
|
/// <param name="clientIds"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task<LCIMPartiallySuccessResult> AddMembers(string convId,
|
||||||
|
IEnumerable<string> clientIds) {
|
||||||
|
ConvCommand conv = new ConvCommand {
|
||||||
|
Cid = convId,
|
||||||
|
};
|
||||||
|
conv.M.AddRange(clientIds);
|
||||||
|
// 签名参数
|
||||||
|
if (Client.SignatureFactory != null) {
|
||||||
|
LCIMSignature signature = await Client.SignatureFactory.CreateConversationSignature(convId,
|
||||||
|
Client.Id,
|
||||||
|
clientIds,
|
||||||
|
LCIMSignatureAction.Invite);
|
||||||
|
conv.S = signature.Signature;
|
||||||
|
conv.T = signature.Timestamp;
|
||||||
|
conv.N = signature.Nonce;
|
||||||
|
}
|
||||||
|
GenericCommand request = NewCommand(CommandType.Conv, OpType.Add);
|
||||||
|
request.ConvMessage = conv;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
List<string> allowedIds = response.ConvMessage.AllowedPids.ToList();
|
||||||
|
List<ErrorCommand> errors = response.ConvMessage.FailedPids.ToList();
|
||||||
|
return NewPartiallySuccessResult(allowedIds, errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 移除成员
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convId"></param>
|
||||||
|
/// <param name="removeIds"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task<LCIMPartiallySuccessResult> RemoveMembers(string convId,
|
||||||
|
IEnumerable<string> removeIds) {
|
||||||
|
ConvCommand conv = new ConvCommand {
|
||||||
|
Cid = convId,
|
||||||
|
};
|
||||||
|
conv.M.AddRange(removeIds);
|
||||||
|
// 签名参数
|
||||||
|
if (Client.SignatureFactory != null) {
|
||||||
|
LCIMSignature signature = await Client.SignatureFactory.CreateConversationSignature(convId,
|
||||||
|
Client.Id,
|
||||||
|
removeIds,
|
||||||
|
LCIMSignatureAction.Kick);
|
||||||
|
conv.S = signature.Signature;
|
||||||
|
conv.T = signature.Timestamp;
|
||||||
|
conv.N = signature.Nonce;
|
||||||
|
}
|
||||||
|
GenericCommand request = NewCommand(CommandType.Conv, OpType.Remove);
|
||||||
|
request.ConvMessage = conv;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
List<string> allowedIds = response.ConvMessage.AllowedPids.ToList();
|
||||||
|
List<ErrorCommand> errors = response.ConvMessage.FailedPids.ToList();
|
||||||
|
return NewPartiallySuccessResult(allowedIds, errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 静音
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task Mute(string convId) {
|
||||||
|
ConvCommand conv = new ConvCommand {
|
||||||
|
Cid = convId
|
||||||
|
};
|
||||||
|
GenericCommand request = NewCommand(CommandType.Conv, OpType.Mute);
|
||||||
|
request.ConvMessage = conv;
|
||||||
|
await Client.Connection.SendRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解除静音
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task Unmute(string convId) {
|
||||||
|
ConvCommand conv = new ConvCommand {
|
||||||
|
Cid = convId
|
||||||
|
};
|
||||||
|
GenericCommand request = NewCommand(CommandType.Conv, OpType.Unmute);
|
||||||
|
request.ConvMessage = conv;
|
||||||
|
await Client.Connection.SendRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 禁言用户
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convId"></param>
|
||||||
|
/// <param name="clientIds"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task<LCIMPartiallySuccessResult> MuteMembers(string convId,
|
||||||
|
IEnumerable<string> clientIds) {
|
||||||
|
if (clientIds == null || clientIds.Count() == 0) {
|
||||||
|
throw new ArgumentNullException(nameof(clientIds));
|
||||||
|
}
|
||||||
|
ConvCommand conv = new ConvCommand {
|
||||||
|
Cid = convId
|
||||||
|
};
|
||||||
|
conv.M.AddRange(clientIds);
|
||||||
|
GenericCommand request = NewCommand(CommandType.Conv, OpType.AddShutup);
|
||||||
|
request.ConvMessage = conv;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
return NewPartiallySuccessResult(response.ConvMessage.AllowedPids, response.ConvMessage.FailedPids);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解除用户禁言
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convId"></param>
|
||||||
|
/// <param name="clientIds"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task<LCIMPartiallySuccessResult> UnmuteMembers(string convId,
|
||||||
|
IEnumerable<string> clientIds) {
|
||||||
|
ConvCommand conv = new ConvCommand {
|
||||||
|
Cid = convId
|
||||||
|
};
|
||||||
|
conv.M.AddRange(clientIds);
|
||||||
|
GenericCommand request = NewCommand(CommandType.Conv, OpType.RemoveShutup);
|
||||||
|
request.ConvMessage = conv;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
return NewPartiallySuccessResult(response.ConvMessage.AllowedPids, response.ConvMessage.FailedPids);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 拉黑成员
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convId"></param>
|
||||||
|
/// <param name="clientIds"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task<LCIMPartiallySuccessResult> BlockMembers(string convId,
|
||||||
|
IEnumerable<string> clientIds) {
|
||||||
|
BlacklistCommand blacklist = new BlacklistCommand {
|
||||||
|
SrcCid = convId,
|
||||||
|
};
|
||||||
|
blacklist.ToPids.AddRange(clientIds);
|
||||||
|
if (Client.SignatureFactory != null) {
|
||||||
|
LCIMSignature signature = await Client.SignatureFactory.CreateBlacklistSignature(convId,
|
||||||
|
Client.Id,
|
||||||
|
clientIds,
|
||||||
|
LCIMSignatureAction.ConversationBlockClients);
|
||||||
|
blacklist.S = signature.Signature;
|
||||||
|
blacklist.T = signature.Timestamp;
|
||||||
|
blacklist.N = signature.Nonce;
|
||||||
|
}
|
||||||
|
GenericCommand request = NewCommand(CommandType.Blacklist, OpType.Block);
|
||||||
|
request.BlacklistMessage = blacklist;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
return NewPartiallySuccessResult(response.BlacklistMessage.AllowedPids, response.BlacklistMessage.FailedPids);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 移除成员黑名单
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convId"></param>
|
||||||
|
/// <param name="clientIds"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task<LCIMPartiallySuccessResult> UnblockMembers(string convId,
|
||||||
|
IEnumerable<string> clientIds) {
|
||||||
|
BlacklistCommand blacklist = new BlacklistCommand {
|
||||||
|
SrcCid = convId,
|
||||||
|
};
|
||||||
|
blacklist.ToPids.AddRange(clientIds);
|
||||||
|
if (Client.SignatureFactory != null) {
|
||||||
|
LCIMSignature signature = await Client.SignatureFactory.CreateBlacklistSignature(convId,
|
||||||
|
Client.Id,
|
||||||
|
clientIds,
|
||||||
|
LCIMSignatureAction.ConversationUnblockClients);
|
||||||
|
blacklist.S = signature.Signature;
|
||||||
|
blacklist.T = signature.Timestamp;
|
||||||
|
blacklist.N = signature.Nonce;
|
||||||
|
}
|
||||||
|
GenericCommand request = NewCommand(CommandType.Blacklist, OpType.Unblock);
|
||||||
|
request.BlacklistMessage = blacklist;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
return NewPartiallySuccessResult(response.BlacklistMessage.AllowedPids, response.BlacklistMessage.FailedPids);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 修改成员角色
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convId"></param>
|
||||||
|
/// <param name="memberId"></param>
|
||||||
|
/// <param name="role"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task UpdateMemberRole(string convId,
|
||||||
|
string memberId,
|
||||||
|
string role) {
|
||||||
|
ConvCommand conv = new ConvCommand {
|
||||||
|
Cid = convId,
|
||||||
|
TargetClientId = memberId,
|
||||||
|
Info = new ConvMemberInfo {
|
||||||
|
Pid = memberId,
|
||||||
|
Role = role
|
||||||
|
}
|
||||||
|
};
|
||||||
|
GenericCommand request = NewCommand(CommandType.Conv, OpType.MemberInfoUpdate);
|
||||||
|
request.ConvMessage = conv;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取所有成员角色
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task<ReadOnlyCollection<LCIMConversationMemberInfo>> GetAllMemberInfo(string convId) {
|
||||||
|
string path = "classes/_ConversationMemberInfo";
|
||||||
|
string token = await Client.SessionController.GetToken();
|
||||||
|
Dictionary<string, object> headers = new Dictionary<string, object> {
|
||||||
|
{ "X-LC-IM-Session-Token", token }
|
||||||
|
};
|
||||||
|
Dictionary<string, object> queryParams = new Dictionary<string, object> {
|
||||||
|
{ "client_id", Client.Id },
|
||||||
|
{ "cid", convId }
|
||||||
|
};
|
||||||
|
Dictionary<string, object> response = await LCApplication.HttpClient.Get<Dictionary<string, object>>(path,
|
||||||
|
headers: headers, queryParams: queryParams);
|
||||||
|
List<object> results = response["results"] as List<object>;
|
||||||
|
return results.Select(item => {
|
||||||
|
Dictionary<string, object> memberInfo = item as Dictionary<string, object>;
|
||||||
|
return new LCIMConversationMemberInfo {
|
||||||
|
ConversationId = memberInfo["cid"] as string,
|
||||||
|
MemberId = memberInfo["clientId"] as string,
|
||||||
|
Role = memberInfo["role"] as string
|
||||||
|
};
|
||||||
|
}).ToList().AsReadOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询禁言成员
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convId"></param>
|
||||||
|
/// <param name="limit"></param>
|
||||||
|
/// <param name="next"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task<LCIMPageResult> QueryMutedMembers(string convId,
|
||||||
|
int limit = 10,
|
||||||
|
string next = null) {
|
||||||
|
ConvCommand conv = new ConvCommand {
|
||||||
|
Cid = convId,
|
||||||
|
Limit = limit
|
||||||
|
};
|
||||||
|
if (next != null) {
|
||||||
|
conv.Next = next;
|
||||||
|
}
|
||||||
|
GenericCommand request = NewCommand(CommandType.Conv, OpType.QueryShutup);
|
||||||
|
request.ConvMessage = conv;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
return new LCIMPageResult {
|
||||||
|
Results = new ReadOnlyCollection<string>(response.ConvMessage.M),
|
||||||
|
Next = response.ConvMessage.Next
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询黑名单用户
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convId"></param>
|
||||||
|
/// <param name="limit"></param>
|
||||||
|
/// <param name="next"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task<LCIMPageResult> QueryBlockedMembers(string convId,
|
||||||
|
int limit = 10,
|
||||||
|
string next = null) {
|
||||||
|
BlacklistCommand black = new BlacklistCommand {
|
||||||
|
SrcCid = convId,
|
||||||
|
Limit = limit
|
||||||
|
};
|
||||||
|
if (next != null) {
|
||||||
|
black.Next = next;
|
||||||
|
}
|
||||||
|
GenericCommand request = NewCommand(CommandType.Blacklist, OpType.Query);
|
||||||
|
request.BlacklistMessage = black;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
return new LCIMPageResult {
|
||||||
|
Results = new ReadOnlyCollection<string>(response.BlacklistMessage.BlockedPids),
|
||||||
|
Next = response.BlacklistMessage.Next
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="query"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task<ReadOnlyCollection<LCIMConversation>> Find(LCIMConversationQuery query) {
|
||||||
|
GenericCommand command = new GenericCommand {
|
||||||
|
Cmd = CommandType.Conv,
|
||||||
|
Op = OpType.Query,
|
||||||
|
AppId = LCApplication.AppId,
|
||||||
|
PeerId = Client.Id,
|
||||||
|
};
|
||||||
|
ConvCommand convMessage = new ConvCommand();
|
||||||
|
string where = query.Condition.BuildWhere();
|
||||||
|
if (!string.IsNullOrEmpty(where)) {
|
||||||
|
try {
|
||||||
|
convMessage.Where = new JsonObjectMessage {
|
||||||
|
Data = where
|
||||||
|
};
|
||||||
|
} catch (Exception e) {
|
||||||
|
LCLogger.Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
command.ConvMessage = convMessage;
|
||||||
|
GenericCommand response = await Connection.SendRequest(command);
|
||||||
|
JsonObjectMessage results = response.ConvMessage.Results;
|
||||||
|
List<object> convs = JsonConvert.DeserializeObject<List<object>>(results.Data, new LCJsonConverter());
|
||||||
|
return convs.Select(item => {
|
||||||
|
Dictionary<string, object> conv = item as Dictionary<string, object>;
|
||||||
|
string convId = conv["objectId"] as string;
|
||||||
|
if (!Client.ConversationDict.TryGetValue(convId, out LCIMConversation conversation)) {
|
||||||
|
// 解析是哪种类型的对话
|
||||||
|
if (conv.TryGetValue("tr", out object transient) && (bool)transient == true) {
|
||||||
|
conversation = new LCIMChatRoom(Client);
|
||||||
|
} else if (conv.ContainsKey("tempConv") && conv.ContainsKey("tempConvTTL")) {
|
||||||
|
conversation = new LCIMTemporaryConversation(Client);
|
||||||
|
} else if (conv.TryGetValue("sys", out object sys) && (bool)sys == true) {
|
||||||
|
conversation = new LCIMServiceConversation(Client);
|
||||||
|
} else {
|
||||||
|
conversation = new LCIMConversation(Client);
|
||||||
|
}
|
||||||
|
Client.ConversationDict[convId] = conversation;
|
||||||
|
}
|
||||||
|
conversation.MergeFrom(conv);
|
||||||
|
return conversation;
|
||||||
|
}).ToList().AsReadOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取临时对话
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convIds"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task<List<LCIMTemporaryConversation>> GetTemporaryConversations(IEnumerable<string> convIds) {
|
||||||
|
if (convIds == null || convIds.Count() == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ConvCommand convMessage = new ConvCommand();
|
||||||
|
convMessage.TempConvIds.AddRange(convIds);
|
||||||
|
GenericCommand request = NewCommand(CommandType.Conv, OpType.Query);
|
||||||
|
request.ConvMessage = convMessage;
|
||||||
|
GenericCommand response = await Connection.SendRequest(request);
|
||||||
|
JsonObjectMessage results = response.ConvMessage.Results;
|
||||||
|
List<object> convs = JsonConvert.DeserializeObject<List<object>>(results.Data, new LCJsonConverter());
|
||||||
|
List<LCIMTemporaryConversation> convList = convs.Select(item => {
|
||||||
|
LCIMTemporaryConversation temporaryConversation = new LCIMTemporaryConversation(Client);
|
||||||
|
temporaryConversation.MergeFrom(item as Dictionary<string, object>);
|
||||||
|
return temporaryConversation;
|
||||||
|
}).ToList();
|
||||||
|
return convList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 拉取对话接收/已读情况
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task FetchReciptTimestamp(string convId) {
|
||||||
|
ConvCommand convCommand = new ConvCommand {
|
||||||
|
Cid = convId
|
||||||
|
};
|
||||||
|
GenericCommand request = NewCommand(CommandType.Conv, OpType.MaxRead);
|
||||||
|
request.ConvMessage = convCommand;
|
||||||
|
GenericCommand response = await Connection.SendRequest(request);
|
||||||
|
convCommand = response.ConvMessage;
|
||||||
|
LCIMConversation conversation = await Client.GetOrQueryConversation(convCommand.Cid);
|
||||||
|
conversation.LastDeliveredTimestamp = convCommand.MaxAckTimestamp;
|
||||||
|
conversation.LastReadTimestamp = convCommand.MaxReadTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取在线成员
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convId"></param>
|
||||||
|
/// <param name="limit"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task<ReadOnlyCollection<string>> GetOnlineMembers(string convId,
|
||||||
|
int limit) {
|
||||||
|
ConvCommand conv = new ConvCommand {
|
||||||
|
Cid = convId,
|
||||||
|
Limit = limit
|
||||||
|
};
|
||||||
|
GenericCommand request = NewCommand(CommandType.Conv, OpType.Members);
|
||||||
|
request.ConvMessage = conv;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
ReadOnlyCollection<string> members = response.ConvMessage.M
|
||||||
|
.ToList().AsReadOnly();
|
||||||
|
return members;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询是否订阅
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task<bool> CheckSubscription(string convId) {
|
||||||
|
ConvCommand conv = new ConvCommand();
|
||||||
|
conv.Cids.Add(convId);
|
||||||
|
GenericCommand request = NewCommand(CommandType.Conv, OpType.IsMember);
|
||||||
|
request.ConvMessage = conv;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
JsonObjectMessage jsonObj = response.ConvMessage.Results;
|
||||||
|
Dictionary<string, object> result = JsonConvert.DeserializeObject<Dictionary<string, object>>(jsonObj.Data);
|
||||||
|
if (result.TryGetValue(convId, out object obj)) {
|
||||||
|
return (bool)obj;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LCIMPartiallySuccessResult NewPartiallySuccessResult(IEnumerable<string> succesfulIds,
|
||||||
|
IEnumerable<ErrorCommand> errors) {
|
||||||
|
LCIMPartiallySuccessResult result = new LCIMPartiallySuccessResult {
|
||||||
|
SuccessfulClientIdList = succesfulIds.ToList()
|
||||||
|
};
|
||||||
|
if (errors != null) {
|
||||||
|
result.FailureList = new List<LCIMOperationFailure>();
|
||||||
|
foreach (ErrorCommand error in errors) {
|
||||||
|
LCIMOperationFailure failure = new LCIMOperationFailure {
|
||||||
|
Code = error.Code,
|
||||||
|
Reason = error.Reason,
|
||||||
|
IdList = error.Pids?.ToList()
|
||||||
|
};
|
||||||
|
result.FailureList.Add(failure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 消息处理
|
||||||
|
|
||||||
|
internal override async Task OnNotification(GenericCommand notification) {
|
||||||
|
if (notification.Cmd == CommandType.Conv) {
|
||||||
|
await OnConversation(notification);
|
||||||
|
} else if (notification.Cmd == CommandType.Unread) {
|
||||||
|
await OnUnread(notification);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnUnread(GenericCommand notification) {
|
||||||
|
UnreadCommand unread = notification.UnreadMessage;
|
||||||
|
|
||||||
|
IEnumerable<string> convIds = unread.Convs
|
||||||
|
.Select(conv => conv.Cid);
|
||||||
|
Dictionary<string, LCIMConversation> conversationDict = (await Client.GetConversationList(convIds))
|
||||||
|
.ToDictionary(item => item.Id);
|
||||||
|
ReadOnlyCollection<LCIMConversation> conversations = unread.Convs.Select(conv => {
|
||||||
|
// 设置对话中的未读数据
|
||||||
|
LCIMConversation conversation = conversationDict[conv.Cid];
|
||||||
|
conversation.Unread = conv.Unread;
|
||||||
|
if (conv.HasData || conv.HasBinaryMsg) {
|
||||||
|
// 如果有消息,则反序列化
|
||||||
|
LCIMMessage message = null;
|
||||||
|
if (conv.HasBinaryMsg) {
|
||||||
|
// 二进制消息
|
||||||
|
byte[] bytes = conv.BinaryMsg.ToByteArray();
|
||||||
|
message = LCIMBinaryMessage.Deserialize(bytes);
|
||||||
|
} else {
|
||||||
|
// 类型消息
|
||||||
|
message = LCIMTypedMessage.Deserialize(conv.Data);
|
||||||
|
}
|
||||||
|
// 填充消息数据
|
||||||
|
message.ConversationId = conv.Cid;
|
||||||
|
message.Id = conv.Mid;
|
||||||
|
message.FromClientId = conv.From;
|
||||||
|
message.SentTimestamp = conv.Timestamp;
|
||||||
|
message.Mentioned = conv.Mentioned;
|
||||||
|
conversation.LastMessage = message;
|
||||||
|
}
|
||||||
|
return conversation;
|
||||||
|
}).ToList().AsReadOnly();
|
||||||
|
Client.OnUnreadMessagesCountUpdated?.Invoke(conversations);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnConversation(GenericCommand notification) {
|
||||||
|
ConvCommand convMessage = notification.ConvMessage;
|
||||||
|
switch (notification.Op) {
|
||||||
|
case OpType.Joined:
|
||||||
|
await OnJoined(convMessage);
|
||||||
|
break;
|
||||||
|
case OpType.MembersJoined:
|
||||||
|
await OnMembersJoined(convMessage);
|
||||||
|
break;
|
||||||
|
case OpType.Left:
|
||||||
|
await OnLeft(convMessage);
|
||||||
|
break;
|
||||||
|
case OpType.MembersLeft:
|
||||||
|
await OnMemberLeft(convMessage);
|
||||||
|
break;
|
||||||
|
case OpType.Blocked:
|
||||||
|
await OnBlocked(convMessage);
|
||||||
|
break;
|
||||||
|
case OpType.Unblocked:
|
||||||
|
await OnUnblocked(convMessage);
|
||||||
|
break;
|
||||||
|
case OpType.MembersBlocked:
|
||||||
|
await OnMembersBlocked(convMessage);
|
||||||
|
break;
|
||||||
|
case OpType.MembersUnblocked:
|
||||||
|
await OnMembersUnblocked(convMessage);
|
||||||
|
break;
|
||||||
|
case OpType.Shutuped:
|
||||||
|
await OnMuted(convMessage);
|
||||||
|
break;
|
||||||
|
case OpType.Unshutuped:
|
||||||
|
await OnUnmuted(convMessage);
|
||||||
|
break;
|
||||||
|
case OpType.MembersShutuped:
|
||||||
|
await OnMembersMuted(convMessage);
|
||||||
|
break;
|
||||||
|
case OpType.MembersUnshutuped:
|
||||||
|
await OnMembersUnmuted(convMessage);
|
||||||
|
break;
|
||||||
|
case OpType.Updated:
|
||||||
|
await OnPropertiesUpdated(convMessage);
|
||||||
|
break;
|
||||||
|
case OpType.MemberInfoChanged:
|
||||||
|
await OnMemberInfoChanged(convMessage);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前用户加入会话
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convMessage"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task OnJoined(ConvCommand convMessage) {
|
||||||
|
LCIMConversation conversation = await Client.GetOrQueryConversation(convMessage.Cid);
|
||||||
|
Client.OnInvited?.Invoke(conversation, convMessage.InitBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 有用户加入会话
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convMessage"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task OnMembersJoined(ConvCommand convMessage) {
|
||||||
|
LCIMConversation conversation = await Client.GetOrQueryConversation(convMessage.Cid);
|
||||||
|
ReadOnlyCollection<string> joinedIds = new ReadOnlyCollection<string>(convMessage.M);
|
||||||
|
conversation.ids.UnionWith(joinedIds);
|
||||||
|
Client.OnMembersJoined?.Invoke(conversation, joinedIds, convMessage.InitBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前用户离开会话
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convMessage"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task OnLeft(ConvCommand convMessage) {
|
||||||
|
LCIMConversation conversation = await Client.GetOrQueryConversation(convMessage.Cid);
|
||||||
|
// TODO 从内存中清除对话
|
||||||
|
|
||||||
|
Client.OnKicked?.Invoke(conversation, convMessage.InitBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 有成员离开会话
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convMessage"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task OnMemberLeft(ConvCommand convMessage) {
|
||||||
|
LCIMConversation conversation = await Client.GetOrQueryConversation(convMessage.Cid);
|
||||||
|
ReadOnlyCollection<string> leftIdList = new ReadOnlyCollection<string>(convMessage.M);
|
||||||
|
conversation.ids.RemoveWhere(item => leftIdList.Contains(item));
|
||||||
|
Client.OnMembersLeft?.Invoke(conversation, leftIdList, convMessage.InitBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前用户被禁言
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convMessage"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task OnMuted(ConvCommand convMessage) {
|
||||||
|
LCIMConversation conversation = await Client.GetOrQueryConversation(convMessage.Cid);
|
||||||
|
Client.OnMuted?.Invoke(conversation, convMessage.InitBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前用户被解除禁言
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convMessage"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task OnUnmuted(ConvCommand convMessage) {
|
||||||
|
LCIMConversation conversation = await Client.GetOrQueryConversation(convMessage.Cid);
|
||||||
|
Client.OnUnmuted?.Invoke(conversation, convMessage.InitBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 有成员被禁言
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convMessage"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task OnMembersMuted(ConvCommand convMessage) {
|
||||||
|
LCIMConversation conversation = await Client.GetOrQueryConversation(convMessage.Cid);
|
||||||
|
ReadOnlyCollection<string> mutedMemberIds = new ReadOnlyCollection<string>(convMessage.M);
|
||||||
|
conversation.mutedIds.UnionWith(mutedMemberIds);
|
||||||
|
Client.OnMembersMuted?.Invoke(conversation, mutedMemberIds, convMessage.InitBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 有成员被解除禁言
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convMessage"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task OnMembersUnmuted(ConvCommand convMessage) {
|
||||||
|
LCIMConversation conversation = await Client.GetOrQueryConversation(convMessage.Cid);
|
||||||
|
ReadOnlyCollection<string> unmutedMemberIds = new ReadOnlyCollection<string>(convMessage.M);
|
||||||
|
conversation.mutedIds.RemoveWhere(id => unmutedMemberIds.Contains(id));
|
||||||
|
Client.OnMembersUnmuted?.Invoke(conversation, unmutedMemberIds, convMessage.InitBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前用户被拉黑
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convMessage"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task OnBlocked(ConvCommand convMessage) {
|
||||||
|
LCIMConversation conversation = await Client.GetOrQueryConversation(convMessage.Cid);
|
||||||
|
Client.OnBlocked?.Invoke(conversation, convMessage.InitBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前用户被解除黑名单
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convMessage"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task OnUnblocked(ConvCommand convMessage) {
|
||||||
|
LCIMConversation conversation = await Client.GetOrQueryConversation(convMessage.Cid);
|
||||||
|
Client.OnUnblocked?.Invoke(conversation, convMessage.InitBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 有用户被拉黑
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convMessage"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task OnMembersBlocked(ConvCommand convMessage) {
|
||||||
|
LCIMConversation conversation = await Client.GetOrQueryConversation(convMessage.Cid);
|
||||||
|
ReadOnlyCollection<string> blockedMemberIds = convMessage.M.ToList().AsReadOnly();
|
||||||
|
Client.OnMembersBlocked?.Invoke(conversation, blockedMemberIds, convMessage.InitBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 有用户被移除黑名单
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convMessage"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task OnMembersUnblocked(ConvCommand convMessage) {
|
||||||
|
LCIMConversation conversation = await Client.GetOrQueryConversation(convMessage.Cid);
|
||||||
|
ReadOnlyCollection<string> unblockedMemberIds = convMessage.M.ToList().AsReadOnly();
|
||||||
|
Client.OnMembersUnblocked?.Invoke(conversation, unblockedMemberIds, convMessage.InitBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 对话属性被修改
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="conv"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task OnPropertiesUpdated(ConvCommand conv) {
|
||||||
|
LCIMConversation conversation = await Client.GetOrQueryConversation(conv.Cid);
|
||||||
|
Dictionary<string, object> updatedAttr = JsonConvert.DeserializeObject<Dictionary<string, object>>(conv.AttrModified.Data,
|
||||||
|
new LCJsonConverter());
|
||||||
|
// 更新内存数据
|
||||||
|
conversation.MergeInfo(updatedAttr);
|
||||||
|
Client.OnConversationInfoUpdated?.Invoke(conversation,
|
||||||
|
new ReadOnlyDictionary<string, object>(updatedAttr),
|
||||||
|
conv.InitBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户角色被修改
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="conv"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task OnMemberInfoChanged(ConvCommand conv) {
|
||||||
|
LCIMConversation conversation = await Client.GetOrQueryConversation(conv.Cid);
|
||||||
|
ConvMemberInfo memberInfo = conv.Info;
|
||||||
|
Client.OnMemberInfoUpdated?.Invoke(conversation, memberInfo.Pid, memberInfo.Role, conv.InitBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using LeanCloud.Realtime.Internal.Protocol;
|
||||||
|
|
||||||
|
namespace LeanCloud.Realtime.Internal.Controller {
|
||||||
|
internal class LCIMGoAwayController : LCIMController {
|
||||||
|
internal LCIMGoAwayController(LCIMClient client) : base(client) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 消息处理
|
||||||
|
|
||||||
|
internal override async Task OnNotification(GenericCommand notification) {
|
||||||
|
// 清空缓存,断开连接,等待重新连接
|
||||||
|
await Connection.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,344 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Google.Protobuf;
|
||||||
|
using LeanCloud.Realtime.Internal.Protocol;
|
||||||
|
|
||||||
|
namespace LeanCloud.Realtime.Internal.Controller {
|
||||||
|
internal class LCIMMessageController : LCIMController {
|
||||||
|
internal LCIMMessageController(LCIMClient client) : base(client) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 内部接口
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送消息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convId"></param>
|
||||||
|
/// <param name="message"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task<LCIMMessage> Send(string convId,
|
||||||
|
LCIMMessage message,
|
||||||
|
LCIMMessageSendOptions options) {
|
||||||
|
DirectCommand direct = new DirectCommand {
|
||||||
|
FromPeerId = Client.Id,
|
||||||
|
Cid = convId,
|
||||||
|
};
|
||||||
|
if (message is LCIMTypedMessage typedMessage) {
|
||||||
|
direct.Msg = JsonConvert.SerializeObject(typedMessage.Encode());
|
||||||
|
} else if (message is LCIMBinaryMessage binaryMessage) {
|
||||||
|
direct.BinaryMsg = ByteString.CopyFrom(binaryMessage.Data);
|
||||||
|
} else {
|
||||||
|
throw new ArgumentException("Message MUST be LCIMTypedMessage or LCIMBinaryMessage.");
|
||||||
|
}
|
||||||
|
// 暂态消息
|
||||||
|
if (options.Transient) {
|
||||||
|
direct.Transient = options.Transient;
|
||||||
|
}
|
||||||
|
// 消息接收回执
|
||||||
|
if (options.Receipt) {
|
||||||
|
direct.R = options.Receipt;
|
||||||
|
}
|
||||||
|
// 遗愿消息
|
||||||
|
if (options.Will) {
|
||||||
|
direct.Will = options.Will;
|
||||||
|
}
|
||||||
|
// 推送数据
|
||||||
|
if (options.PushData != null) {
|
||||||
|
direct.PushData = JsonConvert.SerializeObject(options.PushData);
|
||||||
|
}
|
||||||
|
// 提醒所有人
|
||||||
|
if (message.MentionAll) {
|
||||||
|
direct.MentionAll = message.MentionAll;
|
||||||
|
}
|
||||||
|
// 提醒用户列表
|
||||||
|
if (message.MentionIdList != null &&
|
||||||
|
message.MentionIdList.Count > 0) {
|
||||||
|
direct.MentionPids.AddRange(message.MentionIdList);
|
||||||
|
}
|
||||||
|
GenericCommand command = NewCommand(CommandType.Direct);
|
||||||
|
command.DirectMessage = direct;
|
||||||
|
// 优先级
|
||||||
|
if (command.Priority > 0) {
|
||||||
|
command.Priority = (int)options.Priority;
|
||||||
|
}
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(command);
|
||||||
|
// 消息发送应答
|
||||||
|
AckCommand ack = response.AckMessage;
|
||||||
|
message.Id = ack.Uid;
|
||||||
|
message.SentTimestamp = ack.T;
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 撤回消息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convId"></param>
|
||||||
|
/// <param name="message"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task RecallMessage(string convId,
|
||||||
|
LCIMMessage message) {
|
||||||
|
PatchCommand patch = new PatchCommand();
|
||||||
|
PatchItem item = new PatchItem {
|
||||||
|
Cid = convId,
|
||||||
|
Mid = message.Id,
|
||||||
|
From = Client.Id,
|
||||||
|
Recall = true,
|
||||||
|
Timestamp = message.SentTimestamp,
|
||||||
|
PatchTimestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
|
||||||
|
};
|
||||||
|
patch.Patches.Add(item);
|
||||||
|
GenericCommand request = NewCommand(CommandType.Patch, OpType.Modify);
|
||||||
|
request.PatchMessage = patch;
|
||||||
|
await Client.Connection.SendRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 修改消息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convId"></param>
|
||||||
|
/// <param name="oldMessage"></param>
|
||||||
|
/// <param name="newMessage"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task UpdateMessage(string convId,
|
||||||
|
LCIMMessage oldMessage,
|
||||||
|
LCIMMessage newMessage) {
|
||||||
|
PatchCommand patch = new PatchCommand();
|
||||||
|
PatchItem item = new PatchItem {
|
||||||
|
Cid = convId,
|
||||||
|
Mid = oldMessage.Id,
|
||||||
|
From = Client.Id,
|
||||||
|
Recall = false,
|
||||||
|
Timestamp = oldMessage.SentTimestamp,
|
||||||
|
PatchTimestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
|
||||||
|
};
|
||||||
|
if (newMessage is LCIMTypedMessage typedMessage) {
|
||||||
|
item.Data = JsonConvert.SerializeObject(typedMessage.Encode());
|
||||||
|
} else if (newMessage is LCIMBinaryMessage binaryMessage) {
|
||||||
|
item.BinaryMsg = ByteString.CopyFrom(binaryMessage.Data);
|
||||||
|
}
|
||||||
|
if (newMessage.MentionIdList != null) {
|
||||||
|
item.MentionPids.AddRange(newMessage.MentionIdList);
|
||||||
|
}
|
||||||
|
if (newMessage.MentionAll) {
|
||||||
|
item.MentionAll = newMessage.MentionAll;
|
||||||
|
}
|
||||||
|
patch.Patches.Add(item);
|
||||||
|
GenericCommand request = NewCommand(CommandType.Patch, OpType.Modify);
|
||||||
|
request.PatchMessage = patch;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询消息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convId"></param>
|
||||||
|
/// <param name="start"></param>
|
||||||
|
/// <param name="end"></param>
|
||||||
|
/// <param name="direction"></param>
|
||||||
|
/// <param name="limit"></param>
|
||||||
|
/// <param name="messageType"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task<ReadOnlyCollection<LCIMMessage>> QueryMessages(string convId,
|
||||||
|
LCIMMessageQueryEndpoint start = null,
|
||||||
|
LCIMMessageQueryEndpoint end = null,
|
||||||
|
LCIMMessageQueryDirection direction = LCIMMessageQueryDirection.NewToOld,
|
||||||
|
int limit = 20,
|
||||||
|
int messageType = 0) {
|
||||||
|
LogsCommand logs = new LogsCommand {
|
||||||
|
Cid = convId
|
||||||
|
};
|
||||||
|
if (start != null) {
|
||||||
|
logs.T = start.SentTimestamp;
|
||||||
|
logs.Mid = start.MessageId;
|
||||||
|
logs.TIncluded = start.IsClosed;
|
||||||
|
}
|
||||||
|
if (end != null) {
|
||||||
|
logs.Tt = end.SentTimestamp;
|
||||||
|
logs.Tmid = end.MessageId;
|
||||||
|
logs.TtIncluded = end.IsClosed;
|
||||||
|
}
|
||||||
|
logs.Direction = direction == LCIMMessageQueryDirection.NewToOld ?
|
||||||
|
LogsCommand.Types.QueryDirection.Old : LogsCommand.Types.QueryDirection.New;
|
||||||
|
logs.Limit = limit;
|
||||||
|
if (messageType != 0) {
|
||||||
|
logs.Lctype = messageType;
|
||||||
|
}
|
||||||
|
GenericCommand request = NewCommand(CommandType.Logs, OpType.Open);
|
||||||
|
request.LogsMessage = logs;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
// 反序列化聊天记录
|
||||||
|
return response.LogsMessage.Logs.Select(item => {
|
||||||
|
LCIMMessage message;
|
||||||
|
if (item.Bin) {
|
||||||
|
// 二进制消息
|
||||||
|
byte[] bytes = Convert.FromBase64String(item.Data);
|
||||||
|
message = LCIMBinaryMessage.Deserialize(bytes);
|
||||||
|
} else {
|
||||||
|
// 类型消息
|
||||||
|
message = LCIMTypedMessage.Deserialize(item.Data);
|
||||||
|
}
|
||||||
|
message.ConversationId = convId;
|
||||||
|
message.Id = item.MsgId;
|
||||||
|
message.FromClientId = item.From;
|
||||||
|
message.SentTimestamp = item.Timestamp;
|
||||||
|
message.DeliveredTimestamp = item.AckAt;
|
||||||
|
message.ReadTimestamp = item.ReadAt;
|
||||||
|
message.PatchedTimestamp = item.PatchTimestamp;
|
||||||
|
message.MentionAll = item.MentionAll;
|
||||||
|
message.MentionIdList = item.MentionPids.ToList();
|
||||||
|
message.Mentioned = message.MentionAll ||
|
||||||
|
message.MentionIdList.Contains(Client.Id);
|
||||||
|
return message;
|
||||||
|
}).ToList().AsReadOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 确认收到消息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convId"></param>
|
||||||
|
/// <param name="msgId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task Ack(string convId,
|
||||||
|
string msgId) {
|
||||||
|
AckCommand ack = new AckCommand {
|
||||||
|
Cid = convId,
|
||||||
|
Mid = msgId
|
||||||
|
};
|
||||||
|
GenericCommand command = NewCommand(CommandType.Ack);
|
||||||
|
command.AckMessage = ack;
|
||||||
|
await Client.Connection.SendCommand(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 确认已读消息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="convId"></param>
|
||||||
|
/// <param name="msg"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task Read(string convId,
|
||||||
|
LCIMMessage msg) {
|
||||||
|
ReadCommand read = new ReadCommand();
|
||||||
|
ReadTuple tuple = new ReadTuple {
|
||||||
|
Cid = convId,
|
||||||
|
Mid = msg.Id,
|
||||||
|
Timestamp = msg.SentTimestamp
|
||||||
|
};
|
||||||
|
read.Convs.Add(tuple);
|
||||||
|
GenericCommand command = NewCommand(CommandType.Read);
|
||||||
|
command.ReadMessage = read;
|
||||||
|
await Client.Connection.SendCommand(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 消息处理
|
||||||
|
|
||||||
|
internal override async Task OnNotification(GenericCommand notification) {
|
||||||
|
if (notification.Cmd == CommandType.Direct) {
|
||||||
|
await OnMessaage(notification);
|
||||||
|
} else if (notification.Cmd == CommandType.Patch) {
|
||||||
|
await OnMessagePatched(notification);
|
||||||
|
} else if (notification.Cmd == CommandType.Rcp) {
|
||||||
|
await OnMessageReceipt(notification);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 接收消息事件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="notification"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task OnMessaage(GenericCommand notification) {
|
||||||
|
DirectCommand direct = notification.DirectMessage;
|
||||||
|
// 反序列化消息
|
||||||
|
LCIMMessage message;
|
||||||
|
if (direct.HasBinaryMsg) {
|
||||||
|
// 二进制消息
|
||||||
|
byte[] bytes = direct.BinaryMsg.ToByteArray();
|
||||||
|
message = LCIMBinaryMessage.Deserialize(bytes);
|
||||||
|
} else {
|
||||||
|
// 类型消息
|
||||||
|
message = LCIMTypedMessage.Deserialize(direct.Msg);
|
||||||
|
}
|
||||||
|
// 填充消息数据
|
||||||
|
message.ConversationId = direct.Cid;
|
||||||
|
message.Id = direct.Id;
|
||||||
|
message.FromClientId = direct.FromPeerId;
|
||||||
|
message.SentTimestamp = direct.Timestamp;
|
||||||
|
message.MentionAll = direct.MentionAll;
|
||||||
|
message.MentionIdList = direct.MentionPids.ToList();
|
||||||
|
message.Mentioned = message.MentionAll ||
|
||||||
|
message.MentionIdList.Contains(Client.Id);
|
||||||
|
message.PatchedTimestamp = direct.PatchTimestamp;
|
||||||
|
message.IsTransient = direct.Transient;
|
||||||
|
// 通知服务端已接收
|
||||||
|
if (!message.IsTransient) {
|
||||||
|
// 只有非暂态消息才需要发送 ack
|
||||||
|
_ = Ack(message.ConversationId, message.Id);
|
||||||
|
}
|
||||||
|
// 获取对话
|
||||||
|
LCIMConversation conversation = await Client.GetOrQueryConversation(direct.Cid);
|
||||||
|
conversation.Unread++;
|
||||||
|
conversation.LastMessage = message;
|
||||||
|
Client.OnMessage?.Invoke(conversation, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 消息被修改事件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="notification"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task OnMessagePatched(GenericCommand notification) {
|
||||||
|
PatchCommand patchMessage = notification.PatchMessage;
|
||||||
|
foreach (PatchItem patch in patchMessage.Patches) {
|
||||||
|
// 获取对话
|
||||||
|
LCIMConversation conversation = await Client.GetOrQueryConversation(patch.Cid);
|
||||||
|
LCIMMessage message;
|
||||||
|
if (patch.HasBinaryMsg) {
|
||||||
|
byte[] bytes = patch.BinaryMsg.ToByteArray();
|
||||||
|
message = LCIMBinaryMessage.Deserialize(bytes);
|
||||||
|
} else {
|
||||||
|
message = LCIMTypedMessage.Deserialize(patch.Data);
|
||||||
|
}
|
||||||
|
message.ConversationId = patch.Cid;
|
||||||
|
message.Id = patch.Mid;
|
||||||
|
message.FromClientId = patch.From;
|
||||||
|
message.SentTimestamp = patch.Timestamp;
|
||||||
|
message.PatchedTimestamp = patch.PatchTimestamp;
|
||||||
|
if (message is LCIMRecalledMessage recalledMessage) {
|
||||||
|
// 消息撤回
|
||||||
|
Client.OnMessageRecalled?.Invoke(conversation, recalledMessage);
|
||||||
|
} else {
|
||||||
|
// 消息修改
|
||||||
|
Client.OnMessageUpdated?.Invoke(conversation, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 消息回执事件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="notification"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task OnMessageReceipt(GenericCommand notification) {
|
||||||
|
RcpCommand rcp = notification.RcpMessage;
|
||||||
|
string convId = rcp.Cid;
|
||||||
|
string msgId = rcp.Id;
|
||||||
|
long timestamp = rcp.T;
|
||||||
|
bool isRead = rcp.Read;
|
||||||
|
string fromId = rcp.From;
|
||||||
|
LCIMConversation conversation = await Client.GetOrQueryConversation(convId);
|
||||||
|
if (isRead) {
|
||||||
|
Client.OnMessageRead?.Invoke(conversation, msgId);
|
||||||
|
} else {
|
||||||
|
Client.OnMessageDelivered?.Invoke(conversation, msgId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,148 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using LeanCloud.Realtime.Internal.Protocol;
|
||||||
|
|
||||||
|
namespace LeanCloud.Realtime.Internal.Controller {
|
||||||
|
internal class LCIMSessionController : LCIMController {
|
||||||
|
private string token;
|
||||||
|
private DateTimeOffset expiredAt;
|
||||||
|
|
||||||
|
internal LCIMSessionController(LCIMClient client)
|
||||||
|
: base(client) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 内部接口
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 打开会话
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task Open(bool force) {
|
||||||
|
SessionCommand session = await NewSessionCommand();
|
||||||
|
session.R = !force;
|
||||||
|
session.ConfigBitmap = 0x2B;
|
||||||
|
GenericCommand request = NewCommand(CommandType.Session, OpType.Open);
|
||||||
|
request.SessionMessage = session;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
UpdateSession(response.SessionMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 重新打开会话,重连时调用
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task Reopen() {
|
||||||
|
SessionCommand session = await NewSessionCommand();
|
||||||
|
session.R = true;
|
||||||
|
GenericCommand request = NewCommand(CommandType.Session, OpType.Open);
|
||||||
|
request.SessionMessage = session;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
if (response.Op == OpType.Opened) {
|
||||||
|
UpdateSession(response.SessionMessage);
|
||||||
|
} else if (response.Op == OpType.Closed) {
|
||||||
|
await OnClosed(response.SessionMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 关闭会话
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task Close() {
|
||||||
|
GenericCommand request = NewCommand(CommandType.Session, OpType.Close);
|
||||||
|
await Client.Connection.SendRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取可用 token
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task<string> GetToken() {
|
||||||
|
if (IsExpired) {
|
||||||
|
await Refresh();
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private async Task Refresh() {
|
||||||
|
SessionCommand session = await NewSessionCommand();
|
||||||
|
GenericCommand request = NewCommand(CommandType.Session, OpType.Refresh);
|
||||||
|
request.SessionMessage = session;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
UpdateSession(response.SessionMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<SessionCommand> NewSessionCommand() {
|
||||||
|
SessionCommand session = new SessionCommand();
|
||||||
|
if (Client.Tag != null) {
|
||||||
|
session.Tag = Client.Tag;
|
||||||
|
}
|
||||||
|
if (Client.DeviceId != null) {
|
||||||
|
session.DeviceId = Client.DeviceId;
|
||||||
|
}
|
||||||
|
LCIMSignature signature = null;
|
||||||
|
if (Client.SignatureFactory != null) {
|
||||||
|
signature = await Client.SignatureFactory.CreateConnectSignature(Client.Id);
|
||||||
|
}
|
||||||
|
if (signature == null && !string.IsNullOrEmpty(Client.SessionToken)) {
|
||||||
|
Dictionary<string, object> ret = await LCApplication.HttpClient.Post<Dictionary<string, object>>("rtm/sign", data: new Dictionary<string, object> {
|
||||||
|
{ "session_token", Client.SessionToken }
|
||||||
|
});
|
||||||
|
signature = new LCIMSignature {
|
||||||
|
Signature = ret["signature"] as string,
|
||||||
|
Timestamp = (long)ret["timestamp"],
|
||||||
|
Nonce = ret["nonce"] as string
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (signature != null) {
|
||||||
|
session.S = signature.Signature;
|
||||||
|
session.T = signature.Timestamp;
|
||||||
|
session.N = signature.Nonce;
|
||||||
|
}
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateSession(SessionCommand session) {
|
||||||
|
token = session.St;
|
||||||
|
int ttl = session.StTtl;
|
||||||
|
expiredAt = DateTimeOffset.Now + TimeSpan.FromSeconds(ttl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsExpired {
|
||||||
|
get {
|
||||||
|
return DateTimeOffset.Now > expiredAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 消息处理
|
||||||
|
|
||||||
|
internal override async Task OnNotification(GenericCommand notification) {
|
||||||
|
switch (notification.Op) {
|
||||||
|
case OpType.Closed:
|
||||||
|
await OnClosed(notification.SessionMessage);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 被关闭
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="session"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task OnClosed(SessionCommand session) {
|
||||||
|
int code = session.Code;
|
||||||
|
string reason = session.Reason;
|
||||||
|
string detail = session.Detail;
|
||||||
|
await Connection.Close();
|
||||||
|
Client.OnClose?.Invoke(code, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,61 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Net.Http;
|
||||||
|
using LeanCloud.Storage.Internal;
|
||||||
|
using LeanCloud.Common;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace LeanCloud.Realtime.Internal.Router {
|
||||||
|
/// <summary>
|
||||||
|
/// RTM Router
|
||||||
|
/// </summary>
|
||||||
|
internal class LCRTMRouter {
|
||||||
|
/// <summary>
|
||||||
|
/// 请求超时
|
||||||
|
/// </summary>
|
||||||
|
private const int REQUEST_TIMEOUT = 10000;
|
||||||
|
|
||||||
|
private LCRTMServer rtmServer;
|
||||||
|
|
||||||
|
internal LCRTMRouter() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取服务器地址
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task<LCRTMServer> GetServer() {
|
||||||
|
if (rtmServer == null || !rtmServer.IsValid) {
|
||||||
|
await Fetch();
|
||||||
|
}
|
||||||
|
return rtmServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task<LCRTMServer> Fetch() {
|
||||||
|
string server = await LCApplication.AppRouter.GetRealtimeServer();
|
||||||
|
string url = $"{server}/v1/route?appId={LCApplication.AppId}&secure=1";
|
||||||
|
|
||||||
|
HttpRequestMessage request = new HttpRequestMessage {
|
||||||
|
RequestUri = new Uri(url),
|
||||||
|
Method = HttpMethod.Get
|
||||||
|
};
|
||||||
|
HttpClient client = new HttpClient();
|
||||||
|
LCHttpUtils.PrintRequest(client, request);
|
||||||
|
|
||||||
|
Task<HttpResponseMessage> requestTask = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
if (await Task.WhenAny(requestTask, Task.Delay(REQUEST_TIMEOUT)) != requestTask) {
|
||||||
|
throw new TimeoutException("Request timeout.");
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpResponseMessage response = await requestTask;
|
||||||
|
request.Dispose();
|
||||||
|
string resultString = await response.Content.ReadAsStringAsync();
|
||||||
|
response.Dispose();
|
||||||
|
LCHttpUtils.PrintResponse(response, resultString);
|
||||||
|
|
||||||
|
rtmServer = JsonConvert.DeserializeObject<LCRTMServer>(resultString, new LCJsonConverter());
|
||||||
|
|
||||||
|
return rtmServer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
using System;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace LeanCloud.Realtime.Internal.Router {
|
||||||
|
internal class LCRTMServer {
|
||||||
|
[JsonProperty("groupId")]
|
||||||
|
internal string GroupId {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonProperty("groupUrl")]
|
||||||
|
internal string GroupUrl {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonProperty("server")]
|
||||||
|
internal string Primary {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonProperty("secondary")]
|
||||||
|
internal string Secondary {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonProperty("ttl")]
|
||||||
|
internal int Ttl {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTimeOffset createdAt;
|
||||||
|
|
||||||
|
internal LCRTMServer() {
|
||||||
|
createdAt = DateTimeOffset.Now;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool IsValid => DateTimeOffset.Now < createdAt + TimeSpan.FromSeconds(Ttl);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,150 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Net.WebSockets;
|
||||||
|
|
||||||
|
namespace LeanCloud.Realtime.Internal.WebSocket {
|
||||||
|
/// <summary>
|
||||||
|
/// WebSocket 客户端,负责底层连接和事件,只与通信协议相关
|
||||||
|
/// </summary>
|
||||||
|
internal class LCWebSocketClient {
|
||||||
|
// .net standard 2.0 好像在拼合 Frame 时有 bug,所以将这个值调整大一些
|
||||||
|
private const int RECV_BUFFER_SIZE = 1024 * 5;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 关闭超时
|
||||||
|
/// </summary>
|
||||||
|
private const int CLOSE_TIMEOUT = 5000;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 连接超时
|
||||||
|
/// </summary>
|
||||||
|
private const int CONNECT_TIMEOUT = 10000;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 消息事件
|
||||||
|
/// </summary>
|
||||||
|
internal Action<byte[]> OnMessage;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 连接关闭
|
||||||
|
/// </summary>
|
||||||
|
internal Action OnClose;
|
||||||
|
|
||||||
|
private ClientWebSocket ws;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 连接指定 ws 服务器
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="server"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task Connect(string server) {
|
||||||
|
LCLogger.Debug($"Connecting WebSocket: {server}");
|
||||||
|
Task timeoutTask = Task.Delay(CONNECT_TIMEOUT);
|
||||||
|
ws = new ClientWebSocket();
|
||||||
|
ws.Options.AddSubProtocol("lc.protobuf2.3");
|
||||||
|
Task connectTask = ws.ConnectAsync(new Uri(server), default);
|
||||||
|
if (await Task.WhenAny(connectTask, timeoutTask) == connectTask) {
|
||||||
|
LCLogger.Debug($"Connected WebSocket: {server}");
|
||||||
|
await connectTask;
|
||||||
|
// 接收
|
||||||
|
_ = StartReceive();
|
||||||
|
} else {
|
||||||
|
throw new TimeoutException("Connect timeout");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 主动关闭连接
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task Close() {
|
||||||
|
LCLogger.Debug("Closing WebSocket");
|
||||||
|
OnMessage = null;
|
||||||
|
OnClose = null;
|
||||||
|
try {
|
||||||
|
// 发送关闭帧可能会很久,所以增加超时
|
||||||
|
// 主动挥手关闭,不会再收到 Close Frame
|
||||||
|
Task closeTask = ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, default);
|
||||||
|
Task delayTask = Task.Delay(CLOSE_TIMEOUT);
|
||||||
|
await Task.WhenAny(closeTask, delayTask);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LCLogger.Error(e);
|
||||||
|
} finally {
|
||||||
|
ws.Abort();
|
||||||
|
ws.Dispose();
|
||||||
|
LCLogger.Debug("Closed WebSocket");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送数据
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task Send(byte[] data) {
|
||||||
|
ArraySegment<byte> bytes = new ArraySegment<byte>(data);
|
||||||
|
if (ws.State == WebSocketState.Open) {
|
||||||
|
try {
|
||||||
|
await ws.SendAsync(bytes, WebSocketMessageType.Binary, true, default);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LCLogger.Error(e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
string message = $"Error Websocket state: {ws.State}";
|
||||||
|
LCLogger.Error(message);
|
||||||
|
throw new Exception(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 接收数据
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task StartReceive() {
|
||||||
|
byte[] buffer = new byte[RECV_BUFFER_SIZE];
|
||||||
|
try {
|
||||||
|
while (ws.State == WebSocketState.Open) {
|
||||||
|
WebSocketReceiveResult result = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), default);
|
||||||
|
if (result.MessageType == WebSocketMessageType.Close) {
|
||||||
|
LCLogger.Debug($"Receive Closed: {result.CloseStatus}");
|
||||||
|
if (ws.State == WebSocketState.CloseReceived) {
|
||||||
|
// 如果是服务端主动关闭,则挥手关闭,并认为是断线
|
||||||
|
try {
|
||||||
|
Task closeTask = ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, default);
|
||||||
|
await Task.WhenAny(closeTask, Task.Delay(CLOSE_TIMEOUT));
|
||||||
|
} catch (Exception e) {
|
||||||
|
LCLogger.Error(e);
|
||||||
|
} finally {
|
||||||
|
HandleExceptionClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (result.MessageType == WebSocketMessageType.Binary) {
|
||||||
|
// 拼合 WebSocket Message
|
||||||
|
int length = result.Count;
|
||||||
|
byte[] data = new byte[length];
|
||||||
|
Array.Copy(buffer, data, length);
|
||||||
|
OnMessage?.Invoke(data);
|
||||||
|
} else {
|
||||||
|
LCLogger.Error($"Error message type: {result.MessageType}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 客户端网络异常
|
||||||
|
LCLogger.Error(e);
|
||||||
|
HandleExceptionClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleExceptionClose() {
|
||||||
|
try {
|
||||||
|
ws.Abort();
|
||||||
|
ws.Dispose();
|
||||||
|
} catch (Exception e) {
|
||||||
|
LCLogger.Error(e);
|
||||||
|
} finally {
|
||||||
|
OnClose?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,492 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using LeanCloud.Common;
|
||||||
|
using LeanCloud.Storage;
|
||||||
|
using LeanCloud.Realtime.Internal.Protocol;
|
||||||
|
using LeanCloud.Realtime.Internal.Controller;
|
||||||
|
using LeanCloud.Realtime.Internal.Connection;
|
||||||
|
|
||||||
|
namespace LeanCloud.Realtime {
|
||||||
|
/// <summary>
|
||||||
|
/// 通信客户端
|
||||||
|
/// </summary>
|
||||||
|
public class LCIMClient {
|
||||||
|
internal Dictionary<string, LCIMConversation> ConversationDict;
|
||||||
|
|
||||||
|
public string Id {
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Tag {
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string DeviceId {
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string SessionToken {
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 事件
|
||||||
|
|
||||||
|
#region 连接状态事件
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 客户端连接断开
|
||||||
|
/// </summary>
|
||||||
|
public Action OnPaused {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 客户端连接恢复正常
|
||||||
|
/// </summary>
|
||||||
|
public Action OnResume {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前客户端被服务端强行下线
|
||||||
|
/// </summary>
|
||||||
|
public Action<int, string> OnClose {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 对话事件
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前用户被加入某个对话的黑名单
|
||||||
|
/// </summary>
|
||||||
|
public Action<LCIMConversation, string> OnBlocked {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当用户被解除黑名单
|
||||||
|
/// </summary>
|
||||||
|
public Action<LCIMConversation, string> OnUnblocked {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前用户在某个对话中被禁言
|
||||||
|
/// </summary>
|
||||||
|
public Action<LCIMConversation, string> OnMuted;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前用户在某个对话中被解除禁言
|
||||||
|
/// </summary>
|
||||||
|
public Action<LCIMConversation, string> OnUnmuted;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 该对话信息被更新
|
||||||
|
/// </summary>
|
||||||
|
public Action<LCIMConversation, ReadOnlyDictionary<string, object>, string> OnConversationInfoUpdated;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前用户被添加至某个对话
|
||||||
|
/// </summary>
|
||||||
|
public Action<LCIMConversation, string> OnInvited {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前用户被从某个对话中移除
|
||||||
|
/// </summary>
|
||||||
|
public Action<LCIMConversation, string> OnKicked {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 有用户被添加至某个对话
|
||||||
|
/// </summary>
|
||||||
|
public Action<LCIMConversation, ReadOnlyCollection<string>, string> OnMembersJoined {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 有成员被从某个对话中移除
|
||||||
|
/// </summary>
|
||||||
|
public Action<LCIMConversation, ReadOnlyCollection<string>, string> OnMembersLeft {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 有成员被加入某个对话的黑名单
|
||||||
|
/// </summary>
|
||||||
|
public Action<LCIMConversation, ReadOnlyCollection<string>, string> OnMembersBlocked {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 有成员被移出某个对话的黑名单
|
||||||
|
/// </summary>
|
||||||
|
public Action<LCIMConversation, ReadOnlyCollection<string>, string> OnMembersUnblocked {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 有成员在某个对话中被禁言
|
||||||
|
/// </summary>
|
||||||
|
public Action<LCIMConversation, ReadOnlyCollection<string>, string> OnMembersMuted {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 有成员被移出某个对话的黑名单
|
||||||
|
/// </summary>
|
||||||
|
public Action<LCIMConversation, ReadOnlyCollection<string>, string> OnMembersUnmuted {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 有成员的对话信息被更新
|
||||||
|
/// </summary>
|
||||||
|
public Action<LCIMConversation, string, string, string> OnMemberInfoUpdated;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 消息事件
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前用户收到消息
|
||||||
|
/// </summary>
|
||||||
|
public Action<LCIMConversation, LCIMMessage> OnMessage {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 消息被撤回
|
||||||
|
/// </summary>
|
||||||
|
public Action<LCIMConversation, LCIMRecalledMessage> OnMessageRecalled {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 消息被修改
|
||||||
|
/// </summary>
|
||||||
|
public Action<LCIMConversation, LCIMMessage> OnMessageUpdated {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 消息已送达
|
||||||
|
/// </summary>
|
||||||
|
public Action<LCIMConversation, string> OnMessageDelivered {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 消息已读
|
||||||
|
/// </summary>
|
||||||
|
public Action<LCIMConversation, string> OnMessageRead {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 未读消息数目更新
|
||||||
|
/// </summary>
|
||||||
|
public Action<ReadOnlyCollection<LCIMConversation>> OnUnreadMessagesCountUpdated {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 最近分发消息更新
|
||||||
|
/// </summary>
|
||||||
|
public Action OnLastDeliveredAtUpdated {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 最近已读消息更新
|
||||||
|
/// </summary>
|
||||||
|
public Action OnLastReadAtUpdated {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
internal ILCIMSignatureFactory SignatureFactory {
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal LCConnection Connection {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal LCIMSessionController SessionController {
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal LCIMMessageController MessageController {
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal LCIMGoAwayController GoAwayController {
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal LCIMConversationController ConversationController {
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 接口
|
||||||
|
|
||||||
|
public LCIMClient(string clientId,
|
||||||
|
string tag = null,
|
||||||
|
string deviceId = null,
|
||||||
|
ILCIMSignatureFactory signatureFactory = null) {
|
||||||
|
if (string.IsNullOrEmpty(clientId)) {
|
||||||
|
throw new ArgumentNullException(nameof(clientId));
|
||||||
|
}
|
||||||
|
SetUpClient(clientId, tag, deviceId, signatureFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LCIMClient(LCUser user,
|
||||||
|
string tag = null,
|
||||||
|
string deviceId = null,
|
||||||
|
ILCIMSignatureFactory signatureFactory = null) {
|
||||||
|
if (user == null) {
|
||||||
|
throw new ArgumentNullException(nameof(user));
|
||||||
|
}
|
||||||
|
if (string.IsNullOrEmpty(user.ObjectId) ||
|
||||||
|
string.IsNullOrEmpty(user.SessionToken)) {
|
||||||
|
throw new ArgumentException("User must be authenticacted.");
|
||||||
|
}
|
||||||
|
SetUpClient(user.ObjectId, tag, deviceId, signatureFactory);
|
||||||
|
SessionToken = user.SessionToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetUpClient(string clientId,
|
||||||
|
string tag,
|
||||||
|
string deviceId,
|
||||||
|
ILCIMSignatureFactory signatureFactory) {
|
||||||
|
Id = clientId;
|
||||||
|
Tag = tag;
|
||||||
|
DeviceId = deviceId;
|
||||||
|
SignatureFactory = signatureFactory;
|
||||||
|
|
||||||
|
ConversationDict = new Dictionary<string, LCIMConversation>();
|
||||||
|
|
||||||
|
// 模块
|
||||||
|
SessionController = new LCIMSessionController(this);
|
||||||
|
ConversationController = new LCIMConversationController(this);
|
||||||
|
MessageController = new LCIMMessageController(this);
|
||||||
|
GoAwayController = new LCIMGoAwayController(this);
|
||||||
|
|
||||||
|
Connection = new LCConnection(Id) {
|
||||||
|
OnNotification = OnConnectionNotification,
|
||||||
|
OnDisconnect = OnConnectionDisconnect,
|
||||||
|
OnReconnected = OnConnectionReconnect
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 登录
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="force">是否强制登录</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task Open(bool force = true) {
|
||||||
|
await Connection.Connect();
|
||||||
|
try {
|
||||||
|
// 打开 Session
|
||||||
|
await SessionController.Open(force);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LCLogger.Error(e);
|
||||||
|
// 如果 session 阶段异常,则关闭连接
|
||||||
|
await Connection.Close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 关闭
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task Close() {
|
||||||
|
// 关闭 session
|
||||||
|
await SessionController.Close();
|
||||||
|
await Connection.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建普通对话
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="members"></param>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="unique"></param>
|
||||||
|
/// <param name="properties"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<LCIMConversation> CreateConversation(
|
||||||
|
IEnumerable<string> members,
|
||||||
|
string name = null,
|
||||||
|
bool unique = true,
|
||||||
|
Dictionary<string, object> properties = null) {
|
||||||
|
return await ConversationController.CreateConv(members: members,
|
||||||
|
name: name,
|
||||||
|
unique: unique,
|
||||||
|
properties: properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建聊天室
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="properties"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<LCIMChatRoom> CreateChatRoom(
|
||||||
|
string name,
|
||||||
|
Dictionary<string, object> properties = null) {
|
||||||
|
LCIMChatRoom chatRoom = await ConversationController.CreateConv(name: name,
|
||||||
|
transient: true,
|
||||||
|
properties: properties) as LCIMChatRoom;
|
||||||
|
return chatRoom;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建临时对话
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="members"></param>
|
||||||
|
/// <param name="ttl"></param>
|
||||||
|
/// <param name="properties"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<LCIMTemporaryConversation> CreateTemporaryConversation(
|
||||||
|
IEnumerable<string> members,
|
||||||
|
int ttl = 86400,
|
||||||
|
Dictionary<string, object> properties = null) {
|
||||||
|
LCIMTemporaryConversation tempConversation = await ConversationController.CreateConv(members: members,
|
||||||
|
temporary: true,
|
||||||
|
temporaryTtl: ttl,
|
||||||
|
properties: properties) as LCIMTemporaryConversation;
|
||||||
|
return tempConversation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据 id 获取对话
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<LCIMConversation> GetConversation(string id) {
|
||||||
|
if (string.IsNullOrEmpty(id)) {
|
||||||
|
throw new ArgumentNullException(nameof(id));
|
||||||
|
}
|
||||||
|
if (LCIMConversation.IsTemporayConversation(id)) {
|
||||||
|
List<LCIMTemporaryConversation> temporaryConversationList = await ConversationController.GetTemporaryConversations(new string[] { id });
|
||||||
|
if (temporaryConversationList == null || temporaryConversationList.Count < 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return temporaryConversationList[0];
|
||||||
|
}
|
||||||
|
LCIMConversationQuery query = GetQuery()
|
||||||
|
.WhereEqualTo("objectId", id)
|
||||||
|
.Limit(1);
|
||||||
|
ReadOnlyCollection<LCIMConversation> results = await ConversationController.Find(query);
|
||||||
|
if (results == null || results.Count < 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return results[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取某些特定的对话
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ids"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<ReadOnlyCollection<LCIMConversation>> GetConversationList(IEnumerable<string> ids) {
|
||||||
|
if (ids == null || ids.Count() == 0) {
|
||||||
|
throw new ArgumentNullException(nameof(ids));
|
||||||
|
}
|
||||||
|
// 区分临时对话
|
||||||
|
IEnumerable<string> tempConvIds = ids.Where(item => {
|
||||||
|
return LCIMConversation.IsTemporayConversation(item);
|
||||||
|
});
|
||||||
|
IEnumerable<string> convIds = ids.Where(item => {
|
||||||
|
return !tempConvIds.Contains(item);
|
||||||
|
});
|
||||||
|
List<LCIMConversation> conversationList = new List<LCIMConversation>();
|
||||||
|
if (tempConvIds.Count() > 0) {
|
||||||
|
List<LCIMTemporaryConversation> temporaryConversations = await ConversationController.GetTemporaryConversations(tempConvIds);
|
||||||
|
conversationList.AddRange(temporaryConversations);
|
||||||
|
}
|
||||||
|
if (convIds.Count() > 0) {
|
||||||
|
LCIMConversationQuery query = GetQuery()
|
||||||
|
.WhereContainedIn("objectId", convIds)
|
||||||
|
.Limit(convIds.Count());
|
||||||
|
ReadOnlyCollection<LCIMConversation> conversations = await ConversationController.Find(query);
|
||||||
|
conversationList.AddRange(conversations);
|
||||||
|
}
|
||||||
|
return conversationList.AsReadOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取对话查询对象
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public LCIMConversationQuery GetQuery() {
|
||||||
|
return new LCIMConversationQuery(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private void OnConnectionNotification(GenericCommand notification) {
|
||||||
|
switch (notification.Cmd) {
|
||||||
|
case CommandType.Session:
|
||||||
|
_ = SessionController.OnNotification(notification);
|
||||||
|
break;
|
||||||
|
case CommandType.Conv:
|
||||||
|
case CommandType.Unread:
|
||||||
|
_ = ConversationController.OnNotification(notification);
|
||||||
|
break;
|
||||||
|
case CommandType.Direct:
|
||||||
|
case CommandType.Patch:
|
||||||
|
case CommandType.Rcp:
|
||||||
|
_ = MessageController.OnNotification(notification);
|
||||||
|
break;
|
||||||
|
case CommandType.Goaway:
|
||||||
|
_ = GoAwayController.OnNotification(notification);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnConnectionDisconnect() {
|
||||||
|
OnPaused?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnConnectionReconnect() {
|
||||||
|
_ = HandleReconnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleReconnected() {
|
||||||
|
try {
|
||||||
|
// 打开 Session
|
||||||
|
await SessionController.Reopen();
|
||||||
|
// 回调用户
|
||||||
|
OnResume?.Invoke();
|
||||||
|
} catch (Exception e) {
|
||||||
|
LCLogger.Error(e);
|
||||||
|
await Connection.Close();
|
||||||
|
// TODO 告知
|
||||||
|
OnClose?.Invoke(0, string.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task<LCIMConversation> GetOrQueryConversation(string convId) {
|
||||||
|
if (ConversationDict.TryGetValue(convId, out LCIMConversation conversation)) {
|
||||||
|
return conversation;
|
||||||
|
}
|
||||||
|
conversation = await GetConversation(convId);
|
||||||
|
return conversation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using LeanCloud.Storage;
|
||||||
|
|
||||||
|
namespace LeanCloud.Realtime {
|
||||||
|
/// <summary>
|
||||||
|
/// 音频消息
|
||||||
|
/// </summary>
|
||||||
|
public class LCIMAudioMessage : LCIMFileMessage {
|
||||||
|
/// <summary>
|
||||||
|
/// 时长
|
||||||
|
/// </summary>
|
||||||
|
public double Duration {
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal LCIMAudioMessage() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public LCIMAudioMessage(LCFile file) : base(file) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override Dictionary<string, object> Encode() {
|
||||||
|
Dictionary<string, object> data = base.Encode();
|
||||||
|
Dictionary<string, object> fileData = data[MessageFileKey] as Dictionary<string, object>;
|
||||||
|
Dictionary<string, object> metaData = fileData[MessageDataMetaDataKey] as Dictionary<string, object>;
|
||||||
|
if (File.MetaData != null &&
|
||||||
|
File.MetaData.TryGetValue(MessageDataMetaDurationKey, out object duration)) {
|
||||||
|
metaData[MessageDataMetaDurationKey] = duration;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Decode(Dictionary<string, object> msgData) {
|
||||||
|
base.Decode(msgData);
|
||||||
|
|
||||||
|
if (File.MetaData == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (File.MetaData.TryGetValue(MessageDataMetaDurationKey, out object duration) &&
|
||||||
|
double.TryParse(duration as string, out double d)) {
|
||||||
|
Duration = d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int MessageType => AudioMessageType;
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue