@pooflabs/web 0.0.76 → 0.0.77
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/README.md +32 -0
- package/dist/{index-BilNgDSc.js → index-Bfk81d8_.js} +883 -25
- package/dist/index-Bfk81d8_.js.map +1 -0
- package/dist/{index-C5Cnn7Hq.esm.js → index-CFh1x-M0.esm.js} +353 -156
- package/dist/index-CFh1x-M0.esm.js.map +1 -0
- package/dist/index-CdOv7Nw2.esm.js +6 -0
- package/dist/index-CdOv7Nw2.esm.js.map +1 -0
- package/dist/{index-DeVykeX4.esm.js → index-CeQ8hE3s.esm.js} +881 -24
- package/dist/index-CeQ8hE3s.esm.js.map +1 -0
- package/dist/index-DDXzCx2W.js +8 -0
- package/dist/index-DDXzCx2W.js.map +1 -0
- package/dist/{index-BrlvWT1Q.js → index-DQVpAl5t.js} +353 -156
- package/dist/index-DQVpAl5t.js.map +1 -0
- package/dist/{index-COMIXUxl.js → index-KUU0aVzP.js} +882 -24
- package/dist/index-KUU0aVzP.js.map +1 -0
- package/dist/{index-Dsh0H37n.esm.js → index-R7t9pRt_.esm.js} +882 -23
- package/dist/index-R7t9pRt_.esm.js.map +1 -0
- package/dist/{index.browser-CSTWylhG.esm.js → index.browser-BE44CEaJ.esm.js} +3 -3
- package/dist/{index.browser-CSTWylhG.esm.js.map → index.browser-BE44CEaJ.esm.js.map} +1 -1
- package/dist/{index.browser-DZjyUgtx.esm.js → index.browser-C-_FEr5M.esm.js} +449 -479
- package/dist/index.browser-C-_FEr5M.esm.js.map +1 -0
- package/dist/index.browser-C9bFQZyQ.esm.js +1373 -0
- package/dist/index.browser-C9bFQZyQ.esm.js.map +1 -0
- package/dist/index.browser-Dbq5Qf1G.esm.js +242 -0
- package/dist/index.browser-Dbq5Qf1G.esm.js.map +1 -0
- package/dist/index.browser-Df7yN8D5.js +245 -0
- package/dist/index.browser-Df7yN8D5.js.map +1 -0
- package/dist/{index.browser-CMO2pjaF.js → index.browser-Di1_YZpi.js} +3 -3
- package/dist/{index.browser-CMO2pjaF.js.map → index.browser-Di1_YZpi.js.map} +1 -1
- package/dist/{index.browser-BOJRGZWX.js → index.browser-DjEZSiqI.js} +449 -479
- package/dist/index.browser-DjEZSiqI.js.map +1 -0
- package/dist/index.browser-dszs5oe5.js +1376 -0
- package/dist/index.browser-dszs5oe5.js.map +1 -0
- package/dist/index.esm.js +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/{index.native-CkUXrvPQ.js → index.native-CKd2b3_F.js} +2756 -188
- package/dist/index.native-CKd2b3_F.js.map +1 -0
- package/dist/{index.native-H-fEcP_L.esm.js → index.native-DAyMVhFq.esm.js} +2755 -189
- package/dist/index.native-DAyMVhFq.esm.js.map +1 -0
- package/dist/index.native.esm.js +1 -1
- package/dist/index.native.js +2 -1
- package/dist/index.native.js.map +1 -1
- package/dist/{phantom-wallet-provider-DrNrxSUL.js → phantom-wallet-provider-DJ6qf2VB.js} +4 -4
- package/dist/{phantom-wallet-provider-DrNrxSUL.js.map → phantom-wallet-provider-DJ6qf2VB.js.map} +1 -1
- package/dist/{phantom-wallet-provider-5IQi4ihD.esm.js → phantom-wallet-provider-DQ0uhl2v.esm.js} +4 -4
- package/dist/{phantom-wallet-provider-5IQi4ihD.esm.js.map → phantom-wallet-provider-DQ0uhl2v.esm.js.map} +1 -1
- package/dist/{privy-wallet-provider-BtLH1dpY.esm.js → privy-wallet-provider-BhiHnphv.esm.js} +3 -3
- package/dist/privy-wallet-provider-BhiHnphv.esm.js.map +1 -0
- package/dist/{privy-wallet-provider-CrRfcONv.js → privy-wallet-provider-CMCv5g3O.js} +3 -3
- package/dist/privy-wallet-provider-CMCv5g3O.js.map +1 -0
- package/dist/{solana-mobile-wallet-provider-C3l6mxSm.esm.js → solana-mobile-wallet-provider-DSSk_6CR.esm.js} +4 -4
- package/dist/{solana-mobile-wallet-provider-C3l6mxSm.esm.js.map → solana-mobile-wallet-provider-DSSk_6CR.esm.js.map} +1 -1
- package/dist/{solana-mobile-wallet-provider-QcGazewW.js → solana-mobile-wallet-provider-DaFNesCe.js} +4 -4
- package/dist/{solana-mobile-wallet-provider-QcGazewW.js.map → solana-mobile-wallet-provider-DaFNesCe.js.map} +1 -1
- package/package.json +2 -2
- package/dist/index-Bdcc5821.js +0 -2375
- package/dist/index-Bdcc5821.js.map +0 -1
- package/dist/index-BilNgDSc.js.map +0 -1
- package/dist/index-BrlvWT1Q.js.map +0 -1
- package/dist/index-C5Cnn7Hq.esm.js.map +0 -1
- package/dist/index-COMIXUxl.js.map +0 -1
- package/dist/index-CrOVJFX9.esm.js +0 -2373
- package/dist/index-CrOVJFX9.esm.js.map +0 -1
- package/dist/index-DeVykeX4.esm.js.map +0 -1
- package/dist/index-Dsh0H37n.esm.js.map +0 -1
- package/dist/index.browser--rDwfvXH.esm.js +0 -307
- package/dist/index.browser--rDwfvXH.esm.js.map +0 -1
- package/dist/index.browser-BOJRGZWX.js.map +0 -1
- package/dist/index.browser-CLZv9v_y.js +0 -310
- package/dist/index.browser-CLZv9v_y.js.map +0 -1
- package/dist/index.browser-DQKnuR3q.esm.js +0 -1468
- package/dist/index.browser-DQKnuR3q.esm.js.map +0 -1
- package/dist/index.browser-DZjyUgtx.esm.js.map +0 -1
- package/dist/index.browser-DqO3G-HJ.js +0 -1471
- package/dist/index.browser-DqO3G-HJ.js.map +0 -1
- package/dist/index.native-CkUXrvPQ.js.map +0 -1
- package/dist/index.native-H-fEcP_L.esm.js.map +0 -1
- package/dist/privy-wallet-provider-BtLH1dpY.esm.js.map +0 -1
- package/dist/privy-wallet-provider-CrRfcONv.js.map +0 -1
|
@@ -110,6 +110,8 @@ const SolanaMobileWalletAdapterErrorCode = {
|
|
|
110
110
|
ERROR_WALLET_NOT_FOUND: 'ERROR_WALLET_NOT_FOUND',
|
|
111
111
|
ERROR_INVALID_PROTOCOL_VERSION: 'ERROR_INVALID_PROTOCOL_VERSION'};
|
|
112
112
|
class SolanaMobileWalletAdapterError extends Error {
|
|
113
|
+
data;
|
|
114
|
+
code;
|
|
113
115
|
constructor(...args) {
|
|
114
116
|
const [code, message, data] = args;
|
|
115
117
|
super(message);
|
|
@@ -119,6 +121,9 @@ class SolanaMobileWalletAdapterError extends Error {
|
|
|
119
121
|
}
|
|
120
122
|
}
|
|
121
123
|
class SolanaMobileWalletAdapterProtocolError extends Error {
|
|
124
|
+
data;
|
|
125
|
+
code;
|
|
126
|
+
jsonRpcMessageId;
|
|
122
127
|
constructor(...args) {
|
|
123
128
|
const [jsonRpcMessageId, code, message, data] = args;
|
|
124
129
|
super(message);
|
|
@@ -129,31 +134,6 @@ class SolanaMobileWalletAdapterProtocolError extends Error {
|
|
|
129
134
|
}
|
|
130
135
|
}
|
|
131
136
|
|
|
132
|
-
/******************************************************************************
|
|
133
|
-
Copyright (c) Microsoft Corporation.
|
|
134
|
-
|
|
135
|
-
Permission to use, copy, modify, and/or distribute this software for any
|
|
136
|
-
purpose with or without fee is hereby granted.
|
|
137
|
-
|
|
138
|
-
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
139
|
-
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
140
|
-
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
141
|
-
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
142
|
-
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
143
|
-
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
144
|
-
PERFORMANCE OF THIS SOFTWARE.
|
|
145
|
-
***************************************************************************** */
|
|
146
|
-
|
|
147
|
-
function __awaiter(thisArg, _arguments, P, generator) {
|
|
148
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
149
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
150
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
151
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
152
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
153
|
-
step((generator = generator.apply(thisArg, [])).next());
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
|
|
157
137
|
function encode(input) {
|
|
158
138
|
return window.btoa(input);
|
|
159
139
|
}
|
|
@@ -175,15 +155,13 @@ function toUint8Array(base64EncodedByteArray) {
|
|
|
175
155
|
.map((c) => c.charCodeAt(0)));
|
|
176
156
|
}
|
|
177
157
|
|
|
178
|
-
function createHelloReq(ecdhPublicKey, associationKeypairPrivateKey) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
return response;
|
|
186
|
-
});
|
|
158
|
+
async function createHelloReq(ecdhPublicKey, associationKeypairPrivateKey) {
|
|
159
|
+
const publicKeyBuffer = await crypto.subtle.exportKey('raw', ecdhPublicKey);
|
|
160
|
+
const signatureBuffer = await crypto.subtle.sign({ hash: 'SHA-256', name: 'ECDSA' }, associationKeypairPrivateKey, publicKeyBuffer);
|
|
161
|
+
const response = new Uint8Array(publicKeyBuffer.byteLength + signatureBuffer.byteLength);
|
|
162
|
+
response.set(new Uint8Array(publicKeyBuffer), 0);
|
|
163
|
+
response.set(new Uint8Array(signatureBuffer), publicKeyBuffer.byteLength);
|
|
164
|
+
return response;
|
|
187
165
|
}
|
|
188
166
|
|
|
189
167
|
function createSIWSMessage(payload) {
|
|
@@ -225,16 +203,14 @@ function createMobileWalletProxy(protocolVersion, protocolRequestHandler) {
|
|
|
225
203
|
return null;
|
|
226
204
|
}
|
|
227
205
|
if (target[p] == null) {
|
|
228
|
-
target[p] = function (inputParams) {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
return handleMobileWalletResponse(p, result, protocolVersion);
|
|
237
|
-
});
|
|
206
|
+
target[p] = async function (inputParams) {
|
|
207
|
+
const { method, params } = handleMobileWalletRequest(p, inputParams, protocolVersion);
|
|
208
|
+
const result = await protocolRequestHandler(method, params);
|
|
209
|
+
// if the request tried to sign in but the wallet did not return a sign in result, fallback on message signing
|
|
210
|
+
if (method === 'authorize' && params.sign_in_payload && !result.sign_in_result) {
|
|
211
|
+
result['sign_in_result'] = await signInFallback(params.sign_in_payload, result, protocolRequestHandler);
|
|
212
|
+
}
|
|
213
|
+
return handleMobileWalletResponse(p, result, protocolVersion);
|
|
238
214
|
};
|
|
239
215
|
}
|
|
240
216
|
return target[p];
|
|
@@ -338,39 +314,43 @@ function handleMobileWalletResponse(method, response, protocolVersion) {
|
|
|
338
314
|
if (capabilities.supports_clone_authorization === true) {
|
|
339
315
|
features.push(SolanaCloneAuthorization);
|
|
340
316
|
}
|
|
341
|
-
return
|
|
317
|
+
return {
|
|
318
|
+
...capabilities,
|
|
319
|
+
features: features,
|
|
320
|
+
};
|
|
342
321
|
}
|
|
343
322
|
case 'v1': {
|
|
344
|
-
return
|
|
323
|
+
return {
|
|
324
|
+
...capabilities,
|
|
325
|
+
supports_sign_and_send_transactions: true,
|
|
326
|
+
supports_clone_authorization: capabilities.features.includes(SolanaCloneAuthorization)
|
|
327
|
+
};
|
|
345
328
|
}
|
|
346
329
|
}
|
|
347
330
|
}
|
|
348
331
|
}
|
|
349
332
|
return response;
|
|
350
333
|
}
|
|
351
|
-
function signInFallback(signInPayload, authorizationResult, protocolRequestHandler) {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
addresses: [address],
|
|
359
|
-
payloads: [siwsMessage]
|
|
360
|
-
});
|
|
361
|
-
const signedPayload = toUint8Array(signMessageResult.signed_payloads[0]);
|
|
362
|
-
const signedMessage = fromUint8Array$1(signedPayload.slice(0, signedPayload.length - 64));
|
|
363
|
-
const signature = fromUint8Array$1(signedPayload.slice(signedPayload.length - 64));
|
|
364
|
-
const signInResult = {
|
|
365
|
-
address: address,
|
|
366
|
-
// Workaround: some wallets have been observed to only reply with the message signature.
|
|
367
|
-
// This is non-compliant with the spec, but in the interest of maximizing compatibility,
|
|
368
|
-
// detect this case and reuse the original message.
|
|
369
|
-
signed_message: signedMessage.length == 0 ? siwsMessage : signedMessage,
|
|
370
|
-
signature
|
|
371
|
-
};
|
|
372
|
-
return signInResult;
|
|
334
|
+
async function signInFallback(signInPayload, authorizationResult, protocolRequestHandler) {
|
|
335
|
+
const domain = signInPayload.domain ?? window.location.host;
|
|
336
|
+
const address = authorizationResult.accounts[0].address;
|
|
337
|
+
const siwsMessage = createSIWSMessageBase64Url({ ...signInPayload, domain, address: base64ToBase58(address) });
|
|
338
|
+
const signMessageResult = await protocolRequestHandler('sign_messages', {
|
|
339
|
+
addresses: [address],
|
|
340
|
+
payloads: [siwsMessage]
|
|
373
341
|
});
|
|
342
|
+
const signedPayload = toUint8Array(signMessageResult.signed_payloads[0]);
|
|
343
|
+
const signedMessage = fromUint8Array$1(signedPayload.slice(0, signedPayload.length - 64));
|
|
344
|
+
const signature = fromUint8Array$1(signedPayload.slice(signedPayload.length - 64));
|
|
345
|
+
const signInResult = {
|
|
346
|
+
address: address,
|
|
347
|
+
// Workaround: some wallets have been observed to only reply with the message signature.
|
|
348
|
+
// This is non-compliant with the spec, but in the interest of maximizing compatibility,
|
|
349
|
+
// detect this case and reuse the original message.
|
|
350
|
+
signed_message: signedMessage.length == 0 ? siwsMessage : signedMessage,
|
|
351
|
+
signature
|
|
352
|
+
};
|
|
353
|
+
return signInResult;
|
|
374
354
|
}
|
|
375
355
|
|
|
376
356
|
const SEQUENCE_NUMBER_BYTES = 4;
|
|
@@ -386,28 +366,24 @@ function createSequenceNumberVector(sequenceNumber) {
|
|
|
386
366
|
|
|
387
367
|
const INITIALIZATION_VECTOR_BYTES = 12;
|
|
388
368
|
const ENCODED_PUBLIC_KEY_LENGTH_BYTES = 65;
|
|
389
|
-
function encryptMessage(plaintext, sequenceNumber, sharedSecret) {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
return response;
|
|
400
|
-
});
|
|
369
|
+
async function encryptMessage(plaintext, sequenceNumber, sharedSecret) {
|
|
370
|
+
const sequenceNumberVector = createSequenceNumberVector(sequenceNumber);
|
|
371
|
+
const initializationVector = new Uint8Array(INITIALIZATION_VECTOR_BYTES);
|
|
372
|
+
crypto.getRandomValues(initializationVector);
|
|
373
|
+
const ciphertext = await crypto.subtle.encrypt(getAlgorithmParams(sequenceNumberVector, initializationVector), sharedSecret, new TextEncoder().encode(plaintext));
|
|
374
|
+
const response = new Uint8Array(sequenceNumberVector.byteLength + initializationVector.byteLength + ciphertext.byteLength);
|
|
375
|
+
response.set(new Uint8Array(sequenceNumberVector), 0);
|
|
376
|
+
response.set(new Uint8Array(initializationVector), sequenceNumberVector.byteLength);
|
|
377
|
+
response.set(new Uint8Array(ciphertext), sequenceNumberVector.byteLength + initializationVector.byteLength);
|
|
378
|
+
return response;
|
|
401
379
|
}
|
|
402
|
-
function decryptMessage(message, sharedSecret) {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
return plaintext;
|
|
410
|
-
});
|
|
380
|
+
async function decryptMessage(message, sharedSecret) {
|
|
381
|
+
const sequenceNumberVector = message.slice(0, SEQUENCE_NUMBER_BYTES);
|
|
382
|
+
const initializationVector = message.slice(SEQUENCE_NUMBER_BYTES, SEQUENCE_NUMBER_BYTES + INITIALIZATION_VECTOR_BYTES);
|
|
383
|
+
const ciphertext = message.slice(SEQUENCE_NUMBER_BYTES + INITIALIZATION_VECTOR_BYTES);
|
|
384
|
+
const plaintextBuffer = await crypto.subtle.decrypt(getAlgorithmParams(sequenceNumberVector, initializationVector), sharedSecret, ciphertext);
|
|
385
|
+
const plaintext = getUtf8Decoder().decode(plaintextBuffer);
|
|
386
|
+
return plaintext;
|
|
411
387
|
}
|
|
412
388
|
function getAlgorithmParams(sequenceNumber, initializationVector) {
|
|
413
389
|
return {
|
|
@@ -425,22 +401,18 @@ function getUtf8Decoder() {
|
|
|
425
401
|
return _utf8Decoder;
|
|
426
402
|
}
|
|
427
403
|
|
|
428
|
-
function generateAssociationKeypair() {
|
|
429
|
-
return
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
}, false /* extractable */, ['sign'] /* keyUsages */);
|
|
434
|
-
});
|
|
404
|
+
async function generateAssociationKeypair() {
|
|
405
|
+
return await crypto.subtle.generateKey({
|
|
406
|
+
name: 'ECDSA',
|
|
407
|
+
namedCurve: 'P-256',
|
|
408
|
+
}, false /* extractable */, ['sign'] /* keyUsages */);
|
|
435
409
|
}
|
|
436
410
|
|
|
437
|
-
function generateECDHKeypair() {
|
|
438
|
-
return
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
}, false /* extractable */, ['deriveKey', 'deriveBits'] /* keyUsages */);
|
|
443
|
-
});
|
|
411
|
+
async function generateECDHKeypair() {
|
|
412
|
+
return await crypto.subtle.generateKey({
|
|
413
|
+
name: 'ECDH',
|
|
414
|
+
namedCurve: 'P-256',
|
|
415
|
+
}, false /* extractable */, ['deriveKey', 'deriveBits'] /* keyUsages */);
|
|
444
416
|
}
|
|
445
417
|
|
|
446
418
|
// https://stackoverflow.com/a/9458996/802047
|
|
@@ -486,12 +458,12 @@ function getIntentURL(methodPathname, intentUrlBase) {
|
|
|
486
458
|
try {
|
|
487
459
|
baseUrl = new URL(intentUrlBase);
|
|
488
460
|
}
|
|
489
|
-
catch
|
|
490
|
-
if (
|
|
461
|
+
catch { } // eslint-disable-line no-empty
|
|
462
|
+
if (baseUrl?.protocol !== 'https:') {
|
|
491
463
|
throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_FORBIDDEN_WALLET_BASE_URL, 'Base URLs supplied by wallets must be valid `https` URLs');
|
|
492
464
|
}
|
|
493
465
|
}
|
|
494
|
-
baseUrl
|
|
466
|
+
baseUrl ||= new URL(`${INTENT_NAME}:/`);
|
|
495
467
|
const pathname = methodPathname.startsWith('/')
|
|
496
468
|
? // Method is an absolute path. Replace it wholesale.
|
|
497
469
|
methodPathname
|
|
@@ -499,94 +471,82 @@ function getIntentURL(methodPathname, intentUrlBase) {
|
|
|
499
471
|
[...getPathParts(baseUrl.pathname), ...getPathParts(methodPathname)].join('/');
|
|
500
472
|
return new URL(pathname, baseUrl);
|
|
501
473
|
}
|
|
502
|
-
function getAssociateAndroidIntentURL(associationPublicKey, putativePort, associationURLBase, protocolVersions = ['v1']) {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
url.searchParams.set('v', version);
|
|
512
|
-
});
|
|
513
|
-
return url;
|
|
474
|
+
async function getAssociateAndroidIntentURL(associationPublicKey, putativePort, associationURLBase, protocolVersions = ['v1']) {
|
|
475
|
+
const associationPort = assertAssociationPort(putativePort);
|
|
476
|
+
const exportedKey = await crypto.subtle.exportKey('raw', associationPublicKey);
|
|
477
|
+
const encodedKey = arrayBufferToBase64String(exportedKey);
|
|
478
|
+
const url = getIntentURL('v1/associate/local', associationURLBase);
|
|
479
|
+
url.searchParams.set('association', getStringWithURLUnsafeCharactersReplaced(encodedKey));
|
|
480
|
+
url.searchParams.set('port', `${associationPort}`);
|
|
481
|
+
protocolVersions.forEach((version) => {
|
|
482
|
+
url.searchParams.set('v', version);
|
|
514
483
|
});
|
|
484
|
+
return url;
|
|
515
485
|
}
|
|
516
|
-
function getRemoteAssociateAndroidIntentURL(associationPublicKey, hostAuthority, reflectorId, associationURLBase, protocolVersions = ['v1']) {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
url.searchParams.set('v', version);
|
|
526
|
-
});
|
|
527
|
-
return url;
|
|
486
|
+
async function getRemoteAssociateAndroidIntentURL(associationPublicKey, hostAuthority, reflectorId, associationURLBase, protocolVersions = ['v1']) {
|
|
487
|
+
const exportedKey = await crypto.subtle.exportKey('raw', associationPublicKey);
|
|
488
|
+
const encodedKey = arrayBufferToBase64String(exportedKey);
|
|
489
|
+
const url = getIntentURL('v1/associate/remote', associationURLBase);
|
|
490
|
+
url.searchParams.set('association', getStringWithURLUnsafeCharactersReplaced(encodedKey));
|
|
491
|
+
url.searchParams.set('reflector', `${hostAuthority}`);
|
|
492
|
+
url.searchParams.set('id', `${fromUint8Array$1(reflectorId, true)}`);
|
|
493
|
+
protocolVersions.forEach((version) => {
|
|
494
|
+
url.searchParams.set('v', version);
|
|
528
495
|
});
|
|
496
|
+
return url;
|
|
529
497
|
}
|
|
530
498
|
|
|
531
|
-
function encryptJsonRpcMessage(jsonRpcMessage, sharedSecret) {
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
return encryptMessage(plaintext, sequenceNumber, sharedSecret);
|
|
536
|
-
});
|
|
499
|
+
async function encryptJsonRpcMessage(jsonRpcMessage, sharedSecret) {
|
|
500
|
+
const plaintext = JSON.stringify(jsonRpcMessage);
|
|
501
|
+
const sequenceNumber = jsonRpcMessage.id;
|
|
502
|
+
return encryptMessage(plaintext, sequenceNumber, sharedSecret);
|
|
537
503
|
}
|
|
538
|
-
function decryptJsonRpcMessage(message, sharedSecret) {
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
return jsonRpcMessage;
|
|
546
|
-
});
|
|
504
|
+
async function decryptJsonRpcMessage(message, sharedSecret) {
|
|
505
|
+
const plaintext = await decryptMessage(message, sharedSecret);
|
|
506
|
+
const jsonRpcMessage = JSON.parse(plaintext);
|
|
507
|
+
if (Object.hasOwnProperty.call(jsonRpcMessage, 'error')) {
|
|
508
|
+
throw new SolanaMobileWalletAdapterProtocolError(jsonRpcMessage.id, jsonRpcMessage.error.code, jsonRpcMessage.error.message);
|
|
509
|
+
}
|
|
510
|
+
return jsonRpcMessage;
|
|
547
511
|
}
|
|
548
512
|
|
|
549
|
-
function parseHelloRsp(payloadBuffer, // The X9.62-encoded wallet endpoint ephemeral ECDH public keypoint.
|
|
513
|
+
async function parseHelloRsp(payloadBuffer, // The X9.62-encoded wallet endpoint ephemeral ECDH public keypoint.
|
|
550
514
|
associationPublicKey, ecdhPrivateKey) {
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
return aesKeyMaterialVal;
|
|
565
|
-
});
|
|
515
|
+
const [associationPublicKeyBuffer, walletPublicKey] = await Promise.all([
|
|
516
|
+
crypto.subtle.exportKey('raw', associationPublicKey),
|
|
517
|
+
crypto.subtle.importKey('raw', payloadBuffer.slice(0, ENCODED_PUBLIC_KEY_LENGTH_BYTES), { name: 'ECDH', namedCurve: 'P-256' }, false /* extractable */, [] /* keyUsages */),
|
|
518
|
+
]);
|
|
519
|
+
const sharedSecret = await crypto.subtle.deriveBits({ name: 'ECDH', public: walletPublicKey }, ecdhPrivateKey, 256);
|
|
520
|
+
const ecdhSecretKey = await crypto.subtle.importKey('raw', sharedSecret, 'HKDF', false /* extractable */, ['deriveKey'] /* keyUsages */);
|
|
521
|
+
const aesKeyMaterialVal = await crypto.subtle.deriveKey({
|
|
522
|
+
name: 'HKDF',
|
|
523
|
+
hash: 'SHA-256',
|
|
524
|
+
salt: new Uint8Array(associationPublicKeyBuffer),
|
|
525
|
+
info: new Uint8Array(),
|
|
526
|
+
}, ecdhSecretKey, { name: 'AES-GCM', length: 128 }, false /* extractable */, ['encrypt', 'decrypt']);
|
|
527
|
+
return aesKeyMaterialVal;
|
|
566
528
|
}
|
|
567
529
|
|
|
568
|
-
function parseSessionProps(message, sharedSecret) {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_INVALID_PROTOCOL_VERSION, `Unknown/unsupported protocol version: ${jsonProperties.v}`);
|
|
585
|
-
}
|
|
530
|
+
async function parseSessionProps(message, sharedSecret) {
|
|
531
|
+
const plaintext = await decryptMessage(message, sharedSecret);
|
|
532
|
+
const jsonProperties = JSON.parse(plaintext);
|
|
533
|
+
let protocolVersion = 'legacy';
|
|
534
|
+
if (Object.hasOwnProperty.call(jsonProperties, 'v')) {
|
|
535
|
+
switch (jsonProperties.v) {
|
|
536
|
+
case 1:
|
|
537
|
+
case '1':
|
|
538
|
+
case 'v1':
|
|
539
|
+
protocolVersion = 'v1';
|
|
540
|
+
break;
|
|
541
|
+
case 'legacy':
|
|
542
|
+
protocolVersion = 'legacy';
|
|
543
|
+
break;
|
|
544
|
+
default:
|
|
545
|
+
throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_INVALID_PROTOCOL_VERSION, `Unknown/unsupported protocol version: ${jsonProperties.v}`);
|
|
586
546
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
547
|
+
}
|
|
548
|
+
return ({
|
|
549
|
+
protocol_version: protocolVersion
|
|
590
550
|
});
|
|
591
551
|
}
|
|
592
552
|
|
|
@@ -631,47 +591,43 @@ function launchUrlThroughHiddenFrame(url) {
|
|
|
631
591
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
632
592
|
_frame.contentWindow.location.href = url.toString();
|
|
633
593
|
}
|
|
634
|
-
function launchAssociation(associationUrl) {
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
break;
|
|
657
|
-
}
|
|
658
|
-
default:
|
|
659
|
-
assertUnreachable(browser);
|
|
594
|
+
async function launchAssociation(associationUrl) {
|
|
595
|
+
if (associationUrl.protocol === 'https:') {
|
|
596
|
+
// The association URL is an Android 'App Link' or iOS 'Universal Link'.
|
|
597
|
+
// These are regular web URLs that are designed to launch an app if it
|
|
598
|
+
// is installed or load the actual target webpage if not.
|
|
599
|
+
window.location.assign(associationUrl);
|
|
600
|
+
}
|
|
601
|
+
else {
|
|
602
|
+
// The association URL has a custom protocol (eg. `solana-wallet:`)
|
|
603
|
+
try {
|
|
604
|
+
const browser = getBrowser();
|
|
605
|
+
switch (browser) {
|
|
606
|
+
case Browser.Firefox:
|
|
607
|
+
// If a custom protocol is not supported in Firefox, it throws.
|
|
608
|
+
launchUrlThroughHiddenFrame(associationUrl);
|
|
609
|
+
// If we reached this line, it's supported.
|
|
610
|
+
break;
|
|
611
|
+
case Browser.Other: {
|
|
612
|
+
const detectionPromise = getDetectionPromise();
|
|
613
|
+
window.location.assign(associationUrl);
|
|
614
|
+
await detectionPromise;
|
|
615
|
+
break;
|
|
660
616
|
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_WALLET_NOT_FOUND, 'Found no installed wallet that supports the mobile wallet protocol.');
|
|
617
|
+
default:
|
|
618
|
+
assertUnreachable(browser);
|
|
664
619
|
}
|
|
665
620
|
}
|
|
666
|
-
|
|
621
|
+
catch (e) {
|
|
622
|
+
throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_WALLET_NOT_FOUND, 'Found no installed wallet that supports the mobile wallet protocol.');
|
|
623
|
+
}
|
|
624
|
+
}
|
|
667
625
|
}
|
|
668
|
-
function startSession(associationPublicKey, associationURLBase) {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
return randomAssociationPort;
|
|
674
|
-
});
|
|
626
|
+
async function startSession(associationPublicKey, associationURLBase) {
|
|
627
|
+
const randomAssociationPort = getRandomAssociationPort();
|
|
628
|
+
const associationUrl = await getAssociateAndroidIntentURL(associationPublicKey, randomAssociationPort, associationURLBase);
|
|
629
|
+
await launchAssociation(associationUrl);
|
|
630
|
+
return randomAssociationPort;
|
|
675
631
|
}
|
|
676
632
|
|
|
677
633
|
const WEBSOCKET_CONNECTION_CONFIG = {
|
|
@@ -700,7 +656,7 @@ function assertSecureEndpointSpecificURI(walletUriBase) {
|
|
|
700
656
|
try {
|
|
701
657
|
url = new URL(walletUriBase);
|
|
702
658
|
}
|
|
703
|
-
catch
|
|
659
|
+
catch {
|
|
704
660
|
throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_FORBIDDEN_WALLET_BASE_URL, 'Invalid base URL supplied by wallet');
|
|
705
661
|
}
|
|
706
662
|
if (url.protocol !== 'https:') {
|
|
@@ -725,25 +681,38 @@ function getReflectorIdFromByteArray(byteArray) {
|
|
|
725
681
|
let { value: length, offset } = decodeVarLong(byteArray);
|
|
726
682
|
return new Uint8Array(byteArray.slice(offset, offset + length));
|
|
727
683
|
}
|
|
728
|
-
function transact(callback, config) {
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
684
|
+
async function transact(callback, config) {
|
|
685
|
+
const { wallet, close } = await startScenario(config);
|
|
686
|
+
try {
|
|
687
|
+
return await callback(await wallet);
|
|
688
|
+
}
|
|
689
|
+
finally {
|
|
690
|
+
close();
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
async function startScenario(config) {
|
|
694
|
+
assertSecureContext();
|
|
695
|
+
const associationKeypair = await generateAssociationKeypair();
|
|
696
|
+
const sessionPort = await startSession(associationKeypair.publicKey, config?.baseUri);
|
|
697
|
+
const websocketURL = `ws://localhost:${sessionPort}/solana-wallet`;
|
|
698
|
+
let connectionStartTime;
|
|
699
|
+
const getNextRetryDelayMs = (() => {
|
|
700
|
+
const schedule = [...WEBSOCKET_CONNECTION_CONFIG.retryDelayScheduleMs];
|
|
701
|
+
return () => (schedule.length > 1 ? schedule.shift() : schedule[0]);
|
|
702
|
+
})();
|
|
703
|
+
let nextJsonRpcMessageId = 1;
|
|
704
|
+
let lastKnownInboundSequenceNumber = 0;
|
|
705
|
+
let state = { __type: 'disconnected' };
|
|
706
|
+
let socket;
|
|
707
|
+
let sessionEstablished = false;
|
|
708
|
+
let handleForceClose;
|
|
709
|
+
return { close: () => {
|
|
710
|
+
socket.close();
|
|
711
|
+
handleForceClose();
|
|
712
|
+
}, wallet: new Promise((resolve, reject) => {
|
|
744
713
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
745
714
|
const jsonRpcResponsePromises = {};
|
|
746
|
-
const handleOpen = () =>
|
|
715
|
+
const handleOpen = async () => {
|
|
747
716
|
if (state.__type !== 'connecting') {
|
|
748
717
|
console.warn('Expected adapter state to be `connecting` at the moment the websocket opens. ' +
|
|
749
718
|
`Got \`${state.__type}\`.`);
|
|
@@ -757,14 +726,14 @@ function transact(callback, config) {
|
|
|
757
726
|
// APP_PING was sent by the wallet/websocket server. We must continue to support this behavior
|
|
758
727
|
// in case the user is using a wallet that has not updated their walletlib implementation.
|
|
759
728
|
const { associationKeypair } = state;
|
|
760
|
-
const ecdhKeypair =
|
|
761
|
-
socket.send(
|
|
729
|
+
const ecdhKeypair = await generateECDHKeypair();
|
|
730
|
+
socket.send(await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
|
|
762
731
|
state = {
|
|
763
732
|
__type: 'hello_req_sent',
|
|
764
733
|
associationPublicKey: associationKeypair.publicKey,
|
|
765
734
|
ecdhPrivateKey: ecdhKeypair.privateKey,
|
|
766
735
|
};
|
|
767
|
-
}
|
|
736
|
+
};
|
|
768
737
|
const handleClose = (evt) => {
|
|
769
738
|
if (evt.wasClean) {
|
|
770
739
|
state = { __type: 'disconnected' };
|
|
@@ -774,28 +743,28 @@ function transact(callback, config) {
|
|
|
774
743
|
}
|
|
775
744
|
disposeSocket();
|
|
776
745
|
};
|
|
777
|
-
const handleError = (_evt) =>
|
|
746
|
+
const handleError = async (_evt) => {
|
|
778
747
|
disposeSocket();
|
|
779
748
|
if (Date.now() - connectionStartTime >= WEBSOCKET_CONNECTION_CONFIG.timeoutMs) {
|
|
780
749
|
reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_TIMEOUT, `Failed to connect to the wallet websocket at ${websocketURL}.`));
|
|
781
750
|
}
|
|
782
751
|
else {
|
|
783
|
-
|
|
752
|
+
await new Promise((resolve) => {
|
|
784
753
|
const retryDelayMs = getNextRetryDelayMs();
|
|
785
754
|
retryWaitTimeoutId = window.setTimeout(resolve, retryDelayMs);
|
|
786
755
|
});
|
|
787
756
|
attemptSocketConnection();
|
|
788
757
|
}
|
|
789
|
-
}
|
|
790
|
-
const handleMessage = (evt) =>
|
|
791
|
-
const responseBuffer =
|
|
758
|
+
};
|
|
759
|
+
const handleMessage = async (evt) => {
|
|
760
|
+
const responseBuffer = await evt.data.arrayBuffer();
|
|
792
761
|
switch (state.__type) {
|
|
793
762
|
case 'connecting':
|
|
794
763
|
if (responseBuffer.byteLength !== 0) {
|
|
795
764
|
throw new Error('Encountered unexpected message while connecting');
|
|
796
765
|
}
|
|
797
|
-
const ecdhKeypair =
|
|
798
|
-
socket.send(
|
|
766
|
+
const ecdhKeypair = await generateECDHKeypair();
|
|
767
|
+
socket.send(await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
|
|
799
768
|
state = {
|
|
800
769
|
__type: 'hello_req_sent',
|
|
801
770
|
associationPublicKey: associationKeypair.publicKey,
|
|
@@ -810,7 +779,7 @@ function transact(callback, config) {
|
|
|
810
779
|
throw new Error('Encrypted message has invalid sequence number');
|
|
811
780
|
}
|
|
812
781
|
lastKnownInboundSequenceNumber = sequenceNumber;
|
|
813
|
-
const jsonRpcMessage =
|
|
782
|
+
const jsonRpcMessage = await decryptJsonRpcMessage(responseBuffer, state.sharedSecret);
|
|
814
783
|
const responsePromise = jsonRpcResponsePromises[jsonRpcMessage.id];
|
|
815
784
|
delete jsonRpcResponsePromises[jsonRpcMessage.id];
|
|
816
785
|
responsePromise.resolve(jsonRpcMessage.result);
|
|
@@ -829,8 +798,8 @@ function transact(callback, config) {
|
|
|
829
798
|
case 'hello_req_sent': {
|
|
830
799
|
// if we receive an APP_PING message (empty message), resend the HELLO_REQ (see above)
|
|
831
800
|
if (responseBuffer.byteLength === 0) {
|
|
832
|
-
const ecdhKeypair =
|
|
833
|
-
socket.send(
|
|
801
|
+
const ecdhKeypair = await generateECDHKeypair();
|
|
802
|
+
socket.send(await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
|
|
834
803
|
state = {
|
|
835
804
|
__type: 'hello_req_sent',
|
|
836
805
|
associationPublicKey: associationKeypair.publicKey,
|
|
@@ -838,10 +807,10 @@ function transact(callback, config) {
|
|
|
838
807
|
};
|
|
839
808
|
break;
|
|
840
809
|
}
|
|
841
|
-
const sharedSecret =
|
|
810
|
+
const sharedSecret = await parseHelloRsp(responseBuffer, state.associationPublicKey, state.ecdhPrivateKey);
|
|
842
811
|
const sessionPropertiesBuffer = responseBuffer.slice(ENCODED_PUBLIC_KEY_LENGTH_BYTES);
|
|
843
812
|
const sessionProperties = sessionPropertiesBuffer.byteLength !== 0
|
|
844
|
-
?
|
|
813
|
+
? await (async () => {
|
|
845
814
|
const sequenceNumberVector = sessionPropertiesBuffer.slice(0, SEQUENCE_NUMBER_BYTES);
|
|
846
815
|
const sequenceNumber = getSequenceNumberFromByteArray(sequenceNumberVector);
|
|
847
816
|
if (sequenceNumber !== (lastKnownInboundSequenceNumber + 1)) {
|
|
@@ -849,15 +818,15 @@ function transact(callback, config) {
|
|
|
849
818
|
}
|
|
850
819
|
lastKnownInboundSequenceNumber = sequenceNumber;
|
|
851
820
|
return parseSessionProps(sessionPropertiesBuffer, sharedSecret);
|
|
852
|
-
})
|
|
821
|
+
})() : { protocol_version: 'legacy' };
|
|
853
822
|
state = { __type: 'connected', sharedSecret, sessionProperties };
|
|
854
|
-
const wallet = createMobileWalletProxy(sessionProperties.protocol_version, (method, params) =>
|
|
823
|
+
const wallet = createMobileWalletProxy(sessionProperties.protocol_version, async (method, params) => {
|
|
855
824
|
const id = nextJsonRpcMessageId++;
|
|
856
|
-
socket.send(
|
|
825
|
+
socket.send(await encryptJsonRpcMessage({
|
|
857
826
|
id,
|
|
858
827
|
jsonrpc: '2.0',
|
|
859
828
|
method,
|
|
860
|
-
params: params
|
|
829
|
+
params: params ?? {},
|
|
861
830
|
}, sharedSecret));
|
|
862
831
|
return new Promise((resolve, reject) => {
|
|
863
832
|
jsonRpcResponsePromises[id] = {
|
|
@@ -883,21 +852,25 @@ function transact(callback, config) {
|
|
|
883
852
|
reject,
|
|
884
853
|
};
|
|
885
854
|
});
|
|
886
|
-
})
|
|
855
|
+
});
|
|
856
|
+
sessionEstablished = true;
|
|
887
857
|
try {
|
|
888
|
-
resolve(
|
|
858
|
+
resolve(wallet);
|
|
889
859
|
}
|
|
890
860
|
catch (e) {
|
|
891
861
|
reject(e);
|
|
892
862
|
}
|
|
893
|
-
finally {
|
|
894
|
-
disposeSocket();
|
|
895
|
-
socket.close();
|
|
896
|
-
}
|
|
897
863
|
break;
|
|
898
864
|
}
|
|
899
865
|
}
|
|
900
|
-
}
|
|
866
|
+
};
|
|
867
|
+
handleForceClose = () => {
|
|
868
|
+
socket.removeEventListener('message', handleMessage);
|
|
869
|
+
disposeSocket();
|
|
870
|
+
if (!sessionEstablished) {
|
|
871
|
+
reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session was closed before connection.`, { closeEvent: new CloseEvent('socket was closed before connection') }));
|
|
872
|
+
}
|
|
873
|
+
};
|
|
901
874
|
let disposeSocket;
|
|
902
875
|
let retryWaitTimeoutId;
|
|
903
876
|
const attemptSocketConnection = () => {
|
|
@@ -922,246 +895,243 @@ function transact(callback, config) {
|
|
|
922
895
|
};
|
|
923
896
|
};
|
|
924
897
|
attemptSocketConnection();
|
|
925
|
-
});
|
|
926
|
-
});
|
|
898
|
+
}) };
|
|
927
899
|
}
|
|
928
|
-
function startRemoteScenario(config) {
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
const
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
900
|
+
async function startRemoteScenario(config) {
|
|
901
|
+
assertSecureContext();
|
|
902
|
+
const associationKeypair = await generateAssociationKeypair();
|
|
903
|
+
const websocketURL = `wss://${config?.remoteHostAuthority}/reflect`;
|
|
904
|
+
let connectionStartTime;
|
|
905
|
+
const getNextRetryDelayMs = (() => {
|
|
906
|
+
const schedule = [...WEBSOCKET_CONNECTION_CONFIG.retryDelayScheduleMs];
|
|
907
|
+
return () => (schedule.length > 1 ? schedule.shift() : schedule[0]);
|
|
908
|
+
})();
|
|
909
|
+
let nextJsonRpcMessageId = 1;
|
|
910
|
+
let lastKnownInboundSequenceNumber = 0;
|
|
911
|
+
let encoding;
|
|
912
|
+
let state = { __type: 'disconnected' };
|
|
913
|
+
let socket;
|
|
914
|
+
let disposeSocket;
|
|
915
|
+
let decodeBytes = async (evt) => {
|
|
916
|
+
if (encoding == 'base64') { // base64 encoding
|
|
917
|
+
const message = await evt.data;
|
|
918
|
+
return toUint8Array(message).buffer;
|
|
919
|
+
}
|
|
920
|
+
else {
|
|
921
|
+
return await evt.data.arrayBuffer();
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
// Reflector Connection Phase
|
|
925
|
+
// here we connect to the reflector and wait for the REFLECTOR_ID message
|
|
926
|
+
// so we build the association URL and return that back to the caller
|
|
927
|
+
const associationUrl = await new Promise((resolve, reject) => {
|
|
928
|
+
const handleOpen = async () => {
|
|
929
|
+
if (state.__type !== 'connecting') {
|
|
930
|
+
console.warn('Expected adapter state to be `connecting` at the moment the websocket opens. ' +
|
|
931
|
+
`Got \`${state.__type}\`.`);
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
if (socket.protocol.includes(WEBSOCKET_PROTOCOL_BASE64)) {
|
|
935
|
+
encoding = 'base64';
|
|
948
936
|
}
|
|
949
937
|
else {
|
|
950
|
-
|
|
938
|
+
encoding = 'binary';
|
|
951
939
|
}
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
disposeSocket();
|
|
982
|
-
if (Date.now() - connectionStartTime >= WEBSOCKET_CONNECTION_CONFIG.timeoutMs) {
|
|
983
|
-
reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_TIMEOUT, `Failed to connect to the wallet websocket at ${websocketURL}.`));
|
|
984
|
-
}
|
|
985
|
-
else {
|
|
986
|
-
yield new Promise((resolve) => {
|
|
987
|
-
const retryDelayMs = getNextRetryDelayMs();
|
|
988
|
-
retryWaitTimeoutId = window.setTimeout(resolve, retryDelayMs);
|
|
989
|
-
});
|
|
990
|
-
attemptSocketConnection();
|
|
991
|
-
}
|
|
992
|
-
});
|
|
993
|
-
const handleReflectorIdMessage = (evt) => __awaiter(this, void 0, void 0, function* () {
|
|
994
|
-
const responseBuffer = yield decodeBytes(evt);
|
|
995
|
-
if (state.__type === 'connecting') {
|
|
996
|
-
if (responseBuffer.byteLength == 0) {
|
|
997
|
-
throw new Error('Encountered unexpected message while connecting');
|
|
998
|
-
}
|
|
999
|
-
const reflectorId = getReflectorIdFromByteArray(responseBuffer);
|
|
1000
|
-
state = {
|
|
1001
|
-
__type: 'reflector_id_received',
|
|
1002
|
-
reflectorId: reflectorId
|
|
1003
|
-
};
|
|
1004
|
-
const associationUrl = yield getRemoteAssociateAndroidIntentURL(associationKeypair.publicKey, config.remoteHostAuthority, reflectorId, config === null || config === void 0 ? void 0 : config.baseUri);
|
|
1005
|
-
socket.removeEventListener('message', handleReflectorIdMessage);
|
|
1006
|
-
resolve(associationUrl);
|
|
1007
|
-
}
|
|
1008
|
-
});
|
|
1009
|
-
let retryWaitTimeoutId;
|
|
1010
|
-
const attemptSocketConnection = () => {
|
|
1011
|
-
if (disposeSocket) {
|
|
1012
|
-
disposeSocket();
|
|
1013
|
-
}
|
|
1014
|
-
state = { __type: 'connecting', associationKeypair };
|
|
1015
|
-
if (connectionStartTime === undefined) {
|
|
1016
|
-
connectionStartTime = Date.now();
|
|
940
|
+
socket.removeEventListener('open', handleOpen);
|
|
941
|
+
};
|
|
942
|
+
const handleClose = (evt) => {
|
|
943
|
+
if (evt.wasClean) {
|
|
944
|
+
state = { __type: 'disconnected' };
|
|
945
|
+
}
|
|
946
|
+
else {
|
|
947
|
+
reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session dropped unexpectedly (${evt.code}: ${evt.reason}).`, { closeEvent: evt }));
|
|
948
|
+
}
|
|
949
|
+
disposeSocket();
|
|
950
|
+
};
|
|
951
|
+
const handleError = async (_evt) => {
|
|
952
|
+
disposeSocket();
|
|
953
|
+
if (Date.now() - connectionStartTime >= WEBSOCKET_CONNECTION_CONFIG.timeoutMs) {
|
|
954
|
+
reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_TIMEOUT, `Failed to connect to the wallet websocket at ${websocketURL}.`));
|
|
955
|
+
}
|
|
956
|
+
else {
|
|
957
|
+
await new Promise((resolve) => {
|
|
958
|
+
const retryDelayMs = getNextRetryDelayMs();
|
|
959
|
+
retryWaitTimeoutId = window.setTimeout(resolve, retryDelayMs);
|
|
960
|
+
});
|
|
961
|
+
attemptSocketConnection();
|
|
962
|
+
}
|
|
963
|
+
};
|
|
964
|
+
const handleReflectorIdMessage = async (evt) => {
|
|
965
|
+
const responseBuffer = await decodeBytes(evt);
|
|
966
|
+
if (state.__type === 'connecting') {
|
|
967
|
+
if (responseBuffer.byteLength == 0) {
|
|
968
|
+
throw new Error('Encountered unexpected message while connecting');
|
|
1017
969
|
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
socket.addEventListener('message', handleReflectorIdMessage);
|
|
1023
|
-
disposeSocket = () => {
|
|
1024
|
-
window.clearTimeout(retryWaitTimeoutId);
|
|
1025
|
-
socket.removeEventListener('open', handleOpen);
|
|
1026
|
-
socket.removeEventListener('close', handleClose);
|
|
1027
|
-
socket.removeEventListener('error', handleError);
|
|
1028
|
-
socket.removeEventListener('message', handleReflectorIdMessage);
|
|
970
|
+
const reflectorId = getReflectorIdFromByteArray(responseBuffer);
|
|
971
|
+
state = {
|
|
972
|
+
__type: 'reflector_id_received',
|
|
973
|
+
reflectorId: reflectorId
|
|
1029
974
|
};
|
|
975
|
+
const associationUrl = await getRemoteAssociateAndroidIntentURL(associationKeypair.publicKey, config.remoteHostAuthority, reflectorId, config?.baseUri);
|
|
976
|
+
socket.removeEventListener('message', handleReflectorIdMessage);
|
|
977
|
+
resolve(associationUrl);
|
|
978
|
+
}
|
|
979
|
+
};
|
|
980
|
+
let retryWaitTimeoutId;
|
|
981
|
+
const attemptSocketConnection = () => {
|
|
982
|
+
if (disposeSocket) {
|
|
983
|
+
disposeSocket();
|
|
984
|
+
}
|
|
985
|
+
state = { __type: 'connecting', associationKeypair };
|
|
986
|
+
if (connectionStartTime === undefined) {
|
|
987
|
+
connectionStartTime = Date.now();
|
|
988
|
+
}
|
|
989
|
+
socket = new WebSocket(websocketURL, [WEBSOCKET_PROTOCOL_BINARY, WEBSOCKET_PROTOCOL_BASE64]);
|
|
990
|
+
socket.addEventListener('open', handleOpen);
|
|
991
|
+
socket.addEventListener('close', handleClose);
|
|
992
|
+
socket.addEventListener('error', handleError);
|
|
993
|
+
socket.addEventListener('message', handleReflectorIdMessage);
|
|
994
|
+
disposeSocket = () => {
|
|
995
|
+
window.clearTimeout(retryWaitTimeoutId);
|
|
996
|
+
socket.removeEventListener('open', handleOpen);
|
|
997
|
+
socket.removeEventListener('close', handleClose);
|
|
998
|
+
socket.removeEventListener('error', handleError);
|
|
999
|
+
socket.removeEventListener('message', handleReflectorIdMessage);
|
|
1030
1000
|
};
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1001
|
+
};
|
|
1002
|
+
attemptSocketConnection();
|
|
1003
|
+
});
|
|
1004
|
+
// Wallet Connection Phase
|
|
1005
|
+
// here we return the association URL (containing the reflector ID) to the caller +
|
|
1006
|
+
// a promise that will resolve the MobileWallet object once the wallet connects.
|
|
1007
|
+
let sessionEstablished = false;
|
|
1008
|
+
let handleClose;
|
|
1009
|
+
return { associationUrl, close: () => {
|
|
1010
|
+
socket.close();
|
|
1011
|
+
handleClose();
|
|
1012
|
+
}, wallet: new Promise((resolve, reject) => {
|
|
1013
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1014
|
+
const jsonRpcResponsePromises = {};
|
|
1015
|
+
const handleMessage = async (evt) => {
|
|
1016
|
+
const responseBuffer = await decodeBytes(evt);
|
|
1017
|
+
switch (state.__type) {
|
|
1018
|
+
case 'reflector_id_received':
|
|
1019
|
+
if (responseBuffer.byteLength !== 0) {
|
|
1020
|
+
throw new Error('Encountered unexpected message while awaiting reflection');
|
|
1021
|
+
}
|
|
1022
|
+
const ecdhKeypair = await generateECDHKeypair();
|
|
1023
|
+
const binaryMsg = await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey);
|
|
1024
|
+
if (encoding == 'base64') {
|
|
1025
|
+
socket.send(fromUint8Array$1(binaryMsg));
|
|
1026
|
+
}
|
|
1027
|
+
else {
|
|
1028
|
+
socket.send(binaryMsg);
|
|
1029
|
+
}
|
|
1030
|
+
state = {
|
|
1031
|
+
__type: 'hello_req_sent',
|
|
1032
|
+
associationPublicKey: associationKeypair.publicKey,
|
|
1033
|
+
ecdhPrivateKey: ecdhKeypair.privateKey,
|
|
1034
|
+
};
|
|
1035
|
+
break;
|
|
1036
|
+
case 'connected':
|
|
1037
|
+
try {
|
|
1038
|
+
const sequenceNumberVector = responseBuffer.slice(0, SEQUENCE_NUMBER_BYTES);
|
|
1039
|
+
const sequenceNumber = getSequenceNumberFromByteArray(sequenceNumberVector);
|
|
1040
|
+
if (sequenceNumber !== (lastKnownInboundSequenceNumber + 1)) {
|
|
1041
|
+
throw new Error('Encrypted message has invalid sequence number');
|
|
1050
1042
|
}
|
|
1051
|
-
|
|
1052
|
-
const
|
|
1053
|
-
|
|
1054
|
-
|
|
1043
|
+
lastKnownInboundSequenceNumber = sequenceNumber;
|
|
1044
|
+
const jsonRpcMessage = await decryptJsonRpcMessage(responseBuffer, state.sharedSecret);
|
|
1045
|
+
const responsePromise = jsonRpcResponsePromises[jsonRpcMessage.id];
|
|
1046
|
+
delete jsonRpcResponsePromises[jsonRpcMessage.id];
|
|
1047
|
+
responsePromise.resolve(jsonRpcMessage.result);
|
|
1048
|
+
}
|
|
1049
|
+
catch (e) {
|
|
1050
|
+
if (e instanceof SolanaMobileWalletAdapterProtocolError) {
|
|
1051
|
+
const responsePromise = jsonRpcResponsePromises[e.jsonRpcMessageId];
|
|
1052
|
+
delete jsonRpcResponsePromises[e.jsonRpcMessageId];
|
|
1053
|
+
responsePromise.reject(e);
|
|
1055
1054
|
}
|
|
1056
1055
|
else {
|
|
1057
|
-
|
|
1056
|
+
throw e;
|
|
1058
1057
|
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
const sequenceNumberVector = responseBuffer.slice(0, SEQUENCE_NUMBER_BYTES);
|
|
1058
|
+
}
|
|
1059
|
+
break;
|
|
1060
|
+
case 'hello_req_sent': {
|
|
1061
|
+
const sharedSecret = await parseHelloRsp(responseBuffer, state.associationPublicKey, state.ecdhPrivateKey);
|
|
1062
|
+
const sessionPropertiesBuffer = responseBuffer.slice(ENCODED_PUBLIC_KEY_LENGTH_BYTES);
|
|
1063
|
+
const sessionProperties = sessionPropertiesBuffer.byteLength !== 0
|
|
1064
|
+
? await (async () => {
|
|
1065
|
+
const sequenceNumberVector = sessionPropertiesBuffer.slice(0, SEQUENCE_NUMBER_BYTES);
|
|
1068
1066
|
const sequenceNumber = getSequenceNumberFromByteArray(sequenceNumberVector);
|
|
1069
1067
|
if (sequenceNumber !== (lastKnownInboundSequenceNumber + 1)) {
|
|
1070
1068
|
throw new Error('Encrypted message has invalid sequence number');
|
|
1071
1069
|
}
|
|
1072
1070
|
lastKnownInboundSequenceNumber = sequenceNumber;
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1071
|
+
return parseSessionProps(sessionPropertiesBuffer, sharedSecret);
|
|
1072
|
+
})() : { protocol_version: 'legacy' };
|
|
1073
|
+
state = { __type: 'connected', sharedSecret, sessionProperties };
|
|
1074
|
+
const wallet = createMobileWalletProxy(sessionProperties.protocol_version, async (method, params) => {
|
|
1075
|
+
const id = nextJsonRpcMessageId++;
|
|
1076
|
+
const binaryMsg = await encryptJsonRpcMessage({
|
|
1077
|
+
id,
|
|
1078
|
+
jsonrpc: '2.0',
|
|
1079
|
+
method,
|
|
1080
|
+
params: params ?? {},
|
|
1081
|
+
}, sharedSecret);
|
|
1082
|
+
if (encoding == 'base64') {
|
|
1083
|
+
socket.send(fromUint8Array$1(binaryMsg));
|
|
1077
1084
|
}
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
const responsePromise = jsonRpcResponsePromises[e.jsonRpcMessageId];
|
|
1081
|
-
delete jsonRpcResponsePromises[e.jsonRpcMessageId];
|
|
1082
|
-
responsePromise.reject(e);
|
|
1083
|
-
}
|
|
1084
|
-
else {
|
|
1085
|
-
throw e;
|
|
1086
|
-
}
|
|
1085
|
+
else {
|
|
1086
|
+
socket.send(binaryMsg);
|
|
1087
1087
|
}
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
state = { __type: 'connected', sharedSecret, sessionProperties };
|
|
1103
|
-
const wallet = createMobileWalletProxy(sessionProperties.protocol_version, (method, params) => __awaiter(this, void 0, void 0, function* () {
|
|
1104
|
-
const id = nextJsonRpcMessageId++;
|
|
1105
|
-
const binaryMsg = yield encryptJsonRpcMessage({
|
|
1106
|
-
id,
|
|
1107
|
-
jsonrpc: '2.0',
|
|
1108
|
-
method,
|
|
1109
|
-
params: params !== null && params !== void 0 ? params : {},
|
|
1110
|
-
}, sharedSecret);
|
|
1111
|
-
if (encoding == 'base64') {
|
|
1112
|
-
socket.send(fromUint8Array$1(binaryMsg));
|
|
1113
|
-
}
|
|
1114
|
-
else {
|
|
1115
|
-
socket.send(binaryMsg);
|
|
1116
|
-
}
|
|
1117
|
-
return new Promise((resolve, reject) => {
|
|
1118
|
-
jsonRpcResponsePromises[id] = {
|
|
1119
|
-
resolve(result) {
|
|
1120
|
-
switch (method) {
|
|
1121
|
-
case 'authorize':
|
|
1122
|
-
case 'reauthorize': {
|
|
1123
|
-
const { wallet_uri_base } = result;
|
|
1124
|
-
if (wallet_uri_base != null) {
|
|
1125
|
-
try {
|
|
1126
|
-
assertSecureEndpointSpecificURI(wallet_uri_base);
|
|
1127
|
-
}
|
|
1128
|
-
catch (e) {
|
|
1129
|
-
reject(e);
|
|
1130
|
-
return;
|
|
1131
|
-
}
|
|
1088
|
+
return new Promise((resolve, reject) => {
|
|
1089
|
+
jsonRpcResponsePromises[id] = {
|
|
1090
|
+
resolve(result) {
|
|
1091
|
+
switch (method) {
|
|
1092
|
+
case 'authorize':
|
|
1093
|
+
case 'reauthorize': {
|
|
1094
|
+
const { wallet_uri_base } = result;
|
|
1095
|
+
if (wallet_uri_base != null) {
|
|
1096
|
+
try {
|
|
1097
|
+
assertSecureEndpointSpecificURI(wallet_uri_base);
|
|
1098
|
+
}
|
|
1099
|
+
catch (e) {
|
|
1100
|
+
reject(e);
|
|
1101
|
+
return;
|
|
1132
1102
|
}
|
|
1133
|
-
break;
|
|
1134
1103
|
}
|
|
1104
|
+
break;
|
|
1135
1105
|
}
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
}
|
|
1141
|
-
})
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
catch (e) {
|
|
1147
|
-
reject(e);
|
|
1148
|
-
}
|
|
1149
|
-
break;
|
|
1106
|
+
}
|
|
1107
|
+
resolve(result);
|
|
1108
|
+
},
|
|
1109
|
+
reject,
|
|
1110
|
+
};
|
|
1111
|
+
});
|
|
1112
|
+
});
|
|
1113
|
+
sessionEstablished = true;
|
|
1114
|
+
try {
|
|
1115
|
+
resolve(wallet);
|
|
1150
1116
|
}
|
|
1117
|
+
catch (e) {
|
|
1118
|
+
reject(e);
|
|
1119
|
+
}
|
|
1120
|
+
break;
|
|
1151
1121
|
}
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1122
|
+
}
|
|
1123
|
+
};
|
|
1124
|
+
socket.addEventListener('message', handleMessage);
|
|
1125
|
+
handleClose = () => {
|
|
1126
|
+
socket.removeEventListener('message', handleMessage);
|
|
1127
|
+
disposeSocket();
|
|
1128
|
+
if (!sessionEstablished) {
|
|
1129
|
+
reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session was closed before connection.`, { closeEvent: new CloseEvent('socket was closed before connection') }));
|
|
1130
|
+
}
|
|
1131
|
+
};
|
|
1132
|
+
}) };
|
|
1163
1133
|
}
|
|
1164
1134
|
|
|
1165
1135
|
exports.startRemoteScenario = startRemoteScenario;
|
|
1166
1136
|
exports.transact = transact;
|
|
1167
|
-
//# sourceMappingURL=index.browser-
|
|
1137
|
+
//# sourceMappingURL=index.browser-DjEZSiqI.js.map
|