Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Push notifications #27

Merged
merged 16 commits into from
May 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ project.xcworkspace
.settings
local.properties
android.iml
*google-services.json

# Cocoapods
#
Expand Down
70 changes: 70 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,73 @@ We're working on testing the end-to-end installation and will provide more platf
Your app must use Android `minSdkVersion = 22` to work with the `xmtp-react-native` SDK.

We're working on testing the end-to-end installation and will provide more platform-specific configuration details.

## 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-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.

### 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 [export-kotlin-proto-code](https://github.com/xmtp/example-notification-server-go/tree/np/export-kotlin-proto-code) branch in the `example-notifications-server-go` repo.

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.

**To run a notification server based on the example branch:**

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 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"
```

4. You should now be able to see push notifications coming across the local network.

### Update the Android example app to send push notifications

1. 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.

2. Uncomment `apply plugin: 'com.google.gms.google-services'` in the example app's `build.gradle` file.

3. Uncomment `classpath('com.google.gms:google-services:4.3.15')` in the top level of the example app's `build.gradle` file.

4. Sync the gradle project.

5. 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.

6. Change the example app's environment to `production` in both places in `AuthView.tsx`.

7. Replace `YOUR_FIREBASE_SENDER_ID` in the `PullController.ts` with your sender ID from Firebase.

### Update the iOS example app to send push notifications

Coming soon.
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,6 @@ repositories {
dependencies {
implementation project(':expo-modules-core')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
implementation "org.xmtp:android:0.1.1"
implementation "org.xmtp:android:0.1.3"
implementation 'com.google.code.gson:gson:2.10.1'
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ import org.xmtp.android.library.Conversation
import org.xmtp.android.library.SigningKey
import org.xmtp.android.library.XMTPEnvironment
import org.xmtp.android.library.XMTPException
import org.xmtp.android.library.messages.EnvelopeBuilder
import org.xmtp.android.library.messages.InvitationV1ContextBuilder
import org.xmtp.android.library.messages.PrivateKeyBuilder
import org.xmtp.android.library.messages.Signature
import org.xmtp.android.library.push.XMTPPush
import org.xmtp.proto.message.contents.SignatureOuterClass
import java.util.Date
import java.util.UUID
Expand Down Expand Up @@ -84,6 +86,7 @@ class XMTPModule : Module() {
)

private var client: Client? = null
private var xmtpPush: XMTPPush? = null
private var signer: ReactNativeSigner? = null
private val conversations: MutableMap<String, Conversation> = mutableMapOf()
private val subscriptions: MutableMap<String, Job> = mutableMapOf()
Expand All @@ -92,7 +95,7 @@ class XMTPModule : Module() {
Name("XMTP")
Events("sign", "authed", "conversation", "message")

Function("address") {
Function("address") { clientAddress: String ->
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

App wouldn't compile unless I added these but haven't hooked up the testing stuff to actually use them.

if (client != null) {
client!!.address
} else {
Expand Down Expand Up @@ -129,7 +132,7 @@ class XMTPModule : Module() {

//
// Client API
AsyncFunction("listConversations") { ->
AsyncFunction("listConversations") { clientAddress: String ->
if (client == null) {
throw XMTPException("No client")
}
Expand All @@ -140,7 +143,7 @@ class XMTPModule : Module() {
}
}

AsyncFunction("loadMessages") { conversationTopic: String, conversationID: String?, limit: Int?, before: Long?, after: Long? ->
AsyncFunction("loadMessages") { clientAddress: String, conversationTopic: String, conversationID: String?, limit: Int?, before: Long?, after: Long? ->
if (client == null) {
throw XMTPException("No client")
}
Expand All @@ -155,7 +158,7 @@ class XMTPModule : Module() {
}

// TODO: Support content types
AsyncFunction("sendMessage") { conversationTopic: String, conversationID: String?, content: String ->
AsyncFunction("sendMessage") { clientAddress: String, conversationTopic: String, conversationID: String?, content: String ->
if (client == null) {
throw XMTPException("No client")
}
Expand All @@ -168,7 +171,7 @@ class XMTPModule : Module() {
DecodedMessageWrapper.encode(decodedMessage)
}

AsyncFunction("createConversation") { peerAddress: String, conversationID: String? ->
AsyncFunction("createConversation") { clientAddress: String, peerAddress: String, conversationID: String? ->
if (client == null) {
throw XMTPException("No client")
}
Expand All @@ -181,15 +184,44 @@ class XMTPModule : Module() {
ConversationWrapper.encode(conversation)
}

Function("subscribeToConversations") { subscribeToConversations() }
Function("subscribeToConversations") { clientAddress: String ->
subscribeToConversations()
}

AsyncFunction("subscribeToMessages") { topic: String, conversationID: String? ->
AsyncFunction("subscribeToMessages") { clientAddress: String, topic: String, conversationID: String? ->
subscribeToMessages(topic = topic, conversationId = conversationID)
}

AsyncFunction("unsubscribeFromMessages") { topic: String, conversationID: String? ->
AsyncFunction("unsubscribeFromMessages") { clientAddress: String, topic: String, conversationID: String? ->
unsubscribeFromMessages(topic = topic, conversationId = conversationID)
}

Function("registerPushToken") { pushServer: String, token: String ->
xmtpPush = XMTPPush(appContext.reactContext!!, pushServer)
xmtpPush?.register(token)
}

Function("subscribePushTopics") { topics: List<String> ->
if (topics.isNotEmpty()) {
if (xmtpPush == null) {
throw XMTPException("Push server not registered")
}
xmtpPush?.subscribe(topics)
}
}

AsyncFunction("decodeMessage") { topic: String, encryptedMessage: String, conversationID: String? ->
if (client == null) {
throw XMTPException("No client")
}
val encryptedMessageData = Base64.decode(encryptedMessage, Base64.NO_WRAP)
val envelope = EnvelopeBuilder.buildFromString(topic, Date(), encryptedMessageData)
val conversation =
findConversation(topic = topic, conversationId = conversationID)
?: throw XMTPException("no conversation found for $topic")
val decodedMessage = conversation.decode(envelope)
DecodedMessageWrapper.encode(decodedMessage)
}
}

//
Expand Down
2 changes: 2 additions & 0 deletions example/android/app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
apply plugin: "com.android.application"
apply plugin: "com.facebook.react"
// apply plugin: 'com.google.gms.google-services'

import com.android.build.OutputFile

Expand Down Expand Up @@ -192,6 +193,7 @@ android {
dependencies {
// The version of react-native is set by the React Native Gradle Plugin
implementation("com.facebook.react:react-android")
implementation platform('com.google.firebase:firebase-bom:31.5.0')

def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true";
def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true";
Expand Down
131 changes: 98 additions & 33 deletions example/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,34 +1,99 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="expo.modules.xmtpreactnativesdk.example">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<queries>
<intent>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="https"/>
</intent>
</queries>
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:usesCleartextTraffic="true">
<meta-data android:name="expo.modules.updates.ENABLED" android:value="true"/>
<meta-data android:name="expo.modules.updates.EXPO_SDK_VERSION" android:value="48.0.0"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://exp.host/@anonymous/xmtp-react-native-sdk-example"/>
<activity android:name=".MainActivity" android:label="@string/app_name" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:exported="true" android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="expo.modules.xmtpreactnativesdk.example"/>
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" android:exported="false"/>
</application>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="expo.modules.xmtpreactnativesdk.example">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<permission
android:name="${applicationId}.permission.C2D_MESSAGE"
android:protectionLevel="signature" />

<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
</queries>
<application
android:name=".MainApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true">
<meta-data
android:name="expo.modules.updates.ENABLED"
android:value="true" />
<meta-data
android:name="expo.modules.updates.EXPO_SDK_VERSION"
android:value="48.0.0" />
<meta-data
android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH"
android:value="ALWAYS" />
<meta-data
android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS"
android:value="0" />
<meta-data
android:name="expo.modules.updates.EXPO_UPDATE_URL"
android:value="https://exp.host/@anonymous/xmtp-react-native-sdk-example" />

<activity
android:name=".MainActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:theme="@style/Theme.App.SplashScreen"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data android:scheme="expo.modules.xmtpreactnativesdk.example" />
</intent-filter>
</activity>
<activity
android:name="com.facebook.react.devsupport.DevSettingsActivity"
android:exported="false" />

<meta-data
android:name="com.dieam.reactnativepushnotification.notification_channel_name"
android:value="YOUR NOTIFICATION CHANNEL NAME" />
<meta-data
android:name="com.dieam.reactnativepushnotification.notification_channel_description"
android:value="YOUR NOTIFICATION CHANNEL DESCRIPTION" />
<meta-data
android:name="com.dieam.reactnativepushnotification.notification_color"
android:resource="@android:color/white" />
<receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationPublisher" />
<receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationBootEventReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<service
android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationListenerService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
</application>
</manifest>
1 change: 1 addition & 0 deletions example/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ buildscript {
dependencies {
classpath('com.android.tools.build:gradle:7.4.1')
classpath('com.facebook.react:react-native-gradle-plugin')
// classpath('com.google.gms:google-services:4.3.15')
}
}

Expand Down
Loading