@solana-mobile/mobile-wallet-adapter-protocol 2.2.4 → 2.2.6

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/lib/cjs/index.js CHANGED
@@ -1,8 +1,7 @@
1
1
  'use strict';
2
2
 
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
3
  var walletStandardUtil = require('@solana/wallet-standard-util');
4
+ var codecsStrings = require('@solana/codecs-strings');
6
5
 
7
6
  // Typescript `enums` thwart tree-shaking. See https://bargsten.org/jsts/enums/
8
7
  const SolanaMobileWalletAdapterErrorCode = {
@@ -15,8 +14,12 @@ const SolanaMobileWalletAdapterErrorCode = {
15
14
  ERROR_WALLET_NOT_FOUND: 'ERROR_WALLET_NOT_FOUND',
16
15
  ERROR_INVALID_PROTOCOL_VERSION: 'ERROR_INVALID_PROTOCOL_VERSION',
17
16
  ERROR_BROWSER_NOT_SUPPORTED: 'ERROR_BROWSER_NOT_SUPPORTED',
17
+ ERROR_LOOPBACK_ACCESS_BLOCKED: 'ERROR_LOOPBACK_ACCESS_BLOCKED',
18
+ ERROR_ASSOCIATION_CANCELLED: 'ERROR_ASSOCIATION_CANCELLED',
18
19
  };
19
20
  class SolanaMobileWalletAdapterError extends Error {
21
+ data;
22
+ code;
20
23
  constructor(...args) {
21
24
  const [code, message, data] = args;
22
25
  super(message);
@@ -36,6 +39,9 @@ const SolanaMobileWalletAdapterProtocolErrorCode = {
36
39
  ERROR_ATTEST_ORIGIN_ANDROID: -100,
37
40
  };
38
41
  class SolanaMobileWalletAdapterProtocolError extends Error {
42
+ data;
43
+ code;
44
+ jsonRpcMessageId;
39
45
  constructor(...args) {
40
46
  const [jsonRpcMessageId, code, message, data] = args;
41
47
  super(message);
@@ -46,35 +52,10 @@ class SolanaMobileWalletAdapterProtocolError extends Error {
46
52
  }
47
53
  }
48
54
 
49
- /******************************************************************************
50
- Copyright (c) Microsoft Corporation.
51
-
52
- Permission to use, copy, modify, and/or distribute this software for any
53
- purpose with or without fee is hereby granted.
54
-
55
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
56
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
57
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
58
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
59
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
60
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
61
- PERFORMANCE OF THIS SOFTWARE.
62
- ***************************************************************************** */
63
-
64
- function __awaiter(thisArg, _arguments, P, generator) {
65
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
66
- return new (P || (P = Promise))(function (resolve, reject) {
67
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
68
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
69
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
70
- step((generator = generator.apply(thisArg, _arguments || [])).next());
71
- });
72
- }
73
-
74
55
  function encode(input) {
75
56
  return window.btoa(input);
76
57
  }
77
- function fromUint8Array(byteArray, urlsafe) {
58
+ function fromUint8Array$1(byteArray, urlsafe) {
78
59
  const base64 = window.btoa(String.fromCharCode.call(null, ...byteArray));
79
60
  if (urlsafe) {
80
61
  return base64
@@ -92,22 +73,23 @@ function toUint8Array(base64EncodedByteArray) {
92
73
  .map((c) => c.charCodeAt(0)));
93
74
  }
94
75
 
95
- function createHelloReq(ecdhPublicKey, associationKeypairPrivateKey) {
96
- return __awaiter(this, void 0, void 0, function* () {
97
- const publicKeyBuffer = yield crypto.subtle.exportKey('raw', ecdhPublicKey);
98
- const signatureBuffer = yield crypto.subtle.sign({ hash: 'SHA-256', name: 'ECDSA' }, associationKeypairPrivateKey, publicKeyBuffer);
99
- const response = new Uint8Array(publicKeyBuffer.byteLength + signatureBuffer.byteLength);
100
- response.set(new Uint8Array(publicKeyBuffer), 0);
101
- response.set(new Uint8Array(signatureBuffer), publicKeyBuffer.byteLength);
102
- return response;
103
- });
76
+ async function createHelloReq(ecdhPublicKey, associationKeypairPrivateKey) {
77
+ const publicKeyBuffer = await crypto.subtle.exportKey('raw', ecdhPublicKey);
78
+ const signatureBuffer = await crypto.subtle.sign({ hash: 'SHA-256', name: 'ECDSA' }, associationKeypairPrivateKey, publicKeyBuffer);
79
+ const response = new Uint8Array(publicKeyBuffer.byteLength + signatureBuffer.byteLength);
80
+ response.set(new Uint8Array(publicKeyBuffer), 0);
81
+ response.set(new Uint8Array(signatureBuffer), publicKeyBuffer.byteLength);
82
+ return response;
104
83
  }
105
84
 
106
85
  function createSIWSMessage(payload) {
107
86
  return walletStandardUtil.createSignInMessageText(payload);
108
87
  }
109
- function createSIWSMessageBase64(payload) {
110
- return encode(createSIWSMessage(payload));
88
+ function createSIWSMessageBase64Url(payload) {
89
+ return encode(createSIWSMessage(payload))
90
+ .replace(/\+/g, '-')
91
+ .replace(/\//g, '_')
92
+ .replace(/=+$/, ''); // convert to base64url encoding;
111
93
  }
112
94
 
113
95
  // optional features
@@ -115,6 +97,13 @@ const SolanaSignTransactions = 'solana:signTransactions';
115
97
  const SolanaCloneAuthorization = 'solana:cloneAuthorization';
116
98
  const SolanaSignInWithSolana = 'solana:signInWithSolana';
117
99
 
100
+ function fromUint8Array(byteArray) {
101
+ return codecsStrings.getBase58Decoder().decode(byteArray);
102
+ }
103
+ function base64ToBase58(base64EncodedString) {
104
+ return fromUint8Array(toUint8Array(base64EncodedString));
105
+ }
106
+
118
107
  /**
119
108
  * Creates a {@link MobileWallet} proxy that handles backwards compatibility and API to RPC conversion.
120
109
  *
@@ -133,16 +122,14 @@ function createMobileWalletProxy(protocolVersion, protocolRequestHandler) {
133
122
  return null;
134
123
  }
135
124
  if (target[p] == null) {
136
- target[p] = function (inputParams) {
137
- return __awaiter(this, void 0, void 0, function* () {
138
- const { method, params } = handleMobileWalletRequest(p, inputParams, protocolVersion);
139
- const result = yield protocolRequestHandler(method, params);
140
- // if the request tried to sign in but the wallet did not return a sign in result, fallback on message signing
141
- if (method === 'authorize' && params.sign_in_payload && !result.sign_in_result) {
142
- result['sign_in_result'] = yield signInFallback(params.sign_in_payload, result, protocolRequestHandler);
143
- }
144
- return handleMobileWalletResponse(p, result, protocolVersion);
145
- });
125
+ target[p] = async function (inputParams) {
126
+ const { method, params } = handleMobileWalletRequest(p, inputParams, protocolVersion);
127
+ const result = await protocolRequestHandler(method, params);
128
+ // if the request tried to sign in but the wallet did not return a sign in result, fallback on message signing
129
+ if (method === 'authorize' && params.sign_in_payload && !result.sign_in_result) {
130
+ result['sign_in_result'] = await signInFallback(params.sign_in_payload, result, protocolRequestHandler);
131
+ }
132
+ return handleMobileWalletResponse(p, result, protocolVersion);
146
133
  };
147
134
  }
148
135
  return target[p];
@@ -246,33 +233,43 @@ function handleMobileWalletResponse(method, response, protocolVersion) {
246
233
  if (capabilities.supports_clone_authorization === true) {
247
234
  features.push(SolanaCloneAuthorization);
248
235
  }
249
- return Object.assign(Object.assign({}, capabilities), { features: features });
236
+ return {
237
+ ...capabilities,
238
+ features: features,
239
+ };
250
240
  }
251
241
  case 'v1': {
252
- return Object.assign(Object.assign({}, capabilities), { supports_sign_and_send_transactions: true, supports_clone_authorization: capabilities.features.includes(SolanaCloneAuthorization) });
242
+ return {
243
+ ...capabilities,
244
+ supports_sign_and_send_transactions: true,
245
+ supports_clone_authorization: capabilities.features.includes(SolanaCloneAuthorization)
246
+ };
253
247
  }
254
248
  }
255
249
  }
256
250
  }
257
251
  return response;
258
252
  }
259
- function signInFallback(signInPayload, authorizationResult, protocolRequestHandler) {
260
- var _a;
261
- return __awaiter(this, void 0, void 0, function* () {
262
- const domain = (_a = signInPayload.domain) !== null && _a !== void 0 ? _a : window.location.host;
263
- const address = authorizationResult.accounts[0].address;
264
- const siwsMessage = createSIWSMessageBase64(Object.assign(Object.assign({}, signInPayload), { domain, address }));
265
- const signMessageResult = yield protocolRequestHandler('sign_messages', {
266
- addresses: [address],
267
- payloads: [siwsMessage]
268
- });
269
- const signInResult = {
270
- address: address,
271
- signed_message: siwsMessage,
272
- signature: signMessageResult.signed_payloads[0].slice(siwsMessage.length)
273
- };
274
- return signInResult;
253
+ async function signInFallback(signInPayload, authorizationResult, protocolRequestHandler) {
254
+ const domain = signInPayload.domain ?? window.location.host;
255
+ const address = authorizationResult.accounts[0].address;
256
+ const siwsMessage = createSIWSMessageBase64Url({ ...signInPayload, domain, address: base64ToBase58(address) });
257
+ const signMessageResult = await protocolRequestHandler('sign_messages', {
258
+ addresses: [address],
259
+ payloads: [siwsMessage]
275
260
  });
261
+ const signedPayload = toUint8Array(signMessageResult.signed_payloads[0]);
262
+ const signedMessage = fromUint8Array$1(signedPayload.slice(0, signedPayload.length - 64));
263
+ const signature = fromUint8Array$1(signedPayload.slice(signedPayload.length - 64));
264
+ const signInResult = {
265
+ address: address,
266
+ // Workaround: some wallets have been observed to only reply with the message signature.
267
+ // This is non-compliant with the spec, but in the interest of maximizing compatibility,
268
+ // detect this case and reuse the original message.
269
+ signed_message: signedMessage.length == 0 ? siwsMessage : signedMessage,
270
+ signature
271
+ };
272
+ return signInResult;
276
273
  }
277
274
 
278
275
  const SEQUENCE_NUMBER_BYTES = 4;
@@ -288,28 +285,24 @@ function createSequenceNumberVector(sequenceNumber) {
288
285
 
289
286
  const INITIALIZATION_VECTOR_BYTES = 12;
290
287
  const ENCODED_PUBLIC_KEY_LENGTH_BYTES = 65;
291
- function encryptMessage(plaintext, sequenceNumber, sharedSecret) {
292
- return __awaiter(this, void 0, void 0, function* () {
293
- const sequenceNumberVector = createSequenceNumberVector(sequenceNumber);
294
- const initializationVector = new Uint8Array(INITIALIZATION_VECTOR_BYTES);
295
- crypto.getRandomValues(initializationVector);
296
- const ciphertext = yield crypto.subtle.encrypt(getAlgorithmParams(sequenceNumberVector, initializationVector), sharedSecret, new TextEncoder().encode(plaintext));
297
- const response = new Uint8Array(sequenceNumberVector.byteLength + initializationVector.byteLength + ciphertext.byteLength);
298
- response.set(new Uint8Array(sequenceNumberVector), 0);
299
- response.set(new Uint8Array(initializationVector), sequenceNumberVector.byteLength);
300
- response.set(new Uint8Array(ciphertext), sequenceNumberVector.byteLength + initializationVector.byteLength);
301
- return response;
302
- });
288
+ async function encryptMessage(plaintext, sequenceNumber, sharedSecret) {
289
+ const sequenceNumberVector = createSequenceNumberVector(sequenceNumber);
290
+ const initializationVector = new Uint8Array(INITIALIZATION_VECTOR_BYTES);
291
+ crypto.getRandomValues(initializationVector);
292
+ const ciphertext = await crypto.subtle.encrypt(getAlgorithmParams(sequenceNumberVector, initializationVector), sharedSecret, new TextEncoder().encode(plaintext));
293
+ const response = new Uint8Array(sequenceNumberVector.byteLength + initializationVector.byteLength + ciphertext.byteLength);
294
+ response.set(new Uint8Array(sequenceNumberVector), 0);
295
+ response.set(new Uint8Array(initializationVector), sequenceNumberVector.byteLength);
296
+ response.set(new Uint8Array(ciphertext), sequenceNumberVector.byteLength + initializationVector.byteLength);
297
+ return response;
303
298
  }
304
- function decryptMessage(message, sharedSecret) {
305
- return __awaiter(this, void 0, void 0, function* () {
306
- const sequenceNumberVector = message.slice(0, SEQUENCE_NUMBER_BYTES);
307
- const initializationVector = message.slice(SEQUENCE_NUMBER_BYTES, SEQUENCE_NUMBER_BYTES + INITIALIZATION_VECTOR_BYTES);
308
- const ciphertext = message.slice(SEQUENCE_NUMBER_BYTES + INITIALIZATION_VECTOR_BYTES);
309
- const plaintextBuffer = yield crypto.subtle.decrypt(getAlgorithmParams(sequenceNumberVector, initializationVector), sharedSecret, ciphertext);
310
- const plaintext = getUtf8Decoder().decode(plaintextBuffer);
311
- return plaintext;
312
- });
299
+ async function decryptMessage(message, sharedSecret) {
300
+ const sequenceNumberVector = message.slice(0, SEQUENCE_NUMBER_BYTES);
301
+ const initializationVector = message.slice(SEQUENCE_NUMBER_BYTES, SEQUENCE_NUMBER_BYTES + INITIALIZATION_VECTOR_BYTES);
302
+ const ciphertext = message.slice(SEQUENCE_NUMBER_BYTES + INITIALIZATION_VECTOR_BYTES);
303
+ const plaintextBuffer = await crypto.subtle.decrypt(getAlgorithmParams(sequenceNumberVector, initializationVector), sharedSecret, ciphertext);
304
+ const plaintext = getUtf8Decoder().decode(plaintextBuffer);
305
+ return plaintext;
313
306
  }
314
307
  function getAlgorithmParams(sequenceNumber, initializationVector) {
315
308
  return {
@@ -327,22 +320,18 @@ function getUtf8Decoder() {
327
320
  return _utf8Decoder;
328
321
  }
329
322
 
330
- function generateAssociationKeypair() {
331
- return __awaiter(this, void 0, void 0, function* () {
332
- return yield crypto.subtle.generateKey({
333
- name: 'ECDSA',
334
- namedCurve: 'P-256',
335
- }, false /* extractable */, ['sign'] /* keyUsages */);
336
- });
323
+ async function generateAssociationKeypair() {
324
+ return await crypto.subtle.generateKey({
325
+ name: 'ECDSA',
326
+ namedCurve: 'P-256',
327
+ }, false /* extractable */, ['sign'] /* keyUsages */);
337
328
  }
338
329
 
339
- function generateECDHKeypair() {
340
- return __awaiter(this, void 0, void 0, function* () {
341
- return yield crypto.subtle.generateKey({
342
- name: 'ECDH',
343
- namedCurve: 'P-256',
344
- }, false /* extractable */, ['deriveKey', 'deriveBits'] /* keyUsages */);
345
- });
330
+ async function generateECDHKeypair() {
331
+ return await crypto.subtle.generateKey({
332
+ name: 'ECDH',
333
+ namedCurve: 'P-256',
334
+ }, false /* extractable */, ['deriveKey', 'deriveBits'] /* keyUsages */);
346
335
  }
347
336
 
348
337
  // https://stackoverflow.com/a/9458996/802047
@@ -388,12 +377,12 @@ function getIntentURL(methodPathname, intentUrlBase) {
388
377
  try {
389
378
  baseUrl = new URL(intentUrlBase);
390
379
  }
391
- catch (_a) { } // eslint-disable-line no-empty
392
- if ((baseUrl === null || baseUrl === void 0 ? void 0 : baseUrl.protocol) !== 'https:') {
380
+ catch { } // eslint-disable-line no-empty
381
+ if (baseUrl?.protocol !== 'https:') {
393
382
  throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_FORBIDDEN_WALLET_BASE_URL, 'Base URLs supplied by wallets must be valid `https` URLs');
394
383
  }
395
384
  }
396
- baseUrl || (baseUrl = new URL(`${INTENT_NAME}:/`));
385
+ baseUrl ||= new URL(`${INTENT_NAME}:/`);
397
386
  const pathname = methodPathname.startsWith('/')
398
387
  ? // Method is an absolute path. Replace it wholesale.
399
388
  methodPathname
@@ -401,94 +390,82 @@ function getIntentURL(methodPathname, intentUrlBase) {
401
390
  [...getPathParts(baseUrl.pathname), ...getPathParts(methodPathname)].join('/');
402
391
  return new URL(pathname, baseUrl);
403
392
  }
404
- function getAssociateAndroidIntentURL(associationPublicKey, putativePort, associationURLBase, protocolVersions = ['v1']) {
405
- return __awaiter(this, void 0, void 0, function* () {
406
- const associationPort = assertAssociationPort(putativePort);
407
- const exportedKey = yield crypto.subtle.exportKey('raw', associationPublicKey);
408
- const encodedKey = arrayBufferToBase64String(exportedKey);
409
- const url = getIntentURL('v1/associate/local', associationURLBase);
410
- url.searchParams.set('association', getStringWithURLUnsafeCharactersReplaced(encodedKey));
411
- url.searchParams.set('port', `${associationPort}`);
412
- protocolVersions.forEach((version) => {
413
- url.searchParams.set('v', version);
414
- });
415
- return url;
393
+ async function getAssociateAndroidIntentURL(associationPublicKey, putativePort, associationURLBase, protocolVersions = ['v1']) {
394
+ const associationPort = assertAssociationPort(putativePort);
395
+ const exportedKey = await crypto.subtle.exportKey('raw', associationPublicKey);
396
+ const encodedKey = arrayBufferToBase64String(exportedKey);
397
+ const url = getIntentURL('v1/associate/local', associationURLBase);
398
+ url.searchParams.set('association', getStringWithURLUnsafeCharactersReplaced(encodedKey));
399
+ url.searchParams.set('port', `${associationPort}`);
400
+ protocolVersions.forEach((version) => {
401
+ url.searchParams.set('v', version);
416
402
  });
403
+ return url;
417
404
  }
418
- function getRemoteAssociateAndroidIntentURL(associationPublicKey, hostAuthority, reflectorId, associationURLBase, protocolVersions = ['v1']) {
419
- return __awaiter(this, void 0, void 0, function* () {
420
- const exportedKey = yield crypto.subtle.exportKey('raw', associationPublicKey);
421
- const encodedKey = arrayBufferToBase64String(exportedKey);
422
- const url = getIntentURL('v1/associate/remote', associationURLBase);
423
- url.searchParams.set('association', getStringWithURLUnsafeCharactersReplaced(encodedKey));
424
- url.searchParams.set('reflector', `${hostAuthority}`);
425
- url.searchParams.set('id', `${fromUint8Array(reflectorId, true)}`);
426
- protocolVersions.forEach((version) => {
427
- url.searchParams.set('v', version);
428
- });
429
- return url;
405
+ async function getRemoteAssociateAndroidIntentURL(associationPublicKey, hostAuthority, reflectorId, associationURLBase, protocolVersions = ['v1']) {
406
+ const exportedKey = await crypto.subtle.exportKey('raw', associationPublicKey);
407
+ const encodedKey = arrayBufferToBase64String(exportedKey);
408
+ const url = getIntentURL('v1/associate/remote', associationURLBase);
409
+ url.searchParams.set('association', getStringWithURLUnsafeCharactersReplaced(encodedKey));
410
+ url.searchParams.set('reflector', `${hostAuthority}`);
411
+ url.searchParams.set('id', `${fromUint8Array$1(reflectorId, true)}`);
412
+ protocolVersions.forEach((version) => {
413
+ url.searchParams.set('v', version);
430
414
  });
415
+ return url;
431
416
  }
432
417
 
433
- function encryptJsonRpcMessage(jsonRpcMessage, sharedSecret) {
434
- return __awaiter(this, void 0, void 0, function* () {
435
- const plaintext = JSON.stringify(jsonRpcMessage);
436
- const sequenceNumber = jsonRpcMessage.id;
437
- return encryptMessage(plaintext, sequenceNumber, sharedSecret);
438
- });
418
+ async function encryptJsonRpcMessage(jsonRpcMessage, sharedSecret) {
419
+ const plaintext = JSON.stringify(jsonRpcMessage);
420
+ const sequenceNumber = jsonRpcMessage.id;
421
+ return encryptMessage(plaintext, sequenceNumber, sharedSecret);
439
422
  }
440
- function decryptJsonRpcMessage(message, sharedSecret) {
441
- return __awaiter(this, void 0, void 0, function* () {
442
- const plaintext = yield decryptMessage(message, sharedSecret);
443
- const jsonRpcMessage = JSON.parse(plaintext);
444
- if (Object.hasOwnProperty.call(jsonRpcMessage, 'error')) {
445
- throw new SolanaMobileWalletAdapterProtocolError(jsonRpcMessage.id, jsonRpcMessage.error.code, jsonRpcMessage.error.message);
446
- }
447
- return jsonRpcMessage;
448
- });
423
+ async function decryptJsonRpcMessage(message, sharedSecret) {
424
+ const plaintext = await decryptMessage(message, sharedSecret);
425
+ const jsonRpcMessage = JSON.parse(plaintext);
426
+ if (Object.hasOwnProperty.call(jsonRpcMessage, 'error')) {
427
+ throw new SolanaMobileWalletAdapterProtocolError(jsonRpcMessage.id, jsonRpcMessage.error.code, jsonRpcMessage.error.message);
428
+ }
429
+ return jsonRpcMessage;
449
430
  }
450
431
 
451
- function parseHelloRsp(payloadBuffer, // The X9.62-encoded wallet endpoint ephemeral ECDH public keypoint.
432
+ async function parseHelloRsp(payloadBuffer, // The X9.62-encoded wallet endpoint ephemeral ECDH public keypoint.
452
433
  associationPublicKey, ecdhPrivateKey) {
453
- return __awaiter(this, void 0, void 0, function* () {
454
- const [associationPublicKeyBuffer, walletPublicKey] = yield Promise.all([
455
- crypto.subtle.exportKey('raw', associationPublicKey),
456
- crypto.subtle.importKey('raw', payloadBuffer.slice(0, ENCODED_PUBLIC_KEY_LENGTH_BYTES), { name: 'ECDH', namedCurve: 'P-256' }, false /* extractable */, [] /* keyUsages */),
457
- ]);
458
- const sharedSecret = yield crypto.subtle.deriveBits({ name: 'ECDH', public: walletPublicKey }, ecdhPrivateKey, 256);
459
- const ecdhSecretKey = yield crypto.subtle.importKey('raw', sharedSecret, 'HKDF', false /* extractable */, ['deriveKey'] /* keyUsages */);
460
- const aesKeyMaterialVal = yield crypto.subtle.deriveKey({
461
- name: 'HKDF',
462
- hash: 'SHA-256',
463
- salt: new Uint8Array(associationPublicKeyBuffer),
464
- info: new Uint8Array(),
465
- }, ecdhSecretKey, { name: 'AES-GCM', length: 128 }, false /* extractable */, ['encrypt', 'decrypt']);
466
- return aesKeyMaterialVal;
467
- });
434
+ const [associationPublicKeyBuffer, walletPublicKey] = await Promise.all([
435
+ crypto.subtle.exportKey('raw', associationPublicKey),
436
+ crypto.subtle.importKey('raw', payloadBuffer.slice(0, ENCODED_PUBLIC_KEY_LENGTH_BYTES), { name: 'ECDH', namedCurve: 'P-256' }, false /* extractable */, [] /* keyUsages */),
437
+ ]);
438
+ const sharedSecret = await crypto.subtle.deriveBits({ name: 'ECDH', public: walletPublicKey }, ecdhPrivateKey, 256);
439
+ const ecdhSecretKey = await crypto.subtle.importKey('raw', sharedSecret, 'HKDF', false /* extractable */, ['deriveKey'] /* keyUsages */);
440
+ const aesKeyMaterialVal = await crypto.subtle.deriveKey({
441
+ name: 'HKDF',
442
+ hash: 'SHA-256',
443
+ salt: new Uint8Array(associationPublicKeyBuffer),
444
+ info: new Uint8Array(),
445
+ }, ecdhSecretKey, { name: 'AES-GCM', length: 128 }, false /* extractable */, ['encrypt', 'decrypt']);
446
+ return aesKeyMaterialVal;
468
447
  }
469
448
 
470
- function parseSessionProps(message, sharedSecret) {
471
- return __awaiter(this, void 0, void 0, function* () {
472
- const plaintext = yield decryptMessage(message, sharedSecret);
473
- const jsonProperties = JSON.parse(plaintext);
474
- let protocolVersion = 'legacy';
475
- if (Object.hasOwnProperty.call(jsonProperties, 'v')) {
476
- switch (jsonProperties.v) {
477
- case 1:
478
- case '1':
479
- case 'v1':
480
- protocolVersion = 'v1';
481
- break;
482
- case 'legacy':
483
- protocolVersion = 'legacy';
484
- break;
485
- default:
486
- throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_INVALID_PROTOCOL_VERSION, `Unknown/unsupported protocol version: ${jsonProperties.v}`);
487
- }
449
+ async function parseSessionProps(message, sharedSecret) {
450
+ const plaintext = await decryptMessage(message, sharedSecret);
451
+ const jsonProperties = JSON.parse(plaintext);
452
+ let protocolVersion = 'legacy';
453
+ if (Object.hasOwnProperty.call(jsonProperties, 'v')) {
454
+ switch (jsonProperties.v) {
455
+ case 1:
456
+ case '1':
457
+ case 'v1':
458
+ protocolVersion = 'v1';
459
+ break;
460
+ case 'legacy':
461
+ protocolVersion = 'legacy';
462
+ break;
463
+ default:
464
+ throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_INVALID_PROTOCOL_VERSION, `Unknown/unsupported protocol version: ${jsonProperties.v}`);
488
465
  }
489
- return ({
490
- protocol_version: protocolVersion
491
- });
466
+ }
467
+ return ({
468
+ protocol_version: protocolVersion
492
469
  });
493
470
  }
494
471
 
@@ -533,47 +510,43 @@ function launchUrlThroughHiddenFrame(url) {
533
510
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
534
511
  _frame.contentWindow.location.href = url.toString();
535
512
  }
536
- function launchAssociation(associationUrl) {
537
- return __awaiter(this, void 0, void 0, function* () {
538
- if (associationUrl.protocol === 'https:') {
539
- // The association URL is an Android 'App Link' or iOS 'Universal Link'.
540
- // These are regular web URLs that are designed to launch an app if it
541
- // is installed or load the actual target webpage if not.
542
- window.location.assign(associationUrl);
543
- }
544
- else {
545
- // The association URL has a custom protocol (eg. `solana-wallet:`)
546
- try {
547
- const browser = getBrowser();
548
- switch (browser) {
549
- case Browser.Firefox:
550
- // If a custom protocol is not supported in Firefox, it throws.
551
- launchUrlThroughHiddenFrame(associationUrl);
552
- // If we reached this line, it's supported.
553
- break;
554
- case Browser.Other: {
555
- const detectionPromise = getDetectionPromise();
556
- window.location.assign(associationUrl);
557
- yield detectionPromise;
558
- break;
559
- }
560
- default:
561
- assertUnreachable(browser);
513
+ async function launchAssociation(associationUrl) {
514
+ if (associationUrl.protocol === 'https:') {
515
+ // The association URL is an Android 'App Link' or iOS 'Universal Link'.
516
+ // These are regular web URLs that are designed to launch an app if it
517
+ // is installed or load the actual target webpage if not.
518
+ window.location.assign(associationUrl);
519
+ }
520
+ else {
521
+ // The association URL has a custom protocol (eg. `solana-wallet:`)
522
+ try {
523
+ const browser = getBrowser();
524
+ switch (browser) {
525
+ case Browser.Firefox:
526
+ // If a custom protocol is not supported in Firefox, it throws.
527
+ launchUrlThroughHiddenFrame(associationUrl);
528
+ // If we reached this line, it's supported.
529
+ break;
530
+ case Browser.Other: {
531
+ const detectionPromise = getDetectionPromise();
532
+ window.location.assign(associationUrl);
533
+ await detectionPromise;
534
+ break;
562
535
  }
563
- }
564
- catch (e) {
565
- throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_WALLET_NOT_FOUND, 'Found no installed wallet that supports the mobile wallet protocol.');
536
+ default:
537
+ assertUnreachable(browser);
566
538
  }
567
539
  }
568
- });
540
+ catch (e) {
541
+ throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_WALLET_NOT_FOUND, 'Found no installed wallet that supports the mobile wallet protocol.');
542
+ }
543
+ }
569
544
  }
570
- function startSession(associationPublicKey, associationURLBase) {
571
- return __awaiter(this, void 0, void 0, function* () {
572
- const randomAssociationPort = getRandomAssociationPort();
573
- const associationUrl = yield getAssociateAndroidIntentURL(associationPublicKey, randomAssociationPort, associationURLBase);
574
- yield launchAssociation(associationUrl);
575
- return randomAssociationPort;
576
- });
545
+ async function startSession(associationPublicKey, associationURLBase) {
546
+ const randomAssociationPort = getRandomAssociationPort();
547
+ const associationUrl = await getAssociateAndroidIntentURL(associationPublicKey, randomAssociationPort, associationURLBase);
548
+ await launchAssociation(associationUrl);
549
+ return randomAssociationPort;
577
550
  }
578
551
 
579
552
  const WEBSOCKET_CONNECTION_CONFIG = {
@@ -602,7 +575,7 @@ function assertSecureEndpointSpecificURI(walletUriBase) {
602
575
  try {
603
576
  url = new URL(walletUriBase);
604
577
  }
605
- catch (_a) {
578
+ catch {
606
579
  throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_FORBIDDEN_WALLET_BASE_URL, 'Invalid base URL supplied by wallet');
607
580
  }
608
581
  if (url.protocol !== 'https:') {
@@ -627,25 +600,38 @@ function getReflectorIdFromByteArray(byteArray) {
627
600
  let { value: length, offset } = decodeVarLong(byteArray);
628
601
  return new Uint8Array(byteArray.slice(offset, offset + length));
629
602
  }
630
- function transact(callback, config) {
631
- return __awaiter(this, void 0, void 0, function* () {
632
- assertSecureContext();
633
- const associationKeypair = yield generateAssociationKeypair();
634
- const sessionPort = yield startSession(associationKeypair.publicKey, config === null || config === void 0 ? void 0 : config.baseUri);
635
- const websocketURL = `ws://localhost:${sessionPort}/solana-wallet`;
636
- let connectionStartTime;
637
- const getNextRetryDelayMs = (() => {
638
- const schedule = [...WEBSOCKET_CONNECTION_CONFIG.retryDelayScheduleMs];
639
- return () => (schedule.length > 1 ? schedule.shift() : schedule[0]);
640
- })();
641
- let nextJsonRpcMessageId = 1;
642
- let lastKnownInboundSequenceNumber = 0;
643
- let state = { __type: 'disconnected' };
644
- return new Promise((resolve, reject) => {
645
- let socket;
603
+ async function transact(callback, config) {
604
+ const { wallet, close } = await startScenario(config);
605
+ try {
606
+ return await callback(await wallet);
607
+ }
608
+ finally {
609
+ close();
610
+ }
611
+ }
612
+ async function startScenario(config) {
613
+ assertSecureContext();
614
+ const associationKeypair = await generateAssociationKeypair();
615
+ const sessionPort = await startSession(associationKeypair.publicKey, config?.baseUri);
616
+ const websocketURL = `ws://localhost:${sessionPort}/solana-wallet`;
617
+ let connectionStartTime;
618
+ const getNextRetryDelayMs = (() => {
619
+ const schedule = [...WEBSOCKET_CONNECTION_CONFIG.retryDelayScheduleMs];
620
+ return () => (schedule.length > 1 ? schedule.shift() : schedule[0]);
621
+ })();
622
+ let nextJsonRpcMessageId = 1;
623
+ let lastKnownInboundSequenceNumber = 0;
624
+ let state = { __type: 'disconnected' };
625
+ let socket;
626
+ let sessionEstablished = false;
627
+ let handleForceClose;
628
+ return { close: () => {
629
+ socket.close();
630
+ handleForceClose();
631
+ }, wallet: new Promise((resolve, reject) => {
646
632
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
647
633
  const jsonRpcResponsePromises = {};
648
- const handleOpen = () => __awaiter(this, void 0, void 0, function* () {
634
+ const handleOpen = async () => {
649
635
  if (state.__type !== 'connecting') {
650
636
  console.warn('Expected adapter state to be `connecting` at the moment the websocket opens. ' +
651
637
  `Got \`${state.__type}\`.`);
@@ -659,14 +645,14 @@ function transact(callback, config) {
659
645
  // APP_PING was sent by the wallet/websocket server. We must continue to support this behavior
660
646
  // in case the user is using a wallet that has not updated their walletlib implementation.
661
647
  const { associationKeypair } = state;
662
- const ecdhKeypair = yield generateECDHKeypair();
663
- socket.send(yield createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
648
+ const ecdhKeypair = await generateECDHKeypair();
649
+ socket.send(await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
664
650
  state = {
665
651
  __type: 'hello_req_sent',
666
652
  associationPublicKey: associationKeypair.publicKey,
667
653
  ecdhPrivateKey: ecdhKeypair.privateKey,
668
654
  };
669
- });
655
+ };
670
656
  const handleClose = (evt) => {
671
657
  if (evt.wasClean) {
672
658
  state = { __type: 'disconnected' };
@@ -676,28 +662,28 @@ function transact(callback, config) {
676
662
  }
677
663
  disposeSocket();
678
664
  };
679
- const handleError = (_evt) => __awaiter(this, void 0, void 0, function* () {
665
+ const handleError = async (_evt) => {
680
666
  disposeSocket();
681
667
  if (Date.now() - connectionStartTime >= WEBSOCKET_CONNECTION_CONFIG.timeoutMs) {
682
668
  reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_TIMEOUT, `Failed to connect to the wallet websocket at ${websocketURL}.`));
683
669
  }
684
670
  else {
685
- yield new Promise((resolve) => {
671
+ await new Promise((resolve) => {
686
672
  const retryDelayMs = getNextRetryDelayMs();
687
673
  retryWaitTimeoutId = window.setTimeout(resolve, retryDelayMs);
688
674
  });
689
675
  attemptSocketConnection();
690
676
  }
691
- });
692
- const handleMessage = (evt) => __awaiter(this, void 0, void 0, function* () {
693
- const responseBuffer = yield evt.data.arrayBuffer();
677
+ };
678
+ const handleMessage = async (evt) => {
679
+ const responseBuffer = await evt.data.arrayBuffer();
694
680
  switch (state.__type) {
695
681
  case 'connecting':
696
682
  if (responseBuffer.byteLength !== 0) {
697
683
  throw new Error('Encountered unexpected message while connecting');
698
684
  }
699
- const ecdhKeypair = yield generateECDHKeypair();
700
- socket.send(yield createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
685
+ const ecdhKeypair = await generateECDHKeypair();
686
+ socket.send(await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
701
687
  state = {
702
688
  __type: 'hello_req_sent',
703
689
  associationPublicKey: associationKeypair.publicKey,
@@ -712,7 +698,7 @@ function transact(callback, config) {
712
698
  throw new Error('Encrypted message has invalid sequence number');
713
699
  }
714
700
  lastKnownInboundSequenceNumber = sequenceNumber;
715
- const jsonRpcMessage = yield decryptJsonRpcMessage(responseBuffer, state.sharedSecret);
701
+ const jsonRpcMessage = await decryptJsonRpcMessage(responseBuffer, state.sharedSecret);
716
702
  const responsePromise = jsonRpcResponsePromises[jsonRpcMessage.id];
717
703
  delete jsonRpcResponsePromises[jsonRpcMessage.id];
718
704
  responsePromise.resolve(jsonRpcMessage.result);
@@ -731,8 +717,8 @@ function transact(callback, config) {
731
717
  case 'hello_req_sent': {
732
718
  // if we receive an APP_PING message (empty message), resend the HELLO_REQ (see above)
733
719
  if (responseBuffer.byteLength === 0) {
734
- const ecdhKeypair = yield generateECDHKeypair();
735
- socket.send(yield createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
720
+ const ecdhKeypair = await generateECDHKeypair();
721
+ socket.send(await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
736
722
  state = {
737
723
  __type: 'hello_req_sent',
738
724
  associationPublicKey: associationKeypair.publicKey,
@@ -740,10 +726,10 @@ function transact(callback, config) {
740
726
  };
741
727
  break;
742
728
  }
743
- const sharedSecret = yield parseHelloRsp(responseBuffer, state.associationPublicKey, state.ecdhPrivateKey);
729
+ const sharedSecret = await parseHelloRsp(responseBuffer, state.associationPublicKey, state.ecdhPrivateKey);
744
730
  const sessionPropertiesBuffer = responseBuffer.slice(ENCODED_PUBLIC_KEY_LENGTH_BYTES);
745
731
  const sessionProperties = sessionPropertiesBuffer.byteLength !== 0
746
- ? yield (() => __awaiter(this, void 0, void 0, function* () {
732
+ ? await (async () => {
747
733
  const sequenceNumberVector = sessionPropertiesBuffer.slice(0, SEQUENCE_NUMBER_BYTES);
748
734
  const sequenceNumber = getSequenceNumberFromByteArray(sequenceNumberVector);
749
735
  if (sequenceNumber !== (lastKnownInboundSequenceNumber + 1)) {
@@ -751,15 +737,15 @@ function transact(callback, config) {
751
737
  }
752
738
  lastKnownInboundSequenceNumber = sequenceNumber;
753
739
  return parseSessionProps(sessionPropertiesBuffer, sharedSecret);
754
- }))() : { protocol_version: 'legacy' };
740
+ })() : { protocol_version: 'legacy' };
755
741
  state = { __type: 'connected', sharedSecret, sessionProperties };
756
- const wallet = createMobileWalletProxy(sessionProperties.protocol_version, (method, params) => __awaiter(this, void 0, void 0, function* () {
742
+ const wallet = createMobileWalletProxy(sessionProperties.protocol_version, async (method, params) => {
757
743
  const id = nextJsonRpcMessageId++;
758
- socket.send(yield encryptJsonRpcMessage({
744
+ socket.send(await encryptJsonRpcMessage({
759
745
  id,
760
746
  jsonrpc: '2.0',
761
747
  method,
762
- params: params !== null && params !== void 0 ? params : {},
748
+ params: params ?? {},
763
749
  }, sharedSecret));
764
750
  return new Promise((resolve, reject) => {
765
751
  jsonRpcResponsePromises[id] = {
@@ -785,21 +771,25 @@ function transact(callback, config) {
785
771
  reject,
786
772
  };
787
773
  });
788
- }));
774
+ });
775
+ sessionEstablished = true;
789
776
  try {
790
- resolve(yield callback(wallet));
777
+ resolve(wallet);
791
778
  }
792
779
  catch (e) {
793
780
  reject(e);
794
781
  }
795
- finally {
796
- disposeSocket();
797
- socket.close();
798
- }
799
782
  break;
800
783
  }
801
784
  }
802
- });
785
+ };
786
+ handleForceClose = () => {
787
+ socket.removeEventListener('message', handleMessage);
788
+ disposeSocket();
789
+ if (!sessionEstablished) {
790
+ reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session was closed before connection.`, { closeEvent: new CloseEvent('socket was closed before connection') }));
791
+ }
792
+ };
803
793
  let disposeSocket;
804
794
  let retryWaitTimeoutId;
805
795
  const attemptSocketConnection = () => {
@@ -824,244 +814,241 @@ function transact(callback, config) {
824
814
  };
825
815
  };
826
816
  attemptSocketConnection();
827
- });
828
- });
817
+ }) };
829
818
  }
830
- function startRemoteScenario(config) {
831
- return __awaiter(this, void 0, void 0, function* () {
832
- assertSecureContext();
833
- const associationKeypair = yield generateAssociationKeypair();
834
- const websocketURL = `wss://${config === null || config === void 0 ? void 0 : config.remoteHostAuthority}/reflect`;
835
- let connectionStartTime;
836
- const getNextRetryDelayMs = (() => {
837
- const schedule = [...WEBSOCKET_CONNECTION_CONFIG.retryDelayScheduleMs];
838
- return () => (schedule.length > 1 ? schedule.shift() : schedule[0]);
839
- })();
840
- let nextJsonRpcMessageId = 1;
841
- let lastKnownInboundSequenceNumber = 0;
842
- let encoding;
843
- let state = { __type: 'disconnected' };
844
- let socket;
845
- let disposeSocket;
846
- let decodeBytes = (evt) => __awaiter(this, void 0, void 0, function* () {
847
- if (encoding == 'base64') { // base64 encoding
848
- const message = yield evt.data;
849
- return toUint8Array(message).buffer;
819
+ async function startRemoteScenario(config) {
820
+ assertSecureContext();
821
+ const associationKeypair = await generateAssociationKeypair();
822
+ const websocketURL = `wss://${config?.remoteHostAuthority}/reflect`;
823
+ let connectionStartTime;
824
+ const getNextRetryDelayMs = (() => {
825
+ const schedule = [...WEBSOCKET_CONNECTION_CONFIG.retryDelayScheduleMs];
826
+ return () => (schedule.length > 1 ? schedule.shift() : schedule[0]);
827
+ })();
828
+ let nextJsonRpcMessageId = 1;
829
+ let lastKnownInboundSequenceNumber = 0;
830
+ let encoding;
831
+ let state = { __type: 'disconnected' };
832
+ let socket;
833
+ let disposeSocket;
834
+ let decodeBytes = async (evt) => {
835
+ if (encoding == 'base64') { // base64 encoding
836
+ const message = await evt.data;
837
+ return toUint8Array(message).buffer;
838
+ }
839
+ else {
840
+ return await evt.data.arrayBuffer();
841
+ }
842
+ };
843
+ // Reflector Connection Phase
844
+ // here we connect to the reflector and wait for the REFLECTOR_ID message
845
+ // so we build the association URL and return that back to the caller
846
+ const associationUrl = await new Promise((resolve, reject) => {
847
+ const handleOpen = async () => {
848
+ if (state.__type !== 'connecting') {
849
+ console.warn('Expected adapter state to be `connecting` at the moment the websocket opens. ' +
850
+ `Got \`${state.__type}\`.`);
851
+ return;
852
+ }
853
+ if (socket.protocol.includes(WEBSOCKET_PROTOCOL_BASE64)) {
854
+ encoding = 'base64';
850
855
  }
851
856
  else {
852
- return yield evt.data.arrayBuffer();
857
+ encoding = 'binary';
853
858
  }
854
- });
855
- // Reflector Connection Phase
856
- // here we connect to the reflector and wait for the REFLECTOR_ID message
857
- // so we build the association URL and return that back to the caller
858
- const associationUrl = yield new Promise((resolve, reject) => {
859
- const handleOpen = () => __awaiter(this, void 0, void 0, function* () {
860
- if (state.__type !== 'connecting') {
861
- console.warn('Expected adapter state to be `connecting` at the moment the websocket opens. ' +
862
- `Got \`${state.__type}\`.`);
863
- return;
864
- }
865
- if (socket.protocol.includes(WEBSOCKET_PROTOCOL_BASE64)) {
866
- encoding = 'base64';
867
- }
868
- else {
869
- encoding = 'binary';
870
- }
871
- socket.removeEventListener('open', handleOpen);
872
- });
873
- const handleClose = (evt) => {
874
- if (evt.wasClean) {
875
- state = { __type: 'disconnected' };
876
- }
877
- else {
878
- reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session dropped unexpectedly (${evt.code}: ${evt.reason}).`, { closeEvent: evt }));
879
- }
880
- disposeSocket();
881
- };
882
- const handleError = (_evt) => __awaiter(this, void 0, void 0, function* () {
883
- disposeSocket();
884
- if (Date.now() - connectionStartTime >= WEBSOCKET_CONNECTION_CONFIG.timeoutMs) {
885
- reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_TIMEOUT, `Failed to connect to the wallet websocket at ${websocketURL}.`));
886
- }
887
- else {
888
- yield new Promise((resolve) => {
889
- const retryDelayMs = getNextRetryDelayMs();
890
- retryWaitTimeoutId = window.setTimeout(resolve, retryDelayMs);
891
- });
892
- attemptSocketConnection();
893
- }
894
- });
895
- const handleReflectorIdMessage = (evt) => __awaiter(this, void 0, void 0, function* () {
896
- const responseBuffer = yield decodeBytes(evt);
897
- if (state.__type === 'connecting') {
898
- if (responseBuffer.byteLength == 0) {
899
- throw new Error('Encountered unexpected message while connecting');
900
- }
901
- const reflectorId = getReflectorIdFromByteArray(responseBuffer);
902
- state = {
903
- __type: 'reflector_id_received',
904
- reflectorId: reflectorId
905
- };
906
- const associationUrl = yield getRemoteAssociateAndroidIntentURL(associationKeypair.publicKey, config.remoteHostAuthority, reflectorId, config === null || config === void 0 ? void 0 : config.baseUri);
907
- socket.removeEventListener('message', handleReflectorIdMessage);
908
- resolve(associationUrl);
909
- }
910
- });
911
- let retryWaitTimeoutId;
912
- const attemptSocketConnection = () => {
913
- if (disposeSocket) {
914
- disposeSocket();
915
- }
916
- state = { __type: 'connecting', associationKeypair };
917
- if (connectionStartTime === undefined) {
918
- connectionStartTime = Date.now();
859
+ socket.removeEventListener('open', handleOpen);
860
+ };
861
+ const handleClose = (evt) => {
862
+ if (evt.wasClean) {
863
+ state = { __type: 'disconnected' };
864
+ }
865
+ else {
866
+ reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session dropped unexpectedly (${evt.code}: ${evt.reason}).`, { closeEvent: evt }));
867
+ }
868
+ disposeSocket();
869
+ };
870
+ const handleError = async (_evt) => {
871
+ disposeSocket();
872
+ if (Date.now() - connectionStartTime >= WEBSOCKET_CONNECTION_CONFIG.timeoutMs) {
873
+ reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_TIMEOUT, `Failed to connect to the wallet websocket at ${websocketURL}.`));
874
+ }
875
+ else {
876
+ await new Promise((resolve) => {
877
+ const retryDelayMs = getNextRetryDelayMs();
878
+ retryWaitTimeoutId = window.setTimeout(resolve, retryDelayMs);
879
+ });
880
+ attemptSocketConnection();
881
+ }
882
+ };
883
+ const handleReflectorIdMessage = async (evt) => {
884
+ const responseBuffer = await decodeBytes(evt);
885
+ if (state.__type === 'connecting') {
886
+ if (responseBuffer.byteLength == 0) {
887
+ throw new Error('Encountered unexpected message while connecting');
919
888
  }
920
- socket = new WebSocket(websocketURL, [WEBSOCKET_PROTOCOL_BINARY, WEBSOCKET_PROTOCOL_BASE64]);
921
- socket.addEventListener('open', handleOpen);
922
- socket.addEventListener('close', handleClose);
923
- socket.addEventListener('error', handleError);
924
- socket.addEventListener('message', handleReflectorIdMessage);
925
- disposeSocket = () => {
926
- window.clearTimeout(retryWaitTimeoutId);
927
- socket.removeEventListener('open', handleOpen);
928
- socket.removeEventListener('close', handleClose);
929
- socket.removeEventListener('error', handleError);
930
- socket.removeEventListener('message', handleReflectorIdMessage);
889
+ const reflectorId = getReflectorIdFromByteArray(responseBuffer);
890
+ state = {
891
+ __type: 'reflector_id_received',
892
+ reflectorId: reflectorId
931
893
  };
894
+ const associationUrl = await getRemoteAssociateAndroidIntentURL(associationKeypair.publicKey, config.remoteHostAuthority, reflectorId, config?.baseUri);
895
+ socket.removeEventListener('message', handleReflectorIdMessage);
896
+ resolve(associationUrl);
897
+ }
898
+ };
899
+ let retryWaitTimeoutId;
900
+ const attemptSocketConnection = () => {
901
+ if (disposeSocket) {
902
+ disposeSocket();
903
+ }
904
+ state = { __type: 'connecting', associationKeypair };
905
+ if (connectionStartTime === undefined) {
906
+ connectionStartTime = Date.now();
907
+ }
908
+ socket = new WebSocket(websocketURL, [WEBSOCKET_PROTOCOL_BINARY, WEBSOCKET_PROTOCOL_BASE64]);
909
+ socket.addEventListener('open', handleOpen);
910
+ socket.addEventListener('close', handleClose);
911
+ socket.addEventListener('error', handleError);
912
+ socket.addEventListener('message', handleReflectorIdMessage);
913
+ disposeSocket = () => {
914
+ window.clearTimeout(retryWaitTimeoutId);
915
+ socket.removeEventListener('open', handleOpen);
916
+ socket.removeEventListener('close', handleClose);
917
+ socket.removeEventListener('error', handleError);
918
+ socket.removeEventListener('message', handleReflectorIdMessage);
932
919
  };
933
- attemptSocketConnection();
934
- });
935
- // Wallet Connection Phase
936
- // here we return the association URL (containing the reflector ID) to the caller +
937
- // a promise that will resolve the MobileWallet object once the wallet connects.
938
- let sessionEstablished = false;
939
- let handleClose;
940
- return { associationUrl, close: () => {
941
- socket.close();
942
- handleClose();
943
- }, wallet: new Promise((resolve, reject) => {
944
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
945
- const jsonRpcResponsePromises = {};
946
- const handleMessage = (evt) => __awaiter(this, void 0, void 0, function* () {
947
- const responseBuffer = yield decodeBytes(evt);
948
- switch (state.__type) {
949
- case 'reflector_id_received':
950
- if (responseBuffer.byteLength !== 0) {
951
- throw new Error('Encountered unexpected message while awaiting reflection');
920
+ };
921
+ attemptSocketConnection();
922
+ });
923
+ // Wallet Connection Phase
924
+ // here we return the association URL (containing the reflector ID) to the caller +
925
+ // a promise that will resolve the MobileWallet object once the wallet connects.
926
+ let sessionEstablished = false;
927
+ let handleClose;
928
+ return { associationUrl, close: () => {
929
+ socket.close();
930
+ handleClose();
931
+ }, wallet: new Promise((resolve, reject) => {
932
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
933
+ const jsonRpcResponsePromises = {};
934
+ const handleMessage = async (evt) => {
935
+ const responseBuffer = await decodeBytes(evt);
936
+ switch (state.__type) {
937
+ case 'reflector_id_received':
938
+ if (responseBuffer.byteLength !== 0) {
939
+ throw new Error('Encountered unexpected message while awaiting reflection');
940
+ }
941
+ const ecdhKeypair = await generateECDHKeypair();
942
+ const binaryMsg = await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey);
943
+ if (encoding == 'base64') {
944
+ socket.send(fromUint8Array$1(binaryMsg));
945
+ }
946
+ else {
947
+ socket.send(binaryMsg);
948
+ }
949
+ state = {
950
+ __type: 'hello_req_sent',
951
+ associationPublicKey: associationKeypair.publicKey,
952
+ ecdhPrivateKey: ecdhKeypair.privateKey,
953
+ };
954
+ break;
955
+ case 'connected':
956
+ try {
957
+ const sequenceNumberVector = responseBuffer.slice(0, SEQUENCE_NUMBER_BYTES);
958
+ const sequenceNumber = getSequenceNumberFromByteArray(sequenceNumberVector);
959
+ if (sequenceNumber !== (lastKnownInboundSequenceNumber + 1)) {
960
+ throw new Error('Encrypted message has invalid sequence number');
952
961
  }
953
- const ecdhKeypair = yield generateECDHKeypair();
954
- const binaryMsg = yield createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey);
955
- if (encoding == 'base64') {
956
- socket.send(fromUint8Array(binaryMsg));
962
+ lastKnownInboundSequenceNumber = sequenceNumber;
963
+ const jsonRpcMessage = await decryptJsonRpcMessage(responseBuffer, state.sharedSecret);
964
+ const responsePromise = jsonRpcResponsePromises[jsonRpcMessage.id];
965
+ delete jsonRpcResponsePromises[jsonRpcMessage.id];
966
+ responsePromise.resolve(jsonRpcMessage.result);
967
+ }
968
+ catch (e) {
969
+ if (e instanceof SolanaMobileWalletAdapterProtocolError) {
970
+ const responsePromise = jsonRpcResponsePromises[e.jsonRpcMessageId];
971
+ delete jsonRpcResponsePromises[e.jsonRpcMessageId];
972
+ responsePromise.reject(e);
957
973
  }
958
974
  else {
959
- socket.send(binaryMsg);
975
+ throw e;
960
976
  }
961
- state = {
962
- __type: 'hello_req_sent',
963
- associationPublicKey: associationKeypair.publicKey,
964
- ecdhPrivateKey: ecdhKeypair.privateKey,
965
- };
966
- break;
967
- case 'connected':
968
- try {
969
- const sequenceNumberVector = responseBuffer.slice(0, SEQUENCE_NUMBER_BYTES);
977
+ }
978
+ break;
979
+ case 'hello_req_sent': {
980
+ const sharedSecret = await parseHelloRsp(responseBuffer, state.associationPublicKey, state.ecdhPrivateKey);
981
+ const sessionPropertiesBuffer = responseBuffer.slice(ENCODED_PUBLIC_KEY_LENGTH_BYTES);
982
+ const sessionProperties = sessionPropertiesBuffer.byteLength !== 0
983
+ ? await (async () => {
984
+ const sequenceNumberVector = sessionPropertiesBuffer.slice(0, SEQUENCE_NUMBER_BYTES);
970
985
  const sequenceNumber = getSequenceNumberFromByteArray(sequenceNumberVector);
971
986
  if (sequenceNumber !== (lastKnownInboundSequenceNumber + 1)) {
972
987
  throw new Error('Encrypted message has invalid sequence number');
973
988
  }
974
989
  lastKnownInboundSequenceNumber = sequenceNumber;
975
- const jsonRpcMessage = yield decryptJsonRpcMessage(responseBuffer, state.sharedSecret);
976
- const responsePromise = jsonRpcResponsePromises[jsonRpcMessage.id];
977
- delete jsonRpcResponsePromises[jsonRpcMessage.id];
978
- responsePromise.resolve(jsonRpcMessage.result);
990
+ return parseSessionProps(sessionPropertiesBuffer, sharedSecret);
991
+ })() : { protocol_version: 'legacy' };
992
+ state = { __type: 'connected', sharedSecret, sessionProperties };
993
+ const wallet = createMobileWalletProxy(sessionProperties.protocol_version, async (method, params) => {
994
+ const id = nextJsonRpcMessageId++;
995
+ const binaryMsg = await encryptJsonRpcMessage({
996
+ id,
997
+ jsonrpc: '2.0',
998
+ method,
999
+ params: params ?? {},
1000
+ }, sharedSecret);
1001
+ if (encoding == 'base64') {
1002
+ socket.send(fromUint8Array$1(binaryMsg));
979
1003
  }
980
- catch (e) {
981
- if (e instanceof SolanaMobileWalletAdapterProtocolError) {
982
- const responsePromise = jsonRpcResponsePromises[e.jsonRpcMessageId];
983
- delete jsonRpcResponsePromises[e.jsonRpcMessageId];
984
- responsePromise.reject(e);
985
- }
986
- else {
987
- throw e;
988
- }
1004
+ else {
1005
+ socket.send(binaryMsg);
989
1006
  }
990
- break;
991
- case 'hello_req_sent': {
992
- const sharedSecret = yield parseHelloRsp(responseBuffer, state.associationPublicKey, state.ecdhPrivateKey);
993
- const sessionPropertiesBuffer = responseBuffer.slice(ENCODED_PUBLIC_KEY_LENGTH_BYTES);
994
- const sessionProperties = sessionPropertiesBuffer.byteLength !== 0
995
- ? yield (() => __awaiter(this, void 0, void 0, function* () {
996
- const sequenceNumberVector = sessionPropertiesBuffer.slice(0, SEQUENCE_NUMBER_BYTES);
997
- const sequenceNumber = getSequenceNumberFromByteArray(sequenceNumberVector);
998
- if (sequenceNumber !== (lastKnownInboundSequenceNumber + 1)) {
999
- throw new Error('Encrypted message has invalid sequence number');
1000
- }
1001
- lastKnownInboundSequenceNumber = sequenceNumber;
1002
- return parseSessionProps(sessionPropertiesBuffer, sharedSecret);
1003
- }))() : { protocol_version: 'legacy' };
1004
- state = { __type: 'connected', sharedSecret, sessionProperties };
1005
- const wallet = createMobileWalletProxy(sessionProperties.protocol_version, (method, params) => __awaiter(this, void 0, void 0, function* () {
1006
- const id = nextJsonRpcMessageId++;
1007
- const binaryMsg = yield encryptJsonRpcMessage({
1008
- id,
1009
- jsonrpc: '2.0',
1010
- method,
1011
- params: params !== null && params !== void 0 ? params : {},
1012
- }, sharedSecret);
1013
- if (encoding == 'base64') {
1014
- socket.send(fromUint8Array(binaryMsg));
1015
- }
1016
- else {
1017
- socket.send(binaryMsg);
1018
- }
1019
- return new Promise((resolve, reject) => {
1020
- jsonRpcResponsePromises[id] = {
1021
- resolve(result) {
1022
- switch (method) {
1023
- case 'authorize':
1024
- case 'reauthorize': {
1025
- const { wallet_uri_base } = result;
1026
- if (wallet_uri_base != null) {
1027
- try {
1028
- assertSecureEndpointSpecificURI(wallet_uri_base);
1029
- }
1030
- catch (e) {
1031
- reject(e);
1032
- return;
1033
- }
1007
+ return new Promise((resolve, reject) => {
1008
+ jsonRpcResponsePromises[id] = {
1009
+ resolve(result) {
1010
+ switch (method) {
1011
+ case 'authorize':
1012
+ case 'reauthorize': {
1013
+ const { wallet_uri_base } = result;
1014
+ if (wallet_uri_base != null) {
1015
+ try {
1016
+ assertSecureEndpointSpecificURI(wallet_uri_base);
1017
+ }
1018
+ catch (e) {
1019
+ reject(e);
1020
+ return;
1034
1021
  }
1035
- break;
1036
1022
  }
1023
+ break;
1037
1024
  }
1038
- resolve(result);
1039
- },
1040
- reject,
1041
- };
1042
- });
1043
- }));
1044
- sessionEstablished = true;
1045
- try {
1046
- resolve(wallet);
1047
- }
1048
- catch (e) {
1049
- reject(e);
1050
- }
1051
- break;
1025
+ }
1026
+ resolve(result);
1027
+ },
1028
+ reject,
1029
+ };
1030
+ });
1031
+ });
1032
+ sessionEstablished = true;
1033
+ try {
1034
+ resolve(wallet);
1052
1035
  }
1036
+ catch (e) {
1037
+ reject(e);
1038
+ }
1039
+ break;
1053
1040
  }
1054
- });
1055
- socket.addEventListener('message', handleMessage);
1056
- handleClose = () => {
1057
- socket.removeEventListener('message', handleMessage);
1058
- disposeSocket();
1059
- if (!sessionEstablished) {
1060
- reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session was closed before connection.`, { closeEvent: new CloseEvent('socket was closed before connection') }));
1061
- }
1062
- };
1063
- }) };
1064
- });
1041
+ }
1042
+ };
1043
+ socket.addEventListener('message', handleMessage);
1044
+ handleClose = () => {
1045
+ socket.removeEventListener('message', handleMessage);
1046
+ disposeSocket();
1047
+ if (!sessionEstablished) {
1048
+ reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session was closed before connection.`, { closeEvent: new CloseEvent('socket was closed before connection') }));
1049
+ }
1050
+ };
1051
+ }) };
1065
1052
  }
1066
1053
 
1067
1054
  exports.SolanaCloneAuthorization = SolanaCloneAuthorization;
@@ -1072,4 +1059,5 @@ exports.SolanaMobileWalletAdapterProtocolErrorCode = SolanaMobileWalletAdapterPr
1072
1059
  exports.SolanaSignInWithSolana = SolanaSignInWithSolana;
1073
1060
  exports.SolanaSignTransactions = SolanaSignTransactions;
1074
1061
  exports.startRemoteScenario = startRemoteScenario;
1062
+ exports.startScenario = startScenario;
1075
1063
  exports.transact = transact;