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