@solana-mobile/mobile-wallet-adapter-protocol 0.0.1-alpha.6 → 0.9.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/lib/cjs/index.js CHANGED
@@ -2,65 +2,41 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- class SolanaMobileWalletAdapterSecureContextRequiredError extends Error {
6
- constructor() {
7
- super('The mobile wallet adapter protocol must be used in a secure context (`https`).');
8
- this.name = 'SolanaMobileWalletAdapterSecureContextRequiredError';
9
- }
10
- }
11
- class SolanaMobileWalletAdapterForbiddenWalletBaseURLError extends Error {
12
- constructor() {
13
- super('Base URLs supplied by wallets must be valid `https` URLs');
14
- this.name = 'SolanaMobileWalletAdapterForbiddenWalletBaseURLError';
15
- }
16
- }
17
- class SolanaMobileWalletAdapterWalletNotInstalledError extends Error {
18
- constructor() {
19
- super(`Found no installed wallet that supports the mobile wallet protocol.`);
20
- this.name = 'SolanaMobileWalletAdapterWalletNotInstalledError';
21
- }
22
- }
23
- class SolanaMobileWalletAdapterProtocolSessionEstablishmentError extends Error {
24
- constructor(port) {
25
- super(`Failed to connect to the wallet websocket on port ${port}.`);
26
- this.name = 'SolanaMobileWalletAdapterProtocolSessionEstablishmentError';
27
- }
28
- }
29
- class SolanaMobileWalletAdapterProtocolAssociationPortOutOfRangeError extends Error {
30
- constructor(port) {
31
- super(`Association port number must be between 49152 and 65535. ${port} given.`);
32
- this.name = 'SolanaMobileWalletAdapterProtocolAssociationPortOutOfRangeError';
33
- }
34
- }
35
- class SolanaMobileWalletAdapterProtocolSessionClosedError extends Error {
36
- constructor(code, reason) {
37
- super(`The wallet session dropped unexpectedly (${code}: ${reason}).`);
38
- this.name = 'SolanaMobileWalletAdapterProtocolSessionClosedError';
39
- }
40
- }
41
- class SolanaMobileWalletAdapterProtocolReauthorizeError extends Error {
42
- constructor() {
43
- super('The auth token provided has gone stale and needs reauthorization.');
44
- this.name = 'SolanaMobileWalletAdapterProtocolReauthorizeError';
5
+ // Typescript `enums` thwart tree-shaking. See https://bargsten.org/jsts/enums/
6
+ const SolanaMobileWalletAdapterErrorCode = {
7
+ ERROR_ASSOCIATION_PORT_OUT_OF_RANGE: 'ERROR_ASSOCIATION_PORT_OUT_OF_RANGE',
8
+ ERROR_FORBIDDEN_WALLET_BASE_URL: 'ERROR_FORBIDDEN_WALLET_BASE_URL',
9
+ ERROR_SECURE_CONTEXT_REQUIRED: 'ERROR_SECURE_CONTEXT_REQUIRED',
10
+ ERROR_SESSION_CLOSED: 'ERROR_SESSION_CLOSED',
11
+ ERROR_WALLET_NOT_FOUND: 'ERROR_WALLET_NOT_FOUND',
12
+ };
13
+ class SolanaMobileWalletAdapterError extends Error {
14
+ constructor(...args) {
15
+ const [code, message, data] = args;
16
+ super(message);
17
+ this.code = code;
18
+ this.data = data;
19
+ this.name = 'SolanaMobileWalletAdapterError';
45
20
  }
46
21
  }
47
22
  // Typescript `enums` thwart tree-shaking. See https://bargsten.org/jsts/enums/
48
- const SolanaMobileWalletAdapterProtocolError = {
49
- ERROR_REAUTHORIZE: -1,
50
- ERROR_AUTHORIZATION_FAILED: -2,
51
- ERROR_INVALID_PAYLOAD: -3,
52
- ERROR_NOT_SIGNED: -4,
53
- ERROR_NOT_COMMITTED: -5,
23
+ const SolanaMobileWalletAdapterProtocolErrorCode = {
24
+ // Keep these in sync with `mobilewalletadapter/common/ProtocolContract.java`.
25
+ ERROR_AUTHORIZATION_FAILED: -1,
26
+ ERROR_INVALID_PAYLOADS: -2,
27
+ ERROR_NOT_SIGNED: -3,
28
+ ERROR_NOT_SUBMITTED: -4,
29
+ ERROR_TOO_MANY_PAYLOADS: -5,
54
30
  ERROR_ATTEST_ORIGIN_ANDROID: -100,
55
31
  };
56
- class SolanaMobileWalletAdapterProtocolJsonRpcError extends Error {
32
+ class SolanaMobileWalletAdapterProtocolError extends Error {
57
33
  constructor(...args) {
58
34
  const [jsonRpcMessageId, code, message, data] = args;
59
35
  super(message);
60
36
  this.code = code;
61
37
  this.data = data;
62
38
  this.jsonRpcMessageId = jsonRpcMessageId;
63
- this.name = 'SolanaNativeWalletAdapterJsonRpcError';
39
+ this.name = 'SolanaMobileWalletAdapterProtocolError';
64
40
  }
65
41
  }
66
42
 
@@ -100,6 +76,17 @@ function createHelloReq(ecdhPublicKey, associationKeypairPrivateKey) {
100
76
  });
101
77
  }
102
78
 
79
+ const SEQUENCE_NUMBER_BYTES = 4;
80
+ function createSequenceNumberVector(sequenceNumber) {
81
+ if (sequenceNumber >= 4294967296) {
82
+ throw new Error('Outbound sequence number overflow. The maximum sequence number is 32-bytes.');
83
+ }
84
+ const byteArray = new ArrayBuffer(SEQUENCE_NUMBER_BYTES);
85
+ const view = new DataView(byteArray);
86
+ view.setUint32(0, sequenceNumber, /* littleEndian */ false);
87
+ return new Uint8Array(byteArray);
88
+ }
89
+
103
90
  function generateAssociationKeypair() {
104
91
  return __awaiter(this, void 0, void 0, function* () {
105
92
  return yield crypto.subtle.generateKey({
@@ -122,30 +109,34 @@ const INITIALIZATION_VECTOR_BYTES = 12;
122
109
  function encryptJsonRpcMessage(jsonRpcMessage, sharedSecret) {
123
110
  return __awaiter(this, void 0, void 0, function* () {
124
111
  const plaintext = JSON.stringify(jsonRpcMessage);
112
+ const sequenceNumberVector = createSequenceNumberVector(jsonRpcMessage.id);
125
113
  const initializationVector = new Uint8Array(INITIALIZATION_VECTOR_BYTES);
126
114
  crypto.getRandomValues(initializationVector);
127
- const ciphertext = yield crypto.subtle.encrypt(getAlgorithmParams(initializationVector), sharedSecret, Buffer.from(plaintext));
128
- const response = new Uint8Array(initializationVector.byteLength + ciphertext.byteLength);
129
- response.set(new Uint8Array(initializationVector), 0);
130
- response.set(new Uint8Array(ciphertext), initializationVector.byteLength);
115
+ const ciphertext = yield crypto.subtle.encrypt(getAlgorithmParams(sequenceNumberVector, initializationVector), sharedSecret, Buffer.from(plaintext));
116
+ const response = new Uint8Array(sequenceNumberVector.byteLength + initializationVector.byteLength + ciphertext.byteLength);
117
+ response.set(new Uint8Array(sequenceNumberVector), 0);
118
+ response.set(new Uint8Array(initializationVector), sequenceNumberVector.byteLength);
119
+ response.set(new Uint8Array(ciphertext), sequenceNumberVector.byteLength + initializationVector.byteLength);
131
120
  return response;
132
121
  });
133
122
  }
134
123
  function decryptJsonRpcMessage(message, sharedSecret) {
135
124
  return __awaiter(this, void 0, void 0, function* () {
136
- const initializationVector = message.slice(0, INITIALIZATION_VECTOR_BYTES);
137
- const ciphertext = message.slice(INITIALIZATION_VECTOR_BYTES);
138
- const plaintextBuffer = yield crypto.subtle.decrypt(getAlgorithmParams(initializationVector), sharedSecret, ciphertext);
125
+ const sequenceNumberVector = message.slice(0, SEQUENCE_NUMBER_BYTES);
126
+ const initializationVector = message.slice(SEQUENCE_NUMBER_BYTES, SEQUENCE_NUMBER_BYTES + INITIALIZATION_VECTOR_BYTES);
127
+ const ciphertext = message.slice(SEQUENCE_NUMBER_BYTES + INITIALIZATION_VECTOR_BYTES);
128
+ const plaintextBuffer = yield crypto.subtle.decrypt(getAlgorithmParams(sequenceNumberVector, initializationVector), sharedSecret, ciphertext);
139
129
  const plaintext = getUtf8Decoder().decode(plaintextBuffer);
140
130
  const jsonRpcMessage = JSON.parse(plaintext);
141
131
  if (Object.hasOwnProperty.call(jsonRpcMessage, 'error')) {
142
- throw new SolanaMobileWalletAdapterProtocolJsonRpcError(jsonRpcMessage.id, jsonRpcMessage.error.code, jsonRpcMessage.error.message);
132
+ throw new SolanaMobileWalletAdapterProtocolError(jsonRpcMessage.id, jsonRpcMessage.error.code, jsonRpcMessage.error.message);
143
133
  }
144
134
  return jsonRpcMessage;
145
135
  });
146
136
  }
147
- function getAlgorithmParams(initializationVector) {
137
+ function getAlgorithmParams(sequenceNumber, initializationVector) {
148
138
  return {
139
+ additionalData: sequenceNumber,
149
140
  iv: initializationVector,
150
141
  name: 'AES-GCM',
151
142
  tagLength: 128, // 16 byte tag => 128 bits
@@ -183,7 +174,7 @@ function getRandomAssociationPort() {
183
174
  }
184
175
  function assertAssociationPort(port) {
185
176
  if (port < 49152 || port > 65535) {
186
- throw new SolanaMobileWalletAdapterProtocolAssociationPortOutOfRangeError(port);
177
+ throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_ASSOCIATION_PORT_OUT_OF_RANGE, `Association port number must be between 49152 and 65535. ${port} given.`, { port });
187
178
  }
188
179
  return port;
189
180
  }
@@ -223,7 +214,7 @@ function getIntentURL(methodPathname, intentUrlBase) {
223
214
  }
224
215
  catch (_a) { } // eslint-disable-line no-empty
225
216
  if ((baseUrl === null || baseUrl === void 0 ? void 0 : baseUrl.protocol) !== 'https:') {
226
- throw new SolanaMobileWalletAdapterForbiddenWalletBaseURLError();
217
+ throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_FORBIDDEN_WALLET_BASE_URL, 'Base URLs supplied by wallets must be valid `https` URLs');
227
218
  }
228
219
  }
229
220
  baseUrl || (baseUrl = new URL(`${INTENT_NAME}:/`));
@@ -318,7 +309,7 @@ function startSession(associationPublicKey, associationURLBase) {
318
309
  }
319
310
  }
320
311
  catch (e) {
321
- throw new SolanaMobileWalletAdapterWalletNotInstalledError();
312
+ throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_WALLET_NOT_FOUND, 'Found no installed wallet that supports the mobile wallet protocol.');
322
313
  }
323
314
  }
324
315
  return randomAssociationPort;
@@ -326,32 +317,56 @@ function startSession(associationPublicKey, associationURLBase) {
326
317
  }
327
318
 
328
319
  const WEBSOCKET_CONNECTION_CONFIG = {
329
- maxAttempts: 34,
330
320
  /**
331
321
  * 300 milliseconds is a generally accepted threshold for what someone
332
322
  * would consider an acceptable response time for a user interface
333
- * after having performed a low-attention tapping task. We set the
323
+ * after having performed a low-attention tapping task. We set the initial
334
324
  * interval at which we wait for the wallet to set up the websocket at
335
- * half this, as per the Nyquist frequency.
325
+ * half this, as per the Nyquist frequency, with a progressive backoff
326
+ * sequence from there. The total wait time is 30s, which allows for the
327
+ * user to be presented with a disambiguation dialog, select a wallet, and
328
+ * for the wallet app to subsequently start.
336
329
  */
337
- retryDelayMs: 150,
330
+ retryDelayScheduleMs: [150, 150, 200, 500, 500, 750, 750, 1000],
331
+ timeoutMs: 30000,
338
332
  };
339
333
  const WEBSOCKET_PROTOCOL = 'com.solana.mobilewalletadapter.v1';
340
334
  function assertSecureContext() {
341
335
  if (typeof window === 'undefined' || window.isSecureContext !== true) {
342
- throw new SolanaMobileWalletAdapterSecureContextRequiredError();
336
+ throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SECURE_CONTEXT_REQUIRED, 'The mobile wallet adapter protocol must be used in a secure context (`https`).');
343
337
  }
344
338
  }
339
+ function assertSecureEndpointSpecificURI(walletUriBase) {
340
+ let url;
341
+ try {
342
+ url = new URL(walletUriBase);
343
+ }
344
+ catch (_a) {
345
+ throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_FORBIDDEN_WALLET_BASE_URL, 'Invalid base URL supplied by wallet');
346
+ }
347
+ if (url.protocol !== 'https:') {
348
+ throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_FORBIDDEN_WALLET_BASE_URL, 'Base URLs supplied by wallets must be valid `https` URLs');
349
+ }
350
+ }
351
+ function getSequenceNumberFromByteArray(byteArray) {
352
+ const view = new DataView(byteArray);
353
+ return view.getUint32(0, /* littleEndian */ false);
354
+ }
345
355
  function transact(callback, config) {
346
356
  return __awaiter(this, void 0, void 0, function* () {
347
357
  assertSecureContext();
348
358
  const associationKeypair = yield generateAssociationKeypair();
349
359
  const sessionPort = yield startSession(associationKeypair.publicKey, config === null || config === void 0 ? void 0 : config.baseUri);
350
360
  const websocketURL = `ws://localhost:${sessionPort}/solana-wallet`;
361
+ let connectionStartTime;
362
+ const getNextRetryDelayMs = (() => {
363
+ const schedule = [...WEBSOCKET_CONNECTION_CONFIG.retryDelayScheduleMs];
364
+ return () => (schedule.length > 1 ? schedule.shift() : schedule[0]);
365
+ })();
351
366
  let nextJsonRpcMessageId = 1;
367
+ let lastKnownInboundSequenceNumber = 0;
352
368
  let state = { __type: 'disconnected' };
353
369
  return new Promise((resolve, reject) => {
354
- let attempts = 0;
355
370
  let socket;
356
371
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
357
372
  const jsonRpcResponsePromises = {};
@@ -376,18 +391,19 @@ function transact(callback, config) {
376
391
  state = { __type: 'disconnected' };
377
392
  }
378
393
  else {
379
- reject(new SolanaMobileWalletAdapterProtocolSessionClosedError(evt.code, evt.reason));
394
+ reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session dropped unexpectedly (${evt.code}: ${evt.reason}).`, { closeEvent: evt }));
380
395
  }
381
396
  disposeSocket();
382
397
  };
383
398
  const handleError = (_evt) => __awaiter(this, void 0, void 0, function* () {
384
399
  disposeSocket();
385
- if (++attempts >= WEBSOCKET_CONNECTION_CONFIG.maxAttempts) {
386
- reject(new SolanaMobileWalletAdapterProtocolSessionEstablishmentError(sessionPort));
400
+ if (Date.now() - connectionStartTime >= WEBSOCKET_CONNECTION_CONFIG.timeoutMs) {
401
+ reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_WALLET_NOT_FOUND, `Failed to connect to the wallet websocket on port ${sessionPort}.`));
387
402
  }
388
403
  else {
389
404
  yield new Promise((resolve) => {
390
- retryWaitTimeoutId = window.setTimeout(resolve, WEBSOCKET_CONNECTION_CONFIG.retryDelayMs);
405
+ const retryDelayMs = getNextRetryDelayMs();
406
+ retryWaitTimeoutId = window.setTimeout(resolve, retryDelayMs);
391
407
  });
392
408
  attemptSocketConnection();
393
409
  }
@@ -397,13 +413,19 @@ function transact(callback, config) {
397
413
  switch (state.__type) {
398
414
  case 'connected':
399
415
  try {
416
+ const sequenceNumberVector = responseBuffer.slice(0, SEQUENCE_NUMBER_BYTES);
417
+ const sequenceNumber = getSequenceNumberFromByteArray(sequenceNumberVector);
418
+ if (sequenceNumber <= lastKnownInboundSequenceNumber) {
419
+ throw new Error('Encrypted message has invalid sequence number');
420
+ }
421
+ lastKnownInboundSequenceNumber = sequenceNumber;
400
422
  const jsonRpcMessage = yield decryptJsonRpcMessage(responseBuffer, state.sharedSecret);
401
423
  const responsePromise = jsonRpcResponsePromises[jsonRpcMessage.id];
402
424
  delete jsonRpcResponsePromises[jsonRpcMessage.id];
403
425
  responsePromise.resolve(jsonRpcMessage.result);
404
426
  }
405
427
  catch (e) {
406
- if (e instanceof SolanaMobileWalletAdapterProtocolJsonRpcError) {
428
+ if (e instanceof SolanaMobileWalletAdapterProtocolError) {
407
429
  const responsePromise = jsonRpcResponsePromises[e.jsonRpcMessageId];
408
430
  delete jsonRpcResponsePromises[e.jsonRpcMessageId];
409
431
  responsePromise.reject(e);
@@ -433,7 +455,28 @@ function transact(callback, config) {
433
455
  params,
434
456
  }, sharedSecret));
435
457
  return new Promise((resolve, reject) => {
436
- jsonRpcResponsePromises[id] = { resolve, reject };
458
+ jsonRpcResponsePromises[id] = {
459
+ resolve(result) {
460
+ switch (p) {
461
+ case 'authorize':
462
+ case 'reauthorize': {
463
+ const { wallet_uri_base } = result;
464
+ if (wallet_uri_base != null) {
465
+ try {
466
+ assertSecureEndpointSpecificURI(wallet_uri_base);
467
+ }
468
+ catch (e) {
469
+ reject(e);
470
+ return;
471
+ }
472
+ }
473
+ break;
474
+ }
475
+ }
476
+ resolve(result);
477
+ },
478
+ reject,
479
+ };
437
480
  });
438
481
  });
439
482
  };
@@ -468,6 +511,9 @@ function transact(callback, config) {
468
511
  disposeSocket();
469
512
  }
470
513
  state = { __type: 'connecting', associationKeypair };
514
+ if (connectionStartTime === undefined) {
515
+ connectionStartTime = Date.now();
516
+ }
471
517
  socket = new WebSocket(websocketURL, [WEBSOCKET_PROTOCOL]);
472
518
  socket.addEventListener('open', handleOpen);
473
519
  socket.addEventListener('close', handleClose);
@@ -486,13 +532,8 @@ function transact(callback, config) {
486
532
  });
487
533
  }
488
534
 
489
- exports.SolanaMobileWalletAdapterForbiddenWalletBaseURLError = SolanaMobileWalletAdapterForbiddenWalletBaseURLError;
490
- exports.SolanaMobileWalletAdapterProtocolAssociationPortOutOfRangeError = SolanaMobileWalletAdapterProtocolAssociationPortOutOfRangeError;
535
+ exports.SolanaMobileWalletAdapterError = SolanaMobileWalletAdapterError;
536
+ exports.SolanaMobileWalletAdapterErrorCode = SolanaMobileWalletAdapterErrorCode;
491
537
  exports.SolanaMobileWalletAdapterProtocolError = SolanaMobileWalletAdapterProtocolError;
492
- exports.SolanaMobileWalletAdapterProtocolJsonRpcError = SolanaMobileWalletAdapterProtocolJsonRpcError;
493
- exports.SolanaMobileWalletAdapterProtocolReauthorizeError = SolanaMobileWalletAdapterProtocolReauthorizeError;
494
- exports.SolanaMobileWalletAdapterProtocolSessionClosedError = SolanaMobileWalletAdapterProtocolSessionClosedError;
495
- exports.SolanaMobileWalletAdapterProtocolSessionEstablishmentError = SolanaMobileWalletAdapterProtocolSessionEstablishmentError;
496
- exports.SolanaMobileWalletAdapterSecureContextRequiredError = SolanaMobileWalletAdapterSecureContextRequiredError;
497
- exports.SolanaMobileWalletAdapterWalletNotInstalledError = SolanaMobileWalletAdapterWalletNotInstalledError;
538
+ exports.SolanaMobileWalletAdapterProtocolErrorCode = SolanaMobileWalletAdapterProtocolErrorCode;
498
539
  exports.transact = transact;
@@ -4,65 +4,41 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var reactNative = require('react-native');
6
6
 
7
- class SolanaMobileWalletAdapterSecureContextRequiredError extends Error {
8
- constructor() {
9
- super('The mobile wallet adapter protocol must be used in a secure context (`https`).');
10
- this.name = 'SolanaMobileWalletAdapterSecureContextRequiredError';
11
- }
12
- }
13
- class SolanaMobileWalletAdapterForbiddenWalletBaseURLError extends Error {
14
- constructor() {
15
- super('Base URLs supplied by wallets must be valid `https` URLs');
16
- this.name = 'SolanaMobileWalletAdapterForbiddenWalletBaseURLError';
17
- }
18
- }
19
- class SolanaMobileWalletAdapterWalletNotInstalledError extends Error {
20
- constructor() {
21
- super(`Found no installed wallet that supports the mobile wallet protocol.`);
22
- this.name = 'SolanaMobileWalletAdapterWalletNotInstalledError';
23
- }
24
- }
25
- class SolanaMobileWalletAdapterProtocolSessionEstablishmentError extends Error {
26
- constructor(port) {
27
- super(`Failed to connect to the wallet websocket on port ${port}.`);
28
- this.name = 'SolanaMobileWalletAdapterProtocolSessionEstablishmentError';
29
- }
30
- }
31
- class SolanaMobileWalletAdapterProtocolAssociationPortOutOfRangeError extends Error {
32
- constructor(port) {
33
- super(`Association port number must be between 49152 and 65535. ${port} given.`);
34
- this.name = 'SolanaMobileWalletAdapterProtocolAssociationPortOutOfRangeError';
35
- }
36
- }
37
- class SolanaMobileWalletAdapterProtocolSessionClosedError extends Error {
38
- constructor(code, reason) {
39
- super(`The wallet session dropped unexpectedly (${code}: ${reason}).`);
40
- this.name = 'SolanaMobileWalletAdapterProtocolSessionClosedError';
41
- }
42
- }
43
- class SolanaMobileWalletAdapterProtocolReauthorizeError extends Error {
44
- constructor() {
45
- super('The auth token provided has gone stale and needs reauthorization.');
46
- this.name = 'SolanaMobileWalletAdapterProtocolReauthorizeError';
7
+ // Typescript `enums` thwart tree-shaking. See https://bargsten.org/jsts/enums/
8
+ const SolanaMobileWalletAdapterErrorCode = {
9
+ ERROR_ASSOCIATION_PORT_OUT_OF_RANGE: 'ERROR_ASSOCIATION_PORT_OUT_OF_RANGE',
10
+ ERROR_FORBIDDEN_WALLET_BASE_URL: 'ERROR_FORBIDDEN_WALLET_BASE_URL',
11
+ ERROR_SECURE_CONTEXT_REQUIRED: 'ERROR_SECURE_CONTEXT_REQUIRED',
12
+ ERROR_SESSION_CLOSED: 'ERROR_SESSION_CLOSED',
13
+ ERROR_WALLET_NOT_FOUND: 'ERROR_WALLET_NOT_FOUND',
14
+ };
15
+ class SolanaMobileWalletAdapterError extends Error {
16
+ constructor(...args) {
17
+ const [code, message, data] = args;
18
+ super(message);
19
+ this.code = code;
20
+ this.data = data;
21
+ this.name = 'SolanaMobileWalletAdapterError';
47
22
  }
48
23
  }
49
24
  // Typescript `enums` thwart tree-shaking. See https://bargsten.org/jsts/enums/
50
- const SolanaMobileWalletAdapterProtocolError = {
51
- ERROR_REAUTHORIZE: -1,
52
- ERROR_AUTHORIZATION_FAILED: -2,
53
- ERROR_INVALID_PAYLOAD: -3,
54
- ERROR_NOT_SIGNED: -4,
55
- ERROR_NOT_COMMITTED: -5,
25
+ const SolanaMobileWalletAdapterProtocolErrorCode = {
26
+ // Keep these in sync with `mobilewalletadapter/common/ProtocolContract.java`.
27
+ ERROR_AUTHORIZATION_FAILED: -1,
28
+ ERROR_INVALID_PAYLOADS: -2,
29
+ ERROR_NOT_SIGNED: -3,
30
+ ERROR_NOT_SUBMITTED: -4,
31
+ ERROR_TOO_MANY_PAYLOADS: -5,
56
32
  ERROR_ATTEST_ORIGIN_ANDROID: -100,
57
33
  };
58
- class SolanaMobileWalletAdapterProtocolJsonRpcError extends Error {
34
+ class SolanaMobileWalletAdapterProtocolError extends Error {
59
35
  constructor(...args) {
60
36
  const [jsonRpcMessageId, code, message, data] = args;
61
37
  super(message);
62
38
  this.code = code;
63
39
  this.data = data;
64
40
  this.jsonRpcMessageId = jsonRpcMessageId;
65
- this.name = 'SolanaNativeWalletAdapterJsonRpcError';
41
+ this.name = 'SolanaMobileWalletAdapterProtocolError';
66
42
  }
67
43
  }
68
44
 
@@ -106,10 +82,36 @@ const SolanaMobileWalletAdapter = reactNative.Platform.OS === 'android' && react
106
82
  : LINKING_ERROR);
107
83
  },
108
84
  });
85
+ function getErrorMessage(e) {
86
+ switch (e.code) {
87
+ case 'ERROR_WALLET_NOT_FOUND':
88
+ return 'Found no installed wallet that supports the mobile wallet protocol.';
89
+ default:
90
+ return e.message;
91
+ }
92
+ }
93
+ function handleError(e) {
94
+ if (e instanceof Error) {
95
+ const reactNativeError = e;
96
+ switch (reactNativeError.code) {
97
+ case undefined:
98
+ throw e;
99
+ case 'JSON_RPC_ERROR': {
100
+ const details = reactNativeError.userInfo;
101
+ throw new SolanaMobileWalletAdapterProtocolError(0 /* jsonRpcMessageId */, details.jsonRpcErrorCode, e.message);
102
+ }
103
+ default:
104
+ throw new SolanaMobileWalletAdapterError(reactNativeError.code, getErrorMessage(reactNativeError), reactNativeError.userInfo);
105
+ }
106
+ }
107
+ throw e;
108
+ }
109
109
  function transact(callback, config) {
110
110
  return __awaiter(this, void 0, void 0, function* () {
111
+ let didSuccessfullyConnect = false;
111
112
  try {
112
113
  yield SolanaMobileWalletAdapter.startSession(config);
114
+ didSuccessfullyConnect = true;
113
115
  const wallet = new Proxy({}, {
114
116
  get(target, p) {
115
117
  if (target[p] == null) {
@@ -123,11 +125,7 @@ function transact(callback, config) {
123
125
  return yield SolanaMobileWalletAdapter.invoke(method, params);
124
126
  }
125
127
  catch (e) {
126
- if (e instanceof Error && e.code === 'JSON_RPC_ERROR') {
127
- const details = e.userInfo;
128
- throw new SolanaMobileWalletAdapterProtocolJsonRpcError(0 /* jsonRpcMessageId */, details.jsonRpcErrorCode, e.message);
129
- }
130
- throw e;
128
+ return handleError(e);
131
129
  }
132
130
  });
133
131
  };
@@ -143,19 +141,19 @@ function transact(callback, config) {
143
141
  });
144
142
  return yield callback(wallet);
145
143
  }
144
+ catch (e) {
145
+ return handleError(e);
146
+ }
146
147
  finally {
147
- yield SolanaMobileWalletAdapter.endSession();
148
+ if (didSuccessfullyConnect) {
149
+ yield SolanaMobileWalletAdapter.endSession();
150
+ }
148
151
  }
149
152
  });
150
153
  }
151
154
 
152
- exports.SolanaMobileWalletAdapterForbiddenWalletBaseURLError = SolanaMobileWalletAdapterForbiddenWalletBaseURLError;
153
- exports.SolanaMobileWalletAdapterProtocolAssociationPortOutOfRangeError = SolanaMobileWalletAdapterProtocolAssociationPortOutOfRangeError;
155
+ exports.SolanaMobileWalletAdapterError = SolanaMobileWalletAdapterError;
156
+ exports.SolanaMobileWalletAdapterErrorCode = SolanaMobileWalletAdapterErrorCode;
154
157
  exports.SolanaMobileWalletAdapterProtocolError = SolanaMobileWalletAdapterProtocolError;
155
- exports.SolanaMobileWalletAdapterProtocolJsonRpcError = SolanaMobileWalletAdapterProtocolJsonRpcError;
156
- exports.SolanaMobileWalletAdapterProtocolReauthorizeError = SolanaMobileWalletAdapterProtocolReauthorizeError;
157
- exports.SolanaMobileWalletAdapterProtocolSessionClosedError = SolanaMobileWalletAdapterProtocolSessionClosedError;
158
- exports.SolanaMobileWalletAdapterProtocolSessionEstablishmentError = SolanaMobileWalletAdapterProtocolSessionEstablishmentError;
159
- exports.SolanaMobileWalletAdapterSecureContextRequiredError = SolanaMobileWalletAdapterSecureContextRequiredError;
160
- exports.SolanaMobileWalletAdapterWalletNotInstalledError = SolanaMobileWalletAdapterWalletNotInstalledError;
158
+ exports.SolanaMobileWalletAdapterProtocolErrorCode = SolanaMobileWalletAdapterProtocolErrorCode;
161
159
  exports.transact = transact;