@thru/wallet 0.2.22

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.
Files changed (69) hide show
  1. package/README.md +67 -0
  2. package/android/build.gradle +37 -0
  3. package/android/src/main/AndroidManifest.xml +1 -0
  4. package/android/src/main/java/org/thru/walletnative/ThruWebViewBridgeModule.kt +77 -0
  5. package/app.plugin.cjs +101 -0
  6. package/dist/BrowserSDK-CpRFiJsW.d.ts +409 -0
  7. package/dist/index.d.ts +23 -0
  8. package/dist/index.js +941 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/native/react.d.ts +109 -0
  11. package/dist/native/react.js +2381 -0
  12. package/dist/native/react.js.map +1 -0
  13. package/dist/native.d.ts +329 -0
  14. package/dist/native.js +1126 -0
  15. package/dist/native.js.map +1 -0
  16. package/dist/react-ui.d.ts +5 -0
  17. package/dist/react-ui.js +266 -0
  18. package/dist/react-ui.js.map +1 -0
  19. package/dist/react.d.ts +66 -0
  20. package/dist/react.js +1151 -0
  21. package/dist/react.js.map +1 -0
  22. package/expo-module.config.json +6 -0
  23. package/package.json +114 -0
  24. package/src/BrowserSDK.ts +315 -0
  25. package/src/index.ts +27 -0
  26. package/src/interfaces/IThruChain.ts +37 -0
  27. package/src/interfaces/accounts.ts +61 -0
  28. package/src/interfaces/index.ts +9 -0
  29. package/src/interfaces/types.ts +95 -0
  30. package/src/native/NativeSDK.test.ts +819 -0
  31. package/src/native/NativeSDK.ts +773 -0
  32. package/src/native/index.ts +39 -0
  33. package/src/native/provider/NativeProvider.ts +363 -0
  34. package/src/native/provider/WebViewBridge.test.ts +339 -0
  35. package/src/native/provider/WebViewBridge.ts +339 -0
  36. package/src/native/provider/chains/ThruChain.ts +85 -0
  37. package/src/native/provider/shell.html +88 -0
  38. package/src/native/provider/shell.test.ts +56 -0
  39. package/src/native/provider/shell.ts +111 -0
  40. package/src/native/provider/shims-html.d.ts +4 -0
  41. package/src/native/react/ThruContext.ts +37 -0
  42. package/src/native/react/ThruProvider.tsx +168 -0
  43. package/src/native/react/ThruWalletSheet.tsx +1162 -0
  44. package/src/native/react/android-webauthn.ts +37 -0
  45. package/src/native/react/hooks/useAccounts.ts +35 -0
  46. package/src/native/react/hooks/useThru.ts +11 -0
  47. package/src/native/react/hooks/useWallet.ts +71 -0
  48. package/src/native/react/hooks/useWalletAvailability.ts +31 -0
  49. package/src/native/react/hooks/waitForWallet.ts +21 -0
  50. package/src/native/react/index.ts +29 -0
  51. package/src/protocol/index.ts +2 -0
  52. package/src/protocol/postMessage.ts +283 -0
  53. package/src/protocol/walletState.ts +12 -0
  54. package/src/provider/EmbeddedProvider.ts +330 -0
  55. package/src/provider/IframeManager.ts +438 -0
  56. package/src/provider/chains/ThruChain.ts +86 -0
  57. package/src/provider/index.ts +17 -0
  58. package/src/provider/types/messages.ts +37 -0
  59. package/src/react/ThruContext.ts +31 -0
  60. package/src/react/ThruProvider.tsx +169 -0
  61. package/src/react/hooks/useAccounts.ts +38 -0
  62. package/src/react/hooks/useThru.ts +11 -0
  63. package/src/react/hooks/useWallet.ts +81 -0
  64. package/src/react/index.ts +30 -0
  65. package/src/react-ui/ThruAccountSwitcher.tsx +187 -0
  66. package/src/react-ui/custom.d.ts +8 -0
  67. package/src/react-ui/index.ts +1 -0
  68. package/src/static/logo.png +0 -0
  69. package/src/static/logomark_red.svg +11 -0
@@ -0,0 +1,330 @@
1
+ import {
2
+ AddressType,
3
+ normalizeWalletAccountResult,
4
+ } from '../interfaces';
5
+ import type {
6
+ AddressType as AddressTypeValue,
7
+ ConnectResult,
8
+ IThruChain,
9
+ WalletAccount,
10
+ } from '../interfaces';
11
+ import {
12
+ DEFAULT_IFRAME_URL,
13
+ EMBEDDED_PROVIDER_EVENTS,
14
+ POST_MESSAGE_REQUEST_TYPES,
15
+ createRequestId,
16
+ type ConnectMetadataInput,
17
+ type ConnectRequestPayload,
18
+ type ManageAccountsResult,
19
+ type SelectAccountPayload,
20
+ } from '../protocol';
21
+ import { IframeManager } from './IframeManager';
22
+ import { EmbeddedThruChain } from './chains/ThruChain';
23
+
24
+ export interface EmbeddedProviderConfig {
25
+ iframeUrl?: string;
26
+ addressTypes?: AddressTypeValue[];
27
+ }
28
+
29
+ export interface ConnectOptions {
30
+ metadata?: ConnectMetadataInput;
31
+ }
32
+
33
+ /**
34
+ * Main embedded provider class
35
+ * Manages iframe lifecycle, connection state, and chain-specific interfaces
36
+ */
37
+ export class EmbeddedProvider {
38
+ private iframeManager: IframeManager;
39
+ private _thruChain?: IThruChain;
40
+ private connected = false;
41
+ private accounts: WalletAccount[] = [];
42
+ private selectedAccount: WalletAccount | null = null;
43
+ private eventListeners = new Map<string, Set<Function>>();
44
+ private inlineMode = false;
45
+ constructor(config: EmbeddedProviderConfig) {
46
+ const iframeUrl = config.iframeUrl || DEFAULT_IFRAME_URL;
47
+ this.iframeManager = new IframeManager(iframeUrl);
48
+
49
+ // Set up event forwarding from iframe
50
+ this.iframeManager.onEvent = (eventType: string, payload: any) => {
51
+ this.emit(eventType, payload);
52
+
53
+ if (eventType === EMBEDDED_PROVIDER_EVENTS.UI_SHOW) {
54
+ if (this.inlineMode) {
55
+ this.iframeManager.showInline();
56
+ } else {
57
+ this.iframeManager.showModal();
58
+ }
59
+ return;
60
+ }
61
+
62
+ if (
63
+ eventType === EMBEDDED_PROVIDER_EVENTS.DISCONNECT ||
64
+ eventType === EMBEDDED_PROVIDER_EVENTS.LOCK
65
+ ) {
66
+ this.clearConnection();
67
+ return;
68
+ }
69
+
70
+ if (eventType === EMBEDDED_PROVIDER_EVENTS.ACCOUNT_CHANGED) {
71
+ const account =
72
+ (payload && (payload.account as WalletAccount | undefined)) || null;
73
+ this.refreshAccountCache(account ?? null);
74
+ }
75
+ };
76
+
77
+ // Create chain instances
78
+ const addressTypes = config.addressTypes || [AddressType.THRU];
79
+ if (addressTypes.includes(AddressType.THRU)) {
80
+ this._thruChain = new EmbeddedThruChain(this.iframeManager, this);
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Initialize the provider (must be called before use)
86
+ * Creates iframe and waits for it to be ready
87
+ */
88
+ async initialize(): Promise<void> {
89
+ await this.iframeManager.createIframe();
90
+ }
91
+
92
+ /**
93
+ * Mount the wallet iframe inline in a container (for inline connect button).
94
+ */
95
+ async mountInline(container: HTMLElement): Promise<void> {
96
+ this.inlineMode = true;
97
+ await this.iframeManager.mountInline(container);
98
+ }
99
+
100
+ /**
101
+ * Connect to wallet
102
+ * Shows iframe modal and requests connection
103
+ */
104
+ async connect(options?: ConnectOptions): Promise<ConnectResult> {
105
+ // Emit connecting event
106
+ this.emit(EMBEDDED_PROVIDER_EVENTS.CONNECT_START, {});
107
+
108
+ try {
109
+ if (this.inlineMode) {
110
+ this.iframeManager.showInline();
111
+ } else {
112
+ this.iframeManager.showModal();
113
+ }
114
+
115
+ const payload: ConnectRequestPayload = {};
116
+
117
+ if (options?.metadata) {
118
+ payload.metadata = options.metadata;
119
+ }
120
+
121
+ const response = await this.iframeManager.sendMessage({
122
+ id: createRequestId(),
123
+ type: POST_MESSAGE_REQUEST_TYPES.CONNECT,
124
+ payload,
125
+ origin: window.location.origin,
126
+ });
127
+
128
+ const result = normalizeWalletAccountResult(response.result);
129
+ this.connected = true;
130
+ this.accounts = result.accounts;
131
+ this.selectedAccount = result.selectedAccount;
132
+
133
+ // Emit success event
134
+ this.emit(EMBEDDED_PROVIDER_EVENTS.CONNECT, result);
135
+
136
+ // Hide iframe after successful connection
137
+ if (!this.inlineMode) {
138
+ this.iframeManager.hide();
139
+ }
140
+
141
+ return result;
142
+ } catch (error) {
143
+ if (!this.inlineMode) {
144
+ this.iframeManager.hide();
145
+ }
146
+ this.emit(EMBEDDED_PROVIDER_EVENTS.CONNECT_ERROR, { error });
147
+ throw error;
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Disconnect from wallet
153
+ */
154
+ async disconnect(): Promise<void> {
155
+ try {
156
+ await this.iframeManager.sendMessage({
157
+ id: createRequestId(),
158
+ type: POST_MESSAGE_REQUEST_TYPES.DISCONNECT,
159
+ origin: window.location.origin,
160
+ });
161
+
162
+ this.clearConnection();
163
+ this.emit(EMBEDDED_PROVIDER_EVENTS.DISCONNECT, {});
164
+ } catch (error) {
165
+ this.clearConnection();
166
+ this.emit(EMBEDDED_PROVIDER_EVENTS.ERROR, { error });
167
+ throw error;
168
+ } finally {
169
+ if (!this.inlineMode) {
170
+ this.iframeManager.hide();
171
+ }
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Check if connected
177
+ */
178
+ isConnected(): boolean {
179
+ return this.connected;
180
+ }
181
+
182
+ /**
183
+ * Get accounts
184
+ */
185
+ getAccounts(): WalletAccount[] {
186
+ return this.accounts;
187
+ }
188
+
189
+ getSelectedAccount(): WalletAccount | null {
190
+ return this.selectedAccount;
191
+ }
192
+
193
+ async selectAccount(publicKey: string): Promise<WalletAccount> {
194
+ if (!this.connected) {
195
+ throw new Error("Wallet not connected");
196
+ }
197
+
198
+ const knownAccount =
199
+ this.accounts.find((acc) => acc.address === publicKey) ?? null;
200
+ if (!knownAccount) {
201
+ console.warn(
202
+ "[EmbeddedProvider] Selecting account not present in local cache",
203
+ );
204
+ }
205
+ const payload: SelectAccountPayload = { publicKey };
206
+
207
+ const response = await this.iframeManager.sendMessage({
208
+ id: createRequestId(),
209
+ type: POST_MESSAGE_REQUEST_TYPES.SELECT_ACCOUNT,
210
+ payload,
211
+ origin: window.location.origin,
212
+ });
213
+
214
+ const account = response.result.account;
215
+
216
+ this.refreshAccountCache(account);
217
+ return account;
218
+ }
219
+
220
+ async manageAccounts(): Promise<ManageAccountsResult> {
221
+ if (!this.connected) {
222
+ throw new Error("Wallet not connected");
223
+ }
224
+
225
+ if (this.inlineMode) {
226
+ this.iframeManager.showInline();
227
+ } else {
228
+ this.iframeManager.showModal();
229
+ }
230
+
231
+ try {
232
+ const response = await this.iframeManager.sendMessage({
233
+ id: createRequestId(),
234
+ type: POST_MESSAGE_REQUEST_TYPES.MANAGE_ACCOUNTS,
235
+ origin: window.location.origin,
236
+ });
237
+
238
+ const result = normalizeWalletAccountResult({
239
+ accounts: response.result.accounts,
240
+ selectedAccount: response.result.selectedAccount,
241
+ });
242
+ this.accounts = result.accounts;
243
+ this.selectedAccount = result.selectedAccount;
244
+ if (this.selectedAccount) {
245
+ this.emit(EMBEDDED_PROVIDER_EVENTS.ACCOUNT_CHANGED, {
246
+ account: this.selectedAccount,
247
+ });
248
+ }
249
+ return result;
250
+ } finally {
251
+ if (!this.inlineMode) {
252
+ this.iframeManager.hide();
253
+ }
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Get Thru chain API
259
+ */
260
+ get thru(): IThruChain {
261
+ if (!this._thruChain) {
262
+ throw new Error("Thru chain not enabled in provider config");
263
+ }
264
+ return this._thruChain;
265
+ }
266
+
267
+ /**
268
+ * Event emitter: on
269
+ */
270
+ on(event: string, callback: Function): void {
271
+ if (!this.eventListeners.has(event)) {
272
+ this.eventListeners.set(event, new Set());
273
+ }
274
+ this.eventListeners.get(event)!.add(callback);
275
+ }
276
+
277
+ /**
278
+ * Event emitter: off
279
+ */
280
+ off(event: string, callback: Function): void {
281
+ this.eventListeners.get(event)?.delete(callback);
282
+ }
283
+
284
+ /**
285
+ * Emit event to all listeners
286
+ */
287
+ private emit(event: string, data?: any): void {
288
+ this.eventListeners.get(event)?.forEach((callback) => {
289
+ try {
290
+ callback(data);
291
+ } catch (error) {
292
+ console.error(`Error in event listener for ${event}:`, error);
293
+ }
294
+ });
295
+ }
296
+
297
+ /**
298
+ * Get iframe manager (for chain implementations)
299
+ * @internal
300
+ */
301
+ getIframeManager(): IframeManager {
302
+ return this.iframeManager;
303
+ }
304
+
305
+ /**
306
+ * Destroy provider and cleanup
307
+ */
308
+ destroy(): void {
309
+ this.iframeManager.destroy();
310
+ this.eventListeners.clear();
311
+ this.clearConnection();
312
+ }
313
+
314
+ private refreshAccountCache(account: WalletAccount | null): void {
315
+ if (!account) {
316
+ this.accounts = [];
317
+ this.selectedAccount = null;
318
+ return;
319
+ }
320
+
321
+ this.accounts = [account];
322
+ this.selectedAccount = account;
323
+ }
324
+
325
+ private clearConnection(): void {
326
+ this.connected = false;
327
+ this.accounts = [];
328
+ this.selectedAccount = null;
329
+ }
330
+ }