Шестое чувство Facebook

Расширение для Chrome показывает, когда кто-то набирает текст


64e325dfbed6484bbed294822dd81311.jpg

Некоторые люди слишком много времени проводят в социальных сетях. Настолько много, что у них уже возникает зависимость. Один из таких — программист Александр Кирзенберг (Alexandre Kirszenberg), который к тому же любит копаться во внутренностях Facebook — в коде JavaScript, отвечающем за пользовательский интерфейс и коммуникации.

«Пару месяцев назад я задумался о маленьком статусном индикаторе, который показывает, когда один из ваших друзей набирает вам текст, — пишет Александр. — Такое маленькое расширение UI выдаёт много информации о собеседнике. Если индикатор несколько раз загорается и тухнет, это говорит о нерешительности. Если он загорелся надолго, кто-то пишет вам большое эссе. И нет ничего хуже того мучительного чувства, когда индикатор тухнет и больше не загорается».
Первым делом Александр решил найти, присылает ли Facebook уведомления о том, что кто-то набирает сообщения в Facebook Messenger, даже если у вас не запущен этот Facebook Messenger. Оказалось, что присылает.

Всем френдам рассылается событие typ длинного опроса /pull. Небольшое исследование показало, что событие действительно рассылается для каждого чата, даже если получатель не открывал его и никогда не получал.

Так появилось расширение Facebook Sixth Sense для браузера Chrome. Оно показывает прямо в браузере, когда кто-то набирает сообщение в Facebook.

8dd50992aff74129af43255a3c06b566.jpg

Для создания этого расширения программист использовал недокументированные Facebook API. Пришлось разбираться с кодом JavaScript, который Facebook безжалостно минифицирует, превращая в мешанину символов.

Сначала он выяснил, какие модули Facebook импортирует через программный интерфейс системы организации модулей Asynchronous Module Definition для асинхронной загрузки (у Facebook собственная реализация AMD). Модули и зависимости определяются стандартной функцией __d(name, dependencies, factory). Есть также require и requireLazy для импорта модулей.

Проще всего посмотреть, как это работает, в консоли браузера (хотя Facebook строго запрещает это делать). Похоже, там работают серьёзные ребята, с ними лучше не шутить.

b8efc11b31334b9ba6033ead43dc0092.png

Но мы всё-таки осмелимся.

c3b573528a934a55b11e9575f3db4fe9.png

Как видим, Facebook всегда загружает последнюю версию React — отличной библиотеки компонентов пользовательских интерфейсов. Facebook довольно интенсивно использует React по всему сайту. В коде Facebook более 15 000 компонентов React (по состоянию на октябрь 2015 года).

В исходном коде можно поискать __d( и посмотреть список модулей, доступных для импорта. Для главной страницы там всего-то 3000 модулей.

В чате Facebook Messenger, разумеется, тоже используются компоненты React. Нам нужно перехватить нотификации о наборе текста. Для более детального изучения кода Александр Кирзенберг рекомендует использовать инструмент React Developer Tools.

ab32c920c8794ef5a714f178e343bf4b.jpg

3618a60734db4b3988b0ab733fab5139.jpg

После установки этого расширения в инструментах разработчика Chrome появляется новая вкладка React. На ней выделяем чат.

56d56427300a4c73966ff1e2086958ac.jpg

Здесь среди различных компонентов Facebook Messenger ищем индикатор набора текста, он находится между  и .

Поиск __d('ChatTyping в кодовой базе React находит два модуля: ChatTypingIndicator.react.js и ChatTypingIndicators.react.js. Это именно то, что нам нужно, пишет Кирзенберг. Он замечает, что некоторые модули подгружаются по мере необходимости, так что ChatTypingIndicators.react.js можно обнаружить только со второго раза.

Вот его код.

function() {
  var k = c('MercuryThreadInformer').getForFBID(this.props.viewer)
    , l = c('MercuryTypingReceiver').getForFBID(this.props.viewer);
  this._subscriptions = new (c('SubscriptionsHandler'))();
  this._subscriptions.addSubscriptions(
    l.addRetroactiveListener(
      'state-changed',
      this.typingStateChanged
    ),
    k.subscribe(
      'messages-received',
      this.messagesReceived
    )
  );
},


А именно, нас интересует вызов c('MercuryTypingReceiver').

В консоли можно посмотреть, как он работает.

> MercuryTypingReceiver.getForFBID
// function (i){var j=this._getInstances();if(!j[i])j[i]=new this(i);return j[i];}
> MercuryTypingReceiver.get
// function (){return this.getForFBID(c('CurrentUser').getID());}


Для проверки, как работает статусный индикатор, Александр использовал приложение Messenger на собственном смартфоне, чтобы отправить себе на ПК соответствующие события и словить их в консоли.

Более подробно изучив код, он нашёл ещё два полезных модуля MercuryThreads и ShortProfiles. Первый получает всю информацию о треде Messenger по его идентификатору, второй делает то же самое по профилю.

В общем, после всех изысканий вот как выглядит окончательный код расширения для Chrome, всего 40 строчек.

function getUserId(fbid) {
  return fbid.split(':')[1];
}

requireLazy(
  ['MercuryTypingReceiver', 'MercuryThreads', 'ShortProfiles'],
  (MercuryTypingReceiver, MercuryThreads, ShortProfiles) => {

    MercuryTypingReceiver
      .get()
      .addRetroactiveListener('state-changed', onStateChanged);

    // Called every time a user starts or stops typing in a thread
    function onStateChanged(state) {

      // State is a dictionary that maps thread ids to the list of the
      // currently typing users ids'
      const threadIds = Object.keys(state);

      // Walk through all threads in order to retrieve a list of all
      // user ids
      const userIds = threadIds.reduce(
        (res, threadId) => res.concat(state[threadId].map(getUserId)),
        []
      );

      MercuryThreads.get().getMultiThreadMeta(threadIds, threads => {
        ShortProfiles.getMulti(userIds, users => {
          // Now that we've retrieved all the information we need
          // about the threads and the users, we send it to the
          // Chrome application to process and display it to the user.
          window.postMessage({
            type: 'update',
            threads,
            users,
            state,
          }, '*');
        });
      });

    }
  }

);


Неплохой такой хак, слегка раскрывающий внутренности Facebook.

Исходный код расширения опубликован на Github.

Кстати, по временным меткам из мессенджера Facebook можно даже отслеживать режим сна своих френдов (исходный код).

01ae6ce53df84b14913666b323987743.png

© Habrahabr.ru