@solana-mobile/mobile-wallet-adapter-protocol 2.2.0-new-arch-beta → 2.2.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.
Files changed (31) hide show
  1. package/README.md +69 -69
  2. package/android/build.gradle +158 -158
  3. package/android/gradle/wrapper/gradle-wrapper.properties +5 -5
  4. package/android/gradle.properties +5 -5
  5. package/android/src/main/java/com/solanamobile/mobilewalletadapter/reactnative/JSONSerializationUtils.kt +11 -9
  6. package/android/src/main/java/com/solanamobile/mobilewalletadapter/reactnative/SolanaMobileWalletAdapterModule.kt +201 -201
  7. package/android/src/main/java/com/solanamobile/mobilewalletadapter/reactnative/SolanaMobileWalletAdapterPackage.kt +35 -35
  8. package/android/src/newarch/SolanaMobileWalletAdapter.kt +7 -7
  9. package/android/src/oldarch/SolanaMobileWalletAdapter.kt +16 -16
  10. package/lib/cjs/index.browser.js +249 -184
  11. package/lib/cjs/index.js +249 -184
  12. package/lib/cjs/index.native.js +8 -0
  13. package/lib/esm/index.browser.js +249 -184
  14. package/lib/esm/index.js +249 -184
  15. package/lib/types/index.browser.d.ts +10 -4
  16. package/lib/types/index.browser.d.ts.map +1 -1
  17. package/lib/types/index.d.ts +10 -4
  18. package/lib/types/index.d.ts.map +1 -1
  19. package/lib/types/index.native.d.ts +10 -4
  20. package/lib/types/index.native.d.ts.map +1 -1
  21. package/package.json +71 -66
  22. package/src/__forks__/react-native/base64Utils.ts +1 -1
  23. package/src/__forks__/react-native/transact.ts +91 -91
  24. package/src/base64Utils.ts +19 -0
  25. package/src/codegenSpec/NativeSolanaMobileWalletAdapter.ts +13 -13
  26. package/src/createMobileWalletProxy.ts +7 -0
  27. package/src/errors.ts +2 -0
  28. package/src/getAssociateAndroidIntentURL.ts +3 -4
  29. package/src/startSession.ts +2 -34
  30. package/src/transact.ts +593 -499
  31. package/src/types.ts +9 -0
package/lib/cjs/index.js CHANGED
@@ -14,6 +14,7 @@ const SolanaMobileWalletAdapterErrorCode = {
14
14
  ERROR_SESSION_TIMEOUT: 'ERROR_SESSION_TIMEOUT',
15
15
  ERROR_WALLET_NOT_FOUND: 'ERROR_WALLET_NOT_FOUND',
16
16
  ERROR_INVALID_PROTOCOL_VERSION: 'ERROR_INVALID_PROTOCOL_VERSION',
17
+ ERROR_BROWSER_NOT_SUPPORTED: 'ERROR_BROWSER_NOT_SUPPORTED',
17
18
  };
18
19
  class SolanaMobileWalletAdapterError extends Error {
19
20
  constructor(...args) {
@@ -70,6 +71,27 @@ function __awaiter(thisArg, _arguments, P, generator) {
70
71
  });
71
72
  }
72
73
 
74
+ function encode(input) {
75
+ return window.btoa(input);
76
+ }
77
+ function fromUint8Array(byteArray, urlsafe) {
78
+ const base64 = window.btoa(String.fromCharCode.call(null, ...byteArray));
79
+ if (urlsafe) {
80
+ return base64
81
+ .replace(/\+/g, '-')
82
+ .replace(/\//g, '_')
83
+ .replace(/=+$/, '');
84
+ }
85
+ else
86
+ return base64;
87
+ }
88
+ function toUint8Array(base64EncodedByteArray) {
89
+ return new Uint8Array(window
90
+ .atob(base64EncodedByteArray)
91
+ .split('')
92
+ .map((c) => c.charCodeAt(0)));
93
+ }
94
+
73
95
  function createHelloReq(ecdhPublicKey, associationKeypairPrivateKey) {
74
96
  return __awaiter(this, void 0, void 0, function* () {
75
97
  const publicKeyBuffer = yield crypto.subtle.exportKey('raw', ecdhPublicKey);
@@ -81,10 +103,6 @@ function createHelloReq(ecdhPublicKey, associationKeypairPrivateKey) {
81
103
  });
82
104
  }
83
105
 
84
- function encode(input) {
85
- return window.btoa(input);
86
- }
87
-
88
106
  function createSIWSMessage(payload) {
89
107
  return walletStandardUtil.createSignInMessageText(payload);
90
108
  }
@@ -107,6 +125,13 @@ const SolanaSignInWithSolana = 'solana:signInWithSolana';
107
125
  function createMobileWalletProxy(protocolVersion, protocolRequestHandler) {
108
126
  return new Proxy({}, {
109
127
  get(target, p) {
128
+ // Wrapping a Proxy in a promise results in the Proxy being asked for a 'then' property so must
129
+ // return null if 'then' is called on this proxy to let the 'resolve()' call know this is not a promise.
130
+ // see: https://stackoverflow.com/a/53890904
131
+ //@ts-ignore
132
+ if (p === 'then') {
133
+ return null;
134
+ }
110
135
  if (target[p] == null) {
111
136
  target[p] = function (inputParams) {
112
137
  return __awaiter(this, void 0, void 0, function* () {
@@ -320,66 +345,15 @@ function generateECDHKeypair() {
320
345
  });
321
346
  }
322
347
 
323
- function encryptJsonRpcMessage(jsonRpcMessage, sharedSecret) {
324
- return __awaiter(this, void 0, void 0, function* () {
325
- const plaintext = JSON.stringify(jsonRpcMessage);
326
- const sequenceNumber = jsonRpcMessage.id;
327
- return encryptMessage(plaintext, sequenceNumber, sharedSecret);
328
- });
329
- }
330
- function decryptJsonRpcMessage(message, sharedSecret) {
331
- return __awaiter(this, void 0, void 0, function* () {
332
- const plaintext = yield decryptMessage(message, sharedSecret);
333
- const jsonRpcMessage = JSON.parse(plaintext);
334
- if (Object.hasOwnProperty.call(jsonRpcMessage, 'error')) {
335
- throw new SolanaMobileWalletAdapterProtocolError(jsonRpcMessage.id, jsonRpcMessage.error.code, jsonRpcMessage.error.message);
336
- }
337
- return jsonRpcMessage;
338
- });
339
- }
340
-
341
- function parseHelloRsp(payloadBuffer, // The X9.62-encoded wallet endpoint ephemeral ECDH public keypoint.
342
- associationPublicKey, ecdhPrivateKey) {
343
- return __awaiter(this, void 0, void 0, function* () {
344
- const [associationPublicKeyBuffer, walletPublicKey] = yield Promise.all([
345
- crypto.subtle.exportKey('raw', associationPublicKey),
346
- crypto.subtle.importKey('raw', payloadBuffer.slice(0, ENCODED_PUBLIC_KEY_LENGTH_BYTES), { name: 'ECDH', namedCurve: 'P-256' }, false /* extractable */, [] /* keyUsages */),
347
- ]);
348
- const sharedSecret = yield crypto.subtle.deriveBits({ name: 'ECDH', public: walletPublicKey }, ecdhPrivateKey, 256);
349
- const ecdhSecretKey = yield crypto.subtle.importKey('raw', sharedSecret, 'HKDF', false /* extractable */, ['deriveKey'] /* keyUsages */);
350
- const aesKeyMaterialVal = yield crypto.subtle.deriveKey({
351
- name: 'HKDF',
352
- hash: 'SHA-256',
353
- salt: new Uint8Array(associationPublicKeyBuffer),
354
- info: new Uint8Array(),
355
- }, ecdhSecretKey, { name: 'AES-GCM', length: 128 }, false /* extractable */, ['encrypt', 'decrypt']);
356
- return aesKeyMaterialVal;
357
- });
358
- }
359
-
360
- function parseSessionProps(message, sharedSecret) {
361
- return __awaiter(this, void 0, void 0, function* () {
362
- const plaintext = yield decryptMessage(message, sharedSecret);
363
- const jsonProperties = JSON.parse(plaintext);
364
- let protocolVersion = 'legacy';
365
- if (Object.hasOwnProperty.call(jsonProperties, 'v')) {
366
- switch (jsonProperties.v) {
367
- case 1:
368
- case '1':
369
- case 'v1':
370
- protocolVersion = 'v1';
371
- break;
372
- case 'legacy':
373
- protocolVersion = 'legacy';
374
- break;
375
- default:
376
- throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_INVALID_PROTOCOL_VERSION, `Unknown/unsupported protocol version: ${jsonProperties.v}`);
377
- }
378
- }
379
- return ({
380
- protocol_version: protocolVersion
381
- });
382
- });
348
+ // https://stackoverflow.com/a/9458996/802047
349
+ function arrayBufferToBase64String(buffer) {
350
+ let binary = '';
351
+ const bytes = new Uint8Array(buffer);
352
+ const len = bytes.byteLength;
353
+ for (let ii = 0; ii < len; ii++) {
354
+ binary += String.fromCharCode(bytes[ii]);
355
+ }
356
+ return window.btoa(binary);
383
357
  }
384
358
 
385
359
  function getRandomAssociationPort() {
@@ -392,17 +366,6 @@ function assertAssociationPort(port) {
392
366
  return port;
393
367
  }
394
368
 
395
- // https://stackoverflow.com/a/9458996/802047
396
- function arrayBufferToBase64String(buffer) {
397
- let binary = '';
398
- const bytes = new Uint8Array(buffer);
399
- const len = bytes.byteLength;
400
- for (let ii = 0; ii < len; ii++) {
401
- binary += String.fromCharCode(bytes[ii]);
402
- }
403
- return window.btoa(binary);
404
- }
405
-
406
369
  function getStringWithURLUnsafeCharactersReplaced(unsafeBase64EncodedString) {
407
370
  return unsafeBase64EncodedString.replace(/[/+=]/g, (m) => ({
408
371
  '/': '_',
@@ -411,24 +374,6 @@ function getStringWithURLUnsafeCharactersReplaced(unsafeBase64EncodedString) {
411
374
  }[m]));
412
375
  }
413
376
 
414
- function getRandomReflectorId() {
415
- return assertReflectorId(getRandomInt(0, 9007199254740991)); // 0 < id < 2^53 - 1
416
- }
417
- function getRandomInt(min, max) {
418
- const randomBuffer = new Uint32Array(1);
419
- window.crypto.getRandomValues(randomBuffer);
420
- let randomNumber = randomBuffer[0] / (0xffffffff + 1);
421
- min = Math.ceil(min);
422
- max = Math.floor(max);
423
- return Math.floor(randomNumber * (max - min + 1)) + min;
424
- }
425
- function assertReflectorId(id) {
426
- if (id < 0 || id > 9007199254740991) { // 0 < id < 2^53 - 1
427
- throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_REFLECTOR_ID_OUT_OF_RANGE, `Association port number must be between 49152 and 65535. ${id} given.`, { id });
428
- }
429
- return id;
430
- }
431
-
432
377
  const INTENT_NAME = 'solana-wallet';
433
378
  function getPathParts(pathString) {
434
379
  return (pathString
@@ -470,15 +415,14 @@ function getAssociateAndroidIntentURL(associationPublicKey, putativePort, associ
470
415
  return url;
471
416
  });
472
417
  }
473
- function getRemoteAssociateAndroidIntentURL(associationPublicKey, hostAuthority, putativeId, associationURLBase, protocolVersions = ['v1']) {
418
+ function getRemoteAssociateAndroidIntentURL(associationPublicKey, hostAuthority, reflectorId, associationURLBase, protocolVersions = ['v1']) {
474
419
  return __awaiter(this, void 0, void 0, function* () {
475
- const reflectorId = assertReflectorId(putativeId);
476
420
  const exportedKey = yield crypto.subtle.exportKey('raw', associationPublicKey);
477
421
  const encodedKey = arrayBufferToBase64String(exportedKey);
478
422
  const url = getIntentURL('v1/associate/remote', associationURLBase);
479
423
  url.searchParams.set('association', getStringWithURLUnsafeCharactersReplaced(encodedKey));
480
424
  url.searchParams.set('reflector', `${hostAuthority}`);
481
- url.searchParams.set('id', `${reflectorId}`);
425
+ url.searchParams.set('id', `${fromUint8Array(reflectorId, true)}`);
482
426
  protocolVersions.forEach((version) => {
483
427
  url.searchParams.set('v', version);
484
428
  });
@@ -486,6 +430,68 @@ function getRemoteAssociateAndroidIntentURL(associationPublicKey, hostAuthority,
486
430
  });
487
431
  }
488
432
 
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
+ });
439
+ }
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
+ });
449
+ }
450
+
451
+ function parseHelloRsp(payloadBuffer, // The X9.62-encoded wallet endpoint ephemeral ECDH public keypoint.
452
+ 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
+ });
468
+ }
469
+
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
+ }
488
+ }
489
+ return ({
490
+ protocol_version: protocolVersion
491
+ });
492
+ });
493
+ }
494
+
489
495
  // Typescript `enums` thwart tree-shaking. See https://bargsten.org/jsts/enums/
490
496
  const Browser = {
491
497
  Firefox: 0,
@@ -514,7 +520,7 @@ function getDetectionPromise() {
514
520
  const timeoutId = setTimeout(() => {
515
521
  cleanup();
516
522
  reject();
517
- }, 2000);
523
+ }, 3000);
518
524
  });
519
525
  }
520
526
  let _frame = null;
@@ -569,13 +575,6 @@ function startSession(associationPublicKey, associationURLBase) {
569
575
  return randomAssociationPort;
570
576
  });
571
577
  }
572
- function getRemoteSessionUrl(associationPublicKey, hostAuthority, associationURLBase) {
573
- return __awaiter(this, void 0, void 0, function* () {
574
- const randomReflectorId = getRandomReflectorId();
575
- const associationUrl = yield getRemoteAssociateAndroidIntentURL(associationPublicKey, hostAuthority, randomReflectorId, associationURLBase);
576
- return { associationUrl, reflectorId: randomReflectorId };
577
- });
578
- }
579
578
 
580
579
  const WEBSOCKET_CONNECTION_CONFIG = {
581
580
  /**
@@ -591,7 +590,8 @@ const WEBSOCKET_CONNECTION_CONFIG = {
591
590
  retryDelayScheduleMs: [150, 150, 200, 500, 500, 750, 750, 1000],
592
591
  timeoutMs: 30000,
593
592
  };
594
- const WEBSOCKET_PROTOCOL = 'com.solana.mobilewalletadapter.v1';
593
+ const WEBSOCKET_PROTOCOL_BINARY = 'com.solana.mobilewalletadapter.v1';
594
+ const WEBSOCKET_PROTOCOL_BASE64 = 'com.solana.mobilewalletadapter.v1.base64';
595
595
  function assertSecureContext() {
596
596
  if (typeof window === 'undefined' || window.isSecureContext !== true) {
597
597
  throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SECURE_CONTEXT_REQUIRED, 'The mobile wallet adapter protocol must be used in a secure context (`https`).');
@@ -613,6 +613,20 @@ function getSequenceNumberFromByteArray(byteArray) {
613
613
  const view = new DataView(byteArray);
614
614
  return view.getUint32(0, /* littleEndian */ false);
615
615
  }
616
+ function decodeVarLong(byteArray) {
617
+ var bytes = new Uint8Array(byteArray), l = byteArray.byteLength, limit = 10, value = 0, offset = 0, b;
618
+ do {
619
+ if (offset >= l || offset > limit)
620
+ throw new RangeError('Failed to decode varint');
621
+ b = bytes[offset++];
622
+ value |= (b & 0x7F) << (7 * offset);
623
+ } while (b >= 0x80);
624
+ return { value, offset };
625
+ }
626
+ function getReflectorIdFromByteArray(byteArray) {
627
+ let { value: length, offset } = decodeVarLong(byteArray);
628
+ return new Uint8Array(byteArray.slice(offset, offset + length));
629
+ }
616
630
  function transact(callback, config) {
617
631
  return __awaiter(this, void 0, void 0, function* () {
618
632
  assertSecureContext();
@@ -796,7 +810,7 @@ function transact(callback, config) {
796
810
  if (connectionStartTime === undefined) {
797
811
  connectionStartTime = Date.now();
798
812
  }
799
- socket = new WebSocket(websocketURL, [WEBSOCKET_PROTOCOL]);
813
+ socket = new WebSocket(websocketURL, [WEBSOCKET_PROTOCOL_BINARY]);
800
814
  socket.addEventListener('open', handleOpen);
801
815
  socket.addEventListener('close', handleClose);
802
816
  socket.addEventListener('error', handleError);
@@ -813,12 +827,11 @@ function transact(callback, config) {
813
827
  });
814
828
  });
815
829
  }
816
- function transactRemote(callback, config) {
830
+ function startRemoteScenario(config) {
817
831
  return __awaiter(this, void 0, void 0, function* () {
818
832
  assertSecureContext();
819
833
  const associationKeypair = yield generateAssociationKeypair();
820
- const { associationUrl, reflectorId } = yield getRemoteSessionUrl(associationKeypair.publicKey, config.remoteHostAuthority, config === null || config === void 0 ? void 0 : config.baseUri);
821
- const websocketURL = `wss://${config === null || config === void 0 ? void 0 : config.remoteHostAuthority}/reflect?id=${reflectorId}`;
834
+ const websocketURL = `wss://${config === null || config === void 0 ? void 0 : config.remoteHostAuthority}/reflect`;
822
835
  let connectionStartTime;
823
836
  const getNextRetryDelayMs = (() => {
824
837
  const schedule = [...WEBSOCKET_CONNECTION_CONFIG.retryDelayScheduleMs];
@@ -826,50 +839,125 @@ function transactRemote(callback, config) {
826
839
  })();
827
840
  let nextJsonRpcMessageId = 1;
828
841
  let lastKnownInboundSequenceNumber = 0;
842
+ let encoding;
829
843
  let state = { __type: 'disconnected' };
830
- return { associationUrl, result: new Promise((resolve, reject) => {
831
- let socket;
832
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
833
- const jsonRpcResponsePromises = {};
834
- const handleOpen = () => __awaiter(this, void 0, void 0, function* () {
835
- if (state.__type !== 'connecting') {
836
- console.warn('Expected adapter state to be `connecting` at the moment the websocket opens. ' +
837
- `Got \`${state.__type}\`.`);
838
- return;
839
- }
840
- socket.removeEventListener('open', handleOpen);
841
- });
842
- const handleClose = (evt) => {
843
- if (evt.wasClean) {
844
- state = { __type: 'disconnected' };
845
- }
846
- else {
847
- reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session dropped unexpectedly (${evt.code}: ${evt.reason}).`, { closeEvent: evt }));
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;
850
+ }
851
+ else {
852
+ return yield evt.data.arrayBuffer();
853
+ }
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');
848
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) {
849
914
  disposeSocket();
915
+ }
916
+ state = { __type: 'connecting', associationKeypair };
917
+ if (connectionStartTime === undefined) {
918
+ connectionStartTime = Date.now();
919
+ }
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);
850
931
  };
851
- const handleError = (_evt) => __awaiter(this, void 0, void 0, function* () {
852
- disposeSocket();
853
- if (Date.now() - connectionStartTime >= WEBSOCKET_CONNECTION_CONFIG.timeoutMs) {
854
- reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_TIMEOUT, `Failed to connect to the wallet websocket at ${websocketURL}.`));
855
- }
856
- else {
857
- yield new Promise((resolve) => {
858
- const retryDelayMs = getNextRetryDelayMs();
859
- retryWaitTimeoutId = window.setTimeout(resolve, retryDelayMs);
860
- });
861
- attemptSocketConnection();
862
- }
863
- });
932
+ };
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 = {};
864
946
  const handleMessage = (evt) => __awaiter(this, void 0, void 0, function* () {
865
- const responseBuffer = yield evt.data.arrayBuffer();
947
+ const responseBuffer = yield decodeBytes(evt);
866
948
  switch (state.__type) {
867
- case 'connecting':
949
+ case 'reflector_id_received':
868
950
  if (responseBuffer.byteLength !== 0) {
869
- throw new Error('Encountered unexpected message while connecting');
951
+ throw new Error('Encountered unexpected message while awaiting reflection');
870
952
  }
871
953
  const ecdhKeypair = yield generateECDHKeypair();
872
- socket.send(yield createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
954
+ const binaryMsg = yield createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey);
955
+ if (encoding == 'base64') {
956
+ socket.send(fromUint8Array(binaryMsg));
957
+ }
958
+ else {
959
+ socket.send(binaryMsg);
960
+ }
873
961
  state = {
874
962
  __type: 'hello_req_sent',
875
963
  associationPublicKey: associationKeypair.publicKey,
@@ -916,12 +1004,18 @@ function transactRemote(callback, config) {
916
1004
  state = { __type: 'connected', sharedSecret, sessionProperties };
917
1005
  const wallet = createMobileWalletProxy(sessionProperties.protocol_version, (method, params) => __awaiter(this, void 0, void 0, function* () {
918
1006
  const id = nextJsonRpcMessageId++;
919
- socket.send(yield encryptJsonRpcMessage({
1007
+ const binaryMsg = yield encryptJsonRpcMessage({
920
1008
  id,
921
1009
  jsonrpc: '2.0',
922
1010
  method,
923
1011
  params: params !== null && params !== void 0 ? params : {},
924
- }, sharedSecret));
1012
+ }, sharedSecret);
1013
+ if (encoding == 'base64') {
1014
+ socket.send(fromUint8Array(binaryMsg));
1015
+ }
1016
+ else {
1017
+ socket.send(binaryMsg);
1018
+ }
925
1019
  return new Promise((resolve, reject) => {
926
1020
  jsonRpcResponsePromises[id] = {
927
1021
  resolve(result) {
@@ -947,22 +1041,9 @@ function transactRemote(callback, config) {
947
1041
  };
948
1042
  });
949
1043
  }));
1044
+ sessionEstablished = true;
950
1045
  try {
951
- resolve(yield callback(new Proxy(wallet, {
952
- get(target, p) {
953
- if (p == 'terminateSession') {
954
- return function () {
955
- return __awaiter(this, void 0, void 0, function* () {
956
- disposeSocket();
957
- socket.close();
958
- return;
959
- });
960
- };
961
- }
962
- else
963
- return target[p];
964
- },
965
- })));
1046
+ resolve(wallet);
966
1047
  }
967
1048
  catch (e) {
968
1049
  reject(e);
@@ -971,30 +1052,14 @@ function transactRemote(callback, config) {
971
1052
  }
972
1053
  }
973
1054
  });
974
- let disposeSocket;
975
- let retryWaitTimeoutId;
976
- const attemptSocketConnection = () => {
977
- if (disposeSocket) {
978
- disposeSocket();
979
- }
980
- state = { __type: 'connecting', associationKeypair };
981
- if (connectionStartTime === undefined) {
982
- connectionStartTime = Date.now();
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') }));
983
1061
  }
984
- socket = new WebSocket(websocketURL, [WEBSOCKET_PROTOCOL]);
985
- socket.addEventListener('open', handleOpen);
986
- socket.addEventListener('close', handleClose);
987
- socket.addEventListener('error', handleError);
988
- socket.addEventListener('message', handleMessage);
989
- disposeSocket = () => {
990
- window.clearTimeout(retryWaitTimeoutId);
991
- socket.removeEventListener('open', handleOpen);
992
- socket.removeEventListener('close', handleClose);
993
- socket.removeEventListener('error', handleError);
994
- socket.removeEventListener('message', handleMessage);
995
- };
996
1062
  };
997
- attemptSocketConnection();
998
1063
  }) };
999
1064
  });
1000
1065
  }
@@ -1006,5 +1071,5 @@ exports.SolanaMobileWalletAdapterProtocolError = SolanaMobileWalletAdapterProtoc
1006
1071
  exports.SolanaMobileWalletAdapterProtocolErrorCode = SolanaMobileWalletAdapterProtocolErrorCode;
1007
1072
  exports.SolanaSignInWithSolana = SolanaSignInWithSolana;
1008
1073
  exports.SolanaSignTransactions = SolanaSignTransactions;
1074
+ exports.startRemoteScenario = startRemoteScenario;
1009
1075
  exports.transact = transact;
1010
- exports.transactRemote = transactRemote;
@@ -16,6 +16,7 @@ const SolanaMobileWalletAdapterErrorCode = {
16
16
  ERROR_SESSION_TIMEOUT: 'ERROR_SESSION_TIMEOUT',
17
17
  ERROR_WALLET_NOT_FOUND: 'ERROR_WALLET_NOT_FOUND',
18
18
  ERROR_INVALID_PROTOCOL_VERSION: 'ERROR_INVALID_PROTOCOL_VERSION',
19
+ ERROR_BROWSER_NOT_SUPPORTED: 'ERROR_BROWSER_NOT_SUPPORTED',
19
20
  };
20
21
  class SolanaMobileWalletAdapterError extends Error {
21
22
  constructor(...args) {
@@ -96,6 +97,13 @@ const SolanaSignInWithSolana = 'solana:signInWithSolana';
96
97
  function createMobileWalletProxy(protocolVersion, protocolRequestHandler) {
97
98
  return new Proxy({}, {
98
99
  get(target, p) {
100
+ // Wrapping a Proxy in a promise results in the Proxy being asked for a 'then' property so must
101
+ // return null if 'then' is called on this proxy to let the 'resolve()' call know this is not a promise.
102
+ // see: https://stackoverflow.com/a/53890904
103
+ //@ts-ignore
104
+ if (p === 'then') {
105
+ return null;
106
+ }
99
107
  if (target[p] == null) {
100
108
  target[p] = function (inputParams) {
101
109
  return __awaiter(this, void 0, void 0, function* () {