Twilioから、2者間での匿名コミュニケーションを実現する新しいプロダクト、Twilio Proxyがベータリリースとして発表されました。 Twilioアカウントをお持ちの方であれば、どなたでも利用することができます。
この記事ではTwilio Proxyでいったいどんなことが実現できるのか、どのような仕組みなのか、そしてコード例を交えながらの実際の使い方について順に見ていきたいと思います。
Twilio Proxyとは何か
冒頭で触れたように、Twilio Proxy (以下、Proxy) はTwilioのプラットフォームで2者間の匿名コミュニケーションを簡単に実装できるように用意された一連のAPIで、現在は音声通話とSMSの2種類のチャンネルに対応しています。
「匿名コミュニケーション」とだけ聞くと、何やらアングラなことをやっているような印象を抱く方もいらっしゃるかも分かりませんが、そういったものよりむしろ、UberやAirbnbに代表されるようなライドシェアリングや民泊ビジネス、あるいは日本のココナラが行なっているような、個人のスキルやノウハウの売買を仲介するようなサービスでは、匿名コミュニケーションは両者をダイレクトに繋げる用途に大変有用なものです。 なぜなら従来の個人間のやり取りには電話番号やメールアドレスの交換がついて回ったものですが、言うまでもなく見ず知らずの相手にこうした個人情報を共有することは、特に女性や未成年の方にとっては非常に抵抗のあるものです。
この匿名コミュニケーションの中でも特に匿名通話、つまりお互いの電話番号を教えあうことなく両者の通話を実現する用途は、これまでも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をかじったことのある方ならここまで読み進めて、「ああなんだ、そんなの簡単に実装できるじゃん」と思うかもしれません。
ところがどっこい、この仕組みを実際のサービスに取り入れて使い物になるようにするには、乗り越えなければならない実装上の課題が多くあります。 例えば:
- 2者間のマッチングをどのように管理するか
- 発信先の相手が複数ある場合、それをどのようにエンドユーザーに選ばせるか
- 両者が通話できる分数を制限したい場合はどうするか
といったようなところが真っ先に挙げられるでしょう。
Proxyは、あらかじめ上記のようなシナリオを念頭に開発されており、ひとたびその仕組みを把握してしまえば、自社のアプリケーションに匿名通話の仕組みを簡単に導入できることでしょう。
ProxyのSMS機能は日本で使い物になるのか?
前節で触れたように、現在Proxyでは匿名通話に使用できる音声通話のほかに、チャンネルとしてSMSを使用した匿名メッセージングも可能です。
しかし日本のTwilio番号 (050、0800、0120) は残念ながらSMSの送受信には対応していません。 日本国内でTwilioを使用したSMS送信を使用する場合は基本的に「+1」から始まるアメリカの電話番号を使用することになりますが、一般のエンドユーザーが自分の携帯電話を使用してアメリカ番号にSMSを送信すると、通常¥50〜¥100といった高額なSMS送信料が通信キャリアーに課されるため、あまり現実的とは言えません。
そんなわけで本稿では、Proxyの中でも匿名通話に的を絞って話を進めます。 Proxyを使用した匿名メッセージングが日本で真価を発揮するのは、Facebook MessangerやLINEといったような追加のチャンネルが利用できるようになってからでしょう。
ProxyのAPI階層
Proxyでは匿名コミュニケーションを実現するための仕組みを一連のAPIリソースとして定義しており、それらの役割と相関関係を理解することで、実装の手順もおのずと見えてきます。
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を匿名番号として割り当てます。
次に2番目のSession
が作成されて、そこにCさんとDさんという二人のParticipants
が追加されると、Twilioはこの両者にも先ほどと同様、+8150xxxxxxxxを匿名番号として割り当てます。
現在Service
に追加されているParticipants
がすべて別の電話番号で識別できるため、Session
ごとに異なる電話番号を割り当てる必要はありません。
さて、続いて3番目のSession
が作成されました。 今回のParticipants
はEさんとBさんです。 Eさんには前の2つのケース同様、+8150xxxxxxxxが割り当てられますが、一方BさんはすでにAさんとのSession
が現在進行中のため、同じ番号を割り当ててしまうとBさんはEさんと話しているつもりなのに実は相手はAさんだった……といった具合に会話がちゃんぽんになってしまいます。
そこでこの3番目のSession
では、Bさんには別のTwilio番号である+8150yyyyyyyyがTwilioから割り当てられます。
ちなみに上の図のイラストで、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のドキュメントページで確認できます。
このバージョンが古い場合、コード中から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桁の英数字) が表示されるので控えておきます。
PhoneNumber
をService
に追加する
続いて、上記で作成した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の送受信に対応しないため、既定値のままではそのSession
のParticipant
追加の段階で「割り当てられる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
はご自身のものと置き換えてください。 Service
とSession
も、前項までで作成し控えておいたものを使用します。
Functionを実行して、Participant
の作成に成功すると、そのSID (KPで始まる34文字の英数字) が表示されます。 これは今回コード中で使用しないので、控えておかなくてもかまいません。
一人目のParticipant
が作成できたら、今度は上記コードのidentifier
とunique_name
を別の人の電話番号と名前に置き換えて保存し、もう一度Functionを実行します。 するとSession
にParticipant
がもうひとつ作成され、先ほどとは別のSIDが表示されます。
Twilio番号に電話をかける!
これで匿名通話のお膳立てがすべて完了しました。 前項でParticipant
として設定した電話番号のいずれかから、Service
のPhoneNumber
として追加した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))
};
通話を行った数だけSession
にInteractions
リソースが作成され、その各SIDがブラウザーに表示されたことが確認できるでしょう。
終わりに
ここまでTwilio Proxyの概要と、その使い方の基本について見てきました。 実際のユースケースでは、Sessions
にParticipants
を追加した後、各Participant
に紐づいたエンドユーザーに対して、通話に使用するTwilio番号を通知したいでしょう。 これは通話発信APIのFrom
番号としてそのParticipant
のProxyIdentifier
を、To
番号としてIdentifier
を指定し通話を発信、TwiMLの<Say>
動詞などを使用して「この電話番号に折り返せば相手の電話につながる」旨のガイダンスを流してあげることで実現できます。
他にもSession
のTTL
を使用した制限時間の適用や、Participants
への番号割り当てのアルゴリズム選択、各種コールバックの使用方法など、取り上げたい話題はまだありますが、記事があまりに長くなってしまうため今回はこの辺でお開き。 リクエストが多ければ続編も検討したいと思います。
それでは皆さま、風邪🤧や暴飲暴食🍺🍖に気をつけつつ楽しい年末年始を!(笑)