Riferimento API

Route HTTP

PlainApp espone un piccolo set di endpoint HTTP insieme all'API GraphQL su POST /graphql. Questi endpoint gestiscono servizio file, upload multipart, streaming zip, casting DLNA, eventi WebSocket e controllo di sistema. Tutti gli endpoint file/upload/zip richiedono l'intestazione c-id (e di solito un parametro di query id cifrato).

Definizioni di tipo

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

Operazioni

http

POST /graphql

Endpoint GraphQL principale. Invia qui tutte le query e le mutation GraphQL con le intestazioni c-id + Authorization. L'endpoint /peer_graphql è l'equivalente peer-to-peer usato tra dispositivi abbinati.

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

Serve un file, un URI content:// o un'icona di pacchetto. Il parametro di query id è una stringa cifrata restituita da files()/fileIds(). Supporta generazione di miniature (?w=&h=&cc=), byte-range (?offset=&length=) per trasporti a bassa velocità, conversione HEIF→PNG, transcodifica 3gp→MP4 e modalità download (dl=1) con un'intestazione Content-Disposition di tipo 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 di un URL HTTP peer. Il parametro di query id decifra un URL http(s) completo su un dispositivo peer abbinato; il server restituisce in streaming la risposta upstream. Usato per i download di file peer-to-peer su Wi-Fi Aware.

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

POST /upload

Carica un singolo file via multipart/form-data. La parte "info" (JSON cifrato con ChaCha20) deve precedere la parte "file". Quando isAppFile=true i byte vengono importati nell'AppFileStore content-addressable (deduplicato per hash); altrimenti il file viene scritto in info.dir/<fileName>. Restituisce il nome file finale (può avere un suffisso "(1)" per evitare sovrascritture).

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

Carica un chunk di un upload chunked e ripristinabile. Ogni chunk viene scritto su disco in upload_tmp/{fileId}/chunk_{index}. Restituisce "<index>:<savedSize>". Dopo l'arrivo di tutti i chunk, richiama la mutation GraphQL mergeChunks per assemblare il file finale.

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

Trasmette in streaming una singola directory come archivio zip. Il parametro di query id decifra un percorso di directory. Sul dispositivo viene eseguita una sola operazione zip alla volta — le richieste concorrenti ottengono HTTP 429.

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

GET /zip/files

Trasmette in streaming più file (o risultati di ricerca media) come un singolo zip. Il parametro id decifra in { type, query, id, name }. Per il tipo FILE la lista file è memorizzata in TempHelper sotto request.id dalla query GraphQL files(); per i tipi media il server esegue 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 media DLNA. Serve un percorso media registrato in precedenza (registrato tramite UrlHelper.getMediaHttpUrl) a un TV / renderer DLNA. Le sorgenti URL sono proxate, gli URI content:// sono trasmessi in streaming, le immagini sono servite così come sono e tutti gli altri file sono serviti con intestazioni specifiche DLNA + supporto HTTP 206 range in modo che i renderer accettino lo stream.

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

NOTIFY /callback/cast

Callback del renderer DLNA. Riceve il NOTIFY XML degli eventi del renderer e aggiorna lo stato di CastPlayer. Su TransportState=STOPPED (senza AVTransportURIMetaData) il player passa automaticamente al prossimo elemento della playlist. Analizza anche RelTime / TrackDuration per gli aggiornamenti di posizione.

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 di health-check non autenticato. Restituisce il nome del pacchetto dell'app come testo semplice. Usalo per verificare che il server HTTP sia raggiungibile.

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

GET /shutdown

Arresta il server HTTP. Richiamabile solo da localhost — le richieste remote ottengono HTTP 403. Chiude tutte le sessioni WebSocket, svuota l'insieme dei client online ed elimina il server HTTP.

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

POST /init

Inizializza una sessione client. Richiede l'intestazione c-id. Se è presente un body cifrato valido (decifrato con il token in cache) la sessione è considerata autenticata e il server risponde 200. Altrimenti: se non è impostata una password, il server risponde con una password appena resettata (200); se è impostata una password, risponde 204 No Content e il client deve autenticarsi tramite il flusso di 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 principale. Usa ?cid=<client-id> per registrare una sessione; il primo frame binario deve essere un timestamp cifrato (oppure, con ?auth=1, una AuthRequest cifrata contenente la password). Dopo l'autenticazione il server invia frame di eventi cifrati (notifiche, aggiornamenti chat, ecc.) su questo 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 di presenza peer. Usa ?cid=<peer-id> per mantenere un peer abbinato segnato come online. Il primo frame binario deve essere una richiesta cifrata con PeerChatParser; in caso di successo il server invia il testo "ok" e segna il peer come online finché il socket non viene chiuso.

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)