# RPC 機能

## 概要

RPC 機能は DataChannel 経由で Sora の一部 HTTP API をクライアント側から呼び出しを行うための仕組みです。

RPC 機能の詳細については Sora ドキュメントの [RPC 機能](https://sora-doc.shiguredo.jp/RPC) をご確認ください。

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

## 前提条件

RPC を使う前提は次のとおりです。

- Sora 接続が確立していること

  例: `await Sora.createConnection(config)` で作成した `SoraConnection` が、 `conn.connect()` で接続されている状態である
- 利用したいメソッド名が、 Sora から払い出された `rpc_methods` に含まれていること
- `rpc` DataChannel が使える状態になっていること

## 利用可能な RPC メソッド

利用可能なメソッド名は、 `SoraSignalingMessageEvent` イベント通知から取得することができます。
`event.data?['type']` が `offer` のとき、 `event.data?['rpc_methods']` にメソッド名のリストが入ります。
またメソッドは Sora の認証ウェブフックで払い出されたものだけが含まれます。

`SoraSignalingMessageEvent` の `event` プロパティが `SoraSignalingEvent` であり、シグナリングメッセージ本体はその `data` に入っています。
`data` は `null` の場合があるため、 `data?['type']` のように null 安全に確認します。

以下のコード例では、接続済みの `SoraConnection` を `conn` として保持しているものとします。

```dart
import 'dart:async';

import 'package:sora_sdk/sora_sdk.dart';
```

```dart
void listenRpcMethods(SoraConnection conn) {
  conn.events.listen((event) {
    if (event case SoraSignalingMessageEvent(event: final signalingEvent)) {
      final data = signalingEvent.data;
      if (signalingEvent.direction == 'received' &&
          data?['type'] == 'offer') {
        final methods = data?['rpc_methods'];
        if (methods is List) {
          for (final method in methods.whereType<String>()) {
            print(method);
          }
        }
      }
    }
  });
}
```

> **注釈**
>
> イベントについては [イベント](event.html) をご確認ください。

## RPC を送信する

RPC リクエストを送信するには `rpc()` を実行します。利用可能なメソッド名は、 offer の `rpc_methods` に含まれる文字列をそのまま使います。
`SoraRpcOptions.timeout` はミリ秒単位で指定します。 `notification: true` を指定しない通常のリクエストでは、サーバーからのレスポンスを待ちます。

```dart
Future<Object?> callRpc(SoraConnection conn) async {
  try {
    return await conn.rpc(
      // サンプルのためダミーのメソッド名です
      'example.method',
      params: <String, Object?>{'value': 123},
      options: const SoraRpcOptions(timeout: 5000),
    );
  } on SoraRpcError catch (error) {
    print('${error.code} ${error.message}');
  } on TimeoutException {
    print('timeout');
  }
  return null;
}
```

### Notification として送信する

レスポンス不要で送る場合は `notification: true` を使います。

> **注釈**
>
> Notification の場合、サーバーは通知として受け取るためエラーコードやレスポンスは返しません。

```dart
Future<void> sendNotification(SoraConnection conn) async {
  await conn.rpc(
    'example.notify',
    params: <String, Object?>{'message': 'hello'},
    options: const SoraRpcOptions(notification: true),
  );
}
```

### RequestSimulcastRid メソッドのリクエスト例

サイマルキャストにおいて受信側で希望する RID を変更したい場合の例です。

```dart
Future<Object?> requestSimulcastRid(SoraConnection conn) {
  return conn.rpc(
    'RequestSimulcastRid',
    params: <String, Object?>{
      'channel_id': 'example-channel',
      'receiver_connection_id': conn.connectionId,
      'sender_connection_id': '',
      'rid': 'r1',
    },
  );
}
```

### PutSignalingNotifyMetadataItem メソッドのリクエスト例

シグナリング通知メタデータの 1 項目を更新したい場合の例です。

```dart
Future<Object?> putSignalingNotifyMetadataItem(SoraConnection conn) {
  return conn.rpc(
    'PutSignalingNotifyMetadataItem',
    params: <String, Object?>{
      'channel_id': 'example-channel',
      'connection_id': conn.connectionId,
      'key': 'display_name',
      'value': 'Alice',
    },
  );
}
```

## エラーハンドリング

- サーバーから RPC エラーが返ると `SoraRpcError`
- タイムアウトを超えると `TimeoutException`
- 切断時は待機中の RPC が `SoraRpcError(code: -1, message: 'Disconnected')` で完了
- `SoraConnection.dispose()` 後に `rpc()` を呼ぶと `StateError`

`SoraConnectionErrorEvent` は接続自体のエラーを通知するイベントです。
RPC サーバーが返したエラーは `SoraRpcError` 、RPC の応答待ちが指定時間を超えた場合は `TimeoutException` で処理します。

```dart
Future<void> callRpcWithErrorHandling(SoraConnection conn) async {
  try {
    await conn.rpc(
      'example.method',
      options: const SoraRpcOptions(timeout: 3000),
    );
  } on SoraRpcError catch (error) {
    print(error);
  } on TimeoutException {
    print('timeout');
  }
}
```
