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