@thru/browser-sdk 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,85 @@
1
+ import { AddressType, ConnectResult, WalletAddress, IThruChain } from '@thru/chain-interfaces';
2
+ export { ConnectResult, IThruChain, SignMessageParams, SignMessageResult, WalletAddress } from '@thru/chain-interfaces';
3
+ import { ConnectMetadataInput } from '@thru/protocol';
4
+ import { Thru } from '@thru/thru-sdk';
5
+ export { ErrorCode } from '@thru/embedded-provider';
6
+
7
+ interface BrowserSDKConfig {
8
+ iframeUrl?: string;
9
+ addressTypes?: AddressType[];
10
+ rpcUrl?: string;
11
+ }
12
+ interface ConnectOptions {
13
+ metadata?: ConnectMetadataInput;
14
+ }
15
+ type SDKEvent = 'connect' | 'disconnect' | 'lock' | 'error';
16
+ type EventCallback = (...args: any[]) => void;
17
+ /**
18
+ * Browser SDK - Main entry point for dApp developers
19
+ * Wraps EmbeddedProvider with a clean, simple API
20
+ */
21
+ declare class BrowserSDK {
22
+ private provider;
23
+ private eventListeners;
24
+ private initialized;
25
+ private thruClient;
26
+ private connectInFlight;
27
+ private lastConnectResult;
28
+ constructor(config?: BrowserSDKConfig);
29
+ /**
30
+ * Initialize the SDK (creates iframe)
31
+ * Must be called before using the SDK
32
+ */
33
+ initialize(): Promise<void>;
34
+ /**
35
+ * Connect to wallet
36
+ * Shows wallet modal and requests connection
37
+ */
38
+ connect(options?: ConnectOptions): Promise<ConnectResult>;
39
+ /**
40
+ * Disconnect from wallet
41
+ */
42
+ disconnect(): Promise<void>;
43
+ /**
44
+ * Check if connected
45
+ */
46
+ isConnected(): boolean;
47
+ /**
48
+ * Get all addresses
49
+ */
50
+ getAddresses(): WalletAddress[];
51
+ /**
52
+ * Get Thru chain API (iframe-backed signer)
53
+ */
54
+ get thru(): IThruChain;
55
+ /**
56
+ * Event emitter: on
57
+ */
58
+ on(event: SDKEvent, callback: EventCallback): void;
59
+ /**
60
+ * Event emitter: off
61
+ */
62
+ off(event: SDKEvent, callback: EventCallback): void;
63
+ /**
64
+ * Event emitter: once (listen once and auto-remove)
65
+ */
66
+ once(event: SDKEvent, callback: EventCallback): void;
67
+ /**
68
+ * Emit event to all listeners
69
+ */
70
+ private emit;
71
+ /**
72
+ * Set up event forwarding from provider to SDK
73
+ */
74
+ private setupEventForwarding;
75
+ /**
76
+ * Destroy SDK and cleanup
77
+ */
78
+ destroy(): void;
79
+ private resolveMetadata;
80
+ private resolveAppUrl;
81
+ private deriveAppName;
82
+ getThru(): Thru;
83
+ }
84
+
85
+ export { BrowserSDK, type BrowserSDKConfig, type ConnectOptions, type EventCallback, type SDKEvent };
package/dist/index.js ADDED
@@ -0,0 +1,209 @@
1
+ import { EmbeddedProvider } from '@thru/embedded-provider';
2
+ export { ErrorCode } from '@thru/embedded-provider';
3
+ import { EMBEDDED_PROVIDER_EVENTS } from '@thru/protocol';
4
+ import { createThruClient } from '@thru/thru-sdk';
5
+
6
+ // ../chain-interfaces/dist/index.js
7
+ var AddressType = {
8
+ THRU: "thru"
9
+ };
10
+ var BrowserSDK = class {
11
+ constructor(config = {}) {
12
+ this.eventListeners = /* @__PURE__ */ new Map();
13
+ this.initialized = false;
14
+ this.connectInFlight = null;
15
+ this.lastConnectResult = null;
16
+ this.provider = new EmbeddedProvider({
17
+ iframeUrl: config.iframeUrl,
18
+ addressTypes: config.addressTypes || [AddressType.THRU]
19
+ });
20
+ this.thruClient = createThruClient({
21
+ baseUrl: config.rpcUrl
22
+ });
23
+ this.setupEventForwarding();
24
+ }
25
+ /**
26
+ * Initialize the SDK (creates iframe)
27
+ * Must be called before using the SDK
28
+ */
29
+ async initialize() {
30
+ if (this.initialized) {
31
+ return;
32
+ }
33
+ await this.provider.initialize();
34
+ this.initialized = true;
35
+ }
36
+ /**
37
+ * Connect to wallet
38
+ * Shows wallet modal and requests connection
39
+ */
40
+ async connect(options) {
41
+ if (!this.initialized) {
42
+ await this.initialize();
43
+ }
44
+ if (this.connectInFlight) {
45
+ return this.connectInFlight;
46
+ }
47
+ if (this.lastConnectResult && this.provider.isConnected()) {
48
+ return this.lastConnectResult;
49
+ }
50
+ this.emit("connect", { status: "connecting" });
51
+ const inFlight = (async () => {
52
+ try {
53
+ const metadata = this.resolveMetadata(options?.metadata);
54
+ const providerOptions = metadata ? { metadata } : void 0;
55
+ const result = await this.provider.connect(providerOptions);
56
+ this.lastConnectResult = result;
57
+ this.emit("connect", result);
58
+ return result;
59
+ } catch (error) {
60
+ this.emit("error", error);
61
+ throw error;
62
+ } finally {
63
+ this.connectInFlight = null;
64
+ }
65
+ })();
66
+ this.connectInFlight = inFlight;
67
+ return inFlight;
68
+ }
69
+ /**
70
+ * Disconnect from wallet
71
+ */
72
+ async disconnect() {
73
+ try {
74
+ await this.provider.disconnect();
75
+ this.emit("disconnect", {});
76
+ this.lastConnectResult = null;
77
+ } catch (error) {
78
+ this.emit("error", error);
79
+ throw error;
80
+ }
81
+ }
82
+ /**
83
+ * Check if connected
84
+ */
85
+ isConnected() {
86
+ return this.provider.isConnected();
87
+ }
88
+ /**
89
+ * Get all addresses
90
+ */
91
+ getAddresses() {
92
+ return this.provider.getAddresses();
93
+ }
94
+ /**
95
+ * Get Thru chain API (iframe-backed signer)
96
+ */
97
+ get thru() {
98
+ return this.provider.thru;
99
+ }
100
+ /**
101
+ * Event emitter: on
102
+ */
103
+ on(event, callback) {
104
+ if (!this.eventListeners.has(event)) {
105
+ this.eventListeners.set(event, /* @__PURE__ */ new Set());
106
+ }
107
+ this.eventListeners.get(event).add(callback);
108
+ }
109
+ /**
110
+ * Event emitter: off
111
+ */
112
+ off(event, callback) {
113
+ this.eventListeners.get(event)?.delete(callback);
114
+ }
115
+ /**
116
+ * Event emitter: once (listen once and auto-remove)
117
+ */
118
+ once(event, callback) {
119
+ const wrappedCallback = (...args) => {
120
+ callback(...args);
121
+ this.off(event, wrappedCallback);
122
+ };
123
+ this.on(event, wrappedCallback);
124
+ }
125
+ /**
126
+ * Emit event to all listeners
127
+ */
128
+ emit(event, data) {
129
+ this.eventListeners.get(event)?.forEach((callback) => {
130
+ try {
131
+ callback(data);
132
+ } catch (error) {
133
+ console.error(`Error in SDK event listener for ${event}:`, error);
134
+ }
135
+ });
136
+ }
137
+ /**
138
+ * Set up event forwarding from provider to SDK
139
+ */
140
+ setupEventForwarding() {
141
+ this.provider.on(EMBEDDED_PROVIDER_EVENTS.CONNECT, (data) => {
142
+ });
143
+ this.provider.on(EMBEDDED_PROVIDER_EVENTS.DISCONNECT, (data) => {
144
+ this.emit("disconnect", data);
145
+ });
146
+ this.provider.on(EMBEDDED_PROVIDER_EVENTS.ERROR, (data) => {
147
+ this.emit("error", data);
148
+ });
149
+ this.provider.on(EMBEDDED_PROVIDER_EVENTS.LOCK, (data) => {
150
+ this.emit("lock", data);
151
+ this.emit("disconnect", { reason: "locked" });
152
+ });
153
+ }
154
+ /**
155
+ * Destroy SDK and cleanup
156
+ */
157
+ destroy() {
158
+ this.provider.destroy();
159
+ this.eventListeners.clear();
160
+ this.initialized = false;
161
+ this.connectInFlight = null;
162
+ this.lastConnectResult = null;
163
+ }
164
+ resolveMetadata(input) {
165
+ const defaultOrigin = typeof window !== "undefined" ? window.location.origin : void 0;
166
+ if (!defaultOrigin && !input) {
167
+ return void 0;
168
+ }
169
+ const appId = input?.appId || defaultOrigin;
170
+ const appUrl = this.resolveAppUrl(defaultOrigin, input?.appUrl);
171
+ const appName = input?.appName || this.deriveAppName(appUrl ?? appId);
172
+ const metadata = {};
173
+ if (appId) metadata.appId = appId;
174
+ if (appUrl) metadata.appUrl = appUrl;
175
+ if (appName) metadata.appName = appName;
176
+ if (input?.imageUrl) metadata.imageUrl = input.imageUrl;
177
+ return metadata;
178
+ }
179
+ resolveAppUrl(defaultOrigin, providedUrl) {
180
+ const candidate = providedUrl || defaultOrigin;
181
+ if (!candidate) {
182
+ return void 0;
183
+ }
184
+ try {
185
+ const url = new URL(candidate, defaultOrigin);
186
+ return url.toString();
187
+ } catch {
188
+ return defaultOrigin;
189
+ }
190
+ }
191
+ deriveAppName(source) {
192
+ if (!source) {
193
+ return void 0;
194
+ }
195
+ try {
196
+ const hostname = new URL(source).hostname;
197
+ return hostname || source;
198
+ } catch {
199
+ return source;
200
+ }
201
+ }
202
+ getThru() {
203
+ return this.thruClient;
204
+ }
205
+ };
206
+
207
+ export { BrowserSDK };
208
+ //# sourceMappingURL=index.js.map
209
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../chain-interfaces/src/types.ts","../src/BrowserSDK.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export const AddressType = {\n THRU: 'thru',\n} as const;\n\nexport type AddressType = typeof AddressType[keyof typeof AddressType];\n\nexport interface 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 type {\n AddressType as AddressTypeValue,\n ConnectResult,\n IThruChain,\n WalletAddress,\n} from '@thru/chain-interfaces';\nimport { AddressType } from '@thru/chain-interfaces';\nimport { EmbeddedProvider } from '@thru/embedded-provider';\nimport { EMBEDDED_PROVIDER_EVENTS, type ConnectMetadataInput } from '@thru/protocol';\nimport { createThruClient, Thru } from '@thru/thru-sdk';\n\nexport interface BrowserSDKConfig {\n iframeUrl?: string;\n addressTypes?: AddressTypeValue[];\n rpcUrl?: string;\n}\n\nexport interface ConnectOptions {\n metadata?: ConnectMetadataInput;\n}\n\nexport type SDKEvent = 'connect' | 'disconnect' | 'lock' | 'error';\n\nexport type EventCallback = (...args: any[]) => void;\n\n/**\n * Browser SDK - Main entry point for dApp developers\n * Wraps EmbeddedProvider with a clean, simple API\n */\nexport class BrowserSDK {\n private provider: EmbeddedProvider;\n private eventListeners = new Map<SDKEvent, Set<EventCallback>>();\n private initialized = false;\n private thruClient: Thru;\n private connectInFlight: Promise<ConnectResult> | null = null;\n private lastConnectResult: ConnectResult | null = null;\n\n constructor(config: BrowserSDKConfig = {}) {\n this.provider = new EmbeddedProvider({\n iframeUrl: config.iframeUrl,\n addressTypes: config.addressTypes || [AddressType.THRU],\n });\n\n this.thruClient = createThruClient({\n baseUrl: config.rpcUrl,\n });\n\n // Forward provider events to SDK events\n this.setupEventForwarding();\n }\n\n /**\n * Initialize the SDK (creates iframe)\n * Must be called before using the SDK\n */\n async initialize(): Promise<void> {\n if (this.initialized) {\n return;\n }\n\n await this.provider.initialize();\n this.initialized = true;\n }\n\n /**\n * Connect to wallet\n * Shows wallet modal and requests connection\n */\n async connect(options?: ConnectOptions): Promise<ConnectResult> {\n // Auto-initialize if not done yet\n if (!this.initialized) {\n await this.initialize();\n }\n\n if (this.connectInFlight) {\n return this.connectInFlight;\n }\n\n if (this.lastConnectResult && this.provider.isConnected()) {\n return this.lastConnectResult;\n }\n\n this.emit('connect', { status: 'connecting' });\n\n const inFlight = (async () => {\n try {\n const metadata = this.resolveMetadata(options?.metadata);\n const providerOptions = metadata ? { metadata } : undefined;\n const result = await this.provider.connect(providerOptions);\n this.lastConnectResult = result;\n this.emit('connect', result);\n return result;\n } catch (error) {\n this.emit('error', error);\n throw error;\n } finally {\n this.connectInFlight = null;\n }\n })();\n\n this.connectInFlight = inFlight;\n return inFlight;\n }\n\n /**\n * Disconnect from wallet\n */\n async disconnect(): Promise<void> {\n try {\n await this.provider.disconnect();\n this.emit('disconnect', {});\n this.lastConnectResult = null;\n } catch (error) {\n this.emit('error', error);\n throw error;\n }\n }\n\n /**\n * Check if connected\n */\n isConnected(): boolean {\n return this.provider.isConnected();\n }\n\n /**\n * Get all addresses\n */\n getAddresses(): WalletAddress[] {\n return this.provider.getAddresses();\n }\n\n /**\n * Get Thru chain API (iframe-backed signer)\n */\n get thru(): IThruChain {\n return this.provider.thru;\n }\n\n /**\n * Event emitter: on\n */\n on(event: SDKEvent, callback: EventCallback): 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: SDKEvent, callback: EventCallback): void {\n this.eventListeners.get(event)?.delete(callback);\n }\n\n /**\n * Event emitter: once (listen once and auto-remove)\n */\n once(event: SDKEvent, callback: EventCallback): void {\n const wrappedCallback = (...args: any[]) => {\n callback(...args);\n this.off(event, wrappedCallback);\n };\n this.on(event, wrappedCallback);\n }\n\n /**\n * Emit event to all listeners\n */\n private emit(event: SDKEvent, data?: any): void {\n this.eventListeners.get(event)?.forEach(callback => {\n try {\n callback(data);\n } catch (error) {\n console.error(`Error in SDK event listener for ${event}:`, error);\n }\n });\n }\n\n /**\n * Set up event forwarding from provider to SDK\n */\n private setupEventForwarding(): void {\n // Forward all relevant provider events to SDK events\n this.provider.on(EMBEDDED_PROVIDER_EVENTS.CONNECT, (data: any) => {\n // Already handled in connect() method\n });\n\n this.provider.on(EMBEDDED_PROVIDER_EVENTS.DISCONNECT, (data: any) => {\n this.emit('disconnect', data);\n });\n\n this.provider.on(EMBEDDED_PROVIDER_EVENTS.ERROR, (data: any) => {\n this.emit('error', data);\n });\n\n this.provider.on(EMBEDDED_PROVIDER_EVENTS.LOCK, (data: any) => {\n this.emit('lock', data);\n this.emit('disconnect', { reason: 'locked' });\n });\n }\n\n /**\n * Destroy SDK and cleanup\n */\n destroy(): void {\n this.provider.destroy();\n this.eventListeners.clear();\n this.initialized = false;\n this.connectInFlight = null;\n this.lastConnectResult = null;\n }\n\n private resolveMetadata(input?: ConnectMetadataInput): ConnectMetadataInput | undefined {\n const defaultOrigin = typeof window !== 'undefined' ? window.location.origin : undefined;\n if (!defaultOrigin && !input) {\n return undefined;\n }\n\n const appId = input?.appId || defaultOrigin;\n const appUrl = this.resolveAppUrl(defaultOrigin, input?.appUrl);\n const appName = input?.appName || this.deriveAppName(appUrl ?? appId);\n\n const metadata: ConnectMetadataInput = {};\n if (appId) metadata.appId = appId;\n if (appUrl) metadata.appUrl = appUrl;\n if (appName) metadata.appName = appName;\n if (input?.imageUrl) metadata.imageUrl = input.imageUrl;\n\n return metadata;\n }\n\n private resolveAppUrl(defaultOrigin?: string, providedUrl?: string): string | undefined {\n const candidate = providedUrl || defaultOrigin;\n if (!candidate) {\n return undefined;\n }\n\n try {\n const url = new URL(candidate, defaultOrigin);\n return url.toString();\n } catch {\n return defaultOrigin;\n }\n }\n\n private deriveAppName(source?: string): string | undefined {\n if (!source) {\n return undefined;\n }\n\n try {\n const hostname = new URL(source).hostname;\n return hostname || source;\n } catch {\n return source;\n }\n }\n\n public getThru(): Thru {\n return this.thruClient;\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@thru/browser-sdk",
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/embedded-provider": "0.0.4",
15
+ "@thru/chain-interfaces": "0.0.4",
16
+ "@thru/protocol": "0.0.4",
17
+ "@thru/thru-sdk": "0.0.4"
18
+ },
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "dev": "tsup --watch",
22
+ "lint": "eslint src/",
23
+ "clean": "rm -rf dist"
24
+ }
25
+ }
@@ -0,0 +1,264 @@
1
+ import type {
2
+ AddressType as AddressTypeValue,
3
+ ConnectResult,
4
+ IThruChain,
5
+ WalletAddress,
6
+ } from '@thru/chain-interfaces';
7
+ import { AddressType } from '@thru/chain-interfaces';
8
+ import { EmbeddedProvider } from '@thru/embedded-provider';
9
+ import { EMBEDDED_PROVIDER_EVENTS, type ConnectMetadataInput } from '@thru/protocol';
10
+ import { createThruClient, Thru } from '@thru/thru-sdk';
11
+
12
+ export interface BrowserSDKConfig {
13
+ iframeUrl?: string;
14
+ addressTypes?: AddressTypeValue[];
15
+ rpcUrl?: string;
16
+ }
17
+
18
+ export interface ConnectOptions {
19
+ metadata?: ConnectMetadataInput;
20
+ }
21
+
22
+ export type SDKEvent = 'connect' | 'disconnect' | 'lock' | 'error';
23
+
24
+ export type EventCallback = (...args: any[]) => void;
25
+
26
+ /**
27
+ * Browser SDK - Main entry point for dApp developers
28
+ * Wraps EmbeddedProvider with a clean, simple API
29
+ */
30
+ export class BrowserSDK {
31
+ private provider: EmbeddedProvider;
32
+ private eventListeners = new Map<SDKEvent, Set<EventCallback>>();
33
+ private initialized = false;
34
+ private thruClient: Thru;
35
+ private connectInFlight: Promise<ConnectResult> | null = null;
36
+ private lastConnectResult: ConnectResult | null = null;
37
+
38
+ constructor(config: BrowserSDKConfig = {}) {
39
+ this.provider = new EmbeddedProvider({
40
+ iframeUrl: config.iframeUrl,
41
+ addressTypes: config.addressTypes || [AddressType.THRU],
42
+ });
43
+
44
+ this.thruClient = createThruClient({
45
+ baseUrl: config.rpcUrl,
46
+ });
47
+
48
+ // Forward provider events to SDK events
49
+ this.setupEventForwarding();
50
+ }
51
+
52
+ /**
53
+ * Initialize the SDK (creates iframe)
54
+ * Must be called before using the SDK
55
+ */
56
+ async initialize(): Promise<void> {
57
+ if (this.initialized) {
58
+ return;
59
+ }
60
+
61
+ await this.provider.initialize();
62
+ this.initialized = true;
63
+ }
64
+
65
+ /**
66
+ * Connect to wallet
67
+ * Shows wallet modal and requests connection
68
+ */
69
+ async connect(options?: ConnectOptions): Promise<ConnectResult> {
70
+ // Auto-initialize if not done yet
71
+ if (!this.initialized) {
72
+ await this.initialize();
73
+ }
74
+
75
+ if (this.connectInFlight) {
76
+ return this.connectInFlight;
77
+ }
78
+
79
+ if (this.lastConnectResult && this.provider.isConnected()) {
80
+ return this.lastConnectResult;
81
+ }
82
+
83
+ this.emit('connect', { status: 'connecting' });
84
+
85
+ const inFlight = (async () => {
86
+ try {
87
+ const metadata = this.resolveMetadata(options?.metadata);
88
+ const providerOptions = metadata ? { metadata } : undefined;
89
+ const result = await this.provider.connect(providerOptions);
90
+ this.lastConnectResult = result;
91
+ this.emit('connect', result);
92
+ return result;
93
+ } catch (error) {
94
+ this.emit('error', error);
95
+ throw error;
96
+ } finally {
97
+ this.connectInFlight = null;
98
+ }
99
+ })();
100
+
101
+ this.connectInFlight = inFlight;
102
+ return inFlight;
103
+ }
104
+
105
+ /**
106
+ * Disconnect from wallet
107
+ */
108
+ async disconnect(): Promise<void> {
109
+ try {
110
+ await this.provider.disconnect();
111
+ this.emit('disconnect', {});
112
+ this.lastConnectResult = null;
113
+ } catch (error) {
114
+ this.emit('error', error);
115
+ throw error;
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Check if connected
121
+ */
122
+ isConnected(): boolean {
123
+ return this.provider.isConnected();
124
+ }
125
+
126
+ /**
127
+ * Get all addresses
128
+ */
129
+ getAddresses(): WalletAddress[] {
130
+ return this.provider.getAddresses();
131
+ }
132
+
133
+ /**
134
+ * Get Thru chain API (iframe-backed signer)
135
+ */
136
+ get thru(): IThruChain {
137
+ return this.provider.thru;
138
+ }
139
+
140
+ /**
141
+ * Event emitter: on
142
+ */
143
+ on(event: SDKEvent, callback: EventCallback): void {
144
+ if (!this.eventListeners.has(event)) {
145
+ this.eventListeners.set(event, new Set());
146
+ }
147
+ this.eventListeners.get(event)!.add(callback);
148
+ }
149
+
150
+ /**
151
+ * Event emitter: off
152
+ */
153
+ off(event: SDKEvent, callback: EventCallback): void {
154
+ this.eventListeners.get(event)?.delete(callback);
155
+ }
156
+
157
+ /**
158
+ * Event emitter: once (listen once and auto-remove)
159
+ */
160
+ once(event: SDKEvent, callback: EventCallback): void {
161
+ const wrappedCallback = (...args: any[]) => {
162
+ callback(...args);
163
+ this.off(event, wrappedCallback);
164
+ };
165
+ this.on(event, wrappedCallback);
166
+ }
167
+
168
+ /**
169
+ * Emit event to all listeners
170
+ */
171
+ private emit(event: SDKEvent, data?: any): void {
172
+ this.eventListeners.get(event)?.forEach(callback => {
173
+ try {
174
+ callback(data);
175
+ } catch (error) {
176
+ console.error(`Error in SDK event listener for ${event}:`, error);
177
+ }
178
+ });
179
+ }
180
+
181
+ /**
182
+ * Set up event forwarding from provider to SDK
183
+ */
184
+ private setupEventForwarding(): void {
185
+ // Forward all relevant provider events to SDK events
186
+ this.provider.on(EMBEDDED_PROVIDER_EVENTS.CONNECT, (data: any) => {
187
+ // Already handled in connect() method
188
+ });
189
+
190
+ this.provider.on(EMBEDDED_PROVIDER_EVENTS.DISCONNECT, (data: any) => {
191
+ this.emit('disconnect', data);
192
+ });
193
+
194
+ this.provider.on(EMBEDDED_PROVIDER_EVENTS.ERROR, (data: any) => {
195
+ this.emit('error', data);
196
+ });
197
+
198
+ this.provider.on(EMBEDDED_PROVIDER_EVENTS.LOCK, (data: any) => {
199
+ this.emit('lock', data);
200
+ this.emit('disconnect', { reason: 'locked' });
201
+ });
202
+ }
203
+
204
+ /**
205
+ * Destroy SDK and cleanup
206
+ */
207
+ destroy(): void {
208
+ this.provider.destroy();
209
+ this.eventListeners.clear();
210
+ this.initialized = false;
211
+ this.connectInFlight = null;
212
+ this.lastConnectResult = null;
213
+ }
214
+
215
+ private resolveMetadata(input?: ConnectMetadataInput): ConnectMetadataInput | undefined {
216
+ const defaultOrigin = typeof window !== 'undefined' ? window.location.origin : undefined;
217
+ if (!defaultOrigin && !input) {
218
+ return undefined;
219
+ }
220
+
221
+ const appId = input?.appId || defaultOrigin;
222
+ const appUrl = this.resolveAppUrl(defaultOrigin, input?.appUrl);
223
+ const appName = input?.appName || this.deriveAppName(appUrl ?? appId);
224
+
225
+ const metadata: ConnectMetadataInput = {};
226
+ if (appId) metadata.appId = appId;
227
+ if (appUrl) metadata.appUrl = appUrl;
228
+ if (appName) metadata.appName = appName;
229
+ if (input?.imageUrl) metadata.imageUrl = input.imageUrl;
230
+
231
+ return metadata;
232
+ }
233
+
234
+ private resolveAppUrl(defaultOrigin?: string, providedUrl?: string): string | undefined {
235
+ const candidate = providedUrl || defaultOrigin;
236
+ if (!candidate) {
237
+ return undefined;
238
+ }
239
+
240
+ try {
241
+ const url = new URL(candidate, defaultOrigin);
242
+ return url.toString();
243
+ } catch {
244
+ return defaultOrigin;
245
+ }
246
+ }
247
+
248
+ private deriveAppName(source?: string): string | undefined {
249
+ if (!source) {
250
+ return undefined;
251
+ }
252
+
253
+ try {
254
+ const hostname = new URL(source).hostname;
255
+ return hostname || source;
256
+ } catch {
257
+ return source;
258
+ }
259
+ }
260
+
261
+ public getThru(): Thru {
262
+ return this.thruClient;
263
+ }
264
+ }
package/src/index.ts ADDED
@@ -0,0 +1,20 @@
1
+ // Main exports
2
+ export {
3
+ BrowserSDK,
4
+ type BrowserSDKConfig,
5
+ type SDKEvent,
6
+ type EventCallback,
7
+ type ConnectOptions,
8
+ } from './BrowserSDK';
9
+
10
+ // Re-export types from chain-interfaces for convenience
11
+ export type {
12
+ IThruChain,
13
+ WalletAddress,
14
+ ConnectResult,
15
+ SignMessageParams,
16
+ SignMessageResult,
17
+ } from '@thru/chain-interfaces';
18
+
19
+ // Re-export error codes from embedded-provider
20
+ export { ErrorCode } from '@thru/embedded-provider';
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,23 @@
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
+ '@thru/embedded-provider',
14
+ '@thru/protocol',
15
+ 'crypto',
16
+ 'buffer',
17
+ 'stream',
18
+ 'http',
19
+ 'https',
20
+ 'url',
21
+ 'zlib',
22
+ ],
23
+ });