@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.
- package/.DS_Store +0 -0
- package/.gitignore +2 -0
- package/README.md +69 -69
- package/android/.gitignore +14 -0
- package/android/build.gradle +2 -2
- package/lib/cjs/index.browser.js +249 -184
- package/lib/cjs/index.js +249 -184
- package/lib/cjs/index.native.js +8 -0
- package/lib/esm/index.browser.js +249 -184
- package/lib/esm/index.js +249 -184
- 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 +2 -1
- 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/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
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
return
|
|
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,
|
|
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
|
-
},
|
|
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
|
|
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, [
|
|
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
|
|
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
|
|
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
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
const
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
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
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
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
|
|
943
|
+
const responseBuffer = yield decodeBytes(evt);
|
|
862
944
|
switch (state.__type) {
|
|
863
|
-
case '
|
|
945
|
+
case 'reflector_id_received':
|
|
864
946
|
if (responseBuffer.byteLength !== 0) {
|
|
865
|
-
throw new Error('Encountered unexpected message while
|
|
947
|
+
throw new Error('Encountered unexpected message while awaiting reflection');
|
|
866
948
|
}
|
|
867
949
|
const ecdhKeypair = yield generateECDHKeypair();
|
|
868
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
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,
|
|
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
|
-
|
|
252
|
-
|
|
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
|
-
|
|
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/
|
|
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":""}
|