chore: 抽象出各模块的 Controller
parent
cd453ce134
commit
890fdc76f4
|
@ -17,9 +17,9 @@ namespace LeanCloud.Realtime {
|
||||||
Cid = Id,
|
Cid = Id,
|
||||||
Limit = limit
|
Limit = limit
|
||||||
};
|
};
|
||||||
GenericCommand request = client.NewCommand(CommandType.Conv, OpType.Members);
|
GenericCommand request = Client.NewCommand(CommandType.Conv, OpType.Members);
|
||||||
request.ConvMessage = conv;
|
request.ConvMessage = conv;
|
||||||
GenericCommand response = await client.connection.SendRequest(request);
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
List<string> memberList = response.ConvMessage.M.ToList();
|
List<string> memberList = response.ConvMessage.M.ToList();
|
||||||
return memberList;
|
return memberList;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ using System.Linq;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Google.Protobuf;
|
using Google.Protobuf;
|
||||||
using LeanCloud.Realtime.Protocol;
|
using LeanCloud.Realtime.Protocol;
|
||||||
using LeanCloud.Storage.Internal.Codec;
|
|
||||||
using LeanCloud.Storage;
|
using LeanCloud.Storage;
|
||||||
|
|
||||||
namespace LeanCloud.Realtime {
|
namespace LeanCloud.Realtime {
|
||||||
|
@ -71,12 +70,14 @@ namespace LeanCloud.Realtime {
|
||||||
get; private set;
|
get; private set;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected readonly LCIMClient client;
|
protected LCIMClient Client {
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
private Dictionary<string, object> customProperties;
|
private Dictionary<string, object> customProperties;
|
||||||
|
|
||||||
internal LCIMConversation(LCIMClient client) {
|
internal LCIMConversation(LCIMClient client) {
|
||||||
this.client = client;
|
Client = client;
|
||||||
customProperties = new Dictionary<string, object>();
|
customProperties = new Dictionary<string, object>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,13 +86,7 @@ namespace LeanCloud.Realtime {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<int> GetMembersCount() {
|
public async Task<int> GetMembersCount() {
|
||||||
ConvCommand conv = new ConvCommand {
|
return await Client.ConversationController.GetMembersCount(Id);
|
||||||
Cid = Id,
|
|
||||||
};
|
|
||||||
GenericCommand command = client.NewCommand(CommandType.Conv, OpType.Count);
|
|
||||||
command.ConvMessage = conv;
|
|
||||||
GenericCommand response = await client.connection.SendRequest(command);
|
|
||||||
return response.ConvMessage.Count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -99,45 +94,26 @@ namespace LeanCloud.Realtime {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="message"></param>
|
/// <param name="message"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<LCIMConversation> Read() {
|
public async Task Read() {
|
||||||
if (LastMessage == null) {
|
if (LastMessage == null) {
|
||||||
return this;
|
return;
|
||||||
}
|
}
|
||||||
ReadCommand read = new ReadCommand();
|
await Client.ConversationController.Read(Id, LastMessage);
|
||||||
ReadTuple tuple = new ReadTuple {
|
|
||||||
Cid = Id,
|
|
||||||
Mid = LastMessage.Id,
|
|
||||||
Timestamp = LastMessage.SentTimestamp
|
|
||||||
};
|
|
||||||
read.Convs.Add(tuple);
|
|
||||||
GenericCommand request = client.NewCommand(CommandType.Read, OpType.Open);
|
|
||||||
request.ReadMessage = read;
|
|
||||||
await client.connection.SendRequest(request);
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<LCIMConversation> Save() {
|
/// <summary>
|
||||||
ConvCommand conv = new ConvCommand {
|
/// 修改对话属性
|
||||||
Cid = Id,
|
/// </summary>
|
||||||
};
|
/// <param name="attributes"></param>
|
||||||
// 注意序列化是否与存储一致
|
/// <returns></returns>
|
||||||
string json = JsonConvert.SerializeObject(LCEncoder.Encode(customProperties));
|
public async Task UpdateInfo(Dictionary<string, object> attributes) {
|
||||||
conv.Attr = new JsonObjectMessage {
|
if (attributes == null || attributes.Count == 0) {
|
||||||
Data = json
|
throw new ArgumentNullException(nameof(attributes));
|
||||||
};
|
|
||||||
GenericCommand request = client.NewCommand(CommandType.Conv, OpType.Update);
|
|
||||||
request.ConvMessage = conv;
|
|
||||||
GenericCommand response = await client.connection.SendRequest(request);
|
|
||||||
JsonObjectMessage attr = response.ConvMessage.AttrModified;
|
|
||||||
// 更新自定义属性
|
|
||||||
if (attr != null) {
|
|
||||||
Dictionary<string, object> data = JsonConvert.DeserializeObject<Dictionary<string, object>>(attr.Data);
|
|
||||||
Dictionary<string, object> objectData = LCDecoder.Decode(data) as Dictionary<string, object>;
|
|
||||||
foreach (KeyValuePair<string, object> kv in objectData) {
|
|
||||||
customProperties[kv.Key] = kv.Value;
|
|
||||||
}
|
}
|
||||||
|
Dictionary<string, object> updatedAttr = await Client.ConversationController.UpdateInfo(Id, attributes);
|
||||||
|
if (updatedAttr != null) {
|
||||||
|
MergeInfo(updatedAttr);
|
||||||
}
|
}
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -145,30 +121,11 @@ namespace LeanCloud.Realtime {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="clientIds">用户 Id</param>
|
/// <param name="clientIds">用户 Id</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<LCIMPartiallySuccessResult> Add(IEnumerable<string> clientIds) {
|
public async Task<LCIMPartiallySuccessResult> AddMembers(IEnumerable<string> clientIds) {
|
||||||
if (clientIds == null || clientIds.Count() == 0) {
|
if (clientIds == null || clientIds.Count() == 0) {
|
||||||
throw new ArgumentNullException(nameof(clientIds));
|
throw new ArgumentNullException(nameof(clientIds));
|
||||||
}
|
}
|
||||||
ConvCommand conv = new ConvCommand {
|
return await Client.ConversationController.AddMembers(Id, clientIds);
|
||||||
Cid = Id,
|
|
||||||
};
|
|
||||||
conv.M.AddRange(clientIds);
|
|
||||||
// 签名参数
|
|
||||||
if (client.SignatureFactory != null) {
|
|
||||||
LCIMSignature signature = client.SignatureFactory.CreateConversationSignature(Id,
|
|
||||||
client.ClientId,
|
|
||||||
clientIds,
|
|
||||||
LCIMSignatureAction.Invite);
|
|
||||||
conv.S = signature.Signature;
|
|
||||||
conv.T = signature.Timestamp;
|
|
||||||
conv.N = signature.Nonce;
|
|
||||||
}
|
|
||||||
GenericCommand request = client.NewCommand(CommandType.Conv, OpType.Add);
|
|
||||||
request.ConvMessage = conv;
|
|
||||||
GenericCommand response = await client.connection.SendRequest(request);
|
|
||||||
List<string> allowedIds = response.ConvMessage.AllowedPids.ToList();
|
|
||||||
List<ErrorCommand> errors = response.ConvMessage.FailedPids.ToList();
|
|
||||||
return NewPartiallySuccessResult(allowedIds, errors);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -176,56 +133,35 @@ namespace LeanCloud.Realtime {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="removeIds">用户 Id</param>
|
/// <param name="removeIds">用户 Id</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<LCIMPartiallySuccessResult> Remove(IEnumerable<string> removeIds) {
|
public async Task<LCIMPartiallySuccessResult> RemoveMembers(IEnumerable<string> removeIds) {
|
||||||
if (removeIds == null || removeIds.Count() == 0) {
|
if (removeIds == null || removeIds.Count() == 0) {
|
||||||
throw new ArgumentNullException(nameof(removeIds));
|
throw new ArgumentNullException(nameof(removeIds));
|
||||||
}
|
}
|
||||||
ConvCommand conv = new ConvCommand {
|
return await Client.ConversationController.RemoveMembers(Id, removeIds);
|
||||||
Cid = Id,
|
|
||||||
};
|
|
||||||
conv.M.AddRange(removeIds);
|
|
||||||
// 签名参数
|
|
||||||
if (client.SignatureFactory != null) {
|
|
||||||
LCIMSignature signature = client.SignatureFactory.CreateConversationSignature(Id,
|
|
||||||
client.ClientId,
|
|
||||||
removeIds,
|
|
||||||
LCIMSignatureAction.Kick);
|
|
||||||
conv.S = signature.Signature;
|
|
||||||
conv.T = signature.Timestamp;
|
|
||||||
conv.N = signature.Nonce;
|
|
||||||
}
|
|
||||||
GenericCommand request = client.NewCommand(CommandType.Conv, OpType.Remove);
|
|
||||||
request.ConvMessage = conv;
|
|
||||||
GenericCommand response = await client.connection.SendRequest(request);
|
|
||||||
List<string> allowedIds = response.ConvMessage.AllowedPids.ToList();
|
|
||||||
List<ErrorCommand> errors = response.ConvMessage.FailedPids.ToList();
|
|
||||||
return NewPartiallySuccessResult(allowedIds, errors);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 加入对话
|
/// 加入对话
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<LCIMConversation> Join() {
|
public async Task Join() {
|
||||||
LCIMPartiallySuccessResult result = await Add(new string[] { client.ClientId });
|
LCIMPartiallySuccessResult result = await AddMembers(new string[] { Client.Id });
|
||||||
if (!result.IsSuccess) {
|
if (!result.IsSuccess) {
|
||||||
LCIMOperationFailure error = result.FailureList[0];
|
LCIMOperationFailure error = result.FailureList[0];
|
||||||
throw new LCException(error.Code, error.Reason);
|
throw new LCException(error.Code, error.Reason);
|
||||||
}
|
}
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 离开对话
|
/// 离开对话
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<LCIMConversation> Quit() {
|
public async Task Quit() {
|
||||||
LCIMPartiallySuccessResult result = await Remove(new string[] { client.ClientId });
|
LCIMPartiallySuccessResult result = await RemoveMembers(new string[] { Client.Id });
|
||||||
if (!result.IsSuccess) {
|
if (!result.IsSuccess) {
|
||||||
LCIMOperationFailure error = result.FailureList[0];
|
LCIMOperationFailure error = result.FailureList[0];
|
||||||
throw new LCException(error.Code, error.Reason);
|
throw new LCException(error.Code, error.Reason);
|
||||||
}
|
}
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -234,24 +170,10 @@ namespace LeanCloud.Realtime {
|
||||||
/// <param name="message"></param>
|
/// <param name="message"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<LCIMMessage> Send(LCIMMessage message) {
|
public async Task<LCIMMessage> Send(LCIMMessage message) {
|
||||||
DirectCommand direct = new DirectCommand {
|
if (message == null) {
|
||||||
FromPeerId = client.ClientId,
|
throw new ArgumentNullException(nameof(message));
|
||||||
Cid = Id,
|
|
||||||
};
|
|
||||||
if (message is LCIMTypedMessage typedMessage) {
|
|
||||||
direct.Msg = JsonConvert.SerializeObject(typedMessage.Encode());
|
|
||||||
} else if (message is LCIMBinaryMessage binaryMessage) {
|
|
||||||
direct.BinaryMsg = ByteString.CopyFrom(binaryMessage.Data);
|
|
||||||
} else {
|
|
||||||
throw new ArgumentException("Message MUST BE LCIMTypedMessage or LCIMBinaryMessage.");
|
|
||||||
}
|
}
|
||||||
GenericCommand command = client.NewDirectCommand();
|
await Client.MessageController.Send(Id, message);
|
||||||
command.DirectMessage = direct;
|
|
||||||
GenericCommand response = await client.connection.SendRequest(command);
|
|
||||||
// 消息发送应答
|
|
||||||
AckCommand ack = response.AckMessage;
|
|
||||||
message.Id = ack.Uid;
|
|
||||||
message.DeliveredTimestamp = ack.T;
|
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,30 +181,18 @@ namespace LeanCloud.Realtime {
|
||||||
/// 静音
|
/// 静音
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<LCIMConversation> Mute() {
|
public async Task Mute() {
|
||||||
ConvCommand conv = new ConvCommand {
|
await Client.ConversationController.Mute(Id);
|
||||||
Cid = Id
|
|
||||||
};
|
|
||||||
GenericCommand request = client.NewCommand(CommandType.Conv, OpType.Mute);
|
|
||||||
request.ConvMessage = conv;
|
|
||||||
await client.connection.SendRequest(request);
|
|
||||||
IsMute = true;
|
IsMute = true;
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 取消静音
|
/// 取消静音
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<LCIMConversation> Unmute() {
|
public async Task Unmute() {
|
||||||
ConvCommand conv = new ConvCommand {
|
await Client.ConversationController.Unmute(Id);
|
||||||
Cid = Id
|
|
||||||
};
|
|
||||||
GenericCommand request = client.NewCommand(CommandType.Conv, OpType.Unmute);
|
|
||||||
request.ConvMessage = conv;
|
|
||||||
await client.connection.SendRequest(request);
|
|
||||||
IsMute = false;
|
IsMute = false;
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -294,14 +204,7 @@ namespace LeanCloud.Realtime {
|
||||||
if (clientIds == null || clientIds.Count() == 0) {
|
if (clientIds == null || clientIds.Count() == 0) {
|
||||||
throw new ArgumentNullException(nameof(clientIds));
|
throw new ArgumentNullException(nameof(clientIds));
|
||||||
}
|
}
|
||||||
ConvCommand conv = new ConvCommand {
|
return await Client.ConversationController.MuteMembers(Id, clientIds);
|
||||||
Cid = Id
|
|
||||||
};
|
|
||||||
conv.M.AddRange(clientIds);
|
|
||||||
GenericCommand request = client.NewCommand(CommandType.Conv, OpType.AddShutup);
|
|
||||||
request.ConvMessage = conv;
|
|
||||||
GenericCommand response = await client.connection.SendRequest(request);
|
|
||||||
return NewPartiallySuccessResult(response.ConvMessage.AllowedPids, response.ConvMessage.FailedPids);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -313,14 +216,7 @@ namespace LeanCloud.Realtime {
|
||||||
if (clientIds == null || clientIds.Count() == 0) {
|
if (clientIds == null || clientIds.Count() == 0) {
|
||||||
throw new ArgumentNullException(nameof(clientIds));
|
throw new ArgumentNullException(nameof(clientIds));
|
||||||
}
|
}
|
||||||
ConvCommand conv = new ConvCommand {
|
return await Client.ConversationController.UnmuteMembers(Id, clientIds);
|
||||||
Cid = Id
|
|
||||||
};
|
|
||||||
conv.M.AddRange(clientIds);
|
|
||||||
GenericCommand request = client.NewCommand(CommandType.Conv, OpType.Remove);
|
|
||||||
request.ConvMessage = conv;
|
|
||||||
GenericCommand response = await client.connection.SendRequest(request);
|
|
||||||
return NewPartiallySuccessResult(response.ConvMessage.AllowedPids, response.ConvMessage.FailedPids);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -332,46 +228,14 @@ namespace LeanCloud.Realtime {
|
||||||
if (clientIds == null || clientIds.Count() == 0) {
|
if (clientIds == null || clientIds.Count() == 0) {
|
||||||
throw new ArgumentNullException(nameof(clientIds));
|
throw new ArgumentNullException(nameof(clientIds));
|
||||||
}
|
}
|
||||||
BlacklistCommand blacklist = new BlacklistCommand {
|
return await Client.ConversationController.BlockMembers(Id, clientIds);
|
||||||
SrcCid = Id,
|
|
||||||
};
|
|
||||||
blacklist.ToPids.AddRange(clientIds);
|
|
||||||
if (client.SignatureFactory != null) {
|
|
||||||
LCIMSignature signature = client.SignatureFactory.CreateBlacklistSignature(Id,
|
|
||||||
client.ClientId,
|
|
||||||
clientIds,
|
|
||||||
LCIMSignatureAction.ConversationBlockClients);
|
|
||||||
blacklist.S = signature.Signature;
|
|
||||||
blacklist.T = signature.Timestamp;
|
|
||||||
blacklist.N = signature.Nonce;
|
|
||||||
}
|
|
||||||
GenericCommand request = client.NewCommand(CommandType.Blacklist, OpType.Block);
|
|
||||||
request.BlacklistMessage = blacklist;
|
|
||||||
GenericCommand response = await client.connection.SendRequest(request);
|
|
||||||
return NewPartiallySuccessResult(response.BlacklistMessage.AllowedPids, response.BlacklistMessage.FailedPids);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<LCIMPartiallySuccessResult> UnblockMembers(IEnumerable<string> clientIds) {
|
public async Task<LCIMPartiallySuccessResult> UnblockMembers(IEnumerable<string> clientIds) {
|
||||||
if (clientIds == null || clientIds.Count() == 0) {
|
if (clientIds == null || clientIds.Count() == 0) {
|
||||||
throw new ArgumentNullException(nameof(clientIds));
|
throw new ArgumentNullException(nameof(clientIds));
|
||||||
}
|
}
|
||||||
BlacklistCommand blacklist = new BlacklistCommand {
|
return await Client.ConversationController.UnblockMembers(Id, clientIds);
|
||||||
SrcCid = Id,
|
|
||||||
};
|
|
||||||
blacklist.ToPids.AddRange(clientIds);
|
|
||||||
if (client.SignatureFactory != null) {
|
|
||||||
LCIMSignature signature = client.SignatureFactory.CreateBlacklistSignature(Id,
|
|
||||||
client.ClientId,
|
|
||||||
clientIds,
|
|
||||||
LCIMSignatureAction.ConversationUnblockClients);
|
|
||||||
blacklist.S = signature.Signature;
|
|
||||||
blacklist.T = signature.Timestamp;
|
|
||||||
blacklist.N = signature.Nonce;
|
|
||||||
}
|
|
||||||
GenericCommand request = client.NewCommand(CommandType.Blacklist, OpType.Unblock);
|
|
||||||
request.BlacklistMessage = blacklist;
|
|
||||||
GenericCommand response = await client.connection.SendRequest(request);
|
|
||||||
return NewPartiallySuccessResult(response.BlacklistMessage.AllowedPids, response.BlacklistMessage.FailedPids);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -379,21 +243,11 @@ namespace LeanCloud.Realtime {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="message"></param>
|
/// <param name="message"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<LCIMRecalledMessage> Recall(LCIMMessage message) {
|
public async Task RecallMessage(LCIMMessage message) {
|
||||||
if (message == null) {
|
if (message == null) {
|
||||||
throw new ArgumentNullException(nameof(message));
|
throw new ArgumentNullException(nameof(message));
|
||||||
}
|
}
|
||||||
PatchCommand patch = new PatchCommand();
|
await Client.MessageController.RecallMessage(Id, message);
|
||||||
PatchItem item = new PatchItem {
|
|
||||||
Cid = Id,
|
|
||||||
Mid = message.Id,
|
|
||||||
Recall = true
|
|
||||||
};
|
|
||||||
patch.Patches.Add(item);
|
|
||||||
GenericCommand request = client.NewCommand(CommandType.Patch, OpType.Modify);
|
|
||||||
request.PatchMessage = patch;
|
|
||||||
GenericCommand response = await client.connection.SendRequest(request);
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -402,61 +256,45 @@ namespace LeanCloud.Realtime {
|
||||||
/// <param name="oldMessage"></param>
|
/// <param name="oldMessage"></param>
|
||||||
/// <param name="newMessage"></param>
|
/// <param name="newMessage"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<LCIMMessage> Update(LCIMMessage oldMessage, LCIMMessage newMessage) {
|
public async Task UpdateMessage(LCIMMessage oldMessage, LCIMMessage newMessage) {
|
||||||
if (oldMessage == null) {
|
if (oldMessage == null) {
|
||||||
throw new ArgumentNullException(nameof(oldMessage));
|
throw new ArgumentNullException(nameof(oldMessage));
|
||||||
}
|
}
|
||||||
if (newMessage == null) {
|
if (newMessage == null) {
|
||||||
throw new ArgumentNullException(nameof(newMessage));
|
throw new ArgumentNullException(nameof(newMessage));
|
||||||
}
|
}
|
||||||
PatchCommand patch = new PatchCommand();
|
await Client.MessageController.UpdateMessage(Id, oldMessage, newMessage);
|
||||||
PatchItem item = new PatchItem {
|
|
||||||
Cid = Id,
|
|
||||||
Mid = oldMessage.Id,
|
|
||||||
Timestamp = oldMessage.DeliveredTimestamp,
|
|
||||||
Recall = false,
|
|
||||||
};
|
|
||||||
if (newMessage is LCIMTypedMessage typedMessage) {
|
|
||||||
item.Data = JsonConvert.SerializeObject(typedMessage.Encode());
|
|
||||||
} else if (newMessage is LCIMBinaryMessage binaryMessage) {
|
|
||||||
item.BinaryMsg = ByteString.CopyFrom(binaryMessage.Data);
|
|
||||||
}
|
|
||||||
if (newMessage.MentionList != null) {
|
|
||||||
item.MentionPids.AddRange(newMessage.MentionList);
|
|
||||||
}
|
|
||||||
if (newMessage.MentionAll) {
|
|
||||||
item.MentionAll = newMessage.MentionAll;
|
|
||||||
}
|
|
||||||
patch.Patches.Add(item);
|
|
||||||
GenericCommand request = client.NewCommand(CommandType.Patch, OpType.Modify);
|
|
||||||
request.PatchMessage = patch;
|
|
||||||
GenericCommand response = await client.connection.SendRequest(request);
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<LCIMConversation> UpdateMemberRole(string memberId, string role) {
|
/// <summary>
|
||||||
|
/// 更新对话中成员的角色
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="memberId"></param>
|
||||||
|
/// <param name="role"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task UpdateMemberRole(string memberId, string role) {
|
||||||
if (string.IsNullOrEmpty(memberId)) {
|
if (string.IsNullOrEmpty(memberId)) {
|
||||||
throw new ArgumentNullException(nameof(memberId));
|
throw new ArgumentNullException(nameof(memberId));
|
||||||
}
|
}
|
||||||
if (role != LCIMConversationMemberInfo.Manager && role != LCIMConversationMemberInfo.Member) {
|
if (role != LCIMConversationMemberInfo.Manager && role != LCIMConversationMemberInfo.Member) {
|
||||||
throw new ArgumentException("role MUST be Manager Or Memebr");
|
throw new ArgumentException("role MUST be Manager Or Memebr");
|
||||||
}
|
}
|
||||||
ConvCommand conv = new ConvCommand {
|
await Client.ConversationController.UpdateMemberRole(Id, memberId, role);
|
||||||
Cid = Id,
|
|
||||||
TargetClientId = memberId,
|
|
||||||
Info = new ConvMemberInfo {
|
|
||||||
Pid = memberId,
|
|
||||||
Role = role
|
|
||||||
}
|
|
||||||
};
|
|
||||||
GenericCommand request = client.NewCommand(CommandType.Conv, OpType.MemberInfoUpdate);
|
|
||||||
request.ConvMessage = conv;
|
|
||||||
GenericCommand response = await client.connection.SendRequest(request);
|
|
||||||
// TODO 同步 members
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取对话中成员的角色(只返回管理员)
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<List<LCIMConversationMemberInfo>> GetAllMemberInfo() {
|
||||||
|
return await Client.ConversationController.GetAllMemberInfo(Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取对话中指定成员的角色
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="memberId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
public async Task<LCIMConversationMemberInfo> GetMemberInfo(string memberId) {
|
public async Task<LCIMConversationMemberInfo> GetMemberInfo(string memberId) {
|
||||||
if (string.IsNullOrEmpty(memberId)) {
|
if (string.IsNullOrEmpty(memberId)) {
|
||||||
throw new ArgumentNullException(nameof(memberId));
|
throw new ArgumentNullException(nameof(memberId));
|
||||||
|
@ -470,88 +308,20 @@ namespace LeanCloud.Realtime {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<LCIMConversationMemberInfo>> GetAllMemberInfo() {
|
public async Task<LCIMPageResult> QueryMutedMembers(int limit = 10, string next = null) {
|
||||||
string path = "classes/_ConversationMemberInfo";
|
return await Client.ConversationController.QueryMutedMembers(Id, limit, next);
|
||||||
Dictionary<string, object> headers = new Dictionary<string, object> {
|
|
||||||
{ "X-LC-IM-Session-Token", client.SessionToken }
|
|
||||||
};
|
|
||||||
Dictionary<string, object> queryParams = new Dictionary<string, object> {
|
|
||||||
{ "client_id", client.ClientId },
|
|
||||||
{ "cid", Id }
|
|
||||||
};
|
|
||||||
Dictionary<string, object> response = await LCApplication.HttpClient.Get<Dictionary<string, object>>(path,
|
|
||||||
headers: headers, queryParams: queryParams);
|
|
||||||
List<object> results = response["results"] as List<object>;
|
|
||||||
List<LCIMConversationMemberInfo> memberList = new List<LCIMConversationMemberInfo>();
|
|
||||||
foreach (Dictionary<string, object> item in results) {
|
|
||||||
LCIMConversationMemberInfo member = new LCIMConversationMemberInfo {
|
|
||||||
ConversationId = item["cid"] as string,
|
|
||||||
MemberId = item["clientId"] as string,
|
|
||||||
Role = item["role"] as string
|
|
||||||
};
|
|
||||||
memberList.Add(member);
|
|
||||||
}
|
|
||||||
return memberList;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<LCIMPageResult> QueryMutedMembers(int limit = 50, string next = null) {
|
public async Task<LCIMPageResult> QueryBlockedMembers(int limit = 10, string next = null) {
|
||||||
ConvCommand conv = new ConvCommand {
|
return await Client.ConversationController.QueryBlockedMembers(Id, limit, next);
|
||||||
Cid = Id,
|
|
||||||
Limit = limit,
|
|
||||||
Next = next
|
|
||||||
};
|
|
||||||
GenericCommand request = client.NewCommand(CommandType.Conv, OpType.QueryShutup);
|
|
||||||
request.ConvMessage = conv;
|
|
||||||
GenericCommand response = await client.connection.SendRequest(request);
|
|
||||||
return new LCIMPageResult {
|
|
||||||
Results = response.ConvMessage.M.ToList(),
|
|
||||||
Next = response.ConvMessage.Next
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<LCIMMessage>> QueryMessage(LCIMMessageQueryEndpoint start = null,
|
public async Task<List<LCIMMessage>> QueryMessages(LCIMMessageQueryEndpoint start = null,
|
||||||
LCIMMessageQueryEndpoint end = null,
|
LCIMMessageQueryEndpoint end = null,
|
||||||
LCIMMessageQueryDirection direction = LCIMMessageQueryDirection.NewToOld,
|
LCIMMessageQueryDirection direction = LCIMMessageQueryDirection.NewToOld,
|
||||||
int limit = 20,
|
int limit = 20,
|
||||||
int messageType = 0) {
|
int messageType = 0) {
|
||||||
LogsCommand logs = new LogsCommand {
|
return await Client.MessageController.QueryMessages(Id, start, end, direction, limit, messageType);
|
||||||
Cid = Id
|
|
||||||
};
|
|
||||||
if (start != null) {
|
|
||||||
logs.T = start.SentTimestamp;
|
|
||||||
logs.Mid = start.MessageId;
|
|
||||||
logs.TIncluded = start.IsClosed;
|
|
||||||
}
|
|
||||||
if (end != null) {
|
|
||||||
logs.Tt = end.SentTimestamp;
|
|
||||||
logs.Tmid = end.MessageId;
|
|
||||||
logs.TtIncluded = end.IsClosed;
|
|
||||||
}
|
|
||||||
logs.Direction = direction == LCIMMessageQueryDirection.NewToOld ?
|
|
||||||
LogsCommand.Types.QueryDirection.Old : LogsCommand.Types.QueryDirection.New;
|
|
||||||
logs.Limit = limit;
|
|
||||||
if (messageType != 0) {
|
|
||||||
logs.Lctype = messageType;
|
|
||||||
}
|
|
||||||
GenericCommand request = client.NewCommand(CommandType.Logs, OpType.Open);
|
|
||||||
request.LogsMessage = logs;
|
|
||||||
GenericCommand response = await client.connection.SendRequest(request);
|
|
||||||
// TODO 反序列化聊天记录
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private LCIMPartiallySuccessResult NewPartiallySuccessResult(IEnumerable<string> succesfulIds, IEnumerable<ErrorCommand> errors) {
|
|
||||||
LCIMPartiallySuccessResult result = new LCIMPartiallySuccessResult {
|
|
||||||
SuccessfulClientIdList = succesfulIds.ToList()
|
|
||||||
};
|
|
||||||
if (errors != null) {
|
|
||||||
result.FailureList = new List<LCIMOperationFailure>();
|
|
||||||
foreach (ErrorCommand error in errors) {
|
|
||||||
result.FailureList.Add(new LCIMOperationFailure(error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void MergeFrom(ConvCommand conv) {
|
internal void MergeFrom(ConvCommand conv) {
|
||||||
|
@ -595,5 +365,14 @@ namespace LeanCloud.Realtime {
|
||||||
MutedMemberIdList = muo as List<string>;
|
MutedMemberIdList = muo as List<string>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void MergeInfo(Dictionary<string, object> attr) {
|
||||||
|
if (attr == null || attr.Count == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach (KeyValuePair<string, object> kv in attr) {
|
||||||
|
customProperties[kv.Key] = kv.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -234,12 +234,16 @@ namespace LeanCloud.Realtime {
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
public async Task<List<LCIMConversation>> Find() {
|
public async Task<List<LCIMConversation>> Find() {
|
||||||
GenericCommand command = new GenericCommand {
|
GenericCommand command = new GenericCommand {
|
||||||
Cmd = CommandType.Conv,
|
Cmd = CommandType.Conv,
|
||||||
Op = OpType.Query,
|
Op = OpType.Query,
|
||||||
AppId = LCApplication.AppId,
|
AppId = LCApplication.AppId,
|
||||||
PeerId = client.ClientId,
|
PeerId = client.Id,
|
||||||
};
|
};
|
||||||
ConvCommand conv = new ConvCommand();
|
ConvCommand conv = new ConvCommand();
|
||||||
string where = condition.BuildWhere();
|
string where = condition.BuildWhere();
|
||||||
|
@ -249,16 +253,16 @@ namespace LeanCloud.Realtime {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
command.ConvMessage = conv;
|
command.ConvMessage = conv;
|
||||||
GenericCommand response = await client.connection.SendRequest(command);
|
GenericCommand response = await client.Connection.SendRequest(command);
|
||||||
JsonObjectMessage results = response.ConvMessage.Results;
|
JsonObjectMessage results = response.ConvMessage.Results;
|
||||||
List<object> convs = JsonConvert.DeserializeObject<List<object>>(results.Data, new LCJsonConverter());
|
List<object> convs = JsonConvert.DeserializeObject<List<object>>(results.Data, new LCJsonConverter());
|
||||||
List<LCIMConversation> convList = new List<LCIMConversation>(convs.Count);
|
List<LCIMConversation> convList = new List<LCIMConversation>(convs.Count);
|
||||||
foreach (object c in convs) {
|
foreach (object c in convs) {
|
||||||
Dictionary<string, object> cd = c as Dictionary<string, object>;
|
Dictionary<string, object> cd = c as Dictionary<string, object>;
|
||||||
string convId = cd["objectId"] as string;
|
string convId = cd["objectId"] as string;
|
||||||
if (!client.conversationDict.TryGetValue(convId, out LCIMConversation conversation)) {
|
if (!client.ConversationDict.TryGetValue(convId, out LCIMConversation conversation)) {
|
||||||
conversation = new LCIMConversation(client);
|
conversation = new LCIMConversation(client);
|
||||||
client.conversationDict[convId] = conversation;
|
client.ConversationDict[convId] = conversation;
|
||||||
}
|
}
|
||||||
conversation.MergeFrom(cd);
|
conversation.MergeFrom(cd);
|
||||||
convList.Add(conversation);
|
convList.Add(conversation);
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using LeanCloud.Realtime.Protocol;
|
||||||
|
using LeanCloud.Realtime.Internal.WebSocket;
|
||||||
|
|
||||||
|
namespace LeanCloud.Realtime.Internal.Controller {
|
||||||
|
internal abstract class LCIMController {
|
||||||
|
protected LCIMClient Client {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal LCIMController(LCIMClient client) {
|
||||||
|
Client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal abstract Task OnNotification(GenericCommand notification);
|
||||||
|
|
||||||
|
protected LCWebSocketConnection Connection {
|
||||||
|
get {
|
||||||
|
return Client.Connection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,402 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using LeanCloud.Realtime.Protocol;
|
||||||
|
using LeanCloud.Storage.Internal;
|
||||||
|
using LeanCloud.Storage.Internal.Codec;
|
||||||
|
using Google.Protobuf;
|
||||||
|
|
||||||
|
namespace LeanCloud.Realtime.Internal.Controller {
|
||||||
|
internal class LCIMConversationController : LCIMController {
|
||||||
|
internal LCIMConversationController(LCIMClient client) : base(client) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建对话
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="members"></param>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="transient"></param>
|
||||||
|
/// <param name="unique"></param>
|
||||||
|
/// <param name="temporary"></param>
|
||||||
|
/// <param name="temporaryTtl"></param>
|
||||||
|
/// <param name="properties"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task<LCIMConversation> CreateConv(
|
||||||
|
IEnumerable<string> members = null,
|
||||||
|
string name = null,
|
||||||
|
bool transient = false,
|
||||||
|
bool unique = true,
|
||||||
|
bool temporary = false,
|
||||||
|
int temporaryTtl = 86400,
|
||||||
|
Dictionary<string, object> properties = null) {
|
||||||
|
GenericCommand request = Client.NewCommand(CommandType.Conv, OpType.Start);
|
||||||
|
ConvCommand conv = new ConvCommand {
|
||||||
|
Transient = transient,
|
||||||
|
Unique = unique,
|
||||||
|
};
|
||||||
|
if (members != null) {
|
||||||
|
conv.M.AddRange(members);
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(name)) {
|
||||||
|
conv.N = name;
|
||||||
|
}
|
||||||
|
if (temporary) {
|
||||||
|
conv.TempConv = temporary;
|
||||||
|
conv.TempConvTTL = temporaryTtl;
|
||||||
|
}
|
||||||
|
if (properties != null) {
|
||||||
|
conv.Attr = new JsonObjectMessage {
|
||||||
|
Data = JsonConvert.SerializeObject(LCEncoder.Encode(properties))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (Client.SignatureFactory != null) {
|
||||||
|
LCIMSignature signature = Client.SignatureFactory.CreateStartConversationSignature(Client.Id, members);
|
||||||
|
conv.S = signature.Signature;
|
||||||
|
conv.T = signature.Timestamp;
|
||||||
|
conv.N = signature.Nonce;
|
||||||
|
}
|
||||||
|
request.ConvMessage = conv;
|
||||||
|
GenericCommand response = await Connection.SendRequest(request);
|
||||||
|
string convId = response.ConvMessage.Cid;
|
||||||
|
if (!Client.ConversationDict.TryGetValue(convId, out LCIMConversation conversation)) {
|
||||||
|
if (transient) {
|
||||||
|
conversation = new LCIMChatRoom(Client);
|
||||||
|
} else if (temporary) {
|
||||||
|
conversation = new LCIMTemporaryConversation(Client);
|
||||||
|
} else if (properties != null && properties.ContainsKey("system")) {
|
||||||
|
conversation = new LCIMServiceConversation(Client);
|
||||||
|
} else {
|
||||||
|
conversation = new LCIMConversation(Client);
|
||||||
|
}
|
||||||
|
Client.ConversationDict[convId] = conversation;
|
||||||
|
}
|
||||||
|
// 合并请求数据
|
||||||
|
conversation.Name = name;
|
||||||
|
conversation.MemberIdList = members?.ToList();
|
||||||
|
// 合并服务端推送的数据
|
||||||
|
conversation.MergeFrom(response.ConvMessage);
|
||||||
|
return conversation;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task<int> GetMembersCount(string convId) {
|
||||||
|
ConvCommand conv = new ConvCommand {
|
||||||
|
Cid = convId,
|
||||||
|
};
|
||||||
|
GenericCommand command = Client.NewCommand(CommandType.Conv, OpType.Count);
|
||||||
|
command.ConvMessage = conv;
|
||||||
|
GenericCommand response = await Connection.SendRequest(command);
|
||||||
|
return response.ConvMessage.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task Read(string convId, LCIMMessage message) {
|
||||||
|
ReadCommand read = new ReadCommand();
|
||||||
|
ReadTuple tuple = new ReadTuple {
|
||||||
|
Cid = convId,
|
||||||
|
Mid = message.Id,
|
||||||
|
Timestamp = message.SentTimestamp
|
||||||
|
};
|
||||||
|
read.Convs.Add(tuple);
|
||||||
|
GenericCommand request = Client.NewCommand(CommandType.Read, OpType.Open);
|
||||||
|
request.ReadMessage = read;
|
||||||
|
await Client.Connection.SendRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task<Dictionary<string, object>> UpdateInfo(string convId, Dictionary<string, object> attributes) {
|
||||||
|
ConvCommand conv = new ConvCommand {
|
||||||
|
Cid = convId,
|
||||||
|
};
|
||||||
|
conv.Attr = new JsonObjectMessage {
|
||||||
|
Data = JsonConvert.SerializeObject(attributes)
|
||||||
|
};
|
||||||
|
GenericCommand request = Client.NewCommand(CommandType.Conv, OpType.Update);
|
||||||
|
request.ConvMessage = conv;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
JsonObjectMessage attr = response.ConvMessage.AttrModified;
|
||||||
|
// 更新自定义属性
|
||||||
|
if (attr != null) {
|
||||||
|
Dictionary<string, object> updatedAttr = JsonConvert.DeserializeObject<Dictionary<string, object>>(attr.Data);
|
||||||
|
return updatedAttr;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task<LCIMPartiallySuccessResult> AddMembers(string convId, IEnumerable<string> clientIds) {
|
||||||
|
ConvCommand conv = new ConvCommand {
|
||||||
|
Cid = convId,
|
||||||
|
};
|
||||||
|
conv.M.AddRange(clientIds);
|
||||||
|
// 签名参数
|
||||||
|
if (Client.SignatureFactory != null) {
|
||||||
|
LCIMSignature signature = Client.SignatureFactory.CreateConversationSignature(convId,
|
||||||
|
Client.Id,
|
||||||
|
clientIds,
|
||||||
|
LCIMSignatureAction.Invite);
|
||||||
|
conv.S = signature.Signature;
|
||||||
|
conv.T = signature.Timestamp;
|
||||||
|
conv.N = signature.Nonce;
|
||||||
|
}
|
||||||
|
GenericCommand request = Client.NewCommand(CommandType.Conv, OpType.Add);
|
||||||
|
request.ConvMessage = conv;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
List<string> allowedIds = response.ConvMessage.AllowedPids.ToList();
|
||||||
|
List<ErrorCommand> errors = response.ConvMessage.FailedPids.ToList();
|
||||||
|
return NewPartiallySuccessResult(allowedIds, errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task<LCIMPartiallySuccessResult> RemoveMembers(string convId, IEnumerable<string> removeIds) {
|
||||||
|
ConvCommand conv = new ConvCommand {
|
||||||
|
Cid = convId,
|
||||||
|
};
|
||||||
|
conv.M.AddRange(removeIds);
|
||||||
|
// 签名参数
|
||||||
|
if (Client.SignatureFactory != null) {
|
||||||
|
LCIMSignature signature = Client.SignatureFactory.CreateConversationSignature(convId,
|
||||||
|
Client.Id,
|
||||||
|
removeIds,
|
||||||
|
LCIMSignatureAction.Kick);
|
||||||
|
conv.S = signature.Signature;
|
||||||
|
conv.T = signature.Timestamp;
|
||||||
|
conv.N = signature.Nonce;
|
||||||
|
}
|
||||||
|
GenericCommand request = Client.NewCommand(CommandType.Conv, OpType.Remove);
|
||||||
|
request.ConvMessage = conv;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
List<string> allowedIds = response.ConvMessage.AllowedPids.ToList();
|
||||||
|
List<ErrorCommand> errors = response.ConvMessage.FailedPids.ToList();
|
||||||
|
return NewPartiallySuccessResult(allowedIds, errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task Mute(string convId) {
|
||||||
|
ConvCommand conv = new ConvCommand {
|
||||||
|
Cid = convId
|
||||||
|
};
|
||||||
|
GenericCommand request = Client.NewCommand(CommandType.Conv, OpType.Mute);
|
||||||
|
request.ConvMessage = conv;
|
||||||
|
await Client.Connection.SendRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task Unmute(string convId) {
|
||||||
|
ConvCommand conv = new ConvCommand {
|
||||||
|
Cid = convId
|
||||||
|
};
|
||||||
|
GenericCommand request = Client.NewCommand(CommandType.Conv, OpType.Unmute);
|
||||||
|
request.ConvMessage = conv;
|
||||||
|
await Client.Connection.SendRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task<LCIMPartiallySuccessResult> MuteMembers(string convId, IEnumerable<string> clientIds) {
|
||||||
|
if (clientIds == null || clientIds.Count() == 0) {
|
||||||
|
throw new ArgumentNullException(nameof(clientIds));
|
||||||
|
}
|
||||||
|
ConvCommand conv = new ConvCommand {
|
||||||
|
Cid = convId
|
||||||
|
};
|
||||||
|
conv.M.AddRange(clientIds);
|
||||||
|
GenericCommand request = Client.NewCommand(CommandType.Conv, OpType.AddShutup);
|
||||||
|
request.ConvMessage = conv;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
return NewPartiallySuccessResult(response.ConvMessage.AllowedPids, response.ConvMessage.FailedPids);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task<LCIMPartiallySuccessResult> UnmuteMembers(string convId, IEnumerable<string> clientIds) {
|
||||||
|
ConvCommand conv = new ConvCommand {
|
||||||
|
Cid = convId
|
||||||
|
};
|
||||||
|
conv.M.AddRange(clientIds);
|
||||||
|
GenericCommand request = Client.NewCommand(CommandType.Conv, OpType.Remove);
|
||||||
|
request.ConvMessage = conv;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
return NewPartiallySuccessResult(response.ConvMessage.AllowedPids, response.ConvMessage.FailedPids);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task<LCIMPartiallySuccessResult> BlockMembers(string convId, IEnumerable<string> clientIds) {
|
||||||
|
BlacklistCommand blacklist = new BlacklistCommand {
|
||||||
|
SrcCid = convId,
|
||||||
|
};
|
||||||
|
blacklist.ToPids.AddRange(clientIds);
|
||||||
|
if (Client.SignatureFactory != null) {
|
||||||
|
LCIMSignature signature = Client.SignatureFactory.CreateBlacklistSignature(convId,
|
||||||
|
Client.Id,
|
||||||
|
clientIds,
|
||||||
|
LCIMSignatureAction.ConversationBlockClients);
|
||||||
|
blacklist.S = signature.Signature;
|
||||||
|
blacklist.T = signature.Timestamp;
|
||||||
|
blacklist.N = signature.Nonce;
|
||||||
|
}
|
||||||
|
GenericCommand request = Client.NewCommand(CommandType.Blacklist, OpType.Block);
|
||||||
|
request.BlacklistMessage = blacklist;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
return NewPartiallySuccessResult(response.BlacklistMessage.AllowedPids, response.BlacklistMessage.FailedPids);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task<LCIMPartiallySuccessResult> UnblockMembers(string convId, IEnumerable<string> clientIds) {
|
||||||
|
BlacklistCommand blacklist = new BlacklistCommand {
|
||||||
|
SrcCid = convId,
|
||||||
|
};
|
||||||
|
blacklist.ToPids.AddRange(clientIds);
|
||||||
|
if (Client.SignatureFactory != null) {
|
||||||
|
LCIMSignature signature = Client.SignatureFactory.CreateBlacklistSignature(convId,
|
||||||
|
Client.Id,
|
||||||
|
clientIds,
|
||||||
|
LCIMSignatureAction.ConversationUnblockClients);
|
||||||
|
blacklist.S = signature.Signature;
|
||||||
|
blacklist.T = signature.Timestamp;
|
||||||
|
blacklist.N = signature.Nonce;
|
||||||
|
}
|
||||||
|
GenericCommand request = Client.NewCommand(CommandType.Blacklist, OpType.Unblock);
|
||||||
|
request.BlacklistMessage = blacklist;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
return NewPartiallySuccessResult(response.BlacklistMessage.AllowedPids, response.BlacklistMessage.FailedPids);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task UpdateMemberRole(string convId, string memberId, string role) {
|
||||||
|
ConvCommand conv = new ConvCommand {
|
||||||
|
Cid = convId,
|
||||||
|
TargetClientId = memberId,
|
||||||
|
Info = new ConvMemberInfo {
|
||||||
|
Pid = memberId,
|
||||||
|
Role = role
|
||||||
|
}
|
||||||
|
};
|
||||||
|
GenericCommand request = Client.NewCommand(CommandType.Conv, OpType.MemberInfoUpdate);
|
||||||
|
request.ConvMessage = conv;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task<List<LCIMConversationMemberInfo>> GetAllMemberInfo(string convId) {
|
||||||
|
string path = "classes/_ConversationMemberInfo";
|
||||||
|
string token = await Client.SessionController.GetToken();
|
||||||
|
Dictionary<string, object> headers = new Dictionary<string, object> {
|
||||||
|
{ "X-LC-IM-Session-Token", token }
|
||||||
|
};
|
||||||
|
Dictionary<string, object> queryParams = new Dictionary<string, object> {
|
||||||
|
{ "client_id", Client.Id },
|
||||||
|
{ "cid", convId }
|
||||||
|
};
|
||||||
|
Dictionary<string, object> response = await LCApplication.HttpClient.Get<Dictionary<string, object>>(path,
|
||||||
|
headers: headers, queryParams: queryParams);
|
||||||
|
List<object> results = response["results"] as List<object>;
|
||||||
|
List<LCIMConversationMemberInfo> memberList = new List<LCIMConversationMemberInfo>();
|
||||||
|
foreach (Dictionary<string, object> item in results) {
|
||||||
|
LCIMConversationMemberInfo member = new LCIMConversationMemberInfo {
|
||||||
|
ConversationId = item["cid"] as string,
|
||||||
|
MemberId = item["clientId"] as string,
|
||||||
|
Role = item["role"] as string
|
||||||
|
};
|
||||||
|
memberList.Add(member);
|
||||||
|
}
|
||||||
|
return memberList;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task<LCIMPageResult> QueryMutedMembers(string convId, int limit = 10, string next = null) {
|
||||||
|
ConvCommand conv = new ConvCommand {
|
||||||
|
Cid = convId,
|
||||||
|
Limit = limit,
|
||||||
|
Next = next
|
||||||
|
};
|
||||||
|
GenericCommand request = Client.NewCommand(CommandType.Conv, OpType.QueryShutup);
|
||||||
|
request.ConvMessage = conv;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
return new LCIMPageResult {
|
||||||
|
Results = response.ConvMessage.M.ToList(),
|
||||||
|
Next = response.ConvMessage.Next
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task<LCIMPageResult> QueryBlockedMembers(string convId, int limit = 10, string next = null) {
|
||||||
|
BlacklistCommand black = new BlacklistCommand {
|
||||||
|
SrcCid = convId,
|
||||||
|
Limit = limit,
|
||||||
|
Next = next
|
||||||
|
};
|
||||||
|
GenericCommand request = Client.NewCommand(CommandType.Blacklist, OpType.Query);
|
||||||
|
request.BlacklistMessage = black;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
return new LCIMPageResult {
|
||||||
|
Results = response.BlacklistMessage.BlockedPids.ToList(),
|
||||||
|
Next = response.BlacklistMessage.Next
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private LCIMPartiallySuccessResult NewPartiallySuccessResult(IEnumerable<string> succesfulIds, IEnumerable<ErrorCommand> errors) {
|
||||||
|
LCIMPartiallySuccessResult result = new LCIMPartiallySuccessResult {
|
||||||
|
SuccessfulClientIdList = succesfulIds.ToList()
|
||||||
|
};
|
||||||
|
if (errors != null) {
|
||||||
|
result.FailureList = new List<LCIMOperationFailure>();
|
||||||
|
foreach (ErrorCommand error in errors) {
|
||||||
|
result.FailureList.Add(new LCIMOperationFailure(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override async Task OnNotification(GenericCommand notification) {
|
||||||
|
ConvCommand conv = notification.ConvMessage;
|
||||||
|
switch (notification.Op) {
|
||||||
|
case OpType.Joined:
|
||||||
|
await OnConversationJoined(conv);
|
||||||
|
break;
|
||||||
|
case OpType.MembersJoined:
|
||||||
|
await OnConversationMembersJoined(conv);
|
||||||
|
break;
|
||||||
|
case OpType.Left:
|
||||||
|
await OnConversationLeft(conv);
|
||||||
|
break;
|
||||||
|
case OpType.MembersLeft:
|
||||||
|
await OnConversationMemberLeft(conv);
|
||||||
|
break;
|
||||||
|
case OpType.Updated:
|
||||||
|
await OnConversationPropertiesUpdated(conv);
|
||||||
|
break;
|
||||||
|
case OpType.MemberInfoChanged:
|
||||||
|
await OnConversationMemberInfoChanged(conv);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnConversationJoined(ConvCommand conv) {
|
||||||
|
LCIMConversation conversation = await Client.GetOrQueryConversation(conv.Cid);
|
||||||
|
conversation.MergeFrom(conv);
|
||||||
|
Client.OnInvited?.Invoke(conversation, conv.InitBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnConversationMembersJoined(ConvCommand conv) {
|
||||||
|
LCIMConversation conversation = await Client.GetOrQueryConversation(conv.Cid);
|
||||||
|
conversation.MergeFrom(conv);
|
||||||
|
Client.OnMembersJoined?.Invoke(conversation, conv.M.ToList(), conv.InitBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnConversationLeft(ConvCommand conv) {
|
||||||
|
LCIMConversation conversation = await Client.GetOrQueryConversation(conv.Cid);
|
||||||
|
Client.OnKicked?.Invoke(conversation, conv.InitBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnConversationMemberLeft(ConvCommand conv) {
|
||||||
|
LCIMConversation conversation = await Client.GetOrQueryConversation(conv.Cid);
|
||||||
|
List<string> leftIdList = conv.M.ToList();
|
||||||
|
Client.OnMembersLeft?.Invoke(conversation, leftIdList, conv.InitBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnConversationPropertiesUpdated(ConvCommand conv) {
|
||||||
|
LCIMConversation conversation = await Client.GetOrQueryConversation(conv.Cid);
|
||||||
|
Dictionary<string, object> updatedAttr = JsonConvert.DeserializeObject<Dictionary<string, object>>(conv.AttrModified.Data,
|
||||||
|
new LCJsonConverter());
|
||||||
|
// 更新内存数据
|
||||||
|
conversation.MergeInfo(updatedAttr);
|
||||||
|
Client.OnConversationInfoUpdated?.Invoke(conversation, updatedAttr, conv.InitBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnConversationMemberInfoChanged(ConvCommand conv) {
|
||||||
|
LCIMConversation conversation = await Client.GetOrQueryConversation(conv.Cid);
|
||||||
|
ConvMemberInfo memberInfo = conv.Info;
|
||||||
|
Client.OnMemberInfoUpdated?.Invoke(conversation, memberInfo.Pid, memberInfo.Role, conv.InitBy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using LeanCloud.Realtime.Protocol;
|
||||||
|
|
||||||
|
namespace LeanCloud.Realtime.Internal.Controller {
|
||||||
|
internal class LCIMGoAwayController : LCIMController {
|
||||||
|
internal LCIMGoAwayController(LCIMClient client) : base(client) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override async Task OnNotification(GenericCommand notification) {
|
||||||
|
// TODO 清空缓存,断开连接,等待重新连接
|
||||||
|
Connection.Router.Reset();
|
||||||
|
await Connection.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,150 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Google.Protobuf;
|
||||||
|
using LeanCloud.Storage.Internal;
|
||||||
|
using LeanCloud.Realtime.Protocol;
|
||||||
|
|
||||||
|
namespace LeanCloud.Realtime.Internal.Controller {
|
||||||
|
internal class LCIMMessageController : LCIMController {
|
||||||
|
internal LCIMMessageController(LCIMClient client) : base(client) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task<LCIMMessage> Send(string convId, LCIMMessage message) {
|
||||||
|
DirectCommand direct = new DirectCommand {
|
||||||
|
FromPeerId = Client.Id,
|
||||||
|
Cid = convId,
|
||||||
|
};
|
||||||
|
if (message is LCIMTypedMessage typedMessage) {
|
||||||
|
direct.Msg = JsonConvert.SerializeObject(typedMessage.Encode());
|
||||||
|
} else if (message is LCIMBinaryMessage binaryMessage) {
|
||||||
|
direct.BinaryMsg = ByteString.CopyFrom(binaryMessage.Data);
|
||||||
|
} else {
|
||||||
|
throw new ArgumentException("Message MUST BE LCIMTypedMessage or LCIMBinaryMessage.");
|
||||||
|
}
|
||||||
|
GenericCommand command = Client.NewDirectCommand();
|
||||||
|
command.DirectMessage = direct;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(command);
|
||||||
|
// 消息发送应答
|
||||||
|
AckCommand ack = response.AckMessage;
|
||||||
|
message.Id = ack.Uid;
|
||||||
|
message.DeliveredTimestamp = ack.T;
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task RecallMessage(string convId, LCIMMessage message) {
|
||||||
|
PatchCommand patch = new PatchCommand();
|
||||||
|
PatchItem item = new PatchItem {
|
||||||
|
Cid = convId,
|
||||||
|
Mid = message.Id,
|
||||||
|
Recall = true
|
||||||
|
};
|
||||||
|
patch.Patches.Add(item);
|
||||||
|
GenericCommand request = Client.NewCommand(CommandType.Patch, OpType.Modify);
|
||||||
|
request.PatchMessage = patch;
|
||||||
|
await Client.Connection.SendRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task UpdateMessage(string convId, LCIMMessage oldMessage, LCIMMessage newMessage) {
|
||||||
|
PatchCommand patch = new PatchCommand();
|
||||||
|
PatchItem item = new PatchItem {
|
||||||
|
Cid = convId,
|
||||||
|
Mid = oldMessage.Id,
|
||||||
|
Timestamp = oldMessage.DeliveredTimestamp,
|
||||||
|
Recall = false,
|
||||||
|
};
|
||||||
|
if (newMessage is LCIMTypedMessage typedMessage) {
|
||||||
|
item.Data = JsonConvert.SerializeObject(typedMessage.Encode());
|
||||||
|
} else if (newMessage is LCIMBinaryMessage binaryMessage) {
|
||||||
|
item.BinaryMsg = ByteString.CopyFrom(binaryMessage.Data);
|
||||||
|
}
|
||||||
|
if (newMessage.MentionList != null) {
|
||||||
|
item.MentionPids.AddRange(newMessage.MentionList);
|
||||||
|
}
|
||||||
|
if (newMessage.MentionAll) {
|
||||||
|
item.MentionAll = newMessage.MentionAll;
|
||||||
|
}
|
||||||
|
patch.Patches.Add(item);
|
||||||
|
GenericCommand request = Client.NewCommand(CommandType.Patch, OpType.Modify);
|
||||||
|
request.PatchMessage = patch;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task<List<LCIMMessage>> QueryMessages(string convId,
|
||||||
|
LCIMMessageQueryEndpoint start = null,
|
||||||
|
LCIMMessageQueryEndpoint end = null,
|
||||||
|
LCIMMessageQueryDirection direction = LCIMMessageQueryDirection.NewToOld,
|
||||||
|
int limit = 20,
|
||||||
|
int messageType = 0) {
|
||||||
|
LogsCommand logs = new LogsCommand {
|
||||||
|
Cid = convId
|
||||||
|
};
|
||||||
|
if (start != null) {
|
||||||
|
logs.T = start.SentTimestamp;
|
||||||
|
logs.Mid = start.MessageId;
|
||||||
|
logs.TIncluded = start.IsClosed;
|
||||||
|
}
|
||||||
|
if (end != null) {
|
||||||
|
logs.Tt = end.SentTimestamp;
|
||||||
|
logs.Tmid = end.MessageId;
|
||||||
|
logs.TtIncluded = end.IsClosed;
|
||||||
|
}
|
||||||
|
logs.Direction = direction == LCIMMessageQueryDirection.NewToOld ?
|
||||||
|
LogsCommand.Types.QueryDirection.Old : LogsCommand.Types.QueryDirection.New;
|
||||||
|
logs.Limit = limit;
|
||||||
|
if (messageType != 0) {
|
||||||
|
logs.Lctype = messageType;
|
||||||
|
}
|
||||||
|
GenericCommand request = Client.NewCommand(CommandType.Logs, OpType.Open);
|
||||||
|
request.LogsMessage = logs;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
// TODO 反序列化聊天记录
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override async Task OnNotification(GenericCommand notification) {
|
||||||
|
DirectCommand direct = notification.DirectMessage;
|
||||||
|
LCIMMessage message = null;
|
||||||
|
if (direct.HasBinaryMsg) {
|
||||||
|
// 二进制消息
|
||||||
|
byte[] bytes = direct.BinaryMsg.ToByteArray();
|
||||||
|
message = new LCIMBinaryMessage(bytes);
|
||||||
|
} else {
|
||||||
|
// 文本消息
|
||||||
|
string messageData = direct.Msg;
|
||||||
|
Dictionary<string, object> msg = JsonConvert.DeserializeObject<Dictionary<string, object>>(messageData,
|
||||||
|
new LCJsonConverter());
|
||||||
|
int msgType = (int)(long)msg["_lctype"];
|
||||||
|
switch (msgType) {
|
||||||
|
case -1:
|
||||||
|
message = new LCIMTextMessage();
|
||||||
|
break;
|
||||||
|
case -2:
|
||||||
|
message = new LCIMImageMessage();
|
||||||
|
break;
|
||||||
|
case -3:
|
||||||
|
message = new LCIMAudioMessage();
|
||||||
|
break;
|
||||||
|
case -4:
|
||||||
|
message = new LCIMVideoMessage();
|
||||||
|
break;
|
||||||
|
case -5:
|
||||||
|
message = new LCIMLocationMessage();
|
||||||
|
break;
|
||||||
|
case -6:
|
||||||
|
message = new LCIMFileMessage();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
message.Decode(direct);
|
||||||
|
}
|
||||||
|
// 获取对话
|
||||||
|
LCIMConversation conversation = await Client.GetOrQueryConversation(direct.Cid);
|
||||||
|
Client.OnMessage?.Invoke(conversation, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using LeanCloud.Realtime.Protocol;
|
||||||
|
|
||||||
|
namespace LeanCloud.Realtime.Internal.Controller {
|
||||||
|
internal class LCIMSessionController : LCIMController {
|
||||||
|
private string token;
|
||||||
|
private DateTimeOffset expiredAt;
|
||||||
|
|
||||||
|
internal LCIMSessionController(LCIMClient client) : base(client) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task Open() {
|
||||||
|
SessionCommand session = NewSessionCommand();
|
||||||
|
GenericCommand request = Client.NewCommand(CommandType.Session, OpType.Open);
|
||||||
|
request.SessionMessage = session;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
UpdateSession(response.SessionMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task Close() {
|
||||||
|
GenericCommand request = Client.NewCommand(CommandType.Session, OpType.Close);
|
||||||
|
await Client.Connection.SendRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task<string> GetToken() {
|
||||||
|
if (IsExpired) {
|
||||||
|
await Refresh();
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Refresh() {
|
||||||
|
SessionCommand session = NewSessionCommand();
|
||||||
|
GenericCommand request = Client.NewCommand(CommandType.Session, OpType.Refresh);
|
||||||
|
request.SessionMessage = session;
|
||||||
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
UpdateSession(response.SessionMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SessionCommand NewSessionCommand() {
|
||||||
|
SessionCommand session = new SessionCommand();
|
||||||
|
if (Client.SignatureFactory != null) {
|
||||||
|
LCIMSignature signature = Client.SignatureFactory.CreateConnectSignature(Client.Id);
|
||||||
|
session.S = signature.Signature;
|
||||||
|
session.T = signature.Timestamp;
|
||||||
|
session.N = signature.Nonce;
|
||||||
|
}
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateSession(SessionCommand session) {
|
||||||
|
token = session.St;
|
||||||
|
int ttl = session.StTtl;
|
||||||
|
expiredAt = DateTimeOffset.Now + TimeSpan.FromSeconds(ttl);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override async Task OnNotification(GenericCommand notification) {
|
||||||
|
switch (notification.Op) {
|
||||||
|
case OpType.Closed:
|
||||||
|
await OnClosed(notification.SessionMessage);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsExpired {
|
||||||
|
get {
|
||||||
|
return DateTimeOffset.Now > expiredAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnClosed(SessionCommand session) {
|
||||||
|
int code = session.Code;
|
||||||
|
string reason = session.Reason;
|
||||||
|
string detail = session.Detail;
|
||||||
|
await Connection.Close();
|
||||||
|
Client.OnClose?.Invoke(code, reason, detail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using LeanCloud.Realtime.Protocol;
|
||||||
|
|
||||||
|
namespace LeanCloud.Realtime.Internal.Controller {
|
||||||
|
internal class LCIMUnreadController : LCIMController {
|
||||||
|
internal LCIMUnreadController(LCIMClient client) : base(client) {
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override async Task OnNotification(GenericCommand notification) {
|
||||||
|
UnreadCommand unread = notification.UnreadMessage;
|
||||||
|
List<LCIMConversation> conversationList = new List<LCIMConversation>();
|
||||||
|
foreach (UnreadTuple conv in unread.Convs) {
|
||||||
|
// 查询对话
|
||||||
|
LCIMConversation conversation = await Client.GetOrQueryConversation(conv.Cid);
|
||||||
|
conversation.Unread = conv.Unread;
|
||||||
|
// TODO 反序列化对话
|
||||||
|
// 最后一条消息
|
||||||
|
JsonConvert.DeserializeObject<Dictionary<string, object>>(conv.Data);
|
||||||
|
conversationList.Add(conversation);
|
||||||
|
}
|
||||||
|
Client.OnUnreadMessagesCountUpdated?.Invoke(conversationList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,10 @@ namespace LeanCloud.Realtime.Internal.Router {
|
||||||
return rtmServer.Server;
|
return rtmServer.Server;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void Reset() {
|
||||||
|
rtmServer = null;
|
||||||
|
}
|
||||||
|
|
||||||
async Task<LCRTMServer> Fetch() {
|
async Task<LCRTMServer> Fetch() {
|
||||||
string server = await LCApplication.AppRouter.GetRealtimeServer();
|
string server = await LCApplication.AppRouter.GetRealtimeServer();
|
||||||
string url = $"{server}/v1/route?appId={LCApplication.AppId}&secure=1";
|
string url = $"{server}/v1/route?appId={LCApplication.AppId}&secure=1";
|
||||||
|
|
|
@ -11,7 +11,8 @@ using Google.Protobuf;
|
||||||
namespace LeanCloud.Realtime.Internal.WebSocket {
|
namespace LeanCloud.Realtime.Internal.WebSocket {
|
||||||
internal class LCWebSocketConnection {
|
internal class LCWebSocketConnection {
|
||||||
private const int KEEP_ALIVE_INTERVAL = 10;
|
private const int KEEP_ALIVE_INTERVAL = 10;
|
||||||
private const int RECV_BUFFER_SIZE = 1024;
|
// .net standard 2.0 好像在拼合 Frame 时有 bug,所以将这个值调整大一些
|
||||||
|
private const int RECV_BUFFER_SIZE = 1024 * 5;
|
||||||
|
|
||||||
private ClientWebSocket ws;
|
private ClientWebSocket ws;
|
||||||
|
|
||||||
|
@ -19,22 +20,31 @@ namespace LeanCloud.Realtime.Internal.WebSocket {
|
||||||
|
|
||||||
private readonly object requestILock = new object();
|
private readonly object requestILock = new object();
|
||||||
|
|
||||||
private Dictionary<int, TaskCompletionSource<GenericCommand>> responses;
|
private readonly Dictionary<int, TaskCompletionSource<GenericCommand>> responses;
|
||||||
|
|
||||||
private string id;
|
private readonly string id;
|
||||||
|
|
||||||
|
internal LCRTMRouter Router {
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
internal Func<GenericCommand, Task> OnNotification {
|
internal Func<GenericCommand, Task> OnNotification {
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal Action<int, string> OnClose {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
internal LCWebSocketConnection(string id) {
|
internal LCWebSocketConnection(string id) {
|
||||||
|
Router = new LCRTMRouter();
|
||||||
|
|
||||||
this.id = id;
|
this.id = id;
|
||||||
responses = new Dictionary<int, TaskCompletionSource<GenericCommand>>();
|
responses = new Dictionary<int, TaskCompletionSource<GenericCommand>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task Connect() {
|
internal async Task Connect() {
|
||||||
LCRTMRouter rtmRouter = new LCRTMRouter();
|
string rtmServer = await Router.GetServer();
|
||||||
string rtmServer = await rtmRouter.GetServer();
|
|
||||||
|
|
||||||
ws = new ClientWebSocket();
|
ws = new ClientWebSocket();
|
||||||
ws.Options.AddSubProtocol("lc.protobuf2.3");
|
ws.Options.AddSubProtocol("lc.protobuf2.3");
|
||||||
|
@ -71,8 +81,7 @@ namespace LeanCloud.Realtime.Internal.WebSocket {
|
||||||
do {
|
do {
|
||||||
result = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), default);
|
result = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), default);
|
||||||
if (result.MessageType == WebSocketMessageType.Close) {
|
if (result.MessageType == WebSocketMessageType.Close) {
|
||||||
// TODO 区分主动断开和被动断开
|
OnClose?.Invoke(-1, null);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 拼合 WebSocket Frame
|
// 拼合 WebSocket Frame
|
||||||
|
@ -91,8 +100,9 @@ namespace LeanCloud.Realtime.Internal.WebSocket {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// TODO 连接断开
|
// 连接断开
|
||||||
LCLogger.Error(e.Message);
|
LCLogger.Error(e.Message);
|
||||||
|
await ws.CloseAsync(WebSocketCloseStatus.EndpointUnavailable, "read error", default);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,37 +4,42 @@ using System.Threading.Tasks;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using LeanCloud.Realtime.Internal.WebSocket;
|
using LeanCloud.Realtime.Internal.WebSocket;
|
||||||
using LeanCloud.Realtime.Protocol;
|
using LeanCloud.Realtime.Protocol;
|
||||||
using LeanCloud.Storage.Internal.Codec;
|
using LeanCloud.Realtime.Internal.Controller;
|
||||||
using LeanCloud.Storage.Internal;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime {
|
namespace LeanCloud.Realtime {
|
||||||
public class LCIMClient {
|
public class LCIMClient {
|
||||||
internal LCWebSocketConnection connection;
|
internal Dictionary<string, LCIMConversation> ConversationDict;
|
||||||
|
|
||||||
internal Dictionary<string, LCIMConversation> conversationDict;
|
public string Id {
|
||||||
|
|
||||||
public string ClientId {
|
|
||||||
get; private set;
|
get; private set;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO 判断过期
|
#region 事件
|
||||||
internal string SessionToken {
|
|
||||||
get; private set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 当前用户被加入某个对话的黑名单
|
/// 当前用户被加入某个对话的黑名单
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Action OnBlocked {
|
public Action<LCIMConversation, string> OnBlocked {
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 当前客户端在某个对话中被禁言
|
/// 当用户被解除黑名单
|
||||||
|
/// </summary>
|
||||||
|
public Action<LCIMConversation, string> OnUnblocked {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前用户在某个对话中被禁言
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Action<LCIMConversation, string> OnMuted;
|
public Action<LCIMConversation, string> OnMuted;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前用户在某个对话中被解除禁言
|
||||||
|
/// </summary>
|
||||||
|
public Action<LCIMConversation, string> OnUnmuted;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 客户端连接断开
|
/// 客户端连接断开
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -52,7 +57,7 @@ namespace LeanCloud.Realtime {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 当前客户端被服务端强行下线
|
/// 当前客户端被服务端强行下线
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Action OnOffline {
|
public Action<int, string, string> OnClose {
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,26 +139,26 @@ namespace LeanCloud.Realtime {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 有成员的对话信息被更新
|
/// 有成员的对话信息被更新
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Action<LCIMConversation, string, Dictionary<string, object>, string> OnMemberInfoUpdated;
|
public Action<LCIMConversation, string, string, string> OnMemberInfoUpdated;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 当前用户收到消息
|
/// 当前用户收到消息
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Action<LCIMConversation, LCIMMessage> OnMessageReceived {
|
public Action<LCIMConversation, LCIMMessage> OnMessage {
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 消息被撤回
|
/// 消息被撤回
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Action<LCIMConversation, LCIMMessage> OnMessageRecall {
|
public Action<LCIMConversation, LCIMMessage> OnMessageRecalled {
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 消息被修改
|
/// 消息被修改
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Action<LCIMConversation, LCIMMessage> OnMessageUpdate {
|
public Action<LCIMConversation, LCIMMessage> OnMessageUpdated {
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,14 +169,62 @@ namespace LeanCloud.Realtime {
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public Action OnLastDeliveredAtUpdated {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action OnLastReadAtUpdated {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
internal ILCIMSignatureFactory SignatureFactory {
|
internal ILCIMSignatureFactory SignatureFactory {
|
||||||
get; private set;
|
get; private set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LCIMClient(string clientId, ILCIMSignatureFactory signatureFactory = null) {
|
internal LCWebSocketConnection Connection {
|
||||||
ClientId = clientId;
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal LCIMSessionController SessionController {
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal LCIMMessageController MessageController {
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal LCIMUnreadController UnreadController {
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal LCIMGoAwayController GoAwayController {
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal LCIMConversationController ConversationController {
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LCIMClient(string clientId,
|
||||||
|
ILCIMSignatureFactory signatureFactory = null) {
|
||||||
|
Id = clientId;
|
||||||
SignatureFactory = signatureFactory;
|
SignatureFactory = signatureFactory;
|
||||||
conversationDict = new Dictionary<string, LCIMConversation>();
|
ConversationDict = new Dictionary<string, LCIMConversation>();
|
||||||
|
|
||||||
|
SessionController = new LCIMSessionController(this);
|
||||||
|
ConversationController = new LCIMConversationController(this);
|
||||||
|
MessageController = new LCIMMessageController(this);
|
||||||
|
UnreadController = new LCIMUnreadController(this);
|
||||||
|
GoAwayController = new LCIMGoAwayController(this);
|
||||||
|
|
||||||
|
Connection = new LCWebSocketConnection(Id) {
|
||||||
|
OnNotification = OnNotification
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -179,22 +232,9 @@ namespace LeanCloud.Realtime {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task Open() {
|
public async Task Open() {
|
||||||
connection = new LCWebSocketConnection(ClientId) {
|
await Connection.Connect();
|
||||||
OnNotification = OnNotification
|
// 打开 Session
|
||||||
};
|
await SessionController.Open();
|
||||||
await connection.Connect();
|
|
||||||
// Open Session
|
|
||||||
GenericCommand request = NewCommand(CommandType.Session, OpType.Open);
|
|
||||||
SessionCommand session = new SessionCommand();
|
|
||||||
if (SignatureFactory != null) {
|
|
||||||
LCIMSignature signature = SignatureFactory.CreateConnectSignature(ClientId);
|
|
||||||
session.S = signature.Signature;
|
|
||||||
session.T = signature.Timestamp;
|
|
||||||
session.N = signature.Nonce;
|
|
||||||
}
|
|
||||||
request.SessionMessage = session;
|
|
||||||
GenericCommand response = await connection.SendRequest(request);
|
|
||||||
SessionToken = response.SessionMessage.St;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -202,91 +242,63 @@ namespace LeanCloud.Realtime {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task Close() {
|
public async Task Close() {
|
||||||
GenericCommand request = NewCommand(CommandType.Session, OpType.Close);
|
// 关闭 session
|
||||||
await connection.SendRequest(request);
|
await SessionController.Close();
|
||||||
await connection.Close();
|
await Connection.Close();
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建普通对话
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="members"></param>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="unique"></param>
|
||||||
|
/// <param name="properties"></param>
|
||||||
|
/// <returns></returns>
|
||||||
public async Task<LCIMConversation> CreateConversation(
|
public async Task<LCIMConversation> CreateConversation(
|
||||||
IEnumerable<string> members,
|
IEnumerable<string> members,
|
||||||
string name = null,
|
string name = null,
|
||||||
bool unique = true,
|
bool unique = true,
|
||||||
Dictionary<string, object> properties = null) {
|
Dictionary<string, object> properties = null) {
|
||||||
return await CreateConv(members: members, name: name, unique: unique, properties: properties);
|
return await ConversationController.CreateConv(members: members,
|
||||||
|
name: name,
|
||||||
|
unique: unique,
|
||||||
|
properties: properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建聊天室
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="properties"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<LCIMChatRoom> CreateChatRoom(
|
||||||
|
string name,
|
||||||
|
Dictionary<string, object> properties = null) {
|
||||||
|
LCIMChatRoom chatRoom = await ConversationController.CreateConv(name: name,
|
||||||
|
transient: true,
|
||||||
|
properties: properties) as LCIMChatRoom;
|
||||||
|
return chatRoom;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建临时对话
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="members"></param>
|
||||||
|
/// <param name="ttl"></param>
|
||||||
|
/// <param name="properties"></param>
|
||||||
|
/// <returns></returns>
|
||||||
public async Task<LCIMTemporaryConversation> CreateTemporaryConversation(
|
public async Task<LCIMTemporaryConversation> CreateTemporaryConversation(
|
||||||
IEnumerable<string> members,
|
IEnumerable<string> members,
|
||||||
int ttl = 86400,
|
int ttl = 86400,
|
||||||
Dictionary<string, object> properties = null) {
|
Dictionary<string, object> properties = null) {
|
||||||
LCIMTemporaryConversation tempConversation = await CreateConv(members: members, temporary: true, temporaryTtl: ttl, properties: properties) as LCIMTemporaryConversation;
|
LCIMTemporaryConversation tempConversation = await ConversationController.CreateConv(members: members,
|
||||||
|
temporary: true,
|
||||||
|
temporaryTtl: ttl,
|
||||||
|
properties: properties) as LCIMTemporaryConversation;
|
||||||
return tempConversation;
|
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 request = NewCommand(CommandType.Conv, OpType.Start);
|
|
||||||
ConvCommand conv = new ConvCommand {
|
|
||||||
Transient = transient,
|
|
||||||
Unique = unique,
|
|
||||||
};
|
|
||||||
if (members != null) {
|
|
||||||
conv.M.AddRange(members);
|
|
||||||
}
|
|
||||||
if (!string.IsNullOrEmpty(name)) {
|
|
||||||
conv.N = name;
|
|
||||||
}
|
|
||||||
if (temporary) {
|
|
||||||
conv.TempConv = temporary;
|
|
||||||
conv.TempConvTTL = temporaryTtl;
|
|
||||||
}
|
|
||||||
if (properties != null) {
|
|
||||||
conv.Attr = new JsonObjectMessage {
|
|
||||||
Data = JsonConvert.SerializeObject(LCEncoder.Encode(properties))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (SignatureFactory != null) {
|
|
||||||
LCIMSignature signature = SignatureFactory.CreateStartConversationSignature(ClientId, members);
|
|
||||||
conv.S = signature.Signature;
|
|
||||||
conv.T = signature.Timestamp;
|
|
||||||
conv.N = signature.Nonce;
|
|
||||||
}
|
|
||||||
request.ConvMessage = conv;
|
|
||||||
GenericCommand response = await connection.SendRequest(request);
|
|
||||||
string convId = response.ConvMessage.Cid;
|
|
||||||
if (!conversationDict.TryGetValue(convId, out LCIMConversation conversation)) {
|
|
||||||
if (transient) {
|
|
||||||
conversation = new LCIMChatRoom(this);
|
|
||||||
} else if (temporary) {
|
|
||||||
conversation = new LCIMTemporaryConversation(this);
|
|
||||||
} else if (properties != null && properties.ContainsKey("system")) {
|
|
||||||
conversation = new LCIMServiceConversation(this);
|
|
||||||
} else {
|
|
||||||
conversation = new LCIMConversation(this);
|
|
||||||
}
|
|
||||||
conversationDict[convId] = conversation;
|
|
||||||
}
|
|
||||||
// 合并请求数据
|
|
||||||
conversation.Name = name;
|
|
||||||
conversation.MemberIdList = members?.ToList();
|
|
||||||
// 合并服务端推送的数据
|
|
||||||
conversation.MergeFrom(response.ConvMessage);
|
|
||||||
return conversation;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取某个特定的对话
|
/// 获取某个特定的对话
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -331,164 +343,34 @@ namespace LeanCloud.Realtime {
|
||||||
return new LCIMConversationQuery(this);
|
return new LCIMConversationQuery(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region 通知处理
|
||||||
|
|
||||||
private async Task OnNotification(GenericCommand notification) {
|
private async Task OnNotification(GenericCommand notification) {
|
||||||
switch (notification.Cmd) {
|
switch (notification.Cmd) {
|
||||||
case CommandType.Session:
|
case CommandType.Session:
|
||||||
await OnSessionNotification(notification);
|
await SessionController.OnNotification(notification);
|
||||||
break;
|
break;
|
||||||
case CommandType.Conv:
|
case CommandType.Conv:
|
||||||
OnConversationNotification(notification);
|
await ConversationController.OnNotification(notification);
|
||||||
break;
|
break;
|
||||||
case CommandType.Direct:
|
case CommandType.Direct:
|
||||||
await OnDirectNotification(notification.DirectMessage);
|
await MessageController.OnNotification(notification);
|
||||||
break;
|
break;
|
||||||
case CommandType.Unread:
|
case CommandType.Unread:
|
||||||
await OnUnreadNotification(notification.UnreadMessage);
|
await UnreadController.OnNotification(notification);
|
||||||
|
break;
|
||||||
|
case CommandType.Goaway:
|
||||||
|
await GoAwayController.OnNotification(notification);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OnSessionNotification(GenericCommand notification) {
|
#endregion
|
||||||
switch (notification.Op) {
|
|
||||||
case OpType.Closed:
|
|
||||||
await OnSessionClosed(notification.SessionMessage);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task OnSessionClosed(SessionCommand session) {
|
|
||||||
int code = session.Code;
|
|
||||||
string reason = session.Reason;
|
|
||||||
string detail = session.Detail;
|
|
||||||
await connection.Close();
|
|
||||||
// TODO 关闭连接后回调给开发者
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnConversationNotification(GenericCommand notification) {
|
|
||||||
ConvCommand conv = notification.ConvMessage;
|
|
||||||
switch (notification.Op) {
|
|
||||||
case OpType.Joined:
|
|
||||||
OnConversationJoined(conv);
|
|
||||||
break;
|
|
||||||
case OpType.MembersJoined:
|
|
||||||
OnConversationMembersJoined(conv);
|
|
||||||
break;
|
|
||||||
case OpType.Left:
|
|
||||||
OnConversationLeft(conv);
|
|
||||||
break;
|
|
||||||
case OpType.MembersLeft:
|
|
||||||
OnConversationMemberLeft(conv);
|
|
||||||
break;
|
|
||||||
case OpType.Updated:
|
|
||||||
OnConversationPropertiesUpdated(conv);
|
|
||||||
break;
|
|
||||||
case OpType.MemberInfoChanged:
|
|
||||||
OnConversationMemberInfoChanged(conv);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OnConversationJoined(ConvCommand conv) {
|
|
||||||
LCIMConversation conversation = await GetOrQueryConversation(conv.Cid);
|
|
||||||
conversation.MergeFrom(conv);
|
|
||||||
OnInvited?.Invoke(conversation, conv.InitBy);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OnConversationMembersJoined(ConvCommand conv) {
|
|
||||||
LCIMConversation conversation = await GetOrQueryConversation(conv.Cid);
|
|
||||||
conversation.MergeFrom(conv);
|
|
||||||
OnMembersJoined?.Invoke(conversation, conv.M.ToList(), conv.InitBy);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnConversationLeft(ConvCommand conv) {
|
|
||||||
if (conversationDict.TryGetValue(conv.Cid, out LCIMConversation conversation)) {
|
|
||||||
OnKicked?.Invoke(conversation, conv.InitBy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnConversationMemberLeft(ConvCommand conv) {
|
|
||||||
if (conversationDict.TryGetValue(conv.Cid, out LCIMConversation conversation)) {
|
|
||||||
List<string> leftIdList = conv.M.ToList();
|
|
||||||
OnMembersLeft?.Invoke(conversation, leftIdList, conv.InitBy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnConversationPropertiesUpdated(ConvCommand conv) {
|
|
||||||
if (conversationDict.TryGetValue(conv.Cid, out LCIMConversation conversation)) {
|
|
||||||
// TODO 修改对话属性,并回调给开发者
|
|
||||||
|
|
||||||
OnConversationInfoUpdated?.Invoke(conversation, null, conv.InitBy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnConversationMemberInfoChanged(ConvCommand conv) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task OnDirectNotification(DirectCommand direct) {
|
|
||||||
LCIMMessage message = null;
|
|
||||||
if (direct.HasBinaryMsg) {
|
|
||||||
// 二进制消息
|
|
||||||
byte[] bytes = direct.BinaryMsg.ToByteArray();
|
|
||||||
message = new LCIMBinaryMessage(bytes);
|
|
||||||
} else {
|
|
||||||
// 文本消息
|
|
||||||
string messageData = direct.Msg;
|
|
||||||
Dictionary<string, object> msg = JsonConvert.DeserializeObject<Dictionary<string, object>>(messageData,
|
|
||||||
new LCJsonConverter());
|
|
||||||
int msgType = (int)(long)msg["_lctype"];
|
|
||||||
switch (msgType) {
|
|
||||||
case -1:
|
|
||||||
message = new LCIMTextMessage();
|
|
||||||
break;
|
|
||||||
case -2:
|
|
||||||
message = new LCIMImageMessage();
|
|
||||||
break;
|
|
||||||
case -3:
|
|
||||||
message = new LCIMAudioMessage();
|
|
||||||
break;
|
|
||||||
case -4:
|
|
||||||
message = new LCIMVideoMessage();
|
|
||||||
break;
|
|
||||||
case -5:
|
|
||||||
message = new LCIMLocationMessage();
|
|
||||||
break;
|
|
||||||
case -6:
|
|
||||||
message = new LCIMFileMessage();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
message.Decode(direct);
|
|
||||||
}
|
|
||||||
// 获取对话
|
|
||||||
LCIMConversation conversation = await GetOrQueryConversation(direct.Cid);
|
|
||||||
OnMessageReceived?.Invoke(conversation, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task OnUnreadNotification(UnreadCommand unread) {
|
|
||||||
List<LCIMConversation> conversationList = new List<LCIMConversation>();
|
|
||||||
foreach (UnreadTuple conv in unread.Convs) {
|
|
||||||
// 查询对话
|
|
||||||
LCIMConversation conversation = await GetOrQueryConversation(conv.Cid);
|
|
||||||
conversation.Unread = conv.Unread;
|
|
||||||
// TODO 反序列化对话
|
|
||||||
// 最后一条消息
|
|
||||||
JsonConvert.DeserializeObject<Dictionary<string, object>>(conv.Data);
|
|
||||||
conversationList.Add(conversation);
|
|
||||||
}
|
|
||||||
OnUnreadMessagesCountUpdated?.Invoke(conversationList);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal async Task<LCIMConversation> GetOrQueryConversation(string convId) {
|
internal async Task<LCIMConversation> GetOrQueryConversation(string convId) {
|
||||||
if (conversationDict.TryGetValue(convId, out LCIMConversation conversation)) {
|
if (ConversationDict.TryGetValue(convId, out LCIMConversation conversation)) {
|
||||||
return conversation;
|
return conversation;
|
||||||
}
|
}
|
||||||
conversation = await GetConversation(convId);
|
conversation = await GetConversation(convId);
|
||||||
|
@ -496,11 +378,16 @@ namespace LeanCloud.Realtime {
|
||||||
}
|
}
|
||||||
|
|
||||||
internal GenericCommand NewCommand(CommandType cmd, OpType op) {
|
internal GenericCommand NewCommand(CommandType cmd, OpType op) {
|
||||||
|
GenericCommand command = NewCommand(cmd);
|
||||||
|
command.Op = op;
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal GenericCommand NewCommand(CommandType cmd) {
|
||||||
return new GenericCommand {
|
return new GenericCommand {
|
||||||
Cmd = cmd,
|
Cmd = cmd,
|
||||||
Op = op,
|
|
||||||
AppId = LCApplication.AppId,
|
AppId = LCApplication.AppId,
|
||||||
PeerId = ClientId,
|
PeerId = Id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -508,7 +395,7 @@ namespace LeanCloud.Realtime {
|
||||||
return new GenericCommand {
|
return new GenericCommand {
|
||||||
Cmd = CommandType.Direct,
|
Cmd = CommandType.Direct,
|
||||||
AppId = LCApplication.AppId,
|
AppId = LCApplication.AppId,
|
||||||
PeerId = ClientId,
|
PeerId = Id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,5 +20,6 @@
|
||||||
<Folder Include="Message\" />
|
<Folder Include="Message\" />
|
||||||
<Folder Include="Internal\WebSocket\" />
|
<Folder Include="Internal\WebSocket\" />
|
||||||
<Folder Include="Signature\" />
|
<Folder Include="Signature\" />
|
||||||
|
<Folder Include="Internal\Controller\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -114,7 +114,7 @@ namespace Realtime.Test {
|
||||||
conversation.Name = "leancloud";
|
conversation.Name = "leancloud";
|
||||||
conversation["k1"] = "v1";
|
conversation["k1"] = "v1";
|
||||||
conversation["k2"] = "v2";
|
conversation["k2"] = "v2";
|
||||||
await conversation.Save();
|
await conversation.UpdateInfo();
|
||||||
|
|
||||||
Assert.AreEqual(conversation.Name, "leancloud");
|
Assert.AreEqual(conversation.Name, "leancloud");
|
||||||
Assert.AreEqual(conversation["k1"], "v1");
|
Assert.AreEqual(conversation["k1"], "v1");
|
||||||
|
|
|
@ -46,9 +46,9 @@ namespace RealtimeConsole {
|
||||||
|
|
||||||
//_ = OpenAndClose();
|
//_ = OpenAndClose();
|
||||||
|
|
||||||
//SendMessage().Wait();
|
SendMessage().Wait();
|
||||||
|
|
||||||
_ = Unread();
|
//Unread().Wait();
|
||||||
|
|
||||||
Console.ReadKey(true);
|
Console.ReadKey(true);
|
||||||
}
|
}
|
||||||
|
@ -134,7 +134,7 @@ namespace RealtimeConsole {
|
||||||
|
|
||||||
LCIMClient c5 = new LCIMClient("c5");
|
LCIMClient c5 = new LCIMClient("c5");
|
||||||
await c5.Open();
|
await c5.Open();
|
||||||
await conversation.Add(new string[] { "c5" });
|
await conversation.AddMembers(new string[] { "c5" });
|
||||||
}
|
}
|
||||||
|
|
||||||
static async Task Signature() {
|
static async Task Signature() {
|
||||||
|
@ -152,9 +152,9 @@ namespace RealtimeConsole {
|
||||||
LCIMChatRoom chatRoom = await hello.CreateChatRoom(name);
|
LCIMChatRoom chatRoom = await hello.CreateChatRoom(name);
|
||||||
Console.WriteLine(chatRoom.Name);
|
Console.WriteLine(chatRoom.Name);
|
||||||
|
|
||||||
await chatRoom.Add(new string[] { "world" });
|
await chatRoom.AddMembers(new string[] { "world" });
|
||||||
|
|
||||||
await chatRoom.Remove(new string[] { "world" });
|
await chatRoom.RemoveMembers(new string[] { "world" });
|
||||||
}
|
}
|
||||||
|
|
||||||
static async Task TemporaryConversation() {
|
static async Task TemporaryConversation() {
|
||||||
|
@ -190,7 +190,7 @@ namespace RealtimeConsole {
|
||||||
LCIMClient world = new LCIMClient("world");
|
LCIMClient world = new LCIMClient("world");
|
||||||
await world.Open();
|
await world.Open();
|
||||||
|
|
||||||
world.OnMessageReceived = (conv, message) => {
|
world.OnMessage = (conv, message) => {
|
||||||
Console.WriteLine(message);
|
Console.WriteLine(message);
|
||||||
if (message is LCIMTypedMessage typedMessage) {
|
if (message is LCIMTypedMessage typedMessage) {
|
||||||
Console.WriteLine(typedMessage["k1"]);
|
Console.WriteLine(typedMessage["k1"]);
|
||||||
|
|
Loading…
Reference in New Issue