@schematichq/schematic-react 1.2.12 → 1.2.14

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.
@@ -799,7 +799,7 @@ function contextString(context) {
799
799
  }, {});
800
800
  return JSON.stringify(sortedContext);
801
801
  }
802
- var version = "1.2.12";
802
+ var version = "1.2.14";
803
803
  var anonymousIdKey = "schematicId";
804
804
  var Schematic = class {
805
805
  additionalHeaders = {};
@@ -824,6 +824,9 @@ var Schematic = class {
824
824
  webSocketConnectionTimeout = 1e4;
825
825
  webSocketReconnect = true;
826
826
  webSocketMaxReconnectAttempts = 7;
827
+ // Max attempts after connection disrupted
828
+ webSocketMaxConnectionAttempts = 3;
829
+ // Max attempts for initial connection
827
830
  webSocketInitialRetryDelay = 1e3;
828
831
  webSocketMaxRetryDelay = 3e4;
829
832
  wsReconnectAttempts = 0;
@@ -945,13 +948,17 @@ var Schematic = class {
945
948
  /**
946
949
  * Resolve fallback value according to priority order:
947
950
  * 1. Callsite fallback value (if provided)
948
- * 2. Initialization fallback value (flagValueDefaults)
949
- * 3. Default to false
951
+ * 2. Boolean value from flagCheckDefaults initialization option
952
+ * 3. Boolean value from flagValueDefaults initialization option
953
+ * 4. Default to false
950
954
  */
951
955
  resolveFallbackValue(key, callsiteFallback) {
952
956
  if (callsiteFallback !== void 0) {
953
957
  return callsiteFallback;
954
958
  }
959
+ if (key in this.flagCheckDefaults) {
960
+ return this.flagCheckDefaults[key].value;
961
+ }
955
962
  if (key in this.flagValueDefaults) {
956
963
  return this.flagValueDefaults[key];
957
964
  }
@@ -1047,7 +1054,7 @@ var Schematic = class {
1047
1054
  this.submitFlagCheckEvent(key, result, context);
1048
1055
  return result.value;
1049
1056
  }).catch((error) => {
1050
- console.error("There was a problem with the fetch operation:", error);
1057
+ console.warn("There was a problem with the fetch operation:", error);
1051
1058
  const errorResult = this.resolveFallbackCheckFlagReturn(
1052
1059
  key,
1053
1060
  fallback,
@@ -1070,7 +1077,7 @@ var Schematic = class {
1070
1077
  try {
1071
1078
  await this.setContext(context);
1072
1079
  } catch (error) {
1073
- console.error(
1080
+ console.warn(
1074
1081
  "WebSocket connection failed, falling back to REST:",
1075
1082
  error
1076
1083
  );
@@ -1213,7 +1220,7 @@ var Schematic = class {
1213
1220
  this.submitFlagCheckEvent(key, result, context);
1214
1221
  return result.value;
1215
1222
  } catch (error) {
1216
- console.error("REST API call failed, using fallback value:", error);
1223
+ console.warn("REST API call failed, using fallback value:", error);
1217
1224
  const errorResult = this.resolveFallbackCheckFlagReturn(
1218
1225
  key,
1219
1226
  fallback,
@@ -1262,7 +1269,7 @@ var Schematic = class {
1262
1269
  {}
1263
1270
  );
1264
1271
  }).catch((error) => {
1265
- console.error("There was a problem with the fetch operation:", error);
1272
+ console.warn("There was a problem with the fetch operation:", error);
1266
1273
  return {};
1267
1274
  });
1268
1275
  };
@@ -1279,7 +1286,7 @@ var Schematic = class {
1279
1286
  user: body.keys
1280
1287
  });
1281
1288
  } catch (error) {
1282
- console.error("Error setting context:", error);
1289
+ console.warn("Error setting context:", error);
1283
1290
  }
1284
1291
  return this.handleEvent("identify", body);
1285
1292
  };
@@ -1337,7 +1344,7 @@ var Schematic = class {
1337
1344
  const socket = await this.conn;
1338
1345
  await this.wsSendMessage(socket, context);
1339
1346
  } catch (error) {
1340
- console.error("Failed to establish WebSocket connection:", error);
1347
+ console.warn("Failed to establish WebSocket connection:", error);
1341
1348
  throw error;
1342
1349
  }
1343
1350
  };
@@ -1758,7 +1765,7 @@ var Schematic = class {
1758
1765
  }
1759
1766
  socket.close();
1760
1767
  } catch (error) {
1761
- console.error("Error during cleanup:", error);
1768
+ console.warn("Error during cleanup:", error);
1762
1769
  } finally {
1763
1770
  this.conn = null;
1764
1771
  this.currentWebSocket = null;
@@ -1901,14 +1908,49 @@ var Schematic = class {
1901
1908
  }
1902
1909
  }, delay);
1903
1910
  };
1904
- // Open a websocket connection
1905
- wsConnect = () => {
1911
+ // Open a websocket connection with retry logic for timeouts
1912
+ wsConnect = async () => {
1906
1913
  if (this.isOffline()) {
1907
1914
  this.debug("wsConnect: skipped (offline mode)");
1908
- return Promise.reject(
1909
- new Error("WebSocket connection skipped in offline mode")
1910
- );
1915
+ throw new Error("WebSocket connection skipped in offline mode");
1916
+ }
1917
+ let lastError = null;
1918
+ const maxAttempts = this.webSocketMaxConnectionAttempts;
1919
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
1920
+ try {
1921
+ const socket = await this.wsConnectOnce();
1922
+ this.wsReconnectAttempts = 0;
1923
+ return socket;
1924
+ } catch (error) {
1925
+ lastError = error instanceof Error ? error : new Error(String(error));
1926
+ const isTimeout = lastError.message === "WebSocket connection timeout";
1927
+ if (!isTimeout) {
1928
+ this.debug(
1929
+ `WebSocket connection failed with non-timeout error, not retrying:`,
1930
+ lastError.message
1931
+ );
1932
+ throw lastError;
1933
+ }
1934
+ if (attempt < maxAttempts - 1) {
1935
+ const baseDelay = this.webSocketInitialRetryDelay * Math.pow(2, attempt);
1936
+ const cappedDelay = Math.min(baseDelay, this.webSocketMaxRetryDelay);
1937
+ const jitter = cappedDelay * 0.2 * Math.random();
1938
+ const delay = cappedDelay + jitter;
1939
+ this.debug(
1940
+ `WebSocket connection timeout (attempt ${attempt + 1}/${maxAttempts}), retrying in ${delay.toFixed(0)}ms`
1941
+ );
1942
+ await new Promise((resolve) => setTimeout(resolve, delay));
1943
+ } else {
1944
+ this.debug(
1945
+ `WebSocket connection timeout (attempt ${attempt + 1}/${maxAttempts}), no more retries`
1946
+ );
1947
+ }
1948
+ }
1911
1949
  }
1950
+ throw lastError ?? new Error("WebSocket connection failed");
1951
+ };
1952
+ // Single attempt to open a websocket connection (no retry logic)
1953
+ wsConnectOnce = () => {
1912
1954
  return new Promise((resolve, reject) => {
1913
1955
  const wsUrl = `${this.webSocketUrl}/flags/bootstrap?apiKey=${this.apiKey}`;
1914
1956
  this.debug(`connecting to WebSocket:`, wsUrl);
@@ -1919,6 +1961,7 @@ var Schematic = class {
1919
1961
  let isResolved = false;
1920
1962
  timeoutId = setTimeout(() => {
1921
1963
  if (!isResolved) {
1964
+ isResolved = true;
1922
1965
  this.debug(
1923
1966
  `WebSocket connection timeout after ${this.webSocketConnectionTimeout}ms`
1924
1967
  );
@@ -1927,16 +1970,17 @@ var Schematic = class {
1927
1970
  }
1928
1971
  }, this.webSocketConnectionTimeout);
1929
1972
  webSocket.onopen = () => {
1973
+ if (isResolved) return;
1930
1974
  isResolved = true;
1931
1975
  if (timeoutId !== null) {
1932
1976
  clearTimeout(timeoutId);
1933
1977
  }
1934
- this.wsReconnectAttempts = 0;
1935
1978
  this.wsIntentionalDisconnect = false;
1936
1979
  this.debug(`WebSocket connection ${connectionId} opened successfully`);
1937
1980
  resolve(webSocket);
1938
1981
  };
1939
1982
  webSocket.onerror = (error) => {
1983
+ if (isResolved) return;
1940
1984
  isResolved = true;
1941
1985
  if (timeoutId !== null) {
1942
1986
  clearTimeout(timeoutId);
@@ -1945,7 +1989,6 @@ var Schematic = class {
1945
1989
  reject(error);
1946
1990
  };
1947
1991
  webSocket.onclose = () => {
1948
- isResolved = true;
1949
1992
  if (timeoutId !== null) {
1950
1993
  clearTimeout(timeoutId);
1951
1994
  }
@@ -1955,7 +1998,7 @@ var Schematic = class {
1955
1998
  this.currentWebSocket = null;
1956
1999
  this.isConnecting = false;
1957
2000
  }
1958
- if (!this.wsIntentionalDisconnect && this.webSocketReconnect) {
2001
+ if (!isResolved && !this.wsIntentionalDisconnect && this.webSocketReconnect) {
1959
2002
  this.attemptReconnect();
1960
2003
  }
1961
2004
  };
@@ -2035,12 +2078,31 @@ var Schematic = class {
2035
2078
  getFlagCheck = (flagKey) => {
2036
2079
  const contextStr = contextString(this.context);
2037
2080
  const checks = this.checks[contextStr] ?? {};
2038
- return checks[flagKey];
2081
+ const check = checks[flagKey];
2082
+ if (check !== void 0) {
2083
+ return check;
2084
+ }
2085
+ if (flagKey in this.flagCheckDefaults || flagKey in this.flagValueDefaults) {
2086
+ return this.resolveFallbackCheckFlagReturn(
2087
+ flagKey,
2088
+ void 0,
2089
+ "Default value used"
2090
+ );
2091
+ }
2092
+ return void 0;
2039
2093
  };
2040
2094
  // flagValues state
2041
2095
  getFlagValue = (flagKey) => {
2042
- const check = this.getFlagCheck(flagKey);
2043
- return check?.value;
2096
+ const contextStr = contextString(this.context);
2097
+ const checks = this.checks[contextStr] ?? {};
2098
+ const check = checks[flagKey];
2099
+ if (check?.value !== void 0) {
2100
+ return check.value;
2101
+ }
2102
+ if (flagKey in this.flagCheckDefaults || flagKey in this.flagValueDefaults) {
2103
+ return this.resolveFallbackValue(flagKey);
2104
+ }
2105
+ return void 0;
2044
2106
  };
2045
2107
  /** Register an event listener that will be notified with the boolean value for a given flag when this value changes */
2046
2108
  addFlagValueListener = (flagKey, listener) => {
@@ -2137,7 +2199,7 @@ var notifyFlagValueListener = (listener, value) => {
2137
2199
  var import_react = __toESM(require("react"));
2138
2200
 
2139
2201
  // src/version.ts
2140
- var version2 = "1.2.12";
2202
+ var version2 = "1.2.14";
2141
2203
 
2142
2204
  // src/context/schematic.tsx
2143
2205
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -754,7 +754,7 @@ function contextString(context) {
754
754
  }, {});
755
755
  return JSON.stringify(sortedContext);
756
756
  }
757
- var version = "1.2.12";
757
+ var version = "1.2.14";
758
758
  var anonymousIdKey = "schematicId";
759
759
  var Schematic = class {
760
760
  additionalHeaders = {};
@@ -779,6 +779,9 @@ var Schematic = class {
779
779
  webSocketConnectionTimeout = 1e4;
780
780
  webSocketReconnect = true;
781
781
  webSocketMaxReconnectAttempts = 7;
782
+ // Max attempts after connection disrupted
783
+ webSocketMaxConnectionAttempts = 3;
784
+ // Max attempts for initial connection
782
785
  webSocketInitialRetryDelay = 1e3;
783
786
  webSocketMaxRetryDelay = 3e4;
784
787
  wsReconnectAttempts = 0;
@@ -900,13 +903,17 @@ var Schematic = class {
900
903
  /**
901
904
  * Resolve fallback value according to priority order:
902
905
  * 1. Callsite fallback value (if provided)
903
- * 2. Initialization fallback value (flagValueDefaults)
904
- * 3. Default to false
906
+ * 2. Boolean value from flagCheckDefaults initialization option
907
+ * 3. Boolean value from flagValueDefaults initialization option
908
+ * 4. Default to false
905
909
  */
906
910
  resolveFallbackValue(key, callsiteFallback) {
907
911
  if (callsiteFallback !== void 0) {
908
912
  return callsiteFallback;
909
913
  }
914
+ if (key in this.flagCheckDefaults) {
915
+ return this.flagCheckDefaults[key].value;
916
+ }
910
917
  if (key in this.flagValueDefaults) {
911
918
  return this.flagValueDefaults[key];
912
919
  }
@@ -1002,7 +1009,7 @@ var Schematic = class {
1002
1009
  this.submitFlagCheckEvent(key, result, context);
1003
1010
  return result.value;
1004
1011
  }).catch((error) => {
1005
- console.error("There was a problem with the fetch operation:", error);
1012
+ console.warn("There was a problem with the fetch operation:", error);
1006
1013
  const errorResult = this.resolveFallbackCheckFlagReturn(
1007
1014
  key,
1008
1015
  fallback,
@@ -1025,7 +1032,7 @@ var Schematic = class {
1025
1032
  try {
1026
1033
  await this.setContext(context);
1027
1034
  } catch (error) {
1028
- console.error(
1035
+ console.warn(
1029
1036
  "WebSocket connection failed, falling back to REST:",
1030
1037
  error
1031
1038
  );
@@ -1168,7 +1175,7 @@ var Schematic = class {
1168
1175
  this.submitFlagCheckEvent(key, result, context);
1169
1176
  return result.value;
1170
1177
  } catch (error) {
1171
- console.error("REST API call failed, using fallback value:", error);
1178
+ console.warn("REST API call failed, using fallback value:", error);
1172
1179
  const errorResult = this.resolveFallbackCheckFlagReturn(
1173
1180
  key,
1174
1181
  fallback,
@@ -1217,7 +1224,7 @@ var Schematic = class {
1217
1224
  {}
1218
1225
  );
1219
1226
  }).catch((error) => {
1220
- console.error("There was a problem with the fetch operation:", error);
1227
+ console.warn("There was a problem with the fetch operation:", error);
1221
1228
  return {};
1222
1229
  });
1223
1230
  };
@@ -1234,7 +1241,7 @@ var Schematic = class {
1234
1241
  user: body.keys
1235
1242
  });
1236
1243
  } catch (error) {
1237
- console.error("Error setting context:", error);
1244
+ console.warn("Error setting context:", error);
1238
1245
  }
1239
1246
  return this.handleEvent("identify", body);
1240
1247
  };
@@ -1292,7 +1299,7 @@ var Schematic = class {
1292
1299
  const socket = await this.conn;
1293
1300
  await this.wsSendMessage(socket, context);
1294
1301
  } catch (error) {
1295
- console.error("Failed to establish WebSocket connection:", error);
1302
+ console.warn("Failed to establish WebSocket connection:", error);
1296
1303
  throw error;
1297
1304
  }
1298
1305
  };
@@ -1713,7 +1720,7 @@ var Schematic = class {
1713
1720
  }
1714
1721
  socket.close();
1715
1722
  } catch (error) {
1716
- console.error("Error during cleanup:", error);
1723
+ console.warn("Error during cleanup:", error);
1717
1724
  } finally {
1718
1725
  this.conn = null;
1719
1726
  this.currentWebSocket = null;
@@ -1856,14 +1863,49 @@ var Schematic = class {
1856
1863
  }
1857
1864
  }, delay);
1858
1865
  };
1859
- // Open a websocket connection
1860
- wsConnect = () => {
1866
+ // Open a websocket connection with retry logic for timeouts
1867
+ wsConnect = async () => {
1861
1868
  if (this.isOffline()) {
1862
1869
  this.debug("wsConnect: skipped (offline mode)");
1863
- return Promise.reject(
1864
- new Error("WebSocket connection skipped in offline mode")
1865
- );
1870
+ throw new Error("WebSocket connection skipped in offline mode");
1871
+ }
1872
+ let lastError = null;
1873
+ const maxAttempts = this.webSocketMaxConnectionAttempts;
1874
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
1875
+ try {
1876
+ const socket = await this.wsConnectOnce();
1877
+ this.wsReconnectAttempts = 0;
1878
+ return socket;
1879
+ } catch (error) {
1880
+ lastError = error instanceof Error ? error : new Error(String(error));
1881
+ const isTimeout = lastError.message === "WebSocket connection timeout";
1882
+ if (!isTimeout) {
1883
+ this.debug(
1884
+ `WebSocket connection failed with non-timeout error, not retrying:`,
1885
+ lastError.message
1886
+ );
1887
+ throw lastError;
1888
+ }
1889
+ if (attempt < maxAttempts - 1) {
1890
+ const baseDelay = this.webSocketInitialRetryDelay * Math.pow(2, attempt);
1891
+ const cappedDelay = Math.min(baseDelay, this.webSocketMaxRetryDelay);
1892
+ const jitter = cappedDelay * 0.2 * Math.random();
1893
+ const delay = cappedDelay + jitter;
1894
+ this.debug(
1895
+ `WebSocket connection timeout (attempt ${attempt + 1}/${maxAttempts}), retrying in ${delay.toFixed(0)}ms`
1896
+ );
1897
+ await new Promise((resolve) => setTimeout(resolve, delay));
1898
+ } else {
1899
+ this.debug(
1900
+ `WebSocket connection timeout (attempt ${attempt + 1}/${maxAttempts}), no more retries`
1901
+ );
1902
+ }
1903
+ }
1866
1904
  }
1905
+ throw lastError ?? new Error("WebSocket connection failed");
1906
+ };
1907
+ // Single attempt to open a websocket connection (no retry logic)
1908
+ wsConnectOnce = () => {
1867
1909
  return new Promise((resolve, reject) => {
1868
1910
  const wsUrl = `${this.webSocketUrl}/flags/bootstrap?apiKey=${this.apiKey}`;
1869
1911
  this.debug(`connecting to WebSocket:`, wsUrl);
@@ -1874,6 +1916,7 @@ var Schematic = class {
1874
1916
  let isResolved = false;
1875
1917
  timeoutId = setTimeout(() => {
1876
1918
  if (!isResolved) {
1919
+ isResolved = true;
1877
1920
  this.debug(
1878
1921
  `WebSocket connection timeout after ${this.webSocketConnectionTimeout}ms`
1879
1922
  );
@@ -1882,16 +1925,17 @@ var Schematic = class {
1882
1925
  }
1883
1926
  }, this.webSocketConnectionTimeout);
1884
1927
  webSocket.onopen = () => {
1928
+ if (isResolved) return;
1885
1929
  isResolved = true;
1886
1930
  if (timeoutId !== null) {
1887
1931
  clearTimeout(timeoutId);
1888
1932
  }
1889
- this.wsReconnectAttempts = 0;
1890
1933
  this.wsIntentionalDisconnect = false;
1891
1934
  this.debug(`WebSocket connection ${connectionId} opened successfully`);
1892
1935
  resolve(webSocket);
1893
1936
  };
1894
1937
  webSocket.onerror = (error) => {
1938
+ if (isResolved) return;
1895
1939
  isResolved = true;
1896
1940
  if (timeoutId !== null) {
1897
1941
  clearTimeout(timeoutId);
@@ -1900,7 +1944,6 @@ var Schematic = class {
1900
1944
  reject(error);
1901
1945
  };
1902
1946
  webSocket.onclose = () => {
1903
- isResolved = true;
1904
1947
  if (timeoutId !== null) {
1905
1948
  clearTimeout(timeoutId);
1906
1949
  }
@@ -1910,7 +1953,7 @@ var Schematic = class {
1910
1953
  this.currentWebSocket = null;
1911
1954
  this.isConnecting = false;
1912
1955
  }
1913
- if (!this.wsIntentionalDisconnect && this.webSocketReconnect) {
1956
+ if (!isResolved && !this.wsIntentionalDisconnect && this.webSocketReconnect) {
1914
1957
  this.attemptReconnect();
1915
1958
  }
1916
1959
  };
@@ -1990,12 +2033,31 @@ var Schematic = class {
1990
2033
  getFlagCheck = (flagKey) => {
1991
2034
  const contextStr = contextString(this.context);
1992
2035
  const checks = this.checks[contextStr] ?? {};
1993
- return checks[flagKey];
2036
+ const check = checks[flagKey];
2037
+ if (check !== void 0) {
2038
+ return check;
2039
+ }
2040
+ if (flagKey in this.flagCheckDefaults || flagKey in this.flagValueDefaults) {
2041
+ return this.resolveFallbackCheckFlagReturn(
2042
+ flagKey,
2043
+ void 0,
2044
+ "Default value used"
2045
+ );
2046
+ }
2047
+ return void 0;
1994
2048
  };
1995
2049
  // flagValues state
1996
2050
  getFlagValue = (flagKey) => {
1997
- const check = this.getFlagCheck(flagKey);
1998
- return check?.value;
2051
+ const contextStr = contextString(this.context);
2052
+ const checks = this.checks[contextStr] ?? {};
2053
+ const check = checks[flagKey];
2054
+ if (check?.value !== void 0) {
2055
+ return check.value;
2056
+ }
2057
+ if (flagKey in this.flagCheckDefaults || flagKey in this.flagValueDefaults) {
2058
+ return this.resolveFallbackValue(flagKey);
2059
+ }
2060
+ return void 0;
1999
2061
  };
2000
2062
  /** Register an event listener that will be notified with the boolean value for a given flag when this value changes */
2001
2063
  addFlagValueListener = (flagKey, listener) => {
@@ -2092,7 +2154,7 @@ var notifyFlagValueListener = (listener, value) => {
2092
2154
  import React, { createContext, useEffect, useMemo, useRef } from "react";
2093
2155
 
2094
2156
  // src/version.ts
2095
- var version2 = "1.2.12";
2157
+ var version2 = "1.2.14";
2096
2158
 
2097
2159
  // src/context/schematic.tsx
2098
2160
  import { jsx } from "react/jsx-runtime";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schematichq/schematic-react",
3
- "version": "1.2.12",
3
+ "version": "1.2.14",
4
4
  "main": "dist/schematic-react.cjs.js",
5
5
  "module": "dist/schematic-react.esm.js",
6
6
  "types": "dist/schematic-react.d.ts",
@@ -31,7 +31,7 @@
31
31
  "prepare": "husky"
32
32
  },
33
33
  "dependencies": {
34
- "@schematichq/schematic-js": "^1.2.12"
34
+ "@schematichq/schematic-js": "^1.2.14"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@eslint/js": "^9.39.1",
@@ -50,7 +50,7 @@
50
50
  "happy-dom": "^20.0.10",
51
51
  "husky": "^9.1.7",
52
52
  "jsdom": "^27.2.0",
53
- "prettier": "^3.6.2",
53
+ "prettier": "^3.7.4",
54
54
  "react": "^19.2.0",
55
55
  "react-dom": "^19.2.0",
56
56
  "typescript": "^5.9.3",