Skip to content

Commit 7e2da21

Browse files
authored
Merge pull request #147 from MetaMask/handle-rn-read-only-rpc
fix: concurrency handling and read-only calls improvements
2 parents 6f8fcc5 + abd9748 commit 7e2da21

File tree

9 files changed

+44
-41
lines changed

9 files changed

+44
-41
lines changed

metamask-android-sdk/build.gradle

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ android {
1515
targetSdk 33
1616

1717
ext.versionCode = 1
18-
ext.versionName = "0.6.4"
18+
ext.versionName = "0.6.5"
1919

2020
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
2121
consumerProguardFiles 'consumer-rules.pro'
@@ -53,7 +53,6 @@ dependencies {
5353
implementation 'androidx.appcompat:appcompat:1.6.1'
5454
implementation 'com.google.android.material:material:1.9.0'
5555
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
56-
implementation 'androidx.test.ext:junit-ktx:1.1.5'
5756
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.1"
5857

5958
testImplementation 'junit:junit:4.13.2'
@@ -68,7 +67,7 @@ dependencies {
6867

6968
ext {
7069
PUBLISH_GROUP_ID = 'io.metamask.androidsdk'
71-
PUBLISH_VERSION = '0.6.4'
70+
PUBLISH_VERSION = '0.6.5'
7271
PUBLISH_ARTIFACT_ID = 'metamask-android-sdk'
7372
}
7473

metamask-android-sdk/src/main/java/io/metamask/androidsdk/ReadOnlyRPCProvider.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package io.metamask.androidsdk
22

33
import org.json.JSONObject
44

5-
open class ReadOnlyRPCProvider(private val infuraAPIKey: String?, readonlyRPCMap: Map<String, String>?, private val logger: Logger = DefaultLogger) {
5+
open class ReadOnlyRPCProvider(private val infuraAPIKey: String?, private val readonlyRPCMap: Map<String, String>?, private val logger: Logger = DefaultLogger) {
66
val rpcUrls: Map<String, String> = when {
77
readonlyRPCMap != null && infuraAPIKey != null -> {
88
// Merge infuraReadonlyRPCMap with readonlyRPCMap, overriding infura's keys if they are present in readonlyRPCMap
@@ -16,14 +16,16 @@ open class ReadOnlyRPCProvider(private val infuraAPIKey: String?, readonlyRPCMap
1616
}
1717

1818
fun supportsChain(chainId: String): Boolean {
19-
return !rpcUrls[chainId].isNullOrEmpty()
19+
val apiKey = infuraAPIKey ?: ""
20+
val readonlyMap = readonlyRPCMap ?: mapOf()
21+
return !rpcUrls[chainId].isNullOrEmpty() && (readonlyMap.containsKey(chainId) || apiKey.isNotEmpty())
2022
}
2123

2224
fun infuraReadonlyRPCMap(infuraAPIKey: String) : Map<String, String> {
2325
return mapOf(
2426
// ###### Ethereum ######
2527
// Mainnet
26-
//"0x1" to "https://mainnet.infura.io/v3/${infuraAPIKey}",
28+
"0x1" to "https://mainnet.infura.io/v3/${infuraAPIKey}",
2729

2830
// Sepolia 11155111
2931
"0x2a" to "https://sepolia.infura.io/v3/${infuraAPIKey}",
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package io.metamask.androidsdk
22

33
object SDKInfo {
4-
const val VERSION = "0.6.4"
4+
const val VERSION = "0.6.5"
55
const val PLATFORM = "android"
66
}

metamask-android-sdk/src/test/java/io/metamask/androidsdk/EthereumTests.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ class EthereumTests {
264264
private fun findRequestIdForAccountRequest(method: EthereumMethod): String {
265265
return communicationClient.submittedRequests.entries.find {
266266
it.value.request.method == method.value
267-
}?.key ?: throw IllegalStateException("No account request found")
267+
}?.key ?: ""
268268
}
269269

270270
private fun prepareCommunicationClient() {

nativesdk/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,19 @@ The MetaMask Android SDK Native Module is a Kotlin [Android Native Module](https
55

66
This module handles encrypted communication between the dapp and MetaMask and then relays the messages over to the Android SDK communication layer implemented in React Native in the wallet. The wallet calls the Native Module via NativeModules - an API that enables react-native code to call native Kotlin primitives.
77

8+
## Compiling
9+
To compile the nativesdk as a `.aar` file, you need to modify the project's `settings.gradle` file to have the nativesdk as a standalone module to include to the project:
10+
```
11+
include ':app'
12+
include ':nativesdk'
13+
include ':metamask-android-sdk'
14+
```
15+
Then run
16+
```
17+
./gradlew assembleDebug
18+
```
19+
The output file will be located in `nativesdk/build/outputs/aar`. This nativesdk library is then embedded in the MetaMask mobile in the path `android/libs/nativesdk.aar`. It becomes the server-side component of the AIDL communication mechanism described below in the Architecture section.
20+
821
## Architecture
922
The client SDK communicates with the server SDK (Android Native Module) via IPC implemented using AIDL.
1023

nativesdk/build.gradle.kts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,12 @@ android {
3636
dependencies {
3737

3838
compileOnly(files("libs/ecies.aar"))
39-
implementation("com.facebook.react:react-native:+")
39+
implementation("com.facebook.react:react-android:0.73.3")
4040
implementation("androidx.core:core-ktx:1.7.0")
4141
implementation ("com.squareup.okhttp3:okhttp:4.9.2")
4242
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
4343
implementation("androidx.appcompat:appcompat:1.6.1")
4444
implementation("com.google.android.material:material:1.9.0")
4545
testImplementation("junit:junit:4.13.2")
46-
androidTestImplementation("androidx.test.ext:junit:1.1.5")
4746
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
4847
}

nativesdk/src/main/java/io/metamask/nativesdk/CommunicationClient.kt

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -97,15 +97,6 @@ class CommunicationClient(reactContext: ReactApplicationContext) : ReactContextB
9797
connectionStatusManager.onMetaMaskBroadcastUnregistered()
9898
}
9999

100-
override fun invalidate() {
101-
super.invalidate()
102-
Logger.log("CommunicationClient:: invalidate - app terminated")
103-
104-
if (!didPerformTearDown) {
105-
destroyActiveConnections()
106-
}
107-
}
108-
109100
override fun onCatalystInstanceDestroy() {
110101
super.onCatalystInstanceDestroy()
111102
Logger.log("CommunicationClient:: onCatalystInstanceDestroy - app terminating")
@@ -142,13 +133,14 @@ class CommunicationClient(reactContext: ReactApplicationContext) : ReactContextB
142133

143134
Logger.log("CommunicationClient:: Binding native module")
144135
val intent = Intent(reactAppContext, MessageService::class.java)
145-
return reactAppContext.bindService(
136+
val success = reactAppContext.bindService(
146137
intent,
147138
serviceConnection,
148139
Context.BIND_IMPORTANT)
149140

150141
// Metamask ready, start sending messages
151142
connectionStatusManager.onMetaMaskReady()
143+
return success
152144
}
153145

154146
@ReactMethod
@@ -184,7 +176,7 @@ class CommunicationClient(reactContext: ReactApplicationContext) : ReactContextB
184176

185177
if (type == "ready") {
186178
val dataJson = json.optJSONObject("data")
187-
val id = dataJson.optString("id")
179+
val id = dataJson?.optString("id")
188180
val sessionId = SessionManager.getInstance().sessionId
189181

190182
if (id != sessionId) {

nativesdk/src/main/java/io/metamask/nativesdk/KeyExchange.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,7 @@ class KeyExchange {
4848

4949
fun resetKeys() {
5050
privateKey = crypto.generatePrivateKey()
51-
privateKey?.let {
52-
publicKey = crypto.publicKey(it)
53-
}
51+
publicKey = crypto.publicKey(privateKey)
5452
setIsKeysExchanged(false)
5553
theirPublicKey = null
5654
}

nativesdk/src/main/java/io/metamask/nativesdk/MessageService.kt

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -162,11 +162,17 @@ class MessageService : Service(), MetaMaskConnectionStatusCallback {
162162
}
163163
}
164164

165-
private fun sendMessage(message: String) {
165+
private fun sendMessage(key: String = MESSAGE, message: String) {
166166
val bundle = Bundle().apply {
167-
putString(MESSAGE, message)
167+
putString(key, message)
168+
}
169+
try {
170+
synchronized(this@MessageService) {
171+
dappMessageServiceCallback?.onMessageReceived(bundle)
172+
}
173+
} catch (e: Exception) {
174+
Logger.log("MessageService:: Error sending message to dapp: ${e.message}")
168175
}
169-
dappMessageServiceCallback?.onMessageReceived(bundle)
170176
}
171177

172178
private fun handleKeyExchange(message: String) {
@@ -199,7 +205,7 @@ class MessageService : Service(), MetaMaskConnectionStatusCallback {
199205

200206
Logger.log("MessageService:: $keysExchangedMessage")
201207
val payload = keyExchange.encrypt(keysExchangedMessage)
202-
sendMessage(payload)
208+
sendMessage(message=payload)
203209
}
204210
}
205211

@@ -248,18 +254,17 @@ class MessageService : Service(), MetaMaskConnectionStatusCallback {
248254
private fun resumeQueuedJobs() {
249255
Logger.log("MessageService:: Resuming jobs")
250256

251-
while (jobQueue.isNotEmpty()) {
252-
val job = jobQueue.removeFirstOrNull()
253-
job?.invoke()
257+
synchronized(this) {
258+
while (jobQueue.isNotEmpty()) {
259+
val job = jobQueue.removeFirstOrNull()
260+
job?.invoke()
261+
}
254262
}
255263
}
256264

257265
private fun sendKeyExchangeMessage(message: String) {
258266
Logger.log("MessageService:: Sending key exchange: $message")
259-
val bundle = Bundle().apply {
260-
putString(KEY_EXCHANGE, message)
261-
}
262-
dappMessageServiceCallback?.onMessageReceived(bundle)
267+
sendMessage(key=KEY_EXCHANGE, message=message)
263268
}
264269

265270
private fun handleMessage(message: String, id: String) {
@@ -329,12 +334,7 @@ class MessageService : Service(), MetaMaskConnectionStatusCallback {
329334
override fun onReceive(context: Context, intent: Intent) {
330335
if (intent.action == "local.service") {
331336
val message = intent.getStringExtra(EventType.MESSAGE.value) as String
332-
333-
val bundle = Bundle().apply {
334-
putString(EventType.MESSAGE.value, message)
335-
}
336-
337-
dappMessageServiceCallback?.onMessageReceived(bundle)
337+
sendMessage(message=message)
338338
}
339339
}
340340
}

0 commit comments

Comments
 (0)