* 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
oneRain 2020-03-12 16:23:21 +08:00
parent 57b1a59cd0
commit bf2af41565
32 changed files with 14246 additions and 0 deletions

View File

@ -0,0 +1,8 @@
using System;
namespace LeanCloud.Realtime {
public class LCIMChatRoom : LCIMConversation {
public LCIMChatRoom() {
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -0,0 +1,7 @@
using System;
namespace LeanCloud.Realtime {
public class LCIMConversationQuery {
public LCIMConversationQuery() {
}
}
}

View File

@ -0,0 +1,8 @@
using System;
namespace LeanCloud.Realtime {
public class LCIMTemporaryConversation : LCIMConversation {
public LCIMTemporaryConversation() {
}
}
}

View File

@ -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++;
};
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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++;
};
}
}
}
}

View File

@ -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;
}
}
}

193
Realtime/LCIMClient.cs Normal file
View File

@ -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,
};
}
}
}

View File

@ -0,0 +1,7 @@
using System;
namespace LeanCloud.Realtime.Message {
public class LCIMAudioMessage {
public LCIMAudioMessage() {
}
}
}

View File

@ -0,0 +1,7 @@
using System;
namespace LeanCloud.Realtime {
public class LCIMFileMessage {
public LCIMFileMessage() {
}
}
}

View File

@ -0,0 +1,7 @@
using System;
namespace LeanCloud.Realtime.Message {
public class LCIMImageMessage {
public LCIMImageMessage() {
}
}
}

View File

@ -0,0 +1,7 @@
using System;
namespace LeanCloud.Realtime.Message {
public class LCIMLocationMessage {
public LCIMLocationMessage() {
}
}
}

View File

@ -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() {
}
}
}

View File

@ -0,0 +1,8 @@
using System;
namespace LeanCloud.Realtime {
public class LCIMRecalledMessage {
public LCIMRecalledMessage() {
}
}
}

View File

@ -0,0 +1,8 @@
using System;
namespace LeanCloud.Realtime {
public class LCIMTextMessage {
public LCIMTextMessage() {
}
}
}

View File

@ -0,0 +1,7 @@
using System;
namespace LeanCloud.Realtime.Message {
public class LCIMTypedMessage {
public LCIMTypedMessage() {
}
}
}

23
Realtime/Realtime.csproj Normal file
View File

@ -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

View File

@ -0,0 +1 @@
protoc --proto_path=. --csharp_out=. messages2.proto.orig

View File

@ -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;
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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>

View File

@ -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;
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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("")]

View File

@ -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>

View File

@ -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>

View File

@ -9,6 +9,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Storage", "Storage\Storage.
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Storage.Test", "Test\Storage.Test\Storage.Test.csproj", "{531F8181-FFE0-476E-9D0A-93F13CAD1183}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Storage.Test", "Test\Storage.Test\Storage.Test.csproj", "{531F8181-FFE0-476E-9D0A-93F13CAD1183}"
EndProject 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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}.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.ActiveCfg = Release|Any CPU
{531F8181-FFE0-476E-9D0A-93F13CAD1183}.Release|Any CPU.Build.0 = 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 EndGlobalSection
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{531F8181-FFE0-476E-9D0A-93F13CAD1183} = {C827DA2F-6AB4-48D8-AB5B-6DAB925F8933} {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 EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution GlobalSection(MonoDevelopProperties) = preSolution
version = 0.1.0 version = 0.1.0