예제 #1
0
        /// <summary>
        /// Creates an <see cref="EtpClient"/> instance configurated with the
        /// current connection and authorization parameters.
        /// </summary>
        /// <param name="webSocketType">The WebSocket type.</param>
        /// <param name="etpVersion">The ETP version.</param>
        /// <param name="url">The WebSocket URL.</param>
        /// <param name="authorization">The client's authorization details.</param>
        /// <param name="etpEncoding">The encoding to use.</param>
        /// <returns>A new <see cref="IEtpClient"/> instance.</returns>
        protected IEtpClient CreateClient(WebSocketType webSocketType, EtpVersion etpVersion, string url, Security.Authorization authorization = null, EtpEncoding etpEncoding = EtpEncoding.Binary)
        {
            var version = GetType().Assembly.GetName().Version.ToString();

            if (authorization == null)
            {
                authorization = Security.Authorization.Basic(TestSettings.Username, TestSettings.Password);
            }

            var endpointInfo = EtpFactory.CreateClientEndpointInfo(GetType().AssemblyQualifiedName, version, "ETP DevKit Integration Test");

            var client = EtpFactory.CreateClient(webSocketType, url, etpVersion, etpEncoding, endpointInfo, authorization: authorization);

            if (etpVersion == EtpVersion.v11)
            {
                client.Register(new v11.Protocol.ChannelStreaming.ChannelStreamingConsumerHandler());
                client.Register(new v11.Protocol.Discovery.DiscoveryCustomerHandler());
                client.Register(new v11.Protocol.Store.StoreCustomerHandler());
            }
            else
            {
                client.Register(new v12.Protocol.ChannelStreaming.ChannelStreamingConsumerHandler());
                client.Register(new v12.Protocol.Discovery.DiscoveryCustomerHandler());
                client.Register(new v12.Protocol.Store.StoreCustomerHandler());
            }

            return(client);
        }
예제 #2
0
        /// <summary>
        /// Creates an <see cref="EtpClient"/> instance configurated with the
        /// current connection and authorization parameters.
        /// </summary>
        /// <param name="webSocketType">The WebSocket type.</param>
        /// <param name="etpSubProtocol">The ETP websocket sub-protocol</param>
        /// <param name="url">The WebSocket URL.</param>
        /// <returns>A new <see cref="IEtpClient"/> instance.</returns>
        protected IEtpClient CreateClient(WebSocketType webSocketType, string etpSubProtocol, string url, IDictionary <string, string> headers = null)
        {
            var version = GetType().Assembly.GetName().Version.ToString();

            if (headers == null)
            {
                headers = Security.Authorization.Basic(TestSettings.Username, TestSettings.Password);
            }

            var client = EtpFactory.CreateClient(webSocketType, url, GetType().AssemblyQualifiedName, version, etpSubProtocol, headers);

            if (client.SupportedVersion == EtpVersion.v11)
            {
                client.Register <v11.Protocol.ChannelStreaming.IChannelStreamingConsumer, v11.Protocol.ChannelStreaming.ChannelStreamingConsumerHandler>();
                client.Register <v11.Protocol.Discovery.IDiscoveryCustomer, v11.Protocol.Discovery.DiscoveryCustomerHandler>();
                client.Register <v11.Protocol.Store.IStoreCustomer, v11.Protocol.Store.StoreCustomerHandler>();
            }
            else
            {
                client.Register <v12.Protocol.ChannelStreaming.IChannelStreamingConsumer, v12.Protocol.ChannelStreaming.ChannelStreamingConsumerHandler>();
                client.Register <v12.Protocol.Discovery.IDiscoveryCustomer, v12.Protocol.Discovery.DiscoveryCustomerHandler>();
                client.Register <v12.Protocol.Store.IStoreCustomer, v12.Protocol.Store.StoreCustomerHandler>();
            }

            return(client);
        }
예제 #3
0
        public async Task Handle(WebSocket socket, WebSocketType type)
        {
            switch (type)
            {
            case WebSocketType.Client:
                // Add socket to list of client WebSockets
                _clientWebSockets.Add(socket);
                break;

            case WebSocketType.FrontEnd:
                // Add socket to list of frontend WebSockets
                _frontendWebSockets.Add(socket);
                break;

            case WebSocketType.Plugin:
                // Add socket to list of plugin WebSockets
                _pluginWebSockets.Add(socket);
                break;

            default:
                throw new ArgumentOutOfRangeException(nameof(type), type, "Invalid WebSocket type");
            }

            // Keep connection to socket open
            await ListenTo(socket, type);
        }
예제 #4
0
        /// <summary>
        /// Initialize ELM327COM websocket client.
        /// </summary>
        /// <param name="url">URL of the server.</param>
        /// <param name="codes">Parameter code list</param>
        /// <returns></returns>
        private WebSocket initializeELM327COMWSClient(string url, List <OBDIIParameterCode> codes)
        {
            if (codes.Count == 0)
            {
                return(null);
            }

            const WebSocketType WSType   = WebSocketType.ELM327;
            WebSocket           wsClient = new WebSocket(url);

            wsClient.Opened += (sender, e) =>
            {
                foreach (OBDIIParameterCode code in codes)
                {
                    // Send read packet both slow and fast readmode.
                    ELM327COMReadJSONFormat sendcode = new ELM327COMReadJSONFormat();
                    sendcode.code      = code.ToString();
                    sendcode.read_mode = ELM327COMReadJSONFormat.SlowReadModeCode;
                    sendcode.flag      = true;
                    wsClient.Send(sendcode.Serialize());

                    sendcode.read_mode = ELM327COMReadJSONFormat.FastReadModeCode;
                    wsClient.Send(sendcode.Serialize());
                }
            };
            wsClient.Error           += (sender, e) => wsErrorMsg(WSType.ToString(), e.Exception.ToString(), e.Exception.Message);
            wsClient.Closed          += (sender, e) => wsClosedReconnect(WSType.ToString(), wsClient);
            wsClient.MessageReceived += (sender, e) => parseVALMessage(e.Message, WSType);

            return(wsClient);
        }
예제 #5
0
        /// <summary>
        /// Initialize ArduinoCOM websocket client
        /// </summary>
        /// <param name="url">URL of the server</param>
        /// <param name="codes">parameter code list to activate</param>
        /// <returns></returns>
        private WebSocket initializeArduinoCOMWSClient(string url, List <ArduinoParameterCode> codes)
        {
            if (codes.Count == 0)
            {
                return(null);
            }

            const WebSocketType WSType   = WebSocketType.ARDUINO;
            WebSocket           wsClient = new WebSocket(url);

            wsClient.Opened += (sender, e) =>
            {
                foreach (ArduinoParameterCode code in codes)
                {
                    ArduinoWSSendJSONFormat sendcode = new ArduinoWSSendJSONFormat();
                    sendcode.code = code.ToString();
                    sendcode.flag = true;

                    ArduinoWSIntervalJSONFormat definitervalcode = new ArduinoWSIntervalJSONFormat();
                    definitervalcode.interval = DEFI_ARDUINO_PACKET_INTERVAL;

                    wsClient.Send(sendcode.Serialize());
                    wsClient.Send(definitervalcode.Serialize());
                }
            };
            wsClient.Error           += (sender, e) => wsErrorMsg(WSType.ToString(), e.Exception.ToString(), e.Exception.Message);
            wsClient.Closed          += (sender, e) => wsClosedReconnect(WSType.ToString(), wsClient);
            wsClient.MessageReceived += (sender, e) => parseVALMessage(e.Message, WSType);

            return(wsClient);
        }
예제 #6
0
 public override bool SupportWebSocket(WebSocketType type)
 {
     if (type == WebSocketType.Tickers)
     {
         return(true);
     }
     return(false);
 }
예제 #7
0
        /// <summary>
        /// Creates an <see cref="EtpSocketServer"/> instance.
        /// </summary>
        /// <returns>A new <see cref="EtpSocketServer"/> instance.</returns>
        protected IEtpSelfHostedWebServer CreateServer(WebSocketType webSocketType)
        {
            var version = GetType().Assembly.GetName().Version.ToString();
            var port    = GetAvailablePort();
            var server  = EtpFactory.CreateSelfHostedWebServer(webSocketType, port, GetType().AssemblyQualifiedName, version);

            return(server);
        }
예제 #8
0
        protected void SetUpOnceCleanUpTwice(WebSocketType webSocketType)
        {
            SetupStartOpen(webSocketType);

            _server?.Stop();
            CleanUp();

            _server?.Stop();
            CleanUp();
        }
예제 #9
0
        /// <summary>
        /// Initializes common resources.
        /// </summary>
        /// <param name="webSocketType">The WebSocket type.</param>
        /// <param name="etpSubProtocol">The ETP websocket sub-protocol</param>
        protected void SetUp(WebSocketType webSocketType, string etpSubProtocol)
        {
            // Clean up any remaining resources
            _client?.Dispose();
            _server?.Dispose();

            // Create server and client instances
            _server = CreateServer(webSocketType);
            _client = CreateClient(webSocketType, etpSubProtocol, _server.Uri.ToWebSocketUri().ToString());
        }
예제 #10
0
        protected void RunStressTest(WebSocketType webSocketType, Action <WebSocketType> setup, Action cleanup)
        {
            int iterations = webSocketType == WebSocketType.Native ? nativeIterations : webSocket4NetIterations;

            for (int i = 0; i < iterations; i++)
            {
                Logger.Debug($"Starting iteration {i}");
                setup(webSocketType);

                cleanup();
            }
        }
 public override bool SupportWebSocket(WebSocketType type)
 {
     if (type == WebSocketType.Ticker || type == WebSocketType.Trades || type == WebSocketType.OrderBook)
     {
         return(true);
     }
     if (type == WebSocketType.Tickers)
     {
         return(true);
     }
     return(false);
 }
예제 #12
0
        private Task <bool> CheckAuthentication(WebSocketMessage message, WebSocketType type)
        {
            if (!string.Equals(message.Type, "auth", StringComparison.InvariantCultureIgnoreCase))
            {
                return(Task.FromResult(false));
            }

            if (!string.Equals(message.Value, type.ToString(), StringComparison.InvariantCultureIgnoreCase))
            {
                return(Task.FromResult(false));
            }

            return(Task.FromResult(true));
        }
예제 #13
0
        /// <summary>
        /// Resets any modified test settings.
        /// </summary>
        public static void Reset()
        {
            ServerCapabilitiesUrl = Settings.Default.ServerCapabilitiesUrl;
            AuthTokenUrl          = Settings.Default.AuthTokenUrl;
            ServerUrl             = Settings.Default.ServerUrl;
            Username   = Settings.Default.Username;
            Password   = Settings.Default.Password;
            EtpVersion = Settings.Default.EtpVersion;
            DefaultTimeoutInMilliseconds = 5000;
            ProxyUsername = Settings.Default.ProxyUsername;
            ProxyPassword = Settings.Default.ProxyPassword;

            WebSocketType = EtpSettings.DefaultWebSocketType;
        }
예제 #14
0
        /// <summary>
        /// Creates an <see cref="IEtpSelfHostedWebServer"/> using the specified WebSocket type.
        /// </summary>
        /// <param name="webSocketType">The specified WebSocket type.</param>
        /// <param name="port">The port.</param>
        /// <param name="application">The server application name.</param>
        /// <param name="version">The server application version.</param>
        /// <returns>The <see cref="IEtpSelfHostedWebServer"/></returns>
        public static IEtpSelfHostedWebServer CreateSelfHostedWebServer(WebSocketType webSocketType, int port, string application, string version)
        {
            switch (webSocketType)
            {
            case WebSocketType.Native:
                return(new Native.EtpSelfHostedWebServer(port, application, version));

            case WebSocketType.WebSocket4Net:
                return(new WebSocket4Net.EtpSelfHostedWebServer(port, application, version));

            default:
                throw new ArgumentException($"Unrecognized WebSocket type: {webSocketType}", "webSocketType");
            }
        }
예제 #15
0
        /// <summary>
        /// Creates an <see cref="IEtpClient"/> using the specified WebSocket type.
        /// </summary>
        /// <param name="webSocketType">The specified WebSocket type.</param>
        /// <param name="uri">The ETP server URI.</param>
        /// <param name="application">The client application name.</param>
        /// <param name="version">The client application version.</param>
        /// <param name="etpSubProtocol">The ETP sub protocol.</param>
        /// <param name="headers">The WebSocket headers.</param>
        /// <returns>The <see cref="IEtpClient"/></returns>
        public static IEtpClient CreateClient(WebSocketType webSocketType, string uri, string application, string version, string etpSubProtocol, IDictionary <string, string> headers)
        {
            switch (webSocketType)
            {
            case WebSocketType.Native:
                return(new Native.EtpClient(uri, application, version, etpSubProtocol, headers));

            case WebSocketType.WebSocket4Net:
                return(new WebSocket4Net.EtpClient(uri, application, version, etpSubProtocol, headers));

            default:
                throw new ArgumentException($"Unrecognized WebSocket type: {webSocketType}", "webSocketType");
            }
        }
예제 #16
0
        /// <summary>
        /// Initializes common resources.
        /// </summary>
        /// <param name="webSocketType">The WebSocket type.</param>
        /// <param name="etpSubProtocol">The ETP websocket sub-protocol</param>
        protected void SetUpWithProxy(WebSocketType webSocketType, string etpSubProtocol)
        {
            // Clean up any remaining resources
            _client?.Dispose();
            _server?.Dispose();

            var proxiedServer = CreateServer(webSocketType);

            _server = new EtpSelfHostedProxyWebServer(GetAvailablePort(), proxiedServer);

            // Use hostname so .NET will connect through the proxy.
            var uri = new UriBuilder(proxiedServer.Uri.Scheme, "vcap.me", proxiedServer.Uri.Port, proxiedServer.Uri.AbsolutePath, proxiedServer.Uri.Query).Uri;

            _client = CreateClient(webSocketType, etpSubProtocol, uri.ToWebSocketUri().ToString());
        }
 protected void SetupStartOpen(WebSocketType webSocketType)
 {
     SetUp(webSocketType, EtpSettings.Etp11SubProtocol);
     _server.Start();
     _client.Open();
 }
예제 #18
0
 public override bool SupportWebSocket(WebSocketType type) {
     if(type == WebSocketType.Tickers)
         return true;
     return false;
 }
예제 #19
0
 /// <summary>
 /// Creates an <see cref="IEtpClient"/> using the specified WebSocket type.
 /// </summary>
 /// <param name="webSocketType">The specified WebSocket type.</param>
 /// <param name="uri">The ETP server URI.</param>
 /// <param name="application">The client application name.</param>
 /// <param name="version">The client application version.</param>
 /// <param name="etpSubProtocol">The ETP sub protocol.</param>
 /// <returns>The <see cref="IEtpClient"/></returns>
 public static IEtpClient CreateClient(WebSocketType webSocketType, string uri, string application, string version, string etpSubProtocol)
 {
     return(CreateClient(webSocketType, uri, application, version, etpSubProtocol, EmptyHeaders));
 }
예제 #20
0
 public override bool SupportWebSocket(WebSocketType type)
 {
     return(false); // TODO return true
 }
예제 #21
0
 public abstract bool SupportWebSocket(WebSocketType type);
 protected void SetUp(WebSocketType webSocketType)
 {
     SetUp(webSocketType, EtpSettings.Etp11SubProtocol);
 }
예제 #23
0
 protected void SetupStartOpen(WebSocketType webSocketType)
 {
     SetUp(webSocketType, EtpVersion.v11);
     _server.Start();
     _client.Open();
 }
예제 #24
0
 protected void SetUp(WebSocketType webSocketType)
 {
     SetUp(webSocketType, EtpVersion.v11);
 }
예제 #25
0
        private async Task ListenTo(WebSocket socket, WebSocketType type)
        {
            await using var memory = new MemoryStream();
            bool isAuthenticated = false;

            while (socket.State == WebSocketState.Open)
            {
                // Read message
                var message = await GetMessage(socket, memory);

                _log.LogInformation("WebSocket request ({Type}): {Json}", message.Type, message.Value);

                // Check authentication
                if (!isAuthenticated)
                {
                    _log.LogDebug("Unauthenticated socket, checking authentication");
                    isAuthenticated = await CheckAuthentication(message, type);

                    // Break connection if still not authenticated
                    if (!isAuthenticated)
                    {
                        _log.LogDebug("Did not pass authentication, kicking");
                        await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Unauthenticated",
                                                CancellationToken.None);

                        return;
                    }

                    // Start listening from next message
                    _log.LogDebug("Passed authentication, sending catchup");

                    // Send EliteAPI information
                    await SendTo(socket, new WebSocketMessage("EliteAPI", $"{{\"Version\": \"{_api.Version}\"}}"));

                    switch (type)
                    {
                    // Send catchup messages to frontend
                    case WebSocketType.FrontEnd:
                        await Catchup(socket, _frontendCatchupMessages);

                        break;

                    // Send catchup messages to client
                    case WebSocketType.Client:
                        await Catchup(socket, _clientCatchupMessages);

                        break;

                    // Send catchup messages to client
                    case WebSocketType.Plugin:
                        await Catchup(socket, _pluginCatchupMessages);

                        break;

                    default:
                        throw new ArgumentOutOfRangeException(nameof(type), type, "Invalid WebSocket type");
                    }
                }


                // Process message
                switch (message.Type.ToLower())
                {
                case "userprofile.get":
                    await SendTo(socket, new WebSocketMessage("UserProfile", UserProfile.Get()));

                    break;

                case "userprofile.set":
                    UserProfile.Set(message.Value);
                    await SendTo(socket, new WebSocketMessage("UserProfile", UserProfile.Get()));

                    break;

                case "eliteva.install":
                    Process.Start("taskkill", "/F /IM voiceattack.exe");
                    await _eliteVaInstaller.DownloadLatestVersion();
                    await SendTo(socket, new WebSocketMessage("UserProfile", UserProfile.Get()));

                    break;

                case "eliteva.latest":
                    await SendTo(socket, new WebSocketMessage("EliteVA.Latest", await _eliteVaInstaller.GetLatestVersion()));

                    break;
                }
            }
        }
예제 #26
0
        /// <summary>
        /// Parse VAL message from Websocket server.
        /// </summary>
        /// <param name="jsonmsg">JSONMessage</param>
        /// <param name="wsClientType">WebsocketClientType</param>
        private void parseVALMessage(string jsonmsg, WebSocketType wsClientType)
        {
            //Ignore "DMY" message. (DMY message is sent from server in order to keep-alive wifi connection (to prevent wifi low-power(high latency) mode).
            if (jsonmsg == "DMY")
            {
                return;
            }

            string receivedJSONMode;

            try
            {
                JObject jobject = JObject.Parse(jsonmsg);
                receivedJSONMode = jobject.Property("mode").Value.ToString();
            }
            catch (KeyNotFoundException ex)
            {
                logger.Error(ex.GetType().ToString() + " " + ex.Message + " JSON:" + jsonmsg);
                return;
            }
            catch (JsonReaderException ex)
            {
                logger.Error(ex.GetType().ToString() + " " + ex.Message + " JSON:" + jsonmsg);
                return;
            }
            catch (JsonException ex)
            {
                logger.Error(ex.GetType().ToString() + " " + ex.Message + " JSON:" + jsonmsg);
                return;
            }

            try
            {
                if (receivedJSONMode == ValueJSONFormat.ModeCode)
                {
                    ValueJSONFormat valJson = JsonConvert.DeserializeObject <ValueJSONFormat>(jsonmsg);
                    valJson.Validate();

                    switch (wsClientType)
                    {
                    case WebSocketType.DEFI:
                        foreach (String code in valJson.val.Keys)
                        {
                            double value = double.Parse(valJson.val[code]);
                            if (code == DefiParameterCode.Engine_Speed.ToString())
                            {
                                this.engineRev = value;
                            }
                            else
                            {
                                throw new InvalidOperationException("Unexpected parameter code is returned on parsing VAL. ServerType:" + wsClientType.ToString() + " Code:" + code);
                            }
                        }
                        break;

                    case WebSocketType.SSM:
                        foreach (string code in valJson.val.Keys)
                        {
                            double value = double.Parse(valJson.val[code]);
                            if (code == SSMParameterCode.Vehicle_Speed.ToString())
                            {
                                this.vehicleSpeed = value;
                            }
                            else if (code == SSMParameterCode.Engine_Speed.ToString())
                            {
                                this.engineRev = value;
                            }
                            else if (code == SSMParameterCode.Fuel_Injection_1_Pulse_Width.ToString())
                            {
                                this.injPulseWidth = value;
                            }
                            else if (code == SSMParameterCode.Mass_Air_Flow.ToString())
                            {
                                this.massAirFlow = value;
                            }
                            else if (code == SSMParameterCode.Air_Fuel_Sensor_1.ToString())
                            {
                                this.afRatio = value;
                            }
                            else
                            {
                                throw new InvalidOperationException("Unexpected parameter code is returned on parsing VAL. ServerType:" + wsClientType.ToString() + " Code:" + code);
                            }
                        }
                        break;

                    case WebSocketType.ARDUINO:
                        foreach (string code in valJson.val.Keys)
                        {
                            double value = double.Parse(valJson.val[code]);
                            if (code == ArduinoParameterCode.Engine_Speed.ToString())
                            {
                                this.engineRev = value;
                            }
                            else if (code == ArduinoParameterCode.Vehicle_Speed.ToString())
                            {
                                this.vehicleSpeed = value;
                            }
                            else
                            {
                                throw new InvalidOperationException("Unexpected parameter code is returned on parsing VAL. ServerType:" + wsClientType.ToString() + " Code:" + code);
                            }
                        }
                        break;

                    case WebSocketType.ELM327:
                        foreach (string code in valJson.val.Keys)
                        {
                            double value = double.Parse(valJson.val[code]);
                            if (code == OBDIIParameterCode.Engine_Speed.ToString())
                            {
                                this.engineRev = value;
                            }
                            else if (code == OBDIIParameterCode.Vehicle_Speed.ToString())
                            {
                                this.vehicleSpeed = value;
                            }
                            else if (code == OBDIIParameterCode.Mass_Air_Flow.ToString())
                            {
                                this.massAirFlow = value;
                            }
                            else if (code == OBDIIParameterCode.Command_equivalence_ratio.ToString())
                            {
                                this.afRatio = value;
                            }
                            else if (code == OBDIIParameterCode.Engine_fuel_rate.ToString())
                            {
                                this.fuelRate = value;
                            }
                            else
                            {
                                throw new InvalidOperationException("Unexpected parameter code is returned on parsing VAL. ServerType:" + wsClientType.ToString() + " Code:" + code);
                            }
                        }
                        break;
                    }

                    //Finally fire VALMessageReceived event
                    VALMessageParsed(this, null);
                }
                else if (receivedJSONMode == ErrorJSONFormat.ModeCode)
                {
                    ErrorJSONFormat err_json = JsonConvert.DeserializeObject <ErrorJSONFormat>(jsonmsg);
                    err_json.Validate();
                    logger.Error("Error occured from " + wsClientType.ToString() + ":" + err_json.msg);
                }
                else if (receivedJSONMode == ResponseJSONFormat.ModeCode)
                {
                    ResponseJSONFormat res_json = JsonConvert.DeserializeObject <ResponseJSONFormat>(jsonmsg);
                    res_json.Validate();
                    logger.Info("Response from " + wsClientType.ToString() + ":" + res_json.msg);
                }
            }
            catch (JSONFormatsException ex)
            {
                logger.Error(ex.GetType().ToString() + " " + ex.Message + " JSON:" + jsonmsg);
                return;
            }
            catch (JsonException ex)
            {
                logger.Error(ex.GetType().ToString() + " " + ex.Message + " JSON:" + jsonmsg);
                return;
            }
            catch (KeyNotFoundException ex)
            {
                logger.Error(ex.GetType().ToString() + " " + ex.Message + " JSON:" + jsonmsg);
                return;
            }
            catch (FormatException ex)
            {
                logger.Error(ex.GetType().ToString() + " " + ex.Message + " JSON:" + jsonmsg);
                return;
            }
            catch (InvalidOperationException ex)
            {
                logger.Error(ex.GetType().ToString() + " " + ex.Message + " JSON:" + jsonmsg);
                return;
            }
        }
예제 #27
0
        public async Task Broadcast(WebSocketMessage message, WebSocketType type, bool useDuringCatchup, bool onlySaveLatestForCatchup)
        {
            switch (type)
            {
            case WebSocketType.Client:
                // Store in catchup
                if (useDuringCatchup)
                {
                    if (onlySaveLatestForCatchup)
                    {
                        // Replace of same type
                        _clientCatchupMessages.RemoveAll(x =>
                                                         string.Equals(x.Type, message.Type, StringComparison.InvariantCultureIgnoreCase));
                    }

                    // Add
                    _clientCatchupMessages.Add(message);
                }

                // Broadcast to client WebSockets
                foreach (var clientWebSocket in _clientWebSockets)
                {
                    await SendTo(clientWebSocket, message);
                }
                break;

            case WebSocketType.FrontEnd:
                // Store in catchup
                if (useDuringCatchup)
                {
                    if (onlySaveLatestForCatchup)
                    {
                        // Replace of same type
                        _frontendCatchupMessages.RemoveAll(x =>
                                                           string.Equals(x.Type, message.Type, StringComparison.InvariantCultureIgnoreCase));
                    }

                    // Add
                    _frontendCatchupMessages.Add(message);
                }

                // Broadcast to frontend WebSockets
                foreach (var frontendWebSocket in _frontendWebSockets)
                {
                    await SendTo(frontendWebSocket, message);
                }
                break;

            case WebSocketType.Plugin:
                // Store in catchup
                if (useDuringCatchup)
                {
                    if (onlySaveLatestForCatchup)
                    {
                        // Replace of same type
                        _pluginCatchupMessages.RemoveAll(x =>
                                                         string.Equals(x.Type, message.Type, StringComparison.InvariantCultureIgnoreCase));
                    }

                    // Add
                    _pluginCatchupMessages.Add(message);
                }

                // Broadcast to frontend WebSockets
                foreach (var pluginWebSocket in _pluginWebSockets)
                {
                    await SendTo(pluginWebSocket, message);
                }
                break;


            default:
                throw new ArgumentOutOfRangeException(nameof(type), type, "Invalid WebSocket type");
            }
        }