@thru/embedded-provider 0.2.0 → 0.2.1

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 ADDED
@@ -0,0 +1,135 @@
1
+ # @thru/embedded-provider
2
+
3
+ Client-side provider for embedding the Thru wallet into any web application. Manages an iframe that hosts the wallet UI, communicates with it over `postMessage`, and exposes a simple API for connecting, signing transactions, and managing accounts.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @thru/embedded-provider
9
+ ```
10
+
11
+ ## Basic Usage
12
+
13
+ ```typescript
14
+ import { EmbeddedProvider } from '@thru/embedded-provider';
15
+
16
+ const provider = new EmbeddedProvider({
17
+ iframeUrl: 'https://wallet.thru.io',
18
+ });
19
+
20
+ // Initialize iframe (must be called before any other operation)
21
+ await provider.initialize();
22
+
23
+ // Connect to wallet (opens modal)
24
+ const result = await provider.connect();
25
+ console.log(result.accounts);
26
+
27
+ // Sign a transaction via the Thru chain interface
28
+ const signed = await provider.thru.signTransaction(base64EncodedTx);
29
+
30
+ // Disconnect
31
+ await provider.disconnect();
32
+
33
+ // Cleanup when done
34
+ provider.destroy();
35
+ ```
36
+
37
+ ## API
38
+
39
+ ### `EmbeddedProvider`
40
+
41
+ Main entry point. Creates and manages the wallet iframe.
42
+
43
+ #### Constructor
44
+
45
+ ```typescript
46
+ new EmbeddedProvider(config: EmbeddedProviderConfig)
47
+ ```
48
+
49
+ | Option | Type | Default | Description |
50
+ |--------|------|---------|-------------|
51
+ | `iframeUrl` | `string` | `DEFAULT_IFRAME_URL` | URL of the hosted wallet application |
52
+ | `addressTypes` | `AddressType[]` | `[AddressType.THRU]` | Chain types to enable |
53
+
54
+ #### Methods
55
+
56
+ | Method | Returns | Description |
57
+ |--------|---------|-------------|
58
+ | `initialize()` | `Promise<void>` | Create the iframe and wait for it to signal readiness |
59
+ | `connect(options?)` | `Promise<ConnectResult>` | Open the wallet modal and request a connection |
60
+ | `disconnect()` | `Promise<void>` | Disconnect the current session |
61
+ | `isConnected()` | `boolean` | Whether a wallet session is active |
62
+ | `getAccounts()` | `WalletAccount[]` | List of connected accounts |
63
+ | `getSelectedAccount()` | `WalletAccount \| null` | Currently selected account |
64
+ | `selectAccount(publicKey)` | `Promise<WalletAccount>` | Switch the active account |
65
+ | `mountInline(container)` | `Promise<void>` | Mount the wallet inline inside a DOM element instead of as a modal |
66
+ | `on(event, callback)` | `void` | Subscribe to provider events |
67
+ | `off(event, callback)` | `void` | Unsubscribe from provider events |
68
+ | `destroy()` | `void` | Remove the iframe and clean up all listeners |
69
+
70
+ #### Properties
71
+
72
+ | Property | Type | Description |
73
+ |----------|------|-------------|
74
+ | `thru` | `IThruChain` | Chain-specific interface for signing transactions on the Thru network |
75
+
76
+ ### `EmbeddedThruChain`
77
+
78
+ Implements `IThruChain`. Accessed via `provider.thru`.
79
+
80
+ | Method | Returns | Description |
81
+ |--------|---------|-------------|
82
+ | `connect()` | `Promise<{ publicKey: string }>` | Connect and return the Thru address |
83
+ | `disconnect()` | `Promise<void>` | Disconnect |
84
+ | `signTransaction(serializedTransaction)` | `Promise<string>` | Sign a base64-encoded transaction, returns the signed result |
85
+
86
+ ### Events
87
+
88
+ Subscribe with `provider.on(event, callback)`:
89
+
90
+ - `connect` -- Wallet connected successfully
91
+ - `connect:start` -- Connection flow initiated
92
+ - `connect:error` -- Connection attempt failed
93
+ - `disconnect` -- Wallet disconnected
94
+ - `lock` -- Wallet locked by the user
95
+ - `account:changed` -- Active account switched
96
+ - `ui:show` -- Wallet UI requested to be shown
97
+
98
+ ### Display Modes
99
+
100
+ The provider supports two display modes:
101
+
102
+ - **Modal** (default) -- The iframe is appended to `document.body` as a full-screen overlay.
103
+ - **Inline** -- The iframe is mounted inside a container element you provide, useful for embedding a connect button directly in your page layout.
104
+
105
+ ```typescript
106
+ // Inline mode
107
+ const container = document.getElementById('wallet-mount');
108
+ await provider.mountInline(container);
109
+ ```
110
+
111
+ ## Security
112
+
113
+ The `IframeManager` validates that the iframe URL belongs to a set of trusted origins before loading it. Allowed origins:
114
+
115
+ - `https://wallet.thru.io`
116
+ - `https://wallet.thru.org`
117
+ - `https://thru-wallet.up.railway.app`
118
+ - `http://localhost` (any port, for development)
119
+
120
+ Messages are sent with a strict `targetOrigin` and each iframe instance is tagged with a unique frame ID to prevent cross-talk.
121
+
122
+ ## Key Capabilities
123
+
124
+ - Iframe lifecycle management with automatic readiness detection
125
+ - Request/response correlation over `postMessage` with per-request timeouts
126
+ - Origin validation to prevent unauthorized wallet iframes
127
+ - Modal and inline display modes
128
+ - Event-driven architecture for connection state changes
129
+ - WebAuthn (passkey) support via iframe `allow` policy
130
+ - Chain-specific interfaces via the `IThruChain` abstraction
131
+
132
+ ## Dependencies
133
+
134
+ - `@thru/chain-interfaces` -- Shared chain interface types (`IThruChain`, `WalletAccount`)
135
+ - `@thru/protocol` -- Message type constants, request/response schemas, and helper utilities
package/dist/index.d.ts CHANGED
@@ -11,14 +11,19 @@ declare class IframeManager {
11
11
  private iframe;
12
12
  private iframeUrl;
13
13
  private iframeOrigin;
14
+ private frameId;
14
15
  private messageHandlers;
15
16
  private messageListener;
16
17
  private readyPromise;
18
+ private displayMode;
19
+ private inlineContainer;
20
+ private visible;
17
21
  /**
18
22
  * Callback for event broadcasts from iframe (no request id)
19
23
  */
20
24
  onEvent?: (eventType: string, payload: any) => void;
21
25
  constructor(iframeUrl: string);
26
+ private getIframeSrc;
22
27
  /**
23
28
  * Create and inject iframe into DOM
24
29
  * Returns a promise that resolves when iframe is ready
@@ -28,6 +33,18 @@ declare class IframeManager {
28
33
  * Wait for iframe to send 'ready' signal
29
34
  */
30
35
  private waitForReady;
36
+ /**
37
+ * Mount iframe inline inside the provided container.
38
+ */
39
+ mountInline(container: HTMLElement): Promise<void>;
40
+ /**
41
+ * Show iframe inline (embedded in container).
42
+ */
43
+ showInline(): void;
44
+ /**
45
+ * Show iframe as a full-screen modal.
46
+ */
47
+ showModal(): void;
31
48
  /**
32
49
  * Show iframe modal
33
50
  */
@@ -36,6 +53,9 @@ declare class IframeManager {
36
53
  * Hide iframe modal
37
54
  */
38
55
  hide(): void;
56
+ isInline(): boolean;
57
+ private applyIframeStyles;
58
+ private setVisibility;
39
59
  /**
40
60
  * Send message to iframe and wait for response
41
61
  */
@@ -48,6 +68,7 @@ declare class IframeManager {
48
68
  * Handle event broadcasts from iframe
49
69
  */
50
70
  private handleEvent;
71
+ private isMessageFromIframe;
51
72
  /**
52
73
  * Destroy iframe and cleanup
53
74
  */
@@ -72,12 +93,17 @@ declare class EmbeddedProvider {
72
93
  private accounts;
73
94
  private selectedAccount;
74
95
  private eventListeners;
96
+ private inlineMode;
75
97
  constructor(config: EmbeddedProviderConfig);
76
98
  /**
77
99
  * Initialize the provider (must be called before use)
78
100
  * Creates iframe and waits for it to be ready
79
101
  */
80
102
  initialize(): Promise<void>;
103
+ /**
104
+ * Mount the wallet iframe inline in a container (for inline connect button).
105
+ */
106
+ mountInline(container: HTMLElement): Promise<void>;
81
107
  /**
82
108
  * Connect to wallet
83
109
  * Shows iframe modal and requests connection
package/dist/index.js CHANGED
@@ -49,6 +49,7 @@ var EmbeddedThruChain = class {
49
49
  // src/IframeManager.ts
50
50
  var ALLOWED_IFRAME_ORIGINS = [
51
51
  "https://thru-wallet.up.railway.app",
52
+ "https://wallet.thru.io",
52
53
  "https://wallet.thru.org",
53
54
  // Allow localhost for development (any port)
54
55
  "http://localhost"
@@ -81,9 +82,18 @@ var IframeManager = class {
81
82
  this.messageHandlers = /* @__PURE__ */ new Map();
82
83
  this.messageListener = null;
83
84
  this.readyPromise = null;
85
+ this.displayMode = "modal";
86
+ this.inlineContainer = null;
87
+ this.visible = false;
84
88
  validateIframeOrigin(iframeUrl);
85
89
  this.iframeUrl = iframeUrl;
86
90
  this.iframeOrigin = new URL(iframeUrl).origin;
91
+ this.frameId = createRequestId("frame");
92
+ }
93
+ getIframeSrc() {
94
+ const url = new URL(this.iframeUrl);
95
+ url.searchParams.set("tn_frame_id", this.frameId);
96
+ return url.toString();
87
97
  }
88
98
  /**
89
99
  * Create and inject iframe into DOM
@@ -96,24 +106,23 @@ var IframeManager = class {
96
106
  this.readyPromise = (async () => {
97
107
  if (!this.iframe) {
98
108
  this.iframe = document.createElement("iframe");
99
- this.iframe.src = this.iframeUrl;
100
- this.iframe.style.cssText = `
101
- position: fixed;
102
- top: 0;
103
- left: 0;
104
- width: 100%;
105
- height: 100%;
106
- border: none;
107
- z-index: 999999;
108
- display: none;
109
- background: rgba(0, 0, 0, 0.5);
110
- `;
111
- document.body.appendChild(this.iframe);
109
+ this.iframe.src = this.getIframeSrc();
110
+ this.iframe.allow = "publickey-credentials-get; publickey-credentials-create";
111
+ this.applyIframeStyles();
112
+ this.setVisibility(false);
113
+ if (this.displayMode === "inline" && this.inlineContainer) {
114
+ this.inlineContainer.appendChild(this.iframe);
115
+ } else {
116
+ document.body.appendChild(this.iframe);
117
+ }
112
118
  this.messageListener = this.handleMessage.bind(this);
113
119
  window.addEventListener("message", this.messageListener);
114
120
  }
115
121
  await this.waitForReady();
116
- })();
122
+ })().catch((error) => {
123
+ this.readyPromise = null;
124
+ throw error;
125
+ });
117
126
  return this.readyPromise;
118
127
  }
119
128
  /**
@@ -121,50 +130,139 @@ var IframeManager = class {
121
130
  */
122
131
  waitForReady() {
123
132
  return new Promise((resolve, reject) => {
133
+ let resolved = false;
134
+ let readyHandler;
135
+ const cleanup = () => {
136
+ if (resolved) {
137
+ return;
138
+ }
139
+ resolved = true;
140
+ window.removeEventListener("message", readyHandler);
141
+ clearTimeout(timeout);
142
+ };
124
143
  const timeout = setTimeout(() => {
144
+ cleanup();
125
145
  reject(new Error("Iframe ready timeout - wallet failed to load"));
126
146
  }, 1e4);
127
- const readyHandler = (event) => {
128
- if (event.origin !== this.iframeOrigin) {
147
+ readyHandler = (event) => {
148
+ if (!this.isMessageFromIframe(event)) {
129
149
  return;
130
150
  }
131
- if (event.data.type === IFRAME_READY_EVENT) {
132
- clearTimeout(timeout);
133
- window.removeEventListener("message", readyHandler);
151
+ if (event.data?.type === IFRAME_READY_EVENT) {
152
+ cleanup();
134
153
  resolve();
135
154
  }
136
155
  };
137
156
  window.addEventListener("message", readyHandler);
138
157
  });
139
158
  }
159
+ /**
160
+ * Mount iframe inline inside the provided container.
161
+ */
162
+ async mountInline(container) {
163
+ this.inlineContainer = container;
164
+ this.displayMode = "inline";
165
+ await this.createIframe();
166
+ this.showInline();
167
+ }
168
+ /**
169
+ * Show iframe inline (embedded in container).
170
+ */
171
+ showInline() {
172
+ if (!this.iframe) {
173
+ return;
174
+ }
175
+ this.displayMode = "inline";
176
+ if (this.inlineContainer && this.iframe.parentElement !== this.inlineContainer) {
177
+ this.inlineContainer.appendChild(this.iframe);
178
+ }
179
+ this.applyIframeStyles();
180
+ this.setVisibility(true);
181
+ }
182
+ /**
183
+ * Show iframe as a full-screen modal.
184
+ */
185
+ showModal() {
186
+ if (!this.iframe) {
187
+ return;
188
+ }
189
+ this.displayMode = "modal";
190
+ if (this.iframe.parentElement !== document.body) {
191
+ document.body.appendChild(this.iframe);
192
+ }
193
+ this.applyIframeStyles();
194
+ this.setVisibility(true);
195
+ }
140
196
  /**
141
197
  * Show iframe modal
142
198
  */
143
199
  show() {
144
- if (this.iframe) {
145
- this.iframe.style.display = "block";
146
- }
200
+ this.showModal();
147
201
  }
148
202
  /**
149
203
  * Hide iframe modal
150
204
  */
151
205
  hide() {
152
- if (this.iframe) {
153
- this.iframe.style.display = "none";
206
+ this.setVisibility(false);
207
+ }
208
+ isInline() {
209
+ return this.displayMode === "inline";
210
+ }
211
+ applyIframeStyles() {
212
+ if (!this.iframe) {
213
+ return;
214
+ }
215
+ if (this.displayMode === "inline") {
216
+ this.iframe.style.cssText = `
217
+ position: relative;
218
+ width: 100%;
219
+ height: 100%;
220
+ border: none;
221
+ z-index: 1;
222
+ display: block;
223
+ background: transparent;
224
+ `;
225
+ return;
154
226
  }
227
+ this.iframe.style.cssText = `
228
+ position: fixed;
229
+ top: 0;
230
+ left: 0;
231
+ width: 100%;
232
+ height: 100%;
233
+ border: none;
234
+ z-index: 999999;
235
+ display: block;
236
+ background: rgba(0, 0, 0, 0.5);
237
+ `;
238
+ }
239
+ setVisibility(visible) {
240
+ if (!this.iframe) {
241
+ return;
242
+ }
243
+ this.visible = visible;
244
+ this.iframe.style.opacity = visible ? "1" : "0";
245
+ this.iframe.style.pointerEvents = visible ? "auto" : "none";
246
+ this.iframe.style.visibility = visible ? "visible" : "hidden";
155
247
  }
156
248
  /**
157
249
  * Send message to iframe and wait for response
158
250
  */
159
251
  async sendMessage(request) {
252
+ if (this.readyPromise) {
253
+ await this.readyPromise;
254
+ } else {
255
+ await this.createIframe();
256
+ }
160
257
  if (!this.iframe?.contentWindow) {
161
258
  throw new Error("Iframe not initialized - call createIframe() first");
162
259
  }
163
260
  return new Promise((resolve, reject) => {
261
+ const timeoutMs = request.type === POST_MESSAGE_REQUEST_TYPES.CONNECT || request.type === POST_MESSAGE_REQUEST_TYPES.SIGN_MESSAGE || request.type === POST_MESSAGE_REQUEST_TYPES.SIGN_TRANSACTION ? 5 * 60 * 1e3 : 30 * 1e3;
164
262
  const timeout = setTimeout(() => {
165
263
  this.messageHandlers.delete(request.id);
166
264
  reject(new Error("Request timeout - wallet did not respond"));
167
- }, 3e4);
265
+ }, timeoutMs);
168
266
  this.messageHandlers.set(request.id, (response) => {
169
267
  clearTimeout(timeout);
170
268
  this.messageHandlers.delete(request.id);
@@ -183,8 +281,7 @@ var IframeManager = class {
183
281
  * Handle incoming messages from iframe
184
282
  */
185
283
  handleMessage(event) {
186
- const iframeOrigin = new URL(this.iframeUrl).origin;
187
- if (event.origin !== iframeOrigin) {
284
+ if (!this.isMessageFromIframe(event)) {
188
285
  return;
189
286
  }
190
287
  const data = event.data;
@@ -207,6 +304,22 @@ var IframeManager = class {
207
304
  this.onEvent(data.event, data.data);
208
305
  }
209
306
  }
307
+ isMessageFromIframe(event) {
308
+ if (event.origin !== this.iframeOrigin) {
309
+ return false;
310
+ }
311
+ const data = event.data;
312
+ if (!data || data.frameId !== this.frameId) {
313
+ return false;
314
+ }
315
+ if (!event.source) {
316
+ return true;
317
+ }
318
+ if (this.iframe?.contentWindow && event.source !== this.iframe.contentWindow) {
319
+ return false;
320
+ }
321
+ return true;
322
+ }
210
323
  /**
211
324
  * Destroy iframe and cleanup
212
325
  */
@@ -231,10 +344,19 @@ var EmbeddedProvider = class {
231
344
  this.accounts = [];
232
345
  this.selectedAccount = null;
233
346
  this.eventListeners = /* @__PURE__ */ new Map();
347
+ this.inlineMode = false;
234
348
  const iframeUrl = config.iframeUrl || DEFAULT_IFRAME_URL;
235
349
  this.iframeManager = new IframeManager(iframeUrl);
236
350
  this.iframeManager.onEvent = (eventType, payload) => {
237
351
  this.emit(eventType, payload);
352
+ if (eventType === EMBEDDED_PROVIDER_EVENTS.UI_SHOW) {
353
+ if (this.inlineMode) {
354
+ this.iframeManager.showInline();
355
+ } else {
356
+ this.iframeManager.showModal();
357
+ }
358
+ return;
359
+ }
238
360
  if (eventType === EMBEDDED_PROVIDER_EVENTS.DISCONNECT || eventType === EMBEDDED_PROVIDER_EVENTS.LOCK) {
239
361
  this.connected = false;
240
362
  this.accounts = [];
@@ -258,14 +380,25 @@ var EmbeddedProvider = class {
258
380
  async initialize() {
259
381
  await this.iframeManager.createIframe();
260
382
  }
383
+ /**
384
+ * Mount the wallet iframe inline in a container (for inline connect button).
385
+ */
386
+ async mountInline(container) {
387
+ this.inlineMode = true;
388
+ await this.iframeManager.mountInline(container);
389
+ }
261
390
  /**
262
391
  * Connect to wallet
263
392
  * Shows iframe modal and requests connection
264
393
  */
265
394
  async connect(options) {
266
395
  this.emit(EMBEDDED_PROVIDER_EVENTS.CONNECT_START, {});
267
- this.iframeManager.show();
268
396
  try {
397
+ if (this.inlineMode) {
398
+ this.iframeManager.showInline();
399
+ } else {
400
+ this.iframeManager.showModal();
401
+ }
269
402
  const payload = {};
270
403
  if (options?.metadata) {
271
404
  payload.metadata = options.metadata;
@@ -280,10 +413,14 @@ var EmbeddedProvider = class {
280
413
  this.accounts = response.result.accounts;
281
414
  this.selectedAccount = response.result.accounts[0] ?? null;
282
415
  this.emit(EMBEDDED_PROVIDER_EVENTS.CONNECT, response.result);
283
- this.iframeManager.hide();
416
+ if (!this.inlineMode) {
417
+ this.iframeManager.hide();
418
+ }
284
419
  return response.result;
285
420
  } catch (error) {
286
- this.iframeManager.hide();
421
+ if (!this.inlineMode) {
422
+ this.iframeManager.hide();
423
+ }
287
424
  this.emit(EMBEDDED_PROVIDER_EVENTS.CONNECT_ERROR, { error });
288
425
  throw error;
289
426
  }
@@ -298,13 +435,17 @@ var EmbeddedProvider = class {
298
435
  type: POST_MESSAGE_REQUEST_TYPES.DISCONNECT,
299
436
  origin: window.location.origin
300
437
  });
301
- this.connected = false;
302
- this.accounts = [];
303
- this.iframeManager.hide();
304
438
  this.emit(EMBEDDED_PROVIDER_EVENTS.DISCONNECT, {});
305
439
  } catch (error) {
306
440
  this.emit(EMBEDDED_PROVIDER_EVENTS.ERROR, { error });
307
441
  throw error;
442
+ } finally {
443
+ this.connected = false;
444
+ this.accounts = [];
445
+ this.selectedAccount = null;
446
+ if (!this.inlineMode) {
447
+ this.iframeManager.hide();
448
+ }
308
449
  }
309
450
  }
310
451
  /**
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../../chain-interfaces/src/types.ts","../src/chains/ThruChain.ts","../src/IframeManager.ts","../src/EmbeddedProvider.ts"],"names":["DEFAULT_IFRAME_URL","EMBEDDED_PROVIDER_EVENTS","createRequestId","POST_MESSAGE_REQUEST_TYPES"],"mappings":"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","file":"index.js","sourcesContent":["export const AddressType = {\n THRU: 'thru',\n} as const;\n\nexport type AddressType = typeof AddressType[keyof typeof AddressType];\n\nexport interface WalletAccount {\n accountType: AddressType;\n address: string;\n label: string;\n}\n\nexport interface AppMetadata {\n appId: string;\n appName: string;\n appUrl: string;\n imageUrl?: string;\n}\n\nexport interface ConnectResult {\n walletId?: string;\n accounts: WalletAccount[];\n status?: 'pending' | 'completed';\n metadata?: AppMetadata;\n}\n\nexport interface ConnectedApp {\n accountId: number;\n appId: string;\n origin: string;\n metadata: AppMetadata;\n connectedAt: number;\n updatedAt: number;\n}\n\nexport interface SignMessageParams {\n message: string | Uint8Array;\n networkId: string;\n}\n\nexport interface SignMessageResult {\n signature: Uint8Array;\n publicKey: string;\n}\n","import { AddressType, type IThruChain } from '@thru/chain-interfaces';\nimport { POST_MESSAGE_REQUEST_TYPES, createRequestId } from '@thru/protocol';\nimport type { EmbeddedProvider } from '../EmbeddedProvider';\nimport type { IframeManager } from '../IframeManager';\n\n/**\n * EmbeddedThruChain - postMessage-backed Thru chain adapter.\n */\nexport class EmbeddedThruChain implements IThruChain {\n private readonly iframeManager: IframeManager;\n private readonly provider: EmbeddedProvider;\n\n constructor(iframeManager: IframeManager, provider: EmbeddedProvider) {\n this.iframeManager = iframeManager;\n this.provider = provider;\n }\n\n get connected(): boolean {\n return this.provider.isConnected();\n }\n\n async connect(): Promise<{ publicKey: string }> {\n const result = await this.provider.connect();\n const thruAccount = result.accounts.find((addr) => addr.accountType === AddressType.THRU);\n\n if (!thruAccount) {\n throw new Error('Thru address not found in connection result');\n }\n\n return { publicKey: thruAccount.address };\n }\n\n async disconnect(): Promise<void> {\n await this.provider.disconnect();\n }\n\n async signTransaction(serializedTransaction: string): Promise<string> {\n if (!this.provider.isConnected()) {\n throw new Error('Wallet not connected');\n }\n if (typeof serializedTransaction !== 'string' || serializedTransaction.length === 0) {\n throw new Error('Transaction payload must be a base64 encoded string');\n }\n\n this.iframeManager.show();\n\n try {\n const response = await this.iframeManager.sendMessage({\n id: createRequestId(),\n type: POST_MESSAGE_REQUEST_TYPES.SIGN_TRANSACTION,\n payload: { transaction: serializedTransaction },\n origin: window.location.origin,\n });\n return response.result.signedTransaction;\n } finally {\n this.iframeManager.hide();\n }\n }\n}\n","import type {\n InferSuccessfulPostMessageResponse,\n PostMessageEvent,\n PostMessageRequest,\n PostMessageResponse,\n} from './types/messages';\nimport { IFRAME_READY_EVENT, POST_MESSAGE_EVENT_TYPE } from './types/messages';\n\n/**\n * Allowed origins for wallet iframe URLs\n * Only iframes from these origins can be loaded for security\n */\nconst ALLOWED_IFRAME_ORIGINS = [\n 'https://thru-wallet.up.railway.app',\n 'https://wallet.thru.org',\n // Allow localhost for development (any port)\n 'http://localhost',\n];\n\n/**\n * Validates that the iframe URL is from a trusted origin\n * @throws Error if the origin is not allowed\n */\nfunction validateIframeOrigin(iframeUrl: string): void {\n let url: URL;\n try {\n url = new URL(iframeUrl);\n } catch (error) {\n throw new Error(\n `Invalid iframe URL: ${iframeUrl}. URL must be a valid absolute URL.`\n );\n }\n\n const origin = url.origin;\n\n // Check if origin matches any allowed origin\n // For localhost, we allow any port (e.g., http://localhost:3000)\n const isAllowed = ALLOWED_IFRAME_ORIGINS.some((allowedOrigin) => {\n if (allowedOrigin === 'http://localhost') {\n // Match exactly http://localhost or http://localhost:port\n return origin === 'http://localhost' || origin.match(/^http:\\/\\/localhost:\\d+$/);\n }\n return origin === allowedOrigin;\n });\n\n if (!isAllowed) {\n throw new Error(\n `Untrusted iframe origin: ${origin}. ` +\n `Only trusted wallet origins are allowed: ${ALLOWED_IFRAME_ORIGINS.join(', ')}. ` +\n `This security check prevents malicious websites from loading unauthorized wallet iframes.`\n );\n }\n}\n\n/**\n * Manages iframe lifecycle and postMessage communication\n * Handles creating, showing/hiding iframe, and message passing\n */\nexport class IframeManager {\n private iframe: HTMLIFrameElement | null = null;\n private iframeUrl: string;\n private iframeOrigin: string;\n private messageHandlers = new Map<string, (response: PostMessageResponse) => void>();\n private messageListener: ((event: MessageEvent) => void) | null = null;\n private readyPromise: Promise<void> | null = null;\n\n /**\n * Callback for event broadcasts from iframe (no request id)\n */\n public onEvent?: (eventType: string, payload: any) => void;\n\n constructor(iframeUrl: string) {\n // Validate origin before accepting the URL\n validateIframeOrigin(iframeUrl);\n\n this.iframeUrl = iframeUrl;\n this.iframeOrigin = new URL(iframeUrl).origin;\n }\n\n /**\n * Create and inject iframe into DOM\n * Returns a promise that resolves when iframe is ready\n */\n async createIframe(): Promise<void> {\n if (this.readyPromise) {\n return this.readyPromise;\n }\n\n this.readyPromise = (async () => {\n if (!this.iframe) {\n this.iframe = document.createElement('iframe');\n this.iframe.src = this.iframeUrl;\n this.iframe.style.cssText = `\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n border: none;\n z-index: 999999;\n display: none;\n background: rgba(0, 0, 0, 0.5);\n `;\n\n document.body.appendChild(this.iframe);\n\n // Set up message listener\n this.messageListener = this.handleMessage.bind(this);\n window.addEventListener('message', this.messageListener);\n }\n\n // Wait for iframe ready signal\n await this.waitForReady();\n })();\n\n return this.readyPromise;\n }\n\n /**\n * Wait for iframe to send 'ready' signal\n */\n private waitForReady(): Promise<void> {\n return new Promise((resolve, reject) => {\n const timeout = setTimeout(() => {\n reject(new Error('Iframe ready timeout - wallet failed to load'));\n }, 10000); // 10 second timeout\n\n const readyHandler = (event: MessageEvent) => {\n if (event.origin !== this.iframeOrigin) {\n return;\n }\n\n if (event.data.type === IFRAME_READY_EVENT) {\n clearTimeout(timeout);\n window.removeEventListener('message', readyHandler);\n resolve();\n }\n };\n\n window.addEventListener('message', readyHandler);\n });\n }\n\n /**\n * Show iframe modal\n */\n show(): void {\n if (this.iframe) {\n this.iframe.style.display = 'block';\n }\n }\n\n /**\n * Hide iframe modal\n */\n hide(): void {\n if (this.iframe) {\n this.iframe.style.display = 'none';\n }\n }\n\n /**\n * Send message to iframe and wait for response\n */\n async sendMessage<TRequest extends PostMessageRequest>(\n request: TRequest\n ): Promise<InferSuccessfulPostMessageResponse<TRequest>> {\n if (!this.iframe?.contentWindow) {\n throw new Error('Iframe not initialized - call createIframe() first');\n }\n\n return new Promise<InferSuccessfulPostMessageResponse<TRequest>>((resolve, reject) => {\n const timeout = setTimeout(() => {\n this.messageHandlers.delete(request.id);\n reject(new Error('Request timeout - wallet did not respond'));\n }, 30000); // 30 second timeout\n\n // Store handler for this request\n this.messageHandlers.set(request.id, (response: PostMessageResponse) => {\n clearTimeout(timeout);\n this.messageHandlers.delete(request.id);\n\n if (response.success) {\n resolve(response as InferSuccessfulPostMessageResponse<TRequest>);\n } else {\n const error = new Error(response.error?.message || 'Unknown error');\n (error as any).code = response.error?.code;\n reject(error);\n }\n });\n\n // Send message to iframe\n this.iframe!.contentWindow!.postMessage(request, this.iframeOrigin);\n });\n }\n\n /**\n * Handle incoming messages from iframe\n */\n private handleMessage(event: MessageEvent): void {\n // Validate origin\n const iframeOrigin = new URL(this.iframeUrl).origin;\n if (event.origin !== iframeOrigin) {\n return; // Ignore messages from other origins\n }\n\n const data = event.data;\n\n // Handle response to a specific request (has id)\n if (data.id && this.messageHandlers.has(data.id)) {\n const handler = this.messageHandlers.get(data.id);\n if (handler) {\n handler(data as PostMessageResponse);\n }\n return;\n }\n\n // Handle event broadcasts (type === 'event')\n if (data.type === POST_MESSAGE_EVENT_TYPE) {\n this.handleEvent(data as PostMessageEvent);\n }\n }\n\n /**\n * Handle event broadcasts from iframe\n */\n private handleEvent(data: PostMessageEvent): void {\n // Forward to EmbeddedProvider via callback\n if (this.onEvent) {\n this.onEvent(data.event, data.data);\n }\n }\n\n /**\n * Destroy iframe and cleanup\n */\n destroy(): void {\n if (this.iframe) {\n this.iframe.remove();\n this.iframe = null;\n }\n\n this.readyPromise = null;\n\n if (this.messageListener) {\n window.removeEventListener('message', this.messageListener);\n this.messageListener = null;\n }\n\n this.messageHandlers.clear();\n }\n}\n","import type {\n AddressType as AddressTypeValue,\n ConnectResult,\n IThruChain,\n WalletAccount,\n} from '@thru/chain-interfaces';\nimport { AddressType } from '@thru/chain-interfaces';\nimport {\n DEFAULT_IFRAME_URL,\n EMBEDDED_PROVIDER_EVENTS,\n POST_MESSAGE_REQUEST_TYPES,\n createRequestId,\n type ConnectMetadataInput,\n type ConnectRequestPayload,\n type SelectAccountPayload\n} from '@thru/protocol';\nimport { IframeManager } from './IframeManager';\nimport { EmbeddedThruChain } from './chains/ThruChain';\n\nexport interface EmbeddedProviderConfig {\n iframeUrl?: string;\n addressTypes?: AddressTypeValue[];\n}\n\nexport interface ConnectOptions {\n metadata?: ConnectMetadataInput;\n}\n\n/**\n * Main embedded provider class\n * Manages iframe lifecycle, connection state, and chain-specific interfaces\n */\nexport class EmbeddedProvider {\n private iframeManager: IframeManager;\n private _thruChain?: IThruChain;\n private connected = false;\n private accounts: WalletAccount[] = [];\n private selectedAccount: WalletAccount | null = null;\n private eventListeners = new Map<string, Set<Function>>();\n constructor(config: EmbeddedProviderConfig) {\n const iframeUrl = config.iframeUrl || DEFAULT_IFRAME_URL;\n this.iframeManager = new IframeManager(iframeUrl);\n\n // Set up event forwarding from iframe\n this.iframeManager.onEvent = (eventType: string, payload: any) => {\n this.emit(eventType, payload);\n\n if (\n eventType === EMBEDDED_PROVIDER_EVENTS.DISCONNECT ||\n eventType === EMBEDDED_PROVIDER_EVENTS.LOCK\n ) {\n this.connected = false;\n this.accounts = [];\n this.selectedAccount = null;\n return;\n }\n\n if (eventType === EMBEDDED_PROVIDER_EVENTS.ACCOUNT_CHANGED) {\n const account = (payload && (payload.account as WalletAccount | undefined)) || null;\n this.refreshAccountCache(account ?? null);\n }\n };\n\n // Create chain instances\n const addressTypes = config.addressTypes || [AddressType.THRU];\n if (addressTypes.includes(AddressType.THRU)) {\n this._thruChain = new EmbeddedThruChain(this.iframeManager, this);\n }\n }\n\n /**\n * Initialize the provider (must be called before use)\n * Creates iframe and waits for it to be ready\n */\n async initialize(): Promise<void> {\n await this.iframeManager.createIframe();\n }\n\n /**\n * Connect to wallet\n * Shows iframe modal and requests connection\n */\n async connect(options?: ConnectOptions): Promise<ConnectResult> {\n // Emit connecting event\n this.emit(EMBEDDED_PROVIDER_EVENTS.CONNECT_START, {});\n\n // Show iframe modal\n this.iframeManager.show();\n\n try {\n const payload: ConnectRequestPayload = {};\n\n if (options?.metadata) {\n payload.metadata = options.metadata;\n }\n\n const response = await this.iframeManager.sendMessage({\n id: createRequestId(),\n type: POST_MESSAGE_REQUEST_TYPES.CONNECT,\n payload,\n origin: window.location.origin,\n });\n\n this.connected = true;\n this.accounts = response.result.accounts;\n this.selectedAccount = response.result.accounts[0] ?? null;\n\n // Emit success event\n this.emit(EMBEDDED_PROVIDER_EVENTS.CONNECT, response.result);\n\n // Hide iframe after successful connection\n this.iframeManager.hide();\n\n return response.result;\n } catch (error) {\n this.iframeManager.hide();\n this.emit(EMBEDDED_PROVIDER_EVENTS.CONNECT_ERROR, { error });\n throw error;\n }\n }\n\n /**\n * Disconnect from wallet\n */\n async disconnect(): Promise<void> {\n try {\n await this.iframeManager.sendMessage({\n id: createRequestId(),\n type: POST_MESSAGE_REQUEST_TYPES.DISCONNECT,\n origin: window.location.origin,\n });\n\n this.connected = false;\n this.accounts = [];\n this.iframeManager.hide();\n\n this.emit(EMBEDDED_PROVIDER_EVENTS.DISCONNECT, {});\n } catch (error) {\n this.emit(EMBEDDED_PROVIDER_EVENTS.ERROR, { error });\n throw error;\n }\n }\n\n /**\n * Check if connected\n */\n isConnected(): boolean {\n return this.connected;\n }\n\n /**\n * Get accounts\n */\n getAccounts(): WalletAccount[] {\n return this.accounts;\n }\n\n getSelectedAccount(): WalletAccount | null {\n return this.selectedAccount;\n }\n\n async selectAccount(publicKey: string): Promise<WalletAccount> {\n if (!this.connected) {\n throw new Error('Wallet not connected');\n }\n\n const knownAccount = this.accounts.find(acc => acc.address === publicKey) ?? null;\n if (!knownAccount) {\n console.warn('[EmbeddedProvider] Selecting account not present in local cache');\n }\n const payload: SelectAccountPayload = { publicKey };\n\n const response = await this.iframeManager.sendMessage({\n id: createRequestId(),\n type: POST_MESSAGE_REQUEST_TYPES.SELECT_ACCOUNT,\n payload,\n origin: window.location.origin,\n });\n\n const account = response.result.account;\n\n this.refreshAccountCache(account);\n return account;\n }\n\n /**\n * Get Thru chain API\n */\n get thru(): IThruChain {\n if (!this._thruChain) {\n throw new Error('Thru chain not enabled in provider config');\n }\n return this._thruChain;\n }\n\n /**\n * Event emitter: on\n */\n on(event: string, callback: Function): void {\n if (!this.eventListeners.has(event)) {\n this.eventListeners.set(event, new Set());\n }\n this.eventListeners.get(event)!.add(callback);\n }\n\n /**\n * Event emitter: off\n */\n off(event: string, callback: Function): void {\n this.eventListeners.get(event)?.delete(callback);\n }\n\n /**\n * Emit event to all listeners\n */\n private emit(event: string, data?: any): void {\n this.eventListeners.get(event)?.forEach(callback => {\n try {\n callback(data);\n } catch (error) {\n console.error(`Error in event listener for ${event}:`, error);\n }\n });\n }\n\n /**\n * Get iframe manager (for chain implementations)\n * @internal\n */\n getIframeManager(): IframeManager {\n return this.iframeManager;\n }\n\n /**\n * Destroy provider and cleanup\n */\n destroy(): void {\n this.iframeManager.destroy();\n this.eventListeners.clear();\n this.connected = false;\n this.accounts = [];\n this.selectedAccount = null;\n }\n\n private refreshAccountCache(account: WalletAccount | null): void {\n if (!account) {\n this.selectedAccount = null;\n return;\n }\n\n const existingIdx = this.accounts.findIndex(acc => acc.address === account.address);\n if (existingIdx >= 0) {\n this.accounts[existingIdx] = account;\n } else {\n this.accounts = [...this.accounts, account];\n }\n this.selectedAccount = account;\n }\n}\n"]}
1
+ {"version":3,"sources":["../../chain-interfaces/src/types.ts","../src/chains/ThruChain.ts","../src/IframeManager.ts","../src/EmbeddedProvider.ts"],"names":["createRequestId","POST_MESSAGE_REQUEST_TYPES","DEFAULT_IFRAME_URL","EMBEDDED_PROVIDER_EVENTS"],"mappings":"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","file":"index.js","sourcesContent":["export const AddressType = {\n THRU: 'thru',\n} as const;\n\nexport type AddressType = typeof AddressType[keyof typeof AddressType];\n\nexport interface WalletAccount {\n accountType: AddressType;\n address: string;\n label: string;\n}\n\nexport interface AppMetadata {\n appId: string;\n appName: string;\n appUrl: string;\n imageUrl?: string;\n}\n\nexport interface ConnectResult {\n walletId?: string;\n accounts: WalletAccount[];\n status?: 'pending' | 'completed';\n metadata?: AppMetadata;\n}\n\nexport interface ConnectedApp {\n accountId: number;\n appId: string;\n origin: string;\n metadata: AppMetadata;\n connectedAt: number;\n updatedAt: number;\n}\n\nexport interface SignMessageParams {\n message: string | Uint8Array;\n networkId: string;\n}\n\nexport interface SignMessageResult {\n signature: Uint8Array;\n publicKey: string;\n}\n","import { AddressType, type IThruChain } from '@thru/chain-interfaces';\nimport { POST_MESSAGE_REQUEST_TYPES, createRequestId } from '@thru/protocol';\nimport type { EmbeddedProvider } from '../EmbeddedProvider';\nimport type { IframeManager } from '../IframeManager';\n\n/**\n * EmbeddedThruChain - postMessage-backed Thru chain adapter.\n */\nexport class EmbeddedThruChain implements IThruChain {\n private readonly iframeManager: IframeManager;\n private readonly provider: EmbeddedProvider;\n\n constructor(iframeManager: IframeManager, provider: EmbeddedProvider) {\n this.iframeManager = iframeManager;\n this.provider = provider;\n }\n\n get connected(): boolean {\n return this.provider.isConnected();\n }\n\n async connect(): Promise<{ publicKey: string }> {\n const result = await this.provider.connect();\n const thruAccount = result.accounts.find((addr) => addr.accountType === AddressType.THRU);\n\n if (!thruAccount) {\n throw new Error('Thru address not found in connection result');\n }\n\n return { publicKey: thruAccount.address };\n }\n\n async disconnect(): Promise<void> {\n await this.provider.disconnect();\n }\n\n async signTransaction(serializedTransaction: string): Promise<string> {\n if (!this.provider.isConnected()) {\n throw new Error('Wallet not connected');\n }\n if (typeof serializedTransaction !== 'string' || serializedTransaction.length === 0) {\n throw new Error('Transaction payload must be a base64 encoded string');\n }\n\n this.iframeManager.show();\n\n try {\n const response = await this.iframeManager.sendMessage({\n id: createRequestId(),\n type: POST_MESSAGE_REQUEST_TYPES.SIGN_TRANSACTION,\n payload: { transaction: serializedTransaction },\n origin: window.location.origin,\n });\n return response.result.signedTransaction;\n } finally {\n this.iframeManager.hide();\n }\n }\n}\n","import type {\n InferSuccessfulPostMessageResponse,\n PostMessageEvent,\n PostMessageRequest,\n PostMessageResponse,\n} from './types/messages';\nimport {\n IFRAME_READY_EVENT,\n POST_MESSAGE_EVENT_TYPE,\n POST_MESSAGE_REQUEST_TYPES,\n createRequestId,\n} from './types/messages';\n\n/**\n * Allowed origins for wallet iframe URLs\n * Only iframes from these origins can be loaded for security\n */\nconst ALLOWED_IFRAME_ORIGINS = [\n 'https://thru-wallet.up.railway.app',\n 'https://wallet.thru.io',\n 'https://wallet.thru.org',\n // Allow localhost for development (any port)\n 'http://localhost',\n];\n\n/**\n * Validates that the iframe URL is from a trusted origin\n * @throws Error if the origin is not allowed\n */\nfunction validateIframeOrigin(iframeUrl: string): void {\n let url: URL;\n try {\n url = new URL(iframeUrl);\n } catch (error) {\n throw new Error(\n `Invalid iframe URL: ${iframeUrl}. URL must be a valid absolute URL.`\n );\n }\n\n const origin = url.origin;\n\n // Check if origin matches any allowed origin\n // For localhost, we allow any port (e.g., http://localhost:3000)\n const isAllowed = ALLOWED_IFRAME_ORIGINS.some((allowedOrigin) => {\n if (allowedOrigin === 'http://localhost') {\n // Match exactly http://localhost or http://localhost:port\n return origin === 'http://localhost' || origin.match(/^http:\\/\\/localhost:\\d+$/);\n }\n return origin === allowedOrigin;\n });\n\n if (!isAllowed) {\n throw new Error(\n `Untrusted iframe origin: ${origin}. ` +\n `Only trusted wallet origins are allowed: ${ALLOWED_IFRAME_ORIGINS.join(', ')}. ` +\n `This security check prevents malicious websites from loading unauthorized wallet iframes.`\n );\n }\n}\n\n/**\n * Manages iframe lifecycle and postMessage communication\n * Handles creating, showing/hiding iframe, and message passing\n */\nexport class IframeManager {\n private iframe: HTMLIFrameElement | null = null;\n private iframeUrl: string;\n private iframeOrigin: string;\n private frameId: string;\n private messageHandlers = new Map<string, (response: PostMessageResponse) => void>();\n private messageListener: ((event: MessageEvent) => void) | null = null;\n private readyPromise: Promise<void> | null = null;\n private displayMode: 'modal' | 'inline' = 'modal';\n private inlineContainer: HTMLElement | null = null;\n private visible = false;\n\n /**\n * Callback for event broadcasts from iframe (no request id)\n */\n public onEvent?: (eventType: string, payload: any) => void;\n\n constructor(iframeUrl: string) {\n // Validate origin before accepting the URL\n validateIframeOrigin(iframeUrl);\n\n this.iframeUrl = iframeUrl;\n this.iframeOrigin = new URL(iframeUrl).origin;\n /* Used to correlate postMessage traffic with the correct iframe instance.\n Important in dev (React Strict Mode) where iframes can be created twice. */\n this.frameId = createRequestId('frame');\n }\n\n private getIframeSrc(): string {\n const url = new URL(this.iframeUrl);\n url.searchParams.set('tn_frame_id', this.frameId);\n return url.toString();\n }\n\n /**\n * Create and inject iframe into DOM\n * Returns a promise that resolves when iframe is ready\n */\n async createIframe(): Promise<void> {\n if (this.readyPromise) {\n return this.readyPromise;\n }\n\n this.readyPromise = (async () => {\n if (!this.iframe) {\n this.iframe = document.createElement('iframe');\n this.iframe.src = this.getIframeSrc();\n /* Allow WebAuthn in cross-origin iframe for passkey auth. */\n this.iframe.allow = 'publickey-credentials-get; publickey-credentials-create';\n this.applyIframeStyles();\n /* Keep hidden (but still load) until the wallet asks to show UI. */\n this.setVisibility(false);\n\n if (this.displayMode === 'inline' && this.inlineContainer) {\n this.inlineContainer.appendChild(this.iframe);\n } else {\n document.body.appendChild(this.iframe);\n }\n\n // Set up message listener\n this.messageListener = this.handleMessage.bind(this);\n window.addEventListener('message', this.messageListener);\n }\n\n await this.waitForReady();\n })().catch((error) => {\n this.readyPromise = null;\n throw error;\n });\n\n return this.readyPromise;\n }\n\n /**\n * Wait for iframe to send 'ready' signal\n */\n private waitForReady(): Promise<void> {\n return new Promise((resolve, reject) => {\n let resolved = false;\n let readyHandler: (event: MessageEvent) => void;\n const cleanup = () => {\n if (resolved) {\n return;\n }\n resolved = true;\n window.removeEventListener('message', readyHandler);\n clearTimeout(timeout);\n };\n\n const timeout = setTimeout(() => {\n cleanup();\n reject(new Error('Iframe ready timeout - wallet failed to load'));\n }, 10000);\n\n readyHandler = (event: MessageEvent) => {\n if (!this.isMessageFromIframe(event)) {\n return;\n }\n\n if (event.data?.type === IFRAME_READY_EVENT) {\n cleanup();\n resolve();\n }\n };\n\n window.addEventListener('message', readyHandler);\n });\n }\n\n /**\n * Mount iframe inline inside the provided container.\n */\n async mountInline(container: HTMLElement): Promise<void> {\n this.inlineContainer = container;\n this.displayMode = 'inline';\n await this.createIframe();\n this.showInline();\n }\n\n /**\n * Show iframe inline (embedded in container).\n */\n showInline(): void {\n if (!this.iframe) {\n return;\n }\n this.displayMode = 'inline';\n if (this.inlineContainer && this.iframe.parentElement !== this.inlineContainer) {\n this.inlineContainer.appendChild(this.iframe);\n }\n this.applyIframeStyles();\n this.setVisibility(true);\n }\n\n /**\n * Show iframe as a full-screen modal.\n */\n showModal(): void {\n if (!this.iframe) {\n return;\n }\n this.displayMode = 'modal';\n if (this.iframe.parentElement !== document.body) {\n document.body.appendChild(this.iframe);\n }\n this.applyIframeStyles();\n this.setVisibility(true);\n }\n\n /**\n * Show iframe modal\n */\n show(): void {\n this.showModal();\n }\n\n /**\n * Hide iframe modal\n */\n hide(): void {\n this.setVisibility(false);\n }\n\n isInline(): boolean {\n return this.displayMode === 'inline';\n }\n\n private applyIframeStyles(): void {\n if (!this.iframe) {\n return;\n }\n\n if (this.displayMode === 'inline') {\n this.iframe.style.cssText = `\n position: relative;\n width: 100%;\n height: 100%;\n border: none;\n z-index: 1;\n display: block;\n background: transparent;\n `;\n return;\n }\n\n this.iframe.style.cssText = `\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n border: none;\n z-index: 999999;\n display: block;\n background: rgba(0, 0, 0, 0.5);\n `;\n }\n\n private setVisibility(visible: boolean): void {\n if (!this.iframe) {\n return;\n }\n this.visible = visible;\n this.iframe.style.opacity = visible ? '1' : '0';\n this.iframe.style.pointerEvents = visible ? 'auto' : 'none';\n this.iframe.style.visibility = visible ? 'visible' : 'hidden';\n }\n\n /**\n * Send message to iframe and wait for response\n */\n async sendMessage<TRequest extends PostMessageRequest>(\n request: TRequest\n ): Promise<InferSuccessfulPostMessageResponse<TRequest>> {\n /* Ensure the iframe has navigated to the wallet origin before we try to\n postMessage to a strict targetOrigin. Otherwise the iframe can still be\n about:blank (same-origin with the dapp) and postMessage will throw. */\n if (this.readyPromise) {\n await this.readyPromise;\n } else {\n await this.createIframe();\n }\n\n if (!this.iframe?.contentWindow) {\n throw new Error('Iframe not initialized - call createIframe() first');\n }\n\n return new Promise<InferSuccessfulPostMessageResponse<TRequest>>((resolve, reject) => {\n /* CONNECT/SIGN_* requests require a human click and can take minutes.\n Keep a longer timeout to avoid breaking \"inline connect button\" flows. */\n const timeoutMs =\n request.type === POST_MESSAGE_REQUEST_TYPES.CONNECT ||\n request.type === POST_MESSAGE_REQUEST_TYPES.SIGN_MESSAGE ||\n request.type === POST_MESSAGE_REQUEST_TYPES.SIGN_TRANSACTION\n ? 5 * 60 * 1000 /* 5 minutes */\n : 30 * 1000; /* 30 seconds */\n\n const timeout = setTimeout(() => {\n this.messageHandlers.delete(request.id);\n reject(new Error('Request timeout - wallet did not respond'));\n }, timeoutMs);\n\n // Store handler for this request\n this.messageHandlers.set(request.id, (response: PostMessageResponse) => {\n clearTimeout(timeout);\n this.messageHandlers.delete(request.id);\n\n if (response.success) {\n resolve(response as InferSuccessfulPostMessageResponse<TRequest>);\n } else {\n const error = new Error(response.error?.message || 'Unknown error');\n (error as any).code = response.error?.code;\n reject(error);\n }\n });\n\n // Send message to iframe\n this.iframe!.contentWindow!.postMessage(request, this.iframeOrigin);\n });\n }\n\n /**\n * Handle incoming messages from iframe\n */\n private handleMessage(event: MessageEvent): void {\n if (!this.isMessageFromIframe(event)) {\n return; // Ignore messages from other origins\n }\n\n const data = event.data;\n\n // Handle response to a specific request (has id)\n if (data.id && this.messageHandlers.has(data.id)) {\n const handler = this.messageHandlers.get(data.id);\n if (handler) {\n handler(data as PostMessageResponse);\n }\n return;\n }\n\n // Handle event broadcasts (type === 'event')\n if (data.type === POST_MESSAGE_EVENT_TYPE) {\n this.handleEvent(data as PostMessageEvent);\n }\n }\n\n /**\n * Handle event broadcasts from iframe\n */\n private handleEvent(data: PostMessageEvent): void {\n // Forward to EmbeddedProvider via callback\n if (this.onEvent) {\n this.onEvent(data.event, data.data);\n }\n }\n\n private isMessageFromIframe(event: MessageEvent): boolean {\n if (event.origin !== this.iframeOrigin) {\n return false;\n }\n\n const data = event.data as any;\n if (!data || data.frameId !== this.frameId) {\n return false;\n }\n\n /* Some browsers (notably Safari) can provide a null `event.source` for\n cross-origin postMessage events. Frame id + origin is sufficient. */\n if (!event.source) {\n return true;\n }\n\n if (this.iframe?.contentWindow && event.source !== this.iframe.contentWindow) {\n return false;\n }\n\n return true;\n }\n\n /**\n * Destroy iframe and cleanup\n */\n destroy(): void {\n if (this.iframe) {\n this.iframe.remove();\n this.iframe = null;\n }\n\n this.readyPromise = null;\n\n if (this.messageListener) {\n window.removeEventListener('message', this.messageListener);\n this.messageListener = null;\n }\n\n this.messageHandlers.clear();\n }\n}\n","import type {\n AddressType as AddressTypeValue,\n ConnectResult,\n IThruChain,\n WalletAccount,\n} from '@thru/chain-interfaces';\nimport { AddressType } from '@thru/chain-interfaces';\nimport {\n DEFAULT_IFRAME_URL,\n EMBEDDED_PROVIDER_EVENTS,\n POST_MESSAGE_REQUEST_TYPES,\n createRequestId,\n type ConnectMetadataInput,\n type ConnectRequestPayload,\n type SelectAccountPayload\n} from '@thru/protocol';\nimport { IframeManager } from './IframeManager';\nimport { EmbeddedThruChain } from './chains/ThruChain';\n\nexport interface EmbeddedProviderConfig {\n iframeUrl?: string;\n addressTypes?: AddressTypeValue[];\n}\n\nexport interface ConnectOptions {\n metadata?: ConnectMetadataInput;\n}\n\n/**\n * Main embedded provider class\n * Manages iframe lifecycle, connection state, and chain-specific interfaces\n */\nexport class EmbeddedProvider {\n private iframeManager: IframeManager;\n private _thruChain?: IThruChain;\n private connected = false;\n private accounts: WalletAccount[] = [];\n private selectedAccount: WalletAccount | null = null;\n private eventListeners = new Map<string, Set<Function>>();\n private inlineMode = false;\n constructor(config: EmbeddedProviderConfig) {\n const iframeUrl = config.iframeUrl || DEFAULT_IFRAME_URL;\n this.iframeManager = new IframeManager(iframeUrl);\n\n // Set up event forwarding from iframe\n this.iframeManager.onEvent = (eventType: string, payload: any) => {\n this.emit(eventType, payload);\n\n if (eventType === EMBEDDED_PROVIDER_EVENTS.UI_SHOW) {\n if (this.inlineMode) {\n this.iframeManager.showInline();\n } else {\n this.iframeManager.showModal();\n }\n return;\n }\n\n if (\n eventType === EMBEDDED_PROVIDER_EVENTS.DISCONNECT ||\n eventType === EMBEDDED_PROVIDER_EVENTS.LOCK\n ) {\n this.connected = false;\n this.accounts = [];\n this.selectedAccount = null;\n return;\n }\n\n if (eventType === EMBEDDED_PROVIDER_EVENTS.ACCOUNT_CHANGED) {\n const account = (payload && (payload.account as WalletAccount | undefined)) || null;\n this.refreshAccountCache(account ?? null);\n }\n };\n\n // Create chain instances\n const addressTypes = config.addressTypes || [AddressType.THRU];\n if (addressTypes.includes(AddressType.THRU)) {\n this._thruChain = new EmbeddedThruChain(this.iframeManager, this);\n }\n }\n\n /**\n * Initialize the provider (must be called before use)\n * Creates iframe and waits for it to be ready\n */\n async initialize(): Promise<void> {\n await this.iframeManager.createIframe();\n }\n\n /**\n * Mount the wallet iframe inline in a container (for inline connect button).\n */\n async mountInline(container: HTMLElement): Promise<void> {\n this.inlineMode = true;\n await this.iframeManager.mountInline(container);\n }\n\n /**\n * Connect to wallet\n * Shows iframe modal and requests connection\n */\n async connect(options?: ConnectOptions): Promise<ConnectResult> {\n // Emit connecting event\n this.emit(EMBEDDED_PROVIDER_EVENTS.CONNECT_START, {});\n\n try {\n if (this.inlineMode) {\n this.iframeManager.showInline();\n } else {\n this.iframeManager.showModal();\n }\n\n const payload: ConnectRequestPayload = {};\n\n if (options?.metadata) {\n payload.metadata = options.metadata;\n }\n\n const response = await this.iframeManager.sendMessage({\n id: createRequestId(),\n type: POST_MESSAGE_REQUEST_TYPES.CONNECT,\n payload,\n origin: window.location.origin,\n });\n\n this.connected = true;\n this.accounts = response.result.accounts;\n this.selectedAccount = response.result.accounts[0] ?? null;\n\n // Emit success event\n this.emit(EMBEDDED_PROVIDER_EVENTS.CONNECT, response.result);\n\n // Hide iframe after successful connection\n if (!this.inlineMode) {\n this.iframeManager.hide();\n }\n\n return response.result;\n } catch (error) {\n if (!this.inlineMode) {\n this.iframeManager.hide();\n }\n this.emit(EMBEDDED_PROVIDER_EVENTS.CONNECT_ERROR, { error });\n throw error;\n }\n }\n\n /**\n * Disconnect from wallet\n */\n async disconnect(): Promise<void> {\n try {\n await this.iframeManager.sendMessage({\n id: createRequestId(),\n type: POST_MESSAGE_REQUEST_TYPES.DISCONNECT,\n origin: window.location.origin,\n });\n\n this.emit(EMBEDDED_PROVIDER_EVENTS.DISCONNECT, {});\n } catch (error) {\n this.emit(EMBEDDED_PROVIDER_EVENTS.ERROR, { error });\n throw error;\n } finally {\n this.connected = false;\n this.accounts = [];\n this.selectedAccount = null;\n if (!this.inlineMode) {\n this.iframeManager.hide();\n }\n }\n }\n\n /**\n * Check if connected\n */\n isConnected(): boolean {\n return this.connected;\n }\n\n /**\n * Get accounts\n */\n getAccounts(): WalletAccount[] {\n return this.accounts;\n }\n\n getSelectedAccount(): WalletAccount | null {\n return this.selectedAccount;\n }\n\n async selectAccount(publicKey: string): Promise<WalletAccount> {\n if (!this.connected) {\n throw new Error('Wallet not connected');\n }\n\n const knownAccount = this.accounts.find(acc => acc.address === publicKey) ?? null;\n if (!knownAccount) {\n console.warn('[EmbeddedProvider] Selecting account not present in local cache');\n }\n const payload: SelectAccountPayload = { publicKey };\n\n const response = await this.iframeManager.sendMessage({\n id: createRequestId(),\n type: POST_MESSAGE_REQUEST_TYPES.SELECT_ACCOUNT,\n payload,\n origin: window.location.origin,\n });\n\n const account = response.result.account;\n\n this.refreshAccountCache(account);\n return account;\n }\n\n /**\n * Get Thru chain API\n */\n get thru(): IThruChain {\n if (!this._thruChain) {\n throw new Error('Thru chain not enabled in provider config');\n }\n return this._thruChain;\n }\n\n /**\n * Event emitter: on\n */\n on(event: string, callback: Function): void {\n if (!this.eventListeners.has(event)) {\n this.eventListeners.set(event, new Set());\n }\n this.eventListeners.get(event)!.add(callback);\n }\n\n /**\n * Event emitter: off\n */\n off(event: string, callback: Function): void {\n this.eventListeners.get(event)?.delete(callback);\n }\n\n /**\n * Emit event to all listeners\n */\n private emit(event: string, data?: any): void {\n this.eventListeners.get(event)?.forEach(callback => {\n try {\n callback(data);\n } catch (error) {\n console.error(`Error in event listener for ${event}:`, error);\n }\n });\n }\n\n /**\n * Get iframe manager (for chain implementations)\n * @internal\n */\n getIframeManager(): IframeManager {\n return this.iframeManager;\n }\n\n /**\n * Destroy provider and cleanup\n */\n destroy(): void {\n this.iframeManager.destroy();\n this.eventListeners.clear();\n this.connected = false;\n this.accounts = [];\n this.selectedAccount = null;\n }\n\n private refreshAccountCache(account: WalletAccount | null): void {\n if (!account) {\n this.selectedAccount = null;\n return;\n }\n\n const existingIdx = this.accounts.findIndex(acc => acc.address === account.address);\n if (existingIdx >= 0) {\n this.accounts[existingIdx] = account;\n } else {\n this.accounts = [...this.accounts, account];\n }\n this.selectedAccount = account;\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thru/embedded-provider",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -11,8 +11,8 @@
11
11
  }
12
12
  },
13
13
  "dependencies": {
14
- "@thru/chain-interfaces": "0.2.0",
15
- "@thru/protocol": "0.2.0"
14
+ "@thru/chain-interfaces": "0.2.1",
15
+ "@thru/protocol": "0.2.1"
16
16
  },
17
17
  "devDependencies": {
18
18
  "@types/node": "^24.7.0"
@@ -37,6 +37,7 @@ export class EmbeddedProvider {
37
37
  private accounts: WalletAccount[] = [];
38
38
  private selectedAccount: WalletAccount | null = null;
39
39
  private eventListeners = new Map<string, Set<Function>>();
40
+ private inlineMode = false;
40
41
  constructor(config: EmbeddedProviderConfig) {
41
42
  const iframeUrl = config.iframeUrl || DEFAULT_IFRAME_URL;
42
43
  this.iframeManager = new IframeManager(iframeUrl);
@@ -45,6 +46,15 @@ export class EmbeddedProvider {
45
46
  this.iframeManager.onEvent = (eventType: string, payload: any) => {
46
47
  this.emit(eventType, payload);
47
48
 
49
+ if (eventType === EMBEDDED_PROVIDER_EVENTS.UI_SHOW) {
50
+ if (this.inlineMode) {
51
+ this.iframeManager.showInline();
52
+ } else {
53
+ this.iframeManager.showModal();
54
+ }
55
+ return;
56
+ }
57
+
48
58
  if (
49
59
  eventType === EMBEDDED_PROVIDER_EVENTS.DISCONNECT ||
50
60
  eventType === EMBEDDED_PROVIDER_EVENTS.LOCK
@@ -76,6 +86,14 @@ export class EmbeddedProvider {
76
86
  await this.iframeManager.createIframe();
77
87
  }
78
88
 
89
+ /**
90
+ * Mount the wallet iframe inline in a container (for inline connect button).
91
+ */
92
+ async mountInline(container: HTMLElement): Promise<void> {
93
+ this.inlineMode = true;
94
+ await this.iframeManager.mountInline(container);
95
+ }
96
+
79
97
  /**
80
98
  * Connect to wallet
81
99
  * Shows iframe modal and requests connection
@@ -84,10 +102,13 @@ export class EmbeddedProvider {
84
102
  // Emit connecting event
85
103
  this.emit(EMBEDDED_PROVIDER_EVENTS.CONNECT_START, {});
86
104
 
87
- // Show iframe modal
88
- this.iframeManager.show();
89
-
90
105
  try {
106
+ if (this.inlineMode) {
107
+ this.iframeManager.showInline();
108
+ } else {
109
+ this.iframeManager.showModal();
110
+ }
111
+
91
112
  const payload: ConnectRequestPayload = {};
92
113
 
93
114
  if (options?.metadata) {
@@ -109,11 +130,15 @@ export class EmbeddedProvider {
109
130
  this.emit(EMBEDDED_PROVIDER_EVENTS.CONNECT, response.result);
110
131
 
111
132
  // Hide iframe after successful connection
112
- this.iframeManager.hide();
133
+ if (!this.inlineMode) {
134
+ this.iframeManager.hide();
135
+ }
113
136
 
114
137
  return response.result;
115
138
  } catch (error) {
116
- this.iframeManager.hide();
139
+ if (!this.inlineMode) {
140
+ this.iframeManager.hide();
141
+ }
117
142
  this.emit(EMBEDDED_PROVIDER_EVENTS.CONNECT_ERROR, { error });
118
143
  throw error;
119
144
  }
@@ -130,14 +155,17 @@ export class EmbeddedProvider {
130
155
  origin: window.location.origin,
131
156
  });
132
157
 
133
- this.connected = false;
134
- this.accounts = [];
135
- this.iframeManager.hide();
136
-
137
158
  this.emit(EMBEDDED_PROVIDER_EVENTS.DISCONNECT, {});
138
159
  } catch (error) {
139
160
  this.emit(EMBEDDED_PROVIDER_EVENTS.ERROR, { error });
140
161
  throw error;
162
+ } finally {
163
+ this.connected = false;
164
+ this.accounts = [];
165
+ this.selectedAccount = null;
166
+ if (!this.inlineMode) {
167
+ this.iframeManager.hide();
168
+ }
141
169
  }
142
170
  }
143
171
 
@@ -4,7 +4,12 @@ import type {
4
4
  PostMessageRequest,
5
5
  PostMessageResponse,
6
6
  } from './types/messages';
7
- import { IFRAME_READY_EVENT, POST_MESSAGE_EVENT_TYPE } from './types/messages';
7
+ import {
8
+ IFRAME_READY_EVENT,
9
+ POST_MESSAGE_EVENT_TYPE,
10
+ POST_MESSAGE_REQUEST_TYPES,
11
+ createRequestId,
12
+ } from './types/messages';
8
13
 
9
14
  /**
10
15
  * Allowed origins for wallet iframe URLs
@@ -12,6 +17,7 @@ import { IFRAME_READY_EVENT, POST_MESSAGE_EVENT_TYPE } from './types/messages';
12
17
  */
13
18
  const ALLOWED_IFRAME_ORIGINS = [
14
19
  'https://thru-wallet.up.railway.app',
20
+ 'https://wallet.thru.io',
15
21
  'https://wallet.thru.org',
16
22
  // Allow localhost for development (any port)
17
23
  'http://localhost',
@@ -60,9 +66,13 @@ export class IframeManager {
60
66
  private iframe: HTMLIFrameElement | null = null;
61
67
  private iframeUrl: string;
62
68
  private iframeOrigin: string;
69
+ private frameId: string;
63
70
  private messageHandlers = new Map<string, (response: PostMessageResponse) => void>();
64
71
  private messageListener: ((event: MessageEvent) => void) | null = null;
65
72
  private readyPromise: Promise<void> | null = null;
73
+ private displayMode: 'modal' | 'inline' = 'modal';
74
+ private inlineContainer: HTMLElement | null = null;
75
+ private visible = false;
66
76
 
67
77
  /**
68
78
  * Callback for event broadcasts from iframe (no request id)
@@ -75,6 +85,15 @@ export class IframeManager {
75
85
 
76
86
  this.iframeUrl = iframeUrl;
77
87
  this.iframeOrigin = new URL(iframeUrl).origin;
88
+ /* Used to correlate postMessage traffic with the correct iframe instance.
89
+ Important in dev (React Strict Mode) where iframes can be created twice. */
90
+ this.frameId = createRequestId('frame');
91
+ }
92
+
93
+ private getIframeSrc(): string {
94
+ const url = new URL(this.iframeUrl);
95
+ url.searchParams.set('tn_frame_id', this.frameId);
96
+ return url.toString();
78
97
  }
79
98
 
80
99
  /**
@@ -89,29 +108,29 @@ export class IframeManager {
89
108
  this.readyPromise = (async () => {
90
109
  if (!this.iframe) {
91
110
  this.iframe = document.createElement('iframe');
92
- this.iframe.src = this.iframeUrl;
93
- this.iframe.style.cssText = `
94
- position: fixed;
95
- top: 0;
96
- left: 0;
97
- width: 100%;
98
- height: 100%;
99
- border: none;
100
- z-index: 999999;
101
- display: none;
102
- background: rgba(0, 0, 0, 0.5);
103
- `;
104
-
105
- document.body.appendChild(this.iframe);
111
+ this.iframe.src = this.getIframeSrc();
112
+ /* Allow WebAuthn in cross-origin iframe for passkey auth. */
113
+ this.iframe.allow = 'publickey-credentials-get; publickey-credentials-create';
114
+ this.applyIframeStyles();
115
+ /* Keep hidden (but still load) until the wallet asks to show UI. */
116
+ this.setVisibility(false);
117
+
118
+ if (this.displayMode === 'inline' && this.inlineContainer) {
119
+ this.inlineContainer.appendChild(this.iframe);
120
+ } else {
121
+ document.body.appendChild(this.iframe);
122
+ }
106
123
 
107
124
  // Set up message listener
108
125
  this.messageListener = this.handleMessage.bind(this);
109
126
  window.addEventListener('message', this.messageListener);
110
127
  }
111
128
 
112
- // Wait for iframe ready signal
113
129
  await this.waitForReady();
114
- })();
130
+ })().catch((error) => {
131
+ this.readyPromise = null;
132
+ throw error;
133
+ });
115
134
 
116
135
  return this.readyPromise;
117
136
  }
@@ -121,18 +140,29 @@ export class IframeManager {
121
140
  */
122
141
  private waitForReady(): Promise<void> {
123
142
  return new Promise((resolve, reject) => {
143
+ let resolved = false;
144
+ let readyHandler: (event: MessageEvent) => void;
145
+ const cleanup = () => {
146
+ if (resolved) {
147
+ return;
148
+ }
149
+ resolved = true;
150
+ window.removeEventListener('message', readyHandler);
151
+ clearTimeout(timeout);
152
+ };
153
+
124
154
  const timeout = setTimeout(() => {
155
+ cleanup();
125
156
  reject(new Error('Iframe ready timeout - wallet failed to load'));
126
- }, 10000); // 10 second timeout
157
+ }, 10000);
127
158
 
128
- const readyHandler = (event: MessageEvent) => {
129
- if (event.origin !== this.iframeOrigin) {
159
+ readyHandler = (event: MessageEvent) => {
160
+ if (!this.isMessageFromIframe(event)) {
130
161
  return;
131
162
  }
132
163
 
133
- if (event.data.type === IFRAME_READY_EVENT) {
134
- clearTimeout(timeout);
135
- window.removeEventListener('message', readyHandler);
164
+ if (event.data?.type === IFRAME_READY_EVENT) {
165
+ cleanup();
136
166
  resolve();
137
167
  }
138
168
  };
@@ -141,22 +171,103 @@ export class IframeManager {
141
171
  });
142
172
  }
143
173
 
174
+ /**
175
+ * Mount iframe inline inside the provided container.
176
+ */
177
+ async mountInline(container: HTMLElement): Promise<void> {
178
+ this.inlineContainer = container;
179
+ this.displayMode = 'inline';
180
+ await this.createIframe();
181
+ this.showInline();
182
+ }
183
+
184
+ /**
185
+ * Show iframe inline (embedded in container).
186
+ */
187
+ showInline(): void {
188
+ if (!this.iframe) {
189
+ return;
190
+ }
191
+ this.displayMode = 'inline';
192
+ if (this.inlineContainer && this.iframe.parentElement !== this.inlineContainer) {
193
+ this.inlineContainer.appendChild(this.iframe);
194
+ }
195
+ this.applyIframeStyles();
196
+ this.setVisibility(true);
197
+ }
198
+
199
+ /**
200
+ * Show iframe as a full-screen modal.
201
+ */
202
+ showModal(): void {
203
+ if (!this.iframe) {
204
+ return;
205
+ }
206
+ this.displayMode = 'modal';
207
+ if (this.iframe.parentElement !== document.body) {
208
+ document.body.appendChild(this.iframe);
209
+ }
210
+ this.applyIframeStyles();
211
+ this.setVisibility(true);
212
+ }
213
+
144
214
  /**
145
215
  * Show iframe modal
146
216
  */
147
217
  show(): void {
148
- if (this.iframe) {
149
- this.iframe.style.display = 'block';
150
- }
218
+ this.showModal();
151
219
  }
152
220
 
153
221
  /**
154
222
  * Hide iframe modal
155
223
  */
156
224
  hide(): void {
157
- if (this.iframe) {
158
- this.iframe.style.display = 'none';
225
+ this.setVisibility(false);
226
+ }
227
+
228
+ isInline(): boolean {
229
+ return this.displayMode === 'inline';
230
+ }
231
+
232
+ private applyIframeStyles(): void {
233
+ if (!this.iframe) {
234
+ return;
235
+ }
236
+
237
+ if (this.displayMode === 'inline') {
238
+ this.iframe.style.cssText = `
239
+ position: relative;
240
+ width: 100%;
241
+ height: 100%;
242
+ border: none;
243
+ z-index: 1;
244
+ display: block;
245
+ background: transparent;
246
+ `;
247
+ return;
248
+ }
249
+
250
+ this.iframe.style.cssText = `
251
+ position: fixed;
252
+ top: 0;
253
+ left: 0;
254
+ width: 100%;
255
+ height: 100%;
256
+ border: none;
257
+ z-index: 999999;
258
+ display: block;
259
+ background: rgba(0, 0, 0, 0.5);
260
+ `;
261
+ }
262
+
263
+ private setVisibility(visible: boolean): void {
264
+ if (!this.iframe) {
265
+ return;
159
266
  }
267
+ this.visible = visible;
268
+ this.iframe.style.opacity = visible ? '1' : '0';
269
+ this.iframe.style.pointerEvents = visible ? 'auto' : 'none';
270
+ this.iframe.style.visibility = visible ? 'visible' : 'hidden';
160
271
  }
161
272
 
162
273
  /**
@@ -165,15 +276,33 @@ export class IframeManager {
165
276
  async sendMessage<TRequest extends PostMessageRequest>(
166
277
  request: TRequest
167
278
  ): Promise<InferSuccessfulPostMessageResponse<TRequest>> {
279
+ /* Ensure the iframe has navigated to the wallet origin before we try to
280
+ postMessage to a strict targetOrigin. Otherwise the iframe can still be
281
+ about:blank (same-origin with the dapp) and postMessage will throw. */
282
+ if (this.readyPromise) {
283
+ await this.readyPromise;
284
+ } else {
285
+ await this.createIframe();
286
+ }
287
+
168
288
  if (!this.iframe?.contentWindow) {
169
289
  throw new Error('Iframe not initialized - call createIframe() first');
170
290
  }
171
291
 
172
292
  return new Promise<InferSuccessfulPostMessageResponse<TRequest>>((resolve, reject) => {
293
+ /* CONNECT/SIGN_* requests require a human click and can take minutes.
294
+ Keep a longer timeout to avoid breaking "inline connect button" flows. */
295
+ const timeoutMs =
296
+ request.type === POST_MESSAGE_REQUEST_TYPES.CONNECT ||
297
+ request.type === POST_MESSAGE_REQUEST_TYPES.SIGN_MESSAGE ||
298
+ request.type === POST_MESSAGE_REQUEST_TYPES.SIGN_TRANSACTION
299
+ ? 5 * 60 * 1000 /* 5 minutes */
300
+ : 30 * 1000; /* 30 seconds */
301
+
173
302
  const timeout = setTimeout(() => {
174
303
  this.messageHandlers.delete(request.id);
175
304
  reject(new Error('Request timeout - wallet did not respond'));
176
- }, 30000); // 30 second timeout
305
+ }, timeoutMs);
177
306
 
178
307
  // Store handler for this request
179
308
  this.messageHandlers.set(request.id, (response: PostMessageResponse) => {
@@ -198,9 +327,7 @@ export class IframeManager {
198
327
  * Handle incoming messages from iframe
199
328
  */
200
329
  private handleMessage(event: MessageEvent): void {
201
- // Validate origin
202
- const iframeOrigin = new URL(this.iframeUrl).origin;
203
- if (event.origin !== iframeOrigin) {
330
+ if (!this.isMessageFromIframe(event)) {
204
331
  return; // Ignore messages from other origins
205
332
  }
206
333
 
@@ -231,6 +358,29 @@ export class IframeManager {
231
358
  }
232
359
  }
233
360
 
361
+ private isMessageFromIframe(event: MessageEvent): boolean {
362
+ if (event.origin !== this.iframeOrigin) {
363
+ return false;
364
+ }
365
+
366
+ const data = event.data as any;
367
+ if (!data || data.frameId !== this.frameId) {
368
+ return false;
369
+ }
370
+
371
+ /* Some browsers (notably Safari) can provide a null `event.source` for
372
+ cross-origin postMessage events. Frame id + origin is sufficient. */
373
+ if (!event.source) {
374
+ return true;
375
+ }
376
+
377
+ if (this.iframe?.contentWindow && event.source !== this.iframe.contentWindow) {
378
+ return false;
379
+ }
380
+
381
+ return true;
382
+ }
383
+
234
384
  /**
235
385
  * Destroy iframe and cleanup
236
386
  */