https://d226lax1qjow5r.cloudfront.net/blog/blogposts/real-time-sms-demo-with-react-node-and-google-translate-dr/E_SMS-Translations_1200x600.png

React、Node、Google翻訳を使ったリアルタイムSMSデモ

最終更新日 May 24, 2021

所要時間:2 分

昨年、私は Google翻訳APIを使ってSMSメッセージを翻訳した。チームの他のメンバーに見せた後、彼らは私たちが参加したカンファレンスで他の開発者に見せびらかすことができるデモを欲しがった。それに基づいて、私はリアルタイムで翻訳を表示できるフロントエンドをReactで作ることにした。

WebSocketの構築

WebSocketとは?

このデモでは、WebSocketを使用するのが素晴らしいソリューションだと考えました。WebSocketを使ったことがない人は、クライアントとサーバーがリアルタイムで通信できるプロトコルだ。WebSocketは双方向で、クライアントとサーバーはメッセージを送受信できる。最初にWebSocketに接続する場合、HTTPプロトコルをWebSocketプロトコルにアップグレードすることで接続が行われ、中断されない限り接続が維持されます。一度確立されると、継続的なコンテンツのストリームが提供されます。まさに、翻訳されたSMSメッセージを受信するために必要なものです。

NodeでWebSocketサーバーを作成する

WebSocketを作成する最初のステップとして、サーバーはクライアント接続を許可するパスを必要とする。前回の 前の投稿WebSocketサーバーと、クライアントが必要とするイベントとリスナーを作成するために、いくつかのマイナーな変更を加えることができます。

NPMの wsパッケージを使えば、これを動かすのに必要なものをすぐに作ることができる。

npm install ws

インストールが完了したら、パッケージをサーバーファイルにインクルードし、WebSocketサーバーを作成します。 WSには pathオプションを使用して、クライアントが接続に使用するルートを設定します。

const express = require('express');
const WebSocket = require('ws');

const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server, path: "/socket" });

このコードで、クライアントはWebSocketルートに接続できるようになります。 /socket.サーバの準備ができたので、次に connectionイベントをリッスンする必要があります。クライアントが接続すると、サーバは以下を使用して必要な他のリスナーを設定します:

wss.on('connection', (ws) => {
  ws.isAlive = true;
  ws.translateTo = 'en';

  ws.on('pong', () => {
    ws.isAlive = true;
  });

  ws.on('message', (message) => {
    translateTo = message;
  });

});

主な指摘は2つある:

  1. 接続時に isAlivetrueに設定し pongイベントをリッスンします。このイベントは、サーバーがクライアントとの接続を確認し、維持するためのものです。サーバは pingを送信し pongで応答し、まだ生きている接続であることを確認する。

  2. ここでは translateToを保存するプロパティとして設定しています。 translateToは、ドロップダウンを使用して各クライアントを通じて設定されます。私たちのブースのデモアプリを使用している人が別の言語を選択すると、そのアクションはSMSテキストを要求された言語に翻訳するためにこれを設定します。

つながりを保つ

注意すべき必須項目のひとつは、切断するクライアントのチェックである。切断処理中にサーバーが気づかず、問題が発生する可能性があるからだ。親友がいれば setInterval()を使えば、クライアントがまだそこにいるかどうかをチェックし、必要に応じて再接続することができる。

setInterval(() => {
  wss.clients.forEach((ws) => {
    if (!ws.isAlive) return ws.terminate();
    ws.isAlive = false;
    ws.ping(null, false, true);
  });
}, 10000);

クライアントへのメッセージ送信

これでWebSocketが接続され、監視されるようになったので、Nexmoからの受信メッセージ、翻訳、クライアントへの応答を処理することができます。メソッド handleRouteメソッドを元の状態から更新して、各クライアントのレスポンスを追加する必要があります。

const handleRoute = (req, res) => {

  let params = req.body;

  if (req.method === "GET") {
    params = req.query
  }

  if (!params.to || !params.msisdn) {
    res.status(400).send({ 'error': 'This is not a valid inbound SMS message!' });
  } else {
    wss.clients.forEach(async (client) => {
      let translation = await translateText(params, client.translateTo);
      let response = {
        from: obfuscateNumber(req.body.msisdn),
        translation: translation.translatedText,
        originalLanguage: translation.detectedSourceLanguage,
        originalMessage: params.text,
        translatedTo: client.translateTo
      }

      client.send(JSON.stringify(response));
    });

    res.status(200).end();
  }

};

この wss.clients.forEachメソッドは各接続を繰り返し、NexmoからGoogle Translate APIにSMSパラメータを送ります。翻訳が戻ってきたら、フロントエンドが持つべきデータを決定し、文字列として送り返すことができます。 client.send(JSON.stringify(response)).

ここで起こったことをまとめる:各クライアントは /socketルートを呼び出して接続を確立する。SMSメッセージは送信者の電話からNexmoに送られ、Nexmoがルートを呼び出す。 /inboundSMSルートを呼び出す。アプリは接続された各クライアントのテキストメッセージをGoogle翻訳APIに渡し、最後にクライアントUIに送り返す。

Diagram of WebSocket FlowDiagram of WebSocket Flow

次に、それを画面に表示するためのUIパーツを作ってみよう。

ReactによるWebSockets

WebSocketサーバーが動いたので、次はメッセージを画面に表示しよう。私は リアクトを使うのが好きだし、もっと重要なのは React Hooksを使うのが好きなので、WebSocketへの接続を助けてくれるものを探してみた。案の定、私のニーズにぴったりのものが見つかった。

デモアプリのUIは create-react-appで構築されており Grommetフレームワークを使用した。これらのトピックはこの記事の範囲外ですが、私の ソースコードを手に入れ、それに従ってください。

WebSocketへの接続

ここでの最初のステップは、コネクションを確立し、双方向のコミュニケーションを始めることだ。私が見つけたモジュールは react-use-websocketで、このモジュールの設定はとても簡単だった。

npm install react-use-websocket

このようなReactフック・ライブラリは大量にあり、短時間で印象的な機能を作成するのに役立つ。この例では、モジュールをインポートし、設定のためのいくつかの項目を設定するだけで、接続を得ることができた。

import useWebSocket from 'react-use-websocket';

const App = () => {
  const STATIC_OPTIONS = useMemo(() => ({
    shouldReconnect: (closeEvent) => true,
  }), []);

  const protocolPrefix = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
  let { host } = window.location;
  const [sendMessage, lastMessage, readyState] = useWebSocket(`${protocolPrefix}//${host}/socket`, STATIC_OPTIONS);

  //...
}

コンポーネントでは useWebSocketメソッドをインポートし、WebSocket URLとオブジェクト STATIC_OPTIONSを第2引数として渡します。この useWebSocketメソッドを返すカスタムフックです。 sendMessageメソッドです、 lastMessageオブジェクト(これが翻訳されたメッセージです)、そして readyStateを返すカスタムフックです。

着信メッセージの受信

いったん react-use-websocketプロパティからのメッセージのリッスンを開始することができます。 lastMessageプロパティからのメッセージのリッスンを開始します。サーバーから受信したメッセージはここに入力され、コンポーネントを更新します。サーバーに複数のメッセージタイプがある場合は、ここでその情報を確認します。私たちは1つしか持っていないので、より簡単な実装です。

const [messageHistory, setMessageHistory] = useState([]);

useEffect(() => {
  if (lastMessage !== null) {
    setMessageHistory(prev => prev.concat(lastMessage))
  }
}, [lastMessage]);

return (
  <Main>
    {messageHistory.map((message, idx) => {
      let msg = JSON.parse(message.data);
      return (
        <Box>
          <Text>From: {msg.from}</Text>
          <Heading level={2}>{msg.translation}</Heading>
        </Box>
      )
    })}
  </Main>
)

組み込みのフック useEffectは状態が更新されるたびに実行される。このフックは lastMessageがNULLでない場合、新しいメッセージが前のメッセージステート配列の最後に追加され、UIはすべてのメッセージをレンダリングするために map関数を使用してUIを更新します。この関数は messageHistoryには、サーバーから渡されたすべての JSON 文字列が格納されます。WebSocketの主な機能は完了しましたが、まだいくつかの項目を追加したいと思います。

サーバーへのメッセージ送信

これは翻訳デモなので、複数の言語があることは、Nexmo SMSメッセージと連動したGoogle翻訳APIの力を示す優れた方法です。言語を選択できるドロップダウンを作成しました。このドロップダウンでサーバーとの双方向通信が行われ、アプリはクライアントから選択した言語を送信します。

const languages = [
  { label: "English", value: "en"},
  { label: "French", value: "fr"},
  { label: "German", value: "de"},
  { label: "Spanish", value: "es"}
];

<Select
  labelKey="label"
  onChange={({ option }) => {
    sendMessage(option.value)
    setTranslateValue(option.label)
  }}
  options={languages}
  value={translateValue}
  valueKey="value"
/>

ここで sendMessageからの関数 react-use-websocketからの関数は、情報をサーバーに送り返し、それを消費する方法である。この処理は、先ほど設定したイベントハンドラが役に立つ。Google Translate APIがメッセージをどの言語に翻訳して画面に表示するかを決めるのは、このドロップダウンだ。

接続状態表示

これは会議環境でのデモなので、接続インジケータを持つのは良いアイデアだと思った。フロントエンドがWebSocketに接続されている限り、ランプは緑色に表示される。

const CONNECTION_STATUS_CONNECTING = 0;
const CONNECTION_STATUS_OPEN = 1;
const CONNECTION_STATUS_CLOSING = 2;

function Status({ status }) {
  switch (status) {
    case CONNECTION_STATUS_OPEN:
      return <>Connected<div className="led green"></div></>;
    case CONNECTION_STATUS_CONNECTING:
      return <>Connecting<div className="led yellow"></div></>;
    case CONNECTION_STATUS_CLOSING:
      return <>Closing<div className="led yellow"></div></>;
    default:
      return <>Disconnected<div className="led grey"></div></>;;
  }
}

//....
<Status status={readyState} />
//...

コンポーネントは Statusコンポーネントは readyStateを使用してさまざまなステータスを切り替え、ユーザーに表示します。赤に変わったら、WebSocket サーバーに何か問題があることがわかります。

すべてを立ち上げて実行すると、次のようになる:

Animation of Working Demo AppAnimation of Working Demo App

試してみる

その デモ・アプリケーション・コードデモ・アプリケーションのコードは、私たちのコミュニティGitHubにあります。READMEを作成しましたので、セットアップを行い、サーバー上でローカルに実行するか、Herokuにデプロイしてください。また、Dockerfileも用意したので、そちらを利用したい場合はそちらを利用してほしい。もし何か問題があれば、遠慮なくご連絡ください。

シェア:

https://a.storyblok.com/f/270183/384x384/444c073b5e/kellyjandrews.png
Kelly J Andrews元チームメンバー

ケリー・J・アンドリュースはネクスモの開発者支持者であり、5歳で初めてBASICを使い、30年以上コンピュータをいじってきた。

1997年に初めてウェブページを作り、初めてJavaScriptを試してみて初めて、彼は真の天職を見つけた。ケリーは今、JavaScript、テスト可能なコード、そして迅速なデリバリーのために戦っている。

カラオケを歌ったり、マジックを披露したり、カブスやファイティング・アイリッシュを応援したり。