@tonconnect/sdk 3.0.0 → 3.0.1-beta.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/lib/cjs/index.cjs CHANGED
@@ -346,6 +346,194 @@ function encodeTelegramUrlParameters(parameters) {
346
346
  .replaceAll('%', '--');
347
347
  }
348
348
 
349
+ /**
350
+ * Creates an AbortController instance with an optional AbortSignal.
351
+ *
352
+ * @param {AbortSignal} [signal] - An optional AbortSignal to use for aborting the controller.
353
+ * @returns {AbortController} - An instance of AbortController.
354
+ */
355
+ function createAbortController(signal) {
356
+ const abortController = new AbortController();
357
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
358
+ abortController.abort();
359
+ }
360
+ else {
361
+ signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', () => abortController.abort(), { once: true });
362
+ }
363
+ return abortController;
364
+ }
365
+ /**
366
+ * Executes a function and provides deferred behavior, allowing for a timeout and abort functionality.
367
+ *
368
+ * @param {Deferrable<T>} fn - The function to execute. It should return a promise that resolves with the desired result.
369
+ * @param {DeferOptions} options - Optional configuration options for the defer behavior.
370
+ * @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.
371
+ */
372
+ function defer(fn, options) {
373
+ const timeout = options === null || options === void 0 ? void 0 : options.timeout;
374
+ const signal = options === null || options === void 0 ? void 0 : options.signal;
375
+ const abortController = createAbortController(signal);
376
+ return new Promise((resolve, reject) => {
377
+ if (abortController.signal.aborted) {
378
+ reject(new TonConnectError('Operation aborted'));
379
+ return;
380
+ }
381
+ let timeoutId;
382
+ if (typeof timeout !== 'undefined') {
383
+ timeoutId = setTimeout(() => {
384
+ reject(new TonConnectError(`Timeout after ${timeout}ms`));
385
+ abortController.abort();
386
+ }, timeout);
387
+ }
388
+ abortController.signal.addEventListener('abort', () => {
389
+ clearTimeout(timeoutId);
390
+ reject(new TonConnectError('Operation aborted'));
391
+ }, { once: true });
392
+ const deferOptions = { timeout, abort: abortController.signal };
393
+ fn(resolve, reject, deferOptions).finally(() => clearTimeout(timeoutId));
394
+ });
395
+ }
396
+
397
+ /**
398
+ * Delays the execution of code for a specified number of milliseconds.
399
+ * @param {number} timeout - The number of milliseconds to delay the execution.
400
+ * @param {DelayOptions} [options] - Optional configuration options for the delay.
401
+ * @return {Promise<void>} - A promise that resolves after the specified delay, or rejects if the delay is aborted.
402
+ */
403
+ function delay(timeout, options) {
404
+ return __awaiter(this, void 0, void 0, function* () {
405
+ return yield defer((resolve, reject, options) => __awaiter(this, void 0, void 0, function* () {
406
+ var _a, _b;
407
+ if ((_a = options.signal) === null || _a === void 0 ? void 0 : _a.aborted) {
408
+ reject(new TonConnectError('Delay aborted'));
409
+ return;
410
+ }
411
+ const timeoutId = setTimeout(() => resolve(), timeout);
412
+ (_b = options.signal) === null || _b === void 0 ? void 0 : _b.addEventListener('abort', () => {
413
+ clearTimeout(timeoutId);
414
+ reject(new TonConnectError('Delay aborted'));
415
+ });
416
+ }), { signal: options === null || options === void 0 ? void 0 : options.signal });
417
+ });
418
+ }
419
+
420
+ /**
421
+ * Function to call ton api until we get response.
422
+ * Because ton network is pretty unstable we need to make sure response is final.
423
+ * @param {T} fn - function to call
424
+ * @param {CallForSuccessOptions} [options] - optional configuration options
425
+ */
426
+ function callForSuccess(fn, options) {
427
+ var _a, _b;
428
+ return __awaiter(this, void 0, void 0, function* () {
429
+ const attempts = (_a = options === null || options === void 0 ? void 0 : options.attempts) !== null && _a !== void 0 ? _a : 10;
430
+ const delayMs = (_b = options === null || options === void 0 ? void 0 : options.delayMs) !== null && _b !== void 0 ? _b : 200;
431
+ const abortController = createAbortController(options === null || options === void 0 ? void 0 : options.signal);
432
+ if (typeof fn !== 'function') {
433
+ throw new TonConnectError(`Expected a function, got ${typeof fn}`);
434
+ }
435
+ let i = 0;
436
+ let lastError;
437
+ while (i < attempts) {
438
+ if (abortController.signal.aborted) {
439
+ throw new TonConnectError(`Aborted after attempts ${i}`);
440
+ }
441
+ try {
442
+ return yield fn({ signal: abortController.signal });
443
+ }
444
+ catch (err) {
445
+ lastError = err;
446
+ i++;
447
+ yield delay(delayMs);
448
+ }
449
+ }
450
+ throw lastError;
451
+ });
452
+ }
453
+
454
+ function logDebug(...args) {
455
+ {
456
+ try {
457
+ console.debug('[TON_CONNECT_SDK]', ...args);
458
+ }
459
+ catch (_a) { }
460
+ }
461
+ }
462
+ function logError(...args) {
463
+ {
464
+ try {
465
+ console.error('[TON_CONNECT_SDK]', ...args);
466
+ }
467
+ catch (_a) { }
468
+ }
469
+ }
470
+ function logWarning(...args) {
471
+ {
472
+ try {
473
+ console.warn('[TON_CONNECT_SDK]', ...args);
474
+ }
475
+ catch (_a) { }
476
+ }
477
+ }
478
+
479
+ /**
480
+ * Create a resource.
481
+ *
482
+ * @template T - The type of the resource.
483
+ * @template Args - The type of the arguments for creating the resource.
484
+ *
485
+ * @param {(...args: Args) => Promise<T>} createFn - A function that creates the resource.
486
+ * @param {(resource: T) => Promise<void>} [disposeFn] - An optional function that disposes the resource.
487
+ */
488
+ function createResource(createFn, disposeFn) {
489
+ let currentResource = null;
490
+ let currentArgs = null;
491
+ let currentPromise = null;
492
+ let abortController = null;
493
+ // create a new resource
494
+ const create = (...args) => __awaiter(this, void 0, void 0, function* () {
495
+ abortController === null || abortController === void 0 ? void 0 : abortController.abort();
496
+ abortController = createAbortController();
497
+ currentArgs = args;
498
+ const promise = createFn(abortController.signal, ...args);
499
+ currentPromise = promise;
500
+ const resource = yield promise;
501
+ if (currentPromise !== promise) {
502
+ yield disposeFn(resource);
503
+ throw new TonConnectError('Resource creation was aborted by a new resource creation');
504
+ }
505
+ currentResource = resource;
506
+ return currentResource;
507
+ });
508
+ // get the current resource
509
+ const current = () => {
510
+ return currentResource !== null && currentResource !== void 0 ? currentResource : null;
511
+ };
512
+ // dispose the current resource
513
+ const dispose = () => __awaiter(this, void 0, void 0, function* () {
514
+ const resource = currentResource;
515
+ currentResource = null;
516
+ const promise = currentPromise;
517
+ currentPromise = null;
518
+ abortController === null || abortController === void 0 ? void 0 : abortController.abort();
519
+ yield Promise.allSettled([
520
+ resource ? disposeFn(resource) : Promise.resolve(),
521
+ promise ? disposeFn(yield promise) : Promise.resolve()
522
+ ]);
523
+ });
524
+ // recreate the current resource
525
+ const recreate = () => __awaiter(this, void 0, void 0, function* () {
526
+ yield dispose();
527
+ return create(...(currentArgs !== null && currentArgs !== void 0 ? currentArgs : []));
528
+ });
529
+ return {
530
+ create,
531
+ current,
532
+ dispose,
533
+ recreate
534
+ };
535
+ }
536
+
349
537
  class BridgeGateway {
350
538
  constructor(storage, bridgeUrl, sessionId, listener, errorsListener) {
351
539
  this.bridgeUrl = bridgeUrl;
@@ -356,67 +544,77 @@ class BridgeGateway {
356
544
  this.postPath = 'message';
357
545
  this.heartbeatMessage = 'heartbeat';
358
546
  this.defaultTtl = 300;
359
- this.isClosed = false;
547
+ this.eventSource = createResource((signal, options) => __awaiter(this, void 0, void 0, function* () {
548
+ const eventSourceConfig = {
549
+ bridgeUrl: this.bridgeUrl,
550
+ ssePath: this.ssePath,
551
+ sessionId: this.sessionId,
552
+ bridgeGatewayStorage: this.bridgeGatewayStorage,
553
+ errorHandler: this.errorsHandler.bind(this),
554
+ messageHandler: this.messagesHandler.bind(this),
555
+ signal: signal
556
+ };
557
+ return yield createEventSource(eventSourceConfig, options);
558
+ }), (resource) => __awaiter(this, void 0, void 0, function* () {
559
+ resource.close();
560
+ }));
360
561
  this.bridgeGatewayStorage = new HttpBridgeGatewayStorage(storage, bridgeUrl);
361
562
  }
563
+ get isReady() {
564
+ const eventSource = this.eventSource.current();
565
+ return (eventSource === null || eventSource === void 0 ? void 0 : eventSource.readyState) === EventSource.OPEN;
566
+ }
567
+ get isClosed() {
568
+ const eventSource = this.eventSource.current();
569
+ return (eventSource === null || eventSource === void 0 ? void 0 : eventSource.readyState) !== EventSource.OPEN;
570
+ }
571
+ get isConnecting() {
572
+ const eventSource = this.eventSource.current();
573
+ return (eventSource === null || eventSource === void 0 ? void 0 : eventSource.readyState) === EventSource.CONNECTING;
574
+ }
362
575
  registerSession(options) {
363
576
  return __awaiter(this, void 0, void 0, function* () {
364
- const url = new URL(addPathToUrl(this.bridgeUrl, this.ssePath));
365
- url.searchParams.append('client_id', this.sessionId);
366
- const lastEventId = yield this.bridgeGatewayStorage.getLastEventId();
367
- if (this.isClosed) {
368
- return;
369
- }
370
- if (lastEventId) {
371
- url.searchParams.append('last_event_id', lastEventId);
372
- }
373
- this.eventSource = new EventSource(url.toString());
374
- return new Promise((resolve, reject) => {
375
- const timeout = (options === null || options === void 0 ? void 0 : options.openingDeadlineMS) ? setTimeout(() => {
376
- var _a;
377
- if (((_a = this.eventSource) === null || _a === void 0 ? void 0 : _a.readyState) !== EventSource.OPEN) {
378
- reject(new TonConnectError('Bridge connection timeout'));
379
- this.close();
380
- }
381
- }, options.openingDeadlineMS) : undefined;
382
- this.eventSource.onerror = () => reject;
383
- this.eventSource.onopen = () => {
384
- clearTimeout(timeout);
385
- this.isClosed = false;
386
- this.eventSource.onerror = this.errorsHandler.bind(this);
387
- this.eventSource.onmessage = this.messagesHandler.bind(this);
388
- resolve();
389
- };
390
- });
577
+ yield this.eventSource.create(options);
391
578
  });
392
579
  }
393
- send(message, receiver, topic, ttl) {
580
+ send(message, receiver, topic, ttlOrOptions) {
394
581
  return __awaiter(this, void 0, void 0, function* () {
582
+ // TODO: remove deprecated method
583
+ const options = {};
584
+ if (typeof ttlOrOptions === 'number') {
585
+ options.ttl = ttlOrOptions;
586
+ }
587
+ else {
588
+ options.ttl = ttlOrOptions === null || ttlOrOptions === void 0 ? void 0 : ttlOrOptions.ttl;
589
+ options.signal = ttlOrOptions === null || ttlOrOptions === void 0 ? void 0 : ttlOrOptions.signal;
590
+ }
395
591
  const url = new URL(addPathToUrl(this.bridgeUrl, this.postPath));
396
592
  url.searchParams.append('client_id', this.sessionId);
397
593
  url.searchParams.append('to', receiver);
398
- url.searchParams.append('ttl', (ttl || this.defaultTtl).toString());
594
+ url.searchParams.append('ttl', ((options === null || options === void 0 ? void 0 : options.ttl) || this.defaultTtl).toString());
399
595
  url.searchParams.append('topic', topic);
400
- const response = yield fetch(url, {
401
- method: 'post',
402
- body: protocol.Base64.encode(message)
403
- });
404
- if (!response.ok) {
405
- throw new TonConnectError(`Bridge send failed, status ${response.status}`);
406
- }
596
+ const body = protocol.Base64.encode(message);
597
+ yield callForSuccess((options) => __awaiter(this, void 0, void 0, function* () {
598
+ const response = yield this.post(url, body, options.signal);
599
+ if (!response.ok) {
600
+ throw new TonConnectError(`Bridge send failed, status ${response.status}`);
601
+ }
602
+ }), { attempts: Number.MAX_SAFE_INTEGER, delayMs: 5000, signal: options === null || options === void 0 ? void 0 : options.signal });
407
603
  });
408
604
  }
409
605
  pause() {
410
606
  var _a;
411
- (_a = this.eventSource) === null || _a === void 0 ? void 0 : _a.close();
607
+ (_a = this.eventSource) === null || _a === void 0 ? void 0 : _a.dispose();
412
608
  }
413
609
  unPause() {
414
- return this.registerSession();
610
+ return __awaiter(this, void 0, void 0, function* () {
611
+ yield this.eventSource.recreate();
612
+ });
415
613
  }
416
614
  close() {
417
- var _a;
418
- this.isClosed = true;
419
- (_a = this.eventSource) === null || _a === void 0 ? void 0 : _a.close();
615
+ return __awaiter(this, void 0, void 0, function* () {
616
+ yield this.eventSource.dispose();
617
+ });
420
618
  }
421
619
  setListener(listener) {
422
620
  this.listener = listener;
@@ -424,20 +622,36 @@ class BridgeGateway {
424
622
  setErrorsListener(errorsListener) {
425
623
  this.errorsListener = errorsListener;
426
624
  }
625
+ post(url, body, signal) {
626
+ return __awaiter(this, void 0, void 0, function* () {
627
+ const response = yield fetch(url, {
628
+ method: 'post',
629
+ body: body,
630
+ signal: signal
631
+ });
632
+ if (!response.ok) {
633
+ throw new TonConnectError(`Bridge send failed, status ${response.status}`);
634
+ }
635
+ return response;
636
+ });
637
+ }
427
638
  errorsHandler(e) {
428
- var _a, _b;
429
- if (!this.isClosed) {
430
- if (((_a = this.eventSource) === null || _a === void 0 ? void 0 : _a.readyState) === EventSource.CLOSED) {
431
- this.eventSource.close();
432
- this.registerSession();
639
+ return __awaiter(this, void 0, void 0, function* () {
640
+ if (this.isConnecting) {
641
+ logError('Bridge error', JSON.stringify(e));
433
642
  return;
434
643
  }
435
- if (((_b = this.eventSource) === null || _b === void 0 ? void 0 : _b.readyState) === EventSource.CONNECTING) {
436
- console.debug('[TON_CONNET_SDK_ERROR]: Bridge error', JSON.stringify(e));
644
+ if (this.isReady) {
645
+ this.errorsListener(e);
437
646
  return;
438
647
  }
439
- this.errorsListener(e);
440
- }
648
+ if (this.isClosed) {
649
+ logDebug('Bridge reconnecting, 200ms delay');
650
+ yield delay(200);
651
+ yield this.eventSource.recreate();
652
+ return;
653
+ }
654
+ });
441
655
  }
442
656
  messagesHandler(e) {
443
657
  return __awaiter(this, void 0, void 0, function* () {
@@ -445,19 +659,66 @@ class BridgeGateway {
445
659
  return;
446
660
  }
447
661
  yield this.bridgeGatewayStorage.storeLastEventId(e.lastEventId);
448
- if (!this.isClosed) {
449
- let bridgeIncomingMessage;
450
- try {
451
- bridgeIncomingMessage = JSON.parse(e.data);
452
- }
453
- catch (e) {
454
- throw new TonConnectError(`Bridge message parse failed, message ${e.data}`);
455
- }
456
- this.listener(bridgeIncomingMessage);
662
+ if (this.isClosed) {
663
+ return;
664
+ }
665
+ let bridgeIncomingMessage;
666
+ try {
667
+ bridgeIncomingMessage = JSON.parse(e.data);
668
+ }
669
+ catch (e) {
670
+ throw new TonConnectError(`Bridge message parse failed, message ${e.data}`);
457
671
  }
672
+ this.listener(bridgeIncomingMessage);
458
673
  });
459
674
  }
460
675
  }
676
+ function createEventSource(config, options) {
677
+ return __awaiter(this, void 0, void 0, function* () {
678
+ return yield defer((resolve, reject, deferOptions) => __awaiter(this, void 0, void 0, function* () {
679
+ var _a;
680
+ const abortController = createAbortController(deferOptions.signal);
681
+ const signal = abortController.signal;
682
+ if (signal.aborted) {
683
+ reject(new TonConnectError('Bridge connection aborted'));
684
+ return;
685
+ }
686
+ const url = new URL(addPathToUrl(config.bridgeUrl, config.ssePath));
687
+ url.searchParams.append('client_id', config.sessionId);
688
+ const lastEventId = yield config.bridgeGatewayStorage.getLastEventId();
689
+ if (lastEventId) {
690
+ url.searchParams.append('last_event_id', lastEventId);
691
+ }
692
+ if (signal.aborted) {
693
+ reject(new TonConnectError('Bridge connection aborted'));
694
+ return;
695
+ }
696
+ const eventSource = new EventSource(url.toString());
697
+ eventSource.onerror = (reason) => {
698
+ if (signal.aborted) {
699
+ reject(new TonConnectError('Bridge connection aborted'));
700
+ return;
701
+ }
702
+ config.errorHandler(reason);
703
+ };
704
+ eventSource.onopen = () => {
705
+ if (signal.aborted) {
706
+ reject(new TonConnectError('Bridge connection aborted'));
707
+ return;
708
+ }
709
+ resolve(eventSource);
710
+ };
711
+ eventSource.onmessage = (event) => {
712
+ config.messageHandler(event);
713
+ };
714
+ (_a = config === null || config === void 0 ? void 0 : config.signal) === null || _a === void 0 ? void 0 : _a.addEventListener('abort', () => {
715
+ logError('Bridge connection aborted');
716
+ eventSource.close();
717
+ reject(new TonConnectError('Bridge connection aborted'));
718
+ });
719
+ }), { timeout: options === null || options === void 0 ? void 0 : options.openingDeadlineMS, signal: config === null || config === void 0 ? void 0 : config.signal });
720
+ });
721
+ }
461
722
 
462
723
  function isPendingConnectionHttp(connection) {
463
724
  return !('connectEvent' in connection);
@@ -622,31 +883,6 @@ class BridgeConnectionStorage {
622
883
 
623
884
  const PROTOCOL_VERSION = 2;
624
885
 
625
- function logDebug(...args) {
626
- {
627
- try {
628
- console.debug('[TON_CONNECT_SDK]', ...args);
629
- }
630
- catch (_a) { }
631
- }
632
- }
633
- function logError(...args) {
634
- {
635
- try {
636
- console.error('[TON_CONNECT_SDK]', ...args);
637
- }
638
- catch (_a) { }
639
- }
640
- }
641
- function logWarning(...args) {
642
- {
643
- try {
644
- console.warn('[TON_CONNECT_SDK]', ...args);
645
- }
646
- catch (_a) { }
647
- }
648
- }
649
-
650
886
  class BridgeProvider {
651
887
  constructor(storage, walletConnectionSource) {
652
888
  this.storage = storage;
@@ -658,6 +894,7 @@ class BridgeProvider {
658
894
  this.gateway = null;
659
895
  this.pendingGateways = [];
660
896
  this.listeners = [];
897
+ this.defaultOpeningDeadlineMS = 5000;
661
898
  this.connectionStorage = new BridgeConnectionStorage(storage);
662
899
  }
663
900
  static fromStorage(storage) {
@@ -670,7 +907,7 @@ class BridgeProvider {
670
907
  return new BridgeProvider(storage, { bridgeUrl: connection.session.bridgeUrl });
671
908
  });
672
909
  }
673
- connect(message) {
910
+ connect(message, options) {
674
911
  this.closeGateways();
675
912
  const sessionCrypto = new protocol.SessionCrypto();
676
913
  this.session = {
@@ -685,20 +922,40 @@ class BridgeProvider {
685
922
  connectionSource: this.walletConnectionSource,
686
923
  sessionCrypto
687
924
  })
688
- .then(() => this.openGateways(sessionCrypto));
925
+ .then(() => __awaiter(this, void 0, void 0, function* () {
926
+ yield callForSuccess(_options => this.openGateways(sessionCrypto, {
927
+ openingDeadlineMS: options === null || options === void 0 ? void 0 : options.openingDeadlineMS,
928
+ signal: _options === null || _options === void 0 ? void 0 : _options.signal
929
+ }), {
930
+ attempts: Number.MAX_SAFE_INTEGER,
931
+ delayMs: 5000,
932
+ signal: options === null || options === void 0 ? void 0 : options.signal
933
+ });
934
+ }));
689
935
  const universalLink = 'universalLink' in this.walletConnectionSource &&
690
936
  this.walletConnectionSource.universalLink
691
937
  ? this.walletConnectionSource.universalLink
692
938
  : this.standardUniversalLink;
693
939
  return this.generateUniversalLink(universalLink, message);
694
940
  }
695
- restoreConnection() {
941
+ restoreConnection(options) {
942
+ var _a, _b;
696
943
  return __awaiter(this, void 0, void 0, function* () {
944
+ const abortController = createAbortController(options === null || options === void 0 ? void 0 : options.signal);
945
+ (_a = this.abortController) === null || _a === void 0 ? void 0 : _a.abort();
946
+ this.abortController = abortController;
947
+ if (abortController.signal.aborted) {
948
+ return;
949
+ }
697
950
  this.closeGateways();
698
951
  const storedConnection = yield this.connectionStorage.getHttpConnection();
699
952
  if (!storedConnection) {
700
953
  return;
701
954
  }
955
+ if (abortController.signal.aborted) {
956
+ return;
957
+ }
958
+ const openingDeadlineMS = (_b = options === null || options === void 0 ? void 0 : options.openingDeadlineMS) !== null && _b !== void 0 ? _b : this.defaultOpeningDeadlineMS;
702
959
  if (isPendingConnectionHttp(storedConnection)) {
703
960
  this.session = {
704
961
  sessionCrypto: storedConnection.sessionCrypto,
@@ -706,25 +963,54 @@ class BridgeProvider {
706
963
  ? this.walletConnectionSource.bridgeUrl
707
964
  : ''
708
965
  };
709
- return this.openGateways(storedConnection.sessionCrypto, { openingDeadlineMS: 5000 });
966
+ return yield this.openGateways(storedConnection.sessionCrypto, {
967
+ openingDeadlineMS: openingDeadlineMS,
968
+ signal: abortController === null || abortController === void 0 ? void 0 : abortController.signal
969
+ });
710
970
  }
711
971
  if (Array.isArray(this.walletConnectionSource)) {
712
972
  throw new TonConnectError('Internal error. Connection source is array while WalletConnectionSourceHTTP was expected.');
713
973
  }
714
974
  this.session = storedConnection.session;
975
+ if (this.gateway) {
976
+ logDebug('Gateway is already opened, closing previous gateway');
977
+ yield this.gateway.close();
978
+ }
715
979
  this.gateway = new BridgeGateway(this.storage, this.walletConnectionSource.bridgeUrl, storedConnection.session.sessionCrypto.sessionId, this.gatewayListener.bind(this), this.gatewayErrorsListener.bind(this));
980
+ if (abortController.signal.aborted) {
981
+ return;
982
+ }
983
+ // notify listeners about stored connection
984
+ this.listeners.forEach(listener => listener(storedConnection.connectEvent));
985
+ // wait for the connection to be opened
716
986
  try {
717
- yield this.gateway.registerSession({ openingDeadlineMS: 5000 });
987
+ yield callForSuccess(options => this.gateway.registerSession({
988
+ openingDeadlineMS: openingDeadlineMS,
989
+ signal: options.signal
990
+ }), {
991
+ attempts: Number.MAX_SAFE_INTEGER,
992
+ delayMs: 5000,
993
+ signal: abortController.signal
994
+ });
718
995
  }
719
996
  catch (e) {
720
- yield this.disconnect();
997
+ yield this.disconnect({ signal: abortController.signal });
721
998
  return;
722
999
  }
723
- this.listeners.forEach(listener => listener(storedConnection.connectEvent));
724
1000
  });
725
1001
  }
726
- sendRequest(request, onRequestSent) {
1002
+ sendRequest(request, optionsOrOnRequestSent) {
1003
+ // TODO: remove deprecated method
1004
+ const options = {};
1005
+ if (typeof optionsOrOnRequestSent === 'function') {
1006
+ options.onRequestSent = optionsOrOnRequestSent;
1007
+ }
1008
+ else {
1009
+ options.onRequestSent = optionsOrOnRequestSent === null || optionsOrOnRequestSent === void 0 ? void 0 : optionsOrOnRequestSent.onRequestSent;
1010
+ options.signal = optionsOrOnRequestSent === null || optionsOrOnRequestSent === void 0 ? void 0 : optionsOrOnRequestSent.signal;
1011
+ }
727
1012
  return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
1013
+ var _a;
728
1014
  if (!this.gateway || !this.session || !('walletPublicKey' in this.session)) {
729
1015
  throw new TonConnectError('Trying to send bridge request without session');
730
1016
  }
@@ -733,8 +1019,8 @@ class BridgeProvider {
733
1019
  logDebug('Send http-bridge request:', Object.assign(Object.assign({}, request), { id }));
734
1020
  const encodedRequest = this.session.sessionCrypto.encrypt(JSON.stringify(Object.assign(Object.assign({}, request), { id })), protocol.hexToByteArray(this.session.walletPublicKey));
735
1021
  try {
736
- yield this.gateway.send(encodedRequest, this.session.walletPublicKey, request.method);
737
- onRequestSent === null || onRequestSent === void 0 ? void 0 : onRequestSent();
1022
+ yield this.gateway.send(encodedRequest, this.session.walletPublicKey, request.method, { signal: options === null || options === void 0 ? void 0 : options.signal });
1023
+ (_a = options === null || options === void 0 ? void 0 : options.onRequestSent) === null || _a === void 0 ? void 0 : _a.call(options);
738
1024
  this.pendingRequests.set(id.toString(), resolve);
739
1025
  }
740
1026
  catch (e) {
@@ -748,7 +1034,7 @@ class BridgeProvider {
748
1034
  this.session = null;
749
1035
  this.gateway = null;
750
1036
  }
751
- disconnect() {
1037
+ disconnect(options) {
752
1038
  return __awaiter(this, void 0, void 0, function* () {
753
1039
  return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
754
1040
  let called = false;
@@ -757,10 +1043,14 @@ class BridgeProvider {
757
1043
  this.removeBridgeAndSession().then(resolve);
758
1044
  };
759
1045
  try {
760
- yield this.sendRequest({ method: 'disconnect', params: [] }, onRequestSent);
1046
+ this.closeGateways();
1047
+ yield this.sendRequest({ method: 'disconnect', params: [] }, {
1048
+ onRequestSent: onRequestSent,
1049
+ signal: options === null || options === void 0 ? void 0 : options.signal
1050
+ });
761
1051
  }
762
1052
  catch (e) {
763
- console.debug(e);
1053
+ logDebug('Disconnect error:', e);
764
1054
  if (!called) {
765
1055
  this.removeBridgeAndSession().then(resolve);
766
1056
  }
@@ -789,10 +1079,14 @@ class BridgeProvider {
789
1079
  pendingGatewaysListener(gateway, bridgeUrl, bridgeIncomingMessage) {
790
1080
  return __awaiter(this, void 0, void 0, function* () {
791
1081
  if (!this.pendingGateways.includes(gateway)) {
792
- gateway.close();
1082
+ yield gateway.close();
793
1083
  return;
794
1084
  }
795
1085
  this.closeGateways({ except: gateway });
1086
+ if (this.gateway) {
1087
+ logDebug('Gateway is already opened, closing previous gateway');
1088
+ yield this.gateway.close();
1089
+ }
796
1090
  this.session.bridgeUrl = bridgeUrl;
797
1091
  this.gateway = gateway;
798
1092
  this.gateway.setErrorsListener(this.gatewayErrorsListener.bind(this));
@@ -831,6 +1125,7 @@ class BridgeProvider {
831
1125
  yield this.updateSession(walletMessage, bridgeIncomingMessage.from);
832
1126
  }
833
1127
  if (walletMessage.event === 'disconnect') {
1128
+ logDebug(`Removing bridge and session: received disconnect event`);
834
1129
  yield this.removeBridgeAndSession();
835
1130
  }
836
1131
  listeners.forEach(listener => listener(walletMessage));
@@ -894,8 +1189,15 @@ class BridgeProvider {
894
1189
  return url.toString();
895
1190
  }
896
1191
  openGateways(sessionCrypto, options) {
1192
+ var _a;
897
1193
  return __awaiter(this, void 0, void 0, function* () {
1194
+ const abortController = createAbortController(options === null || options === void 0 ? void 0 : options.signal);
1195
+ (_a = this.abortController) === null || _a === void 0 ? void 0 : _a.abort();
1196
+ this.abortController = abortController;
898
1197
  if (Array.isArray(this.walletConnectionSource)) {
1198
+ // close all gateways before opening new ones
1199
+ this.pendingGateways.map(bridge => bridge.close().catch(e => console.error(e)));
1200
+ // open new gateways
899
1201
  this.pendingGateways = this.walletConnectionSource.map(source => {
900
1202
  const gateway = new BridgeGateway(this.storage, source.bridgeUrl, sessionCrypto.sessionId, () => { }, e => {
901
1203
  console.error(e);
@@ -903,12 +1205,28 @@ class BridgeProvider {
903
1205
  gateway.setListener(message => this.pendingGatewaysListener(gateway, source.bridgeUrl, message));
904
1206
  return gateway;
905
1207
  });
906
- yield Promise.allSettled(this.pendingGateways.map(bridge => bridge.registerSession(options)));
1208
+ yield Promise.allSettled(this.pendingGateways.map(bridge => callForSuccess((_options) => {
1209
+ return bridge.registerSession({
1210
+ openingDeadlineMS: options === null || options === void 0 ? void 0 : options.openingDeadlineMS,
1211
+ signal: _options.signal
1212
+ });
1213
+ }, {
1214
+ attempts: Number.MAX_SAFE_INTEGER,
1215
+ delayMs: 5000,
1216
+ signal: abortController.signal
1217
+ })));
907
1218
  return;
908
1219
  }
909
1220
  else {
1221
+ if (this.gateway) {
1222
+ logDebug(`Gateway is already opened, closing previous gateway`);
1223
+ yield this.gateway.close();
1224
+ }
910
1225
  this.gateway = new BridgeGateway(this.storage, this.walletConnectionSource.bridgeUrl, sessionCrypto.sessionId, this.gatewayListener.bind(this), this.gatewayErrorsListener.bind(this));
911
- return this.gateway.registerSession(options);
1226
+ return yield this.gateway.registerSession({
1227
+ openingDeadlineMS: options === null || options === void 0 ? void 0 : options.openingDeadlineMS,
1228
+ signal: abortController.signal
1229
+ });
912
1230
  }
913
1231
  });
914
1232
  }
@@ -1171,14 +1489,24 @@ class InjectedProvider {
1171
1489
  this.listeners.push(eventsCallback);
1172
1490
  return () => (this.listeners = this.listeners.filter(listener => listener !== eventsCallback));
1173
1491
  }
1174
- sendRequest(request, onRequestSent) {
1492
+ sendRequest(request, optionsOrOnRequestSent) {
1493
+ var _a;
1175
1494
  return __awaiter(this, void 0, void 0, function* () {
1495
+ // TODO: remove deprecated method
1496
+ const options = {};
1497
+ if (typeof optionsOrOnRequestSent === 'function') {
1498
+ options.onRequestSent = optionsOrOnRequestSent;
1499
+ }
1500
+ else {
1501
+ options.onRequestSent = optionsOrOnRequestSent === null || optionsOrOnRequestSent === void 0 ? void 0 : optionsOrOnRequestSent.onRequestSent;
1502
+ options.signal = optionsOrOnRequestSent === null || optionsOrOnRequestSent === void 0 ? void 0 : optionsOrOnRequestSent.signal;
1503
+ }
1176
1504
  const id = (yield this.connectionStorage.getNextRpcRequestId()).toString();
1177
1505
  yield this.connectionStorage.increaseNextRpcRequestId();
1178
1506
  logDebug('Send injected-bridge request:', Object.assign(Object.assign({}, request), { id }));
1179
1507
  const result = this.injectedWallet.send(Object.assign(Object.assign({}, request), { id }));
1180
1508
  result.then(response => logDebug('Wallet message received:', response));
1181
- onRequestSent === null || onRequestSent === void 0 ? void 0 : onRequestSent();
1509
+ (_a = options === null || options === void 0 ? void 0 : options.onRequestSent) === null || _a === void 0 ? void 0 : _a.call(options);
1182
1510
  return result;
1183
1511
  });
1184
1512
  }
@@ -1195,7 +1523,7 @@ class InjectedProvider {
1195
1523
  this.listeners.forEach(listener => listener(connectEvent));
1196
1524
  }
1197
1525
  catch (e) {
1198
- logDebug(e);
1526
+ logDebug('Injected Provider connect error:', e);
1199
1527
  const connectEventError = {
1200
1528
  event: 'connect_error',
1201
1529
  payload: {
@@ -1324,19 +1652,6 @@ const FALLBACK_WALLETS_LIST = [
1324
1652
  ],
1325
1653
  platforms: ['ios', 'android', 'chrome', 'firefox', 'macos']
1326
1654
  },
1327
- {
1328
- app_name: 'openmask',
1329
- name: 'OpenMask',
1330
- image: 'https://raw.githubusercontent.com/OpenProduct/openmask-extension/main/public/openmask-logo-288.png',
1331
- about_url: 'https://www.openmask.app/',
1332
- bridge: [
1333
- {
1334
- type: 'js',
1335
- key: 'openmask'
1336
- }
1337
- ],
1338
- platforms: ['chrome']
1339
- },
1340
1655
  {
1341
1656
  app_name: 'mytonwallet',
1342
1657
  name: 'MyTonWallet',
@@ -1353,7 +1668,20 @@ const FALLBACK_WALLETS_LIST = [
1353
1668
  url: 'https://tonconnectbridge.mytonwallet.org/bridge/'
1354
1669
  }
1355
1670
  ],
1356
- platforms: ['chrome', 'windows', 'macos', 'linux']
1671
+ platforms: ['chrome', 'windows', 'macos', 'linux', 'ios', 'android', 'firefox']
1672
+ },
1673
+ {
1674
+ app_name: 'openmask',
1675
+ name: 'OpenMask',
1676
+ image: 'https://raw.githubusercontent.com/OpenProduct/openmask-extension/main/public/openmask-logo-288.png',
1677
+ about_url: 'https://www.openmask.app/',
1678
+ bridge: [
1679
+ {
1680
+ type: 'js',
1681
+ key: 'openmask'
1682
+ }
1683
+ ],
1684
+ platforms: ['chrome']
1357
1685
  },
1358
1686
  {
1359
1687
  app_name: 'tonhub',
@@ -1373,19 +1701,6 @@ const FALLBACK_WALLETS_LIST = [
1373
1701
  ],
1374
1702
  platforms: ['ios', 'android']
1375
1703
  },
1376
- {
1377
- app_name: 'tonflow',
1378
- name: 'TonFlow',
1379
- image: 'https://tonflow.net/assets/images/tonflow_ico_192.png',
1380
- about_url: 'https://tonflow.net',
1381
- bridge: [
1382
- {
1383
- type: 'js',
1384
- key: 'tonflow'
1385
- }
1386
- ],
1387
- platforms: ['chrome']
1388
- },
1389
1704
  {
1390
1705
  app_name: 'dewallet',
1391
1706
  name: 'DeWallet',
@@ -1692,59 +2007,119 @@ class TonConnect {
1692
2007
  }
1693
2008
  };
1694
2009
  }
1695
- connect(wallet, request) {
1696
- var _a;
2010
+ connect(wallet, requestOrOptions) {
2011
+ var _a, _b;
2012
+ // TODO: remove deprecated method
2013
+ const options = {};
2014
+ if (typeof requestOrOptions === 'object' && 'tonProof' in requestOrOptions) {
2015
+ options.request = requestOrOptions;
2016
+ }
2017
+ if (typeof requestOrOptions === 'object' &&
2018
+ ('openingDeadlineMS' in requestOrOptions ||
2019
+ 'signal' in requestOrOptions ||
2020
+ 'request' in requestOrOptions)) {
2021
+ options.request = requestOrOptions === null || requestOrOptions === void 0 ? void 0 : requestOrOptions.request;
2022
+ options.openingDeadlineMS = requestOrOptions === null || requestOrOptions === void 0 ? void 0 : requestOrOptions.openingDeadlineMS;
2023
+ options.signal = requestOrOptions === null || requestOrOptions === void 0 ? void 0 : requestOrOptions.signal;
2024
+ }
1697
2025
  if (this.connected) {
1698
2026
  throw new WalletAlreadyConnectedError();
1699
2027
  }
1700
- (_a = this.provider) === null || _a === void 0 ? void 0 : _a.closeConnection();
2028
+ const abortController = createAbortController(options === null || options === void 0 ? void 0 : options.signal);
2029
+ (_a = this.abortController) === null || _a === void 0 ? void 0 : _a.abort();
2030
+ this.abortController = abortController;
2031
+ if (abortController.signal.aborted) {
2032
+ throw new TonConnectError('Connection was aborted');
2033
+ }
2034
+ (_b = this.provider) === null || _b === void 0 ? void 0 : _b.closeConnection();
1701
2035
  this.provider = this.createProvider(wallet);
1702
- return this.provider.connect(this.createConnectRequest(request));
2036
+ return this.provider.connect(this.createConnectRequest(options === null || options === void 0 ? void 0 : options.request), {
2037
+ openingDeadlineMS: options === null || options === void 0 ? void 0 : options.openingDeadlineMS,
2038
+ signal: abortController.signal
2039
+ });
1703
2040
  }
1704
2041
  /**
1705
2042
  * Try to restore existing session and reconnect to the corresponding wallet. Call it immediately when your app is loaded.
1706
2043
  */
1707
- restoreConnection() {
2044
+ restoreConnection(options) {
2045
+ var _a, _b;
1708
2046
  return __awaiter(this, void 0, void 0, function* () {
2047
+ const abortController = createAbortController(options === null || options === void 0 ? void 0 : options.signal);
2048
+ (_a = this.abortController) === null || _a === void 0 ? void 0 : _a.abort();
2049
+ this.abortController = abortController;
2050
+ if (abortController.signal.aborted) {
2051
+ return;
2052
+ }
2053
+ // TODO: potentially race condition here
1709
2054
  const [bridgeConnectionType, embeddedWallet] = yield Promise.all([
1710
2055
  this.bridgeConnectionStorage.storedConnectionType(),
1711
2056
  this.walletsList.getEmbeddedWallet()
1712
2057
  ]);
2058
+ if (abortController.signal.aborted) {
2059
+ return;
2060
+ }
2061
+ let provider = null;
1713
2062
  try {
1714
2063
  switch (bridgeConnectionType) {
1715
2064
  case 'http':
1716
- this.provider = yield BridgeProvider.fromStorage(this.dappSettings.storage);
2065
+ provider = yield BridgeProvider.fromStorage(this.dappSettings.storage);
1717
2066
  break;
1718
2067
  case 'injected':
1719
- this.provider = yield InjectedProvider.fromStorage(this.dappSettings.storage);
2068
+ provider = yield InjectedProvider.fromStorage(this.dappSettings.storage);
1720
2069
  break;
1721
2070
  default:
1722
2071
  if (embeddedWallet) {
1723
- this.provider = yield this.createProvider(embeddedWallet);
2072
+ provider = this.createProvider(embeddedWallet);
1724
2073
  }
1725
2074
  else {
1726
2075
  return;
1727
2076
  }
1728
2077
  }
1729
2078
  }
1730
- catch (_a) {
2079
+ catch (_c) {
1731
2080
  yield this.bridgeConnectionStorage.removeConnection();
1732
- this.provider = null;
2081
+ provider === null || provider === void 0 ? void 0 : provider.closeConnection();
2082
+ provider = null;
1733
2083
  return;
1734
2084
  }
1735
- this.provider.listen(this.walletEventsListener.bind(this));
1736
- return this.provider.restoreConnection();
2085
+ if (abortController.signal.aborted) {
2086
+ provider === null || provider === void 0 ? void 0 : provider.closeConnection();
2087
+ return;
2088
+ }
2089
+ if (!provider) {
2090
+ logError('Provider is not restored');
2091
+ return;
2092
+ }
2093
+ (_b = this.provider) === null || _b === void 0 ? void 0 : _b.closeConnection();
2094
+ this.provider = provider;
2095
+ provider.listen(this.walletEventsListener.bind(this));
2096
+ return yield callForSuccess((_options) => __awaiter(this, void 0, void 0, function* () {
2097
+ return provider === null || provider === void 0 ? void 0 : provider.restoreConnection({
2098
+ openingDeadlineMS: options === null || options === void 0 ? void 0 : options.openingDeadlineMS,
2099
+ signal: _options.signal
2100
+ });
2101
+ }), {
2102
+ attempts: Number.MAX_SAFE_INTEGER,
2103
+ delayMs: 5000,
2104
+ signal: options === null || options === void 0 ? void 0 : options.signal
2105
+ });
1737
2106
  });
1738
2107
  }
1739
- /**
1740
- * Asks connected wallet to sign and send the transaction.
1741
- * @param transaction transaction to send.
1742
- * @param onRequestSent (optional) will be called after the transaction is sent to the wallet.
1743
- * @returns signed transaction boc that allows you to find the transaction in the blockchain.
1744
- * If user rejects transaction, method will throw the corresponding error.
1745
- */
1746
- sendTransaction(transaction, onRequestSent) {
2108
+ sendTransaction(transaction, optionsOrOnRequestSent) {
1747
2109
  return __awaiter(this, void 0, void 0, function* () {
2110
+ // TODO: remove deprecated method
2111
+ const options = {};
2112
+ if (typeof optionsOrOnRequestSent === 'function') {
2113
+ options.onRequestSent = optionsOrOnRequestSent;
2114
+ }
2115
+ else {
2116
+ options.onRequestSent = optionsOrOnRequestSent === null || optionsOrOnRequestSent === void 0 ? void 0 : optionsOrOnRequestSent.onRequestSent;
2117
+ options.signal = optionsOrOnRequestSent === null || optionsOrOnRequestSent === void 0 ? void 0 : optionsOrOnRequestSent.signal;
2118
+ }
2119
+ const abortController = createAbortController(options === null || options === void 0 ? void 0 : options.signal);
2120
+ if (abortController.signal.aborted) {
2121
+ throw new TonConnectError('Transaction sending was aborted');
2122
+ }
1748
2123
  this.checkConnection();
1749
2124
  checkSendTransactionSupport(this.wallet.device.features, {
1750
2125
  requiredMessagesNumber: transaction.messages.length
@@ -1753,7 +2128,7 @@ class TonConnect {
1753
2128
  const from = transaction.from || this.account.address;
1754
2129
  const network = transaction.network || this.account.chain;
1755
2130
  const response = yield this.provider.sendRequest(sendTransactionParser.convertToRpcRequest(Object.assign(Object.assign({}, tx), { valid_until: validUntil, from,
1756
- network })), onRequestSent);
2131
+ network })), { onRequestSent: options.onRequestSent, signal: abortController.signal });
1757
2132
  if (sendTransactionParser.isError(response)) {
1758
2133
  return sendTransactionParser.parseAndThrowError(response);
1759
2134
  }
@@ -1763,13 +2138,22 @@ class TonConnect {
1763
2138
  /**
1764
2139
  * Disconnect form thw connected wallet and drop current session.
1765
2140
  */
1766
- disconnect() {
2141
+ disconnect(options) {
2142
+ var _a, _b;
1767
2143
  return __awaiter(this, void 0, void 0, function* () {
1768
2144
  if (!this.connected) {
1769
2145
  throw new WalletNotConnectedError();
1770
2146
  }
1771
- yield this.provider.disconnect();
2147
+ const abortController = createAbortController(options === null || options === void 0 ? void 0 : options.signal);
2148
+ (_a = this.abortController) === null || _a === void 0 ? void 0 : _a.abort();
2149
+ this.abortController = abortController;
2150
+ if (abortController.signal.aborted) {
2151
+ throw new TonConnectError('Disconnect was aborted');
2152
+ }
1772
2153
  this.onWalletDisconnected();
2154
+ yield ((_b = this.provider) === null || _b === void 0 ? void 0 : _b.disconnect({
2155
+ signal: abortController.signal
2156
+ }));
1773
2157
  });
1774
2158
  }
1775
2159
  /**