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