diff --git a/docs/build/img/notif-diagram.png b/docs/build/img/notif-diagram.png index 3d510a601..a76b16609 100644 Binary files a/docs/build/img/notif-diagram.png and b/docs/build/img/notif-diagram.png differ diff --git a/docs/build/messages/custom.mdx b/docs/build/messages/custom.mdx index f64535aa5..6f8068fd8 100644 --- a/docs/build/messages/custom.mdx +++ b/docs/build/messages/custom.mdx @@ -11,7 +11,7 @@ import TabItem from "@theme/TabItem"; :::caution -Be aware that your custom content type may not be automatically recognized or supported by other applications, which could result in it being overlooked or only its fallback text being displayed. +Be aware that your custom content type may not be automatically recognized or supported by other apps, which could result in the other apps overlooking or only displaying the fallback text for your custom content type. ::: @@ -86,6 +86,12 @@ export class ContentTypeMultiplyNumberCodec )}` // return undefined to indicate that this content type should not be displayed if it's not supported by a client } + + // Set to true to enable push notifications for interoperable content types. + // Receiving clients must handle this field appropriately. + shouldPush(): boolean { + return true; + } } ``` @@ -150,6 +156,11 @@ export class ContentTypeMultiplyNumberCodec )}` // return undefined to indicate that this content type should not be displayed if it's not supported by a client } + + // This method is optional and can be used to determine if the content type should have a push notification + shouldPush() { + return true; + } } export const multiplyNumbersContentTypeConfig: ContentTypeConfiguration = { @@ -220,6 +231,11 @@ data class MultiplyNumberCodec( override fun fallback(content: NumberPair): String? { return "Error: This app does not support numbers." } + + // This method is optional and can be used to determine if the content type should have a push notification + override fun shouldPush(): Boolean { + return true; + } } ``` @@ -269,6 +285,12 @@ public struct MultiplyNumbersCodec: ContentCodec { public func fallback(content: MultiplyNumbers) throws -> String? { return "MultiplyNumbersCodec is not supported" } + + + // This method is optional and can be used to determine if the content type should have a push notification + public func shouldPush() -> Bool { + return true; + } } ``` @@ -334,6 +356,11 @@ class MultiplyNumbersCodec extends Codec { @override // Fallback function in case the codec is not supported String fallback(MultiplyNumbers content) => "MultiplyNumbersCodec is not supported"; + + + // This method is optional and can be used to determine if the content type should have a push notification + @override + bool shouldPush() => true; } ``` @@ -393,6 +420,11 @@ class ContentTypeMultiplyNumberCodec fallback(content: MultiplyNumbers): string { return `MultiplyNumbersCodec is not supported` } + + // This method is optional and can be used to determine if the content type should trigger a push notification + shouldPush(): boolean { + return true; + } } ``` diff --git a/docs/build/messages/reaction.mdx b/docs/build/messages/reaction.mdx index f6ff60fd1..ae46cbbcb 100644 --- a/docs/build/messages/reaction.mdx +++ b/docs/build/messages/reaction.mdx @@ -341,3 +341,7 @@ _To handle unsupported content types, refer to the [fallback](/docs/build/messag ## Display the reaction Generally, reactions should be interpreted as emoji. So, "smile" would translate to 😄 in UI clients. That being said, how you ultimately choose to render a reaction in your app is up to you. + +## Notifications and reactions + +Reactions have `shouldPush` set to `false`, which means that reactions do not trigger push notifications as long as the notification server respects this flag. diff --git a/docs/build/messages/read-receipt.mdx b/docs/build/messages/read-receipt.mdx index 16dd9c152..7e1c16019 100644 --- a/docs/build/messages/read-receipt.mdx +++ b/docs/build/messages/read-receipt.mdx @@ -297,6 +297,10 @@ if (message.contentTypeId === "xmtp.org/readReceipt:1.0") { _`ReadReceipts` have an `undefined` or `nil` fallback, indicating the message is not expected to be displayed. Refer to [how handle unsupported content types](/docs/build/messages/#handle-an-unsupported-content-type-error) section._ +## Notifications and read receipts + +Read receipts have `shouldPush` set to `false`, which means that read receipts do not trigger push notifications as long as the notification server respects this flag. + ## Use a read receipt Generally, a read receipt indicator should be displayed under the message it's associated with. The indicator can include a timestamp. Ultimately, how you choose to display a read receipt indicator is completely up to you. diff --git a/docs/build/notifications.md b/docs/build/notifications.md deleted file mode 100644 index 1d6070e36..000000000 --- a/docs/build/notifications.md +++ /dev/null @@ -1,186 +0,0 @@ ---- -sidebar_label: Notifications -sidebar_position: 20 ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; -import pushnotifsettings from '/docs/concepts/img/push-notif-settings.png'; -import pushnotifsdecrypted from '/docs/concepts/img/push-notifs-decrypted.jpg'; -import badgingorb from '/docs/concepts/img/badging-orb.jpg'; -import unreadbadge from '/docs/concepts/img/unread-badge.png'; - -# Build push notifications with XMTP - -Push notifications can be a highly effective way to engage your users and increase app retention. In addition to providing push notifications for new messages, provide them for new conversations. - -![](./img/notif-diagram.png) - -## Run your own notification server - -See the [example-notification-server-go](https://github.com/xmtp/example-notification-server-go) for an example push notification server written in Golang that you can use as a reference for how you might provide a server for your app. - -This example branch can serve as the basis for what you might want to provide for your own notification server. The branch also demonstrates how to generate the proto code if you decide to perform these tasks for your own app. This proto code from the example notification server has already been generated in the `xmtp-android` example app. - -Check out the [Setup Guide for XMTP Notification Server](/docs/tutorials/other/notifications-server). - -## Firebase Cloud Messaging - -You can use a Firebase Cloud Messaging server and an example push notification server to enable the `xmtp-react-native` example app to send push notifications. - -Perform this setup to understand how you might want to enable push notifications for your own app built with the `xmtp-react-native` SDK. - -1. Create an FCM project. - -2. Generate FCM credentials, which you need to run the example notification server. To do this, from the FCM dashboard, click the gear icon next to **Project Overview** and select **Project settings**. Select **Service accounts**. Select **Go** and click **Generate new private key**. - -3. This will download a JSON file containing your service account credentials. e.g. `xmtp-web-firebase-adminsdk-sd34dsd-d34dcdf3.json` - -4. Get the FCM project ID and FCM credentials you created earlier and run: - - ```bash - YOURFCMJSON=`cat YOURFIREBASEADMINFROMSTEP4.json` - ``` - - ```bash - dev/run \ - --xmtp-listener-tls \ - --xmtp-listener \ - --api \ - -x "production.xmtp.network:5556" \ - -d "postgres://postgres:xmtp@localhost:25432/postgres?sslmode=disable" \ - --fcm-enabled \ - --fcm-credentials-json=$YOURFCMJSON \ - --fcm-project-id="YOURFCMPROJECTID" - ``` - -5. You should now be able to see push notifications coming across the local network. - -## Decode a notification envelope - -You can decode a single `Envelope` from XMTP using the `decode` method: - - - - -Not applicable - - - - -Not applicable - - - - -```kotlin -val conversation = - client.conversations.newConversation("0x3F11b27F323b62B159D2642964fa27C46C841897") - -// Assume this function returns an Envelope that contains a message for the above conversation -val envelope = getEnvelopeFromXMTP() - -val decodedMessage = conversation.decode(envelope) -``` - - - - -```swift -let conversation = try await client.conversations.newConversation( - with: "0x3F11b27F323b62B159D2642964fa27C46C841897") - -// Assume this function returns an Envelope that contains a message for the above conversation -let envelope = getEnvelopeFromXMTP() - -let decodedMessage = try conversation.decode(envelope) -``` - - - - -Code sample coming soon - - - - -Code sample coming soon - - - - -## Android - -For Android apps, see [Enable the `xmtp-android` example app to send push notifications](https://github.com/xmtp/xmtp-android/blob/main/library/src/main/java/org/xmtp/android/library/push/README.md) to explore how you might enable push notifications for your own app built with the [`xmtp-android` SDK](https://github.com/xmtp/xmtp-android). - -1. Checkout the `push-notifications-example` branch - -2. Add your `google-services.json` file to the `example/android/app` folder if you haven't already done it as a part of the FCM project creation process. - -3. Uncomment `apply plugin: 'com.google.gms.google-services'` in the example app's `build.gradle` file. - -4. Uncomment `classpath('com.google.gms:google-services:4.3.15')` in the top level of the example app's `build.gradle` file. - -5. Sync the gradle project. - -6. Replace `YOUR_SERVER_ADDRESS` in the `PullController.ts` file. If you're using the example notification server, it should be something like `YOURIPADDRESS:8080` since the Android emulator takes over localhost. - -7. Change the example app's environment to `production` in both places in `AuthView.tsx`. - -8. Replace `YOUR_FIREBASE_SENDER_ID` in the `PullController.ts` with your sender ID from Firebase. - -## iOS - -For iOS apps, see the `xmtp-ios` [example app and notification service](https://github.com/xmtp/xmtp-ios/tree/main/XMTPiOSExample) to explore how you might enable push notifications for your own app built with the [`xmtp-ios` SDK](https://github.com/xmtp/xmtp-ios). - -When building an iOS app with XMTP, you might want to suppress certain Apple push notifications. For example, you should suppress push notifications for a user's own messages. - -You can do this using the [com.apple.developer.usernotifications.filtering](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_developer_usernotifications_filtering) entitlement. - -To use this entitlement, you must first receive permission from Apple. To request permission, use the app owner's Apple developer account to submit this application: [https://developer.apple.com/contact/request/notification-service](https://developer.apple.com/contact/request/notification-service). - -:::tip Submit your application early - -The approval process can take 2-3 weeks or longer. - -::: - -Here are some sample answers to help you complete the application: - -- **App name:** [Your app name] -- **App Store URL:** [Your app store URL] -- **Apple ID of App:** [Your app ID] -- **App Type:** Messaging -- **Does your app provide end-to-end encryption?** Yes -- **Explain why existing APIs are not adequate for your app:** The existing APIs always show some sort of notification when a push comes in. We don't want to show a notification for a user's own messages. -- **Explain why your app doesn’t show a visible notification each time a push notification is received:** The server delivering notifications only knows of the existence of a conversation. It does not know who the sender or recipient are. That data is decoded on the device in the extension. As a result, it sends a push notification for every message that occurs in the conversation. We want to filter out notifications for notifications that the user sent. -- **When your extension runs, what system and network resources does it need?** We might need to make a GRPC request in order to load additional information about a conversation. This is only necessary when we haven't stored the conversation details locally, which is expected to be less common than being able to just decode the conversation locally. -- **How often does your extension run? What can trigger it to run?** The extension will run whenever a message is sent or received in a conversation. The frequency will depend on how active a user is. - -## React Native - -See [A practical guide to building a push notification client](https://github.com/xmtp/example-notification-server-go/blob/main/docs/notifications-client-guide.md). This guide is based on a React Native example, but can serve as a reference for how you might provide a notification client in your language of choice. - -## Best practices for notifications - -- Display push notifications only for messages sent **to** a user. In other words, do not send a push notification to a user about a message they sent. To do this, filter out messages sent by the user and don't send push notifications for them. - -- Provide a separate setting for enabling and disabling direct message push notifications. For example, if you’re building a Lens app, provide a setting for XMTP push notifications that’s separate from Lens push notifications for posts, comments, likes, and so forth. For example, here are push notification settings in the Orb app: - - - -- Decrypt messages for push notifications so you can display the contents within the notification. For example, here is a decrypted push notification provided by the [Converse app](https://getconverse.app/). - - - -- Display badges that indicate the presence of new notifications, messages, or conversations to help with engagement and interaction success. - - - Here is a conversation icon badge showing the presence of an unread message: - - - - Along these lines, be sure to unbadge conversations in which the user sent the latest message to avoid displaying unnecessary badges as users send messages across different apps. The action of sending the latest message implies that the user has seen the conversation. - - - Here is an app icon badge showing the number of unread messages in the Orb app: - - diff --git a/docs/build/notifications/_category_.json b/docs/build/notifications/_category_.json new file mode 100644 index 000000000..460eee063 --- /dev/null +++ b/docs/build/notifications/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Notifications", + "position": 20, + "collapsible": false, + "collapsed": false +} diff --git a/docs/build/notifications/android.md b/docs/build/notifications/android.md new file mode 100644 index 000000000..30af6ca24 --- /dev/null +++ b/docs/build/notifications/android.md @@ -0,0 +1,132 @@ +--- +sidebar_label: Android +sidebar_position: 22 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import pushnotifsettings from '/docs/concepts/img/push-notif-settings.png'; +import pushnotifsdecrypted from '/docs/concepts/img/push-notifs-decrypted.jpg'; +import badgingorb from '/docs/concepts/img/badging-orb.jpg'; +import unreadbadge from '/docs/concepts/img/unread-badge.png'; + +# Enable the example app to send push notifications + +You can use a Firebase Cloud Messaging server and an example push notification server to enable the `xmtp-android` example app to send push notifications. + +Perform this setup to understand how you might want to enable push notifications for your own app built with the `xmtp-android` SDK. + +## Set up a Firebase Cloud Messaging server + +For this tutorial, we'll use [Firebase Cloud Messaging](https://console.firebase.google.com/) (FCM) as a convenient way to set up a messaging server. + +1. Create an FCM project. + +2. Add the example app to the FCM project. This generates a `google-services.json` file that you need in subsequent steps. + +3. Add the `google-services.json` file to the example app's project as described in the FCM project creation process. + +4. Generate FCM credentials, which you need to run the example notification server. To do this, from the FCM dashboard, click the gear icon next to **Project Overview** and select **Project settings**. Select **Service accounts**. Select **Go** and click **Generate new private key**. + +## Run an example notification server + +Now that you have an FCM server set up, take a look at the `kotlin` folder in the `example-notifications-server-go` repo. + +These files can serve as the basis for what you might want to provide for your own notification server. This proto code from the example notification server has already been generated and add in the `xmtp-android` example app if you use the example notification server as is. + +**To run an example notification server:** + +1. Clone the [example-notification-server-go](https://github.com/xmtp/example-notification-server-go) repo. + +2. Complete the steps in [Local Setup](https://github.com/xmtp/example-notification-server-go/blob/np/export-kotlin-proto-code/README.md#local-setup). + +3. Get the FCM project ID and the FCM credentials you created in step 4 of setting up FCM and run: + + ```bash + YOURFCMJSON=$(cat /path/to/FCMCredentials.json) + ``` + + ```bash + dev/run \ + --xmtp-listener-tls \ + --xmtp-listener \ + --api \ + -x "grpc.production.xmtp.network:443" \ + -d "postgres://postgres:xmtp@localhost:25432/postgres?sslmode=disable" \ + --fcm-enabled \ + --fcm-credentials-json=$YOURFCMJSON \ + --fcm-project-id="YOURFCMPROJECTID" + ``` + +4. You should now be able to see push notifications coming across the local network. + +## Update the example app to send push notifications + +1. Add your `google-services.json` file to the `example` folder, if you haven't already done it as a part of the FCM project creation process. + +2. Uncomment `id 'com.google.gms.google-services'` in the example app's `build.gradle` file. + +3. Uncomment the following code in the top level of the example app's `build.gradle` file: + + ``` + buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.google.gms:google-services:4.3.15' + } + } + ``` + +4. Sync the gradle project. + +5. Add the example notification server address to the example app's `MainActivity`. In this case, it should be `PushNotificationTokenManager.init(this, "10.0.2.2:8080")`. + +6. Change the example app's environment to `XMTPEnvironment.PRODUCTION` in `ClientManager.kt`. + +7. Set up the example app to register the FCM token with the network and then subscribe each conversation to push notifications. For example: + + ```kotlin + XMTPPush(context, "10.0.2.2:8080").register(token) + ``` + + ```kotlin + val hmacKeysResult = ClientManager.client.conversations.getHmacKeys() + val subscriptions = conversations.map { + val hmacKeys = hmacKeysResult.hmacKeysMap + val result = hmacKeys[it.topic]?.valuesList?.map { hmacKey -> + Service.Subscription.HmacKey.newBuilder().also { sub_key -> + sub_key.key = hmacKey.hmacKey + sub_key.thirtyDayPeriodsSinceEpoch = hmacKey.thirtyDayPeriodsSinceEpoch + }.build() + } + + Service.Subscription.newBuilder().also { sub -> + sub.addAllHmacKeys(result) + sub.topic = it.topic + sub.isSilent = it.version == Conversation.Version.V1 + }.build() + } + + XMTPPush(context, "10.0.2.2:8080").subscribeWithMetadata(subscriptions) + ``` + + ```kotlin + XMTPPush(context, "10.0.2.2:8080").unsubscribe(conversations.map { it.topic }) + ``` + +## Decode a notification envelope + +You can decode a single `Envelope` from XMTP using the `decode` method: + +```kotlin +val conversation = + client.conversations.newConversation("0x3F11b27F323b62B159D2642964fa27C46C841897") + +// Assume this function returns an Envelope that contains a message for the above conversation +val envelope = getEnvelopeFromXMTP() + +val decodedMessage = conversation.decode(envelope) +``` diff --git a/docs/build/notifications/img/notifications/cmd1.png b/docs/build/notifications/img/notifications/cmd1.png new file mode 100644 index 000000000..b3b7c26ed Binary files /dev/null and b/docs/build/notifications/img/notifications/cmd1.png differ diff --git a/docs/build/notifications/img/notifications/cmd2.png b/docs/build/notifications/img/notifications/cmd2.png new file mode 100644 index 000000000..fee25d6cf Binary files /dev/null and b/docs/build/notifications/img/notifications/cmd2.png differ diff --git a/docs/build/notifications/img/notifications/cmd3.png b/docs/build/notifications/img/notifications/cmd3.png new file mode 100644 index 000000000..bb0df9f2c Binary files /dev/null and b/docs/build/notifications/img/notifications/cmd3.png differ diff --git a/docs/build/notifications/img/notifications/cmd4.png b/docs/build/notifications/img/notifications/cmd4.png new file mode 100644 index 000000000..6503eb65f Binary files /dev/null and b/docs/build/notifications/img/notifications/cmd4.png differ diff --git a/docs/build/notifications/ios.md b/docs/build/notifications/ios.md new file mode 100644 index 000000000..efce3aa20 --- /dev/null +++ b/docs/build/notifications/ios.md @@ -0,0 +1,74 @@ +--- +sidebar_label: iOS +sidebar_position: 22 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import pushnotifsettings from '/docs/concepts/img/push-notif-settings.png'; +import pushnotifsdecrypted from '/docs/concepts/img/push-notifs-decrypted.jpg'; +import badgingorb from '/docs/concepts/img/badging-orb.jpg'; +import unreadbadge from '/docs/concepts/img/unread-badge.png'; + +# Enable the example app to send push notifications + +For iOS apps, see the `xmtp-ios` [example app and notification service](https://github.com/xmtp/xmtp-ios/tree/main/XMTPiOSExample) to explore how you might enable push notifications for your own app built with the [`xmtp-ios` SDK](https://github.com/xmtp/xmtp-ios). + +To adapt the push notification setup for the XMTP iOS example app, follow this comprehensive guide: + +This guide will help you set up push notifications for the XMTP iOS example app using Firebase Cloud Messaging (FCM) and a custom notification server. + +## Prerequisites + +- An iOS device for testing, as push notifications do not work on simulators. +- A Firebase account and a project set up in the Firebase console. + +## Firebase Cloud Messaging Setup + +1. **Create an FCM Project**: Go to the [Firebase Console](https://console.firebase.google.com/), create a new project, and follow the setup instructions. + +2. **Add Your App to the FCM Project**: Add your iOS app to the project by following the Firebase setup workflow. You'll need your app's bundle ID. + +3. **Download `GoogleService-Info.plist`**: At the end of the setup, download the `GoogleService-Info.plist` file and add it to your Xcode project. + +4. **Generate FCM Credentials**: In the Firebase console, navigate to your project settings, select the Cloud Messaging tab, and note your server key and sender ID. You'll need these for your notification server. + +## Configure the iOS App for Push Notifications + +1. **Enable Push Notifications**: In Xcode, go to your project's target capabilities and enable Push Notifications. + +2. **Register for Notifications**: Modify the `AppDelegate` to register for remote notifications and handle the device token. + +3. **Handle Incoming Notifications**: Implement the necessary delegate methods to handle incoming notifications and foreground notification display. + +## Run the Notification Server + +1. **Clone and Configure the Notification Server**: If you're using the example notification server, clone the repository and follow the setup instructions. Make sure to configure it with your FCM server key. + +2. **Run the Server**: Start the server locally or deploy it to a hosting service. + +- **Subscribe to Push Notifications in the App**: When initializing the XMTP client in your app, subscribe to push notifications using the device token obtained during registration. + +- **Decode a Notification Envelope**: When you receive a push notification, you may want to decode the notification envelope to display a message preview or other information. + +## Apple entitlement + +You can utilize the [com.apple.developer.usernotifications.filtering](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_developer_usernotifications_filtering) entitlement. To do so, you need to obtain permission from Apple. Submit the application using the app owner's Apple developer account via [https://developer.apple.com/contact/request/notification-service](https://developer.apple.com/contact/request/notification-service). + +:::tip Submit your application early + +The approval process can take 2-3 weeks or longer. + +::: + +Here are some sample answers to help you complete the application: + +- **App name:** [Your app name] +- **App Store URL:** [Your app store URL] +- **Apple ID of App:** [Your app ID] +- **App Type:** Messaging +- **Does your app provide end-to-end encryption?:** Yes +- **Explain why existing APIs are not adequate for your app:** The existing APIs always show some sort of notification when a push comes in. We don't want to show a notification for a user's own messages. +- **Explain why your app doesn’t show a visible notification each time a push notification is received:** The server delivering notifications only knows of the existence of a conversation. It does not know who the sender or recipient are. That data is decoded on the device in the extension. As a result, it sends a push notification for every message that occurs in the conversation. We want to filter out notifications for notifications that the user sent. +- **When your extension runs, what system and network resources does it need?:** We might need to make a GRPC request in order to load additional information about a conversation. This is only necessary when we haven't stored the conversation details locally, which is expected to be less common than being able to just decode the conversation locally. +- **How often does your extension run? What can trigger it to run?:** The extension will run whenever a message is sent or received in a conversation. The frequency will depend on how active a user is. diff --git a/docs/build/notifications/notifications.md b/docs/build/notifications/notifications.md new file mode 100644 index 000000000..4a6486513 --- /dev/null +++ b/docs/build/notifications/notifications.md @@ -0,0 +1,64 @@ +--- +sidebar_label: overview +sidebar_position: 20 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import pushnotifsettings from '/docs/concepts/img/push-notif-settings.png'; +import pushnotifsdecrypted from '/docs/concepts/img/push-notifs-decrypted.jpg'; +import badgingorb from '/docs/concepts/img/badging-orb.jpg'; +import unreadbadge from '/docs/concepts/img/unread-badge.png'; + +# Notifications with XMTP + +XMTP notifications are designed to seamlessly integrate into both mobile and web applications, providing developers with a robust framework for managing message delivery and notifications. This guide briefly introduces the key components and directs you to resources for detailed exploration. + +## Concepts + +- [**Overview**](./overview): Understand the fundamentals of XMTP notifications, including their importance and how they enhance the user experience in messaging applications. + +- **Mobile**: Learn how XMTP notifications are tailored for mobile platforms, ensuring efficient delivery and user engagement through services like Firebase. + + - [**Android**](./android): Set up your notifications client for Android devices using the Kotlin SDK + - [**iOS**](./ios): Learn how to navigate App Store guidelines and integrate notifications using the iOS SDK + +- [**Example Notification Server**](./server): Access a practical example of setting up a notification server with XMTP, providing a solid starting point for your own implementation. + +## Resources + +- [**Example Notification Server Implementation**](https://github.com/xmtp/example-notification-server-go): Dive into an example notification server to understand the integration process and how to leverage XMTP for efficient notification handling + +- [**Build a Push Notification Client**](https://github.com/xmtp/example-notification-server-go/blob/main/docs/notifications-client-guide.md): Learn how to implement a notifications client in your language and framework of choice + +- [**Integration Test Suite**](https://github.com/xmtp/example-notification-server-go/blob/main/integration/README.md): Run an integration test suite for the notification server + +## Example apps + +- **iOS**: If you are an iOS developer looking to integrate XMTP notifications, you can explore the example app and notification service featured in the [xmtp-ios repository](https://github.com/xmtp/xmtp-ios/tree/main/XMTPiOSExample) from the `xmtp-ios` SDK, along with its corresponding [documentation](./ios). + +- **Android**: Android developers aiming to integrate push notifications into their applications can refer to this [example app](https://github.com/xmtp/xmtp-android/tree/main/example) and its accompanying [documentation](./android). + +## Best practices for notifications + +- Display push notifications only for messages sent **to** a user. In other words, do not send a push notification to a user about a message they sent. To do this, filter out messages sent by the user and don't send push notifications for them. + +- Provide a separate setting for enabling and disabling direct message push notifications. For example, if you’re building a Lens app, provide a setting for XMTP push notifications that’s separate from Lens push notifications for posts, comments, likes, and so forth. For example, here are push notification settings in the Orb app: + + + +- Decrypt messages for push notifications so you can display the contents within the notification. For example, here is a decrypted push notification provided by the [Converse app](https://getconverse.app/). + + + +- Display badges that indicate the presence of new notifications, messages, or conversations to help with engagement and interaction success. + + - Here is a conversation icon badge showing the presence of an unread message: + + + + Along these lines, be sure to unbadge conversations in which the user sent the latest message to avoid displaying unnecessary badges as users send messages across different apps. The action of sending the latest message implies that the user has seen the conversation. + + - Here is an app icon badge showing the number of unread messages in the Orb app: + + diff --git a/docs/build/notifications/overview.md b/docs/build/notifications/overview.md new file mode 100644 index 000000000..f2adf9d67 --- /dev/null +++ b/docs/build/notifications/overview.md @@ -0,0 +1,155 @@ +--- +sidebar_label: Overview +sidebar_position: 20 +--- + +# Notification architecture overview + +The notification architecture might appear complex initially, but we've simplified it into manageable concepts and steps. This overview walks you through the notification handling and delivery process in XMTP, using practical examples to illustrate each step. + +![Sequence diagram illustrating an overview of the notification handling and delivery process in XMTP](../img/notif-diagram.png) + +To describe the process with an example, consider a message being sent from Alice to Bob using a mobile messaging app. This app is built with React Native for iOS devices and uses Firebase for notifications. Here is a detailed explanation of each step, along with code examples. + +## 1. Initialize the XMTP client + +Alice opens the frontend on her iOS device and initializes her new wallet with XMTP. + +```javascript +import { Client } from "@XMTP"; // XMTP JavaScript client + +// Alice initializes her wallet on the Frontend +const XMTPClient = await Client.create(aliceSigner, { env: "dev" }); +``` + +## 2. Register the device with Firebase + +When Alice launches the frontend on her device for the first time, the app registers with Firebase to receive notifications. + +```javascript +// Get token from Firebase +const deviceToken = await messaging().getToken(); +// Get unique ID for the device +const installationId = await installations().getId(); +// Assume this function sets up your notification client +const client = createNotificationClient(); + +await client.registerInstallation({ + installationId, + deliveryMechanism: { + deliveryMechanismType: { + value: deviceToken, + case: "FirebaseDeviceToken", + }, + }, +}); +``` + +## 3. Subscribe to topics + +The notification server adds Alice's `installationId` to certain topics. The list of `subscriptionDetails` includes all information needed to join topics, such as user permission and HMAC keys for safely checking messages. + +- `consentState`: Notifications are only subscribed to if the `consentState` of a conversation is "allowed". This ensures that users receive notifications only for conversations they have consented to. [Learn more](../user-consent) + +- `Invite` topic V2: Clients use invite topics to initiate conversations between wallets. [Learn more](https://github.com/xmtp/proto/blob/main/PROTOCOL.md#invitations) + +- `Intro` topic V1: Clients use intro topics to store the first message sent between two wallets + +```javascript +let subscriptionDetails = []; + +// Filter conversations to only include those with user consent, to respect privacy and avoid SPAM. +const consentedConversations = conversations.filter( + (conversation) => conversation.consentState === "allowed", +); + +// Compile the subscription info, attaching the HMAC key when available. +consentedConversations.forEach((conversation) => { + subscriptionDetails.push({ + topic: conversation.topic, + hmacKey: conversation.hmacKey || null, + }); +}); + +// Special topics without HMAC keys +subscriptionDetails.push({ + topic: buildUserInviteTopic(userAddress), + hmacKey: null, +}); +subscriptionDetails.push({ + topic: buildUserIntroTopic(userAddress), + hmacKey: null, +}); + +// This operation sends the subscription details to the notification service. +await notificationClient.subscribeWithMetadata({ + installationId, + subscriptions: subscriptionDetails, +}); +``` + +## 4. Listen for notifications + +Alice's frontend is now listening for incoming notifications. + +```javascript +// Listener for incoming Firebase notifications +messaging().onMessage(async (remoteMessage) => { + console.log("A new message arrived!", remoteMessage); +}); +``` + +## 5. Send a message + +Bob sends a message to Alice using his instance of the frontend. + +```javascript +const bobClient = await Client.create(bobSigner, { env: "dev" }); +const conversation = await bobClient.conversations.newConversation(aliceWallet); +await conversation.send("Hello Alice!"); +``` + +## 6. XMTP network dispatch + +The XMTP network sends the encrypted message and topic to the notification server. + +```javascript +// Pseudo-code for XMTP network sending encrypted message +const messageTopic = "XMTP/0/dm-alice-XMTP-topic-id"; +sendToNotificationServer(encryptedMessage, messageTopic); +``` + +## 7. Trigger push notifications + +The notification server triggers a push notification to Firebase. + +```javascript +const message = { + data: { + topic: messageTopic, + message: encryptedMessage, + }, + topic: messageTopic, +}; +firebase_admin.messaging().send(message); +``` + +## 8. Firebase notification forwarding + +Firebase forwards the notification to Alice's device. + +## 9. Decrypt the message + +Alice's frontend receives the notification and decrypts the message. + +```javascript +// Decrypting the message when a notification is received from Firebase +Firebase.messaging().onMessage((payload) => { + const decryptedMessage = decryptMessage(payload.data.message, encryptionKey); + console.log("Decrypted message:", decryptedMessage); +}); +``` + +The XMTP framework's notification architecture, in conjunction with Firebase Cloud Messaging, offers a secure and reliable approach to notification management and delivery. This comprehensive guide outlines the essential steps, from device registration to message decryption, to streamline the process and improve developers' notification implementation experience. + +For more details, see [A Practical Guide To Building A Push Notification Client](https://github.com/XMTP/example-notification-server-go/blob/main/docs/notifications-client-guide.md?plain=1). diff --git a/docs/tutorials/other/notifications-server.md b/docs/build/notifications/server.md similarity index 70% rename from docs/tutorials/other/notifications-server.md rename to docs/build/notifications/server.md index fc0935aba..5c63ae6dd 100644 --- a/docs/tutorials/other/notifications-server.md +++ b/docs/build/notifications/server.md @@ -1,54 +1,44 @@ --- -sidebar_label: Go notifications server -sidebar_position: 2 +slug: server +title: Notification server --- -# Setup guide for XMTP notifications server +# Setup Guide for XMTP Notification Server This guide is a supplement to [core instructions](https://github.com/xmtp/example-notification-server-go/blob/np/export-kotlin-proto-code/README.md#local-setup) provided in the `example-notification-server-go` repository and aims to address some common misconceptions and issues encountered during the setup. This guide is written for macOS users, but the steps should be similar for Linux users. -![XMTP notification server post card](../img/notifications/bells.png) - - - ## Install Docker -1. Install docker desktop depending on your OS: +1. Install Docker Desktop depending on your OS: - [Mac](https://docs.docker.com/docker-for-mac/install/) - [Windows](https://docs.docker.com/docker-for-windows/install/) - [Linux](https://docs.docker.com/engine/install/) -
- - :::tip - After installation, make sure Docker is running by searching for Docker in Spotlight and opening the application. You don't need to do anything with the UI from Docker. We are going to use only terminal commands. - ::: - 2. You need to have Docker and Docker Compose installed on your system. If you don't have Docker and Docker Compose installed, you can install them using Homebrew: ```bash brew install docker docker-compose docker-credential-desktop ``` -3. Make sure Docker Desktop is running by searching for Docker in Spotlight and opening the application. +3. Make sure Docker Desktop is running by searching for Docker in Spotlight and opening the application. You don't need to do anything with the UI from Docker. We are going to use only terminal commands. ## Install Go -1. If you need to upgrade Go on your system, you can do it via Homebrew: +If you need to upgrade Go on your system, you can do it via Homebrew: - ```bash - brew install go - ``` +```bash +brew install go +``` - If you encounter any errors like `Error: go 1.17.2 is already installed`, it means you already have Go installed on your system. You can check the version of Go installed on your system using: +If you encounter any errors like `Error: go 1.17.2 is already installed`, it means you already have Go installed on your system. You can check the version of Go installed on your system using: - ```bash - brew update - brew upgrade go - ``` +```bash +brew update +brew upgrade go +``` - After these steps, you should be able to update Homebrew and upgrade Go without issues. +After these steps, you should be able to update Homebrew and upgrade Go without issues. ## Set up the server @@ -58,7 +48,7 @@ This guide is a supplement to [core instructions](https://github.com/xmtp/exampl ./dev/up ``` - ![./dev/up in CLI](../img/notifications/cmd1.png) + ![set up the server](./img/notifications/cmd1.png) If you encounter an error like `error getting credentials - err: docker-credential-desktop resolves to executable in current directory (./docker-credential-desktop), out:`, it's likely because Docker is not running. Make sure Docker Desktop is running and try the command again. @@ -78,32 +68,32 @@ This guide is a supplement to [core instructions](https://github.com/xmtp/exampl ## Run the server -1. The server can be run using the `./dev/run` script with the `--xmtp-listener` and `--api` flags: +Run the server using the `./dev/run` script with the `--xmtp-listener` and `--api` flags: - ```bash - ./dev/up - ``` +```bash +./dev/up +``` - ![./dev/up in CLI](../img/notifications/cmd2.png) +![./dev/up in CLI](./img/notifications/cmd2.png) - ```bash - source .env - ./dev/run --xmtp-listener --api - ``` +```bash +source .env +./dev/run --xmtp-listener --api +``` - This will start both the `worker` and the `api` service. The `worker` listens for new messages on the XMTP network and sends push notifications, and the `api` service handles HTTP/GRPC requests. +This starts both the `worker` and `api` services. The `worker` listens for new messages on the XMTP network and sends push notifications. The `api` service handles HTTP/GRPC requests. - ![./dev/run --xmtp-listener --api in CLI](../img/notifications/cmd3.png) +![./dev/run --xmtp-listener --api in CLI](./img/notifications/cmd3.png) You can now send notifications to your device using an [XMTP push notification client](https://github.com/xmtp/example-notification-server-go/blob/main/docs/notifications-client-guide.md). -![Server and API server started in CLI](../img/notifications/cmd4.png) +![dev/run in CLI](./img/notifications/cmd4.png) ## Troubleshooting - In case Docker or Docker Compose commands are not recognized, it might mean that they are not installed or their executable paths are not included in your system's PATH variable. Make sure Docker and Docker Compose are installed and their paths are included in your system's PATH. -- If you encounter Go-related errors during the build, it's often due to missing packages or outdated versions. Always make sure your Go is up-to-date and use the `go get` or `go mod download` commands to fetch the necessary dependencies. +- If you encounter Go-related errors during the build, it's often due to missing packages or outdated versions. Always make sure your Go is up to date and use the `go get` or `go mod download` commands to fetch the necessary dependencies. - If you encounter any error like `./dev/up: line 3: docker-compose: command not found`, it's because you don't have Docker Compose installed on your system. Use the above command to install it. @@ -112,6 +102,7 @@ You can now send notifications to your device using an [XMTP push notification c - If `brew update` is giving errors, it might be due to changes in Homebrew's repository. Homebrew has switched from using "master" to "main" as its default branch. The steps provided in the "Upgrading Go" section should help resolve this issue. - If you encounter any errors during `brew update` like `fatal: couldn't find remote ref refs/heads/master`, this means Homebrew is having trouble updating its repositories. To fix this, run: + ```bash cd $(brew --repository) git checkout main @@ -129,4 +120,4 @@ You can now send notifications to your device using an [XMTP push notification c } ``` -If you have further issues or questions, don't hesitate to ask for help in the [XMTP Discord](https://discord.gg/xmtp). \ No newline at end of file +If you have further issues or questions, don't hesitate to ask for help in the [XMTP Discord](https://discord.gg/xmtp).