ton-provider-system 0.1.9 → 0.1.11
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/index.cjs +144 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -2
- package/dist/index.d.ts +26 -2
- package/dist/index.js +144 -24
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/rpc.json +4 -2
package/dist/index.cjs
CHANGED
|
@@ -217,7 +217,8 @@ function resolveProvider(id, config) {
|
|
|
217
217
|
apiKey,
|
|
218
218
|
rps: config.rps,
|
|
219
219
|
priority: config.priority,
|
|
220
|
-
isDynamic: config.isDynamic || false
|
|
220
|
+
isDynamic: config.isDynamic || false,
|
|
221
|
+
browserCompatible: config.browserCompatible !== void 0 ? config.browserCompatible : true
|
|
221
222
|
};
|
|
222
223
|
}
|
|
223
224
|
function resolveAllProviders(config) {
|
|
@@ -776,7 +777,8 @@ var HealthChecker = class {
|
|
|
776
777
|
latencyMs: null,
|
|
777
778
|
seqno: null,
|
|
778
779
|
blocksBehind: 0,
|
|
779
|
-
lastTested: null
|
|
780
|
+
lastTested: null,
|
|
781
|
+
browserCompatible: provider.browserCompatible
|
|
780
782
|
};
|
|
781
783
|
this.results.set(key, testingResult);
|
|
782
784
|
try {
|
|
@@ -836,7 +838,8 @@ var HealthChecker = class {
|
|
|
836
838
|
seqno,
|
|
837
839
|
blocksBehind,
|
|
838
840
|
lastTested: /* @__PURE__ */ new Date(),
|
|
839
|
-
cachedEndpoint: normalizedEndpoint
|
|
841
|
+
cachedEndpoint: normalizedEndpoint,
|
|
842
|
+
browserCompatible: provider.browserCompatible
|
|
840
843
|
};
|
|
841
844
|
this.results.set(key, result);
|
|
842
845
|
this.logger.debug(
|
|
@@ -847,6 +850,7 @@ var HealthChecker = class {
|
|
|
847
850
|
const endTime = performance.now();
|
|
848
851
|
const latencyMs = Math.round(endTime - startTime);
|
|
849
852
|
const errorMsg = error.message || String(error) || "Unknown error";
|
|
853
|
+
const isCorsError = this.isCorsError(error, errorMsg);
|
|
850
854
|
const is429 = errorMsg.includes("429") || errorMsg.toLowerCase().includes("rate limit");
|
|
851
855
|
const is404 = errorMsg.includes("404") || errorMsg.toLowerCase().includes("not found");
|
|
852
856
|
const is503 = errorMsg.includes("503") || errorMsg.toLowerCase().includes("service unavailable");
|
|
@@ -861,6 +865,7 @@ var HealthChecker = class {
|
|
|
861
865
|
} else if (isTimeout) {
|
|
862
866
|
status = "offline";
|
|
863
867
|
}
|
|
868
|
+
const browserCompatible = isCorsError ? false : provider.browserCompatible;
|
|
864
869
|
const result = {
|
|
865
870
|
id: provider.id,
|
|
866
871
|
network: provider.network,
|
|
@@ -870,7 +875,8 @@ var HealthChecker = class {
|
|
|
870
875
|
seqno: null,
|
|
871
876
|
blocksBehind: 0,
|
|
872
877
|
lastTested: /* @__PURE__ */ new Date(),
|
|
873
|
-
error: errorMsg
|
|
878
|
+
error: errorMsg,
|
|
879
|
+
browserCompatible
|
|
874
880
|
};
|
|
875
881
|
this.results.set(key, result);
|
|
876
882
|
this.logger.warn(`Provider ${provider.id} health check failed: ${result.error}`);
|
|
@@ -952,21 +958,25 @@ var HealthChecker = class {
|
|
|
952
958
|
const existing = this.results.get(key);
|
|
953
959
|
const result = existing ? {
|
|
954
960
|
...existing,
|
|
955
|
-
success:
|
|
956
|
-
//
|
|
961
|
+
success: true,
|
|
962
|
+
// Degraded providers are still usable, just slower/rate-limited
|
|
957
963
|
status: "degraded",
|
|
958
964
|
error: error || "Marked as degraded",
|
|
959
|
-
lastTested: /* @__PURE__ */ new Date()
|
|
965
|
+
lastTested: /* @__PURE__ */ new Date(),
|
|
966
|
+
browserCompatible: existing.browserCompatible ?? true
|
|
960
967
|
} : {
|
|
961
968
|
id: providerId,
|
|
962
969
|
network,
|
|
963
|
-
success:
|
|
970
|
+
success: true,
|
|
971
|
+
// Degraded providers are still usable
|
|
964
972
|
status: "degraded",
|
|
965
973
|
latencyMs: null,
|
|
966
974
|
seqno: null,
|
|
967
975
|
blocksBehind: 0,
|
|
968
976
|
lastTested: /* @__PURE__ */ new Date(),
|
|
969
|
-
error: error || "Marked as degraded"
|
|
977
|
+
error: error || "Marked as degraded",
|
|
978
|
+
browserCompatible: true
|
|
979
|
+
// Default to compatible if unknown
|
|
970
980
|
};
|
|
971
981
|
this.results.set(key, result);
|
|
972
982
|
}
|
|
@@ -982,7 +992,8 @@ var HealthChecker = class {
|
|
|
982
992
|
success: false,
|
|
983
993
|
// Ensure success is false for offline providers
|
|
984
994
|
error: error || "Marked as offline",
|
|
985
|
-
lastTested: /* @__PURE__ */ new Date()
|
|
995
|
+
lastTested: /* @__PURE__ */ new Date(),
|
|
996
|
+
browserCompatible: existing.browserCompatible ?? true
|
|
986
997
|
} : {
|
|
987
998
|
id: providerId,
|
|
988
999
|
network,
|
|
@@ -992,7 +1003,9 @@ var HealthChecker = class {
|
|
|
992
1003
|
seqno: null,
|
|
993
1004
|
blocksBehind: 0,
|
|
994
1005
|
lastTested: /* @__PURE__ */ new Date(),
|
|
995
|
-
error: error || "Marked as offline"
|
|
1006
|
+
error: error || "Marked as offline",
|
|
1007
|
+
browserCompatible: true
|
|
1008
|
+
// Default to compatible if unknown
|
|
996
1009
|
};
|
|
997
1010
|
this.results.set(key, result);
|
|
998
1011
|
}
|
|
@@ -1124,6 +1137,24 @@ var HealthChecker = class {
|
|
|
1124
1137
|
sleep(ms) {
|
|
1125
1138
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1126
1139
|
}
|
|
1140
|
+
/**
|
|
1141
|
+
* Detect CORS errors (browser compatibility issues)
|
|
1142
|
+
*
|
|
1143
|
+
* CORS errors occur when:
|
|
1144
|
+
* - Request header field is not allowed by Access-Control-Allow-Headers
|
|
1145
|
+
* - Specifically, x-ton-client-version header is blocked by some providers
|
|
1146
|
+
* - Error message contains "CORS", "Access-Control", or "x-ton-client-version"
|
|
1147
|
+
*/
|
|
1148
|
+
isCorsError(error, errorMsg) {
|
|
1149
|
+
const msg = errorMsg.toLowerCase();
|
|
1150
|
+
if (msg.includes("cors") || msg.includes("access-control") || msg.includes("x-ton-client-version") || msg.includes("not allowed by access-control-allow-headers") || msg.includes("blocked by cors policy")) {
|
|
1151
|
+
return true;
|
|
1152
|
+
}
|
|
1153
|
+
if (error.name === "TypeError" && (msg.includes("failed to fetch") || msg.includes("network error"))) {
|
|
1154
|
+
return false;
|
|
1155
|
+
}
|
|
1156
|
+
return false;
|
|
1157
|
+
}
|
|
1127
1158
|
};
|
|
1128
1159
|
function createHealthChecker(config, logger, rateLimiter) {
|
|
1129
1160
|
return new HealthChecker(config, logger, rateLimiter);
|
|
@@ -1568,7 +1599,7 @@ var DEFAULT_CONFIG2 = {
|
|
|
1568
1599
|
minStatus: ["available", "degraded"]
|
|
1569
1600
|
};
|
|
1570
1601
|
var ProviderSelector = class {
|
|
1571
|
-
constructor(registry, healthChecker, config, logger) {
|
|
1602
|
+
constructor(registry, healthChecker, config, logger, adapter = "node") {
|
|
1572
1603
|
// Selection state
|
|
1573
1604
|
this.selectedProviderId = null;
|
|
1574
1605
|
this.autoSelect = true;
|
|
@@ -1580,6 +1611,7 @@ var ProviderSelector = class {
|
|
|
1580
1611
|
this.healthChecker = healthChecker;
|
|
1581
1612
|
this.config = { ...DEFAULT_CONFIG2, ...config };
|
|
1582
1613
|
this.logger = logger || consoleLogger4;
|
|
1614
|
+
this.adapter = adapter;
|
|
1583
1615
|
}
|
|
1584
1616
|
// ========================================================================
|
|
1585
1617
|
// Selection Methods
|
|
@@ -1620,9 +1652,19 @@ var ProviderSelector = class {
|
|
|
1620
1652
|
* Find the best provider for a network (recalculates)
|
|
1621
1653
|
*/
|
|
1622
1654
|
findBestProvider(network) {
|
|
1623
|
-
|
|
1655
|
+
let providers = this.registry.getProvidersForNetwork(network);
|
|
1656
|
+
if (this.adapter === "browser") {
|
|
1657
|
+
const beforeCount = providers.length;
|
|
1658
|
+
providers = this.filterBrowserCompatible(providers, network);
|
|
1659
|
+
const filteredCount = beforeCount - providers.length;
|
|
1660
|
+
if (filteredCount > 0) {
|
|
1661
|
+
this.logger.debug(
|
|
1662
|
+
`Filtered out ${filteredCount} browser-incompatible provider(s) for ${network}`
|
|
1663
|
+
);
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1624
1666
|
if (providers.length === 0) {
|
|
1625
|
-
this.logger.warn(`No providers available for ${network}`);
|
|
1667
|
+
this.logger.warn(`No browser-compatible providers available for ${network}`);
|
|
1626
1668
|
return null;
|
|
1627
1669
|
}
|
|
1628
1670
|
const scored = providers.map((provider) => ({
|
|
@@ -1639,13 +1681,25 @@ var ProviderSelector = class {
|
|
|
1639
1681
|
);
|
|
1640
1682
|
this.activeProviderByNetwork.set(network, defaultProvider.id);
|
|
1641
1683
|
return defaultProvider;
|
|
1642
|
-
}
|
|
1684
|
+
}
|
|
1685
|
+
if (health.success === true) {
|
|
1643
1686
|
this.logger.warn(
|
|
1644
1687
|
`No healthy providers for ${network}, using default: ${defaultProvider.id}`
|
|
1645
1688
|
);
|
|
1646
1689
|
this.activeProviderByNetwork.set(network, defaultProvider.id);
|
|
1647
1690
|
return defaultProvider;
|
|
1648
1691
|
}
|
|
1692
|
+
if (health.success === false && health.lastTested) {
|
|
1693
|
+
const timeSinceFailure = Date.now() - health.lastTested.getTime();
|
|
1694
|
+
const cooldownMs = 3e4;
|
|
1695
|
+
if (timeSinceFailure > cooldownMs) {
|
|
1696
|
+
this.logger.warn(
|
|
1697
|
+
`No healthy providers for ${network}, retrying failed default after cooldown: ${defaultProvider.id}`
|
|
1698
|
+
);
|
|
1699
|
+
this.activeProviderByNetwork.set(network, defaultProvider.id);
|
|
1700
|
+
return defaultProvider;
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1649
1703
|
}
|
|
1650
1704
|
for (const provider of providers) {
|
|
1651
1705
|
const health = this.healthChecker.getResult(provider.id, network);
|
|
@@ -1656,8 +1710,19 @@ var ProviderSelector = class {
|
|
|
1656
1710
|
this.activeProviderByNetwork.set(network, provider.id);
|
|
1657
1711
|
return provider;
|
|
1658
1712
|
}
|
|
1713
|
+
if (health.success === false && health.lastTested) {
|
|
1714
|
+
const timeSinceFailure = Date.now() - health.lastTested.getTime();
|
|
1715
|
+
const cooldownMs = 3e4;
|
|
1716
|
+
if (timeSinceFailure > cooldownMs) {
|
|
1717
|
+
this.logger.warn(
|
|
1718
|
+
`No healthy providers for ${network}, retrying failed provider after cooldown: ${provider.id}`
|
|
1719
|
+
);
|
|
1720
|
+
this.activeProviderByNetwork.set(network, provider.id);
|
|
1721
|
+
return provider;
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1659
1724
|
}
|
|
1660
|
-
this.logger.error(`No available providers for ${network} (all tested and failed)`);
|
|
1725
|
+
this.logger.error(`No available providers for ${network} (all tested and failed, cooldown active)`);
|
|
1661
1726
|
return null;
|
|
1662
1727
|
}
|
|
1663
1728
|
const best = scored[0].provider;
|
|
@@ -1680,7 +1745,10 @@ var ProviderSelector = class {
|
|
|
1680
1745
|
* Get all available providers for a network, sorted by score
|
|
1681
1746
|
*/
|
|
1682
1747
|
getAvailableProviders(network) {
|
|
1683
|
-
|
|
1748
|
+
let providers = this.registry.getProvidersForNetwork(network);
|
|
1749
|
+
if (this.adapter === "browser") {
|
|
1750
|
+
providers = this.filterBrowserCompatible(providers, network);
|
|
1751
|
+
}
|
|
1684
1752
|
return providers.map((provider) => ({
|
|
1685
1753
|
provider,
|
|
1686
1754
|
score: this.scoreProvider(provider, network)
|
|
@@ -1690,7 +1758,10 @@ var ProviderSelector = class {
|
|
|
1690
1758
|
* Get the next best provider (for failover)
|
|
1691
1759
|
*/
|
|
1692
1760
|
getNextProvider(network, excludeIds) {
|
|
1693
|
-
|
|
1761
|
+
let providers = this.registry.getProvidersForNetwork(network);
|
|
1762
|
+
if (this.adapter === "browser") {
|
|
1763
|
+
providers = this.filterBrowserCompatible(providers, network);
|
|
1764
|
+
}
|
|
1694
1765
|
const available = providers.filter((p) => !excludeIds.includes(p.id)).map((provider) => ({
|
|
1695
1766
|
provider,
|
|
1696
1767
|
score: this.scoreProvider(provider, network)
|
|
@@ -1712,6 +1783,13 @@ var ProviderSelector = class {
|
|
|
1712
1783
|
return 0.01 * (1 / (provider.priority + 1));
|
|
1713
1784
|
}
|
|
1714
1785
|
if (health.success === false) {
|
|
1786
|
+
if (health.lastTested) {
|
|
1787
|
+
const timeSinceFailure = Date.now() - health.lastTested.getTime();
|
|
1788
|
+
const cooldownMs = 3e4;
|
|
1789
|
+
if (timeSinceFailure > cooldownMs) {
|
|
1790
|
+
return 1e-3 * (1 / (provider.priority + 1));
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1715
1793
|
return 0;
|
|
1716
1794
|
}
|
|
1717
1795
|
if (health.status === "offline") {
|
|
@@ -1874,12 +1952,39 @@ var ProviderSelector = class {
|
|
|
1874
1952
|
endpointV2: this.customEndpoint,
|
|
1875
1953
|
rps: 10,
|
|
1876
1954
|
priority: 0,
|
|
1877
|
-
isDynamic: false
|
|
1955
|
+
isDynamic: false,
|
|
1956
|
+
browserCompatible: true
|
|
1957
|
+
// Custom endpoints are assumed compatible
|
|
1878
1958
|
};
|
|
1879
1959
|
}
|
|
1960
|
+
/**
|
|
1961
|
+
* Filter providers to only include browser-compatible ones
|
|
1962
|
+
*
|
|
1963
|
+
* Checks both:
|
|
1964
|
+
* 1. Provider config browserCompatible flag
|
|
1965
|
+
* 2. Health check result browserCompatible flag (if health check was performed)
|
|
1966
|
+
*/
|
|
1967
|
+
filterBrowserCompatible(providers, network) {
|
|
1968
|
+
return providers.filter((provider) => {
|
|
1969
|
+
if (!provider.browserCompatible) {
|
|
1970
|
+
this.logger.debug(
|
|
1971
|
+
`Provider ${provider.id} marked as browser-incompatible in config`
|
|
1972
|
+
);
|
|
1973
|
+
return false;
|
|
1974
|
+
}
|
|
1975
|
+
const health = this.healthChecker.getResult(provider.id, network);
|
|
1976
|
+
if (health && health.browserCompatible === false) {
|
|
1977
|
+
this.logger.debug(
|
|
1978
|
+
`Provider ${provider.id} marked as browser-incompatible by health check (CORS error detected)`
|
|
1979
|
+
);
|
|
1980
|
+
return false;
|
|
1981
|
+
}
|
|
1982
|
+
return true;
|
|
1983
|
+
});
|
|
1984
|
+
}
|
|
1880
1985
|
};
|
|
1881
|
-
function createSelector(registry, healthChecker, config, logger) {
|
|
1882
|
-
return new ProviderSelector(registry, healthChecker, config, logger);
|
|
1986
|
+
function createSelector(registry, healthChecker, config, logger, adapter = "node") {
|
|
1987
|
+
return new ProviderSelector(registry, healthChecker, config, logger, adapter);
|
|
1883
1988
|
}
|
|
1884
1989
|
|
|
1885
1990
|
// src/core/manager.ts
|
|
@@ -1986,7 +2091,8 @@ var _ProviderManager = class _ProviderManager {
|
|
|
1986
2091
|
this.registry,
|
|
1987
2092
|
this.healthChecker,
|
|
1988
2093
|
void 0,
|
|
1989
|
-
this.options.logger
|
|
2094
|
+
this.options.logger,
|
|
2095
|
+
this.options.adapter
|
|
1990
2096
|
);
|
|
1991
2097
|
this.initialized = true;
|
|
1992
2098
|
this.notifyListeners();
|
|
@@ -2253,7 +2359,20 @@ var _ProviderManager = class _ProviderManager {
|
|
|
2253
2359
|
*/
|
|
2254
2360
|
getProviders() {
|
|
2255
2361
|
if (!this.initialized || !this.network) return [];
|
|
2256
|
-
|
|
2362
|
+
let providers = this.registry.getProvidersForNetwork(this.network);
|
|
2363
|
+
if (this.options.adapter === "browser") {
|
|
2364
|
+
providers = providers.filter((provider) => {
|
|
2365
|
+
if (!provider.browserCompatible) {
|
|
2366
|
+
return false;
|
|
2367
|
+
}
|
|
2368
|
+
const health = this.healthChecker.getResult(provider.id, this.network);
|
|
2369
|
+
if (health && health.browserCompatible === false) {
|
|
2370
|
+
return false;
|
|
2371
|
+
}
|
|
2372
|
+
return true;
|
|
2373
|
+
});
|
|
2374
|
+
}
|
|
2375
|
+
return providers;
|
|
2257
2376
|
}
|
|
2258
2377
|
/**
|
|
2259
2378
|
* Get provider health results for current network
|
|
@@ -2299,7 +2418,8 @@ var _ProviderManager = class _ProviderManager {
|
|
|
2299
2418
|
latencyMs: null,
|
|
2300
2419
|
seqno: null,
|
|
2301
2420
|
blocksBehind: 0,
|
|
2302
|
-
lastTested: null
|
|
2421
|
+
lastTested: null,
|
|
2422
|
+
browserCompatible: provider.browserCompatible
|
|
2303
2423
|
},
|
|
2304
2424
|
rateLimit: rateLimit || {
|
|
2305
2425
|
tokens: 0,
|