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

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.
@@ -1,1056 +1,907 @@
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_wallet_standard_util = require("@solana/wallet-standard-util");
3
+ let _solana_codecs_strings = require("@solana/codecs-strings");
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
-
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/createSIWSMessage.ts
85
78
  function createSIWSMessage(payload) {
86
- return walletStandardUtil.createSignInMessageText(payload);
79
+ return (0, _solana_wallet_standard_util.createSignInMessageText)(payload);
87
80
  }
88
81
  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
-
82
+ return encode(createSIWSMessage(payload)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
83
+ }
84
+ //#endregion
85
+ //#region src/types.ts
86
+ const SolanaSignTransactions = "solana:signTransactions";
87
+ const SolanaCloneAuthorization = "solana:cloneAuthorization";
88
+ const SolanaSignInWithSolana = "solana:signInWithSolana";
89
+ //#endregion
90
+ //#region src/base58Utils.ts
100
91
  function fromUint8Array(byteArray) {
101
- return codecsStrings.getBase58Decoder().decode(byteArray);
92
+ return (0, _solana_codecs_strings.getBase58Decoder)().decode(byteArray);
102
93
  }
103
94
  function base64ToBase58(base64EncodedString) {
104
- return fromUint8Array(toUint8Array(base64EncodedString));
95
+ return fromUint8Array(toUint8Array(base64EncodedString));
105
96
  }
106
-
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
+ let { chain } = params;
141
+ if (protocolVersion === "legacy") {
142
+ switch (chain) {
143
+ case "solana:testnet":
144
+ chain = "testnet";
145
+ break;
146
+ case "solana:devnet":
147
+ chain = "devnet";
148
+ break;
149
+ case "solana:mainnet":
150
+ chain = "mainnet-beta";
151
+ break;
152
+ default: chain = params.cluster;
153
+ }
154
+ params.cluster = chain;
155
+ } else {
156
+ switch (chain) {
157
+ case "testnet":
158
+ case "devnet":
159
+ chain = `solana:${chain}`;
160
+ break;
161
+ case "mainnet-beta":
162
+ chain = "solana:mainnet";
163
+ break;
164
+ }
165
+ params.chain = chain;
166
+ }
167
+ }
168
+ case "reauthorize": {
169
+ const { auth_token, identity } = params;
170
+ if (auth_token) switch (protocolVersion) {
171
+ case "legacy":
172
+ method = "reauthorize";
173
+ params = {
174
+ auth_token,
175
+ identity
176
+ };
177
+ break;
178
+ default:
179
+ method = "authorize";
180
+ break;
181
+ }
182
+ break;
183
+ }
184
+ }
185
+ return {
186
+ method,
187
+ params
188
+ };
217
189
  }
218
190
  /**
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
- */
191
+ * Handles all {@link MobileWallet} API responses and modifies the response for backwards compatibility, if needed
192
+ *
193
+ * @param method the {@link MobileWallet} method that was called
194
+ * @param response the original response that was returned by the method call
195
+ * @param protocolVersion the protocol version in use for this session/request
196
+ * @returns the possibly modified response
197
+ */
226
198
  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;
199
+ switch (method) {
200
+ case "getCapabilities": {
201
+ const capabilities = response;
202
+ switch (protocolVersion) {
203
+ case "legacy": {
204
+ const features = [SolanaSignTransactions];
205
+ if (capabilities.supports_clone_authorization === true) features.push(SolanaCloneAuthorization);
206
+ return {
207
+ ...capabilities,
208
+ features
209
+ };
210
+ }
211
+ case "v1": return {
212
+ ...capabilities,
213
+ supports_sign_and_send_transactions: true,
214
+ supports_clone_authorization: capabilities.features.includes(SolanaCloneAuthorization)
215
+ };
216
+ }
217
+ }
218
+ }
219
+ return response;
252
220
  }
253
221
  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;
222
+ const domain = signInPayload.domain ?? window.location.host;
223
+ const address = authorizationResult.accounts[0].address;
224
+ const siwsMessage = createSIWSMessageBase64Url({
225
+ ...signInPayload,
226
+ domain,
227
+ address: base64ToBase58(address)
228
+ });
229
+ const signedPayload = toUint8Array((await protocolRequestHandler("sign_messages", {
230
+ addresses: [address],
231
+ payloads: [siwsMessage]
232
+ })).signed_payloads[0]);
233
+ const signedMessage = fromUint8Array$1(signedPayload.slice(0, signedPayload.length - 64));
234
+ const signature = fromUint8Array$1(signedPayload.slice(signedPayload.length - 64));
235
+ return {
236
+ address,
237
+ signed_message: signedMessage.length == 0 ? siwsMessage : signedMessage,
238
+ signature
239
+ };
273
240
  }
274
-
275
- const SEQUENCE_NUMBER_BYTES = 4;
276
241
  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);
242
+ if (sequenceNumber >= 4294967296) throw new Error("Outbound sequence number overflow. The maximum sequence number is 32-bytes.");
243
+ const byteArray = /* @__PURE__ */ new ArrayBuffer(4);
244
+ new DataView(byteArray).setUint32(0, sequenceNumber, false);
245
+ return new Uint8Array(byteArray);
284
246
  }
285
-
247
+ //#endregion
248
+ //#region src/encryptedMessage.ts
286
249
  const INITIALIZATION_VECTOR_BYTES = 12;
287
- const ENCODED_PUBLIC_KEY_LENGTH_BYTES = 65;
288
250
  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;
251
+ const sequenceNumberVector = createSequenceNumberVector(sequenceNumber);
252
+ const initializationVector = new Uint8Array(INITIALIZATION_VECTOR_BYTES);
253
+ crypto.getRandomValues(initializationVector);
254
+ const ciphertext = await crypto.subtle.encrypt(getAlgorithmParams(sequenceNumberVector, initializationVector), sharedSecret, new TextEncoder().encode(plaintext));
255
+ const response = new Uint8Array(sequenceNumberVector.byteLength + initializationVector.byteLength + ciphertext.byteLength);
256
+ response.set(new Uint8Array(sequenceNumberVector), 0);
257
+ response.set(new Uint8Array(initializationVector), sequenceNumberVector.byteLength);
258
+ response.set(new Uint8Array(ciphertext), sequenceNumberVector.byteLength + initializationVector.byteLength);
259
+ return response;
298
260
  }
299
261
  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;
262
+ const sequenceNumberVector = message.slice(0, 4);
263
+ const initializationVector = message.slice(4, 4 + INITIALIZATION_VECTOR_BYTES);
264
+ const ciphertext = message.slice(4 + INITIALIZATION_VECTOR_BYTES);
265
+ const plaintextBuffer = await crypto.subtle.decrypt(getAlgorithmParams(sequenceNumberVector, initializationVector), sharedSecret, ciphertext);
266
+ return getUtf8Decoder().decode(plaintextBuffer);
306
267
  }
307
268
  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
- };
269
+ return {
270
+ additionalData: sequenceNumber,
271
+ iv: initializationVector,
272
+ name: "AES-GCM",
273
+ tagLength: 128
274
+ };
314
275
  }
315
276
  let _utf8Decoder;
316
277
  function getUtf8Decoder() {
317
- if (_utf8Decoder === undefined) {
318
- _utf8Decoder = new TextDecoder('utf-8');
319
- }
320
- return _utf8Decoder;
278
+ if (_utf8Decoder === void 0) _utf8Decoder = new TextDecoder("utf-8");
279
+ return _utf8Decoder;
321
280
  }
322
-
281
+ //#endregion
282
+ //#region src/generateAssociationKeypair.ts
323
283
  async function generateAssociationKeypair() {
324
- return await crypto.subtle.generateKey({
325
- name: 'ECDSA',
326
- namedCurve: 'P-256',
327
- }, false /* extractable */, ['sign'] /* keyUsages */);
284
+ return await crypto.subtle.generateKey({
285
+ name: "ECDSA",
286
+ namedCurve: "P-256"
287
+ }, false, ["sign"]);
328
288
  }
329
-
289
+ //#endregion
290
+ //#region src/generateECDHKeypair.ts
330
291
  async function generateECDHKeypair() {
331
- return await crypto.subtle.generateKey({
332
- name: 'ECDH',
333
- namedCurve: 'P-256',
334
- }, false /* extractable */, ['deriveKey', 'deriveBits'] /* keyUsages */);
292
+ return await crypto.subtle.generateKey({
293
+ name: "ECDH",
294
+ namedCurve: "P-256"
295
+ }, false, ["deriveKey", "deriveBits"]);
335
296
  }
336
-
337
- // https://stackoverflow.com/a/9458996/802047
297
+ //#endregion
298
+ //#region src/arrayBufferToBase64String.ts
338
299
  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
-
300
+ let binary = "";
301
+ const bytes = new Uint8Array(buffer);
302
+ const len = bytes.byteLength;
303
+ for (let ii = 0; ii < len; ii++) binary += String.fromCharCode(bytes[ii]);
304
+ return window.btoa(binary);
305
+ }
306
+ //#endregion
307
+ //#region src/associationPort.ts
348
308
  function getRandomAssociationPort() {
349
- return assertAssociationPort(49152 + Math.floor(Math.random() * (65535 - 49152 + 1)));
309
+ return assertAssociationPort(49152 + Math.floor(Math.random() * 16384));
350
310
  }
351
311
  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;
312
+ 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 });
313
+ return port;
356
314
  }
357
-
315
+ //#endregion
316
+ //#region src/getStringWithURLUnsafeBase64CharactersReplaced.ts
358
317
  function getStringWithURLUnsafeCharactersReplaced(unsafeBase64EncodedString) {
359
- return unsafeBase64EncodedString.replace(/[/+=]/g, (m) => ({
360
- '/': '_',
361
- '+': '-',
362
- '=': '.',
363
- }[m]));
364
- }
365
-
366
- const INTENT_NAME = 'solana-wallet';
318
+ return unsafeBase64EncodedString.replace(/[/+=]/g, (m) => ({
319
+ "/": "_",
320
+ "+": "-",
321
+ "=": "."
322
+ })[m]);
323
+ }
324
+ //#endregion
325
+ //#region src/getAssociateAndroidIntentURL.ts
326
+ const INTENT_NAME = "solana-wallet";
367
327
  function getPathParts(pathString) {
368
- return (pathString
369
- // Strip leading and trailing slashes
370
- .replace(/(^\/+|\/+$)/g, '')
371
- // Return an array of directories
372
- .split('/'));
328
+ return pathString.replace(/(^\/+|\/+$)/g, "").split("/");
373
329
  }
374
330
  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
-
331
+ let baseUrl = null;
332
+ if (intentUrlBase) {
333
+ try {
334
+ baseUrl = new URL(intentUrlBase);
335
+ } catch {}
336
+ if (baseUrl?.protocol !== "https:") throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_FORBIDDEN_WALLET_BASE_URL, "Base URLs supplied by wallets must be valid `https` URLs");
337
+ }
338
+ baseUrl ||= new URL(`${INTENT_NAME}:/`);
339
+ const pathname = methodPathname.startsWith("/") ? methodPathname : [...getPathParts(baseUrl.pathname), ...getPathParts(methodPathname)].join("/");
340
+ return new URL(pathname, baseUrl);
341
+ }
342
+ async function getAssociateAndroidIntentURL(associationPublicKey, putativePort, associationURLBase, protocolVersions = ["v1"]) {
343
+ const associationPort = assertAssociationPort(putativePort);
344
+ const encodedKey = arrayBufferToBase64String(await crypto.subtle.exportKey("raw", associationPublicKey));
345
+ const url = getIntentURL("v1/associate/local", associationURLBase);
346
+ url.searchParams.set("association", getStringWithURLUnsafeCharactersReplaced(encodedKey));
347
+ url.searchParams.set("port", `${associationPort}`);
348
+ protocolVersions.forEach((version) => {
349
+ url.searchParams.set("v", version);
350
+ });
351
+ return url;
352
+ }
353
+ async function getRemoteAssociateAndroidIntentURL(associationPublicKey, hostAuthority, reflectorId, associationURLBase, protocolVersions = ["v1"]) {
354
+ const encodedKey = arrayBufferToBase64String(await crypto.subtle.exportKey("raw", associationPublicKey));
355
+ const url = getIntentURL("v1/associate/remote", associationURLBase);
356
+ url.searchParams.set("association", getStringWithURLUnsafeCharactersReplaced(encodedKey));
357
+ url.searchParams.set("reflector", `${hostAuthority}`);
358
+ url.searchParams.set("id", `${fromUint8Array$1(reflectorId, true)}`);
359
+ protocolVersions.forEach((version) => {
360
+ url.searchParams.set("v", version);
361
+ });
362
+ return url;
363
+ }
364
+ //#endregion
365
+ //#region src/jsonRpcMessage.ts
418
366
  async function encryptJsonRpcMessage(jsonRpcMessage, sharedSecret) {
419
- const plaintext = JSON.stringify(jsonRpcMessage);
420
- const sequenceNumber = jsonRpcMessage.id;
421
- return encryptMessage(plaintext, sequenceNumber, sharedSecret);
367
+ const plaintext = JSON.stringify(jsonRpcMessage);
368
+ const sequenceNumber = jsonRpcMessage.id;
369
+ return encryptMessage(plaintext, sequenceNumber, sharedSecret);
422
370
  }
423
371
  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
-
372
+ const plaintext = await decryptMessage(message, sharedSecret);
373
+ const jsonRpcMessage = JSON.parse(plaintext);
374
+ if (Object.hasOwnProperty.call(jsonRpcMessage, "error")) throw new SolanaMobileWalletAdapterProtocolError(jsonRpcMessage.id, jsonRpcMessage.error.code, jsonRpcMessage.error.message);
375
+ return jsonRpcMessage;
376
+ }
377
+ //#endregion
378
+ //#region src/parseHelloRsp.ts
379
+ async function parseHelloRsp(payloadBuffer, associationPublicKey, ecdhPrivateKey) {
380
+ const [associationPublicKeyBuffer, walletPublicKey] = await Promise.all([crypto.subtle.exportKey("raw", associationPublicKey), crypto.subtle.importKey("raw", payloadBuffer.slice(0, 65), {
381
+ name: "ECDH",
382
+ namedCurve: "P-256"
383
+ }, false, [])]);
384
+ const sharedSecret = await crypto.subtle.deriveBits({
385
+ name: "ECDH",
386
+ public: walletPublicKey
387
+ }, ecdhPrivateKey, 256);
388
+ const ecdhSecretKey = await crypto.subtle.importKey("raw", sharedSecret, "HKDF", false, ["deriveKey"]);
389
+ return await crypto.subtle.deriveKey({
390
+ name: "HKDF",
391
+ hash: "SHA-256",
392
+ salt: new Uint8Array(associationPublicKeyBuffer),
393
+ info: new Uint8Array()
394
+ }, ecdhSecretKey, {
395
+ name: "AES-GCM",
396
+ length: 128
397
+ }, false, ["encrypt", "decrypt"]);
398
+ }
399
+ //#endregion
400
+ //#region src/parseSessionProps.ts
449
401
  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/
402
+ const plaintext = await decryptMessage(message, sharedSecret);
403
+ const jsonProperties = JSON.parse(plaintext);
404
+ let protocolVersion = "legacy";
405
+ if (Object.hasOwnProperty.call(jsonProperties, "v")) switch (jsonProperties.v) {
406
+ case 1:
407
+ case "1":
408
+ case "v1":
409
+ protocolVersion = "v1";
410
+ break;
411
+ case "legacy":
412
+ protocolVersion = "legacy";
413
+ break;
414
+ default: throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_INVALID_PROTOCOL_VERSION, `Unknown/unsupported protocol version: ${jsonProperties.v}`);
415
+ }
416
+ return { protocol_version: protocolVersion };
417
+ }
418
+ //#endregion
419
+ //#region src/startSession.ts
473
420
  const Browser = {
474
- Firefox: 0,
475
- Other: 1,
421
+ Firefox: 0,
422
+ Other: 1
476
423
  };
477
424
  function assertUnreachable(x) {
478
- return x;
425
+ return x;
479
426
  }
480
427
  function getBrowser() {
481
- return navigator.userAgent.indexOf('Firefox/') !== -1 ? Browser.Firefox : Browser.Other;
428
+ return navigator.userAgent.indexOf("Firefox/") !== -1 ? Browser.Firefox : Browser.Other;
482
429
  }
483
430
  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
- });
431
+ return new Promise((resolve, reject) => {
432
+ function cleanup() {
433
+ clearTimeout(timeoutId);
434
+ window.removeEventListener("blur", handleBlur);
435
+ }
436
+ function handleBlur() {
437
+ cleanup();
438
+ resolve();
439
+ }
440
+ window.addEventListener("blur", handleBlur);
441
+ const timeoutId = setTimeout(() => {
442
+ cleanup();
443
+ reject();
444
+ }, 3e3);
445
+ });
502
446
  }
503
447
  let _frame = null;
504
448
  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();
449
+ if (_frame == null) {
450
+ _frame = document.createElement("iframe");
451
+ _frame.style.display = "none";
452
+ document.body.appendChild(_frame);
453
+ }
454
+ _frame.contentWindow.location.href = url.toString();
512
455
  }
513
456
  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
- }
457
+ if (associationUrl.protocol === "https:") window.location.assign(associationUrl);
458
+ else try {
459
+ const browser = getBrowser();
460
+ switch (browser) {
461
+ case Browser.Firefox:
462
+ launchUrlThroughHiddenFrame(associationUrl);
463
+ break;
464
+ case Browser.Other: {
465
+ const detectionPromise = getDetectionPromise();
466
+ window.location.assign(associationUrl);
467
+ await detectionPromise;
468
+ break;
469
+ }
470
+ default: assertUnreachable(browser);
471
+ }
472
+ } catch (e) {
473
+ throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_WALLET_NOT_FOUND, "Found no installed wallet that supports the mobile wallet protocol.");
474
+ }
544
475
  }
545
476
  async function startSession(associationPublicKey, associationURLBase) {
546
- const randomAssociationPort = getRandomAssociationPort();
547
- const associationUrl = await getAssociateAndroidIntentURL(associationPublicKey, randomAssociationPort, associationURLBase);
548
- await launchAssociation(associationUrl);
549
- return randomAssociationPort;
477
+ const randomAssociationPort = getRandomAssociationPort();
478
+ await launchAssociation(await getAssociateAndroidIntentURL(associationPublicKey, randomAssociationPort, associationURLBase));
479
+ return randomAssociationPort;
550
480
  }
551
-
481
+ //#endregion
482
+ //#region src/transact.ts
552
483
  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,
484
+ retryDelayScheduleMs: [
485
+ 150,
486
+ 150,
487
+ 200,
488
+ 500,
489
+ 500,
490
+ 750,
491
+ 750,
492
+ 1e3
493
+ ],
494
+ timeoutMs: 3e4
565
495
  };
566
- const WEBSOCKET_PROTOCOL_BINARY = 'com.solana.mobilewalletadapter.v1';
567
- const WEBSOCKET_PROTOCOL_BASE64 = 'com.solana.mobilewalletadapter.v1.base64';
496
+ const WEBSOCKET_PROTOCOL_BINARY = "com.solana.mobilewalletadapter.v1";
497
+ const WEBSOCKET_PROTOCOL_BASE64 = "com.solana.mobilewalletadapter.v1.base64";
568
498
  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
- }
499
+ 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
500
  }
573
501
  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
- }
502
+ let url;
503
+ try {
504
+ url = new URL(walletUriBase);
505
+ } catch {
506
+ throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_FORBIDDEN_WALLET_BASE_URL, "Invalid base URL supplied by wallet");
507
+ }
508
+ if (url.protocol !== "https:") throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_FORBIDDEN_WALLET_BASE_URL, "Base URLs supplied by wallets must be valid `https` URLs");
584
509
  }
585
510
  function getSequenceNumberFromByteArray(byteArray) {
586
- const view = new DataView(byteArray);
587
- return view.getUint32(0, /* littleEndian */ false);
511
+ return new DataView(byteArray).getUint32(0, false);
588
512
  }
589
513
  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 };
514
+ var bytes = new Uint8Array(byteArray), l = byteArray.byteLength, limit = 10, value = 0, offset = 0, b;
515
+ do {
516
+ if (offset >= l || offset > limit) throw new RangeError("Failed to decode varint");
517
+ b = bytes[offset++];
518
+ value |= (b & 127) << 7 * offset;
519
+ } while (b >= 128);
520
+ return {
521
+ value,
522
+ offset
523
+ };
598
524
  }
599
525
  function getReflectorIdFromByteArray(byteArray) {
600
- let { value: length, offset } = decodeVarLong(byteArray);
601
- return new Uint8Array(byteArray.slice(offset, offset + length));
526
+ let { value: length, offset } = decodeVarLong(byteArray);
527
+ return new Uint8Array(byteArray.slice(offset, offset + length));
602
528
  }
603
529
  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
- }
530
+ const { wallet, close } = await startScenario(config);
531
+ try {
532
+ return await callback(await wallet);
533
+ } finally {
534
+ close();
535
+ }
611
536
  }
612
537
  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
- }) };
538
+ assertSecureContext();
539
+ const associationKeypair = await generateAssociationKeypair();
540
+ const websocketURL = `ws://localhost:${await startSession(associationKeypair.publicKey, config?.baseUri)}/solana-wallet`;
541
+ let connectionStartTime;
542
+ const getNextRetryDelayMs = (() => {
543
+ const schedule = [...WEBSOCKET_CONNECTION_CONFIG.retryDelayScheduleMs];
544
+ return () => schedule.length > 1 ? schedule.shift() : schedule[0];
545
+ })();
546
+ let nextJsonRpcMessageId = 1;
547
+ let lastKnownInboundSequenceNumber = 0;
548
+ let state = { __type: "disconnected" };
549
+ let socket;
550
+ let sessionEstablished = false;
551
+ let handleForceClose;
552
+ return {
553
+ close: () => {
554
+ socket.close();
555
+ handleForceClose();
556
+ },
557
+ wallet: new Promise((resolve, reject) => {
558
+ const jsonRpcResponsePromises = {};
559
+ const handleOpen = async () => {
560
+ if (state.__type !== "connecting") {
561
+ console.warn(`Expected adapter state to be \`connecting\` at the moment the websocket opens. Got \`${state.__type}\`.`);
562
+ return;
563
+ }
564
+ socket.removeEventListener("open", handleOpen);
565
+ const { associationKeypair } = state;
566
+ const ecdhKeypair = await generateECDHKeypair();
567
+ socket.send(await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
568
+ state = {
569
+ __type: "hello_req_sent",
570
+ associationPublicKey: associationKeypair.publicKey,
571
+ ecdhPrivateKey: ecdhKeypair.privateKey
572
+ };
573
+ };
574
+ const handleClose = (evt) => {
575
+ if (evt.wasClean) state = { __type: "disconnected" };
576
+ else reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session dropped unexpectedly (${evt.code}: ${evt.reason}).`, { closeEvent: evt }));
577
+ disposeSocket();
578
+ };
579
+ const handleError = async (_evt) => {
580
+ disposeSocket();
581
+ if (Date.now() - connectionStartTime >= WEBSOCKET_CONNECTION_CONFIG.timeoutMs) reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_TIMEOUT, `Failed to connect to the wallet websocket at ${websocketURL}.`));
582
+ else {
583
+ await new Promise((resolve) => {
584
+ const retryDelayMs = getNextRetryDelayMs();
585
+ retryWaitTimeoutId = window.setTimeout(resolve, retryDelayMs);
586
+ });
587
+ attemptSocketConnection();
588
+ }
589
+ };
590
+ const handleMessage = async (evt) => {
591
+ const responseBuffer = await evt.data.arrayBuffer();
592
+ switch (state.__type) {
593
+ case "connecting":
594
+ if (responseBuffer.byteLength !== 0) throw new Error("Encountered unexpected message while connecting");
595
+ const ecdhKeypair = await generateECDHKeypair();
596
+ socket.send(await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
597
+ state = {
598
+ __type: "hello_req_sent",
599
+ associationPublicKey: associationKeypair.publicKey,
600
+ ecdhPrivateKey: ecdhKeypair.privateKey
601
+ };
602
+ break;
603
+ case "connected":
604
+ try {
605
+ const sequenceNumber = getSequenceNumberFromByteArray(responseBuffer.slice(0, 4));
606
+ if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) throw new Error("Encrypted message has invalid sequence number");
607
+ lastKnownInboundSequenceNumber = sequenceNumber;
608
+ const jsonRpcMessage = await decryptJsonRpcMessage(responseBuffer, state.sharedSecret);
609
+ const responsePromise = jsonRpcResponsePromises[jsonRpcMessage.id];
610
+ delete jsonRpcResponsePromises[jsonRpcMessage.id];
611
+ responsePromise.resolve(jsonRpcMessage.result);
612
+ } catch (e) {
613
+ if (e instanceof SolanaMobileWalletAdapterProtocolError) {
614
+ const responsePromise = jsonRpcResponsePromises[e.jsonRpcMessageId];
615
+ delete jsonRpcResponsePromises[e.jsonRpcMessageId];
616
+ responsePromise.reject(e);
617
+ } else throw e;
618
+ }
619
+ break;
620
+ case "hello_req_sent": {
621
+ if (responseBuffer.byteLength === 0) {
622
+ const ecdhKeypair = await generateECDHKeypair();
623
+ socket.send(await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
624
+ state = {
625
+ __type: "hello_req_sent",
626
+ associationPublicKey: associationKeypair.publicKey,
627
+ ecdhPrivateKey: ecdhKeypair.privateKey
628
+ };
629
+ break;
630
+ }
631
+ const sharedSecret = await parseHelloRsp(responseBuffer, state.associationPublicKey, state.ecdhPrivateKey);
632
+ const sessionPropertiesBuffer = responseBuffer.slice(65);
633
+ const sessionProperties = sessionPropertiesBuffer.byteLength !== 0 ? await (async () => {
634
+ const sequenceNumber = getSequenceNumberFromByteArray(sessionPropertiesBuffer.slice(0, 4));
635
+ if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) throw new Error("Encrypted message has invalid sequence number");
636
+ lastKnownInboundSequenceNumber = sequenceNumber;
637
+ return parseSessionProps(sessionPropertiesBuffer, sharedSecret);
638
+ })() : { protocol_version: "legacy" };
639
+ state = {
640
+ __type: "connected",
641
+ sharedSecret,
642
+ sessionProperties
643
+ };
644
+ const wallet = createMobileWalletProxy(sessionProperties.protocol_version, async (method, params) => {
645
+ const id = nextJsonRpcMessageId++;
646
+ socket.send(await encryptJsonRpcMessage({
647
+ id,
648
+ jsonrpc: "2.0",
649
+ method,
650
+ params: params ?? {}
651
+ }, sharedSecret));
652
+ return new Promise((resolve, reject) => {
653
+ jsonRpcResponsePromises[id] = {
654
+ resolve(result) {
655
+ switch (method) {
656
+ case "authorize":
657
+ case "reauthorize": {
658
+ const { wallet_uri_base } = result;
659
+ if (wallet_uri_base != null) try {
660
+ assertSecureEndpointSpecificURI(wallet_uri_base);
661
+ } catch (e) {
662
+ reject(e);
663
+ return;
664
+ }
665
+ break;
666
+ }
667
+ }
668
+ resolve(result);
669
+ },
670
+ reject
671
+ };
672
+ });
673
+ });
674
+ sessionEstablished = true;
675
+ try {
676
+ resolve(wallet);
677
+ } catch (e) {
678
+ reject(e);
679
+ }
680
+ break;
681
+ }
682
+ }
683
+ };
684
+ handleForceClose = () => {
685
+ socket.removeEventListener("message", handleMessage);
686
+ disposeSocket();
687
+ if (!sessionEstablished) reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session was closed before connection.`, { closeEvent: new CloseEvent("socket was closed before connection") }));
688
+ };
689
+ let disposeSocket;
690
+ let retryWaitTimeoutId;
691
+ const attemptSocketConnection = () => {
692
+ if (disposeSocket) disposeSocket();
693
+ state = {
694
+ __type: "connecting",
695
+ associationKeypair
696
+ };
697
+ if (connectionStartTime === void 0) connectionStartTime = Date.now();
698
+ socket = new WebSocket(websocketURL, [WEBSOCKET_PROTOCOL_BINARY]);
699
+ socket.addEventListener("open", handleOpen);
700
+ socket.addEventListener("close", handleClose);
701
+ socket.addEventListener("error", handleError);
702
+ socket.addEventListener("message", handleMessage);
703
+ disposeSocket = () => {
704
+ window.clearTimeout(retryWaitTimeoutId);
705
+ socket.removeEventListener("open", handleOpen);
706
+ socket.removeEventListener("close", handleClose);
707
+ socket.removeEventListener("error", handleError);
708
+ socket.removeEventListener("message", handleMessage);
709
+ };
710
+ };
711
+ attemptSocketConnection();
712
+ })
713
+ };
818
714
  }
819
715
  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
-
716
+ assertSecureContext();
717
+ const associationKeypair = await generateAssociationKeypair();
718
+ const websocketURL = `wss://${config?.remoteHostAuthority}/reflect`;
719
+ let connectionStartTime;
720
+ const getNextRetryDelayMs = (() => {
721
+ const schedule = [...WEBSOCKET_CONNECTION_CONFIG.retryDelayScheduleMs];
722
+ return () => schedule.length > 1 ? schedule.shift() : schedule[0];
723
+ })();
724
+ let nextJsonRpcMessageId = 1;
725
+ let lastKnownInboundSequenceNumber = 0;
726
+ let encoding;
727
+ let state = { __type: "disconnected" };
728
+ let socket;
729
+ let disposeSocket;
730
+ let decodeBytes = async (evt) => {
731
+ if (encoding == "base64") return toUint8Array(await evt.data).buffer;
732
+ else return await evt.data.arrayBuffer();
733
+ };
734
+ const associationUrl = await new Promise((resolve, reject) => {
735
+ const handleOpen = async () => {
736
+ if (state.__type !== "connecting") {
737
+ console.warn(`Expected adapter state to be \`connecting\` at the moment the websocket opens. Got \`${state.__type}\`.`);
738
+ return;
739
+ }
740
+ if (socket.protocol.includes(WEBSOCKET_PROTOCOL_BASE64)) encoding = "base64";
741
+ else encoding = "binary";
742
+ socket.removeEventListener("open", handleOpen);
743
+ };
744
+ const handleClose = (evt) => {
745
+ if (evt.wasClean) state = { __type: "disconnected" };
746
+ else reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session dropped unexpectedly (${evt.code}: ${evt.reason}).`, { closeEvent: evt }));
747
+ disposeSocket();
748
+ };
749
+ const handleError = async (_evt) => {
750
+ disposeSocket();
751
+ if (Date.now() - connectionStartTime >= WEBSOCKET_CONNECTION_CONFIG.timeoutMs) reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_TIMEOUT, `Failed to connect to the wallet websocket at ${websocketURL}.`));
752
+ else {
753
+ await new Promise((resolve) => {
754
+ const retryDelayMs = getNextRetryDelayMs();
755
+ retryWaitTimeoutId = window.setTimeout(resolve, retryDelayMs);
756
+ });
757
+ attemptSocketConnection();
758
+ }
759
+ };
760
+ const handleReflectorIdMessage = async (evt) => {
761
+ const responseBuffer = await decodeBytes(evt);
762
+ if (state.__type === "connecting") {
763
+ if (responseBuffer.byteLength == 0) throw new Error("Encountered unexpected message while connecting");
764
+ const reflectorId = getReflectorIdFromByteArray(responseBuffer);
765
+ state = {
766
+ __type: "reflector_id_received",
767
+ reflectorId
768
+ };
769
+ const associationUrl = await getRemoteAssociateAndroidIntentURL(associationKeypair.publicKey, config.remoteHostAuthority, reflectorId, config?.baseUri);
770
+ socket.removeEventListener("message", handleReflectorIdMessage);
771
+ resolve(associationUrl);
772
+ }
773
+ };
774
+ let retryWaitTimeoutId;
775
+ const attemptSocketConnection = () => {
776
+ if (disposeSocket) disposeSocket();
777
+ state = {
778
+ __type: "connecting",
779
+ associationKeypair
780
+ };
781
+ if (connectionStartTime === void 0) connectionStartTime = Date.now();
782
+ socket = new WebSocket(websocketURL, [WEBSOCKET_PROTOCOL_BINARY, WEBSOCKET_PROTOCOL_BASE64]);
783
+ socket.addEventListener("open", handleOpen);
784
+ socket.addEventListener("close", handleClose);
785
+ socket.addEventListener("error", handleError);
786
+ socket.addEventListener("message", handleReflectorIdMessage);
787
+ disposeSocket = () => {
788
+ window.clearTimeout(retryWaitTimeoutId);
789
+ socket.removeEventListener("open", handleOpen);
790
+ socket.removeEventListener("close", handleClose);
791
+ socket.removeEventListener("error", handleError);
792
+ socket.removeEventListener("message", handleReflectorIdMessage);
793
+ };
794
+ };
795
+ attemptSocketConnection();
796
+ });
797
+ let sessionEstablished = false;
798
+ let handleClose;
799
+ return {
800
+ associationUrl,
801
+ close: () => {
802
+ socket.close();
803
+ handleClose();
804
+ },
805
+ wallet: new Promise((resolve, reject) => {
806
+ const jsonRpcResponsePromises = {};
807
+ const handleMessage = async (evt) => {
808
+ const responseBuffer = await decodeBytes(evt);
809
+ switch (state.__type) {
810
+ case "reflector_id_received":
811
+ if (responseBuffer.byteLength !== 0) throw new Error("Encountered unexpected message while awaiting reflection");
812
+ const ecdhKeypair = await generateECDHKeypair();
813
+ const binaryMsg = await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey);
814
+ if (encoding == "base64") socket.send(fromUint8Array$1(binaryMsg));
815
+ else socket.send(binaryMsg);
816
+ state = {
817
+ __type: "hello_req_sent",
818
+ associationPublicKey: associationKeypair.publicKey,
819
+ ecdhPrivateKey: ecdhKeypair.privateKey
820
+ };
821
+ break;
822
+ case "connected":
823
+ try {
824
+ const sequenceNumber = getSequenceNumberFromByteArray(responseBuffer.slice(0, 4));
825
+ if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) throw new Error("Encrypted message has invalid sequence number");
826
+ lastKnownInboundSequenceNumber = sequenceNumber;
827
+ const jsonRpcMessage = await decryptJsonRpcMessage(responseBuffer, state.sharedSecret);
828
+ const responsePromise = jsonRpcResponsePromises[jsonRpcMessage.id];
829
+ delete jsonRpcResponsePromises[jsonRpcMessage.id];
830
+ responsePromise.resolve(jsonRpcMessage.result);
831
+ } catch (e) {
832
+ if (e instanceof SolanaMobileWalletAdapterProtocolError) {
833
+ const responsePromise = jsonRpcResponsePromises[e.jsonRpcMessageId];
834
+ delete jsonRpcResponsePromises[e.jsonRpcMessageId];
835
+ responsePromise.reject(e);
836
+ } else throw e;
837
+ }
838
+ break;
839
+ case "hello_req_sent": {
840
+ const sharedSecret = await parseHelloRsp(responseBuffer, state.associationPublicKey, state.ecdhPrivateKey);
841
+ const sessionPropertiesBuffer = responseBuffer.slice(65);
842
+ const sessionProperties = sessionPropertiesBuffer.byteLength !== 0 ? await (async () => {
843
+ const sequenceNumber = getSequenceNumberFromByteArray(sessionPropertiesBuffer.slice(0, 4));
844
+ if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) throw new Error("Encrypted message has invalid sequence number");
845
+ lastKnownInboundSequenceNumber = sequenceNumber;
846
+ return parseSessionProps(sessionPropertiesBuffer, sharedSecret);
847
+ })() : { protocol_version: "legacy" };
848
+ state = {
849
+ __type: "connected",
850
+ sharedSecret,
851
+ sessionProperties
852
+ };
853
+ const wallet = createMobileWalletProxy(sessionProperties.protocol_version, async (method, params) => {
854
+ const id = nextJsonRpcMessageId++;
855
+ const binaryMsg = await encryptJsonRpcMessage({
856
+ id,
857
+ jsonrpc: "2.0",
858
+ method,
859
+ params: params ?? {}
860
+ }, sharedSecret);
861
+ if (encoding == "base64") socket.send(fromUint8Array$1(binaryMsg));
862
+ else socket.send(binaryMsg);
863
+ return new Promise((resolve, reject) => {
864
+ jsonRpcResponsePromises[id] = {
865
+ resolve(result) {
866
+ switch (method) {
867
+ case "authorize":
868
+ case "reauthorize": {
869
+ const { wallet_uri_base } = result;
870
+ if (wallet_uri_base != null) try {
871
+ assertSecureEndpointSpecificURI(wallet_uri_base);
872
+ } catch (e) {
873
+ reject(e);
874
+ return;
875
+ }
876
+ break;
877
+ }
878
+ }
879
+ resolve(result);
880
+ },
881
+ reject
882
+ };
883
+ });
884
+ });
885
+ sessionEstablished = true;
886
+ try {
887
+ resolve(wallet);
888
+ } catch (e) {
889
+ reject(e);
890
+ }
891
+ break;
892
+ }
893
+ }
894
+ };
895
+ socket.addEventListener("message", handleMessage);
896
+ handleClose = () => {
897
+ socket.removeEventListener("message", handleMessage);
898
+ disposeSocket();
899
+ if (!sessionEstablished) reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session was closed before connection.`, { closeEvent: new CloseEvent("socket was closed before connection") }));
900
+ };
901
+ })
902
+ };
903
+ }
904
+ //#endregion
1054
905
  exports.SolanaCloneAuthorization = SolanaCloneAuthorization;
1055
906
  exports.SolanaMobileWalletAdapterError = SolanaMobileWalletAdapterError;
1056
907
  exports.SolanaMobileWalletAdapterErrorCode = SolanaMobileWalletAdapterErrorCode;
@@ -1061,3 +912,5 @@ exports.SolanaSignTransactions = SolanaSignTransactions;
1061
912
  exports.startRemoteScenario = startRemoteScenario;
1062
913
  exports.startScenario = startScenario;
1063
914
  exports.transact = transact;
915
+
916
+ //# sourceMappingURL=index.browser.js.map