@tonconnect/sdk 3.3.0-beta.1 → 3.3.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/tonconnect-sdk.min.js +1 -1
- package/dist/tonconnect-sdk.min.js.map +1 -1
- package/lib/cjs/index.cjs +345 -146
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/esm/index.mjs +337 -142
- package/lib/esm/index.mjs.map +1 -1
- package/lib/types/index.d.ts +33 -3
- package/package.json +10 -9
package/lib/esm/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CONNECT_EVENT_ERROR_CODES, SEND_TRANSACTION_ERROR_CODES, SIGN_DATA_ERROR_CODES, Base64, SessionCrypto, hexToByteArray, CONNECT_ITEM_ERROR_CODES } from '@tonconnect/protocol';
|
|
2
|
-
export { CHAIN, CONNECT_EVENT_ERROR_CODES, CONNECT_ITEM_ERROR_CODES, SEND_TRANSACTION_ERROR_CODES, SIGN_DATA_ERROR_CODES } from '@tonconnect/protocol';
|
|
2
|
+
export { CHAIN, CONNECT_EVENT_ERROR_CODES, CONNECT_ITEM_ERROR_CODES, SEND_TRANSACTION_ERROR_CODES, SIGN_DATA_ERROR_CODES, SessionCrypto } from '@tonconnect/protocol';
|
|
3
3
|
import '@tonconnect/isomorphic-eventsource';
|
|
4
4
|
import '@tonconnect/isomorphic-fetch';
|
|
5
5
|
|
|
@@ -412,12 +412,12 @@ function delay(timeout, options) {
|
|
|
412
412
|
return __awaiter(this, void 0, void 0, function* () {
|
|
413
413
|
return new Promise((resolve, reject) => {
|
|
414
414
|
var _a, _b;
|
|
415
|
-
if ((_a =
|
|
415
|
+
if ((_a = void 0 ) === null || _a === void 0 ? void 0 : _a.aborted) {
|
|
416
416
|
reject(new TonConnectError('Delay aborted'));
|
|
417
417
|
return;
|
|
418
418
|
}
|
|
419
419
|
const timeoutId = setTimeout(() => resolve(), timeout);
|
|
420
|
-
(_b =
|
|
420
|
+
(_b = void 0 ) === null || _b === void 0 ? void 0 : _b.addEventListener('abort', () => {
|
|
421
421
|
clearTimeout(timeoutId);
|
|
422
422
|
reject(new TonConnectError('Delay aborted'));
|
|
423
423
|
});
|
|
@@ -448,6 +448,7 @@ function createAbortController(signal) {
|
|
|
448
448
|
* @param {T} fn - function to call
|
|
449
449
|
* @param {CallForSuccessOptions} [options] - optional configuration options
|
|
450
450
|
*/
|
|
451
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
451
452
|
function callForSuccess(fn, options) {
|
|
452
453
|
return __awaiter(this, void 0, void 0, function* () {
|
|
453
454
|
var _a, _b;
|
|
@@ -512,6 +513,7 @@ function logWarning(...args) {
|
|
|
512
513
|
* @param {(...args: Args) => Promise<T>} createFn - A function that creates the resource.
|
|
513
514
|
* @param {(resource: T) => Promise<void>} [disposeFn] - An optional function that disposes the resource.
|
|
514
515
|
*/
|
|
516
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
515
517
|
function createResource(createFn, disposeFn) {
|
|
516
518
|
let currentResource = null;
|
|
517
519
|
let currentArgs = null;
|
|
@@ -1471,22 +1473,6 @@ function getWindow() {
|
|
|
1471
1473
|
}
|
|
1472
1474
|
return window;
|
|
1473
1475
|
}
|
|
1474
|
-
/**
|
|
1475
|
-
* The function try to get window keys, if it is not available it returns empty array.
|
|
1476
|
-
* As an example, for Safari's private mode it returns empty array, because the browser does not allow to get window keys.
|
|
1477
|
-
*/
|
|
1478
|
-
function tryGetWindowKeys() {
|
|
1479
|
-
const window = getWindow();
|
|
1480
|
-
if (!window) {
|
|
1481
|
-
return [];
|
|
1482
|
-
}
|
|
1483
|
-
try {
|
|
1484
|
-
return Object.keys(window);
|
|
1485
|
-
}
|
|
1486
|
-
catch (_a) {
|
|
1487
|
-
return [];
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
1476
|
function getDocument() {
|
|
1491
1477
|
if (typeof document === 'undefined') {
|
|
1492
1478
|
return undefined;
|
|
@@ -1531,6 +1517,43 @@ function isLocalStorageAvailable() {
|
|
|
1531
1517
|
function isNodeJs() {
|
|
1532
1518
|
return (typeof process !== 'undefined' && process.versions != null && process.versions.node != null);
|
|
1533
1519
|
}
|
|
1520
|
+
/**
|
|
1521
|
+
* Returns the current domain (hostname) if available.
|
|
1522
|
+
* In browser environment, returns window.location.hostname.
|
|
1523
|
+
* In Node.js environment or when window is not available, returns null.
|
|
1524
|
+
*/
|
|
1525
|
+
function getDomain() {
|
|
1526
|
+
try {
|
|
1527
|
+
// In browser environment
|
|
1528
|
+
if (typeof window !== 'undefined' && window.location) {
|
|
1529
|
+
return window.location.hostname;
|
|
1530
|
+
}
|
|
1531
|
+
else {
|
|
1532
|
+
// In Node.js environment, skip domain validation
|
|
1533
|
+
return null;
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
catch (_a) {
|
|
1537
|
+
return null;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
/**
|
|
1541
|
+
* Returns an array of [key, value] pairs from window object if available.
|
|
1542
|
+
* In browser environment, returns Object.entries(window).
|
|
1543
|
+
* In Node.js environment or when window is not available, returns empty array.
|
|
1544
|
+
*/
|
|
1545
|
+
function getWindowEntries() {
|
|
1546
|
+
const window = getWindow();
|
|
1547
|
+
if (!window) {
|
|
1548
|
+
return [];
|
|
1549
|
+
}
|
|
1550
|
+
try {
|
|
1551
|
+
return Object.entries(window);
|
|
1552
|
+
}
|
|
1553
|
+
catch (_a) {
|
|
1554
|
+
return [];
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1534
1557
|
|
|
1535
1558
|
class InjectedProvider {
|
|
1536
1559
|
static fromStorage(storage) {
|
|
@@ -1553,8 +1576,8 @@ class InjectedProvider {
|
|
|
1553
1576
|
if (!this.window) {
|
|
1554
1577
|
return [];
|
|
1555
1578
|
}
|
|
1556
|
-
const
|
|
1557
|
-
const wallets =
|
|
1579
|
+
const windowEntries = getWindowEntries();
|
|
1580
|
+
const wallets = windowEntries.filter(([_key, value]) => isJSBridgeWithMetadata(value));
|
|
1558
1581
|
return wallets.map(([jsBridgeKey, wallet]) => ({
|
|
1559
1582
|
name: wallet.tonconnect.walletInfo.name,
|
|
1560
1583
|
appName: wallet.tonconnect.walletInfo.app_name,
|
|
@@ -2100,11 +2123,6 @@ function enableQaMode() {
|
|
|
2100
2123
|
function isQaModeEnabled() {
|
|
2101
2124
|
return qaModeEnabled;
|
|
2102
2125
|
}
|
|
2103
|
-
function logValidationError(message) {
|
|
2104
|
-
if (isQaModeEnabled()) {
|
|
2105
|
-
console.error(`[QA Mode] Validation failed: ${message}`);
|
|
2106
|
-
}
|
|
2107
|
-
}
|
|
2108
2126
|
function showQaModeBanner() {
|
|
2109
2127
|
if (typeof window === 'undefined')
|
|
2110
2128
|
return;
|
|
@@ -2173,10 +2191,10 @@ function addQaModeStyles() {
|
|
|
2173
2191
|
function startBannerObserver() {
|
|
2174
2192
|
if (typeof window === 'undefined' || bannerObserver)
|
|
2175
2193
|
return;
|
|
2176
|
-
bannerObserver = new MutationObserver(
|
|
2177
|
-
mutations.forEach(
|
|
2194
|
+
bannerObserver = new MutationObserver(mutations => {
|
|
2195
|
+
mutations.forEach(mutation => {
|
|
2178
2196
|
if (mutation.type === 'childList') {
|
|
2179
|
-
mutation.removedNodes.forEach(
|
|
2197
|
+
mutation.removedNodes.forEach(node => {
|
|
2180
2198
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
2181
2199
|
const element = node;
|
|
2182
2200
|
if (element.id === 'ton-connect-qa-banner' && qaModeEnabled) {
|
|
@@ -2205,10 +2223,11 @@ function startBannerObserver() {
|
|
|
2205
2223
|
class WalletsListManager {
|
|
2206
2224
|
constructor(options) {
|
|
2207
2225
|
var _a;
|
|
2208
|
-
this.
|
|
2209
|
-
this.
|
|
2226
|
+
this.walletsListDTOCache = null;
|
|
2227
|
+
this.walletsListDTOCacheCreationTimestamp = null;
|
|
2210
2228
|
if (isQaModeEnabled()) {
|
|
2211
|
-
this.walletsListSource =
|
|
2229
|
+
this.walletsListSource =
|
|
2230
|
+
'https://raw.githubusercontent.com/ton-connect/wallets-list-staging/refs/heads/main/wallets-v2.json';
|
|
2212
2231
|
}
|
|
2213
2232
|
else {
|
|
2214
2233
|
this.walletsListSource =
|
|
@@ -2218,23 +2237,11 @@ class WalletsListManager {
|
|
|
2218
2237
|
}
|
|
2219
2238
|
getWallets() {
|
|
2220
2239
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2221
|
-
|
|
2222
|
-
this.
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
if (!this.walletsListCache) {
|
|
2227
|
-
this.walletsListCache = this.fetchWalletsList();
|
|
2228
|
-
this.walletsListCache
|
|
2229
|
-
.then(() => {
|
|
2230
|
-
this.walletsListCacheCreationTimestamp = Date.now();
|
|
2231
|
-
})
|
|
2232
|
-
.catch(() => {
|
|
2233
|
-
this.walletsListCache = null;
|
|
2234
|
-
this.walletsListCacheCreationTimestamp = null;
|
|
2235
|
-
});
|
|
2236
|
-
}
|
|
2237
|
-
return this.walletsListCache;
|
|
2240
|
+
const [walletsListDTO, currentlyInjectedWallets] = yield Promise.all([
|
|
2241
|
+
this.fetchWalletsListDTO(),
|
|
2242
|
+
this.getCurrentlyInjectedWallets()
|
|
2243
|
+
]);
|
|
2244
|
+
return this.mergeWalletsLists(this.walletConfigDTOListToWalletConfigList(walletsListDTO), currentlyInjectedWallets);
|
|
2238
2245
|
});
|
|
2239
2246
|
}
|
|
2240
2247
|
getEmbeddedWallet() {
|
|
@@ -2244,7 +2251,28 @@ class WalletsListManager {
|
|
|
2244
2251
|
return embeddedWallets.length === 1 ? embeddedWallets[0] : null;
|
|
2245
2252
|
});
|
|
2246
2253
|
}
|
|
2247
|
-
|
|
2254
|
+
fetchWalletsListDTO() {
|
|
2255
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2256
|
+
if (this.cacheTTLMs &&
|
|
2257
|
+
this.walletsListDTOCacheCreationTimestamp &&
|
|
2258
|
+
Date.now() > this.walletsListDTOCacheCreationTimestamp + this.cacheTTLMs) {
|
|
2259
|
+
this.walletsListDTOCache = null;
|
|
2260
|
+
}
|
|
2261
|
+
if (!this.walletsListDTOCache) {
|
|
2262
|
+
this.walletsListDTOCache = this.fetchWalletsListFromSource();
|
|
2263
|
+
this.walletsListDTOCache
|
|
2264
|
+
.then(() => {
|
|
2265
|
+
this.walletsListDTOCacheCreationTimestamp = Date.now();
|
|
2266
|
+
})
|
|
2267
|
+
.catch(() => {
|
|
2268
|
+
this.walletsListDTOCache = null;
|
|
2269
|
+
this.walletsListDTOCacheCreationTimestamp = null;
|
|
2270
|
+
});
|
|
2271
|
+
}
|
|
2272
|
+
return this.walletsListDTOCache;
|
|
2273
|
+
});
|
|
2274
|
+
}
|
|
2275
|
+
fetchWalletsListFromSource() {
|
|
2248
2276
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2249
2277
|
let walletsList = [];
|
|
2250
2278
|
try {
|
|
@@ -2265,16 +2293,21 @@ class WalletsListManager {
|
|
|
2265
2293
|
logError(e);
|
|
2266
2294
|
walletsList = FALLBACK_WALLETS_LIST;
|
|
2267
2295
|
}
|
|
2268
|
-
|
|
2269
|
-
try {
|
|
2270
|
-
currentlyInjectedWallets = InjectedProvider.getCurrentlyInjectedWallets();
|
|
2271
|
-
}
|
|
2272
|
-
catch (e) {
|
|
2273
|
-
logError(e);
|
|
2274
|
-
}
|
|
2275
|
-
return this.mergeWalletsLists(this.walletConfigDTOListToWalletConfigList(walletsList), currentlyInjectedWallets);
|
|
2296
|
+
return walletsList;
|
|
2276
2297
|
});
|
|
2277
2298
|
}
|
|
2299
|
+
getCurrentlyInjectedWallets() {
|
|
2300
|
+
if (!isQaModeEnabled()) {
|
|
2301
|
+
return [];
|
|
2302
|
+
}
|
|
2303
|
+
try {
|
|
2304
|
+
return InjectedProvider.getCurrentlyInjectedWallets();
|
|
2305
|
+
}
|
|
2306
|
+
catch (e) {
|
|
2307
|
+
logError(e);
|
|
2308
|
+
return [];
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2278
2311
|
walletConfigDTOListToWalletConfigList(walletConfigDTO) {
|
|
2279
2312
|
return walletConfigDTO.map(walletConfigDTO => {
|
|
2280
2313
|
const walletConfig = {
|
|
@@ -2917,8 +2950,9 @@ class TonConnectTracker {
|
|
|
2917
2950
|
}
|
|
2918
2951
|
}
|
|
2919
2952
|
|
|
2920
|
-
const tonConnectSdkVersion = "3.3.0-beta.
|
|
2953
|
+
const tonConnectSdkVersion = "3.3.0-beta.3";
|
|
2921
2954
|
|
|
2955
|
+
const bounceableTag = 0x11;
|
|
2922
2956
|
const noBounceableTag = 0x51;
|
|
2923
2957
|
const testOnlyTag = 0x80;
|
|
2924
2958
|
/**
|
|
@@ -2988,23 +3022,40 @@ function parseUserFriendlyAddress(address) {
|
|
|
2988
3022
|
throw new WrongAddressError(`Invalid address length: ${address}`);
|
|
2989
3023
|
}
|
|
2990
3024
|
const addr = decoded.slice(0, 34);
|
|
2991
|
-
const checksum = decoded.slice(34);
|
|
3025
|
+
const checksum = decoded.slice(34, 36);
|
|
2992
3026
|
const calculatedChecksum = crc16(addr);
|
|
2993
3027
|
if (!checksum.every((byte, i) => byte === calculatedChecksum[i])) {
|
|
2994
3028
|
throw new WrongAddressError(`Invalid checksum in address: ${address}`);
|
|
2995
3029
|
}
|
|
2996
|
-
|
|
2997
|
-
|
|
3030
|
+
let tag = addr[0];
|
|
3031
|
+
let isTestOnly = false;
|
|
3032
|
+
let isBounceable = false;
|
|
3033
|
+
if (tag & testOnlyTag) {
|
|
3034
|
+
isTestOnly = true;
|
|
3035
|
+
tag = tag ^ testOnlyTag;
|
|
3036
|
+
}
|
|
3037
|
+
if (tag !== bounceableTag && tag !== noBounceableTag) {
|
|
3038
|
+
throw new WrongAddressError(`Unknown address tag: ${tag}`);
|
|
3039
|
+
}
|
|
3040
|
+
isBounceable = tag === bounceableTag;
|
|
3041
|
+
let wc = null;
|
|
3042
|
+
if (addr[1] === 0xff) {
|
|
3043
|
+
// TODO we should read signed integer here
|
|
3044
|
+
wc = -1;
|
|
3045
|
+
}
|
|
3046
|
+
else {
|
|
3047
|
+
wc = addr[1];
|
|
3048
|
+
}
|
|
2998
3049
|
const hex = addr.slice(2);
|
|
2999
3050
|
if (wc !== 0 && wc !== -1) {
|
|
3000
3051
|
throw new WrongAddressError(`Invalid workchain: ${wc}`);
|
|
3001
3052
|
}
|
|
3002
|
-
const testOnly = (tag & testOnlyTag) !== 0;
|
|
3003
|
-
const isBounceable = (tag & 0x40) !== 0;
|
|
3004
3053
|
return {
|
|
3005
3054
|
wc,
|
|
3006
|
-
hex: Array.from(hex)
|
|
3007
|
-
|
|
3055
|
+
hex: Array.from(hex)
|
|
3056
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
3057
|
+
.join(''),
|
|
3058
|
+
testOnly: isTestOnly,
|
|
3008
3059
|
isBounceable
|
|
3009
3060
|
};
|
|
3010
3061
|
}
|
|
@@ -3081,12 +3132,21 @@ const BASE64_REGEX = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{
|
|
|
3081
3132
|
const BOC_PREFIX = 'te6cc';
|
|
3082
3133
|
const INTEGER_REGEX = /^-?\d+$/;
|
|
3083
3134
|
const POSITIVE_INTEGER_REGEX = /^\d+$/;
|
|
3135
|
+
const MAX_DOMAIN_BYTES = 128;
|
|
3136
|
+
const MAX_PAYLOAD_BYTES = 128;
|
|
3137
|
+
const MAX_TOTAL_BYTES = 222;
|
|
3084
3138
|
function isValidNumber(value) {
|
|
3085
3139
|
return typeof value === 'number' && !isNaN(value);
|
|
3086
3140
|
}
|
|
3087
3141
|
function isValidString(value) {
|
|
3088
3142
|
return typeof value === 'string' && value.length > 0;
|
|
3089
3143
|
}
|
|
3144
|
+
function isValidAddress(value) {
|
|
3145
|
+
return isValidString(value) && (isValidRawAddress(value) || isValidUserFriendlyAddress(value));
|
|
3146
|
+
}
|
|
3147
|
+
function isValidNetwork(value) {
|
|
3148
|
+
return isValidString(value) && /^-?\d+$/.test(value);
|
|
3149
|
+
}
|
|
3090
3150
|
function isValidBoc(value) {
|
|
3091
3151
|
return typeof value === 'string' && BASE64_REGEX.test(value) && value.startsWith(BOC_PREFIX);
|
|
3092
3152
|
}
|
|
@@ -3100,49 +3160,33 @@ function hasExtraProperties(obj, allowedKeys) {
|
|
|
3100
3160
|
return Object.keys(obj).some(key => !allowedKeys.includes(key));
|
|
3101
3161
|
}
|
|
3102
3162
|
function validateSendTransactionRequest(data) {
|
|
3103
|
-
console.log('[Validation Debug] validateSendTransactionRequest called');
|
|
3104
|
-
console.log('[Validation Debug] isQaModeEnabled():', isQaModeEnabled());
|
|
3105
3163
|
if (!isValidObject(data)) {
|
|
3106
|
-
|
|
3107
|
-
logValidationError(error);
|
|
3108
|
-
const shouldReturnNull = isQaModeEnabled();
|
|
3109
|
-
console.log('[Validation Debug] Should return null:', shouldReturnNull);
|
|
3110
|
-
return shouldReturnNull ? null : error;
|
|
3164
|
+
return 'Request must be an object';
|
|
3111
3165
|
}
|
|
3112
3166
|
const allowedKeys = ['validUntil', 'network', 'from', 'messages'];
|
|
3113
3167
|
if (hasExtraProperties(data, allowedKeys)) {
|
|
3114
|
-
|
|
3115
|
-
logValidationError(error);
|
|
3116
|
-
return isQaModeEnabled() ? null : error;
|
|
3168
|
+
return 'Request contains extra properties';
|
|
3117
3169
|
}
|
|
3118
|
-
if (
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3170
|
+
if (data.validUntil) {
|
|
3171
|
+
if (!isValidNumber(data.validUntil)) {
|
|
3172
|
+
return "Incorrect 'validUntil'";
|
|
3173
|
+
}
|
|
3174
|
+
const now = Math.floor(Date.now() / 1000);
|
|
3175
|
+
const fiveMinutesFromNow = now + 300;
|
|
3176
|
+
if (data.validUntil > fiveMinutesFromNow) {
|
|
3177
|
+
console.warn(`validUntil (${data.validUntil}) is more than 5 minutes from now (${now})`);
|
|
3178
|
+
}
|
|
3127
3179
|
}
|
|
3128
3180
|
if (data.network !== undefined) {
|
|
3129
|
-
if (!
|
|
3130
|
-
|
|
3131
|
-
logValidationError(error);
|
|
3132
|
-
return isQaModeEnabled() ? null : error;
|
|
3181
|
+
if (!isValidNetwork(data.network)) {
|
|
3182
|
+
return "Invalid 'network' format";
|
|
3133
3183
|
}
|
|
3134
3184
|
}
|
|
3135
|
-
if (data.from !== undefined) {
|
|
3136
|
-
|
|
3137
|
-
const error = "Invalid 'from' address format";
|
|
3138
|
-
logValidationError(error);
|
|
3139
|
-
return isQaModeEnabled() ? null : error;
|
|
3140
|
-
}
|
|
3185
|
+
if (data.from !== undefined && !isValidAddress(data.from)) {
|
|
3186
|
+
return "Invalid 'from' address format";
|
|
3141
3187
|
}
|
|
3142
3188
|
if (!isValidArray(data.messages) || data.messages.length === 0) {
|
|
3143
|
-
|
|
3144
|
-
logValidationError(error);
|
|
3145
|
-
return isQaModeEnabled() ? null : error;
|
|
3189
|
+
return "'messages' is required";
|
|
3146
3190
|
}
|
|
3147
3191
|
for (let i = 0; i < data.messages.length; i++) {
|
|
3148
3192
|
const message = data.messages[i];
|
|
@@ -3188,7 +3232,9 @@ function validateTransactionMessage(message, index) {
|
|
|
3188
3232
|
return `Invalid 'extraCurrency' in message at index ${index}`;
|
|
3189
3233
|
}
|
|
3190
3234
|
for (const [key, value] of Object.entries(message.extraCurrency)) {
|
|
3191
|
-
if (!INTEGER_REGEX.test(key) ||
|
|
3235
|
+
if (!INTEGER_REGEX.test(key) ||
|
|
3236
|
+
typeof value !== 'string' ||
|
|
3237
|
+
!POSITIVE_INTEGER_REGEX.test(value)) {
|
|
3192
3238
|
return `Invalid 'extraCurrency' format in message at index ${index}`;
|
|
3193
3239
|
}
|
|
3194
3240
|
}
|
|
@@ -3197,20 +3243,46 @@ function validateTransactionMessage(message, index) {
|
|
|
3197
3243
|
}
|
|
3198
3244
|
function validateConnectAdditionalRequest(data) {
|
|
3199
3245
|
if (!isValidObject(data)) {
|
|
3200
|
-
return
|
|
3246
|
+
return 'Request must be an object';
|
|
3201
3247
|
}
|
|
3202
3248
|
const allowedKeys = ['tonProof'];
|
|
3203
3249
|
if (hasExtraProperties(data, allowedKeys)) {
|
|
3204
|
-
return
|
|
3250
|
+
return 'Request contains extra properties';
|
|
3205
3251
|
}
|
|
3206
|
-
if (data.tonProof !== undefined
|
|
3207
|
-
|
|
3252
|
+
if (data.tonProof !== undefined) {
|
|
3253
|
+
if (typeof data.tonProof !== 'string') {
|
|
3254
|
+
return "Invalid 'tonProof'";
|
|
3255
|
+
}
|
|
3256
|
+
const payload = data.tonProof;
|
|
3257
|
+
if (payload.length === 0) {
|
|
3258
|
+
return "Empty 'tonProof' payload";
|
|
3259
|
+
}
|
|
3260
|
+
// Get current domain for validation first
|
|
3261
|
+
const domain = getDomain();
|
|
3262
|
+
if (!domain) {
|
|
3263
|
+
// In Node.js environment, skip domain validation
|
|
3264
|
+
return null;
|
|
3265
|
+
}
|
|
3266
|
+
// Validate domain size (max 128 bytes)
|
|
3267
|
+
const domainBytes = new TextEncoder().encode(domain).length;
|
|
3268
|
+
if (domainBytes > MAX_DOMAIN_BYTES) {
|
|
3269
|
+
return 'Current domain exceeds 128 bytes limit';
|
|
3270
|
+
}
|
|
3271
|
+
// Validate payload size (max 128 bytes)
|
|
3272
|
+
const payloadBytes = new TextEncoder().encode(payload).length;
|
|
3273
|
+
if (payloadBytes > MAX_PAYLOAD_BYTES) {
|
|
3274
|
+
return "'tonProof' payload exceeds 128 bytes limit";
|
|
3275
|
+
}
|
|
3276
|
+
// Validate total size (domain + payload <= 222 bytes)
|
|
3277
|
+
if (domainBytes + payloadBytes > MAX_TOTAL_BYTES) {
|
|
3278
|
+
return "'tonProof' domain + payload exceeds 222 bytes limit";
|
|
3279
|
+
}
|
|
3208
3280
|
}
|
|
3209
3281
|
return null;
|
|
3210
3282
|
}
|
|
3211
3283
|
function validateSignDataPayload(data) {
|
|
3212
3284
|
if (!isValidObject(data)) {
|
|
3213
|
-
return
|
|
3285
|
+
return 'Payload must be an object';
|
|
3214
3286
|
}
|
|
3215
3287
|
if (!isValidString(data.type)) {
|
|
3216
3288
|
return "'type' is required";
|
|
@@ -3229,17 +3301,17 @@ function validateSignDataPayload(data) {
|
|
|
3229
3301
|
function validateSignDataPayloadText(data) {
|
|
3230
3302
|
const allowedKeys = ['type', 'text', 'network', 'from'];
|
|
3231
3303
|
if (hasExtraProperties(data, allowedKeys)) {
|
|
3232
|
-
return
|
|
3304
|
+
return 'Text payload contains extra properties';
|
|
3233
3305
|
}
|
|
3234
3306
|
if (!isValidString(data.text)) {
|
|
3235
3307
|
return "'text' is required";
|
|
3236
3308
|
}
|
|
3237
3309
|
if (data.network !== undefined) {
|
|
3238
|
-
if (!
|
|
3310
|
+
if (!isValidNetwork(data.network)) {
|
|
3239
3311
|
return "Invalid 'network' format";
|
|
3240
3312
|
}
|
|
3241
3313
|
}
|
|
3242
|
-
if (data.from !== undefined && !
|
|
3314
|
+
if (data.from !== undefined && !isValidAddress(data.from)) {
|
|
3243
3315
|
return "Invalid 'from'";
|
|
3244
3316
|
}
|
|
3245
3317
|
return null;
|
|
@@ -3247,17 +3319,17 @@ function validateSignDataPayloadText(data) {
|
|
|
3247
3319
|
function validateSignDataPayloadBinary(data) {
|
|
3248
3320
|
const allowedKeys = ['type', 'bytes', 'network', 'from'];
|
|
3249
3321
|
if (hasExtraProperties(data, allowedKeys)) {
|
|
3250
|
-
return
|
|
3322
|
+
return 'Binary payload contains extra properties';
|
|
3251
3323
|
}
|
|
3252
3324
|
if (!isValidString(data.bytes)) {
|
|
3253
3325
|
return "'bytes' is required";
|
|
3254
3326
|
}
|
|
3255
3327
|
if (data.network !== undefined) {
|
|
3256
|
-
if (!
|
|
3328
|
+
if (!isValidNetwork(data.network)) {
|
|
3257
3329
|
return "Invalid 'network' format";
|
|
3258
3330
|
}
|
|
3259
3331
|
}
|
|
3260
|
-
if (data.from !== undefined && !
|
|
3332
|
+
if (data.from !== undefined && !isValidAddress(data.from)) {
|
|
3261
3333
|
return "Invalid 'from'";
|
|
3262
3334
|
}
|
|
3263
3335
|
return null;
|
|
@@ -3265,7 +3337,7 @@ function validateSignDataPayloadBinary(data) {
|
|
|
3265
3337
|
function validateSignDataPayloadCell(data) {
|
|
3266
3338
|
const allowedKeys = ['type', 'schema', 'cell', 'network', 'from'];
|
|
3267
3339
|
if (hasExtraProperties(data, allowedKeys)) {
|
|
3268
|
-
return
|
|
3340
|
+
return 'Cell payload contains extra properties';
|
|
3269
3341
|
}
|
|
3270
3342
|
if (!isValidString(data.schema)) {
|
|
3271
3343
|
return "'schema' is required";
|
|
@@ -3277,15 +3349,95 @@ function validateSignDataPayloadCell(data) {
|
|
|
3277
3349
|
return "Invalid 'cell' format (must be valid base64)";
|
|
3278
3350
|
}
|
|
3279
3351
|
if (data.network !== undefined) {
|
|
3280
|
-
if (!
|
|
3352
|
+
if (!isValidNetwork(data.network)) {
|
|
3281
3353
|
return "Invalid 'network' format";
|
|
3282
3354
|
}
|
|
3283
3355
|
}
|
|
3284
|
-
if (data.from !== undefined && !
|
|
3356
|
+
if (data.from !== undefined && !isValidAddress(data.from)) {
|
|
3285
3357
|
return "Invalid 'from'";
|
|
3286
3358
|
}
|
|
3287
3359
|
return null;
|
|
3288
3360
|
}
|
|
3361
|
+
/**
|
|
3362
|
+
* Validates ton_proof item received from wallet in connect event.
|
|
3363
|
+
*/
|
|
3364
|
+
// eslint-disable-next-line complexity
|
|
3365
|
+
function validateTonProofItemReply(data) {
|
|
3366
|
+
if (!isValidObject(data)) {
|
|
3367
|
+
return 'ton_proof item must be an object';
|
|
3368
|
+
}
|
|
3369
|
+
const allowedKeys = ['error', 'proof', 'name'];
|
|
3370
|
+
if (hasExtraProperties(data, allowedKeys)) {
|
|
3371
|
+
return 'ton_proof item contains extra properties';
|
|
3372
|
+
}
|
|
3373
|
+
const hasProof = Object.prototype.hasOwnProperty.call(data, 'proof');
|
|
3374
|
+
const hasError = Object.prototype.hasOwnProperty.call(data, 'error');
|
|
3375
|
+
if (!hasProof && !hasError) {
|
|
3376
|
+
return "'ton_proof' item must contain either 'proof' or 'error'";
|
|
3377
|
+
}
|
|
3378
|
+
if (hasProof && hasError) {
|
|
3379
|
+
return "'ton_proof' item must contain either 'proof' or 'error', not both";
|
|
3380
|
+
}
|
|
3381
|
+
if (hasProof) {
|
|
3382
|
+
const proof = data.proof;
|
|
3383
|
+
if (!isValidObject(proof)) {
|
|
3384
|
+
return "Invalid 'proof' object";
|
|
3385
|
+
}
|
|
3386
|
+
const allowedProofKeys = ['timestamp', 'domain', 'payload', 'signature'];
|
|
3387
|
+
if (hasExtraProperties(proof, allowedProofKeys)) {
|
|
3388
|
+
return 'ton_proof item contains extra properties';
|
|
3389
|
+
}
|
|
3390
|
+
if (!isValidNumber(proof.timestamp)) {
|
|
3391
|
+
return "Invalid 'proof.timestamp'";
|
|
3392
|
+
}
|
|
3393
|
+
const domain = proof.domain;
|
|
3394
|
+
if (!isValidObject(domain)) {
|
|
3395
|
+
return "Invalid 'proof.domain'";
|
|
3396
|
+
}
|
|
3397
|
+
if (!isValidNumber(domain.lengthBytes)) {
|
|
3398
|
+
return "Invalid 'proof.domain.lengthBytes'";
|
|
3399
|
+
}
|
|
3400
|
+
if (!isValidString(domain.value)) {
|
|
3401
|
+
return "Invalid 'proof.domain.value'";
|
|
3402
|
+
}
|
|
3403
|
+
// Try to verify that provided byte length matches actual byte length of value
|
|
3404
|
+
try {
|
|
3405
|
+
const encoderAvailable = typeof TextEncoder !== 'undefined';
|
|
3406
|
+
const actualLength = encoderAvailable
|
|
3407
|
+
? new TextEncoder().encode(domain.value).length
|
|
3408
|
+
: domain.value.length;
|
|
3409
|
+
if (actualLength !== domain.lengthBytes) {
|
|
3410
|
+
return "'proof.domain.lengthBytes' does not match 'proof.domain.value'";
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
catch (_a) {
|
|
3414
|
+
// Ignore environment issues; best-effort validation
|
|
3415
|
+
}
|
|
3416
|
+
if (!isValidString(proof.payload)) {
|
|
3417
|
+
return "Invalid 'proof.payload'";
|
|
3418
|
+
}
|
|
3419
|
+
if (!isValidString(proof.signature) || !BASE64_REGEX.test(proof.signature)) {
|
|
3420
|
+
return "Invalid 'proof.signature' format";
|
|
3421
|
+
}
|
|
3422
|
+
}
|
|
3423
|
+
if (hasError) {
|
|
3424
|
+
const error = data.error;
|
|
3425
|
+
if (!isValidObject(error)) {
|
|
3426
|
+
return "Invalid 'error' object";
|
|
3427
|
+
}
|
|
3428
|
+
const allowedErrorKeys = ['code', 'message'];
|
|
3429
|
+
if (hasExtraProperties(error, allowedErrorKeys)) {
|
|
3430
|
+
return 'ton_proof error contains extra properties';
|
|
3431
|
+
}
|
|
3432
|
+
if (!isValidNumber(error.code)) {
|
|
3433
|
+
return "Invalid 'error.code'";
|
|
3434
|
+
}
|
|
3435
|
+
if (!isValidString(error.message)) {
|
|
3436
|
+
return "Invalid 'error.message'";
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
return null;
|
|
3440
|
+
}
|
|
3289
3441
|
|
|
3290
3442
|
class TonConnect {
|
|
3291
3443
|
/**
|
|
@@ -3609,6 +3761,34 @@ class TonConnect {
|
|
|
3609
3761
|
prevAbortController === null || prevAbortController === void 0 ? void 0 : prevAbortController.abort();
|
|
3610
3762
|
});
|
|
3611
3763
|
}
|
|
3764
|
+
/**
|
|
3765
|
+
* Gets the current session ID if available.
|
|
3766
|
+
* @returns session ID string or null if not available.
|
|
3767
|
+
*/
|
|
3768
|
+
getSessionId() {
|
|
3769
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
3770
|
+
if (!this.provider || !this.connected) {
|
|
3771
|
+
return null;
|
|
3772
|
+
}
|
|
3773
|
+
try {
|
|
3774
|
+
const connection = yield this.bridgeConnectionStorage.getConnection();
|
|
3775
|
+
if (!connection || connection.type === 'injected') {
|
|
3776
|
+
return null;
|
|
3777
|
+
}
|
|
3778
|
+
if ('sessionCrypto' in connection) {
|
|
3779
|
+
// Pending connection
|
|
3780
|
+
return connection.sessionCrypto.sessionId;
|
|
3781
|
+
}
|
|
3782
|
+
else {
|
|
3783
|
+
// Established connection
|
|
3784
|
+
return connection.session.sessionCrypto.sessionId;
|
|
3785
|
+
}
|
|
3786
|
+
}
|
|
3787
|
+
catch (_a) {
|
|
3788
|
+
return null;
|
|
3789
|
+
}
|
|
3790
|
+
});
|
|
3791
|
+
}
|
|
3612
3792
|
/**
|
|
3613
3793
|
* Pause bridge HTTP connection. Might be helpful, if you want to pause connections while browser tab is unfocused,
|
|
3614
3794
|
* or if you use SDK with NodeJS and want to save server resources.
|
|
@@ -3698,44 +3878,59 @@ class TonConnect {
|
|
|
3698
3878
|
}
|
|
3699
3879
|
};
|
|
3700
3880
|
if (tonProofItem) {
|
|
3881
|
+
const validationError = validateTonProofItemReply(tonProofItem);
|
|
3701
3882
|
let tonProof = undefined;
|
|
3702
|
-
|
|
3703
|
-
if (
|
|
3704
|
-
|
|
3705
|
-
name: 'ton_proof',
|
|
3706
|
-
proof: {
|
|
3707
|
-
timestamp: tonProofItem.proof.timestamp,
|
|
3708
|
-
domain: {
|
|
3709
|
-
lengthBytes: tonProofItem.proof.domain.lengthBytes,
|
|
3710
|
-
value: tonProofItem.proof.domain.value,
|
|
3711
|
-
},
|
|
3712
|
-
payload: tonProofItem.proof.payload,
|
|
3713
|
-
signature: tonProofItem.proof.signature,
|
|
3714
|
-
}
|
|
3715
|
-
};
|
|
3716
|
-
}
|
|
3717
|
-
else if ('error' in tonProofItem) { // error
|
|
3718
|
-
tonProof = {
|
|
3719
|
-
name: 'ton_proof',
|
|
3720
|
-
error: {
|
|
3721
|
-
code: tonProofItem.error.code,
|
|
3722
|
-
message: tonProofItem.error.message,
|
|
3723
|
-
}
|
|
3724
|
-
};
|
|
3725
|
-
}
|
|
3726
|
-
else {
|
|
3727
|
-
throw new TonConnectError('Invalid data format');
|
|
3883
|
+
if (validationError) {
|
|
3884
|
+
if (isQaModeEnabled()) {
|
|
3885
|
+
console.error('TonProofItem validation failed: ' + validationError);
|
|
3728
3886
|
}
|
|
3729
|
-
}
|
|
3730
|
-
catch (e) {
|
|
3731
3887
|
tonProof = {
|
|
3732
3888
|
name: 'ton_proof',
|
|
3733
3889
|
error: {
|
|
3734
3890
|
code: CONNECT_ITEM_ERROR_CODES.UNKNOWN_ERROR,
|
|
3735
|
-
message:
|
|
3891
|
+
message: validationError
|
|
3736
3892
|
}
|
|
3737
3893
|
};
|
|
3738
3894
|
}
|
|
3895
|
+
else {
|
|
3896
|
+
try {
|
|
3897
|
+
if ('proof' in tonProofItem) {
|
|
3898
|
+
tonProof = {
|
|
3899
|
+
name: 'ton_proof',
|
|
3900
|
+
proof: {
|
|
3901
|
+
timestamp: tonProofItem.proof.timestamp,
|
|
3902
|
+
domain: {
|
|
3903
|
+
lengthBytes: tonProofItem.proof.domain.lengthBytes,
|
|
3904
|
+
value: tonProofItem.proof.domain.value
|
|
3905
|
+
},
|
|
3906
|
+
payload: tonProofItem.proof.payload,
|
|
3907
|
+
signature: tonProofItem.proof.signature
|
|
3908
|
+
}
|
|
3909
|
+
};
|
|
3910
|
+
}
|
|
3911
|
+
else if ('error' in tonProofItem) {
|
|
3912
|
+
tonProof = {
|
|
3913
|
+
name: 'ton_proof',
|
|
3914
|
+
error: {
|
|
3915
|
+
code: tonProofItem.error.code,
|
|
3916
|
+
message: tonProofItem.error.message
|
|
3917
|
+
}
|
|
3918
|
+
};
|
|
3919
|
+
}
|
|
3920
|
+
else {
|
|
3921
|
+
throw new TonConnectError('Invalid data format');
|
|
3922
|
+
}
|
|
3923
|
+
}
|
|
3924
|
+
catch (e) {
|
|
3925
|
+
tonProof = {
|
|
3926
|
+
name: 'ton_proof',
|
|
3927
|
+
error: {
|
|
3928
|
+
code: CONNECT_ITEM_ERROR_CODES.UNKNOWN_ERROR,
|
|
3929
|
+
message: 'Invalid data format'
|
|
3930
|
+
}
|
|
3931
|
+
};
|
|
3932
|
+
}
|
|
3933
|
+
}
|
|
3739
3934
|
wallet.connectItems = { tonProof };
|
|
3740
3935
|
}
|
|
3741
3936
|
this.wallet = wallet;
|