https://habrahabr.ru/company/Voximplant/blog/346188/- Разработка веб-сайтов
- Программирование
- JavaScript
- Блог компании Voximplant
В конце лета мы
добавили в наше облако Voximplant поддержку месседжинга. Теперь с помощью него и россыпи SDK под разные платформы можно делать собственные мобильные или веб-мессенджеры: голосовые звонки в любых комбинациях между телефонными сетями и SDK — есть, видеозвонки между SDK — есть, месседжинг — есть. А еще у текстовых сообщений есть ключевое отличие от голосовых и видеозвонков: их контент должен оставаться. Voximplant может записать голосовой и видеозвонок на стороне облака и отдать URL с получившимся файлом, но это «медленная» история для CRM, систем управления заказами и колл-центров. А сообщения — это быстрая история. Пользователь очень огорчается, когда клик по «старому» чату в Skype вызывает зависание мобильного или веб-приложения, которое пытается выкачать хоть сколько-нибудь истории с нагруженных серверов по неустойчивому 3G. В наших SDK мы предусмотрели несколько механизмов для максимально быстрой работы с историей сообщений, о которых под катом.
В чем, собственно, проблема?
Новый пользователь мессенджера начинает с единственным объектом
Messenger, который дает доступ к API и позволяет получать эвенты. Общение между пользователями начинается, когда один из них создает объект
Conversation («беседа» или «чат» на двоих и более) и они начинают обмениваться сообщениями с помощью метода этого объекта
sendMessage. О происходящих событиях клиенты узнают с помощью эвентов. Например, если пользователь «А» хочет отправить пользователю «Б» сообщение в первый раз, то он создает conversation на двоих, после чего им обоим приходит эвент
CreateConversation, по которому пользователь «Б» узнает, что с ним хотят общаться. Также эвенты сигнализируют о новых сообщениях, присоединяющихся к conversations и покидающим их пользователям, смене админского статуса или о том, что пользователь печатает текст.
Вся эта идиллия длится ровно до того момента, пока один из пользователей не закрывает вкладку с чатом. И не открывает ее снова через день. Или через месяц. Или через год. Что нужно сделать разработчику, чтобы пользователь увидел все то новое, что произошло за время его отсутствия? И желательно так, чтобы не подвесить браузер.
Сериализация и нумерация — два кита истории сообщений
Главная деталь механизма — это последовательная нумерация всех сообщений в conversation. В эвенте
SendMessage есть поле
seq, которое содержит уникальный идентификатор сообщения. Идентификатор уникален в рамках conversation и постоянно увеличивается. Соответственно, если мы закрыли страницу браузера, открыли ее через год и хотим узнать, какие новые сообщения за это время пришли, все что нужно сделать – это хранить где-нибудь sequence id последнего полученного сообщения, а после открытия страницы запросить у облака недостающие сообщения. Или, например, последние несколько десятков, и подгружать остальные, только если пользователь решил посмотреть лог.
Вспомогательная деталь — это сериализация. SDK высокоуровневый и работает с объектами. Например, если мы хотим получить новые сообщения для conversation, то вначале нужно получить объект для этого conversation с помощью
getConversation, а затем — сообщения с помощью метода этого объекта,
retransmitEvents
Но если мы только загрузили страницу, то откуда у нас объекты? У нас кучка id'шек, предусмотрительно сохраненных в localStorage. А объекты придется создавать, и каждое такое создание объекта — это запрос к облаку для получения нужной информации.
Решение — встроенный механизм сериализации объектов с помощью методов
toCache и
create...FromCache, которые создают JSON-представление внутренностей объекта и могут восстановить объект из такого JSON без обращения к серверу. А JSON можно хранить в localStorage, мгновенно восстанавливая при загрузке страницы сотню каналов и миллион сообщений.
Миллион сообщений — а JavaScript или localStorage не лопнут?
С веб-страницами, в отличии от desktop и мобильных приложений, все сложно. Когда пользователь командует «закрыть вкладку» или «закрыть браузер», срабатывает эвент «beforeunload», на который можно подписаться. Сообщения «у вас есть несохраненные данные» в google docs — это строка, которую разработчик вернул из обработчика. Раньше в нем можно было делать даже alert'ы, но скам-страницы «ваш браузер заблокирован, дайте денег» мягко намекнули разработчикам браузеров, что многое позволять в обработчике «beforeunload» не стоит.
Тем не менее, современные браузеры дают нашему коду несколько секунд, прежде чем покажут такое сообщение и пользователь начнет беспокоиться:
А за несколько секунд вполне можно сериализовать в localstorage несколько сотен conversations с миллионом сообщений. Но тут важно помнить, что по умолчанию localStorage ограничен 5-10 мегабайтами, и даже меньше для мобильных браузеров или если пользователь копался в настройках.
Лучшие практики, чтобы ничего не лопнуло
Если вы делаете новый «Skype for Web» и планируете действительно большое количество сообщений у ваших пользователей, то для хранения сериализованных объектов лучше использовать indexedDB, которое сейчас поддерживают все популярные браузеры. Квоты там по умолчанию намного больше и можно явно попросить у пользователя еще с помощью «Quota Management API» и специфичных браузерных штук.
Второй момент — если в каком-то conversation с последнего посещения накопилось много сообщений, то будет разумно запросить у сервера последние несколько десятков, а остальные подгрузить, только если пользователь поскроллил лог. Получается разновидность «обратного бесконечного скролла» — новые элементы будут возникать не снизу, как при скролле страницы фейсбука, а сверху.
В этом году мы планируем существенно расширить наш messaging, добавив управление через HTTP и вебхуки. Это позволит разработчикам делать интеграцию с другими мессенджерами, программное управление сообщениями вроде «чата с операторами» и другие интересные штуки.