@tonconnect/sdk 3.0.0 → 3.0.1-beta.1

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/lib/esm/index.mjs CHANGED
@@ -343,6 +343,217 @@ function encodeTelegramUrlParameters(parameters) {
343
343
  .replaceAll('%', '--');
344
344
  }
345
345
 
346
+ /**
347
+ * Delays the execution of code for a specified number of milliseconds.
348
+ * @param {number} timeout - The number of milliseconds to delay the execution.
349
+ * @param {DelayOptions} [options] - Optional configuration options for the delay.
350
+ * @return {Promise<void>} - A promise that resolves after the specified delay, or rejects if the delay is aborted.
351
+ */
352
+ function delay(timeout, options) {
353
+ return __awaiter(this, void 0, void 0, function* () {
354
+ return new Promise((resolve, reject) => {
355
+ var _a, _b;
356
+ if ((_a = options === null || options === void 0 ? void 0 : options.signal) === null || _a === void 0 ? void 0 : _a.aborted) {
357
+ reject(new TonConnectError('Delay aborted'));
358
+ return;
359
+ }
360
+ const timeoutId = setTimeout(() => resolve(), timeout);
361
+ (_b = options === null || options === void 0 ? void 0 : options.signal) === null || _b === void 0 ? void 0 : _b.addEventListener('abort', () => {
362
+ clearTimeout(timeoutId);
363
+ reject(new TonConnectError('Delay aborted'));
364
+ });
365
+ });
366
+ });
367
+ }
368
+
369
+ /**
370
+ * Creates an AbortController instance with an optional AbortSignal.
371
+ *
372
+ * @param {AbortSignal} [signal] - An optional AbortSignal to use for aborting the controller.
373
+ * @returns {AbortController} - An instance of AbortController.
374
+ */
375
+ function createAbortController(signal) {
376
+ const abortController = new AbortController();
377
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
378
+ abortController.abort();
379
+ }
380
+ else {
381
+ signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', () => abortController.abort(), { once: true });
382
+ }
383
+ return abortController;
384
+ }
385
+
386
+ /**
387
+ * Function to call ton api until we get response.
388
+ * Because ton network is pretty unstable we need to make sure response is final.
389
+ * @param {T} fn - function to call
390
+ * @param {CallForSuccessOptions} [options] - optional configuration options
391
+ */
392
+ function callForSuccess(fn, options) {
393
+ var _a, _b;
394
+ return __awaiter(this, void 0, void 0, function* () {
395
+ const attempts = (_a = options === null || options === void 0 ? void 0 : options.attempts) !== null && _a !== void 0 ? _a : 10;
396
+ const delayMs = (_b = options === null || options === void 0 ? void 0 : options.delayMs) !== null && _b !== void 0 ? _b : 200;
397
+ const abortController = createAbortController(options === null || options === void 0 ? void 0 : options.signal);
398
+ if (typeof fn !== 'function') {
399
+ throw new TonConnectError(`Expected a function, got ${typeof fn}`);
400
+ }
401
+ let i = 0;
402
+ let lastError;
403
+ while (i < attempts) {
404
+ if (abortController.signal.aborted) {
405
+ throw new TonConnectError(`Aborted after attempts ${i}`);
406
+ }
407
+ try {
408
+ return yield fn({ signal: abortController.signal });
409
+ }
410
+ catch (err) {
411
+ lastError = err;
412
+ i++;
413
+ if (i < attempts) {
414
+ yield delay(delayMs);
415
+ }
416
+ }
417
+ }
418
+ throw lastError;
419
+ });
420
+ }
421
+
422
+ function logDebug(...args) {
423
+ {
424
+ try {
425
+ console.debug('[TON_CONNECT_SDK]', ...args);
426
+ }
427
+ catch (_a) { }
428
+ }
429
+ }
430
+ function logError(...args) {
431
+ {
432
+ try {
433
+ console.error('[TON_CONNECT_SDK]', ...args);
434
+ }
435
+ catch (_a) { }
436
+ }
437
+ }
438
+ function logWarning(...args) {
439
+ {
440
+ try {
441
+ console.warn('[TON_CONNECT_SDK]', ...args);
442
+ }
443
+ catch (_a) { }
444
+ }
445
+ }
446
+
447
+ /**
448
+ * Create a resource.
449
+ *
450
+ * @template T - The type of the resource.
451
+ * @template Args - The type of the arguments for creating the resource.
452
+ *
453
+ * @param {(...args: Args) => Promise<T>} createFn - A function that creates the resource.
454
+ * @param {(resource: T) => Promise<void>} [disposeFn] - An optional function that disposes the resource.
455
+ */
456
+ function createResource(createFn, disposeFn) {
457
+ let currentResource = null;
458
+ let currentArgs = null;
459
+ let currentPromise = null;
460
+ let currentSignal = null;
461
+ let abortController = null;
462
+ // create a new resource
463
+ const create = (signal, ...args) => __awaiter(this, void 0, void 0, function* () {
464
+ currentSignal = signal !== null && signal !== void 0 ? signal : null;
465
+ abortController === null || abortController === void 0 ? void 0 : abortController.abort();
466
+ abortController = createAbortController(signal);
467
+ if (abortController.signal.aborted) {
468
+ throw new TonConnectError('Resource creation was aborted');
469
+ }
470
+ currentArgs = args !== null && args !== void 0 ? args : null;
471
+ const promise = createFn(signal, ...args);
472
+ currentPromise = promise;
473
+ const resource = yield promise;
474
+ if (currentPromise !== promise) {
475
+ yield disposeFn(resource);
476
+ throw new TonConnectError('Resource creation was aborted by a new resource creation');
477
+ }
478
+ currentResource = resource;
479
+ return currentResource;
480
+ });
481
+ // get the current resource
482
+ const current = () => {
483
+ return currentResource !== null && currentResource !== void 0 ? currentResource : null;
484
+ };
485
+ // dispose the current resource
486
+ const dispose = () => __awaiter(this, void 0, void 0, function* () {
487
+ try {
488
+ const resource = currentResource;
489
+ currentResource = null;
490
+ const promise = currentPromise;
491
+ currentPromise = null;
492
+ abortController === null || abortController === void 0 ? void 0 : abortController.abort();
493
+ yield Promise.allSettled([
494
+ resource ? disposeFn(resource) : Promise.resolve(),
495
+ promise ? disposeFn(yield promise) : Promise.resolve()
496
+ ]);
497
+ }
498
+ catch (e) {
499
+ logError('Failed to dispose the resource', e);
500
+ }
501
+ });
502
+ // recreate the current resource
503
+ const recreate = (delayMs) => __awaiter(this, void 0, void 0, function* () {
504
+ const resource = currentResource;
505
+ const promise = currentPromise;
506
+ const args = currentArgs;
507
+ const signal = currentSignal;
508
+ yield delay(delayMs);
509
+ if (resource === currentResource &&
510
+ promise === currentPromise &&
511
+ args === currentArgs &&
512
+ signal === currentSignal) {
513
+ return create(currentSignal, ...(args !== null && args !== void 0 ? args : []));
514
+ }
515
+ throw new TonConnectError('Resource recreation was aborted by a new resource creation');
516
+ });
517
+ return {
518
+ create,
519
+ current,
520
+ dispose,
521
+ recreate
522
+ };
523
+ }
524
+
525
+ /**
526
+ * Executes a function and provides deferred behavior, allowing for a timeout and abort functionality.
527
+ *
528
+ * @param {Deferrable<T>} fn - The function to execute. It should return a promise that resolves with the desired result.
529
+ * @param {DeferOptions} options - Optional configuration options for the defer behavior.
530
+ * @returns {Promise<T>} - A promise that resolves with the result of the executed function, or rejects with an error if it times out or is aborted.
531
+ */
532
+ function timeout(fn, options) {
533
+ const timeout = options === null || options === void 0 ? void 0 : options.timeout;
534
+ const signal = options === null || options === void 0 ? void 0 : options.signal;
535
+ const abortController = createAbortController(signal);
536
+ return new Promise((resolve, reject) => {
537
+ if (abortController.signal.aborted) {
538
+ reject(new TonConnectError('Operation aborted'));
539
+ return;
540
+ }
541
+ let timeoutId;
542
+ if (typeof timeout !== 'undefined') {
543
+ timeoutId = setTimeout(() => {
544
+ abortController.abort();
545
+ reject(new TonConnectError(`Timeout after ${timeout}ms`));
546
+ }, timeout);
547
+ }
548
+ abortController.signal.addEventListener('abort', () => {
549
+ clearTimeout(timeoutId);
550
+ reject(new TonConnectError('Operation aborted'));
551
+ }, { once: true });
552
+ const deferOptions = { timeout, abort: abortController.signal };
553
+ fn(resolve, reject, deferOptions).finally(() => clearTimeout(timeoutId));
554
+ });
555
+ }
556
+
346
557
  class BridgeGateway {
347
558
  constructor(storage, bridgeUrl, sessionId, listener, errorsListener) {
348
559
  this.bridgeUrl = bridgeUrl;
@@ -353,67 +564,85 @@ class BridgeGateway {
353
564
  this.postPath = 'message';
354
565
  this.heartbeatMessage = 'heartbeat';
355
566
  this.defaultTtl = 300;
356
- this.isClosed = false;
567
+ this.defaultReconnectDelay = 5000;
568
+ this.eventSource = createResource((signal, openingDeadlineMS) => __awaiter(this, void 0, void 0, function* () {
569
+ const eventSourceConfig = {
570
+ bridgeUrl: this.bridgeUrl,
571
+ ssePath: this.ssePath,
572
+ sessionId: this.sessionId,
573
+ bridgeGatewayStorage: this.bridgeGatewayStorage,
574
+ errorHandler: this.errorsHandler.bind(this),
575
+ messageHandler: this.messagesHandler.bind(this),
576
+ signal: signal,
577
+ openingDeadlineMS: openingDeadlineMS
578
+ };
579
+ return yield createEventSource(eventSourceConfig);
580
+ }), (resource) => __awaiter(this, void 0, void 0, function* () {
581
+ resource.close();
582
+ }));
357
583
  this.bridgeGatewayStorage = new HttpBridgeGatewayStorage(storage, bridgeUrl);
358
584
  }
585
+ get isReady() {
586
+ const eventSource = this.eventSource.current();
587
+ return (eventSource === null || eventSource === void 0 ? void 0 : eventSource.readyState) === EventSource.OPEN;
588
+ }
589
+ get isClosed() {
590
+ const eventSource = this.eventSource.current();
591
+ return (eventSource === null || eventSource === void 0 ? void 0 : eventSource.readyState) !== EventSource.OPEN;
592
+ }
593
+ get isConnecting() {
594
+ const eventSource = this.eventSource.current();
595
+ return (eventSource === null || eventSource === void 0 ? void 0 : eventSource.readyState) === EventSource.CONNECTING;
596
+ }
359
597
  registerSession(options) {
360
598
  return __awaiter(this, void 0, void 0, function* () {
361
- const url = new URL(addPathToUrl(this.bridgeUrl, this.ssePath));
362
- url.searchParams.append('client_id', this.sessionId);
363
- const lastEventId = yield this.bridgeGatewayStorage.getLastEventId();
364
- if (this.isClosed) {
365
- return;
366
- }
367
- if (lastEventId) {
368
- url.searchParams.append('last_event_id', lastEventId);
369
- }
370
- this.eventSource = new EventSource(url.toString());
371
- return new Promise((resolve, reject) => {
372
- const timeout = (options === null || options === void 0 ? void 0 : options.openingDeadlineMS) ? setTimeout(() => {
373
- var _a;
374
- if (((_a = this.eventSource) === null || _a === void 0 ? void 0 : _a.readyState) !== EventSource.OPEN) {
375
- reject(new TonConnectError('Bridge connection timeout'));
376
- this.close();
377
- }
378
- }, options.openingDeadlineMS) : undefined;
379
- this.eventSource.onerror = () => reject;
380
- this.eventSource.onopen = () => {
381
- clearTimeout(timeout);
382
- this.isClosed = false;
383
- this.eventSource.onerror = this.errorsHandler.bind(this);
384
- this.eventSource.onmessage = this.messagesHandler.bind(this);
385
- resolve();
386
- };
387
- });
599
+ yield this.eventSource.create(options === null || options === void 0 ? void 0 : options.signal, options === null || options === void 0 ? void 0 : options.openingDeadlineMS);
388
600
  });
389
601
  }
390
- send(message, receiver, topic, ttl) {
602
+ send(message, receiver, topic, ttlOrOptions) {
603
+ var _a;
391
604
  return __awaiter(this, void 0, void 0, function* () {
605
+ // TODO: remove deprecated method
606
+ const options = {};
607
+ if (typeof ttlOrOptions === 'number') {
608
+ options.ttl = ttlOrOptions;
609
+ }
610
+ else {
611
+ options.ttl = ttlOrOptions === null || ttlOrOptions === void 0 ? void 0 : ttlOrOptions.ttl;
612
+ options.signal = ttlOrOptions === null || ttlOrOptions === void 0 ? void 0 : ttlOrOptions.signal;
613
+ options.attempts = ttlOrOptions === null || ttlOrOptions === void 0 ? void 0 : ttlOrOptions.attempts;
614
+ }
392
615
  const url = new URL(addPathToUrl(this.bridgeUrl, this.postPath));
393
616
  url.searchParams.append('client_id', this.sessionId);
394
617
  url.searchParams.append('to', receiver);
395
- url.searchParams.append('ttl', (ttl || this.defaultTtl).toString());
618
+ url.searchParams.append('ttl', ((options === null || options === void 0 ? void 0 : options.ttl) || this.defaultTtl).toString());
396
619
  url.searchParams.append('topic', topic);
397
- const response = yield fetch(url, {
398
- method: 'post',
399
- body: Base64.encode(message)
620
+ const body = Base64.encode(message);
621
+ yield callForSuccess((options) => __awaiter(this, void 0, void 0, function* () {
622
+ const response = yield this.post(url, body, options.signal);
623
+ if (!response.ok) {
624
+ throw new TonConnectError(`Bridge send failed, status ${response.status}`);
625
+ }
626
+ }), {
627
+ attempts: (_a = options === null || options === void 0 ? void 0 : options.attempts) !== null && _a !== void 0 ? _a : Number.MAX_SAFE_INTEGER,
628
+ delayMs: 5000,
629
+ signal: options === null || options === void 0 ? void 0 : options.signal
400
630
  });
401
- if (!response.ok) {
402
- throw new TonConnectError(`Bridge send failed, status ${response.status}`);
403
- }
404
631
  });
405
632
  }
406
633
  pause() {
407
- var _a;
408
- (_a = this.eventSource) === null || _a === void 0 ? void 0 : _a.close();
634
+ this.eventSource.dispose().catch(e => logError(`Bridge pause failed, ${e}`));
409
635
  }
410
636
  unPause() {
411
- return this.registerSession();
637
+ return __awaiter(this, void 0, void 0, function* () {
638
+ const RECREATE_WITHOUT_DELAY = 0;
639
+ yield this.eventSource.recreate(RECREATE_WITHOUT_DELAY);
640
+ });
412
641
  }
413
642
  close() {
414
- var _a;
415
- this.isClosed = true;
416
- (_a = this.eventSource) === null || _a === void 0 ? void 0 : _a.close();
643
+ return __awaiter(this, void 0, void 0, function* () {
644
+ yield this.eventSource.dispose().catch(e => logError(`Bridge close failed, ${e}`));
645
+ });
417
646
  }
418
647
  setListener(listener) {
419
648
  this.listener = listener;
@@ -421,20 +650,38 @@ class BridgeGateway {
421
650
  setErrorsListener(errorsListener) {
422
651
  this.errorsListener = errorsListener;
423
652
  }
424
- errorsHandler(e) {
425
- var _a, _b;
426
- if (!this.isClosed) {
427
- if (((_a = this.eventSource) === null || _a === void 0 ? void 0 : _a.readyState) === EventSource.CLOSED) {
428
- this.eventSource.close();
429
- this.registerSession();
430
- return;
653
+ post(url, body, signal) {
654
+ return __awaiter(this, void 0, void 0, function* () {
655
+ const response = yield fetch(url, {
656
+ method: 'post',
657
+ body: body,
658
+ signal: signal
659
+ });
660
+ if (!response.ok) {
661
+ throw new TonConnectError(`Bridge send failed, status ${response.status}`);
431
662
  }
432
- if (((_b = this.eventSource) === null || _b === void 0 ? void 0 : _b.readyState) === EventSource.CONNECTING) {
433
- console.debug('[TON_CONNET_SDK_ERROR]: Bridge error', JSON.stringify(e));
434
- return;
663
+ return response;
664
+ });
665
+ }
666
+ errorsHandler(e) {
667
+ return __awaiter(this, void 0, void 0, function* () {
668
+ try {
669
+ if (this.isConnecting) {
670
+ logError('Bridge error', JSON.stringify(e));
671
+ return;
672
+ }
673
+ if (this.isReady) {
674
+ this.errorsListener(e);
675
+ return;
676
+ }
677
+ if (this.isClosed) {
678
+ logDebug(`Bridge reconnecting, ${this.defaultReconnectDelay}ms delay`);
679
+ yield this.eventSource.recreate(this.defaultReconnectDelay);
680
+ return;
681
+ }
435
682
  }
436
- this.errorsListener(e);
437
- }
683
+ catch (e) { }
684
+ });
438
685
  }
439
686
  messagesHandler(e) {
440
687
  return __awaiter(this, void 0, void 0, function* () {
@@ -442,19 +689,71 @@ class BridgeGateway {
442
689
  return;
443
690
  }
444
691
  yield this.bridgeGatewayStorage.storeLastEventId(e.lastEventId);
445
- if (!this.isClosed) {
446
- let bridgeIncomingMessage;
447
- try {
448
- bridgeIncomingMessage = JSON.parse(e.data);
449
- }
450
- catch (e) {
451
- throw new TonConnectError(`Bridge message parse failed, message ${e.data}`);
452
- }
453
- this.listener(bridgeIncomingMessage);
692
+ if (this.isClosed) {
693
+ return;
694
+ }
695
+ let bridgeIncomingMessage;
696
+ try {
697
+ bridgeIncomingMessage = JSON.parse(e.data);
454
698
  }
699
+ catch (e) {
700
+ throw new TonConnectError(`Bridge message parse failed, message ${e.data}`);
701
+ }
702
+ this.listener(bridgeIncomingMessage);
455
703
  });
456
704
  }
457
705
  }
706
+ /**
707
+ * Creates an event source.
708
+ * @param {CreateEventSourceConfig} config - Configuration for creating an event source.
709
+ */
710
+ function createEventSource(config) {
711
+ return __awaiter(this, void 0, void 0, function* () {
712
+ return yield timeout((resolve, reject, deferOptions) => __awaiter(this, void 0, void 0, function* () {
713
+ var _a;
714
+ const abortController = createAbortController(deferOptions.signal);
715
+ const signal = abortController.signal;
716
+ if (signal.aborted) {
717
+ reject(new TonConnectError('Bridge connection aborted'));
718
+ return;
719
+ }
720
+ const url = new URL(addPathToUrl(config.bridgeUrl, config.ssePath));
721
+ url.searchParams.append('client_id', config.sessionId);
722
+ const lastEventId = yield config.bridgeGatewayStorage.getLastEventId();
723
+ if (lastEventId) {
724
+ url.searchParams.append('last_event_id', lastEventId);
725
+ }
726
+ if (signal.aborted) {
727
+ reject(new TonConnectError('Bridge connection aborted'));
728
+ return;
729
+ }
730
+ const eventSource = new EventSource(url.toString());
731
+ eventSource.onerror = (reason) => {
732
+ if (signal.aborted) {
733
+ eventSource.close();
734
+ reject(new TonConnectError('Bridge connection aborted'));
735
+ return;
736
+ }
737
+ config.errorHandler(reason);
738
+ };
739
+ eventSource.onopen = () => {
740
+ if (signal.aborted) {
741
+ eventSource.close();
742
+ reject(new TonConnectError('Bridge connection aborted'));
743
+ return;
744
+ }
745
+ resolve(eventSource);
746
+ };
747
+ eventSource.onmessage = (event) => {
748
+ config.messageHandler(event);
749
+ };
750
+ (_a = config.signal) === null || _a === void 0 ? void 0 : _a.addEventListener('abort', () => {
751
+ eventSource.close();
752
+ reject(new TonConnectError('Bridge connection aborted'));
753
+ });
754
+ }), { timeout: config.openingDeadlineMS, signal: config.signal });
755
+ });
756
+ }
458
757
 
459
758
  function isPendingConnectionHttp(connection) {
460
759
  return !('connectEvent' in connection);
@@ -619,31 +918,6 @@ class BridgeConnectionStorage {
619
918
 
620
919
  const PROTOCOL_VERSION = 2;
621
920
 
622
- function logDebug(...args) {
623
- {
624
- try {
625
- console.debug('[TON_CONNECT_SDK]', ...args);
626
- }
627
- catch (_a) { }
628
- }
629
- }
630
- function logError(...args) {
631
- {
632
- try {
633
- console.error('[TON_CONNECT_SDK]', ...args);
634
- }
635
- catch (_a) { }
636
- }
637
- }
638
- function logWarning(...args) {
639
- {
640
- try {
641
- console.warn('[TON_CONNECT_SDK]', ...args);
642
- }
643
- catch (_a) { }
644
- }
645
- }
646
-
647
921
  class BridgeProvider {
648
922
  constructor(storage, walletConnectionSource) {
649
923
  this.storage = storage;
@@ -655,6 +929,7 @@ class BridgeProvider {
655
929
  this.gateway = null;
656
930
  this.pendingGateways = [];
657
931
  this.listeners = [];
932
+ this.defaultOpeningDeadlineMS = 5000;
658
933
  this.connectionStorage = new BridgeConnectionStorage(storage);
659
934
  }
660
935
  static fromStorage(storage) {
@@ -667,7 +942,11 @@ class BridgeProvider {
667
942
  return new BridgeProvider(storage, { bridgeUrl: connection.session.bridgeUrl });
668
943
  });
669
944
  }
670
- connect(message) {
945
+ connect(message, options) {
946
+ var _a;
947
+ const abortController = createAbortController(options === null || options === void 0 ? void 0 : options.signal);
948
+ (_a = this.abortController) === null || _a === void 0 ? void 0 : _a.abort();
949
+ this.abortController = abortController;
671
950
  this.closeGateways();
672
951
  const sessionCrypto = new SessionCrypto();
673
952
  this.session = {
@@ -682,20 +961,43 @@ class BridgeProvider {
682
961
  connectionSource: this.walletConnectionSource,
683
962
  sessionCrypto
684
963
  })
685
- .then(() => this.openGateways(sessionCrypto));
964
+ .then(() => __awaiter(this, void 0, void 0, function* () {
965
+ if (abortController.signal.aborted) {
966
+ return;
967
+ }
968
+ yield callForSuccess(_options => this.openGateways(sessionCrypto, {
969
+ openingDeadlineMS: options === null || options === void 0 ? void 0 : options.openingDeadlineMS,
970
+ signal: _options === null || _options === void 0 ? void 0 : _options.signal
971
+ }), {
972
+ attempts: Number.MAX_SAFE_INTEGER,
973
+ delayMs: 5000,
974
+ signal: abortController.signal
975
+ });
976
+ }));
686
977
  const universalLink = 'universalLink' in this.walletConnectionSource &&
687
978
  this.walletConnectionSource.universalLink
688
979
  ? this.walletConnectionSource.universalLink
689
980
  : this.standardUniversalLink;
690
981
  return this.generateUniversalLink(universalLink, message);
691
982
  }
692
- restoreConnection() {
983
+ restoreConnection(options) {
984
+ var _a, _b;
693
985
  return __awaiter(this, void 0, void 0, function* () {
986
+ const abortController = createAbortController(options === null || options === void 0 ? void 0 : options.signal);
987
+ (_a = this.abortController) === null || _a === void 0 ? void 0 : _a.abort();
988
+ this.abortController = abortController;
989
+ if (abortController.signal.aborted) {
990
+ return;
991
+ }
694
992
  this.closeGateways();
695
993
  const storedConnection = yield this.connectionStorage.getHttpConnection();
696
994
  if (!storedConnection) {
697
995
  return;
698
996
  }
997
+ if (abortController.signal.aborted) {
998
+ return;
999
+ }
1000
+ const openingDeadlineMS = (_b = options === null || options === void 0 ? void 0 : options.openingDeadlineMS) !== null && _b !== void 0 ? _b : this.defaultOpeningDeadlineMS;
699
1001
  if (isPendingConnectionHttp(storedConnection)) {
700
1002
  this.session = {
701
1003
  sessionCrypto: storedConnection.sessionCrypto,
@@ -703,25 +1005,55 @@ class BridgeProvider {
703
1005
  ? this.walletConnectionSource.bridgeUrl
704
1006
  : ''
705
1007
  };
706
- return this.openGateways(storedConnection.sessionCrypto, { openingDeadlineMS: 5000 });
1008
+ return yield this.openGateways(storedConnection.sessionCrypto, {
1009
+ openingDeadlineMS: openingDeadlineMS,
1010
+ signal: abortController === null || abortController === void 0 ? void 0 : abortController.signal
1011
+ });
707
1012
  }
708
1013
  if (Array.isArray(this.walletConnectionSource)) {
709
1014
  throw new TonConnectError('Internal error. Connection source is array while WalletConnectionSourceHTTP was expected.');
710
1015
  }
711
1016
  this.session = storedConnection.session;
1017
+ if (this.gateway) {
1018
+ logDebug('Gateway is already opened, closing previous gateway');
1019
+ yield this.gateway.close();
1020
+ }
712
1021
  this.gateway = new BridgeGateway(this.storage, this.walletConnectionSource.bridgeUrl, storedConnection.session.sessionCrypto.sessionId, this.gatewayListener.bind(this), this.gatewayErrorsListener.bind(this));
1022
+ if (abortController.signal.aborted) {
1023
+ return;
1024
+ }
1025
+ // notify listeners about stored connection
1026
+ this.listeners.forEach(listener => listener(storedConnection.connectEvent));
1027
+ // wait for the connection to be opened
713
1028
  try {
714
- yield this.gateway.registerSession({ openingDeadlineMS: 5000 });
1029
+ yield callForSuccess(options => this.gateway.registerSession({
1030
+ openingDeadlineMS: openingDeadlineMS,
1031
+ signal: options.signal
1032
+ }), {
1033
+ attempts: Number.MAX_SAFE_INTEGER,
1034
+ delayMs: 5000,
1035
+ signal: abortController.signal
1036
+ });
715
1037
  }
716
1038
  catch (e) {
717
- yield this.disconnect();
1039
+ yield this.disconnect({ signal: abortController.signal });
718
1040
  return;
719
1041
  }
720
- this.listeners.forEach(listener => listener(storedConnection.connectEvent));
721
1042
  });
722
1043
  }
723
- sendRequest(request, onRequestSent) {
1044
+ sendRequest(request, optionsOrOnRequestSent) {
1045
+ // TODO: remove deprecated method
1046
+ const options = {};
1047
+ if (typeof optionsOrOnRequestSent === 'function') {
1048
+ options.onRequestSent = optionsOrOnRequestSent;
1049
+ }
1050
+ else {
1051
+ options.onRequestSent = optionsOrOnRequestSent === null || optionsOrOnRequestSent === void 0 ? void 0 : optionsOrOnRequestSent.onRequestSent;
1052
+ options.signal = optionsOrOnRequestSent === null || optionsOrOnRequestSent === void 0 ? void 0 : optionsOrOnRequestSent.signal;
1053
+ options.attempts = optionsOrOnRequestSent === null || optionsOrOnRequestSent === void 0 ? void 0 : optionsOrOnRequestSent.attempts;
1054
+ }
724
1055
  return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
1056
+ var _a;
725
1057
  if (!this.gateway || !this.session || !('walletPublicKey' in this.session)) {
726
1058
  throw new TonConnectError('Trying to send bridge request without session');
727
1059
  }
@@ -730,8 +1062,8 @@ class BridgeProvider {
730
1062
  logDebug('Send http-bridge request:', Object.assign(Object.assign({}, request), { id }));
731
1063
  const encodedRequest = this.session.sessionCrypto.encrypt(JSON.stringify(Object.assign(Object.assign({}, request), { id })), hexToByteArray(this.session.walletPublicKey));
732
1064
  try {
733
- yield this.gateway.send(encodedRequest, this.session.walletPublicKey, request.method);
734
- onRequestSent === null || onRequestSent === void 0 ? void 0 : onRequestSent();
1065
+ yield this.gateway.send(encodedRequest, this.session.walletPublicKey, request.method, { attempts: options === null || options === void 0 ? void 0 : options.attempts, signal: options === null || options === void 0 ? void 0 : options.signal });
1066
+ (_a = options === null || options === void 0 ? void 0 : options.onRequestSent) === null || _a === void 0 ? void 0 : _a.call(options);
735
1067
  this.pendingRequests.set(id.toString(), resolve);
736
1068
  }
737
1069
  catch (e) {
@@ -745,23 +1077,41 @@ class BridgeProvider {
745
1077
  this.session = null;
746
1078
  this.gateway = null;
747
1079
  }
748
- disconnect() {
1080
+ disconnect(options) {
749
1081
  return __awaiter(this, void 0, void 0, function* () {
750
1082
  return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
751
1083
  let called = false;
1084
+ let timeoutId = null;
752
1085
  const onRequestSent = () => {
753
- called = true;
754
- this.removeBridgeAndSession().then(resolve);
1086
+ if (!called) {
1087
+ called = true;
1088
+ this.removeBridgeAndSession().then(resolve);
1089
+ }
755
1090
  };
756
1091
  try {
757
- yield this.sendRequest({ method: 'disconnect', params: [] }, onRequestSent);
1092
+ this.closeGateways();
1093
+ const abortController = createAbortController(options === null || options === void 0 ? void 0 : options.signal);
1094
+ timeoutId = setTimeout(() => {
1095
+ abortController.abort();
1096
+ }, this.defaultOpeningDeadlineMS);
1097
+ yield this.sendRequest({ method: 'disconnect', params: [] }, {
1098
+ onRequestSent: onRequestSent,
1099
+ signal: abortController.signal,
1100
+ attempts: 1,
1101
+ });
758
1102
  }
759
1103
  catch (e) {
760
- console.debug(e);
1104
+ logDebug('Disconnect error:', e);
761
1105
  if (!called) {
762
1106
  this.removeBridgeAndSession().then(resolve);
763
1107
  }
764
1108
  }
1109
+ finally {
1110
+ if (timeoutId) {
1111
+ clearTimeout(timeoutId);
1112
+ }
1113
+ onRequestSent();
1114
+ }
765
1115
  }));
766
1116
  });
767
1117
  }
@@ -786,10 +1136,14 @@ class BridgeProvider {
786
1136
  pendingGatewaysListener(gateway, bridgeUrl, bridgeIncomingMessage) {
787
1137
  return __awaiter(this, void 0, void 0, function* () {
788
1138
  if (!this.pendingGateways.includes(gateway)) {
789
- gateway.close();
1139
+ yield gateway.close();
790
1140
  return;
791
1141
  }
792
1142
  this.closeGateways({ except: gateway });
1143
+ if (this.gateway) {
1144
+ logDebug('Gateway is already opened, closing previous gateway');
1145
+ yield this.gateway.close();
1146
+ }
793
1147
  this.session.bridgeUrl = bridgeUrl;
794
1148
  this.gateway = gateway;
795
1149
  this.gateway.setErrorsListener(this.gatewayErrorsListener.bind(this));
@@ -828,6 +1182,7 @@ class BridgeProvider {
828
1182
  yield this.updateSession(walletMessage, bridgeIncomingMessage.from);
829
1183
  }
830
1184
  if (walletMessage.event === 'disconnect') {
1185
+ logDebug(`Removing bridge and session: received disconnect event`);
831
1186
  yield this.removeBridgeAndSession();
832
1187
  }
833
1188
  listeners.forEach(listener => listener(walletMessage));
@@ -893,6 +1248,9 @@ class BridgeProvider {
893
1248
  openGateways(sessionCrypto, options) {
894
1249
  return __awaiter(this, void 0, void 0, function* () {
895
1250
  if (Array.isArray(this.walletConnectionSource)) {
1251
+ // close all gateways before opening new ones
1252
+ this.pendingGateways.map(bridge => bridge.close().catch(e => console.error(e)));
1253
+ // open new gateways
896
1254
  this.pendingGateways = this.walletConnectionSource.map(source => {
897
1255
  const gateway = new BridgeGateway(this.storage, source.bridgeUrl, sessionCrypto.sessionId, () => { }, e => {
898
1256
  console.error(e);
@@ -900,12 +1258,31 @@ class BridgeProvider {
900
1258
  gateway.setListener(message => this.pendingGatewaysListener(gateway, source.bridgeUrl, message));
901
1259
  return gateway;
902
1260
  });
903
- yield Promise.allSettled(this.pendingGateways.map(bridge => bridge.registerSession(options)));
1261
+ yield Promise.allSettled(this.pendingGateways.map(bridge => callForSuccess((_options) => {
1262
+ if (!this.pendingGateways.some(item => item === bridge)) {
1263
+ return bridge.close();
1264
+ }
1265
+ return bridge.registerSession({
1266
+ openingDeadlineMS: options === null || options === void 0 ? void 0 : options.openingDeadlineMS,
1267
+ signal: _options.signal
1268
+ });
1269
+ }, {
1270
+ attempts: Number.MAX_SAFE_INTEGER,
1271
+ delayMs: 5000,
1272
+ signal: options === null || options === void 0 ? void 0 : options.signal
1273
+ })));
904
1274
  return;
905
1275
  }
906
1276
  else {
1277
+ if (this.gateway) {
1278
+ logDebug(`Gateway is already opened, closing previous gateway`);
1279
+ yield this.gateway.close();
1280
+ }
907
1281
  this.gateway = new BridgeGateway(this.storage, this.walletConnectionSource.bridgeUrl, sessionCrypto.sessionId, this.gatewayListener.bind(this), this.gatewayErrorsListener.bind(this));
908
- return this.gateway.registerSession(options);
1282
+ return yield this.gateway.registerSession({
1283
+ openingDeadlineMS: options === null || options === void 0 ? void 0 : options.openingDeadlineMS,
1284
+ signal: options === null || options === void 0 ? void 0 : options.signal
1285
+ });
909
1286
  }
910
1287
  });
911
1288
  }
@@ -1168,14 +1545,24 @@ class InjectedProvider {
1168
1545
  this.listeners.push(eventsCallback);
1169
1546
  return () => (this.listeners = this.listeners.filter(listener => listener !== eventsCallback));
1170
1547
  }
1171
- sendRequest(request, onRequestSent) {
1548
+ sendRequest(request, optionsOrOnRequestSent) {
1549
+ var _a;
1172
1550
  return __awaiter(this, void 0, void 0, function* () {
1551
+ // TODO: remove deprecated method
1552
+ const options = {};
1553
+ if (typeof optionsOrOnRequestSent === 'function') {
1554
+ options.onRequestSent = optionsOrOnRequestSent;
1555
+ }
1556
+ else {
1557
+ options.onRequestSent = optionsOrOnRequestSent === null || optionsOrOnRequestSent === void 0 ? void 0 : optionsOrOnRequestSent.onRequestSent;
1558
+ options.signal = optionsOrOnRequestSent === null || optionsOrOnRequestSent === void 0 ? void 0 : optionsOrOnRequestSent.signal;
1559
+ }
1173
1560
  const id = (yield this.connectionStorage.getNextRpcRequestId()).toString();
1174
1561
  yield this.connectionStorage.increaseNextRpcRequestId();
1175
1562
  logDebug('Send injected-bridge request:', Object.assign(Object.assign({}, request), { id }));
1176
1563
  const result = this.injectedWallet.send(Object.assign(Object.assign({}, request), { id }));
1177
1564
  result.then(response => logDebug('Wallet message received:', response));
1178
- onRequestSent === null || onRequestSent === void 0 ? void 0 : onRequestSent();
1565
+ (_a = options === null || options === void 0 ? void 0 : options.onRequestSent) === null || _a === void 0 ? void 0 : _a.call(options);
1179
1566
  return result;
1180
1567
  });
1181
1568
  }
@@ -1192,7 +1579,7 @@ class InjectedProvider {
1192
1579
  this.listeners.forEach(listener => listener(connectEvent));
1193
1580
  }
1194
1581
  catch (e) {
1195
- logDebug(e);
1582
+ logDebug('Injected Provider connect error:', e);
1196
1583
  const connectEventError = {
1197
1584
  event: 'connect_error',
1198
1585
  payload: {
@@ -1321,19 +1708,6 @@ const FALLBACK_WALLETS_LIST = [
1321
1708
  ],
1322
1709
  platforms: ['ios', 'android', 'chrome', 'firefox', 'macos']
1323
1710
  },
1324
- {
1325
- app_name: 'openmask',
1326
- name: 'OpenMask',
1327
- image: 'https://raw.githubusercontent.com/OpenProduct/openmask-extension/main/public/openmask-logo-288.png',
1328
- about_url: 'https://www.openmask.app/',
1329
- bridge: [
1330
- {
1331
- type: 'js',
1332
- key: 'openmask'
1333
- }
1334
- ],
1335
- platforms: ['chrome']
1336
- },
1337
1711
  {
1338
1712
  app_name: 'mytonwallet',
1339
1713
  name: 'MyTonWallet',
@@ -1350,7 +1724,20 @@ const FALLBACK_WALLETS_LIST = [
1350
1724
  url: 'https://tonconnectbridge.mytonwallet.org/bridge/'
1351
1725
  }
1352
1726
  ],
1353
- platforms: ['chrome', 'windows', 'macos', 'linux']
1727
+ platforms: ['chrome', 'windows', 'macos', 'linux', 'ios', 'android', 'firefox']
1728
+ },
1729
+ {
1730
+ app_name: 'openmask',
1731
+ name: 'OpenMask',
1732
+ image: 'https://raw.githubusercontent.com/OpenProduct/openmask-extension/main/public/openmask-logo-288.png',
1733
+ about_url: 'https://www.openmask.app/',
1734
+ bridge: [
1735
+ {
1736
+ type: 'js',
1737
+ key: 'openmask'
1738
+ }
1739
+ ],
1740
+ platforms: ['chrome']
1354
1741
  },
1355
1742
  {
1356
1743
  app_name: 'tonhub',
@@ -1370,19 +1757,6 @@ const FALLBACK_WALLETS_LIST = [
1370
1757
  ],
1371
1758
  platforms: ['ios', 'android']
1372
1759
  },
1373
- {
1374
- app_name: 'tonflow',
1375
- name: 'TonFlow',
1376
- image: 'https://tonflow.net/assets/images/tonflow_ico_192.png',
1377
- about_url: 'https://tonflow.net',
1378
- bridge: [
1379
- {
1380
- type: 'js',
1381
- key: 'tonflow'
1382
- }
1383
- ],
1384
- platforms: ['chrome']
1385
- },
1386
1760
  {
1387
1761
  app_name: 'dewallet',
1388
1762
  name: 'DeWallet',
@@ -1689,59 +2063,128 @@ class TonConnect {
1689
2063
  }
1690
2064
  };
1691
2065
  }
1692
- connect(wallet, request) {
1693
- var _a;
2066
+ connect(wallet, requestOrOptions) {
2067
+ var _a, _b;
2068
+ // TODO: remove deprecated method
2069
+ const options = {};
2070
+ if (typeof requestOrOptions === 'object' && 'tonProof' in requestOrOptions) {
2071
+ options.request = requestOrOptions;
2072
+ }
2073
+ if (typeof requestOrOptions === 'object' &&
2074
+ ('openingDeadlineMS' in requestOrOptions ||
2075
+ 'signal' in requestOrOptions ||
2076
+ 'request' in requestOrOptions)) {
2077
+ options.request = requestOrOptions === null || requestOrOptions === void 0 ? void 0 : requestOrOptions.request;
2078
+ options.openingDeadlineMS = requestOrOptions === null || requestOrOptions === void 0 ? void 0 : requestOrOptions.openingDeadlineMS;
2079
+ options.signal = requestOrOptions === null || requestOrOptions === void 0 ? void 0 : requestOrOptions.signal;
2080
+ }
1694
2081
  if (this.connected) {
1695
2082
  throw new WalletAlreadyConnectedError();
1696
2083
  }
1697
- (_a = this.provider) === null || _a === void 0 ? void 0 : _a.closeConnection();
2084
+ const abortController = createAbortController(options === null || options === void 0 ? void 0 : options.signal);
2085
+ (_a = this.abortController) === null || _a === void 0 ? void 0 : _a.abort();
2086
+ this.abortController = abortController;
2087
+ if (abortController.signal.aborted) {
2088
+ throw new TonConnectError('Connection was aborted');
2089
+ }
2090
+ (_b = this.provider) === null || _b === void 0 ? void 0 : _b.closeConnection();
1698
2091
  this.provider = this.createProvider(wallet);
1699
- return this.provider.connect(this.createConnectRequest(request));
2092
+ abortController.signal.addEventListener('abort', () => {
2093
+ var _a;
2094
+ (_a = this.provider) === null || _a === void 0 ? void 0 : _a.closeConnection();
2095
+ this.provider = null;
2096
+ });
2097
+ return this.provider.connect(this.createConnectRequest(options === null || options === void 0 ? void 0 : options.request), {
2098
+ openingDeadlineMS: options === null || options === void 0 ? void 0 : options.openingDeadlineMS,
2099
+ signal: abortController.signal
2100
+ });
1700
2101
  }
1701
2102
  /**
1702
2103
  * Try to restore existing session and reconnect to the corresponding wallet. Call it immediately when your app is loaded.
1703
2104
  */
1704
- restoreConnection() {
2105
+ restoreConnection(options) {
2106
+ var _a, _b;
1705
2107
  return __awaiter(this, void 0, void 0, function* () {
2108
+ const abortController = createAbortController(options === null || options === void 0 ? void 0 : options.signal);
2109
+ (_a = this.abortController) === null || _a === void 0 ? void 0 : _a.abort();
2110
+ this.abortController = abortController;
2111
+ if (abortController.signal.aborted) {
2112
+ return;
2113
+ }
2114
+ // TODO: potentially race condition here
1706
2115
  const [bridgeConnectionType, embeddedWallet] = yield Promise.all([
1707
2116
  this.bridgeConnectionStorage.storedConnectionType(),
1708
2117
  this.walletsList.getEmbeddedWallet()
1709
2118
  ]);
2119
+ if (abortController.signal.aborted) {
2120
+ return;
2121
+ }
2122
+ let provider = null;
1710
2123
  try {
1711
2124
  switch (bridgeConnectionType) {
1712
2125
  case 'http':
1713
- this.provider = yield BridgeProvider.fromStorage(this.dappSettings.storage);
2126
+ provider = yield BridgeProvider.fromStorage(this.dappSettings.storage);
1714
2127
  break;
1715
2128
  case 'injected':
1716
- this.provider = yield InjectedProvider.fromStorage(this.dappSettings.storage);
2129
+ provider = yield InjectedProvider.fromStorage(this.dappSettings.storage);
1717
2130
  break;
1718
2131
  default:
1719
2132
  if (embeddedWallet) {
1720
- this.provider = yield this.createProvider(embeddedWallet);
2133
+ provider = this.createProvider(embeddedWallet);
1721
2134
  }
1722
2135
  else {
1723
2136
  return;
1724
2137
  }
1725
2138
  }
1726
2139
  }
1727
- catch (_a) {
2140
+ catch (_c) {
1728
2141
  yield this.bridgeConnectionStorage.removeConnection();
1729
- this.provider = null;
2142
+ provider === null || provider === void 0 ? void 0 : provider.closeConnection();
2143
+ provider = null;
1730
2144
  return;
1731
2145
  }
1732
- this.provider.listen(this.walletEventsListener.bind(this));
1733
- return this.provider.restoreConnection();
2146
+ if (abortController.signal.aborted) {
2147
+ provider === null || provider === void 0 ? void 0 : provider.closeConnection();
2148
+ return;
2149
+ }
2150
+ if (!provider) {
2151
+ logError('Provider is not restored');
2152
+ return;
2153
+ }
2154
+ (_b = this.provider) === null || _b === void 0 ? void 0 : _b.closeConnection();
2155
+ this.provider = provider;
2156
+ provider.listen(this.walletEventsListener.bind(this));
2157
+ abortController.signal.addEventListener('abort', () => {
2158
+ provider === null || provider === void 0 ? void 0 : provider.closeConnection();
2159
+ provider = null;
2160
+ });
2161
+ return yield callForSuccess((_options) => __awaiter(this, void 0, void 0, function* () {
2162
+ return provider === null || provider === void 0 ? void 0 : provider.restoreConnection({
2163
+ openingDeadlineMS: options === null || options === void 0 ? void 0 : options.openingDeadlineMS,
2164
+ signal: _options.signal
2165
+ });
2166
+ }), {
2167
+ attempts: Number.MAX_SAFE_INTEGER,
2168
+ delayMs: 5000,
2169
+ signal: options === null || options === void 0 ? void 0 : options.signal
2170
+ });
1734
2171
  });
1735
2172
  }
1736
- /**
1737
- * Asks connected wallet to sign and send the transaction.
1738
- * @param transaction transaction to send.
1739
- * @param onRequestSent (optional) will be called after the transaction is sent to the wallet.
1740
- * @returns signed transaction boc that allows you to find the transaction in the blockchain.
1741
- * If user rejects transaction, method will throw the corresponding error.
1742
- */
1743
- sendTransaction(transaction, onRequestSent) {
2173
+ sendTransaction(transaction, optionsOrOnRequestSent) {
1744
2174
  return __awaiter(this, void 0, void 0, function* () {
2175
+ // TODO: remove deprecated method
2176
+ const options = {};
2177
+ if (typeof optionsOrOnRequestSent === 'function') {
2178
+ options.onRequestSent = optionsOrOnRequestSent;
2179
+ }
2180
+ else {
2181
+ options.onRequestSent = optionsOrOnRequestSent === null || optionsOrOnRequestSent === void 0 ? void 0 : optionsOrOnRequestSent.onRequestSent;
2182
+ options.signal = optionsOrOnRequestSent === null || optionsOrOnRequestSent === void 0 ? void 0 : optionsOrOnRequestSent.signal;
2183
+ }
2184
+ const abortController = createAbortController(options === null || options === void 0 ? void 0 : options.signal);
2185
+ if (abortController.signal.aborted) {
2186
+ throw new TonConnectError('Transaction sending was aborted');
2187
+ }
1745
2188
  this.checkConnection();
1746
2189
  checkSendTransactionSupport(this.wallet.device.features, {
1747
2190
  requiredMessagesNumber: transaction.messages.length
@@ -1750,7 +2193,7 @@ class TonConnect {
1750
2193
  const from = transaction.from || this.account.address;
1751
2194
  const network = transaction.network || this.account.chain;
1752
2195
  const response = yield this.provider.sendRequest(sendTransactionParser.convertToRpcRequest(Object.assign(Object.assign({}, tx), { valid_until: validUntil, from,
1753
- network })), onRequestSent);
2196
+ network })), { onRequestSent: options.onRequestSent, signal: abortController.signal });
1754
2197
  if (sendTransactionParser.isError(response)) {
1755
2198
  return sendTransactionParser.parseAndThrowError(response);
1756
2199
  }
@@ -1760,13 +2203,23 @@ class TonConnect {
1760
2203
  /**
1761
2204
  * Disconnect form thw connected wallet and drop current session.
1762
2205
  */
1763
- disconnect() {
2206
+ disconnect(options) {
2207
+ var _a;
1764
2208
  return __awaiter(this, void 0, void 0, function* () {
1765
2209
  if (!this.connected) {
1766
2210
  throw new WalletNotConnectedError();
1767
2211
  }
1768
- yield this.provider.disconnect();
2212
+ const abortController = createAbortController(options === null || options === void 0 ? void 0 : options.signal);
2213
+ const prevAbortController = this.abortController;
2214
+ this.abortController = abortController;
2215
+ if (abortController.signal.aborted) {
2216
+ throw new TonConnectError('Disconnect was aborted');
2217
+ }
1769
2218
  this.onWalletDisconnected();
2219
+ yield ((_a = this.provider) === null || _a === void 0 ? void 0 : _a.disconnect({
2220
+ signal: abortController.signal
2221
+ }));
2222
+ prevAbortController === null || prevAbortController === void 0 ? void 0 : prevAbortController.abort();
1770
2223
  });
1771
2224
  }
1772
2225
  /**
@@ -1801,12 +2254,12 @@ class TonConnect {
1801
2254
  this.pauseConnection();
1802
2255
  }
1803
2256
  else {
1804
- this.unPauseConnection();
2257
+ this.unPauseConnection().catch(e => logError('Cannot unpause connection', e));
1805
2258
  }
1806
2259
  });
1807
2260
  }
1808
2261
  catch (e) {
1809
- console.error('Cannot subscribe to the document.visibilitychange: ', e);
2262
+ logError('Cannot subscribe to the document.visibilitychange: ', e);
1810
2263
  }
1811
2264
  }
1812
2265
  createProvider(wallet) {