DEV Community

Pipat Sampaokit
Pipat Sampaokit

Posted on • Edited on

Serverless Push Notification With AWS IoT

AWS IoT is an option to create IoT devices without back-end servers as it provides MQTT as a service for us. Having a mobile application to interact with the devices is also possible without servers thanks to AWS Cognito that enables us to access many AWS services securely from the mobile application.

With this setup, serverless push notification from IoT devices to the mobile application can be achieved with only a few more configuration. And since I rarely find any example on this topic so I put it here for anyone exploring the option.

TL;DR

Use IoT Rule SQL like this to transform the data published into a topic to a valid GCM/FCM payload and forward it to Platform Application Endpoints.

image

Prerequisites

This post focuses only on how to set up the push notification with AWS IoT. Readers are assumed to be familiar with following topics.

  • How to connect microcontrollers, such as ESP32, to AWS IoT.
  • How to set up Firebase Cloud Messaging (FCM) to work with an Android application in your framework of choice, though in this example, I use Ionic and its Capacitor plugin.
  • How to set up AWS Cognito, and possiblity Amplify Library, to work with a mobiile application.

Runtime Architecture

image

  1. IoT devices publish to an MQTT topic with a JSON data.
  2. An IoT Rule is configured to transform the published data to a valid FCM notification payload and forward the payload to an SNS Topic.
  3. The SNS Topic forwards the payload to all subscribed Platform Application Endpoints.
  4. AWS sends requests to FCM with the payload, and the push notifications are delivered to the mobile application

Setup

image

1. Aquire FCM ServerKey from Firebase Console and use it to create SNS Platform Application.

aws sns create-platform-application \
    --name dev.foo-project.foo-mobile-devices.sns-app \
    --platform GCM \
    --attributes PlatformCredential=<Your FCM ServerKey>
Enter fullscreen mode Exit fullscreen mode

2. Create the SNS Topic.

aws sns create-topic --name dev__foo-project__push-notification__sns-topic
Enter fullscreen mode Exit fullscreen mode

3. The IoT rule we are about to create will need an IAM role that grants a permission to trigger the above SNS Topic.

echo '{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "iot.amazonaws.com"
            },
            "Action": [
                "sts:AssumeRole"
            ]
        }
    ]
}' > trust-policy.json

echo '{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "sns:Publish",
            "Resource": "arn:aws:sns:<your_region>:<your_account_id>:dev__foo-project__push-notification__sns-topic"
        }
    ]
}' > policy.json

aws iam create-role --role-name dev.foo-project.iot-rule-action.role --assume-role-policy-document file://trust-policy.json
aws iam put-role-policy --role-name dev.foo-project.iot-rule-action.role --policy-name dev.foo-project.iot-rule-action.policy --policy-document file://policy.json
Enter fullscreen mode Exit fullscreen mode

4. And finally, the main piece of today recipe, the IoT Rule.

echo '{
    "Sql": "SELECT concat('{\"priority\":\"high\",\"notification\":{\"title\":\"', title ,'\",\"body\":\"', body ,'\",\"mutable_content\":true,\"sound\":\"Tri-tone\",\"badge\":1,\"click_action\":\"foo\"},\"data\":{}}') as GCM, '' as default FROM 'dev.foo-project/push-notification'",
    "Actions": [
        {
            "Sns": {
                "MessageFormat": "JSON",
                "TargetArn": "arn:aws:sns:<your_region>:<your_account_id>:dev__foo-project__push-notification__sns-topic",
                "RoleArn": "arn:aws:iam::<your_account_id>:role/dev.foo-project.iot-rule-action.role"
            }
        }
    ]
}' > rule-payload.json

aws iot create-topic-rule \
    --rule-name dev__foo_project__push_notification__iot_rule \
    --topic-rule-payload file://rule-payload.json
Enter fullscreen mode Exit fullscreen mode

It can transform a payload of the form { "title": "...", "body": "..." } to a valid FCM/GCM payload.

User Notification Registration Flow

image

1. The mobile application is set up to use AWS Cognito + Amplify Authentication. Here is an example as a React Component.

import Amplify, { Auth } from "aws-amplify";
import { Authenticator, SignIn } from "aws-amplify-react";

Amplify.configure({
    aws_project_region: 'your_region',
    aws_cognito_identity_pool_id: 'your_cognito_identity_pool_id',
    aws_cognito_region: 'your_region',
    aws_user_pools_id: 'your_user_pool_id',
    aws_user_pools_web_client_id: 'your_user_pool_client_id',
    aws_appsync_authenticationType: "AMAZON_COGNITO_USER_POOLS",
    oauth: {},
});
Auth.configure({
    authenticationFlowType: "USER_PASSWORD_AUTH",
});
Amplify.Logger.LOG_LEVEL = "VERBOSE";

const AppWithAuth: React.FC = () => {
    const [isSignedIn, setSignedIn] = useState(false);
    return (
        <Authenticator           
            onStateChange={(state) => {
                if (state === "signedIn") {
                    setSignedIn(true);
                }
            }}>
            <SignIn />
            <App ready={isSignedIn} />
        </Authenticator>
    )
}
Enter fullscreen mode Exit fullscreen mode

2. After the user signin, we can use the credentials from Cognito to create any AWS SDK Clients

import { SNSClient } from "@aws-sdk/client-sns";
import { IoTClient } from "@aws-sdk/client-iot";

const amplifyCredentialProvider = async () => {
    const awsCredentials = await Auth.currentCredentials();
    return Auth.essentialCredentials(awsCredentials);
};

export const iotClient = new IoTClient({
    region: config.region,
    credentials: amplifyCredentialProvider,
});

export const snsClient = new SNSClient({
    region: config.region,
    credentials: amplifyCredentialProvider,
});
Enter fullscreen mode Exit fullscreen mode

3. The application retrieves FCM token from Firebase and use SNSClient to create the platform application endpoint and subscribe to the SNS topic

import { ActionPerformed, PushNotifications, PushNotificationSchema, Token } from "@capacitor/push-notifications";
import { CreatePlatformEndpointCommand, SubscribeCommand } from "@aws-sdk/client-sns";

const App: Rect.FC<{ready: boolean}> = ({ ready }) => {
    useEffect(() => {
        if (ready) {
            // Retrieve FCM Token
            PushNotifications.addListener("registration", async (token: Token) => {
                const endpoint = await snsClient.send(
                    new CreatePlatformEndpointCommand({
                        PlatformApplicationArn: config.snsPlatformApplicationArn,
                        Token: token.value,
                    })
                );
                const subscriptionResult = await snsClient.send(
                    new SubscribeCommand({
                        Protocol: "application",
                        TopicArn: config.snsTopicArn,
                        Endpoint: endpoint.EndpointArn,
                    })
                );
            });
            // FCM Registration Error
            PushNotifications.addListener("registrationError", (error: any) => {
            });
            // Notification received when the app is in foreground
            PushNotifications.addListener("pushNotificationReceived", (notification: PushNotificationSchema) => {
            });
            // Notification data when the user tab the notification in system tray
            PushNotifications.addListener("pushNotificationActionPerformed", (notification: ActionPerformed) => {
            });

            PushNotifications.register();
        }
    }, [ready])
    return (
        <BlaBlaBla />
    )
}
Enter fullscreen mode Exit fullscreen mode

Testing

1. We will use AWS Console' MQTT Test Client tool to simulate a device publishing to the MQTT topic.

image

2. Yey!, the notification arrives at my phone.

image

Top comments (0)