diff --git a/android/build.gradle b/android/build.gradle index 92721f84a..bb7cb8dba 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -98,7 +98,7 @@ repositories { dependencies { implementation project(':expo-modules-core') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}" - implementation "org.xmtp:android:4.5.6" + implementation "org.xmtp:android:4.6.0-dev.c78f91f" 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" diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt index 2fabff38e..b9cf9ad3a 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt @@ -163,7 +163,7 @@ class XMTPModule : Module() { return reactContext } - private fun apiEnvironments(env: String, customLocalUrl: String? = null, appVersion: String? = null): ClientOptions.Api { + private fun apiEnvironments(env: String, customLocalUrl: String? = null, appVersion: String? = null, gatewayUrl: String? = null): ClientOptions.Api { return when (env) { "local" -> { if (customLocalUrl.isNullOrBlank()) { @@ -171,12 +171,14 @@ class XMTPModule : Module() { env = XMTPEnvironment.LOCAL, isSecure = false, appVersion = appVersion, + gatewayUrl = gatewayUrl, ) } else { ClientOptions.Api( env = XMTPEnvironment.LOCAL.withValue(customLocalUrl), isSecure = false, appVersion = appVersion, + gatewayUrl = gatewayUrl, ) } } @@ -185,12 +187,14 @@ class XMTPModule : Module() { env = XMTPEnvironment.PRODUCTION, isSecure = true, appVersion = appVersion, + gatewayUrl = gatewayUrl, ) else -> ClientOptions.Api( env = XMTPEnvironment.DEV, isSecure = true, appVersion = appVersion, + gatewayUrl = gatewayUrl, ) } } @@ -216,7 +220,7 @@ class XMTPModule : Module() { else -> "https://message-history.dev.ephemera.network/" } return ClientOptions( - api = apiEnvironments(authOptions.environment, authOptions.customLocalUrl, authOptions.appVersion), + api = apiEnvironments(authOptions.environment, authOptions.customLocalUrl, authOptions.appVersion, authOptions.gatewayUrl), preAuthenticateToInboxCallback = preAuthenticateToInboxCallback, appContext = context, dbEncryptionKey = encryptionKeyBytes, diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/AuthParamsWrapper.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/AuthParamsWrapper.kt index 101e0c891..a6944cea7 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/AuthParamsWrapper.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/AuthParamsWrapper.kt @@ -11,20 +11,27 @@ class AuthParamsWrapper( val deviceSyncEnabled: Boolean, val debugEventsEnabled: Boolean, val appVersion: String?, + val gatewayUrl: String? ) { companion object { fun authParamsFromJson(authParams: String): AuthParamsWrapper { val jsonOptions = JsonParser.parseString(authParams).asJsonObject return AuthParamsWrapper( jsonOptions.get("environment").asString, - if (jsonOptions.has("dbDirectory")) jsonOptions.get("dbDirectory").asString else null, - if (jsonOptions.has("historySyncUrl")) jsonOptions.get("historySyncUrl").asString else null, - if (jsonOptions.has("customLocalUrl")) jsonOptions.get("customLocalUrl").asString else null, + if (jsonOptions.has("dbDirectory")) stringOrNull(jsonOptions.get("dbDirectory").asString) else null, + if (jsonOptions.has("historySyncUrl")) stringOrNull(jsonOptions.get("historySyncUrl").asString) else null, + if (jsonOptions.has("customLocalUrl")) stringOrNull(jsonOptions.get("customLocalUrl").asString) else null, if (jsonOptions.has("deviceSyncEnabled")) jsonOptions.get("deviceSyncEnabled").asBoolean else true, if (jsonOptions.has("debugEventsEnabled")) jsonOptions.get("debugEventsEnabled").asBoolean else false, - if (jsonOptions.has("appVersion")) jsonOptions.get("appVersion").asString else null, + if (jsonOptions.has("appVersion")) stringOrNull(jsonOptions.get("appVersion").asString) else null, + if (jsonOptions.has("gatewayUrl")) stringOrNull(jsonOptions.get("gatewayUrl").asString) else null, ) } + + // Helper function to convert empty strings to null + private fun stringOrNull(value: String?): String? { + return if (value.isNullOrEmpty()) null else value + } } } diff --git a/example/EXAMPLE.env b/example/EXAMPLE.env index 5c653dc31..a3ad2f26a 100644 --- a/example/EXAMPLE.env +++ b/example/EXAMPLE.env @@ -1,2 +1,3 @@ TEST_PRIVATE_KEY=INSERT_TEST_PRIVATE_KEY_HERE THIRD_WEB_CLIENT_ID=INSERT_CLIENT_ID_HERE +GATEWAY_URL=GATEWAY_PAYER_URL_HERE diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 629c89a72..baa6aa489 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1736,16 +1736,16 @@ PODS: - SQLCipher/standard (4.5.7): - SQLCipher/common - SwiftProtobuf (1.28.2) - - XMTP (4.5.5): + - XMTP (4.6.0-dev.4a8ee5d): - Connect-Swift (= 1.0.0) - CryptoSwift (= 1.8.3) - SQLCipher (= 4.5.7) - - XMTPReactNative (5.0.3): + - XMTPReactNative (5.1.0-dev): - CSecp256k1 (~> 0.2) - ExpoModulesCore - MessagePacker - SQLCipher (= 4.5.7) - - XMTP (= 4.5.5) + - XMTP (= 4.6.0-dev.4a8ee5d) - Yoga (0.0.0) DEPENDENCIES: @@ -2155,8 +2155,8 @@ SPEC CHECKSUMS: SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 SQLCipher: 5e6bfb47323635c8b657b1b27d25c5f1baf63bf5 SwiftProtobuf: 4dbaffec76a39a8dc5da23b40af1a5dc01a4c02d - XMTP: dc1eede674b3a4bd05d5ceee19ee002c95961f0f - XMTPReactNative: 421dfe638ba0c587a03fb5dd420512242b93ac1f + XMTP: a0ec6e29b9a643198103eff675e098635c146ebe + XMTPReactNative: 7b8f2bdd675e885e46b8a17d5a14e22fb5a87832 Yoga: feb4910aba9742cfedc059e2b2902e22ffe9954a PODFILE CHECKSUM: 283c313cbc1ba9857a692b5901eb740dad922eca diff --git a/example/src/tests/clientTests.ts b/example/src/tests/clientTests.ts index 080487d5f..2096d9fc7 100644 --- a/example/src/tests/clientTests.ts +++ b/example/src/tests/clientTests.ts @@ -1,8 +1,9 @@ import { ethers, Wallet } from 'ethers' import RNFS from 'react-native-fs' +import Config from 'react-native-config' import { ArchiveOptions } from 'xmtp-react-native-sdk/lib/ArchiveOptions' import { InstallationId } from 'xmtp-react-native-sdk/lib/Client' - +import { XMTPEnvironment } from 'xmtp-react-native-sdk/lib/Client' import { Test, assert, @@ -293,6 +294,7 @@ test('can make a client', async () => { dbEncryptionKey: keyBytes, deviceSyncEnabled: false, appVersion: '0.0.0', + gatewayUrl: 'https://xmtp3.org' }) const inboxId = await Client.getOrCreateInboxId( @@ -307,6 +309,52 @@ test('can make a client', async () => { return true }) +test('can make a new client and build existing from existing db using d14n staging testnet', async () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const keyBytes = new Uint8Array([ + 233, 120, 198, 96, 154, 65, 132, 17, 132, 96, 250, 40, 103, 35, 125, 64, + 166, 83, 208, 224, 254, 44, 205, 227, 175, 49, 234, 129, 74, 252, 135, 145, + ]) + const dbDirPath = `${RNFS.DocumentDirectoryPath}/xmtp_db` + const directoryExists = await RNFS.exists(dbDirPath) + if (!directoryExists) { + await RNFS.mkdir(dbDirPath) + } + if (!Config.GATEWAY_URL) { + throw new Error('GATEWAY_URL environment variable is required. Please set it in your .env file (copy from EXAMPLE.env if needed).') + } + + const options = { + env: 'dev' as XMTPEnvironment, + dbEncryptionKey: keyBytes, + dbDirectory: dbDirPath, + deviceSyncEnabled: false, + appVersion: '0.0.0', + gatewayUrl: Config.GATEWAY_URL + } + + const client = await Client.createRandom(options) + + const inboxId = await Client.getOrCreateInboxId( + client.publicIdentity, + 'dev' + ) + + assert( + client.inboxId === inboxId, + `inboxIds should match but were ${client.inboxId} and ${inboxId}` + ) + + const clientFromBundle = await Client.build(client.publicIdentity, options) + + assert( + clientFromBundle.inboxId === client.inboxId, + `inboxIds should match but were ${clientFromBundle.inboxId} and ${client.inboxId}` + ) + + return true +}) + test('static can message', async () => { const [alix, bo] = await createClients(2) diff --git a/example/src/types/react-native-config.d.ts b/example/src/types/react-native-config.d.ts index cfd3947a9..3395ce54e 100644 --- a/example/src/types/react-native-config.d.ts +++ b/example/src/types/react-native-config.d.ts @@ -3,6 +3,7 @@ declare module 'react-native-config' { THIRD_WEB_CLIENT_ID?: string TEST_PRIVATE_KEY?: string TEST_V3_PRIVATE_KEY?: string + GATEWAY_URL?: string } export const Config: NativeConfig diff --git a/ios/Wrappers/AuthParamsWrapper.swift b/ios/Wrappers/AuthParamsWrapper.swift index 23a2c92a5..36ece676c 100644 --- a/ios/Wrappers/AuthParamsWrapper.swift +++ b/ios/Wrappers/AuthParamsWrapper.swift @@ -16,12 +16,13 @@ struct AuthParamsWrapper { let deviceSyncEnabled: Bool let debugEventsEnabled: Bool let appVersion: String? + let gatewayUrl: String? init( environment: String, dbDirectory: String?, historySyncUrl: String?, customLocalUrl: String?, deviceSyncEnabled: Bool, debugEventsEnabled: Bool, - appVersion: String? + appVersion: String?, gatewayUrl: String? ) { self.environment = environment self.dbDirectory = dbDirectory @@ -30,6 +31,7 @@ struct AuthParamsWrapper { self.deviceSyncEnabled = deviceSyncEnabled self.debugEventsEnabled = debugEventsEnabled self.appVersion = appVersion + self.gatewayUrl = gatewayUrl } static func authParamsFromJson(_ authParams: String) -> AuthParamsWrapper { @@ -41,18 +43,19 @@ struct AuthParamsWrapper { environment: "dev", dbDirectory: nil, historySyncUrl: nil, customLocalUrl: nil, deviceSyncEnabled: true, debugEventsEnabled: false, - appVersion: nil) + appVersion: nil, gatewayUrl: nil) } let environment = jsonOptions["environment"] as? String ?? "dev" - let dbDirectory = jsonOptions["dbDirectory"] as? String - let historySyncUrl = jsonOptions["historySyncUrl"] as? String - let customLocalUrl = jsonOptions["customLocalUrl"] as? String + let dbDirectory = Self.stringOrNil(jsonOptions["dbDirectory"] as? String) + let historySyncUrl = Self.stringOrNil(jsonOptions["historySyncUrl"] as? String) + let customLocalUrl = Self.stringOrNil(jsonOptions["customLocalUrl"] as? String) let deviceSyncEnabled = jsonOptions["deviceSyncEnabled"] as? Bool ?? true let debugEventsEnabled = jsonOptions["debugEventsEnabled"] as? Bool ?? false - let appVersion = jsonOptions["appVersion"] as? String + let appVersion = Self.stringOrNil(jsonOptions["appVersion"] as? String) + let gatewayUrl = Self.stringOrNil(jsonOptions["gatewayUrl"] as? String) return AuthParamsWrapper( environment: environment, @@ -61,9 +64,16 @@ struct AuthParamsWrapper { customLocalUrl: customLocalUrl, deviceSyncEnabled: deviceSyncEnabled, debugEventsEnabled: debugEventsEnabled, - appVersion: appVersion + appVersion: appVersion, + gatewayUrl: gatewayUrl ) } + + // Helper function to convert empty strings to nil + private static func stringOrNil(_ value: String?) -> String? { + guard let value = value, !value.isEmpty else { return nil } + return value + } } struct WalletParamsWrapper { diff --git a/ios/XMTPModule.swift b/ios/XMTPModule.swift index b7fdca54c..21362cd9e 100644 --- a/ios/XMTPModule.swift +++ b/ios/XMTPModule.swift @@ -2987,7 +2987,7 @@ public class XMTPModule: Module { case revokeInstallations = "revokeInstallations" } - func createApiClient(env: String, customLocalUrl: String? = nil, appVersion: String? = nil) + func createApiClient(env: String, customLocalUrl: String? = nil, appVersion: String? = nil, gatewayUrl: String? = nil) -> XMTP.ClientOptions.Api { switch env { @@ -2998,19 +2998,22 @@ public class XMTPModule: Module { return XMTP.ClientOptions.Api( env: XMTP.XMTPEnvironment.local, isSecure: false, - appVersion: appVersion + appVersion: appVersion, + gatewayUrl: gatewayUrl ) case "production": return XMTP.ClientOptions.Api( env: XMTP.XMTPEnvironment.production, isSecure: true, - appVersion: appVersion + appVersion: appVersion, + gatewayUrl: gatewayUrl ) default: return XMTP.ClientOptions.Api( env: XMTP.XMTPEnvironment.dev, isSecure: true, - appVersion: appVersion + appVersion: appVersion, + gatewayUrl: gatewayUrl ) } } @@ -3025,7 +3028,8 @@ public class XMTPModule: Module { api: createApiClient( env: authOptions.environment, customLocalUrl: authOptions.customLocalUrl, - appVersion: authOptions.appVersion + appVersion: authOptions.appVersion, + gatewayUrl: authOptions.gatewayUrl ), preAuthenticateToInboxCallback: preAuthenticateToInboxCallback, dbEncryptionKey: dbEncryptionKey, diff --git a/ios/XMTPReactNative.podspec b/ios/XMTPReactNative.podspec index 82d49ab22..8f18df805 100644 --- a/ios/XMTPReactNative.podspec +++ b/ios/XMTPReactNative.podspec @@ -26,7 +26,7 @@ Pod::Spec.new do |s| s.source_files = "**/*.{h,m,swift}" s.dependency "MessagePacker" - s.dependency "XMTP", "= 4.5.6" + s.dependency "XMTP", "= 4.6.0-dev.4a8ee5d" s.dependency 'CSecp256k1', '~> 0.2' s.dependency "SQLCipher", "= 4.5.7" end diff --git a/package.json b/package.json index b301611f0..23864b4ff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@xmtp/react-native-sdk", - "version": "5.0.5", + "version": "5.1.0-dev", "description": "Wraps for native xmtp sdks for react native", "main": "build/index.js", "types": "build/index.d.ts", diff --git a/src/index.ts b/src/index.ts index 68381f73c..862bb30ef 100644 --- a/src/index.ts +++ b/src/index.ts @@ -128,7 +128,8 @@ export async function createRandom( customLocalHost?: string | undefined, deviceSyncEnabled?: boolean | undefined, debugEventsEnabled?: boolean | undefined, - appVersion?: string | undefined + appVersion?: string | undefined, + gatewayUrl?: string | undefined ): Promise { const authParams: AuthParams = { environment, @@ -138,6 +139,7 @@ export async function createRandom( deviceSyncEnabled, debugEventsEnabled, appVersion, + gatewayUrl } return await XMTPModule.createRandom( hasPreAuthenticateToInboxCallback, @@ -159,7 +161,8 @@ export async function create( customLocalHost?: string | undefined, deviceSyncEnabled?: boolean | undefined, debugEventsEnabled?: boolean | undefined, - appVersion?: string | undefined + appVersion?: string | undefined, + gatewayUrl?: string | undefined ): Promise { const authParams: AuthParams = { environment, @@ -169,6 +172,7 @@ export async function create( deviceSyncEnabled, debugEventsEnabled, appVersion, + gatewayUrl } const signerParams: SignerParams = { signerType, @@ -194,7 +198,8 @@ export async function build( customLocalHost?: string | undefined, deviceSyncEnabled?: boolean | undefined, debugEventsEnabled?: boolean | undefined, - appVersion?: string | undefined + appVersion?: string | undefined, + gatewayUrl?: string | undefined ): Promise { const authParams: AuthParams = { environment, @@ -204,6 +209,7 @@ export async function build( deviceSyncEnabled, debugEventsEnabled, appVersion, + gatewayUrl, } return await XMTPModule.build( JSON.stringify(identity), @@ -222,7 +228,8 @@ export async function ffiCreateClient( customLocalHost?: string | undefined, deviceSyncEnabled?: boolean | undefined, debugEventsEnabled?: boolean | undefined, - appVersion?: string | undefined + appVersion?: string | undefined, + gatewayUrl?: string | undefined ): Promise { const authParams: AuthParams = { environment, @@ -232,6 +239,7 @@ export async function ffiCreateClient( deviceSyncEnabled, debugEventsEnabled, appVersion, + gatewayUrl, } return await XMTPModule.ffiCreateClient( JSON.stringify(identity), @@ -1773,6 +1781,7 @@ interface AuthParams { deviceSyncEnabled?: boolean debugEventsEnabled?: boolean appVersion?: string + gatewayUrl?: string } interface SignerParams { diff --git a/src/lib/Client.ts b/src/lib/Client.ts index a5bcad14d..954571d0b 100644 --- a/src/lib/Client.ts +++ b/src/lib/Client.ts @@ -107,7 +107,8 @@ export class Client< options.customLocalHost, options.deviceSyncEnabled, options.debugEventsEnabled, - options.appVersion + options.appVersion, + options.gatewayUrl ) this.removeSubscription(authInboxSubscription) @@ -196,7 +197,8 @@ export class Client< options.customLocalHost, options.deviceSyncEnabled, options.debugEventsEnabled, - options.appVersion + options.appVersion, + options.gatewayUrl ) })().catch((error) => { this.removeAllSubscriptions(authInboxSubscription) @@ -235,7 +237,8 @@ export class Client< options.customLocalHost, options.deviceSyncEnabled, options.debugEventsEnabled, - options.appVersion + options.appVersion, + options.gatewayUrl ) return new Client( @@ -281,7 +284,8 @@ export class Client< options.customLocalHost, options.deviceSyncEnabled, options.debugEventsEnabled, - options.appVersion + options.appVersion, + options.gatewayUrl ) return new Client( @@ -1211,4 +1215,8 @@ export type ClientOptions = { * OPTIONAL specify if debug events should be tracked defaults to false */ debugEventsEnabled?: boolean + /** + * OPTIONAL specify to connect to decentralized backend gateway + */ + gatewayUrl?: string }