@solana-mobile/mobile-wallet-adapter-protocol 2.2.1 → 2.2.2-hotfix.0
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 +100 -83
- package/lib/cjs/index.native.js +8 -0
- package/package.json +28 -16
- 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.11.1'
|
|
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.0.8"
|
|
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
|
+
taskKey = "SolanaMobileWalletAdapterSessionBackgroundTask",
|
|
65
|
+
timeout = 0,
|
|
66
|
+
data = Arguments.createMap(),
|
|
67
|
+
isAllowedInForeground = true
|
|
68
|
+
)
|
|
58
69
|
|
|
59
70
|
init {
|
|
60
71
|
reactContext.addActivityEventListener(mActivityEventListener)
|
|
@@ -68,67 +79,77 @@ 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")
|
|
72
83
|
try {
|
|
73
84
|
val uriPrefix = config?.getString("baseUri")?.let { Uri.parse(it) }
|
|
74
85
|
val localAssociation =
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
86
|
+
LocalAssociationScenario(
|
|
87
|
+
CLIENT_TIMEOUT_MS,
|
|
88
|
+
)
|
|
78
89
|
val intent =
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
90
|
+
LocalAssociationIntentCreator.createAssociationIntent(
|
|
91
|
+
uriPrefix,
|
|
92
|
+
localAssociation.port,
|
|
93
|
+
localAssociation.session
|
|
94
|
+
)
|
|
95
|
+
val headlessJsTaskContext =
|
|
96
|
+
HeadlessJsTaskContext.getInstance(reactApplicationContext)
|
|
97
|
+
var sessionTaskId = headlessJsTaskContext.startTask(sessionBackgroundTaskConfig)
|
|
84
98
|
associationResultCallback = { resultCode ->
|
|
85
99
|
if (resultCode == Activity.RESULT_CANCELED) {
|
|
86
|
-
Log.d(
|
|
100
|
+
Log.d(TAG, "Local association cancelled by user, ending session")
|
|
87
101
|
promise.reject(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
102
|
+
"Session not established: Local association cancelled by user",
|
|
103
|
+
LocalAssociationScenario.ConnectionFailedException(
|
|
104
|
+
"Local association cancelled by user"
|
|
105
|
+
)
|
|
92
106
|
)
|
|
93
107
|
localAssociation.close()
|
|
94
108
|
}
|
|
109
|
+
|
|
110
|
+
// stop the headless js task, regardless if the association was successful or not
|
|
111
|
+
try {
|
|
112
|
+
if (headlessJsTaskContext.isTaskRunning(sessionTaskId)) {
|
|
113
|
+
headlessJsTaskContext.finishTask(sessionTaskId)
|
|
114
|
+
}
|
|
115
|
+
} catch (e: Exception) {
|
|
116
|
+
Log.w(TAG, "Failed to finish headless task during cleanup", e)
|
|
117
|
+
}
|
|
95
118
|
}
|
|
96
119
|
currentActivity?.startActivityForResult(intent, REQUEST_LOCAL_ASSOCIATION)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
val client =
|
|
101
|
-
|
|
102
|
-
.start()
|
|
103
|
-
.get(ASSOCIATION_TIMEOUT_MS.toLong(), TimeUnit.MILLISECONDS)
|
|
120
|
+
?: throw NullPointerException(
|
|
121
|
+
"Could not find a current activity from which to launch a local association"
|
|
122
|
+
)
|
|
123
|
+
val client = localAssociation.start()
|
|
124
|
+
.get(ASSOCIATION_TIMEOUT_MS.toLong(), TimeUnit.MILLISECONDS)
|
|
104
125
|
sessionState = SessionState(client, localAssociation)
|
|
105
126
|
val sessionPropertiesMap: WritableMap = WritableNativeMap()
|
|
106
127
|
sessionPropertiesMap.putString(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
128
|
+
"protocol_version",
|
|
129
|
+
when (localAssociation.session.sessionProperties.protocolVersion) {
|
|
130
|
+
ProtocolVersion.LEGACY -> "legacy"
|
|
131
|
+
ProtocolVersion.V1 -> "v1"
|
|
132
|
+
}
|
|
112
133
|
)
|
|
113
134
|
promise.resolve(sessionPropertiesMap)
|
|
114
135
|
} catch (e: ActivityNotFoundException) {
|
|
115
|
-
Log.e(
|
|
136
|
+
Log.e(TAG, "Found no installed wallet that supports the mobile wallet protocol", e)
|
|
116
137
|
cleanup()
|
|
117
138
|
promise.reject("ERROR_WALLET_NOT_FOUND", e)
|
|
118
139
|
} catch (e: TimeoutException) {
|
|
119
|
-
Log.e(
|
|
140
|
+
Log.e(TAG, "Timed out waiting for local association to be ready", e)
|
|
120
141
|
cleanup()
|
|
121
142
|
promise.reject("Timed out waiting for local association to be ready", e)
|
|
122
143
|
} catch (e: InterruptedException) {
|
|
123
|
-
Log.w(
|
|
144
|
+
Log.w(TAG, "Interrupted while waiting for local association to be ready", e)
|
|
124
145
|
cleanup()
|
|
125
146
|
promise.reject(e)
|
|
126
147
|
} catch (e: ExecutionException) {
|
|
127
|
-
Log.e(
|
|
148
|
+
Log.e(TAG, "Failed establishing local association with wallet", e.cause)
|
|
128
149
|
cleanup()
|
|
129
150
|
promise.reject(e)
|
|
130
151
|
} catch (e: Throwable) {
|
|
131
|
-
Log.e(
|
|
152
|
+
Log.e(TAG, "Failed to start session", e)
|
|
132
153
|
cleanup()
|
|
133
154
|
promise.reject(e)
|
|
134
155
|
}
|
|
@@ -137,58 +158,54 @@ class SolanaMobileWalletAdapterModule(reactContext: ReactApplicationContext) :
|
|
|
137
158
|
|
|
138
159
|
@ReactMethod
|
|
139
160
|
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)
|
|
161
|
+
sessionState?.let {
|
|
162
|
+
Log.d(TAG, "invoke `$method` with params $params")
|
|
163
|
+
try {
|
|
164
|
+
val result = it.client
|
|
165
|
+
.methodCall(method, convertMapToJson(params), CLIENT_TIMEOUT_MS)
|
|
166
|
+
.get() as JSONObject
|
|
167
|
+
promise.resolve(convertJsonToMap(result))
|
|
168
|
+
} catch (e: ExecutionException) {
|
|
169
|
+
val cause = e.cause
|
|
170
|
+
if (cause is JsonRpc20Client.JsonRpc20RemoteException) {
|
|
171
|
+
val userInfo = Arguments.createMap()
|
|
172
|
+
userInfo.putInt("jsonRpcErrorCode", cause.code)
|
|
173
|
+
promise.reject("JSON_RPC_ERROR", cause, userInfo)
|
|
174
|
+
} else if (cause is TimeoutException) {
|
|
175
|
+
promise.reject("Timed out waiting for response", e)
|
|
176
|
+
} else {
|
|
177
|
+
throw e
|
|
163
178
|
}
|
|
179
|
+
} catch (e: Throwable) {
|
|
180
|
+
Log.e(TAG, "Failed to invoke `$method` with params $params", e)
|
|
181
|
+
promise.reject(e)
|
|
164
182
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
183
|
+
} ?: throw NullPointerException(
|
|
184
|
+
"Tried to invoke `$method` without an active session"
|
|
185
|
+
)
|
|
168
186
|
|
|
169
187
|
@ReactMethod
|
|
170
188
|
override fun endSession(promise: Promise): Unit {
|
|
171
189
|
sessionState?.let {
|
|
172
190
|
launch {
|
|
173
|
-
Log.d(
|
|
191
|
+
Log.d(TAG, "endSession")
|
|
174
192
|
try {
|
|
175
193
|
it.localAssociation
|
|
176
|
-
|
|
177
|
-
|
|
194
|
+
.close()
|
|
195
|
+
.get(ASSOCIATION_TIMEOUT_MS.toLong(), TimeUnit.MILLISECONDS)
|
|
178
196
|
cleanup()
|
|
179
197
|
promise.resolve(true)
|
|
180
198
|
} catch (e: TimeoutException) {
|
|
181
|
-
Log.e(
|
|
199
|
+
Log.e(TAG, "Timed out waiting for local association to close", e)
|
|
182
200
|
cleanup()
|
|
183
201
|
promise.reject("Failed to end session", e)
|
|
184
202
|
} catch (e: Throwable) {
|
|
185
|
-
Log.e(
|
|
203
|
+
Log.e(TAG, "Failed to end session", e)
|
|
186
204
|
cleanup()
|
|
187
205
|
promise.reject("Failed to end session", e)
|
|
188
206
|
}
|
|
189
207
|
}
|
|
190
|
-
}
|
|
191
|
-
?: throw NullPointerException("Tried to end a session without an active session")
|
|
208
|
+
} ?: throw NullPointerException("Tried to end a session without an active session")
|
|
192
209
|
}
|
|
193
210
|
|
|
194
211
|
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' +
|
package/package.json
CHANGED
|
@@ -1,31 +1,42 @@
|
|
|
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.2-hotfix.0",
|
|
5
5
|
"author": "Steven Luscher <steven.luscher@solanamobile.com>",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/solana-mobile/mobile-wallet-adapter.git"
|
|
9
9
|
},
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
"exports": {
|
|
12
|
+
"edge-light": {
|
|
13
|
+
"import": "./lib/esm/index.js",
|
|
14
|
+
"require": "./lib/cjs/index.js"
|
|
15
|
+
},
|
|
16
|
+
"workerd": {
|
|
17
|
+
"import": "./lib/esm/index.js",
|
|
18
|
+
"require": "./lib/cjs/index.js"
|
|
19
|
+
},
|
|
20
|
+
"browser": {
|
|
21
|
+
"import": "./lib/cjs/index.browser.js",
|
|
22
|
+
"require": "./lib/esm/index.browser.js"
|
|
23
|
+
},
|
|
24
|
+
"node": {
|
|
25
|
+
"import": "./lib/esm/index.js",
|
|
26
|
+
"require": "./lib/cjs/index.js"
|
|
27
|
+
},
|
|
28
|
+
"react-native": "./lib/cjs/index.native.js",
|
|
29
|
+
"types": "./lib/types/index.d.ts"
|
|
30
|
+
},
|
|
17
31
|
"browser": {
|
|
18
32
|
"./lib/cjs/index.js": "./lib/cjs/index.browser.js",
|
|
19
33
|
"./lib/esm/index.js": "./lib/esm/index.browser.js"
|
|
20
34
|
},
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"types": "./lib/types/index.d.ts"
|
|
27
|
-
}
|
|
28
|
-
},
|
|
35
|
+
"main": "lib/cjs/index.js",
|
|
36
|
+
"module": "lib/esm/index.js",
|
|
37
|
+
"react-native": "lib/cjs/index.native.js",
|
|
38
|
+
"types": "lib/types/index.d.ts",
|
|
39
|
+
"type": "module",
|
|
29
40
|
"files": [
|
|
30
41
|
"android",
|
|
31
42
|
"src/codegenSpec",
|
|
@@ -33,6 +44,7 @@
|
|
|
33
44
|
"lib",
|
|
34
45
|
"LICENSE"
|
|
35
46
|
],
|
|
47
|
+
"sideEffects": false,
|
|
36
48
|
"publishConfig": {
|
|
37
49
|
"access": "public"
|
|
38
50
|
},
|
|
@@ -67,4 +79,4 @@
|
|
|
67
79
|
"javaPackageName": "com.solanamobile.mobilewalletadapter.reactnative"
|
|
68
80
|
}
|
|
69
81
|
}
|
|
70
|
-
}
|
|
82
|
+
}
|
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
|
-
}
|