示例#1
0
        public async Task <object> RPC(string funcName, JsonElement body)
        {
            try {
                LCLogger.Debug($"RPC: {funcName}");
                LCLogger.Debug(body.ToString());

                if (Functions.TryGetValue(funcName, out MethodInfo mi))
                {
                    LCEngine.InitRequestContext(Request);

                    object[] ps     = ParseParameters(mi, body);
                    object   result = await LCEngine.Invoke(mi, ps);

                    if (result != null)
                    {
                        return(new Dictionary <string, object> {
                            { "result", LCCloud.Encode(result) }
                        });
                    }
                }
                return(body);
            } catch (Exception e) {
                return(StatusCode(500, e.Message));
            }
        }
示例#2
0
        internal async Task <GenericCommand> SendRequest(GenericCommand request)
        {
            if (IsIdempotentCommand(request))
            {
                GenericCommand sendingReq = requestToResponses.Keys.FirstOrDefault(item => {
                    // TRICK 除了 I 其他字段相等
                    request.I = item.I;
                    return(Equals(request, item));
                });
                if (sendingReq != null)
                {
                    LCLogger.Warn("duplicated request");
                    if (requestToResponses.TryGetValue(sendingReq, out TaskCompletionSource <GenericCommand> waitingTcs))
                    {
                        return(await waitingTcs.Task);
                    }
                    LCLogger.Error($"error request: {request}");
                }
            }

            TaskCompletionSource <GenericCommand> tcs = new TaskCompletionSource <GenericCommand>();

            request.I = requestI++;
            requestToResponses.Add(request, tcs);
            try {
                await SendCommand(request);
            } catch (Exception e) {
                tcs.TrySetException(e);
            }
            return(await tcs.Task);
        }
示例#3
0
        public async Task Connect(string server,
                                  string subProtocol = null)
        {
            LCLogger.Debug($"Connecting WebSocket: {server}");
            Task timeoutTask = Task.Delay(CONNECT_TIMEOUT);

            ws = new ClientWebSocket();
            ws.Options.SetBuffer(RECV_BUFFER_SIZE, SEND_BUFFER_SIZE);
            if (!string.IsNullOrEmpty(subProtocol))
            {
                ws.Options.AddSubProtocol(subProtocol);
            }
            Task connectTask = ws.ConnectAsync(new Uri(server), default);

            if (await Task.WhenAny(connectTask, timeoutTask) == connectTask)
            {
                LCLogger.Debug($"Connected WebSocket: {server}");
                await connectTask;
                // 接收
                _ = StartReceive();
            }
            else
            {
                throw new TimeoutException("Connect timeout");
            }
        }
示例#4
0
 private void OnMessage(byte[] bytes, int length)
 {
     try {
         GenericCommand command = GenericCommand.Parser.ParseFrom(bytes, 0, length);
         LCLogger.Debug($"{id} <= {FormatCommand(command)}");
         if (command.HasI)
         {
             // 应答
             int requestIndex = command.I;
             if (requestToResponses.TryGetValue(command, out TaskCompletionSource <GenericCommand> tcs))
             {
                 requestToResponses.Remove(command);
                 if (command.HasErrorMessage)
                 {
                     // 错误
                     ErrorCommand error  = command.ErrorMessage;
                     int          code   = error.Code;
                     string       detail = error.Detail;
                     // 包装成异常抛出
                     LCException exception = new LCException(code, detail);
                     tcs.TrySetException(exception);
                 }
                 else
                 {
                     tcs.TrySetResult(command);
                 }
             }
             else
             {
                 LCLogger.Error($"No request for {requestIndex}");
             }
         }
         else
         {
             if (command.Cmd == CommandType.Echo)
             {
                 // 心跳应答
                 heartBeat.Pong();
             }
             else if (command.Cmd == CommandType.Goaway)
             {
                 // 针对连接的消息
                 Reset();
             }
             else
             {
                 // 通知
                 string peerId = command.HasPeerId ? command.PeerId : defaultClientId;
                 if (idToClients.TryGetValue(peerId, out LCIMClient client))
                 {
                     // 通知具体客户端
                     client.HandleNotification(command);
                 }
             }
         }
     } catch (Exception e) {
         LCLogger.Error(e);
     }
 }
示例#5
0
        public object Get()
        {
            LCLogger.Debug("Ping ~~~");

            return(new Dictionary <string, string> {
                { "runtime", $"dotnet-{Environment.Version}" },
                { "version", LCCore.SDKVersion }
            });
        }
示例#6
0
 private static async Task SaveToLocal()
 {
     try {
         string json = currentUser.ToString();
         await LCCore.PersistenceController.WriteText(USER_DATA, json);
     } catch (Exception e) {
         LCLogger.Error(e.Message);
     }
 }
示例#7
0
        private static void OnNotification(Dictionary <string, object> notification)
        {
            if (!notification.TryGetValue("cmd", out object cmd) ||
                !"data".Equals(cmd))
            {
                return;
            }
            if (!notification.TryGetValue("msg", out object msg) ||
                !(msg is IEnumerable <object> list))
            {
                return;
            }

            foreach (object item in list)
            {
                if (item is Dictionary <string, object> dict)
                {
                    if (!dict.TryGetValue("op", out object op))
                    {
                        continue;
                    }
                    switch (op as string)
                    {
                    case "create":
                        OnCreateNotification(dict);
                        break;

                    case "update":
                        OnUpdateNotification(dict);
                        break;

                    case "enter":
                        OnEnterNotification(dict);
                        break;

                    case "leave":
                        OnLeaveNotification(dict);
                        break;

                    case "delete":
                        OnDeleteNotification(dict);
                        break;

                    case "login":
                        OnLoginNotification(dict);
                        break;

                    default:
                        LCLogger.Debug($"Not support: {op}");
                        break;
                    }
                }
            }
        }
示例#8
0
 private void HandleExceptionClose()
 {
     try {
         ws.Abort();
         ws.Dispose();
     } catch (Exception e) {
         LCLogger.Error(e);
     } finally {
         OnClose?.Invoke();
     }
 }
示例#9
0
 /// <summary>
 /// Signing in
 /// </summary>
 /// <param name="force">If this is ture (default value), and single device sign-on is enabled, users already logged in on another device with the same tag will be logged out.</param>
 /// <returns></returns>
 public async Task Open(bool force = true)
 {
     try {
         // 打开 Session
         await SessionController.Open(force);
     } catch (Exception e) {
         LCLogger.Error(e);
         // 如果 session 阶段异常,则关闭连接
         throw e;
     }
 }
 private void OnClientMessage(byte[] bytes, int length)
 {
     try {
         string json = Encoding.UTF8.GetString(bytes, 0, length);
         Dictionary <string, object> msg = JsonConvert.DeserializeObject <Dictionary <string, object> >(json,
                                                                                                        LCJsonConverter.Default);
         LCLogger.Debug($"{id} <= {json}");
         if (msg.TryGetValue("i", out object i))
         {
             int requestIndex = Convert.ToInt32(i);
             if (responses.TryGetValue(requestIndex, out TaskCompletionSource <Dictionary <string, object> > tcs))
             {
                 if (msg.TryGetValue("error", out object error))
                 {
                     // 错误
                     if (error is Dictionary <string, object> dict)
                     {
                         int    code   = Convert.ToInt32(dict["code"]);
                         string detail = dict["detail"] as string;
                         tcs.SetException(new LCException(code, detail));
                     }
                     else
                     {
                         tcs.SetException(new Exception(error as string));
                     }
                 }
                 else
                 {
                     tcs.SetResult(msg);
                 }
                 responses.Remove(requestIndex);
             }
             else
             {
                 LCLogger.Error($"No request for {requestIndex}");
             }
         }
         else
         {
             if (json == "{}")
             {
                 heartBeat.Pong();
             }
             else
             {
                 // 通知
                 OnNotification?.Invoke(msg);
             }
         }
     } catch (Exception e) {
         LCLogger.Error(e);
     }
 }
示例#11
0
        private async Task StartReceive()
        {
            byte[] recvBuffer = new byte[RECV_BUFFER_SIZE];
            byte[] msgBuffer  = new byte[MSG_BUFFER_SIZE];
            int    offset     = 0;

            try {
                while (ws.State == WebSocketState.Open)
                {
                    WebSocketReceiveResult result = await ws.ReceiveAsync(new ArraySegment <byte>(recvBuffer), default);

                    if (result.MessageType == WebSocketMessageType.Close)
                    {
                        LCLogger.Debug($"Receive Closed: {result.CloseStatus}");
                        if (ws.State == WebSocketState.CloseReceived)
                        {
                            // 如果是服务端主动关闭,则挥手关闭,并认为是断线
                            try {
                                Task closeTask = ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, default);
                                await Task.WhenAny(closeTask, Task.Delay(CLOSE_TIMEOUT));
                            } catch (Exception e) {
                                LCLogger.Error(e);
                            } finally {
                                HandleExceptionClose();
                            }
                        }
                    }
                    else
                    {
                        // 拼合 WebSocket Message
                        int length = result.Count;
                        if (offset + length > msgBuffer.Length)
                        {
                            // 反序列化数组大小不够,则以 2x 扩充
                            byte[] newBuffer = new byte[msgBuffer.Length * 2];
                            Array.Copy(msgBuffer, newBuffer, msgBuffer.Length);
                            msgBuffer = newBuffer;
                        }
                        Array.Copy(recvBuffer, 0, msgBuffer, offset, length);
                        offset += length;
                        if (result.EndOfMessage)
                        {
                            OnMessage?.Invoke(msgBuffer, offset);
                            offset = 0;
                        }
                    }
                }
            } catch (Exception e) {
                // 客户端网络异常
                LCLogger.Error(e);
                HandleExceptionClose();
            }
        }
示例#12
0
 private async void StartPing()
 {
     while (running)
     {
         try {
             await Task.Delay(PING_INTERVAL, heartBeatCTS.Token);
         } catch (TaskCanceledException) {
             return;
         }
         LCLogger.Debug("Ping ~~~");
         SendPing();
     }
 }
        public async Task <object> Hook(string className, string hookName, JsonElement body)
        {
            try {
                LCLogger.Debug($"Hook: {className}#{hookName}");
                LCLogger.Debug(body.ToString());

                LCEngine.CheckHookKey(Request);

                string classHookName = GetClassHookName(className, hookName);
                if (ClassHooks.TryGetValue(classHookName, out MethodInfo mi))
                {
                    Dictionary <string, object> data = LCEngine.Decode(body);

                    LCObjectData objectData = LCObjectData.Decode(data["object"] as Dictionary <string, object>);
                    objectData.ClassName = className;
                    LCObject obj = LCObject.Create(className);
                    obj.Merge(objectData);

                    // 避免死循环
                    if (hookName.StartsWith("before"))
                    {
                        obj.DisableBeforeHook();
                    }
                    else
                    {
                        obj.DisableAfterHook();
                    }

                    LCEngine.InitRequestContext(Request);

                    LCUser user = null;
                    if (data.TryGetValue("user", out object userObj) &&
                        userObj != null)
                    {
                        user = new LCUser();
                        user.Merge(LCObjectData.Decode(userObj as Dictionary <string, object>));
                        LCEngineRequestContext.CurrentUser = user;
                    }

                    LCObject result = await LCEngine.Invoke(mi, new object[] { obj }) as LCObject;

                    if (result != null)
                    {
                        return(LCCloud.Encode(result));
                    }
                }
                return(body);
            } catch (Exception e) {
                return(StatusCode(500, e.Message));
            }
        }
        /// <summary>
        /// Sends text message.
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        internal async Task SendText(string text)
        {
            LCLogger.Debug($"{id} => {text}");
            Task sendTask = client.Send(text);

            if (await Task.WhenAny(sendTask, Task.Delay(SEND_TIMEOUT)) == sendTask)
            {
                await sendTask;
            }
            else
            {
                throw new TimeoutException("Send request time out");
            }
        }
示例#15
0
        internal async Task SendCommand(GenericCommand command)
        {
            LCLogger.Debug($"{id} => {FormatCommand(command)}");
            byte[] bytes    = command.ToByteArray();
            Task   sendTask = ws.Send(bytes);

            if (await Task.WhenAny(sendTask, Task.Delay(SEND_TIMEOUT)) == sendTask)
            {
                await sendTask;
            }
            else
            {
                throw new TimeoutException("Send request");
            }
        }
示例#16
0
        protected virtual void SendPing()
        {
            // 发送 ping 包
            GenericCommand command = new GenericCommand {
                Cmd    = CommandType.Echo,
                AppId  = LCCore.AppId,
                PeerId = connection.id
            };

            try {
                _ = connection.SendCommand(command);
            } catch (Exception e) {
                LCLogger.Error(e.Message);
            }
        }
示例#17
0
        LCException HandleErrorResponse(HttpStatusCode statusCode, string responseContent)
        {
            int    code    = (int)statusCode;
            string message = responseContent;

            try {
                // 尝试获取 LeanCloud 返回错误信息
                Dictionary <string, object> error = JsonConvert.DeserializeObject <Dictionary <string, object> >(responseContent,
                                                                                                                 LCJsonConverter.Default);
                code    = (int)error["code"];
                message = error["error"].ToString();
            } catch (Exception e) {
                LCLogger.Error(e);
            }
            return(new LCException(code, message));
        }
示例#18
0
        internal static object GetFunctions(HttpRequest request)
        {
            CheckMasterKey(request);

            List <string> functions = new List <string>();

            functions.AddRange(Functions.Keys);
            functions.AddRange(ClassHooks.Keys);
            functions.AddRange(UserHooks.Keys);
            foreach (string func in functions)
            {
                LCLogger.Debug(func);
            }

            return(new Dictionary <string, List <string> > {
                { "result", functions }
            });
        }
示例#19
0
        public static void PrintResponse(HttpResponseMessage response, string content = null)
        {
            if (LCLogger.LogDelegate == null)
            {
                return;
            }
            StringBuilder sb = new StringBuilder();

            sb.AppendLine("=== HTTP Response Start ===");
            sb.AppendLine($"URL: {response.RequestMessage.RequestUri}");
            sb.AppendLine($"Status Code: {response.StatusCode}");
            if (!string.IsNullOrEmpty(content))
            {
                sb.AppendLine($"Content: {content}");
            }
            sb.AppendLine("=== HTTP Response End ===");
            LCLogger.Debug(sb.ToString());
        }
示例#20
0
 public async Task Close()
 {
     LCLogger.Debug("Closing WebSocket");
     OnMessage = null;
     OnClose   = null;
     try {
         // 发送关闭帧可能会很久,所以增加超时
         // 主动挥手关闭,不会再收到 Close Frame
         Task closeTask = ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, default);
         Task delayTask = Task.Delay(CLOSE_TIMEOUT);
         await Task.WhenAny(closeTask, delayTask);
     } catch (Exception e) {
         LCLogger.Error(e);
     } finally {
         ws.Abort();
         ws.Dispose();
         LCLogger.Debug("Closed WebSocket");
     }
 }
        public async Task Connect()
        {
            try {
                LCRTMServer rtmServer = await router.GetServer();

                try {
                    LCLogger.Debug($"Primary Server");
                    await client.Connect(rtmServer.Primary, SUB_PROTOCOL);
                } catch (Exception e) {
                    LCLogger.Error(e);
                    LCLogger.Debug($"Secondary Server");
                    await client.Connect(rtmServer.Secondary, SUB_PROTOCOL);
                }
                // 启动心跳
                heartBeat.Start();
            } catch (Exception e) {
                throw e;
            }
        }
示例#22
0
        public static void PrintRequest(HttpClient client, HttpRequestMessage request, string content = null)
        {
            if (LCLogger.LogDelegate == null)
            {
                return;
            }
            if (client == null)
            {
                return;
            }
            if (request == null)
            {
                return;
            }
            StringBuilder sb = new StringBuilder();

            sb.AppendLine("=== HTTP Request Start ===");
            sb.AppendLine($"URL: {request.RequestUri}");
            sb.AppendLine($"Method: {request.Method}");
            sb.AppendLine($"Headers: ");
            foreach (var header in client.DefaultRequestHeaders)
            {
                sb.AppendLine($"\t{header.Key}: {string.Join(",", header.Value.ToArray())}");
            }
            foreach (var header in request.Headers)
            {
                sb.AppendLine($"\t{header.Key}: {string.Join(",", header.Value.ToArray())}");
            }
            if (request.Content != null)
            {
                foreach (var header in request.Content.Headers)
                {
                    sb.AppendLine($"\t{header.Key}: {string.Join(",", header.Value.ToArray())}");
                }
            }
            if (!string.IsNullOrEmpty(content))
            {
                sb.AppendLine($"Content: {content}");
            }
            sb.AppendLine("=== HTTP Request End ===");
            LCLogger.Debug(sb.ToString());
        }
示例#23
0
        /// <summary>
        /// Gets the currently logged in LCUser with a valid session, from
        /// memory or disk if necessary.
        /// </summary>
        /// <returns></returns>
        public static async Task <LCUser> GetCurrent()
        {
            if (currentUser != null)
            {
                return(currentUser);
            }

            string data = await LCCore.PersistenceController.ReadText(USER_DATA);

            if (!string.IsNullOrEmpty(data))
            {
                try {
                    currentUser = ParseObject(data) as LCUser;
                } catch (Exception e) {
                    LCLogger.Error(e);
                    await LCCore.PersistenceController.Delete(USER_DATA);
                }
            }
            return(currentUser);
        }
        public async Task <object> HookLogin(JsonElement body)
        {
            try {
                LCLogger.Debug(LCEngine.OnLogin);
                LCLogger.Debug(body.ToString());

                LCEngine.CheckHookKey(Request);

                if (UserHooks.TryGetValue(LCEngine.OnLogin, out MethodInfo mi))
                {
                    LCEngine.InitRequestContext(Request);

                    Dictionary <string, object> dict = LCEngine.Decode(body);
                    return(await Invoke(mi, dict));
                }
                return(body);
            } catch (Exception e) {
                return(StatusCode(500, e.Message));
            }
        }
示例#25
0
        public async Task Send(byte[] data,
                               WebSocketMessageType messageType = WebSocketMessageType.Binary)
        {
            ArraySegment <byte> bytes = new ArraySegment <byte>(data);

            if (ws.State == WebSocketState.Open)
            {
                try {
                    await ws.SendAsync(bytes, messageType, true, default);
                } catch (Exception e) {
                    LCLogger.Error(e);
                    throw e;
                }
            }
            else
            {
                string message = $"Error Websocket state: {ws.State}";
                LCLogger.Error(message);
                throw new Exception(message);
            }
        }
示例#26
0
        internal async Task ConnectInternal()
        {
            state = State.Connecting;
            try {
                LCRTMServer rtmServer = await router.GetServer();

                try {
                    LCLogger.Debug($"Primary Server");
                    await ws.Connect(rtmServer.Primary, SUB_PROTOCOL);
                } catch (Exception e) {
                    LCLogger.Error(e);
                    LCLogger.Debug($"Secondary Server");
                    await ws.Connect(rtmServer.Secondary, SUB_PROTOCOL);
                }
                // 启动心跳
                heartBeat.Start();
                state = State.Open;
            } catch (Exception e) {
                state = State.Closed;
                throw e;
            }
        }
示例#27
0
        private async Task Reconnect()
        {
            while (true)
            {
                int reconnectCount = 0;
                // 重连策略
                while (reconnectCount < MAX_RECONNECT_TIMES)
                {
                    try {
                        LCLogger.Debug($"Reconnecting... {reconnectCount}");
                        await Connect();

                        break;
                    } catch (Exception e) {
                        reconnectCount++;
                        LCLogger.Error(e);
                        LCLogger.Debug($"Reconnect after {RECONNECT_INTERVAL}ms");
                        await Task.Delay(RECONNECT_INTERVAL);
                    }
                }
                if (reconnectCount < MAX_RECONNECT_TIMES)
                {
                    // 重连成功
                    LCLogger.Debug("Reconnected");
                    ws.OnMessage = OnMessage;
                    ws.OnClose   = OnDisconnect;
                    foreach (LCIMClient client in idToClients.Values)
                    {
                        client.HandleReconnected();
                    }
                    break;
                }
                else
                {
                    // 重置 Router,继续尝试重连
                    router = new LCRTMRouter();
                }
            }
        }
示例#28
0
        async Task <LCAppServer> FetchAppServer()
        {
            // 判断节点地区
            if (!IsInternalApp(appId))
            {
                // 国内节点必须配置自定义域名
                throw new Exception("Please init with your server url.");
            }
            // 向 App Router 请求地址
            if (appServer == null || !appServer.IsValid)
            {
                try {
                    HttpRequestMessage request = new HttpRequestMessage {
                        RequestUri = new Uri($"https://app-router.com/2/route?appId={appId}"),
                        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);

                    Dictionary <string, object> data = JsonConvert.DeserializeObject <Dictionary <string, object> >(resultString);
                    appServer = new LCAppServer(data);
                } catch (Exception e) {
                    LCLogger.Error(e);
                    // 拉取服务地址失败后,使用国际节点的默认服务地址
                    appServer = LCAppServer.GetInternalFallbackAppServer(appId);
                }
            }
            return(appServer);
        }
        private async Task Reconnect()
        {
            while (true)
            {
                int reconnectCount = 0;
                // 重连策略
                while (reconnectCount < MAX_RECONNECT_TIMES)
                {
                    try {
                        LCLogger.Debug($"Reconnecting... {reconnectCount}");
                        await Connect();

                        break;
                    } catch (Exception e) {
                        reconnectCount++;
                        LCLogger.Error(e);
                        LCLogger.Debug($"Reconnect after {RECONNECT_INTERVAL}ms");
                        await Task.Delay(RECONNECT_INTERVAL);
                    }
                }
                if (reconnectCount < MAX_RECONNECT_TIMES)
                {
                    // 重连成功
                    LCLogger.Debug("Reconnected");
                    client.OnMessage = OnClientMessage;
                    client.OnClose   = OnClientDisconnect;
                    OnReconnected?.Invoke();
                    break;
                }
                else
                {
                    // 重置 Router,继续尝试重连
                    router = new LCRTMRouter();
                }
            }
        }
示例#30
0
        internal async Task <ReadOnlyCollection <LCIMConversation> > Find(LCIMConversationQuery query)
        {
            GenericCommand command = new GenericCommand {
                Cmd    = CommandType.Conv,
                Op     = OpType.Query,
                AppId  = LCCore.AppId,
                PeerId = Client.Id,
            };
            ConvCommand convMessage = new ConvCommand();

            string where = query.Condition.BuildWhere();
            if (!string.IsNullOrEmpty(where))
            {
                try {
                    convMessage.Where = new JsonObjectMessage {
                        Data = where
                    };
                } catch (Exception e) {
                    LCLogger.Error(e);
                }
            }
            int flag = 0;

            if (query.Compact)
            {
                flag += LCIMConversationQuery.CompactFlag;
            }
            if (query.WithLastMessageRefreshed)
            {
                flag += LCIMConversationQuery.WithLastMessageFlag;
            }
            if (flag > 0)
            {
                convMessage.Flag = flag;
            }
            convMessage.Skip  = query.Condition.Skip;
            convMessage.Limit = query.Condition.Limit;
            string orders = query.Condition.BuildOrders();

            if (!string.IsNullOrEmpty(orders))
            {
                convMessage.Sort = orders;
            }
            command.ConvMessage = convMessage;
            GenericCommand response = await Connection.SendRequest(command);

            JsonObjectMessage results = response.ConvMessage.Results;
            List <object>     convs   = JsonConvert.DeserializeObject <List <object> >(results.Data,
                                                                                       LCJsonConverter.Default);

            return(convs.Select(item => {
                Dictionary <string, object> conv = item as Dictionary <string, object>;
                string convId = conv["objectId"] as string;
                if (!Client.ConversationDict.TryGetValue(convId, out LCIMConversation conversation))
                {
                    // 解析是哪种类型的对话
                    if (conv.TryGetValue("tr", out object transient) && (bool)transient == true)
                    {
                        conversation = new LCIMChatRoom(Client);
                    }
                    else if (conv.ContainsKey("tempConv") && conv.ContainsKey("tempConvTTL"))
                    {
                        conversation = new LCIMTemporaryConversation(Client);
                    }
                    else if (conv.TryGetValue("sys", out object sys) && (bool)sys == true)
                    {
                        conversation = new LCIMServiceConversation(Client);
                    }
                    else
                    {
                        conversation = new LCIMConversation(Client);
                    }
                    Client.ConversationDict[convId] = conversation;
                }
                conversation.MergeFrom(conv);
                return conversation;
            }).ToList().AsReadOnly());
        }