Twilio Functionsのためのローカル開発環境


はじめに

本業とは関係ないところ(父と近所の手伝い)で、Twilioを使った電話システムの開発に初めてチャレンジしてみました。

極力サーバーの構成はシンプルにしたいので、Twilio Functionsを使いました。AWS Lambdaみたいな感じでサーバーレスにTwilioのシステムを構築できるのが、とても良いですね!

Twilio Functionsの設定画面で直接Node.jsのコードを書いていけば作れるんですけど、やっぱりローカルのエディタでコード書いて、Gitにスムーズにコミットしたりしたいじゃないですか。

日本語の情報が全くなかったんですが、ちゃんと調べたらとても簡単にできたので、メモがてら残しておきます。

Functionsとローカル開発の違いで面倒臭いところ

Functionsは全体をこんな関数でくくって、終了時はcallbackを呼び出します。普通にNode.jsサーバー立てる場合はこんなことしない。

Functions
exports.handler = function(context, event, callback) {
  // ...
  callback(null, "Everything is gunna be alright");
}

そして、twimlclientオブジェクトの作り方も微妙に違います。

local
const twiml = new require('twilio').twiml.VoiceResponse();
const client = require('twilio')(ACCOUNT_SID, AUTH_TOKEN);
const callerId = process.env.CALLER_ID;
Functions
const twiml = new Twilio.twiml.VoiceResponse();
const client = context.getTwilioClient();
const callerId = context.CALLER_ID; // 環境変数はcontextに全部入る

こういうのを、「全部Functionsの仕様に寄せて書いたらローカルで動く」ようにしておきたいわけです。

twilio-runを使おう

最初は自分でcallback関数を作ったり頑張ろうとしかけたんですが、GitHubを探したらこんな素晴らしいCLIツールを見つけました!車輪の再発明せずに済んでよかった。。
https://github.com/dkundel/twilio-run

以下、twilio-runを使った構築手順です。前提条件はこんな感じ。

  • Twilio電話番号が1つ取得済み
  • 開発デバイスはMac
  • HomebrewとNode.jsがインストール済み

Twilio Functionsの設定

一番シンプルな構成として、サンプルで用意されてる「Hello Voice」を使って新規Functionを作成します。

  • PATHは/hello
  • Check for valid Twilio signatureはチェック
  • EVENTはIncoming Voice Callsを選択

こいつをローカルにコピペしてそのまんま動かすのが今回のゴール。

ローカルの設定

ngrokインストール

brew cask install ngrok

プロジェクト作成

mkdir hello-twilio
cd hello-twilio
npm init -y

twilio-runインストール

npm install twilio-run --save-dev

functionsディレクトリ作成
twilio-runは、実行時に./functions配下の全コードを自動的にホスティングするので、必ずこの名前で作るのがポイントです。

mkdir functions

Functionのソースコード作成
./functions配下に作成します。先ほどTwilio上で作ったHello Voiceをコピペ!1

functions/hello.js
exports.handler = function(context, event, callback) {
    let twiml = new Twilio.twiml.VoiceResponse();
    twiml.say("Hello World");
    callback(null, twiml);
};

twilio-run起動
デフォルトはポート3000番でホスティングされます。
PATHはJSのファイル名helloがそのまんま使われます。

$ npx twilio-run
┌────────────────────────────────────┐
│                                    │
│   Twilio functions available at:   │
│   => http://localhost:3000/hello   │
│                                    │
│                                    │
│   ⚠ No assets directory found      │
│                                    │
└────────────────────────────────────┘

ngrok起動
ローカルのhttpサーバーを外部公開してくれます。
Forwardingのサブドメインは毎回変わるので、固定したければ有償プランが必要です。

$ ngrok http 3000

ngrok by @inconshreveable                                            (Ctrl+C to quit)

Session Status                online
Session Expires               7 hours, 59 minutes
Version                       2.2.8
Region                        United States (us)
Web Interface                 http://127.0.0.1:4040
Forwarding                    http://example.ngrok.io -> localhost:3000
Forwarding                    https://example.ngrok.io -> localhost:3000

ブラウザでhttpsの方にアクセスしてみましょう。
https://example.ngrok.io/hello

こんなXMLが表示されたら、ローカル側は準備完了!

Twilio電話番号を開発環境に向ける

電話をかけたらローカルのプログラムが動くようにTwilio側を設定します。「通話着信時」に「Webhook」として先ほどのURLを登録すればOK。

電話をかけてみよう

該当の番号に電話をかけると、英語で「Hello World」と聞こえますね!
こんな風に書き換えてtwilio-runを再起動すると、日本語でも喋ってくれますね!

functions/hello.js
exports.handler = function(context, event, callback) {
  let twiml = new Twilio.twiml.VoiceResponse();
  twiml.say("世界よこんにちは", { language: "ja-jp" });
  callback(null, twiml);
};

本番環境に反映

一通りローカルでの開発が完了したら、Functionsの設定画面で最初に作った「Hello World」コードの中身を、ローカルの「hello.js」の内容に置き換えて保存します。(何も変更しなくて良いのが最高!)

その上で、電話番号の設定画面をFunctionに向けます。

ローカルでスムーズに開発し、サーバーレスなTwilio Functionsで本番運用する体制がこれで整いました!めでたしめでたし。

おわりに

kintone hack 2018の戦友、高橋さんの記事が取っ掛かりとして大変参考になりました。Twilioについてググってヒットするのは、半分以上が高橋さんの記事ですねw エバンジェリスト流石っす!
TwilioのFunctionsを使ったサーバーレス受付電話システム

実際に僕が作ったプログラムは、3つのFunctionが連携する、もう少しだけ複雑なシステムです。近々そちらのコードも公開しますねー。

2019/04/09 公開しました!
解説記事を丁寧に書く時間がなかなか取れそうにないので、ドキュメント一切無しだけど晒してしまいますw
https://github.com/the-red/twilio-neighborhood-network

田舎のガラケーしか持っていないオッチャン達が使うには、「音声電話だけで100%完結するシステム」って超便利だなーと今回思いました。Twilioの面白さに目覚めることができたので、今後も色々と作ってみたいと思います
次のステップは、モチロンkintone連携だな!


  1. 僕はセミコロン無しでJavaScript書くのが好きなんですが、Twilio Functionsに貼り付けたときセミコロンが無いと「Missing semicolon」と警告が出ちゃって気持ち悪いので、Twilio向けのコードだけはPrettierでセミコロン付ける設定にしてます。