Пришла мне как-то идея сделать мобильное приложение на базе Telegram. Полез в npm и сразу нашёл react-native-telegram, но это оказалась обёртка над Bot API и тут я понял, что будет весело.

У Telegram с инструментами для разработчиков в целом нормально — Bot API, MTProto, TDLib. Только под RN ничего нет и вряд ли когда-то будет, насколько я знаю уже есть популярные Telegram-клиенты на React-Native, но видимо они не стали упаковывать это в библиотеку и делиться опытом с народом.

Кто пробовал запилить свой клиент Telegram на RN, тот знает, что без хороших навыков нативной разработки особо ничего не получится. В какой-то момент я устал мучиться с patch-package и кучей натива внутри RN проекта, поэтому решил, что пора это упаковать в либу. Через два года и одиннадцать релизов она оказалась в официальной документации TDLib.

Библиотека, которую никто не сделал

Официальный ответ Telegram на «хочу собрать настоящий клиент» — это TDLib, их C++ библиотека. Она закрывает протокол, локальное хранилище, шифрование, поток апдейтов, синхронизацию чатов и сообщений, передачу файлов и большую часть клиентской обвязки, которую самому переписывать не хочется. На TDLib работают Telegram Web Z, Telegram X на Android и большинство серьёзных third-party клиентов. Это начинка настоящего Telegram-клиента — всё, что внутри, до UI.

И это массивный C++ codebase. Официальные build-инструкции начинаются с установки cmake, OpenSSL, gperf, PHP и пары других пакетов, а заканчиваются примерно через час — когда машина наконец дособирает всё. Под одну платформу. Под вторую — отдельно. Если ты пишешь нативно, то особых проблем нет, ты делаешь это один раз. Для React-Native всё иначе: нет верхнеуровневого API и удобной доки для тех, кто не так сильно шарит в нативе.

Когда я в очередной раз собирался копировать тот же самый TDLib native setup из одного проекта в другой, я наконец признал, что пишу библиотеку.

Что такое react-native-tdlib

react-native-tdlib — это один React-Native модуль, который даёт возможность использовать TDLib API на обеих платформах:

  • 53 first-class метода. Auth, чаты, сообщения, реакции, файлы, пользователи, options — всё типизировано через .d.ts. Список растёт вместе со сценариями в example-app.

  • Предсобранные TDLib-бинарники. iOS — xcframework со слайсами под device и simulator. Android — arm64-v8a, armeabi-v7a, x86_64. Никакого brew, никакого cmake, никаких ночных сборок.

  • Real-time апдейты через NativeEventEmitter. Новые сообщения, typing, read receipts, прогресс загрузок, реакции — поток апдейтов TDLib идёт одним каналом, на который подписываешься один раз.

  • Паритет платформ. iOS и Android отдают одинаковый TDLib JSON. JS пишешь один раз.

  • Telegram-like example-app. Auth-wizard, чат-лист с живыми апдейтами, чат с реакциями, reply, typing, превью фото, пагинация. Запускается из коробки.

Пример каким легким стал старт в разработке приложений использующих TDLib

npm install react-native-tdlib
cd ios && pod install
import TdLib from 'react-native-tdlib';
import { NativeEventEmitter, NativeModules } from 'react-native';

const emitter = new NativeEventEmitter(NativeModules.TdLibModule);

await TdLib.startTdLib({ api_id: 12345, api_hash: 'your_hash' });

emitter.addListener('tdlib-update', e => {
  if (e.type === 'updateNewMessage') {
    console.log('📨', JSON.parse(e.raw).message);
  }
});

await TdLib.login({ countrycode: '+1', phoneNumber: '5551234567' });
await TdLib.verifyPhoneNumber('12345');

await TdLib.loadChats(25);
const chats = JSON.parse(await TdLib.getChats(25));
await TdLib.sendMessage(chats[0].id, 'Hello from React-Native!');

Технический разбор того, как устроен сам bridge — в Part 1 и Part 2 (на английском). Эта статья не про это, а про то, зачем всё это вообще существует.

Неочевидная авторизация в TDLib

Самое неочевидное в TDLib - это login, потому что он не выглядит как «вызови метод и получи ответ». Это поток апдейтов с разными типами, через который сервер ведёт тебя по своему сценарию. Возвращающемуся пользователю TDLib может сразу прислать authorizationStateReady, новому попросить email, а кому-то пропустить SMS и сразу запросить 2FA.

В библиотеке auth управляется напрямую через updateAuthorizationState:

emitter.addListener('tdlib-update', e => {
  if (!e.type.startsWith('updateAuthorizationState')) return;
  const state = JSON.parse(e.raw).authorization_state['@type'];

  switch (state) {
    case 'authorizationStateWaitPhoneNumber': /* показать ввод телефона */ break;
    case 'authorizationStateWaitCode':        /* показать ввод SMS-кода */ break;
    case 'authorizationStateWaitPassword':    /* показать ввод 2FA */ break;
    case 'authorizationStateReady':           /* ✅ залогинен */ break;
  }
});

UI пишется не как система с фиксированными шагами, а как реакция на текущее состояние. Это первый и самый частый момент, на котором спотыкаются люди, которые впервые работают с TDLib.

Почему не просто голый td_json_client

В начале я думал вообще не делать высокоуровневую обёртку. У TDLib уже есть JSON-интерфейс: отправляешь запрос через td_json_client_send, потом забираешь ответы и апдейты через td_json_client_receive. На бумаге звучит нормально и в C++ это тоже нормально, но в React-Native это очень не оптимизировано и больно.

Проблема в том, что receive нужно постоянно крутить в цикле. Если делать это из JS, каждый проход — это поход через bridge, JSON туда-обратно, парсинг, таймауты, ожидание следующего апдейта. Пока у тебя простой auth flow, это ещё терпимо. Но как только открываешь чат, грузишь историю, скроллишь, получаешь новые сообщения, статусы, прогресс файлов — апдейты начинают лететь плотным потоком, и JS-loop становится не тем местом, где хочется это обрабатывать.

В какой-то момент стало очевидно, что эта логика должна жить в нативном коде. Нативная часть постоянно слушает TDLib, а в JS уже отправляет готовые апдейты через NativeEventEmitter.

При этом низкоуровневый путь я оставил и если нужный TDLib-метод ещё не обёрнут, можно вызвать напрямую, но использовать td_json_client_send/receive как основной API в React-Native явно плохая идея.

Roadmap

Что хочу добавить в ближайших релизах:

  • New Architecture. Поддержка TurboModules / New Architecture один из главных следующих шагов.

  • Голосовые и видео-сообщения. Сейчас они достижимы через generic file API, но запись и playback с правильным UX заслуживают отдельной обёртки.

  • Секретные чаты. Тут тоже нужен отдельный набор методов.

Если у тебя есть фича, ради которой ты пришёл, и её нет в этом списке, то заведи issue, не стесняйся.

Что дальше

react-native-tdlib уже закрывает базовый путь к Telegram-клиенту на React-Native. Дальше я хочу развивать библиотеку не просто как набор обёрток над TDLib, а как нормальный toolkit для React-Native разработчиков. TDLib остаётся мощным C++ движком под капотом, но снаружи работа с ним должна ощущаться как обычная npm-библиотека: установил, запустил, получил апдейты, отправил сообщение, пошёл дальше.

Проект открыт на GitHub. Если react-native-tdlib сэкономил тебе время или просто кажется полезным для React-Native экосистемы, поставь звезду — это лучший сигнал, что библиотеку стоит развивать дальше.