Twilioブログ

ローコード開発ツール『FileMaker』でICTシステムを作る方法

こんにちは、Twilioマーケチームのkatsu.tです。

事業のオンライン化が進んでいる昨今、ITを活用したビジネスモデルの見直しなど、ICT推進を行うことで進化を遂げている企業様を多く目にします。

ビジネスにおいて「電話」というツールは必要不可欠です。しかしそこにある『コミュニケーション』という、ビジネス上の重要な情報源の多くはデジタル化されていません。
その背景には多額の設備投資が必要であるといった、電話のデジタル化を行うハードルの高さがあります。

そこで本記事では、DX加速を支援するローコード開発ツール『FileMaker』とTwilioを使って、ICTシステムを構築する方法をご紹介いたします。

FileMakerとは

Claris FileMakerはノーコード・ローコードで業務アプリケーションを素早く開発し、チームで運用するためのプラットフォームです。

iOSやWebとの高い親和性のほか、クラウドとオンプレミスから選択可能なサーバー環境など、創業35年という長い歴史の中で培った信頼性と豊富な機能が大きな特長です。

https://www.claris.com/ja/

FileMakerとTwilioの連携方法

Twilioは初期費用や継続的な設備保守費用などが一切かからない、月額数百円という低コストで始められるAPIサービスです。
このTwilioとFileMakerを連携させることで、ICT環境を素早く構築できます。

本記事では以下機能を保有したアプリケーションを作成します。

  • 通話
    • FileMakerと電話間で通話できる機能
  • 発信
    • FileMakerに設置されているボタンをクリックすると、保存されている電話番号に発信する機能
    • 任意の電話番号を入力して発信する機能
  • 着信
    • FileMakerに着信し、通話する機能

事前準備

まずはそれぞれのアカウントと電話番号を取得してください。

※FileMaker ProのWebビューアはWeb RTCに対応していないため、本記事ではFileMaker WebDirectを利用します。
※FileMaker WebDirectが利用できる環境をご用意ください。

1.FileMakerの新規アプリケーション作成

始めにFileMaker Proを使って、新規アプリケーションを作成します。
「作成 > 新規」の順でクリックして任意の名前をつけ、新規アプリケーションを作成します。
※WebDirectを利用するため、事前にFileMaker Cloudにアプリケーションを作成する設定を行っています。

2.データベースの作成

次にデータベースを作成します。

本記事では、下記二つのデータベースを作成します。

  • 顧客情報を保存するデータベース
  • Twilioと連携するためのHTML、JavaScriptを保存するデータベース

①テーブルの作成

管理 > データベースをクリックして、データベース設定を開きます。

次に、デフォルトで作成されているテーブルを削除します。

”users”テーブルと”html”テーブルを作成します。

②フィールドの追加

テーブルが作成できたら、それぞれにフィールドを追加します。
htmlテーブルには”id”フィールドと"html"フィールドを追加します。

usersテーブルには”html_id”フィールドと”phone_number”フィールドを追加します。

③リレーションシップの作成

削除したデフォルトのテーブルは、リレーションシップから削除します。

usersテーブルのhtml_idとhtmlテーブルのidのリレーションを作成します。

以上でデータベースの作成は終了です。

3.レイアウトを作成

Twilioと連携して電話を受ける画面を作成します。

レイアウトの管理を開き、”レイアウト”を選択して「開く」をクリックします。

①main画面の設定

レイアウト設定画面が開いたら、レイアウト名を”main”に変更し、レコードを表示の部分を”html”に変更します。

②Webビューアの作成

Webビューアを選択し、任意の大きさで画面に配置します。

設置したWebビューアをダブルクリックし、設定画面を開きます。
画面が開いたら”設定”をクリックして計算式の設定を開き、”html::html”と入力しOKをクリックします。

値がセットされたら、OKをクリックしてWebビューアの設定を閉じます。

HTMLの表示設定ができたら、オブジェクトに任意の名前をつけておきます。

③ポータルツールの作成

ユーザー情報を表示するためにポータルツールを作成します。
ポータルツールのアイコンをクリックし、任意の大きさで画面に配置します。

ポータルツールが配置できたら、ポータル設定を開きレコードを表示に”users”を選択します。

続いて、ポータルにフィールドを追加します。
今回は電話番号が表示されればよいため、phone_numberをフィールドに含みます。

FileMakerから電話をワンクリックで発信するためのボタンを配置します。
先に、電話番号表示部分を少し小さくします。

続いて、開いたスペースにボタンを配置し、任意の表示テキストを入力します。

④電話発信用スクリプト作成

電話発信用のスクリプトを作成していきます。
「スクリプト > スクリプトワークスペース」を選択して、ワークスペースを開きます。

開けたら、画面左上の”+”ボタンをクリックして、新規スクリプトを作成します。

今回は”outgoing”という名前でスクリプトを作成します。

スクリプトの一行目に”WebビューアでJavaScriptを実行”を実装し、歯車マークをクリックします。

引数を設定します。

  • オブジェクト名:browser-phone
  • 関数名:outgoing
  • 引数:users::phone_number

OKをクリックして変更を保存したら、再びレイアウトの編集に戻り、発信ボタンの処理を設定します。

  • 処理:スクリプト実行
  • 開始:outgoing

4.Twilioの設定

Webブラウザで電話をかける時、Twilioでは下記のような流れで電話を発信します。
Client Connection Overview

また上記図の”1.You connect to Twilio”の前段に、Twilioとコネクションを貼るためのトークンを発行する処理が必要です。

ここではTokenの発行、ブラウザフォンから発信した際の挙動、そしてTwilioに着信があった際にブラウザフォンに転送する挙動の実装方法をご紹介いたします。

①Twilio Functionsの設定

TwilioではNode.jsが利用できる、Twilio Functionsというサーバーレス環境を提供しています。
本記事ではバックエンドのインフラ部分としてTwilio Functionsを利用するため、その準備を行っていきます。

Twilio Functionsはアプリケーションの環境を分けるためにServiceという概念を持っています。
基本的にはひとつのアプリケーションにひとつのServiceを作ります。

サイドバーの「・・・」メニューをクリックし、プロダクト一覧の下の方にあるFunctionsをクリックします

画面が開いたら”Create Service”をクリックします。

Service Nameに任意の名前(英数字)を入力し、Nextをクリックします。

②ブラウザフォンから発信する際の挙動を実装

Serviceが作成できたら、ブラウザフォンから発信する処理を実装します。
画面左上の”Add+”をクリックして、”Add Function”を選択します。
Functionが追加できたら、任意の名前でパスを入力して、Enter(macはReturn)キーを押します。

続いて、発信の処理を実装していきます。
下記コードを貼り付けて、”Save”をクリックします。

exports.handler = function(context, event, callback) {
    let twiml = new Twilio.twiml.VoiceResponse();

    if(event.To) {
      const attr = isAValidPhoneNumber(event.To) ? 'number' : 'client';

      const dial = twiml.dial({
        answerOnBridge: true,
        callerId: "+815012345678", // Twilioで購入した番号に置き換えてください。
      });
      dial[attr]({}, event.To);
    } else {
      twiml.say({
          voice: 'alice',
          language: 'ja-JP'
      }, '発信先が入力されていません。発信内容をご確認ください。');

    }

     callback(null, twiml);
};

function isAValidPhoneNumber(number) {
  return /^[\d\+\-\(\) ]+$/.test(number);
}

Saveできたら「・・・」メニューをクリックして”Copy URL”を選択し、URLをコピーします。

CopyしたURLを元にTwiML APPsを作成していきます。
「・・・」メニューをクリックし、プロダクト一覧の中から、Programmable Voiceを選択します。

画面が開いたら、TwiML → TwiML Appsの順番に選択します。

続いて、”Create new TwiML App”を選択します。
※すでにTwiML Appを作成したことがある方は「+」ボタンをクリックします。

FRIENDLY NAMEには任意の名前、VoiceのREQUEST URLには先ほどコピーしたURLを貼り付けてCreateをクリックします。

一覧画面から作成したTwiML Appをクリックして、TwiML App画面を開くとSIDが発行されているので、こちらをコピーしておきます。

③Tokenの発行処理を実装

続いて、ブラウザフォンがTwilioとコネクションを貼るために利用するTokenを発行する処理を実装していきます。

作成したServiceの画面を再び開き、Functionを作成します。

Functionができたら、下記コードを貼り付けてSaveをクリックします。

exports.handler = function(context, event, callback) {
  
  let response = new Twilio.Response();

  // Add CORS Headers
  let headers = {
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Methods": "GET",
    "Content-Type": "application/json"
  };
    
  // Set headers in response
  response.setHeaders(headers);
  
  response.setStatusCode(200);
  
  let ClientCapability = require('twilio').jwt.ClientCapability;

  const identity = event.identity;
  const capability = new ClientCapability({
    accountSid: context.ACCOUNT_SID,
    authToken: context.AUTH_TOKEN,
  });

// 着信時にブラウザフォンを識別する時に利用するIdを設定 capability.addScope(new ClientCapability.IncomingClientScope(identity));
// ブラウザフォンから発信する際の処理を指定 capability.addScope(new ClientCapability.OutgoingClientScope({ applicationSid: 'APXXXXXXXXXXXXXXXX', // 作成したTwiML AppのSIDに置き換えてください。 clientName: identity, })); // Include identity and token in a JSON response response.setBody({ 'identity': identity, 'token': capability.toJwt() }); callback(null, response); };

tokenの横のProtectedをクリックして、Publicを選択します。

/tokenのURLはFileMakerにブラウザフォンを実装する際に利用するため、メモを取っておきます。

④着信時の処理を実装

最後に、Twilioに着信後ブラウザフォンに通話を転送する実装を行います。
”Add+”をクリックしてAdd Functionを選択し、”incoming”の名前でFunctionを作成します。

Functionが作成できたら、下記ソースコードを転記し、Saveをクリックします。

exports.handler = function(context, event, callback) {
  const response = new Twilio.twiml.VoiceResponse();
  const dial = response.dial();
  const client = dial.client();
  client.identity('userName'); // 転送先のブラウザフォンにログインしているユーザー名を入れます。

  return callback(null, response);
};

ここまで実装できたらFunctionをDeployします。
画面下の”Deploy All”をクリックしてください。

⑤着信時の処理を電話番号に設定

購入した電話番号の設定画面を開き、”A CALL COMES IN”にFunctionを選択して、FUNCTION PATHに”/incoming”を設定します。

※まだ電話番号を購入していない方は、電話番号の購入方法をご確認いただき、電話番号をご購入ください。
※ブラウザフォンに着信機能をつけない場合は、こちらの設定は必要ありません。
※”/incoming”が見つからない方は一つ前の手順の”Deploy All”を再度行ってみてください。

5.FileMakerにブラウザフォンを実装

ここまできたら、完了までもう少しです。

5.1.FileMakerにブラウザフォンを実装

配置したWebビューアにブラウザフォンを実装していきます。
レイアウトをクリックして、htmlを選択します。

”新規レコード”をクリックして、HTML用の新規レコードを追加します。

idに”1”、htmlに下記コードを入力してください。
※途中の'https://xxxxxxx.twil.io/token?identity='は手順4.3でコピーしたものに置き換えてください。

data:text/html,<!DOCTYPE html>
<html lang="ja">
    <head>
        <title>twilio phone</title>
        <script src="https://media.twiliocdn.com/sdk/js/client/v1.13/twilio.min.js"></script>
    </head>
    <body>
        <div>
            <input type="text" id="identity" hidden>
            <button id="connect" hidden onclick="connect()">接続</button>
            <p id="display-name"></p>
            <input type="text" id='phone-number' hidden>
            <button id="outgoing" hidden onclick="makeCall()">発信</button>
            <button id="hangup" hidden onclick="hangup()">切断</button>
            <button id="reject" hidden onclick="reject()">拒否</button>
            <button id="accept" hidden onclick="accept()">接続</button>
        </div>
        <div>
            <p id="status"></p>
        </div>

        <script>
            const device = new Twilio.Device;
            var request = new XMLHttpRequest();
            var connectObj = null;

            connect();

            // Twilioに接続
            function connect() {
                var identity = "userName";
                // Twilioセットアップ
                request.open('GET', 'https://xxxxxxx.twil.io/token?identity=' + identity, true); // '?identity='より前の部分を手順4.3で作成したURLに変更します

                request.onload = function (e) {
                    var data = JSON.parse(this.response);
                    var option = {
                        edge: 'tokyo'
                    }
                    device.setup(data.token, option);
                    updateMessageByElement('display-name', data.identity);

                    device.on('ready', function (device) {
                        showReadyDiaplay();
                        updateMessageByElement('status', 'Twilio.Device Ready!');
                    });

                    device.on('error', function (error) {
                        showDefaultDiaplay();
                        updateMessageByElement('status', 'Twilio.Device Error: ' + error.message);
                    });

                    device.on('connect', function (conn) {
                        showOutgoingDisplay();
                        updateMessageByElement('status', 'Successfully established call!');
                    });

                    device.on('disconnect', function (conn) {
                        showReadyDiaplay();
                        updateMessageByElement('status', 'Call ended.');
                    });

                    device.on('incoming', function (conn) {
                        updateMessageByElement('status', 'Incoming connection from ' + conn.parameters.From);
                        connectObj = conn;
                        showIncommingDiaplay();
                    });
                };

                request.onerror = function () {
                    showDefaultDiaplay();
                    updateMessageByElement('status', 'Twilio.Device Error: Token取得先URLをご確認ください');
                };

                request.send();
            }

            // 発信ボタン押下
            function makeCall() {
                var params = {
                    To: document.getElementById('phone-number').value,
                    From: document.getElementById('display-name').textContent
                };

                if (device) {
                    updateMessageByElement('status', 'Calling ' + params.To + '...');
                    showOutgoingDisplay();
                    var outgoingConnection = device.connect(params);
                    outgoingConnection.on('ringing', function () {
                        console.log('Ringing...');
                    });
                }
            }

            function outgoing(phoneNumber) {
                var params = {
                    To: '+81' + phoneNumber.substr(1),
                    From: document.getElementById('display-name').textContent
                };

                if (device) {
                    updateMessageByElement('status', 'Calling ' + params.To + '...');
                    showOutgoingDisplay();
                    var outgoingConnection = device.connect(params);
                    outgoingConnection.on('ringing', function () {
                        console.log('Ringing...');
                    });
                }
            }

            // 接続ボタン押下
            function hangup() {
                updateMessageByElement('status', 'Hanging up...');
                if (device) {
                    device.disconnectAll();
                }
            }

            // 拒否ボタン押下
            function reject() {
                if (connectObj != null) {
                    connectObj.reject();
                    showReadyDiaplay();
                }
            }

            // 接続ボタン押下
            function accept() {
                if (connectObj != null) {
                    connectObj.accept();
                    showOutgoingDisplay();
                }
            }

            // 画面更新用メソッド
            function updateMessageByElement(elementId, message) {
                document.getElementById(elementId).innerHTML = message;
            }

            function showReadyDiaplay() {
                updateShowOrHideByElement('identity', 'hide');
                updateShowOrHideByElement('connect', 'hide');
                updateShowOrHideByElement('display-name', 'show');
                updateShowOrHideByElement('outgoing', 'show');
                updateShowOrHideByElement('phone-number', 'show');
                updateShowOrHideByElement('hangup', 'hide');
                updateShowOrHideByElement('reject', 'hide');
                updateShowOrHideByElement('accept', 'hide');
            }

            function showDefaultDiaplay() {
                updateShowOrHideByElement('identity', 'show');
                updateShowOrHideByElement('connect', 'show');
                updateShowOrHideByElement('display-name', 'hide');
                updateShowOrHideByElement('outgoing', 'hide');
                updateShowOrHideByElement('phone-number', 'hide');
                updateShowOrHideByElement('hangup', 'hide');
                updateShowOrHideByElement('reject', 'hide');
                updateShowOrHideByElement('accept', 'hide');
            }

            function showOutgoingDisplay() {
                updateShowOrHideByElement('identity', 'hide');
                updateShowOrHideByElement('display-name', 'show');
                updateShowOrHideByElement('connect', 'hide');
                updateShowOrHideByElement('outgoing', 'hide');
                updateShowOrHideByElement('phone-number', 'show');
                updateShowOrHideByElement('hangup', 'show');
                updateShowOrHideByElement('reject', 'hide');
                updateShowOrHideByElement('accept', 'hide');
            }

            function showIncommingDiaplay() {
                updateShowOrHideByElement('identity', 'hide');
                updateShowOrHideByElement('display-name', 'show');
                updateShowOrHideByElement('connect', 'hide');
                updateShowOrHideByElement('outgoing', 'hide');
                updateShowOrHideByElement('phone-number', 'show');
                updateShowOrHideByElement('hangup', 'hide');
                updateShowOrHideByElement('reject', 'show');
                updateShowOrHideByElement('accept', 'show');
            }

            function updateShowOrHideByElement(elementId, displayStatus) {
                if (displayStatus == 'show') {
                    document.getElementById(elementId).style.display = "block";
                } else {
                    document.getElementById(elementId).style.display = "none";
                }
            }
        </script>

    </body>
</html>

以上で実装は完了です!
実装が完了したら、発着信をテストしてみましょう!

6.発着信を確認する

実装が完了したら、発着信を確認します。
usersレイアウトを開き、新規レコードを追加します。
レコードが追加できたら以下値を入力します。

  • html_id:1
  • phone_number:通話できる電話番号

レコードが作成できたらClaris FileMaker Cloudへログインし、FileMaker WebDirectでアプリを開きます。

画面が開いたら、電話番号横の電話番号かテキストフィールドに”E.164形式”(先頭0を取り、国番号を含む電話番号。例:+819012345678)を入力して、発信をお試しください。

うまくいかない時の確認ポイント

  • Dial: Invalid callerId value
    • 手順4.2のcallerIdにてTwilioで購入した電話番号に置き換えられているかご確認ください。
  • 下記エラーが表示
    • 手順5.1のURLの置き換えが置き換えられているかご確認ください
    Access to XMLHttpRequest at 'https://xxxxxxx.twil.io/token?identity=userName' from origin 'https://kddi-web-testing.account.filemaker-cloud.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

 

まとめ

「今まで顧客管理をしていなかった」「顧客情報をExcelで管理していた」といった方も、FileMakerを利用することで顧客情報を簡単にデータベース化できます。またTwilioと組み合わせることで、さらにFileMakerの利用の幅を広げることが可能です。
業務効率化や顧客満足度の向上を目指して、ぜひFileMakerとTwilioを組み合わせたICTシステムの構築をご検討してみてくださいね。

アプリケーションエンジニア 葛 智紀
アプリケーションエンジニア 葛 智紀

前職でiOS、Androidのネイティブアプリケーション開発、AngularやLaravelを用いたウェブアプリケーション開発に従事。KDDIウェブコミュニケーションズではTwilioの最新情報の発信やTwilioを用いた地域課題解決を担当。 個人では、Google Developer Group Tokyoのオーガナイザーを務める。

CTA_もっと詳しく知りたい方へ

Share!!

この記事を読んだ人へのオススメ

  • お役立ち情報
  • イベント情報
  • 相談会申込
  • 導入事例