@blazium/ton-connect-mobile 1.0.0
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/LICENSE +22 -0
- package/README.md +271 -0
- package/dist/adapters/expo.d.ts +25 -0
- package/dist/adapters/expo.js +145 -0
- package/dist/adapters/index.d.ts +6 -0
- package/dist/adapters/index.js +12 -0
- package/dist/adapters/react-native.d.ts +25 -0
- package/dist/adapters/react-native.js +133 -0
- package/dist/adapters/web.d.ts +27 -0
- package/dist/adapters/web.js +147 -0
- package/dist/core/crypto.d.ts +28 -0
- package/dist/core/crypto.js +183 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.js +21 -0
- package/dist/core/protocol.d.ts +50 -0
- package/dist/core/protocol.js +260 -0
- package/dist/index.d.ts +112 -0
- package/dist/index.js +502 -0
- package/dist/types/index.d.ts +192 -0
- package/dist/types/index.js +6 -0
- package/package.json +57 -0
- package/src/adapters/expo.ts +160 -0
- package/src/adapters/index.ts +8 -0
- package/src/adapters/react-native.ts +148 -0
- package/src/adapters/web.ts +176 -0
- package/src/core/crypto.ts +238 -0
- package/src/core/index.ts +7 -0
- package/src/core/protocol.ts +330 -0
- package/src/index.d.ts +19 -0
- package/src/index.ts +578 -0
- package/src/types/index.ts +206 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TON Connect Mobile SDK
|
|
3
|
+
* Production-ready implementation for React Native and Expo
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Type declarations for runtime globals (imported from index.d.ts via reference)
|
|
7
|
+
/// <reference path="./index.d.ts" />
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
TonConnectMobileConfig,
|
|
11
|
+
ConnectionStatus,
|
|
12
|
+
WalletInfo,
|
|
13
|
+
SendTransactionRequest,
|
|
14
|
+
StatusChangeCallback,
|
|
15
|
+
PlatformAdapter,
|
|
16
|
+
} from './types';
|
|
17
|
+
import {
|
|
18
|
+
buildConnectionRequest,
|
|
19
|
+
buildTransactionRequest,
|
|
20
|
+
parseCallbackURL,
|
|
21
|
+
extractWalletInfo,
|
|
22
|
+
validateConnectionResponse,
|
|
23
|
+
validateTransactionRequest,
|
|
24
|
+
validateTransactionResponse,
|
|
25
|
+
} from './core/protocol';
|
|
26
|
+
import type {
|
|
27
|
+
ConnectionResponsePayload,
|
|
28
|
+
TransactionResponsePayload,
|
|
29
|
+
ErrorResponse,
|
|
30
|
+
} from './types';
|
|
31
|
+
import { verifyConnectionProof, generateSessionId } from './core/crypto';
|
|
32
|
+
import { ExpoAdapter } from './adapters/expo';
|
|
33
|
+
import { ReactNativeAdapter } from './adapters/react-native';
|
|
34
|
+
import { WebAdapter } from './adapters/web';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Custom error classes
|
|
38
|
+
*/
|
|
39
|
+
export class TonConnectError extends Error {
|
|
40
|
+
constructor(message: string, public code?: string) {
|
|
41
|
+
super(message);
|
|
42
|
+
this.name = 'TonConnectError';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class ConnectionTimeoutError extends TonConnectError {
|
|
47
|
+
constructor() {
|
|
48
|
+
super('Connection request timed out', 'CONNECTION_TIMEOUT');
|
|
49
|
+
this.name = 'ConnectionTimeoutError';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class TransactionTimeoutError extends TonConnectError {
|
|
54
|
+
constructor() {
|
|
55
|
+
super('Transaction request timed out', 'TRANSACTION_TIMEOUT');
|
|
56
|
+
this.name = 'TransactionTimeoutError';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export class UserRejectedError extends TonConnectError {
|
|
61
|
+
constructor() {
|
|
62
|
+
super('User rejected the request', 'USER_REJECTED');
|
|
63
|
+
this.name = 'UserRejectedError';
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export class ConnectionInProgressError extends TonConnectError {
|
|
68
|
+
constructor() {
|
|
69
|
+
super('Connection request already in progress', 'CONNECTION_IN_PROGRESS');
|
|
70
|
+
this.name = 'ConnectionInProgressError';
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export class TransactionInProgressError extends TonConnectError {
|
|
75
|
+
constructor() {
|
|
76
|
+
super('Transaction request already in progress', 'TRANSACTION_IN_PROGRESS');
|
|
77
|
+
this.name = 'TransactionInProgressError';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Main TON Connect Mobile SDK class
|
|
83
|
+
*/
|
|
84
|
+
export class TonConnectMobile {
|
|
85
|
+
private adapter: PlatformAdapter;
|
|
86
|
+
private config: Required<TonConnectMobileConfig>;
|
|
87
|
+
private statusChangeCallbacks: Set<StatusChangeCallback> = new Set();
|
|
88
|
+
private currentStatus: ConnectionStatus = { connected: false, wallet: null };
|
|
89
|
+
private urlUnsubscribe: (() => void) | null = null;
|
|
90
|
+
private connectionPromise: {
|
|
91
|
+
resolve: (wallet: WalletInfo) => void;
|
|
92
|
+
reject: (error: Error) => void;
|
|
93
|
+
timeout: number | null;
|
|
94
|
+
} | null = null;
|
|
95
|
+
private transactionPromise: {
|
|
96
|
+
resolve: (response: { boc: string; signature: string }) => void;
|
|
97
|
+
reject: (error: Error) => void;
|
|
98
|
+
timeout: number | null;
|
|
99
|
+
} | null = null;
|
|
100
|
+
|
|
101
|
+
constructor(config: TonConnectMobileConfig) {
|
|
102
|
+
// Validate config
|
|
103
|
+
if (!config.manifestUrl) {
|
|
104
|
+
throw new TonConnectError('manifestUrl is required');
|
|
105
|
+
}
|
|
106
|
+
if (!config.scheme) {
|
|
107
|
+
throw new TonConnectError('scheme is required');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
this.config = {
|
|
111
|
+
storageKeyPrefix: 'tonconnect_',
|
|
112
|
+
connectionTimeout: 300000, // 5 minutes
|
|
113
|
+
transactionTimeout: 300000, // 5 minutes
|
|
114
|
+
...config,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// Initialize platform adapter
|
|
118
|
+
this.adapter = this.createAdapter();
|
|
119
|
+
|
|
120
|
+
// Set up URL listener
|
|
121
|
+
this.setupURLListener();
|
|
122
|
+
|
|
123
|
+
// Load persisted session
|
|
124
|
+
this.loadSession();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Create platform adapter based on available modules
|
|
129
|
+
*/
|
|
130
|
+
private createAdapter(): PlatformAdapter {
|
|
131
|
+
// Check if we're in a web environment
|
|
132
|
+
// eslint-disable-next-line no-undef
|
|
133
|
+
if (typeof globalThis !== 'undefined' && (globalThis as any).window && (globalThis as any).document) {
|
|
134
|
+
// Web platform
|
|
135
|
+
return new WebAdapter();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Try to detect Expo environment
|
|
139
|
+
try {
|
|
140
|
+
// Check if expo-linking is available
|
|
141
|
+
if (typeof require !== 'undefined') {
|
|
142
|
+
const expoLinking = require('expo-linking');
|
|
143
|
+
if (expoLinking) {
|
|
144
|
+
return new ExpoAdapter();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
} catch {
|
|
148
|
+
// expo-linking not available, continue to React Native adapter
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Fall back to React Native adapter
|
|
152
|
+
// This will work for both React Native CLI and Expo (since Expo also has react-native)
|
|
153
|
+
return new ReactNativeAdapter();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Set up URL listener for wallet callbacks
|
|
158
|
+
*/
|
|
159
|
+
private setupURLListener(): void {
|
|
160
|
+
this.urlUnsubscribe = this.adapter.addURLListener((url) => {
|
|
161
|
+
this.handleCallback(url);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Also check initial URL (when app was opened via deep link)
|
|
165
|
+
this.adapter.getInitialURL().then((url) => {
|
|
166
|
+
if (url) {
|
|
167
|
+
this.handleCallback(url);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Handle callback from wallet
|
|
174
|
+
*/
|
|
175
|
+
private handleCallback(url: string): void {
|
|
176
|
+
const parsed = parseCallbackURL(url, this.config.scheme);
|
|
177
|
+
|
|
178
|
+
if (parsed.type === 'connect' && parsed.data) {
|
|
179
|
+
this.handleConnectionResponse(parsed.data as ConnectionResponsePayload);
|
|
180
|
+
} else if (parsed.type === 'transaction' && parsed.data) {
|
|
181
|
+
this.handleTransactionResponse(parsed.data as TransactionResponsePayload);
|
|
182
|
+
} else if (parsed.type === 'error' && parsed.data) {
|
|
183
|
+
const errorData = parsed.data as ErrorResponse;
|
|
184
|
+
if (errorData?.error) {
|
|
185
|
+
if (errorData.error.code === 300 || errorData.error.message?.toLowerCase().includes('reject')) {
|
|
186
|
+
this.rejectWithError(new UserRejectedError());
|
|
187
|
+
} else {
|
|
188
|
+
this.rejectWithError(
|
|
189
|
+
new TonConnectError(errorData.error.message || 'Unknown error', String(errorData.error.code))
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Handle connection response from wallet
|
|
198
|
+
*/
|
|
199
|
+
private handleConnectionResponse(response: ConnectionResponsePayload): void {
|
|
200
|
+
if (!validateConnectionResponse(response)) {
|
|
201
|
+
this.rejectWithError(new TonConnectError('Invalid connection response'));
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Verify proof if present
|
|
206
|
+
if (response.proof) {
|
|
207
|
+
const isValid = verifyConnectionProof(response, this.config.manifestUrl);
|
|
208
|
+
if (!isValid) {
|
|
209
|
+
this.rejectWithError(new TonConnectError('Connection proof verification failed'));
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
// Log warning if proof is missing (security consideration)
|
|
214
|
+
console.warn('TON Connect: Connection proof missing - wallet may not support proof verification');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const wallet = extractWalletInfo(response);
|
|
218
|
+
|
|
219
|
+
// Validate session ID before saving
|
|
220
|
+
if (!this.validateSessionId(response.session)) {
|
|
221
|
+
this.rejectWithError(new TonConnectError('Invalid session ID format'));
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Save session
|
|
226
|
+
this.saveSession(response.session, wallet).catch((error) => {
|
|
227
|
+
console.error('TON Connect: Failed to save session:', error);
|
|
228
|
+
// Continue anyway - connection is still valid
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Update status
|
|
232
|
+
this.currentStatus = { connected: true, wallet };
|
|
233
|
+
this.notifyStatusChange();
|
|
234
|
+
|
|
235
|
+
// Resolve connection promise
|
|
236
|
+
if (this.connectionPromise) {
|
|
237
|
+
if (this.connectionPromise.timeout !== null) {
|
|
238
|
+
clearTimeout(this.connectionPromise.timeout);
|
|
239
|
+
}
|
|
240
|
+
this.connectionPromise.resolve(wallet);
|
|
241
|
+
this.connectionPromise = null;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Handle transaction response from wallet
|
|
247
|
+
*/
|
|
248
|
+
private handleTransactionResponse(response: TransactionResponsePayload): void {
|
|
249
|
+
if (!validateTransactionResponse(response)) {
|
|
250
|
+
this.rejectWithError(new TonConnectError('Invalid transaction response'));
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Resolve transaction promise
|
|
255
|
+
if (this.transactionPromise) {
|
|
256
|
+
if (this.transactionPromise.timeout !== null) {
|
|
257
|
+
clearTimeout(this.transactionPromise.timeout);
|
|
258
|
+
}
|
|
259
|
+
this.transactionPromise.resolve({
|
|
260
|
+
boc: response.boc,
|
|
261
|
+
signature: response.signature,
|
|
262
|
+
});
|
|
263
|
+
this.transactionPromise = null;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Reject current promise with error
|
|
269
|
+
*/
|
|
270
|
+
private rejectWithError(error: Error): void {
|
|
271
|
+
if (this.connectionPromise) {
|
|
272
|
+
if (this.connectionPromise.timeout !== null) {
|
|
273
|
+
clearTimeout(this.connectionPromise.timeout);
|
|
274
|
+
}
|
|
275
|
+
this.connectionPromise.reject(error);
|
|
276
|
+
this.connectionPromise = null;
|
|
277
|
+
}
|
|
278
|
+
if (this.transactionPromise) {
|
|
279
|
+
if (this.transactionPromise.timeout !== null) {
|
|
280
|
+
clearTimeout(this.transactionPromise.timeout);
|
|
281
|
+
}
|
|
282
|
+
this.transactionPromise.reject(error);
|
|
283
|
+
this.transactionPromise = null;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Connect to wallet
|
|
289
|
+
*/
|
|
290
|
+
async connect(): Promise<WalletInfo> {
|
|
291
|
+
// If already connected, return current wallet
|
|
292
|
+
if (this.currentStatus.connected && this.currentStatus.wallet) {
|
|
293
|
+
return this.currentStatus.wallet;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// CRITICAL FIX: Check if connection is already in progress
|
|
297
|
+
if (this.connectionPromise) {
|
|
298
|
+
throw new ConnectionInProgressError();
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Build connection request URL
|
|
302
|
+
const url = buildConnectionRequest(this.config.manifestUrl, this.config.scheme);
|
|
303
|
+
|
|
304
|
+
// Create promise for connection
|
|
305
|
+
return new Promise<WalletInfo>((resolve, reject) => {
|
|
306
|
+
let timeout: number | null = null;
|
|
307
|
+
|
|
308
|
+
this.connectionPromise = {
|
|
309
|
+
resolve: (wallet: WalletInfo) => {
|
|
310
|
+
if (timeout !== null) {
|
|
311
|
+
clearTimeout(timeout);
|
|
312
|
+
}
|
|
313
|
+
this.connectionPromise = null;
|
|
314
|
+
resolve(wallet);
|
|
315
|
+
},
|
|
316
|
+
reject: (error: Error) => {
|
|
317
|
+
if (timeout !== null) {
|
|
318
|
+
clearTimeout(timeout);
|
|
319
|
+
}
|
|
320
|
+
this.connectionPromise = null;
|
|
321
|
+
reject(error);
|
|
322
|
+
},
|
|
323
|
+
timeout: null,
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
// Set timeout
|
|
327
|
+
timeout = setTimeout(() => {
|
|
328
|
+
if (this.connectionPromise) {
|
|
329
|
+
this.connectionPromise.reject(new ConnectionTimeoutError());
|
|
330
|
+
}
|
|
331
|
+
}, this.config.connectionTimeout) as unknown as number;
|
|
332
|
+
|
|
333
|
+
this.connectionPromise.timeout = timeout;
|
|
334
|
+
|
|
335
|
+
// Open wallet app
|
|
336
|
+
this.adapter.openURL(url).catch((error) => {
|
|
337
|
+
if (this.connectionPromise) {
|
|
338
|
+
this.connectionPromise.reject(
|
|
339
|
+
new TonConnectError(`Failed to open wallet: ${error?.message || String(error)}`)
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Send transaction
|
|
348
|
+
*/
|
|
349
|
+
async sendTransaction(request: SendTransactionRequest): Promise<{ boc: string; signature: string }> {
|
|
350
|
+
// Validate request
|
|
351
|
+
const validation = validateTransactionRequest(request);
|
|
352
|
+
if (!validation.valid) {
|
|
353
|
+
throw new TonConnectError(validation.error || 'Invalid transaction request');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Check if connected
|
|
357
|
+
if (!this.currentStatus.connected || !this.currentStatus.wallet) {
|
|
358
|
+
throw new TonConnectError('Not connected to wallet. Call connect() first.');
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// CRITICAL FIX: Check if transaction is already in progress
|
|
362
|
+
if (this.transactionPromise) {
|
|
363
|
+
throw new TransactionInProgressError();
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Build transaction request URL
|
|
367
|
+
const url = buildTransactionRequest(this.config.manifestUrl, request, this.config.scheme);
|
|
368
|
+
|
|
369
|
+
// Create promise for transaction
|
|
370
|
+
return new Promise<{ boc: string; signature: string }>((resolve, reject) => {
|
|
371
|
+
let timeout: number | null = null;
|
|
372
|
+
|
|
373
|
+
this.transactionPromise = {
|
|
374
|
+
resolve: (response: { boc: string; signature: string }) => {
|
|
375
|
+
if (timeout !== null) {
|
|
376
|
+
clearTimeout(timeout);
|
|
377
|
+
}
|
|
378
|
+
this.transactionPromise = null;
|
|
379
|
+
resolve(response);
|
|
380
|
+
},
|
|
381
|
+
reject: (error: Error) => {
|
|
382
|
+
if (timeout !== null) {
|
|
383
|
+
clearTimeout(timeout);
|
|
384
|
+
}
|
|
385
|
+
this.transactionPromise = null;
|
|
386
|
+
reject(error);
|
|
387
|
+
},
|
|
388
|
+
timeout: null,
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
// Set timeout
|
|
392
|
+
timeout = setTimeout(() => {
|
|
393
|
+
if (this.transactionPromise) {
|
|
394
|
+
this.transactionPromise.reject(new TransactionTimeoutError());
|
|
395
|
+
}
|
|
396
|
+
}, this.config.transactionTimeout) as unknown as number;
|
|
397
|
+
|
|
398
|
+
this.transactionPromise.timeout = timeout;
|
|
399
|
+
|
|
400
|
+
// Open wallet app
|
|
401
|
+
this.adapter.openURL(url).catch((error) => {
|
|
402
|
+
if (this.transactionPromise) {
|
|
403
|
+
this.transactionPromise.reject(
|
|
404
|
+
new TonConnectError(`Failed to open wallet: ${error?.message || String(error)}`)
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Disconnect from wallet
|
|
413
|
+
*/
|
|
414
|
+
async disconnect(): Promise<void> {
|
|
415
|
+
// Clear session
|
|
416
|
+
await this.clearSession();
|
|
417
|
+
|
|
418
|
+
// Update status
|
|
419
|
+
this.currentStatus = { connected: false, wallet: null };
|
|
420
|
+
this.notifyStatusChange();
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Get current connection status
|
|
425
|
+
*/
|
|
426
|
+
getStatus(): ConnectionStatus {
|
|
427
|
+
return { ...this.currentStatus };
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Subscribe to status changes
|
|
432
|
+
*/
|
|
433
|
+
onStatusChange(callback: StatusChangeCallback): () => void {
|
|
434
|
+
this.statusChangeCallbacks.add(callback);
|
|
435
|
+
|
|
436
|
+
// Immediately call with current status
|
|
437
|
+
callback(this.getStatus());
|
|
438
|
+
|
|
439
|
+
// Return unsubscribe function
|
|
440
|
+
return () => {
|
|
441
|
+
this.statusChangeCallbacks.delete(callback);
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Notify all status change callbacks
|
|
447
|
+
*/
|
|
448
|
+
private notifyStatusChange(): void {
|
|
449
|
+
const status = this.getStatus();
|
|
450
|
+
this.statusChangeCallbacks.forEach((callback) => {
|
|
451
|
+
try {
|
|
452
|
+
callback(status);
|
|
453
|
+
} catch (error) {
|
|
454
|
+
// Ignore errors in callbacks
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Validate session ID format
|
|
461
|
+
*/
|
|
462
|
+
private validateSessionId(sessionId: string): boolean {
|
|
463
|
+
if (!sessionId || typeof sessionId !== 'string') {
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
// Session ID should be reasonable length (1-200 characters)
|
|
467
|
+
if (sessionId.length === 0 || sessionId.length > 200) {
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
// Basic validation: should not contain control characters
|
|
471
|
+
if (/[\x00-\x1F\x7F]/.test(sessionId)) {
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
474
|
+
return true;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Save session to storage
|
|
479
|
+
*/
|
|
480
|
+
private async saveSession(sessionId: string, wallet: WalletInfo): Promise<void> {
|
|
481
|
+
// Validate inputs
|
|
482
|
+
if (!this.validateSessionId(sessionId)) {
|
|
483
|
+
throw new TonConnectError('Invalid session ID format');
|
|
484
|
+
}
|
|
485
|
+
if (!wallet || !wallet.address || !wallet.publicKey) {
|
|
486
|
+
throw new TonConnectError('Invalid wallet data');
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
try {
|
|
490
|
+
const sessionKey = `${this.config.storageKeyPrefix}session`;
|
|
491
|
+
const walletKey = `${this.config.storageKeyPrefix}wallet`;
|
|
492
|
+
|
|
493
|
+
await this.adapter.setItem(sessionKey, sessionId);
|
|
494
|
+
await this.adapter.setItem(walletKey, JSON.stringify(wallet));
|
|
495
|
+
} catch (error) {
|
|
496
|
+
// Log error but don't throw - connection is still valid
|
|
497
|
+
console.error('TON Connect: Failed to save session to storage:', error);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Load session from storage
|
|
503
|
+
*/
|
|
504
|
+
private async loadSession(): Promise<void> {
|
|
505
|
+
try {
|
|
506
|
+
const sessionKey = `${this.config.storageKeyPrefix}session`;
|
|
507
|
+
const walletKey = `${this.config.storageKeyPrefix}wallet`;
|
|
508
|
+
|
|
509
|
+
const sessionId = await this.adapter.getItem(sessionKey);
|
|
510
|
+
const walletJson = await this.adapter.getItem(walletKey);
|
|
511
|
+
|
|
512
|
+
if (sessionId && walletJson) {
|
|
513
|
+
try {
|
|
514
|
+
// Validate session ID
|
|
515
|
+
if (!this.validateSessionId(sessionId)) {
|
|
516
|
+
await this.clearSession();
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const wallet = JSON.parse(walletJson) as WalletInfo;
|
|
521
|
+
|
|
522
|
+
// Validate wallet data
|
|
523
|
+
if (!wallet || !wallet.address || !wallet.publicKey) {
|
|
524
|
+
await this.clearSession();
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
this.currentStatus = { connected: true, wallet };
|
|
529
|
+
this.notifyStatusChange();
|
|
530
|
+
} catch (error) {
|
|
531
|
+
// Invalid wallet data, clear it
|
|
532
|
+
console.error('TON Connect: Invalid session data, clearing:', error);
|
|
533
|
+
await this.clearSession();
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
} catch (error) {
|
|
537
|
+
// Log storage errors for debugging
|
|
538
|
+
console.error('TON Connect: Failed to load session from storage:', error);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Clear session from storage
|
|
544
|
+
*/
|
|
545
|
+
private async clearSession(): Promise<void> {
|
|
546
|
+
try {
|
|
547
|
+
const sessionKey = `${this.config.storageKeyPrefix}session`;
|
|
548
|
+
const walletKey = `${this.config.storageKeyPrefix}wallet`;
|
|
549
|
+
|
|
550
|
+
await this.adapter.removeItem(sessionKey);
|
|
551
|
+
await this.adapter.removeItem(walletKey);
|
|
552
|
+
} catch (error) {
|
|
553
|
+
// Ignore storage errors
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Cleanup resources
|
|
559
|
+
*/
|
|
560
|
+
destroy(): void {
|
|
561
|
+
if (this.urlUnsubscribe) {
|
|
562
|
+
this.urlUnsubscribe();
|
|
563
|
+
this.urlUnsubscribe = null;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if ('destroy' in this.adapter && typeof (this.adapter as { destroy?: () => void }).destroy === 'function') {
|
|
567
|
+
(this.adapter as { destroy: () => void }).destroy();
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
this.statusChangeCallbacks.clear();
|
|
571
|
+
this.connectionPromise = null;
|
|
572
|
+
this.transactionPromise = null;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// Export types
|
|
577
|
+
export * from './types';
|
|
578
|
+
|