@solana-mobile/mobile-wallet-adapter-protocol 2.2.2 → 2.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build.gradle +2 -2
- package/android/src/main/java/com/solanamobile/mobilewalletadapter/reactnative/SolanaMobileWalletAdapterModule.kt +116 -84
- package/lib/cjs/index.native.js +8 -0
- package/lib/types/index.browser.d.ts +1 -1
- package/lib/types/index.d.ts +1 -1
- package/lib/types/index.native.d.ts +1 -1
- package/package.json +3 -3
- package/.gitignore +0 -2
- package/android/.gitignore +0 -14
- package/src/__forks__/react-native/base64Utils.ts +0 -1
- package/src/__forks__/react-native/transact.ts +0 -91
- package/src/arrayBufferToBase64String.ts +0 -10
- package/src/associationPort.ts +0 -19
- package/src/base64Utils.ts +0 -22
- package/src/createHelloReq.ts +0 -12
- package/src/createMobileWalletProxy.ts +0 -182
- package/src/createSIWSMessage.ts +0 -14
- package/src/createSequenceNumberVector.ts +0 -11
- package/src/encryptedMessage.ts +0 -60
- package/src/errors.ts +0 -101
- package/src/generateAssociationKeypair.ts +0 -10
- package/src/generateECDHKeypair.ts +0 -10
- package/src/getAssociateAndroidIntentURL.ts +0 -77
- package/src/getJWS.ts +0 -19
- package/src/getStringWithURLUnsafeBase64CharactersReplaced.ts +0 -11
- package/src/index.ts +0 -3
- package/src/jsonRpcMessage.ts +0 -38
- package/src/parseHelloRsp.ts +0 -46
- package/src/parseSessionProps.ts +0 -33
- package/src/reflectorId.ts +0 -31
- package/src/startSession.ts +0 -98
- package/src/transact.ts +0 -593
- package/src/types.ts +0 -201
- package/tsconfig.cjs.json +0 -7
- package/tsconfig.json +0 -8
package/android/build.gradle
CHANGED
|
@@ -8,7 +8,7 @@ buildscript {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
dependencies {
|
|
11
|
-
classpath 'com.android.tools.build:gradle:8.
|
|
11
|
+
classpath 'com.android.tools.build:gradle:8.13.0'
|
|
12
12
|
// noinspection DifferentKotlinGradleVersion
|
|
13
13
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
14
14
|
}
|
|
@@ -144,7 +144,7 @@ def kotlin_version = getExtOrDefault('kotlinVersion')
|
|
|
144
144
|
dependencies {
|
|
145
145
|
//noinspection GradleDynamicVersion
|
|
146
146
|
implementation "com.facebook.react:react-native:+" // From node_modules
|
|
147
|
-
implementation "com.solanamobile:mobile-wallet-adapter-clientlib:2.0
|
|
147
|
+
implementation "com.solanamobile:mobile-wallet-adapter-clientlib:2.1.0"
|
|
148
148
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
149
149
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0"
|
|
150
150
|
}
|
|
@@ -6,6 +6,8 @@ import android.content.Intent
|
|
|
6
6
|
import android.net.Uri
|
|
7
7
|
import android.util.Log
|
|
8
8
|
import com.facebook.react.bridge.*
|
|
9
|
+
import com.facebook.react.jstasks.HeadlessJsTaskConfig
|
|
10
|
+
import com.facebook.react.jstasks.HeadlessJsTaskContext
|
|
9
11
|
import com.solana.mobilewalletadapter.clientlib.protocol.JsonRpc20Client
|
|
10
12
|
import com.solana.mobilewalletadapter.clientlib.protocol.MobileWalletAdapterClient
|
|
11
13
|
import com.solana.mobilewalletadapter.clientlib.scenario.LocalAssociationIntentCreator
|
|
@@ -21,40 +23,49 @@ import kotlinx.coroutines.sync.Mutex
|
|
|
21
23
|
import org.json.JSONObject
|
|
22
24
|
|
|
23
25
|
class SolanaMobileWalletAdapterModule(reactContext: ReactApplicationContext) :
|
|
24
|
-
|
|
26
|
+
SolanaMobileWalletAdapterSpec(reactContext), CoroutineScope {
|
|
25
27
|
|
|
26
28
|
data class SessionState(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
val client: MobileWalletAdapterClient,
|
|
30
|
+
val localAssociation: LocalAssociationScenario,
|
|
29
31
|
)
|
|
30
32
|
|
|
31
33
|
override val coroutineContext =
|
|
32
|
-
|
|
34
|
+
Dispatchers.IO + CoroutineName("SolanaMobileWalletAdapterModuleScope") + SupervisorJob()
|
|
33
35
|
|
|
34
36
|
companion object {
|
|
35
37
|
const val NAME = "SolanaMobileWalletAdapter"
|
|
38
|
+
private const val TAG = "SolanaMobileWalletAdapterModule"
|
|
36
39
|
private const val ASSOCIATION_TIMEOUT_MS = 10000
|
|
37
40
|
private const val CLIENT_TIMEOUT_MS = 90000
|
|
38
41
|
private const val REQUEST_LOCAL_ASSOCIATION = 0
|
|
39
|
-
|
|
40
|
-
// Used to ensure that you can't start more than one session at a time.
|
|
41
|
-
private val mutex: Mutex = Mutex()
|
|
42
|
-
private var sessionState: SessionState? = null
|
|
43
|
-
private var associationResultCallback: ((Int) -> Unit)? = null
|
|
44
42
|
}
|
|
45
43
|
|
|
44
|
+
// Used to ensure that you can't start more than one session at a time.
|
|
45
|
+
private val mutex: Mutex = Mutex()
|
|
46
|
+
private var sessionState: SessionState? = null
|
|
47
|
+
private var associationResultCallback: ((Int) -> Unit)? = null
|
|
48
|
+
|
|
46
49
|
private val mActivityEventListener: ActivityEventListener =
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
50
|
+
object : BaseActivityEventListener() {
|
|
51
|
+
override fun onActivityResult(
|
|
52
|
+
activity: Activity,
|
|
53
|
+
requestCode: Int,
|
|
54
|
+
resultCode: Int,
|
|
55
|
+
data: Intent?
|
|
56
|
+
) {
|
|
57
|
+
if (requestCode == REQUEST_LOCAL_ASSOCIATION)
|
|
58
|
+
associationResultCallback?.invoke(resultCode)
|
|
57
59
|
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private val sessionBackgroundTaskConfig
|
|
63
|
+
get() = HeadlessJsTaskConfig(
|
|
64
|
+
"SolanaMobileWalletAdapterSessionBackgroundTask",
|
|
65
|
+
Arguments.createMap(),
|
|
66
|
+
0,
|
|
67
|
+
true
|
|
68
|
+
)
|
|
58
69
|
|
|
59
70
|
init {
|
|
60
71
|
reactContext.addActivityEventListener(mActivityEventListener)
|
|
@@ -68,67 +79,92 @@ class SolanaMobileWalletAdapterModule(reactContext: ReactApplicationContext) :
|
|
|
68
79
|
override fun startSession(config: ReadableMap?, promise: Promise): Unit {
|
|
69
80
|
launch {
|
|
70
81
|
mutex.lock()
|
|
71
|
-
Log.d(
|
|
82
|
+
Log.d(TAG, "startSession with config $config")
|
|
83
|
+
var sessionTaskId: Int? = null
|
|
84
|
+
val headlessJsTaskContext = HeadlessJsTaskContext.getInstance(reactApplicationContext)
|
|
85
|
+
val finishHeadlessTask = { taskId: Int? ->
|
|
86
|
+
try {
|
|
87
|
+
if (taskId != null && headlessJsTaskContext.isTaskRunning(taskId)) {
|
|
88
|
+
headlessJsTaskContext.finishTask(taskId)
|
|
89
|
+
}
|
|
90
|
+
// fix for Expo 52/RN 0.72/0.73 where the older kotlin/gradle toolchain complains
|
|
91
|
+
// about the above if statement being used as an expression. Explicitly returning
|
|
92
|
+
// Unit here tells the compiler that the above if is not an expression
|
|
93
|
+
Unit
|
|
94
|
+
} catch (e: Exception) {
|
|
95
|
+
Log.w(TAG, "Failed to finish headless JS task", e)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
72
98
|
try {
|
|
73
99
|
val uriPrefix = config?.getString("baseUri")?.let { Uri.parse(it) }
|
|
74
100
|
val localAssociation =
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
101
|
+
LocalAssociationScenario(
|
|
102
|
+
CLIENT_TIMEOUT_MS,
|
|
103
|
+
)
|
|
78
104
|
val intent =
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
105
|
+
LocalAssociationIntentCreator.createAssociationIntent(
|
|
106
|
+
uriPrefix,
|
|
107
|
+
localAssociation.port,
|
|
108
|
+
localAssociation.session
|
|
109
|
+
)
|
|
110
|
+
withContext(Dispatchers.Main) {
|
|
111
|
+
sessionTaskId = headlessJsTaskContext.startTask(sessionBackgroundTaskConfig)
|
|
112
|
+
}
|
|
84
113
|
associationResultCallback = { resultCode ->
|
|
85
114
|
if (resultCode == Activity.RESULT_CANCELED) {
|
|
86
|
-
Log.d(
|
|
115
|
+
Log.d(TAG, "Local association cancelled by user, ending session")
|
|
87
116
|
promise.reject(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
117
|
+
"Session not established: Local association cancelled by user",
|
|
118
|
+
LocalAssociationScenario.ConnectionFailedException(
|
|
119
|
+
"Local association cancelled by user"
|
|
120
|
+
)
|
|
92
121
|
)
|
|
93
122
|
localAssociation.close()
|
|
94
123
|
}
|
|
124
|
+
|
|
125
|
+
// stop the headless js task, regardless if the association was successful or not
|
|
126
|
+
finishHeadlessTask(sessionTaskId)
|
|
95
127
|
}
|
|
96
|
-
currentActivity?.
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
.get(ASSOCIATION_TIMEOUT_MS.toLong(), TimeUnit.MILLISECONDS)
|
|
128
|
+
reactApplicationContext.currentActivity?.apply {
|
|
129
|
+
startActivityForResult(intent, REQUEST_LOCAL_ASSOCIATION)
|
|
130
|
+
} ?: throw NullPointerException(
|
|
131
|
+
"Could not find a current activity from which to launch a local association"
|
|
132
|
+
)
|
|
133
|
+
val client = localAssociation.start()
|
|
134
|
+
.get(ASSOCIATION_TIMEOUT_MS.toLong(), TimeUnit.MILLISECONDS)
|
|
104
135
|
sessionState = SessionState(client, localAssociation)
|
|
105
136
|
val sessionPropertiesMap: WritableMap = WritableNativeMap()
|
|
106
137
|
sessionPropertiesMap.putString(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
138
|
+
"protocol_version",
|
|
139
|
+
when (localAssociation.session.sessionProperties.protocolVersion) {
|
|
140
|
+
ProtocolVersion.LEGACY -> "legacy"
|
|
141
|
+
ProtocolVersion.V1 -> "v1"
|
|
142
|
+
}
|
|
112
143
|
)
|
|
113
144
|
promise.resolve(sessionPropertiesMap)
|
|
114
145
|
} catch (e: ActivityNotFoundException) {
|
|
115
|
-
Log.e(
|
|
146
|
+
Log.e(TAG, "Found no installed wallet that supports the mobile wallet protocol", e)
|
|
147
|
+
finishHeadlessTask(sessionTaskId)
|
|
116
148
|
cleanup()
|
|
117
149
|
promise.reject("ERROR_WALLET_NOT_FOUND", e)
|
|
118
150
|
} catch (e: TimeoutException) {
|
|
119
|
-
Log.e(
|
|
151
|
+
Log.e(TAG, "Timed out waiting for local association to be ready", e)
|
|
152
|
+
finishHeadlessTask(sessionTaskId)
|
|
120
153
|
cleanup()
|
|
121
154
|
promise.reject("Timed out waiting for local association to be ready", e)
|
|
122
155
|
} catch (e: InterruptedException) {
|
|
123
|
-
Log.w(
|
|
156
|
+
Log.w(TAG, "Interrupted while waiting for local association to be ready", e)
|
|
157
|
+
finishHeadlessTask(sessionTaskId)
|
|
124
158
|
cleanup()
|
|
125
159
|
promise.reject(e)
|
|
126
160
|
} catch (e: ExecutionException) {
|
|
127
|
-
Log.e(
|
|
161
|
+
Log.e(TAG, "Failed establishing local association with wallet", e.cause)
|
|
162
|
+
finishHeadlessTask(sessionTaskId)
|
|
128
163
|
cleanup()
|
|
129
164
|
promise.reject(e)
|
|
130
165
|
} catch (e: Throwable) {
|
|
131
|
-
Log.e(
|
|
166
|
+
Log.e(TAG, "Failed to start session", e)
|
|
167
|
+
finishHeadlessTask(sessionTaskId)
|
|
132
168
|
cleanup()
|
|
133
169
|
promise.reject(e)
|
|
134
170
|
}
|
|
@@ -137,58 +173,54 @@ class SolanaMobileWalletAdapterModule(reactContext: ReactApplicationContext) :
|
|
|
137
173
|
|
|
138
174
|
@ReactMethod
|
|
139
175
|
override fun invoke(method: String, params: ReadableMap?, promise: Promise): Unit =
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
val
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
} else {
|
|
158
|
-
throw e
|
|
159
|
-
}
|
|
160
|
-
} catch (e: Throwable) {
|
|
161
|
-
Log.e(name, "Failed to invoke `$method` with params $params", e)
|
|
162
|
-
promise.reject(e)
|
|
176
|
+
sessionState?.let {
|
|
177
|
+
Log.d(TAG, "invoke `$method` with params $params")
|
|
178
|
+
try {
|
|
179
|
+
val result = it.client
|
|
180
|
+
.methodCall(method, convertMapToJson(params), CLIENT_TIMEOUT_MS)
|
|
181
|
+
.get() as JSONObject
|
|
182
|
+
promise.resolve(convertJsonToMap(result))
|
|
183
|
+
} catch (e: ExecutionException) {
|
|
184
|
+
val cause = e.cause
|
|
185
|
+
if (cause is JsonRpc20Client.JsonRpc20RemoteException) {
|
|
186
|
+
val userInfo = Arguments.createMap()
|
|
187
|
+
userInfo.putInt("jsonRpcErrorCode", cause.code)
|
|
188
|
+
promise.reject("JSON_RPC_ERROR", cause, userInfo)
|
|
189
|
+
} else if (cause is TimeoutException) {
|
|
190
|
+
promise.reject("Timed out waiting for response", e)
|
|
191
|
+
} else {
|
|
192
|
+
throw e
|
|
163
193
|
}
|
|
194
|
+
} catch (e: Throwable) {
|
|
195
|
+
Log.e(TAG, "Failed to invoke `$method` with params $params", e)
|
|
196
|
+
promise.reject(e)
|
|
164
197
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
198
|
+
} ?: throw NullPointerException(
|
|
199
|
+
"Tried to invoke `$method` without an active session"
|
|
200
|
+
)
|
|
168
201
|
|
|
169
202
|
@ReactMethod
|
|
170
203
|
override fun endSession(promise: Promise): Unit {
|
|
171
204
|
sessionState?.let {
|
|
172
205
|
launch {
|
|
173
|
-
Log.d(
|
|
206
|
+
Log.d(TAG, "endSession")
|
|
174
207
|
try {
|
|
175
208
|
it.localAssociation
|
|
176
|
-
|
|
177
|
-
|
|
209
|
+
.close()
|
|
210
|
+
.get(ASSOCIATION_TIMEOUT_MS.toLong(), TimeUnit.MILLISECONDS)
|
|
178
211
|
cleanup()
|
|
179
212
|
promise.resolve(true)
|
|
180
213
|
} catch (e: TimeoutException) {
|
|
181
|
-
Log.e(
|
|
214
|
+
Log.e(TAG, "Timed out waiting for local association to close", e)
|
|
182
215
|
cleanup()
|
|
183
216
|
promise.reject("Failed to end session", e)
|
|
184
217
|
} catch (e: Throwable) {
|
|
185
|
-
Log.e(
|
|
218
|
+
Log.e(TAG, "Failed to end session", e)
|
|
186
219
|
cleanup()
|
|
187
220
|
promise.reject("Failed to end session", e)
|
|
188
221
|
}
|
|
189
222
|
}
|
|
190
|
-
}
|
|
191
|
-
?: throw NullPointerException("Tried to end a session without an active session")
|
|
223
|
+
} ?: throw NullPointerException("Tried to end a session without an active session")
|
|
192
224
|
}
|
|
193
225
|
|
|
194
226
|
private fun cleanup() {
|
package/lib/cjs/index.native.js
CHANGED
|
@@ -247,6 +247,14 @@ function signInFallback(signInPayload, authorizationResult, protocolRequestHandl
|
|
|
247
247
|
});
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
+
reactNative.AppRegistry.registerHeadlessTask('SolanaMobileWalletAdapterSessionBackgroundTask', () => {
|
|
251
|
+
return () => __awaiter(void 0, void 0, void 0, function* () {
|
|
252
|
+
// This is a no-op task that is used to keep the app alive while the session is active.
|
|
253
|
+
// The actual session management is handled in the native module.
|
|
254
|
+
// This is necessary for the React Native Android implementation to work correctly.
|
|
255
|
+
// The task is started before startActivityResult and stopped when the activity result callback is triggered
|
|
256
|
+
});
|
|
257
|
+
});
|
|
250
258
|
const LINKING_ERROR = `The package 'solana-mobile-wallet-adapter-protocol' doesn't seem to be linked. Make sure: \n\n` +
|
|
251
259
|
'- You rebuilt the app after installing the package\n' +
|
|
252
260
|
'- If you are using Lerna workspaces\n' +
|
|
@@ -247,7 +247,7 @@ type SignInPayloadWithRequiredFields = Partial<SignInPayload> & Required<Pick<Si
|
|
|
247
247
|
type SignInResult = Readonly<{
|
|
248
248
|
address: Base64EncodedAddress;
|
|
249
249
|
signed_message: Base64EncodedSignedMessage;
|
|
250
|
-
signature:
|
|
250
|
+
signature: Base64EncodedSignature;
|
|
251
251
|
signature_type?: string;
|
|
252
252
|
}>;
|
|
253
253
|
type Scenario = Readonly<{
|
package/lib/types/index.d.ts
CHANGED
|
@@ -247,7 +247,7 @@ type SignInPayloadWithRequiredFields = Partial<SignInPayload> & Required<Pick<Si
|
|
|
247
247
|
type SignInResult = Readonly<{
|
|
248
248
|
address: Base64EncodedAddress;
|
|
249
249
|
signed_message: Base64EncodedSignedMessage;
|
|
250
|
-
signature:
|
|
250
|
+
signature: Base64EncodedSignature;
|
|
251
251
|
signature_type?: string;
|
|
252
252
|
}>;
|
|
253
253
|
type Scenario = Readonly<{
|
|
@@ -247,7 +247,7 @@ type SignInPayloadWithRequiredFields = Partial<SignInPayload> & Required<Pick<Si
|
|
|
247
247
|
type SignInResult = Readonly<{
|
|
248
248
|
address: Base64EncodedAddress;
|
|
249
249
|
signed_message: Base64EncodedSignedMessage;
|
|
250
|
-
signature:
|
|
250
|
+
signature: Base64EncodedSignature;
|
|
251
251
|
signature_type?: string;
|
|
252
252
|
}>;
|
|
253
253
|
type Scenario = Readonly<{
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solana-mobile/mobile-wallet-adapter-protocol",
|
|
3
3
|
"description": "An implementation of the Solana Mobile Mobile Wallet Adapter protocol. Use this to open a session with a mobile wallet app, and to issue API calls to it.",
|
|
4
|
-
"version": "2.2.
|
|
4
|
+
"version": "2.2.4",
|
|
5
5
|
"author": "Steven Luscher <steven.luscher@solanamobile.com>",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"require": "./lib/cjs/index.js"
|
|
19
19
|
},
|
|
20
20
|
"browser": {
|
|
21
|
-
"import": "./lib/
|
|
22
|
-
"require": "./lib/
|
|
21
|
+
"import": "./lib/esm/index.browser.js",
|
|
22
|
+
"require": "./lib/cjs/index.browser.js"
|
|
23
23
|
},
|
|
24
24
|
"node": {
|
|
25
25
|
"import": "./lib/esm/index.js",
|
package/.gitignore
DELETED
package/android/.gitignore
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { encode, fromUint8Array, toUint8Array } from 'js-base64';
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { Platform } from 'react-native';
|
|
2
|
-
|
|
3
|
-
import NativeSolanaMobileWalletAdapter from '../../codegenSpec/NativeSolanaMobileWalletAdapter.js';
|
|
4
|
-
import createMobileWalletProxy from '../../createMobileWalletProxy.js';
|
|
5
|
-
import { SolanaMobileWalletAdapterError, SolanaMobileWalletAdapterProtocolError } from '../../errors.js';
|
|
6
|
-
import { MobileWallet, SessionProperties, WalletAssociationConfig } from '../../types.js';
|
|
7
|
-
|
|
8
|
-
type ReactNativeError = Error & { code?: string; userInfo?: Record<string, unknown> };
|
|
9
|
-
|
|
10
|
-
const LINKING_ERROR =
|
|
11
|
-
`The package 'solana-mobile-wallet-adapter-protocol' doesn't seem to be linked. Make sure: \n\n` +
|
|
12
|
-
'- You rebuilt the app after installing the package\n' +
|
|
13
|
-
'- If you are using Lerna workspaces\n' +
|
|
14
|
-
' - You have added `@solana-mobile/mobile-wallet-adapter-protocol` as an explicit dependency, and\n' +
|
|
15
|
-
' - You have added `@solana-mobile/mobile-wallet-adapter-protocol` to the `nohoist` section of your package.json\n' +
|
|
16
|
-
'- You are not using Expo managed workflow\n';
|
|
17
|
-
|
|
18
|
-
const SolanaMobileWalletAdapter =
|
|
19
|
-
Platform.OS === 'android' && NativeSolanaMobileWalletAdapter
|
|
20
|
-
? NativeSolanaMobileWalletAdapter
|
|
21
|
-
: (new Proxy(
|
|
22
|
-
{},
|
|
23
|
-
{
|
|
24
|
-
get() {
|
|
25
|
-
throw new Error(
|
|
26
|
-
Platform.OS !== 'android'
|
|
27
|
-
? 'The package `solana-mobile-wallet-adapter-protocol` is only compatible with React Native Android'
|
|
28
|
-
: LINKING_ERROR,
|
|
29
|
-
);
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
) as typeof NativeSolanaMobileWalletAdapter);
|
|
33
|
-
|
|
34
|
-
function getErrorMessage(e: ReactNativeError): string {
|
|
35
|
-
switch (e.code) {
|
|
36
|
-
case 'ERROR_WALLET_NOT_FOUND':
|
|
37
|
-
return 'Found no installed wallet that supports the mobile wallet protocol.';
|
|
38
|
-
default:
|
|
39
|
-
return e.message;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function handleError(e: any): never {
|
|
44
|
-
if (e instanceof Error) {
|
|
45
|
-
const reactNativeError: ReactNativeError = e;
|
|
46
|
-
switch (reactNativeError.code) {
|
|
47
|
-
case undefined:
|
|
48
|
-
throw e;
|
|
49
|
-
case 'JSON_RPC_ERROR': {
|
|
50
|
-
const details = reactNativeError.userInfo as Readonly<{ jsonRpcErrorCode: number }>;
|
|
51
|
-
throw new SolanaMobileWalletAdapterProtocolError(
|
|
52
|
-
0 /* jsonRpcMessageId */,
|
|
53
|
-
details.jsonRpcErrorCode,
|
|
54
|
-
e.message,
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
default:
|
|
58
|
-
throw new SolanaMobileWalletAdapterError<any>(
|
|
59
|
-
reactNativeError.code,
|
|
60
|
-
getErrorMessage(reactNativeError),
|
|
61
|
-
reactNativeError.userInfo,
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
throw e;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export async function transact<TReturn>(
|
|
69
|
-
callback: (wallet: MobileWallet) => TReturn,
|
|
70
|
-
config?: WalletAssociationConfig,
|
|
71
|
-
): Promise<TReturn> {
|
|
72
|
-
let didSuccessfullyConnect = false;
|
|
73
|
-
try {
|
|
74
|
-
const sessionProperties: SessionProperties = await SolanaMobileWalletAdapter.startSession(config);
|
|
75
|
-
didSuccessfullyConnect = true;
|
|
76
|
-
const wallet = createMobileWalletProxy(sessionProperties.protocol_version, async (method, params) => {
|
|
77
|
-
try {
|
|
78
|
-
return SolanaMobileWalletAdapter.invoke(method, params);
|
|
79
|
-
} catch (e) {
|
|
80
|
-
return handleError(e);
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
return await callback(wallet);
|
|
84
|
-
} catch (e) {
|
|
85
|
-
return handleError(e);
|
|
86
|
-
} finally {
|
|
87
|
-
if (didSuccessfullyConnect) {
|
|
88
|
-
await SolanaMobileWalletAdapter.endSession();
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
// https://stackoverflow.com/a/9458996/802047
|
|
2
|
-
export default function arrayBufferToBase64String(buffer: ArrayBuffer) {
|
|
3
|
-
let binary = '';
|
|
4
|
-
const bytes = new Uint8Array(buffer);
|
|
5
|
-
const len = bytes.byteLength;
|
|
6
|
-
for (let ii = 0; ii < len; ii++) {
|
|
7
|
-
binary += String.fromCharCode(bytes[ii]);
|
|
8
|
-
}
|
|
9
|
-
return window.btoa(binary);
|
|
10
|
-
}
|
package/src/associationPort.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { SolanaMobileWalletAdapterError, SolanaMobileWalletAdapterErrorCode } from './errors.js';
|
|
2
|
-
|
|
3
|
-
declare const tag: unique symbol;
|
|
4
|
-
export type AssociationPort = number & { readonly [tag]: 'AssociationPort' };
|
|
5
|
-
|
|
6
|
-
export function getRandomAssociationPort(): AssociationPort {
|
|
7
|
-
return assertAssociationPort(49152 + Math.floor(Math.random() * (65535 - 49152 + 1)));
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function assertAssociationPort(port: number): AssociationPort {
|
|
11
|
-
if (port < 49152 || port > 65535) {
|
|
12
|
-
throw new SolanaMobileWalletAdapterError(
|
|
13
|
-
SolanaMobileWalletAdapterErrorCode.ERROR_ASSOCIATION_PORT_OUT_OF_RANGE,
|
|
14
|
-
`Association port number must be between 49152 and 65535. ${port} given.`,
|
|
15
|
-
{ port },
|
|
16
|
-
);
|
|
17
|
-
}
|
|
18
|
-
return port as AssociationPort;
|
|
19
|
-
}
|
package/src/base64Utils.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
export function encode(input: string): string {
|
|
2
|
-
return window.btoa(input);
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
export function fromUint8Array(byteArray: Uint8Array, urlsafe?: boolean): string {
|
|
6
|
-
const base64 = window.btoa(String.fromCharCode.call(null, ...byteArray));
|
|
7
|
-
if (urlsafe) {
|
|
8
|
-
return base64
|
|
9
|
-
.replace(/\+/g, '-')
|
|
10
|
-
.replace(/\//g, '_')
|
|
11
|
-
.replace(/=+$/, '');
|
|
12
|
-
} else return base64;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function toUint8Array(base64EncodedByteArray: string): Uint8Array {
|
|
16
|
-
return new Uint8Array(
|
|
17
|
-
window
|
|
18
|
-
.atob(base64EncodedByteArray)
|
|
19
|
-
.split('')
|
|
20
|
-
.map((c) => c.charCodeAt(0)),
|
|
21
|
-
);
|
|
22
|
-
}
|
package/src/createHelloReq.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export default async function createHelloReq(ecdhPublicKey: CryptoKey, associationKeypairPrivateKey: CryptoKey) {
|
|
2
|
-
const publicKeyBuffer = await crypto.subtle.exportKey('raw', ecdhPublicKey);
|
|
3
|
-
const signatureBuffer = await crypto.subtle.sign(
|
|
4
|
-
{ hash: 'SHA-256', name: 'ECDSA' },
|
|
5
|
-
associationKeypairPrivateKey,
|
|
6
|
-
publicKeyBuffer,
|
|
7
|
-
);
|
|
8
|
-
const response = new Uint8Array(publicKeyBuffer.byteLength + signatureBuffer.byteLength);
|
|
9
|
-
response.set(new Uint8Array(publicKeyBuffer), 0);
|
|
10
|
-
response.set(new Uint8Array(signatureBuffer), publicKeyBuffer.byteLength);
|
|
11
|
-
return response;
|
|
12
|
-
}
|