@schematichq/schematic-react 1.2.16 → 1.2.21
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 +86 -60
- package/dist/schematic-react.esm.js +86 -60
- package/package.json +17 -17
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
|
|
@@ -651,6 +651,36 @@ function v4(options, buf, offset) {
|
|
|
651
651
|
}
|
|
652
652
|
var v4_default = v4;
|
|
653
653
|
var import_polyfill = __toESM2(require_browser_polyfill());
|
|
654
|
+
function EntitlementValueTypeFromJSON(json) {
|
|
655
|
+
return EntitlementValueTypeFromJSONTyped(json, false);
|
|
656
|
+
}
|
|
657
|
+
function EntitlementValueTypeFromJSONTyped(json, ignoreDiscriminator) {
|
|
658
|
+
return json;
|
|
659
|
+
}
|
|
660
|
+
function FeatureEntitlementFromJSON(json) {
|
|
661
|
+
return FeatureEntitlementFromJSONTyped(json, false);
|
|
662
|
+
}
|
|
663
|
+
function FeatureEntitlementFromJSONTyped(json, ignoreDiscriminator) {
|
|
664
|
+
if (json == null) {
|
|
665
|
+
return json;
|
|
666
|
+
}
|
|
667
|
+
return {
|
|
668
|
+
allocation: json["allocation"] == null ? void 0 : json["allocation"],
|
|
669
|
+
creditId: json["credit_id"] == null ? void 0 : json["credit_id"],
|
|
670
|
+
creditRemaining: json["credit_remaining"] == null ? void 0 : json["credit_remaining"],
|
|
671
|
+
creditTotal: json["credit_total"] == null ? void 0 : json["credit_total"],
|
|
672
|
+
creditUsed: json["credit_used"] == null ? void 0 : json["credit_used"],
|
|
673
|
+
eventName: json["event_name"] == null ? void 0 : json["event_name"],
|
|
674
|
+
featureId: json["feature_id"],
|
|
675
|
+
featureKey: json["feature_key"],
|
|
676
|
+
metricPeriod: json["metric_period"] == null ? void 0 : json["metric_period"],
|
|
677
|
+
metricResetAt: json["metric_reset_at"] == null ? void 0 : new Date(json["metric_reset_at"]),
|
|
678
|
+
monthReset: json["month_reset"] == null ? void 0 : json["month_reset"],
|
|
679
|
+
softLimit: json["soft_limit"] == null ? void 0 : json["soft_limit"],
|
|
680
|
+
usage: json["usage"] == null ? void 0 : json["usage"],
|
|
681
|
+
valueType: EntitlementValueTypeFromJSON(json["value_type"])
|
|
682
|
+
};
|
|
683
|
+
}
|
|
654
684
|
function CheckFlagResponseDataFromJSON(json) {
|
|
655
685
|
return CheckFlagResponseDataFromJSONTyped(json, false);
|
|
656
686
|
}
|
|
@@ -660,6 +690,7 @@ function CheckFlagResponseDataFromJSONTyped(json, ignoreDiscriminator) {
|
|
|
660
690
|
}
|
|
661
691
|
return {
|
|
662
692
|
companyId: json["company_id"] == null ? void 0 : json["company_id"],
|
|
693
|
+
entitlement: json["entitlement"] == null ? void 0 : FeatureEntitlementFromJSON(json["entitlement"]),
|
|
663
694
|
error: json["error"] == null ? void 0 : json["error"],
|
|
664
695
|
featureAllocation: json["feature_allocation"] == null ? void 0 : json["feature_allocation"],
|
|
665
696
|
featureUsage: json["feature_usage"] == null ? void 0 : json["feature_usage"],
|
|
@@ -799,7 +830,7 @@ function contextString(context) {
|
|
|
799
830
|
}, {});
|
|
800
831
|
return JSON.stringify(sortedContext);
|
|
801
832
|
}
|
|
802
|
-
var version = "1.2.
|
|
833
|
+
var version = "1.2.21";
|
|
803
834
|
var anonymousIdKey = "schematicId";
|
|
804
835
|
var Schematic = class {
|
|
805
836
|
additionalHeaders = {};
|
|
@@ -845,6 +876,7 @@ var Schematic = class {
|
|
|
845
876
|
retryTimer = null;
|
|
846
877
|
flagValueDefaults = {};
|
|
847
878
|
flagCheckDefaults = {};
|
|
879
|
+
fallbackCheckCache = {};
|
|
848
880
|
constructor(apiKey, options) {
|
|
849
881
|
this.apiKey = apiKey;
|
|
850
882
|
this.eventQueue = [];
|
|
@@ -1013,8 +1045,8 @@ var Schematic = class {
|
|
|
1013
1045
|
/**
|
|
1014
1046
|
* Get value for a single flag.
|
|
1015
1047
|
* In WebSocket mode, returns cached values if connection is active, otherwise establishes
|
|
1016
|
-
* new connection and then returns the
|
|
1017
|
-
* connection fails.
|
|
1048
|
+
* new connection and then returns the requested value. Falls back to preconfigured fallback
|
|
1049
|
+
* values if WebSocket connection fails.
|
|
1018
1050
|
* In REST mode, makes an API call for each check.
|
|
1019
1051
|
*/
|
|
1020
1052
|
async checkFlag(options) {
|
|
@@ -1083,10 +1115,17 @@ var Schematic = class {
|
|
|
1083
1115
|
await this.setContext(context);
|
|
1084
1116
|
} catch (error) {
|
|
1085
1117
|
console.warn(
|
|
1086
|
-
"WebSocket connection failed,
|
|
1118
|
+
"WebSocket connection failed, using fallback value:",
|
|
1087
1119
|
error
|
|
1088
1120
|
);
|
|
1089
|
-
|
|
1121
|
+
const errorResult = this.resolveFallbackCheckFlagReturn(
|
|
1122
|
+
key,
|
|
1123
|
+
fallback,
|
|
1124
|
+
"WebSocket connection failed",
|
|
1125
|
+
error instanceof Error ? error.message : String(error)
|
|
1126
|
+
);
|
|
1127
|
+
this.submitFlagCheckEvent(key, errorResult, context);
|
|
1128
|
+
return errorResult.value;
|
|
1090
1129
|
}
|
|
1091
1130
|
const contextVals = this.checks[contextStr] ?? {};
|
|
1092
1131
|
const flagCheck = contextVals[key];
|
|
@@ -1097,6 +1136,13 @@ var Schematic = class {
|
|
|
1097
1136
|
);
|
|
1098
1137
|
if (typeof flagCheck !== "undefined") {
|
|
1099
1138
|
this.submitFlagCheckEvent(key, flagCheck, context);
|
|
1139
|
+
} else {
|
|
1140
|
+
const fallbackResult = this.resolveFallbackCheckFlagReturn(
|
|
1141
|
+
key,
|
|
1142
|
+
fallback,
|
|
1143
|
+
"No flag values available"
|
|
1144
|
+
);
|
|
1145
|
+
this.submitFlagCheckEvent(key, fallbackResult, context);
|
|
1100
1146
|
}
|
|
1101
1147
|
return result;
|
|
1102
1148
|
} catch (error) {
|
|
@@ -1189,53 +1235,6 @@ var Schematic = class {
|
|
|
1189
1235
|
this.debug(`submitting flag check event:`, eventBody);
|
|
1190
1236
|
return this.handleEvent("flag_check", EventBodyFlagCheckToJSON(eventBody));
|
|
1191
1237
|
}
|
|
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
1238
|
/**
|
|
1240
1239
|
* Make an API call to fetch all flag values for a given context.
|
|
1241
1240
|
* Recommended for use in REST mode only.
|
|
@@ -1309,6 +1308,9 @@ var Schematic = class {
|
|
|
1309
1308
|
this.setIsPending(false);
|
|
1310
1309
|
return Promise.resolve();
|
|
1311
1310
|
}
|
|
1311
|
+
if (contextString(context) === contextString(this.context) && this.conn !== null && !this.isPending) {
|
|
1312
|
+
return;
|
|
1313
|
+
}
|
|
1312
1314
|
try {
|
|
1313
1315
|
this.setIsPending(true);
|
|
1314
1316
|
if (!this.conn) {
|
|
@@ -1348,7 +1350,10 @@ var Schematic = class {
|
|
|
1348
1350
|
await this.wsSendMessage(socket, context);
|
|
1349
1351
|
} catch (error) {
|
|
1350
1352
|
console.warn("Failed to establish WebSocket connection:", error);
|
|
1351
|
-
|
|
1353
|
+
this.context = context;
|
|
1354
|
+
this.flushContextDependentEventQueue();
|
|
1355
|
+
this.setIsPending(false);
|
|
1356
|
+
this.attemptReconnect();
|
|
1352
1357
|
}
|
|
1353
1358
|
};
|
|
1354
1359
|
/**
|
|
@@ -1989,7 +1994,10 @@ var Schematic = class {
|
|
|
1989
1994
|
clearTimeout(timeoutId);
|
|
1990
1995
|
}
|
|
1991
1996
|
this.debug(`WebSocket connection ${connectionId} error:`, error);
|
|
1992
|
-
|
|
1997
|
+
const wrappedError = new Error(
|
|
1998
|
+
"WebSocket connection failed during handshake"
|
|
1999
|
+
);
|
|
2000
|
+
reject(wrappedError);
|
|
1993
2001
|
};
|
|
1994
2002
|
webSocket.onclose = () => {
|
|
1995
2003
|
if (timeoutId !== null) {
|
|
@@ -2036,6 +2044,20 @@ var Schematic = class {
|
|
|
2036
2044
|
}
|
|
2037
2045
|
};
|
|
2038
2046
|
socket.addEventListener("message", messageHandler);
|
|
2047
|
+
socket.addEventListener("close", (event) => {
|
|
2048
|
+
if (!resolved) {
|
|
2049
|
+
resolved = true;
|
|
2050
|
+
if (event.code === 4001) {
|
|
2051
|
+
reject(
|
|
2052
|
+
new Error(
|
|
2053
|
+
`Authentication failed: ${event.reason !== "" ? event.reason : "Invalid API key"}`
|
|
2054
|
+
)
|
|
2055
|
+
);
|
|
2056
|
+
} else {
|
|
2057
|
+
reject(new Error("WebSocket connection closed unexpectedly"));
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
});
|
|
2039
2061
|
this.currentWebSocket = socket;
|
|
2040
2062
|
const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
|
|
2041
2063
|
const messagePayload = {
|
|
@@ -2072,6 +2094,7 @@ var Schematic = class {
|
|
|
2072
2094
|
};
|
|
2073
2095
|
};
|
|
2074
2096
|
setIsPending = (isPending) => {
|
|
2097
|
+
if (this.isPending === isPending) return;
|
|
2075
2098
|
this.isPending = isPending;
|
|
2076
2099
|
this.isPendingListeners.forEach(
|
|
2077
2100
|
(listener) => notifyPendingListener(listener, isPending)
|
|
@@ -2086,11 +2109,14 @@ var Schematic = class {
|
|
|
2086
2109
|
return check;
|
|
2087
2110
|
}
|
|
2088
2111
|
if (flagKey in this.flagCheckDefaults || flagKey in this.flagValueDefaults) {
|
|
2089
|
-
|
|
2090
|
-
flagKey
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2112
|
+
if (!(flagKey in this.fallbackCheckCache)) {
|
|
2113
|
+
this.fallbackCheckCache[flagKey] = this.resolveFallbackCheckFlagReturn(
|
|
2114
|
+
flagKey,
|
|
2115
|
+
void 0,
|
|
2116
|
+
"Default value used"
|
|
2117
|
+
);
|
|
2118
|
+
}
|
|
2119
|
+
return this.fallbackCheckCache[flagKey];
|
|
2094
2120
|
}
|
|
2095
2121
|
return void 0;
|
|
2096
2122
|
};
|
|
@@ -2202,7 +2228,7 @@ var notifyFlagValueListener = (listener, value) => {
|
|
|
2202
2228
|
var import_react = __toESM(require("react"));
|
|
2203
2229
|
|
|
2204
2230
|
// src/version.ts
|
|
2205
|
-
var version2 = "1.2.
|
|
2231
|
+
var version2 = "1.2.21";
|
|
2206
2232
|
|
|
2207
2233
|
// src/context/schematic.tsx
|
|
2208
2234
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
@@ -606,6 +606,36 @@ function v4(options, buf, offset) {
|
|
|
606
606
|
}
|
|
607
607
|
var v4_default = v4;
|
|
608
608
|
var import_polyfill = __toESM(require_browser_polyfill());
|
|
609
|
+
function EntitlementValueTypeFromJSON(json) {
|
|
610
|
+
return EntitlementValueTypeFromJSONTyped(json, false);
|
|
611
|
+
}
|
|
612
|
+
function EntitlementValueTypeFromJSONTyped(json, ignoreDiscriminator) {
|
|
613
|
+
return json;
|
|
614
|
+
}
|
|
615
|
+
function FeatureEntitlementFromJSON(json) {
|
|
616
|
+
return FeatureEntitlementFromJSONTyped(json, false);
|
|
617
|
+
}
|
|
618
|
+
function FeatureEntitlementFromJSONTyped(json, ignoreDiscriminator) {
|
|
619
|
+
if (json == null) {
|
|
620
|
+
return json;
|
|
621
|
+
}
|
|
622
|
+
return {
|
|
623
|
+
allocation: json["allocation"] == null ? void 0 : json["allocation"],
|
|
624
|
+
creditId: json["credit_id"] == null ? void 0 : json["credit_id"],
|
|
625
|
+
creditRemaining: json["credit_remaining"] == null ? void 0 : json["credit_remaining"],
|
|
626
|
+
creditTotal: json["credit_total"] == null ? void 0 : json["credit_total"],
|
|
627
|
+
creditUsed: json["credit_used"] == null ? void 0 : json["credit_used"],
|
|
628
|
+
eventName: json["event_name"] == null ? void 0 : json["event_name"],
|
|
629
|
+
featureId: json["feature_id"],
|
|
630
|
+
featureKey: json["feature_key"],
|
|
631
|
+
metricPeriod: json["metric_period"] == null ? void 0 : json["metric_period"],
|
|
632
|
+
metricResetAt: json["metric_reset_at"] == null ? void 0 : new Date(json["metric_reset_at"]),
|
|
633
|
+
monthReset: json["month_reset"] == null ? void 0 : json["month_reset"],
|
|
634
|
+
softLimit: json["soft_limit"] == null ? void 0 : json["soft_limit"],
|
|
635
|
+
usage: json["usage"] == null ? void 0 : json["usage"],
|
|
636
|
+
valueType: EntitlementValueTypeFromJSON(json["value_type"])
|
|
637
|
+
};
|
|
638
|
+
}
|
|
609
639
|
function CheckFlagResponseDataFromJSON(json) {
|
|
610
640
|
return CheckFlagResponseDataFromJSONTyped(json, false);
|
|
611
641
|
}
|
|
@@ -615,6 +645,7 @@ function CheckFlagResponseDataFromJSONTyped(json, ignoreDiscriminator) {
|
|
|
615
645
|
}
|
|
616
646
|
return {
|
|
617
647
|
companyId: json["company_id"] == null ? void 0 : json["company_id"],
|
|
648
|
+
entitlement: json["entitlement"] == null ? void 0 : FeatureEntitlementFromJSON(json["entitlement"]),
|
|
618
649
|
error: json["error"] == null ? void 0 : json["error"],
|
|
619
650
|
featureAllocation: json["feature_allocation"] == null ? void 0 : json["feature_allocation"],
|
|
620
651
|
featureUsage: json["feature_usage"] == null ? void 0 : json["feature_usage"],
|
|
@@ -754,7 +785,7 @@ function contextString(context) {
|
|
|
754
785
|
}, {});
|
|
755
786
|
return JSON.stringify(sortedContext);
|
|
756
787
|
}
|
|
757
|
-
var version = "1.2.
|
|
788
|
+
var version = "1.2.21";
|
|
758
789
|
var anonymousIdKey = "schematicId";
|
|
759
790
|
var Schematic = class {
|
|
760
791
|
additionalHeaders = {};
|
|
@@ -800,6 +831,7 @@ var Schematic = class {
|
|
|
800
831
|
retryTimer = null;
|
|
801
832
|
flagValueDefaults = {};
|
|
802
833
|
flagCheckDefaults = {};
|
|
834
|
+
fallbackCheckCache = {};
|
|
803
835
|
constructor(apiKey, options) {
|
|
804
836
|
this.apiKey = apiKey;
|
|
805
837
|
this.eventQueue = [];
|
|
@@ -968,8 +1000,8 @@ var Schematic = class {
|
|
|
968
1000
|
/**
|
|
969
1001
|
* Get value for a single flag.
|
|
970
1002
|
* In WebSocket mode, returns cached values if connection is active, otherwise establishes
|
|
971
|
-
* new connection and then returns the
|
|
972
|
-
* connection fails.
|
|
1003
|
+
* new connection and then returns the requested value. Falls back to preconfigured fallback
|
|
1004
|
+
* values if WebSocket connection fails.
|
|
973
1005
|
* In REST mode, makes an API call for each check.
|
|
974
1006
|
*/
|
|
975
1007
|
async checkFlag(options) {
|
|
@@ -1038,10 +1070,17 @@ var Schematic = class {
|
|
|
1038
1070
|
await this.setContext(context);
|
|
1039
1071
|
} catch (error) {
|
|
1040
1072
|
console.warn(
|
|
1041
|
-
"WebSocket connection failed,
|
|
1073
|
+
"WebSocket connection failed, using fallback value:",
|
|
1042
1074
|
error
|
|
1043
1075
|
);
|
|
1044
|
-
|
|
1076
|
+
const errorResult = this.resolveFallbackCheckFlagReturn(
|
|
1077
|
+
key,
|
|
1078
|
+
fallback,
|
|
1079
|
+
"WebSocket connection failed",
|
|
1080
|
+
error instanceof Error ? error.message : String(error)
|
|
1081
|
+
);
|
|
1082
|
+
this.submitFlagCheckEvent(key, errorResult, context);
|
|
1083
|
+
return errorResult.value;
|
|
1045
1084
|
}
|
|
1046
1085
|
const contextVals = this.checks[contextStr] ?? {};
|
|
1047
1086
|
const flagCheck = contextVals[key];
|
|
@@ -1052,6 +1091,13 @@ var Schematic = class {
|
|
|
1052
1091
|
);
|
|
1053
1092
|
if (typeof flagCheck !== "undefined") {
|
|
1054
1093
|
this.submitFlagCheckEvent(key, flagCheck, context);
|
|
1094
|
+
} else {
|
|
1095
|
+
const fallbackResult = this.resolveFallbackCheckFlagReturn(
|
|
1096
|
+
key,
|
|
1097
|
+
fallback,
|
|
1098
|
+
"No flag values available"
|
|
1099
|
+
);
|
|
1100
|
+
this.submitFlagCheckEvent(key, fallbackResult, context);
|
|
1055
1101
|
}
|
|
1056
1102
|
return result;
|
|
1057
1103
|
} catch (error) {
|
|
@@ -1144,53 +1190,6 @@ var Schematic = class {
|
|
|
1144
1190
|
this.debug(`submitting flag check event:`, eventBody);
|
|
1145
1191
|
return this.handleEvent("flag_check", EventBodyFlagCheckToJSON(eventBody));
|
|
1146
1192
|
}
|
|
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
1193
|
/**
|
|
1195
1194
|
* Make an API call to fetch all flag values for a given context.
|
|
1196
1195
|
* Recommended for use in REST mode only.
|
|
@@ -1264,6 +1263,9 @@ var Schematic = class {
|
|
|
1264
1263
|
this.setIsPending(false);
|
|
1265
1264
|
return Promise.resolve();
|
|
1266
1265
|
}
|
|
1266
|
+
if (contextString(context) === contextString(this.context) && this.conn !== null && !this.isPending) {
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1267
1269
|
try {
|
|
1268
1270
|
this.setIsPending(true);
|
|
1269
1271
|
if (!this.conn) {
|
|
@@ -1303,7 +1305,10 @@ var Schematic = class {
|
|
|
1303
1305
|
await this.wsSendMessage(socket, context);
|
|
1304
1306
|
} catch (error) {
|
|
1305
1307
|
console.warn("Failed to establish WebSocket connection:", error);
|
|
1306
|
-
|
|
1308
|
+
this.context = context;
|
|
1309
|
+
this.flushContextDependentEventQueue();
|
|
1310
|
+
this.setIsPending(false);
|
|
1311
|
+
this.attemptReconnect();
|
|
1307
1312
|
}
|
|
1308
1313
|
};
|
|
1309
1314
|
/**
|
|
@@ -1944,7 +1949,10 @@ var Schematic = class {
|
|
|
1944
1949
|
clearTimeout(timeoutId);
|
|
1945
1950
|
}
|
|
1946
1951
|
this.debug(`WebSocket connection ${connectionId} error:`, error);
|
|
1947
|
-
|
|
1952
|
+
const wrappedError = new Error(
|
|
1953
|
+
"WebSocket connection failed during handshake"
|
|
1954
|
+
);
|
|
1955
|
+
reject(wrappedError);
|
|
1948
1956
|
};
|
|
1949
1957
|
webSocket.onclose = () => {
|
|
1950
1958
|
if (timeoutId !== null) {
|
|
@@ -1991,6 +1999,20 @@ var Schematic = class {
|
|
|
1991
1999
|
}
|
|
1992
2000
|
};
|
|
1993
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
|
+
});
|
|
1994
2016
|
this.currentWebSocket = socket;
|
|
1995
2017
|
const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
|
|
1996
2018
|
const messagePayload = {
|
|
@@ -2027,6 +2049,7 @@ var Schematic = class {
|
|
|
2027
2049
|
};
|
|
2028
2050
|
};
|
|
2029
2051
|
setIsPending = (isPending) => {
|
|
2052
|
+
if (this.isPending === isPending) return;
|
|
2030
2053
|
this.isPending = isPending;
|
|
2031
2054
|
this.isPendingListeners.forEach(
|
|
2032
2055
|
(listener) => notifyPendingListener(listener, isPending)
|
|
@@ -2041,11 +2064,14 @@ var Schematic = class {
|
|
|
2041
2064
|
return check;
|
|
2042
2065
|
}
|
|
2043
2066
|
if (flagKey in this.flagCheckDefaults || flagKey in this.flagValueDefaults) {
|
|
2044
|
-
|
|
2045
|
-
flagKey
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2067
|
+
if (!(flagKey in this.fallbackCheckCache)) {
|
|
2068
|
+
this.fallbackCheckCache[flagKey] = this.resolveFallbackCheckFlagReturn(
|
|
2069
|
+
flagKey,
|
|
2070
|
+
void 0,
|
|
2071
|
+
"Default value used"
|
|
2072
|
+
);
|
|
2073
|
+
}
|
|
2074
|
+
return this.fallbackCheckCache[flagKey];
|
|
2049
2075
|
}
|
|
2050
2076
|
return void 0;
|
|
2051
2077
|
};
|
|
@@ -2157,7 +2183,7 @@ var notifyFlagValueListener = (listener, value) => {
|
|
|
2157
2183
|
import React, { createContext, useEffect, useMemo, useRef } from "react";
|
|
2158
2184
|
|
|
2159
2185
|
// src/version.ts
|
|
2160
|
-
var version2 = "1.2.
|
|
2186
|
+
var version2 = "1.2.21";
|
|
2161
2187
|
|
|
2162
2188
|
// src/context/schematic.tsx
|
|
2163
2189
|
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.21",
|
|
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.21"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@eslint/js": "^
|
|
38
|
-
"@microsoft/api-extractor": "^7.
|
|
37
|
+
"@eslint/js": "^10.0.1",
|
|
38
|
+
"@microsoft/api-extractor": "^7.57.6",
|
|
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.
|
|
45
|
-
"eslint": "^
|
|
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
|
+
"eslint": "^10.0.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.4.0",
|
|
50
|
+
"happy-dom": "^20.8.3",
|
|
51
51
|
"husky": "^9.1.7",
|
|
52
|
-
"jsdom": "^
|
|
53
|
-
"prettier": "^3.
|
|
54
|
-
"react": "^19.2.
|
|
55
|
-
"react-dom": "^19.2.
|
|
52
|
+
"jsdom": "^28.1.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.56.1",
|
|
58
|
+
"vitest": "^4.0.18"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
61
|
"react": ">=18"
|