API リファレンス

HTTP ルート

PlainApp は POST /graphql の GraphQL API に加えて、少数の HTTP エンドポイントを提供します。これらのエンドポイントは、ファイル提供、マルチパートアップロード、zip ストリーミング、DLNA キャスト、WebSocket イベント、システム制御を処理します。すべての file/upload/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

再開可能なチャンクアップロードの 1 つのチャンクをアップロードします。各チャンクはディスク上の 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 操作は 1 つだけです——同時リクエストは 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 で登録) を TV / 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" テキストを送信し、socket が閉じるまで peer をオンラインとしてマークします。

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)