Référence de l'API

Routes HTTP

PlainApp expose un petit ensemble d'endpoints HTTP à côté de l'API GraphQL à POST /graphql. Ces endpoints gèrent le service de fichiers, les uploads multipart, le streaming zip, le casting DLNA, les événements WebSocket et le contrôle système. Tous les endpoints de fichiers/upload/zip requièrent l'en-tête c-id (et généralement un paramètre de requête id chiffré).

Définitions de types

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

Opérations

http

POST /graphql

Endpoint GraphQL principal. Envoyez toutes les requêtes et mutations GraphQL ici avec les en-têtes c-id + Authorization. L'endpoint /peer_graphql est l'équivalent pair-à-pair utilisé entre les appareils appairés.

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

Sert un fichier, un URI content:// ou une icône de paquet. Le paramètre de requête id est une chaîne chiffrée renvoyée par files()/fileIds(). Prend en charge la génération de vignettes (?w=&h=&cc=), les plages d'octets (?offset=&length=) pour les transports à faible débit, la conversion HEIF→PNG, le transcodage 3gp→MP4 et le mode téléchargement (dl=1) avec un en-tête 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 d'une URL HTTP pair. Le paramètre de requête id déchiffre une URL http(s) complète sur un appareil pair appairé ; le serveur renvoie la réponse amont en streaming. Utilisé pour les téléchargements de fichiers pair-à-pair via Wi-Fi Aware.

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

POST /upload

Upload d'un fichier unique via multipart/form-data. La partie « info » (JSON chiffré ChaCha20) doit précéder la partie « file ». Quand isAppFile=true, les octets sont importés dans l'AppFileStore à adressage par contenu (dédupliqué par hachage) ; sinon le fichier est écrit dans info.dir/<fileName>. Renvoie le nom final du fichier (peut avoir un suffixe « (1) » pour éviter l'écrasement).

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 d'un chunk d'un upload chunked reprendable. Chaque chunk est écrit sur disque dans upload_tmp/{fileId}/chunk_{index}. Renvoie « <index>:<savedSize> ». Après l'arrivée de tous les chunks, appelez la mutation GraphQL mergeChunks pour assembler le fichier 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

Stream un répertoire unique sous forme d'archive zip. Le paramètre de requête id déchiffre un chemin de répertoire. Une seule opération zip s'exécute à la fois sur l'appareil — les requêtes concurrentes reçoivent HTTP 429.

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

GET /zip/files

Stream plusieurs fichiers (ou résultats de recherche média) sous forme de zip unique. Le id déchiffre { type, query, id, name }. Pour le type FILE la liste de fichiers est stockée dans TempHelper sous request.id par la requête GraphQL files() ; pour les types média le serveur exécute 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 média DLNA. Sert un chemin média préalablement enregistré (enregistré via UrlHelper.getMediaHttpUrl) à un téléviseur / renderer DLNA. Les sources URL sont proxyfiées, les URIs content:// sont streamés, les images sont servies telles quelles, et tous les autres fichiers sont servis avec des en-têtes spécifiques DLNA + support HTTP 206 range pour que les renderers acceptent le flux.

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. Reçoit le XML NOTIFY de l'événement du renderer et met à jour l'état de CastPlayer. Sur TransportState=STOPPED (sans AVTransportURIMetaData) le player passe automatiquement à l'élément suivant de la liste de lecture. Analyse aussi RelTime / TrackDuration pour les mises à jour de position.

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 non authentifié. Renvoie le nom du paquet de l'app en texte brut. Utilisez-le pour vérifier que le serveur HTTP est accessible.

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

GET /shutdown

Arrête le serveur HTTP. Appelable uniquement depuis localhost — les requêtes distantes reçoivent HTTP 403. Ferme toutes les sessions WebSocket, vide l'ensemble des clients en ligne et dispose le serveur HTTP.

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

POST /init

Initialise une session client. Requiert l'en-tête c-id. Si un corps chiffré valide est présent (déchiffré avec le token en cache) la session est considérée comme authentifiée et le serveur répond 200. Sinon : si aucun mot de passe n'est défini, le serveur répond avec un mot de passe fraîchement réinitialisé (200) ; si un mot de passe est défini, répond 204 No Content et le client doit s'authentifier via le flux de connexion 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. Utilisez ?cid=<client-id> pour enregistrer une session ; la première trame binaire doit être un timestamp chiffré (ou, avec ?auth=1, une AuthRequest chiffrée contenant le mot de passe). Après authentification, le serveur pousse des trames d'événements chiffrées (notifications, mises à jour de chat, etc.) sur ce 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 présence pair. Utilisez ?cid=<peer-id> pour garder un pair appairé marqué en ligne. La première trame binaire doit être une requête chiffrée PeerChatParser ; en cas de succès le serveur envoie le texte « ok » et marque le pair en ligne jusqu'à la fermeture du 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)