@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.
@@ -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
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src"],
8
+ "exclude": ["node_modules", "dist"]
9
+ }
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
+ });