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