@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.
@@ -8,7 +8,7 @@ buildscript {
8
8
  }
9
9
 
10
10
  dependencies {
11
- classpath 'com.android.tools.build:gradle:8.10.1'
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.7"
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
- SolanaMobileWalletAdapterSpec(reactContext), CoroutineScope {
26
+ SolanaMobileWalletAdapterSpec(reactContext), CoroutineScope {
25
27
 
26
28
  data class SessionState(
27
- val client: MobileWalletAdapterClient,
28
- val localAssociation: LocalAssociationScenario,
29
+ val client: MobileWalletAdapterClient,
30
+ val localAssociation: LocalAssociationScenario,
29
31
  )
30
32
 
31
33
  override val coroutineContext =
32
- Dispatchers.IO + CoroutineName("SolanaMobileWalletAdapterModuleScope") + SupervisorJob()
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
- object : BaseActivityEventListener() {
48
- override fun onActivityResult(
49
- activity: Activity?,
50
- requestCode: Int,
51
- resultCode: Int,
52
- data: Intent?
53
- ) {
54
- if (requestCode == REQUEST_LOCAL_ASSOCIATION)
55
- associationResultCallback?.invoke(resultCode)
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(name, "startSession with config $config")
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
- LocalAssociationScenario(
76
- CLIENT_TIMEOUT_MS,
77
- )
101
+ LocalAssociationScenario(
102
+ CLIENT_TIMEOUT_MS,
103
+ )
78
104
  val intent =
79
- LocalAssociationIntentCreator.createAssociationIntent(
80
- uriPrefix,
81
- localAssociation.port,
82
- localAssociation.session
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(name, "Local association cancelled by user, ending session")
115
+ Log.d(TAG, "Local association cancelled by user, ending session")
87
116
  promise.reject(
88
- "Session not established: Local association cancelled by user",
89
- LocalAssociationScenario.ConnectionFailedException(
90
- "Local association cancelled by user"
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?.startActivityForResult(intent, REQUEST_LOCAL_ASSOCIATION)
97
- ?: throw NullPointerException(
98
- "Could not find a current activity from which to launch a local association"
99
- )
100
- val client =
101
- localAssociation
102
- .start()
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
- "protocol_version",
108
- when (localAssociation.session.sessionProperties.protocolVersion) {
109
- ProtocolVersion.LEGACY -> "legacy"
110
- ProtocolVersion.V1 -> "v1"
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(name, "Found no installed wallet that supports the mobile wallet protocol", 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(name, "Timed out waiting for local association to be ready", 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(name, "Interrupted while waiting for local association to be ready", e)
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(name, "Failed establishing local association with wallet", e.cause)
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(name, "Failed to start session", 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
- sessionState?.let {
141
- Log.d(name, "invoke `$method` with params $params")
142
- try {
143
- val result =
144
- it.client
145
- .methodCall(method, convertMapToJson(params), CLIENT_TIMEOUT_MS)
146
- .get() as
147
- JSONObject
148
- promise.resolve(convertJsonToMap(result))
149
- } catch (e: ExecutionException) {
150
- val cause = e.cause
151
- if (cause is JsonRpc20Client.JsonRpc20RemoteException) {
152
- val userInfo = Arguments.createMap()
153
- userInfo.putInt("jsonRpcErrorCode", cause.code)
154
- promise.reject("JSON_RPC_ERROR", cause, userInfo)
155
- } else if (cause is TimeoutException) {
156
- promise.reject("Timed out waiting for response", e)
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
- ?: throw NullPointerException(
166
- "Tried to invoke `$method` without an active session"
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(name, "endSession")
206
+ Log.d(TAG, "endSession")
174
207
  try {
175
208
  it.localAssociation
176
- .close()
177
- .get(ASSOCIATION_TIMEOUT_MS.toLong(), TimeUnit.MILLISECONDS)
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(name, "Timed out waiting for local association to close", 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(name, "Failed to end session", 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() {
@@ -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: Base64EncodedAddress;
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: Base64EncodedAddress;
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: Base64EncodedAddress;
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.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/cjs/index.browser.js",
22
- "require": "./lib/esm/index.browser.js"
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
@@ -1,2 +0,0 @@
1
- lib/
2
- android/build
@@ -1,14 +0,0 @@
1
- # OSX
2
- #
3
- .DS_Store
4
-
5
- # Android/IJ
6
- #
7
- .classpath
8
- .cxx
9
- .gradle
10
- .idea
11
- .project
12
- .settings
13
- local.properties
14
- android.iml
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }