@solana-mobile/mobile-wallet-adapter-protocol 2.1.5 → 2.1.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.
Files changed (44) hide show
  1. package/.DS_Store +0 -0
  2. package/.gitignore +2 -0
  3. package/README.md +69 -69
  4. package/android/.gitignore +14 -0
  5. package/android/build.gradle +2 -2
  6. package/lib/cjs/index.browser.js +249 -184
  7. package/lib/cjs/index.js +249 -184
  8. package/lib/cjs/index.native.js +8 -0
  9. package/lib/esm/index.browser.js +249 -184
  10. package/lib/esm/index.js +249 -184
  11. package/lib/types/index.browser.d.ts +10 -4
  12. package/lib/types/index.browser.d.ts.map +1 -1
  13. package/lib/types/index.d.ts +10 -4
  14. package/lib/types/index.d.ts.map +1 -1
  15. package/lib/types/index.native.d.ts +10 -4
  16. package/lib/types/index.native.d.ts.map +1 -1
  17. package/package.json +2 -1
  18. package/src/__forks__/react-native/base64Utils.ts +1 -0
  19. package/src/__forks__/react-native/transact.ts +91 -0
  20. package/src/arrayBufferToBase64String.ts +10 -0
  21. package/src/associationPort.ts +19 -0
  22. package/src/base64Utils.ts +22 -0
  23. package/src/codegenSpec/NativeSolanaMobileWalletAdapter.ts +13 -0
  24. package/src/createHelloReq.ts +12 -0
  25. package/src/createMobileWalletProxy.ts +182 -0
  26. package/src/createSIWSMessage.ts +14 -0
  27. package/src/createSequenceNumberVector.ts +11 -0
  28. package/src/encryptedMessage.ts +60 -0
  29. package/src/errors.ts +101 -0
  30. package/src/generateAssociationKeypair.ts +10 -0
  31. package/src/generateECDHKeypair.ts +10 -0
  32. package/src/getAssociateAndroidIntentURL.ts +77 -0
  33. package/src/getJWS.ts +19 -0
  34. package/src/getStringWithURLUnsafeBase64CharactersReplaced.ts +11 -0
  35. package/src/index.ts +3 -0
  36. package/src/jsonRpcMessage.ts +38 -0
  37. package/src/parseHelloRsp.ts +46 -0
  38. package/src/parseSessionProps.ts +33 -0
  39. package/src/reflectorId.ts +31 -0
  40. package/src/startSession.ts +98 -0
  41. package/src/transact.ts +593 -0
  42. package/src/types.ts +201 -0
  43. package/tsconfig.cjs.json +7 -0
  44. package/tsconfig.json +8 -0
package/lib/esm/index.js CHANGED
@@ -10,6 +10,7 @@ const SolanaMobileWalletAdapterErrorCode = {
10
10
  ERROR_SESSION_TIMEOUT: 'ERROR_SESSION_TIMEOUT',
11
11
  ERROR_WALLET_NOT_FOUND: 'ERROR_WALLET_NOT_FOUND',
12
12
  ERROR_INVALID_PROTOCOL_VERSION: 'ERROR_INVALID_PROTOCOL_VERSION',
13
+ ERROR_BROWSER_NOT_SUPPORTED: 'ERROR_BROWSER_NOT_SUPPORTED',
13
14
  };
14
15
  class SolanaMobileWalletAdapterError extends Error {
15
16
  constructor(...args) {
@@ -66,6 +67,27 @@ function __awaiter(thisArg, _arguments, P, generator) {
66
67
  });
67
68
  }
68
69
 
70
+ function encode(input) {
71
+ return window.btoa(input);
72
+ }
73
+ function fromUint8Array(byteArray, urlsafe) {
74
+ const base64 = window.btoa(String.fromCharCode.call(null, ...byteArray));
75
+ if (urlsafe) {
76
+ return base64
77
+ .replace(/\+/g, '-')
78
+ .replace(/\//g, '_')
79
+ .replace(/=+$/, '');
80
+ }
81
+ else
82
+ return base64;
83
+ }
84
+ function toUint8Array(base64EncodedByteArray) {
85
+ return new Uint8Array(window
86
+ .atob(base64EncodedByteArray)
87
+ .split('')
88
+ .map((c) => c.charCodeAt(0)));
89
+ }
90
+
69
91
  function createHelloReq(ecdhPublicKey, associationKeypairPrivateKey) {
70
92
  return __awaiter(this, void 0, void 0, function* () {
71
93
  const publicKeyBuffer = yield crypto.subtle.exportKey('raw', ecdhPublicKey);
@@ -77,10 +99,6 @@ function createHelloReq(ecdhPublicKey, associationKeypairPrivateKey) {
77
99
  });
78
100
  }
79
101
 
80
- function encode(input) {
81
- return window.btoa(input);
82
- }
83
-
84
102
  function createSIWSMessage(payload) {
85
103
  return createSignInMessageText(payload);
86
104
  }
@@ -103,6 +121,13 @@ const SolanaSignInWithSolana = 'solana:signInWithSolana';
103
121
  function createMobileWalletProxy(protocolVersion, protocolRequestHandler) {
104
122
  return new Proxy({}, {
105
123
  get(target, p) {
124
+ // Wrapping a Proxy in a promise results in the Proxy being asked for a 'then' property so must
125
+ // return null if 'then' is called on this proxy to let the 'resolve()' call know this is not a promise.
126
+ // see: https://stackoverflow.com/a/53890904
127
+ //@ts-ignore
128
+ if (p === 'then') {
129
+ return null;
130
+ }
106
131
  if (target[p] == null) {
107
132
  target[p] = function (inputParams) {
108
133
  return __awaiter(this, void 0, void 0, function* () {
@@ -316,66 +341,15 @@ function generateECDHKeypair() {
316
341
  });
317
342
  }
318
343
 
319
- function encryptJsonRpcMessage(jsonRpcMessage, sharedSecret) {
320
- return __awaiter(this, void 0, void 0, function* () {
321
- const plaintext = JSON.stringify(jsonRpcMessage);
322
- const sequenceNumber = jsonRpcMessage.id;
323
- return encryptMessage(plaintext, sequenceNumber, sharedSecret);
324
- });
325
- }
326
- function decryptJsonRpcMessage(message, sharedSecret) {
327
- return __awaiter(this, void 0, void 0, function* () {
328
- const plaintext = yield decryptMessage(message, sharedSecret);
329
- const jsonRpcMessage = JSON.parse(plaintext);
330
- if (Object.hasOwnProperty.call(jsonRpcMessage, 'error')) {
331
- throw new SolanaMobileWalletAdapterProtocolError(jsonRpcMessage.id, jsonRpcMessage.error.code, jsonRpcMessage.error.message);
332
- }
333
- return jsonRpcMessage;
334
- });
335
- }
336
-
337
- function parseHelloRsp(payloadBuffer, // The X9.62-encoded wallet endpoint ephemeral ECDH public keypoint.
338
- associationPublicKey, ecdhPrivateKey) {
339
- return __awaiter(this, void 0, void 0, function* () {
340
- const [associationPublicKeyBuffer, walletPublicKey] = yield Promise.all([
341
- crypto.subtle.exportKey('raw', associationPublicKey),
342
- crypto.subtle.importKey('raw', payloadBuffer.slice(0, ENCODED_PUBLIC_KEY_LENGTH_BYTES), { name: 'ECDH', namedCurve: 'P-256' }, false /* extractable */, [] /* keyUsages */),
343
- ]);
344
- const sharedSecret = yield crypto.subtle.deriveBits({ name: 'ECDH', public: walletPublicKey }, ecdhPrivateKey, 256);
345
- const ecdhSecretKey = yield crypto.subtle.importKey('raw', sharedSecret, 'HKDF', false /* extractable */, ['deriveKey'] /* keyUsages */);
346
- const aesKeyMaterialVal = yield crypto.subtle.deriveKey({
347
- name: 'HKDF',
348
- hash: 'SHA-256',
349
- salt: new Uint8Array(associationPublicKeyBuffer),
350
- info: new Uint8Array(),
351
- }, ecdhSecretKey, { name: 'AES-GCM', length: 128 }, false /* extractable */, ['encrypt', 'decrypt']);
352
- return aesKeyMaterialVal;
353
- });
354
- }
355
-
356
- function parseSessionProps(message, sharedSecret) {
357
- return __awaiter(this, void 0, void 0, function* () {
358
- const plaintext = yield decryptMessage(message, sharedSecret);
359
- const jsonProperties = JSON.parse(plaintext);
360
- let protocolVersion = 'legacy';
361
- if (Object.hasOwnProperty.call(jsonProperties, 'v')) {
362
- switch (jsonProperties.v) {
363
- case 1:
364
- case '1':
365
- case 'v1':
366
- protocolVersion = 'v1';
367
- break;
368
- case 'legacy':
369
- protocolVersion = 'legacy';
370
- break;
371
- default:
372
- throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_INVALID_PROTOCOL_VERSION, `Unknown/unsupported protocol version: ${jsonProperties.v}`);
373
- }
374
- }
375
- return ({
376
- protocol_version: protocolVersion
377
- });
378
- });
344
+ // https://stackoverflow.com/a/9458996/802047
345
+ function arrayBufferToBase64String(buffer) {
346
+ let binary = '';
347
+ const bytes = new Uint8Array(buffer);
348
+ const len = bytes.byteLength;
349
+ for (let ii = 0; ii < len; ii++) {
350
+ binary += String.fromCharCode(bytes[ii]);
351
+ }
352
+ return window.btoa(binary);
379
353
  }
380
354
 
381
355
  function getRandomAssociationPort() {
@@ -388,17 +362,6 @@ function assertAssociationPort(port) {
388
362
  return port;
389
363
  }
390
364
 
391
- // https://stackoverflow.com/a/9458996/802047
392
- function arrayBufferToBase64String(buffer) {
393
- let binary = '';
394
- const bytes = new Uint8Array(buffer);
395
- const len = bytes.byteLength;
396
- for (let ii = 0; ii < len; ii++) {
397
- binary += String.fromCharCode(bytes[ii]);
398
- }
399
- return window.btoa(binary);
400
- }
401
-
402
365
  function getStringWithURLUnsafeCharactersReplaced(unsafeBase64EncodedString) {
403
366
  return unsafeBase64EncodedString.replace(/[/+=]/g, (m) => ({
404
367
  '/': '_',
@@ -407,24 +370,6 @@ function getStringWithURLUnsafeCharactersReplaced(unsafeBase64EncodedString) {
407
370
  }[m]));
408
371
  }
409
372
 
410
- function getRandomReflectorId() {
411
- return assertReflectorId(getRandomInt(0, 9007199254740991)); // 0 < id < 2^53 - 1
412
- }
413
- function getRandomInt(min, max) {
414
- const randomBuffer = new Uint32Array(1);
415
- window.crypto.getRandomValues(randomBuffer);
416
- let randomNumber = randomBuffer[0] / (0xffffffff + 1);
417
- min = Math.ceil(min);
418
- max = Math.floor(max);
419
- return Math.floor(randomNumber * (max - min + 1)) + min;
420
- }
421
- function assertReflectorId(id) {
422
- if (id < 0 || id > 9007199254740991) { // 0 < id < 2^53 - 1
423
- throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_REFLECTOR_ID_OUT_OF_RANGE, `Association port number must be between 49152 and 65535. ${id} given.`, { id });
424
- }
425
- return id;
426
- }
427
-
428
373
  const INTENT_NAME = 'solana-wallet';
429
374
  function getPathParts(pathString) {
430
375
  return (pathString
@@ -466,15 +411,14 @@ function getAssociateAndroidIntentURL(associationPublicKey, putativePort, associ
466
411
  return url;
467
412
  });
468
413
  }
469
- function getRemoteAssociateAndroidIntentURL(associationPublicKey, hostAuthority, putativeId, associationURLBase, protocolVersions = ['v1']) {
414
+ function getRemoteAssociateAndroidIntentURL(associationPublicKey, hostAuthority, reflectorId, associationURLBase, protocolVersions = ['v1']) {
470
415
  return __awaiter(this, void 0, void 0, function* () {
471
- const reflectorId = assertReflectorId(putativeId);
472
416
  const exportedKey = yield crypto.subtle.exportKey('raw', associationPublicKey);
473
417
  const encodedKey = arrayBufferToBase64String(exportedKey);
474
418
  const url = getIntentURL('v1/associate/remote', associationURLBase);
475
419
  url.searchParams.set('association', getStringWithURLUnsafeCharactersReplaced(encodedKey));
476
420
  url.searchParams.set('reflector', `${hostAuthority}`);
477
- url.searchParams.set('id', `${reflectorId}`);
421
+ url.searchParams.set('id', `${fromUint8Array(reflectorId, true)}`);
478
422
  protocolVersions.forEach((version) => {
479
423
  url.searchParams.set('v', version);
480
424
  });
@@ -482,6 +426,68 @@ function getRemoteAssociateAndroidIntentURL(associationPublicKey, hostAuthority,
482
426
  });
483
427
  }
484
428
 
429
+ function encryptJsonRpcMessage(jsonRpcMessage, sharedSecret) {
430
+ return __awaiter(this, void 0, void 0, function* () {
431
+ const plaintext = JSON.stringify(jsonRpcMessage);
432
+ const sequenceNumber = jsonRpcMessage.id;
433
+ return encryptMessage(plaintext, sequenceNumber, sharedSecret);
434
+ });
435
+ }
436
+ function decryptJsonRpcMessage(message, sharedSecret) {
437
+ return __awaiter(this, void 0, void 0, function* () {
438
+ const plaintext = yield decryptMessage(message, sharedSecret);
439
+ const jsonRpcMessage = JSON.parse(plaintext);
440
+ if (Object.hasOwnProperty.call(jsonRpcMessage, 'error')) {
441
+ throw new SolanaMobileWalletAdapterProtocolError(jsonRpcMessage.id, jsonRpcMessage.error.code, jsonRpcMessage.error.message);
442
+ }
443
+ return jsonRpcMessage;
444
+ });
445
+ }
446
+
447
+ function parseHelloRsp(payloadBuffer, // The X9.62-encoded wallet endpoint ephemeral ECDH public keypoint.
448
+ associationPublicKey, ecdhPrivateKey) {
449
+ return __awaiter(this, void 0, void 0, function* () {
450
+ const [associationPublicKeyBuffer, walletPublicKey] = yield Promise.all([
451
+ crypto.subtle.exportKey('raw', associationPublicKey),
452
+ crypto.subtle.importKey('raw', payloadBuffer.slice(0, ENCODED_PUBLIC_KEY_LENGTH_BYTES), { name: 'ECDH', namedCurve: 'P-256' }, false /* extractable */, [] /* keyUsages */),
453
+ ]);
454
+ const sharedSecret = yield crypto.subtle.deriveBits({ name: 'ECDH', public: walletPublicKey }, ecdhPrivateKey, 256);
455
+ const ecdhSecretKey = yield crypto.subtle.importKey('raw', sharedSecret, 'HKDF', false /* extractable */, ['deriveKey'] /* keyUsages */);
456
+ const aesKeyMaterialVal = yield crypto.subtle.deriveKey({
457
+ name: 'HKDF',
458
+ hash: 'SHA-256',
459
+ salt: new Uint8Array(associationPublicKeyBuffer),
460
+ info: new Uint8Array(),
461
+ }, ecdhSecretKey, { name: 'AES-GCM', length: 128 }, false /* extractable */, ['encrypt', 'decrypt']);
462
+ return aesKeyMaterialVal;
463
+ });
464
+ }
465
+
466
+ function parseSessionProps(message, sharedSecret) {
467
+ return __awaiter(this, void 0, void 0, function* () {
468
+ const plaintext = yield decryptMessage(message, sharedSecret);
469
+ const jsonProperties = JSON.parse(plaintext);
470
+ let protocolVersion = 'legacy';
471
+ if (Object.hasOwnProperty.call(jsonProperties, 'v')) {
472
+ switch (jsonProperties.v) {
473
+ case 1:
474
+ case '1':
475
+ case 'v1':
476
+ protocolVersion = 'v1';
477
+ break;
478
+ case 'legacy':
479
+ protocolVersion = 'legacy';
480
+ break;
481
+ default:
482
+ throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_INVALID_PROTOCOL_VERSION, `Unknown/unsupported protocol version: ${jsonProperties.v}`);
483
+ }
484
+ }
485
+ return ({
486
+ protocol_version: protocolVersion
487
+ });
488
+ });
489
+ }
490
+
485
491
  // Typescript `enums` thwart tree-shaking. See https://bargsten.org/jsts/enums/
486
492
  const Browser = {
487
493
  Firefox: 0,
@@ -510,7 +516,7 @@ function getDetectionPromise() {
510
516
  const timeoutId = setTimeout(() => {
511
517
  cleanup();
512
518
  reject();
513
- }, 2000);
519
+ }, 3000);
514
520
  });
515
521
  }
516
522
  let _frame = null;
@@ -565,13 +571,6 @@ function startSession(associationPublicKey, associationURLBase) {
565
571
  return randomAssociationPort;
566
572
  });
567
573
  }
568
- function getRemoteSessionUrl(associationPublicKey, hostAuthority, associationURLBase) {
569
- return __awaiter(this, void 0, void 0, function* () {
570
- const randomReflectorId = getRandomReflectorId();
571
- const associationUrl = yield getRemoteAssociateAndroidIntentURL(associationPublicKey, hostAuthority, randomReflectorId, associationURLBase);
572
- return { associationUrl, reflectorId: randomReflectorId };
573
- });
574
- }
575
574
 
576
575
  const WEBSOCKET_CONNECTION_CONFIG = {
577
576
  /**
@@ -587,7 +586,8 @@ const WEBSOCKET_CONNECTION_CONFIG = {
587
586
  retryDelayScheduleMs: [150, 150, 200, 500, 500, 750, 750, 1000],
588
587
  timeoutMs: 30000,
589
588
  };
590
- const WEBSOCKET_PROTOCOL = 'com.solana.mobilewalletadapter.v1';
589
+ const WEBSOCKET_PROTOCOL_BINARY = 'com.solana.mobilewalletadapter.v1';
590
+ const WEBSOCKET_PROTOCOL_BASE64 = 'com.solana.mobilewalletadapter.v1.base64';
591
591
  function assertSecureContext() {
592
592
  if (typeof window === 'undefined' || window.isSecureContext !== true) {
593
593
  throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SECURE_CONTEXT_REQUIRED, 'The mobile wallet adapter protocol must be used in a secure context (`https`).');
@@ -609,6 +609,20 @@ function getSequenceNumberFromByteArray(byteArray) {
609
609
  const view = new DataView(byteArray);
610
610
  return view.getUint32(0, /* littleEndian */ false);
611
611
  }
612
+ function decodeVarLong(byteArray) {
613
+ var bytes = new Uint8Array(byteArray), l = byteArray.byteLength, limit = 10, value = 0, offset = 0, b;
614
+ do {
615
+ if (offset >= l || offset > limit)
616
+ throw new RangeError('Failed to decode varint');
617
+ b = bytes[offset++];
618
+ value |= (b & 0x7F) << (7 * offset);
619
+ } while (b >= 0x80);
620
+ return { value, offset };
621
+ }
622
+ function getReflectorIdFromByteArray(byteArray) {
623
+ let { value: length, offset } = decodeVarLong(byteArray);
624
+ return new Uint8Array(byteArray.slice(offset, offset + length));
625
+ }
612
626
  function transact(callback, config) {
613
627
  return __awaiter(this, void 0, void 0, function* () {
614
628
  assertSecureContext();
@@ -792,7 +806,7 @@ function transact(callback, config) {
792
806
  if (connectionStartTime === undefined) {
793
807
  connectionStartTime = Date.now();
794
808
  }
795
- socket = new WebSocket(websocketURL, [WEBSOCKET_PROTOCOL]);
809
+ socket = new WebSocket(websocketURL, [WEBSOCKET_PROTOCOL_BINARY]);
796
810
  socket.addEventListener('open', handleOpen);
797
811
  socket.addEventListener('close', handleClose);
798
812
  socket.addEventListener('error', handleError);
@@ -809,12 +823,11 @@ function transact(callback, config) {
809
823
  });
810
824
  });
811
825
  }
812
- function transactRemote(callback, config) {
826
+ function startRemoteScenario(config) {
813
827
  return __awaiter(this, void 0, void 0, function* () {
814
828
  assertSecureContext();
815
829
  const associationKeypair = yield generateAssociationKeypair();
816
- const { associationUrl, reflectorId } = yield getRemoteSessionUrl(associationKeypair.publicKey, config.remoteHostAuthority, config === null || config === void 0 ? void 0 : config.baseUri);
817
- const websocketURL = `wss://${config === null || config === void 0 ? void 0 : config.remoteHostAuthority}/reflect?id=${reflectorId}`;
830
+ const websocketURL = `wss://${config === null || config === void 0 ? void 0 : config.remoteHostAuthority}/reflect`;
818
831
  let connectionStartTime;
819
832
  const getNextRetryDelayMs = (() => {
820
833
  const schedule = [...WEBSOCKET_CONNECTION_CONFIG.retryDelayScheduleMs];
@@ -822,50 +835,125 @@ function transactRemote(callback, config) {
822
835
  })();
823
836
  let nextJsonRpcMessageId = 1;
824
837
  let lastKnownInboundSequenceNumber = 0;
838
+ let encoding;
825
839
  let state = { __type: 'disconnected' };
826
- return { associationUrl, result: new Promise((resolve, reject) => {
827
- let socket;
828
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
829
- const jsonRpcResponsePromises = {};
830
- const handleOpen = () => __awaiter(this, void 0, void 0, function* () {
831
- if (state.__type !== 'connecting') {
832
- console.warn('Expected adapter state to be `connecting` at the moment the websocket opens. ' +
833
- `Got \`${state.__type}\`.`);
834
- return;
835
- }
836
- socket.removeEventListener('open', handleOpen);
837
- });
838
- const handleClose = (evt) => {
839
- if (evt.wasClean) {
840
- state = { __type: 'disconnected' };
841
- }
842
- else {
843
- reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session dropped unexpectedly (${evt.code}: ${evt.reason}).`, { closeEvent: evt }));
840
+ let socket;
841
+ let disposeSocket;
842
+ let decodeBytes = (evt) => __awaiter(this, void 0, void 0, function* () {
843
+ if (encoding == 'base64') { // base64 encoding
844
+ const message = yield evt.data;
845
+ return toUint8Array(message).buffer;
846
+ }
847
+ else {
848
+ return yield evt.data.arrayBuffer();
849
+ }
850
+ });
851
+ // Reflector Connection Phase
852
+ // here we connect to the reflector and wait for the REFLECTOR_ID message
853
+ // so we build the association URL and return that back to the caller
854
+ const associationUrl = yield new Promise((resolve, reject) => {
855
+ const handleOpen = () => __awaiter(this, void 0, void 0, function* () {
856
+ if (state.__type !== 'connecting') {
857
+ console.warn('Expected adapter state to be `connecting` at the moment the websocket opens. ' +
858
+ `Got \`${state.__type}\`.`);
859
+ return;
860
+ }
861
+ if (socket.protocol.includes(WEBSOCKET_PROTOCOL_BASE64)) {
862
+ encoding = 'base64';
863
+ }
864
+ else {
865
+ encoding = 'binary';
866
+ }
867
+ socket.removeEventListener('open', handleOpen);
868
+ });
869
+ const handleClose = (evt) => {
870
+ if (evt.wasClean) {
871
+ state = { __type: 'disconnected' };
872
+ }
873
+ else {
874
+ reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session dropped unexpectedly (${evt.code}: ${evt.reason}).`, { closeEvent: evt }));
875
+ }
876
+ disposeSocket();
877
+ };
878
+ const handleError = (_evt) => __awaiter(this, void 0, void 0, function* () {
879
+ disposeSocket();
880
+ if (Date.now() - connectionStartTime >= WEBSOCKET_CONNECTION_CONFIG.timeoutMs) {
881
+ reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_TIMEOUT, `Failed to connect to the wallet websocket at ${websocketURL}.`));
882
+ }
883
+ else {
884
+ yield new Promise((resolve) => {
885
+ const retryDelayMs = getNextRetryDelayMs();
886
+ retryWaitTimeoutId = window.setTimeout(resolve, retryDelayMs);
887
+ });
888
+ attemptSocketConnection();
889
+ }
890
+ });
891
+ const handleReflectorIdMessage = (evt) => __awaiter(this, void 0, void 0, function* () {
892
+ const responseBuffer = yield decodeBytes(evt);
893
+ if (state.__type === 'connecting') {
894
+ if (responseBuffer.byteLength == 0) {
895
+ throw new Error('Encountered unexpected message while connecting');
844
896
  }
897
+ const reflectorId = getReflectorIdFromByteArray(responseBuffer);
898
+ state = {
899
+ __type: 'reflector_id_received',
900
+ reflectorId: reflectorId
901
+ };
902
+ const associationUrl = yield getRemoteAssociateAndroidIntentURL(associationKeypair.publicKey, config.remoteHostAuthority, reflectorId, config === null || config === void 0 ? void 0 : config.baseUri);
903
+ socket.removeEventListener('message', handleReflectorIdMessage);
904
+ resolve(associationUrl);
905
+ }
906
+ });
907
+ let retryWaitTimeoutId;
908
+ const attemptSocketConnection = () => {
909
+ if (disposeSocket) {
845
910
  disposeSocket();
911
+ }
912
+ state = { __type: 'connecting', associationKeypair };
913
+ if (connectionStartTime === undefined) {
914
+ connectionStartTime = Date.now();
915
+ }
916
+ socket = new WebSocket(websocketURL, [WEBSOCKET_PROTOCOL_BINARY, WEBSOCKET_PROTOCOL_BASE64]);
917
+ socket.addEventListener('open', handleOpen);
918
+ socket.addEventListener('close', handleClose);
919
+ socket.addEventListener('error', handleError);
920
+ socket.addEventListener('message', handleReflectorIdMessage);
921
+ disposeSocket = () => {
922
+ window.clearTimeout(retryWaitTimeoutId);
923
+ socket.removeEventListener('open', handleOpen);
924
+ socket.removeEventListener('close', handleClose);
925
+ socket.removeEventListener('error', handleError);
926
+ socket.removeEventListener('message', handleReflectorIdMessage);
846
927
  };
847
- const handleError = (_evt) => __awaiter(this, void 0, void 0, function* () {
848
- disposeSocket();
849
- if (Date.now() - connectionStartTime >= WEBSOCKET_CONNECTION_CONFIG.timeoutMs) {
850
- reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_TIMEOUT, `Failed to connect to the wallet websocket at ${websocketURL}.`));
851
- }
852
- else {
853
- yield new Promise((resolve) => {
854
- const retryDelayMs = getNextRetryDelayMs();
855
- retryWaitTimeoutId = window.setTimeout(resolve, retryDelayMs);
856
- });
857
- attemptSocketConnection();
858
- }
859
- });
928
+ };
929
+ attemptSocketConnection();
930
+ });
931
+ // Wallet Connection Phase
932
+ // here we return the association URL (containing the reflector ID) to the caller +
933
+ // a promise that will resolve the MobileWallet object once the wallet connects.
934
+ let sessionEstablished = false;
935
+ let handleClose;
936
+ return { associationUrl, close: () => {
937
+ socket.close();
938
+ handleClose();
939
+ }, wallet: new Promise((resolve, reject) => {
940
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
941
+ const jsonRpcResponsePromises = {};
860
942
  const handleMessage = (evt) => __awaiter(this, void 0, void 0, function* () {
861
- const responseBuffer = yield evt.data.arrayBuffer();
943
+ const responseBuffer = yield decodeBytes(evt);
862
944
  switch (state.__type) {
863
- case 'connecting':
945
+ case 'reflector_id_received':
864
946
  if (responseBuffer.byteLength !== 0) {
865
- throw new Error('Encountered unexpected message while connecting');
947
+ throw new Error('Encountered unexpected message while awaiting reflection');
866
948
  }
867
949
  const ecdhKeypair = yield generateECDHKeypair();
868
- socket.send(yield createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
950
+ const binaryMsg = yield createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey);
951
+ if (encoding == 'base64') {
952
+ socket.send(fromUint8Array(binaryMsg));
953
+ }
954
+ else {
955
+ socket.send(binaryMsg);
956
+ }
869
957
  state = {
870
958
  __type: 'hello_req_sent',
871
959
  associationPublicKey: associationKeypair.publicKey,
@@ -912,12 +1000,18 @@ function transactRemote(callback, config) {
912
1000
  state = { __type: 'connected', sharedSecret, sessionProperties };
913
1001
  const wallet = createMobileWalletProxy(sessionProperties.protocol_version, (method, params) => __awaiter(this, void 0, void 0, function* () {
914
1002
  const id = nextJsonRpcMessageId++;
915
- socket.send(yield encryptJsonRpcMessage({
1003
+ const binaryMsg = yield encryptJsonRpcMessage({
916
1004
  id,
917
1005
  jsonrpc: '2.0',
918
1006
  method,
919
1007
  params: params !== null && params !== void 0 ? params : {},
920
- }, sharedSecret));
1008
+ }, sharedSecret);
1009
+ if (encoding == 'base64') {
1010
+ socket.send(fromUint8Array(binaryMsg));
1011
+ }
1012
+ else {
1013
+ socket.send(binaryMsg);
1014
+ }
921
1015
  return new Promise((resolve, reject) => {
922
1016
  jsonRpcResponsePromises[id] = {
923
1017
  resolve(result) {
@@ -943,22 +1037,9 @@ function transactRemote(callback, config) {
943
1037
  };
944
1038
  });
945
1039
  }));
1040
+ sessionEstablished = true;
946
1041
  try {
947
- resolve(yield callback(new Proxy(wallet, {
948
- get(target, p) {
949
- if (p == 'terminateSession') {
950
- return function () {
951
- return __awaiter(this, void 0, void 0, function* () {
952
- disposeSocket();
953
- socket.close();
954
- return;
955
- });
956
- };
957
- }
958
- else
959
- return target[p];
960
- },
961
- })));
1042
+ resolve(wallet);
962
1043
  }
963
1044
  catch (e) {
964
1045
  reject(e);
@@ -967,32 +1048,16 @@ function transactRemote(callback, config) {
967
1048
  }
968
1049
  }
969
1050
  });
970
- let disposeSocket;
971
- let retryWaitTimeoutId;
972
- const attemptSocketConnection = () => {
973
- if (disposeSocket) {
974
- disposeSocket();
975
- }
976
- state = { __type: 'connecting', associationKeypair };
977
- if (connectionStartTime === undefined) {
978
- connectionStartTime = Date.now();
1051
+ socket.addEventListener('message', handleMessage);
1052
+ handleClose = () => {
1053
+ socket.removeEventListener('message', handleMessage);
1054
+ disposeSocket();
1055
+ if (!sessionEstablished) {
1056
+ reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session was closed before connection.`, { closeEvent: new CloseEvent('socket was closed before connection') }));
979
1057
  }
980
- socket = new WebSocket(websocketURL, [WEBSOCKET_PROTOCOL]);
981
- socket.addEventListener('open', handleOpen);
982
- socket.addEventListener('close', handleClose);
983
- socket.addEventListener('error', handleError);
984
- socket.addEventListener('message', handleMessage);
985
- disposeSocket = () => {
986
- window.clearTimeout(retryWaitTimeoutId);
987
- socket.removeEventListener('open', handleOpen);
988
- socket.removeEventListener('close', handleClose);
989
- socket.removeEventListener('error', handleError);
990
- socket.removeEventListener('message', handleMessage);
991
- };
992
1058
  };
993
- attemptSocketConnection();
994
1059
  }) };
995
1060
  });
996
1061
  }
997
1062
 
998
- export { SolanaCloneAuthorization, SolanaMobileWalletAdapterError, SolanaMobileWalletAdapterErrorCode, SolanaMobileWalletAdapterProtocolError, SolanaMobileWalletAdapterProtocolErrorCode, SolanaSignInWithSolana, SolanaSignTransactions, transact, transactRemote };
1063
+ export { SolanaCloneAuthorization, SolanaMobileWalletAdapterError, SolanaMobileWalletAdapterErrorCode, SolanaMobileWalletAdapterProtocolError, SolanaMobileWalletAdapterProtocolErrorCode, SolanaSignInWithSolana, SolanaSignTransactions, startRemoteScenario, transact };
@@ -11,6 +11,7 @@ declare const SolanaMobileWalletAdapterErrorCode: {
11
11
  readonly ERROR_SESSION_TIMEOUT: "ERROR_SESSION_TIMEOUT";
12
12
  readonly ERROR_WALLET_NOT_FOUND: "ERROR_WALLET_NOT_FOUND";
13
13
  readonly ERROR_INVALID_PROTOCOL_VERSION: "ERROR_INVALID_PROTOCOL_VERSION";
14
+ readonly ERROR_BROWSER_NOT_SUPPORTED: "ERROR_BROWSER_NOT_SUPPORTED";
14
15
  };
15
16
  type SolanaMobileWalletAdapterErrorCodeEnum = (typeof SolanaMobileWalletAdapterErrorCode)[keyof typeof SolanaMobileWalletAdapterErrorCode];
16
17
  type ErrorDataTypeMap = {
@@ -28,6 +29,7 @@ type ErrorDataTypeMap = {
28
29
  [SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_TIMEOUT]: undefined;
29
30
  [SolanaMobileWalletAdapterErrorCode.ERROR_WALLET_NOT_FOUND]: undefined;
30
31
  [SolanaMobileWalletAdapterErrorCode.ERROR_INVALID_PROTOCOL_VERSION]: undefined;
32
+ [SolanaMobileWalletAdapterErrorCode.ERROR_BROWSER_NOT_SUPPORTED]: undefined;
31
33
  };
32
34
  declare class SolanaMobileWalletAdapterError<TErrorCode extends SolanaMobileWalletAdapterErrorCodeEnum> extends Error {
33
35
  data: ErrorDataTypeMap[TErrorCode] | undefined;
@@ -248,10 +250,14 @@ type SignInResult = Readonly<{
248
250
  signature: Base64EncodedAddress;
249
251
  signature_type?: string;
250
252
  }>;
251
- declare function transact<TReturn>(callback: (wallet: MobileWallet) => TReturn, config?: WalletAssociationConfig): Promise<TReturn>;
252
- declare function transactRemote<TReturn>(callback: (wallet: RemoteMobileWallet) => TReturn, config: RemoteWalletAssociationConfig): Promise<{
253
+ type Scenario = Readonly<{
254
+ wallet: Promise<MobileWallet>;
255
+ close: () => void;
256
+ }>;
257
+ type RemoteScenario = Scenario & Readonly<{
253
258
  associationUrl: URL;
254
- result: Promise<TReturn>;
255
259
  }>;
256
- export { SolanaMobileWalletAdapterErrorCode, SolanaMobileWalletAdapterError, SolanaMobileWalletAdapterProtocolErrorCode, SolanaMobileWalletAdapterProtocolError, transact, transactRemote, Account, AppIdentity, AssociationKeypair, ProtocolVersion, SessionProperties, AuthorizationResult, AuthToken, Base64EncodedAddress, Base64EncodedTransaction, Cluster, Chain, Finality, WalletAssociationConfig, RemoteWalletAssociationConfig, AuthorizeAPI, CloneAuthorizationAPI, DeauthorizeAPI, GetCapabilitiesAPI, ReauthorizeAPI, SignMessagesAPI, SignTransactionsAPI, SignAndSendTransactionsAPI, MobileWallet, TerminateSessionAPI, RemoteMobileWallet, SolanaSignTransactions, SolanaCloneAuthorization, SolanaSignInWithSolana, SignInPayload, SignInPayloadWithRequiredFields, SignInResult };
260
+ declare function transact<TReturn>(callback: (wallet: MobileWallet) => TReturn, config?: WalletAssociationConfig): Promise<TReturn>;
261
+ declare function startRemoteScenario(config: RemoteWalletAssociationConfig): Promise<RemoteScenario>;
262
+ export { SolanaMobileWalletAdapterErrorCode, SolanaMobileWalletAdapterError, SolanaMobileWalletAdapterProtocolErrorCode, SolanaMobileWalletAdapterProtocolError, transact, startRemoteScenario, Account, AppIdentity, AssociationKeypair, ProtocolVersion, SessionProperties, AuthorizationResult, AuthToken, Base64EncodedAddress, Base64EncodedTransaction, Cluster, Chain, Finality, WalletAssociationConfig, RemoteWalletAssociationConfig, AuthorizeAPI, CloneAuthorizationAPI, DeauthorizeAPI, GetCapabilitiesAPI, ReauthorizeAPI, SignMessagesAPI, SignTransactionsAPI, SignAndSendTransactionsAPI, MobileWallet, TerminateSessionAPI, RemoteMobileWallet, SolanaSignTransactions, SolanaCloneAuthorization, SolanaSignInWithSolana, SignInPayload, SignInPayloadWithRequiredFields, SignInResult, Scenario, RemoteScenario };
257
263
  //# sourceMappingURL=index.browser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.browser.d.ts","sourceRoot":"","sources":["../../src/index.ts","../../src/errors.ts","../../src/createHelloReq.ts","../../src/types.ts","../../src/base64Utils.ts","../../src/createSIWSMessage.ts","../../src/createMobileWalletProxy.ts","../../src/createSequenceNumberVector.ts","../../src/parseHelloRsp.ts","../../src/encryptedMessage.ts","../../src/generateAssociationKeypair.ts","../../src/generateECDHKeypair.ts","../../src/jsonRpcMessage.ts","../../src/parseSessionProps.ts","../../src/associationPort.ts","../../src/arrayBufferToBase64String.ts","../../src/getStringWithURLUnsafeBase64CharactersReplaced.ts","../../src/reflectorId.ts","../../src/getAssociateAndroidIntentURL.ts","../../src/startSession.ts","../../src/transact.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"index.browser.d.ts","sourceRoot":"","sources":["../../src/index.ts","../../src/errors.ts","../../src/base64Utils.ts","../../src/createHelloReq.ts","../../src/types.ts","../../src/createSIWSMessage.ts","../../src/createMobileWalletProxy.ts","../../src/createSequenceNumberVector.ts","../../src/parseHelloRsp.ts","../../src/encryptedMessage.ts","../../src/generateAssociationKeypair.ts","../../src/generateECDHKeypair.ts","../../src/arrayBufferToBase64String.ts","../../src/associationPort.ts","../../src/getStringWithURLUnsafeBase64CharactersReplaced.ts","../../src/getAssociateAndroidIntentURL.ts","../../src/jsonRpcMessage.ts","../../src/parseSessionProps.ts","../../src/startSession.ts","../../src/transact.ts"],"names":[],"mappings":""}