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

97 lines
8.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Требования к серверу App Store для совместимости с F7cloud
Чтобы клиент F7cloud корректно работал с вашим appstore, сервер должен соблюдать следующие условия.
**Изменения в клиенте** (для совместимости с кастомным appstore) зарегистрированы в [CHANGELOG-APPSTORE.md](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": ...}`.
**Неправильно (как сейчас):**
```json
{"data":[{"id":"spreed","releases":[...],...}]}
```
**Правильно:**
```json
[{"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 будет корректно получать и обрабатывать список приложений без изменений в коде клиента.