Node.js
twilio
TwilioDay 12

Twilio Proxyの使い方

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

Twilio Proxyとは何か

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

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

<?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

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

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

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

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

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

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

そんなわけで本稿では、Proxyの中でも匿名通話に的を絞って話を進めます。 Proxyを使用した匿名メッセージングが日本で真価を発揮するのは、Facebook MessangerLINEといったような追加のチャンネルが利用できるようになってからでしょう。

ProxyのAPI階層

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

proxy-resources.001.jpeg

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

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

Sessionsは、Service内での個々の匿名通話のセッションを表しており、これはつまり民泊サービスにおける貸し手と借り手や、占いサービスにおける占い師とお客さんとの個々の取引を表します。
Sessionsには有効期限 (DateExpiry) や生存時間 (TTL) を設けることができ、これを過ぎるとSessionsStatusプロパティーが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

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

match002.jpg

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

match003.jpg

ちなみに上の図のイラストで、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から利用できるサーバーレス環境であるTwilio Functionsを使用します。
なお、Twilio Functionsについてはまた機会を改めて詳しいチュートリアルのシリーズを作りたいと思っています。 どうぞご期待ください。

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

まず、FunctionsのConfiguration (構成) ページのDependencies (依存関係) セクションで、現在Twilio Runtime環境にインストールされているTwilioヘルパーライブラリーのバージョンを確認しておきます。
なお、最新のSDKバージョンは、SDKのドキュメントページで確認できます。

dependencies.jpg

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

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

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

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

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

Twilio 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 (保存) 」ボタンをクリックして保存します。

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桁の英数字) が表示されるので控えておきます。

PhoneNumberServiceに追加する

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

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は、「アクティブな電話番号」ページで使用するTwilio番号をクリックすると左上に表示されます。
再度FunctionのURLにアクセスすると、追加したTwilio番号のSIDが表示されるはずです。

匿名通話のSessionを作成する

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

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の送受信に対応しないため、既定値のままではそのSessionParticipant追加の段階で「割り当てられるTwilio番号の候補がない」という旨のエラーが発生してしまいます。
これを防ぐには、このmodeパラメーターに明示的にvoice-onlyという値を指定して、このSessionでは音声通話のみを許可するようにします。

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

Participantsを作成する

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

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はご自身のものと置き換えてください。 ServiceSessionも、前項までで作成し控えておいたものを使用します。

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

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

Twilio番号に電話をかける!

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

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

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))
};

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

終わりに

ここまでTwilio Proxyの概要と、その使い方の基本について見てきました。 実際のユースケースでは、SessionsParticipantsを追加した後、各Participantに紐づいたエンドユーザーに対して、通話に使用するTwilio番号を通知したいでしょう。 これは通話発信APIFrom番号としてそのParticipantProxyIdentifierを、To番号としてIdentifierを指定し通話を発信、TwiMLの<Say>動詞などを使用して「この電話番号に折り返せば相手の電話につながる」旨のガイダンスを流してあげることで実現できます。

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

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