@schematichq/schematic-react 1.2.16 → 1.2.17
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/README.md +60 -1
- package/dist/schematic-react.cjs.js +31 -54
- package/dist/schematic-react.esm.js +31 -54
- package/package.json +16 -16
package/README.md
CHANGED
|
@@ -136,6 +136,66 @@ const MyComponent = () => {
|
|
|
136
136
|
|
|
137
137
|
*Note: `useSchematicIsPending` is checking if entitlement data has been loaded, typically via `identify`. It should, therefore, be used to wrap flag and entitlement checks, but never the initial call to `identify`.*
|
|
138
138
|
|
|
139
|
+
## Fallback Behavior
|
|
140
|
+
|
|
141
|
+
The SDK includes built-in fallback behavior you can use to ensure your application continues to function even when unable to reach Schematic (e.g., during service disruptions or network issues).
|
|
142
|
+
|
|
143
|
+
### Flag Check Fallbacks
|
|
144
|
+
|
|
145
|
+
When flag checks cannot reach Schematic, they use fallback values in the following priority order:
|
|
146
|
+
|
|
147
|
+
1. Callsite fallback - fallback values can be provided directly in the hook options
|
|
148
|
+
2. Initialization defaults - fallback values configured via `flagCheckDefaults` or `flagValueDefaults` options when initializing the provider
|
|
149
|
+
3. Default value - Returns `false` if no fallback is configured
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
// Provide a fallback value at the callsite
|
|
153
|
+
import { useSchematicFlag } from "@schematichq/schematic-react";
|
|
154
|
+
|
|
155
|
+
const MyComponent = () => {
|
|
156
|
+
const isFeatureEnabled = useSchematicFlag("feature-flag", {
|
|
157
|
+
fallback: true, // Used if API request fails
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return isFeatureEnabled ? <Feature /> : <Fallback />;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// Or configure defaults at initialization
|
|
164
|
+
import { SchematicProvider } from "@schematichq/schematic-react";
|
|
165
|
+
|
|
166
|
+
ReactDOM.render(
|
|
167
|
+
<SchematicProvider
|
|
168
|
+
publishableKey="your-publishable-key"
|
|
169
|
+
flagValueDefaults={{
|
|
170
|
+
"feature-flag": true, // Used if API request fails and no callsite fallback
|
|
171
|
+
}}
|
|
172
|
+
flagCheckDefaults={{
|
|
173
|
+
"another-flag": {
|
|
174
|
+
flag: "another-flag",
|
|
175
|
+
value: true,
|
|
176
|
+
reason: "Default value",
|
|
177
|
+
},
|
|
178
|
+
}}
|
|
179
|
+
>
|
|
180
|
+
<App />
|
|
181
|
+
</SchematicProvider>,
|
|
182
|
+
document.getElementById("root"),
|
|
183
|
+
);
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Event Queueing and Retry
|
|
187
|
+
|
|
188
|
+
When events (track, identify) cannot be sent due to network issues, they are automatically queued and retried:
|
|
189
|
+
|
|
190
|
+
- Events are queued in memory (up to 100 events by default, configurable via `maxEventQueueSize`)
|
|
191
|
+
- Failed events are retried with exponential backoff (up to 5 attempts by default, configurable via `maxEventRetries`)
|
|
192
|
+
- Events are automatically flushed when the network connection is restored
|
|
193
|
+
- Events queued when the page is hidden are sent when the page becomes visible
|
|
194
|
+
|
|
195
|
+
### WebSocket Fallback
|
|
196
|
+
|
|
197
|
+
In WebSocket mode, if the WebSocket connection fails, the SDK will provide the last known value or the configured fallback values as [outlined above](/#flag-check-fallbacks). The WebSocket will also automatically attempt to re-establish it's connection with Schematic using an exponential backoff.
|
|
198
|
+
|
|
139
199
|
## React Native
|
|
140
200
|
|
|
141
201
|
### Handling app background/foreground
|
|
@@ -217,7 +277,6 @@ ReactDOM.render(
|
|
|
217
277
|
|
|
218
278
|
Offline mode automatically enables debug mode to help with troubleshooting.
|
|
219
279
|
|
|
220
|
-
|
|
221
280
|
## License
|
|
222
281
|
|
|
223
282
|
MIT
|
|
@@ -799,7 +799,7 @@ function contextString(context) {
|
|
|
799
799
|
}, {});
|
|
800
800
|
return JSON.stringify(sortedContext);
|
|
801
801
|
}
|
|
802
|
-
var version = "1.2.
|
|
802
|
+
var version = "1.2.17";
|
|
803
803
|
var anonymousIdKey = "schematicId";
|
|
804
804
|
var Schematic = class {
|
|
805
805
|
additionalHeaders = {};
|
|
@@ -1013,8 +1013,8 @@ var Schematic = class {
|
|
|
1013
1013
|
/**
|
|
1014
1014
|
* Get value for a single flag.
|
|
1015
1015
|
* In WebSocket mode, returns cached values if connection is active, otherwise establishes
|
|
1016
|
-
* new connection and then returns the
|
|
1017
|
-
* connection fails.
|
|
1016
|
+
* new connection and then returns the requested value. Falls back to preconfigured fallback
|
|
1017
|
+
* values if WebSocket connection fails.
|
|
1018
1018
|
* In REST mode, makes an API call for each check.
|
|
1019
1019
|
*/
|
|
1020
1020
|
async checkFlag(options) {
|
|
@@ -1083,10 +1083,17 @@ var Schematic = class {
|
|
|
1083
1083
|
await this.setContext(context);
|
|
1084
1084
|
} catch (error) {
|
|
1085
1085
|
console.warn(
|
|
1086
|
-
"WebSocket connection failed,
|
|
1086
|
+
"WebSocket connection failed, using fallback value:",
|
|
1087
1087
|
error
|
|
1088
1088
|
);
|
|
1089
|
-
|
|
1089
|
+
const errorResult = this.resolveFallbackCheckFlagReturn(
|
|
1090
|
+
key,
|
|
1091
|
+
fallback,
|
|
1092
|
+
"WebSocket connection failed",
|
|
1093
|
+
error instanceof Error ? error.message : String(error)
|
|
1094
|
+
);
|
|
1095
|
+
this.submitFlagCheckEvent(key, errorResult, context);
|
|
1096
|
+
return errorResult.value;
|
|
1090
1097
|
}
|
|
1091
1098
|
const contextVals = this.checks[contextStr] ?? {};
|
|
1092
1099
|
const flagCheck = contextVals[key];
|
|
@@ -1189,53 +1196,6 @@ var Schematic = class {
|
|
|
1189
1196
|
this.debug(`submitting flag check event:`, eventBody);
|
|
1190
1197
|
return this.handleEvent("flag_check", EventBodyFlagCheckToJSON(eventBody));
|
|
1191
1198
|
}
|
|
1192
|
-
/**
|
|
1193
|
-
* Helper method for falling back to REST API when WebSocket connection fails
|
|
1194
|
-
*/
|
|
1195
|
-
async fallbackToRest(key, context, fallback) {
|
|
1196
|
-
if (this.isOffline()) {
|
|
1197
|
-
const resolvedFallback = this.resolveFallbackValue(key, fallback);
|
|
1198
|
-
this.debug(`fallbackToRest offline result: ${key}`, {
|
|
1199
|
-
value: resolvedFallback,
|
|
1200
|
-
offlineMode: true
|
|
1201
|
-
});
|
|
1202
|
-
return resolvedFallback;
|
|
1203
|
-
}
|
|
1204
|
-
try {
|
|
1205
|
-
const requestUrl = `${this.apiUrl}/flags/${key}/check`;
|
|
1206
|
-
const response = await fetch(requestUrl, {
|
|
1207
|
-
method: "POST",
|
|
1208
|
-
headers: {
|
|
1209
|
-
...this.additionalHeaders ?? {},
|
|
1210
|
-
"Content-Type": "application/json;charset=UTF-8",
|
|
1211
|
-
"X-Schematic-Api-Key": this.apiKey
|
|
1212
|
-
},
|
|
1213
|
-
body: JSON.stringify(context)
|
|
1214
|
-
});
|
|
1215
|
-
if (!response.ok) {
|
|
1216
|
-
throw new Error("Network response was not ok");
|
|
1217
|
-
}
|
|
1218
|
-
const responseJson = await response.json();
|
|
1219
|
-
const data = CheckFlagResponseFromJSON(responseJson);
|
|
1220
|
-
this.debug(`fallbackToRest result: ${key}`, data);
|
|
1221
|
-
const result = CheckFlagReturnFromJSON(data.data);
|
|
1222
|
-
if (typeof result.featureUsageEvent === "string") {
|
|
1223
|
-
this.updateFeatureUsageEventMap(result);
|
|
1224
|
-
}
|
|
1225
|
-
this.submitFlagCheckEvent(key, result, context);
|
|
1226
|
-
return result.value;
|
|
1227
|
-
} catch (error) {
|
|
1228
|
-
console.warn("REST API call failed, using fallback value:", error);
|
|
1229
|
-
const errorResult = this.resolveFallbackCheckFlagReturn(
|
|
1230
|
-
key,
|
|
1231
|
-
fallback,
|
|
1232
|
-
"API request failed (fallback)",
|
|
1233
|
-
error instanceof Error ? error.message : String(error)
|
|
1234
|
-
);
|
|
1235
|
-
this.submitFlagCheckEvent(key, errorResult, context);
|
|
1236
|
-
return errorResult.value;
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
1199
|
/**
|
|
1240
1200
|
* Make an API call to fetch all flag values for a given context.
|
|
1241
1201
|
* Recommended for use in REST mode only.
|
|
@@ -1989,7 +1949,10 @@ var Schematic = class {
|
|
|
1989
1949
|
clearTimeout(timeoutId);
|
|
1990
1950
|
}
|
|
1991
1951
|
this.debug(`WebSocket connection ${connectionId} error:`, error);
|
|
1992
|
-
|
|
1952
|
+
const wrappedError = new Error(
|
|
1953
|
+
"WebSocket connection failed during handshake"
|
|
1954
|
+
);
|
|
1955
|
+
reject(wrappedError);
|
|
1993
1956
|
};
|
|
1994
1957
|
webSocket.onclose = () => {
|
|
1995
1958
|
if (timeoutId !== null) {
|
|
@@ -2036,6 +1999,20 @@ var Schematic = class {
|
|
|
2036
1999
|
}
|
|
2037
2000
|
};
|
|
2038
2001
|
socket.addEventListener("message", messageHandler);
|
|
2002
|
+
socket.addEventListener("close", (event) => {
|
|
2003
|
+
if (!resolved) {
|
|
2004
|
+
resolved = true;
|
|
2005
|
+
if (event.code === 4001) {
|
|
2006
|
+
reject(
|
|
2007
|
+
new Error(
|
|
2008
|
+
`Authentication failed: ${event.reason !== "" ? event.reason : "Invalid API key"}`
|
|
2009
|
+
)
|
|
2010
|
+
);
|
|
2011
|
+
} else {
|
|
2012
|
+
reject(new Error("WebSocket connection closed unexpectedly"));
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
});
|
|
2039
2016
|
this.currentWebSocket = socket;
|
|
2040
2017
|
const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
|
|
2041
2018
|
const messagePayload = {
|
|
@@ -2202,7 +2179,7 @@ var notifyFlagValueListener = (listener, value) => {
|
|
|
2202
2179
|
var import_react = __toESM(require("react"));
|
|
2203
2180
|
|
|
2204
2181
|
// src/version.ts
|
|
2205
|
-
var version2 = "1.2.
|
|
2182
|
+
var version2 = "1.2.17";
|
|
2206
2183
|
|
|
2207
2184
|
// src/context/schematic.tsx
|
|
2208
2185
|
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.
|
|
757
|
+
var version = "1.2.17";
|
|
758
758
|
var anonymousIdKey = "schematicId";
|
|
759
759
|
var Schematic = class {
|
|
760
760
|
additionalHeaders = {};
|
|
@@ -968,8 +968,8 @@ var Schematic = class {
|
|
|
968
968
|
/**
|
|
969
969
|
* Get value for a single flag.
|
|
970
970
|
* In WebSocket mode, returns cached values if connection is active, otherwise establishes
|
|
971
|
-
* new connection and then returns the
|
|
972
|
-
* connection fails.
|
|
971
|
+
* new connection and then returns the requested value. Falls back to preconfigured fallback
|
|
972
|
+
* values if WebSocket connection fails.
|
|
973
973
|
* In REST mode, makes an API call for each check.
|
|
974
974
|
*/
|
|
975
975
|
async checkFlag(options) {
|
|
@@ -1038,10 +1038,17 @@ var Schematic = class {
|
|
|
1038
1038
|
await this.setContext(context);
|
|
1039
1039
|
} catch (error) {
|
|
1040
1040
|
console.warn(
|
|
1041
|
-
"WebSocket connection failed,
|
|
1041
|
+
"WebSocket connection failed, using fallback value:",
|
|
1042
1042
|
error
|
|
1043
1043
|
);
|
|
1044
|
-
|
|
1044
|
+
const errorResult = this.resolveFallbackCheckFlagReturn(
|
|
1045
|
+
key,
|
|
1046
|
+
fallback,
|
|
1047
|
+
"WebSocket connection failed",
|
|
1048
|
+
error instanceof Error ? error.message : String(error)
|
|
1049
|
+
);
|
|
1050
|
+
this.submitFlagCheckEvent(key, errorResult, context);
|
|
1051
|
+
return errorResult.value;
|
|
1045
1052
|
}
|
|
1046
1053
|
const contextVals = this.checks[contextStr] ?? {};
|
|
1047
1054
|
const flagCheck = contextVals[key];
|
|
@@ -1144,53 +1151,6 @@ var Schematic = class {
|
|
|
1144
1151
|
this.debug(`submitting flag check event:`, eventBody);
|
|
1145
1152
|
return this.handleEvent("flag_check", EventBodyFlagCheckToJSON(eventBody));
|
|
1146
1153
|
}
|
|
1147
|
-
/**
|
|
1148
|
-
* Helper method for falling back to REST API when WebSocket connection fails
|
|
1149
|
-
*/
|
|
1150
|
-
async fallbackToRest(key, context, fallback) {
|
|
1151
|
-
if (this.isOffline()) {
|
|
1152
|
-
const resolvedFallback = this.resolveFallbackValue(key, fallback);
|
|
1153
|
-
this.debug(`fallbackToRest offline result: ${key}`, {
|
|
1154
|
-
value: resolvedFallback,
|
|
1155
|
-
offlineMode: true
|
|
1156
|
-
});
|
|
1157
|
-
return resolvedFallback;
|
|
1158
|
-
}
|
|
1159
|
-
try {
|
|
1160
|
-
const requestUrl = `${this.apiUrl}/flags/${key}/check`;
|
|
1161
|
-
const response = await fetch(requestUrl, {
|
|
1162
|
-
method: "POST",
|
|
1163
|
-
headers: {
|
|
1164
|
-
...this.additionalHeaders ?? {},
|
|
1165
|
-
"Content-Type": "application/json;charset=UTF-8",
|
|
1166
|
-
"X-Schematic-Api-Key": this.apiKey
|
|
1167
|
-
},
|
|
1168
|
-
body: JSON.stringify(context)
|
|
1169
|
-
});
|
|
1170
|
-
if (!response.ok) {
|
|
1171
|
-
throw new Error("Network response was not ok");
|
|
1172
|
-
}
|
|
1173
|
-
const responseJson = await response.json();
|
|
1174
|
-
const data = CheckFlagResponseFromJSON(responseJson);
|
|
1175
|
-
this.debug(`fallbackToRest result: ${key}`, data);
|
|
1176
|
-
const result = CheckFlagReturnFromJSON(data.data);
|
|
1177
|
-
if (typeof result.featureUsageEvent === "string") {
|
|
1178
|
-
this.updateFeatureUsageEventMap(result);
|
|
1179
|
-
}
|
|
1180
|
-
this.submitFlagCheckEvent(key, result, context);
|
|
1181
|
-
return result.value;
|
|
1182
|
-
} catch (error) {
|
|
1183
|
-
console.warn("REST API call failed, using fallback value:", error);
|
|
1184
|
-
const errorResult = this.resolveFallbackCheckFlagReturn(
|
|
1185
|
-
key,
|
|
1186
|
-
fallback,
|
|
1187
|
-
"API request failed (fallback)",
|
|
1188
|
-
error instanceof Error ? error.message : String(error)
|
|
1189
|
-
);
|
|
1190
|
-
this.submitFlagCheckEvent(key, errorResult, context);
|
|
1191
|
-
return errorResult.value;
|
|
1192
|
-
}
|
|
1193
|
-
}
|
|
1194
1154
|
/**
|
|
1195
1155
|
* Make an API call to fetch all flag values for a given context.
|
|
1196
1156
|
* Recommended for use in REST mode only.
|
|
@@ -1944,7 +1904,10 @@ var Schematic = class {
|
|
|
1944
1904
|
clearTimeout(timeoutId);
|
|
1945
1905
|
}
|
|
1946
1906
|
this.debug(`WebSocket connection ${connectionId} error:`, error);
|
|
1947
|
-
|
|
1907
|
+
const wrappedError = new Error(
|
|
1908
|
+
"WebSocket connection failed during handshake"
|
|
1909
|
+
);
|
|
1910
|
+
reject(wrappedError);
|
|
1948
1911
|
};
|
|
1949
1912
|
webSocket.onclose = () => {
|
|
1950
1913
|
if (timeoutId !== null) {
|
|
@@ -1991,6 +1954,20 @@ var Schematic = class {
|
|
|
1991
1954
|
}
|
|
1992
1955
|
};
|
|
1993
1956
|
socket.addEventListener("message", messageHandler);
|
|
1957
|
+
socket.addEventListener("close", (event) => {
|
|
1958
|
+
if (!resolved) {
|
|
1959
|
+
resolved = true;
|
|
1960
|
+
if (event.code === 4001) {
|
|
1961
|
+
reject(
|
|
1962
|
+
new Error(
|
|
1963
|
+
`Authentication failed: ${event.reason !== "" ? event.reason : "Invalid API key"}`
|
|
1964
|
+
)
|
|
1965
|
+
);
|
|
1966
|
+
} else {
|
|
1967
|
+
reject(new Error("WebSocket connection closed unexpectedly"));
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
});
|
|
1994
1971
|
this.currentWebSocket = socket;
|
|
1995
1972
|
const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
|
|
1996
1973
|
const messagePayload = {
|
|
@@ -2157,7 +2134,7 @@ var notifyFlagValueListener = (listener, value) => {
|
|
|
2157
2134
|
import React, { createContext, useEffect, useMemo, useRef } from "react";
|
|
2158
2135
|
|
|
2159
2136
|
// src/version.ts
|
|
2160
|
-
var version2 = "1.2.
|
|
2137
|
+
var version2 = "1.2.17";
|
|
2161
2138
|
|
|
2162
2139
|
// src/context/schematic.tsx
|
|
2163
2140
|
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.
|
|
3
|
+
"version": "1.2.17",
|
|
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,31 +31,31 @@
|
|
|
31
31
|
"prepare": "husky"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@schematichq/schematic-js": "^1.2.
|
|
34
|
+
"@schematichq/schematic-js": "^1.2.17"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@eslint/js": "^
|
|
38
|
-
"@microsoft/api-extractor": "^7.
|
|
37
|
+
"@eslint/js": "^10.0.1",
|
|
38
|
+
"@microsoft/api-extractor": "^7.56.3",
|
|
39
39
|
"@testing-library/dom": "^10.4.1",
|
|
40
40
|
"@testing-library/jest-dom": "^6.9.1",
|
|
41
|
-
"@testing-library/react": "^16.3.
|
|
42
|
-
"@types/react": "^19.2.
|
|
43
|
-
"@vitest/browser": "^4.0.
|
|
44
|
-
"esbuild": "^0.27.
|
|
41
|
+
"@testing-library/react": "^16.3.2",
|
|
42
|
+
"@types/react": "^19.2.14",
|
|
43
|
+
"@vitest/browser": "^4.0.18",
|
|
44
|
+
"esbuild": "^0.27.3",
|
|
45
45
|
"eslint": "^9.39.2",
|
|
46
46
|
"eslint-plugin-import": "^2.32.0",
|
|
47
47
|
"eslint-plugin-react": "^7.37.5",
|
|
48
48
|
"eslint-plugin-react-hooks": "^7.0.1",
|
|
49
|
-
"globals": "^
|
|
50
|
-
"happy-dom": "^20.
|
|
49
|
+
"globals": "^17.3.0",
|
|
50
|
+
"happy-dom": "^20.6.1",
|
|
51
51
|
"husky": "^9.1.7",
|
|
52
|
-
"jsdom": "^
|
|
53
|
-
"prettier": "^3.
|
|
54
|
-
"react": "^19.2.
|
|
55
|
-
"react-dom": "^19.2.
|
|
52
|
+
"jsdom": "^28.0.0",
|
|
53
|
+
"prettier": "^3.8.1",
|
|
54
|
+
"react": "^19.2.4",
|
|
55
|
+
"react-dom": "^19.2.4",
|
|
56
56
|
"typescript": "^5.9.3",
|
|
57
|
-
"typescript-eslint": "^8.
|
|
58
|
-
"vitest": "^4.0.
|
|
57
|
+
"typescript-eslint": "^8.55.0",
|
|
58
|
+
"vitest": "^4.0.18"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
61
|
"react": ">=18"
|