void ProcessEventMessage(ServerEventMessage e)
        {
            var parts = e.Data.SplitOnFirst(' ');

            e.Selector = parts[0];
            e.Json     = parts[1];

            if (!string.IsNullOrEmpty(e.Selector))
            {
                parts = e.Selector.SplitOnFirst('.');
                e.Op  = parts[0];
                var target = parts[1].Replace("%20", " ");

                var tokens = target.SplitOnFirst('$');
                e.Target = tokens[0];
                if (tokens.Length > 1)
                {
                    e.CssSelector = tokens[1];
                }

                if (e.Op == "cmd")
                {
                    switch (e.Target)
                    {
                    case "onConnect":
                        ProcessOnConnectMessage(e);
                        return;

                    case "onJoin":
                        ProcessOnJoinMessage(e);
                        return;

                    case "onLeave":
                        ProcessOnLeaveMessage(e);
                        return;

                    case "onHeartbeat":
                        ProcessOnHeartbeatMessage(e);
                        return;

                    default:
                        ServerEventCallback cb;
                        if (Handlers.TryGetValue(e.Target, out cb))
                        {
                            cb(this, e);
                        }
                        break;
                    }
                }

                ServerEventCallback receiver;
                NamedReceivers.TryGetValue(e.Op, out receiver);
                if (receiver != null)
                {
                    receiver(this, e);
                }
            }

            OnMessageReceived(e);
        }
        void ProcessEventMessage(ServerEventMessage e)
        {
            var parts = e.Data.SplitOnFirst(' ');

            e.Selector = parts[0];
            var selParts = e.Selector.SplitOnFirst('@');

            if (selParts.Length > 1)
            {
                e.Channel  = selParts[0];
                e.Selector = selParts[1];
            }

            e.Json = parts[1];

            if (!string.IsNullOrEmpty(e.Selector))
            {
                parts = e.Selector.SplitOnFirst('.');
                if (parts.Length < 2)
                {
                    throw new ArgumentException($"Invalid Selector '{e.Selector}'");
                }

                e.Op = parts[0];
                var target = parts[1].Replace("%20", " ");

                var tokens = target.SplitOnFirst('$');
                e.Target = tokens[0];
                if (tokens.Length > 1)
                {
                    e.CssSelector = tokens[1];
                }

                if (e.Op == "cmd")
                {
                    switch (e.Target)
                    {
                    case "onConnect":
                        ProcessOnConnectMessage(e);
                        return;

                    case "onJoin":
                        ProcessOnJoinMessage(e);
                        return;

                    case "onLeave":
                        ProcessOnLeaveMessage(e);
                        return;

                    case "onUpdate":
                        ProcessOnUpdateMessage(e);
                        return;

                    case "onHeartbeat":
                        ProcessOnHeartbeatMessage(e);
                        return;

                    default:
                        ServerEventCallback cb;
                        if (Handlers.TryGetValue(e.Target, out cb))
                        {
                            cb(this, e);
                        }
                        break;
                    }
                }

                ServerEventCallback receiver;
                NamedReceivers.TryGetValue(e.Op, out receiver);
                receiver?.Invoke(this, e);
            }

            OnMessageReceived(e);
        }