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.d.cts CHANGED
@@ -59,6 +59,8 @@ interface ProviderConfig {
59
59
  enabled: boolean;
60
60
  /** Whether this provider requires dynamic endpoint discovery (e.g., Orbs) */
61
61
  isDynamic?: boolean;
62
+ /** Whether this provider is compatible with browser environments (default: true) */
63
+ browserCompatible?: boolean;
62
64
  /** Optional description or notes */
63
65
  description?: string;
64
66
  }
@@ -114,6 +116,8 @@ interface ResolvedProvider {
114
116
  priority: number;
115
117
  /** Whether dynamic discovery is needed */
116
118
  isDynamic: boolean;
119
+ /** Whether this provider is compatible with browser environments */
120
+ browserCompatible: boolean;
117
121
  }
118
122
  /**
119
123
  * Provider health check result
@@ -139,6 +143,8 @@ interface ProviderHealthResult {
139
143
  cachedEndpoint?: string;
140
144
  /** Error message if failed */
141
145
  error?: string;
146
+ /** Whether this provider is compatible with browser environments */
147
+ browserCompatible: boolean;
142
148
  }
143
149
  /**
144
150
  * Provider state for runtime tracking
@@ -1039,6 +1045,15 @@ declare class HealthChecker {
1039
1045
  */
1040
1046
  private callGetMasterchainInfo;
1041
1047
  private sleep;
1048
+ /**
1049
+ * Detect CORS errors (browser compatibility issues)
1050
+ *
1051
+ * CORS errors occur when:
1052
+ * - Request header field is not allowed by Access-Control-Allow-Headers
1053
+ * - Specifically, x-ton-client-version header is blocked by some providers
1054
+ * - Error message contains "CORS", "Access-Control", or "x-ton-client-version"
1055
+ */
1056
+ private isCorsError;
1042
1057
  }
1043
1058
  /**
1044
1059
  * Create a health checker with default configuration
@@ -1078,12 +1093,13 @@ declare class ProviderSelector {
1078
1093
  private healthChecker;
1079
1094
  private config;
1080
1095
  private logger;
1096
+ private adapter;
1081
1097
  private selectedProviderId;
1082
1098
  private autoSelect;
1083
1099
  private customEndpoint;
1084
1100
  private bestProviderByNetwork;
1085
1101
  private activeProviderByNetwork;
1086
- constructor(registry: ProviderRegistry, healthChecker: HealthChecker, config?: Partial<SelectionConfig>, logger?: Logger);
1102
+ constructor(registry: ProviderRegistry, healthChecker: HealthChecker, config?: Partial<SelectionConfig>, logger?: Logger, adapter?: 'node' | 'browser');
1087
1103
  /**
1088
1104
  * Get the best provider for a network
1089
1105
  */
@@ -1166,11 +1182,19 @@ declare class ProviderSelector {
1166
1182
  * Create a pseudo-provider for custom endpoint
1167
1183
  */
1168
1184
  private createCustomProvider;
1185
+ /**
1186
+ * Filter providers to only include browser-compatible ones
1187
+ *
1188
+ * Checks both:
1189
+ * 1. Provider config browserCompatible flag
1190
+ * 2. Health check result browserCompatible flag (if health check was performed)
1191
+ */
1192
+ private filterBrowserCompatible;
1169
1193
  }
1170
1194
  /**
1171
1195
  * Create a provider selector
1172
1196
  */
1173
- declare function createSelector(registry: ProviderRegistry, healthChecker: HealthChecker, config?: Partial<SelectionConfig>, logger?: Logger): ProviderSelector;
1197
+ declare function createSelector(registry: ProviderRegistry, healthChecker: HealthChecker, config?: Partial<SelectionConfig>, logger?: Logger, adapter?: 'node' | 'browser'): ProviderSelector;
1174
1198
 
1175
1199
  /**
1176
1200
  * Unified Provider System - Provider Manager
package/dist/index.d.ts CHANGED
@@ -59,6 +59,8 @@ interface ProviderConfig {
59
59
  enabled: boolean;
60
60
  /** Whether this provider requires dynamic endpoint discovery (e.g., Orbs) */
61
61
  isDynamic?: boolean;
62
+ /** Whether this provider is compatible with browser environments (default: true) */
63
+ browserCompatible?: boolean;
62
64
  /** Optional description or notes */
63
65
  description?: string;
64
66
  }
@@ -114,6 +116,8 @@ interface ResolvedProvider {
114
116
  priority: number;
115
117
  /** Whether dynamic discovery is needed */
116
118
  isDynamic: boolean;
119
+ /** Whether this provider is compatible with browser environments */
120
+ browserCompatible: boolean;
117
121
  }
118
122
  /**
119
123
  * Provider health check result
@@ -139,6 +143,8 @@ interface ProviderHealthResult {
139
143
  cachedEndpoint?: string;
140
144
  /** Error message if failed */
141
145
  error?: string;
146
+ /** Whether this provider is compatible with browser environments */
147
+ browserCompatible: boolean;
142
148
  }
143
149
  /**
144
150
  * Provider state for runtime tracking
@@ -1039,6 +1045,15 @@ declare class HealthChecker {
1039
1045
  */
1040
1046
  private callGetMasterchainInfo;
1041
1047
  private sleep;
1048
+ /**
1049
+ * Detect CORS errors (browser compatibility issues)
1050
+ *
1051
+ * CORS errors occur when:
1052
+ * - Request header field is not allowed by Access-Control-Allow-Headers
1053
+ * - Specifically, x-ton-client-version header is blocked by some providers
1054
+ * - Error message contains "CORS", "Access-Control", or "x-ton-client-version"
1055
+ */
1056
+ private isCorsError;
1042
1057
  }
1043
1058
  /**
1044
1059
  * Create a health checker with default configuration
@@ -1078,12 +1093,13 @@ declare class ProviderSelector {
1078
1093
  private healthChecker;
1079
1094
  private config;
1080
1095
  private logger;
1096
+ private adapter;
1081
1097
  private selectedProviderId;
1082
1098
  private autoSelect;
1083
1099
  private customEndpoint;
1084
1100
  private bestProviderByNetwork;
1085
1101
  private activeProviderByNetwork;
1086
- constructor(registry: ProviderRegistry, healthChecker: HealthChecker, config?: Partial<SelectionConfig>, logger?: Logger);
1102
+ constructor(registry: ProviderRegistry, healthChecker: HealthChecker, config?: Partial<SelectionConfig>, logger?: Logger, adapter?: 'node' | 'browser');
1087
1103
  /**
1088
1104
  * Get the best provider for a network
1089
1105
  */
@@ -1166,11 +1182,19 @@ declare class ProviderSelector {
1166
1182
  * Create a pseudo-provider for custom endpoint
1167
1183
  */
1168
1184
  private createCustomProvider;
1185
+ /**
1186
+ * Filter providers to only include browser-compatible ones
1187
+ *
1188
+ * Checks both:
1189
+ * 1. Provider config browserCompatible flag
1190
+ * 2. Health check result browserCompatible flag (if health check was performed)
1191
+ */
1192
+ private filterBrowserCompatible;
1169
1193
  }
1170
1194
  /**
1171
1195
  * Create a provider selector
1172
1196
  */
1173
- declare function createSelector(registry: ProviderRegistry, healthChecker: HealthChecker, config?: Partial<SelectionConfig>, logger?: Logger): ProviderSelector;
1197
+ declare function createSelector(registry: ProviderRegistry, healthChecker: HealthChecker, config?: Partial<SelectionConfig>, logger?: Logger, adapter?: 'node' | 'browser'): ProviderSelector;
1174
1198
 
1175
1199
  /**
1176
1200
  * Unified Provider System - Provider Manager
package/dist/index.js CHANGED
@@ -214,7 +214,8 @@ function resolveProvider(id, config) {
214
214
  apiKey,
215
215
  rps: config.rps,
216
216
  priority: config.priority,
217
- isDynamic: config.isDynamic || false
217
+ isDynamic: config.isDynamic || false,
218
+ browserCompatible: config.browserCompatible !== void 0 ? config.browserCompatible : true
218
219
  };
219
220
  }
220
221
  function resolveAllProviders(config) {
@@ -773,7 +774,8 @@ var HealthChecker = class {
773
774
  latencyMs: null,
774
775
  seqno: null,
775
776
  blocksBehind: 0,
776
- lastTested: null
777
+ lastTested: null,
778
+ browserCompatible: provider.browserCompatible
777
779
  };
778
780
  this.results.set(key, testingResult);
779
781
  try {
@@ -833,7 +835,8 @@ var HealthChecker = class {
833
835
  seqno,
834
836
  blocksBehind,
835
837
  lastTested: /* @__PURE__ */ new Date(),
836
- cachedEndpoint: normalizedEndpoint
838
+ cachedEndpoint: normalizedEndpoint,
839
+ browserCompatible: provider.browserCompatible
837
840
  };
838
841
  this.results.set(key, result);
839
842
  this.logger.debug(
@@ -844,6 +847,7 @@ var HealthChecker = class {
844
847
  const endTime = performance.now();
845
848
  const latencyMs = Math.round(endTime - startTime);
846
849
  const errorMsg = error.message || String(error) || "Unknown error";
850
+ const isCorsError = this.isCorsError(error, errorMsg);
847
851
  const is429 = errorMsg.includes("429") || errorMsg.toLowerCase().includes("rate limit");
848
852
  const is404 = errorMsg.includes("404") || errorMsg.toLowerCase().includes("not found");
849
853
  const is503 = errorMsg.includes("503") || errorMsg.toLowerCase().includes("service unavailable");
@@ -858,6 +862,7 @@ var HealthChecker = class {
858
862
  } else if (isTimeout) {
859
863
  status = "offline";
860
864
  }
865
+ const browserCompatible = isCorsError ? false : provider.browserCompatible;
861
866
  const result = {
862
867
  id: provider.id,
863
868
  network: provider.network,
@@ -867,7 +872,8 @@ var HealthChecker = class {
867
872
  seqno: null,
868
873
  blocksBehind: 0,
869
874
  lastTested: /* @__PURE__ */ new Date(),
870
- error: errorMsg
875
+ error: errorMsg,
876
+ browserCompatible
871
877
  };
872
878
  this.results.set(key, result);
873
879
  this.logger.warn(`Provider ${provider.id} health check failed: ${result.error}`);
@@ -949,21 +955,25 @@ var HealthChecker = class {
949
955
  const existing = this.results.get(key);
950
956
  const result = existing ? {
951
957
  ...existing,
952
- success: false,
953
- // CRITICAL: Always set success: false for degraded providers
958
+ success: true,
959
+ // Degraded providers are still usable, just slower/rate-limited
954
960
  status: "degraded",
955
961
  error: error || "Marked as degraded",
956
- lastTested: /* @__PURE__ */ new Date()
962
+ lastTested: /* @__PURE__ */ new Date(),
963
+ browserCompatible: existing.browserCompatible ?? true
957
964
  } : {
958
965
  id: providerId,
959
966
  network,
960
- success: false,
967
+ success: true,
968
+ // Degraded providers are still usable
961
969
  status: "degraded",
962
970
  latencyMs: null,
963
971
  seqno: null,
964
972
  blocksBehind: 0,
965
973
  lastTested: /* @__PURE__ */ new Date(),
966
- error: error || "Marked as degraded"
974
+ error: error || "Marked as degraded",
975
+ browserCompatible: true
976
+ // Default to compatible if unknown
967
977
  };
968
978
  this.results.set(key, result);
969
979
  }
@@ -979,7 +989,8 @@ var HealthChecker = class {
979
989
  success: false,
980
990
  // Ensure success is false for offline providers
981
991
  error: error || "Marked as offline",
982
- lastTested: /* @__PURE__ */ new Date()
992
+ lastTested: /* @__PURE__ */ new Date(),
993
+ browserCompatible: existing.browserCompatible ?? true
983
994
  } : {
984
995
  id: providerId,
985
996
  network,
@@ -989,7 +1000,9 @@ var HealthChecker = class {
989
1000
  seqno: null,
990
1001
  blocksBehind: 0,
991
1002
  lastTested: /* @__PURE__ */ new Date(),
992
- error: error || "Marked as offline"
1003
+ error: error || "Marked as offline",
1004
+ browserCompatible: true
1005
+ // Default to compatible if unknown
993
1006
  };
994
1007
  this.results.set(key, result);
995
1008
  }
@@ -1121,6 +1134,24 @@ var HealthChecker = class {
1121
1134
  sleep(ms) {
1122
1135
  return new Promise((resolve) => setTimeout(resolve, ms));
1123
1136
  }
1137
+ /**
1138
+ * Detect CORS errors (browser compatibility issues)
1139
+ *
1140
+ * CORS errors occur when:
1141
+ * - Request header field is not allowed by Access-Control-Allow-Headers
1142
+ * - Specifically, x-ton-client-version header is blocked by some providers
1143
+ * - Error message contains "CORS", "Access-Control", or "x-ton-client-version"
1144
+ */
1145
+ isCorsError(error, errorMsg) {
1146
+ const msg = errorMsg.toLowerCase();
1147
+ 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")) {
1148
+ return true;
1149
+ }
1150
+ if (error.name === "TypeError" && (msg.includes("failed to fetch") || msg.includes("network error"))) {
1151
+ return false;
1152
+ }
1153
+ return false;
1154
+ }
1124
1155
  };
1125
1156
  function createHealthChecker(config, logger, rateLimiter) {
1126
1157
  return new HealthChecker(config, logger, rateLimiter);
@@ -1565,7 +1596,7 @@ var DEFAULT_CONFIG2 = {
1565
1596
  minStatus: ["available", "degraded"]
1566
1597
  };
1567
1598
  var ProviderSelector = class {
1568
- constructor(registry, healthChecker, config, logger) {
1599
+ constructor(registry, healthChecker, config, logger, adapter = "node") {
1569
1600
  // Selection state
1570
1601
  this.selectedProviderId = null;
1571
1602
  this.autoSelect = true;
@@ -1577,6 +1608,7 @@ var ProviderSelector = class {
1577
1608
  this.healthChecker = healthChecker;
1578
1609
  this.config = { ...DEFAULT_CONFIG2, ...config };
1579
1610
  this.logger = logger || consoleLogger4;
1611
+ this.adapter = adapter;
1580
1612
  }
1581
1613
  // ========================================================================
1582
1614
  // Selection Methods
@@ -1617,9 +1649,19 @@ var ProviderSelector = class {
1617
1649
  * Find the best provider for a network (recalculates)
1618
1650
  */
1619
1651
  findBestProvider(network) {
1620
- const providers = this.registry.getProvidersForNetwork(network);
1652
+ let providers = this.registry.getProvidersForNetwork(network);
1653
+ if (this.adapter === "browser") {
1654
+ const beforeCount = providers.length;
1655
+ providers = this.filterBrowserCompatible(providers, network);
1656
+ const filteredCount = beforeCount - providers.length;
1657
+ if (filteredCount > 0) {
1658
+ this.logger.debug(
1659
+ `Filtered out ${filteredCount} browser-incompatible provider(s) for ${network}`
1660
+ );
1661
+ }
1662
+ }
1621
1663
  if (providers.length === 0) {
1622
- this.logger.warn(`No providers available for ${network}`);
1664
+ this.logger.warn(`No browser-compatible providers available for ${network}`);
1623
1665
  return null;
1624
1666
  }
1625
1667
  const scored = providers.map((provider) => ({
@@ -1636,13 +1678,25 @@ var ProviderSelector = class {
1636
1678
  );
1637
1679
  this.activeProviderByNetwork.set(network, defaultProvider.id);
1638
1680
  return defaultProvider;
1639
- } else if (health.success === true) {
1681
+ }
1682
+ if (health.success === true) {
1640
1683
  this.logger.warn(
1641
1684
  `No healthy providers for ${network}, using default: ${defaultProvider.id}`
1642
1685
  );
1643
1686
  this.activeProviderByNetwork.set(network, defaultProvider.id);
1644
1687
  return defaultProvider;
1645
1688
  }
1689
+ if (health.success === false && health.lastTested) {
1690
+ const timeSinceFailure = Date.now() - health.lastTested.getTime();
1691
+ const cooldownMs = 3e4;
1692
+ if (timeSinceFailure > cooldownMs) {
1693
+ this.logger.warn(
1694
+ `No healthy providers for ${network}, retrying failed default after cooldown: ${defaultProvider.id}`
1695
+ );
1696
+ this.activeProviderByNetwork.set(network, defaultProvider.id);
1697
+ return defaultProvider;
1698
+ }
1699
+ }
1646
1700
  }
1647
1701
  for (const provider of providers) {
1648
1702
  const health = this.healthChecker.getResult(provider.id, network);
@@ -1653,8 +1707,19 @@ var ProviderSelector = class {
1653
1707
  this.activeProviderByNetwork.set(network, provider.id);
1654
1708
  return provider;
1655
1709
  }
1710
+ if (health.success === false && health.lastTested) {
1711
+ const timeSinceFailure = Date.now() - health.lastTested.getTime();
1712
+ const cooldownMs = 3e4;
1713
+ if (timeSinceFailure > cooldownMs) {
1714
+ this.logger.warn(
1715
+ `No healthy providers for ${network}, retrying failed provider after cooldown: ${provider.id}`
1716
+ );
1717
+ this.activeProviderByNetwork.set(network, provider.id);
1718
+ return provider;
1719
+ }
1720
+ }
1656
1721
  }
1657
- this.logger.error(`No available providers for ${network} (all tested and failed)`);
1722
+ this.logger.error(`No available providers for ${network} (all tested and failed, cooldown active)`);
1658
1723
  return null;
1659
1724
  }
1660
1725
  const best = scored[0].provider;
@@ -1677,7 +1742,10 @@ var ProviderSelector = class {
1677
1742
  * Get all available providers for a network, sorted by score
1678
1743
  */
1679
1744
  getAvailableProviders(network) {
1680
- const providers = this.registry.getProvidersForNetwork(network);
1745
+ let providers = this.registry.getProvidersForNetwork(network);
1746
+ if (this.adapter === "browser") {
1747
+ providers = this.filterBrowserCompatible(providers, network);
1748
+ }
1681
1749
  return providers.map((provider) => ({
1682
1750
  provider,
1683
1751
  score: this.scoreProvider(provider, network)
@@ -1687,7 +1755,10 @@ var ProviderSelector = class {
1687
1755
  * Get the next best provider (for failover)
1688
1756
  */
1689
1757
  getNextProvider(network, excludeIds) {
1690
- const providers = this.registry.getProvidersForNetwork(network);
1758
+ let providers = this.registry.getProvidersForNetwork(network);
1759
+ if (this.adapter === "browser") {
1760
+ providers = this.filterBrowserCompatible(providers, network);
1761
+ }
1691
1762
  const available = providers.filter((p) => !excludeIds.includes(p.id)).map((provider) => ({
1692
1763
  provider,
1693
1764
  score: this.scoreProvider(provider, network)
@@ -1709,6 +1780,13 @@ var ProviderSelector = class {
1709
1780
  return 0.01 * (1 / (provider.priority + 1));
1710
1781
  }
1711
1782
  if (health.success === false) {
1783
+ if (health.lastTested) {
1784
+ const timeSinceFailure = Date.now() - health.lastTested.getTime();
1785
+ const cooldownMs = 3e4;
1786
+ if (timeSinceFailure > cooldownMs) {
1787
+ return 1e-3 * (1 / (provider.priority + 1));
1788
+ }
1789
+ }
1712
1790
  return 0;
1713
1791
  }
1714
1792
  if (health.status === "offline") {
@@ -1871,12 +1949,39 @@ var ProviderSelector = class {
1871
1949
  endpointV2: this.customEndpoint,
1872
1950
  rps: 10,
1873
1951
  priority: 0,
1874
- isDynamic: false
1952
+ isDynamic: false,
1953
+ browserCompatible: true
1954
+ // Custom endpoints are assumed compatible
1875
1955
  };
1876
1956
  }
1957
+ /**
1958
+ * Filter providers to only include browser-compatible ones
1959
+ *
1960
+ * Checks both:
1961
+ * 1. Provider config browserCompatible flag
1962
+ * 2. Health check result browserCompatible flag (if health check was performed)
1963
+ */
1964
+ filterBrowserCompatible(providers, network) {
1965
+ return providers.filter((provider) => {
1966
+ if (!provider.browserCompatible) {
1967
+ this.logger.debug(
1968
+ `Provider ${provider.id} marked as browser-incompatible in config`
1969
+ );
1970
+ return false;
1971
+ }
1972
+ const health = this.healthChecker.getResult(provider.id, network);
1973
+ if (health && health.browserCompatible === false) {
1974
+ this.logger.debug(
1975
+ `Provider ${provider.id} marked as browser-incompatible by health check (CORS error detected)`
1976
+ );
1977
+ return false;
1978
+ }
1979
+ return true;
1980
+ });
1981
+ }
1877
1982
  };
1878
- function createSelector(registry, healthChecker, config, logger) {
1879
- return new ProviderSelector(registry, healthChecker, config, logger);
1983
+ function createSelector(registry, healthChecker, config, logger, adapter = "node") {
1984
+ return new ProviderSelector(registry, healthChecker, config, logger, adapter);
1880
1985
  }
1881
1986
 
1882
1987
  // src/core/manager.ts
@@ -1983,7 +2088,8 @@ var _ProviderManager = class _ProviderManager {
1983
2088
  this.registry,
1984
2089
  this.healthChecker,
1985
2090
  void 0,
1986
- this.options.logger
2091
+ this.options.logger,
2092
+ this.options.adapter
1987
2093
  );
1988
2094
  this.initialized = true;
1989
2095
  this.notifyListeners();
@@ -2250,7 +2356,20 @@ var _ProviderManager = class _ProviderManager {
2250
2356
  */
2251
2357
  getProviders() {
2252
2358
  if (!this.initialized || !this.network) return [];
2253
- return this.registry.getProvidersForNetwork(this.network);
2359
+ let providers = this.registry.getProvidersForNetwork(this.network);
2360
+ if (this.options.adapter === "browser") {
2361
+ providers = providers.filter((provider) => {
2362
+ if (!provider.browserCompatible) {
2363
+ return false;
2364
+ }
2365
+ const health = this.healthChecker.getResult(provider.id, this.network);
2366
+ if (health && health.browserCompatible === false) {
2367
+ return false;
2368
+ }
2369
+ return true;
2370
+ });
2371
+ }
2372
+ return providers;
2254
2373
  }
2255
2374
  /**
2256
2375
  * Get provider health results for current network
@@ -2296,7 +2415,8 @@ var _ProviderManager = class _ProviderManager {
2296
2415
  latencyMs: null,
2297
2416
  seqno: null,
2298
2417
  blocksBehind: 0,
2299
- lastTested: null
2418
+ lastTested: null,
2419
+ browserCompatible: provider.browserCompatible
2300
2420
  },
2301
2421
  rateLimit: rateLimit || {
2302
2422
  tokens: 0,