// Private methods
        private async Task HandleMessage(string message)
        {
            WsClient[] clientSockets = WsManager.GetClientsByUser(this.UserId);
            if (clientSockets == null || clientSockets.Length < 1)
            {
                return;
            }
            var sockets = clientSockets.Select(c => c.Socket).ToArray();

            await BroadcastMessage(message, sockets);
        }
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseWebSockets(new WebSocketOptions()
            {
                KeepAliveInterval = TimeSpan.FromSeconds(120),
                ReceiveBufferSize = 4 * 1024
            });

            app.UseMiddleware <SimpleWsMiddleware>();

            app.UseFileServer();

            int cleanUpRate = 3;             // sec

            Task.Run(() => WsManager.CheckExpired(cleanUpRate));
        }
        public async Task Invoke(HttpContext context)
        {
            // If not WebSockets request - ignore this and go to next middleware
            if (!context.WebSockets.IsWebSocketRequest)
            {
                await _next.Invoke(context);

                return;
            }

            // Establishing WebSocket connection
            CancellationToken ct            = context.RequestAborted;
            WebSocket         currentSocket = await context.WebSockets.AcceptWebSocketAsync();

            if (currentSocket == null || currentSocket.State != WebSocketState.Open)
            {
                return;
            }

            // Getting token from which determine a user/owner
            string   userIdCookie;
            string   tokenCookie;
            DateTime expirationTime;

            try
            {
                userIdCookie   = context.Request.Query[COOKIE_USER_KEY].ToString();
                tokenCookie    = context.Request.Query[COOKIE_TOKEN_KEY].ToString();
                expirationTime = new JwtSecurityTokenHandler().ReadJwtToken(tokenCookie).ValidTo;
            }
            catch (Exception)
            {
                await currentSocket.CloseAsync(
                    WebSocketCloseStatus.PolicyViolation, "Wrong cookies or damaged JWT.", default(CancellationToken));

                return;
            }

            if (expirationTime < DateTime.UtcNow)
            {
                await currentSocket.CloseAsync(
                    WebSocketCloseStatus.PolicyViolation, "JWT expired.", default(CancellationToken));

                return;
            }

            if (!TokenIsValid(userIdCookie, tokenCookie))
            {
                await currentSocket.CloseAsync(
                    WebSocketCloseStatus.PolicyViolation, "Not authorized.", default(CancellationToken));

                return;
            }

            WsClient wsClient = new WsClient(currentSocket, userIdCookie, expirationTime);

            // Adding socket to Manager and subscribing for new messages.
            try
            {
                WsManager.AddClient(wsClient);
                await wsClient.Listen(ct);
            }
            catch (Exception ex)
            {
                // TODO: Cleanup - remove socket if Aborted
                Console.WriteLine(ex.Message);
                WsManager.RemoveClient(wsClient);
            }
        }