# RPC 機能

## 概要

RPC 機能は、 DataChannel 経由で JSON-RPC 2.0 による Sora の一部 HTTP API 呼び出しを行うための仕組みです。
Sora の RPC 機能については [Sora のドキュメント RPC 機能](https://sora-doc.shiguredo.jp/RPC) をご確認ください。

> **警告**
>
> この機能は実験的機能のため、正式版では仕様が変更される可能性があります。

## 設定

RPC 機能は DataChannel 経由のシグナリングが有効な場合に利用できます。

Sora Android SDK で DataChannel 経由のシグナリングを有効にするには、 `SoraMediaChannel` の `dataChannelSignaling` に `true` を設定します。

```kotlin
// DataChannel 経由のシグナリングを有効にして接続
val mediaChannel =
    SoraMediaChannel(
        context = this,
        signalingEndpoint = "wss://example.com/signaling",
        channelId = "sora",
        dataChannelSignaling = true,
        mediaOption = option,
        listener = channelListener,
    )

mediaChannel.connect()
```

## 利用可能なメソッドの確認

利用可能なメソッド一覧は `onOfferMessage` コールバックで `OfferMessage.rpcMethods` から取得します。

```kotlin
override fun onOfferMessage(
    mediaChannel: SoraMediaChannel,
    offer: OfferMessage,
) {
    // 利用可能な RPC メソッド一覧を確認
    val rpcMethodNames: List<String> = offer.rpcMethods ?: emptyList()
    // ["2025.2.0/RequestSimulcastRid", "2025.2.0/PutSignalingNotifyMetadataItem", ...]
}
```

> **注釈**
>
> 利用可能なメソッドは Sora の認証ウェブフックから払い出されている必要があります。

## RPC 機能を利用する

RPC 機能を利用するには `SoraMediaChannel.rpc` メソッドを使います。

### RequestSimulcastRid の例

```kotlin
/**
 * RequestSimulcastRid を呼び出す RPC サンプルです。
 * kotlinx.serialization を使って params を JSON 文字列に変換します。
 * result は JSON 文字列なので decode して利用します。
 */
import android.util.Log
import jp.shiguredo.sora.sdk.channel.SoraMediaChannel
import jp.shiguredo.sora.sdk.channel.rpc.SoraRpcException
import jp.shiguredo.sora.sdk.channel.rpc.SoraRpcResult
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

@Serializable
data class RequestSimulcastRidParams(
    @SerialName("sender_connection_id")
    val senderConnectionId: String? = null,
    val rid: String,
)

@Serializable
data class RequestSimulcastRidResult(
    @SerialName("channel_id")
    val channelId: String,
    @SerialName("receiver_connection_id")
    val receiverConnectionId: String,
    @SerialName("sender_connection_id")
    val senderConnectionId: String? = null,
    val rid: String,
)

fun requestSimulcastRid(
    mediaChannel: SoraMediaChannel,
    scope: CoroutineScope,
) {
    scope.launch {
        try {
            val params =
                RequestSimulcastRidParams(
                    senderConnectionId = "SENDER_CONNECTION_ID",
                    rid = "r1",
                )
            val paramsJson = Json.encodeToString(params)

            val result =
                mediaChannel.rpc(
                    method = "2025.2.0/RequestSimulcastRid",
                    paramsJson = paramsJson,
                )

            when (result) {
                is SoraRpcResult.Success -> {
                    val response =
                        result.result?.let { Json.decodeFromString<RequestSimulcastRidResult>(it) }
                    Log.d("Sora", "RPC success: $response")
                }
                is SoraRpcResult.Error ->
                    Log.e("Sora", "RPC error: ${result.error.message}")
                null -> Unit
            }
        } catch (e: SoraRpcException) {
            Log.e("Sora", "RPC failed: ${e.message}", e)
        }
    }
}
```

### PutSignalingNotifyMetadataItem の例

```kotlin
/**
 * PutSignalingNotifyMetadataItem を呼び出す RPC サンプルです。
 * kotlinx.serialization を使って params を JSON 文字列に変換します。
 */
import android.util.Log
import jp.shiguredo.sora.sdk.channel.SoraMediaChannel
import jp.shiguredo.sora.sdk.channel.rpc.SoraRpcException
import jp.shiguredo.sora.sdk.channel.rpc.SoraRpcResult
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

@Serializable
data class PutSignalingNotifyMetadataItemParams(
    val push: Boolean? = null,
    val key: String,
    val value: String,
)

fun putSignalingNotifyMetadataItem(
    mediaChannel: SoraMediaChannel,
    scope: CoroutineScope,
) {
    scope.launch {
        try {
            val params =
                PutSignalingNotifyMetadataItemParams(
                    push = true,
                    key = "example_key",
                    value = "example_value",
                )
            val paramsJson = Json.encodeToString(params)

            val result =
                mediaChannel.rpc(
                    method = "2025.2.0/PutSignalingNotifyMetadataItem",
                    paramsJson = paramsJson,
                )

            when (result) {
                is SoraRpcResult.Success ->
                    Log.d("Sora", "Metadata updated: ${result.result}")
                is SoraRpcResult.Error ->
                    Log.e("Sora", "Failed to update metadata: ${result.error.message}")
                null -> Unit
            }
        } catch (e: SoraRpcException) {
            Log.e("Sora", "RPC failed: ${e.message}", e)
        }
    }
}
```

### MediaChannel.rpc のレスポンスが不要な場合

`isNotificationRequest` を `true` に設定すると JSON-RPC 2.0 の Notification として送信されます。
この場合は Sora からレスポンスは返らず、戻り値は `null` になります。
Notification に関する詳細は [](https://www.jsonrpc.org/specification#notification) をご確認ください。

```kotlin
/**
 * Notification として送信する RPC サンプルです。
 * レスポンスは返りません。
 */
import android.util.Log
import jp.shiguredo.sora.sdk.channel.SoraMediaChannel
import jp.shiguredo.sora.sdk.channel.rpc.SoraRpcException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

@Serializable
data class PutSignalingNotifyMetadataItemParams(
    val push: Boolean? = null,
    val key: String,
    val value: String,
)

fun sendRpcNotification(
    mediaChannel: SoraMediaChannel,
    scope: CoroutineScope,
) {
    scope.launch {
        try {
            val params =
                PutSignalingNotifyMetadataItemParams(
                    push = true,
                    key = "example_key",
                    value = "example_value",
                )
            val paramsJson = Json.encodeToString(params)

            // Notification としてリクエストする
            mediaChannel.rpc(
                method = "2025.2.0/PutSignalingNotifyMetadataItem",
                paramsJson = paramsJson,
                isNotificationRequest = true,
            )

            Log.d("Sora", "RPC notification sent")
        } catch (e: SoraRpcException) {
            Log.e("Sora", "RPC failed: ${e.message}", e)
        }
    }
}
```

### SoraMediaChannel.rpc について

`SoraMediaChannel.rpc` は suspend 関数として実装されており、Kotlin Coroutines を利用して非同期に RPC リクエストを送信します。
RPC のパラメータとレスポンスは JSON 文字列として扱います。エンコード / デコードは利用者側で行う必要があります。

#### シグネチャ

```kotlin
suspend fun rpc(
    method: String,
    paramsJson: String?,
    isNotificationRequest: Boolean = false,
    timeoutMillis: Long = DEFAULT_RPC_TIMEOUT_MILLIS,
): SoraRpcResult?
```

#### パラメータ

- method: RPC メソッド名 (例: `2025.2.0/RequestSimulcastRid`)
- paramsJson: RPC パラメータの JSON 文字列。null の場合は params を省略。不正な JSON の場合は `SoraRpcErrorReason.PARSE_ERROR` になります
- isNotificationRequest: `true` の場合は Notification として送信し、レスポンスを待たない。デフォルトは `false`
- timeoutMillis: レスポンス待機のタイムアウト時間 (ミリ秒)

#### 戻り値

`SoraRpcResult?` を返します。 Notification の場合は `null` を返します。

- `SoraRpcResult.Success`: リクエスト成功を表し Sora からのレスポンス JSON 文字列が含まれます
- `SoraRpcResult.Error`: リクエスト失敗を表し Sora からの JSON-RPC エラーが含まれます（error.data は JSON 文字列）
- `null`: Notification であるためレスポンスはありません

RPC を利用できない場合や送信に失敗した場合は `SoraRpcException` をスローします。
`SoraRpcException.reason` は以下のいずれかです。

- `NOT_AVAILABLE`: RPC が有効ではない
- `DATA_CHANNEL_UNAVAILABLE`: RPC 用 DataChannel が存在しない
- `DATA_CHANNEL_CLOSED`: RPC 用 DataChannel が開いていない
- `PEER_UNAVAILABLE`: PeerChannel が存在しない
- `SEND_FAILED`: RPC メッセージの送信に失敗
- `TIMEOUT`: RPC レスポンスがタイムアウト
- `PARSE_ERROR`: RPC メッセージの解析に失敗

#### 注意事項

- `rpc` メソッドは suspend 関数のため、コルーチンスコープ内で呼び出す必要があります
- DataChannel 経由のシグナリングが有効で、かつ `type: switched` メッセージ受信後に利用可能です
- 利用可能なメソッドは `onOfferMessage` コールバックで `OfferMessage.rpcMethods` から確認できます
- 利用可能なメソッドは Sora の認証ウェブフックの払い出し結果に依存し、SDK 側では制御できません
- `isNotificationRequest = true` を設定した場合、`null` が返ります
