Referencia de la API

Rutas HTTP

PlainApp expone un pequeño conjunto de endpoints HTTP junto a la API GraphQL en POST /graphql. Estos endpoints gestionan el servicio de archivos, subidas multipart, streaming zip, casting DLNA, eventos WebSocket y control del sistema. Todos los endpoints de archivos/subida/zip requieren la cabecera c-id (y normalmente un parámetro de consulta id cifrado).

Definiciones de tipos

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

Operaciones

http

POST /graphql

Endpoint GraphQL principal. Envía aquí todas las consultas y mutaciones GraphQL con cabeceras c-id + Authorization. El endpoint /peer_graphql es el equivalente peer-to-peer usado entre dispositivos emparejados.

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

Sirve un archivo, un URI content:// o un icono de paquete. El parámetro de consulta id es una cadena cifrada devuelta por files()/fileIds(). Admite generación de miniaturas (?w=&h=&cc=), rangos de bytes (?offset=&length=) para transportes de bajo caudal, conversión HEIF→PNG, transcodificación 3gp→MP4 y modo descarga (dl=1) con una cabecera 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

Proxy de una URL HTTP peer. El parámetro de consulta id descifra a una URL http(s) completa en un dispositivo peer emparejado; el servidor transmite la respuesta upstream. Usado para descargas de archivos peer-to-peer vía Wi-Fi Aware.

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

POST /upload

Subida de un único archivo vía multipart/form-data. La parte "info" (JSON cifrado con ChaCha20) debe preceder a la parte "file". Cuando isAppFile=true los bytes se importan al AppFileStore de direccionamiento por contenido (deduplicado por hash); en caso contrario el archivo se escribe en info.dir/<fileName>. Devuelve el nombre final del archivo (puede tener un sufijo "(1)" para evitar sobrescribir).

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

Subida de un chunk de una subida chunked reanudable. Cada chunk se escribe en disco en upload_tmp/{fileId}/chunk_{index}. Devuelve "<index>:<savedSize>". Tras la llegada de todos los chunks, llama a la mutación GraphQL mergeChunks para ensamblar el archivo final.

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

Transmite un único directorio como archivo zip. El parámetro de consulta id descifra a una ruta de directorio. Solo se ejecuta una operación zip a la vez en el dispositivo — las peticiones concurrentes reciben HTTP 429.

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

GET /zip/files

Transmite múltiples archivos (o resultados de búsqueda multimedia) como un único zip. El id descifra a { type, query, id, name }. Para el tipo FILE la lista de archivos se almacena en TempHelper bajo request.id por la consulta GraphQL files(); para tipos multimedia el servidor ejecuta searchZipItems(type, query, id).

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

GET /media/{id}

Endpoint multimedia DLNA. Sirve una ruta multimedia previamente registrada (registrada vía UrlHelper.getMediaHttpUrl) a un TV / renderer DLNA. Las fuentes URL se proxyan, los URIs content:// se transmiten, las imágenes se sirven tal cual y todos los demás archivos se sirven con cabeceras específicas DLNA + soporte HTTP 206 range para que los renderers acepten el flujo.

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

NOTIFY /callback/cast

Callback de renderer DLNA. Recibe el XML NOTIFY del evento del renderer y actualiza el estado de CastPlayer. En TransportState=STOPPED (sin AVTransportURIMetaData) el player avanza automáticamente al siguiente elemento de la lista de reproducción. También parsea RelTime / TrackDuration para actualizaciones de posición.

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

Endpoint de health-check no autenticado. Devuelve el nombre del paquete de la app como texto plano. Úsalo para verificar que el servidor HTTP es accesible.

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

GET /shutdown

Apaga el servidor HTTP. Solo invocable desde localhost — las peticiones remotas reciben HTTP 403. Cierra todas las sesiones WebSocket, vacía el conjunto de clientes en línea y dispone el servidor HTTP.

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

POST /init

Inicializa una sesión de cliente. Requiere la cabecera c-id. Si hay un cuerpo cifrado válido presente (descifrado con el token en caché) la sesión se considera autenticada y el servidor responde 200. En caso contrario: si no hay contraseña establecida, el servidor responde con una contraseña recién restablecida (200); si hay contraseña establecida, responde 204 No Content y el cliente debe autenticarse vía el flujo de login 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 /

Endpoint WebSocket principal. Usa ?cid=<client-id> para registrar una sesión; el primer frame binario debe ser un timestamp cifrado (o, con ?auth=1, una AuthRequest cifrada que contiene la contraseña). Tras la autenticación el servidor pusha frames de eventos cifrados (notificaciones, actualizaciones de chat, etc.) por este socket.

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 de presencia peer. Usa ?cid=<peer-id> para mantener a un peer emparejado marcado como en línea. El primer frame binario debe ser una petición cifrada con PeerChatParser; en caso de éxito el servidor envía el texto "ok" y marca el peer en línea hasta que se cierra el socket.

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)