@tonconnect/sdk
Version:
Use it to connect your app to TON wallets via TonConnect protocol. You can find more details and the protocol specification in the [docs](https://docs.ton.org/develop/dapps/ton-connect/overview). See the example of sdk usage [here](https://github.com/ton
1,222 lines (1,174 loc) • 249 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var protocol = require('@tonconnect/protocol');
require('@tonconnect/isomorphic-eventsource');
require('@tonconnect/isomorphic-fetch');
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
function __rest(s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
}
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
/**
* Base class for TonConnect errors. You can check if the error was triggered by the @tonconnect/sdk using `err instanceof TonConnectError`.
*/
class TonConnectError extends Error {
get info() {
return '';
}
constructor(message, options) {
super(message, options);
this.message = `${TonConnectError.prefix} ${this.constructor.name}${this.info ? ': ' + this.info : ''}${message ? '\n' + message : ''}`;
Object.setPrototypeOf(this, TonConnectError.prototype);
}
}
TonConnectError.prefix = '[TON_CONNECT_SDK_ERROR]';
/**
* Thrown when passed DappMetadata is in incorrect format.
*/
class DappMetadataError extends TonConnectError {
get info() {
return 'Passed DappMetadata is in incorrect format.';
}
constructor(...args) {
super(...args);
Object.setPrototypeOf(this, DappMetadataError.prototype);
}
}
/**
* Thrown when passed manifest contains errors.
*/
class ManifestContentErrorError extends TonConnectError {
get info() {
return 'Passed `tonconnect-manifest.json` contains errors. Check format of your manifest. See more https://github.com/ton-connect/docs/blob/main/requests-responses.md#app-manifest';
}
constructor(...args) {
super(...args);
Object.setPrototypeOf(this, ManifestContentErrorError.prototype);
}
}
/**
* Thrown when wallet can't get manifest by passed manifestUrl.
*/
class ManifestNotFoundError extends TonConnectError {
get info() {
return 'Manifest not found. Make sure you added `tonconnect-manifest.json` to the root of your app or passed correct manifestUrl. See more https://github.com/ton-connect/docs/blob/main/requests-responses.md#app-manifest';
}
constructor(...args) {
super(...args);
Object.setPrototypeOf(this, ManifestNotFoundError.prototype);
}
}
/**
* Thrown when wallet connection called but wallet already connected. To avoid the error, disconnect the wallet before doing a new connection.
*/
class WalletAlreadyConnectedError extends TonConnectError {
get info() {
return 'Wallet connection called but wallet already connected. To avoid the error, disconnect the wallet before doing a new connection.';
}
constructor(...args) {
super(...args);
Object.setPrototypeOf(this, WalletAlreadyConnectedError.prototype);
}
}
/**
* Thrown when send transaction or other protocol methods called while wallet is not connected.
*/
class WalletNotConnectedError extends TonConnectError {
get info() {
return 'Send transaction or other protocol methods called while wallet is not connected.';
}
constructor(...args) {
super(...args);
Object.setPrototypeOf(this, WalletNotConnectedError.prototype);
}
}
/**
* Thrown when there is an attempt to connect to the injected wallet while it is not exists in the webpage.
*/
class WalletNotInjectedError extends TonConnectError {
get info() {
return 'There is an attempt to connect to the injected wallet while it is not exists in the webpage.';
}
constructor(...args) {
super(...args);
Object.setPrototypeOf(this, WalletNotInjectedError.prototype);
}
}
/**
* Thrown when wallet doesn't support requested feature method.
*/
class WalletNotSupportFeatureError extends TonConnectError {
get info() {
return "Wallet doesn't support requested feature method.";
}
constructor(message, options) {
super(message, options);
Object.setPrototypeOf(this, WalletNotSupportFeatureError.prototype);
}
}
/**
* Thrown when wallet can't get manifest by passed manifestUrl.
*/
class WalletMissingRequiredFeaturesError extends TonConnectError {
get info() {
return 'Missing required features. You need to update your wallet.';
}
constructor(message, options) {
super(message, options);
Object.setPrototypeOf(this, WalletMissingRequiredFeaturesError.prototype);
}
}
class WalletWrongNetworkError extends TonConnectError {
constructor(message, options) {
super(message, options);
this.name = 'WalletWrongNetworkError';
Object.setPrototypeOf(this, WalletWrongNetworkError.prototype);
}
}
function isWalletConnectionSourceJS(value) {
return 'jsBridgeKey' in value;
}
function isWalletConnectionSourceWalletConnect(value) {
return 'type' in value && value.type === 'wallet-connect';
}
/**
* Thrown when user rejects the action in the wallet.
*/
class UserRejectsError extends TonConnectError {
get info() {
return 'User rejects the action in the wallet.';
}
constructor(...args) {
super(...args);
Object.setPrototypeOf(this, UserRejectsError.prototype);
}
}
/**
* Thrown when request to the wallet contains errors.
*/
class BadRequestError extends TonConnectError {
get info() {
return 'Request to the wallet contains errors.';
}
constructor(...args) {
super(...args);
Object.setPrototypeOf(this, BadRequestError.prototype);
}
}
/**
* Thrown when app tries to send rpc request to the injected wallet while not connected.
*/
class UnknownAppError extends TonConnectError {
get info() {
return 'App tries to send rpc request to the injected wallet while not connected.';
}
constructor(...args) {
super(...args);
Object.setPrototypeOf(this, UnknownAppError.prototype);
}
}
/**
* Thrown when `Storage` was not specified in the `DappMetadata` and default `localStorage` was not detected in the Node.js environment.
*/
class LocalstorageNotFoundError extends TonConnectError {
get info() {
return 'Storage was not specified in the `DappMetadata` and default `localStorage` was not detected in the environment.';
}
constructor(...args) {
super(...args);
Object.setPrototypeOf(this, LocalstorageNotFoundError.prototype);
}
}
/**
* Thrown when an error occurred while fetching the wallets list.
*/
class FetchWalletsError extends TonConnectError {
get info() {
return 'An error occurred while fetching the wallets list.';
}
constructor(...args) {
super(...args);
Object.setPrototypeOf(this, FetchWalletsError.prototype);
}
}
/**
* Thrown when passed address is in incorrect format.
*/
class WrongAddressError extends TonConnectError {
get info() {
return 'Passed address is in incorrect format.';
}
constructor(...args) {
super(...args);
Object.setPrototypeOf(this, WrongAddressError.prototype);
}
}
/**
* Thrown when passed hex is in incorrect format.
*/
class ParseHexError extends TonConnectError {
get info() {
return 'Passed hex is in incorrect format.';
}
constructor(...args) {
super(...args);
Object.setPrototypeOf(this, ParseHexError.prototype);
}
}
/**
* Unhanded unknown error.
*/
class UnknownError extends TonConnectError {
constructor(...args) {
super(...args);
Object.setPrototypeOf(this, UnknownError.prototype);
}
}
const connectEventErrorsCodes = {
[protocol.CONNECT_EVENT_ERROR_CODES.UNKNOWN_ERROR]: UnknownError,
[protocol.CONNECT_EVENT_ERROR_CODES.USER_REJECTS_ERROR]: UserRejectsError,
[protocol.CONNECT_EVENT_ERROR_CODES.BAD_REQUEST_ERROR]: BadRequestError,
[protocol.CONNECT_EVENT_ERROR_CODES.UNKNOWN_APP_ERROR]: UnknownAppError,
[protocol.CONNECT_EVENT_ERROR_CODES.MANIFEST_NOT_FOUND_ERROR]: ManifestNotFoundError,
[protocol.CONNECT_EVENT_ERROR_CODES.MANIFEST_CONTENT_ERROR]: ManifestContentErrorError
};
class ConnectErrorsParser {
parseError(error) {
let ErrorConstructor = UnknownError;
if (error.code in connectEventErrorsCodes) {
ErrorConstructor = connectEventErrorsCodes[error.code] || UnknownError;
}
return new ErrorConstructor(error.message);
}
}
const connectErrorsParser = new ConnectErrorsParser();
class RpcParser {
isError(response) {
return 'error' in response;
}
}
const sendTransactionErrors = {
[protocol.SEND_TRANSACTION_ERROR_CODES.UNKNOWN_ERROR]: UnknownError,
[protocol.SEND_TRANSACTION_ERROR_CODES.USER_REJECTS_ERROR]: UserRejectsError,
[protocol.SEND_TRANSACTION_ERROR_CODES.BAD_REQUEST_ERROR]: BadRequestError,
[protocol.SEND_TRANSACTION_ERROR_CODES.UNKNOWN_APP_ERROR]: UnknownAppError
};
class SendTransactionParser extends RpcParser {
convertToRpcRequest(request) {
return {
method: 'sendTransaction',
params: [JSON.stringify(request)]
};
}
parseAndThrowError(response) {
let ErrorConstructor = UnknownError;
if (response.error.code in sendTransactionErrors) {
ErrorConstructor = sendTransactionErrors[response.error.code] || UnknownError;
}
throw new ErrorConstructor(response.error.message);
}
convertFromRpcResponse(rpcResponse) {
return {
boc: rpcResponse.result
};
}
}
const sendTransactionParser = new SendTransactionParser();
const signDataErrors = {
[protocol.SIGN_DATA_ERROR_CODES.UNKNOWN_ERROR]: UnknownError,
[protocol.SIGN_DATA_ERROR_CODES.USER_REJECTS_ERROR]: UserRejectsError,
[protocol.SIGN_DATA_ERROR_CODES.BAD_REQUEST_ERROR]: BadRequestError,
[protocol.SIGN_DATA_ERROR_CODES.UNKNOWN_APP_ERROR]: UnknownAppError
};
class SignDataParser extends RpcParser {
convertToRpcRequest(payload) {
return {
method: 'signData',
params: [JSON.stringify(payload)]
};
}
parseAndThrowError(response) {
let ErrorConstructor = UnknownError;
if (response.error.code in signDataErrors) {
ErrorConstructor = signDataErrors[response.error.code] || UnknownError;
}
throw new ErrorConstructor(response.error.message);
}
convertFromRpcResponse(rpcResponse) {
return rpcResponse.result;
}
}
const signDataParser = new SignDataParser();
class HttpBridgeGatewayStorage {
constructor(storage, bridgeUrl) {
this.storage = storage;
this.storeKey = 'ton-connect-storage_http-bridge-gateway::' + bridgeUrl;
}
storeLastEventId(lastEventId) {
return __awaiter(this, void 0, void 0, function* () {
return this.storage.setItem(this.storeKey, lastEventId);
});
}
removeLastEventId() {
return __awaiter(this, void 0, void 0, function* () {
return this.storage.removeItem(this.storeKey);
});
}
getLastEventId() {
return __awaiter(this, void 0, void 0, function* () {
const stored = yield this.storage.getItem(this.storeKey);
if (!stored) {
return null;
}
return stored;
});
}
}
function removeUrlLastSlash(url) {
if (url.slice(-1) === '/') {
return url.slice(0, -1);
}
return url;
}
function addPathToUrl(url, path) {
return removeUrlLastSlash(url) + '/' + path;
}
function isTelegramUrl(link) {
if (!link) {
return false;
}
const url = new URL(link);
return url.protocol === 'tg:' || url.hostname === 't.me';
}
function isConnectUrl(link) {
if (!link) {
return false;
}
return link.includes('ton_addr') || link.includes('ton--5Faddr');
}
function encodeTelegramUrlParameters(parameters) {
return parameters
.replaceAll('.', '%2E')
.replaceAll('-', '%2D')
.replaceAll('_', '%5F')
.replaceAll('&', '-')
.replaceAll('=', '__')
.replaceAll('%', '--');
}
function decodeTelegramUrlParameters(parameters) {
return parameters
.replaceAll('--', '%')
.replaceAll('__', '=')
.replaceAll('-', '&')
.replaceAll('%5F', '_')
.replaceAll('%2D', '-')
.replaceAll('%2E', '.');
}
/**
* Delays the execution of code for a specified number of milliseconds.
* @param {number} timeout - The number of milliseconds to delay the execution.
* @param {DelayOptions} [options] - Optional configuration options for the delay.
* @return {Promise<void>} - A promise that resolves after the specified delay, or rejects if the delay is aborted.
*/
function delay(timeout, options) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
var _a, _b;
if ((_a = void 0 ) === null || _a === void 0 ? void 0 : _a.aborted) {
reject(new TonConnectError('Delay aborted'));
return;
}
const timeoutId = setTimeout(() => resolve(), timeout);
(_b = void 0 ) === null || _b === void 0 ? void 0 : _b.addEventListener('abort', () => {
clearTimeout(timeoutId);
reject(new TonConnectError('Delay aborted'));
});
});
});
}
/**
* Creates an AbortController instance with an optional AbortSignal.
*
* @param {AbortSignal} [signal] - An optional AbortSignal to use for aborting the controller.
* @returns {AbortController} - An instance of AbortController.
*/
function createAbortController(signal) {
const abortController = new AbortController();
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
abortController.abort();
}
else {
signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', () => abortController.abort(), { once: true });
}
return abortController;
}
/**
* Function to call ton api until we get response.
* Because ton network is pretty unstable we need to make sure response is final.
* @param {T} fn - function to call
* @param {CallForSuccessOptions} [options] - optional configuration options
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function callForSuccess(fn, options) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b;
const attempts = (_a = options === null || options === void 0 ? void 0 : options.attempts) !== null && _a !== void 0 ? _a : 10;
const delayMs = (_b = options === null || options === void 0 ? void 0 : options.delayMs) !== null && _b !== void 0 ? _b : 200;
const abortController = createAbortController(options === null || options === void 0 ? void 0 : options.signal);
if (typeof fn !== 'function') {
throw new TonConnectError(`Expected a function, got ${typeof fn}`);
}
let i = 0;
let lastError;
while (i < attempts) {
if (abortController.signal.aborted) {
throw new TonConnectError(`Aborted after attempts ${i}`);
}
try {
return yield fn({ signal: abortController.signal });
}
catch (err) {
lastError = err;
i++;
if (i < attempts) {
yield delay(delayMs);
}
}
}
throw lastError;
});
}
function logDebug(...args) {
{
try {
console.debug('[TON_CONNECT_SDK]', ...args);
}
catch (_a) { }
}
}
function logError(...args) {
{
try {
console.error('[TON_CONNECT_SDK]', ...args);
}
catch (_a) { }
}
}
function logWarning(...args) {
{
try {
console.warn('[TON_CONNECT_SDK]', ...args);
}
catch (_a) { }
}
}
/**
* Create a resource.
*
* @template T - The type of the resource.
* @template Args - The type of the arguments for creating the resource.
*
* @param {(...args: Args) => Promise<T>} createFn - A function that creates the resource.
* @param {(resource: T) => Promise<void>} [disposeFn] - An optional function that disposes the resource.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function createResource(createFn, disposeFn) {
let currentResource = null;
let currentArgs = null;
let currentPromise = null;
let currentSignal = null;
let abortController = null;
// create a new resource
const create = (signal, ...args) => __awaiter(this, void 0, void 0, function* () {
currentSignal = signal !== null && signal !== void 0 ? signal : null;
abortController === null || abortController === void 0 ? void 0 : abortController.abort();
abortController = createAbortController(signal);
if (abortController.signal.aborted) {
throw new TonConnectError('Resource creation was aborted');
}
currentArgs = args !== null && args !== void 0 ? args : null;
const promise = createFn(abortController.signal, ...args);
currentPromise = promise;
const resource = yield promise;
if (currentPromise !== promise && resource !== currentResource) {
yield disposeFn(resource);
throw new TonConnectError('Resource creation was aborted by a new resource creation');
}
currentResource = resource;
return currentResource;
});
// get the current resource
const current = () => {
return currentResource !== null && currentResource !== void 0 ? currentResource : null;
};
// dispose the current resource
const dispose = () => __awaiter(this, void 0, void 0, function* () {
try {
const resource = currentResource;
currentResource = null;
const promise = currentPromise;
currentPromise = null;
try {
abortController === null || abortController === void 0 ? void 0 : abortController.abort();
}
catch (e) { }
yield Promise.allSettled([
resource ? disposeFn(resource) : Promise.resolve(),
promise ? disposeFn(yield promise) : Promise.resolve()
]);
}
catch (e) { }
});
// recreate the current resource
const recreate = (delayMs) => __awaiter(this, void 0, void 0, function* () {
const resource = currentResource;
const promise = currentPromise;
const args = currentArgs;
const signal = currentSignal;
yield delay(delayMs);
if (resource === currentResource &&
promise === currentPromise &&
args === currentArgs &&
signal === currentSignal) {
return yield create(currentSignal, ...(args !== null && args !== void 0 ? args : []));
}
throw new TonConnectError('Resource recreation was aborted by a new resource creation');
});
return {
create,
current,
dispose,
recreate
};
}
/**
* Executes a function and provides deferred behavior, allowing for a timeout and abort functionality.
*
* @param {Deferrable<T>} fn - The function to execute. It should return a promise that resolves with the desired result.
* @param {DeferOptions} options - Optional configuration options for the defer behavior.
* @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.
*/
function timeout(fn, options) {
const timeout = options === null || options === void 0 ? void 0 : options.timeout;
const signal = options === null || options === void 0 ? void 0 : options.signal;
const abortController = createAbortController(signal);
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
if (abortController.signal.aborted) {
reject(new TonConnectError('Operation aborted'));
return;
}
let timeoutId;
if (typeof timeout !== 'undefined') {
timeoutId = setTimeout(() => {
abortController.abort();
reject(new TonConnectError(`Timeout after ${timeout}ms`));
}, timeout);
}
abortController.signal.addEventListener('abort', () => {
clearTimeout(timeoutId);
reject(new TonConnectError('Operation aborted'));
}, { once: true });
const deferOptions = { timeout, abort: abortController.signal };
yield fn((...args) => {
clearTimeout(timeoutId);
resolve(...args);
}, () => {
clearTimeout(timeoutId);
reject();
}, deferOptions);
}));
}
class BridgeGateway {
get isReady() {
const eventSource = this.eventSource.current();
return (eventSource === null || eventSource === void 0 ? void 0 : eventSource.readyState) === EventSource.OPEN;
}
get isClosed() {
const eventSource = this.eventSource.current();
return (eventSource === null || eventSource === void 0 ? void 0 : eventSource.readyState) !== EventSource.OPEN;
}
get isConnecting() {
const eventSource = this.eventSource.current();
return (eventSource === null || eventSource === void 0 ? void 0 : eventSource.readyState) === EventSource.CONNECTING;
}
constructor(storage, bridgeUrl, sessionId, listener, errorsListener, analyticsManager) {
this.bridgeUrl = bridgeUrl;
this.sessionId = sessionId;
this.listener = listener;
this.errorsListener = errorsListener;
this.ssePath = 'events';
this.postPath = 'message';
this.heartbeatMessage = 'heartbeat';
this.defaultTtl = 300;
this.defaultReconnectDelay = 2000;
this.defaultResendDelay = 5000;
this.eventSource = createResource((signal, openingDeadlineMS, traceId) => __awaiter(this, void 0, void 0, function* () {
const eventSourceConfig = {
bridgeUrl: this.bridgeUrl,
ssePath: this.ssePath,
sessionId: this.sessionId,
bridgeGatewayStorage: this.bridgeGatewayStorage,
errorHandler: this.errorsHandler.bind(this),
messageHandler: this.messagesHandler.bind(this),
signal: signal,
openingDeadlineMS: openingDeadlineMS,
traceId
};
return yield createEventSource(eventSourceConfig);
}), (resource) => __awaiter(this, void 0, void 0, function* () {
resource.close();
}));
this.bridgeGatewayStorage = new HttpBridgeGatewayStorage(storage, bridgeUrl);
this.analytics = analyticsManager === null || analyticsManager === void 0 ? void 0 : analyticsManager.scoped({
bridge_url: bridgeUrl,
client_id: sessionId
});
}
registerSession(options) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c;
try {
(_a = this.analytics) === null || _a === void 0 ? void 0 : _a.emitBridgeClientConnectStarted({
trace_id: options === null || options === void 0 ? void 0 : options.traceId
});
const connectionStarted = Date.now();
yield this.eventSource.create(options === null || options === void 0 ? void 0 : options.signal, options === null || options === void 0 ? void 0 : options.openingDeadlineMS, options === null || options === void 0 ? void 0 : options.traceId);
const bridgeConnectDuration = Date.now() - connectionStarted;
(_b = this.analytics) === null || _b === void 0 ? void 0 : _b.emitBridgeClientConnectEstablished({
bridge_connect_duration: bridgeConnectDuration,
trace_id: options === null || options === void 0 ? void 0 : options.traceId
});
}
catch (error) {
(_c = this.analytics) === null || _c === void 0 ? void 0 : _c.emitBridgeClientConnectError({
trace_id: options === null || options === void 0 ? void 0 : options.traceId,
error_message: String(error)
});
throw error;
}
});
}
send(message, receiver, topic, ttlOrOptions) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
// TODO: remove deprecated method
const options = {};
if (typeof ttlOrOptions === 'number') {
options.ttl = ttlOrOptions;
}
else {
options.ttl = ttlOrOptions === null || ttlOrOptions === void 0 ? void 0 : ttlOrOptions.ttl;
options.signal = ttlOrOptions === null || ttlOrOptions === void 0 ? void 0 : ttlOrOptions.signal;
options.attempts = ttlOrOptions === null || ttlOrOptions === void 0 ? void 0 : ttlOrOptions.attempts;
options.traceId = ttlOrOptions === null || ttlOrOptions === void 0 ? void 0 : ttlOrOptions.traceId;
}
const url = new URL(addPathToUrl(this.bridgeUrl, this.postPath));
url.searchParams.append('client_id', this.sessionId);
url.searchParams.append('to', receiver);
url.searchParams.append('ttl', ((options === null || options === void 0 ? void 0 : options.ttl) || this.defaultTtl).toString());
url.searchParams.append('topic', topic);
if (options === null || options === void 0 ? void 0 : options.traceId) {
url.searchParams.append('trace_id', options.traceId);
}
const body = protocol.Base64.encode(message);
yield callForSuccess((options) => __awaiter(this, void 0, void 0, function* () {
const response = yield this.post(url, body, options.signal);
if (!response.ok) {
throw new TonConnectError(`Bridge send failed, status ${response.status}`);
}
}), {
attempts: (_a = options === null || options === void 0 ? void 0 : options.attempts) !== null && _a !== void 0 ? _a : Number.MAX_SAFE_INTEGER,
delayMs: this.defaultResendDelay,
signal: options === null || options === void 0 ? void 0 : options.signal
});
});
}
pause() {
this.eventSource.dispose().catch(e => logError(`Bridge pause failed, ${e}`));
}
unPause() {
return __awaiter(this, void 0, void 0, function* () {
const RECREATE_WITHOUT_DELAY = 0;
yield this.eventSource.recreate(RECREATE_WITHOUT_DELAY);
});
}
close() {
return __awaiter(this, void 0, void 0, function* () {
yield this.eventSource.dispose().catch(e => logError(`Bridge close failed, ${e}`));
});
}
setListener(listener) {
this.listener = listener;
}
setErrorsListener(errorsListener) {
this.errorsListener = errorsListener;
}
post(url, body, signal) {
return __awaiter(this, void 0, void 0, function* () {
const response = yield fetch(url, {
method: 'post',
body: body,
signal: signal
});
if (!response.ok) {
throw new TonConnectError(`Bridge send failed, status ${response.status}`);
}
return response;
});
}
errorsHandler(eventSource, e) {
return __awaiter(this, void 0, void 0, function* () {
if (this.isConnecting) {
eventSource.close();
throw new TonConnectError('Bridge error, failed to connect');
}
if (this.isReady) {
try {
this.errorsListener(e);
}
catch (e) { }
return;
}
if (this.isClosed) {
eventSource.close();
logDebug(`Bridge reconnecting, ${this.defaultReconnectDelay}ms delay`);
return yield this.eventSource.recreate(this.defaultReconnectDelay);
}
throw new TonConnectError('Bridge error, unknown state');
});
}
messagesHandler(e) {
return __awaiter(this, void 0, void 0, function* () {
if (e.data === this.heartbeatMessage) {
return;
}
yield this.bridgeGatewayStorage.storeLastEventId(e.lastEventId);
if (this.isClosed) {
return;
}
let bridgeIncomingMessage;
try {
const bridgeIncomingMessageRaw = JSON.parse(e.data);
bridgeIncomingMessage = {
message: bridgeIncomingMessageRaw.message,
from: bridgeIncomingMessageRaw.from,
traceId: bridgeIncomingMessageRaw.trace_id
};
}
catch (_) {
throw new TonConnectError(`Bridge message parse failed, message ${e.data}`);
}
this.listener(bridgeIncomingMessage);
});
}
}
/**
* Creates an event source.
* @param {CreateEventSourceConfig} config - Configuration for creating an event source.
*/
function createEventSource(config) {
return __awaiter(this, void 0, void 0, function* () {
return yield timeout((resolve, reject, deferOptions) => __awaiter(this, void 0, void 0, function* () {
var _a;
const abortController = createAbortController(deferOptions.signal);
const signal = abortController.signal;
if (signal.aborted) {
reject(new TonConnectError('Bridge connection aborted'));
return;
}
const url = new URL(addPathToUrl(config.bridgeUrl, config.ssePath));
url.searchParams.append('client_id', config.sessionId);
const lastEventId = yield config.bridgeGatewayStorage.getLastEventId();
if (lastEventId) {
url.searchParams.append('last_event_id', lastEventId);
}
if (config.traceId) {
url.searchParams.append('trace_id', config.traceId);
}
if (signal.aborted) {
reject(new TonConnectError('Bridge connection aborted'));
return;
}
const eventSource = new EventSource(url.toString());
eventSource.onerror = (reason) => __awaiter(this, void 0, void 0, function* () {
if (signal.aborted) {
eventSource.close();
reject(new TonConnectError('Bridge connection aborted'));
return;
}
try {
const newInstance = yield config.errorHandler(eventSource, reason);
if (newInstance !== eventSource) {
eventSource.close();
}
if (newInstance && newInstance !== eventSource) {
resolve(newInstance);
}
}
catch (e) {
eventSource.close();
reject(e);
}
});
eventSource.onopen = () => {
if (signal.aborted) {
eventSource.close();
reject(new TonConnectError('Bridge connection aborted'));
return;
}
resolve(eventSource);
};
eventSource.onmessage = (event) => {
if (signal.aborted) {
eventSource.close();
reject(new TonConnectError('Bridge connection aborted'));
return;
}
config.messageHandler(event);
};
(_a = config.signal) === null || _a === void 0 ? void 0 : _a.addEventListener('abort', () => {
eventSource.close();
reject(new TonConnectError('Bridge connection aborted'));
});
}), { timeout: config.openingDeadlineMS, signal: config.signal });
});
}
const CONNECTION_HTTP_EXPIRATION_TIME = 5 * 60 * 1000;
function isPendingConnectionHttp(connection) {
return !('connectEvent' in connection);
}
function isPendingConnectionHttpRaw(connection) {
return !('connectEvent' in connection);
}
function isExpiredPendingConnectionHttpRaw(connection) {
var _a;
return Date.now() - ((_a = connection.createdAt) !== null && _a !== void 0 ? _a : 0) > CONNECTION_HTTP_EXPIRATION_TIME;
}
const PROTOCOL_VERSION = 2;
/**
* The MIT License (MIT)
*
* Copyright (c) 2010-2020 Robert Kieffer and other contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// Unique ID creation requires a high quality random # generator. In the browser we therefore
// require the crypto API and do not support built-in fallback to lower quality random number
// generators (like Math.random()).
let getRandomValues;
const rnds8 = new Uint8Array(16);
function rng() {
// lazy load so that environments that need to polyfill have a chance to do so
if (!getRandomValues) {
if (typeof crypto === 'undefined' || !crypto.getRandomValues) {
throw new Error('crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported');
}
getRandomValues = crypto.getRandomValues.bind(crypto);
}
return getRandomValues(rnds8);
}
/**
* The MIT License (MIT)
*
* Copyright (c) 2010-2020 Robert Kieffer and other contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* Convert array of 16 byte values to UUID string format of the form:
* XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
*/
const byteToHex = [];
for (let i = 0; i < 256; ++i) {
byteToHex.push((i + 0x100).toString(16).slice(1));
}
function unsafeStringify(arr, offset = 0) {
// Note: Be careful editing this code! It's been tuned for performance
// and works in ways you may not expect. See https://github.com/uuidjs/uuid/pull/434
//
// Note to future-self: No, you can't remove the `toLowerCase()` call.
// REF: https://github.com/uuidjs/uuid/pull/677#issuecomment-1757351351
return (byteToHex[arr[offset + 0]] +
byteToHex[arr[offset + 1]] +
byteToHex[arr[offset + 2]] +
byteToHex[arr[offset + 3]] +
'-' +
byteToHex[arr[offset + 4]] +
byteToHex[arr[offset + 5]] +
'-' +
byteToHex[arr[offset + 6]] +
byteToHex[arr[offset + 7]] +
'-' +
byteToHex[arr[offset + 8]] +
byteToHex[arr[offset + 9]] +
'-' +
byteToHex[arr[offset + 10]] +
byteToHex[arr[offset + 11]] +
byteToHex[arr[offset + 12]] +
byteToHex[arr[offset + 13]] +
byteToHex[arr[offset + 14]] +
byteToHex[arr[offset + 15]]).toLowerCase();
}
/**
* The MIT License (MIT)
*
* Copyright (c) 2010-2020 Robert Kieffer and other contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
const _state = {};
function UUIDv7(options, buf, offset) {
var _a, _b, _c;
let bytes;
if (options) {
// With options: Make UUID independent of internal state
bytes = v7Bytes((_c = (_a = options.random) !== null && _a !== void 0 ? _a : (_b = options.rng) === null || _b === void 0 ? void 0 : _b.call(options)) !== null && _c !== void 0 ? _c : rng(), options.msecs, options.seq, buf, offset);
}
else {
// No options: Use internal state
const now = Date.now();
const rnds = rng();
updateV7State(_state, now, rnds);
bytes = v7Bytes(rnds, _state.msecs, _state.seq, buf, offset);
}
return buf !== null && buf !== void 0 ? buf : unsafeStringify(bytes);
}
// (Private!) Do not use. This method is only exported for testing purposes
// and may change without notice.
function updateV7State(state, now, rnds) {
var _a, _b;
(_a = state.msecs) !== null && _a !== void 0 ? _a : (state.msecs = -Infinity);
(_b = state.seq) !== null && _b !== void 0 ? _b : (state.seq = 0);
if (now > state.msecs) {
// Time has moved on! Pick a new random sequence number
state.seq = (rnds[6] << 23) | (rnds[7] << 16) | (rnds[8] << 8) | rnds[9];
state.msecs = now;
}
else {
// Bump sequence counter w/ 32-bit rollover
state.seq = (state.seq + 1) | 0;
// In case of rollover, bump timestamp to preserve monotonicity. This is
// allowed by the RFC and should self-correct as the system clock catches
// up. See https://www.rfc-editor.org/rfc/rfc9562.html#section-6.2-9.4
if (state.seq === 0) {
state.msecs++;
}
}
return state;
}
function v7Bytes(rnds, msecs, seq, buf, offset = 0) {
if (rnds.length < 16) {
throw new Error('Random bytes length must be >= 16');
}
if (!buf) {
buf = new Uint8Array(16);
offset = 0;
}
else {
if (offset < 0 || offset + 16 > buf.length) {
throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
}
}
// Defaults
msecs !== null && msecs !== void 0 ? msecs : (msecs = Date.now());
seq !== null && seq !== void 0 ? seq : (seq = ((rnds[6] * 0x7f) << 24) | (rnds[7] << 16) | (rnds[8] << 8) | rnds[9]);
// byte 0-5: timestamp (48 bits)
buf[offset++] = (msecs / 0x10000000000) & 0xff;
buf[offset++] = (msecs / 0x100000000) & 0xff;
buf[offset++] = (msecs / 0x1000000) & 0xff;
buf[offset++] = (msecs / 0x10000) & 0xff;
buf[offset++] = (msecs / 0x100) & 0xff;
buf[offset++] = msecs & 0xff;
// byte 6: `version` (4 bits) | sequence bits 28-31 (4 bits)
buf[offset++] = 0x70 | ((seq >>> 28) & 0x0f);
// byte 7: sequence bits 20-27 (8 bits)
buf[offset++] = (seq >>> 20) & 0xff;
// byte 8: `variant` (2 bits) | sequence bits 14-19 (6 bits)
buf[offset++] = 0x80 | ((seq >>> 14) & 0x3f);
// byte 9: sequence bits 6-13 (8 bits)
buf[offset++] = (seq >>> 6) & 0xff;
// byte 10: sequence bits 0-5 (6 bits) | random (2 bits)
buf[offset++] = ((seq << 2) & 0xff) | (rnds[10] & 0x03);
// bytes 11-15: random (40 bits)
buf[offset++] = rnds[11];
buf[offset++] = rnds[12];
buf[offset++] = rnds[13];
buf[offset++] = rnds[14];
buf[offset++] = rnds[15];
return buf;
}
function waitForSome(promises, count) {
return __awaiter(this, void 0, void 0, function* () {
if (count <= 0)
return [];
if (count > promises.length) {
throw new RangeError('count cannot be greater than the number of promises');
}
const results = new Array(promises.length);
let settledCount = 0;
return new Promise(resolve => {
promises.forEach((p, index) => {
Promise.resolve(p)
.then(value => ({ status: 'fulfilled', value }))
.catch(reason => ({ status: 'rejected', reason }))
.then(result => {
results[index] = result;
settledCount++;
if (settledCount === count) {
resolve(results);
}
});
});
});
});
}
class BridgeProvider {
static fromStorage(storage, analyticsManager) {
return __awaiter(this, void 0, void 0, function* () {
const connection = yield storage.getHttpConnection();
if (isPendingConnectionHttp(connection)) {
return new BridgeProvider(storage, connection.connectionSource, analyticsManager);
}
return new BridgeProvider(storage, { bridgeUrl: connection.session.bridgeUrl }, analyticsManager);
});
}
constructor(connectionStorage, walletConnectionSource, analyticsManager) {
var _a;
this.connectionStorage = connectionStorage;
this.walletConnectionSource = walletConnectionSource;
this.analyticsManager = analyticsManager;
this.type = 'http';
this.standardUniversalLink = 'tc://';
this.pendingRequests = new Map();
this.session = null;
this.gateway = null;
this.pendingGateways = [];
this.listeners = [];
this.defaultOpeningDeadlineMS = 12000;
this.defaultRetryTimeoutMS = 2000;
this.optionalOpenGateways = 3;
this.analytics = (_a = this.analyticsManager) === null || _a === void 0 ? void 0 : _a.scoped();
}
connect(message, options) {
var _a, _b;
const traceId = (_a = options === null || options === void 0 ? void 0 : options.traceId) !== null && _a !== void 0 ? _a : UUIDv7();
const abortController = createAbortController(options === null || options === void 0 ? void 0 : options.signal);
(_b = this.abortController) === null || _b === void 0 ? void 0 : _b.abort();
this.abortController = abortController;
this.closeGateways();
const sessionCrypto = new protocol.SessionCrypto();
this.session = {
sessionCrypto,
bridgeUrl: 'bridgeUrl' in this.walletConnectionSource
? this.walletConnectionSource.bridgeUrl
: ''
};
this.connectionStorage
.storeConnection({
type: 'http',
connectionSource: this.walletConnectionSource,
sessionCrypto
})
.then(() => __awaiter(this, void 0, void 0, function* () {
if (abortController.signal.aborted) {
return;
}
yield callForSuccess(_options => {
var _a;
return this.openGateways(sessionCrypto, {
openingDeadlineMS: (_a = options === null || options === void 0 ? void 0 : options.openingDeadlineMS) !== null && _a !== void 0 ? _a : this.defaultOpeningDeadlineMS,
signal: _options === null || _options === void 0 ? void 0 : _options.signal,
traceId
});
}, {
attempts: Number.MAX_SAFE_INTEGER,
delayMs: this.defaultRetryTimeoutMS,
signal: abortController.signal
});
}));
const universalLink = 'universalLink' in this.walletConnectionSource &&
this.walletConnectionSource.universalLink
? this.walletConnectionSource.universalLink
: this.standardUniversalLink;
return this.generateUniversalLink(universalLink, message, { traceId });
}
restoreConnection(options) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c;
const traceId = (_a = options === null || options === void 0 ? void 0 : options.traceId) !== null && _a !== void 0 ? _a : UUIDv7();
const abortController = createAbortController(options === null || options === void 0 ? void 0 : options.signal);
(_b = this.abortController) === null || _b === void 0 ? void 0 : _b.abort();
this.abortController = abortController;
if (abortController.signal.aborted) {
return;
}
this.closeGateways();
const storedConnection = yield this.connectionStorage.getHttpConnection();
if (!storedConnection) {
return;
}
if (abortController.signal.aborted) {
return;
}
const openingDeadlineMS = (_c = options === null || options === void 0 ? void 0 : options.openingDeadlineMS) !== null && _c !== void 0 ? _c : this.defaultOpeningDeadlineMS;
if (isPendingConnectionHttp(storedConnection)) {
this.session = {
sessionCrypto: storedConnection.sessionCrypto,
bridgeUrl: 'bridgeUrl' in this.walletConnectionSource
? this.walletConnectionSource.bridgeUrl
: ''
};
return yield this.openGateways(storedConnection.sessionCrypto, {
openingDeadlineMS: openingDeadlineMS,
signal: abortController === null || abortController === void 0 ? void 0 : abortController.signal,
traceId: options === null || options === void 0 ? void 0 : options.traceId
});
}
if (Array.isArray(this.walletConnectionSource)) {
throw new TonConnectError('Internal error. Connection source is array while WalletConnectionSourceHTTP was expected.');
}
this.se