Привет, Хабр! Меня зовут Максим Толстиков и я iOS-разработчик в Учи.ру. Для отправки пуш-уведомлений наша команда использует Firebase Cloud Messaging. Недавно у нас появилась задача — написать свой шаблон запроса на отправку тестового пуша, который будет эмулировать работу бэкенда. При этом нам важно было оставить  возможность кастомизировать payload, чтобы независимо от инфраструктуры компании разрабатывать клиентскую часть пушей. В ходе решения задачи выяснилось, что Cloud Messaging не так уж и прост — пришлось немало потрудиться, чтобы в нем разобраться. Если вы собираетесь разрабатывать подобные запросы, наша статья поможет вам сэкономить время и силы.

Материал рассчитан на тех, кто уже имеет некоторые навыки в программировании и в целом знаком с iOS, командной строкой, сталкивался с сервисами Firebase и понимает, как устроены пуш-уведомления.

Отправка пушей из Firebase

Для отправки пуша можно использовать:

Notifications composer прост. Но он не такой гибкий, как другие, поэтому он нам не подошел. Admin SDK требует дополнительных усилий по его развертыванию, и мы не были готовы с ним работать. XMPP Protocol считается устаревшим: в сети пишут, что в работе с ним возникают сложности, поэтому мы и от него отказались. 

Остаются Cloud Messaging API (Legacy) и Firebase Cloud Messaging API (V1), так как они позволяют довольно просто описать и отправить запрос. Их мы и решили рассмотреть.

FCM Token

Независимо от того, какой из API мы используем, нам понадобится FCM Token. Это строка, которая возвращается к нам после успешной регистрации устройства в Firebase. Токен служит адресом устройства, на который будем отправлять пуш. Подробнее о токене и о том, где его взять, можно почитать в Set up a Firebase Cloud Messaging client app on в разделе документации.

Теперь посмотрим, как работает каждый из API и как выбрать подходящий.

API (Legacy)

Если через пуш вы хотите передать данные в виде «ключ/значение», то лучше воспользоваться API (Legacy) — с ним легче работать.

'{
  'aps': {
    'alert': {
      'title': 'Title text',
      'body': 'Body text'
 },
    'sound': 'default',
    'badge': 1,
    'mutable-content': 1
  },
  'custom_key': 'custom_value'
}'

Потребуется просто подставить Server key в авторизационный хедер запроса. 

--header 'Authorization: key=Server key'

Найти Server key можно в настройках вашего проекта в Firebase. Для этого:

  1. Открываем настройки.

  2. Переходим во вкладку Cloud Messaging.

  3. В поле Server key копируем токен, состоящий из рандомного набора символов, как отмечено на скриншотах ниже.

Теперь, когда мы получили два ключа — FCM Token и Server key, мы можем собрать свой запрос в любой программе для тестирования http-запросов. Например, в Postman (графическая утилита). Но для лаконичности, чтобы наглядно описать все атрибуты запроса в одном месте, мы воспользуемся curl (консольная утилита).  

Используя Cloud Messaging API (Legacy), мы можем написать запрос так:

curl --location 
--request POST 'https://fcm.googleapis.com/fcm/send' \
--header 'Authorization: key=Server key' \
--header 'Content-Type: application/json' \
--data-raw '{
    'to': 'fcmToken',
    'notification': {
        'mutable_content': true,
	  'sound' 'default',
        'title': 'Title',
	  'body': 'Body',
    },
}'

Осталось вставить запрос в терминал и отправить его. Если все настроено правильно, на устройстве появится пуш-уведомление. Либо, если мы допустили опечатку, получим в ответе ошибку. И скорее всего, на первый запрос вам вернется именно ошибка. В описании ошибки будет полезная информация, которая поможет вам найти причину неудачи.

API (V1)

Для случая, когда в теле пуша вам необходимо передать не просто список параметров в виде «ключ/значение», а структуру с несколькими уровнями вложенности, можно использовать современное API (V1). Это немного сложнее, но только в части получения ключа авторизации (запрос V1 использует OAuth2 Access Token). В целом, разницу между этими подходами можно посмотреть на примерах миграции from legacy to v1, чтобы наглядно увидеть возможности современного API.

Авторизационный токен для такого запроса, согласно разделу Authorize send requests, можно получить вручную или с помощью Admin SDK. Но мы сделаем проще и воспользуемся сервисом OAuth 2.0 Playground, который сгенерирует нам кратковременный токен для нашего аккаунта Firebase.

Для получения токена нужно перейти в сервис OAuth 2.0 Playground и проделать следующие шаги:

Шаг 1

В разделе Firebase Cloud Messaging API v1 нужно отметить пункт https://www.googleapis.com/auth/firebase.messaging и нажать синюю кнопку Authorize APIs.

После этого сервис запросит разрешение на доступ к аккаунту Firebase. Нужно разрешить доступ, чтобы сервис смог сгенерировать временный Access Token.

Шаг 2

Генерируем токен. Для этого нужно нажать синюю кнопку Exchange authorization code for tokens. В результате в ответе консоли мы увидим токен, как отмечено на скриншоте. Либо там будет ошибка, которая опишет, что пошло не так.

Теперь, когда мы получили авторизационный токен, нужно составить правильный URL для запроса. Он содержит Project ID, который мы берем в настройках проекта:

  1. Открываем настройки. 

  2. Во вкладке General в строке Project ID копируем значение и вставляем его в URL, как показано на скриншотах.

https://fcm.googleapis.com/v1/Project ID/messages:send

Когда мы подготовили OAuth Token и URL, можно воспользоваться FCM REST API и составить требуемый json. Например, если в теле пуша вам потребуется передать объект nested_struct, то вы можете написать и отправить такой запрос:

curl -X POST -H 'Authorization: Bearer oauth_access_token' 
  -H 'Content-Type: application/json' 
  -d '{ 
      'message': { 
          'token':'fcmToken',
          'apns': {
              'headers': {
                  'apns-priority': '10',
                  'apns-id': 'uuid',
                  'apns-push-type': 'alert',
               },
               'payload': {
                   'aps': {
                      'mutable-content': 1,
                       'sound' 'default',
                     'alert': {
                           'title': 'Title',
                           'body': Body',
                       },
                       'nested_struct': {
                           'type': 'notification_type',
                           'uchi_unique_key': 'uuid',
                           'another': 'another',
                        },
                    },
                },
            },
        },
   }' https://fcm.googleapis.com/v1/projects/project-id/messages:send HTTP/1.1

В ответе на запрос придут данные, которые можно будет декодировать в такую структуру:

struct PushEntity: Codable {
  let googleSenderId: String
  let gcmMessageId: String
  let google: String
  let aps: Aps

  enum CodingKeys: String, CodingKey {
    case googleSenderId = "google.c.sender.id"
    case gcmMessageId = "gcm.message_id"
    case google = "google.c.a.e"
    case aps
  }
}

extension PushEntity {
  struct Aps: Codable {
    let alert: Alert
    let nestedStruct: [String: String?]


    enum CodingKeys: String, CodingKey {
        case alert
        case nestedStruct = "nested_struct"
    }
  }
}

extension PushEntity.Aps {
  struct Alert: Codable {
    let body: String
    let title: String
  }
}

Если не получилось декодировать, то причину можно будет понять из описания ошибки, которая появится в консоли.

Зачем нужно оборачивать payload в отдельный объект? Мы, например, таким образом инкапсулируем логику обработки данных из пуша для каждого отдельного приложения. Тогда логика обработки пушей у нас остается общей для всех приложений и никогда не меняется.

Заключение

Итак, вы узнали, какие возможности открываются при разработке отправки тестовых пушей для iOS и как использовать различные виды API для отправки подобных запросов.

Теперь вам будет намного легче найти подходящий для вас вариант, чтобы написать запросы для экспериментов с пуш-уведомлениями.

Успешной вам разработки!


Хочешь развивать школьный EdTech вместе с нами — присоединяйся к команде Учи.ру!

Комментарии (2)


  1. Robastik
    00.00.0000 00:00

    Product ID = project-id

    nestedStruct = "nested_truct"

    И текст такой же небрежный и невнятный.


    1. M2015 Автор
      00.00.0000 00:00

      Спасибо, поправил.