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