MQTTメッセージでLambdaからブラウザを更新する 〜Vue+Amplify編

2021.05.03

1 はじめに

CX事業本部の平内(SIN)です。

以前、下記のような記事を書きました。 こちらは、ブラウザ上で、aws-iot-device-sdkを使用して、AWS IoT Coreのメッセージブローカーをsubscribeし、特定のTopicが到着した時点で、ブラウザを更新するものでした。

やってることは、全く同じなのですが、今回は、これをAWS Amplify(以下、Amplify)で作成してみました。使用したウェブフレームワークは、Veu.jsです。

Amplifyでは、ライブラリAWSIoTProviderを使用することで、簡単にPub/Subが可能です。
Amplify Libraries AWSIoTProvider

また、権限については、AmplifyAuthカテゴリを追加することで、CognitoFederated Identitiesが利用可能なので、未認証IDを許可して、特定のトピックをSubscribeするだけのポリシーを追加しています。

「データ更新等に伴い表示を書き換える」というような要件だと、Amplify+AppSyncでGraphQLのSubscriptionで書くと、綺麗に仕上がりそうですが、今回は、あくまでMQTTと飛ばすことを目的として作業していることを、ご了承ください。

2 Vue.js

使用したVue CLIは、Ver 4.5.12 でした。

% vue --version
@vue/cli 4.5.12

プリセットにデフォルトの Default ([Vue 2] babel, eslint) を指定してプロジェクト()を生成しています。

% vue create browser_refresh_sample
? Please pick a preset: (Use arrow keys)
❯ Default ([Vue 2] babel, eslint)
  Default (Vue 3 Preview) ([Vue 3] babel, eslint)
  Manually select features

・・・略・・・

🎉  Successfully created project browser_refresh_sample.
👉  Get started with the following commands:

 $ cd browser_refresh_sample
 $ yarn serve

この後、プロジェクトのフォルダ内で作業進めます。

% cd browser_refresh_sample

3 Amplify

amplifyの方は、4.50.0となっています。

% amplify --version
4.50.0

amplifyの初期化の方も、全てデフォルト設定です。

% amplify init
? Enter a name for the project browserrefreshsample
? Initialize the project with the above configuration? Yes
Using default provider  awscloudformation
? Please choose the profile you want to use developer

・・・略・・・

Your project has been successfully initialized and connected to the cloud!

・・・略・・・

続いて、authカテゴリを追加してpushしました。

% amplify add auth
❯ Default configuration
❯ Username
❯ No, I am done.
% amplify push

・・・略・・・

✔ All resources are updated in the cloud

% amplify status

Current Environment: dev

| Category | Resource name                | Operation | Provider plugin   |
| -------- | ---------------------------- | --------- | ----------------- |
| Auth     | browserrefreshsample0ec00635 | No Change | awscloudformation |

Cloud Formationのコンソールを確認するとベースのスタックと、Authカテゴリのスタックが作成されていることを確認できます。

4 ポリシー

Authカテゴリで追加されたスタックのリソース情報からIdentittyPoolのリンクを辿り、CognitoのIDプールの画面を開きます。

「認証されていないIDに対してアクセスを有効にする」にチェックを入れます。

未認証時に適用されるロールに、必要最小限の権限付与ということで、クライアントID、browser_refresh_sample_idからの接続と、トピックbrowser_refresh_sample_topicのサブスクライブと受信だけを許可しています。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "iot:Connect"
            ],
            "Resource": [
                "arn:aws:iot:ap-northeast-1:*:client/browser_refresh_sample_id"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "iot:Subscribe"
            ],
            "Resource": [
                "arn:aws:iot:ap-northeast-1:*:topicfilter/browser_refresh_sample_topic"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "iot:Receive"
            ],
            "Resource": [
                "arn:aws:iot:ap-northeast-1:*:topic/browser_refresh_sample_topic"
            ]
        }
    ]
}

5 コード

最初に、ライブラリAWSIoTProviderを使用できるように、モジュールをインストールしています。

npm i aws-amplify @aws-amplify/pubsub
npm i core-js@latest

Vue.jsのApp.vueは、下記のように書き換えました。mounted: でMQTTをサブスクライブしています。

<template>
  <div id="app">
      {{ time }}
  </div>
</template>

<script>

import awsconfig from './aws-exports'
Amplify.configure(awsconfig)
import Amplify, { PubSub } from 'aws-amplify';
import { AWSIoTProvider } from '@aws-amplify/pubsub/lib/Providers';

Amplify.addPluggable(new AWSIoTProvider({
  aws_pubsub_region: 'ap-northeast-1',
  aws_pubsub_endpoint: 'wss://xxxxxxxxxxxx-ats.iot.ap-northeast-1.amazonaws.com/mqtt',
  clientId: 'browser_refresh_sample_id'
}));

export default {
  name: 'App',
  data: () => {
    return {
      time: "" 
    }
  },
  mounted: async function () {
    PubSub.subscribe('browser_refresh_sample_topic').subscribe({
      next: data => {
        console.log('Message received', data)
        //location.reload()
      },
      error: error => console.error(error),
      close: () => console.log('Done'),
    });
  },
  created : function(){
    let currentdate = new Date()
    this.time = currentdate.getHours() + ':' + currentdate.getMinutes() + ':' + currentdate.getSeconds() + ' update.'
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

Topicにメッセージを送ってみると、ブラウザ側で反応していることが確認できます。

Topicが受信できていることを確認できたら、location.reload()を有効にして、トピック到着で、ブラウザをリロードします。

mounted: async function () {
    PubSub.subscribe('browser_refresh_sample_topic').subscribe({
      next: data => {
        console.log('Message received', data)
        // ↓この行を有効にする
        location.reload()
      },
      error: error => console.error(error),
      close: () => console.log('Done'),
    });
  },

6 Lambda

Topicを送信するために作成したLambdaは、以下の通りです。Lambdaの権限に、IoT CoreへのPublishの権限追加が必要です。

exports.handler = async (event) => {
    // TODO implement
    const endpoint = 'xxxxxxxxxxxx-ats.iot.ap-northeast-1.amazonaws.com';
    const topic = "browser_refresh_sample_topic"
    const aws = require('aws-sdk');
    const region = 'ap-northeast-1';

    var iotdata = new aws.IotData({
        endpoint: endpoint,
        region: region
    });
    var params = {
        topic: topic,
        payload: '{"action":"refresh"}',
        qos: 0
    };
    let result = await iotdata.publish(params).promise();
    console.log(result);
};

7 最後に

今回は、MQTTのPub/SubでLambdaとブラウザの連携を確認してみました。最初に、話した通り、情報の更新に伴う、画面の更新であれば、MQTTをわざわざ利用する必要なありませんが、MQTTでも、色々連携できれば、応用範囲が広がるように感じています。