API-referentie

HTTP-routes

PlainApp stelt een klein aantal HTTP-endpoints beschikbaar naast de GraphQL-API op POST /graphql. Deze endpoints verzorgen bestandsserving, multipart-uploads, zip-streaming, DLNA-casting, WebSocket-gebeurtenissen en systeembediening. Alle bestand-/upload-/zip-endpoints vereisen de c-id-header (en meestal een versleuteld id-queryparameter).

Typedefinities

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

Bewerkingen

http

POST /graphql

Belangrijkste GraphQL-endpoint. Stuur hier alle GraphQL-queries en -mutations naartoe met c-id + Authorization-headers. Het /peer_graphql-endpoint is het peer-to-peer-equivalent dat tussen gekoppelde apparaten wordt gebruikt.

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

Serveer een bestand, content://-URI of pakketticoon. De id-queryparameter is een versleutelde tekenreeks geretourneerd door files()/fileIds(). Ondersteunt miniatuurgeneratie (?w=&h=&cc=), byte-range (?offset=&length=) voor doorvoerarme transports, HEIF→PNG-conversie, 3gp→MP4-transcodering en downloadmodus (dl=1) met een Content-Disposition attachment-header.

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 van een peer-HTTP-URL. De id-queryparameter ontsleutelt naar een volledige http(s)-URL op een gekoppeld peer-apparaat; de server streamt de upstream-reactie terug. Gebruikt voor peer-to-peer bestandsdownloads 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 één bestand via multipart/form-data. Het "info"-deel (met ChaCha20 versleutelde JSON) moet vóór het "file"-deel staan. Wanneer isAppFile=true worden de bytes geïmporteerd in de content-addressable AppFileStore (gededupliceerd op hash); anders wordt het bestand geschreven naar info.dir/<fileName>. Retourneert de uiteindelijke bestandsnaam (kan een "(1)"-achtervoegsel hebben om overschrijven te voorkomen).

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 één chunk van een hervatbare, chunked upload. Elke chunk wordt naar schijf geschreven op upload_tmp/{fileId}/chunk_{index}. Retourneert "<index>:<savedSize>". Nadat alle chunks zijn binnengekomen, roep de GraphQL-mutation mergeChunks aan om het uiteindelijke bestand samen te stellen.

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 één map als een zip-archief. De id-queryparameter ontsleutelt naar een mappad. Er wordt slechts één zip-bewerking tegelijk op het apparaat uitgevoerd — gelijktijdige verzoeken krijgen HTTP 429.

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

GET /zip/files

Stream meerdere bestanden (of mediazoekresultaten) als één zip. De id ontsleutelt naar { type, query, id, name }. Voor type FILE wordt de bestandslijst door de GraphQL-files()-query opgeslagen in TempHelper onder request.id; voor mediatypen voert de server searchZipItems(type, query, id) uit.

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

GET /media/{id}

DLNA-media-endpoint. Serveert een eerder geregistreerd mediapad (geregistreerd via UrlHelper.getMediaHttpUrl) aan een tv / DLNA-renderer. URL-bronnen worden geproxyd, content://-URI's worden gestreamd, afbeeldingen als-is geserveerd en alle andere bestanden worden geserveerd met DLNA-specifieke headers + HTTP 206-range-ondersteuning zodat renderers de stream accepteren.

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

NOTIFY /callback/cast

DLNA-renderer-callback. Ontvangt de NOTIFY-XML van de renderer en werkt de CastPlayer-status bij. Bij TransportState=STOPPED (zonder AVTransportURIMetaData) gaat de speler automatisch naar het volgende afspeellijstitem. Parst ook RelTime / TrackDuration voor positie-updates.

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

Niet-geauthenticeerd health-check-endpoint. Retourneert de app-pakketnaam als platte tekst. Gebruik dit om te controleren of de HTTP-server bereikbaar is.

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

GET /shutdown

Sluit de HTTP-server af. Alleen aanroepbaar vanaf localhost — externe verzoeken krijgen HTTP 403. Sluit alle WebSocket-sessies, wist de online clientset en verwijdert de HTTP-server.

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

POST /init

Initialiseer een clientsessie. Vereist de c-id-header. Als er een geldige versleutelde body aanwezig is (ontsleuteld met de gecachte token) wordt de sessie als geauthenticeerd beschouwd en reageert de server met 200. Anders: als er geen wachtwoord is ingesteld, reageert de server met een net gereset wachtwoord (200); als er een wachtwoord is ingesteld, reageert met 204 No Content en moet de client authenticeren via de WebSocket-loginflow.

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

WS /

Belangrijkste WebSocket-endpoint. Gebruik ?cid=<client-id> om een sessie te registreren; het eerste binaire frame moet een versleutelde timestamp zijn (of, met ?auth=1, een versleutelde AuthRequest met het wachtwoord). Na authenticatie pusht de server versleutelde gebeurtenisframes (meldingen, chat-updates, enz.) over deze 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

Peer-presence-WebSocket. Gebruik ?cid=<peer-id> om een gekoppelde peer online gemarkeerd te houden. Het eerste binaire frame moet een met PeerChatParser versleuteld verzoek zijn; bij succes stuurt de server de tekst "ok" en markeert de peer online totdat de socket sluit.

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)