@meshconnect/uwc-ton-connector 0.5.2 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ton-connect-connector.d.ts +4 -3
- package/dist/ton-connect-connector.d.ts.map +1 -1
- package/dist/ton-connect-connector.js +53 -28
- package/dist/ton-connect-connector.js.map +1 -1
- package/package.json +1 -1
- package/src/ton-connect-connector.test.ts +174 -70
- package/src/ton-connect-connector.ts +62 -29
|
@@ -5,14 +5,17 @@ export declare class TonConnectConnector implements Connector {
|
|
|
5
5
|
private connectionURI;
|
|
6
6
|
private connectedAddress;
|
|
7
7
|
private connectedNetworkId;
|
|
8
|
-
private visibilityHandler;
|
|
9
8
|
private connectTimeout;
|
|
10
9
|
private toUserFriendlyAddressFn;
|
|
11
10
|
private UserRejectsErrorClass;
|
|
12
11
|
private connectInFlight;
|
|
12
|
+
private abortPendingConnect;
|
|
13
|
+
private pendingConnectPromise;
|
|
14
|
+
private connectGeneration;
|
|
13
15
|
constructor(config: TonConnectConfig);
|
|
14
16
|
/** Connect to a TON wallet via HTTP Bridge (QR code / deeplink). */
|
|
15
17
|
connect(network: Network, provider?: TonConnectWalletProvider): Promise<ConnectorResult>;
|
|
18
|
+
private executeConnect;
|
|
16
19
|
/** Return the connection URI generated by the last connect() call. */
|
|
17
20
|
getConnectionURI(): string;
|
|
18
21
|
/** Sign a message using TON Connect signData. */
|
|
@@ -28,8 +31,6 @@ export declare class TonConnectConnector implements Connector {
|
|
|
28
31
|
private waitForWalletApproval;
|
|
29
32
|
private extractSdkErrorMessage;
|
|
30
33
|
private cleanupSdk;
|
|
31
|
-
private setupVisibilityHandler;
|
|
32
|
-
private removeVisibilityHandler;
|
|
33
34
|
private clearConnectTimeout;
|
|
34
35
|
private buildAvailableAddresses;
|
|
35
36
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ton-connect-connector.d.ts","sourceRoot":"","sources":["../src/ton-connect-connector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,SAAS,EACT,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,wBAAwB,EAExB,kBAAkB,EAClB,iBAAiB,EACjB,aAAa,EAGd,MAAM,wBAAwB,CAAA;
|
|
1
|
+
{"version":3,"file":"ton-connect-connector.d.ts","sourceRoot":"","sources":["../src/ton-connect-connector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,SAAS,EACT,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,wBAAwB,EAExB,kBAAkB,EAClB,iBAAiB,EACjB,aAAa,EAGd,MAAM,wBAAwB,CAAA;AA4B/B,qBAAa,mBAAoB,YAAW,SAAS;IACnD,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,GAAG,CAA2B;IACtC,OAAO,CAAC,aAAa,CAAoB;IACzC,OAAO,CAAC,gBAAgB,CAAsB;IAC9C,OAAO,CAAC,kBAAkB,CAAyB;IACnD,OAAO,CAAC,cAAc,CAA6C;IACnE,OAAO,CAAC,uBAAuB,CAEhB;IACf,OAAO,CAAC,qBAAqB,CAAiD;IAC9E,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,mBAAmB,CAA4B;IACvD,OAAO,CAAC,qBAAqB,CAAwC;IACrE,OAAO,CAAC,iBAAiB,CAAI;gBAEjB,MAAM,EAAE,gBAAgB;IAIpC,oEAAoE;IAC9D,OAAO,CACX,OAAO,EAAE,OAAO,EAChB,QAAQ,CAAC,EAAE,wBAAwB,GAClC,OAAO,CAAC,eAAe,CAAC;YA6Bb,cAAc;IAkG5B,sEAAsE;IACtE,gBAAgB,IAAI,MAAM;IAO1B,iDAAiD;IAC3C,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAsB1D,uDAAuD;IACjD,eAAe,CACnB,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,iBAAiB,CAAC;IA2B7B,qEAAqE;IAC/D,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAOnE,yDAAyD;IACnD,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAmBjC,4DAA4D;IACtD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAIhB,qBAAqB;IAuEnC,OAAO,CAAC,sBAAsB;YAWhB,UAAU;IAWxB,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,uBAAuB;CAQhC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { WalletConnectorError } from '@meshconnect/uwc-types';
|
|
1
2
|
import { NamespacedStorage } from './namespaced-storage';
|
|
2
3
|
import { buildTonTransactionRequest, buildSignDataPayload, executeSignData, unwrapBoc, bocToHash, toFriendlyAddress } from './ton-transaction-utils';
|
|
3
4
|
// 5 minutes matches WalletConnect's proposal expiry. The TonConnect protocol
|
|
@@ -9,11 +10,13 @@ export class TonConnectConnector {
|
|
|
9
10
|
connectionURI;
|
|
10
11
|
connectedAddress = null;
|
|
11
12
|
connectedNetworkId = null;
|
|
12
|
-
visibilityHandler = null;
|
|
13
13
|
connectTimeout = null;
|
|
14
14
|
toUserFriendlyAddressFn = null;
|
|
15
15
|
UserRejectsErrorClass = null;
|
|
16
16
|
connectInFlight = false;
|
|
17
|
+
abortPendingConnect = null;
|
|
18
|
+
pendingConnectPromise = null;
|
|
19
|
+
connectGeneration = 0;
|
|
17
20
|
constructor(config) {
|
|
18
21
|
this.config = config;
|
|
19
22
|
}
|
|
@@ -25,15 +28,25 @@ export class TonConnectConnector {
|
|
|
25
28
|
if (!provider.walletListAppName) {
|
|
26
29
|
throw new Error('walletListAppName is required — set it on TonConnectWalletProvider, or ensure the wallet has a jsBridgeKey in extensionInjectedProvider.tvm');
|
|
27
30
|
}
|
|
28
|
-
if (this.connectInFlight) {
|
|
29
|
-
|
|
31
|
+
if (this.connectInFlight && this.pendingConnectPromise) {
|
|
32
|
+
return this.pendingConnectPromise;
|
|
30
33
|
}
|
|
31
|
-
this.connectInFlight = true;
|
|
32
34
|
if (this.sdk) {
|
|
33
|
-
await this.
|
|
35
|
+
await this.disconnect();
|
|
34
36
|
}
|
|
37
|
+
this.connectInFlight = true;
|
|
38
|
+
this.connectGeneration++;
|
|
39
|
+
const generation = this.connectGeneration;
|
|
40
|
+
const connectExecution = this.executeConnect(network, provider, generation);
|
|
41
|
+
this.pendingConnectPromise = connectExecution;
|
|
42
|
+
return connectExecution;
|
|
43
|
+
}
|
|
44
|
+
async executeConnect(network, provider, generation) {
|
|
35
45
|
try {
|
|
36
46
|
const { TonConnect, toUserFriendlyAddress, UserRejectsError } = await import('@tonconnect/sdk');
|
|
47
|
+
if (this.connectGeneration !== generation) {
|
|
48
|
+
throw new Error('TON Connect connection was cancelled');
|
|
49
|
+
}
|
|
37
50
|
this.toUserFriendlyAddressFn = toUserFriendlyAddress;
|
|
38
51
|
this.UserRejectsErrorClass = UserRejectsError;
|
|
39
52
|
const resolvedManifestUrl = typeof this.config.manifestUrl === 'function'
|
|
@@ -78,7 +91,6 @@ export class TonConnectConnector {
|
|
|
78
91
|
: walletResult.address;
|
|
79
92
|
this.connectedAddress = friendlyAddress;
|
|
80
93
|
this.connectedNetworkId = network.id;
|
|
81
|
-
this.setupVisibilityHandler(sdkRef);
|
|
82
94
|
const availableAddresses = this.buildAvailableAddresses(provider.supportedNetworkIds, friendlyAddress);
|
|
83
95
|
return {
|
|
84
96
|
networkId: network.id,
|
|
@@ -90,7 +102,10 @@ export class TonConnectConnector {
|
|
|
90
102
|
};
|
|
91
103
|
}
|
|
92
104
|
finally {
|
|
93
|
-
this.
|
|
105
|
+
if (this.connectGeneration === generation) {
|
|
106
|
+
this.connectInFlight = false;
|
|
107
|
+
this.pendingConnectPromise = null;
|
|
108
|
+
}
|
|
94
109
|
}
|
|
95
110
|
}
|
|
96
111
|
/** Return the connection URI generated by the last connect() call. */
|
|
@@ -106,7 +121,19 @@ export class TonConnectConnector {
|
|
|
106
121
|
throw new Error('No active TON connection');
|
|
107
122
|
}
|
|
108
123
|
const payload = buildSignDataPayload(message);
|
|
109
|
-
|
|
124
|
+
try {
|
|
125
|
+
return await executeSignData(this.sdk, payload);
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
if (this.UserRejectsErrorClass &&
|
|
129
|
+
error instanceof this.UserRejectsErrorClass) {
|
|
130
|
+
throw new WalletConnectorError({
|
|
131
|
+
type: 'rejected',
|
|
132
|
+
message: 'Message signing was rejected by the user'
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
110
137
|
}
|
|
111
138
|
/** Send a TON transaction via the connected wallet. */
|
|
112
139
|
async sendTransaction(request) {
|
|
@@ -124,7 +151,10 @@ export class TonConnectConnector {
|
|
|
124
151
|
catch (error) {
|
|
125
152
|
if (this.UserRejectsErrorClass &&
|
|
126
153
|
error instanceof this.UserRejectsErrorClass) {
|
|
127
|
-
throw new
|
|
154
|
+
throw new WalletConnectorError({
|
|
155
|
+
type: 'rejected',
|
|
156
|
+
message: 'Transaction was rejected by the user'
|
|
157
|
+
});
|
|
128
158
|
}
|
|
129
159
|
throw error;
|
|
130
160
|
}
|
|
@@ -138,6 +168,9 @@ export class TonConnectConnector {
|
|
|
138
168
|
}
|
|
139
169
|
/** Disconnect from the wallet and clean up resources. */
|
|
140
170
|
async disconnect() {
|
|
171
|
+
this.abortPendingConnect?.();
|
|
172
|
+
this.abortPendingConnect = null;
|
|
173
|
+
this.pendingConnectPromise = null;
|
|
141
174
|
if (this.sdk) {
|
|
142
175
|
try {
|
|
143
176
|
await this.sdk.disconnect();
|
|
@@ -152,7 +185,6 @@ export class TonConnectConnector {
|
|
|
152
185
|
this.connectionURI = undefined;
|
|
153
186
|
this.connectInFlight = false;
|
|
154
187
|
this.clearConnectTimeout();
|
|
155
|
-
this.removeVisibilityHandler();
|
|
156
188
|
}
|
|
157
189
|
/** Tear down SDK, event listeners, and pending timeouts. */
|
|
158
190
|
async destroy() {
|
|
@@ -168,10 +200,19 @@ export class TonConnectConnector {
|
|
|
168
200
|
this.connectionURI = undefined;
|
|
169
201
|
this.cleanupSdk(sdk);
|
|
170
202
|
};
|
|
203
|
+
this.abortPendingConnect = () => {
|
|
204
|
+
if (settled)
|
|
205
|
+
return;
|
|
206
|
+
settled = true;
|
|
207
|
+
this.clearConnectTimeout();
|
|
208
|
+
unsubscribe?.();
|
|
209
|
+
reject(new Error('TON Connect connection was cancelled'));
|
|
210
|
+
};
|
|
171
211
|
this.connectTimeout = setTimeout(() => {
|
|
172
212
|
if (settled)
|
|
173
213
|
return;
|
|
174
214
|
settled = true;
|
|
215
|
+
this.abortPendingConnect = null;
|
|
175
216
|
cleanup();
|
|
176
217
|
reject(new Error('TON wallet connection timed out'));
|
|
177
218
|
}, CONNECT_TIMEOUT_MS);
|
|
@@ -179,6 +220,7 @@ export class TonConnectConnector {
|
|
|
179
220
|
if (settled)
|
|
180
221
|
return;
|
|
181
222
|
settled = true;
|
|
223
|
+
this.abortPendingConnect = null;
|
|
182
224
|
if (wallet?.account?.address) {
|
|
183
225
|
this.clearConnectTimeout();
|
|
184
226
|
unsubscribe();
|
|
@@ -197,6 +239,7 @@ export class TonConnectConnector {
|
|
|
197
239
|
if (settled)
|
|
198
240
|
return;
|
|
199
241
|
settled = true;
|
|
242
|
+
this.abortPendingConnect = null;
|
|
200
243
|
const errorObj = error;
|
|
201
244
|
const message = errorObj?.payload?.message ||
|
|
202
245
|
this.extractSdkErrorMessage(error) ||
|
|
@@ -228,24 +271,6 @@ export class TonConnectConnector {
|
|
|
228
271
|
this.sdk = null;
|
|
229
272
|
}
|
|
230
273
|
}
|
|
231
|
-
setupVisibilityHandler(sdk) {
|
|
232
|
-
this.removeVisibilityHandler();
|
|
233
|
-
this.visibilityHandler = () => {
|
|
234
|
-
if (document.visibilityState === 'visible') {
|
|
235
|
-
sdk.unPauseConnection();
|
|
236
|
-
}
|
|
237
|
-
else {
|
|
238
|
-
sdk.pauseConnection();
|
|
239
|
-
}
|
|
240
|
-
};
|
|
241
|
-
document.addEventListener('visibilitychange', this.visibilityHandler);
|
|
242
|
-
}
|
|
243
|
-
removeVisibilityHandler() {
|
|
244
|
-
if (this.visibilityHandler) {
|
|
245
|
-
document.removeEventListener('visibilitychange', this.visibilityHandler);
|
|
246
|
-
this.visibilityHandler = null;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
274
|
clearConnectTimeout() {
|
|
250
275
|
if (this.connectTimeout) {
|
|
251
276
|
clearTimeout(this.connectTimeout);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ton-connect-connector.js","sourceRoot":"","sources":["../src/ton-connect-connector.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ton-connect-connector.js","sourceRoot":"","sources":["../src/ton-connect-connector.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
2
2
|
import type { Network, TonConnectWalletProvider } from '@meshconnect/uwc-types'
|
|
3
|
+
import { WalletConnectorError } from '@meshconnect/uwc-types'
|
|
3
4
|
|
|
4
5
|
const {
|
|
5
6
|
mockSdkConnect,
|
|
@@ -7,8 +8,6 @@ const {
|
|
|
7
8
|
mockOnStatusChange,
|
|
8
9
|
mockSendTransaction,
|
|
9
10
|
mockSignData,
|
|
10
|
-
mockPauseConnection,
|
|
11
|
-
mockUnPauseConnection,
|
|
12
11
|
MockTonConnect,
|
|
13
12
|
MockUserRejectsError,
|
|
14
13
|
getLastSdkInstance,
|
|
@@ -19,8 +18,6 @@ const {
|
|
|
19
18
|
const mockOnStatusChange = vi.fn()
|
|
20
19
|
const mockSendTransaction = vi.fn()
|
|
21
20
|
const mockSignData = vi.fn()
|
|
22
|
-
const mockPauseConnection = vi.fn()
|
|
23
|
-
const mockUnPauseConnection = vi.fn()
|
|
24
21
|
|
|
25
22
|
class MockUserRejectsError extends Error {
|
|
26
23
|
constructor(message?: string) {
|
|
@@ -51,8 +48,6 @@ const {
|
|
|
51
48
|
bridgeUrl: 'https://bridge.tonapi.io/bridge'
|
|
52
49
|
}
|
|
53
50
|
])
|
|
54
|
-
pauseConnection = mockPauseConnection
|
|
55
|
-
unPauseConnection = mockUnPauseConnection
|
|
56
51
|
__options: unknown = null
|
|
57
52
|
constructor(options?: unknown) {
|
|
58
53
|
this.__options = options
|
|
@@ -67,8 +62,6 @@ const {
|
|
|
67
62
|
mockOnStatusChange,
|
|
68
63
|
mockSendTransaction,
|
|
69
64
|
mockSignData,
|
|
70
|
-
mockPauseConnection,
|
|
71
|
-
mockUnPauseConnection,
|
|
72
65
|
MockTonConnect,
|
|
73
66
|
MockUserRejectsError,
|
|
74
67
|
getLastSdkInstance: () => sdkInstances.current,
|
|
@@ -326,6 +319,38 @@ describe('TonConnectConnector', () => {
|
|
|
326
319
|
expect(mockDisconnect).toHaveBeenCalled()
|
|
327
320
|
})
|
|
328
321
|
|
|
322
|
+
it('extracts last line from multiline SDK error message', async () => {
|
|
323
|
+
mockSdkConnect.mockReturnValue('tc://uri')
|
|
324
|
+
mockDisconnect.mockResolvedValue(undefined)
|
|
325
|
+
mockOnStatusChange.mockImplementation((_callback, errorCallback) => {
|
|
326
|
+
setTimeout(
|
|
327
|
+
() =>
|
|
328
|
+
errorCallback({
|
|
329
|
+
message: 'TonConnectError\nActual error on last line'
|
|
330
|
+
}),
|
|
331
|
+
10
|
|
332
|
+
)
|
|
333
|
+
return vi.fn()
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
await expect(connector.connect(TON_NETWORK, PROVIDER)).rejects.toThrow(
|
|
337
|
+
'Actual error on last line'
|
|
338
|
+
)
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
it('falls back to default message when error has no payload or message', async () => {
|
|
342
|
+
mockSdkConnect.mockReturnValue('tc://uri')
|
|
343
|
+
mockDisconnect.mockResolvedValue(undefined)
|
|
344
|
+
mockOnStatusChange.mockImplementation((_callback, errorCallback) => {
|
|
345
|
+
setTimeout(() => errorCallback({}), 10)
|
|
346
|
+
return vi.fn()
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
await expect(connector.connect(TON_NETWORK, PROVIDER)).rejects.toThrow(
|
|
350
|
+
'TON wallet connection was rejected'
|
|
351
|
+
)
|
|
352
|
+
})
|
|
353
|
+
|
|
329
354
|
it('times out after 5 minutes and cleans up', async () => {
|
|
330
355
|
vi.useFakeTimers()
|
|
331
356
|
|
|
@@ -374,45 +399,116 @@ describe('TonConnectConnector', () => {
|
|
|
374
399
|
expect(mockDisconnect).toHaveBeenCalled()
|
|
375
400
|
})
|
|
376
401
|
|
|
377
|
-
it('
|
|
378
|
-
|
|
379
|
-
mockOnStatusChange.mockImplementation(callback => {
|
|
380
|
-
setTimeout(() => {
|
|
381
|
-
callback({ account: { address: '0:abcdef1234567890' } })
|
|
382
|
-
}, 50)
|
|
383
|
-
return vi.fn()
|
|
384
|
-
})
|
|
402
|
+
it('reuses in-flight connection when connect is called again concurrently', async () => {
|
|
403
|
+
simulateSuccessfulConnect()
|
|
385
404
|
|
|
386
405
|
const first = connector.connect(TON_NETWORK, PROVIDER)
|
|
387
|
-
|
|
388
|
-
|
|
406
|
+
const second = connector.connect(TON_NETWORK, PROVIDER)
|
|
407
|
+
|
|
408
|
+
const [firstResult, secondResult] = await Promise.all([first, second])
|
|
409
|
+
|
|
410
|
+
expect(firstResult.address).toBe(secondResult.address)
|
|
411
|
+
expect(firstResult.networkId).toBe(secondResult.networkId)
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
it('cancels previous connection when called after disconnect', async () => {
|
|
415
|
+
mockOnStatusChange.mockReturnValue(vi.fn())
|
|
416
|
+
mockSdkConnect.mockReturnValue('tc://mock-uri')
|
|
417
|
+
|
|
418
|
+
const first = connector
|
|
419
|
+
.connect(TON_NETWORK, PROVIDER)
|
|
420
|
+
.catch((e: unknown) => e)
|
|
421
|
+
await new Promise(r => setTimeout(r, 0))
|
|
422
|
+
|
|
423
|
+
// Explicit disconnect, then new connect
|
|
424
|
+
await connector.disconnect()
|
|
425
|
+
simulateSuccessfulConnect()
|
|
426
|
+
|
|
427
|
+
const result = await connector.connect(TON_NETWORK, PROVIDER)
|
|
428
|
+
expect(result.address).toBeDefined()
|
|
429
|
+
|
|
430
|
+
const firstError = await first
|
|
431
|
+
expect(firstError).toBeInstanceOf(Error)
|
|
432
|
+
expect((firstError as Error).message).toBe(
|
|
433
|
+
'TON Connect connection was cancelled'
|
|
389
434
|
)
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
it('does not clear in-flight state when stale executeConnect settles after disconnect + reconnect', async () => {
|
|
438
|
+
// First connect starts but never settles (wallet doesn't approve)
|
|
439
|
+
mockOnStatusChange.mockReturnValue(vi.fn())
|
|
440
|
+
mockSdkConnect.mockReturnValue('tc://mock-uri')
|
|
441
|
+
|
|
442
|
+
const first = connector
|
|
443
|
+
.connect(TON_NETWORK, PROVIDER)
|
|
444
|
+
.catch((e: unknown) => e)
|
|
445
|
+
await new Promise(r => setTimeout(r, 0))
|
|
446
|
+
|
|
447
|
+
// Disconnect cancels first, then immediately start a second connect
|
|
448
|
+
await connector.disconnect()
|
|
449
|
+
|
|
450
|
+
// Second connect starts — should set its own in-flight state
|
|
451
|
+
simulateSuccessfulConnect()
|
|
452
|
+
mockDisconnect.mockResolvedValue(undefined)
|
|
390
453
|
|
|
454
|
+
const second = connector.connect(TON_NETWORK, PROVIDER)
|
|
455
|
+
|
|
456
|
+
// First connect's rejection settles — its finally block should NOT
|
|
457
|
+
// clear the second connect's in-flight state
|
|
391
458
|
await first
|
|
459
|
+
|
|
460
|
+
// Second connect should still resolve successfully
|
|
461
|
+
const result = await second
|
|
462
|
+
expect(result.address).toBeDefined()
|
|
392
463
|
})
|
|
393
464
|
|
|
394
|
-
it('
|
|
395
|
-
|
|
396
|
-
|
|
465
|
+
it('passes walletsListSource to SDK when configured', async () => {
|
|
466
|
+
const customConnector = new TonConnectConnector({
|
|
467
|
+
manifestUrl: 'https://example.com/tonconnect-manifest.json',
|
|
468
|
+
walletsListSource: 'https://custom-wallets.example.com/list.json'
|
|
469
|
+
})
|
|
397
470
|
simulateSuccessfulConnect()
|
|
398
471
|
|
|
399
|
-
await
|
|
472
|
+
await customConnector.connect(TON_NETWORK, PROVIDER)
|
|
400
473
|
|
|
401
|
-
|
|
402
|
-
|
|
474
|
+
const sdkInstance = getLastSdkInstance()
|
|
475
|
+
expect(
|
|
476
|
+
(sdkInstance as unknown as { __options: { walletsListSource: string } })
|
|
477
|
+
.__options.walletsListSource
|
|
478
|
+
).toBe('https://custom-wallets.example.com/list.json')
|
|
479
|
+
})
|
|
480
|
+
|
|
481
|
+
it('cancels stale executeConnect if generation changes during setup', async () => {
|
|
482
|
+
mockOnStatusChange.mockReturnValue(vi.fn())
|
|
483
|
+
mockSdkConnect.mockReturnValue('tc://mock-uri')
|
|
484
|
+
|
|
485
|
+
const first = connector
|
|
486
|
+
.connect(TON_NETWORK, PROVIDER)
|
|
487
|
+
.catch((e: unknown) => e)
|
|
488
|
+
await new Promise(r => setTimeout(r, 0))
|
|
489
|
+
|
|
490
|
+
// disconnect increments generation indirectly by resetting state
|
|
491
|
+
await connector.disconnect()
|
|
492
|
+
|
|
493
|
+
// Start new connect
|
|
494
|
+
simulateSuccessfulConnect()
|
|
495
|
+
const result = await connector.connect(TON_NETWORK, PROVIDER)
|
|
496
|
+
expect(result.address).toBeDefined()
|
|
497
|
+
|
|
498
|
+
// First should have been cancelled
|
|
499
|
+
const firstResult = await first
|
|
500
|
+
expect(firstResult).toBeInstanceOf(Error)
|
|
403
501
|
})
|
|
404
502
|
|
|
405
|
-
it('
|
|
406
|
-
|
|
503
|
+
it('disconnects stale session from NamespacedStorage on fresh SDK', async () => {
|
|
504
|
+
setNextConnectedState(true)
|
|
505
|
+
mockDisconnect.mockResolvedValue(undefined)
|
|
407
506
|
simulateSuccessfulConnect()
|
|
408
507
|
|
|
409
508
|
await connector.connect(TON_NETWORK, PROVIDER)
|
|
410
509
|
|
|
411
|
-
expect(
|
|
412
|
-
|
|
413
|
-
expect.any(Function)
|
|
414
|
-
)
|
|
415
|
-
addEventSpy.mockRestore()
|
|
510
|
+
expect(mockDisconnect).toHaveBeenCalled()
|
|
511
|
+
setNextConnectedState(false)
|
|
416
512
|
})
|
|
417
513
|
})
|
|
418
514
|
|
|
@@ -461,6 +557,32 @@ describe('TonConnectConnector', () => {
|
|
|
461
557
|
payload: { type: 'text', text: 'test message' }
|
|
462
558
|
})
|
|
463
559
|
})
|
|
560
|
+
|
|
561
|
+
it('wraps UserRejectsError as WalletConnectorError with type rejected', async () => {
|
|
562
|
+
simulateSuccessfulConnect()
|
|
563
|
+
await connector.connect(TON_NETWORK, PROVIDER)
|
|
564
|
+
|
|
565
|
+
mockSignData.mockRejectedValue(new MockUserRejectsError('User rejected'))
|
|
566
|
+
|
|
567
|
+
const error = await connector.signMessage!('test message').catch(
|
|
568
|
+
(e: unknown) => e
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
expect(error).toBeInstanceOf(WalletConnectorError)
|
|
572
|
+
expect((error as WalletConnectorError).type).toBe('rejected')
|
|
573
|
+
expect((error as WalletConnectorError).message).toContain('rejected')
|
|
574
|
+
})
|
|
575
|
+
|
|
576
|
+
it('rethrows non-UserRejectsError errors', async () => {
|
|
577
|
+
simulateSuccessfulConnect()
|
|
578
|
+
await connector.connect(TON_NETWORK, PROVIDER)
|
|
579
|
+
|
|
580
|
+
mockSignData.mockRejectedValue(new Error('network failure'))
|
|
581
|
+
|
|
582
|
+
await expect(connector.signMessage!('test message')).rejects.toThrow(
|
|
583
|
+
'network failure'
|
|
584
|
+
)
|
|
585
|
+
})
|
|
464
586
|
})
|
|
465
587
|
|
|
466
588
|
describe('sendTransaction', () => {
|
|
@@ -550,7 +672,7 @@ describe('TonConnectConnector', () => {
|
|
|
550
672
|
expect(result).toBe('hash_of_te6cckJettonBoc...')
|
|
551
673
|
})
|
|
552
674
|
|
|
553
|
-
it('wraps UserRejectsError with
|
|
675
|
+
it('wraps UserRejectsError as WalletConnectorError with type rejected', async () => {
|
|
554
676
|
simulateSuccessfulConnect()
|
|
555
677
|
await connector.connect(TON_NETWORK, PROVIDER)
|
|
556
678
|
|
|
@@ -559,13 +681,31 @@ describe('TonConnectConnector', () => {
|
|
|
559
681
|
new MockUserRejectsError('User rejected')
|
|
560
682
|
)
|
|
561
683
|
|
|
684
|
+
const error = await connector.sendTransaction!({
|
|
685
|
+
to: 'UQBxyz',
|
|
686
|
+
amount: '1000000000',
|
|
687
|
+
from: 'UQBabc'
|
|
688
|
+
}).catch((e: unknown) => e)
|
|
689
|
+
|
|
690
|
+
expect(error).toBeInstanceOf(WalletConnectorError)
|
|
691
|
+
expect((error as WalletConnectorError).type).toBe('rejected')
|
|
692
|
+
expect((error as WalletConnectorError).message).toContain('rejected')
|
|
693
|
+
})
|
|
694
|
+
|
|
695
|
+
it('rethrows non-UserRejectsError errors', async () => {
|
|
696
|
+
simulateSuccessfulConnect()
|
|
697
|
+
await connector.connect(TON_NETWORK, PROVIDER)
|
|
698
|
+
|
|
699
|
+
getLastSdkInstance().account = { address: '0:abc', chain: '-239' }
|
|
700
|
+
mockSendTransaction.mockRejectedValue(new Error('network timeout'))
|
|
701
|
+
|
|
562
702
|
await expect(
|
|
563
703
|
connector.sendTransaction!({
|
|
564
704
|
to: 'UQBxyz',
|
|
565
705
|
amount: '1000000000',
|
|
566
706
|
from: 'UQBabc'
|
|
567
707
|
})
|
|
568
|
-
).rejects.toThrow('
|
|
708
|
+
).rejects.toThrow('network timeout')
|
|
569
709
|
})
|
|
570
710
|
})
|
|
571
711
|
|
|
@@ -623,8 +763,7 @@ describe('TonConnectConnector', () => {
|
|
|
623
763
|
})
|
|
624
764
|
|
|
625
765
|
describe('destroy', () => {
|
|
626
|
-
it('disconnects SDK and
|
|
627
|
-
const removeSpy = vi.spyOn(document, 'removeEventListener')
|
|
766
|
+
it('disconnects SDK and clears state', async () => {
|
|
628
767
|
simulateSuccessfulConnect()
|
|
629
768
|
mockDisconnect.mockResolvedValue(undefined)
|
|
630
769
|
await connector.connect(TON_NETWORK, PROVIDER)
|
|
@@ -632,45 +771,10 @@ describe('TonConnectConnector', () => {
|
|
|
632
771
|
await connector.destroy()
|
|
633
772
|
|
|
634
773
|
expect(mockDisconnect).toHaveBeenCalled()
|
|
635
|
-
expect(removeSpy).toHaveBeenCalledWith(
|
|
636
|
-
'visibilitychange',
|
|
637
|
-
expect.any(Function)
|
|
638
|
-
)
|
|
639
|
-
removeSpy.mockRestore()
|
|
640
774
|
})
|
|
641
775
|
|
|
642
776
|
it('is safe to call when never connected', async () => {
|
|
643
777
|
await expect(connector.destroy()).resolves.toBeUndefined()
|
|
644
778
|
})
|
|
645
779
|
})
|
|
646
|
-
|
|
647
|
-
describe('visibility handling', () => {
|
|
648
|
-
it('calls unPauseConnection when page becomes visible', async () => {
|
|
649
|
-
simulateSuccessfulConnect()
|
|
650
|
-
await connector.connect(TON_NETWORK, PROVIDER)
|
|
651
|
-
|
|
652
|
-
Object.defineProperty(document, 'visibilityState', {
|
|
653
|
-
value: 'visible',
|
|
654
|
-
writable: true,
|
|
655
|
-
configurable: true
|
|
656
|
-
})
|
|
657
|
-
document.dispatchEvent(new Event('visibilitychange'))
|
|
658
|
-
|
|
659
|
-
expect(mockUnPauseConnection).toHaveBeenCalled()
|
|
660
|
-
})
|
|
661
|
-
|
|
662
|
-
it('calls pauseConnection when page becomes hidden', async () => {
|
|
663
|
-
simulateSuccessfulConnect()
|
|
664
|
-
await connector.connect(TON_NETWORK, PROVIDER)
|
|
665
|
-
|
|
666
|
-
Object.defineProperty(document, 'visibilityState', {
|
|
667
|
-
value: 'hidden',
|
|
668
|
-
writable: true,
|
|
669
|
-
configurable: true
|
|
670
|
-
})
|
|
671
|
-
document.dispatchEvent(new Event('visibilitychange'))
|
|
672
|
-
|
|
673
|
-
expect(mockPauseConnection).toHaveBeenCalled()
|
|
674
|
-
})
|
|
675
|
-
})
|
|
676
780
|
})
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
NetworkId,
|
|
13
13
|
AvailableAddress
|
|
14
14
|
} from '@meshconnect/uwc-types'
|
|
15
|
+
import { WalletConnectorError } from '@meshconnect/uwc-types'
|
|
15
16
|
import type {
|
|
16
17
|
ITonConnect,
|
|
17
18
|
WalletInfoRemote,
|
|
@@ -44,13 +45,15 @@ export class TonConnectConnector implements Connector {
|
|
|
44
45
|
private connectionURI: string | undefined
|
|
45
46
|
private connectedAddress: string | null = null
|
|
46
47
|
private connectedNetworkId: NetworkId | null = null
|
|
47
|
-
private visibilityHandler: (() => void) | null = null
|
|
48
48
|
private connectTimeout: ReturnType<typeof setTimeout> | null = null
|
|
49
49
|
private toUserFriendlyAddressFn:
|
|
50
50
|
| ((hex: string, testOnly?: boolean) => string)
|
|
51
51
|
| null = null
|
|
52
52
|
private UserRejectsErrorClass: (new (...args: never[]) => Error) | null = null
|
|
53
53
|
private connectInFlight = false
|
|
54
|
+
private abortPendingConnect: (() => void) | null = null
|
|
55
|
+
private pendingConnectPromise: Promise<ConnectorResult> | null = null
|
|
56
|
+
private connectGeneration = 0
|
|
54
57
|
|
|
55
58
|
constructor(config: TonConnectConfig) {
|
|
56
59
|
this.config = config
|
|
@@ -72,18 +75,36 @@ export class TonConnectConnector implements Connector {
|
|
|
72
75
|
)
|
|
73
76
|
}
|
|
74
77
|
|
|
75
|
-
if (this.connectInFlight) {
|
|
76
|
-
|
|
78
|
+
if (this.connectInFlight && this.pendingConnectPromise) {
|
|
79
|
+
return this.pendingConnectPromise
|
|
77
80
|
}
|
|
78
|
-
this.connectInFlight = true
|
|
79
81
|
|
|
80
82
|
if (this.sdk) {
|
|
81
|
-
await this.
|
|
83
|
+
await this.disconnect()
|
|
82
84
|
}
|
|
85
|
+
this.connectInFlight = true
|
|
86
|
+
this.connectGeneration++
|
|
87
|
+
|
|
88
|
+
const generation = this.connectGeneration
|
|
89
|
+
const connectExecution = this.executeConnect(network, provider, generation)
|
|
90
|
+
this.pendingConnectPromise = connectExecution
|
|
91
|
+
|
|
92
|
+
return connectExecution
|
|
93
|
+
}
|
|
83
94
|
|
|
95
|
+
private async executeConnect(
|
|
96
|
+
network: Network,
|
|
97
|
+
provider: TonConnectWalletProvider,
|
|
98
|
+
generation: number
|
|
99
|
+
): Promise<ConnectorResult> {
|
|
84
100
|
try {
|
|
85
101
|
const { TonConnect, toUserFriendlyAddress, UserRejectsError } =
|
|
86
102
|
await import('@tonconnect/sdk')
|
|
103
|
+
|
|
104
|
+
if (this.connectGeneration !== generation) {
|
|
105
|
+
throw new Error('TON Connect connection was cancelled')
|
|
106
|
+
}
|
|
107
|
+
|
|
87
108
|
this.toUserFriendlyAddressFn = toUserFriendlyAddress
|
|
88
109
|
this.UserRejectsErrorClass = UserRejectsError
|
|
89
110
|
|
|
@@ -147,7 +168,6 @@ export class TonConnectConnector implements Connector {
|
|
|
147
168
|
|
|
148
169
|
this.connectedAddress = friendlyAddress
|
|
149
170
|
this.connectedNetworkId = network.id
|
|
150
|
-
this.setupVisibilityHandler(sdkRef)
|
|
151
171
|
|
|
152
172
|
const availableAddresses = this.buildAvailableAddresses(
|
|
153
173
|
provider.supportedNetworkIds,
|
|
@@ -163,7 +183,10 @@ export class TonConnectConnector implements Connector {
|
|
|
163
183
|
availableAddresses
|
|
164
184
|
}
|
|
165
185
|
} finally {
|
|
166
|
-
this.
|
|
186
|
+
if (this.connectGeneration === generation) {
|
|
187
|
+
this.connectInFlight = false
|
|
188
|
+
this.pendingConnectPromise = null
|
|
189
|
+
}
|
|
167
190
|
}
|
|
168
191
|
}
|
|
169
192
|
|
|
@@ -182,7 +205,20 @@ export class TonConnectConnector implements Connector {
|
|
|
182
205
|
}
|
|
183
206
|
|
|
184
207
|
const payload = buildSignDataPayload(message)
|
|
185
|
-
|
|
208
|
+
try {
|
|
209
|
+
return await executeSignData(this.sdk, payload)
|
|
210
|
+
} catch (error) {
|
|
211
|
+
if (
|
|
212
|
+
this.UserRejectsErrorClass &&
|
|
213
|
+
error instanceof this.UserRejectsErrorClass
|
|
214
|
+
) {
|
|
215
|
+
throw new WalletConnectorError({
|
|
216
|
+
type: 'rejected',
|
|
217
|
+
message: 'Message signing was rejected by the user'
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
throw error
|
|
221
|
+
}
|
|
186
222
|
}
|
|
187
223
|
|
|
188
224
|
/** Send a TON transaction via the connected wallet. */
|
|
@@ -206,7 +242,10 @@ export class TonConnectConnector implements Connector {
|
|
|
206
242
|
this.UserRejectsErrorClass &&
|
|
207
243
|
error instanceof this.UserRejectsErrorClass
|
|
208
244
|
) {
|
|
209
|
-
throw new
|
|
245
|
+
throw new WalletConnectorError({
|
|
246
|
+
type: 'rejected',
|
|
247
|
+
message: 'Transaction was rejected by the user'
|
|
248
|
+
})
|
|
210
249
|
}
|
|
211
250
|
throw error
|
|
212
251
|
}
|
|
@@ -222,6 +261,9 @@ export class TonConnectConnector implements Connector {
|
|
|
222
261
|
|
|
223
262
|
/** Disconnect from the wallet and clean up resources. */
|
|
224
263
|
async disconnect(): Promise<void> {
|
|
264
|
+
this.abortPendingConnect?.()
|
|
265
|
+
this.abortPendingConnect = null
|
|
266
|
+
this.pendingConnectPromise = null
|
|
225
267
|
if (this.sdk) {
|
|
226
268
|
try {
|
|
227
269
|
await this.sdk.disconnect()
|
|
@@ -235,7 +277,6 @@ export class TonConnectConnector implements Connector {
|
|
|
235
277
|
this.connectionURI = undefined
|
|
236
278
|
this.connectInFlight = false
|
|
237
279
|
this.clearConnectTimeout()
|
|
238
|
-
this.removeVisibilityHandler()
|
|
239
280
|
}
|
|
240
281
|
|
|
241
282
|
/** Tear down SDK, event listeners, and pending timeouts. */
|
|
@@ -260,9 +301,18 @@ export class TonConnectConnector implements Connector {
|
|
|
260
301
|
this.cleanupSdk(sdk)
|
|
261
302
|
}
|
|
262
303
|
|
|
304
|
+
this.abortPendingConnect = () => {
|
|
305
|
+
if (settled) return
|
|
306
|
+
settled = true
|
|
307
|
+
this.clearConnectTimeout()
|
|
308
|
+
unsubscribe?.()
|
|
309
|
+
reject(new Error('TON Connect connection was cancelled'))
|
|
310
|
+
}
|
|
311
|
+
|
|
263
312
|
this.connectTimeout = setTimeout(() => {
|
|
264
313
|
if (settled) return
|
|
265
314
|
settled = true
|
|
315
|
+
this.abortPendingConnect = null
|
|
266
316
|
cleanup()
|
|
267
317
|
reject(new Error('TON wallet connection timed out'))
|
|
268
318
|
}, CONNECT_TIMEOUT_MS)
|
|
@@ -271,6 +321,7 @@ export class TonConnectConnector implements Connector {
|
|
|
271
321
|
wallet => {
|
|
272
322
|
if (settled) return
|
|
273
323
|
settled = true
|
|
324
|
+
this.abortPendingConnect = null
|
|
274
325
|
if (wallet?.account?.address) {
|
|
275
326
|
this.clearConnectTimeout()
|
|
276
327
|
unsubscribe()
|
|
@@ -288,6 +339,7 @@ export class TonConnectConnector implements Connector {
|
|
|
288
339
|
error => {
|
|
289
340
|
if (settled) return
|
|
290
341
|
settled = true
|
|
342
|
+
this.abortPendingConnect = null
|
|
291
343
|
const errorObj = error as TonConnectErrorEvent
|
|
292
344
|
const message =
|
|
293
345
|
errorObj?.payload?.message ||
|
|
@@ -325,25 +377,6 @@ export class TonConnectConnector implements Connector {
|
|
|
325
377
|
}
|
|
326
378
|
}
|
|
327
379
|
|
|
328
|
-
private setupVisibilityHandler(sdk: ITonConnect): void {
|
|
329
|
-
this.removeVisibilityHandler()
|
|
330
|
-
this.visibilityHandler = () => {
|
|
331
|
-
if (document.visibilityState === 'visible') {
|
|
332
|
-
sdk.unPauseConnection()
|
|
333
|
-
} else {
|
|
334
|
-
sdk.pauseConnection()
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
document.addEventListener('visibilitychange', this.visibilityHandler)
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
private removeVisibilityHandler(): void {
|
|
341
|
-
if (this.visibilityHandler) {
|
|
342
|
-
document.removeEventListener('visibilitychange', this.visibilityHandler)
|
|
343
|
-
this.visibilityHandler = null
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
380
|
private clearConnectTimeout(): void {
|
|
348
381
|
if (this.connectTimeout) {
|
|
349
382
|
clearTimeout(this.connectTimeout)
|