Momento Web SDKを使ってpub/subチャットをS3で構築する

2023.06.15

Introduction

先日、サーバレスキャッシュ・メッセージングサービスを提供するMomentoから、
Momento Web SDKという名前の新しいSDKがリリースされました
Node用に次ぐ、JavaScript用のSDKになります。
このSDKはブラウザ環境でシームレスに機能するように設計されており、
フロントエンドから直接Momentoにアクセスできます。

Momento Web SDKを使うことで、下記のような機能が実現可能になります。

  • チャンネルベースのWebSocket通信
  • クライアント/サーバー間のキャッシュ共有
  • ブラウザ間通信
  • プログラムでAPIトークンを生成

ここではMomento Web SDKをつかってMomento Topicsを使った
pub/sub形式のチャットアプリをフロントエンドのみの実装で作成し、
S3にホスティングしてみます。

注意
今回はMomentoの認証用トークンをプログラム内に記述してますが、
あくまでもデモ用です。
サンプルコードでは認証トークンが誰でも見られる状態なので、
検証以外の用途では使わないでください。

Environment

今回試した環境は以下のとおりです。

  • MacBook Pro (13-inch, M1, 2020)
  • OS : MacOS 13.0.1
  • Node : v18.15.0
  • aws-cli : 2.2.35

AWSアカウントは設定済みとします。
また、DevIOのMomento関連記事はここにあるので、
これらもご確認ください。

Setup

Momento のセットアップ

まずはMomentoのセットアップです。
現在CLIでのサインアップは非推奨なので、
Momentoコンソールでのトークン取得を行います。

今回はデモ用のため、Momentoコンソールにログイン後
有効期限なしでトークンを生成して認証トークンを記録しておきましょう。
また、「chat」という名前でキャッシュも作成しておきます。
※トークンとキャッシュは同じリージョンにしておくこと

Create Demo Program

Next.jsとMomento Topicsをつかって超簡易チャットを実装します。
まずはnpxでNextアプリの雛形を生成。
言語はJavascriptを選択し、あとはデフォルトで生成します。

% npx create-next-app@latest demo-web-sdk
・・・

package.jsonにWeb SDK(@gomomento/sdk-web)を追記しましょう。

{
  "name": "demo-web-sdk",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "next": "13.4.5",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "@gomomento/sdk-web": "^1.25.1" 
  }
}

app/page.jsの修正をします。
下記のように、認証トークンを固定で指定して
TopicClientを初期化し、pub/subを行います。
※何度も言いますが、トークンを誰でも見ることができるので注意

"use client";

import React, { useEffect, useState, useRef } from 'react';
import { TopicClient, CredentialProvider, Configurations } from '@gomomento/sdk-web';
import { useRouter } from 'next/navigation';


const token = "<認証トークン。本来なら認証サーバなどでAPI用トークンを生成して取得すること>";

export default function Home() {

  const [inputValue, setInputValue] = useState('');
  const [items, setItems] = useState([]);
  const router = useRouter();
	var [topicClient, setTopicClient] = useState(null);
	const topicClientRef = useRef(topicClient);

	const updateTopicClient = (client) => {
		topicClientRef.current = client;
		setTopicClient(client);
	};

	useEffect(() => {
		topicClientRef.current = topicClient;
	}, [topicClient]);

	useEffect(() => {
		async function setupMomento() {
			initializeTopicClient();
		}

		if (!topicClient) {
			setupMomento();
		}
  }, [router]);

  //Momento Topicの初期化処理
  const initializeTopicClient = async () => {
    //Momento Topicクライアントを作成
    topicClient = new TopicClient({
      configuration: Configurations.Browser.v1(),
      credentialProvider: CredentialProvider.fromString({ authToken: token })
    });

    updateTopicClient(topicClient);

    //subscbibe開始。
    //cache name = chat(あらかじめ作成しておく)
    //topic name = hello-chat
    await topicClient.subscribe('chat', `hello-chat`, {
      onItem: async (data) => await subscribeMessage(data.value()),
      onError: (err) => console.log(err)
    });
  };
  

    //メッセージを受信したときの処理
  const subscribeMessage = async (newMessage) => {
    console.log(newMessage);
    setItems((prevItems) => [...prevItems, newMessage]);
  };

    //ボタンクリック処理
  const sendMessage = async (event) => {
    event.preventDefault();
    if (inputValue) {
      //メッセージをpublish
      topicClient.publish('chat', `hello-chat`, inputValue);
      setInputValue('');
    }
  };

  const handleInputChange = (e) => {
    setInputValue(e.target.value);
  };


  return (
    <div>
      <input type="text" value={inputValue} onChange={handleInputChange} />
      <button onClick={sendMessage}>Add</button>
      <div>
        {items.map((item, index) => (
          <div key={index}>{item}</div>
        ))}
      </div>
    </div>
  );
};

run devコマンドを実行してローカルで動作確認してみます。

% npm run dev

複数ブラウザで文字を入力してみると、 
各ブラウザに反映されます。

S3で公開する

では次に、exportしたファイルをS3にアップロードして公開してみましょう。
export用にnext.config.jsファイルを少し修正。
以前はnext exportとしてたらしいですが、いまは違う様子。

/** @type {import('next').NextConfig} */
const nextConfig = {
    output: 'export',
}

module.exports = nextConfig

buildするとoutディレクトリが生成されます。
これをS3にアップします。  

% npm run build

aws-cliでS3バケット作成します。

% aws s3api create-bucket --bucket <Your Bucket Name> --create-bucket-configuration LocationConstraint=ap-northeast-1

outディレクトリをまるごとアップロードします。

% cd /path/your/demo-web-sdk
% aws s3 cp out s3://<Your Bucket Name>/ --recursive

↓のようなpolicy.jsonを作成します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::<Your Bucket Name>/*"
      ]
    }
  ]
}

バケットポリシーを適用してpublicアクセスを許可しましょう。

% aws s3api put-bucket-policy --bucket <Your Bucket Name> --policy file://policy.json

AWSコンソールでout/index.htmlのURLを確認し、ブラウザで開いてみてください。
ローカルと同じようにpub/subでメッセージングが可能になってます。

Summary

いろいろ省略しているとはいえ、
サーバ側の実装なしでpub/subのチャットが実装できてしまいました。
これだけでもMomento Web SDKの便利さがわかります。
これを利用すれば、位置情報のトラッキングやステータス・在籍確認などが
最小限のコストで実現できそうです。  

本来であれば認証サーバを別途用意し、Momento Topics用クライアントには
認証サーバで発行した制限付きトークンを使用する方式がベターなので、
実際にはそういった方法を使いましょう。 

2023/7月には認証サポートを強化し、
プライベートチャットにも対応できるようにする予定なのでもう少々お待ち下さい。

なお、Momentoについてのお問い合わせはこちらです。
こちらもお気軽にお問い合わせください。

References