/// <summary>
 /// Gets the information of the connection
 /// </summary>
 /// <param name="websocket"></param>
 /// <param name="session"></param>
 /// <returns></returns>
 public static string GetConnectionInfo(this ManagedWebSocket websocket, Session session = null)
 {
     session = session ?? websocket.Get <Session>("Session");
     return($"- Account: {websocket.Get("AccountInfo", "Visitor")} - Session ID: {session?.SessionID ?? "Unknown"} - Device ID: {session?.DeviceID ?? "Unknown"} - Origin: {(websocket.Headers.TryGetValue("Origin", out var origin) ? origin : session?.AppOrigin ?? "Unknown")}" + "\r\n" +
            $"- App: {session?.AppName ?? "Unknown"} @ {session?.AppPlatform ?? "Unknown"} [{session?.AppAgent ?? "Unknown"}]" + "\r\n" +
            $"- Connection IP: {session?.IP ?? "Unknown"} - Location: {websocket.Get("LocationInfo", "Unknown")} - WebSocket: {websocket.ID} @ {websocket.RemoteEndPoint}");
 }
Example #2
0
        public bool VerifyClient(ManagedWebSocket socket)
        {
            var address = (socket.RemoteEndPoint as IPEndPoint).Address.ToString();

            // Console.WriteLine($"REQUEST FROM {address}, {(socket. ? "" : "not ")}secure, Origin: {socket.Context.Origin}");
            if (connections.Count > Settings.listenerMaxConnections)
            {
                Console.WriteLine("listenerMaxConnections reached, dropping new connections");
                return(false);
            }
            var acceptedOrigins = Settings.listenerAcceptedOrigins;

            if (acceptedOrigins.Count > 0 && acceptedOrigins.Contains(socket.RequestUri.ToString()))
            {
                Console.WriteLine($"listenerAcceptedOrigins doesn't contain {socket.RequestUri.ToString()}");
                return(false);
            }
            if (Settings.listenerForbiddenIPs.Contains(address))
            {
                Console.WriteLine($"listenerForbiddenIPs contains {address}, dropping connection");
                return(false);
            }
            if (Settings.listenerMaxConnectionsPerIP > 0)
            {
                var count = ConnectionCountForIP(address);
                if (count != 0 && count >= Settings.listenerMaxConnectionsPerIP)
                {
                    Console.WriteLine($"listenerMaxConnectionsPerIP reached for '{address}', dropping its new connections");
                    return(false);
                }
            }
            Console.WriteLine("client verification passed");
            return(true);
        }
        /// <summary>
        /// Prepares the information of the connection
        /// </summary>
        /// <param name="websocket"></param>
        /// <param name="correlationID"></param>
        /// <param name="session"></param>
        /// <param name="cancellationToken"></param>
        /// <param name="logger"></param>
        /// <returns></returns>
        public static async Task PrepareConnectionInfoAsync(this ManagedWebSocket websocket, string correlationID = null, Session session = null, CancellationToken cancellationToken = default, Microsoft.Extensions.Logging.ILogger logger = null)
        {
            correlationID = correlationID ?? UtilityService.NewUUID;
            session       = session ?? websocket.Get <Session>("Session");
            var account = "Visitor";

            if (!string.IsNullOrWhiteSpace(session?.User?.ID))
            {
                try
                {
                    var json = await Router.GetService("Users").ProcessRequestAsync(new RequestInfo(session, "Users", "Profile", "GET")
                    {
                        CorrelationID = correlationID
                    }, cancellationToken).ConfigureAwait(false);

                    account = $"{json?.Get<string>("Name") ?? "Unknown"} ({session.User.ID})";
                }
                catch (Exception ex)
                {
                    account = $"Unknown ({session.User.ID})";
                    logger?.LogError($"Error occurred while fetching an account profile => {ex.Message}", ex);
                }
            }
            websocket.Set("AccountInfo", account);
            websocket.Set("LocationInfo", session != null ? await session.GetLocationAsync(correlationID, cancellationToken).ConfigureAwait(false) : "Unknown");
        }
        // ctor.
        // Š¢Š¾Š»ŃŒŠŗŠ¾ Listener Š¼Š¾Š¶ŠµŃ‚ сŠ¾Š·Š“Š°Ń‚ŃŒ этŠ¾Ń‚ ŠŗŠ»Š°ŃŃ.
        internal ServerSideConnection(ManagedWebSocket clientConnection, ServiceProvider serviceProvider, VRpcListener listener)
            : base(clientConnection, isServer: true, serviceProvider, listener.InvokeActions)
        {
            Listener = listener;

            // Š˜Š·Š½Š°Ń‡Š°Š»ŃŒŠ½Ń‹Š¹ Š½Šµ Š°Š²Ń‚Š¾Ń€ŠøŠ·Š¾Š²Š°Š½Š½Ń‹Š¹ ŠæŠ¾Š»ŃŒŠ·Š¾Š²Š°Ń‚ŠµŠ»ŃŒ.
            _user = CreateUnauthorizedUser();
        }
Example #5
0
 public Connection(Listener listener, ManagedWebSocket webSocket) : base(listener)
 {
     WebSocket      = webSocket;
     RemoteAddress  = (webSocket.RemoteEndPoint as IPEndPoint).Address;
     ConnectionTime = DateTime.Now;
     //lastActivityTime = DateTime.Now;
     lastChatTime = DateTime.Now;
 }
 public INetworkingClient GetClient(ManagedWebSocket webSocket)
 {
     if (!webSocket.Extra.TryGetValue(ClientExtra, out var playerObject) ||
         !(playerObject is INetworkingClient client))
     {
         return(null);
     }
     return(client);
 }
Example #7
0
        public void OnDisconnection(ManagedWebSocket ws)
        {
            Connection connection = ws.Get <Connection>("agar");

            Console.WriteLine($"DISCONNECTION FROM {connection.RemoteAddress}");
            connection.OnSocketClose(0, null);
            connections.Remove(connection);
            globalChat.Remove(connection);
        }
Example #8
0
 public static WebSocket CreateServerWebSocket(Stream stream, string subProtocol, TimeSpan keepAliveInterval, int receiveBufferSize)
 {
     return(ManagedWebSocket.CreateFromConnectedStream(
                stream,
                isServer: true,
                subprotocol: subProtocol,
                keepAliveInterval: keepAliveInterval,
                receiveBufferSize: receiveBufferSize));
 }
        public INetworkingClient GetClient(ManagedWebSocket managedWebSocket)
        {
            if (managedWebSocket.Extra.TryGetValue("client", out var clientObj) &&
                clientObj is INetworkingClient client)
            {
                return(client);
            }

            return(null);
        }
Example #10
0
        public void OnData(ManagedWebSocket ws, WebSocketReceiveResult res, byte[] data)
        {
            Connection connection = ws.Get <Connection>("agar");

            if (res.MessageType != WebSocketMessageType.Binary)
            {
                connection.CloseSocket(1003, "Invalid message type");
            }
            connection.OnSocketMessage(data);
        }
Example #11
0
 public void OnConnect(ManagedWebSocket managedWebSocket)
 {
     lock (this)
     {
         WebSocket = managedWebSocket;
         if (dimension != 0)
         {
             entityStreamer.UpdateClientDimension(this, dimension);
         }
     }
 }
 public Task <bool> Verify(INetworkingClientPool networkingClientPool, ManagedWebSocket webSocket, string token,
                           out INetworkingClient client)
 {
     if (!networkingClientPool.TryGet(token, out client))
     {
         return(Task.FromResult(false));
     }
     //TODO: check if already has websocket ect. client.WebSocket
     webSocket.Extra[ClientExtra] = client;
     return(Task.FromResult(true));
 }
Example #13
0
        public void OnConnection(ManagedWebSocket client)
        {
            if (!VerifyClient(client))
            {
                client.CloseAsync(System.Net.WebSockets.WebSocketCloseStatus.InternalServerError, "Connection rejected", CancellationToken.None);
            }
            var newConnection = new Connection(this, client);

            client.Set("agar", newConnection);
            Console.WriteLine($"CONNECTION FROM {newConnection.RemoteAddress}");
            connections.Add(newConnection);
        }
 public void OnConnectionBroken(ManagedWebSocket webSocket)
 {
     if (!webSocket.Extra.Remove(ClientExtra, out var playerObject) ||
         !(playerObject is INetworkingClient client))
     {
         return;
     }
     lock (client)
     {
         client.WebSocket = null;
     }
 }
        public void OnConnect(ManagedWebSocket managedWebSocket)
        {
            lock (this)
            {
                WebSocket = managedWebSocket;
                if (dimension != 0)
                {
                    entityStreamer.UpdateClientDimension(this, dimension);
                }

                if (PositionOverride != null)
                {
                    entityStreamer.UpdateClientPositionOverride(this, PositionOverride);
                }
            }
        }
Example #16
0
        static async Task WhenConnectionIsBrokenAsync(this ManagedWebSocket websocket)
        {
            // prepare
            websocket.SetStatus("Disconnected");
            websocket.Remove("Session", out Session session);
            var correlationID = UtilityService.NewUUID;

            // remove the updater
            if (websocket.Remove("Updater", out IDisposable updater))
            {
                try
                {
                    updater?.Dispose();
                }
                catch (Exception ex)
                {
                    await Global.WriteLogsAsync(RTU.Logger, "Http.InternalAPIs", $"Error occurred while disposing updater: {session?.ToJson()?.ToString(Global.IsDebugResultsEnabled ? Formatting.Indented : Formatting.None)}", ex, Global.ServiceName, LogLevel.Error, correlationID).ConfigureAwait(false);
                }
            }

            // remove the communicator
            if (websocket.Remove("Communicator", out IDisposable communicator))
            {
                try
                {
                    communicator?.Dispose();
                }
                catch (Exception ex)
                {
                    await Global.WriteLogsAsync(RTU.Logger, "Http.InternalAPIs", $"Error occurred while disposing communicator: {session?.ToJson()?.ToString(Global.IsDebugResultsEnabled ? Formatting.Indented : Formatting.None)}", ex, Global.ServiceName, LogLevel.Error, correlationID).ConfigureAwait(false);
                }
            }

            // update the session state
            await Task.WhenAll(
                session != null?session.SendSessionStateAsync(false, correlationID) : Task.CompletedTask,
                Global.IsVisitLogEnabled?Global.WriteLogsAsync(RTU.Logger, "Http.Visits", $"The real-time updater (RTU) is stopped" + "\r\n" + websocket.GetConnectionInfo(session) + "\r\n" + $"- Served times: {websocket.Timestamp.GetElapsedTimes()}", null, Global.ServiceName, LogLevel.Information, correlationID) : Task.CompletedTask
                ).ConfigureAwait(false);
        }
        public static ManagedWebSocket CreateClientWebSocket(Stream innerStream,
                                                             string subProtocol, int receiveBufferSize, int sendBufferSize,
                                                             TimeSpan keepAliveInterval, bool useZeroMaskingKey, ArraySegment <byte> internalBuffer)
        {
            if (innerStream == null)
            {
                throw new ArgumentNullException(nameof(innerStream));
            }

            if (!innerStream.CanRead || !innerStream.CanWrite)
            {
                throw new ArgumentException(!innerStream.CanRead ? SR.NotReadableStream : SR.NotWriteableStream, nameof(innerStream));
            }

            if (subProtocol != null)
            {
                WebSocketValidate.ValidateSubprotocol(subProtocol);
            }

            if (keepAliveInterval != Timeout.InfiniteTimeSpan && keepAliveInterval < TimeSpan.Zero)
            {
                throw new ArgumentOutOfRangeException(nameof(keepAliveInterval), keepAliveInterval,
                                                      SR.Format(SR.net_WebSockets_ArgumentOutOfRange_TooSmall,
                                                                0));
            }

            if (receiveBufferSize <= 0 || sendBufferSize <= 0)
            {
                throw new ArgumentOutOfRangeException(
                          receiveBufferSize <= 0 ? nameof(receiveBufferSize) : nameof(sendBufferSize),
                          receiveBufferSize <= 0 ? receiveBufferSize : sendBufferSize,
                          SR.Format(SR.net_WebSockets_ArgumentOutOfRange_TooSmall, 0));
            }

            return(ManagedWebSocket.CreateFromConnectedStream(
                       innerStream, false, subProtocol, keepAliveInterval,
                       receiveBufferSize, internalBuffer));
        }
        public Task <bool> Verify(INetworkingClientPool networkingClientPool, ManagedWebSocket managedWebSocket,
                                  string token, out INetworkingClient client)
        {
            if (networkingClientPool.TryGet(token, out client))
            {
                //managedWebSocket.Extra.TryAdd("client", client);

                /*if (client.WebSocket != managedWebSocket)
                 * {
                 *  webSocket.CloseWebSocket(client.WebSocket);
                 * }
                 *
                 * client.WebSocket = managedWebSocket;*/
                //return Task.FromResult(true);
                networkingClientPool.Remove(client);
                //TODO: maybe try to reuse clients later when client can send if it was a reconnect or new connection in auth event
            }

            client           = AltNetworking.CreateClient(token);
            client.WebSocket = managedWebSocket; //TODO: maybe do that automatically, but we would lost freedom
            managedWebSocket.Extra.TryAdd("client", client);
            return(Task.FromResult(true));
        }
 public void OnConnectionEstablished(ManagedWebSocket managedWebSocket)
 {
 }
 public void OnError(ManagedWebSocket webSocket, Exception exception)
 {
     Console.WriteLine(exception);
 }
        public void OnMessage(ManagedWebSocket managedWebSocket, WebSocketReceiveResult result, byte[] data)
        {
            Task.Run(async() =>
            {
                var clientEvent = ClientEvent.Parser.ParseFrom(data);
                if (clientEvent == null)
                {
                    return;
                }
                var authEvent = clientEvent.Auth;
                var streamIn  = clientEvent.StreamIn;
                var streamOut = clientEvent.StreamOut;
                if (authEvent != null)
                {
                    var token = authEvent.Token;
                    if (token == null)
                    {
                        return;
                    }
                    var verified = await authenticationProvider.Verify(networkingClientPool, managedWebSocket, token,
                                                                       out var client);
                    if (!verified)
                    {
                        return;
                    }

                    client.OnConnect(managedWebSocket);

                    var sendEvent     = new ServerEvent();
                    var currSendEvent = new EntitySendEvent();
                    lock (AltNetworking.Module.EntityPool.Entities)
                    {
                        foreach (var entity in AltNetworking.Module.EntityPool.Entities)
                        {
                            if (entity.Value.StreamingType == StreamingType.DataStreaming)
                            {
                                var streamedEntity = entity.Value.StreamedEntity.Clone();
                                streamedEntity.Data.Clear();
                                currSendEvent.Entities.Add(streamedEntity);
                            }
                            else
                            {
                                currSendEvent.Entities.Add(entity.Value.StreamedEntity);
                            }
                        }
                    }

                    sendEvent.Send = currSendEvent;
                    await managedWebSocket.SendAsync(sendEvent.ToByteArray(), true);
                }
                else if (streamIn != null)
                {
                    var client = authenticationProvider.GetClient(managedWebSocket);
                    if (client == null)
                    {
                        return;
                    }
                    var entityId = streamIn.EntityId;
                    if (AltNetworking.Module.EntityPool.TryGet(entityId, out var entity))
                    {
                        if (!authenticationProvider.VerifyPosition(client, entity, true))
                        {
                            return;
                        }
                        EntityStreamInHandler?.Invoke(entity, client);
                        entity.ClientStreamedIn(client);
                        var changedKeys = entity.Snapshot.CompareWithClient(client);
                        if (changedKeys != null)
                        {
                            var multipleDataChangeEvent = new EntityMultipleDataChangeEvent {
                                Id = entityId
                            };
                            foreach (var changedKey in changedKeys)
                            {
                                if (entity.StreamedEntity.Data.TryGetValue(changedKey, out var mValue))
                                {
                                    multipleDataChangeEvent.Data[changedKey] = mValue;
                                }
                                else // data got deleted probably
                                {
                                    multipleDataChangeEvent.Data[changedKey] = new MValue {
                                        NullValue = false
                                    };
                                }
                            }

                            var serverEvent = new ServerEvent {
                                MultipleDataChange = multipleDataChangeEvent
                            };
                            await managedWebSocket.SendAsync(serverEvent.ToByteArray(), true);
                        }
                    }
                }
                else if (streamOut != null)
                {
                    var client = authenticationProvider.GetClient(managedWebSocket);
                    if (client == null)
                    {
                        return;
                    }
                    var entityId = streamOut.EntityId;
                    if (AltNetworking.Module.EntityPool.TryGet(entityId, out var entity))
                    {
                        if (!authenticationProvider.VerifyPosition(client, entity, false))
                        {
                            return;
                        }
                        if (entity.ClientStreamedOut(client))
                        {
                            EntityStreamOutHandler?.Invoke(entity, client);
                        }
                    }
                }
            });
        }
Example #22
0
        static async Task WhenConnectionIsEstablishedAsync(this ManagedWebSocket websocket)
        {
            // update status
            websocket.SetStatus("Initializing");
            var correlationID = UtilityService.NewUUID;

            // prepare session
            try
            {
                var query   = websocket.RequestUri.ParseQuery();
                var session = Global.GetSession(websocket.Headers, query, $"{(websocket.RemoteEndPoint as IPEndPoint).Address}");

                // update session identity
                session.SessionID = query.TryGetValue("x-session-id", out var sessionID) ? sessionID.Url64Decode() : "";
                if (string.IsNullOrWhiteSpace(session.SessionID))
                {
                    throw new InvalidRequestException("Session identity is not found");
                }

                // update device identity
                session.DeviceID = query.TryGetValue("x-device-id", out var deviceID) ? deviceID.Url64Decode() : "";
                if (string.IsNullOrWhiteSpace(session.DeviceID))
                {
                    throw new InvalidRequestException("Device identity is not found");
                }

                // update session
                websocket.Set("Session", session);
                await websocket.PrepareConnectionInfoAsync(correlationID, session, Global.CancellationTokenSource.Token, RTU.Logger).ConfigureAwait(false);

                // wait for few times before connecting to API Gateway Router because RxNET needs that
                if (query.ContainsKey("x-restart"))
                {
                    await Task.WhenAll(
                        websocket.SendAsync(new UpdateMessage {
                        Type = "Knock"
                    }),
                        Task.Delay(345, Global.CancellationTokenSource.Token)
                        ).ConfigureAwait(false);
                }
            }
            catch (Exception ex)
            {
                await RTU.WebSocket.CloseWebSocketAsync(websocket, ex is InvalidRequestException?WebSocketCloseStatus.InvalidPayloadData : WebSocketCloseStatus.InternalServerError, ex is InvalidRequestException?$"Request is invalid => {ex.Message}" : ex.Message).ConfigureAwait(false);

                return;
            }

            // subscribe an updater to push messages to client device
            websocket.Set("Updater", Services.Router.IncomingChannel.RealmProxy.Services
                          .GetSubject <UpdateMessage>("messages.update")
                          .Subscribe(
                              async message => await websocket.PushAsync(message).ConfigureAwait(false),
                              async exception => await Global.WriteLogsAsync(RTU.Logger, "Http.InternalAPIs", $"Error occurred while fetching an updating message => {exception.Message}", exception).ConfigureAwait(false)
                              )
                          );

            // subscribe a communicator to update related information
            websocket.Set("Communicator", Services.Router.IncomingChannel.RealmProxy.Services
                          .GetSubject <CommunicateMessage>("messages.services.apigateway")
                          .Subscribe(
                              async message => await websocket.CommunicateAsync(message).ConfigureAwait(false),
                              async exception => await Global.WriteLogsAsync(RTU.Logger, "Http.InternalAPIs", $"Error occurred while fetching an inter-communicating message => {exception.Message}", exception).ConfigureAwait(false)
                              )
                          );

            // update status
            websocket.SetStatus("Connected");
            if (Global.IsVisitLogEnabled)
            {
                await Global.WriteLogsAsync(RTU.Logger, "Http.Visits", $"The real-time updater (RTU) is started" + "\r\n" + websocket.GetConnectionInfo() + "\r\n" + $"- Status: {websocket.GetStatus()}", null, Global.ServiceName, LogLevel.Information, correlationID).ConfigureAwait(false);
            }
        }
Example #23
0
        static async Task WhenMessageIsReceivedAsync(this ManagedWebSocket websocket, WebSocketReceiveResult result, byte[] data)
        {
            // receive continuous messages
            object message;

            if (!result.EndOfMessage)
            {
                websocket.Extra["Message"] = websocket.Extra.TryGetValue("Message", out message) ? (message as byte[]).Concat(data) : data;
                return;
            }

            // last message or single small message
            var stopwatch     = Stopwatch.StartNew();
            var correlationID = UtilityService.NewUUID;

            if (websocket.Extra.TryGetValue("Message", out message))
            {
                message = (message as byte[]).Concat(data);
                websocket.Extra.Remove("Message");
            }
            else
            {
                message = data;
            }

            // check message
            var requestMsg = result.MessageType.Equals(WebSocketMessageType.Text) ? (message as byte[]).GetString() : null;

            if (string.IsNullOrWhiteSpace(requestMsg))
            {
                return;
            }

            // wait for the initializing process is completed
            while ("Initializing".IsEquals(websocket.GetStatus()))
            {
                await Task.Delay(UtilityService.GetRandomNumber(123, 456), Global.CancellationTokenSource.Token).ConfigureAwait(false);
            }

            // check session
            var session = websocket.Get <Session>("Session");

            if (session == null)
            {
                await Task.WhenAll
                (
                    Global.WriteLogsAsync(RTU.Logger, "Http.InternalAPIs", $"No session is attached - Request: {requestMsg}", null, Global.ServiceName, LogLevel.Critical, correlationID),
                    RTU.WebSocket.CloseWebSocketAsync(websocket, WebSocketCloseStatus.PolicyViolation, "No session")
                ).ConfigureAwait(false);

                return;
            }

            // prepare
            var requestObj  = requestMsg.ToExpandoObject();
            var serviceName = requestObj.Get("ServiceName", "");
            var objectName  = requestObj.Get("ObjectName", "");
            var verb        = requestObj.Get("Verb", "GET").ToUpper();
            var query       = new Dictionary <string, string>(requestObj.Get("Query", new Dictionary <string, string>()), StringComparer.OrdinalIgnoreCase);

            query.TryGetValue("object-identity", out var objectIdentity);

            // visit logs
            if (Global.IsVisitLogEnabled)
            {
                await Global.WriteLogsAsync(RTU.Logger, "Http.Visits",
                                            $"Request starting {verb} " + $"/{serviceName}{(string.IsNullOrWhiteSpace(objectName) ? "" : $"/{objectName}")}{(string.IsNullOrWhiteSpace(objectIdentity) ? "" : $"/{objectIdentity}")}".ToLower() + (query.TryGetValue("x-request", out var xrequest) ? $"?x-request={xrequest}" : "") + " HTTPWS/1.1" + " \r\n" +
 /// <summary>
 /// Sends an update message
 /// </summary>
 /// <param name="websocket"></param>
 /// <param name="message"></param>
 /// <param name="cancellationToken"></param>
 /// <param name="toJsonPreCompleted"></param>
 /// <returns></returns>
 public static Task SendAsync(this ManagedWebSocket websocket, UpdateMessage message, CancellationToken cancellationToken = default, Action <JToken> toJsonPreCompleted = null)
 => websocket.SendAsync(message?.ToJson(toJsonPreCompleted), cancellationToken);
 public void OnConnectionBroken(ManagedWebSocket managedWebSocket)
 {
     //TODO: remove after 5min without life signal from client pool
     managedWebSocket.Extra.Remove("client");
 }
 /// <summary>
 /// Sends the message
 /// </summary>
 /// <param name="websocket"></param>
 /// <param name="message"></param>
 /// <param name="cancellationToken"></param>
 /// <returns></returns>
 public static Task SendAsync(this ManagedWebSocket websocket, JToken message, CancellationToken cancellationToken = default)
 => websocket.SendAsync(message?.ToString(Formatting.None), cancellationToken);
 /// <summary>
 /// Sends the messages
 /// </summary>
 /// <param name="websocket"></param>
 /// <param name="messages"></param>
 /// <param name="cancellationToken"></param>
 /// <returns></returns>
 public static Task SendAsync(this ManagedWebSocket websocket, IEnumerable <JToken> messages, CancellationToken cancellationToken = default)
 => websocket.SendAsync(messages?.Where(message => message != null).Select(message => message.ToString(Formatting.None)), cancellationToken);
 /// <summary>
 /// Sends the messages
 /// </summary>
 /// <param name="websocket"></param>
 /// <param name="messages"></param>
 /// <param name="cancellationToken"></param>
 /// <returns></returns>
 public static Task SendAsync(this ManagedWebSocket websocket, IEnumerable <string> messages, CancellationToken cancellationToken = default)
 => (messages ?? new List <string>()).Where(message => !string.IsNullOrWhiteSpace(message)).ForEachAsync((message, token) => websocket.SendAsync(message, token), cancellationToken, true, false);
 public void OnConnectionEstablished(ManagedWebSocket webSocket)
 {
     // we don't care about websockets that are just here, maybe timeout them after 5sec without authentication
 }
 public void OnConnectionBroken(ManagedWebSocket webSocket)
 {
     webSocket.Extra.Remove(ClientExtra);
 }