Twilio Proxyの使い方

Twilioから、2者間での匿名コミュニケーションを実現する新しいプロダクト、[Twilio Proxy](https://www.twilio.com/proxy)が[ベータリリースとして発表]((https://www.twilio.com/blog/2017/12/proxy-now-available-in-beta.html))されました。 Twilioアカウントをお持ちの方であれば、どなたでも利用することができます。
この記事ではTwilio Proxyでいったいどんなことが実現できるのか、どのような仕組みなのか、そしてコード例を交えながらの実際の使い方について順に見ていきたいと思います。

# Twilio Proxyとは何か

冒頭で触れたように、Twilio Proxy (以下、Proxy) はTwilioのプラットフォームで2者間の匿名コミュニケーションを簡単に実装できるように用意された一連のAPIで、現在は音声通話とSMSの2種類のチャンネルに対応しています。
「匿名コミュニケーション」とだけ聞くと、何やらアングラなことをやっているような印象を抱く方もいらっしゃるかも分かりませんが、そういったものよりむしろ、[Uber](https://www.uber.com/en-JP/)や[Airbnb](https://www.airbnb.com)に代表されるようなライドシェアリングや民泊ビジネス、あるいは日本の[ココナラ](https://coconala.com)が行なっているような、個人のスキルやノウハウの売買を仲介するようなサービスでは、匿名コミュニケーションは両者をダイレクトに繋げる用途に大変有用なものです。 なぜなら従来の個人間のやり取りには電話番号やメールアドレスの交換がついて回ったものですが、言うまでもなく見ず知らずの相手にこうした個人情報を共有することは、特に女性や未成年の方にとっては非常に抵抗のあるものです。

この匿名コミュニケーションの中でも特に匿名通話、つまりお互いの電話番号を教えあうことなく両者の通話を実現する用途は、これまでもTwilioのユースケースの中で比較的メジャーなものでした。
基本的な仕組みはこうです。 実際に通話を行なうAさんとBさん、そして両者を仲介するサービス提供会社がいたとします。 サービス提供会社は自社のTwilioアカウントのTwilio番号（たとえば050-1234-5678）にAさんからの通話が着信した際に呼び出されるWebhookが返す[TwiML](https://jp.twilio.com/docs/api/twiml)に`<Dial>`動詞を記述し、その発信先としてBさんの電話番号（たとえば090-1234-5678）を指定しておきます。

```XML
<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Dial callerId="+815012345678">+819012345678</Dial>
</Response>
```

Aさんがサービス提供会社から教えてもらったTwilio電話番号に電話をかけると上記TwiMLが実行されてBさんの電話に着信しますが、Bさんの電話機に表示される発信者番号はAさんの番号ではなくTwilio番号である050-1234-5678になります。
またAさんはBさんの電話番号を知っているわけではなく、Twilio番号にかけるだけでBさんと会話することができるというわけです。

![twilio-masked-number.jpg](https://qiita-image-store.s3.amazonaws.com/0/213722/84c5cce4-1145-eb5c-3d1b-ec085484b303.jpeg)

少しTwilioをかじったことのある方ならここまで読み進めて、「ああなんだ、そんなの簡単に実装できるじゃん」と思うかもしれません。
ところがどっこい、この仕組みを実際のサービスに取り入れて使い物になるようにするには、乗り越えなければならない実装上の課題が多くあります。 例えば：

* 2者間のマッチングをどのように管理するか
* 発信先の相手が複数ある場合、それをどのようにエンドユーザーに選ばせるか
* 両者が通話できる分数を制限したい場合はどうするか

といったようなところが真っ先に挙げられるでしょう。

Proxyは、あらかじめ上記のようなシナリオを念頭に開発されており、ひとたびその仕組みを把握してしまえば、自社のアプリケーションに匿名通話の仕組みを簡単に導入できることでしょう。

# ProxyのSMS機能は日本で使い物になるのか？

前節で触れたように、現在Proxyでは匿名通話に使用できる音声通話のほかに、チャンネルとしてSMSを使用した匿名メッセージングも可能です。
しかし日本のTwilio番号 (050、0800、0120) は残念ながらSMSの送受信には対応していません。 日本国内でTwilioを使用したSMS送信を使用する場合は基本的に「+1」から始まるアメリカの電話番号を使用することになりますが、一般のエンドユーザーが自分の携帯電話を使用してアメリカ番号にSMSを送信すると、通常¥50〜¥100といった[高額なSMS送信料](http://www.startoption.com/sms/)が通信キャリアーに課されるため、あまり現実的とは言えません。

そんなわけで本稿では、Proxyの中でも匿名通話に的を絞って話を進めます。 Proxyを使用した匿名メッセージングが日本で真価を発揮するのは、[Facebook Messanger](https://www.messenger.com)や[LINE](https://line.me)といったような追加のチャンネルが利用できるようになってからでしょう。

# ProxyのAPI階層

Proxyでは匿名コミュニケーションを実現するための仕組みを一連のAPIリソースとして定義しており、それらの役割と相関関係を理解することで、実装の手順もおのずと見えてきます。

![proxy-resources.001.jpeg](https://qiita-image-store.s3.amazonaws.com/0/213722/9719f349-f287-fe69-15ec-4846deab1413.jpeg)

`Services`は、ProxyのAPI階層の最上位に位置するリソースであり、通常のアプリケーション開発ではひとつだけ用意します。 環境をプロダクション用とステージング用で分けたい場合や、複数のアプリケーションをひとつのTwilioプロジェクト (旧称Twilioアカウント) で同時にホストしたい場合は、複数の`Service`を作成して、それぞれのリソースを個別管理できます。

`PhoneNumbers`は`Service`内で使用する電話番号の候補で、Twilioプロジェクトですでに購入済のTwilio番号の電話番号SID (PN〜から始まる34文字の英数字) をキーとして関連づけます。
ひとつの電話番号を複数の`Services`で使いまわすことはできません。

`Sessions`は、`Service`内での個々の匿名通話のセッションを表しており、これはつまり民泊サービスにおける貸し手と借り手や、占いサービスにおける占い師とお客さんとの個々の取引を表します。
`Sessions`には有効期限 (`DateExpiry`) や生存時間 (`TTL`) を設けることができ、これを過ぎると`Sessions`の`Status`プロパティーが`Closed`となり、両者でのコミュニケーションは行えなくなります。

`Participants`は、`Session`内で実際にコミュニケーションを行う個人であり、ひとつの`session`につき最大二名の`participant`を作成できます。
`Participants`の作成時には、実際にその個人が相手とコミュニケーションを行うためのTwilio電話を`PhoneNumbers`リソースから選択するか、Twilioに自動的に選択させます。

`Interactions`は、`Session`内で`Participants`がお互いに交わした通話やメッセージングの個々の履歴を表す、読み取り専用のリソースです。

# Proxyと電話番号

## 電話番号選択の仕組み

Proxyでは、できるだけ少ない数のTwilio番号で匿名通話を実現できるようなロジックが組み込まれています。
たとえば+8150xxxxxxxxと+8150yyyyyyyyというふたつのTwilio番号を持っており、これらの番号をProxyの`PhoneNumbers`リソースとして追加したとします。

まず、最初の`Session`でAさんとBさんという二人の`Participants`が追加された場合、Twilioは両者に+8150xxxxxxxxを匿名番号として割り当てます。

![match001.jpg](https://qiita-image-store.s3.amazonaws.com/0/213722/91b46e7c-29f3-eb34-1c3e-169649d9d106.jpeg)

次に2番目の`Session`が作成されて、そこにCさんとDさんという二人の`Participants`が追加されると、Twilioはこの両者にも先ほどと同様、+8150xxxxxxxxを匿名番号として割り当てます。
現在`Service`に追加されている`Participants`がすべて別の電話番号で識別できるため、`Session`ごとに異なる電話番号を割り当てる必要はありません。

![match002.jpg](https://qiita-image-store.s3.amazonaws.com/0/213722/a4784d4b-a722-6aed-d4f9-ab602dfc35eb.jpeg)

さて、続いて3番目の`Session`が作成されました。 今回の`Participants`はEさんとBさんです。 Eさんには前の2つのケース同様、+8150xxxxxxxxが割り当てられますが、一方BさんはすでにAさんとの`Session`が現在進行中のため、同じ番号を割り当ててしまうとBさんはEさんと話しているつもりなのに実は相手はAさんだった……といった具合に会話がちゃんぽんになってしまいます。
そこでこの3番目の`Session`では、Bさんには別のTwilio番号である+8150yyyyyyyyがTwilioから割り当てられます。

![match003.jpg](https://qiita-image-store.s3.amazonaws.com/0/213722/b826d8c7-be66-0ccf-78c1-e8af16d19dff.jpeg)

ちなみに上の図のイラストで、AさんとEさんはネクタイの色が違うだけの同一人物じゃないか！ という苦情は受け付けないものとします。 ドラクエでもスライムとメタルスライムは別のモンスターです。😉

## いくつTwilio番号を用意すればよいのか

もし上記の3番目の`Session`の例で、`Service`にひとつしか`PhoneNumber`が追加されていなかったらどうなるでしょうか。 そのような場合は`Session`内で`Participants`を作成する際、APIが以下のようなエラーを返します。

> Error: This Service has no compatible Proxy numbers for this Participant. Failed to find a proxy number for +8180XXXXXXXX. Either you have no numbers meeting your service's GeoMatchLevel for the target number, or the numbers you do have are already in use by the participant in other Sessions.
> エラー: ServiceにはこのParticipantに対して互換性のあるProxy番号がありません。 +8180XXXXXXXXに対するProxy番号の検索に失敗しました。 対象の番号でServiceのGeoMatchLevelに合致する番号がないか、番号が他のSessionsでこのParticipantによって使用されています。

つまり、一人の`Participant`が現在`Service`に追加されている`PhoneNumbers`を超える数のセッションを同時に持つことはできないということです（今回は日本国内の利用者同士を想定した解説のため、`GetMatchLevel`については割愛します）。

このことから、用意するTwilio番号の数は、「アプリケーションのひとりのユーザーが同時にコミュニケーションを行う可能性のある最大人数」を目安として決めるとよいわけですが、セッションでのやり取りが一度きりの通話のみで完結するようなユースケースの場合は各`Session`の作成時に`Participants`を作成し、通話が終わったら即座にその`Session`を削除すれば、事実上Twilio番号ひとつだけで運用することができるでしょう。

# Proxyをコードの中で使用する

さて、ここからは実際に手を動かしながら、どのようにしてProxyを使用して匿名通話を実現するのか、その手順を追ってみましょう。

## 用意するもの

* Twilioプロジェクト x 1
* 日本のTwilio番号 x 1
* 通話の発着信に使用できる一般の電話番号 x 2

今回は皆さんにすぐ実装を試してもらえるよう、[Twilio Console](https://jp.twilio.com/console)から利用できるサーバーレス環境である[Twilio Functions](https://jp.twilio.com/console/runtime/functions)を使用します。
なお、Twilio Functionsについてはまた機会を改めて詳しいチュートリアルのシリーズを作りたいと思っています。 どうぞご期待ください。

## Node.jsヘルパーライブラリーのバージョンを確認する

まず、Functionsの[Configuration (構成) ](https://jp.twilio.com/console/runtime/functions/configure)ページのDependencies (依存関係) セクションで、現在Twilio Runtime環境にインストールされているTwilioヘルパーライブラリーのバージョンを確認しておきます。
なお、最新のSDKバージョンは、[SDKのドキュメントページ](https://www.twilio.com/docs/libraries/reference/twilio-node/)で確認できます。

![dependencies.jpg](https://qiita-image-store.s3.amazonaws.com/0/213722/5e110a9b-61d8-b0c9-2127-b026b65fa6ee.jpeg)

このバージョンが古い場合、コード中からProxyが使用できないため、最新版のライブラリー (本稿執筆時点では3.10.1) バージョンに書き換えて、画面下の赤い「Save (保存) 」ボタンをクリックします。

## Function内でアカウントSIDと認証トークンを有効にする

TwilioのクレデンシャルをFunction内の環境変数として追加して、Functionの`context`引数に渡されてくるオブジェクトの`getTwilioClient()`メソッドを使用して簡単にヘルパーライブラリーのクライアントを作成できるようにしておきます。

同じくFunctionsの[Configuration (構成) ](https://jp.twilio.com/console/runtime/functions/configure)ページのCredentials (クレデンシャル) セクションの「Enable ACCOUNT_SID and AUTH_TOKEN (ACCOUNT_SIDとAUTH_TOKENを有効にする) 」チェックボックスをオンにし、画面下の赤い「Save (保存) 」ボタンをクリックします。

## 実際にコードを書くためのFunctionを準備する

Twilio Functionsの[Manage (管理) ](https://jp.twilio.com/console/runtime/functions/manage)ページで、左上の赤い「+」アイコンをクリックして新しいFunctionをひとつ作成します。
テンプレートの選択ボックスが表示されたら、「Blank (空白) 」を選択し、右下の赤い「Create (作成) 」ボタンをクリックしてFunctionの編集ページに遷移させます。

「Properties (プロパティー) 」セクションの「FUNCTION NAME」には覚えやすい名前 (筆者と同じなら「Proxy Playground」）を、「PATH」のドメイン名の右側には同じTwilioプロジェクトの他のFunctionsと重複しないパス名（筆者と同じなら「`/proxy-playground`」）を指定します。

なお、「Configuration (構成) 」セクションの「Check for valid Twilio signature (有効なTwilioの署名を確認する) 」のチェックボックスはオフにしておきます。 これがオンになっていると、外部からのリクエストでFunctionを実行することができなくなります。

これでコードを書く準備が整いました！

## `Service`を作成する

まず手始めに、TwilioプロジェクトにProxyの`Service`を作成します。 テンプレートによってすでに用意されているコードをすべて消去して、次のコードと置き換え、左下の「Save (保存) 」ボタンをクリックして保存します。

```JavaScript
exports.handler = function(context, event, callback) {
  context.getTwilioClient().proxy.services.create({
      uniqueName: 'My Proxy Service'
    })
    .then(res => callback(null, res.sid))
    .catch(err => callback(err, null))
};
```

画面上部に「Your Function has successfully been deployed. (Functionのデプロイに成功しました。) 」という緑色のポップアップが表示されたら、実行の準備は完了です。
PATH欄の右側のコピーアイコンをクリックしてURLをコピーしたら、WebブラウザーでそのURLにアクセスします。
`Service`の作成に成功すると、そのSID (KS〜で始まる34桁の英数字) が表示されるので控えておきます。

## `PhoneNumber`を`Service`に追加する

続いて、上記で作成した`Service`で使用する匿名通話用の電話番号を追加します。 これは`PhoneNumber`リソースを作成することで行います。
先ほどのFunctionのコードを、今度は以下の内容に置き換えて保存します。

```JavaScript
exports.handler = function(context, event, callback) {
  context.getTwilioClient().proxy
    .services('KSxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
    .phoneNumbers.create({
      sid: 'PNxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
    })
    .then(res => callback(null, res.sid))
    .catch(err => callback(err, null))
};
```

`Service`のSIDには前項で控えておいたものを使用し、`PhoneNumber`のSIDには、お手持ちのTwilio番号の電話番号SID (PN〜で始まる34文字の英数字) を使用します。
なお電話番号SIDは、「[アクティブな電話番号](https://jp.twilio.com/console/phone-numbers/incoming)」ページで使用するTwilio番号をクリックすると左上に表示されます。
再度FunctionのURLにアクセスすると、追加したTwilio番号のSIDが表示されるはずです。

## 匿名通話の`Session`を作成する

今度は`Service`内に、匿名通話を行うための`Session`を作成します。
Functionのコードを、以下の内容に置き換えて保存します。

```JavaScript
exports.handler = function(context, event, callback) {
  context.getTwilioClient().proxy
    .services('KSxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
    .sessions.create({
      uniqueName: 'MyFirstSession',
      mode: 'voice-only'
    })
    .then(res => callback(null, res.sid))
    .catch(err => callback(err, null))
};
```

ここで`mode`というオプションのパラメーターを指定していることに注目してください。 これは`Session`で使用できるコミュニケーションのチャンネルを指定するものですが、これは既定値では`voice-and-message`となっており、音声通話とSMSを両方使用できることを意味しています。 ところが前述のように日本のTwilio番号はSMSの送受信に対応しないため、既定値のままではその`Session`の`Participant`追加の段階で「割り当てられるTwilio番号の候補がない」という旨のエラーが発生してしまいます。
これを防ぐには、この`mode`パラメーターに明示的に`voice-only`という値を指定して、この`Session`では音声通話のみを許可するようにします。

再度Functionを実行して`Session`の作成に成功すると、そのSID (KC〜から始まるこれまた34文字の英数字) がブラウザーに表示されるので、これも控えておきます。

## `Participants`を作成する

さて、いよいよここで匿名通話を行う二人の電話番号を`Participants`として`Session`に追加します。

```JavaScript
exports.handler = function(context, event, callback) {
  context.getTwilioClient().proxy
    .services('KSxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
    .sessions('KCxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
    .participants.create({
      identifier: '+8180xxxxxxxx',
      unique_name: 'Manabu'
    })
    .then(res => callback(null, res.sid))
    .catch(err => callback(err, null))
};
```

もちろん、上記コードの`identifier`の電話番号と、`unique_name`はご自身のものと置き換えてください。 `Service`と`Session`も、前項までで作成し控えておいたものを使用します。

Functionを実行して、`Participant`の作成に成功すると、そのSID (KPで始まる34文字の英数字) が表示されます。 これは今回コード中で使用しないので、控えておかなくてもかまいません。

一人目の`Participant`が作成できたら、今度は上記コードの`identifier`と`unique_name`を別の人の電話番号と名前に置き換えて保存し、もう一度Functionを実行します。 すると`Session`に`Participant`がもうひとつ作成され、先ほどとは別のSIDが表示されます。

## Twilio番号に電話をかける！

これで匿名通話のお膳立てがすべて完了しました。 前項で`Participant`として設定した電話番号のいずれかから、`Service`の`PhoneNumber`として追加したTwilio番号に電話をかけてみましょう。 もう一方の`Participant`に通話が着信し、会話できるようになります。 着信側の電話機に発信者としてTwilio番号が表示されていることに注目してください。

何度か電話をかけて遊んだ後は、Functionのコードを下記のものに置き換えて保存し、実行してみてください。

```JavaScript
exports.handler = function(context, event, callback) {
  context.getTwilioClient().proxy
    .services('KSxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
    .sessions('KCxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
    .interactions.list()
    .then(res => {
      let response = '';
      for (let interaction of res) {
        response += `${interaction.sid}\n`;
      }
      callback(null, response);
    })
    .catch(err => callback(err, null))
};
```

通話を行った数だけ`Session`に`Interactions`リソースが作成され、その各SIDがブラウザーに表示されたことが確認できるでしょう。

# 終わりに

ここまでTwilio Proxyの概要と、その使い方の基本について見てきました。 実際のユースケースでは、`Sessions`に`Participants`を追加した後、各`Participant`に紐づいたエンドユーザーに対して、通話に使用するTwilio番号を通知したいでしょう。 これは[通話発信API](https://jp.twilio.com/docs/api/voice/making-calls)の`From`番号としてその`Participant`の`ProxyIdentifier`を、`To`番号として`Identifier`を指定し通話を発信、TwiMLの`<Say>`動詞などを使用して「この電話番号に折り返せば相手の電話につながる」旨のガイダンスを流してあげることで実現できます。

他にも`Session`の`TTL`を使用した制限時間の適用や、`Participants`への番号割り当てのアルゴリズム選択、各種コールバックの使用方法など、取り上げたい話題はまだありますが、記事があまりに長くなってしまうため今回はこの辺でお開き。 リクエストが多ければ続編も検討したいと思います。

それでは皆さま、風邪🤧や暴飲暴食🍺🍖に気をつけつつ楽しい年末年始を！（笑）