@thru/embedded-provider 0.2.0 → 0.2.3
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 +135 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +174 -33
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/EmbeddedProvider.ts +37 -9
- package/src/IframeManager.ts +182 -32
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.
|
|
100
|
-
this.iframe.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
128
|
-
if (
|
|
147
|
+
readyHandler = (event) => {
|
|
148
|
+
if (!this.isMessageFromIframe(event)) {
|
|
129
149
|
return;
|
|
130
150
|
}
|
|
131
|
-
if (event.data
|
|
132
|
-
|
|
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
|
-
|
|
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
|
-
|
|
153
|
-
|
|
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
|
-
},
|
|
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
|
-
|
|
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.
|
|
416
|
+
if (!this.inlineMode) {
|
|
417
|
+
this.iframeManager.hide();
|
|
418
|
+
}
|
|
284
419
|
return response.result;
|
|
285
420
|
} catch (error) {
|
|
286
|
-
this.
|
|
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.
|
|
3
|
+
"version": "0.2.3",
|
|
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.
|
|
15
|
-
"@thru/protocol": "0.2.
|
|
14
|
+
"@thru/chain-interfaces": "0.2.3",
|
|
15
|
+
"@thru/protocol": "0.2.3"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"@types/node": "^24.7.0"
|
package/src/EmbeddedProvider.ts
CHANGED
|
@@ -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.
|
|
133
|
+
if (!this.inlineMode) {
|
|
134
|
+
this.iframeManager.hide();
|
|
135
|
+
}
|
|
113
136
|
|
|
114
137
|
return response.result;
|
|
115
138
|
} catch (error) {
|
|
116
|
-
this.
|
|
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
|
|
package/src/IframeManager.ts
CHANGED
|
@@ -4,7 +4,12 @@ import type {
|
|
|
4
4
|
PostMessageRequest,
|
|
5
5
|
PostMessageResponse,
|
|
6
6
|
} from './types/messages';
|
|
7
|
-
import {
|
|
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.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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);
|
|
157
|
+
}, 10000);
|
|
127
158
|
|
|
128
|
-
|
|
129
|
-
if (
|
|
159
|
+
readyHandler = (event: MessageEvent) => {
|
|
160
|
+
if (!this.isMessageFromIframe(event)) {
|
|
130
161
|
return;
|
|
131
162
|
}
|
|
132
163
|
|
|
133
|
-
if (event.data
|
|
134
|
-
|
|
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
|
-
|
|
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
|
-
|
|
158
|
-
|
|
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
|
-
},
|
|
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
|
-
|
|
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
|
*/
|