connection.js

// import {HubConnection} from '@aspnet/signalr-client';
import { HubConnectionBuilder, LogLevel } from '@aspnet/signalr';
import Manager from "./manager";
import Visitor from "./visitor";
import Message from "./message";
import Chatroom from "./chatroom";
import ChatroomMember from "./chatroomMember";
import lzString from "lz-string";
import axios from "axios";

// const API_URL = process.env.API_URL + ':' + process.env.API_PORT + '/' + process.env.API_PATH;
const API_URL = 'https://beta.chatix.io:5022/v1';

/**
 * @class Connection implements Chatix protocol implementation.
 * This class is private and is used inside ChatixCore.
 * @property {Function} onCOnnected
 * @property {Function} onDisconnected
 * @property {Function} onApplyErrorMessage
 * @property {Function} onApplyDialogMessage
 */
class Connection {

	/**
	 * Connection constructor
    */
   constructor() {
      
      this.onConnected = function(){};
      this.onDisconnected = function(){};
      this.onApplyErrorMessage = function(){};
      /**
       * @todo ждем исправления на бэкенде
       */
      //this.onChatMessageError = function(){};
      this.onInvalidVisitorIdConnected = function(){};
      this.onApplyDialogMessage = function(){};
      this.onConnectManagerToConversation = function(){};
      this.onDisconnectManagerFromConversation = function(){};
      this.onScreencastRequested = function(){};
      this.onConnectManagerToScreencast = function(){};
      this.onDisconnectManagerFromScreencast = function(){};
      this.onScreenDataRequested = function(){};
      this.onManagerSentScreenEvent = function(){};
      this.onConnectManagerToChatroom = function(){};
      this.onDisonnectManagerFromChatroom = function(){};
      this.onConnectVisitorToChatroom = function(){};
      this.onDisconnectVisitorFromChatroom = function(){};
      this.onCreateChatroom = function(){};
      this.onUpdateChatroom = function(){};
      this.onDeleteChatroom = function(){};
      this.onRestoreChatroom = function(){};
      this.onUpdateConversationMessage = function(){};
      this.onDeleteConversationMessage = function(){};
   }

   /**
    * Starts connection
    * 
    * @param {string} websiteId Website ID from Chatix Dashboard 
    * @param {string} visitorId Visitor ID that will be used to connect. 
    * If it is invalid, it will be replaced with new one.
    * @returns {Promise<void>}
    */
   async start(websiteId, visitorId) {
      let hubUrl = API_URL + '/chat' + '?visitorId=' + visitorId  + '&websiteId=' + websiteId;

      this.hubConnection = new HubConnectionBuilder()
         .withUrl(hubUrl)
         .configureLogging(LogLevel.Error)
         .build();

      this.hubConnection.serverTimeoutInMilliseconds = 1000 * 1000 * 1000;

      this.hubConnection.on('ApplyErrorMessage', (message, method) => {
         this.onApplyErrorMessage(message, method);
      });

      /**
       * @todo ждем исправления метода ChatroomMessageEror
       */
      // this.hubConnection.on('ChatMessageError', (descr) => {
      //    this.onChatMessageError(descr);
      // });

      // // если уже есть ошибка связанная с неправильным visitorId
         // if (!localStorage.getItem('visitorId_invalid_error')) {
         //    localStorage.setItem('visitorId_invalid_error', true);

         
         //       .then((res) => {
         //          if (localStorage.getItem('chatix__visitorId_v0')) {
         //             localStorage.removeItem('chatix__visitorId_v0')
         //          }
         //          localStorage.setItem('chatix__visitorId_v1', res.data);


         //          hubUrl = API_URL + '/chat' + '?visitorId=' + res.data + '&websiteId=' + thiss.websiteId;
         //          thiss.hubConnection = new HubConnectionBuilder()
         //             .withUrl(hubUrl)
         //             .configureLogging(LogLevel.None)
         //             .build();

         //          thiss.hubConnection.stop();
         //          localStorage.removeItem('visitorId_invalid_error');
         //       }
         //       );
         // }
         // else {
         //    // console.log('ОШИБКА ' + message + ' В МЕТОДЕ ' + method);
         // }
      this.hubConnection.on('OnInvalidVisitorIdConnected',  () => {
         this.onInvalidVisitorIdConnected();
      });

      this.hubConnection.on("ApplyMessage",  (data) => {
         this.onApplyDialogMessage(Message.buildFromInfo(data));

         // разворачиваем виджет при входящем сообщении
         /** 
          * @todo почему Connection занимается HTML деревом?
          */
         // document.querySelectorAll('.chatixWindow')[0].style.bottom = 0 + 'px';
      });

      this.hubConnection.on("OnConnectManagerToConversation",  (manager) => {
         let m = Manager.buildFromInfo(manager);
         this.onConnectManagerToConversation(m);
      });

      this.hubConnection.on("OnDisconnectManagerFromConversation",  (manager) => {
         let m = Manager.buildFromInfo(manager);
         this.onDisconnectManagerToConversation(m);
      });

      this.hubConnection.on("OnRequestScreenCast",  () => {
         /**
          * @todo почистить этот блок
          */
         // // console.log('Запрос на просмотр экрана');
         // // если браузер edge, то отправляем менеджеру инфу что трансляция невозможна
         // if (document.documentMode || /Edge/.test(navigator.userAgent)) {
         //    // console.log('bad browser');
         //    this.hubConnection.invoke('AllowScreenCast', false).then(function () { });
         // }
         // else {
         //    this.chat.onRequestScreenCast();
         // }
         this.onScreencastRequested();
      });


      this.hubConnection.on("OnConnectManagerToScreencast", (manager) => {
         let m = Manager.buildFromInfo(manager);
         this.onConnectManagerToScreencast(m);
      });

      /**
       * @todo проверить аргументы callback
       */
      this.hubConnection.on("OnDisconnectManagerFromScreenCast",  (manager) => {
         let m = Manager.buildFromInfo(manager);
         this.onDisconnectManagerFromScreencast(m);

         // console.log('Менеджер отключился от трансляции');
         //this.chat.onDisconnectManagerFromScreenCast();

         // slideWidget();
      });

      /**
       * @todo перенести логику обработки из Connection
       */
      this.hubConnection.on("OnGetScreenData",  () => {
         // localStorage.setItem('screenBroadcast', true);
         // // console.log('Отправляем базовую информацию');

         // // создаем новый div с копией innerHTML для того, что бы в нем скрыть не транслируемые эллементы не затрагивая основную страницу
         // let correctHTML = document.getElementsByTagName('html')[0].cloneNode(true);
         // let scrolledElems = [];

         // // console.log(correctHTML);

         // document.querySelectorAll('*').forEach(function (item, i) {
         //    // находим все эллементы с не транслируемым классом
         //    if (item.classList.contains('ym-disable-keys')) {
         //       if (item.nodeName.toLowerCase() === 'input') {
         //          // IE Браузеры и Edge не корректно работают с evlutr  если в начале xPath нет html
         //          let relativePath;
         //          // Если edge браузер
         //          if (document.documentMode || /Edge/.test(navigator.userAgent)) {
         //             relativePath = getDomPath(item);
         //          }
         //          else {
         //             relativePath = getDomPath(item).split('/');
         //             relativePath = relativePath.slice(2);
         //             relativePath = '/' + relativePath.join('/');
         //          }

         //          let hiddenElem = document.evaluate(relativePath, correctHTML, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
         //          hiddenElem.setAttribute('value', 'Содержимое поля недоступно');
         //       }
         //       else {
         //          // IE Браузеры и Edge не корректно работают с evlutr  если в начале xPath нет html
         //          let relativePath;
         //          // Если edge браузер
         //          if (document.documentMode || /Edge/.test(navigator.userAgent)) {
         //             relativePath = getDomPath(item);
         //          }
         //          else {
         //             relativePath = getDomPath(item).split('/');
         //             relativePath = relativePath.slice(2);
         //             relativePath = '/' + relativePath.join('/');
         //          }

         //          let subElem = "<div style='width:" + item.offsetWidth + "px; height:" + item.offsetHeight + "px; background: url(https://223421.selcdn.ru/chatix-dev/assets/img/chatix_hidden_elem.png);'></div>";
         //          let hiddenElem = document.evaluate(relativePath, correctHTML, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
         //          // hiddenElem.remove();

         //          hiddenElem.innerHTML = subElem;
         //       }
         //    }
         //    // находим все поля с паролем что бы скрыть их от трансляции
         //    else if (item.getAttribute('type') === 'password') {
         //       // IE Браузеры и Edge не корректно работают с evlutr  если в начале xPath нет html
         //       let relativePath;
         //       // Если edge браузер
         //       if (document.documentMode || /Edge/.test(navigator.userAgent)) {
         //          relativePath = getDomPath(item);
         //       }
         //       else {
         //          relativePath = getDomPath(item).split('/');
         //          relativePath = relativePath.slice(2);
         //          relativePath = '/' + relativePath.join('/');
         //       }

         //       let hiddenElem = document.evaluate(relativePath, correctHTML, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
         //       hiddenElem.setAttribute('value', 'Содержимое поля недоступно');
         //    }

         //    // находим все элементы со скроллом отличным от 0 в момент начала трансляции
         //    if (item.scrollTop || item.scrollLeft) {
         //       let scrollElem = {
         //          target: getDomPath(item),
         //          scrollTop: item.scrollTop,
         //          scrollLeft: item.scrollLeft
         //       }
         //       scrolledElems.push(scrollElem);
         //    }
         // });

         // // удаляем все скрипты из HTML Отправляемой страницы
         // correctHTML.querySelectorAll('script').forEach(function (item, i) {
         //    item.removeAttribute("src");
         //    item.innerHTML = '';
         // });

         // // console.log(correctHTML.innerHTML);
         // // console.log(lzString.compressToUTF16(correctHTML.innerHTML));

         // // определяем ширину скролла в браузере визитора
         // function widthScroll() {
         //    var div = document.createElement('div');
         //    div.style.overflowY = 'scroll';
         //    div.style.width = '50px';
         //    div.style.height = '50px';
         //    div.style.visibility = 'hidden';
         //    document.querySelectorAll('#chatixContainer')[0].appendChild(div);
         //    var scrollWidth = div.offsetWidth - div.clientWidth;
         //    document.querySelectorAll('#chatixContainer')[0].removeChild(div);
         //    return scrollWidth;
         // }

         // let broadcastData = {};
         // broadcastData.screenWidth = document.body.clientWidth + widthScroll();
         // broadcastData.screenHeight = window.innerHeight;
         // broadcastData.domain = location.origin + '/';
         // broadcastData.scrolledElems = scrolledElems;
         // broadcastData.visitorId = this.visitorId;
         // broadcastData.htmlId = document.getElementsByTagName('html')[0].getAttribute('id');
         // broadcastData.htmlClass = document.getElementsByTagName('html')[0].getAttribute('class');

         // // console.log(lzString.decompressFromUTF16(lzString.compressToUTF16(correctHTML.innerHTML)));
         // // console.log(correctHTML);

         // this.hubConnection.invoke('PostScreenData', lzString.compressToUTF16(correctHTML.innerHTML), broadcastData)
         //    .then(function (result) {
         //       // console.log('Базовая информация отправлена');
         //    })
         //    .catch(function (err) {
         //       console.log(err);
         //    });
         this.onScreenDataRequested();
      });

      /**
       * @todo убрать логику отрисовки из Conection
       */
      // let areaRemoveTimeout;
      this.hubConnection.on("OnPostScreenEventByManager",  (data) => {
         // clearTimeout(areaRemoveTimeout);
         // if (data === 'removeSelectedArea') {
         //    let selectedBlock = document.querySelectorAll('#managerSelected')[0];
         //    if (selectedBlock) {
         //       selectedBlock.style.display = 'none';
         //    }
         // }
         // else if (data === 'endSelectedArea') {
         //    areaRemoveTimeout = setTimeout(function () {
         //       let selectedBlock = document.querySelectorAll('#managerSelected')[0];
         //       if (selectedBlock) {
         //          selectedBlock.style.display = 'none';
         //       }
         //    }, 3500);
         // }
         // // рисование области выделяемой менеджером
         // else {
         //    let selectedBlock = document.querySelectorAll('#managerSelected')[0];
         //    if (selectedBlock) {
         //       selectedBlock.style.display = 'block';
         //       selectedBlock.style.position = 'absolute';
         //       selectedBlock.style.boxShadow = '0px 0px 0px 100000px rgba(0, 0, 0, 0.6)';
         //       selectedBlock.style.zIndex = 1060;
         //       // условия для чтого что бы выделение работало не только по направления слева-направо, сверху-вниз, а в любую сторону
         //       selectedBlock.style.width = Math.abs(data.end.x - data.start.x) + 'px';
         //       selectedBlock.style.height = Math.abs(data.end.y - data.start.y) + 'px';
         //       if (data.start.y <= data.end.y) {
         //          selectedBlock.style.top = data.start.y + -document.documentElement.getBoundingClientRect().top + 'px';
         //       }
         //       else {
         //          selectedBlock.style.top = data.end.y + -document.documentElement.getBoundingClientRect().top + 'px';
         //       }

         //       if (data.start.x <= data.end.x) {
         //          selectedBlock.style.left = data.start.x + -document.documentElement.getBoundingClientRect().left + 'px';
         //       }
         //       else {
         //          selectedBlock.style.left = data.end.x + -document.documentElement.getBoundingClientRect().left + 'px';
         //       }
         //    }
         // 

         this.onManagerSentScreenEvent(data);
      });

      this.hubConnection.on('ApplyChatroomMessage',  (message, chatroomId) => {
         let m = Message.buildFromInfo(message);

         this.onApplyChatroomMessage(m, chatroomId);
      });

      this.hubConnection.on("OnConnectManagerToChatroom",  (manager, chatroomId) => {
         let m = Manager.buildFromInfo(manager);
         this.onConnectManagerToChatroom(m, chatroomId);
      });

      this.hubConnection.on("OnConnectVisitorToChatroom",  (member, chatroomId) => {
         let v = ChatroomMember.buildFromInfo(member);
         this.onConnectVisitorToChatroom(v, chatroomId);
      });

      this.hubConnection.on("OnDisconnectManagerFromChatroom",  (manager, chatroomId) => {
         let m = Manager.buildFromInfo(manager);
         this.onDisonnectManagerFromChatroom(m, chatroomId);
      });

      this.hubConnection.on("OnDisconnectVisitorFromChatroom",  (member, chatroomId) => {
         let v = ChatroomMember.buildFromInfo(member);
         this.onDisonnectVisitorFromChatroom(v, chatroomId);
      });

      this.hubConnection.on("OnCreateChatroom",  (chatroom) => {
         let c = Chatroom.buildFromInfo(chatroom);
         this.onCreateChatroom(c);
      });

      this.hubConnection.on("OnUpdateChatroom",  (chatroom) => {
         let c = Chatroom.buildFromInfo(chatroom);
         this.onUpdateChatroom(c);
      });

      this.hubConnection.on("OnDeleteChatroom",  (chatroomId) => {
         this.onDeleteChatroom(chatroomId);
      });

      this.hubConnection.on("OnRestoreChatroom",  (chatroom) => {
         let c = Chatroom.buildFromInfo(chatroom);
         this.onRestoreChatroom(c);
      });

      this.hubConnection.on("OnUpdateConversationMessage",  (message) => {
         let m = Message.buildFromInfo(message);
         this.onUpdateConversationMessage(m);
      });

      this.hubConnection.on("OnDeleteConversationMessage",  (messageId) => {
         this.onDeleteConversationMessage(messageId);
      });


      async function start() {
         try {
            this.hubConnection.start();
         } catch (err) {
            console.log(err);
            setTimeout(() => start(), 5000);
         }
      }

      this.hubConnection.onclose(async () => {
         await start();
      });

      return await start();
      /**
       * @todo убрать из коннекшена
       .then(function () {
         this.hubConnection.invoke('GetVisitorInfo', null).then(
            function (visitor) {
               let url = window.location.href;
               let title = null;
               let titleTag = document.getElementsByTagName("title")[0];
               if (titleTag) {
                  title = titleTag.innerHTML;
               }

               thiss.hubConnection.invoke('SavePageInfo', url, title);

               thiss.chat.visitor = visitor;
               thiss.chat.callbackSignalrConnected(visitor);
            }
         )
      });
       
       */
      
   }


   /**
    * Gets new visitor ID if passed was invalid.
    * @param {string} websiteId Website ID from Chatix dashboard
    * @returns {Promise<string>}
    */
   async getNewVisitorId(websiteId) {
      try {
         let response = await axios.get(API_URL + '/visitor/get-id?websiteId=' + websiteId);
         if (response.status !== 200) {
            throw new Error("Error occured while trying to get new visitor ID. Status response status code: " + response.status);
         }
         return response.data;
      } catch (e) {
         console.error(e);
         throw e;
      }
   }

   /**
    * Interrupts connection
    * 
    */
   async stop() {
      if (!this.hubConnection) {
         throw new Error("Connection has to be established");
      }
      return await this.hubConnection.stop();
   }

   /**
    * Sends current user input to managers
    *
    * @param {string} text what user is typing
    * @returns {void}
    */
   async sendVisitorTypedText(text) {
      return await this.hubConnection.invoke('VisitorType', text);
   }

   /**
    * Sends visitor details update to manager
    *
    * @param {Visitor} visitor
    * @returns {Visitor} updated visitor entity
    */
   async sendVisitorInfo(visitor) {
      let apiVisitor = await this.hubConnection.invoke('SaveVisitorInfo', visitor);
      return Visitor.buildFromInfo(apiVisitor);
   }

   /**
    * Send user choise for screencast request.
    * 
    * @param {bool} flag User choise to allow screencast. TRUE - allow, FALSE - disallow.
    */
   async allowScreenCast(flag) {
      return await this.hubConnection.invoke('AllowScreenCast', flag);
   }

   /**
    * Sends screencast interruption event to managers
    * 
    * @returns {void}
    */
   async interruptScreenCast() {
      return await this.hubConnection.invoke('InterruptScreenCast');
   }

   /**
    * Gets all website managers
    * 
    * @returns {Manager[]}
    */
   async getWebChatManagers() {
      let apiManagers = await this.hubConnection.invoke('GetWebChatManagers');
      let managers = [];
      for (let m of apiManagers) {
         managers.push(Manager.buildFromInfo(m));
      }
      return managers;
   }

   /**
    * Gets all *connected to visitor* website managers
    * 
    * @returns {Manager[]}
    */
   async getConnectedToConversationManagers() {
      let apiManagers = await this.hubConnection.invoke('GetManagers');
      let managers = [];
      for (let m of apiManagers) {
         managers.push(Manager.buildFromInfo(m));
      }
      return managers;
   }

   /**
    * Gets webchat config
    * 
    * @returns {object}
    */
   async getWebChatInfo() {
      return await this.hubConnection.invoke('GetWebChatInfo');
   }

   /**
    * Gets current visitor information from server
    *
    * @returns {Visitor}
    */
   async getVisitorInfo() {
      let apiVisitor = await this.hubConnection.invoke('GetVisitorInfo', null);
      return Visitor.buildFromInfo(apiVisitor);
   }

   /**
    * Gets manager infromation by his id
    *
    * @param {string} managerId Manager's id
    * @returns {Promise<Manager>}
    */
   async getManagerInfo(managerId) {
      let apiManager = await this.hubConnection.invoke('GetManagerInfo', managerId);
      return Manager.buildFromInfo(apiManager);
   }

   /**
    * Gets messages from dialog between visitor and managers.
    *
    * @param {string|null} lastKnownMessageId last displayed message ID. If NULL, it will return 
    * last messages in dialog
    * @param {number} count Number of messages to return. Can be between 1 and 100.
    * @returns {Promise<Message[]>}
    * @throws {Error}
    */
   async getConversationHistory(lastKnownMessageId = null, count = 30) {
      if (count < 1 || count > 100) {
         throw new Error("Count param is invalid. Valid values are between 1 and 100");
      }
      let apiMessages = await this.hubConnection.invoke('GetHistory', null, lastKnownMessageId, count);
      let messages = [];
      for (let m of apiMessages.items) {
         messages.push(Message.buildFromInfo(m));
      }
      return messages;
   }

   /**
    * Gets messages from specific Chatroom.
    *  
    * @param {string} chatroomId Chatroom ID 
    * @param {string|null} lastKnownMessageId last displayed message ID. If NULL, it will return 
    * last messages in dialog
    * @param {number} count Number of messages to return. Can be between 1 and 100.
    * @returns {Message[]}
    */
   async getChatroomHistory(chatroomId, lastKnownMessageId = null, count = 30) {
      if (count < 1 || count > 100) {
         throw new Error("Count param is invalid. Valid values are between 1 and 100");
      }
      let apiMessages = await this.hubConnection.invoke('GetChatroomHistory', chatroomId, lastKnownMessageId, count);
      let messages = [];
      for (let m of apiMessages) {
         messages.push(Message.buildFromInfo(m));
      }
      return messages;
   }

   /**
    * Returns current connection state as **INT**
    * @todo перепроверить. отдает 2, хотя уже подключен.
    * @return {number}
    */
   getConnectionState() {
      switch (this.hubConnection.connectionState) {
         case 2:
            return Connection.STATE_CONNECTING;
         case 1:
            return Connection.STATE_CONNECTED;
         default:
            return Connection.STATE_DISCONNECTED;
      }
   }

   /**
    * Returns current connection state as **STRING**
    * 
    * @return {string}
    */
   getConnectionStateDescription() {
      switch (this.hubConnection.connection.connectionState) {
         case 1:
            return "Connecting";
         case 2:
            return "Connected";
         default:
            return "Disconnected";
      }
   }

   /**
    * Sends message to conversation
    *
    * @param {Message} message
    * @return {Message} saved message instance
    */
   async sendMessage(message) {
      let apiMessage = await this.hubConnection.invoke('SaveMessage', message, null);
      return Message.buildFromInfo(apiMessage);
   }

   /**
    * Sends user navigation thought pages
    *
    * @param {string} url current page URL
    * @param {string} title current page title
    * @return {void}
    */
   async sendPage(url, title) {
      await this.hubConnection.invoke('SavePageInfo', url, title);
   }

   /**
    * Sends data to screencast
    * 
    * @returns {void}
    */
   async sendBroadcastData(innerHTML, broadcastData) {
      return await this.hubConnection.invoke('PostScreenData', lzString.compressToUTF16(innerHTML), broadcastData);
   }


   /**
    * Sends screencast event to consultants
    * 
    * @param {object} broadcastEvent 
    */
   async sendBroadcastEvent(broadcastEvent) {
      let broadcastEventData = broadcastEvent;
      if (broadcastEventData.mutations) {
         if (broadcastEventData.mutations.length > 0) {
            broadcastEventData.mutations.forEach(function (item) {
               if (item.target) {
                  item.target = lzString.compressToUTF16(item.target);
               }
            });
         }
      }

      return await this.hubConnection.invoke('PostScreenEvent', broadcastEventData, null, null);
   }

   /**
    * Sends text message to specific Chatroom
    * 
    * @param {Message} text Text to be sent to Chatroom
    * @param {string} chatroomId Chatroom ID where message has to be sent
    * @returns {Message} saved message
    */
   async sendChatroomMessage(message, chatroomId) {
      return await this.hubConnection.invoke('SaveChatroomMessage', message, chatroomId);
   }

   /**
    * Gets Chatroom from backend
    * 
    * @param {string} chatroomId Chatroom ID
    */
   async getChatroom(chatroomId) {
      return Chatroom.buildFromInfo(await this.hubConnection.invoke('GetChatroom', chatroomId));
   }

   /**
    * Connects visitor to Chatroom
    * 
    * @param {string} chatroomId Chatroom ID
    */
   async connectToChatroom(chatroomId) {
      let result = await this.hubConnection.invoke('ConnectToChatroom', chatroomId);
      if (result.is_success) {
         return true;
      } else {
         throw new Error(result.description);
      }
   }

   /**
    * Disconnects visitor from Chatroom
    * 
    * @param {string} chatroomId Chatroom ID
    */
   async disconnectFromChatroom(chatroomId) {
      let result = await this.hubConnection.invoke('DisconnectFromChatroom', chatroomId);
      if (result.is_success) {
         return true;
      } else {
         throw new Error(result.description);
      }
   }

   /**
    * Gets all avaliable chatrooms ordered by title.
    * 
    * @param {number} page Number of page to deliver
    * @param {number} itemsPerPage Number of items per page
    */
   async getAllChatrooms(page = 1, itemsPerPage = 100) {
      return await this.hubConnection.invoke('GetAllChatrooms', itemsPerPage, page, false);
   }

   /**
    * Gets all chatrooms with current visitor ordered by title.
    * 
    * @param {number} page Number of page to deliver
    * @param {number} itemsPerPage Number of items per page
    */
   async getMyChatrooms(page = 1, itemsPerPage = 100) {
      return await this.hubConnection.invoke('GetMyChatrooms', itemsPerPage, page, false);
   }

   /**
    * Gets information about another visitor. It returns object similar to Visitor, but without private
    * fields like email, fields, location, etc. If you need to receive this information, use REST API.
    * 
    * @param {string} visitorId Another visitor ID
    */
   async getVisitorByVisitor(visitorId) {
      return ChatroomMember.buildFromInfo(await this.hubConnection.invoke('GetVisitorByVisitor', visitorId));
   }
   
   /**
    * Gets visitors from specific Chatroom. If current visitor is connected to chatroom, response may
    * contain himself.
    * 
    * @param {string} chatroomId Which chatroom visitors return
    * @param {number} itemsPerPage Number of visitors to return per page
    * @param {number} page Data page number
    */
   async getChatroomMembers(chatroomId, itemsPerPage = 10, page = 1) {
      let apiMembers = await this.hubConnection.invoke('GetChatroomVisitors', chatroomId, itemsPerPage, page);
      let result = [];
      for (const member of apiMembers) {
         result.push(ChatroomMember.buildFromInfo(member));
      }
      return result;
   }


}

/**
 * @todo убрать обработку дерева из Connection
 */
// function getDomPath(el) {
//    var stack = [];
//    while (el.parentNode != null) {
//       var sibCount = 0;
//       var sibIndex = 0;
//       for (var i = 0; i < el.parentNode.childNodes.length; i++) {
//          var sib = el.parentNode.childNodes[i];
//          if (sib.nodeName == el.nodeName) {
//             if (sib === el) {
//                sibIndex = sibCount;
//             }
//             sibCount++;
//          }
//       }
//       if (el.hasAttribute) {
//          if (el.hasAttribute('id') && el.id != '') {
//             stack.unshift(el.nodeName.toLowerCase() + "[@id='" + el.id + "']");
//          } else if (sibCount > 1) {
//             // потому что eq начинается с 0, а индекс xPath с 1
//             sibIndex = sibIndex + 1;
//             stack.unshift(el.nodeName.toLowerCase() + '[' + sibIndex + ']');
//          } else {
//             stack.unshift(el.nodeName.toLowerCase());
//          }
//       }
//       el = el.parentNode;
//    }

//    let path = stack.slice(1);

//    path = '/html/' + path.join('/')

//    return path;
// }

Connection.STATE_CONNECTING = 1;
Connection.STATE_CONNECTED = 2;
Connection.STATE_DISCONNECTED = 3;

export default Connection;