@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/README.md +9 -6
- package/android/build.gradle +3 -3
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +2 -1
- package/android/gradle.properties +1 -1
- package/android/gradlew +173 -110
- package/android/gradlew.bat +22 -18
- package/lib/cjs/index.browser.js +844 -1020
- package/lib/cjs/index.browser.js.map +1 -0
- package/lib/cjs/index.js +844 -1020
- package/lib/cjs/index.js.map +1 -0
- package/lib/cjs/index.native.js +241 -312
- package/lib/cjs/index.native.js.map +1 -0
- package/lib/cjs/package.json +1 -3
- package/lib/esm/index.browser.js +842 -1016
- package/lib/esm/index.browser.js.map +1 -0
- package/lib/esm/index.js +842 -1016
- package/lib/esm/index.js.map +1 -0
- package/lib/esm/package.json +1 -3
- package/lib/types/index.d.ts +174 -176
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +75 -75
- package/lib/types/index.browser.d.ts +0 -263
- package/lib/types/index.browser.d.ts.map +0 -1
- package/lib/types/index.native.d.ts +0 -263
- package/lib/types/index.native.d.ts.map +0 -1
package/lib/cjs/index.browser.js
CHANGED
|
@@ -1,1086 +1,907 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
53
|
+
return window.btoa(input);
|
|
77
54
|
}
|
|
78
55
|
function fromUint8Array$1(byteArray, urlsafe) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
79
|
+
return (0, _solana_wallet_standard_util.createSignInMessageText)(payload);
|
|
109
80
|
}
|
|
110
81
|
function createSIWSMessageBase64Url(payload) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
92
|
+
return (0, _solana_codecs_strings.getBase58Decoder)().decode(byteArray);
|
|
124
93
|
}
|
|
125
94
|
function base64ToBase58(base64EncodedString) {
|
|
126
|
-
|
|
95
|
+
return fromUint8Array(toUint8Array(base64EncodedString));
|
|
127
96
|
}
|
|
128
|
-
|
|
97
|
+
//#endregion
|
|
98
|
+
//#region src/createMobileWalletProxy.ts
|
|
129
99
|
/**
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
-
|
|
309
|
+
return assertAssociationPort(49152 + Math.floor(Math.random() * 16384));
|
|
378
310
|
}
|
|
379
311
|
function assertAssociationPort(port) {
|
|
380
|
-
|
|
381
|
-
|
|
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
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
|
|
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
|
-
|
|
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
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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
|
-
|
|
515
|
-
|
|
421
|
+
Firefox: 0,
|
|
422
|
+
Other: 1
|
|
516
423
|
};
|
|
517
424
|
function assertUnreachable(x) {
|
|
518
|
-
|
|
425
|
+
return x;
|
|
519
426
|
}
|
|
520
427
|
function getBrowser() {
|
|
521
|
-
|
|
428
|
+
return navigator.userAgent.indexOf("Firefox/") !== -1 ? Browser.Firefox : Browser.Other;
|
|
522
429
|
}
|
|
523
430
|
function getDetectionPromise() {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
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
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
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
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
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 =
|
|
611
|
-
const WEBSOCKET_PROTOCOL_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
|
-
|
|
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
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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
|
-
|
|
631
|
-
return view.getUint32(0, /* littleEndian */ false);
|
|
511
|
+
return new DataView(byteArray).getUint32(0, false);
|
|
632
512
|
}
|
|
633
513
|
function decodeVarLong(byteArray) {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
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
|
-
|
|
645
|
-
|
|
646
|
-
}
|
|
647
|
-
function transact(callback, config) {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
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.browser.js.map
|