@schematichq/schematic-react 1.2.15 → 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 +38 -56
- package/dist/schematic-react.esm.js +38 -56
- 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 = {};
|
|
@@ -876,8 +876,13 @@ var Schematic = class {
|
|
|
876
876
|
};
|
|
877
877
|
if (options?.storage) {
|
|
878
878
|
this.storage = options.storage;
|
|
879
|
-
} else
|
|
880
|
-
|
|
879
|
+
} else {
|
|
880
|
+
try {
|
|
881
|
+
if (typeof localStorage !== "undefined") {
|
|
882
|
+
this.storage = localStorage;
|
|
883
|
+
}
|
|
884
|
+
} catch {
|
|
885
|
+
}
|
|
881
886
|
}
|
|
882
887
|
if (options?.apiUrl !== void 0) {
|
|
883
888
|
this.apiUrl = options.apiUrl;
|
|
@@ -1008,8 +1013,8 @@ var Schematic = class {
|
|
|
1008
1013
|
/**
|
|
1009
1014
|
* Get value for a single flag.
|
|
1010
1015
|
* In WebSocket mode, returns cached values if connection is active, otherwise establishes
|
|
1011
|
-
* new connection and then returns the
|
|
1012
|
-
* connection fails.
|
|
1016
|
+
* new connection and then returns the requested value. Falls back to preconfigured fallback
|
|
1017
|
+
* values if WebSocket connection fails.
|
|
1013
1018
|
* In REST mode, makes an API call for each check.
|
|
1014
1019
|
*/
|
|
1015
1020
|
async checkFlag(options) {
|
|
@@ -1078,10 +1083,17 @@ var Schematic = class {
|
|
|
1078
1083
|
await this.setContext(context);
|
|
1079
1084
|
} catch (error) {
|
|
1080
1085
|
console.warn(
|
|
1081
|
-
"WebSocket connection failed,
|
|
1086
|
+
"WebSocket connection failed, using fallback value:",
|
|
1082
1087
|
error
|
|
1083
1088
|
);
|
|
1084
|
-
|
|
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;
|
|
1085
1097
|
}
|
|
1086
1098
|
const contextVals = this.checks[contextStr] ?? {};
|
|
1087
1099
|
const flagCheck = contextVals[key];
|
|
@@ -1184,53 +1196,6 @@ var Schematic = class {
|
|
|
1184
1196
|
this.debug(`submitting flag check event:`, eventBody);
|
|
1185
1197
|
return this.handleEvent("flag_check", EventBodyFlagCheckToJSON(eventBody));
|
|
1186
1198
|
}
|
|
1187
|
-
/**
|
|
1188
|
-
* Helper method for falling back to REST API when WebSocket connection fails
|
|
1189
|
-
*/
|
|
1190
|
-
async fallbackToRest(key, context, fallback) {
|
|
1191
|
-
if (this.isOffline()) {
|
|
1192
|
-
const resolvedFallback = this.resolveFallbackValue(key, fallback);
|
|
1193
|
-
this.debug(`fallbackToRest offline result: ${key}`, {
|
|
1194
|
-
value: resolvedFallback,
|
|
1195
|
-
offlineMode: true
|
|
1196
|
-
});
|
|
1197
|
-
return resolvedFallback;
|
|
1198
|
-
}
|
|
1199
|
-
try {
|
|
1200
|
-
const requestUrl = `${this.apiUrl}/flags/${key}/check`;
|
|
1201
|
-
const response = await fetch(requestUrl, {
|
|
1202
|
-
method: "POST",
|
|
1203
|
-
headers: {
|
|
1204
|
-
...this.additionalHeaders ?? {},
|
|
1205
|
-
"Content-Type": "application/json;charset=UTF-8",
|
|
1206
|
-
"X-Schematic-Api-Key": this.apiKey
|
|
1207
|
-
},
|
|
1208
|
-
body: JSON.stringify(context)
|
|
1209
|
-
});
|
|
1210
|
-
if (!response.ok) {
|
|
1211
|
-
throw new Error("Network response was not ok");
|
|
1212
|
-
}
|
|
1213
|
-
const responseJson = await response.json();
|
|
1214
|
-
const data = CheckFlagResponseFromJSON(responseJson);
|
|
1215
|
-
this.debug(`fallbackToRest result: ${key}`, data);
|
|
1216
|
-
const result = CheckFlagReturnFromJSON(data.data);
|
|
1217
|
-
if (typeof result.featureUsageEvent === "string") {
|
|
1218
|
-
this.updateFeatureUsageEventMap(result);
|
|
1219
|
-
}
|
|
1220
|
-
this.submitFlagCheckEvent(key, result, context);
|
|
1221
|
-
return result.value;
|
|
1222
|
-
} catch (error) {
|
|
1223
|
-
console.warn("REST API call failed, using fallback value:", error);
|
|
1224
|
-
const errorResult = this.resolveFallbackCheckFlagReturn(
|
|
1225
|
-
key,
|
|
1226
|
-
fallback,
|
|
1227
|
-
"API request failed (fallback)",
|
|
1228
|
-
error instanceof Error ? error.message : String(error)
|
|
1229
|
-
);
|
|
1230
|
-
this.submitFlagCheckEvent(key, errorResult, context);
|
|
1231
|
-
return errorResult.value;
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
1199
|
/**
|
|
1235
1200
|
* Make an API call to fetch all flag values for a given context.
|
|
1236
1201
|
* Recommended for use in REST mode only.
|
|
@@ -1984,7 +1949,10 @@ var Schematic = class {
|
|
|
1984
1949
|
clearTimeout(timeoutId);
|
|
1985
1950
|
}
|
|
1986
1951
|
this.debug(`WebSocket connection ${connectionId} error:`, error);
|
|
1987
|
-
|
|
1952
|
+
const wrappedError = new Error(
|
|
1953
|
+
"WebSocket connection failed during handshake"
|
|
1954
|
+
);
|
|
1955
|
+
reject(wrappedError);
|
|
1988
1956
|
};
|
|
1989
1957
|
webSocket.onclose = () => {
|
|
1990
1958
|
if (timeoutId !== null) {
|
|
@@ -2031,6 +1999,20 @@ var Schematic = class {
|
|
|
2031
1999
|
}
|
|
2032
2000
|
};
|
|
2033
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
|
+
});
|
|
2034
2016
|
this.currentWebSocket = socket;
|
|
2035
2017
|
const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
|
|
2036
2018
|
const messagePayload = {
|
|
@@ -2197,7 +2179,7 @@ var notifyFlagValueListener = (listener, value) => {
|
|
|
2197
2179
|
var import_react = __toESM(require("react"));
|
|
2198
2180
|
|
|
2199
2181
|
// src/version.ts
|
|
2200
|
-
var version2 = "1.2.
|
|
2182
|
+
var version2 = "1.2.17";
|
|
2201
2183
|
|
|
2202
2184
|
// src/context/schematic.tsx
|
|
2203
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 = {};
|
|
@@ -831,8 +831,13 @@ var Schematic = class {
|
|
|
831
831
|
};
|
|
832
832
|
if (options?.storage) {
|
|
833
833
|
this.storage = options.storage;
|
|
834
|
-
} else
|
|
835
|
-
|
|
834
|
+
} else {
|
|
835
|
+
try {
|
|
836
|
+
if (typeof localStorage !== "undefined") {
|
|
837
|
+
this.storage = localStorage;
|
|
838
|
+
}
|
|
839
|
+
} catch {
|
|
840
|
+
}
|
|
836
841
|
}
|
|
837
842
|
if (options?.apiUrl !== void 0) {
|
|
838
843
|
this.apiUrl = options.apiUrl;
|
|
@@ -963,8 +968,8 @@ var Schematic = class {
|
|
|
963
968
|
/**
|
|
964
969
|
* Get value for a single flag.
|
|
965
970
|
* In WebSocket mode, returns cached values if connection is active, otherwise establishes
|
|
966
|
-
* new connection and then returns the
|
|
967
|
-
* connection fails.
|
|
971
|
+
* new connection and then returns the requested value. Falls back to preconfigured fallback
|
|
972
|
+
* values if WebSocket connection fails.
|
|
968
973
|
* In REST mode, makes an API call for each check.
|
|
969
974
|
*/
|
|
970
975
|
async checkFlag(options) {
|
|
@@ -1033,10 +1038,17 @@ var Schematic = class {
|
|
|
1033
1038
|
await this.setContext(context);
|
|
1034
1039
|
} catch (error) {
|
|
1035
1040
|
console.warn(
|
|
1036
|
-
"WebSocket connection failed,
|
|
1041
|
+
"WebSocket connection failed, using fallback value:",
|
|
1037
1042
|
error
|
|
1038
1043
|
);
|
|
1039
|
-
|
|
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;
|
|
1040
1052
|
}
|
|
1041
1053
|
const contextVals = this.checks[contextStr] ?? {};
|
|
1042
1054
|
const flagCheck = contextVals[key];
|
|
@@ -1139,53 +1151,6 @@ var Schematic = class {
|
|
|
1139
1151
|
this.debug(`submitting flag check event:`, eventBody);
|
|
1140
1152
|
return this.handleEvent("flag_check", EventBodyFlagCheckToJSON(eventBody));
|
|
1141
1153
|
}
|
|
1142
|
-
/**
|
|
1143
|
-
* Helper method for falling back to REST API when WebSocket connection fails
|
|
1144
|
-
*/
|
|
1145
|
-
async fallbackToRest(key, context, fallback) {
|
|
1146
|
-
if (this.isOffline()) {
|
|
1147
|
-
const resolvedFallback = this.resolveFallbackValue(key, fallback);
|
|
1148
|
-
this.debug(`fallbackToRest offline result: ${key}`, {
|
|
1149
|
-
value: resolvedFallback,
|
|
1150
|
-
offlineMode: true
|
|
1151
|
-
});
|
|
1152
|
-
return resolvedFallback;
|
|
1153
|
-
}
|
|
1154
|
-
try {
|
|
1155
|
-
const requestUrl = `${this.apiUrl}/flags/${key}/check`;
|
|
1156
|
-
const response = await fetch(requestUrl, {
|
|
1157
|
-
method: "POST",
|
|
1158
|
-
headers: {
|
|
1159
|
-
...this.additionalHeaders ?? {},
|
|
1160
|
-
"Content-Type": "application/json;charset=UTF-8",
|
|
1161
|
-
"X-Schematic-Api-Key": this.apiKey
|
|
1162
|
-
},
|
|
1163
|
-
body: JSON.stringify(context)
|
|
1164
|
-
});
|
|
1165
|
-
if (!response.ok) {
|
|
1166
|
-
throw new Error("Network response was not ok");
|
|
1167
|
-
}
|
|
1168
|
-
const responseJson = await response.json();
|
|
1169
|
-
const data = CheckFlagResponseFromJSON(responseJson);
|
|
1170
|
-
this.debug(`fallbackToRest result: ${key}`, data);
|
|
1171
|
-
const result = CheckFlagReturnFromJSON(data.data);
|
|
1172
|
-
if (typeof result.featureUsageEvent === "string") {
|
|
1173
|
-
this.updateFeatureUsageEventMap(result);
|
|
1174
|
-
}
|
|
1175
|
-
this.submitFlagCheckEvent(key, result, context);
|
|
1176
|
-
return result.value;
|
|
1177
|
-
} catch (error) {
|
|
1178
|
-
console.warn("REST API call failed, using fallback value:", error);
|
|
1179
|
-
const errorResult = this.resolveFallbackCheckFlagReturn(
|
|
1180
|
-
key,
|
|
1181
|
-
fallback,
|
|
1182
|
-
"API request failed (fallback)",
|
|
1183
|
-
error instanceof Error ? error.message : String(error)
|
|
1184
|
-
);
|
|
1185
|
-
this.submitFlagCheckEvent(key, errorResult, context);
|
|
1186
|
-
return errorResult.value;
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
1154
|
/**
|
|
1190
1155
|
* Make an API call to fetch all flag values for a given context.
|
|
1191
1156
|
* Recommended for use in REST mode only.
|
|
@@ -1939,7 +1904,10 @@ var Schematic = class {
|
|
|
1939
1904
|
clearTimeout(timeoutId);
|
|
1940
1905
|
}
|
|
1941
1906
|
this.debug(`WebSocket connection ${connectionId} error:`, error);
|
|
1942
|
-
|
|
1907
|
+
const wrappedError = new Error(
|
|
1908
|
+
"WebSocket connection failed during handshake"
|
|
1909
|
+
);
|
|
1910
|
+
reject(wrappedError);
|
|
1943
1911
|
};
|
|
1944
1912
|
webSocket.onclose = () => {
|
|
1945
1913
|
if (timeoutId !== null) {
|
|
@@ -1986,6 +1954,20 @@ var Schematic = class {
|
|
|
1986
1954
|
}
|
|
1987
1955
|
};
|
|
1988
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
|
+
});
|
|
1989
1971
|
this.currentWebSocket = socket;
|
|
1990
1972
|
const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
|
|
1991
1973
|
const messagePayload = {
|
|
@@ -2152,7 +2134,7 @@ var notifyFlagValueListener = (listener, value) => {
|
|
|
2152
2134
|
import React, { createContext, useEffect, useMemo, useRef } from "react";
|
|
2153
2135
|
|
|
2154
2136
|
// src/version.ts
|
|
2155
|
-
var version2 = "1.2.
|
|
2137
|
+
var version2 = "1.2.17";
|
|
2156
2138
|
|
|
2157
2139
|
// src/context/schematic.tsx
|
|
2158
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"
|