@schematichq/schematic-js 1.2.1 → 1.2.3
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 +36 -2
- package/dist/schematic.browser.js +2 -2
- package/dist/schematic.cjs.js +317 -19
- package/dist/schematic.d.ts +133 -4
- package/dist/schematic.esm.js +317 -19
- package/package.json +10 -9
package/dist/schematic.cjs.js
CHANGED
|
@@ -572,6 +572,7 @@ __export(index_exports, {
|
|
|
572
572
|
CheckFlagResponseFromJSON: () => CheckFlagResponseFromJSON,
|
|
573
573
|
CheckFlagReturnFromJSON: () => CheckFlagReturnFromJSON,
|
|
574
574
|
CheckFlagsResponseFromJSON: () => CheckFlagsResponseFromJSON,
|
|
575
|
+
EventBodyFlagCheckToJSON: () => EventBodyFlagCheckToJSON,
|
|
575
576
|
RuleType: () => RuleType,
|
|
576
577
|
Schematic: () => Schematic,
|
|
577
578
|
UsagePeriod: () => UsagePeriod
|
|
@@ -646,6 +647,7 @@ function CheckFlagResponseDataFromJSONTyped(json, ignoreDiscriminator) {
|
|
|
646
647
|
error: json["error"] == null ? void 0 : json["error"],
|
|
647
648
|
featureAllocation: json["feature_allocation"] == null ? void 0 : json["feature_allocation"],
|
|
648
649
|
featureUsage: json["feature_usage"] == null ? void 0 : json["feature_usage"],
|
|
650
|
+
featureUsageEvent: json["feature_usage_event"] == null ? void 0 : json["feature_usage_event"],
|
|
649
651
|
featureUsagePeriod: json["feature_usage_period"] == null ? void 0 : json["feature_usage_period"],
|
|
650
652
|
featureUsageResetAt: json["feature_usage_reset_at"] == null ? void 0 : new Date(json["feature_usage_reset_at"]),
|
|
651
653
|
flag: json["flag"],
|
|
@@ -658,6 +660,28 @@ function CheckFlagResponseDataFromJSONTyped(json, ignoreDiscriminator) {
|
|
|
658
660
|
};
|
|
659
661
|
}
|
|
660
662
|
|
|
663
|
+
// src/types/api/models/EventBodyFlagCheck.ts
|
|
664
|
+
function EventBodyFlagCheckToJSON(json) {
|
|
665
|
+
return EventBodyFlagCheckToJSONTyped(json, false);
|
|
666
|
+
}
|
|
667
|
+
function EventBodyFlagCheckToJSONTyped(value, ignoreDiscriminator = false) {
|
|
668
|
+
if (value == null) {
|
|
669
|
+
return value;
|
|
670
|
+
}
|
|
671
|
+
return {
|
|
672
|
+
company_id: value["companyId"],
|
|
673
|
+
error: value["error"],
|
|
674
|
+
flag_id: value["flagId"],
|
|
675
|
+
flag_key: value["flagKey"],
|
|
676
|
+
reason: value["reason"],
|
|
677
|
+
req_company: value["reqCompany"],
|
|
678
|
+
req_user: value["reqUser"],
|
|
679
|
+
rule_id: value["ruleId"],
|
|
680
|
+
user_id: value["userId"],
|
|
681
|
+
value: value["value"]
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
|
|
661
685
|
// src/types/api/models/CheckFlagResponse.ts
|
|
662
686
|
function CheckFlagResponseFromJSON(json) {
|
|
663
687
|
return CheckFlagResponseFromJSONTyped(json, false);
|
|
@@ -723,6 +747,7 @@ var CheckFlagReturnFromJSON = (json) => {
|
|
|
723
747
|
error,
|
|
724
748
|
featureAllocation,
|
|
725
749
|
featureUsage,
|
|
750
|
+
featureUsageEvent,
|
|
726
751
|
featureUsagePeriod,
|
|
727
752
|
featureUsageResetAt,
|
|
728
753
|
flag,
|
|
@@ -742,6 +767,7 @@ var CheckFlagReturnFromJSON = (json) => {
|
|
|
742
767
|
error: error == null ? void 0 : error,
|
|
743
768
|
featureAllocation: featureAllocation == null ? void 0 : featureAllocation,
|
|
744
769
|
featureUsage: featureUsage == null ? void 0 : featureUsage,
|
|
770
|
+
featureUsageEvent: featureUsageEvent === null ? void 0 : featureUsageEvent,
|
|
745
771
|
featureUsagePeriod: featureUsagePeriod == null ? void 0 : featureUsagePeriod,
|
|
746
772
|
featureUsageResetAt: featureUsageResetAt == null ? void 0 : featureUsageResetAt,
|
|
747
773
|
flag,
|
|
@@ -771,7 +797,7 @@ function contextString(context) {
|
|
|
771
797
|
}
|
|
772
798
|
|
|
773
799
|
// src/version.ts
|
|
774
|
-
var version = "1.2.
|
|
800
|
+
var version = "1.2.3";
|
|
775
801
|
|
|
776
802
|
// src/index.ts
|
|
777
803
|
var anonymousIdKey = "schematicId";
|
|
@@ -781,6 +807,8 @@ var Schematic = class {
|
|
|
781
807
|
apiUrl = "https://api.schematichq.com";
|
|
782
808
|
conn = null;
|
|
783
809
|
context = {};
|
|
810
|
+
debugEnabled = false;
|
|
811
|
+
offlineEnabled = false;
|
|
784
812
|
eventQueue;
|
|
785
813
|
eventUrl = "https://c.schematichq.com";
|
|
786
814
|
flagCheckListeners = {};
|
|
@@ -790,11 +818,32 @@ var Schematic = class {
|
|
|
790
818
|
storage;
|
|
791
819
|
useWebSocket = false;
|
|
792
820
|
checks = {};
|
|
821
|
+
featureUsageEventMap = {};
|
|
793
822
|
webSocketUrl = "wss://api.schematichq.com";
|
|
794
823
|
constructor(apiKey, options) {
|
|
795
824
|
this.apiKey = apiKey;
|
|
796
825
|
this.eventQueue = [];
|
|
797
826
|
this.useWebSocket = options?.useWebSocket ?? false;
|
|
827
|
+
this.debugEnabled = options?.debug ?? false;
|
|
828
|
+
this.offlineEnabled = options?.offline ?? false;
|
|
829
|
+
if (typeof window !== "undefined" && typeof window.location !== "undefined") {
|
|
830
|
+
const params = new URLSearchParams(window.location.search);
|
|
831
|
+
const debugParam = params.get("schematic_debug");
|
|
832
|
+
if (debugParam !== null && (debugParam === "" || debugParam === "true" || debugParam === "1")) {
|
|
833
|
+
this.debugEnabled = true;
|
|
834
|
+
}
|
|
835
|
+
const offlineParam = params.get("schematic_offline");
|
|
836
|
+
if (offlineParam !== null && (offlineParam === "" || offlineParam === "true" || offlineParam === "1")) {
|
|
837
|
+
this.offlineEnabled = true;
|
|
838
|
+
this.debugEnabled = true;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
if (this.offlineEnabled && options?.debug !== false) {
|
|
842
|
+
this.debugEnabled = true;
|
|
843
|
+
}
|
|
844
|
+
if (this.offlineEnabled) {
|
|
845
|
+
this.setIsPending(false);
|
|
846
|
+
}
|
|
798
847
|
this.additionalHeaders = {
|
|
799
848
|
"X-Schematic-Client-Version": `schematic-js@${version}`,
|
|
800
849
|
...options?.additionalHeaders ?? {}
|
|
@@ -818,6 +867,13 @@ var Schematic = class {
|
|
|
818
867
|
this.flushEventQueue();
|
|
819
868
|
});
|
|
820
869
|
}
|
|
870
|
+
if (this.offlineEnabled) {
|
|
871
|
+
this.debug(
|
|
872
|
+
"Initialized with offline mode enabled - no network requests will be made"
|
|
873
|
+
);
|
|
874
|
+
} else if (this.debugEnabled) {
|
|
875
|
+
this.debug("Initialized with debug mode enabled");
|
|
876
|
+
}
|
|
821
877
|
}
|
|
822
878
|
/**
|
|
823
879
|
* Get value for a single flag.
|
|
@@ -830,6 +886,14 @@ var Schematic = class {
|
|
|
830
886
|
const { fallback = false, key } = options;
|
|
831
887
|
const context = options.context || this.context;
|
|
832
888
|
const contextStr = contextString(context);
|
|
889
|
+
this.debug(`checkFlag: ${key}`, { context, fallback });
|
|
890
|
+
if (this.isOffline()) {
|
|
891
|
+
this.debug(`checkFlag offline result: ${key}`, {
|
|
892
|
+
value: fallback,
|
|
893
|
+
offlineMode: true
|
|
894
|
+
});
|
|
895
|
+
return fallback;
|
|
896
|
+
}
|
|
833
897
|
if (!this.useWebSocket) {
|
|
834
898
|
const requestUrl = `${this.apiUrl}/flags/${key}/check`;
|
|
835
899
|
return fetch(requestUrl, {
|
|
@@ -846,17 +910,35 @@ var Schematic = class {
|
|
|
846
910
|
}
|
|
847
911
|
return response.json();
|
|
848
912
|
}).then((response) => {
|
|
849
|
-
|
|
913
|
+
const parsedResponse = CheckFlagResponseFromJSON(response);
|
|
914
|
+
this.debug(`checkFlag result: ${key}`, parsedResponse);
|
|
915
|
+
const result = CheckFlagReturnFromJSON(parsedResponse.data);
|
|
916
|
+
if (typeof result.featureUsageEvent === "string") {
|
|
917
|
+
this.updateFeatureUsageEventMap(result);
|
|
918
|
+
}
|
|
919
|
+
this.submitFlagCheckEvent(key, result, context);
|
|
920
|
+
return result.value;
|
|
850
921
|
}).catch((error) => {
|
|
851
922
|
console.error("There was a problem with the fetch operation:", error);
|
|
923
|
+
const errorResult = {
|
|
924
|
+
flag: key,
|
|
925
|
+
value: fallback,
|
|
926
|
+
reason: "API request failed",
|
|
927
|
+
error: error instanceof Error ? error.message : String(error)
|
|
928
|
+
};
|
|
929
|
+
this.submitFlagCheckEvent(key, errorResult, context);
|
|
852
930
|
return fallback;
|
|
853
931
|
});
|
|
854
932
|
}
|
|
855
933
|
try {
|
|
856
934
|
const existingVals = this.checks[contextStr];
|
|
857
|
-
if (this.conn && typeof existingVals !== "undefined" && typeof existingVals[key] !== "undefined") {
|
|
935
|
+
if (this.conn !== null && typeof existingVals !== "undefined" && typeof existingVals[key] !== "undefined") {
|
|
936
|
+
this.debug(`checkFlag cached result: ${key}`, existingVals[key]);
|
|
858
937
|
return existingVals[key].value;
|
|
859
938
|
}
|
|
939
|
+
if (this.isOffline()) {
|
|
940
|
+
return fallback;
|
|
941
|
+
}
|
|
860
942
|
try {
|
|
861
943
|
await this.setContext(context);
|
|
862
944
|
} catch (error) {
|
|
@@ -867,16 +949,74 @@ var Schematic = class {
|
|
|
867
949
|
return this.fallbackToRest(key, context, fallback);
|
|
868
950
|
}
|
|
869
951
|
const contextVals = this.checks[contextStr] ?? {};
|
|
870
|
-
|
|
952
|
+
const flagCheck = contextVals[key];
|
|
953
|
+
const result = flagCheck?.value ?? fallback;
|
|
954
|
+
this.debug(
|
|
955
|
+
`checkFlag WebSocket result: ${key}`,
|
|
956
|
+
typeof flagCheck !== "undefined" ? flagCheck : { value: fallback, fallbackUsed: true }
|
|
957
|
+
);
|
|
958
|
+
if (typeof flagCheck !== "undefined") {
|
|
959
|
+
this.submitFlagCheckEvent(key, flagCheck, context);
|
|
960
|
+
}
|
|
961
|
+
return result;
|
|
871
962
|
} catch (error) {
|
|
872
963
|
console.error("Unexpected error in checkFlag:", error);
|
|
964
|
+
const errorResult = {
|
|
965
|
+
flag: key,
|
|
966
|
+
value: fallback,
|
|
967
|
+
reason: "Unexpected error in flag check",
|
|
968
|
+
error: error instanceof Error ? error.message : String(error)
|
|
969
|
+
};
|
|
970
|
+
this.submitFlagCheckEvent(key, errorResult, context);
|
|
873
971
|
return fallback;
|
|
874
972
|
}
|
|
875
973
|
}
|
|
974
|
+
/**
|
|
975
|
+
* Helper function to log debug messages
|
|
976
|
+
* Only logs if debug mode is enabled
|
|
977
|
+
*/
|
|
978
|
+
debug(message, ...args) {
|
|
979
|
+
if (this.debugEnabled) {
|
|
980
|
+
console.log(`[Schematic] ${message}`, ...args);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* Helper function to check if client is in offline mode
|
|
985
|
+
*/
|
|
986
|
+
isOffline() {
|
|
987
|
+
return this.offlineEnabled;
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* Submit a flag check event
|
|
991
|
+
* Records data about a flag check for analytics
|
|
992
|
+
*/
|
|
993
|
+
submitFlagCheckEvent(flagKey, result, context) {
|
|
994
|
+
const eventBody = {
|
|
995
|
+
flagKey,
|
|
996
|
+
value: result.value,
|
|
997
|
+
reason: result.reason,
|
|
998
|
+
flagId: result.flagId,
|
|
999
|
+
ruleId: result.ruleId,
|
|
1000
|
+
companyId: result.companyId,
|
|
1001
|
+
userId: result.userId,
|
|
1002
|
+
error: result.error,
|
|
1003
|
+
reqCompany: context.company,
|
|
1004
|
+
reqUser: context.user
|
|
1005
|
+
};
|
|
1006
|
+
this.debug(`submitting flag check event:`, eventBody);
|
|
1007
|
+
return this.handleEvent("flag_check", EventBodyFlagCheckToJSON(eventBody));
|
|
1008
|
+
}
|
|
876
1009
|
/**
|
|
877
1010
|
* Helper method for falling back to REST API when WebSocket connection fails
|
|
878
1011
|
*/
|
|
879
1012
|
async fallbackToRest(key, context, fallback) {
|
|
1013
|
+
if (this.isOffline()) {
|
|
1014
|
+
this.debug(`fallbackToRest offline result: ${key}`, {
|
|
1015
|
+
value: fallback,
|
|
1016
|
+
offlineMode: true
|
|
1017
|
+
});
|
|
1018
|
+
return fallback;
|
|
1019
|
+
}
|
|
880
1020
|
try {
|
|
881
1021
|
const requestUrl = `${this.apiUrl}/flags/${key}/check`;
|
|
882
1022
|
const response = await fetch(requestUrl, {
|
|
@@ -891,19 +1031,39 @@ var Schematic = class {
|
|
|
891
1031
|
if (!response.ok) {
|
|
892
1032
|
throw new Error("Network response was not ok");
|
|
893
1033
|
}
|
|
894
|
-
const
|
|
895
|
-
|
|
1034
|
+
const responseJson = await response.json();
|
|
1035
|
+
const data = CheckFlagResponseFromJSON(responseJson);
|
|
1036
|
+
this.debug(`fallbackToRest result: ${key}`, data);
|
|
1037
|
+
const result = CheckFlagReturnFromJSON(data.data);
|
|
1038
|
+
if (typeof result.featureUsageEvent === "string") {
|
|
1039
|
+
this.updateFeatureUsageEventMap(result);
|
|
1040
|
+
}
|
|
1041
|
+
this.submitFlagCheckEvent(key, result, context);
|
|
1042
|
+
return result.value;
|
|
896
1043
|
} catch (error) {
|
|
897
1044
|
console.error("REST API call failed, using fallback value:", error);
|
|
1045
|
+
const errorResult = {
|
|
1046
|
+
flag: key,
|
|
1047
|
+
value: fallback,
|
|
1048
|
+
reason: "API request failed (fallback)",
|
|
1049
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1050
|
+
};
|
|
1051
|
+
this.submitFlagCheckEvent(key, errorResult, context);
|
|
898
1052
|
return fallback;
|
|
899
1053
|
}
|
|
900
1054
|
}
|
|
901
1055
|
/**
|
|
902
1056
|
* Make an API call to fetch all flag values for a given context.
|
|
903
1057
|
* Recommended for use in REST mode only.
|
|
1058
|
+
* In offline mode, returns an empty object.
|
|
904
1059
|
*/
|
|
905
1060
|
checkFlags = async (context) => {
|
|
906
1061
|
context = context || this.context;
|
|
1062
|
+
this.debug(`checkFlags`, { context });
|
|
1063
|
+
if (this.isOffline()) {
|
|
1064
|
+
this.debug(`checkFlags offline result: returning empty object`);
|
|
1065
|
+
return {};
|
|
1066
|
+
}
|
|
907
1067
|
const requestUrl = `${this.apiUrl}/flags/check`;
|
|
908
1068
|
const requestBody = JSON.stringify(context);
|
|
909
1069
|
return fetch(requestUrl, {
|
|
@@ -921,6 +1081,7 @@ var Schematic = class {
|
|
|
921
1081
|
return response.json();
|
|
922
1082
|
}).then((responseJson) => {
|
|
923
1083
|
const resp = CheckFlagsResponseFromJSON(responseJson);
|
|
1084
|
+
this.debug(`checkFlags result:`, resp);
|
|
924
1085
|
return (resp?.data?.flags ?? []).reduce(
|
|
925
1086
|
(accum, flag) => {
|
|
926
1087
|
accum[flag.flag] = flag.value;
|
|
@@ -939,6 +1100,7 @@ var Schematic = class {
|
|
|
939
1100
|
* send an identify event to the Schematic API which will upsert a user and company.
|
|
940
1101
|
*/
|
|
941
1102
|
identify = (body) => {
|
|
1103
|
+
this.debug(`identify:`, body);
|
|
942
1104
|
try {
|
|
943
1105
|
this.setContext({
|
|
944
1106
|
company: body.company?.keys,
|
|
@@ -956,10 +1118,12 @@ var Schematic = class {
|
|
|
956
1118
|
* 2. Send the context to the server
|
|
957
1119
|
* 3. Wait for initial flag values to be returned
|
|
958
1120
|
* The promise resolves when initial flag values are received.
|
|
1121
|
+
* In offline mode, this will just set the context locally without connecting.
|
|
959
1122
|
*/
|
|
960
1123
|
setContext = async (context) => {
|
|
961
|
-
if (!this.useWebSocket) {
|
|
1124
|
+
if (this.isOffline() || !this.useWebSocket) {
|
|
962
1125
|
this.context = context;
|
|
1126
|
+
this.setIsPending(false);
|
|
963
1127
|
return Promise.resolve();
|
|
964
1128
|
}
|
|
965
1129
|
try {
|
|
@@ -977,14 +1141,67 @@ var Schematic = class {
|
|
|
977
1141
|
/**
|
|
978
1142
|
* Send a track event
|
|
979
1143
|
* Track usage for a company and/or user.
|
|
1144
|
+
* Optimistically updates feature usage flags if tracking a featureUsageEvent.
|
|
980
1145
|
*/
|
|
981
1146
|
track = (body) => {
|
|
982
|
-
const { company, user, event, traits } = body;
|
|
983
|
-
|
|
1147
|
+
const { company, user, event, traits, quantity = 1 } = body;
|
|
1148
|
+
const trackData = {
|
|
984
1149
|
company: company ?? this.context.company,
|
|
985
1150
|
event,
|
|
986
1151
|
traits: traits ?? {},
|
|
987
|
-
user: user ?? this.context.user
|
|
1152
|
+
user: user ?? this.context.user,
|
|
1153
|
+
quantity
|
|
1154
|
+
};
|
|
1155
|
+
this.debug(`track:`, trackData);
|
|
1156
|
+
if (event in this.featureUsageEventMap) {
|
|
1157
|
+
this.optimisticallyUpdateFeatureUsage(event, quantity);
|
|
1158
|
+
}
|
|
1159
|
+
return this.handleEvent("track", trackData);
|
|
1160
|
+
};
|
|
1161
|
+
/**
|
|
1162
|
+
* Optimistically update feature usage flags associated with a tracked event
|
|
1163
|
+
* This updates flags in memory with updated usage counts and value/featureUsageExceeded flags
|
|
1164
|
+
* before the network request completes
|
|
1165
|
+
*/
|
|
1166
|
+
optimisticallyUpdateFeatureUsage = (eventName, quantity = 1) => {
|
|
1167
|
+
const flagsForEvent = this.featureUsageEventMap[eventName];
|
|
1168
|
+
if (flagsForEvent === void 0 || flagsForEvent === null) return;
|
|
1169
|
+
this.debug(
|
|
1170
|
+
`Optimistically updating feature usage for event: ${eventName}`,
|
|
1171
|
+
{ quantity }
|
|
1172
|
+
);
|
|
1173
|
+
Object.entries(flagsForEvent).forEach(([flagKey, check]) => {
|
|
1174
|
+
if (check === void 0) return;
|
|
1175
|
+
const updatedCheck = { ...check };
|
|
1176
|
+
if (typeof updatedCheck.featureUsage === "number") {
|
|
1177
|
+
updatedCheck.featureUsage += quantity;
|
|
1178
|
+
if (typeof updatedCheck.featureAllocation === "number") {
|
|
1179
|
+
const wasExceeded = updatedCheck.featureUsageExceeded === true;
|
|
1180
|
+
const nowExceeded = updatedCheck.featureUsage >= updatedCheck.featureAllocation;
|
|
1181
|
+
if (nowExceeded !== wasExceeded) {
|
|
1182
|
+
updatedCheck.featureUsageExceeded = nowExceeded;
|
|
1183
|
+
if (nowExceeded) {
|
|
1184
|
+
updatedCheck.value = false;
|
|
1185
|
+
}
|
|
1186
|
+
this.debug(`Usage limit status changed for flag: ${flagKey}`, {
|
|
1187
|
+
was: wasExceeded ? "exceeded" : "within limits",
|
|
1188
|
+
now: nowExceeded ? "exceeded" : "within limits",
|
|
1189
|
+
featureUsage: updatedCheck.featureUsage,
|
|
1190
|
+
featureAllocation: updatedCheck.featureAllocation,
|
|
1191
|
+
value: updatedCheck.value
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
if (this.featureUsageEventMap[eventName] !== void 0) {
|
|
1196
|
+
this.featureUsageEventMap[eventName][flagKey] = updatedCheck;
|
|
1197
|
+
}
|
|
1198
|
+
const contextStr = contextString(this.context);
|
|
1199
|
+
if (this.checks[contextStr] !== void 0 && this.checks[contextStr] !== null) {
|
|
1200
|
+
this.checks[contextStr][flagKey] = updatedCheck;
|
|
1201
|
+
}
|
|
1202
|
+
this.notifyFlagCheckListeners(flagKey, updatedCheck);
|
|
1203
|
+
this.notifyFlagValueListeners(flagKey, updatedCheck.value);
|
|
1204
|
+
}
|
|
988
1205
|
});
|
|
989
1206
|
};
|
|
990
1207
|
/**
|
|
@@ -1028,8 +1245,13 @@ var Schematic = class {
|
|
|
1028
1245
|
sendEvent = async (event) => {
|
|
1029
1246
|
const captureUrl = `${this.eventUrl}/e`;
|
|
1030
1247
|
const payload = JSON.stringify(event);
|
|
1248
|
+
this.debug(`sending event:`, { url: captureUrl, event });
|
|
1249
|
+
if (this.isOffline()) {
|
|
1250
|
+
this.debug(`event not sent (offline mode):`, { event });
|
|
1251
|
+
return Promise.resolve();
|
|
1252
|
+
}
|
|
1031
1253
|
try {
|
|
1032
|
-
await fetch(captureUrl, {
|
|
1254
|
+
const response = await fetch(captureUrl, {
|
|
1033
1255
|
method: "POST",
|
|
1034
1256
|
headers: {
|
|
1035
1257
|
...this.additionalHeaders ?? {},
|
|
@@ -1037,6 +1259,10 @@ var Schematic = class {
|
|
|
1037
1259
|
},
|
|
1038
1260
|
body: payload
|
|
1039
1261
|
});
|
|
1262
|
+
this.debug(`event sent:`, {
|
|
1263
|
+
status: response.status,
|
|
1264
|
+
statusText: response.statusText
|
|
1265
|
+
});
|
|
1040
1266
|
} catch (error) {
|
|
1041
1267
|
console.error("Error sending Schematic event: ", error);
|
|
1042
1268
|
}
|
|
@@ -1051,8 +1277,13 @@ var Schematic = class {
|
|
|
1051
1277
|
*/
|
|
1052
1278
|
/**
|
|
1053
1279
|
* If using websocket mode, close the connection when done.
|
|
1280
|
+
* In offline mode, this is a no-op.
|
|
1054
1281
|
*/
|
|
1055
1282
|
cleanup = async () => {
|
|
1283
|
+
if (this.isOffline()) {
|
|
1284
|
+
this.debug("cleanup: skipped (offline mode)");
|
|
1285
|
+
return Promise.resolve();
|
|
1286
|
+
}
|
|
1056
1287
|
if (this.conn) {
|
|
1057
1288
|
try {
|
|
1058
1289
|
const socket = await this.conn;
|
|
@@ -1066,16 +1297,26 @@ var Schematic = class {
|
|
|
1066
1297
|
};
|
|
1067
1298
|
// Open a websocket connection
|
|
1068
1299
|
wsConnect = () => {
|
|
1300
|
+
if (this.isOffline()) {
|
|
1301
|
+
this.debug("wsConnect: skipped (offline mode)");
|
|
1302
|
+
return Promise.reject(
|
|
1303
|
+
new Error("WebSocket connection skipped in offline mode")
|
|
1304
|
+
);
|
|
1305
|
+
}
|
|
1069
1306
|
return new Promise((resolve, reject) => {
|
|
1070
1307
|
const wsUrl = `${this.webSocketUrl}/flags/bootstrap?apiKey=${this.apiKey}`;
|
|
1308
|
+
this.debug(`connecting to WebSocket:`, wsUrl);
|
|
1071
1309
|
const webSocket = new WebSocket(wsUrl);
|
|
1072
1310
|
webSocket.onopen = () => {
|
|
1311
|
+
this.debug(`WebSocket connection opened`);
|
|
1073
1312
|
resolve(webSocket);
|
|
1074
1313
|
};
|
|
1075
1314
|
webSocket.onerror = (error) => {
|
|
1315
|
+
this.debug(`WebSocket connection error:`, error);
|
|
1076
1316
|
reject(error);
|
|
1077
1317
|
};
|
|
1078
1318
|
webSocket.onclose = () => {
|
|
1319
|
+
this.debug(`WebSocket connection closed`);
|
|
1079
1320
|
this.conn = null;
|
|
1080
1321
|
};
|
|
1081
1322
|
});
|
|
@@ -1083,21 +1324,44 @@ var Schematic = class {
|
|
|
1083
1324
|
// Send a message on the websocket indicating interest in a particular evaluation context
|
|
1084
1325
|
// and wait for the initial set of flag values to be returned
|
|
1085
1326
|
wsSendMessage = (socket, context) => {
|
|
1327
|
+
if (this.isOffline()) {
|
|
1328
|
+
this.debug("wsSendMessage: skipped (offline mode)");
|
|
1329
|
+
this.setIsPending(false);
|
|
1330
|
+
return Promise.resolve();
|
|
1331
|
+
}
|
|
1086
1332
|
return new Promise((resolve, reject) => {
|
|
1087
1333
|
if (contextString(context) == contextString(this.context)) {
|
|
1334
|
+
this.debug(`WebSocket context unchanged, skipping update`);
|
|
1088
1335
|
return resolve(this.setIsPending(false));
|
|
1089
1336
|
}
|
|
1337
|
+
this.debug(`WebSocket context updated:`, context);
|
|
1090
1338
|
this.context = context;
|
|
1091
1339
|
const sendMessage = () => {
|
|
1092
1340
|
let resolved = false;
|
|
1093
1341
|
const messageHandler = (event) => {
|
|
1094
1342
|
const message = JSON.parse(event.data);
|
|
1343
|
+
this.debug(`WebSocket message received:`, message);
|
|
1095
1344
|
if (!(contextString(context) in this.checks)) {
|
|
1096
1345
|
this.checks[contextString(context)] = {};
|
|
1097
1346
|
}
|
|
1098
1347
|
(message.flags ?? []).forEach((flag) => {
|
|
1099
1348
|
const flagCheck = CheckFlagReturnFromJSON(flag);
|
|
1100
|
-
|
|
1349
|
+
const contextStr = contextString(context);
|
|
1350
|
+
if (this.checks[contextStr] === void 0) {
|
|
1351
|
+
this.checks[contextStr] = {};
|
|
1352
|
+
}
|
|
1353
|
+
this.checks[contextStr][flagCheck.flag] = flagCheck;
|
|
1354
|
+
this.debug(`WebSocket flag update:`, {
|
|
1355
|
+
flag: flagCheck.flag,
|
|
1356
|
+
value: flagCheck.value,
|
|
1357
|
+
flagCheck
|
|
1358
|
+
});
|
|
1359
|
+
if (typeof flagCheck.featureUsageEvent === "string") {
|
|
1360
|
+
this.updateFeatureUsageEventMap(flagCheck);
|
|
1361
|
+
}
|
|
1362
|
+
if (this.flagCheckListeners[flag.flag]?.size > 0 || this.flagValueListeners[flag.flag]?.size > 0) {
|
|
1363
|
+
this.submitFlagCheckEvent(flagCheck.flag, flagCheck, context);
|
|
1364
|
+
}
|
|
1101
1365
|
this.notifyFlagCheckListeners(flag.flag, flagCheck);
|
|
1102
1366
|
this.notifyFlagValueListeners(flag.flag, flagCheck.value);
|
|
1103
1367
|
});
|
|
@@ -1108,19 +1372,23 @@ var Schematic = class {
|
|
|
1108
1372
|
}
|
|
1109
1373
|
};
|
|
1110
1374
|
socket.addEventListener("message", messageHandler);
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
);
|
|
1375
|
+
const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
|
|
1376
|
+
const messagePayload = {
|
|
1377
|
+
apiKey: this.apiKey,
|
|
1378
|
+
clientVersion,
|
|
1379
|
+
data: context
|
|
1380
|
+
};
|
|
1381
|
+
this.debug(`WebSocket sending message:`, messagePayload);
|
|
1382
|
+
socket.send(JSON.stringify(messagePayload));
|
|
1118
1383
|
};
|
|
1119
1384
|
if (socket.readyState === WebSocket.OPEN) {
|
|
1385
|
+
this.debug(`WebSocket already open, sending message`);
|
|
1120
1386
|
sendMessage();
|
|
1121
1387
|
} else if (socket.readyState === WebSocket.CONNECTING) {
|
|
1388
|
+
this.debug(`WebSocket connecting, waiting for open to send message`);
|
|
1122
1389
|
socket.addEventListener("open", sendMessage);
|
|
1123
1390
|
} else {
|
|
1391
|
+
this.debug(`WebSocket is closed, cannot send message`);
|
|
1124
1392
|
reject("WebSocket is not open or connecting");
|
|
1125
1393
|
}
|
|
1126
1394
|
});
|
|
@@ -1177,10 +1445,40 @@ var Schematic = class {
|
|
|
1177
1445
|
};
|
|
1178
1446
|
notifyFlagCheckListeners = (flagKey, check) => {
|
|
1179
1447
|
const listeners = this.flagCheckListeners?.[flagKey] ?? [];
|
|
1448
|
+
if (listeners.size > 0) {
|
|
1449
|
+
this.debug(
|
|
1450
|
+
`Notifying ${listeners.size} flag check listeners for ${flagKey}`,
|
|
1451
|
+
check
|
|
1452
|
+
);
|
|
1453
|
+
}
|
|
1454
|
+
if (typeof check.featureUsageEvent === "string") {
|
|
1455
|
+
this.updateFeatureUsageEventMap(check);
|
|
1456
|
+
}
|
|
1180
1457
|
listeners.forEach((listener) => notifyFlagCheckListener(listener, check));
|
|
1181
1458
|
};
|
|
1459
|
+
/** Add or update a CheckFlagReturn in the featureUsageEventMap */
|
|
1460
|
+
updateFeatureUsageEventMap = (check) => {
|
|
1461
|
+
if (typeof check.featureUsageEvent !== "string") return;
|
|
1462
|
+
const eventName = check.featureUsageEvent;
|
|
1463
|
+
if (this.featureUsageEventMap[eventName] === void 0 || this.featureUsageEventMap[eventName] === null) {
|
|
1464
|
+
this.featureUsageEventMap[eventName] = {};
|
|
1465
|
+
}
|
|
1466
|
+
if (this.featureUsageEventMap[eventName] !== void 0) {
|
|
1467
|
+
this.featureUsageEventMap[eventName][check.flag] = check;
|
|
1468
|
+
}
|
|
1469
|
+
this.debug(
|
|
1470
|
+
`Updated featureUsageEventMap for event: ${eventName}, flag: ${check.flag}`,
|
|
1471
|
+
check
|
|
1472
|
+
);
|
|
1473
|
+
};
|
|
1182
1474
|
notifyFlagValueListeners = (flagKey, value) => {
|
|
1183
1475
|
const listeners = this.flagValueListeners?.[flagKey] ?? [];
|
|
1476
|
+
if (listeners.size > 0) {
|
|
1477
|
+
this.debug(
|
|
1478
|
+
`Notifying ${listeners.size} flag value listeners for ${flagKey}`,
|
|
1479
|
+
{ value }
|
|
1480
|
+
);
|
|
1481
|
+
}
|
|
1184
1482
|
listeners.forEach((listener) => notifyFlagValueListener(listener, value));
|
|
1185
1483
|
};
|
|
1186
1484
|
};
|