Справочник API

HTTP-маршруты

PlainApp предоставляет небольшой набор HTTP-эндпоинтов вместе с GraphQL API на POST /graphql. Эти эндпоинты обрабатывают отдачу файлов, multipart-загрузки, потоковую передачу zip, DLNA-кастинг, WebSocket-события и системное управление. Все эндпоинты file/upload/zip требуют заголовок c-id (и обычно зашифрованный параметр запроса id).

Определения типов

bash
# Common headers for protected HTTP endpoints:
c-id: <client-id>
# /fs, /proxyfs, /zip/* use an encrypted "id" query parameter produced
# by the GraphQL files()/fileIds() queries. The id is opaque to the client.

# Upload endpoints additionally require an encrypted "info" multipart part:
#   info = chaCha20Encrypt(token, json({ dir, size, replace, isAppFile }))
#   file = <raw bytes>
# Chunked uploads use:
#   info = chaCha20Encrypt(token, json({ fileId, index, size }))

# Status codes follow HTTP semantics:
#   200 OK              – success
#   201 Created         – upload stored
#   204 No Content      – init pending password
#   400 Bad Request     – missing/invalid params
#   401 Unauthorized    – bad token
#   403 Forbidden       – expired/invalid encrypted id
#   404 Not Found       – file/entry missing
#   410 Gone            – server shutting down
#   429 Too Many Requests – concurrent zip limit hit

Операции

http

POST /graphql

Основной эндпоинт GraphQL. Отправляйте все GraphQL-запросы и мутации сюда с заголовками c-id + Authorization. Эндпоинт /peer_graphql — это peer-to-peer эквивалент, используемый между связанными устройствами.

bash
curl --request POST \
  --url http://192.168.1.100:8080/graphql \
  --header 'c-id: <client-id>' \
  --header 'Authorization: Bearer <api-token>' \
  --header 'Content-Type: application/json' \
  --data '{"query":"{ app { battery deviceName } }"}'
http

GET /fs

Отдаёт файл, content:// URI или иконку пакета. Параметр запроса id — зашифрованная строка, возвращаемая files()/fileIds(). Поддерживает генерацию миниатюр (?w=&h=&cc=), байтовые диапазоны (?offset=&length=) для низкоскоростных транспортов, конвертацию HEIF→PNG, транскодирование 3gp→MP4 и режим загрузки (dl=1) с заголовком Content-Disposition attachment.

bash
# Original (full size, inline)
curl http://192.168.1.100:8080/fs?id=<encrypted-id> -o file.jpg

# Thumbnail (center-cropped to 200x200)
curl 'http://192.168.1.100:8080/fs?id=<encrypted-id>&w=200&h=200' -o thumb.jpg

# Download (attachment)
curl 'http://192.168.1.100:8080/fs?id=<encrypted-id>&dl=1' -OJ

# Byte range (BLE chunked download)
curl 'http://192.168.1.100:8080/fs?id=<encrypted-id>&offset=0&length=4096' \
  -o chunk.bin
http

GET /proxyfs

Проксирует peer HTTP URL. Параметр запроса id расшифровывается в полный http(s) URL на связанном peer-устройстве; сервер передаёт ответ upstream обратно. Используется для peer-to-peer загрузки файлов по Wi-Fi Aware.

bash
curl http://192.168.1.100:8080/proxyfs?id=<encrypted-peer-url> -o peer-file.jpg
http

POST /upload

Загрузка одного файла через multipart/form-data. Часть "info" (ChaCha20-зашифрованный JSON) должна предшествовать части "file". При isAppFile=true байты импортируются в content-addressable AppFileStore (дедупликация по хэшу); в противном случае файл записывается в info.dir/<fileName>. Возвращает итоговое имя файла (может иметь суффикс "(1)" во избежание перезаписи).

bash
curl --request POST \
  --url http://192.168.1.100:8080/upload \
  --header 'c-id: <client-id>' \
  --form 'info=<encrypted-info-bytes>;type=application/octet-stream' \
  --form 'file=@/path/to/local.jpg'
http

POST /upload_chunk

Загрузка одного чанка возобновляемой, кусочной загрузки. Каждый чанк записывается на диск в upload_tmp/{fileId}/chunk_{index}. Возвращает "<index>:<savedSize>". После получения всех чанков вызовите GraphQL-мутацию mergeChunks для сборки итогового файла.

bash
curl --request POST \
  --url http://192.168.1.100:8080/upload_chunk \
  --header 'c-id: <client-id>' \
  --form 'info=<encrypted-chunk-info-bytes>;type=application/octet-stream' \
  --form 'file=@/path/to/chunk_0.bin'
http

GET /zip/dir

Потоковая передача одного каталога как zip-архива. Параметр запроса id расшифровывается в путь каталога. На устройстве одновременно выполняется только одна zip-операция — параллельные запросы получают HTTP 429.

bash
curl 'http://192.168.1.100:8080/zip/dir?id=<encrypted-dir-id>' \
  -o folder.zip
http

GET /zip/files

Потоковая передача нескольких файлов (или результатов медиапоиска) как одного zip. Параметр id расшифровывается в { type, query, id, name }. Для типа FILE список файлов хранится в TempHelper под request.id GraphQL-запросом files(); для медиатипов сервер выполняет searchZipItems(type, query, id).

bash
curl 'http://192.168.1.100:8080/zip/files?id=<encrypted-request>' \
  -o selection.zip
http

GET /media/{id}

DLNA-медиаэндпоинт. Отдаёт ранее зарегистрированный медиапуть (зарегистрированный через UrlHelper.getMediaHttpUrl) на TV / DLNA-рендерер. URL-источники проксируются, content:// URI передаются потоком, изображения отдаются как есть, а все остальные файлы отдаются с DLNA-специфичными заголовками + поддержкой HTTP 206 range, чтобы рендереры принимали поток.

bash
# Play on a DLNA renderer:
curl http://192.168.1.100:8080/media/<id>.mp4 -o video.mp4
http

NOTIFY /callback/cast

Колбэк DLNA-рендерера. Принимает NOTIFY XML события рендерера и обновляет состояние CastPlayer. На TransportState=STOPPED (без AVTransportURIMetaData) плеер автоматически переходит к следующему элементу плейлиста. Также парсит RelTime / TrackDuration для обновления позиции.

bash
# Sent by the DLNA renderer (not by the client):
NOTIFY /callback/cast HTTP/1.1
Content-Type: text/xml

<?xml ...><e:propertyset>...TransportState val="PLAYING"...</e:propertyset>
http

GET /health

Неавторизованный эндпоинт проверки работоспособности. Возвращает имя пакета приложения как plain text. Используйте для проверки доступности HTTP-сервера.

bash
curl http://192.168.1.100:8080/health
http

GET /shutdown

Останавливает HTTP-сервер. Вызывается только с localhost — удалённые запросы получают HTTP 403. Закрывает все WebSocket-сессии, очищает множество онлайн-клиентов и освобождает HTTP-сервер.

bash
# Must be run on the device (e.g. via adb shell):
curl http://localhost:8080/shutdown
http

POST /init

Инициализирует клиентскую сессию. Требует заголовок c-id. При наличии валидного зашифрованного тела (расшифрованного кэшированным токеном) сессия считается авторизованной, и сервер отвечает 200. Иначе: если пароль не установлен, сервер отвечает свежесброшенным паролем (200); если пароль установлен, отвечает 204 No Content, и клиент должен авторизоваться через поток входа WebSocket.

bash
curl --request POST \
  --url http://192.168.1.100:8080/init \
  --header 'c-id: <client-id>' \
  --data-binary '<encrypted-token-bytes>'
http

WS /

Основной эндпоинт WebSocket. Используйте ?cid=<client-id> для регистрации сессии; первый бинарный кадр должен быть зашифрованным timestamp (или, при ?auth=1, зашифрованным AuthRequest с паролем). После авторизации сервер отправляет зашифрованные кадры событий (уведомления, обновления чата и т.д.) через этот сокет.

bash
# Browser / JS example
const ws = new WebSocket('ws://192.168.1.100:8080/?cid=<client-id>')
ws.binaryType = 'arraybuffer'
ws.send(encryptWithToken(token, Date.now().toString()))

// Login variant:
const ws = new WebSocket('ws://192.168.1.100:8080/?cid=<client-id>&auth=1')
ws.send(encryptWithPassword(password, JSON.stringify({ password: hash })))
http

WS /status

WebSocket присутствия peer'ов. Используйте ?cid=<peer-id>, чтобы удерживать связанный peer отмеченным онлайн. Первый бинарный кадр должен быть зашифрованным запросом PeerChatParser; при успехе сервер отправляет текст "ok" и отмечает peer онлайн до закрытия сокета.

bash
# Peer-to-peer keepalive (typically opened automatically by the chat
# client after pairing — not usually invoked manually)
const ws = new WebSocket('ws://192.168.1.100:8080/status?cid=<peer-id>')
ws.send(peerEncryptedFrame)