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

Group Chat Push Notifications #338

Merged
merged 15 commits into from
Apr 5, 2024
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
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ dependencies {
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.facebook.react:react-native:0.71.3'
implementation "com.daveanthonythomas.moshipack:moshipack:1.0.1"
implementation "org.xmtp:android:0.9.0"
implementation "org.xmtp:android:0.9.1"
// xmtp-android local testing setup below (comment org.xmtp:android above)
// implementation files('<PATH TO XMTP-ANDROID>/xmtp-android/library/build/outputs/aar/library-debug.aar')
// implementation 'com.google.crypto.tink:tink-android:1.7.0'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import com.facebook.common.util.Hex
import org.xmtp.android.library.messages.Topic
import org.xmtp.android.library.push.Service

class ReactNativeSigner(var module: XMTPModule, override var address: String) : SigningKey {
private val continuations: MutableMap<String, Continuation<Signature>> = mutableMapOf()
Expand Down Expand Up @@ -812,6 +814,29 @@ class XMTPModule : Module() {
}
}

AsyncFunction("processGroupMessage") Coroutine { clientAddress: String, id: String, encryptedMessage: String ->
withContext(Dispatchers.IO) {
logV("processGroupMessage")
val client = clients[clientAddress] ?: throw XMTPException("No client")
val group = findGroup(clientAddress, id)

val message = group?.processMessage(Base64.decode(encryptedMessage, NO_WRAP))
?: throw XMTPException("could not decrypt message for $id")
DecodedMessageWrapper.encodeMap(message.decrypt())
}
}

AsyncFunction("processWelcomeMessage") Coroutine { clientAddress: String, encryptedMessage: String ->
withContext(Dispatchers.IO) {
logV("processWelcomeMessage")
val client = clients[clientAddress] ?: throw XMTPException("No client")

val group =
client.conversations.fromWelcome(Base64.decode(encryptedMessage, NO_WRAP))
GroupWrapper.encode(client, group)
}
}

Function("subscribeToConversations") { clientAddress: String ->
logV("subscribeToConversations")
subscribeToConversations(clientAddress = clientAddress)
Expand Down Expand Up @@ -903,13 +928,34 @@ class XMTPModule : Module() {
xmtpPush?.register(token)
}

Function("subscribePushTopics") { topics: List<String> ->
Function("subscribePushTopics") { clientAddress: String, topics: List<String> ->
logV("subscribePushTopics")
if (topics.isNotEmpty()) {
if (xmtpPush == null) {
throw XMTPException("Push server not registered")
}
xmtpPush?.subscribe(topics)
val client = clients[clientAddress] ?: throw XMTPException("No client")

val hmacKeysResult = client.conversations.getHmacKeys()
val subscriptions = topics.map {
val hmacKeys = hmacKeysResult.hmacKeysMap
val result = hmacKeys[it]?.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)
if (!result.isNullOrEmpty()) {
sub.addAllHmacKeys(result)
}
sub.topic = it
}.build()
}

xmtpPush?.subscribeWithMetadata(subscriptions)
}
}

Expand Down
30 changes: 15 additions & 15 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,12 @@ PODS:
- hermes-engine/Pre-built (= 0.71.14)
- hermes-engine/Pre-built (0.71.14)
- libevent (2.1.12)
- LibXMTP (0.4.3-beta3)
- LibXMTP (0.4.3-beta4)
- Logging (1.0.0)
- MessagePacker (0.4.7)
- MMKV (1.3.3):
- MMKVCore (~> 1.3.3)
- MMKVCore (1.3.3)
- MMKV (1.3.4):
- MMKVCore (~> 1.3.4)
- MMKVCore (1.3.4)
- OpenSSL-Universal (1.1.2200)
- RCT-Folly (2021.07.22.00):
- boost
Expand Down Expand Up @@ -322,7 +322,7 @@ PODS:
- React-Core
- react-native-encrypted-storage (4.0.3):
- React-Core
- react-native-get-random-values (1.10.0):
- react-native-get-random-values (1.11.0):
- React-Core
- react-native-mmkv (2.11.0):
- MMKV (>= 1.2.13)
Expand Down Expand Up @@ -449,16 +449,16 @@ PODS:
- GenericJSON (~> 2.0)
- Logging (~> 1.0.0)
- secp256k1.swift (~> 0.1)
- XMTP (0.9.3):
- XMTP (0.9.6):
- Connect-Swift (= 0.12.0)
- GzipSwift
- LibXMTP (= 0.4.3-beta3)
- LibXMTP (= 0.4.3-beta4)
- web3.swift
- XMTPReactNative (0.1.0):
- ExpoModulesCore
- MessagePacker
- secp256k1.swift
- XMTP (= 0.9.3)
- XMTP (= 0.9.6)
- Yoga (1.14.0)

DEPENDENCIES:
Expand Down Expand Up @@ -711,11 +711,11 @@ SPEC CHECKSUMS:
GzipSwift: 893f3e48e597a1a4f62fafcb6514220fcf8287fa
hermes-engine: d7cc127932c89c53374452d6f93473f1970d8e88
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
LibXMTP: 19195625ed53f3f2d5ac3b1048275b6c2f152f03
LibXMTP: 3813a1f6c0cc2b4cd57dc4805c80da92e4b4bcab
Logging: 9ef4ecb546ad3169398d5a723bc9bea1c46bef26
MessagePacker: ab2fe250e86ea7aedd1a9ee47a37083edd41fd02
MMKV: f902fb6719da13c2ab0965233d8963a59416f911
MMKVCore: d26e4d3edd5cb8588c2569222cbd8be4231374e9
MMKV: ed58ad794b3f88c24d604a5b74f3fba17fcbaf74
MMKVCore: a67a1cede26175c413176f404a7cedec43f96a0b
OpenSSL-Universal: 6e1ae0555546e604dbc632a2b9a24a9c46c41ef6
RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1
RCTRequired: e9df143e880d0e879e7a498dc06923d728809c79
Expand All @@ -734,7 +734,7 @@ SPEC CHECKSUMS:
react-native-blob-util: d8fa1a7f726867907a8e43163fdd8b441d4489ea
react-native-config: 86038147314e2e6d10ea9972022aa171e6b1d4d8
react-native-encrypted-storage: db300a3f2f0aba1e818417c1c0a6be549038deb7
react-native-get-random-values: 384787fd76976f5aec9465aff6fa9e9129af1e74
react-native-get-random-values: 21325b2244dfa6b58878f51f9aa42821e7ba3d06
react-native-mmkv: e97c0c79403fb94577e5d902ab1ebd42b0715b43
react-native-netinfo: 8a7fd3f7130ef4ad2fb4276d5c9f8d3f28d2df3d
react-native-quick-base64: 777057ea4286f806b00259ede65dc79c7c706320
Expand Down Expand Up @@ -763,10 +763,10 @@ SPEC CHECKSUMS:
secp256k1.swift: a7e7a214f6db6ce5db32cc6b2b45e5c4dd633634
SwiftProtobuf: 407a385e97fd206c4fbe880cc84123989167e0d1
web3.swift: 2263d1e12e121b2c42ffb63a5a7beb1acaf33959
XMTP: 8c520ef61c6f7ab0db6fc53e40e9947c2c32f9ad
XMTPReactNative: f526ba6c0f19becb0b9cb97e461351f627cc932d
XMTP: 603eecf511ce63f6390b2468fc78500373c1e786
XMTPReactNative: 54fa7119379885089f695af928594d5720666ef6
Yoga: e71803b4c1fff832ccf9b92541e00f9b873119b9

PODFILE CHECKSUM: 95d6ace79946933ecf80684613842ee553dd76a2

COCOAPODS: 1.15.2
COCOAPODS: 1.14.2
2 changes: 1 addition & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"react-native-crypto": "^2.2.0",
"react-native-encrypted-storage": "^4.0.3",
"react-native-fs": "^2.20.0",
"react-native-get-random-values": "^1.10.0",
"react-native-get-random-values": "^1.11.0",
"react-native-mmkv": "^2.8.0",
"react-native-modal-selector": "^2.1.2",
"react-native-quick-base64": "^2.0.8",
Expand Down
158 changes: 110 additions & 48 deletions example/src/LaunchScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { NativeStackScreenProps } from '@react-navigation/native-stack'
import { ConnectWallet, useSigner } from '@thirdweb-dev/react-native'
import React, { useCallback, useEffect, useState } from 'react'
import { Button, ScrollView, StyleSheet, Text, View } from 'react-native'
import EncryptedStorage from 'react-native-encrypted-storage'
import ModalSelector from 'react-native-modal-selector'
import * as XMTP from 'xmtp-react-native-sdk'
import { useXmtp } from 'xmtp-react-native-sdk'
Expand All @@ -13,6 +14,46 @@ import { useSavedKeys } from './hooks'

const appVersion = 'XMTP_RN_EX/0.0.1'

async function getDbEncryptionKey(
network: string,
clear: boolean = false
): Promise<Uint8Array> {
const key = `xmtp-${network}`
const result = await EncryptedStorage.getItem(key)
if ((result && clear === true) || !result) {
if (result) {
await EncryptedStorage.removeItem(key)
}

const randomBytes = crypto.getRandomValues(new Uint8Array(32))
const randomBytesString = uint8ArrayToHexString(randomBytes)
await EncryptedStorage.setItem(key, randomBytesString)
return randomBytes
} else {
return hexStringToUint8Array(result)
}
}

function uint8ArrayToHexString(byteArray: Uint8Array): string {
return Array.from(byteArray, function (byte) {
return ('0' + (byte & 0xff).toString(16)).slice(-2)
}).join('')
}

function hexStringToUint8Array(hexString: string): Uint8Array {
// Ensure the hex string has an even number of characters for proper parsing
if (hexString.length % 2 !== 0) {
console.error('The hex string must have an even number of characters')
return new Uint8Array()
}
// Split the hex string into an array of byte-sized (2 characters) hex strings
const byteStrings = hexString.match(/.{1,2}/g) || []
// Convert each byte-sized hex string into a numeric byte value
const byteArray = byteStrings.map((byteStr) => parseInt(byteStr, 16))
// Create a new Uint8Array from the array of numeric byte values
return new Uint8Array(byteArray)
}

/// Prompt the user to run the tests, generate a wallet, or connect a wallet.
export default function LaunchScreen(
this: any,
Expand Down Expand Up @@ -163,22 +204,31 @@ export default function LaunchScreen(
title={`Use Connected Wallet (${signerAddressDisplay} + )`}
color="orange"
onPress={() => {
console.log(
'Using network ' +
selectedNetwork +
' and enableAlphaMLS ' +
enableGroups
)
configureWallet(
selectedNetwork,
XMTP.Client.create(signer, {
env: selectedNetwork,
appVersion,
preCreateIdentityCallback,
preEnableIdentityCallback,
enableAlphaMls: enableGroups === 'true',
})
)
;(async () => {
console.log(
'Using network ' +
selectedNetwork +
' and enableAlphaMLS ' +
enableGroups
)

const dbEncryptionKey = await getDbEncryptionKey(
selectedNetwork,
true
)

configureWallet(
selectedNetwork,
XMTP.Client.create(signer, {
env: selectedNetwork,
appVersion,
preCreateIdentityCallback,
preEnableIdentityCallback,
enableAlphaMls: enableGroups === 'true',
dbEncryptionKey,
})
)
})().catch(console.error) // Don't forget error handling
}}
/>
</View>
Expand All @@ -189,23 +239,30 @@ export default function LaunchScreen(
title="Use Random Wallet"
color="green"
onPress={() => {
console.log(
'Using network ' +
selectedNetwork +
' and enableAlphaMLS ' +
enableGroups
)
configureWallet(
selectedNetwork,
XMTP.Client.createRandom({
env: selectedNetwork,
appVersion,
codecs: supportedCodecs,
preCreateIdentityCallback,
preEnableIdentityCallback,
enableAlphaMls: enableGroups === 'true',
})
)
;(async () => {
console.log(
'Using network ' +
selectedNetwork +
' and enableAlphaMLS ' +
enableGroups
)
const dbEncryptionKey = await getDbEncryptionKey(
selectedNetwork,
true
)
configureWallet(
selectedNetwork,
XMTP.Client.createRandom({
env: selectedNetwork,
appVersion,
codecs: supportedCodecs,
preCreateIdentityCallback,
preEnableIdentityCallback,
enableAlphaMls: enableGroups === 'true',
dbEncryptionKey,
})
)
})().catch(console.error) // Don't forget error handling
}}
/>
</View>
Expand All @@ -216,21 +273,26 @@ export default function LaunchScreen(
title="Use Saved Wallet"
color="purple"
onPress={() => {
console.log(
'Using network ' +
selectedNetwork +
' and enableAlphaMLS ' +
enableGroups
)
configureWallet(
selectedNetwork,
XMTP.Client.createFromKeyBundle(savedKeys.keyBundle!, {
env: selectedNetwork,
appVersion,
codecs: supportedCodecs,
enableAlphaMls: enableGroups === 'true',
})
)
;(async () => {
console.log(
'Using network ' +
selectedNetwork +
' and enableAlphaMLS ' +
enableGroups
)
const dbEncryptionKey =
await getDbEncryptionKey(selectedNetwork)
configureWallet(
selectedNetwork,
XMTP.Client.createFromKeyBundle(savedKeys.keyBundle!, {
env: selectedNetwork,
appVersion,
codecs: supportedCodecs,
enableAlphaMls: enableGroups === 'true',
dbEncryptionKey,
})
)
})().catch(console.error) // Don't forget error handling
}}
/>
</View>
Expand Down
Loading
Loading