Шестое чувство Facebook
Расширение для Chrome показывает, когда кто-то набирает текст
Некоторые люди слишком много времени проводят в социальных сетях. Настолько много, что у них уже возникает зависимость. Один из таких — программист Александр Кирзенберг (Alexandre Kirszenberg), который к тому же любит копаться во внутренностях Facebook — в коде JavaScript, отвечающем за пользовательский интерфейс и коммуникации.
«Пару месяцев назад я задумался о маленьком статусном индикаторе, который показывает, когда один из ваших друзей набирает вам текст, — пишет Александр. — Такое маленькое расширение UI выдаёт много информации о собеседнике. Если индикатор несколько раз загорается и тухнет, это говорит о нерешительности. Если он загорелся надолго, кто-то пишет вам большое эссе. И нет ничего хуже того мучительного чувства, когда индикатор тухнет и больше не загорается».
Первым делом Александр решил найти, присылает ли Facebook уведомления о том, что кто-то набирает сообщения в Facebook Messenger, даже если у вас не запущен этот Facebook Messenger. Оказалось, что присылает.
Всем френдам рассылается событие typ
длинного опроса /pull
. Небольшое исследование показало, что событие действительно рассылается для каждого чата, даже если получатель не открывал его и никогда не получал.
Так появилось расширение Facebook Sixth Sense для браузера Chrome. Оно показывает прямо в браузере, когда кто-то набирает сообщение в Facebook.
Для создания этого расширения программист использовал недокументированные Facebook API. Пришлось разбираться с кодом JavaScript, который Facebook безжалостно минифицирует, превращая в мешанину символов.
Сначала он выяснил, какие модули Facebook импортирует через программный интерфейс системы организации модулей Asynchronous Module Definition для асинхронной загрузки (у Facebook собственная реализация AMD). Модули и зависимости определяются стандартной функцией __d(name, dependencies, factory)
. Есть также require
и requireLazy
для импорта модулей.
Проще всего посмотреть, как это работает, в консоли браузера (хотя Facebook строго запрещает это делать). Похоже, там работают серьёзные ребята, с ними лучше не шутить.
Но мы всё-таки осмелимся.
Как видим, Facebook всегда загружает последнюю версию React — отличной библиотеки компонентов пользовательских интерфейсов. Facebook довольно интенсивно использует React по всему сайту. В коде Facebook более 15 000 компонентов React (по состоянию на октябрь 2015 года).
В исходном коде можно поискать __d(
и посмотреть список модулей, доступных для импорта. Для главной страницы там всего-то 3000 модулей.
В чате Facebook Messenger, разумеется, тоже используются компоненты React. Нам нужно перехватить нотификации о наборе текста. Для более детального изучения кода Александр Кирзенберг рекомендует использовать инструмент React Developer Tools.
После установки этого расширения в инструментах разработчика Chrome появляется новая вкладка React. На ней выделяем чат.
Здесь среди различных компонентов 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 можно даже отслеживать режим сна своих френдов (исходный код).