* csharp-sdk.sln:
* LCIMClient.cs: * Realtime.csproj: * Utils.cs: * Protobuf.cs: * LCIMMessage.cs: * Program.cs: * LCConnection.cs: * Conversation.cs: * LCIMFileMessage.cs: * Messages2Proto.cs: * LCIMTextMessage.cs: * LCIMTypedMessage.cs: * LCIMAudioMessage.cs: * LCIMImageMessage.cs: * LCApplicationRealtimeExt.cs: * LCIMChatRoom.cs: * messages2.proto.orig: * Realtime.Test.csproj: * LCIMLocationMessage.cs: * LCRTMServer.cs: * LCRTMRouter.cs: * LCIMRecalledMessage.cs: * compile-client-proto.sh: * LCIMConversation.cs: * RealtimeConsole.csproj: * LCIMConversationQuery.cs: * AssemblyInfo.cs: * LCWebSocketClient.cs: * LCIMTemporaryConversation.cs: * LCIMConversationMemberInfo.cs: * packages.config: chore: protobuf, websocket, converstion
parent
57b1a59cd0
commit
bf2af41565
|
@ -0,0 +1,8 @@
|
|||
using System;
|
||||
|
||||
namespace LeanCloud.Realtime {
|
||||
public class LCIMChatRoom : LCIMConversation {
|
||||
public LCIMChatRoom() {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime {
|
||||
public class LCIMConversation {
|
||||
public string Id {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string Name {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string CreatorId {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public List<string> MemberIdList {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public DateTime CreatedAt {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public DateTime UpdatedAt {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public bool IsMute => false;
|
||||
|
||||
public virtual bool IsSystem => false;
|
||||
|
||||
public virtual bool IsTransient => false;
|
||||
|
||||
public LCIMConversation() {
|
||||
|
||||
}
|
||||
|
||||
public void Set(string key, object value) {
|
||||
// 自定义属性
|
||||
|
||||
}
|
||||
|
||||
public async Task<int> Count() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public async Task<LCIMConversation> Save() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public async Task Add(List<string> clientIdList) {
|
||||
|
||||
}
|
||||
|
||||
public async Task Remove(List<string> removeIdList) {
|
||||
|
||||
}
|
||||
|
||||
public async Task<LCIMConversation> Join() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public async Task<LCIMConversation> Quit() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public async Task<LCIMMessage> Send(LCIMMessage message) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<LCIMRecalledMessage> Recall(LCIMMessage message) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<LCIMConversation> Mute() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public async Task<LCIMConversation> Unmute() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public async Task MuteMemberList(List<string> clientIdList) {
|
||||
|
||||
}
|
||||
|
||||
public async Task UnmuteMemberList(List<string> clientIdList) {
|
||||
|
||||
}
|
||||
|
||||
public async Task BlockMemberList(List<string> clientIdList) {
|
||||
|
||||
}
|
||||
|
||||
public async Task UnblockMemberList(List<string> clientIdList) {
|
||||
|
||||
}
|
||||
|
||||
public async Task<LCIMMessage> Update(LCIMMessage oldMessage, LCIMMessage newMessage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<LCIMConversation> UpdateMemberRole(string memberId, string role) {
|
||||
return this;
|
||||
}
|
||||
|
||||
public async Task<LCIMConversationMemberInfo> GetMemberInfo(string memberId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<List<LCIMConversationMemberInfo>> GetAllMemberInfo() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
|
||||
namespace LeanCloud.Realtime {
|
||||
public class LCIMConversationMemberInfo {
|
||||
public string ConversationId {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string MemberId {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public bool IsOwner {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string Role {
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
using System;
|
||||
namespace LeanCloud.Realtime {
|
||||
public class LCIMConversationQuery {
|
||||
public LCIMConversationQuery() {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
using System;
|
||||
|
||||
namespace LeanCloud.Realtime {
|
||||
public class LCIMTemporaryConversation : LCIMConversation {
|
||||
public LCIMTemporaryConversation() {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.WebSockets;
|
||||
using Google.Protobuf;
|
||||
using LeanCloud.Realtime.Protocol;
|
||||
using LeanCloud.Storage;
|
||||
|
||||
namespace LeanCloud.Realtime.Internal {
|
||||
internal class LCConnection {
|
||||
private const int KEEP_ALIVE_INTERVAL = 10;
|
||||
private const int RECV_BUFFER_SIZE = 1024;
|
||||
|
||||
private ClientWebSocket ws;
|
||||
|
||||
private volatile int requestI = 1;
|
||||
|
||||
private readonly object requestILock = new object();
|
||||
|
||||
private readonly Dictionary<int, TaskCompletionSource<GenericCommand>> responses;
|
||||
|
||||
internal LCConnection() {
|
||||
responses = new Dictionary<int, TaskCompletionSource<GenericCommand>>();
|
||||
}
|
||||
|
||||
internal async Task Connect() {
|
||||
ws = new ClientWebSocket();
|
||||
ws.Options.AddSubProtocol("lc.protobuf2.3");
|
||||
ws.Options.KeepAliveInterval = TimeSpan.FromSeconds(KEEP_ALIVE_INTERVAL);
|
||||
await ws.ConnectAsync(new Uri(""), default);
|
||||
}
|
||||
|
||||
internal async Task SendRequest(GenericCommand request) {
|
||||
request.I = RequestI;
|
||||
ArraySegment<byte> bytes = new ArraySegment<byte>(request.ToByteArray());
|
||||
try {
|
||||
await ws.SendAsync(bytes, WebSocketMessageType.Binary, true, default);
|
||||
} catch (Exception e) {
|
||||
// TODO 发送消息异常
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task Close() {
|
||||
await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "1", default);
|
||||
}
|
||||
|
||||
private async Task StartReceive() {
|
||||
byte[] buffer = new byte[RECV_BUFFER_SIZE];
|
||||
try {
|
||||
while (ws.State == WebSocketState.Open) {
|
||||
byte[] data = new byte[0];
|
||||
WebSocketReceiveResult result;
|
||||
do {
|
||||
result = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), default);
|
||||
if (result.MessageType == WebSocketMessageType.Close) {
|
||||
// TODO 区分主动断开和被动断开
|
||||
|
||||
return;
|
||||
}
|
||||
// 拼合 WebSocket Frame
|
||||
byte[] oldData = data;
|
||||
data = new byte[data.Length + result.Count];
|
||||
Array.Copy(oldData, data, oldData.Length);
|
||||
Array.Copy(buffer, 0, data, oldData.Length, result.Count);
|
||||
} while (!result.EndOfMessage);
|
||||
try {
|
||||
GenericCommand command = GenericCommand.Parser.ParseFrom(data);
|
||||
HandleCommand(command);
|
||||
} catch (Exception e) {
|
||||
// 解析消息错误
|
||||
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// TODO 连接断开
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleCommand(GenericCommand command) {
|
||||
if (command.HasI) {
|
||||
// 应答
|
||||
if (responses.TryGetValue(command.I, out TaskCompletionSource<GenericCommand> tcs)) {
|
||||
if (command.HasErrorMessage) {
|
||||
// 错误
|
||||
ErrorCommand error = command.ErrorMessage;
|
||||
int code = error.Code;
|
||||
string detail = error.Detail;
|
||||
// TODO 包装成异常抛出
|
||||
LCException exception = new LCException(code, detail);
|
||||
tcs.SetException(exception);
|
||||
} else {
|
||||
tcs.SetResult(command);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 通知
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private int RequestI {
|
||||
get {
|
||||
lock (requestILock) {
|
||||
return requestI++;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.Http;
|
||||
using LeanCloud;
|
||||
using LeanCloud.Common;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace LeanCloud.Realtime.Internal.Router {
|
||||
internal class LCRTMRouter {
|
||||
private LCRTMServer rtmServer;
|
||||
|
||||
internal LCRTMRouter() {
|
||||
}
|
||||
|
||||
internal async Task<string> GetServer() {
|
||||
if (rtmServer == null || !rtmServer.IsValid) {
|
||||
await Fetch();
|
||||
}
|
||||
return rtmServer.Server;
|
||||
}
|
||||
|
||||
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);
|
||||
HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
|
||||
request.Dispose();
|
||||
|
||||
string resultString = await response.Content.ReadAsStringAsync();
|
||||
response.Dispose();
|
||||
LCHttpUtils.PrintResponse(response, resultString);
|
||||
|
||||
rtmServer = JsonConvert.DeserializeObject<LCRTMServer>(resultString);
|
||||
|
||||
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 Server {
|
||||
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,126 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.WebSockets;
|
||||
using LeanCloud.Realtime.Protocol;
|
||||
using LeanCloud.Storage;
|
||||
using LeanCloud.Realtime.Internal.Router;
|
||||
using LeanCloud.Common;
|
||||
using Google.Protobuf;
|
||||
|
||||
namespace LeanCloud.Realtime.Internal.WebSocket {
|
||||
internal class LCWebSocketClient {
|
||||
private const int KEEP_ALIVE_INTERVAL = 10;
|
||||
private const int RECV_BUFFER_SIZE = 1024;
|
||||
|
||||
private ClientWebSocket ws;
|
||||
|
||||
private volatile int requestI = 1;
|
||||
|
||||
private readonly object requestILock = new object();
|
||||
|
||||
private Dictionary<int, TaskCompletionSource<GenericCommand>> responses;
|
||||
|
||||
internal Action<GenericCommand> OnNotification {
|
||||
get; set;
|
||||
}
|
||||
|
||||
internal LCWebSocketClient() {
|
||||
responses = new Dictionary<int, TaskCompletionSource<GenericCommand>>();
|
||||
}
|
||||
|
||||
internal async Task Connect() {
|
||||
LCRTMRouter rtmRouter = new LCRTMRouter();
|
||||
string rtmServer = await rtmRouter.GetServer();
|
||||
|
||||
ws = new ClientWebSocket();
|
||||
ws.Options.AddSubProtocol("lc.protobuf2.3");
|
||||
ws.Options.KeepAliveInterval = TimeSpan.FromSeconds(KEEP_ALIVE_INTERVAL);
|
||||
await ws.ConnectAsync(new Uri(rtmServer), default);
|
||||
_ = StartReceive();
|
||||
}
|
||||
|
||||
internal Task<GenericCommand> SendRequest(GenericCommand request) {
|
||||
TaskCompletionSource<GenericCommand> tcs = new TaskCompletionSource<GenericCommand>();
|
||||
request.I = RequestI;
|
||||
responses.Add(request.I, tcs);
|
||||
LCLogger.Debug($"=> {request.Cmd}/{request.Op}: {request.ToString()}");
|
||||
ArraySegment<byte> bytes = new ArraySegment<byte>(request.ToByteArray());
|
||||
try {
|
||||
ws.SendAsync(bytes, WebSocketMessageType.Binary, true, default);
|
||||
} catch (Exception e) {
|
||||
// TODO 发送消息异常
|
||||
|
||||
}
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
internal async Task Close() {
|
||||
await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "1", default);
|
||||
}
|
||||
|
||||
private async Task StartReceive() {
|
||||
byte[] buffer = new byte[RECV_BUFFER_SIZE];
|
||||
try {
|
||||
while (ws.State == WebSocketState.Open) {
|
||||
byte[] data = new byte[0];
|
||||
WebSocketReceiveResult result;
|
||||
do {
|
||||
result = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), default);
|
||||
if (result.MessageType == WebSocketMessageType.Close) {
|
||||
// TODO 区分主动断开和被动断开
|
||||
|
||||
return;
|
||||
}
|
||||
// 拼合 WebSocket Frame
|
||||
byte[] oldData = data;
|
||||
data = new byte[data.Length + result.Count];
|
||||
Array.Copy(oldData, data, oldData.Length);
|
||||
Array.Copy(buffer, 0, data, oldData.Length, result.Count);
|
||||
} while (!result.EndOfMessage);
|
||||
try {
|
||||
GenericCommand command = GenericCommand.Parser.ParseFrom(data);
|
||||
LCLogger.Debug($"<= {command.Cmd}/{command.Op}: {command.ToString()}");
|
||||
HandleCommand(command);
|
||||
} catch (Exception e) {
|
||||
// 解析消息错误
|
||||
LCLogger.Error(e.Message);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// TODO 连接断开
|
||||
LCLogger.Error(e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleCommand(GenericCommand command) {
|
||||
if (command.HasI) {
|
||||
// 应答
|
||||
if (responses.TryGetValue(command.I, out TaskCompletionSource<GenericCommand> tcs)) {
|
||||
if (command.HasErrorMessage) {
|
||||
// 错误
|
||||
ErrorCommand error = command.ErrorMessage;
|
||||
int code = error.Code;
|
||||
string detail = error.Detail;
|
||||
// TODO 包装成异常抛出
|
||||
LCException exception = new LCException(code, detail);
|
||||
tcs.SetException(exception);
|
||||
} else {
|
||||
tcs.SetResult(command);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 通知
|
||||
OnNotification?.Invoke(command);
|
||||
}
|
||||
}
|
||||
|
||||
private int RequestI {
|
||||
get {
|
||||
lock (requestILock) {
|
||||
return requestI++;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using LeanCloud.Realtime;
|
||||
using LeanCloud.Realtime.Internal;
|
||||
|
||||
namespace LeanCloud {
|
||||
public static class LCApplicationRealtimeExt {
|
||||
static LCConnection connection;
|
||||
|
||||
public static async Task<LCIMClient> CreateIMClient(this LCApplication application, string clientId) {
|
||||
if (string.IsNullOrEmpty(clientId)) {
|
||||
throw new ArgumentNullException(nameof(clientId));
|
||||
}
|
||||
|
||||
LCIMClient client = new LCIMClient(clientId);
|
||||
return client;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using LeanCloud.Realtime.Internal.WebSocket;
|
||||
using LeanCloud.Realtime.Protocol;
|
||||
using Google.Protobuf;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace LeanCloud.Realtime {
|
||||
public class LCIMClient {
|
||||
private string clientId;
|
||||
|
||||
private LCWebSocketClient client;
|
||||
|
||||
/// <summary>
|
||||
/// 当前用户被加入某个对话的黑名单
|
||||
/// </summary>
|
||||
public Action OnBlocked {
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前客户端被服务端强行下线
|
||||
/// </summary>
|
||||
public Action OnClosed {
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 客户端连接断开
|
||||
/// </summary>
|
||||
public Action OnDisconnected {
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 客户端连接恢复正常
|
||||
/// </summary>
|
||||
public Action OnReconnect {
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前用户被添加至某个对话
|
||||
/// </summary>
|
||||
public Action<LCIMConversation, string> OnInvited {
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前用户被从某个对话中移除
|
||||
/// </summary>
|
||||
public Action<LCIMConversation, string> OnKicked {
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 有用户被添加至某个对话
|
||||
/// </summary>
|
||||
public Action<LCIMConversation, List<string>, string> OnMembersJoined {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public Action<LCIMConversation, List<string>, string> OnMembersLeft {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public LCIMClient(string clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public async Task Open() {
|
||||
client = new LCWebSocketClient {
|
||||
OnNotification = OnNotification
|
||||
};
|
||||
await client.Connect();
|
||||
// Open Session
|
||||
GenericCommand command = NewCommand(CommandType.Session, OpType.Open);
|
||||
command.SessionMessage = new SessionCommand();
|
||||
await client.SendRequest(command);
|
||||
}
|
||||
|
||||
public async Task<LCIMChatRoom> CreateChatRoom(
|
||||
string name,
|
||||
Dictionary<string, object> properties = null) {
|
||||
LCIMChatRoom chatRoom = await CreateConv(name: name, transient: true, properties: properties) as LCIMChatRoom;
|
||||
return chatRoom;
|
||||
}
|
||||
|
||||
public async Task<LCIMConversation> CreateConversation(
|
||||
IEnumerable<string> members,
|
||||
string name = null,
|
||||
bool unique = true,
|
||||
Dictionary<string, object> properties = null) {
|
||||
return await CreateConv(members: members, name: name, unique: unique, properties: properties);
|
||||
}
|
||||
|
||||
public async Task<LCIMTemporaryConversation> CreateTemporaryConversation(
|
||||
IEnumerable<string> members,
|
||||
int ttl = 86400,
|
||||
Dictionary<string, object> properties = null) {
|
||||
LCIMTemporaryConversation tempConversation = await CreateConv(members: members, temporary: true, temporaryTtl: ttl, properties: properties) as LCIMTemporaryConversation;
|
||||
return tempConversation;
|
||||
}
|
||||
|
||||
private 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 command = NewCommand(CommandType.Conv, OpType.Start);
|
||||
ConvCommand conv = new ConvCommand {
|
||||
Transient = transient,
|
||||
Unique = unique,
|
||||
TempConv = temporary,
|
||||
TempConvTTL = temporaryTtl
|
||||
};
|
||||
if (members != null) {
|
||||
conv.M.AddRange(members);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(name)) {
|
||||
conv.N = name;
|
||||
}
|
||||
if (properties != null) {
|
||||
conv.Attr = new JsonObjectMessage {
|
||||
Data = JsonConvert.SerializeObject(properties)
|
||||
};
|
||||
}
|
||||
command.ConvMessage = conv;
|
||||
GenericCommand response = await client.SendRequest(command);
|
||||
// TODO 实例化对话对象
|
||||
|
||||
LCIMConversation conversation = new LCIMConversation();
|
||||
return conversation;
|
||||
}
|
||||
|
||||
public async Task<LCIMConversation> GetConversation(string id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<List<LCIMConversation>> GetConversationList(List<string> idList) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<LCIMConversationQuery> GetConversationQuery() {
|
||||
return null;
|
||||
}
|
||||
|
||||
private void OnNotification(GenericCommand notification) {
|
||||
switch (notification.Cmd) {
|
||||
case CommandType.Conv:
|
||||
OnConversationNotification(notification);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnConversationNotification(GenericCommand notification) {
|
||||
switch (notification.Op) {
|
||||
case OpType.Joined:
|
||||
OnConversationJoined(notification.ConvMessage);
|
||||
break;
|
||||
case OpType.MembersJoined:
|
||||
OnConversationMembersJoined(notification.ConvMessage);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnConversationJoined(ConvCommand conv) {
|
||||
OnInvited?.Invoke(null, conv.InitBy);
|
||||
}
|
||||
|
||||
private void OnConversationMembersJoined(ConvCommand conv) {
|
||||
OnMembersJoined?.Invoke(null, conv.M.ToList(), conv.InitBy);
|
||||
}
|
||||
|
||||
private GenericCommand NewCommand(CommandType cmd, OpType op) {
|
||||
return new GenericCommand {
|
||||
Cmd = cmd,
|
||||
Op = op,
|
||||
AppId = LCApplication.AppId,
|
||||
PeerId = clientId,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
using System;
|
||||
namespace LeanCloud.Realtime.Message {
|
||||
public class LCIMAudioMessage {
|
||||
public LCIMAudioMessage() {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
using System;
|
||||
namespace LeanCloud.Realtime {
|
||||
public class LCIMFileMessage {
|
||||
public LCIMFileMessage() {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
using System;
|
||||
namespace LeanCloud.Realtime.Message {
|
||||
public class LCIMImageMessage {
|
||||
public LCIMImageMessage() {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
using System;
|
||||
namespace LeanCloud.Realtime.Message {
|
||||
public class LCIMLocationMessage {
|
||||
public LCIMLocationMessage() {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Realtime {
|
||||
public class LCIMMessage {
|
||||
public string ConversationId {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string Id {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string FromClientId {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public int SentTimestamp {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public DateTime SentAt {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public int DeliveredTimestamp {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public DateTime DeliveredAt {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public int ReadTimestamp {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public DateTime ReadAt {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public int PatchedTimestamp {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public DateTime PatchedAt {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public List<string> MentionList {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public LCIMMessage() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
using System;
|
||||
|
||||
namespace LeanCloud.Realtime {
|
||||
public class LCIMRecalledMessage {
|
||||
public LCIMRecalledMessage() {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
using System;
|
||||
|
||||
namespace LeanCloud.Realtime {
|
||||
public class LCIMTextMessage {
|
||||
public LCIMTextMessage() {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
using System;
|
||||
namespace LeanCloud.Realtime.Message {
|
||||
public class LCIMTypedMessage {
|
||||
public LCIMTypedMessage() {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<ReleaseVersion>0.1.0</ReleaseVersion>
|
||||
<RootNamespace>LeanCloud.Realtime</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
<ProjectReference Include="..\Storage\Storage.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Google.Protobuf" Version="3.11.4" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Internal\" />
|
||||
<Folder Include="Internal\Router\" />
|
||||
<Folder Include="Conversation\" />
|
||||
<Folder Include="Message\" />
|
||||
<Folder Include="Internal\WebSocket\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1 @@
|
|||
protoc --proto_path=. --csharp_out=. messages2.proto.orig
|
|
@ -0,0 +1,484 @@
|
|||
syntax = "proto2";
|
||||
|
||||
package push_server.messages2;
|
||||
option csharp_namespace = "LeanCloud.Realtime.Protocol";
|
||||
|
||||
// note that this line will be removed by out build script until we
|
||||
// finally upgraded to protobuffer 3
|
||||
option objc_class_prefix = "AVIM";
|
||||
|
||||
enum CommandType {
|
||||
session = 0;
|
||||
conv = 1;
|
||||
direct = 2;
|
||||
ack = 3;
|
||||
rcp = 4;
|
||||
unread = 5;
|
||||
logs = 6;
|
||||
error = 7;
|
||||
login = 8;
|
||||
data = 9;
|
||||
room = 10;
|
||||
read = 11;
|
||||
presence = 12;
|
||||
report = 13;
|
||||
echo = 14;
|
||||
loggedin = 15;
|
||||
logout = 16;
|
||||
loggedout = 17;
|
||||
patch = 18;
|
||||
pubsub = 19;
|
||||
blacklist = 20;
|
||||
goaway = 21;
|
||||
}
|
||||
|
||||
enum OpType {
|
||||
// session
|
||||
open = 1;
|
||||
add = 2;
|
||||
remove = 3;
|
||||
close = 4;
|
||||
opened = 5;
|
||||
closed = 6;
|
||||
query = 7;
|
||||
query_result = 8;
|
||||
conflict = 9;
|
||||
added = 10;
|
||||
removed = 11;
|
||||
refresh = 12;
|
||||
refreshed = 13;
|
||||
|
||||
// conv
|
||||
start = 30;
|
||||
started = 31;
|
||||
joined = 32;
|
||||
members_joined = 33;
|
||||
// add = 34; reuse session.add
|
||||
// added = 35; reuse session.added
|
||||
// remove = 37; reuse session.remove
|
||||
// removed = 38; reuse session.removed
|
||||
left = 39;
|
||||
members_left = 40;
|
||||
// query = 41; reuse session.query
|
||||
results = 42;
|
||||
count = 43;
|
||||
result = 44;
|
||||
update = 45;
|
||||
updated = 46;
|
||||
mute = 47;
|
||||
unmute = 48;
|
||||
status = 49;
|
||||
members = 50;
|
||||
max_read = 51;
|
||||
is_member = 52;
|
||||
member_info_update = 53;
|
||||
member_info_updated = 54;
|
||||
member_info_changed = 55;
|
||||
|
||||
// room
|
||||
join = 80;
|
||||
invite = 81;
|
||||
leave = 82;
|
||||
kick = 83;
|
||||
reject = 84;
|
||||
invited = 85;
|
||||
// joined = 32; reuse the value in conv section
|
||||
// left = 39; reuse the value in conv section
|
||||
kicked = 86;
|
||||
// members-joined = 33; reuse the value in conv section
|
||||
// members-left = 40; reuse the value in conv section
|
||||
|
||||
// report
|
||||
upload = 100;
|
||||
uploaded = 101;
|
||||
|
||||
// pubsub
|
||||
subscribe = 120;
|
||||
subscribed = 121;
|
||||
unsubscribe = 122;
|
||||
unsubscribed = 123;
|
||||
is_subscribed = 124;
|
||||
|
||||
// patch
|
||||
modify = 150;
|
||||
modified = 151;
|
||||
|
||||
// blacklist, query, query_result defined with 7, 8
|
||||
block = 170;
|
||||
unblock = 171;
|
||||
blocked = 172;
|
||||
unblocked = 173;
|
||||
members_blocked = 174;
|
||||
members_unblocked = 175;
|
||||
check_block = 176;
|
||||
check_result = 177;
|
||||
|
||||
add_shutup = 180;
|
||||
remove_shutup = 181;
|
||||
query_shutup = 182;
|
||||
shutup_added = 183;
|
||||
shutup_removed = 184;
|
||||
shutup_result = 185;
|
||||
shutuped = 186;
|
||||
unshutuped = 187;
|
||||
members_shutuped = 188;
|
||||
members_unshutuped = 189;
|
||||
check_shutup = 190; // check_result define in 177
|
||||
}
|
||||
|
||||
enum StatusType {
|
||||
on = 1;
|
||||
off = 2;
|
||||
}
|
||||
|
||||
enum DeviceType {
|
||||
unknown = 0;
|
||||
android = 1;
|
||||
ios = 2;
|
||||
}
|
||||
|
||||
message SemanticVersion {
|
||||
optional int32 major = 1;
|
||||
optional int32 minor = 2;
|
||||
optional int32 patch = 3;
|
||||
optional string preRelease = 4;
|
||||
optional string build = 5;
|
||||
}
|
||||
|
||||
message AndroidVersion {
|
||||
optional string codename = 1;
|
||||
optional string apiLevel = 2;
|
||||
}
|
||||
|
||||
message SystemInfo {
|
||||
optional DeviceType deviceType = 1;
|
||||
optional SemanticVersion osVersion = 2;
|
||||
optional AndroidVersion androidVersion = 3;
|
||||
optional bool isEmulator = 4;
|
||||
}
|
||||
|
||||
message JsonObjectMessage {
|
||||
required string data = 1;
|
||||
}
|
||||
|
||||
message UnreadTuple {
|
||||
required string cid = 1;
|
||||
required int32 unread = 2;
|
||||
optional string mid = 3;
|
||||
optional int64 timestamp = 4;
|
||||
optional string from = 5;
|
||||
optional string data = 6;
|
||||
optional int64 patchTimestamp = 7;
|
||||
optional bool mentioned = 8;
|
||||
optional bytes binaryMsg = 9;
|
||||
optional int32 convType = 10;
|
||||
}
|
||||
|
||||
message LogItem {
|
||||
optional string from = 1;
|
||||
optional string data = 2;
|
||||
optional int64 timestamp = 3;
|
||||
optional string msgId = 4;
|
||||
optional int64 ackAt = 5;
|
||||
optional int64 readAt = 6;
|
||||
optional int64 patchTimestamp = 7;
|
||||
optional bool mentionAll = 8;
|
||||
repeated string mentionPids = 9;
|
||||
optional bool bin = 10;
|
||||
optional int32 convType = 11;
|
||||
}
|
||||
|
||||
message ConvMemberInfo {
|
||||
optional string pid = 1;
|
||||
optional string role = 2;
|
||||
optional string infoId = 3;
|
||||
}
|
||||
|
||||
message LoginCommand {
|
||||
optional SystemInfo systemInfo = 1;
|
||||
}
|
||||
|
||||
message LoggedinCommand {
|
||||
optional bool pushDisabled = 1;
|
||||
}
|
||||
|
||||
message DataCommand {
|
||||
repeated string ids = 1;
|
||||
repeated JsonObjectMessage msg = 2;
|
||||
optional bool offline = 3;
|
||||
}
|
||||
|
||||
message SessionCommand {
|
||||
optional int64 t = 1;
|
||||
optional string n = 2;
|
||||
optional string s = 3;
|
||||
optional string ua = 4;
|
||||
optional bool r = 5;
|
||||
optional string tag = 6;
|
||||
optional string deviceId = 7;
|
||||
repeated string sessionPeerIds = 8;
|
||||
repeated string onlineSessionPeerIds = 9;
|
||||
optional string st = 10;
|
||||
optional int32 stTtl = 11;
|
||||
optional int32 code = 12;
|
||||
optional string reason = 13;
|
||||
optional string deviceToken = 14;
|
||||
optional bool sp = 15;
|
||||
optional string detail = 16;
|
||||
optional int64 lastUnreadNotifTime = 17;
|
||||
optional int64 lastPatchTime = 18;
|
||||
optional int64 configBitmap = 19;
|
||||
optional SystemInfo systemInfo = 20;
|
||||
}
|
||||
|
||||
message ErrorCommand {
|
||||
required int32 code = 1;
|
||||
required string reason = 2;
|
||||
optional int32 appCode = 3;
|
||||
optional string detail = 4;
|
||||
repeated string pids = 5;
|
||||
optional string appMsg = 6;
|
||||
}
|
||||
|
||||
message DirectCommand {
|
||||
optional string msg = 1;
|
||||
optional string uid = 2;
|
||||
optional string fromPeerId = 3;
|
||||
optional int64 timestamp = 4;
|
||||
optional bool offline = 5;
|
||||
optional bool hasMore = 6;
|
||||
repeated string toPeerIds = 7;
|
||||
optional bool r = 10;
|
||||
optional string cid = 11;
|
||||
optional string id = 12;
|
||||
optional bool transient = 13;
|
||||
optional string dt = 14;
|
||||
optional string roomId = 15;
|
||||
optional string pushData = 16;
|
||||
optional bool will = 17;
|
||||
optional int64 patchTimestamp = 18;
|
||||
optional bytes binaryMsg = 19;
|
||||
repeated string mentionPids = 20;
|
||||
optional bool mentionAll = 21;
|
||||
optional int32 convType = 22;
|
||||
}
|
||||
|
||||
message AckCommand {
|
||||
optional int32 code = 1;
|
||||
optional string reason = 2;
|
||||
optional string mid = 3;
|
||||
optional string cid = 4;
|
||||
optional int64 t = 5;
|
||||
optional string uid = 6;
|
||||
optional int64 fromts = 7;
|
||||
optional int64 tots = 8;
|
||||
optional string type = 9;
|
||||
repeated string ids = 10;
|
||||
optional int32 appCode = 11;
|
||||
optional string appMsg = 12;
|
||||
}
|
||||
|
||||
message UnreadCommand {
|
||||
repeated UnreadTuple convs = 1;
|
||||
optional int64 notifTime = 2;
|
||||
}
|
||||
|
||||
message ConvCommand {
|
||||
repeated string m = 1;
|
||||
optional bool transient = 2;
|
||||
optional bool unique = 3;
|
||||
optional string cid = 4;
|
||||
optional string cdate = 5;
|
||||
optional string initBy = 6;
|
||||
optional string sort = 7;
|
||||
optional int32 limit = 8;
|
||||
optional int32 skip = 9;
|
||||
optional int32 flag = 10;
|
||||
optional int32 count = 11;
|
||||
optional string udate = 12;
|
||||
optional int64 t = 13;
|
||||
optional string n = 14;
|
||||
optional string s = 15;
|
||||
|
||||
optional bool statusSub = 16;
|
||||
optional bool statusPub = 17;
|
||||
optional int32 statusTTL = 18;
|
||||
optional string uniqueId = 19;
|
||||
|
||||
optional string targetClientId = 20;
|
||||
optional int64 maxReadTimestamp = 21;
|
||||
optional int64 maxAckTimestamp = 22;
|
||||
optional bool queryAllMembers = 23;
|
||||
repeated MaxReadTuple maxReadTuples = 24;
|
||||
repeated string cids = 25;
|
||||
|
||||
optional ConvMemberInfo info = 26;
|
||||
|
||||
optional bool tempConv = 27;
|
||||
optional int32 tempConvTTL = 28;
|
||||
repeated string tempConvIds = 29;
|
||||
|
||||
repeated string allowedPids = 30;
|
||||
repeated ErrorCommand failedPids = 31;
|
||||
|
||||
// used in shutup query
|
||||
optional string next = 40;
|
||||
|
||||
optional JsonObjectMessage results = 100;
|
||||
optional JsonObjectMessage where = 101;
|
||||
optional JsonObjectMessage attr = 103;
|
||||
optional JsonObjectMessage attrModified = 104;
|
||||
}
|
||||
|
||||
message RoomCommand {
|
||||
optional string roomId = 1;
|
||||
optional string s = 2;
|
||||
optional int64 t = 3;
|
||||
optional string n = 4;
|
||||
optional bool transient = 5;
|
||||
repeated string roomPeerIds = 6;
|
||||
optional string byPeerId = 7;
|
||||
}
|
||||
|
||||
message LogsCommand {
|
||||
optional string cid = 1;
|
||||
optional int32 l = 2;
|
||||
optional int32 limit = 3;
|
||||
optional int64 t = 4;
|
||||
optional int64 tt = 5;
|
||||
optional string tmid = 6;
|
||||
optional string mid = 7;
|
||||
optional string checksum = 8;
|
||||
optional bool stored = 9;
|
||||
enum QueryDirection {
|
||||
OLD = 1;
|
||||
NEW = 2;
|
||||
}
|
||||
optional QueryDirection direction = 10 [default = OLD];
|
||||
optional bool tIncluded = 11;
|
||||
optional bool ttIncluded = 12;
|
||||
optional int32 lctype = 13;
|
||||
|
||||
repeated LogItem logs = 105;
|
||||
}
|
||||
|
||||
message RcpCommand {
|
||||
optional string id = 1;
|
||||
optional string cid = 2;
|
||||
optional int64 t = 3;
|
||||
optional bool read = 4;
|
||||
optional string from = 5;
|
||||
}
|
||||
|
||||
message ReadTuple {
|
||||
required string cid = 1;
|
||||
optional int64 timestamp = 2;
|
||||
optional string mid = 3;
|
||||
}
|
||||
|
||||
message MaxReadTuple {
|
||||
optional string pid = 1;
|
||||
optional int64 maxAckTimestamp = 2;
|
||||
optional int64 maxReadTimestamp = 3;
|
||||
}
|
||||
|
||||
message ReadCommand {
|
||||
optional string cid = 1;
|
||||
repeated string cids = 2;
|
||||
repeated ReadTuple convs = 3;
|
||||
}
|
||||
|
||||
message PresenceCommand {
|
||||
optional StatusType status = 1;
|
||||
repeated string sessionPeerIds = 2;
|
||||
optional string cid = 3;
|
||||
}
|
||||
|
||||
message ReportCommand {
|
||||
optional bool initiative = 1;
|
||||
optional string type = 2;
|
||||
optional string data = 3;
|
||||
}
|
||||
|
||||
message PatchItem {
|
||||
optional string cid = 1;
|
||||
optional string mid = 2;
|
||||
optional int64 timestamp = 3;
|
||||
optional bool recall = 4;
|
||||
optional string data = 5;
|
||||
optional int64 patchTimestamp = 6;
|
||||
optional string from = 7;
|
||||
optional bytes binaryMsg = 8;
|
||||
optional bool mentionAll = 9;
|
||||
repeated string mentionPids = 10;
|
||||
optional int64 patchCode = 11;
|
||||
optional string patchReason = 12;
|
||||
}
|
||||
|
||||
message PatchCommand {
|
||||
repeated PatchItem patches = 1;
|
||||
optional int64 lastPatchTime = 2;
|
||||
}
|
||||
|
||||
message PubsubCommand {
|
||||
optional string cid = 1;
|
||||
repeated string cids = 2;
|
||||
optional string topic = 3;
|
||||
optional string subtopic = 4;
|
||||
repeated string topics = 5;
|
||||
repeated string subtopics = 6;
|
||||
optional JsonObjectMessage results = 7;
|
||||
}
|
||||
|
||||
message BlacklistCommand {
|
||||
optional string srcCid = 1;
|
||||
repeated string toPids = 2;
|
||||
optional string srcPid = 3;
|
||||
repeated string toCids = 4;
|
||||
optional int32 limit = 5;
|
||||
optional string next = 6;
|
||||
|
||||
repeated string blockedPids = 8;
|
||||
repeated string blockedCids = 9;
|
||||
|
||||
repeated string allowedPids = 10;
|
||||
repeated ErrorCommand failedPids = 11;
|
||||
|
||||
optional int64 t = 12;
|
||||
optional string n = 13;
|
||||
optional string s = 14;
|
||||
}
|
||||
|
||||
message GenericCommand {
|
||||
optional CommandType cmd = 1;
|
||||
optional OpType op = 2;
|
||||
|
||||
optional string appId = 3;
|
||||
optional string peerId = 4;
|
||||
optional int32 i = 5;
|
||||
optional string installationId = 6;
|
||||
optional int32 priority = 7;
|
||||
optional int32 service = 8;
|
||||
optional int64 serverTs = 9;
|
||||
optional int64 clientTs = 10;
|
||||
optional int32 notificationType = 11;
|
||||
|
||||
optional LoginCommand loginMessage = 100;
|
||||
optional DataCommand dataMessage = 101;
|
||||
optional SessionCommand sessionMessage = 102;
|
||||
optional ErrorCommand errorMessage = 103;
|
||||
optional DirectCommand directMessage = 104;
|
||||
optional AckCommand ackMessage = 105;
|
||||
optional UnreadCommand unreadMessage = 106;
|
||||
optional ReadCommand readMessage = 107;
|
||||
optional RcpCommand rcpMessage = 108;
|
||||
optional LogsCommand logsMessage = 109;
|
||||
optional ConvCommand convMessage = 110;
|
||||
optional RoomCommand roomMessage = 111;
|
||||
optional PresenceCommand presenceMessage = 112;
|
||||
optional ReportCommand reportMessage = 113;
|
||||
optional PatchCommand patchMessage = 114;
|
||||
optional PubsubCommand pubsubMessage = 115;
|
||||
optional BlacklistCommand blacklistMessage = 116;
|
||||
optional LoggedinCommand loggedinMessage = 117;
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using LeanCloud;
|
||||
using LeanCloud.Common;
|
||||
using LeanCloud.Realtime;
|
||||
|
||||
namespace Realtime.Test {
|
||||
public class Conversation {
|
||||
[SetUp]
|
||||
public void SetUp() {
|
||||
LCLogger.LogDelegate += Utils.Print;
|
||||
LCApplication.Initialize("ikGGdRE2YcVOemAaRbgp1xGJ-gzGzoHsz", "NUKmuRbdAhg1vrb2wexYo1jo", "https://ikggdre2.lc-cn-n1-shared.com");
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown() {
|
||||
LCLogger.LogDelegate -= Utils.Print;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CreateConversation() {
|
||||
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
|
||||
|
||||
string clientId = Guid.NewGuid().ToString();
|
||||
LCIMClient client = new LCIMClient(clientId);
|
||||
|
||||
await client.Open();
|
||||
|
||||
client.OnInvited = (conv, initBy) => {
|
||||
TestContext.WriteLine($"on invited: {initBy}");
|
||||
};
|
||||
|
||||
client.OnMembersJoined = (conv, memberList, initBy) => {
|
||||
TestContext.WriteLine($"on members joined: {initBy}");
|
||||
tcs.SetResult(null);
|
||||
};
|
||||
|
||||
List<string> memberIdList = new List<string> { "world" };
|
||||
string name = Guid.NewGuid().ToString();
|
||||
await client.CreateConversation(memberIdList, name: name, unique: false);
|
||||
|
||||
await tcs.Task;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CreateChatRoom() {
|
||||
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
|
||||
|
||||
string clientId = Guid.NewGuid().ToString();
|
||||
LCIMClient client = new LCIMClient(clientId);
|
||||
|
||||
await client.Open();
|
||||
|
||||
client.OnInvited = (conv, initBy) => {
|
||||
TestContext.WriteLine($"on invited: {initBy}");
|
||||
tcs.SetResult(null);
|
||||
};
|
||||
|
||||
string name = Guid.NewGuid().ToString();
|
||||
await client.CreateChatRoom(name);
|
||||
|
||||
await tcs.Task;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CreateTemporaryConversation() {
|
||||
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
|
||||
|
||||
string clientId = Guid.NewGuid().ToString();
|
||||
LCIMClient client = new LCIMClient(clientId);
|
||||
|
||||
await client.Open();
|
||||
|
||||
client.OnInvited = (conv, initBy) => {
|
||||
TestContext.WriteLine($"on invited: {initBy}");
|
||||
};
|
||||
|
||||
client.OnMembersJoined = (conv, memberList, initBy) => {
|
||||
TestContext.WriteLine($"on members joined: {initBy}");
|
||||
tcs.SetResult(null);
|
||||
};
|
||||
|
||||
List<string> memberIdList = new List<string> { "world" };
|
||||
await client.CreateTemporaryConversation(memberIdList);
|
||||
|
||||
await tcs.Task;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
using NUnit.Framework;
|
||||
using LeanCloud.Realtime.Protocol;
|
||||
using Google.Protobuf;
|
||||
|
||||
namespace Realtime.Test {
|
||||
public class Protobuf {
|
||||
[Test]
|
||||
public void Serialize() {
|
||||
GenericCommand command = new GenericCommand {
|
||||
Cmd = CommandType.Session,
|
||||
Op = OpType.Open,
|
||||
PeerId = "hello"
|
||||
};
|
||||
SessionCommand session = new SessionCommand {
|
||||
Code = 123
|
||||
};
|
||||
command.SessionMessage = session;
|
||||
byte[] bytes = command.ToByteArray();
|
||||
TestContext.WriteLine($"length: {bytes.Length}");
|
||||
|
||||
command = GenericCommand.Parser.ParseFrom(bytes);
|
||||
Assert.AreEqual(command.Cmd, CommandType.Session);
|
||||
Assert.AreEqual(command.Op, OpType.Open);
|
||||
Assert.AreEqual(command.PeerId, "hello");
|
||||
Assert.NotNull(command.SessionMessage);
|
||||
Assert.AreEqual(command.SessionMessage.Code, 123);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<ReleaseVersion>0.1.0</ReleaseVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="nunit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Realtime\Realtime.csproj" />
|
||||
<ProjectReference Include="..\..\Storage\Storage.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,25 @@
|
|||
using System;
|
||||
using LeanCloud;
|
||||
using LeanCloud.Common;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Realtime.Test {
|
||||
public static class Utils {
|
||||
internal static void Print(LCLogLevel level, string info) {
|
||||
switch (level) {
|
||||
case LCLogLevel.Debug:
|
||||
TestContext.Out.WriteLine($"[DEBUG] {info}");
|
||||
break;
|
||||
case LCLogLevel.Warn:
|
||||
TestContext.Out.WriteLine($"[WARNING] {info}");
|
||||
break;
|
||||
case LCLogLevel.Error:
|
||||
TestContext.Out.WriteLine($"[ERROR] {info}");
|
||||
break;
|
||||
default:
|
||||
TestContext.Out.WriteLine(info);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LeanCloud;
|
||||
using LeanCloud.Common;
|
||||
using LeanCloud.Realtime;
|
||||
|
||||
namespace RealtimeConsole {
|
||||
class MainClass {
|
||||
public static void Main(string[] args) {
|
||||
Console.WriteLine("Hello World!");
|
||||
|
||||
Start();
|
||||
|
||||
Console.ReadKey(true);
|
||||
}
|
||||
|
||||
static async Task Start() {
|
||||
LCLogger.LogDelegate += (level, info) => {
|
||||
switch (level) {
|
||||
case LCLogLevel.Debug:
|
||||
Console.WriteLine($"[DEBUG] {info}");
|
||||
break;
|
||||
case LCLogLevel.Warn:
|
||||
Console.WriteLine($"[WARNING] {info}");
|
||||
break;
|
||||
case LCLogLevel.Error:
|
||||
Console.WriteLine($"[ERROR] {info}");
|
||||
break;
|
||||
default:
|
||||
Console.WriteLine(info);
|
||||
break;
|
||||
}
|
||||
};
|
||||
LCApplication.Initialize("ikGGdRE2YcVOemAaRbgp1xGJ-gzGzoHsz", "NUKmuRbdAhg1vrb2wexYo1jo", "https://ikggdre2.lc-cn-n1-shared.com");
|
||||
|
||||
LCIMClient client = new LCIMClient("hello123");
|
||||
|
||||
try {
|
||||
await client.Open();
|
||||
Console.WriteLine($"End {Thread.CurrentThread.ManagedThreadId}");
|
||||
} catch (Exception e) {
|
||||
Console.WriteLine(e.Message);
|
||||
}
|
||||
|
||||
client.OnInvited = (conv, initBy) => {
|
||||
Console.WriteLine($"on invited: {initBy}");
|
||||
};
|
||||
|
||||
client.OnMembersJoined = (conv, memberList, initBy) => {
|
||||
Console.WriteLine($"on members joined: {initBy}");
|
||||
};
|
||||
|
||||
List<string> memberIdList = new List<string> { "world", "code" };
|
||||
string name = Guid.NewGuid().ToString();
|
||||
_ = await client.CreateTemporaryConversation(memberIdList);
|
||||
//_ = await client.CreateChatRoom(name);
|
||||
//_ = await client.CreateConversation(memberIdList, name: name, unique: false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
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("RealtimeConsole")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("")]
|
||||
[assembly: AssemblyCopyright("")]
|
||||
[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("")]
|
|
@ -0,0 +1,71 @@
|
|||
<?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>{7C563EE9-D130-4681-88B8-4523A31F6017}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>RealtimeConsole</RootNamespace>
|
||||
<AssemblyName>RealtimeConsole</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
</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>
|
||||
<ExternalConsole>true</ExternalConsole>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release</OutputPath>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<ExternalConsole>true</ExternalConsole>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Buffers">
|
||||
<HintPath>..\..\packages\System.Buffers.4.4.0\lib\netstandard2.0\System.Buffers.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Numerics.Vectors">
|
||||
<HintPath>..\..\packages\System.Numerics.Vectors.4.4.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="mscorlib" />
|
||||
<Reference Include="System.Numerics" />
|
||||
<Reference Include="System.Runtime.CompilerServices.Unsafe">
|
||||
<HintPath>..\..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Memory">
|
||||
<HintPath>..\..\packages\System.Memory.4.5.2\lib\netstandard2.0\System.Memory.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Google.Protobuf">
|
||||
<HintPath>..\..\packages\Google.Protobuf.3.11.4\lib\net45\Google.Protobuf.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Common\Common.csproj">
|
||||
<Project>{758DE75D-37D7-4392-B564-9484348B505C}</Project>
|
||||
<Name>Common</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Realtime\Realtime.csproj">
|
||||
<Project>{7084C9BD-6D26-4803-9E7F-A6D2E55D963A}</Project>
|
||||
<Name>Realtime</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Storage\Storage.csproj">
|
||||
<Project>{4DCA6CCF-DBD2-4184-9A7E-8775A024D194}</Project>
|
||||
<Name>Storage</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Google.Protobuf" version="3.11.4" targetFramework="net472" />
|
||||
<package id="System.Buffers" version="4.4.0" targetFramework="net472" />
|
||||
<package id="System.Memory" version="4.5.2" targetFramework="net472" />
|
||||
<package id="System.Numerics.Vectors" version="4.4.0" targetFramework="net472" />
|
||||
<package id="System.Runtime.CompilerServices.Unsafe" version="4.5.2" targetFramework="net472" />
|
||||
</packages>
|
|
@ -9,6 +9,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Storage", "Storage\Storage.
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Storage.Test", "Test\Storage.Test\Storage.Test.csproj", "{531F8181-FFE0-476E-9D0A-93F13CAD1183}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Realtime", "Realtime\Realtime.csproj", "{7084C9BD-6D26-4803-9E7F-A6D2E55D963A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Realtime.Test", "Test\Realtime.Test\Realtime.Test.csproj", "{746B0DE6-C504-4568-BA6D-4A08A91A5E35}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RealtimeConsole", "Test\RealtimeConsole\RealtimeConsole.csproj", "{7C563EE9-D130-4681-88B8-4523A31F6017}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -27,9 +33,23 @@ Global
|
|||
{531F8181-FFE0-476E-9D0A-93F13CAD1183}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{531F8181-FFE0-476E-9D0A-93F13CAD1183}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{531F8181-FFE0-476E-9D0A-93F13CAD1183}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7084C9BD-6D26-4803-9E7F-A6D2E55D963A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7084C9BD-6D26-4803-9E7F-A6D2E55D963A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7084C9BD-6D26-4803-9E7F-A6D2E55D963A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7084C9BD-6D26-4803-9E7F-A6D2E55D963A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{746B0DE6-C504-4568-BA6D-4A08A91A5E35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{746B0DE6-C504-4568-BA6D-4A08A91A5E35}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{746B0DE6-C504-4568-BA6D-4A08A91A5E35}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{746B0DE6-C504-4568-BA6D-4A08A91A5E35}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7C563EE9-D130-4681-88B8-4523A31F6017}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7C563EE9-D130-4681-88B8-4523A31F6017}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7C563EE9-D130-4681-88B8-4523A31F6017}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7C563EE9-D130-4681-88B8-4523A31F6017}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{531F8181-FFE0-476E-9D0A-93F13CAD1183} = {C827DA2F-6AB4-48D8-AB5B-6DAB925F8933}
|
||||
{746B0DE6-C504-4568-BA6D-4A08A91A5E35} = {C827DA2F-6AB4-48D8-AB5B-6DAB925F8933}
|
||||
{7C563EE9-D130-4681-88B8-4523A31F6017} = {C827DA2F-6AB4-48D8-AB5B-6DAB925F8933}
|
||||
EndGlobalSection
|
||||
GlobalSection(MonoDevelopProperties) = preSolution
|
||||
version = 0.1.0
|
||||
|
|
Loading…
Reference in New Issue