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