ReactNative+ExpoでAndroid端末へプッシュ通知を送る その②〜AWS Lambdaを使ってプッシュ通知〜
Expoを使用して開発するReactNativeアプリから各端末へプッシュ通知を送る仕組みを開発しています。
前回の記事では、Expoを使ったプッシュ通知に必要なExpoプッシュトークンを取得するところまで書きました。↓
今回は、取得したプッシュトークンを使用して実際にプッシュ通知を送る方法を記載します。
なお、バックエンドは全てAWSにしているため、今回はAWS Lambdaを使ってプッシュ通知を送ってみました。
プッシュ通知の流れ
プッシュ通知を送るまでの流れは下記の3ステップです。
AWS Lambda (Node.js) でプッシュ通知
Node.jsでLambdaを書いてプッシュ通知をしようと思います。
Expoからプッシュ通知を送るためのSDKが各言語向けに用意されているのでそれを使用します。 ちなみにNode.jsの他にはPython、Ruby、Go、Elixir、PHPなどのSDKが有志によって用意されています。
Push Notifications - Expo Documentation
実はNode.js向けSDKのGithubのREADMEのサンプルコードにほぼ答えが載ってるのでした。
上記を参考にして使ったLambdaのソースを抜粋して載せていきます。
準備
expo-server-sdkを追加します。
npm install expo-server-sdk
外部モジュールに依存するのでLambdaはインラインではなくZip形式にしたものをアップロードします。
コード
完全に公開できるものではないので、一部抜粋となりますがご了承ください。
const { Expo } = require('expo-server-sdk'); // Create a new Expo SDK client let expo = new Expo(); exports.handler = async (event, context) => { // 通知メッセージを取得 const messages = await getMessages(event); // 通知する const tickets = await sendPushNotifications(messages); // 通知結果を保存する(必要に応じて) await saveTickets(tickets); } const getMessages = async (event) => { // ここに通知するメッセージを取得する処理を書く ... return [ // 1つ1つのメッセージは以下の形式で { to: pushToken, // Expoプッシュトークン sound: 'default', // 通知時の音を鳴らすかどうかの設定 title: title, // 通知タイトル body: body // 通知本文 } ] } const sendPushNotifications = async (messages) => { // The Expo push notification service accepts batches of notifications so // that you don't need to send 1000 requests to send 1000 notifications. We // recommend you batch your notifications to reduce the number of requests // and to compress them (notifications with similar content will get // compressed). let chunks = expo.chunkPushNotifications(messages); let tickets = []; // Send the chunks to the Expo push notification service. There are // different strategies you could use. A simple one is to send one chunk at a // time, which nicely spreads the load out over time: for (let chunk of chunks) { try { let ticketChunk = await expo.sendPushNotificationsAsync(chunk); console.log(ticketChunk); tickets.push(...ticketChunk); // NOTE: If a ticket contains an error code in ticket.details.error, you // must handle it appropriately. The error codes are listed in the Expo // documentation: // https://docs.expo.io/versions/latest/guides/push-notifications#response-format } catch (error) { console.error(error); } } return tickets; } const saveTickets = async (tickets) => { // ticketsをデータベースに保存するような処理 }
上記ソースコードで言うと、sendPushNotificationsメソッドがプッシュ通知を送るメイン処理となります。
その前にgetMessagesメソッドでプッシュ通知の対象データを返してあげます。 1つ1つのメッセージに設定できる項目は下記に載っています。
Push Notifications - Expo Documentation
最後にsaveTicketsメソッドでプッシュ通知の結果をデータベースに保存します。
この処理を行っている理由ですが、SDKのREADMEには以下のコードも併記されています。
// Later, after the Expo push notification service has delivered the // notifications to Apple or Google (usually quickly, but allow the the service // up to 30 minutes when under load), a "receipt" for each notification is // created. The receipts will be available for at least a day; stale receipts // are deleted. // // The ID of each receipt is sent back in the response "ticket" for each // notification. In summary, sending a notification produces a ticket, which // contains a receipt ID you later use to get the receipt. // // The receipts may contain error codes to which you must respond. In // particular, Apple or Google may block apps that continue to send // notifications to devices that have blocked notifications or have uninstalled // your app. Expo does not control this policy and sends back the feedback from // Apple and Google so you can handle it appropriately. let receiptIds = []; for (let ticket of tickets) { // NOTE: Not all tickets have IDs; for example, tickets for notifications // that could not be enqueued will have error information and no receipt ID. if (ticket.id) { receiptIds.push(ticket.id); } } let receiptIdChunks = expo.chunkPushNotificationReceiptIds(receiptIds); (async () => { // Like sending notifications, there are different strategies you could use // to retrieve batches of receipts from the Expo service. for (let chunk of receiptIdChunks) { try { let receipts = await expo.getPushNotificationReceiptsAsync(chunk); console.log(receipts); // The receipts specify whether Apple or Google successfully received the // notification and information about an error, if one occurred. for (let receipt of receipts) { if (receipt.status === 'ok') { continue; } else if (receipt.status === 'error') { console.error(`There was an error sending a notification: ${receipt.message}`); if (receipt.details && receipt.details.error) { // The error codes are listed in the Expo documentation: // https://docs.expo.io/versions/latest/guides/push-notifications#response-format // You must handle the errors appropriately. console.error(`The error code is ${receipt.details.error}`); } } } } catch (error) { console.error(error); } } })();
プッシュ通知でエラーになった場合はなんらかの対応をしないといけないらしく、そのために上記のようなreceiptの取得処理を行わないといけないようです。
ただ、処理を行うにしても今回のLambdaとは別のLambdaにすべきで、プッシュ通知そのものとは切り離すべきですね。
まとめ
以上のようにして、Lambdaを使ってExpo経由でプッシュ通知を送ることができました。
今回のLambdaはDynamoDB StreamsをトリガーとするLambdaを書いていますが、プッシュ通知を送るケースとしては他にも
- Kinesisを使ったリアルタイムストリーム処理
- CloudWatchの定時実行イベント
などのトリガーでLambdaを動かすことがあると思いますので、そういったケースで今回のコードが参考になれば幸いです。