@solana-mobile/mobile-wallet-adapter-protocol 2.2.6 → 2.2.8

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/lib/cjs/index.js CHANGED
@@ -1,1056 +1,915 @@
1
- 'use strict';
2
-
3
- var walletStandardUtil = require('@solana/wallet-standard-util');
4
- var codecsStrings = require('@solana/codecs-strings');
5
-
6
- // Typescript `enums` thwart tree-shaking. See https://bargsten.org/jsts/enums/
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _solana_codecs_strings = require("@solana/codecs-strings");
3
+ let _solana_wallet_standard_util = require("@solana/wallet-standard-util");
4
+ //#region src/errors.ts
7
5
  const SolanaMobileWalletAdapterErrorCode = {
8
- ERROR_ASSOCIATION_PORT_OUT_OF_RANGE: 'ERROR_ASSOCIATION_PORT_OUT_OF_RANGE',
9
- ERROR_REFLECTOR_ID_OUT_OF_RANGE: 'ERROR_REFLECTOR_ID_OUT_OF_RANGE',
10
- ERROR_FORBIDDEN_WALLET_BASE_URL: 'ERROR_FORBIDDEN_WALLET_BASE_URL',
11
- ERROR_SECURE_CONTEXT_REQUIRED: 'ERROR_SECURE_CONTEXT_REQUIRED',
12
- ERROR_SESSION_CLOSED: 'ERROR_SESSION_CLOSED',
13
- ERROR_SESSION_TIMEOUT: 'ERROR_SESSION_TIMEOUT',
14
- ERROR_WALLET_NOT_FOUND: 'ERROR_WALLET_NOT_FOUND',
15
- ERROR_INVALID_PROTOCOL_VERSION: 'ERROR_INVALID_PROTOCOL_VERSION',
16
- ERROR_BROWSER_NOT_SUPPORTED: 'ERROR_BROWSER_NOT_SUPPORTED',
17
- ERROR_LOOPBACK_ACCESS_BLOCKED: 'ERROR_LOOPBACK_ACCESS_BLOCKED',
18
- ERROR_ASSOCIATION_CANCELLED: 'ERROR_ASSOCIATION_CANCELLED',
6
+ ERROR_ASSOCIATION_PORT_OUT_OF_RANGE: "ERROR_ASSOCIATION_PORT_OUT_OF_RANGE",
7
+ ERROR_REFLECTOR_ID_OUT_OF_RANGE: "ERROR_REFLECTOR_ID_OUT_OF_RANGE",
8
+ ERROR_FORBIDDEN_WALLET_BASE_URL: "ERROR_FORBIDDEN_WALLET_BASE_URL",
9
+ ERROR_SECURE_CONTEXT_REQUIRED: "ERROR_SECURE_CONTEXT_REQUIRED",
10
+ ERROR_SESSION_CLOSED: "ERROR_SESSION_CLOSED",
11
+ ERROR_SESSION_TIMEOUT: "ERROR_SESSION_TIMEOUT",
12
+ ERROR_WALLET_NOT_FOUND: "ERROR_WALLET_NOT_FOUND",
13
+ ERROR_INVALID_PROTOCOL_VERSION: "ERROR_INVALID_PROTOCOL_VERSION",
14
+ ERROR_BROWSER_NOT_SUPPORTED: "ERROR_BROWSER_NOT_SUPPORTED",
15
+ ERROR_LOOPBACK_ACCESS_BLOCKED: "ERROR_LOOPBACK_ACCESS_BLOCKED",
16
+ ERROR_ASSOCIATION_CANCELLED: "ERROR_ASSOCIATION_CANCELLED"
17
+ };
18
+ var SolanaMobileWalletAdapterError = class extends Error {
19
+ data;
20
+ code;
21
+ constructor(...args) {
22
+ const [code, message, data] = args;
23
+ super(message);
24
+ this.code = code;
25
+ this.data = data;
26
+ this.name = "SolanaMobileWalletAdapterError";
27
+ }
19
28
  };
20
- class SolanaMobileWalletAdapterError extends Error {
21
- data;
22
- code;
23
- constructor(...args) {
24
- const [code, message, data] = args;
25
- super(message);
26
- this.code = code;
27
- this.data = data;
28
- this.name = 'SolanaMobileWalletAdapterError';
29
- }
30
- }
31
- // Typescript `enums` thwart tree-shaking. See https://bargsten.org/jsts/enums/
32
29
  const SolanaMobileWalletAdapterProtocolErrorCode = {
33
- // Keep these in sync with `mobilewalletadapter/common/ProtocolContract.java`.
34
- ERROR_AUTHORIZATION_FAILED: -1,
35
- ERROR_INVALID_PAYLOADS: -2,
36
- ERROR_NOT_SIGNED: -3,
37
- ERROR_NOT_SUBMITTED: -4,
38
- ERROR_TOO_MANY_PAYLOADS: -5,
39
- ERROR_ATTEST_ORIGIN_ANDROID: -100,
30
+ ERROR_AUTHORIZATION_FAILED: -1,
31
+ ERROR_INVALID_PAYLOADS: -2,
32
+ ERROR_NOT_SIGNED: -3,
33
+ ERROR_NOT_SUBMITTED: -4,
34
+ ERROR_TOO_MANY_PAYLOADS: -5,
35
+ ERROR_ATTEST_ORIGIN_ANDROID: -100
40
36
  };
41
- class SolanaMobileWalletAdapterProtocolError extends Error {
42
- data;
43
- code;
44
- jsonRpcMessageId;
45
- constructor(...args) {
46
- const [jsonRpcMessageId, code, message, data] = args;
47
- super(message);
48
- this.code = code;
49
- this.data = data;
50
- this.jsonRpcMessageId = jsonRpcMessageId;
51
- this.name = 'SolanaMobileWalletAdapterProtocolError';
52
- }
53
- }
54
-
37
+ var SolanaMobileWalletAdapterProtocolError = class extends Error {
38
+ data;
39
+ code;
40
+ jsonRpcMessageId;
41
+ constructor(...args) {
42
+ const [jsonRpcMessageId, code, message, data] = args;
43
+ super(message);
44
+ this.code = code;
45
+ this.data = data;
46
+ this.jsonRpcMessageId = jsonRpcMessageId;
47
+ this.name = "SolanaMobileWalletAdapterProtocolError";
48
+ }
49
+ };
50
+ //#endregion
51
+ //#region src/base64Utils.ts
55
52
  function encode(input) {
56
- return window.btoa(input);
53
+ return window.btoa(input);
57
54
  }
58
55
  function fromUint8Array$1(byteArray, urlsafe) {
59
- const base64 = window.btoa(String.fromCharCode.call(null, ...byteArray));
60
- if (urlsafe) {
61
- return base64
62
- .replace(/\+/g, '-')
63
- .replace(/\//g, '_')
64
- .replace(/=+$/, '');
65
- }
66
- else
67
- return base64;
56
+ const base64 = window.btoa(String.fromCharCode.call(null, ...byteArray));
57
+ if (urlsafe) return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
58
+ else return base64;
68
59
  }
69
60
  function toUint8Array(base64EncodedByteArray) {
70
- return new Uint8Array(window
71
- .atob(base64EncodedByteArray)
72
- .split('')
73
- .map((c) => c.charCodeAt(0)));
61
+ return new Uint8Array(window.atob(base64EncodedByteArray).split("").map((c) => c.charCodeAt(0)));
74
62
  }
75
-
63
+ //#endregion
64
+ //#region src/createHelloReq.ts
76
65
  async function createHelloReq(ecdhPublicKey, associationKeypairPrivateKey) {
77
- const publicKeyBuffer = await crypto.subtle.exportKey('raw', ecdhPublicKey);
78
- const signatureBuffer = await crypto.subtle.sign({ hash: 'SHA-256', name: 'ECDSA' }, associationKeypairPrivateKey, publicKeyBuffer);
79
- const response = new Uint8Array(publicKeyBuffer.byteLength + signatureBuffer.byteLength);
80
- response.set(new Uint8Array(publicKeyBuffer), 0);
81
- response.set(new Uint8Array(signatureBuffer), publicKeyBuffer.byteLength);
82
- return response;
83
- }
84
-
85
- function createSIWSMessage(payload) {
86
- return walletStandardUtil.createSignInMessageText(payload);
87
- }
88
- function createSIWSMessageBase64Url(payload) {
89
- return encode(createSIWSMessage(payload))
90
- .replace(/\+/g, '-')
91
- .replace(/\//g, '_')
92
- .replace(/=+$/, ''); // convert to base64url encoding;
93
- }
94
-
95
- // optional features
96
- const SolanaSignTransactions = 'solana:signTransactions';
97
- const SolanaCloneAuthorization = 'solana:cloneAuthorization';
98
- const SolanaSignInWithSolana = 'solana:signInWithSolana';
99
-
66
+ const publicKeyBuffer = await crypto.subtle.exportKey("raw", ecdhPublicKey);
67
+ const signatureBuffer = await crypto.subtle.sign({
68
+ hash: "SHA-256",
69
+ name: "ECDSA"
70
+ }, associationKeypairPrivateKey, publicKeyBuffer);
71
+ const response = new Uint8Array(publicKeyBuffer.byteLength + signatureBuffer.byteLength);
72
+ response.set(new Uint8Array(publicKeyBuffer), 0);
73
+ response.set(new Uint8Array(signatureBuffer), publicKeyBuffer.byteLength);
74
+ return response;
75
+ }
76
+ //#endregion
77
+ //#region src/base58Utils.ts
100
78
  function fromUint8Array(byteArray) {
101
- return codecsStrings.getBase58Decoder().decode(byteArray);
79
+ return (0, _solana_codecs_strings.getBase58Decoder)().decode(byteArray);
102
80
  }
103
81
  function base64ToBase58(base64EncodedString) {
104
- return fromUint8Array(toUint8Array(base64EncodedString));
82
+ return fromUint8Array(toUint8Array(base64EncodedString));
105
83
  }
106
-
84
+ //#endregion
85
+ //#region src/createSIWSMessage.ts
86
+ function createSIWSMessage(payload) {
87
+ return (0, _solana_wallet_standard_util.createSignInMessageText)(payload);
88
+ }
89
+ function createSIWSMessageBase64Url(payload) {
90
+ return encode(createSIWSMessage(payload)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
91
+ }
92
+ //#endregion
93
+ //#region src/types.ts
94
+ const SolanaSignTransactions = "solana:signTransactions";
95
+ const SolanaCloneAuthorization = "solana:cloneAuthorization";
96
+ const SolanaSignInWithSolana = "solana:signInWithSolana";
97
+ //#endregion
98
+ //#region src/createMobileWalletProxy.ts
107
99
  /**
108
- * Creates a {@link MobileWallet} proxy that handles backwards compatibility and API to RPC conversion.
109
- *
110
- * @param protocolVersion the protocol version in use for this session/request
111
- * @param protocolRequestHandler callback function that handles sending the RPC request to the wallet endpoint.
112
- * @returns a {@link MobileWallet} proxy
113
- */
100
+ * Creates a {@link MobileWallet} proxy that handles backwards compatibility and API to RPC conversion.
101
+ *
102
+ * @param protocolVersion the protocol version in use for this session/request
103
+ * @param protocolRequestHandler callback function that handles sending the RPC request to the wallet endpoint.
104
+ * @returns a {@link MobileWallet} proxy
105
+ */
114
106
  function createMobileWalletProxy(protocolVersion, protocolRequestHandler) {
115
- return new Proxy({}, {
116
- get(target, p) {
117
- // Wrapping a Proxy in a promise results in the Proxy being asked for a 'then' property so must
118
- // return null if 'then' is called on this proxy to let the 'resolve()' call know this is not a promise.
119
- // see: https://stackoverflow.com/a/53890904
120
- //@ts-ignore
121
- if (p === 'then') {
122
- return null;
123
- }
124
- if (target[p] == null) {
125
- target[p] = async function (inputParams) {
126
- const { method, params } = handleMobileWalletRequest(p, inputParams, protocolVersion);
127
- const result = await protocolRequestHandler(method, params);
128
- // if the request tried to sign in but the wallet did not return a sign in result, fallback on message signing
129
- if (method === 'authorize' && params.sign_in_payload && !result.sign_in_result) {
130
- result['sign_in_result'] = await signInFallback(params.sign_in_payload, result, protocolRequestHandler);
131
- }
132
- return handleMobileWalletResponse(p, result, protocolVersion);
133
- };
134
- }
135
- return target[p];
136
- },
137
- defineProperty() {
138
- return false;
139
- },
140
- deleteProperty() {
141
- return false;
142
- },
143
- });
107
+ return new Proxy({}, {
108
+ get(target, p) {
109
+ if (p === "then") return null;
110
+ if (target[p] == null) target[p] = async function(inputParams) {
111
+ const { method, params } = handleMobileWalletRequest(p, inputParams, protocolVersion);
112
+ const result = await protocolRequestHandler(method, params);
113
+ if (method === "authorize" && params.sign_in_payload && !result.sign_in_result) result.sign_in_result = await signInFallback(params.sign_in_payload, result, protocolRequestHandler);
114
+ return handleMobileWalletResponse(p, result, protocolVersion);
115
+ };
116
+ return target[p];
117
+ },
118
+ defineProperty() {
119
+ return false;
120
+ },
121
+ deleteProperty() {
122
+ return false;
123
+ }
124
+ });
144
125
  }
145
126
  /**
146
- * Handles all {@link MobileWallet} API requests and determines the correct MWA RPC method and params to call.
147
- * This handles backwards compatibility, based on the provided @protocolVersion.
148
- *
149
- * @param methodName the name of {@link MobileWallet} method that was called
150
- * @param methodParams the parameters that were passed to the method
151
- * @param protocolVersion the protocol version in use for this session/request
152
- * @returns the RPC request method and params that should be sent to the wallet endpoint
153
- */
127
+ * Handles all {@link MobileWallet} API requests and determines the correct MWA RPC method and params to call.
128
+ * This handles backwards compatibility, based on the provided @protocolVersion.
129
+ *
130
+ * @param methodName the name of {@link MobileWallet} method that was called
131
+ * @param methodParams the parameters that were passed to the method
132
+ * @param protocolVersion the protocol version in use for this session/request
133
+ * @returns the RPC request method and params that should be sent to the wallet endpoint
134
+ */
154
135
  function handleMobileWalletRequest(methodName, methodParams, protocolVersion) {
155
- let params = methodParams;
156
- let method = methodName
157
- .toString()
158
- .replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)
159
- .toLowerCase();
160
- switch (methodName) {
161
- case 'authorize': {
162
- let { chain } = params;
163
- if (protocolVersion === 'legacy') {
164
- switch (chain) {
165
- case 'solana:testnet': {
166
- chain = 'testnet';
167
- break;
168
- }
169
- case 'solana:devnet': {
170
- chain = 'devnet';
171
- break;
172
- }
173
- case 'solana:mainnet': {
174
- chain = 'mainnet-beta';
175
- break;
176
- }
177
- default: {
178
- chain = params.cluster;
179
- }
180
- }
181
- params.cluster = chain;
182
- }
183
- else {
184
- switch (chain) {
185
- case 'testnet':
186
- case 'devnet': {
187
- chain = `solana:${chain}`;
188
- break;
189
- }
190
- case 'mainnet-beta': {
191
- chain = 'solana:mainnet';
192
- break;
193
- }
194
- }
195
- params.chain = chain;
196
- }
197
- }
198
- case 'reauthorize': {
199
- const { auth_token, identity } = params;
200
- if (auth_token) {
201
- switch (protocolVersion) {
202
- case 'legacy': {
203
- method = 'reauthorize';
204
- params = { auth_token: auth_token, identity: identity };
205
- break;
206
- }
207
- default: {
208
- method = 'authorize';
209
- break;
210
- }
211
- }
212
- }
213
- break;
214
- }
215
- }
216
- return { method, params };
136
+ let params = methodParams;
137
+ let method = methodName.toString().replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`).toLowerCase();
138
+ switch (methodName) {
139
+ case "authorize": {
140
+ const authorizeParams = params;
141
+ let { chain } = authorizeParams;
142
+ if (protocolVersion === "legacy") {
143
+ switch (chain) {
144
+ case "solana:testnet":
145
+ chain = "testnet";
146
+ break;
147
+ case "solana:devnet":
148
+ chain = "devnet";
149
+ break;
150
+ case "solana:mainnet":
151
+ chain = "mainnet-beta";
152
+ break;
153
+ default: chain = authorizeParams.cluster;
154
+ }
155
+ authorizeParams.cluster = chain;
156
+ params = authorizeParams;
157
+ } else {
158
+ switch (chain) {
159
+ case "testnet":
160
+ case "devnet":
161
+ chain = `solana:${chain}`;
162
+ break;
163
+ case "mainnet-beta":
164
+ chain = "solana:mainnet";
165
+ break;
166
+ }
167
+ authorizeParams.chain = chain;
168
+ params = authorizeParams;
169
+ }
170
+ }
171
+ case "reauthorize": {
172
+ const { auth_token, identity } = params;
173
+ if (auth_token) switch (protocolVersion) {
174
+ case "legacy":
175
+ method = "reauthorize";
176
+ params = {
177
+ auth_token,
178
+ identity
179
+ };
180
+ break;
181
+ default:
182
+ method = "authorize";
183
+ break;
184
+ }
185
+ break;
186
+ }
187
+ }
188
+ return {
189
+ method,
190
+ params
191
+ };
217
192
  }
218
193
  /**
219
- * Handles all {@link MobileWallet} API responses and modifies the response for backwards compatibility, if needed
220
- *
221
- * @param method the {@link MobileWallet} method that was called
222
- * @param response the original response that was returned by the method call
223
- * @param protocolVersion the protocol version in use for this session/request
224
- * @returns the possibly modified response
225
- */
194
+ * Handles all {@link MobileWallet} API responses and modifies the response for backwards compatibility, if needed
195
+ *
196
+ * @param method the {@link MobileWallet} method that was called
197
+ * @param response the original response that was returned by the method call
198
+ * @param protocolVersion the protocol version in use for this session/request
199
+ * @returns the possibly modified response
200
+ */
226
201
  function handleMobileWalletResponse(method, response, protocolVersion) {
227
- switch (method) {
228
- case 'getCapabilities': {
229
- const capabilities = response;
230
- switch (protocolVersion) {
231
- case 'legacy': {
232
- const features = [SolanaSignTransactions];
233
- if (capabilities.supports_clone_authorization === true) {
234
- features.push(SolanaCloneAuthorization);
235
- }
236
- return {
237
- ...capabilities,
238
- features: features,
239
- };
240
- }
241
- case 'v1': {
242
- return {
243
- ...capabilities,
244
- supports_sign_and_send_transactions: true,
245
- supports_clone_authorization: capabilities.features.includes(SolanaCloneAuthorization)
246
- };
247
- }
248
- }
249
- }
250
- }
251
- return response;
202
+ switch (method) {
203
+ case "getCapabilities": {
204
+ const capabilities = response;
205
+ switch (protocolVersion) {
206
+ case "legacy": {
207
+ const features = [SolanaSignTransactions];
208
+ if (capabilities.supports_clone_authorization === true) features.push(SolanaCloneAuthorization);
209
+ return {
210
+ ...capabilities,
211
+ features
212
+ };
213
+ }
214
+ case "v1": return {
215
+ ...capabilities,
216
+ supports_sign_and_send_transactions: true,
217
+ supports_clone_authorization: capabilities.features.includes(SolanaCloneAuthorization)
218
+ };
219
+ }
220
+ }
221
+ }
222
+ return response;
252
223
  }
253
224
  async function signInFallback(signInPayload, authorizationResult, protocolRequestHandler) {
254
- const domain = signInPayload.domain ?? window.location.host;
255
- const address = authorizationResult.accounts[0].address;
256
- const siwsMessage = createSIWSMessageBase64Url({ ...signInPayload, domain, address: base64ToBase58(address) });
257
- const signMessageResult = await protocolRequestHandler('sign_messages', {
258
- addresses: [address],
259
- payloads: [siwsMessage]
260
- });
261
- const signedPayload = toUint8Array(signMessageResult.signed_payloads[0]);
262
- const signedMessage = fromUint8Array$1(signedPayload.slice(0, signedPayload.length - 64));
263
- const signature = fromUint8Array$1(signedPayload.slice(signedPayload.length - 64));
264
- const signInResult = {
265
- address: address,
266
- // Workaround: some wallets have been observed to only reply with the message signature.
267
- // This is non-compliant with the spec, but in the interest of maximizing compatibility,
268
- // detect this case and reuse the original message.
269
- signed_message: signedMessage.length == 0 ? siwsMessage : signedMessage,
270
- signature
271
- };
272
- return signInResult;
225
+ const domain = signInPayload.domain ?? window.location.host;
226
+ const address = authorizationResult.accounts[0].address;
227
+ const siwsMessage = createSIWSMessageBase64Url({
228
+ ...signInPayload,
229
+ domain,
230
+ address: base64ToBase58(address)
231
+ });
232
+ const signedPayload = toUint8Array((await protocolRequestHandler("sign_messages", {
233
+ addresses: [address],
234
+ payloads: [siwsMessage]
235
+ })).signed_payloads[0]);
236
+ const signedMessage = fromUint8Array$1(signedPayload.slice(0, signedPayload.length - 64));
237
+ const signature = fromUint8Array$1(signedPayload.slice(signedPayload.length - 64));
238
+ return {
239
+ address,
240
+ signed_message: signedMessage.length == 0 ? siwsMessage : signedMessage,
241
+ signature
242
+ };
273
243
  }
274
-
275
- const SEQUENCE_NUMBER_BYTES = 4;
276
244
  function createSequenceNumberVector(sequenceNumber) {
277
- if (sequenceNumber >= 4294967296) {
278
- throw new Error('Outbound sequence number overflow. The maximum sequence number is 32-bytes.');
279
- }
280
- const byteArray = new ArrayBuffer(SEQUENCE_NUMBER_BYTES);
281
- const view = new DataView(byteArray);
282
- view.setUint32(0, sequenceNumber, /* littleEndian */ false);
283
- return new Uint8Array(byteArray);
245
+ if (sequenceNumber >= 4294967296) throw new Error("Outbound sequence number overflow. The maximum sequence number is 32-bytes.");
246
+ const byteArray = /* @__PURE__ */ new ArrayBuffer(4);
247
+ new DataView(byteArray).setUint32(0, sequenceNumber, false);
248
+ return new Uint8Array(byteArray);
284
249
  }
285
-
250
+ //#endregion
251
+ //#region src/encryptedMessage.ts
286
252
  const INITIALIZATION_VECTOR_BYTES = 12;
287
- const ENCODED_PUBLIC_KEY_LENGTH_BYTES = 65;
288
253
  async function encryptMessage(plaintext, sequenceNumber, sharedSecret) {
289
- const sequenceNumberVector = createSequenceNumberVector(sequenceNumber);
290
- const initializationVector = new Uint8Array(INITIALIZATION_VECTOR_BYTES);
291
- crypto.getRandomValues(initializationVector);
292
- const ciphertext = await crypto.subtle.encrypt(getAlgorithmParams(sequenceNumberVector, initializationVector), sharedSecret, new TextEncoder().encode(plaintext));
293
- const response = new Uint8Array(sequenceNumberVector.byteLength + initializationVector.byteLength + ciphertext.byteLength);
294
- response.set(new Uint8Array(sequenceNumberVector), 0);
295
- response.set(new Uint8Array(initializationVector), sequenceNumberVector.byteLength);
296
- response.set(new Uint8Array(ciphertext), sequenceNumberVector.byteLength + initializationVector.byteLength);
297
- return response;
254
+ const sequenceNumberVector = createSequenceNumberVector(sequenceNumber);
255
+ const initializationVector = new Uint8Array(INITIALIZATION_VECTOR_BYTES);
256
+ crypto.getRandomValues(initializationVector);
257
+ const ciphertext = await crypto.subtle.encrypt(getAlgorithmParams(sequenceNumberVector, initializationVector), sharedSecret, new TextEncoder().encode(plaintext));
258
+ const response = new Uint8Array(sequenceNumberVector.byteLength + initializationVector.byteLength + ciphertext.byteLength);
259
+ response.set(new Uint8Array(sequenceNumberVector), 0);
260
+ response.set(new Uint8Array(initializationVector), sequenceNumberVector.byteLength);
261
+ response.set(new Uint8Array(ciphertext), sequenceNumberVector.byteLength + initializationVector.byteLength);
262
+ return response;
298
263
  }
299
264
  async function decryptMessage(message, sharedSecret) {
300
- const sequenceNumberVector = message.slice(0, SEQUENCE_NUMBER_BYTES);
301
- const initializationVector = message.slice(SEQUENCE_NUMBER_BYTES, SEQUENCE_NUMBER_BYTES + INITIALIZATION_VECTOR_BYTES);
302
- const ciphertext = message.slice(SEQUENCE_NUMBER_BYTES + INITIALIZATION_VECTOR_BYTES);
303
- const plaintextBuffer = await crypto.subtle.decrypt(getAlgorithmParams(sequenceNumberVector, initializationVector), sharedSecret, ciphertext);
304
- const plaintext = getUtf8Decoder().decode(plaintextBuffer);
305
- return plaintext;
265
+ const sequenceNumberVector = message.slice(0, 4);
266
+ const initializationVector = message.slice(4, 4 + INITIALIZATION_VECTOR_BYTES);
267
+ const ciphertext = message.slice(4 + INITIALIZATION_VECTOR_BYTES);
268
+ const plaintextBuffer = await crypto.subtle.decrypt(getAlgorithmParams(sequenceNumberVector, initializationVector), sharedSecret, ciphertext);
269
+ return getUtf8Decoder().decode(plaintextBuffer);
306
270
  }
307
271
  function getAlgorithmParams(sequenceNumber, initializationVector) {
308
- return {
309
- additionalData: sequenceNumber,
310
- iv: initializationVector,
311
- name: 'AES-GCM',
312
- tagLength: 128, // 16 byte tag => 128 bits
313
- };
272
+ return {
273
+ additionalData: sequenceNumber,
274
+ iv: initializationVector,
275
+ name: "AES-GCM",
276
+ tagLength: 128
277
+ };
314
278
  }
315
279
  let _utf8Decoder;
316
280
  function getUtf8Decoder() {
317
- if (_utf8Decoder === undefined) {
318
- _utf8Decoder = new TextDecoder('utf-8');
319
- }
320
- return _utf8Decoder;
281
+ if (_utf8Decoder === void 0) _utf8Decoder = new TextDecoder("utf-8");
282
+ return _utf8Decoder;
321
283
  }
322
-
284
+ //#endregion
285
+ //#region src/generateAssociationKeypair.ts
323
286
  async function generateAssociationKeypair() {
324
- return await crypto.subtle.generateKey({
325
- name: 'ECDSA',
326
- namedCurve: 'P-256',
327
- }, false /* extractable */, ['sign'] /* keyUsages */);
287
+ return await crypto.subtle.generateKey({
288
+ name: "ECDSA",
289
+ namedCurve: "P-256"
290
+ }, false, ["sign"]);
328
291
  }
329
-
292
+ //#endregion
293
+ //#region src/generateECDHKeypair.ts
330
294
  async function generateECDHKeypair() {
331
- return await crypto.subtle.generateKey({
332
- name: 'ECDH',
333
- namedCurve: 'P-256',
334
- }, false /* extractable */, ['deriveKey', 'deriveBits'] /* keyUsages */);
295
+ return await crypto.subtle.generateKey({
296
+ name: "ECDH",
297
+ namedCurve: "P-256"
298
+ }, false, ["deriveKey", "deriveBits"]);
335
299
  }
336
-
337
- // https://stackoverflow.com/a/9458996/802047
300
+ //#endregion
301
+ //#region src/arrayBufferToBase64String.ts
338
302
  function arrayBufferToBase64String(buffer) {
339
- let binary = '';
340
- const bytes = new Uint8Array(buffer);
341
- const len = bytes.byteLength;
342
- for (let ii = 0; ii < len; ii++) {
343
- binary += String.fromCharCode(bytes[ii]);
344
- }
345
- return window.btoa(binary);
346
- }
347
-
303
+ let binary = "";
304
+ const bytes = new Uint8Array(buffer);
305
+ const len = bytes.byteLength;
306
+ for (let ii = 0; ii < len; ii++) binary += String.fromCharCode(bytes[ii]);
307
+ return window.btoa(binary);
308
+ }
309
+ //#endregion
310
+ //#region src/associationPort.ts
348
311
  function getRandomAssociationPort() {
349
- return assertAssociationPort(49152 + Math.floor(Math.random() * (65535 - 49152 + 1)));
312
+ return assertAssociationPort(49152 + Math.floor(Math.random() * 16384));
350
313
  }
351
314
  function assertAssociationPort(port) {
352
- if (port < 49152 || port > 65535) {
353
- throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_ASSOCIATION_PORT_OUT_OF_RANGE, `Association port number must be between 49152 and 65535. ${port} given.`, { port });
354
- }
355
- return port;
315
+ if (port < 49152 || port > 65535) throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_ASSOCIATION_PORT_OUT_OF_RANGE, `Association port number must be between 49152 and 65535. ${port} given.`, { port });
316
+ return port;
356
317
  }
357
-
318
+ //#endregion
319
+ //#region src/getStringWithURLUnsafeBase64CharactersReplaced.ts
358
320
  function getStringWithURLUnsafeCharactersReplaced(unsafeBase64EncodedString) {
359
- return unsafeBase64EncodedString.replace(/[/+=]/g, (m) => ({
360
- '/': '_',
361
- '+': '-',
362
- '=': '.',
363
- }[m]));
364
- }
365
-
366
- const INTENT_NAME = 'solana-wallet';
321
+ return unsafeBase64EncodedString.replace(/[/+=]/g, (m) => ({
322
+ "/": "_",
323
+ "+": "-",
324
+ "=": "."
325
+ })[m]);
326
+ }
327
+ //#endregion
328
+ //#region src/getAssociateAndroidIntentURL.ts
329
+ const INTENT_NAME = "solana-wallet";
367
330
  function getPathParts(pathString) {
368
- return (pathString
369
- // Strip leading and trailing slashes
370
- .replace(/(^\/+|\/+$)/g, '')
371
- // Return an array of directories
372
- .split('/'));
331
+ return pathString.replace(/(^\/+|\/+$)/g, "").split("/");
373
332
  }
374
333
  function getIntentURL(methodPathname, intentUrlBase) {
375
- let baseUrl = null;
376
- if (intentUrlBase) {
377
- try {
378
- baseUrl = new URL(intentUrlBase);
379
- }
380
- catch { } // eslint-disable-line no-empty
381
- if (baseUrl?.protocol !== 'https:') {
382
- throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_FORBIDDEN_WALLET_BASE_URL, 'Base URLs supplied by wallets must be valid `https` URLs');
383
- }
384
- }
385
- baseUrl ||= new URL(`${INTENT_NAME}:/`);
386
- const pathname = methodPathname.startsWith('/')
387
- ? // Method is an absolute path. Replace it wholesale.
388
- methodPathname
389
- : // Method is a relative path. Merge it with the existing one.
390
- [...getPathParts(baseUrl.pathname), ...getPathParts(methodPathname)].join('/');
391
- return new URL(pathname, baseUrl);
392
- }
393
- async function getAssociateAndroidIntentURL(associationPublicKey, putativePort, associationURLBase, protocolVersions = ['v1']) {
394
- const associationPort = assertAssociationPort(putativePort);
395
- const exportedKey = await crypto.subtle.exportKey('raw', associationPublicKey);
396
- const encodedKey = arrayBufferToBase64String(exportedKey);
397
- const url = getIntentURL('v1/associate/local', associationURLBase);
398
- url.searchParams.set('association', getStringWithURLUnsafeCharactersReplaced(encodedKey));
399
- url.searchParams.set('port', `${associationPort}`);
400
- protocolVersions.forEach((version) => {
401
- url.searchParams.set('v', version);
402
- });
403
- return url;
404
- }
405
- async function getRemoteAssociateAndroidIntentURL(associationPublicKey, hostAuthority, reflectorId, associationURLBase, protocolVersions = ['v1']) {
406
- const exportedKey = await crypto.subtle.exportKey('raw', associationPublicKey);
407
- const encodedKey = arrayBufferToBase64String(exportedKey);
408
- const url = getIntentURL('v1/associate/remote', associationURLBase);
409
- url.searchParams.set('association', getStringWithURLUnsafeCharactersReplaced(encodedKey));
410
- url.searchParams.set('reflector', `${hostAuthority}`);
411
- url.searchParams.set('id', `${fromUint8Array$1(reflectorId, true)}`);
412
- protocolVersions.forEach((version) => {
413
- url.searchParams.set('v', version);
414
- });
415
- return url;
416
- }
417
-
334
+ let baseUrl = null;
335
+ if (intentUrlBase) {
336
+ try {
337
+ baseUrl = new URL(intentUrlBase);
338
+ } catch {}
339
+ if (baseUrl?.protocol !== "https:") throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_FORBIDDEN_WALLET_BASE_URL, "Base URLs supplied by wallets must be valid `https` URLs");
340
+ }
341
+ baseUrl ||= new URL(`${INTENT_NAME}:/`);
342
+ const pathname = methodPathname.startsWith("/") ? methodPathname : [...getPathParts(baseUrl.pathname), ...getPathParts(methodPathname)].join("/");
343
+ return new URL(pathname, baseUrl);
344
+ }
345
+ async function getAssociateAndroidIntentURL(associationPublicKey, putativePort, associationURLBase, protocolVersions = ["v1"]) {
346
+ const associationPort = assertAssociationPort(putativePort);
347
+ const encodedKey = arrayBufferToBase64String(await crypto.subtle.exportKey("raw", associationPublicKey));
348
+ const url = getIntentURL("v1/associate/local", associationURLBase);
349
+ url.searchParams.set("association", getStringWithURLUnsafeCharactersReplaced(encodedKey));
350
+ url.searchParams.set("port", `${associationPort}`);
351
+ protocolVersions.forEach((version) => {
352
+ url.searchParams.set("v", version);
353
+ });
354
+ return url;
355
+ }
356
+ async function getRemoteAssociateAndroidIntentURL(associationPublicKey, hostAuthority, reflectorId, associationURLBase, protocolVersions = ["v1"]) {
357
+ const encodedKey = arrayBufferToBase64String(await crypto.subtle.exportKey("raw", associationPublicKey));
358
+ const url = getIntentURL("v1/associate/remote", associationURLBase);
359
+ url.searchParams.set("association", getStringWithURLUnsafeCharactersReplaced(encodedKey));
360
+ url.searchParams.set("reflector", `${hostAuthority}`);
361
+ url.searchParams.set("id", `${fromUint8Array$1(reflectorId, true)}`);
362
+ protocolVersions.forEach((version) => {
363
+ url.searchParams.set("v", version);
364
+ });
365
+ return url;
366
+ }
367
+ //#endregion
368
+ //#region src/jsonRpcMessage.ts
418
369
  async function encryptJsonRpcMessage(jsonRpcMessage, sharedSecret) {
419
- const plaintext = JSON.stringify(jsonRpcMessage);
420
- const sequenceNumber = jsonRpcMessage.id;
421
- return encryptMessage(plaintext, sequenceNumber, sharedSecret);
370
+ const plaintext = JSON.stringify(jsonRpcMessage);
371
+ const sequenceNumber = jsonRpcMessage.id;
372
+ return encryptMessage(plaintext, sequenceNumber, sharedSecret);
422
373
  }
423
374
  async function decryptJsonRpcMessage(message, sharedSecret) {
424
- const plaintext = await decryptMessage(message, sharedSecret);
425
- const jsonRpcMessage = JSON.parse(plaintext);
426
- if (Object.hasOwnProperty.call(jsonRpcMessage, 'error')) {
427
- throw new SolanaMobileWalletAdapterProtocolError(jsonRpcMessage.id, jsonRpcMessage.error.code, jsonRpcMessage.error.message);
428
- }
429
- return jsonRpcMessage;
430
- }
431
-
432
- async function parseHelloRsp(payloadBuffer, // The X9.62-encoded wallet endpoint ephemeral ECDH public keypoint.
433
- associationPublicKey, ecdhPrivateKey) {
434
- const [associationPublicKeyBuffer, walletPublicKey] = await Promise.all([
435
- crypto.subtle.exportKey('raw', associationPublicKey),
436
- crypto.subtle.importKey('raw', payloadBuffer.slice(0, ENCODED_PUBLIC_KEY_LENGTH_BYTES), { name: 'ECDH', namedCurve: 'P-256' }, false /* extractable */, [] /* keyUsages */),
437
- ]);
438
- const sharedSecret = await crypto.subtle.deriveBits({ name: 'ECDH', public: walletPublicKey }, ecdhPrivateKey, 256);
439
- const ecdhSecretKey = await crypto.subtle.importKey('raw', sharedSecret, 'HKDF', false /* extractable */, ['deriveKey'] /* keyUsages */);
440
- const aesKeyMaterialVal = await crypto.subtle.deriveKey({
441
- name: 'HKDF',
442
- hash: 'SHA-256',
443
- salt: new Uint8Array(associationPublicKeyBuffer),
444
- info: new Uint8Array(),
445
- }, ecdhSecretKey, { name: 'AES-GCM', length: 128 }, false /* extractable */, ['encrypt', 'decrypt']);
446
- return aesKeyMaterialVal;
447
- }
448
-
375
+ const plaintext = await decryptMessage(message, sharedSecret);
376
+ const jsonRpcMessage = JSON.parse(plaintext);
377
+ if (Object.hasOwnProperty.call(jsonRpcMessage, "error")) throw new SolanaMobileWalletAdapterProtocolError(jsonRpcMessage.id, jsonRpcMessage.error.code, jsonRpcMessage.error.message);
378
+ return jsonRpcMessage;
379
+ }
380
+ //#endregion
381
+ //#region src/parseHelloRsp.ts
382
+ async function parseHelloRsp(payloadBuffer, associationPublicKey, ecdhPrivateKey) {
383
+ const [associationPublicKeyBuffer, walletPublicKey] = await Promise.all([crypto.subtle.exportKey("raw", associationPublicKey), crypto.subtle.importKey("raw", payloadBuffer.slice(0, 65), {
384
+ name: "ECDH",
385
+ namedCurve: "P-256"
386
+ }, false, [])]);
387
+ const sharedSecret = await crypto.subtle.deriveBits({
388
+ name: "ECDH",
389
+ public: walletPublicKey
390
+ }, ecdhPrivateKey, 256);
391
+ const ecdhSecretKey = await crypto.subtle.importKey("raw", sharedSecret, "HKDF", false, ["deriveKey"]);
392
+ return await crypto.subtle.deriveKey({
393
+ name: "HKDF",
394
+ hash: "SHA-256",
395
+ salt: new Uint8Array(associationPublicKeyBuffer),
396
+ info: new Uint8Array()
397
+ }, ecdhSecretKey, {
398
+ name: "AES-GCM",
399
+ length: 128
400
+ }, false, ["encrypt", "decrypt"]);
401
+ }
402
+ //#endregion
403
+ //#region src/parseSessionProps.ts
449
404
  async function parseSessionProps(message, sharedSecret) {
450
- const plaintext = await decryptMessage(message, sharedSecret);
451
- const jsonProperties = JSON.parse(plaintext);
452
- let protocolVersion = 'legacy';
453
- if (Object.hasOwnProperty.call(jsonProperties, 'v')) {
454
- switch (jsonProperties.v) {
455
- case 1:
456
- case '1':
457
- case 'v1':
458
- protocolVersion = 'v1';
459
- break;
460
- case 'legacy':
461
- protocolVersion = 'legacy';
462
- break;
463
- default:
464
- throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_INVALID_PROTOCOL_VERSION, `Unknown/unsupported protocol version: ${jsonProperties.v}`);
465
- }
466
- }
467
- return ({
468
- protocol_version: protocolVersion
469
- });
470
- }
471
-
472
- // Typescript `enums` thwart tree-shaking. See https://bargsten.org/jsts/enums/
405
+ const plaintext = await decryptMessage(message, sharedSecret);
406
+ const jsonProperties = JSON.parse(plaintext);
407
+ let protocolVersion = "legacy";
408
+ if (Object.hasOwnProperty.call(jsonProperties, "v")) switch (jsonProperties.v) {
409
+ case 1:
410
+ case "1":
411
+ case "v1":
412
+ protocolVersion = "v1";
413
+ break;
414
+ case "legacy":
415
+ protocolVersion = "legacy";
416
+ break;
417
+ default: throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_INVALID_PROTOCOL_VERSION, `Unknown/unsupported protocol version: ${jsonProperties.v}`);
418
+ }
419
+ return { protocol_version: protocolVersion };
420
+ }
421
+ //#endregion
422
+ //#region src/startSession.ts
473
423
  const Browser = {
474
- Firefox: 0,
475
- Other: 1,
424
+ Firefox: 0,
425
+ Other: 1
476
426
  };
477
427
  function assertUnreachable(x) {
478
- return x;
428
+ return x;
479
429
  }
480
430
  function getBrowser() {
481
- return navigator.userAgent.indexOf('Firefox/') !== -1 ? Browser.Firefox : Browser.Other;
431
+ return navigator.userAgent.indexOf("Firefox/") !== -1 ? Browser.Firefox : Browser.Other;
482
432
  }
483
433
  function getDetectionPromise() {
484
- // Chrome and others silently fail if a custom protocol is not supported.
485
- // For these, we wait to see if the browser is navigated away from in
486
- // a reasonable amount of time (ie. the native wallet opened).
487
- return new Promise((resolve, reject) => {
488
- function cleanup() {
489
- clearTimeout(timeoutId);
490
- window.removeEventListener('blur', handleBlur);
491
- }
492
- function handleBlur() {
493
- cleanup();
494
- resolve();
495
- }
496
- window.addEventListener('blur', handleBlur);
497
- const timeoutId = setTimeout(() => {
498
- cleanup();
499
- reject();
500
- }, 3000);
501
- });
434
+ return new Promise((resolve, reject) => {
435
+ function cleanup() {
436
+ clearTimeout(timeoutId);
437
+ window.removeEventListener("blur", handleBlur);
438
+ }
439
+ function handleBlur() {
440
+ cleanup();
441
+ resolve();
442
+ }
443
+ window.addEventListener("blur", handleBlur);
444
+ const timeoutId = setTimeout(() => {
445
+ cleanup();
446
+ reject();
447
+ }, 3e3);
448
+ });
502
449
  }
503
450
  let _frame = null;
504
451
  function launchUrlThroughHiddenFrame(url) {
505
- if (_frame == null) {
506
- _frame = document.createElement('iframe');
507
- _frame.style.display = 'none';
508
- document.body.appendChild(_frame);
509
- }
510
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
511
- _frame.contentWindow.location.href = url.toString();
452
+ if (_frame == null) {
453
+ _frame = document.createElement("iframe");
454
+ _frame.style.display = "none";
455
+ document.body.appendChild(_frame);
456
+ }
457
+ _frame.contentWindow.location.href = url.toString();
512
458
  }
513
459
  async function launchAssociation(associationUrl) {
514
- if (associationUrl.protocol === 'https:') {
515
- // The association URL is an Android 'App Link' or iOS 'Universal Link'.
516
- // These are regular web URLs that are designed to launch an app if it
517
- // is installed or load the actual target webpage if not.
518
- window.location.assign(associationUrl);
519
- }
520
- else {
521
- // The association URL has a custom protocol (eg. `solana-wallet:`)
522
- try {
523
- const browser = getBrowser();
524
- switch (browser) {
525
- case Browser.Firefox:
526
- // If a custom protocol is not supported in Firefox, it throws.
527
- launchUrlThroughHiddenFrame(associationUrl);
528
- // If we reached this line, it's supported.
529
- break;
530
- case Browser.Other: {
531
- const detectionPromise = getDetectionPromise();
532
- window.location.assign(associationUrl);
533
- await detectionPromise;
534
- break;
535
- }
536
- default:
537
- assertUnreachable(browser);
538
- }
539
- }
540
- catch (e) {
541
- throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_WALLET_NOT_FOUND, 'Found no installed wallet that supports the mobile wallet protocol.');
542
- }
543
- }
460
+ if (associationUrl.protocol === "https:") window.location.assign(associationUrl);
461
+ else try {
462
+ const browser = getBrowser();
463
+ switch (browser) {
464
+ case Browser.Firefox:
465
+ launchUrlThroughHiddenFrame(associationUrl);
466
+ break;
467
+ case Browser.Other: {
468
+ const detectionPromise = getDetectionPromise();
469
+ window.location.assign(associationUrl);
470
+ await detectionPromise;
471
+ break;
472
+ }
473
+ default: assertUnreachable(browser);
474
+ }
475
+ } catch {
476
+ throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_WALLET_NOT_FOUND, "Found no installed wallet that supports the mobile wallet protocol.");
477
+ }
544
478
  }
545
479
  async function startSession(associationPublicKey, associationURLBase) {
546
- const randomAssociationPort = getRandomAssociationPort();
547
- const associationUrl = await getAssociateAndroidIntentURL(associationPublicKey, randomAssociationPort, associationURLBase);
548
- await launchAssociation(associationUrl);
549
- return randomAssociationPort;
480
+ const randomAssociationPort = getRandomAssociationPort();
481
+ await launchAssociation(await getAssociateAndroidIntentURL(associationPublicKey, randomAssociationPort, associationURLBase));
482
+ return randomAssociationPort;
550
483
  }
551
-
484
+ //#endregion
485
+ //#region src/transact.ts
552
486
  const WEBSOCKET_CONNECTION_CONFIG = {
553
- /**
554
- * 300 milliseconds is a generally accepted threshold for what someone
555
- * would consider an acceptable response time for a user interface
556
- * after having performed a low-attention tapping task. We set the initial
557
- * interval at which we wait for the wallet to set up the websocket at
558
- * half this, as per the Nyquist frequency, with a progressive backoff
559
- * sequence from there. The total wait time is 30s, which allows for the
560
- * user to be presented with a disambiguation dialog, select a wallet, and
561
- * for the wallet app to subsequently start.
562
- */
563
- retryDelayScheduleMs: [150, 150, 200, 500, 500, 750, 750, 1000],
564
- timeoutMs: 30000,
487
+ retryDelayScheduleMs: [
488
+ 150,
489
+ 150,
490
+ 200,
491
+ 500,
492
+ 500,
493
+ 750,
494
+ 750,
495
+ 1e3
496
+ ],
497
+ timeoutMs: 3e4
565
498
  };
566
- const WEBSOCKET_PROTOCOL_BINARY = 'com.solana.mobilewalletadapter.v1';
567
- const WEBSOCKET_PROTOCOL_BASE64 = 'com.solana.mobilewalletadapter.v1.base64';
499
+ const WEBSOCKET_PROTOCOL_BINARY = "com.solana.mobilewalletadapter.v1";
500
+ const WEBSOCKET_PROTOCOL_BASE64 = "com.solana.mobilewalletadapter.v1.base64";
568
501
  function assertSecureContext() {
569
- if (typeof window === 'undefined' || window.isSecureContext !== true) {
570
- throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SECURE_CONTEXT_REQUIRED, 'The mobile wallet adapter protocol must be used in a secure context (`https`).');
571
- }
502
+ if (typeof window === "undefined" || window.isSecureContext !== true) throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SECURE_CONTEXT_REQUIRED, "The mobile wallet adapter protocol must be used in a secure context (`https`).");
572
503
  }
573
504
  function assertSecureEndpointSpecificURI(walletUriBase) {
574
- let url;
575
- try {
576
- url = new URL(walletUriBase);
577
- }
578
- catch {
579
- throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_FORBIDDEN_WALLET_BASE_URL, 'Invalid base URL supplied by wallet');
580
- }
581
- if (url.protocol !== 'https:') {
582
- throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_FORBIDDEN_WALLET_BASE_URL, 'Base URLs supplied by wallets must be valid `https` URLs');
583
- }
505
+ let url;
506
+ try {
507
+ url = new URL(walletUriBase);
508
+ } catch {
509
+ throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_FORBIDDEN_WALLET_BASE_URL, "Invalid base URL supplied by wallet");
510
+ }
511
+ if (url.protocol !== "https:") throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_FORBIDDEN_WALLET_BASE_URL, "Base URLs supplied by wallets must be valid `https` URLs");
584
512
  }
585
513
  function getSequenceNumberFromByteArray(byteArray) {
586
- const view = new DataView(byteArray);
587
- return view.getUint32(0, /* littleEndian */ false);
514
+ return new DataView(byteArray).getUint32(0, false);
588
515
  }
589
516
  function decodeVarLong(byteArray) {
590
- var bytes = new Uint8Array(byteArray), l = byteArray.byteLength, limit = 10, value = 0, offset = 0, b;
591
- do {
592
- if (offset >= l || offset > limit)
593
- throw new RangeError('Failed to decode varint');
594
- b = bytes[offset++];
595
- value |= (b & 0x7F) << (7 * offset);
596
- } while (b >= 0x80);
597
- return { value, offset };
517
+ const bytes = new Uint8Array(byteArray);
518
+ const l = byteArray.byteLength;
519
+ const limit = 10;
520
+ let value = 0, offset = 0, b;
521
+ do {
522
+ if (offset >= l || offset > limit) throw new RangeError("Failed to decode varint");
523
+ b = bytes[offset++];
524
+ value |= (b & 127) << 7 * offset;
525
+ } while (b >= 128);
526
+ return {
527
+ value,
528
+ offset
529
+ };
598
530
  }
599
531
  function getReflectorIdFromByteArray(byteArray) {
600
- let { value: length, offset } = decodeVarLong(byteArray);
601
- return new Uint8Array(byteArray.slice(offset, offset + length));
532
+ const { value: length, offset } = decodeVarLong(byteArray);
533
+ return new Uint8Array(byteArray.slice(offset, offset + length));
602
534
  }
603
535
  async function transact(callback, config) {
604
- const { wallet, close } = await startScenario(config);
605
- try {
606
- return await callback(await wallet);
607
- }
608
- finally {
609
- close();
610
- }
536
+ const { wallet, close } = await startScenario(config);
537
+ try {
538
+ return await callback(await wallet);
539
+ } finally {
540
+ close();
541
+ }
611
542
  }
612
543
  async function startScenario(config) {
613
- assertSecureContext();
614
- const associationKeypair = await generateAssociationKeypair();
615
- const sessionPort = await startSession(associationKeypair.publicKey, config?.baseUri);
616
- const websocketURL = `ws://localhost:${sessionPort}/solana-wallet`;
617
- let connectionStartTime;
618
- const getNextRetryDelayMs = (() => {
619
- const schedule = [...WEBSOCKET_CONNECTION_CONFIG.retryDelayScheduleMs];
620
- return () => (schedule.length > 1 ? schedule.shift() : schedule[0]);
621
- })();
622
- let nextJsonRpcMessageId = 1;
623
- let lastKnownInboundSequenceNumber = 0;
624
- let state = { __type: 'disconnected' };
625
- let socket;
626
- let sessionEstablished = false;
627
- let handleForceClose;
628
- return { close: () => {
629
- socket.close();
630
- handleForceClose();
631
- }, wallet: new Promise((resolve, reject) => {
632
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
633
- const jsonRpcResponsePromises = {};
634
- const handleOpen = async () => {
635
- if (state.__type !== 'connecting') {
636
- console.warn('Expected adapter state to be `connecting` at the moment the websocket opens. ' +
637
- `Got \`${state.__type}\`.`);
638
- return;
639
- }
640
- socket.removeEventListener('open', handleOpen);
641
- // previous versions of this library and walletlib incorrectly implemented the MWA session
642
- // establishment protocol for local connections. The dapp is supposed to wait for the
643
- // APP_PING message before sending the HELLO_REQ. Instead, the dapp was sending the HELLO_REQ
644
- // immediately upon connection to the websocket server regardless of wether or not an
645
- // APP_PING was sent by the wallet/websocket server. We must continue to support this behavior
646
- // in case the user is using a wallet that has not updated their walletlib implementation.
647
- const { associationKeypair } = state;
648
- const ecdhKeypair = await generateECDHKeypair();
649
- socket.send(await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
650
- state = {
651
- __type: 'hello_req_sent',
652
- associationPublicKey: associationKeypair.publicKey,
653
- ecdhPrivateKey: ecdhKeypair.privateKey,
654
- };
655
- };
656
- const handleClose = (evt) => {
657
- if (evt.wasClean) {
658
- state = { __type: 'disconnected' };
659
- }
660
- else {
661
- reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session dropped unexpectedly (${evt.code}: ${evt.reason}).`, { closeEvent: evt }));
662
- }
663
- disposeSocket();
664
- };
665
- const handleError = async (_evt) => {
666
- disposeSocket();
667
- if (Date.now() - connectionStartTime >= WEBSOCKET_CONNECTION_CONFIG.timeoutMs) {
668
- reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_TIMEOUT, `Failed to connect to the wallet websocket at ${websocketURL}.`));
669
- }
670
- else {
671
- await new Promise((resolve) => {
672
- const retryDelayMs = getNextRetryDelayMs();
673
- retryWaitTimeoutId = window.setTimeout(resolve, retryDelayMs);
674
- });
675
- attemptSocketConnection();
676
- }
677
- };
678
- const handleMessage = async (evt) => {
679
- const responseBuffer = await evt.data.arrayBuffer();
680
- switch (state.__type) {
681
- case 'connecting':
682
- if (responseBuffer.byteLength !== 0) {
683
- throw new Error('Encountered unexpected message while connecting');
684
- }
685
- const ecdhKeypair = await generateECDHKeypair();
686
- socket.send(await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
687
- state = {
688
- __type: 'hello_req_sent',
689
- associationPublicKey: associationKeypair.publicKey,
690
- ecdhPrivateKey: ecdhKeypair.privateKey,
691
- };
692
- break;
693
- case 'connected':
694
- try {
695
- const sequenceNumberVector = responseBuffer.slice(0, SEQUENCE_NUMBER_BYTES);
696
- const sequenceNumber = getSequenceNumberFromByteArray(sequenceNumberVector);
697
- if (sequenceNumber !== (lastKnownInboundSequenceNumber + 1)) {
698
- throw new Error('Encrypted message has invalid sequence number');
699
- }
700
- lastKnownInboundSequenceNumber = sequenceNumber;
701
- const jsonRpcMessage = await decryptJsonRpcMessage(responseBuffer, state.sharedSecret);
702
- const responsePromise = jsonRpcResponsePromises[jsonRpcMessage.id];
703
- delete jsonRpcResponsePromises[jsonRpcMessage.id];
704
- responsePromise.resolve(jsonRpcMessage.result);
705
- }
706
- catch (e) {
707
- if (e instanceof SolanaMobileWalletAdapterProtocolError) {
708
- const responsePromise = jsonRpcResponsePromises[e.jsonRpcMessageId];
709
- delete jsonRpcResponsePromises[e.jsonRpcMessageId];
710
- responsePromise.reject(e);
711
- }
712
- else {
713
- throw e;
714
- }
715
- }
716
- break;
717
- case 'hello_req_sent': {
718
- // if we receive an APP_PING message (empty message), resend the HELLO_REQ (see above)
719
- if (responseBuffer.byteLength === 0) {
720
- const ecdhKeypair = await generateECDHKeypair();
721
- socket.send(await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
722
- state = {
723
- __type: 'hello_req_sent',
724
- associationPublicKey: associationKeypair.publicKey,
725
- ecdhPrivateKey: ecdhKeypair.privateKey,
726
- };
727
- break;
728
- }
729
- const sharedSecret = await parseHelloRsp(responseBuffer, state.associationPublicKey, state.ecdhPrivateKey);
730
- const sessionPropertiesBuffer = responseBuffer.slice(ENCODED_PUBLIC_KEY_LENGTH_BYTES);
731
- const sessionProperties = sessionPropertiesBuffer.byteLength !== 0
732
- ? await (async () => {
733
- const sequenceNumberVector = sessionPropertiesBuffer.slice(0, SEQUENCE_NUMBER_BYTES);
734
- const sequenceNumber = getSequenceNumberFromByteArray(sequenceNumberVector);
735
- if (sequenceNumber !== (lastKnownInboundSequenceNumber + 1)) {
736
- throw new Error('Encrypted message has invalid sequence number');
737
- }
738
- lastKnownInboundSequenceNumber = sequenceNumber;
739
- return parseSessionProps(sessionPropertiesBuffer, sharedSecret);
740
- })() : { protocol_version: 'legacy' };
741
- state = { __type: 'connected', sharedSecret, sessionProperties };
742
- const wallet = createMobileWalletProxy(sessionProperties.protocol_version, async (method, params) => {
743
- const id = nextJsonRpcMessageId++;
744
- socket.send(await encryptJsonRpcMessage({
745
- id,
746
- jsonrpc: '2.0',
747
- method,
748
- params: params ?? {},
749
- }, sharedSecret));
750
- return new Promise((resolve, reject) => {
751
- jsonRpcResponsePromises[id] = {
752
- resolve(result) {
753
- switch (method) {
754
- case 'authorize':
755
- case 'reauthorize': {
756
- const { wallet_uri_base } = result;
757
- if (wallet_uri_base != null) {
758
- try {
759
- assertSecureEndpointSpecificURI(wallet_uri_base);
760
- }
761
- catch (e) {
762
- reject(e);
763
- return;
764
- }
765
- }
766
- break;
767
- }
768
- }
769
- resolve(result);
770
- },
771
- reject,
772
- };
773
- });
774
- });
775
- sessionEstablished = true;
776
- try {
777
- resolve(wallet);
778
- }
779
- catch (e) {
780
- reject(e);
781
- }
782
- break;
783
- }
784
- }
785
- };
786
- handleForceClose = () => {
787
- socket.removeEventListener('message', handleMessage);
788
- disposeSocket();
789
- if (!sessionEstablished) {
790
- reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session was closed before connection.`, { closeEvent: new CloseEvent('socket was closed before connection') }));
791
- }
792
- };
793
- let disposeSocket;
794
- let retryWaitTimeoutId;
795
- const attemptSocketConnection = () => {
796
- if (disposeSocket) {
797
- disposeSocket();
798
- }
799
- state = { __type: 'connecting', associationKeypair };
800
- if (connectionStartTime === undefined) {
801
- connectionStartTime = Date.now();
802
- }
803
- socket = new WebSocket(websocketURL, [WEBSOCKET_PROTOCOL_BINARY]);
804
- socket.addEventListener('open', handleOpen);
805
- socket.addEventListener('close', handleClose);
806
- socket.addEventListener('error', handleError);
807
- socket.addEventListener('message', handleMessage);
808
- disposeSocket = () => {
809
- window.clearTimeout(retryWaitTimeoutId);
810
- socket.removeEventListener('open', handleOpen);
811
- socket.removeEventListener('close', handleClose);
812
- socket.removeEventListener('error', handleError);
813
- socket.removeEventListener('message', handleMessage);
814
- };
815
- };
816
- attemptSocketConnection();
817
- }) };
544
+ assertSecureContext();
545
+ const associationKeypair = await generateAssociationKeypair();
546
+ const websocketURL = `ws://localhost:${await startSession(associationKeypair.publicKey, config?.baseUri)}/solana-wallet`;
547
+ let connectionStartTime;
548
+ const getNextRetryDelayMs = (() => {
549
+ const schedule = [...WEBSOCKET_CONNECTION_CONFIG.retryDelayScheduleMs];
550
+ return () => schedule.length > 1 ? schedule.shift() : schedule[0];
551
+ })();
552
+ let nextJsonRpcMessageId = 1;
553
+ let lastKnownInboundSequenceNumber = 0;
554
+ let state = { __type: "disconnected" };
555
+ let socket;
556
+ let sessionEstablished = false;
557
+ let handleForceClose;
558
+ return {
559
+ close: () => {
560
+ socket.close();
561
+ handleForceClose();
562
+ },
563
+ wallet: new Promise((resolve, reject) => {
564
+ const jsonRpcResponsePromises = {};
565
+ const handleOpen = async () => {
566
+ if (state.__type !== "connecting") {
567
+ console.warn(`Expected adapter state to be \`connecting\` at the moment the websocket opens. Got \`${state.__type}\`.`);
568
+ return;
569
+ }
570
+ socket.removeEventListener("open", handleOpen);
571
+ const { associationKeypair } = state;
572
+ const ecdhKeypair = await generateECDHKeypair();
573
+ socket.send(await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
574
+ state = {
575
+ __type: "hello_req_sent",
576
+ associationPublicKey: associationKeypair.publicKey,
577
+ ecdhPrivateKey: ecdhKeypair.privateKey
578
+ };
579
+ };
580
+ const handleClose = (evt) => {
581
+ if (evt.wasClean) state = { __type: "disconnected" };
582
+ else reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session dropped unexpectedly (${evt.code}: ${evt.reason}).`, { closeEvent: evt }));
583
+ disposeSocket();
584
+ };
585
+ const handleError = async (_evt) => {
586
+ disposeSocket();
587
+ if (Date.now() - connectionStartTime >= WEBSOCKET_CONNECTION_CONFIG.timeoutMs) reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_TIMEOUT, `Failed to connect to the wallet websocket at ${websocketURL}.`));
588
+ else {
589
+ await new Promise((resolve) => {
590
+ const retryDelayMs = getNextRetryDelayMs();
591
+ retryWaitTimeoutId = window.setTimeout(resolve, retryDelayMs);
592
+ });
593
+ attemptSocketConnection();
594
+ }
595
+ };
596
+ const handleMessage = async (evt) => {
597
+ const responseBuffer = await evt.data.arrayBuffer();
598
+ switch (state.__type) {
599
+ case "connecting": {
600
+ if (responseBuffer.byteLength !== 0) throw new Error("Encountered unexpected message while connecting");
601
+ const ecdhKeypair = await generateECDHKeypair();
602
+ socket.send(await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
603
+ state = {
604
+ __type: "hello_req_sent",
605
+ associationPublicKey: associationKeypair.publicKey,
606
+ ecdhPrivateKey: ecdhKeypair.privateKey
607
+ };
608
+ break;
609
+ }
610
+ case "connected":
611
+ try {
612
+ const sequenceNumber = getSequenceNumberFromByteArray(responseBuffer.slice(0, 4));
613
+ if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) throw new Error("Encrypted message has invalid sequence number");
614
+ lastKnownInboundSequenceNumber = sequenceNumber;
615
+ const jsonRpcMessage = await decryptJsonRpcMessage(responseBuffer, state.sharedSecret);
616
+ const responsePromise = jsonRpcResponsePromises[jsonRpcMessage.id];
617
+ delete jsonRpcResponsePromises[jsonRpcMessage.id];
618
+ responsePromise.resolve(jsonRpcMessage.result);
619
+ } catch (e) {
620
+ if (e instanceof SolanaMobileWalletAdapterProtocolError) {
621
+ const responsePromise = jsonRpcResponsePromises[e.jsonRpcMessageId];
622
+ delete jsonRpcResponsePromises[e.jsonRpcMessageId];
623
+ responsePromise.reject(e);
624
+ } else throw e;
625
+ }
626
+ break;
627
+ case "hello_req_sent": {
628
+ if (responseBuffer.byteLength === 0) {
629
+ const ecdhKeypair = await generateECDHKeypair();
630
+ socket.send(await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
631
+ state = {
632
+ __type: "hello_req_sent",
633
+ associationPublicKey: associationKeypair.publicKey,
634
+ ecdhPrivateKey: ecdhKeypair.privateKey
635
+ };
636
+ break;
637
+ }
638
+ const sharedSecret = await parseHelloRsp(responseBuffer, state.associationPublicKey, state.ecdhPrivateKey);
639
+ const sessionPropertiesBuffer = responseBuffer.slice(65);
640
+ const sessionProperties = sessionPropertiesBuffer.byteLength !== 0 ? await (async () => {
641
+ const sequenceNumber = getSequenceNumberFromByteArray(sessionPropertiesBuffer.slice(0, 4));
642
+ if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) throw new Error("Encrypted message has invalid sequence number");
643
+ lastKnownInboundSequenceNumber = sequenceNumber;
644
+ return parseSessionProps(sessionPropertiesBuffer, sharedSecret);
645
+ })() : { protocol_version: "legacy" };
646
+ state = {
647
+ __type: "connected",
648
+ sharedSecret,
649
+ sessionProperties
650
+ };
651
+ const wallet = createMobileWalletProxy(sessionProperties.protocol_version, async (method, params) => {
652
+ const id = nextJsonRpcMessageId++;
653
+ socket.send(await encryptJsonRpcMessage({
654
+ id,
655
+ jsonrpc: "2.0",
656
+ method,
657
+ params: params ?? {}
658
+ }, sharedSecret));
659
+ return new Promise((resolve, reject) => {
660
+ jsonRpcResponsePromises[id] = {
661
+ resolve(result) {
662
+ switch (method) {
663
+ case "authorize":
664
+ case "reauthorize": {
665
+ const { wallet_uri_base } = result;
666
+ if (wallet_uri_base != null) try {
667
+ assertSecureEndpointSpecificURI(wallet_uri_base);
668
+ } catch (e) {
669
+ reject(e);
670
+ return;
671
+ }
672
+ break;
673
+ }
674
+ }
675
+ resolve(result);
676
+ },
677
+ reject
678
+ };
679
+ });
680
+ });
681
+ sessionEstablished = true;
682
+ try {
683
+ resolve(wallet);
684
+ } catch (e) {
685
+ reject(e);
686
+ }
687
+ break;
688
+ }
689
+ }
690
+ };
691
+ handleForceClose = () => {
692
+ socket.removeEventListener("message", handleMessage);
693
+ disposeSocket();
694
+ if (!sessionEstablished) reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session was closed before connection.`, { closeEvent: new CloseEvent("socket was closed before connection") }));
695
+ };
696
+ let disposeSocket;
697
+ let retryWaitTimeoutId;
698
+ const attemptSocketConnection = () => {
699
+ if (disposeSocket) disposeSocket();
700
+ state = {
701
+ __type: "connecting",
702
+ associationKeypair
703
+ };
704
+ if (connectionStartTime === void 0) connectionStartTime = Date.now();
705
+ socket = new WebSocket(websocketURL, [WEBSOCKET_PROTOCOL_BINARY]);
706
+ socket.addEventListener("open", handleOpen);
707
+ socket.addEventListener("close", handleClose);
708
+ socket.addEventListener("error", handleError);
709
+ socket.addEventListener("message", handleMessage);
710
+ disposeSocket = () => {
711
+ window.clearTimeout(retryWaitTimeoutId);
712
+ socket.removeEventListener("open", handleOpen);
713
+ socket.removeEventListener("close", handleClose);
714
+ socket.removeEventListener("error", handleError);
715
+ socket.removeEventListener("message", handleMessage);
716
+ };
717
+ };
718
+ attemptSocketConnection();
719
+ })
720
+ };
818
721
  }
819
722
  async function startRemoteScenario(config) {
820
- assertSecureContext();
821
- const associationKeypair = await generateAssociationKeypair();
822
- const websocketURL = `wss://${config?.remoteHostAuthority}/reflect`;
823
- let connectionStartTime;
824
- const getNextRetryDelayMs = (() => {
825
- const schedule = [...WEBSOCKET_CONNECTION_CONFIG.retryDelayScheduleMs];
826
- return () => (schedule.length > 1 ? schedule.shift() : schedule[0]);
827
- })();
828
- let nextJsonRpcMessageId = 1;
829
- let lastKnownInboundSequenceNumber = 0;
830
- let encoding;
831
- let state = { __type: 'disconnected' };
832
- let socket;
833
- let disposeSocket;
834
- let decodeBytes = async (evt) => {
835
- if (encoding == 'base64') { // base64 encoding
836
- const message = await evt.data;
837
- return toUint8Array(message).buffer;
838
- }
839
- else {
840
- return await evt.data.arrayBuffer();
841
- }
842
- };
843
- // Reflector Connection Phase
844
- // here we connect to the reflector and wait for the REFLECTOR_ID message
845
- // so we build the association URL and return that back to the caller
846
- const associationUrl = await new Promise((resolve, reject) => {
847
- const handleOpen = async () => {
848
- if (state.__type !== 'connecting') {
849
- console.warn('Expected adapter state to be `connecting` at the moment the websocket opens. ' +
850
- `Got \`${state.__type}\`.`);
851
- return;
852
- }
853
- if (socket.protocol.includes(WEBSOCKET_PROTOCOL_BASE64)) {
854
- encoding = 'base64';
855
- }
856
- else {
857
- encoding = 'binary';
858
- }
859
- socket.removeEventListener('open', handleOpen);
860
- };
861
- const handleClose = (evt) => {
862
- if (evt.wasClean) {
863
- state = { __type: 'disconnected' };
864
- }
865
- else {
866
- reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session dropped unexpectedly (${evt.code}: ${evt.reason}).`, { closeEvent: evt }));
867
- }
868
- disposeSocket();
869
- };
870
- const handleError = async (_evt) => {
871
- disposeSocket();
872
- if (Date.now() - connectionStartTime >= WEBSOCKET_CONNECTION_CONFIG.timeoutMs) {
873
- reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_TIMEOUT, `Failed to connect to the wallet websocket at ${websocketURL}.`));
874
- }
875
- else {
876
- await new Promise((resolve) => {
877
- const retryDelayMs = getNextRetryDelayMs();
878
- retryWaitTimeoutId = window.setTimeout(resolve, retryDelayMs);
879
- });
880
- attemptSocketConnection();
881
- }
882
- };
883
- const handleReflectorIdMessage = async (evt) => {
884
- const responseBuffer = await decodeBytes(evt);
885
- if (state.__type === 'connecting') {
886
- if (responseBuffer.byteLength == 0) {
887
- throw new Error('Encountered unexpected message while connecting');
888
- }
889
- const reflectorId = getReflectorIdFromByteArray(responseBuffer);
890
- state = {
891
- __type: 'reflector_id_received',
892
- reflectorId: reflectorId
893
- };
894
- const associationUrl = await getRemoteAssociateAndroidIntentURL(associationKeypair.publicKey, config.remoteHostAuthority, reflectorId, config?.baseUri);
895
- socket.removeEventListener('message', handleReflectorIdMessage);
896
- resolve(associationUrl);
897
- }
898
- };
899
- let retryWaitTimeoutId;
900
- const attemptSocketConnection = () => {
901
- if (disposeSocket) {
902
- disposeSocket();
903
- }
904
- state = { __type: 'connecting', associationKeypair };
905
- if (connectionStartTime === undefined) {
906
- connectionStartTime = Date.now();
907
- }
908
- socket = new WebSocket(websocketURL, [WEBSOCKET_PROTOCOL_BINARY, WEBSOCKET_PROTOCOL_BASE64]);
909
- socket.addEventListener('open', handleOpen);
910
- socket.addEventListener('close', handleClose);
911
- socket.addEventListener('error', handleError);
912
- socket.addEventListener('message', handleReflectorIdMessage);
913
- disposeSocket = () => {
914
- window.clearTimeout(retryWaitTimeoutId);
915
- socket.removeEventListener('open', handleOpen);
916
- socket.removeEventListener('close', handleClose);
917
- socket.removeEventListener('error', handleError);
918
- socket.removeEventListener('message', handleReflectorIdMessage);
919
- };
920
- };
921
- attemptSocketConnection();
922
- });
923
- // Wallet Connection Phase
924
- // here we return the association URL (containing the reflector ID) to the caller +
925
- // a promise that will resolve the MobileWallet object once the wallet connects.
926
- let sessionEstablished = false;
927
- let handleClose;
928
- return { associationUrl, close: () => {
929
- socket.close();
930
- handleClose();
931
- }, wallet: new Promise((resolve, reject) => {
932
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
933
- const jsonRpcResponsePromises = {};
934
- const handleMessage = async (evt) => {
935
- const responseBuffer = await decodeBytes(evt);
936
- switch (state.__type) {
937
- case 'reflector_id_received':
938
- if (responseBuffer.byteLength !== 0) {
939
- throw new Error('Encountered unexpected message while awaiting reflection');
940
- }
941
- const ecdhKeypair = await generateECDHKeypair();
942
- const binaryMsg = await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey);
943
- if (encoding == 'base64') {
944
- socket.send(fromUint8Array$1(binaryMsg));
945
- }
946
- else {
947
- socket.send(binaryMsg);
948
- }
949
- state = {
950
- __type: 'hello_req_sent',
951
- associationPublicKey: associationKeypair.publicKey,
952
- ecdhPrivateKey: ecdhKeypair.privateKey,
953
- };
954
- break;
955
- case 'connected':
956
- try {
957
- const sequenceNumberVector = responseBuffer.slice(0, SEQUENCE_NUMBER_BYTES);
958
- const sequenceNumber = getSequenceNumberFromByteArray(sequenceNumberVector);
959
- if (sequenceNumber !== (lastKnownInboundSequenceNumber + 1)) {
960
- throw new Error('Encrypted message has invalid sequence number');
961
- }
962
- lastKnownInboundSequenceNumber = sequenceNumber;
963
- const jsonRpcMessage = await decryptJsonRpcMessage(responseBuffer, state.sharedSecret);
964
- const responsePromise = jsonRpcResponsePromises[jsonRpcMessage.id];
965
- delete jsonRpcResponsePromises[jsonRpcMessage.id];
966
- responsePromise.resolve(jsonRpcMessage.result);
967
- }
968
- catch (e) {
969
- if (e instanceof SolanaMobileWalletAdapterProtocolError) {
970
- const responsePromise = jsonRpcResponsePromises[e.jsonRpcMessageId];
971
- delete jsonRpcResponsePromises[e.jsonRpcMessageId];
972
- responsePromise.reject(e);
973
- }
974
- else {
975
- throw e;
976
- }
977
- }
978
- break;
979
- case 'hello_req_sent': {
980
- const sharedSecret = await parseHelloRsp(responseBuffer, state.associationPublicKey, state.ecdhPrivateKey);
981
- const sessionPropertiesBuffer = responseBuffer.slice(ENCODED_PUBLIC_KEY_LENGTH_BYTES);
982
- const sessionProperties = sessionPropertiesBuffer.byteLength !== 0
983
- ? await (async () => {
984
- const sequenceNumberVector = sessionPropertiesBuffer.slice(0, SEQUENCE_NUMBER_BYTES);
985
- const sequenceNumber = getSequenceNumberFromByteArray(sequenceNumberVector);
986
- if (sequenceNumber !== (lastKnownInboundSequenceNumber + 1)) {
987
- throw new Error('Encrypted message has invalid sequence number');
988
- }
989
- lastKnownInboundSequenceNumber = sequenceNumber;
990
- return parseSessionProps(sessionPropertiesBuffer, sharedSecret);
991
- })() : { protocol_version: 'legacy' };
992
- state = { __type: 'connected', sharedSecret, sessionProperties };
993
- const wallet = createMobileWalletProxy(sessionProperties.protocol_version, async (method, params) => {
994
- const id = nextJsonRpcMessageId++;
995
- const binaryMsg = await encryptJsonRpcMessage({
996
- id,
997
- jsonrpc: '2.0',
998
- method,
999
- params: params ?? {},
1000
- }, sharedSecret);
1001
- if (encoding == 'base64') {
1002
- socket.send(fromUint8Array$1(binaryMsg));
1003
- }
1004
- else {
1005
- socket.send(binaryMsg);
1006
- }
1007
- return new Promise((resolve, reject) => {
1008
- jsonRpcResponsePromises[id] = {
1009
- resolve(result) {
1010
- switch (method) {
1011
- case 'authorize':
1012
- case 'reauthorize': {
1013
- const { wallet_uri_base } = result;
1014
- if (wallet_uri_base != null) {
1015
- try {
1016
- assertSecureEndpointSpecificURI(wallet_uri_base);
1017
- }
1018
- catch (e) {
1019
- reject(e);
1020
- return;
1021
- }
1022
- }
1023
- break;
1024
- }
1025
- }
1026
- resolve(result);
1027
- },
1028
- reject,
1029
- };
1030
- });
1031
- });
1032
- sessionEstablished = true;
1033
- try {
1034
- resolve(wallet);
1035
- }
1036
- catch (e) {
1037
- reject(e);
1038
- }
1039
- break;
1040
- }
1041
- }
1042
- };
1043
- socket.addEventListener('message', handleMessage);
1044
- handleClose = () => {
1045
- socket.removeEventListener('message', handleMessage);
1046
- disposeSocket();
1047
- if (!sessionEstablished) {
1048
- reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session was closed before connection.`, { closeEvent: new CloseEvent('socket was closed before connection') }));
1049
- }
1050
- };
1051
- }) };
1052
- }
1053
-
723
+ assertSecureContext();
724
+ const associationKeypair = await generateAssociationKeypair();
725
+ const websocketURL = `wss://${config?.remoteHostAuthority}/reflect`;
726
+ let connectionStartTime;
727
+ const getNextRetryDelayMs = (() => {
728
+ const schedule = [...WEBSOCKET_CONNECTION_CONFIG.retryDelayScheduleMs];
729
+ return () => schedule.length > 1 ? schedule.shift() : schedule[0];
730
+ })();
731
+ let nextJsonRpcMessageId = 1;
732
+ let lastKnownInboundSequenceNumber = 0;
733
+ let encoding;
734
+ let state = { __type: "disconnected" };
735
+ let socket;
736
+ let disposeSocket;
737
+ const decodeBytes = async (evt) => {
738
+ if (encoding == "base64") return toUint8Array(await evt.data).buffer;
739
+ else return await evt.data.arrayBuffer();
740
+ };
741
+ const associationUrl = await new Promise((resolve, reject) => {
742
+ const handleOpen = async () => {
743
+ if (state.__type !== "connecting") {
744
+ console.warn(`Expected adapter state to be \`connecting\` at the moment the websocket opens. Got \`${state.__type}\`.`);
745
+ return;
746
+ }
747
+ if (socket.protocol.includes(WEBSOCKET_PROTOCOL_BASE64)) encoding = "base64";
748
+ else encoding = "binary";
749
+ socket.removeEventListener("open", handleOpen);
750
+ };
751
+ const handleClose = (evt) => {
752
+ if (evt.wasClean) state = { __type: "disconnected" };
753
+ else reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session dropped unexpectedly (${evt.code}: ${evt.reason}).`, { closeEvent: evt }));
754
+ disposeSocket();
755
+ };
756
+ const handleError = async (_evt) => {
757
+ disposeSocket();
758
+ if (Date.now() - connectionStartTime >= WEBSOCKET_CONNECTION_CONFIG.timeoutMs) reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_TIMEOUT, `Failed to connect to the wallet websocket at ${websocketURL}.`));
759
+ else {
760
+ await new Promise((resolve) => {
761
+ const retryDelayMs = getNextRetryDelayMs();
762
+ retryWaitTimeoutId = window.setTimeout(resolve, retryDelayMs);
763
+ });
764
+ attemptSocketConnection();
765
+ }
766
+ };
767
+ const handleReflectorIdMessage = async (evt) => {
768
+ const responseBuffer = await decodeBytes(evt);
769
+ if (state.__type === "connecting") {
770
+ if (responseBuffer.byteLength == 0) throw new Error("Encountered unexpected message while connecting");
771
+ const reflectorId = getReflectorIdFromByteArray(responseBuffer);
772
+ state = {
773
+ __type: "reflector_id_received",
774
+ reflectorId
775
+ };
776
+ const associationUrl = await getRemoteAssociateAndroidIntentURL(associationKeypair.publicKey, config.remoteHostAuthority, reflectorId, config?.baseUri);
777
+ socket.removeEventListener("message", handleReflectorIdMessage);
778
+ resolve(associationUrl);
779
+ }
780
+ };
781
+ let retryWaitTimeoutId;
782
+ const attemptSocketConnection = () => {
783
+ if (disposeSocket) disposeSocket();
784
+ state = {
785
+ __type: "connecting",
786
+ associationKeypair
787
+ };
788
+ if (connectionStartTime === void 0) connectionStartTime = Date.now();
789
+ socket = new WebSocket(websocketURL, [WEBSOCKET_PROTOCOL_BINARY, WEBSOCKET_PROTOCOL_BASE64]);
790
+ socket.addEventListener("open", handleOpen);
791
+ socket.addEventListener("close", handleClose);
792
+ socket.addEventListener("error", handleError);
793
+ socket.addEventListener("message", handleReflectorIdMessage);
794
+ disposeSocket = () => {
795
+ window.clearTimeout(retryWaitTimeoutId);
796
+ socket.removeEventListener("open", handleOpen);
797
+ socket.removeEventListener("close", handleClose);
798
+ socket.removeEventListener("error", handleError);
799
+ socket.removeEventListener("message", handleReflectorIdMessage);
800
+ };
801
+ };
802
+ attemptSocketConnection();
803
+ });
804
+ let sessionEstablished = false;
805
+ let handleClose;
806
+ return {
807
+ associationUrl,
808
+ close: () => {
809
+ socket.close();
810
+ handleClose();
811
+ },
812
+ wallet: new Promise((resolve, reject) => {
813
+ const jsonRpcResponsePromises = {};
814
+ const handleMessage = async (evt) => {
815
+ const responseBuffer = await decodeBytes(evt);
816
+ switch (state.__type) {
817
+ case "reflector_id_received": {
818
+ if (responseBuffer.byteLength !== 0) throw new Error("Encountered unexpected message while awaiting reflection");
819
+ const ecdhKeypair = await generateECDHKeypair();
820
+ const binaryMsg = await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey);
821
+ if (encoding == "base64") socket.send(fromUint8Array$1(binaryMsg));
822
+ else socket.send(binaryMsg);
823
+ state = {
824
+ __type: "hello_req_sent",
825
+ associationPublicKey: associationKeypair.publicKey,
826
+ ecdhPrivateKey: ecdhKeypair.privateKey
827
+ };
828
+ break;
829
+ }
830
+ case "connected":
831
+ try {
832
+ const sequenceNumber = getSequenceNumberFromByteArray(responseBuffer.slice(0, 4));
833
+ if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) throw new Error("Encrypted message has invalid sequence number");
834
+ lastKnownInboundSequenceNumber = sequenceNumber;
835
+ const jsonRpcMessage = await decryptJsonRpcMessage(responseBuffer, state.sharedSecret);
836
+ const responsePromise = jsonRpcResponsePromises[jsonRpcMessage.id];
837
+ delete jsonRpcResponsePromises[jsonRpcMessage.id];
838
+ responsePromise.resolve(jsonRpcMessage.result);
839
+ } catch (e) {
840
+ if (e instanceof SolanaMobileWalletAdapterProtocolError) {
841
+ const responsePromise = jsonRpcResponsePromises[e.jsonRpcMessageId];
842
+ delete jsonRpcResponsePromises[e.jsonRpcMessageId];
843
+ responsePromise.reject(e);
844
+ } else throw e;
845
+ }
846
+ break;
847
+ case "hello_req_sent": {
848
+ const sharedSecret = await parseHelloRsp(responseBuffer, state.associationPublicKey, state.ecdhPrivateKey);
849
+ const sessionPropertiesBuffer = responseBuffer.slice(65);
850
+ const sessionProperties = sessionPropertiesBuffer.byteLength !== 0 ? await (async () => {
851
+ const sequenceNumber = getSequenceNumberFromByteArray(sessionPropertiesBuffer.slice(0, 4));
852
+ if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) throw new Error("Encrypted message has invalid sequence number");
853
+ lastKnownInboundSequenceNumber = sequenceNumber;
854
+ return parseSessionProps(sessionPropertiesBuffer, sharedSecret);
855
+ })() : { protocol_version: "legacy" };
856
+ state = {
857
+ __type: "connected",
858
+ sharedSecret,
859
+ sessionProperties
860
+ };
861
+ const wallet = createMobileWalletProxy(sessionProperties.protocol_version, async (method, params) => {
862
+ const id = nextJsonRpcMessageId++;
863
+ const binaryMsg = await encryptJsonRpcMessage({
864
+ id,
865
+ jsonrpc: "2.0",
866
+ method,
867
+ params: params ?? {}
868
+ }, sharedSecret);
869
+ if (encoding == "base64") socket.send(fromUint8Array$1(binaryMsg));
870
+ else socket.send(binaryMsg);
871
+ return new Promise((resolve, reject) => {
872
+ jsonRpcResponsePromises[id] = {
873
+ resolve(result) {
874
+ switch (method) {
875
+ case "authorize":
876
+ case "reauthorize": {
877
+ const { wallet_uri_base } = result;
878
+ if (wallet_uri_base != null) try {
879
+ assertSecureEndpointSpecificURI(wallet_uri_base);
880
+ } catch (e) {
881
+ reject(e);
882
+ return;
883
+ }
884
+ break;
885
+ }
886
+ }
887
+ resolve(result);
888
+ },
889
+ reject
890
+ };
891
+ });
892
+ });
893
+ sessionEstablished = true;
894
+ try {
895
+ resolve(wallet);
896
+ } catch (e) {
897
+ reject(e);
898
+ }
899
+ break;
900
+ }
901
+ }
902
+ };
903
+ socket.addEventListener("message", handleMessage);
904
+ handleClose = () => {
905
+ socket.removeEventListener("message", handleMessage);
906
+ disposeSocket();
907
+ if (!sessionEstablished) reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session was closed before connection.`, { closeEvent: new CloseEvent("socket was closed before connection") }));
908
+ };
909
+ })
910
+ };
911
+ }
912
+ //#endregion
1054
913
  exports.SolanaCloneAuthorization = SolanaCloneAuthorization;
1055
914
  exports.SolanaMobileWalletAdapterError = SolanaMobileWalletAdapterError;
1056
915
  exports.SolanaMobileWalletAdapterErrorCode = SolanaMobileWalletAdapterErrorCode;
@@ -1061,3 +920,5 @@ exports.SolanaSignTransactions = SolanaSignTransactions;
1061
920
  exports.startRemoteScenario = startRemoteScenario;
1062
921
  exports.startScenario = startScenario;
1063
922
  exports.transact = transact;
923
+
924
+ //# sourceMappingURL=index.js.map