@thru/embedded-provider 0.0.4
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/dist/index.d.ts +140 -0
- package/dist/index.js +346 -0
- package/dist/index.js.map +1 -0
- package/package.json +26 -0
- package/src/EmbeddedProvider.ts +206 -0
- package/src/IframeManager.ts +203 -0
- package/src/chains/ThruChain.ts +65 -0
- package/src/index.ts +23 -0
- package/src/types/messages.ts +30 -0
- package/tsconfig.json +9 -0
- package/tsup.config.ts +13 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { PostMessageRequest, InferSuccessfulPostMessageResponse, ConnectMetadataInput } from '@thru/protocol';
|
|
2
|
+
export { ConnectResult, EmbeddedProviderEvent, ErrorCode, PostMessageEvent, PostMessageRequest, PostMessageResponse, RequestType, SignMessagePayload, SignMessageResult, SignTransactionPayload, SignTransactionResult } from '@thru/protocol';
|
|
3
|
+
import { AddressType, ConnectResult, WalletAddress, IThruChain } from '@thru/chain-interfaces';
|
|
4
|
+
export { IThruChain, WalletAddress } from '@thru/chain-interfaces';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Manages iframe lifecycle and postMessage communication
|
|
8
|
+
* Handles creating, showing/hiding iframe, and message passing
|
|
9
|
+
*/
|
|
10
|
+
declare class IframeManager {
|
|
11
|
+
private iframe;
|
|
12
|
+
private iframeUrl;
|
|
13
|
+
private iframeOrigin;
|
|
14
|
+
private messageHandlers;
|
|
15
|
+
private messageListener;
|
|
16
|
+
private readyPromise;
|
|
17
|
+
/**
|
|
18
|
+
* Callback for event broadcasts from iframe (no request id)
|
|
19
|
+
*/
|
|
20
|
+
onEvent?: (eventType: string, payload: any) => void;
|
|
21
|
+
constructor(iframeUrl: string);
|
|
22
|
+
/**
|
|
23
|
+
* Create and inject iframe into DOM
|
|
24
|
+
* Returns a promise that resolves when iframe is ready
|
|
25
|
+
*/
|
|
26
|
+
createIframe(): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Wait for iframe to send 'ready' signal
|
|
29
|
+
*/
|
|
30
|
+
private waitForReady;
|
|
31
|
+
/**
|
|
32
|
+
* Show iframe modal
|
|
33
|
+
*/
|
|
34
|
+
show(): void;
|
|
35
|
+
/**
|
|
36
|
+
* Hide iframe modal
|
|
37
|
+
*/
|
|
38
|
+
hide(): void;
|
|
39
|
+
/**
|
|
40
|
+
* Send message to iframe and wait for response
|
|
41
|
+
*/
|
|
42
|
+
sendMessage<TRequest extends PostMessageRequest>(request: TRequest): Promise<InferSuccessfulPostMessageResponse<TRequest>>;
|
|
43
|
+
/**
|
|
44
|
+
* Handle incoming messages from iframe
|
|
45
|
+
*/
|
|
46
|
+
private handleMessage;
|
|
47
|
+
/**
|
|
48
|
+
* Handle event broadcasts from iframe
|
|
49
|
+
*/
|
|
50
|
+
private handleEvent;
|
|
51
|
+
/**
|
|
52
|
+
* Destroy iframe and cleanup
|
|
53
|
+
*/
|
|
54
|
+
destroy(): void;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface EmbeddedProviderConfig {
|
|
58
|
+
iframeUrl?: string;
|
|
59
|
+
addressTypes?: AddressType[];
|
|
60
|
+
}
|
|
61
|
+
interface ConnectOptions {
|
|
62
|
+
metadata?: ConnectMetadataInput;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Main embedded provider class
|
|
66
|
+
* Manages iframe lifecycle, connection state, and chain-specific interfaces
|
|
67
|
+
*/
|
|
68
|
+
declare class EmbeddedProvider {
|
|
69
|
+
private iframeManager;
|
|
70
|
+
private _thruChain?;
|
|
71
|
+
private connected;
|
|
72
|
+
private addresses;
|
|
73
|
+
private eventListeners;
|
|
74
|
+
constructor(config: EmbeddedProviderConfig);
|
|
75
|
+
/**
|
|
76
|
+
* Initialize the provider (must be called before use)
|
|
77
|
+
* Creates iframe and waits for it to be ready
|
|
78
|
+
*/
|
|
79
|
+
initialize(): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Connect to wallet
|
|
82
|
+
* Shows iframe modal and requests connection
|
|
83
|
+
*/
|
|
84
|
+
connect(options?: ConnectOptions): Promise<ConnectResult>;
|
|
85
|
+
/**
|
|
86
|
+
* Disconnect from wallet
|
|
87
|
+
*/
|
|
88
|
+
disconnect(): Promise<void>;
|
|
89
|
+
/**
|
|
90
|
+
* Check if connected
|
|
91
|
+
*/
|
|
92
|
+
isConnected(): boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Get addresses
|
|
95
|
+
*/
|
|
96
|
+
getAddresses(): WalletAddress[];
|
|
97
|
+
/**
|
|
98
|
+
* Get Solana chain API
|
|
99
|
+
*/
|
|
100
|
+
get thru(): IThruChain;
|
|
101
|
+
/**
|
|
102
|
+
* Event emitter: on
|
|
103
|
+
*/
|
|
104
|
+
on(event: string, callback: Function): void;
|
|
105
|
+
/**
|
|
106
|
+
* Event emitter: off
|
|
107
|
+
*/
|
|
108
|
+
off(event: string, callback: Function): void;
|
|
109
|
+
/**
|
|
110
|
+
* Emit event to all listeners
|
|
111
|
+
*/
|
|
112
|
+
private emit;
|
|
113
|
+
/**
|
|
114
|
+
* Get iframe manager (for chain implementations)
|
|
115
|
+
* @internal
|
|
116
|
+
*/
|
|
117
|
+
getIframeManager(): IframeManager;
|
|
118
|
+
/**
|
|
119
|
+
* Destroy provider and cleanup
|
|
120
|
+
*/
|
|
121
|
+
destroy(): void;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* EmbeddedThruChain - postMessage-backed Thru chain adapter.
|
|
126
|
+
*/
|
|
127
|
+
declare class EmbeddedThruChain implements IThruChain {
|
|
128
|
+
private readonly iframeManager;
|
|
129
|
+
private readonly provider;
|
|
130
|
+
constructor(iframeManager: IframeManager, provider: EmbeddedProvider);
|
|
131
|
+
get publicKey(): string | null;
|
|
132
|
+
get connected(): boolean;
|
|
133
|
+
connect(): Promise<{
|
|
134
|
+
publicKey: string;
|
|
135
|
+
}>;
|
|
136
|
+
disconnect(): Promise<void>;
|
|
137
|
+
signTransaction(serializedTransaction: string): Promise<string>;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export { type ConnectOptions, EmbeddedProvider, type EmbeddedProviderConfig, EmbeddedThruChain, IframeManager };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { POST_MESSAGE_EVENT_TYPE, POST_MESSAGE_REQUEST_TYPES, createRequestId, DEFAULT_IFRAME_URL, EMBEDDED_PROVIDER_EVENTS, IFRAME_READY_EVENT } from '@thru/protocol';
|
|
2
|
+
export { ErrorCode } from '@thru/protocol';
|
|
3
|
+
|
|
4
|
+
// src/types/messages.ts
|
|
5
|
+
|
|
6
|
+
// src/IframeManager.ts
|
|
7
|
+
var IframeManager = class {
|
|
8
|
+
constructor(iframeUrl) {
|
|
9
|
+
this.iframe = null;
|
|
10
|
+
this.messageHandlers = /* @__PURE__ */ new Map();
|
|
11
|
+
this.messageListener = null;
|
|
12
|
+
this.readyPromise = null;
|
|
13
|
+
this.iframeUrl = iframeUrl;
|
|
14
|
+
this.iframeOrigin = new URL(iframeUrl).origin;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Create and inject iframe into DOM
|
|
18
|
+
* Returns a promise that resolves when iframe is ready
|
|
19
|
+
*/
|
|
20
|
+
async createIframe() {
|
|
21
|
+
if (this.readyPromise) {
|
|
22
|
+
return this.readyPromise;
|
|
23
|
+
}
|
|
24
|
+
this.readyPromise = (async () => {
|
|
25
|
+
if (!this.iframe) {
|
|
26
|
+
this.iframe = document.createElement("iframe");
|
|
27
|
+
this.iframe.src = this.iframeUrl;
|
|
28
|
+
this.iframe.style.cssText = `
|
|
29
|
+
position: fixed;
|
|
30
|
+
top: 0;
|
|
31
|
+
left: 0;
|
|
32
|
+
width: 100%;
|
|
33
|
+
height: 100%;
|
|
34
|
+
border: none;
|
|
35
|
+
z-index: 999999;
|
|
36
|
+
display: none;
|
|
37
|
+
background: rgba(0, 0, 0, 0.5);
|
|
38
|
+
`;
|
|
39
|
+
document.body.appendChild(this.iframe);
|
|
40
|
+
this.messageListener = this.handleMessage.bind(this);
|
|
41
|
+
window.addEventListener("message", this.messageListener);
|
|
42
|
+
}
|
|
43
|
+
await this.waitForReady();
|
|
44
|
+
})();
|
|
45
|
+
return this.readyPromise;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Wait for iframe to send 'ready' signal
|
|
49
|
+
*/
|
|
50
|
+
waitForReady() {
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
const timeout = setTimeout(() => {
|
|
53
|
+
reject(new Error("Iframe ready timeout - wallet failed to load"));
|
|
54
|
+
}, 1e4);
|
|
55
|
+
const readyHandler = (event) => {
|
|
56
|
+
if (event.origin !== this.iframeOrigin) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (event.data.type === IFRAME_READY_EVENT) {
|
|
60
|
+
clearTimeout(timeout);
|
|
61
|
+
window.removeEventListener("message", readyHandler);
|
|
62
|
+
resolve();
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
window.addEventListener("message", readyHandler);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Show iframe modal
|
|
70
|
+
*/
|
|
71
|
+
show() {
|
|
72
|
+
if (this.iframe) {
|
|
73
|
+
this.iframe.style.display = "block";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Hide iframe modal
|
|
78
|
+
*/
|
|
79
|
+
hide() {
|
|
80
|
+
if (this.iframe) {
|
|
81
|
+
this.iframe.style.display = "none";
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Send message to iframe and wait for response
|
|
86
|
+
*/
|
|
87
|
+
async sendMessage(request) {
|
|
88
|
+
if (!this.iframe?.contentWindow) {
|
|
89
|
+
throw new Error("Iframe not initialized - call createIframe() first");
|
|
90
|
+
}
|
|
91
|
+
return new Promise((resolve, reject) => {
|
|
92
|
+
const timeout = setTimeout(() => {
|
|
93
|
+
this.messageHandlers.delete(request.id);
|
|
94
|
+
reject(new Error("Request timeout - wallet did not respond"));
|
|
95
|
+
}, 3e4);
|
|
96
|
+
this.messageHandlers.set(request.id, (response) => {
|
|
97
|
+
clearTimeout(timeout);
|
|
98
|
+
this.messageHandlers.delete(request.id);
|
|
99
|
+
if (response.success) {
|
|
100
|
+
resolve(response);
|
|
101
|
+
} else {
|
|
102
|
+
const error = new Error(response.error?.message || "Unknown error");
|
|
103
|
+
error.code = response.error?.code;
|
|
104
|
+
reject(error);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
this.iframe.contentWindow.postMessage(request, this.iframeOrigin);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Handle incoming messages from iframe
|
|
112
|
+
*/
|
|
113
|
+
handleMessage(event) {
|
|
114
|
+
const iframeOrigin = new URL(this.iframeUrl).origin;
|
|
115
|
+
if (event.origin !== iframeOrigin) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const data = event.data;
|
|
119
|
+
if (data.id && this.messageHandlers.has(data.id)) {
|
|
120
|
+
const handler = this.messageHandlers.get(data.id);
|
|
121
|
+
if (handler) {
|
|
122
|
+
handler(data);
|
|
123
|
+
}
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (data.type === POST_MESSAGE_EVENT_TYPE) {
|
|
127
|
+
this.handleEvent(data);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Handle event broadcasts from iframe
|
|
132
|
+
*/
|
|
133
|
+
handleEvent(data) {
|
|
134
|
+
if (this.onEvent) {
|
|
135
|
+
this.onEvent(data.event, data.data);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Destroy iframe and cleanup
|
|
140
|
+
*/
|
|
141
|
+
destroy() {
|
|
142
|
+
if (this.iframe) {
|
|
143
|
+
this.iframe.remove();
|
|
144
|
+
this.iframe = null;
|
|
145
|
+
}
|
|
146
|
+
this.readyPromise = null;
|
|
147
|
+
if (this.messageListener) {
|
|
148
|
+
window.removeEventListener("message", this.messageListener);
|
|
149
|
+
this.messageListener = null;
|
|
150
|
+
}
|
|
151
|
+
this.messageHandlers.clear();
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// ../chain-interfaces/dist/index.js
|
|
156
|
+
var AddressType = {
|
|
157
|
+
THRU: "thru"
|
|
158
|
+
};
|
|
159
|
+
var EmbeddedThruChain = class {
|
|
160
|
+
constructor(iframeManager, provider) {
|
|
161
|
+
this.iframeManager = iframeManager;
|
|
162
|
+
this.provider = provider;
|
|
163
|
+
}
|
|
164
|
+
get publicKey() {
|
|
165
|
+
const addresses = this.provider.getAddresses();
|
|
166
|
+
const thruAddress = addresses.find((addr) => addr.addressType === AddressType.THRU);
|
|
167
|
+
return thruAddress?.address ?? null;
|
|
168
|
+
}
|
|
169
|
+
get connected() {
|
|
170
|
+
return this.provider.isConnected();
|
|
171
|
+
}
|
|
172
|
+
async connect() {
|
|
173
|
+
const result = await this.provider.connect();
|
|
174
|
+
const thruAddress = result.addresses.find((addr) => addr.addressType === AddressType.THRU);
|
|
175
|
+
if (!thruAddress) {
|
|
176
|
+
throw new Error("Thru address not found in connection result");
|
|
177
|
+
}
|
|
178
|
+
return { publicKey: thruAddress.address };
|
|
179
|
+
}
|
|
180
|
+
async disconnect() {
|
|
181
|
+
await this.provider.disconnect();
|
|
182
|
+
}
|
|
183
|
+
async signTransaction(serializedTransaction) {
|
|
184
|
+
if (!this.provider.isConnected()) {
|
|
185
|
+
throw new Error("Wallet not connected");
|
|
186
|
+
}
|
|
187
|
+
if (typeof serializedTransaction !== "string" || serializedTransaction.length === 0) {
|
|
188
|
+
throw new Error("Transaction payload must be a base64 encoded string");
|
|
189
|
+
}
|
|
190
|
+
this.iframeManager.show();
|
|
191
|
+
try {
|
|
192
|
+
const response = await this.iframeManager.sendMessage({
|
|
193
|
+
id: createRequestId(),
|
|
194
|
+
type: POST_MESSAGE_REQUEST_TYPES.SIGN_TRANSACTION,
|
|
195
|
+
payload: { transaction: serializedTransaction },
|
|
196
|
+
origin: window.location.origin
|
|
197
|
+
});
|
|
198
|
+
return response.result.signedTransaction;
|
|
199
|
+
} finally {
|
|
200
|
+
this.iframeManager.hide();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
var EmbeddedProvider = class {
|
|
205
|
+
constructor(config) {
|
|
206
|
+
this.connected = false;
|
|
207
|
+
this.addresses = [];
|
|
208
|
+
this.eventListeners = /* @__PURE__ */ new Map();
|
|
209
|
+
const iframeUrl = config.iframeUrl || DEFAULT_IFRAME_URL;
|
|
210
|
+
this.iframeManager = new IframeManager(iframeUrl);
|
|
211
|
+
this.iframeManager.onEvent = (eventType, payload) => {
|
|
212
|
+
this.emit(eventType, payload);
|
|
213
|
+
if (eventType === EMBEDDED_PROVIDER_EVENTS.DISCONNECT || eventType === EMBEDDED_PROVIDER_EVENTS.LOCK) {
|
|
214
|
+
this.connected = false;
|
|
215
|
+
this.addresses = [];
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
const addressTypes = config.addressTypes || [AddressType.THRU];
|
|
219
|
+
if (addressTypes.includes(AddressType.THRU)) {
|
|
220
|
+
this._thruChain = new EmbeddedThruChain(this.iframeManager, this);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Initialize the provider (must be called before use)
|
|
225
|
+
* Creates iframe and waits for it to be ready
|
|
226
|
+
*/
|
|
227
|
+
async initialize() {
|
|
228
|
+
await this.iframeManager.createIframe();
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Connect to wallet
|
|
232
|
+
* Shows iframe modal and requests connection
|
|
233
|
+
*/
|
|
234
|
+
async connect(options) {
|
|
235
|
+
this.emit(EMBEDDED_PROVIDER_EVENTS.CONNECT_START, {});
|
|
236
|
+
this.iframeManager.show();
|
|
237
|
+
try {
|
|
238
|
+
const payload = {};
|
|
239
|
+
if (options?.metadata) {
|
|
240
|
+
payload.metadata = options.metadata;
|
|
241
|
+
}
|
|
242
|
+
const response = await this.iframeManager.sendMessage({
|
|
243
|
+
id: createRequestId(),
|
|
244
|
+
type: POST_MESSAGE_REQUEST_TYPES.CONNECT,
|
|
245
|
+
payload,
|
|
246
|
+
origin: window.location.origin
|
|
247
|
+
});
|
|
248
|
+
this.connected = true;
|
|
249
|
+
this.addresses = response.result.addresses;
|
|
250
|
+
this.emit(EMBEDDED_PROVIDER_EVENTS.CONNECT, response.result);
|
|
251
|
+
this.iframeManager.hide();
|
|
252
|
+
return response.result;
|
|
253
|
+
} catch (error) {
|
|
254
|
+
this.iframeManager.hide();
|
|
255
|
+
this.emit(EMBEDDED_PROVIDER_EVENTS.CONNECT_ERROR, { error });
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Disconnect from wallet
|
|
261
|
+
*/
|
|
262
|
+
async disconnect() {
|
|
263
|
+
try {
|
|
264
|
+
await this.iframeManager.sendMessage({
|
|
265
|
+
id: createRequestId(),
|
|
266
|
+
type: POST_MESSAGE_REQUEST_TYPES.DISCONNECT,
|
|
267
|
+
origin: window.location.origin
|
|
268
|
+
});
|
|
269
|
+
this.connected = false;
|
|
270
|
+
this.addresses = [];
|
|
271
|
+
this.iframeManager.hide();
|
|
272
|
+
this.emit(EMBEDDED_PROVIDER_EVENTS.DISCONNECT, {});
|
|
273
|
+
} catch (error) {
|
|
274
|
+
this.emit(EMBEDDED_PROVIDER_EVENTS.ERROR, { error });
|
|
275
|
+
throw error;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Check if connected
|
|
280
|
+
*/
|
|
281
|
+
isConnected() {
|
|
282
|
+
return this.connected;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Get addresses
|
|
286
|
+
*/
|
|
287
|
+
getAddresses() {
|
|
288
|
+
return this.addresses;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Get Solana chain API
|
|
292
|
+
*/
|
|
293
|
+
get thru() {
|
|
294
|
+
if (!this._thruChain) {
|
|
295
|
+
throw new Error("Thru chain not enabled in provider config");
|
|
296
|
+
}
|
|
297
|
+
return this._thruChain;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Event emitter: on
|
|
301
|
+
*/
|
|
302
|
+
on(event, callback) {
|
|
303
|
+
if (!this.eventListeners.has(event)) {
|
|
304
|
+
this.eventListeners.set(event, /* @__PURE__ */ new Set());
|
|
305
|
+
}
|
|
306
|
+
this.eventListeners.get(event).add(callback);
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Event emitter: off
|
|
310
|
+
*/
|
|
311
|
+
off(event, callback) {
|
|
312
|
+
this.eventListeners.get(event)?.delete(callback);
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Emit event to all listeners
|
|
316
|
+
*/
|
|
317
|
+
emit(event, data) {
|
|
318
|
+
this.eventListeners.get(event)?.forEach((callback) => {
|
|
319
|
+
try {
|
|
320
|
+
callback(data);
|
|
321
|
+
} catch (error) {
|
|
322
|
+
console.error(`Error in event listener for ${event}:`, error);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Get iframe manager (for chain implementations)
|
|
328
|
+
* @internal
|
|
329
|
+
*/
|
|
330
|
+
getIframeManager() {
|
|
331
|
+
return this.iframeManager;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Destroy provider and cleanup
|
|
335
|
+
*/
|
|
336
|
+
destroy() {
|
|
337
|
+
this.iframeManager.destroy();
|
|
338
|
+
this.eventListeners.clear();
|
|
339
|
+
this.connected = false;
|
|
340
|
+
this.addresses = [];
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
export { EmbeddedProvider, EmbeddedThruChain, IframeManager };
|
|
345
|
+
//# sourceMappingURL=index.js.map
|
|
346
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/IframeManager.ts","../../chain-interfaces/src/types.ts","../src/chains/ThruChain.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":["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 * 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 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","export const AddressType = {\n THRU: 'thru',\n} as const;\n\nexport type AddressType = typeof AddressType[keyof typeof AddressType];\n\nexport interface WalletAddress {\n addressType: AddressType;\n address: 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 addresses: WalletAddress[];\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 publicKey(): string | null {\n const addresses = this.provider.getAddresses();\n const thruAddress = addresses.find((addr) => addr.addressType === AddressType.THRU);\n return thruAddress?.address ?? null;\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 thruAddress = result.addresses.find((addr) => addr.addressType === AddressType.THRU);\n\n if (!thruAddress) {\n throw new Error('Thru address not found in connection result');\n }\n\n return { publicKey: thruAddress.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 { IframeManager } from './IframeManager';\nimport { EmbeddedThruChain } from './chains/ThruChain';\nimport {\n DEFAULT_IFRAME_URL,\n POST_MESSAGE_REQUEST_TYPES,\n EMBEDDED_PROVIDER_EVENTS,\n createRequestId,\n type ConnectMetadataInput,\n type ConnectRequestPayload,\n} from '@thru/protocol';\nimport { AddressType } from '@thru/chain-interfaces';\nimport type {\n IThruChain,\n WalletAddress,\n ConnectResult,\n AddressType as AddressTypeValue,\n} from '@thru/chain-interfaces';\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 addresses: WalletAddress[] = [];\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 // Handle specific events\n if (\n eventType === EMBEDDED_PROVIDER_EVENTS.DISCONNECT ||\n eventType === EMBEDDED_PROVIDER_EVENTS.LOCK\n ) {\n this.connected = false;\n this.addresses = [];\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.addresses = response.result.addresses;\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.addresses = [];\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 addresses\n */\n getAddresses(): WalletAddress[] {\n return this.addresses;\n }\n\n /**\n * Get Solana 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.addresses = [];\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@thru/embedded-provider",
|
|
3
|
+
"version": "0.0.4",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@thru/chain-interfaces": "0.0.4",
|
|
15
|
+
"@thru/protocol": "0.0.4"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^24.7.0"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup",
|
|
22
|
+
"dev": "tsup --watch",
|
|
23
|
+
"lint": "eslint src/",
|
|
24
|
+
"clean": "rm -rf dist"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { IframeManager } from './IframeManager';
|
|
2
|
+
import { EmbeddedThruChain } from './chains/ThruChain';
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_IFRAME_URL,
|
|
5
|
+
POST_MESSAGE_REQUEST_TYPES,
|
|
6
|
+
EMBEDDED_PROVIDER_EVENTS,
|
|
7
|
+
createRequestId,
|
|
8
|
+
type ConnectMetadataInput,
|
|
9
|
+
type ConnectRequestPayload,
|
|
10
|
+
} from '@thru/protocol';
|
|
11
|
+
import { AddressType } from '@thru/chain-interfaces';
|
|
12
|
+
import type {
|
|
13
|
+
IThruChain,
|
|
14
|
+
WalletAddress,
|
|
15
|
+
ConnectResult,
|
|
16
|
+
AddressType as AddressTypeValue,
|
|
17
|
+
} from '@thru/chain-interfaces';
|
|
18
|
+
|
|
19
|
+
export interface EmbeddedProviderConfig {
|
|
20
|
+
iframeUrl?: string;
|
|
21
|
+
addressTypes?: AddressTypeValue[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ConnectOptions {
|
|
25
|
+
metadata?: ConnectMetadataInput;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Main embedded provider class
|
|
30
|
+
* Manages iframe lifecycle, connection state, and chain-specific interfaces
|
|
31
|
+
*/
|
|
32
|
+
export class EmbeddedProvider {
|
|
33
|
+
private iframeManager: IframeManager;
|
|
34
|
+
private _thruChain?: IThruChain;
|
|
35
|
+
private connected = false;
|
|
36
|
+
private addresses: WalletAddress[] = [];
|
|
37
|
+
private eventListeners = new Map<string, Set<Function>>();
|
|
38
|
+
constructor(config: EmbeddedProviderConfig) {
|
|
39
|
+
const iframeUrl = config.iframeUrl || DEFAULT_IFRAME_URL;
|
|
40
|
+
this.iframeManager = new IframeManager(iframeUrl);
|
|
41
|
+
|
|
42
|
+
// Set up event forwarding from iframe
|
|
43
|
+
this.iframeManager.onEvent = (eventType: string, payload: any) => {
|
|
44
|
+
this.emit(eventType, payload);
|
|
45
|
+
|
|
46
|
+
// Handle specific events
|
|
47
|
+
if (
|
|
48
|
+
eventType === EMBEDDED_PROVIDER_EVENTS.DISCONNECT ||
|
|
49
|
+
eventType === EMBEDDED_PROVIDER_EVENTS.LOCK
|
|
50
|
+
) {
|
|
51
|
+
this.connected = false;
|
|
52
|
+
this.addresses = [];
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Create chain instances
|
|
57
|
+
const addressTypes = config.addressTypes || [AddressType.THRU];
|
|
58
|
+
if (addressTypes.includes(AddressType.THRU)) {
|
|
59
|
+
this._thruChain = new EmbeddedThruChain(this.iframeManager, this);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Initialize the provider (must be called before use)
|
|
65
|
+
* Creates iframe and waits for it to be ready
|
|
66
|
+
*/
|
|
67
|
+
async initialize(): Promise<void> {
|
|
68
|
+
await this.iframeManager.createIframe();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Connect to wallet
|
|
73
|
+
* Shows iframe modal and requests connection
|
|
74
|
+
*/
|
|
75
|
+
async connect(options?: ConnectOptions): Promise<ConnectResult> {
|
|
76
|
+
// Emit connecting event
|
|
77
|
+
this.emit(EMBEDDED_PROVIDER_EVENTS.CONNECT_START, {});
|
|
78
|
+
|
|
79
|
+
// Show iframe modal
|
|
80
|
+
this.iframeManager.show();
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const payload: ConnectRequestPayload = {};
|
|
84
|
+
|
|
85
|
+
if (options?.metadata) {
|
|
86
|
+
payload.metadata = options.metadata;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const response = await this.iframeManager.sendMessage({
|
|
90
|
+
id: createRequestId(),
|
|
91
|
+
type: POST_MESSAGE_REQUEST_TYPES.CONNECT,
|
|
92
|
+
payload,
|
|
93
|
+
origin: window.location.origin,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
this.connected = true;
|
|
97
|
+
this.addresses = response.result.addresses;
|
|
98
|
+
|
|
99
|
+
// Emit success event
|
|
100
|
+
this.emit(EMBEDDED_PROVIDER_EVENTS.CONNECT, response.result);
|
|
101
|
+
|
|
102
|
+
// Hide iframe after successful connection
|
|
103
|
+
this.iframeManager.hide();
|
|
104
|
+
|
|
105
|
+
return response.result;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
this.iframeManager.hide();
|
|
108
|
+
this.emit(EMBEDDED_PROVIDER_EVENTS.CONNECT_ERROR, { error });
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Disconnect from wallet
|
|
115
|
+
*/
|
|
116
|
+
async disconnect(): Promise<void> {
|
|
117
|
+
try {
|
|
118
|
+
await this.iframeManager.sendMessage({
|
|
119
|
+
id: createRequestId(),
|
|
120
|
+
type: POST_MESSAGE_REQUEST_TYPES.DISCONNECT,
|
|
121
|
+
origin: window.location.origin,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
this.connected = false;
|
|
125
|
+
this.addresses = [];
|
|
126
|
+
this.iframeManager.hide();
|
|
127
|
+
|
|
128
|
+
this.emit(EMBEDDED_PROVIDER_EVENTS.DISCONNECT, {});
|
|
129
|
+
} catch (error) {
|
|
130
|
+
this.emit(EMBEDDED_PROVIDER_EVENTS.ERROR, { error });
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Check if connected
|
|
137
|
+
*/
|
|
138
|
+
isConnected(): boolean {
|
|
139
|
+
return this.connected;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get addresses
|
|
144
|
+
*/
|
|
145
|
+
getAddresses(): WalletAddress[] {
|
|
146
|
+
return this.addresses;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Get Solana chain API
|
|
151
|
+
*/
|
|
152
|
+
get thru(): IThruChain {
|
|
153
|
+
if (!this._thruChain) {
|
|
154
|
+
throw new Error('Thru chain not enabled in provider config');
|
|
155
|
+
}
|
|
156
|
+
return this._thruChain;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Event emitter: on
|
|
161
|
+
*/
|
|
162
|
+
on(event: string, callback: Function): void {
|
|
163
|
+
if (!this.eventListeners.has(event)) {
|
|
164
|
+
this.eventListeners.set(event, new Set());
|
|
165
|
+
}
|
|
166
|
+
this.eventListeners.get(event)!.add(callback);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Event emitter: off
|
|
171
|
+
*/
|
|
172
|
+
off(event: string, callback: Function): void {
|
|
173
|
+
this.eventListeners.get(event)?.delete(callback);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Emit event to all listeners
|
|
178
|
+
*/
|
|
179
|
+
private emit(event: string, data?: any): void {
|
|
180
|
+
this.eventListeners.get(event)?.forEach(callback => {
|
|
181
|
+
try {
|
|
182
|
+
callback(data);
|
|
183
|
+
} catch (error) {
|
|
184
|
+
console.error(`Error in event listener for ${event}:`, error);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Get iframe manager (for chain implementations)
|
|
191
|
+
* @internal
|
|
192
|
+
*/
|
|
193
|
+
getIframeManager(): IframeManager {
|
|
194
|
+
return this.iframeManager;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Destroy provider and cleanup
|
|
199
|
+
*/
|
|
200
|
+
destroy(): void {
|
|
201
|
+
this.iframeManager.destroy();
|
|
202
|
+
this.eventListeners.clear();
|
|
203
|
+
this.connected = false;
|
|
204
|
+
this.addresses = [];
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
InferSuccessfulPostMessageResponse,
|
|
3
|
+
PostMessageEvent,
|
|
4
|
+
PostMessageRequest,
|
|
5
|
+
PostMessageResponse,
|
|
6
|
+
} from './types/messages';
|
|
7
|
+
import { IFRAME_READY_EVENT, POST_MESSAGE_EVENT_TYPE } from './types/messages';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Manages iframe lifecycle and postMessage communication
|
|
11
|
+
* Handles creating, showing/hiding iframe, and message passing
|
|
12
|
+
*/
|
|
13
|
+
export class IframeManager {
|
|
14
|
+
private iframe: HTMLIFrameElement | null = null;
|
|
15
|
+
private iframeUrl: string;
|
|
16
|
+
private iframeOrigin: string;
|
|
17
|
+
private messageHandlers = new Map<string, (response: PostMessageResponse) => void>();
|
|
18
|
+
private messageListener: ((event: MessageEvent) => void) | null = null;
|
|
19
|
+
private readyPromise: Promise<void> | null = null;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Callback for event broadcasts from iframe (no request id)
|
|
23
|
+
*/
|
|
24
|
+
public onEvent?: (eventType: string, payload: any) => void;
|
|
25
|
+
|
|
26
|
+
constructor(iframeUrl: string) {
|
|
27
|
+
this.iframeUrl = iframeUrl;
|
|
28
|
+
this.iframeOrigin = new URL(iframeUrl).origin;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Create and inject iframe into DOM
|
|
33
|
+
* Returns a promise that resolves when iframe is ready
|
|
34
|
+
*/
|
|
35
|
+
async createIframe(): Promise<void> {
|
|
36
|
+
if (this.readyPromise) {
|
|
37
|
+
return this.readyPromise;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this.readyPromise = (async () => {
|
|
41
|
+
if (!this.iframe) {
|
|
42
|
+
this.iframe = document.createElement('iframe');
|
|
43
|
+
this.iframe.src = this.iframeUrl;
|
|
44
|
+
this.iframe.style.cssText = `
|
|
45
|
+
position: fixed;
|
|
46
|
+
top: 0;
|
|
47
|
+
left: 0;
|
|
48
|
+
width: 100%;
|
|
49
|
+
height: 100%;
|
|
50
|
+
border: none;
|
|
51
|
+
z-index: 999999;
|
|
52
|
+
display: none;
|
|
53
|
+
background: rgba(0, 0, 0, 0.5);
|
|
54
|
+
`;
|
|
55
|
+
|
|
56
|
+
document.body.appendChild(this.iframe);
|
|
57
|
+
|
|
58
|
+
// Set up message listener
|
|
59
|
+
this.messageListener = this.handleMessage.bind(this);
|
|
60
|
+
window.addEventListener('message', this.messageListener);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Wait for iframe ready signal
|
|
64
|
+
await this.waitForReady();
|
|
65
|
+
})();
|
|
66
|
+
|
|
67
|
+
return this.readyPromise;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Wait for iframe to send 'ready' signal
|
|
72
|
+
*/
|
|
73
|
+
private waitForReady(): Promise<void> {
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
const timeout = setTimeout(() => {
|
|
76
|
+
reject(new Error('Iframe ready timeout - wallet failed to load'));
|
|
77
|
+
}, 10000); // 10 second timeout
|
|
78
|
+
|
|
79
|
+
const readyHandler = (event: MessageEvent) => {
|
|
80
|
+
if (event.origin !== this.iframeOrigin) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (event.data.type === IFRAME_READY_EVENT) {
|
|
85
|
+
clearTimeout(timeout);
|
|
86
|
+
window.removeEventListener('message', readyHandler);
|
|
87
|
+
resolve();
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
window.addEventListener('message', readyHandler);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Show iframe modal
|
|
97
|
+
*/
|
|
98
|
+
show(): void {
|
|
99
|
+
if (this.iframe) {
|
|
100
|
+
this.iframe.style.display = 'block';
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Hide iframe modal
|
|
106
|
+
*/
|
|
107
|
+
hide(): void {
|
|
108
|
+
if (this.iframe) {
|
|
109
|
+
this.iframe.style.display = 'none';
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Send message to iframe and wait for response
|
|
115
|
+
*/
|
|
116
|
+
async sendMessage<TRequest extends PostMessageRequest>(
|
|
117
|
+
request: TRequest
|
|
118
|
+
): Promise<InferSuccessfulPostMessageResponse<TRequest>> {
|
|
119
|
+
if (!this.iframe?.contentWindow) {
|
|
120
|
+
throw new Error('Iframe not initialized - call createIframe() first');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return new Promise<InferSuccessfulPostMessageResponse<TRequest>>((resolve, reject) => {
|
|
124
|
+
const timeout = setTimeout(() => {
|
|
125
|
+
this.messageHandlers.delete(request.id);
|
|
126
|
+
reject(new Error('Request timeout - wallet did not respond'));
|
|
127
|
+
}, 30000); // 30 second timeout
|
|
128
|
+
|
|
129
|
+
// Store handler for this request
|
|
130
|
+
this.messageHandlers.set(request.id, (response: PostMessageResponse) => {
|
|
131
|
+
clearTimeout(timeout);
|
|
132
|
+
this.messageHandlers.delete(request.id);
|
|
133
|
+
|
|
134
|
+
if (response.success) {
|
|
135
|
+
resolve(response as InferSuccessfulPostMessageResponse<TRequest>);
|
|
136
|
+
} else {
|
|
137
|
+
const error = new Error(response.error?.message || 'Unknown error');
|
|
138
|
+
(error as any).code = response.error?.code;
|
|
139
|
+
reject(error);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Send message to iframe
|
|
144
|
+
this.iframe!.contentWindow!.postMessage(request, this.iframeOrigin);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Handle incoming messages from iframe
|
|
150
|
+
*/
|
|
151
|
+
private handleMessage(event: MessageEvent): void {
|
|
152
|
+
// Validate origin
|
|
153
|
+
const iframeOrigin = new URL(this.iframeUrl).origin;
|
|
154
|
+
if (event.origin !== iframeOrigin) {
|
|
155
|
+
return; // Ignore messages from other origins
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const data = event.data;
|
|
159
|
+
|
|
160
|
+
// Handle response to a specific request (has id)
|
|
161
|
+
if (data.id && this.messageHandlers.has(data.id)) {
|
|
162
|
+
const handler = this.messageHandlers.get(data.id);
|
|
163
|
+
if (handler) {
|
|
164
|
+
handler(data as PostMessageResponse);
|
|
165
|
+
}
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Handle event broadcasts (type === 'event')
|
|
170
|
+
if (data.type === POST_MESSAGE_EVENT_TYPE) {
|
|
171
|
+
this.handleEvent(data as PostMessageEvent);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Handle event broadcasts from iframe
|
|
177
|
+
*/
|
|
178
|
+
private handleEvent(data: PostMessageEvent): void {
|
|
179
|
+
// Forward to EmbeddedProvider via callback
|
|
180
|
+
if (this.onEvent) {
|
|
181
|
+
this.onEvent(data.event, data.data);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Destroy iframe and cleanup
|
|
187
|
+
*/
|
|
188
|
+
destroy(): void {
|
|
189
|
+
if (this.iframe) {
|
|
190
|
+
this.iframe.remove();
|
|
191
|
+
this.iframe = null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
this.readyPromise = null;
|
|
195
|
+
|
|
196
|
+
if (this.messageListener) {
|
|
197
|
+
window.removeEventListener('message', this.messageListener);
|
|
198
|
+
this.messageListener = null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
this.messageHandlers.clear();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { AddressType, type IThruChain } from '@thru/chain-interfaces';
|
|
2
|
+
import { POST_MESSAGE_REQUEST_TYPES, createRequestId } from '@thru/protocol';
|
|
3
|
+
import type { EmbeddedProvider } from '../EmbeddedProvider';
|
|
4
|
+
import type { IframeManager } from '../IframeManager';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* EmbeddedThruChain - postMessage-backed Thru chain adapter.
|
|
8
|
+
*/
|
|
9
|
+
export class EmbeddedThruChain implements IThruChain {
|
|
10
|
+
private readonly iframeManager: IframeManager;
|
|
11
|
+
private readonly provider: EmbeddedProvider;
|
|
12
|
+
|
|
13
|
+
constructor(iframeManager: IframeManager, provider: EmbeddedProvider) {
|
|
14
|
+
this.iframeManager = iframeManager;
|
|
15
|
+
this.provider = provider;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get publicKey(): string | null {
|
|
19
|
+
const addresses = this.provider.getAddresses();
|
|
20
|
+
const thruAddress = addresses.find((addr) => addr.addressType === AddressType.THRU);
|
|
21
|
+
return thruAddress?.address ?? null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get connected(): boolean {
|
|
25
|
+
return this.provider.isConnected();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async connect(): Promise<{ publicKey: string }> {
|
|
29
|
+
const result = await this.provider.connect();
|
|
30
|
+
const thruAddress = result.addresses.find((addr) => addr.addressType === AddressType.THRU);
|
|
31
|
+
|
|
32
|
+
if (!thruAddress) {
|
|
33
|
+
throw new Error('Thru address not found in connection result');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return { publicKey: thruAddress.address };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async disconnect(): Promise<void> {
|
|
40
|
+
await this.provider.disconnect();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async signTransaction(serializedTransaction: string): Promise<string> {
|
|
44
|
+
if (!this.provider.isConnected()) {
|
|
45
|
+
throw new Error('Wallet not connected');
|
|
46
|
+
}
|
|
47
|
+
if (typeof serializedTransaction !== 'string' || serializedTransaction.length === 0) {
|
|
48
|
+
throw new Error('Transaction payload must be a base64 encoded string');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.iframeManager.show();
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const response = await this.iframeManager.sendMessage({
|
|
55
|
+
id: createRequestId(),
|
|
56
|
+
type: POST_MESSAGE_REQUEST_TYPES.SIGN_TRANSACTION,
|
|
57
|
+
payload: { transaction: serializedTransaction },
|
|
58
|
+
origin: window.location.origin,
|
|
59
|
+
});
|
|
60
|
+
return response.result.signedTransaction;
|
|
61
|
+
} finally {
|
|
62
|
+
this.iframeManager.hide();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Main exports
|
|
2
|
+
export { EmbeddedProvider, type EmbeddedProviderConfig, type ConnectOptions } from './EmbeddedProvider';
|
|
3
|
+
export { IframeManager } from './IframeManager';
|
|
4
|
+
export { EmbeddedThruChain } from './chains/ThruChain';
|
|
5
|
+
|
|
6
|
+
// Type exports
|
|
7
|
+
export type {
|
|
8
|
+
PostMessageRequest,
|
|
9
|
+
PostMessageResponse,
|
|
10
|
+
PostMessageEvent,
|
|
11
|
+
RequestType,
|
|
12
|
+
EmbeddedProviderEvent,
|
|
13
|
+
ConnectResult,
|
|
14
|
+
SignMessagePayload,
|
|
15
|
+
SignMessageResult,
|
|
16
|
+
SignTransactionPayload,
|
|
17
|
+
SignTransactionResult,
|
|
18
|
+
} from './types/messages';
|
|
19
|
+
|
|
20
|
+
export { ErrorCode } from './types/messages';
|
|
21
|
+
|
|
22
|
+
// Re-export types from chain-interfaces for convenience
|
|
23
|
+
export type { IThruChain, WalletAddress } from '@thru/chain-interfaces';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export {
|
|
2
|
+
POST_MESSAGE_REQUEST_TYPES,
|
|
3
|
+
EMBEDDED_PROVIDER_EVENTS,
|
|
4
|
+
POST_MESSAGE_EVENT_TYPE,
|
|
5
|
+
IFRAME_READY_EVENT,
|
|
6
|
+
DEFAULT_IFRAME_URL,
|
|
7
|
+
createRequestId,
|
|
8
|
+
ErrorCode,
|
|
9
|
+
type RequestType,
|
|
10
|
+
type EmbeddedProviderEvent,
|
|
11
|
+
type PostMessageRequest,
|
|
12
|
+
type ConnectRequestMessage,
|
|
13
|
+
type DisconnectRequestMessage,
|
|
14
|
+
type SignMessageRequestMessage,
|
|
15
|
+
type SignTransactionRequestMessage,
|
|
16
|
+
type GetAccountsRequestMessage,
|
|
17
|
+
type DisconnectResult,
|
|
18
|
+
type GetAccountsResult,
|
|
19
|
+
type PostMessageResponse,
|
|
20
|
+
type SuccessfulPostMessageResponse,
|
|
21
|
+
type InferPostMessageResponse,
|
|
22
|
+
type InferSuccessfulPostMessageResponse,
|
|
23
|
+
type PostMessageEvent,
|
|
24
|
+
type ConnectRequestPayload,
|
|
25
|
+
type ConnectResult,
|
|
26
|
+
type SignMessagePayload,
|
|
27
|
+
type SignMessageResult,
|
|
28
|
+
type SignTransactionPayload,
|
|
29
|
+
type SignTransactionResult,
|
|
30
|
+
} from '@thru/protocol';
|
package/tsconfig.json
ADDED
package/tsup.config.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineConfig } from 'tsup';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
entry: ['src/index.ts'],
|
|
5
|
+
format: ['esm'],
|
|
6
|
+
dts: true,
|
|
7
|
+
sourcemap: true,
|
|
8
|
+
clean: true,
|
|
9
|
+
treeshake: true,
|
|
10
|
+
platform: 'browser',
|
|
11
|
+
noExternal: ['@thru/chain-interfaces'],
|
|
12
|
+
external: [],
|
|
13
|
+
});
|