f7cloud_client/docs/APPSTORE-SERVER-REQUIREMENTS.md
root 8b6a0139db f7cloud_client
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-17 22:59:26 +00:00

8.1 KiB
Raw Permalink Blame History

Требования к серверу App Store для совместимости с F7cloud

Чтобы клиент F7cloud корректно работал с вашим appstore, сервер должен соблюдать следующие условия.

Изменения в клиенте (для совместимости с кастомным appstore) зарегистрированы в CHANGELOG-APPSTORE.md.


1. Формат тела ответа GET /api/v1/apps.json

Клиент делает:
responseJson['data'] = json_decode($response->getBody(), true)
То есть в data попадает весь распарсенный JSON-ответ.

Сейчас у вас:
Тело ответа: {"data": [{"id": "spreed", "releases": [...], ...}]}
После парсинга в data попадает объект { data: [ ... ] }. В коде клиента по этому объекту идёт цикл foreach ($response['data'] as $app), и в первой итерации $app оказывается массивом приложений, а не одним приложением. У массива нет ключа releases → ошибка.

Что нужно на appstore:
Тело ответа должно быть массивом приложений в корне, без обёртки {"data": ...}.

Неправильно (как сейчас):

{"data":[{"id":"spreed","releases":[...],...}]}

Правильно:

[{"id":"spreed","releases":[...],...}]

То есть ответ GET /api/v1/apps.json должен отдавать JSON-массив приложений, а не объект с полем data.


2. Ответ 200 и автоматическое заполнение кэша

Кэш на стороне F7cloud заполняется сам: при ответе 200 OK с телом клиент сохраняет полученный JSON в свой кэш. Вручную заполнять кэш на клиенте не нужно — достаточно, чтобы appstore отдавал 200 с полным массивом приложений.

Ответ 304 Not Modified

При ответе 304 Not Modified клиент не получает новое тело, а подставляет в data своё сохранённое содержимое кэша. Если кэша ещё не было (первый запрос или кэш пустой), подставляется пустая строка → json_decode('')null → пустой список приложений и ошибка «приложение не найдено».

Что нужно на appstore:
Для GET /api/v1/apps.json не отдавать 304, когда у клиента ещё нет валидного кэша. Проще всего: по умолчанию всегда отдавать 200 с полным телом (полным массивом приложений). Тогда клиент при первом же запросе получит данные и сам заполнит свой кэш.
Если решите поддерживать 304, отдавайте его только когда запрос содержит заголовок If-None-Match с актуальным ETag и контент не менялся.


3. Структура каждого приложения в массиве

У каждого элемента массива приложений должно быть поле releases — массив (хотя бы пустой []). Клиент делает foreach ($app['releases'] as $release) без проверки; при отсутствии или null возникает ошибка.

Обязательно у каждого приложения:

  • id — идентификатор приложения
  • releases — массив релизов (минимум [])
  • в каждом элементе releases — поля в формате F7cloud (version, download, signature, certificate и т.д.)

4. Заголовки ответа

  • Content-Type: application/json
  • При необходимости кэширования по ETag — заголовок ETag в ответе 200. Тогда клиент сможет при следующем запросе передать If-None-Match и при неизменном контенте получить 304 (с учётом п. 2 — только когда у клиента уже есть валидный кэш).

5. Сертификат и подпись: символы \r\n (перенос строки)

В ответе у вас в полях certificate и signature встречаются последовательности \r\n (CRLF — перевод строки в стиле Windows). В JSON они приходят как экранированные \r\n и после json_decode() превращаются в реальные символы CR и LF в строке.

Как ведёт себя F7cloud (без изменений в коде):

  • Сертификат — передаётся как есть в openssl_get_publickey(), openssl_x509_parse() и в phpseclib loadX509(). Формат PEM допускает и \n (LF), и \r\n (CRLF) между строками. OpenSSL такие сертификаты обрабатывает нормально, проблемой это не является.

  • Подпись — строка передаётся в base64_decode($app['releases'][0]['signature']). В PHP в режиме по умолчанию base64_decode() игнорирует пробельные символы (в т.ч. \r, \n, пробелы) внутри base64-строки (по RFC 2045). То есть подпись с переносами строк обрабатывается корректно.

Итог: F7cloud такие значения обрабатывает, менять что-то на клиенте из-за \r\n не нужно.

Рекомендация для appstore (по желанию): для единообразия и совместимости с разными клиентами можно:

  • в сертификате (PEM) использовать только \n (LF), без \r;
  • в подписи (base64) хранить/отдавать одну строку без переносов (как одну длинную base64-строку).

Это не обязательно для F7cloud, но упрощает отладку и совместимость с другими системами.


Краткий чек-лист для сервера appstore

  1. Тело GET /api/v1/apps.json — JSON-массив приложений в корне: [{...}, {...}], а не {"data": [...]}.
  2. По умолчанию отдавать 200 с полным массивом приложений — кэш на стороне F7cloud заполнится сам. 304 отдавать только при наличии у клиента валидного кэша (If-None-Match + неизменный контент).
  3. У каждого приложения в массиве есть поле releases (массив, не null и не отсутствует).
  4. Ответ с Content-Type: application/json.
  5. \r\n в сертификате и подписи — F7cloud обрабатывает (OpenSSL принимает CRLF в PEM, base64_decode игнорирует пробелы). Менять не обязательно; при желании можно отдавать сертификат с \n и подпись без переносов.

После этих изменений на стороне appstore клиент F7cloud будет корректно получать и обрабатывать список приложений без изменений в коде клиента.