https://d226lax1qjow5r.cloudfront.net/blog/blogposts/javascript-hotline-with-opentok-and-nodejs-dr/Building-a-JavaScript-Hotline-with-OpenTok-and-Node.js.png

OpenTokとNode.jsでJavaScriptホットラインを構築する

最終更新日 April 27, 2021

所要時間:3 分

むかしむかし、JavaScriptについて質問があると、IRC(インターネット・リレー・チャット)に行って答えてくれる人を探したものだ。しかし、IRCはインターネット時代にはとても古い技術です。最近のJavaScript開発者の多くは、IRCクライアントすら持っていないかもしれないし、使ったこともないかもしれない。一方、ほとんどの人はビデオチャットに精通している。

オープントークはすでに 簡単なビデオチャットですぐに始められます。ちょっとしたキュー・ロジックを追加すれば、JavaScriptの質問やその他何でも、すぐにホットラインを持つことができます。

あなたのホットラインをよりフレンドリーなものにするために、次のようなプロジェクトを構築することができます。 グリッチ.これにより、セットアップの手間が省けます。また、ホットラインを さらにまた、他の人が自分のホットラインのためにリミックスできるように完全なプロジェクトを提供することで、あなたのホットラインをより役立つものにします。

グリッチ入門

動くプロジェクトに飛びたいなら、Glitchの JavaScriptホットライン・プロジェクトをすぐにリミックスできる。そうでなければ、ほんの数ステップでゼロからあなた自身のホットラインをコーディングすることができます。まず、Glitchで新しいプロジェクトを作成し、テンプレートから hello-expressテンプレートを選択します。

OpenTok.Videoでビデオチャットを提供するために必要な唯一の追加パッケージです、 opentokが唯一の追加パッケージです。誰かが質問に答える必要があるときにテキストを受け取るオプションを追加することで、ホットラインはユーザー数の変化に対応できるようになります。それをサポートするために body-parserをインストールすることもできます。 nexmoをインストールしてテキストを送信することもできます:

pnpm install opentok body-parser nexmo -s

例を再利用することができます。 server.js, index.htmlおよび client.jsファイルを再利用できる。つまり、セットアップはほぼ完了したことになる。

環境変数の供給

あなたの .envファイルは次のようになる:

OPENTOK_API_KEY="12345678" OPENTOK_SECRET="12a3b4c567d89e0f1234567890ab12345678c901" NEXMO_API_KEY="12ab3456" NEXMO_API_SECRET="123AbcdefghIJklM" FROM_PHONE="441234567890"

これらの変数に実際の値を提供するには、OpenTokとNexmoの両方の開発者アカウントが必要です。また、OpenTokのプロジェクトとNexmoのバーチャル番号も必要です。

あなたの OpenTok アカウントダッシュボード内でで、"OpenTok API" プロジェクトタイプでホットライン用の新しいプロジェクトを作成します。名前をつけ、Video コーデック(VP8 で大丈夫です)を選択すると、API キーとシークレットが表示されます。これらを OPENTOK_API_KEYOPENTOK_SECRETにそれぞれ貼り付けます。

あなたのNexmo認証情報を NEXMO_API_KEYNEXMO_API_SECRETに貼り付けてください。 Nexmoダッシュボード.の設定なしで、"[Your Numbers](${CUSTOMER_DASHBOARD_URL}/your-numbers" から任意の電話番号を使用することができます。 FROM_PHONEこの番号からテキストを送信しますが、テキストや通話は受信しません。

サーバーのセットアップ

サーバーにはすでに、いくつかの初期化、アプリケーションルート用のビュールート、サーバーを起動するリスナーが含まれています。初期化セクションを少し修正して body-parserミドルウェアも使うように初期化セクションを少し修正できます:

const express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.use(express.static('public'));
app.use(bodyParser.json());

このブロックの下に、OpenTokオブジェクトとNexmoオブジェクトを追加します。 .envと2つの空の配列を追加します。 waitingはヘルパーが質問に答えるのを待っているチャットのセッションID、そして helpersは誰も質問していないダウンタイムにヘルプを提供した人の電話番号です。

const OpenTok = require('opentok');
const opentok = new OpenTok(process.env.OPENTOK_API_KEY, process.env.OPENTOK_SECRET);

const Nexmo = require('nexmo');
const nexmo = new Nexmo({
  apiKey: process.env.NEXMO_API_KEY,
  apiSecret: process.env.NEXMO_API_SECRET
});

var waiting = [];
var helpers = [];

ファイルの残りの部分では、デフォルトルートとリスナーの間で、他のルートを宣言できます。それらの下に、質問に答えることを申し出た誰かにテキストを送る関数が必要です:

app.get('/', function(request, response) {
  response.sendFile(__dirname + '/views/index.html');
});

app.get('/ask', function(request, response) {});

app.get('/answer', function(request, response) {});

app.get('/answer/:sessionId', function(request, response) {});

app.post('/text', function(request, response) {});

function textHelper(sessionId) {}

const listener = app.listen(process.env.PORT, function() {
  console.log('Your app is listening on port ' + listener.address().port);
});

質疑応答に参加者を加える

ホットラインアプリケーションの最も基本的な機能は、質問をする人と、その質問に答えてくれる人をつなぐことです。これを実現する最も直接的な方法は、誰かが何かを質問したいと宣言したときにVideoチャットセッションを作成することです。そして、次に回答可能な人を2番目の参加者として追加することができます。データストアを使用してこれらのアクティブなセッションを処理する、より堅牢な方法を作成することもできますが、テスト目的であれば配列で十分です。

誰かが質問をしたら、新しいOpenTokセッションを作成し、そのIDを配列に格納することができます。 waiting配列に格納します。そして、新しいID、アプリケーションのOpenTok APIキー、特定のクライアントを識別するトークンを応答することができます:

app.get('/ask', function(request, response) {
  opentok.createSession(function(err, session) {
    let sessionId = session.sessionId;
    waiting.push(sessionId);
    
    response.send({
      apiKey: process.env.OPENTOK_API_KEY,
      sessionId: sessionId,
      token: opentok.generateToken(sessionId)
    });
    
    if (helpers.length) {
      textHelper(sessionId);
    }
  });
});

最終段階として /askルートでは、最終段階として helpers配列の長さをチェックする。その中にアイテムがあれば textHelper関数を呼び出します。を呼び出します。 textHelperについては後述しますが、現在アプリを使用している人だけを接続することで、ホットラインをシンプルにしたい場合は、この条件全体を削除できます。

これで、誰かが質問に答えたいと申し出ると、キュー内の最初のセッションの値を持つ同様のレスポンスオブジェクトを送信できます。 waitingキューにあります。もし空であれば、クライアントに今は誰も助けを求めていないことを示すレスポンスを送ることができます:

app.get('/answer', function(request, response) {
  if (waiting.length) {
    let sessionId = waiting.shift();
    response.send({
      apiKey: process.env.OPENTOK_API_KEY,
      sessionId: sessionId,
      token: opentok.generateToken(sessionId)
    });
  } else {
    response.send({
      wait: true
    });
  }
});

ヘルパーに質問されたらメールを送る

既存のワークフローが少し複雑になりますが、ヘルパー候補が電話番号を提供し、新しい質問セッションの準備ができたときにテキストを受け取れるようにすることで、ホットラインが遅い時間帯にも対応できるようになります。

誰かの電話番号を保存するのは、ほんの数行のコードだけだ。この /textエンドポイントはリクエストボディで電話番号を受け取り、それを helpersキューに追加できる。そして "OK "ステータスを返す:

app.post('/text', function(request, response) {
  let phone = request.body.phone;
  helpers.push(phone);
  response.sendStatus(200);
});

これで、ヘルパーが保存した電話番号を textHelper関数で利用できるようになりました。 /askルートによって呼び出される関数で、ヘルパーの保存した電話番号を利用できるようになりました。この関数は最初の電話番号を helpersから最初の電話番号を取得し、特定のビデオチャットセッションへのリンクをテキストで送信します:

function textHelper(sessionId) {
  let phone = helpers.shift();
  
  nexmo.message.sendSms(
    process.env.FROM_PHONE,
    phone,
    'JavaScript question for you! Caller is waiting at: https://' + process.env.PROJECT_DOMAIN + '.glitch.me/?id=' + sessionId
  );
}

誰かがテキストで送られたセッションリンクをたどると、クライアントはサーバからその特定のセッションに参加するための認証情報を要求することができる。実際にセッションがリクエストされると、アプリケーションは安全に waitingキューから安全に削除することができます:

app.get('/answer/:sessionId', function(request, response) {
  let sessionId = request.params.sessionId;
  let index = waiting.indexOf(sessionId);
  waiting.splice(index, 1);
  response.send({
    apiKey: process.env.OPENTOK_API_KEY,
    sessionId: sessionId,
    token: opentok.generateToken(sessionId)
  });
});

インターフェイスの追加

テストのためには、すべてのHTMLを1つのページにまとめておくのが一番簡単です。例の index.htmlはすでに client.js.このスクリプト・タグの上に、OpenTokのサーバからOpenTokクライアント・ライブラリをインポートします:

<script src="https://static.opentok.com/v2/js/opentok.min.js"></script>

タグの内容を3つのブロックに置き換えることができます。 <body>タグの内容を3つのブロックに置き換えることができます:質問または回答のプロセスを開始するためのボタン、テキストを受け取りたい人の電話番号を収集するための疑似フォーム、そしてVideo要素のターゲットです:

    <div id="buttons">
      <a href="/ask" class="bigbutton" id="askBtn">Ask a Question</a>
      <a href="/answer" class="bigbutton" id="answerBtn">Answer a Question</a> 
    </div>
    
    <div id="addNumber">
      No one has a question right now. Want a text when someone does?
      <label>Phone number (e.g. 441234123456):
        <input type="tel" id="phoneNumber" name="phoneNumber" />
      </label>
      <button id="phoneBtn">Text me!</button>
    </div>
    
    <div id="videos">
      <div id="subscriber"></div>
      <div id="publisher"></div>
    </div>

このチュートリアルではCSSには触れませんが、最低限 #addNumber要素と #videos要素を隠したいでしょう。既存のスタイルシートは style.css.

クライアント側のセットアップ

クライアントスクリプトに最初にやらせたいことは、URLに idパラメータがあるかどうかを確認することです。これは、誰かが進行中のチャットセッションに参加するためにテキスト内のリンクをたどったことを示します。もしあれば、参加するために必要な認証情報をサーバー上の /answer/:sessionIdエンドポイントから参加に必要なクレデンシャルを取得できます。サーバーレスポンスの処理は、2つの関数のいずれかで行われます、 initializeSessionまたは handleErrorのどちらかで行われます:

let params = new URLSearchParams(window.location.search);
let ongoingId = params.get('id');
if (ongoingId) {
  fetch('/answer/' + ongoingId).then(function fetch(res) {
    return res.json();
  }).then(function fetchJson(json) {
    initializeSession(json.apiKey, json.sessionId, json.token);
  }).catch(function catchErr(error) {
    handleError(error);
  });
}

誰かが特定のセッションに参加したいという、あまり一般的でないケースをカバーしたので、インターフェイスでアクションを処理するために使うスクリプトを設定することができます。ここで handleError.お望みであれば、この関数はエラーをコンソールに送るだけの現在の形よりも、もっと複雑にすることができます。その後で、動的な機能を受け取るトップレベルの要素を選択できます。選択したボタンが存在すれば、クリックハンドラを割り当てることができます:

function handleError(error) {
  if (error) {
    console.error(error);
  }
}

var askBtn = document.querySelector('#askBtn');
var answerBtn = document.querySelector('#answerBtn');
var addPhone = document.querySelector('#addNumber');
var phoneBtn = document.querySelector('#phoneBtn');

if (askBtn) askBtn.onclick = askQuestion;
if (answerBtn) answerBtn.onclick = answerQuestion;
if (phoneBtn) phoneBtn.onclick = addPhoneNumber;

セッションの初期化

質問と回答のセッションを初期化するプロセスは、「質問」または「回答」ボタンをクリックすることで始まります。これらのボタンのハンドラ askQuestionanswerQuestionとほぼ同じです。これらは最初にリンクのデフォルトアクションを抑制し、次にサーバー上の適切なエンドポイントからチャット認証情報を取得します。セッションの作成や参加が可能であれば initializeSessionが認証情報とともに呼び出されます。の場合 answerQuestionの場合、誰も質問していなければ、ヘルパーは代わりに電話番号を提供するオプションを見ます:

function askQuestion(e) {
  e.preventDefault();
  fetch('/ask').then(function fetch(res) {
    return res.json();
  }).then(function fetchJson(json) {
    initializeSession(json.apiKey, json.sessionId, json.token);
  }).catch(function catchErr(error) {
    handleError(error);
  });
}

function answerQuestion(e) {
  e.preventDefault();
  fetch('/answer').then(function fetch(res) {
    return res.json();
  }).then(function fetchJson(json) {
    if (json.wait) {
      addPhone.style.display = 'block';
      return;
    }
    
    initializeSession(json.apiKey, json.sessionId, json.token);
  }).catch(function catchErr(error) {
    handleError(error);
  });
}

この initializeSession関数は、アプリケーション全体の中で最も複雑なロジックかもしれません。驚くべきことに、OpenTok APIは実際に必要なロジックを単純化しています。チャット・セッション・リスナーとDOM要素の間の調整を引き受けてくれるので、例えば <video>要素を作成し、そのソースを割り当てるような低レベルのタスクは舞台裏で行われます。

この関数は、まず OpenTok API に API キーとセッション ID を渡して、セッションのインスタンスを作成します。

クライアント側の OpenTok API の静的メソッドは OT変数の下で利用できます。エディタから OTが定義されていないというエディターのクレームは無視してかまいません。しかし、より堅牢なアプリのためには、エラー・チェックを行って OT が実際に定義されていることを確認するのがベストでしょう。逆に、定義しすぎるのもよくない。 OTを定義しすぎることは避けたい。デフォルトのAPIオブジェクト名を最初に変更しない限りは。

最初に必要なハンドラは streamCreatedイベント用のハンドラです。現在のセッションでストリームが作成されると、IDが subscriber.また、セッションから切断された場合にクライアントに通知するハンドラを追加することもできます。

次に、パブリッシャーの Video フィードのプロパティを定義します。クライアントは常にパブリッシャーです。クライアントの Video は 100% のサイズで publisher要素に追加されます。

最小限のイベントハンドラとコンフィギュレーションがあれば、セッションに接続し、クライアントにパブリッシャーフィードを追加することができます:

function initializeSession(apiKey, sessionId, token) {
  var session = OT.initSession(apiKey, sessionId);

  // Subscribe to a newly created stream
  session.on('streamCreated', function streamCreated(event) {
    var subscriberOptions = {
      insertMode: 'append',
      width: '100%',
      height: '100%'
    };
    session.subscribe(event.stream, 'subscriber', subscriberOptions, handleError);
  });

  session.on('sessionDisconnected', function sessionDisconnected(event) {
    console.log('You were disconnected from the session.', event.reason);
  });

  // initialize the publisher
  var publisherOptions = {
    insertMode: 'append',
    width: '100%',
    height: '100%'
  };
  var publisher = OT.initPublisher('publisher', publisherOptions, handleError);

  // Connect to the session
  session.connect(token, function callback(error) {
    if (error) {
      handleError(error);
    } else {
      // If the connection is successful, publish the publisher to the session
      session.publish(publisher, handleError);
    }
  });
}

電話番号の保存

この時点で、ホットラインが機能しているはずです。誰かが「質問する」ボタンをクリックし、その直後に誰かが「質問に答える」ボタンをクリックすれば、2人はビデオチャットをしていることになり、うまくいけばJavaScriptの謎をすべて解くことができるはずだ。あとは、その場では質問がないけれども、後で質問が出てきたときのために、電話番号をテキストで送信する機能を追加するだけだ。

この addPhoneNumberハンドラは、ボタン・ハンドラと同様に、デフォルトのイベントを抑制し、フェッチを行うだけです。この場合 POSTデータをサーバーに送信します。 Content-Typeapplication/jsonフィールドの値を文字列化します。 #phoneNumberフィールドの値を文字列化します。これが成功したら、電話番号入力フィールドを再び非表示にします:

function addPhoneNumber(e) {
  e.preventDefault();
  fetch('/text', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({phone: document.querySelector('#phoneNumber').value})
  }).then(() => {
    addPhone.style.display = 'none';
  }).catch(function catchErr(error) {
    handleError(error);
  });
}

次のステップ

ホットラインに追加できる機能はたくさんありますし、作成できるホットラインもたくさんあります。ユーザーにとっては、質問や回答を待っている人が何人いるかを見ることができ、セッションが中断したときに再接続できる機能があれば、より強固なものになるでしょう。

すでにNexmo APIを使用しているので、簡単な ボイスチャット.また、すでにOpenTokを使用しているので、次のような機能を追加することもできます。 画面共有または アーカイブよくある質問をアーカイブするオプションを追加することもできます。

OpenTokで何ができるかについては、次のサイトをご覧ください。 TokBoxデベロッパー・センターまた、Glitchでこの例のコードを見たり、リミックスすることができます:

シェア:

https://a.storyblok.com/f/270183/250x250/f231d97f1b/garann-means.png
Garann Meansデベロッパー・エデュケーター

私はJavaScript開発者で、Vonageの開発者教育者です。長年にわたり、テンプレート、Node.js、プログレッシブ・ウェブ・アプリケーション、そしてオフライン・ファースト戦略に熱中してきましたが、私がいつも本当に愛しているのは、便利できちんと文書化されたAPIです。私の目標は、当社のAPIを使用するお客様の体験を、私がお手伝いできる最高のものにすることです。