Masked Calling

このユースケースでは、プライベート音声通話ユースケースに記載されているアイデアを実装する方法を示します。VonageのNode Server SDKを使用して音声プロキシを構築し、仮想番号を使用して参加者の実際の電話番号を隠す方法について説明します。完全なソースコードはGitHubリポジトリでも入手可能です。

概要

2人のユーザーが自分のプライベートな電話番号を明かさずに、お互いに電話をかけられるようにしたい場合があります。

たとえば、ライドシェアリングサービスを運営している場合は、ユーザー同士が話し合って乗車時間や場所を調整できるようにする必要があります。しかし、プライバシーを保護する義務があるため、ユーザーの電話番号を伏せておく必要があります。また、ユーザーがサービスを使用せずにライドシェアを直接手配することは、ビジネス収益の損失という観点から、望ましくありません。

VonageのAPIを使用すると、その通話の参加者に対して、実際の番号を知られないようにするための一時的な番号を提供できます。発信者には、その通話中に使用する一時的な番号だけが表示されます。やり取りの必要がなくなったら、一時的な番号は無効になります。

GitHubリポジトリからソースコードをダウンロードできます。

準備

このユースケースを進めるためには、以下のものが必要になります。

コードリポジトリ

GitHubリポジトリを含むコードがある。

手順

アプリケーションを構築するには、次の手順を実行します。

構成

設定を含む.envファイルを作成する必要があります。作成方法については、GitHub Readmeをご覧ください。このユースケースを実行すると、APIキー、APIシークレット、アプリケーションID、デバッグモード、プロビジョニングされた番号などの変数に必要な値を構成ファイルに入力できます。

音声用APIアプリケーションを作成する

音声用APIアプリケーションはVonage構造です。記述しようとしているアプリケーションと混同しないようにしてください。代わりに、これはAPIを操作するために必要な認証および構成設定のための「コンテナ」となります。

Nexmo CLIを使用して音声用APIアプリケーションを作成できます。アプリケーションの名前と2つのWebhookエンドポイントのURLを指定する必要があります。1つ目は、仮想番号で着信コールを受信したときにVonageのAPIがリクエストを行うエンドポイントで、もう1つはAPIがイベントデータを投稿できるエンドポイントです。

次のNexmo CLIコマンドのドメイン名をngrokドメイン名(ngrokの実行方法)に置き換え、プロジェクトのルートディレクトリで実行します。

nexmo app:create "voice-proxy" --capabilities=voice --voice-answer-url=https://example.com/proxy-call--voice-event-url=https://example.com/event--keyfile=private.key

このコマンドは、認証情報を含むprivate.keyというファイルをダウンロードし、一意のアプリケーションIDを返します。このIDは、以降の手順で必要になるので、メモしておきます。

Webアプリケーションを作成する

このアプリケーションは、ルーティングにExpressのフレームワークを使用し、音声用APIの操作にVonage Node Server SDKを使用します。dotenvは、.envテキストファイルを使用してアプリケーションを設定できるようにするために使用されます。

server.jsでは、コードがアプリケーションの依存性を初期化し、Webサーバーを起動します。ルートハンドラはアプリケーションのホームページ(/)に実装されており、node server.jsを実行し、ブラウザでhttp://localhost:3000にアクセスすることで、サーバーが実行されているかどうかをテストできます。

"use strict";

const express = require('express');
const bodyParser = require('body-parser');

const app = express();
app.set('port', (process.env.PORT || 3000));
app.use(bodyParser.urlencoded({extended: false }));

const config = require(__dirname + '/../config');

const VoiceProxy = require('./VoiceProxy');
const voiceProxy = new VoiceProxy(config);

app.listen(app.get('port'),function() {
  console.log('VoiceProxy App listening on port', app.get('port'));
});

コードは、VoiceProxyクラスのオブジェクトをインスタンス化して、仮想番号に送信されたメッセージを目的の受信者の実際の番号にルーティングします。プロセスのプロキシ処理については、コールをプロキシするで説明していますが、現時点では、このクラスは次のステップで設定するAPIキーとシークレットを使用してVonage Server SDKを初期化することに注意してください。これにより、アプリケーションで音声通話を発信および着信できるようになります。

const VoiceProxy = function(config) {
  this.config = config;
  
  this.nexmo = new Nexmo({
      apiKey: this.config.VONAGE_API_KEY,
      apiSecret: this.config.VONAGE_API_SECRET
    },{
      debug: this.config.VONAGE_DEBUG
    });
  
  // Virtual Numbers to be assigned to UserA and UserB
  this.provisionedNumbers = [].concat(this.config.PROVISIONED_NUMBERS);
  
  // In progress conversations
  this.conversations = [];
};

仮想番号をプロビジョニングする

仮想番号は、アプリケーションユーザーから実際の電話番号を隠すために使用されます。

次のワークフロー図は、仮想番号のプロビジョニングおよび設定のプロセスを示しています。

ユーザーBユーザーAVonageアプリユーザーBユーザーAVonageアプリ初期化番号を検索見つかった番号番号のプロビジョニングプロビジョニングされた番号番号の設定設定された番号

仮想番号をプロビジョニングするには、条件を満たす使用可能な番号を検索します。たとえば、音声機能を持つ特定の国の電話番号は次のとおりです。

const Nexmo = require('nexmo');

/**
 * Create a new VoiceProxy
 */
const VoiceProxy = function(config) {
  this.config = config;

  this.nexmo = new Nexmo({
    apiKey: this.config.NEXMO_API_KEY,
    apiSecret: this.config.NEXMO_API_SECRET
  },{
    debug: this.config.NEXMO_DEBUG
  });

  // Virtual Numbers to be assigned to UserA and UserB
  this.provisionedNumbers = [].concat(this.config.PROVISIONED_NUMBERS);

  // In progress conversations
  this.conversations = [];
};

/**
 * Provision two virtual numbers. Would provision more in a real app.
 */
VoiceProxy.prototype.provisionVirtualNumbers = function() {
  // Buy a UK number with VOICE capabilities.
  // For this example we'll also get SMS so we can send them a text notification
  this.nexmo.number.search('GB', {features: 'VOICE,SMS'}, function(err, res) {
    if(err) {
      console.error(err);
    }
    else {
      const numbers = res.numbers;

      // For demo purposes:
      // - Assume that at least two numbers will be available
      // - Rent just two virtual numbers: one for each conversation participant
      this.rentNumber(numbers[0]);
      this.rentNumber(numbers[1]);
    }
  }.bind(this));
};

次に、必要な番号をレンタルして、アプリケーションに関連付けます。

注: 一部のタイプの番号では、レンタルするために住所が必要です。プログラムで番号を取得できない場合は、Dashboardにアクセスし、必要に応じて番号をレンタルすることができます。

アプリケーションに関連付けられた番号に関連するイベントが発生すると、Vonageはイベントに関する情報とともに、リクエストをWebhookエンドポイントに送信します。設定後、後で使用するために電話番号を保存します。

/**
 * Rent the given numbers
 */
VoiceProxy.prototype.rentNumber = function(number) {
  this.nexmo.number.buy(number.country, number.msisdn, function(err, res) {
    if(err) {
      console.error(err);
    }
    else {
      this.configureNumber(number);
    }
  }.bind(this));
};

/**
 * Configure the number to be associated with the Voice Proxy application.
 */
VoiceProxy.prototype.configureNumber = function(number) {
  const options = {
    voiceCallbackType: 'app',
    voiceCallbackValue: this.config.NEXMO_APP_ID,
  };
  this.nexmo.number.update(number.country, number.msisdn, options, function(err, res) {
    if(err) {
      console.error(err);
    }
    else {
      this.provisionedNumbers.push(number);
    }
  }.bind(this));
};

仮想番号をプロビジョニングするには、ブラウザでhttp://localhost:3000/numbers/provisionにアクセスしてください。

これで、ユーザー間の通信を隠すのに必要な仮想番号が設定されました。

注: 本番アプリケーションでは、仮想番号のプールから選択します。ただし、この機能をそのまま使用して、追加の番号をレンタルする必要があります。

コールを作成する

コールを作成するワークフローは次のとおりです。

ユーザーBユーザーAVonageアプリユーザーBユーザーAVonageアプリ会話の開始基本的なNumber InsightNumber Insightの応答各参加者の実際の番号/仮想番号のマッピングユーザーAへのSMSSMSユーザーBへのSMSSMS

次のコールは以下を行います。

/**
 * Create a new tracked conversation so there is a real/virtual mapping of numbers.
 */
VoiceProxy.prototype.createConversation = function(userANumber, userBNumber, cb) {
  this.checkNumbers(userANumber, userBNumber)
    .then(this.saveConversation.bind(this))
    .then(this.sendSMS.bind(this))
    .then(function(conversation) {
      cb(null, conversation);
    })
    .catch(function(err) {
      cb(err);
    });
};

電話番号を検証する

アプリケーションユーザーが電話番号を入力したら、Number Insightを使用して有効であることを確認します。電話番号が登録されている国も確認できます。

/**
 * Ensure the given numbers are valid and which country they are associated with.
 */
VoiceProxy.prototype.checkNumbers = function(userANumber, userBNumber) {
  const niGetPromise = (number) => new Promise ((resolve) => {
    this.nexmo.numberInsight.get(number, (error, result) => {
      if(error) {
        console.error('error',error);
      }
      else {
        return resolve(result);
      }
    })
  });

  const userAGet = niGetPromise({level: 'basic', number: userANumber});
  const userBGet = niGetPromise({level: 'basic', number: userBNumber});

  return Promise.all([userAGet, userBGet]);
};

電話番号を実際の番号にマッピングする

電話番号が有効であることが確認できたら、実際の番号を仮想番号にマッピングし、コールを保存します。

/**
 * Store the conversation information.
 */
VoiceProxy.prototype.saveConversation = function(results) {
  let userAResult = results[0];
  let userANumber = {
    msisdn: userAResult.international_format_number,
    country: userAResult.country_code
  };

  let userBResult = results[1];
  let userBNumber = {
    msisdn: userBResult.international_format_number,
    country: userBResult.country_code
  };

  // Create conversation object - for demo purposes:
  // - Use first indexed LVN for user A
  // - Use second indexed LVN for user B
  let conversation = {
    userA: {
      realNumber: userANumber,
      virtualNumber: this.provisionedNumbers[0]
    },
    userB: {
      realNumber: userBNumber,
      virtualNumber: this.provisionedNumbers[1]
    }
  };

  this.conversations.push(conversation);

  return conversation;
};

確認のSMSを送信する

プライベート通信システムでは、あるユーザーが別のユーザーに連絡すると、発信者は自分の電話から仮想番号を呼び出します。

SMSを送信し、呼び出す必要がある仮想番号を、会話の参加者に通知します。

/**
 * Send an SMS to each conversation participant so they know each other's
 * virtual number and can call either other via the proxy.
 */
VoiceProxy.prototype.sendSMS = function(conversation) {
  // Send UserA conversation information
  // From the UserB virtual number
  // To the UserA real number
  this.nexmo.message.sendSms(conversation.userB.virtualNumber.msisdn,
                             conversation.userA.realNumber.msisdn,
                             'Call this number to talk to UserB');

  // Send UserB conversation information
  // From the UserA virtual number
  // To the UserB real number
  this.nexmo.message.sendSms(conversation.userA.virtualNumber.msisdn,
                             conversation.userB.realNumber.msisdn,
                             'Call this number to talk to UserB');

  return conversation;
};

ユーザーはお互いにSMSをすることはできません。この機能を有効にするには、プライベートSMS通信を設定する必要があります。

:このユースケースでは、各ユーザーはSMS経由で仮想番号を受け取っています。他のシステムでは、メール、アプリ内通知、または事前定義された番号を使用して通知できます。

着信コールを処理する

Vonageは、仮想番号への着信コールを受信すると、音声アプリケーションの作成時に設定したWebhookエンドポイントにリクエストを送信します。

ユーザーBユーザーAVonageアプリユーザーBユーザーAVonageアプリユーザーAはユーザーBのVonage番号を呼び出します仮想番号を呼び出しますInbound Call(from, to)

着信Webhookからtofromを抽出し、音声プロキシビジネスロジックに渡します。

app.get('/proxy-call', function(req, res) {
  const from = req.query.from;
const to = req.query.to;

const ncco = voiceProxy.getProxyNCCO(from,to);
  res.json(ncco);
});

実際の電話番号を仮想番号に逆マッピングする

これで、コールを発信する電話番号と受信者の仮想番号が分かったので、着信仮想番号を、発信する実際の電話番号に逆マッピングします。

ユーザーBユーザーAVonageアプリユーザーBユーザーAVonageアプリユーザーBの実際の番号を検索番号マッピングルックアップ

コールの方向は次のように識別できます。

  • from番号はユーザーAの実際の番号で、to番号はユーザーBのVonage番号です。
  • from番号はユーザーBの実際の番号で、to番号はユーザーAのVonage番号です。
const fromUserAToUserB = function(from, to, conversation) {
  return (from === conversation.userA.realNumber.msisdn &&
          to === conversation.userB.virtualNumber.msisdn);
};
const fromUserBToUserA = function(from, to, conversation) {
  return (from === conversation.userB.realNumber.msisdn &&
          to === conversation.userA.virtualNumber.msisdn);
};

/**
 * Work out real number to virtual number mapping between users.
 */
VoiceProxy.prototype.getProxyRoute = function(from, to) {
  let proxyRoute = null;
  let conversation;
  for(let i = 0, l = this.conversations.length; i < l; ++i) {
    conversation = this.conversations[i];

    // Use to and from to determine the conversation
    const fromUserA = fromUserAToUserB(from, to, conversation);
    const fromUserB = fromUserBToUserA(from, to, conversation);

    if(fromUserA || fromUserB) {
      proxyRoute = {
        conversation: conversation,
        to: fromUserA? conversation.userB : conversation.userA,
        from: fromUserA? conversation.userA : conversation.userB
      };
      break;
    }
  }

  return proxyRoute;
};

番号検索を実行したら、あとはコールをプロキシするだけです。

コールをプロキシする

仮想番号が関連付けられている電話番号にコールをプロキシします。from番号は常に仮想番号で、toは実際の電話番号です。

ユーザーBユーザーAVonageアプリユーザーBユーザーAVonageアプリユーザーの実際の番号へのプロキシ着信コールユーザーAがユーザーBに電話をかけた。ただしユーザーAとユーザーBは互いに実際の番号を知らない。接続(プロキシ)コール

これを行うには、NCCO(Nexmo Call Control Object)を作成します。このNCCOでは、talkアクションを使用してテキストを読み上げます。talkが完了すると、connectアクションはコールを実際の番号に転送します。

/**
 * Build the NCCO response to instruct Nexmo how to handle the inbound call.
 */
VoiceProxy.prototype.getProxyNCCO = function(from, to) {
  // Determine how the call should be routed
  const proxyRoute = this.getProxyRoute(from, to);

  if(proxyRoute === null) {
    const errorText = 'No conversation found' +
                    ' from: ' + from +
                    ' to: ' + to;
    throw new Error(errorText);
  }

  // Build the NCCO
  let ncco = [];

  const textAction = {
    action: 'talk',
    text: 'Please wait whilst we connect your call'
  };
  ncco.push(textAction);

  const connectAction = {
    action: 'connect',
    from: proxyRoute.from.virtualNumber.msisdn,
    endpoint: [{
      type: 'phone',
      number: proxyRoute.to.realNumber.msisdn
    }]
  };
  ncco.push(connectAction);

  return ncco;
};

NCCOは、WebサーバーによってVonageに返されます。

app.get('/proxy-call', function(req, res) {
  const from = req.query.from;
const to = req.query.to;

const ncco = voiceProxy.getProxyNCCO(from,to);
  res.json(ncco);
});

まとめ

プライベート通信用の音声プロキシを構築する方法を学びました。電話番号のプロビジョニングと設定、Number Insightの実行、匿名性を確保するための実際の番号の仮想番号へのマッピング、着信コールの処理および別のユーザーへのコールのプロキシを行いました。

詳細情報