@solana-mobile/mobile-wallet-adapter-protocol 2.1.4 → 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.
- package/.DS_Store +0 -0
- package/.gitignore +2 -0
- package/README.md +69 -69
- package/android/.gitignore +14 -0
- package/android/build.gradle +158 -146
- package/android/gradle/wrapper/gradle-wrapper.properties +5 -5
- package/android/gradle.properties +5 -5
- package/android/src/main/java/com/solanamobile/mobilewalletadapter/reactnative/JSONSerializationUtils.kt +11 -9
- package/android/src/main/java/com/solanamobile/mobilewalletadapter/reactnative/SolanaMobileWalletAdapterModule.kt +201 -178
- package/android/src/main/java/com/solanamobile/mobilewalletadapter/reactnative/SolanaMobileWalletAdapterPackage.kt +26 -7
- package/android/src/newarch/SolanaMobileWalletAdapter.kt +7 -0
- package/android/src/oldarch/SolanaMobileWalletAdapter.kt +16 -0
- package/lib/cjs/index.browser.js +249 -180
- package/lib/cjs/index.js +249 -180
- package/lib/cjs/index.native.js +12 -2
- package/lib/esm/index.browser.js +249 -180
- package/lib/esm/index.js +249 -180
- package/lib/types/index.browser.d.ts +10 -4
- package/lib/types/index.browser.d.ts.map +1 -1
- package/lib/types/index.d.ts +10 -4
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.native.d.ts +10 -4
- package/lib/types/index.native.d.ts.map +1 -1
- package/package.json +71 -58
- package/src/__forks__/react-native/base64Utils.ts +1 -0
- package/src/__forks__/react-native/transact.ts +91 -0
- package/src/arrayBufferToBase64String.ts +10 -0
- package/src/associationPort.ts +19 -0
- package/src/base64Utils.ts +22 -0
- package/src/codegenSpec/NativeSolanaMobileWalletAdapter.ts +13 -0
- package/src/createHelloReq.ts +12 -0
- package/src/createMobileWalletProxy.ts +182 -0
- package/src/createSIWSMessage.ts +14 -0
- package/src/createSequenceNumberVector.ts +11 -0
- package/src/encryptedMessage.ts +60 -0
- package/src/errors.ts +101 -0
- package/src/generateAssociationKeypair.ts +10 -0
- package/src/generateECDHKeypair.ts +10 -0
- package/src/getAssociateAndroidIntentURL.ts +77 -0
- package/src/getJWS.ts +19 -0
- package/src/getStringWithURLUnsafeBase64CharactersReplaced.ts +11 -0
- package/src/index.ts +3 -0
- package/src/jsonRpcMessage.ts +38 -0
- package/src/parseHelloRsp.ts +46 -0
- package/src/parseSessionProps.ts +33 -0
- package/src/reflectorId.ts +31 -0
- package/src/startSession.ts +98 -0
- package/src/transact.ts +593 -0
- package/src/types.ts +201 -0
- package/tsconfig.cjs.json +7 -0
- package/tsconfig.json +8 -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
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
return
|
|
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,
|
|
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
|
-
},
|
|
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
|
|
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, [
|
|
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
|
|
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
|
|
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
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
const
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
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
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
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
|
|
947
|
+
const responseBuffer = yield decodeBytes(evt);
|
|
866
948
|
switch (state.__type) {
|
|
867
|
-
case '
|
|
949
|
+
case 'reflector_id_received':
|
|
868
950
|
if (responseBuffer.byteLength !== 0) {
|
|
869
|
-
throw new Error('Encountered unexpected message while
|
|
951
|
+
throw new Error('Encountered unexpected message while awaiting reflection');
|
|
870
952
|
}
|
|
871
953
|
const ecdhKeypair = yield generateECDHKeypair();
|
|
872
|
-
|
|
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
|
-
|
|
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,18 +1041,9 @@ function transactRemote(callback, config) {
|
|
|
947
1041
|
};
|
|
948
1042
|
});
|
|
949
1043
|
}));
|
|
1044
|
+
sessionEstablished = true;
|
|
950
1045
|
try {
|
|
951
|
-
resolve(
|
|
952
|
-
get(target, p) {
|
|
953
|
-
if (p === 'terminateSession') {
|
|
954
|
-
disposeSocket();
|
|
955
|
-
socket.close();
|
|
956
|
-
return;
|
|
957
|
-
}
|
|
958
|
-
else
|
|
959
|
-
return target[p];
|
|
960
|
-
},
|
|
961
|
-
})));
|
|
1046
|
+
resolve(wallet);
|
|
962
1047
|
}
|
|
963
1048
|
catch (e) {
|
|
964
1049
|
reject(e);
|
|
@@ -967,30 +1052,14 @@ function transactRemote(callback, config) {
|
|
|
967
1052
|
}
|
|
968
1053
|
}
|
|
969
1054
|
});
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
state = { __type: 'connecting', associationKeypair };
|
|
977
|
-
if (connectionStartTime === undefined) {
|
|
978
|
-
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') }));
|
|
979
1061
|
}
|
|
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
1062
|
};
|
|
993
|
-
attemptSocketConnection();
|
|
994
1063
|
}) };
|
|
995
1064
|
});
|
|
996
1065
|
}
|
|
@@ -1002,5 +1071,5 @@ exports.SolanaMobileWalletAdapterProtocolError = SolanaMobileWalletAdapterProtoc
|
|
|
1002
1071
|
exports.SolanaMobileWalletAdapterProtocolErrorCode = SolanaMobileWalletAdapterProtocolErrorCode;
|
|
1003
1072
|
exports.SolanaSignInWithSolana = SolanaSignInWithSolana;
|
|
1004
1073
|
exports.SolanaSignTransactions = SolanaSignTransactions;
|
|
1074
|
+
exports.startRemoteScenario = startRemoteScenario;
|
|
1005
1075
|
exports.transact = transact;
|
|
1006
|
-
exports.transactRemote = transactRemote;
|
package/lib/cjs/index.native.js
CHANGED
|
@@ -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) {
|
|
@@ -72,6 +73,8 @@ function __awaiter(thisArg, _arguments, P, generator) {
|
|
|
72
73
|
});
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
var NativeSolanaMobileWalletAdapter = reactNative.TurboModuleRegistry.getEnforcing('SolanaMobileWalletAdapter');
|
|
77
|
+
|
|
75
78
|
function createSIWSMessage(payload) {
|
|
76
79
|
return walletStandardUtil.createSignInMessageText(payload);
|
|
77
80
|
}
|
|
@@ -94,6 +97,13 @@ const SolanaSignInWithSolana = 'solana:signInWithSolana';
|
|
|
94
97
|
function createMobileWalletProxy(protocolVersion, protocolRequestHandler) {
|
|
95
98
|
return new Proxy({}, {
|
|
96
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
|
+
}
|
|
97
107
|
if (target[p] == null) {
|
|
98
108
|
target[p] = function (inputParams) {
|
|
99
109
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -243,8 +253,8 @@ const LINKING_ERROR = `The package 'solana-mobile-wallet-adapter-protocol' doesn
|
|
|
243
253
|
' - You have added `@solana-mobile/mobile-wallet-adapter-protocol` as an explicit dependency, and\n' +
|
|
244
254
|
' - You have added `@solana-mobile/mobile-wallet-adapter-protocol` to the `nohoist` section of your package.json\n' +
|
|
245
255
|
'- You are not using Expo managed workflow\n';
|
|
246
|
-
const SolanaMobileWalletAdapter = reactNative.Platform.OS === 'android' &&
|
|
247
|
-
?
|
|
256
|
+
const SolanaMobileWalletAdapter = reactNative.Platform.OS === 'android' && NativeSolanaMobileWalletAdapter
|
|
257
|
+
? NativeSolanaMobileWalletAdapter
|
|
248
258
|
: new Proxy({}, {
|
|
249
259
|
get() {
|
|
250
260
|
throw new Error(reactNative.Platform.OS !== 'android'
|