API 參考

HTTP 路由

PlainApp 在 POST /graphql 的 GraphQL API 之外還提供了一小組 HTTP 端點。這些端點處理檔案服務、分片上傳、zip 串流傳輸、DLNA 投屏、WebSocket 事件和系統控制。所有檔案/上傳/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 端點是已配對裝置之間使用的點對點等價端點。

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 轉碼,以及帶 Content-Disposition 附件標頭的下載模式(dl=1)。

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 查詢參數解密為已配對 peer 裝置上的完整 http(s) URL;伺服器將上游回應串流傳回。用於透過 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 時,位元組被匯入內容定址的 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>"。所有分片到達後,呼叫 mergeChunks GraphQL 變更以組裝最終檔案。

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 類型,檔案清單由 GraphQL files() 查詢儲存在 TempHelper 下的 request.id 中;對於媒體類型,伺服器執行 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 註冊)提供給電視 / DLNA 轉譯器。URL 來源被代理,content:// URI 被串流傳輸,圖片按原樣提供,所有其他檔案以 DLNA 特定標頭 + HTTP 206 範圍支援提供,以便轉譯器接受串流。

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

無需認證的健康檢查端點。以純文字形式傳回應用程式套件名稱。用於驗證 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 標頭。如果存在有效的加密請求主體(用快取的 token 解密),工作階段被視為已認證,伺服器回應 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> 註冊工作階段;第一個二進位框架必須是加密的時間戳記(或使用 ?auth=1 時為包含密碼的加密 AuthRequest)。認證後,伺服器透過此 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 上線狀態 WebSocket。使用 ?cid=<peer-id> 保持已配對 peer 標記為線上。第一個二進位框架必須是 PeerChatParser 加密的請求;成功時伺服器傳送 "ok" 文字並將 peer 標記為線上,直到 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)