はじめに
アプリ内で匿名通話を行うような機能を実装したので、導入手順(主にバックエンド)をまとめました。
背景
お互いの電話番号を知らせずに通話する(匿名通話)という要件があったので、Twilioで050番号を購入して自動転送させるという仕組みをつくりました。
Twilioを使うと、匿名通話のようなプライバシーを守るための機能だけではなく、自動応答(音声)などの仕組みも簡単に導入することができます。
例えば、食べログの電話予約で050番号にかけると、店舗側で受話器をとったときに「食べログからの電話です」という音声が流れるようなのですが、このような機能もTwilioで実装することができます。
また、ユーザAの端末からTwilioの050番号にそのまま架電することもできる(回線交換方式)のですが、以下の問題があったので、パケット交換方式(VoIP)による通話機能を実装することにしました。
- ユーザAに発信料、Twilioアカウントに着信料が発生する
- 0120番号に切り替えても、Twilioアカウントへの着信料が発生する
- ユーザAの発信は通話アプリから行うため(作成アプリには通話アプリへのリンクを置くだけ)、通話後に作成アプリへ戻る手間が発生する
実装
TwilioからユーザBに転送を行うためには、転送指示(転送元、転送先の番号)をTwilioに与える必要があります。
そのため、ユーザAからTwilioに架電したときに、どっかのサーバにフックして転送の指示内容を取得する仕組みをつくってあげます。
フック先のサーバを自分で用意してもいいのですが、幸いなことにTwilioでは素早く簡単にサーバを構築できるサーバーレス環境が準備されています。
サーバレスでの実装にはTwilio FunctionsとTwiML Binsを使用する方法があるので、それぞれ紹介します。
用語については以下をご参照ください。
- TwiML:Twilioのマークアップ言語。記述内容の処理(今回の場合は転送処理)をTwilioが実行する。
- TwiML Apps:クライアントの接続先となるTwilio上のアプリケーション。接続にはアクセストークンが必要。
- Twilio Functions:Node.jsで動作するサーバレスの実行環境。今回はクライアントからのアクセストークン取得とTwiML AppsからのTwiML取得(パターン1)に使用。
- TwiML Bins:自分で記述したTwiMLを配置、利用できる機能。TwiML取得(パターン2)に使用。
※Twilioアカウントと電話番号を取得している前提で紹介していきます。
パターン1(Twilio Functions)
こちらのパターンでは、クライアントからのアクセストークン取得とTwiML AppsからのTwiML取得のスクリプトをそれぞれ記述して、Twilio Functionsに配置します。
構成図
手順
以下が導入手順です。
TwiML Appsの作成
Voice > Manage > TwiML apps
ページのCreate new TwiML App
からTwiML Appsを新しく作成します。
Twilio Functionsの作成
Functions > Functions(classic) > list
からFunctionsを作成します。
以下がアクセストークン取得のFunctions(CallAccessToken)を作成した画面です。
PropertiesのPATHがクライアント側でトークンをフェッチする際のパスとなります。
アクセストークンの生成には、TwilioのアカウントSID、APIキー、APIシークレット、TwiML AppsのSIDが必要です。
これらの値はFunctions > Functions(classic) > Configure
で環境変数として保持します。
exports.handler = function(context, event, callback) {
const AccessToken = require('twilio').jwt.AccessToken;
const VoiceGrant = AccessToken.VoiceGrant;
// Used when generating any kind of tokens
const twilioAccountSid = context.TWILIO_ACCOUNT_SID;
const twilioApiKey = context.TWILIO_API_KEY;
const twilioApiSecret = context.TWILIO_API_SECRET;
// Used specifically for creating Voice tokens
const outgoingApplicationSid = context.TWIML_APP_SID;
const identity = 'user';
// Create a "grant" which enables a client to use Voice as a given user
const voiceGrant = new VoiceGrant({
outgoingApplicationSid: outgoingApplicationSid,
incomingAllow: true, // Optional: add to allow incoming calls
});
// Create an access token which we will sign and return to the client,
// containing the grant we just created
const token = new AccessToken(
twilioAccountSid,
twilioApiKey,
twilioApiSecret,
{identity: identity}
);
token.addGrant(voiceGrant);
// Serialize the token to a JWT string
callback(null, token.toJwt());
};
こちらが自動転送のTwiMLを返すFunctionsです。
クライアントから渡した転送先や転送元(Twilio)の電話番号パラメータTo
Twilio
をもとに、TwiMLを生成します。
exports.handler = function(context, event, callback) {
// 転送先番号
let phoneNumber = event.To;
// 転送元番号(Twilio番号)
let callerNumber = event.Twilio;
// TwiMLインスタンスの作成
let twiml = new Twilio.twiml.VoiceResponse();
let dialParams = {};
let numberParams = {}
dialParams.callerId = callerNumber
// TwiMl生成
const dial = twiml.dial(dialParams);
dial.number(numberParams, phoneNumber);
// TwiML返却
callback(null, twiml);
};
作成したFunctionsのPATHをTwiML AppsのRequest URLにペーストします。
これでユーザAからTwilio(TwiML Apps)に架電したときに、Functionsをフックするようになります。
パターン2(TwiML Bins)
こちらのパターンでは、クライアントからのアクセストークン取得にはTwilio Functions、TwiMLの取得にはTwiML Binsを利用します。
構成図
手順
Functionsのときと同様に、まずはTwiML Appsを作成します。
TwiML Binsの作成
TwiML Bins > My TwiML bins
からTwiML Binsを新しく作成します。
スクリプトに転送元(Twilio)と転送先の番号を埋め込みます(日本の番号であれば+81が必要)。
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Dial callerId="+81[Twilio番号]">
<Number>+81[転送先番号]</Number>
</Dial>
</Response>
最後に、作成したTwiML BinsのURLをTwiML AppsのRequest URLにペーストします。
これでユーザAからTwilio(TwiML Apps)に架電したときに、TwiML Binsをフックするようになります。
クライアント側の実装について(おまけ)
iOSやAndroidについては公式SDKがあるので、Swift/Kotinを使う場合はドキュメントを読めば普通に実装できる思います。
自分の場合はReact Native(Bare workflow)での実装だったので、以下のライブラリを使いました。
スター数もそんなにないので嫌な予感はしていましたが、導入にあたっていくつか罠がありました。
React Nativeで実装する人などほぼいないと思いますが、一応メモとして残しておきます。
導入手順の抜け
ドキュメント通りに導入しても、Androidで発信する際にクラッシュしてしまいます。
proguard-rules.pro
に以下を追記しましょう。
##### Twilio Programmable Voice #####
-keep class com.twilio.** { *; }
-keep class tvo.webrtc.** { *; }
-dontwarn tvo.webrtc.**
-keep class com.twilio.voice.** { *; }
-keepattributes InnerClasses
権限許可モーダルの制御
通話発信を行うためには、iOSであれば「マイク」、Androidであれば「発信と管理」「録音」の権限許可を行う必要があります。
RNTwilioPhone.initialize(CallKeepOptions, fetchAccessTokenCallback)
の処理タイミングで権限許可モーダルが表示されるので、発信画面の表示タイミングと被らないようにうまく制御しましょう。
発信画面の切替(Android)
発信処理を行う以下の処理RNTwilioPhone.startCall(To, "", Caller, ConnectParams)
で、To
に番号をいれると、Androidの場合は通話アプリが立ち上がります。
To
に番号をいれないと自分で作成した発信画面を表示することができるのですが、普通に通話できるのに、「発信できません」というモーダルが表示されてしまいます。
参考資料