@thru/wallet 0.2.22
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 +67 -0
- package/android/build.gradle +37 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/org/thru/walletnative/ThruWebViewBridgeModule.kt +77 -0
- package/app.plugin.cjs +101 -0
- package/dist/BrowserSDK-CpRFiJsW.d.ts +409 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +941 -0
- package/dist/index.js.map +1 -0
- package/dist/native/react.d.ts +109 -0
- package/dist/native/react.js +2381 -0
- package/dist/native/react.js.map +1 -0
- package/dist/native.d.ts +329 -0
- package/dist/native.js +1126 -0
- package/dist/native.js.map +1 -0
- package/dist/react-ui.d.ts +5 -0
- package/dist/react-ui.js +266 -0
- package/dist/react-ui.js.map +1 -0
- package/dist/react.d.ts +66 -0
- package/dist/react.js +1151 -0
- package/dist/react.js.map +1 -0
- package/expo-module.config.json +6 -0
- package/package.json +114 -0
- package/src/BrowserSDK.ts +315 -0
- package/src/index.ts +27 -0
- package/src/interfaces/IThruChain.ts +37 -0
- package/src/interfaces/accounts.ts +61 -0
- package/src/interfaces/index.ts +9 -0
- package/src/interfaces/types.ts +95 -0
- package/src/native/NativeSDK.test.ts +819 -0
- package/src/native/NativeSDK.ts +773 -0
- package/src/native/index.ts +39 -0
- package/src/native/provider/NativeProvider.ts +363 -0
- package/src/native/provider/WebViewBridge.test.ts +339 -0
- package/src/native/provider/WebViewBridge.ts +339 -0
- package/src/native/provider/chains/ThruChain.ts +85 -0
- package/src/native/provider/shell.html +88 -0
- package/src/native/provider/shell.test.ts +56 -0
- package/src/native/provider/shell.ts +111 -0
- package/src/native/provider/shims-html.d.ts +4 -0
- package/src/native/react/ThruContext.ts +37 -0
- package/src/native/react/ThruProvider.tsx +168 -0
- package/src/native/react/ThruWalletSheet.tsx +1162 -0
- package/src/native/react/android-webauthn.ts +37 -0
- package/src/native/react/hooks/useAccounts.ts +35 -0
- package/src/native/react/hooks/useThru.ts +11 -0
- package/src/native/react/hooks/useWallet.ts +71 -0
- package/src/native/react/hooks/useWalletAvailability.ts +31 -0
- package/src/native/react/hooks/waitForWallet.ts +21 -0
- package/src/native/react/index.ts +29 -0
- package/src/protocol/index.ts +2 -0
- package/src/protocol/postMessage.ts +283 -0
- package/src/protocol/walletState.ts +12 -0
- package/src/provider/EmbeddedProvider.ts +330 -0
- package/src/provider/IframeManager.ts +438 -0
- package/src/provider/chains/ThruChain.ts +86 -0
- package/src/provider/index.ts +17 -0
- package/src/provider/types/messages.ts +37 -0
- package/src/react/ThruContext.ts +31 -0
- package/src/react/ThruProvider.tsx +169 -0
- package/src/react/hooks/useAccounts.ts +38 -0
- package/src/react/hooks/useThru.ts +11 -0
- package/src/react/hooks/useWallet.ts +81 -0
- package/src/react/index.ts +30 -0
- package/src/react-ui/ThruAccountSwitcher.tsx +187 -0
- package/src/react-ui/custom.d.ts +8 -0
- package/src/react-ui/index.ts +1 -0
- package/src/static/logo.png +0 -0
- package/src/static/logomark_red.svg +11 -0
package/dist/react.js
ADDED
|
@@ -0,0 +1,1151 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createThruClient } from '@thru/sdk/client';
|
|
3
|
+
import { createContext, useState, useEffect, useCallback, useContext, useMemo, useRef } from 'react';
|
|
4
|
+
import { jsx } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
// src/interfaces/accounts.ts
|
|
7
|
+
function resolveSelectedWalletAccount(accounts, selectedAccount) {
|
|
8
|
+
if (selectedAccount) {
|
|
9
|
+
return accounts.find((account) => account.address === selectedAccount.address) ?? selectedAccount;
|
|
10
|
+
}
|
|
11
|
+
return accounts[0] ?? null;
|
|
12
|
+
}
|
|
13
|
+
function normalizeActiveWalletAccounts(accounts, selectedAccount) {
|
|
14
|
+
const activeAccount = resolveSelectedWalletAccount(accounts, selectedAccount);
|
|
15
|
+
return {
|
|
16
|
+
accounts: activeAccount ? [activeAccount] : [],
|
|
17
|
+
selectedAccount: activeAccount
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function normalizeWalletAccountResult(result, selectedAccount) {
|
|
21
|
+
const active = normalizeActiveWalletAccounts(
|
|
22
|
+
result.accounts,
|
|
23
|
+
result.selectedAccount ?? null
|
|
24
|
+
);
|
|
25
|
+
return {
|
|
26
|
+
...result,
|
|
27
|
+
accounts: active.accounts,
|
|
28
|
+
selectedAccount: active.selectedAccount
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// src/interfaces/types.ts
|
|
33
|
+
var AddressType = {
|
|
34
|
+
THRU: "thru"
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// src/protocol/postMessage.ts
|
|
38
|
+
var POST_MESSAGE_REQUEST_TYPES = {
|
|
39
|
+
CONNECT: "connect",
|
|
40
|
+
DISCONNECT: "disconnect",
|
|
41
|
+
SIGN_MESSAGE: "signMessage",
|
|
42
|
+
SIGN_TRANSACTION: "signTransaction",
|
|
43
|
+
GET_ACCOUNTS: "getAccounts",
|
|
44
|
+
GET_CONNECTION_STATE: "getConnectionState",
|
|
45
|
+
GET_SIGNING_CONTEXT: "getSigningContext",
|
|
46
|
+
SELECT_ACCOUNT: "selectAccount",
|
|
47
|
+
MANAGE_ACCOUNTS: "manageAccounts"
|
|
48
|
+
};
|
|
49
|
+
var EMBEDDED_PROVIDER_EVENTS = {
|
|
50
|
+
CONNECT_START: "connect_start",
|
|
51
|
+
CONNECT: "connect",
|
|
52
|
+
DISCONNECT: "disconnect",
|
|
53
|
+
CONNECT_ERROR: "connect_error",
|
|
54
|
+
ERROR: "error",
|
|
55
|
+
LOCK: "lock",
|
|
56
|
+
UI_SHOW: "ui_show",
|
|
57
|
+
ACCOUNT_CHANGED: "account_changed"
|
|
58
|
+
};
|
|
59
|
+
var POST_MESSAGE_EVENT_TYPE = "event";
|
|
60
|
+
var IFRAME_READY_EVENT = "iframe:ready";
|
|
61
|
+
var DEFAULT_IFRAME_URL = "http://localhost:3000/embedded";
|
|
62
|
+
var REQUEST_ID_PREFIX = "req";
|
|
63
|
+
var createRequestId = (prefix = REQUEST_ID_PREFIX) => {
|
|
64
|
+
const random = Math.random().toString(36).slice(2, 11);
|
|
65
|
+
return `${prefix}_${Date.now()}_${random}`;
|
|
66
|
+
};
|
|
67
|
+
var ErrorCode = {
|
|
68
|
+
USER_REJECTED: "USER_REJECTED",
|
|
69
|
+
WALLET_LOCKED: "WALLET_LOCKED",
|
|
70
|
+
INVALID_PASSWORD: "INVALID_PASSWORD",
|
|
71
|
+
ALREADY_CONNECTED: "ALREADY_CONNECTED",
|
|
72
|
+
ACCOUNT_NOT_FOUND: "ACCOUNT_NOT_FOUND",
|
|
73
|
+
ACCOUNT_CHANGED: "ACCOUNT_CHANGED",
|
|
74
|
+
INVALID_TRANSACTION: "INVALID_TRANSACTION",
|
|
75
|
+
TRANSACTION_FAILED: "TRANSACTION_FAILED",
|
|
76
|
+
INSUFFICIENT_FUNDS: "INSUFFICIENT_FUNDS",
|
|
77
|
+
NETWORK_ERROR: "NETWORK_ERROR",
|
|
78
|
+
TIMEOUT: "TIMEOUT",
|
|
79
|
+
UNKNOWN_ERROR: "UNKNOWN_ERROR"
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// src/provider/IframeManager.ts
|
|
83
|
+
var PRODUCTION_IFRAME_ORIGINS = ["https://wallet.thru.org"];
|
|
84
|
+
var SLOW_REQUEST_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
85
|
+
var FAST_REQUEST_TIMEOUT_MS = 30 * 1e3;
|
|
86
|
+
var SLOW_REQUEST_TYPES = /* @__PURE__ */ new Set([
|
|
87
|
+
POST_MESSAGE_REQUEST_TYPES.CONNECT,
|
|
88
|
+
POST_MESSAGE_REQUEST_TYPES.SIGN_MESSAGE,
|
|
89
|
+
POST_MESSAGE_REQUEST_TYPES.SIGN_TRANSACTION,
|
|
90
|
+
POST_MESSAGE_REQUEST_TYPES.MANAGE_ACCOUNTS
|
|
91
|
+
]);
|
|
92
|
+
function isPrivateIpv4Host(hostname) {
|
|
93
|
+
const parts = hostname.split(".").map((part) => Number(part));
|
|
94
|
+
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
const [a, b] = parts;
|
|
98
|
+
return a === 10 || a === 127 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 100 && b >= 64 && b <= 127;
|
|
99
|
+
}
|
|
100
|
+
function isDevelopmentHostname(hostname) {
|
|
101
|
+
return hostname === "localhost" || hostname === "::1" || !hostname.includes(".") || hostname.endsWith(".local") || hostname.endsWith(".ts.net") || isPrivateIpv4Host(hostname);
|
|
102
|
+
}
|
|
103
|
+
function isAllowedDevelopmentOrigin(url) {
|
|
104
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return false;
|
|
105
|
+
if (typeof window === "undefined") return false;
|
|
106
|
+
const appHostname = window.location.hostname.toLowerCase();
|
|
107
|
+
if (!isDevelopmentHostname(appHostname)) return false;
|
|
108
|
+
return isDevelopmentHostname(url.hostname.toLowerCase());
|
|
109
|
+
}
|
|
110
|
+
function validateIframeOrigin(iframeUrl) {
|
|
111
|
+
let url;
|
|
112
|
+
try {
|
|
113
|
+
url = new URL(iframeUrl);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`Invalid iframe URL: ${iframeUrl}. URL must be a valid absolute URL.`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
const origin = url.origin;
|
|
120
|
+
const isAllowed = PRODUCTION_IFRAME_ORIGINS.includes(origin) || isAllowedDevelopmentOrigin(url);
|
|
121
|
+
if (!isAllowed) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`Untrusted iframe origin: ${origin}. Only trusted wallet origins are allowed: ${PRODUCTION_IFRAME_ORIGINS.join(", ")}. Development builds also allow localhost, LAN, and Tailscale wallet origins. This security check prevents malicious websites from loading unauthorized wallet iframes.`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
var IframeManager = class {
|
|
128
|
+
constructor(iframeUrl) {
|
|
129
|
+
this.iframe = null;
|
|
130
|
+
this.messageHandlers = /* @__PURE__ */ new Map();
|
|
131
|
+
this.messageListener = null;
|
|
132
|
+
this.readyPromise = null;
|
|
133
|
+
this.displayMode = "modal";
|
|
134
|
+
this.inlineContainer = null;
|
|
135
|
+
this.visible = false;
|
|
136
|
+
validateIframeOrigin(iframeUrl);
|
|
137
|
+
this.iframeUrl = iframeUrl;
|
|
138
|
+
this.iframeOrigin = new URL(iframeUrl).origin;
|
|
139
|
+
this.frameId = createRequestId("frame");
|
|
140
|
+
}
|
|
141
|
+
getIframeSrc() {
|
|
142
|
+
const url = new URL(this.iframeUrl);
|
|
143
|
+
url.searchParams.set("tn_frame_id", this.frameId);
|
|
144
|
+
return url.toString();
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Create and inject iframe into DOM
|
|
148
|
+
* Returns a promise that resolves when iframe is ready
|
|
149
|
+
*/
|
|
150
|
+
async createIframe() {
|
|
151
|
+
if (this.readyPromise) {
|
|
152
|
+
return this.readyPromise;
|
|
153
|
+
}
|
|
154
|
+
this.readyPromise = (async () => {
|
|
155
|
+
if (!this.iframe) {
|
|
156
|
+
this.iframe = document.createElement("iframe");
|
|
157
|
+
this.iframe.src = this.getIframeSrc();
|
|
158
|
+
this.iframe.allow = "publickey-credentials-get; publickey-credentials-create";
|
|
159
|
+
this.applyIframeStyles();
|
|
160
|
+
this.setVisibility(false);
|
|
161
|
+
if (this.displayMode === "inline" && this.inlineContainer) {
|
|
162
|
+
this.inlineContainer.appendChild(this.iframe);
|
|
163
|
+
} else {
|
|
164
|
+
document.body.appendChild(this.iframe);
|
|
165
|
+
}
|
|
166
|
+
this.messageListener = this.handleMessage.bind(this);
|
|
167
|
+
window.addEventListener("message", this.messageListener);
|
|
168
|
+
}
|
|
169
|
+
await this.waitForReady();
|
|
170
|
+
})().catch((error) => {
|
|
171
|
+
this.readyPromise = null;
|
|
172
|
+
throw error;
|
|
173
|
+
});
|
|
174
|
+
return this.readyPromise;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Wait for iframe to send 'ready' signal
|
|
178
|
+
*/
|
|
179
|
+
waitForReady() {
|
|
180
|
+
return new Promise((resolve, reject) => {
|
|
181
|
+
let resolved = false;
|
|
182
|
+
let readyHandler;
|
|
183
|
+
const cleanup = () => {
|
|
184
|
+
if (resolved) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
resolved = true;
|
|
188
|
+
window.removeEventListener("message", readyHandler);
|
|
189
|
+
clearTimeout(timeout);
|
|
190
|
+
};
|
|
191
|
+
const timeout = setTimeout(() => {
|
|
192
|
+
cleanup();
|
|
193
|
+
reject(new Error("Iframe ready timeout - wallet failed to load"));
|
|
194
|
+
}, 1e4);
|
|
195
|
+
readyHandler = (event) => {
|
|
196
|
+
if (!this.isMessageFromIframe(event)) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (event.data?.type === IFRAME_READY_EVENT) {
|
|
200
|
+
cleanup();
|
|
201
|
+
resolve();
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
window.addEventListener("message", readyHandler);
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Mount iframe inline inside the provided container.
|
|
209
|
+
*/
|
|
210
|
+
async mountInline(container) {
|
|
211
|
+
this.inlineContainer = container;
|
|
212
|
+
this.displayMode = "inline";
|
|
213
|
+
await this.createIframe();
|
|
214
|
+
this.showInline();
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Show iframe inline (embedded in container).
|
|
218
|
+
*/
|
|
219
|
+
showInline() {
|
|
220
|
+
if (!this.iframe) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
this.displayMode = "inline";
|
|
224
|
+
if (this.inlineContainer && this.iframe.parentElement !== this.inlineContainer) {
|
|
225
|
+
this.inlineContainer.appendChild(this.iframe);
|
|
226
|
+
}
|
|
227
|
+
this.applyIframeStyles();
|
|
228
|
+
this.setVisibility(true);
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Show iframe as a full-screen modal.
|
|
232
|
+
*/
|
|
233
|
+
showModal() {
|
|
234
|
+
if (!this.iframe) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
this.displayMode = "modal";
|
|
238
|
+
if (this.iframe.parentElement !== document.body) {
|
|
239
|
+
document.body.appendChild(this.iframe);
|
|
240
|
+
}
|
|
241
|
+
this.applyIframeStyles();
|
|
242
|
+
this.setVisibility(true);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Show iframe modal
|
|
246
|
+
*/
|
|
247
|
+
show() {
|
|
248
|
+
this.showModal();
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Hide iframe modal
|
|
252
|
+
*/
|
|
253
|
+
hide() {
|
|
254
|
+
this.setVisibility(false);
|
|
255
|
+
}
|
|
256
|
+
isInline() {
|
|
257
|
+
return this.displayMode === "inline";
|
|
258
|
+
}
|
|
259
|
+
applyIframeStyles() {
|
|
260
|
+
if (!this.iframe) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (this.displayMode === "inline") {
|
|
264
|
+
this.iframe.style.cssText = `
|
|
265
|
+
position: relative;
|
|
266
|
+
width: 100%;
|
|
267
|
+
height: 100%;
|
|
268
|
+
border: none;
|
|
269
|
+
z-index: 1;
|
|
270
|
+
display: block;
|
|
271
|
+
background: transparent;
|
|
272
|
+
`;
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
this.iframe.style.cssText = `
|
|
276
|
+
position: fixed;
|
|
277
|
+
top: 0;
|
|
278
|
+
left: 0;
|
|
279
|
+
width: 100%;
|
|
280
|
+
height: 100%;
|
|
281
|
+
border: none;
|
|
282
|
+
z-index: 999999;
|
|
283
|
+
display: block;
|
|
284
|
+
background: rgba(0, 0, 0, 0.5);
|
|
285
|
+
`;
|
|
286
|
+
}
|
|
287
|
+
setVisibility(visible) {
|
|
288
|
+
if (!this.iframe) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
this.visible = visible;
|
|
292
|
+
this.iframe.style.opacity = visible ? "1" : "0";
|
|
293
|
+
this.iframe.style.pointerEvents = visible ? "auto" : "none";
|
|
294
|
+
this.iframe.style.visibility = visible ? "visible" : "hidden";
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Send message to iframe and wait for response
|
|
298
|
+
*/
|
|
299
|
+
async sendMessage(request) {
|
|
300
|
+
if (this.readyPromise) {
|
|
301
|
+
await this.readyPromise;
|
|
302
|
+
} else {
|
|
303
|
+
await this.createIframe();
|
|
304
|
+
}
|
|
305
|
+
if (!this.iframe?.contentWindow) {
|
|
306
|
+
throw new Error("Iframe not initialized - call createIframe() first");
|
|
307
|
+
}
|
|
308
|
+
return new Promise((resolve, reject) => {
|
|
309
|
+
const timeoutMs = SLOW_REQUEST_TYPES.has(request.type) ? SLOW_REQUEST_TIMEOUT_MS : FAST_REQUEST_TIMEOUT_MS;
|
|
310
|
+
const timeout = setTimeout(() => {
|
|
311
|
+
this.messageHandlers.delete(request.id);
|
|
312
|
+
reject(new Error("Request timeout - wallet did not respond"));
|
|
313
|
+
}, timeoutMs);
|
|
314
|
+
this.messageHandlers.set(request.id, (response) => {
|
|
315
|
+
clearTimeout(timeout);
|
|
316
|
+
this.messageHandlers.delete(request.id);
|
|
317
|
+
if (response.success) {
|
|
318
|
+
resolve(response);
|
|
319
|
+
} else {
|
|
320
|
+
const error = new Error(response.error?.message || "Unknown error");
|
|
321
|
+
error.code = response.error?.code;
|
|
322
|
+
error.data = response.error?.data;
|
|
323
|
+
reject(error);
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
this.iframe.contentWindow.postMessage(request, this.iframeOrigin);
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Handle incoming messages from iframe
|
|
331
|
+
*/
|
|
332
|
+
handleMessage(event) {
|
|
333
|
+
if (!this.isMessageFromIframe(event)) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
const data = event.data;
|
|
337
|
+
if (data.id && this.messageHandlers.has(data.id)) {
|
|
338
|
+
const handler = this.messageHandlers.get(data.id);
|
|
339
|
+
if (handler) {
|
|
340
|
+
handler(data);
|
|
341
|
+
}
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
if (data.type === POST_MESSAGE_EVENT_TYPE) {
|
|
345
|
+
this.handleEvent(data);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Handle event broadcasts from iframe
|
|
350
|
+
*/
|
|
351
|
+
handleEvent(data) {
|
|
352
|
+
if (this.onEvent) {
|
|
353
|
+
this.onEvent(data.event, data.data);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
isMessageFromIframe(event) {
|
|
357
|
+
if (event.origin !== this.iframeOrigin) {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
const data = event.data;
|
|
361
|
+
if (!data || data.frameId !== this.frameId) {
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
if (!event.source) {
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
if (this.iframe?.contentWindow && event.source !== this.iframe.contentWindow) {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Destroy iframe and cleanup
|
|
374
|
+
*/
|
|
375
|
+
destroy() {
|
|
376
|
+
if (this.iframe) {
|
|
377
|
+
this.iframe.remove();
|
|
378
|
+
this.iframe = null;
|
|
379
|
+
}
|
|
380
|
+
this.readyPromise = null;
|
|
381
|
+
if (this.messageListener) {
|
|
382
|
+
window.removeEventListener("message", this.messageListener);
|
|
383
|
+
this.messageListener = null;
|
|
384
|
+
}
|
|
385
|
+
this.messageHandlers.clear();
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
// src/provider/chains/ThruChain.ts
|
|
390
|
+
var EmbeddedThruChain = class {
|
|
391
|
+
constructor(iframeManager, provider) {
|
|
392
|
+
this.iframeManager = iframeManager;
|
|
393
|
+
this.provider = provider;
|
|
394
|
+
}
|
|
395
|
+
get connected() {
|
|
396
|
+
return this.provider.isConnected();
|
|
397
|
+
}
|
|
398
|
+
async connect() {
|
|
399
|
+
const result = await this.provider.connect();
|
|
400
|
+
const selectedAccount = result.selectedAccount;
|
|
401
|
+
const thruAccount = selectedAccount?.accountType === AddressType.THRU ? selectedAccount : result.accounts.find((addr) => addr.accountType === AddressType.THRU);
|
|
402
|
+
if (!thruAccount) {
|
|
403
|
+
throw new Error("Thru address not found in connection result");
|
|
404
|
+
}
|
|
405
|
+
return { publicKey: thruAccount.address };
|
|
406
|
+
}
|
|
407
|
+
async disconnect() {
|
|
408
|
+
await this.provider.disconnect();
|
|
409
|
+
}
|
|
410
|
+
async getSigningContext() {
|
|
411
|
+
if (!this.provider.isConnected()) {
|
|
412
|
+
throw new Error("Wallet not connected");
|
|
413
|
+
}
|
|
414
|
+
const response = await this.iframeManager.sendMessage({
|
|
415
|
+
id: createRequestId(),
|
|
416
|
+
type: POST_MESSAGE_REQUEST_TYPES.GET_SIGNING_CONTEXT,
|
|
417
|
+
origin: window.location.origin
|
|
418
|
+
});
|
|
419
|
+
return response.result.signingContext;
|
|
420
|
+
}
|
|
421
|
+
async signTransaction(transaction) {
|
|
422
|
+
if (!this.provider.isConnected()) {
|
|
423
|
+
throw new Error("Wallet not connected");
|
|
424
|
+
}
|
|
425
|
+
this.iframeManager.show();
|
|
426
|
+
try {
|
|
427
|
+
const response = await this.iframeManager.sendMessage({
|
|
428
|
+
id: createRequestId(),
|
|
429
|
+
type: POST_MESSAGE_REQUEST_TYPES.SIGN_TRANSACTION,
|
|
430
|
+
payload: {
|
|
431
|
+
walletAddress: transaction.walletAddress,
|
|
432
|
+
programAddress: transaction.programAddress,
|
|
433
|
+
instructionData: transaction.instructionData,
|
|
434
|
+
readWriteAddresses: transaction.readWriteAddresses,
|
|
435
|
+
readOnlyAddresses: transaction.readOnlyAddresses,
|
|
436
|
+
review: transaction.review
|
|
437
|
+
},
|
|
438
|
+
origin: window.location.origin
|
|
439
|
+
});
|
|
440
|
+
return response.result.signedTransaction;
|
|
441
|
+
} finally {
|
|
442
|
+
this.iframeManager.hide();
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
// src/provider/EmbeddedProvider.ts
|
|
448
|
+
var EmbeddedProvider = class {
|
|
449
|
+
constructor(config) {
|
|
450
|
+
this.connected = false;
|
|
451
|
+
this.accounts = [];
|
|
452
|
+
this.selectedAccount = null;
|
|
453
|
+
this.eventListeners = /* @__PURE__ */ new Map();
|
|
454
|
+
this.inlineMode = false;
|
|
455
|
+
const iframeUrl = config.iframeUrl || DEFAULT_IFRAME_URL;
|
|
456
|
+
this.iframeManager = new IframeManager(iframeUrl);
|
|
457
|
+
this.iframeManager.onEvent = (eventType, payload) => {
|
|
458
|
+
this.emit(eventType, payload);
|
|
459
|
+
if (eventType === EMBEDDED_PROVIDER_EVENTS.UI_SHOW) {
|
|
460
|
+
if (this.inlineMode) {
|
|
461
|
+
this.iframeManager.showInline();
|
|
462
|
+
} else {
|
|
463
|
+
this.iframeManager.showModal();
|
|
464
|
+
}
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
if (eventType === EMBEDDED_PROVIDER_EVENTS.DISCONNECT || eventType === EMBEDDED_PROVIDER_EVENTS.LOCK) {
|
|
468
|
+
this.clearConnection();
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
if (eventType === EMBEDDED_PROVIDER_EVENTS.ACCOUNT_CHANGED) {
|
|
472
|
+
const account = payload && payload.account || null;
|
|
473
|
+
this.refreshAccountCache(account ?? null);
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
const addressTypes = config.addressTypes || [AddressType.THRU];
|
|
477
|
+
if (addressTypes.includes(AddressType.THRU)) {
|
|
478
|
+
this._thruChain = new EmbeddedThruChain(this.iframeManager, this);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Initialize the provider (must be called before use)
|
|
483
|
+
* Creates iframe and waits for it to be ready
|
|
484
|
+
*/
|
|
485
|
+
async initialize() {
|
|
486
|
+
await this.iframeManager.createIframe();
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Mount the wallet iframe inline in a container (for inline connect button).
|
|
490
|
+
*/
|
|
491
|
+
async mountInline(container) {
|
|
492
|
+
this.inlineMode = true;
|
|
493
|
+
await this.iframeManager.mountInline(container);
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Connect to wallet
|
|
497
|
+
* Shows iframe modal and requests connection
|
|
498
|
+
*/
|
|
499
|
+
async connect(options) {
|
|
500
|
+
this.emit(EMBEDDED_PROVIDER_EVENTS.CONNECT_START, {});
|
|
501
|
+
try {
|
|
502
|
+
if (this.inlineMode) {
|
|
503
|
+
this.iframeManager.showInline();
|
|
504
|
+
} else {
|
|
505
|
+
this.iframeManager.showModal();
|
|
506
|
+
}
|
|
507
|
+
const payload = {};
|
|
508
|
+
if (options?.metadata) {
|
|
509
|
+
payload.metadata = options.metadata;
|
|
510
|
+
}
|
|
511
|
+
const response = await this.iframeManager.sendMessage({
|
|
512
|
+
id: createRequestId(),
|
|
513
|
+
type: POST_MESSAGE_REQUEST_TYPES.CONNECT,
|
|
514
|
+
payload,
|
|
515
|
+
origin: window.location.origin
|
|
516
|
+
});
|
|
517
|
+
const result = normalizeWalletAccountResult(response.result);
|
|
518
|
+
this.connected = true;
|
|
519
|
+
this.accounts = result.accounts;
|
|
520
|
+
this.selectedAccount = result.selectedAccount;
|
|
521
|
+
this.emit(EMBEDDED_PROVIDER_EVENTS.CONNECT, result);
|
|
522
|
+
if (!this.inlineMode) {
|
|
523
|
+
this.iframeManager.hide();
|
|
524
|
+
}
|
|
525
|
+
return result;
|
|
526
|
+
} catch (error) {
|
|
527
|
+
if (!this.inlineMode) {
|
|
528
|
+
this.iframeManager.hide();
|
|
529
|
+
}
|
|
530
|
+
this.emit(EMBEDDED_PROVIDER_EVENTS.CONNECT_ERROR, { error });
|
|
531
|
+
throw error;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Disconnect from wallet
|
|
536
|
+
*/
|
|
537
|
+
async disconnect() {
|
|
538
|
+
try {
|
|
539
|
+
await this.iframeManager.sendMessage({
|
|
540
|
+
id: createRequestId(),
|
|
541
|
+
type: POST_MESSAGE_REQUEST_TYPES.DISCONNECT,
|
|
542
|
+
origin: window.location.origin
|
|
543
|
+
});
|
|
544
|
+
this.clearConnection();
|
|
545
|
+
this.emit(EMBEDDED_PROVIDER_EVENTS.DISCONNECT, {});
|
|
546
|
+
} catch (error) {
|
|
547
|
+
this.clearConnection();
|
|
548
|
+
this.emit(EMBEDDED_PROVIDER_EVENTS.ERROR, { error });
|
|
549
|
+
throw error;
|
|
550
|
+
} finally {
|
|
551
|
+
if (!this.inlineMode) {
|
|
552
|
+
this.iframeManager.hide();
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Check if connected
|
|
558
|
+
*/
|
|
559
|
+
isConnected() {
|
|
560
|
+
return this.connected;
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Get accounts
|
|
564
|
+
*/
|
|
565
|
+
getAccounts() {
|
|
566
|
+
return this.accounts;
|
|
567
|
+
}
|
|
568
|
+
getSelectedAccount() {
|
|
569
|
+
return this.selectedAccount;
|
|
570
|
+
}
|
|
571
|
+
async selectAccount(publicKey) {
|
|
572
|
+
if (!this.connected) {
|
|
573
|
+
throw new Error("Wallet not connected");
|
|
574
|
+
}
|
|
575
|
+
const knownAccount = this.accounts.find((acc) => acc.address === publicKey) ?? null;
|
|
576
|
+
if (!knownAccount) {
|
|
577
|
+
console.warn(
|
|
578
|
+
"[EmbeddedProvider] Selecting account not present in local cache"
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
const payload = { publicKey };
|
|
582
|
+
const response = await this.iframeManager.sendMessage({
|
|
583
|
+
id: createRequestId(),
|
|
584
|
+
type: POST_MESSAGE_REQUEST_TYPES.SELECT_ACCOUNT,
|
|
585
|
+
payload,
|
|
586
|
+
origin: window.location.origin
|
|
587
|
+
});
|
|
588
|
+
const account = response.result.account;
|
|
589
|
+
this.refreshAccountCache(account);
|
|
590
|
+
return account;
|
|
591
|
+
}
|
|
592
|
+
async manageAccounts() {
|
|
593
|
+
if (!this.connected) {
|
|
594
|
+
throw new Error("Wallet not connected");
|
|
595
|
+
}
|
|
596
|
+
if (this.inlineMode) {
|
|
597
|
+
this.iframeManager.showInline();
|
|
598
|
+
} else {
|
|
599
|
+
this.iframeManager.showModal();
|
|
600
|
+
}
|
|
601
|
+
try {
|
|
602
|
+
const response = await this.iframeManager.sendMessage({
|
|
603
|
+
id: createRequestId(),
|
|
604
|
+
type: POST_MESSAGE_REQUEST_TYPES.MANAGE_ACCOUNTS,
|
|
605
|
+
origin: window.location.origin
|
|
606
|
+
});
|
|
607
|
+
const result = normalizeWalletAccountResult({
|
|
608
|
+
accounts: response.result.accounts,
|
|
609
|
+
selectedAccount: response.result.selectedAccount
|
|
610
|
+
});
|
|
611
|
+
this.accounts = result.accounts;
|
|
612
|
+
this.selectedAccount = result.selectedAccount;
|
|
613
|
+
if (this.selectedAccount) {
|
|
614
|
+
this.emit(EMBEDDED_PROVIDER_EVENTS.ACCOUNT_CHANGED, {
|
|
615
|
+
account: this.selectedAccount
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
return result;
|
|
619
|
+
} finally {
|
|
620
|
+
if (!this.inlineMode) {
|
|
621
|
+
this.iframeManager.hide();
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Get Thru chain API
|
|
627
|
+
*/
|
|
628
|
+
get thru() {
|
|
629
|
+
if (!this._thruChain) {
|
|
630
|
+
throw new Error("Thru chain not enabled in provider config");
|
|
631
|
+
}
|
|
632
|
+
return this._thruChain;
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Event emitter: on
|
|
636
|
+
*/
|
|
637
|
+
on(event, callback) {
|
|
638
|
+
if (!this.eventListeners.has(event)) {
|
|
639
|
+
this.eventListeners.set(event, /* @__PURE__ */ new Set());
|
|
640
|
+
}
|
|
641
|
+
this.eventListeners.get(event).add(callback);
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Event emitter: off
|
|
645
|
+
*/
|
|
646
|
+
off(event, callback) {
|
|
647
|
+
this.eventListeners.get(event)?.delete(callback);
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Emit event to all listeners
|
|
651
|
+
*/
|
|
652
|
+
emit(event, data) {
|
|
653
|
+
this.eventListeners.get(event)?.forEach((callback) => {
|
|
654
|
+
try {
|
|
655
|
+
callback(data);
|
|
656
|
+
} catch (error) {
|
|
657
|
+
console.error(`Error in event listener for ${event}:`, error);
|
|
658
|
+
}
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Get iframe manager (for chain implementations)
|
|
663
|
+
* @internal
|
|
664
|
+
*/
|
|
665
|
+
getIframeManager() {
|
|
666
|
+
return this.iframeManager;
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Destroy provider and cleanup
|
|
670
|
+
*/
|
|
671
|
+
destroy() {
|
|
672
|
+
this.iframeManager.destroy();
|
|
673
|
+
this.eventListeners.clear();
|
|
674
|
+
this.clearConnection();
|
|
675
|
+
}
|
|
676
|
+
refreshAccountCache(account) {
|
|
677
|
+
if (!account) {
|
|
678
|
+
this.accounts = [];
|
|
679
|
+
this.selectedAccount = null;
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
this.accounts = [account];
|
|
683
|
+
this.selectedAccount = account;
|
|
684
|
+
}
|
|
685
|
+
clearConnection() {
|
|
686
|
+
this.connected = false;
|
|
687
|
+
this.accounts = [];
|
|
688
|
+
this.selectedAccount = null;
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
var BrowserSDK = class {
|
|
692
|
+
constructor(config = {}) {
|
|
693
|
+
this.eventListeners = /* @__PURE__ */ new Map();
|
|
694
|
+
this.initialized = false;
|
|
695
|
+
this.connectInFlight = null;
|
|
696
|
+
this.lastConnectResult = null;
|
|
697
|
+
this.provider = new EmbeddedProvider({
|
|
698
|
+
iframeUrl: config.iframeUrl,
|
|
699
|
+
addressTypes: config.addressTypes || [AddressType.THRU]
|
|
700
|
+
});
|
|
701
|
+
this.thruClient = createThruClient({
|
|
702
|
+
baseUrl: config.rpcUrl
|
|
703
|
+
});
|
|
704
|
+
this.setupEventForwarding();
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Initialize the SDK (creates iframe)
|
|
708
|
+
* Must be called before using the SDK
|
|
709
|
+
*/
|
|
710
|
+
async initialize() {
|
|
711
|
+
if (this.initialized) {
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
await this.provider.initialize();
|
|
715
|
+
this.initialized = true;
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Connect to wallet
|
|
719
|
+
* Shows wallet modal and requests connection
|
|
720
|
+
*/
|
|
721
|
+
async connect(options) {
|
|
722
|
+
if (!this.initialized) {
|
|
723
|
+
await this.initialize();
|
|
724
|
+
}
|
|
725
|
+
if (this.connectInFlight) {
|
|
726
|
+
return this.connectInFlight;
|
|
727
|
+
}
|
|
728
|
+
if (this.lastConnectResult && this.provider.isConnected()) {
|
|
729
|
+
return this.lastConnectResult;
|
|
730
|
+
}
|
|
731
|
+
this.emit("connect", { status: "connecting" });
|
|
732
|
+
const inFlight = (async () => {
|
|
733
|
+
try {
|
|
734
|
+
const metadata = this.resolveMetadata(options?.metadata);
|
|
735
|
+
const providerOptions = metadata ? { metadata } : void 0;
|
|
736
|
+
const result = await this.provider.connect(providerOptions);
|
|
737
|
+
this.lastConnectResult = result;
|
|
738
|
+
this.emit("connect", result);
|
|
739
|
+
return result;
|
|
740
|
+
} catch (error) {
|
|
741
|
+
this.emit("error", error);
|
|
742
|
+
throw error;
|
|
743
|
+
} finally {
|
|
744
|
+
this.connectInFlight = null;
|
|
745
|
+
}
|
|
746
|
+
})();
|
|
747
|
+
this.connectInFlight = inFlight;
|
|
748
|
+
return inFlight;
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Mount the wallet iframe inline in a container.
|
|
752
|
+
*/
|
|
753
|
+
async mountInline(container) {
|
|
754
|
+
await this.provider.mountInline(container);
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Disconnect from wallet
|
|
758
|
+
*/
|
|
759
|
+
async disconnect() {
|
|
760
|
+
try {
|
|
761
|
+
await this.provider.disconnect();
|
|
762
|
+
this.emit("disconnect", {});
|
|
763
|
+
this.lastConnectResult = null;
|
|
764
|
+
} catch (error) {
|
|
765
|
+
this.emit("error", error);
|
|
766
|
+
throw error;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Check if connected
|
|
771
|
+
*/
|
|
772
|
+
isConnected() {
|
|
773
|
+
return this.provider.isConnected();
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Get all accounts
|
|
777
|
+
*/
|
|
778
|
+
getAccounts() {
|
|
779
|
+
const accounts = this.provider.getAccounts();
|
|
780
|
+
this.refreshCachedAccounts(accounts);
|
|
781
|
+
return accounts;
|
|
782
|
+
}
|
|
783
|
+
getSelectedAccount() {
|
|
784
|
+
return this.provider.getSelectedAccount();
|
|
785
|
+
}
|
|
786
|
+
async selectAccount(publicKey) {
|
|
787
|
+
const account = await this.provider.selectAccount(publicKey);
|
|
788
|
+
this.refreshCachedAccounts(this.provider.getAccounts(), account);
|
|
789
|
+
return account;
|
|
790
|
+
}
|
|
791
|
+
async manageAccounts() {
|
|
792
|
+
const result = await this.provider.manageAccounts();
|
|
793
|
+
this.refreshCachedAccounts(result.accounts, result.selectedAccount);
|
|
794
|
+
this.emit("accountChanged", result.selectedAccount);
|
|
795
|
+
return result;
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Get Thru chain API (iframe-backed signer)
|
|
799
|
+
*/
|
|
800
|
+
get thru() {
|
|
801
|
+
return this.provider.thru;
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Event emitter: on
|
|
805
|
+
*/
|
|
806
|
+
on(event, callback) {
|
|
807
|
+
if (!this.eventListeners.has(event)) {
|
|
808
|
+
this.eventListeners.set(event, /* @__PURE__ */ new Set());
|
|
809
|
+
}
|
|
810
|
+
this.eventListeners.get(event).add(callback);
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Event emitter: off
|
|
814
|
+
*/
|
|
815
|
+
off(event, callback) {
|
|
816
|
+
this.eventListeners.get(event)?.delete(callback);
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Event emitter: once (listen once and auto-remove)
|
|
820
|
+
*/
|
|
821
|
+
once(event, callback) {
|
|
822
|
+
const wrappedCallback = (...args) => {
|
|
823
|
+
callback(...args);
|
|
824
|
+
this.off(event, wrappedCallback);
|
|
825
|
+
};
|
|
826
|
+
this.on(event, wrappedCallback);
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Emit event to all listeners
|
|
830
|
+
*/
|
|
831
|
+
emit(event, data) {
|
|
832
|
+
this.eventListeners.get(event)?.forEach((callback) => {
|
|
833
|
+
try {
|
|
834
|
+
callback(data);
|
|
835
|
+
} catch (error) {
|
|
836
|
+
console.error(`Error in SDK event listener for ${event}:`, error);
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* Set up event forwarding from provider to SDK
|
|
842
|
+
*/
|
|
843
|
+
setupEventForwarding() {
|
|
844
|
+
this.provider.on(EMBEDDED_PROVIDER_EVENTS.CONNECT, (data) => {
|
|
845
|
+
});
|
|
846
|
+
this.provider.on(EMBEDDED_PROVIDER_EVENTS.DISCONNECT, (data) => {
|
|
847
|
+
this.emit("disconnect", data);
|
|
848
|
+
});
|
|
849
|
+
this.provider.on(EMBEDDED_PROVIDER_EVENTS.ERROR, (data) => {
|
|
850
|
+
this.emit("error", data);
|
|
851
|
+
});
|
|
852
|
+
this.provider.on(EMBEDDED_PROVIDER_EVENTS.LOCK, (data) => {
|
|
853
|
+
this.emit("lock", data);
|
|
854
|
+
this.emit("disconnect", { reason: "locked" });
|
|
855
|
+
});
|
|
856
|
+
this.provider.on(EMBEDDED_PROVIDER_EVENTS.ACCOUNT_CHANGED, (data) => {
|
|
857
|
+
const account = data?.account ?? data;
|
|
858
|
+
this.refreshCachedAccounts(this.provider.getAccounts(), account ?? null);
|
|
859
|
+
this.emit("accountChanged", account);
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Destroy SDK and cleanup
|
|
864
|
+
*/
|
|
865
|
+
destroy() {
|
|
866
|
+
this.provider.destroy();
|
|
867
|
+
this.eventListeners.clear();
|
|
868
|
+
this.initialized = false;
|
|
869
|
+
this.connectInFlight = null;
|
|
870
|
+
this.lastConnectResult = null;
|
|
871
|
+
}
|
|
872
|
+
resolveMetadata(input) {
|
|
873
|
+
const defaultOrigin = typeof window !== "undefined" ? window.location.origin : void 0;
|
|
874
|
+
if (!defaultOrigin && !input) {
|
|
875
|
+
return void 0;
|
|
876
|
+
}
|
|
877
|
+
const appId = input?.appId || defaultOrigin;
|
|
878
|
+
const appUrl = this.resolveAppUrl(defaultOrigin, input?.appUrl);
|
|
879
|
+
const appName = input?.appName || this.deriveAppName(appUrl ?? appId);
|
|
880
|
+
const metadata = {};
|
|
881
|
+
if (appId) metadata.appId = appId;
|
|
882
|
+
if (appUrl) metadata.appUrl = appUrl;
|
|
883
|
+
if (appName) metadata.appName = appName;
|
|
884
|
+
if (input?.imageUrl) metadata.imageUrl = input.imageUrl;
|
|
885
|
+
return metadata;
|
|
886
|
+
}
|
|
887
|
+
resolveAppUrl(defaultOrigin, providedUrl) {
|
|
888
|
+
const candidate = providedUrl || defaultOrigin;
|
|
889
|
+
if (!candidate) {
|
|
890
|
+
return void 0;
|
|
891
|
+
}
|
|
892
|
+
try {
|
|
893
|
+
const url = new URL(candidate, defaultOrigin);
|
|
894
|
+
return url.toString();
|
|
895
|
+
} catch {
|
|
896
|
+
return defaultOrigin;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
deriveAppName(source) {
|
|
900
|
+
if (!source) {
|
|
901
|
+
return void 0;
|
|
902
|
+
}
|
|
903
|
+
try {
|
|
904
|
+
const hostname = new URL(source).hostname;
|
|
905
|
+
return hostname || source;
|
|
906
|
+
} catch {
|
|
907
|
+
return source;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
getThru() {
|
|
911
|
+
return this.thruClient;
|
|
912
|
+
}
|
|
913
|
+
refreshCachedAccounts(accounts, selectedAccount) {
|
|
914
|
+
const active = normalizeActiveWalletAccounts(accounts, selectedAccount);
|
|
915
|
+
if (this.lastConnectResult) {
|
|
916
|
+
this.lastConnectResult = {
|
|
917
|
+
...this.lastConnectResult,
|
|
918
|
+
accounts: active.accounts,
|
|
919
|
+
selectedAccount: active.selectedAccount
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
var defaultContextValue = {
|
|
925
|
+
wallet: null,
|
|
926
|
+
isConnected: false,
|
|
927
|
+
accounts: [],
|
|
928
|
+
isConnecting: false,
|
|
929
|
+
error: null,
|
|
930
|
+
thru: null,
|
|
931
|
+
selectedAccount: null,
|
|
932
|
+
selectAccount: async () => void 0,
|
|
933
|
+
manageAccounts: async () => ({ accounts: [], selectedAccount: null })
|
|
934
|
+
};
|
|
935
|
+
var ThruContext = createContext(defaultContextValue);
|
|
936
|
+
function ThruProvider({ children, config }) {
|
|
937
|
+
const [sdk, setSdk] = useState(null);
|
|
938
|
+
const [thru, setThru] = useState(null);
|
|
939
|
+
const [isConnected, setIsConnected] = useState(false);
|
|
940
|
+
const [accounts, setAccounts] = useState([]);
|
|
941
|
+
const [isConnecting, setIsConnecting] = useState(false);
|
|
942
|
+
const [error, setError] = useState(null);
|
|
943
|
+
const [selectedAccount, setSelectedAccount] = useState(null);
|
|
944
|
+
useEffect(() => {
|
|
945
|
+
const sdkInstance = new BrowserSDK(config);
|
|
946
|
+
setSdk(sdkInstance);
|
|
947
|
+
setThru(sdkInstance.getThru());
|
|
948
|
+
const updateAccountsFromSdk = () => {
|
|
949
|
+
const active = normalizeActiveWalletAccounts(
|
|
950
|
+
sdkInstance.getAccounts(),
|
|
951
|
+
sdkInstance.getSelectedAccount()
|
|
952
|
+
);
|
|
953
|
+
setAccounts(active.accounts);
|
|
954
|
+
setSelectedAccount(active.selectedAccount);
|
|
955
|
+
};
|
|
956
|
+
const updateSelectedAccount = (account) => {
|
|
957
|
+
const active = normalizeActiveWalletAccounts(
|
|
958
|
+
sdkInstance.getAccounts(),
|
|
959
|
+
account ?? sdkInstance.getSelectedAccount()
|
|
960
|
+
);
|
|
961
|
+
setAccounts(active.accounts);
|
|
962
|
+
setSelectedAccount(active.selectedAccount);
|
|
963
|
+
};
|
|
964
|
+
sdkInstance.initialize().catch((err) => {
|
|
965
|
+
console.error("Failed to initialize SDK:", err);
|
|
966
|
+
setError(err);
|
|
967
|
+
});
|
|
968
|
+
const handleConnect = (result) => {
|
|
969
|
+
if (result.status === "connecting") {
|
|
970
|
+
setIsConnecting(true);
|
|
971
|
+
setError(null);
|
|
972
|
+
} else {
|
|
973
|
+
setIsConnected(true);
|
|
974
|
+
updateAccountsFromSdk();
|
|
975
|
+
setIsConnecting(false);
|
|
976
|
+
setError(null);
|
|
977
|
+
updateSelectedAccount(result?.selectedAccount ?? null);
|
|
978
|
+
}
|
|
979
|
+
};
|
|
980
|
+
const resetData = () => {
|
|
981
|
+
setIsConnected(false);
|
|
982
|
+
setAccounts([]);
|
|
983
|
+
setIsConnecting(false);
|
|
984
|
+
setSelectedAccount(null);
|
|
985
|
+
};
|
|
986
|
+
const handleDisconnect = () => {
|
|
987
|
+
resetData();
|
|
988
|
+
};
|
|
989
|
+
const handleError = (err) => {
|
|
990
|
+
setError(err.error || new Error("Unknown error"));
|
|
991
|
+
setIsConnecting(false);
|
|
992
|
+
};
|
|
993
|
+
const handleLock = () => {
|
|
994
|
+
resetData();
|
|
995
|
+
};
|
|
996
|
+
const handleAccountChanged = (account) => {
|
|
997
|
+
updateAccountsFromSdk();
|
|
998
|
+
updateSelectedAccount(account ?? void 0);
|
|
999
|
+
};
|
|
1000
|
+
sdkInstance.on("connect", handleConnect);
|
|
1001
|
+
sdkInstance.on("disconnect", handleDisconnect);
|
|
1002
|
+
sdkInstance.on("error", handleError);
|
|
1003
|
+
sdkInstance.on("lock", handleLock);
|
|
1004
|
+
sdkInstance.on("accountChanged", handleAccountChanged);
|
|
1005
|
+
return () => {
|
|
1006
|
+
sdkInstance.off("connect", handleConnect);
|
|
1007
|
+
sdkInstance.off("disconnect", handleDisconnect);
|
|
1008
|
+
sdkInstance.off("error", handleError);
|
|
1009
|
+
sdkInstance.off("lock", handleLock);
|
|
1010
|
+
sdkInstance.off("accountChanged", handleAccountChanged);
|
|
1011
|
+
sdkInstance.destroy();
|
|
1012
|
+
};
|
|
1013
|
+
}, []);
|
|
1014
|
+
const selectAccount = useCallback(async (account) => {
|
|
1015
|
+
if (!sdk) {
|
|
1016
|
+
throw new Error("BrowserSDK not initialized");
|
|
1017
|
+
}
|
|
1018
|
+
try {
|
|
1019
|
+
const updated = await sdk.selectAccount(account.address);
|
|
1020
|
+
const active = normalizeActiveWalletAccounts(sdk.getAccounts(), updated);
|
|
1021
|
+
setSelectedAccount(active.selectedAccount);
|
|
1022
|
+
setAccounts(active.accounts);
|
|
1023
|
+
} catch (err) {
|
|
1024
|
+
setError(err instanceof Error ? err : new Error("Failed to select account"));
|
|
1025
|
+
throw err;
|
|
1026
|
+
}
|
|
1027
|
+
}, [sdk]);
|
|
1028
|
+
const manageAccounts = useCallback(async () => {
|
|
1029
|
+
if (!sdk) {
|
|
1030
|
+
throw new Error("BrowserSDK not initialized");
|
|
1031
|
+
}
|
|
1032
|
+
try {
|
|
1033
|
+
const result = await sdk.manageAccounts();
|
|
1034
|
+
const activeResult = normalizeWalletAccountResult(result);
|
|
1035
|
+
setSelectedAccount(activeResult.selectedAccount);
|
|
1036
|
+
setAccounts(activeResult.accounts);
|
|
1037
|
+
setIsConnected(Boolean(activeResult.selectedAccount));
|
|
1038
|
+
return activeResult;
|
|
1039
|
+
} catch (err) {
|
|
1040
|
+
setError(err instanceof Error ? err : new Error("Failed to manage accounts"));
|
|
1041
|
+
throw err;
|
|
1042
|
+
}
|
|
1043
|
+
}, [sdk]);
|
|
1044
|
+
return /* @__PURE__ */ jsx(
|
|
1045
|
+
ThruContext.Provider,
|
|
1046
|
+
{
|
|
1047
|
+
value: {
|
|
1048
|
+
thru,
|
|
1049
|
+
wallet: sdk,
|
|
1050
|
+
isConnected,
|
|
1051
|
+
accounts,
|
|
1052
|
+
isConnecting,
|
|
1053
|
+
error,
|
|
1054
|
+
selectedAccount,
|
|
1055
|
+
selectAccount,
|
|
1056
|
+
manageAccounts
|
|
1057
|
+
},
|
|
1058
|
+
children
|
|
1059
|
+
}
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
1062
|
+
function useThru() {
|
|
1063
|
+
const context = useContext(ThruContext);
|
|
1064
|
+
return context;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
// src/react/hooks/useAccounts.ts
|
|
1068
|
+
function useAccounts(options) {
|
|
1069
|
+
const { accounts, selectedAccount, isConnected, isConnecting } = useThru();
|
|
1070
|
+
const externalOnSelect = options?.onAccountSelect;
|
|
1071
|
+
useEffect(() => {
|
|
1072
|
+
if (selectedAccount) {
|
|
1073
|
+
externalOnSelect?.(selectedAccount);
|
|
1074
|
+
}
|
|
1075
|
+
}, [externalOnSelect, selectedAccount]);
|
|
1076
|
+
return useMemo(
|
|
1077
|
+
() => ({
|
|
1078
|
+
accounts,
|
|
1079
|
+
selectedAccount,
|
|
1080
|
+
isConnected,
|
|
1081
|
+
isConnecting
|
|
1082
|
+
}),
|
|
1083
|
+
[accounts, selectedAccount, isConnected, isConnecting]
|
|
1084
|
+
);
|
|
1085
|
+
}
|
|
1086
|
+
function waitForWallet(getWallet, timeout = 5e3, interval = 100) {
|
|
1087
|
+
return new Promise((resolve, reject) => {
|
|
1088
|
+
const start = Date.now();
|
|
1089
|
+
const check = () => {
|
|
1090
|
+
const sdk = getWallet();
|
|
1091
|
+
if (sdk) return resolve(sdk);
|
|
1092
|
+
if (Date.now() - start > timeout) return reject(new Error("SDK not initialized in time"));
|
|
1093
|
+
setTimeout(check, interval);
|
|
1094
|
+
};
|
|
1095
|
+
check();
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
function useWallet() {
|
|
1099
|
+
const {
|
|
1100
|
+
wallet,
|
|
1101
|
+
isConnected,
|
|
1102
|
+
accounts,
|
|
1103
|
+
selectedAccount,
|
|
1104
|
+
selectAccount,
|
|
1105
|
+
manageAccounts,
|
|
1106
|
+
isConnecting
|
|
1107
|
+
} = useThru();
|
|
1108
|
+
const walletRef = useRef(wallet);
|
|
1109
|
+
useEffect(() => {
|
|
1110
|
+
walletRef.current = wallet;
|
|
1111
|
+
}, [wallet]);
|
|
1112
|
+
const disconnect = async () => {
|
|
1113
|
+
if (!wallet) {
|
|
1114
|
+
throw new Error("SDK not initialized");
|
|
1115
|
+
}
|
|
1116
|
+
await wallet.disconnect();
|
|
1117
|
+
};
|
|
1118
|
+
const connect = async (options) => {
|
|
1119
|
+
try {
|
|
1120
|
+
const readySdk = walletRef.current ?? await waitForWallet(() => walletRef.current);
|
|
1121
|
+
const result = await readySdk.connect(options);
|
|
1122
|
+
return result;
|
|
1123
|
+
} catch (err) {
|
|
1124
|
+
const error = err;
|
|
1125
|
+
throw error;
|
|
1126
|
+
}
|
|
1127
|
+
};
|
|
1128
|
+
const mountInline = async (container) => {
|
|
1129
|
+
const readySdk = walletRef.current ?? await waitForWallet(() => walletRef.current);
|
|
1130
|
+
await readySdk.mountInline(container);
|
|
1131
|
+
};
|
|
1132
|
+
const openAccountSettings = async () => {
|
|
1133
|
+
return manageAccounts();
|
|
1134
|
+
};
|
|
1135
|
+
return {
|
|
1136
|
+
wallet: wallet?.thru,
|
|
1137
|
+
accounts,
|
|
1138
|
+
connect,
|
|
1139
|
+
disconnect,
|
|
1140
|
+
mountInline,
|
|
1141
|
+
manageAccounts: openAccountSettings,
|
|
1142
|
+
isConnected: isConnected && !!wallet,
|
|
1143
|
+
isConnecting,
|
|
1144
|
+
selectedAccount,
|
|
1145
|
+
selectAccount
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
export { BrowserSDK, ErrorCode, ThruProvider, useAccounts, useThru, useWallet };
|
|
1150
|
+
//# sourceMappingURL=react.js.map
|
|
1151
|
+
//# sourceMappingURL=react.js.map
|