@schematichq/schematic-react 1.2.8 → 1.2.10
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/dist/schematic-react.cjs.js +285 -147
- package/dist/schematic-react.esm.js +285 -147
- package/package.json +2 -2
|
@@ -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.10";
|
|
803
803
|
var anonymousIdKey = "schematicId";
|
|
804
804
|
var Schematic = class {
|
|
805
805
|
additionalHeaders = {};
|
|
@@ -829,6 +829,8 @@ var Schematic = class {
|
|
|
829
829
|
wsReconnectAttempts = 0;
|
|
830
830
|
wsReconnectTimer = null;
|
|
831
831
|
wsIntentionalDisconnect = false;
|
|
832
|
+
currentWebSocket = null;
|
|
833
|
+
isConnecting = false;
|
|
832
834
|
maxEventQueueSize = 100;
|
|
833
835
|
// Prevent memory issues with very long network outages
|
|
834
836
|
maxEventRetries = 5;
|
|
@@ -838,6 +840,8 @@ var Schematic = class {
|
|
|
838
840
|
eventRetryMaxDelay = 3e4;
|
|
839
841
|
// Maximum retry delay in ms
|
|
840
842
|
retryTimer = null;
|
|
843
|
+
flagValueDefaults = {};
|
|
844
|
+
flagCheckDefaults = {};
|
|
841
845
|
constructor(apiKey, options) {
|
|
842
846
|
this.apiKey = apiKey;
|
|
843
847
|
this.eventQueue = [];
|
|
@@ -908,6 +912,12 @@ var Schematic = class {
|
|
|
908
912
|
if (options?.eventRetryMaxDelay !== void 0) {
|
|
909
913
|
this.eventRetryMaxDelay = options.eventRetryMaxDelay;
|
|
910
914
|
}
|
|
915
|
+
if (options?.flagValueDefaults !== void 0) {
|
|
916
|
+
this.flagValueDefaults = options.flagValueDefaults;
|
|
917
|
+
}
|
|
918
|
+
if (options?.flagCheckDefaults !== void 0) {
|
|
919
|
+
this.flagCheckDefaults = options.flagCheckDefaults;
|
|
920
|
+
}
|
|
911
921
|
if (typeof window !== "undefined" && window?.addEventListener) {
|
|
912
922
|
window.addEventListener("beforeunload", () => {
|
|
913
923
|
this.flushEventQueue();
|
|
@@ -932,6 +942,62 @@ var Schematic = class {
|
|
|
932
942
|
this.debug("Initialized with debug mode enabled");
|
|
933
943
|
}
|
|
934
944
|
}
|
|
945
|
+
/**
|
|
946
|
+
* Resolve fallback value according to priority order:
|
|
947
|
+
* 1. Callsite fallback value (if provided)
|
|
948
|
+
* 2. Initialization fallback value (flagValueDefaults)
|
|
949
|
+
* 3. Default to false
|
|
950
|
+
*/
|
|
951
|
+
resolveFallbackValue(key, callsiteFallback) {
|
|
952
|
+
if (callsiteFallback !== void 0) {
|
|
953
|
+
return callsiteFallback;
|
|
954
|
+
}
|
|
955
|
+
if (key in this.flagValueDefaults) {
|
|
956
|
+
return this.flagValueDefaults[key];
|
|
957
|
+
}
|
|
958
|
+
return false;
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Resolve complete CheckFlagReturn object according to priority order:
|
|
962
|
+
* 1. Use callsite fallback for boolean value, construct CheckFlagReturn
|
|
963
|
+
* 2. Use flagCheckDefaults if available for this flag
|
|
964
|
+
* 3. Use flagValueDefaults if available for this flag, construct CheckFlagReturn
|
|
965
|
+
* 4. Default CheckFlagReturn with value: false
|
|
966
|
+
*/
|
|
967
|
+
resolveFallbackCheckFlagReturn(key, callsiteFallback, reason = "Fallback value used", error) {
|
|
968
|
+
if (callsiteFallback !== void 0) {
|
|
969
|
+
return {
|
|
970
|
+
flag: key,
|
|
971
|
+
value: callsiteFallback,
|
|
972
|
+
reason,
|
|
973
|
+
error
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
if (key in this.flagCheckDefaults) {
|
|
977
|
+
const defaultReturn = this.flagCheckDefaults[key];
|
|
978
|
+
return {
|
|
979
|
+
...defaultReturn,
|
|
980
|
+
flag: key,
|
|
981
|
+
// Ensure flag matches the requested key
|
|
982
|
+
reason: error !== void 0 ? reason : defaultReturn.reason,
|
|
983
|
+
error
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
if (key in this.flagValueDefaults) {
|
|
987
|
+
return {
|
|
988
|
+
flag: key,
|
|
989
|
+
value: this.flagValueDefaults[key],
|
|
990
|
+
reason,
|
|
991
|
+
error
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
return {
|
|
995
|
+
flag: key,
|
|
996
|
+
value: false,
|
|
997
|
+
reason,
|
|
998
|
+
error
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
935
1001
|
/**
|
|
936
1002
|
* Get value for a single flag.
|
|
937
1003
|
* In WebSocket mode, returns cached values if connection is active, otherwise establishes
|
|
@@ -940,16 +1006,21 @@ var Schematic = class {
|
|
|
940
1006
|
* In REST mode, makes an API call for each check.
|
|
941
1007
|
*/
|
|
942
1008
|
async checkFlag(options) {
|
|
943
|
-
const { fallback
|
|
1009
|
+
const { fallback, key } = options;
|
|
944
1010
|
const context = options.context || this.context;
|
|
945
1011
|
const contextStr = contextString(context);
|
|
946
1012
|
this.debug(`checkFlag: ${key}`, { context, fallback });
|
|
947
1013
|
if (this.isOffline()) {
|
|
1014
|
+
const resolvedFallbackResult = this.resolveFallbackCheckFlagReturn(
|
|
1015
|
+
key,
|
|
1016
|
+
fallback,
|
|
1017
|
+
"Offline mode - using initialization defaults"
|
|
1018
|
+
);
|
|
948
1019
|
this.debug(`checkFlag offline result: ${key}`, {
|
|
949
|
-
value:
|
|
1020
|
+
value: resolvedFallbackResult.value,
|
|
950
1021
|
offlineMode: true
|
|
951
1022
|
});
|
|
952
|
-
return
|
|
1023
|
+
return resolvedFallbackResult.value;
|
|
953
1024
|
}
|
|
954
1025
|
if (!this.useWebSocket) {
|
|
955
1026
|
const requestUrl = `${this.apiUrl}/flags/${key}/check`;
|
|
@@ -977,14 +1048,14 @@ var Schematic = class {
|
|
|
977
1048
|
return result.value;
|
|
978
1049
|
}).catch((error) => {
|
|
979
1050
|
console.error("There was a problem with the fetch operation:", error);
|
|
980
|
-
const errorResult =
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
error
|
|
985
|
-
|
|
1051
|
+
const errorResult = this.resolveFallbackCheckFlagReturn(
|
|
1052
|
+
key,
|
|
1053
|
+
fallback,
|
|
1054
|
+
"API request failed",
|
|
1055
|
+
error instanceof Error ? error.message : String(error)
|
|
1056
|
+
);
|
|
986
1057
|
this.submitFlagCheckEvent(key, errorResult, context);
|
|
987
|
-
return
|
|
1058
|
+
return errorResult.value;
|
|
988
1059
|
});
|
|
989
1060
|
}
|
|
990
1061
|
try {
|
|
@@ -994,7 +1065,7 @@ var Schematic = class {
|
|
|
994
1065
|
return existingVals[key].value;
|
|
995
1066
|
}
|
|
996
1067
|
if (this.isOffline()) {
|
|
997
|
-
return fallback;
|
|
1068
|
+
return this.resolveFallbackValue(key, fallback);
|
|
998
1069
|
}
|
|
999
1070
|
try {
|
|
1000
1071
|
await this.setContext(context);
|
|
@@ -1007,10 +1078,10 @@ var Schematic = class {
|
|
|
1007
1078
|
}
|
|
1008
1079
|
const contextVals = this.checks[contextStr] ?? {};
|
|
1009
1080
|
const flagCheck = contextVals[key];
|
|
1010
|
-
const result = flagCheck?.value ?? fallback;
|
|
1081
|
+
const result = flagCheck?.value ?? this.resolveFallbackValue(key, fallback);
|
|
1011
1082
|
this.debug(
|
|
1012
1083
|
`checkFlag WebSocket result: ${key}`,
|
|
1013
|
-
typeof flagCheck !== "undefined" ? flagCheck : { value:
|
|
1084
|
+
typeof flagCheck !== "undefined" ? flagCheck : { value: result, fallbackUsed: true }
|
|
1014
1085
|
);
|
|
1015
1086
|
if (typeof flagCheck !== "undefined") {
|
|
1016
1087
|
this.submitFlagCheckEvent(key, flagCheck, context);
|
|
@@ -1018,14 +1089,14 @@ var Schematic = class {
|
|
|
1018
1089
|
return result;
|
|
1019
1090
|
} catch (error) {
|
|
1020
1091
|
console.error("Unexpected error in checkFlag:", error);
|
|
1021
|
-
const errorResult =
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
error
|
|
1026
|
-
|
|
1092
|
+
const errorResult = this.resolveFallbackCheckFlagReturn(
|
|
1093
|
+
key,
|
|
1094
|
+
fallback,
|
|
1095
|
+
"Unexpected error in flag check",
|
|
1096
|
+
error instanceof Error ? error.message : String(error)
|
|
1097
|
+
);
|
|
1027
1098
|
this.submitFlagCheckEvent(key, errorResult, context);
|
|
1028
|
-
return
|
|
1099
|
+
return errorResult.value;
|
|
1029
1100
|
}
|
|
1030
1101
|
}
|
|
1031
1102
|
/**
|
|
@@ -1037,6 +1108,49 @@ var Schematic = class {
|
|
|
1037
1108
|
console.log(`[Schematic] ${message}`, ...args);
|
|
1038
1109
|
}
|
|
1039
1110
|
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Create a persistent message handler for websocket flag updates
|
|
1113
|
+
*/
|
|
1114
|
+
createPersistentMessageHandler(context) {
|
|
1115
|
+
return (event) => {
|
|
1116
|
+
const message = JSON.parse(event.data);
|
|
1117
|
+
this.debug(`WebSocket persistent message received:`, message);
|
|
1118
|
+
if (!(contextString(context) in this.checks)) {
|
|
1119
|
+
this.checks[contextString(context)] = {};
|
|
1120
|
+
}
|
|
1121
|
+
(message.flags ?? []).forEach((flag) => {
|
|
1122
|
+
const flagCheck = CheckFlagReturnFromJSON(flag);
|
|
1123
|
+
const contextStr = contextString(context);
|
|
1124
|
+
if (this.checks[contextStr] === void 0) {
|
|
1125
|
+
this.checks[contextStr] = {};
|
|
1126
|
+
}
|
|
1127
|
+
this.checks[contextStr][flagCheck.flag] = flagCheck;
|
|
1128
|
+
this.debug(`WebSocket flag update:`, {
|
|
1129
|
+
flag: flagCheck.flag,
|
|
1130
|
+
value: flagCheck.value,
|
|
1131
|
+
flagCheck
|
|
1132
|
+
});
|
|
1133
|
+
if (typeof flagCheck.featureUsageEvent === "string") {
|
|
1134
|
+
this.updateFeatureUsageEventMap(flagCheck);
|
|
1135
|
+
}
|
|
1136
|
+
if ((this.flagCheckListeners[flag.flag]?.size ?? 0) > 0 || (this.flagValueListeners[flag.flag]?.size ?? 0) > 0) {
|
|
1137
|
+
this.submitFlagCheckEvent(flagCheck.flag, flagCheck, context);
|
|
1138
|
+
}
|
|
1139
|
+
this.debug(`About to notify listeners for flag ${flag.flag}`, {
|
|
1140
|
+
flag: flag.flag,
|
|
1141
|
+
value: flagCheck.value
|
|
1142
|
+
});
|
|
1143
|
+
this.notifyFlagCheckListeners(flag.flag, flagCheck);
|
|
1144
|
+
this.notifyFlagValueListeners(flag.flag, flagCheck.value);
|
|
1145
|
+
this.debug(`Finished notifying listeners for flag ${flag.flag}`, {
|
|
1146
|
+
flag: flag.flag,
|
|
1147
|
+
value: flagCheck.value
|
|
1148
|
+
});
|
|
1149
|
+
});
|
|
1150
|
+
this.flushContextDependentEventQueue();
|
|
1151
|
+
this.setIsPending(false);
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1040
1154
|
/**
|
|
1041
1155
|
* Helper function to check if client is in offline mode
|
|
1042
1156
|
*/
|
|
@@ -1068,11 +1182,12 @@ var Schematic = class {
|
|
|
1068
1182
|
*/
|
|
1069
1183
|
async fallbackToRest(key, context, fallback) {
|
|
1070
1184
|
if (this.isOffline()) {
|
|
1185
|
+
const resolvedFallback = this.resolveFallbackValue(key, fallback);
|
|
1071
1186
|
this.debug(`fallbackToRest offline result: ${key}`, {
|
|
1072
|
-
value:
|
|
1187
|
+
value: resolvedFallback,
|
|
1073
1188
|
offlineMode: true
|
|
1074
1189
|
});
|
|
1075
|
-
return
|
|
1190
|
+
return resolvedFallback;
|
|
1076
1191
|
}
|
|
1077
1192
|
try {
|
|
1078
1193
|
const requestUrl = `${this.apiUrl}/flags/${key}/check`;
|
|
@@ -1099,14 +1214,14 @@ var Schematic = class {
|
|
|
1099
1214
|
return result.value;
|
|
1100
1215
|
} catch (error) {
|
|
1101
1216
|
console.error("REST API call failed, using fallback value:", error);
|
|
1102
|
-
const errorResult =
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
error
|
|
1107
|
-
|
|
1217
|
+
const errorResult = this.resolveFallbackCheckFlagReturn(
|
|
1218
|
+
key,
|
|
1219
|
+
fallback,
|
|
1220
|
+
"API request failed (fallback)",
|
|
1221
|
+
error instanceof Error ? error.message : String(error)
|
|
1222
|
+
);
|
|
1108
1223
|
this.submitFlagCheckEvent(key, errorResult, context);
|
|
1109
|
-
return
|
|
1224
|
+
return errorResult.value;
|
|
1110
1225
|
}
|
|
1111
1226
|
}
|
|
1112
1227
|
/**
|
|
@@ -1187,6 +1302,19 @@ var Schematic = class {
|
|
|
1187
1302
|
try {
|
|
1188
1303
|
this.setIsPending(true);
|
|
1189
1304
|
if (!this.conn) {
|
|
1305
|
+
if (this.isConnecting) {
|
|
1306
|
+
this.debug(
|
|
1307
|
+
`Connection already in progress, waiting for it to complete`
|
|
1308
|
+
);
|
|
1309
|
+
while (this.isConnecting && this.conn === null) {
|
|
1310
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1311
|
+
}
|
|
1312
|
+
if (this.conn !== null) {
|
|
1313
|
+
const socket2 = await this.conn;
|
|
1314
|
+
await this.wsSendMessage(socket2, context);
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1190
1318
|
if (this.wsReconnectTimer !== null) {
|
|
1191
1319
|
this.debug(
|
|
1192
1320
|
`Cancelling scheduled reconnection, connecting immediately`
|
|
@@ -1194,7 +1322,17 @@ var Schematic = class {
|
|
|
1194
1322
|
clearTimeout(this.wsReconnectTimer);
|
|
1195
1323
|
this.wsReconnectTimer = null;
|
|
1196
1324
|
}
|
|
1197
|
-
this.
|
|
1325
|
+
this.isConnecting = true;
|
|
1326
|
+
try {
|
|
1327
|
+
this.conn = this.wsConnect();
|
|
1328
|
+
const socket2 = await this.conn;
|
|
1329
|
+
this.isConnecting = false;
|
|
1330
|
+
await this.wsSendMessage(socket2, context);
|
|
1331
|
+
return;
|
|
1332
|
+
} catch (error) {
|
|
1333
|
+
this.isConnecting = false;
|
|
1334
|
+
throw error;
|
|
1335
|
+
}
|
|
1198
1336
|
}
|
|
1199
1337
|
const socket = await this.conn;
|
|
1200
1338
|
await this.wsSendMessage(socket, context);
|
|
@@ -1359,10 +1497,14 @@ var Schematic = class {
|
|
|
1359
1497
|
}
|
|
1360
1498
|
}
|
|
1361
1499
|
if (readyEvents.length === 0) {
|
|
1362
|
-
this.debug(
|
|
1500
|
+
this.debug(
|
|
1501
|
+
`No events ready for retry yet (${notReadyEvents.length} still in backoff)`
|
|
1502
|
+
);
|
|
1363
1503
|
return;
|
|
1364
1504
|
}
|
|
1365
|
-
this.debug(
|
|
1505
|
+
this.debug(
|
|
1506
|
+
`Flushing event queue: ${readyEvents.length} ready, ${notReadyEvents.length} waiting`
|
|
1507
|
+
);
|
|
1366
1508
|
this.eventQueue = notReadyEvents;
|
|
1367
1509
|
for (const event of readyEvents) {
|
|
1368
1510
|
try {
|
|
@@ -1427,7 +1569,10 @@ var Schematic = class {
|
|
|
1427
1569
|
} catch (error) {
|
|
1428
1570
|
const retryCount = (event.retry_count ?? 0) + 1;
|
|
1429
1571
|
if (retryCount <= this.maxEventRetries) {
|
|
1430
|
-
this.debug(
|
|
1572
|
+
this.debug(
|
|
1573
|
+
`Event failed to send (attempt ${retryCount}/${this.maxEventRetries}), queueing for retry:`,
|
|
1574
|
+
error
|
|
1575
|
+
);
|
|
1431
1576
|
const baseDelay = this.eventRetryInitialDelay * Math.pow(2, retryCount - 1);
|
|
1432
1577
|
const jitterDelay = Math.min(baseDelay, this.eventRetryMaxDelay);
|
|
1433
1578
|
const nextRetryAt = Date.now() + jitterDelay;
|
|
@@ -1438,15 +1583,22 @@ var Schematic = class {
|
|
|
1438
1583
|
};
|
|
1439
1584
|
if (this.eventQueue.length < this.maxEventQueueSize) {
|
|
1440
1585
|
this.eventQueue.push(retryEvent);
|
|
1441
|
-
this.debug(
|
|
1586
|
+
this.debug(
|
|
1587
|
+
`Event queued for retry in ${jitterDelay}ms (${this.eventQueue.length}/${this.maxEventQueueSize})`
|
|
1588
|
+
);
|
|
1442
1589
|
} else {
|
|
1443
|
-
this.debug(
|
|
1590
|
+
this.debug(
|
|
1591
|
+
`Event queue full (${this.maxEventQueueSize}), dropping oldest event`
|
|
1592
|
+
);
|
|
1444
1593
|
this.eventQueue.shift();
|
|
1445
1594
|
this.eventQueue.push(retryEvent);
|
|
1446
1595
|
}
|
|
1447
1596
|
this.startRetryTimer();
|
|
1448
1597
|
} else {
|
|
1449
|
-
this.debug(
|
|
1598
|
+
this.debug(
|
|
1599
|
+
`Event failed permanently after ${this.maxEventRetries} attempts, dropping:`,
|
|
1600
|
+
error
|
|
1601
|
+
);
|
|
1450
1602
|
}
|
|
1451
1603
|
}
|
|
1452
1604
|
return Promise.resolve();
|
|
@@ -1476,11 +1628,17 @@ var Schematic = class {
|
|
|
1476
1628
|
if (this.conn) {
|
|
1477
1629
|
try {
|
|
1478
1630
|
const socket = await this.conn;
|
|
1631
|
+
if (this.currentWebSocket === socket) {
|
|
1632
|
+
this.debug(`Cleaning up current websocket tracking`);
|
|
1633
|
+
this.currentWebSocket = null;
|
|
1634
|
+
}
|
|
1479
1635
|
socket.close();
|
|
1480
1636
|
} catch (error) {
|
|
1481
1637
|
console.error("Error during cleanup:", error);
|
|
1482
1638
|
} finally {
|
|
1483
1639
|
this.conn = null;
|
|
1640
|
+
this.currentWebSocket = null;
|
|
1641
|
+
this.isConnecting = false;
|
|
1484
1642
|
}
|
|
1485
1643
|
}
|
|
1486
1644
|
};
|
|
@@ -1505,7 +1663,9 @@ var Schematic = class {
|
|
|
1505
1663
|
if (this.conn !== null) {
|
|
1506
1664
|
try {
|
|
1507
1665
|
const socket = await this.conn;
|
|
1508
|
-
socket.
|
|
1666
|
+
if (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING) {
|
|
1667
|
+
socket.close();
|
|
1668
|
+
}
|
|
1509
1669
|
} catch (error) {
|
|
1510
1670
|
this.debug("Error closing connection on offline:", error);
|
|
1511
1671
|
}
|
|
@@ -1520,7 +1680,9 @@ var Schematic = class {
|
|
|
1520
1680
|
* Handle browser coming back online
|
|
1521
1681
|
*/
|
|
1522
1682
|
handleNetworkOnline = () => {
|
|
1523
|
-
this.debug(
|
|
1683
|
+
this.debug(
|
|
1684
|
+
"Network online, attempting reconnection and flushing queued events"
|
|
1685
|
+
);
|
|
1524
1686
|
this.wsReconnectAttempts = 0;
|
|
1525
1687
|
if (this.wsReconnectTimer !== null) {
|
|
1526
1688
|
clearTimeout(this.wsReconnectTimer);
|
|
@@ -1543,7 +1705,10 @@ var Schematic = class {
|
|
|
1543
1705
|
return;
|
|
1544
1706
|
}
|
|
1545
1707
|
if (this.wsReconnectTimer !== null) {
|
|
1546
|
-
|
|
1708
|
+
this.debug(
|
|
1709
|
+
`Reconnection attempt already scheduled, ignoring duplicate request`
|
|
1710
|
+
);
|
|
1711
|
+
return;
|
|
1547
1712
|
}
|
|
1548
1713
|
const delay = this.calculateReconnectDelay();
|
|
1549
1714
|
this.debug(
|
|
@@ -1556,23 +1721,57 @@ var Schematic = class {
|
|
|
1556
1721
|
`Attempting to reconnect (attempt ${this.wsReconnectAttempts}/${this.webSocketMaxReconnectAttempts})`
|
|
1557
1722
|
);
|
|
1558
1723
|
try {
|
|
1559
|
-
this.conn
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1724
|
+
if (this.conn !== null) {
|
|
1725
|
+
this.debug(`Cleaning up existing connection before reconnection`);
|
|
1726
|
+
try {
|
|
1727
|
+
const existingSocket = await this.conn;
|
|
1728
|
+
if (this.currentWebSocket === existingSocket) {
|
|
1729
|
+
this.debug(`Existing websocket is current, will be replaced`);
|
|
1730
|
+
this.currentWebSocket = null;
|
|
1731
|
+
}
|
|
1732
|
+
if (existingSocket.readyState === WebSocket.OPEN || existingSocket.readyState === WebSocket.CONNECTING) {
|
|
1733
|
+
existingSocket.close();
|
|
1734
|
+
}
|
|
1735
|
+
} catch (error) {
|
|
1736
|
+
this.debug(`Error cleaning up existing connection:`, error);
|
|
1737
|
+
}
|
|
1738
|
+
this.conn = null;
|
|
1739
|
+
this.currentWebSocket = null;
|
|
1740
|
+
this.isConnecting = false;
|
|
1741
|
+
}
|
|
1742
|
+
this.isConnecting = true;
|
|
1743
|
+
try {
|
|
1744
|
+
this.conn = this.wsConnect();
|
|
1745
|
+
const socket = await this.conn;
|
|
1746
|
+
this.isConnecting = false;
|
|
1747
|
+
this.debug(`Reconnection context check:`, {
|
|
1748
|
+
hasCompany: this.context.company !== void 0,
|
|
1749
|
+
hasUser: this.context.user !== void 0,
|
|
1750
|
+
context: this.context
|
|
1751
|
+
});
|
|
1752
|
+
if (this.context.company !== void 0 || this.context.user !== void 0) {
|
|
1753
|
+
this.debug(`Reconnected, force re-sending context`);
|
|
1754
|
+
await this.wsSendMessage(socket, this.context, true);
|
|
1755
|
+
} else {
|
|
1756
|
+
this.debug(
|
|
1757
|
+
`No context to re-send after reconnection - websocket ready for new context`
|
|
1758
|
+
);
|
|
1759
|
+
this.debug(
|
|
1760
|
+
`Setting up tracking for reconnected websocket (no context to send)`
|
|
1761
|
+
);
|
|
1762
|
+
this.currentWebSocket = socket;
|
|
1763
|
+
}
|
|
1764
|
+
this.flushEventQueue().catch((error) => {
|
|
1765
|
+
this.debug(
|
|
1766
|
+
"Error flushing event queue after websocket reconnection:",
|
|
1767
|
+
error
|
|
1768
|
+
);
|
|
1769
|
+
});
|
|
1770
|
+
this.debug(`Reconnection successful`);
|
|
1771
|
+
} catch (error) {
|
|
1772
|
+
this.isConnecting = false;
|
|
1773
|
+
throw error;
|
|
1571
1774
|
}
|
|
1572
|
-
this.flushEventQueue().catch((error) => {
|
|
1573
|
-
this.debug("Error flushing event queue after websocket reconnection:", error);
|
|
1574
|
-
});
|
|
1575
|
-
this.debug(`Reconnection successful`);
|
|
1576
1775
|
} catch (error) {
|
|
1577
1776
|
this.debug(`Reconnection attempt failed:`, error);
|
|
1578
1777
|
}
|
|
@@ -1590,6 +1789,8 @@ var Schematic = class {
|
|
|
1590
1789
|
const wsUrl = `${this.webSocketUrl}/flags/bootstrap?apiKey=${this.apiKey}`;
|
|
1591
1790
|
this.debug(`connecting to WebSocket:`, wsUrl);
|
|
1592
1791
|
const webSocket = new WebSocket(wsUrl);
|
|
1792
|
+
const connectionId = Math.random().toString(36).substring(7);
|
|
1793
|
+
this.debug(`Creating WebSocket connection ${connectionId} to ${wsUrl}`);
|
|
1593
1794
|
let timeoutId = null;
|
|
1594
1795
|
let isResolved = false;
|
|
1595
1796
|
timeoutId = setTimeout(() => {
|
|
@@ -1608,7 +1809,7 @@ var Schematic = class {
|
|
|
1608
1809
|
}
|
|
1609
1810
|
this.wsReconnectAttempts = 0;
|
|
1610
1811
|
this.wsIntentionalDisconnect = false;
|
|
1611
|
-
this.debug(`WebSocket connection opened`);
|
|
1812
|
+
this.debug(`WebSocket connection ${connectionId} opened successfully`);
|
|
1612
1813
|
resolve(webSocket);
|
|
1613
1814
|
};
|
|
1614
1815
|
webSocket.onerror = (error) => {
|
|
@@ -1616,7 +1817,7 @@ var Schematic = class {
|
|
|
1616
1817
|
if (timeoutId !== null) {
|
|
1617
1818
|
clearTimeout(timeoutId);
|
|
1618
1819
|
}
|
|
1619
|
-
this.debug(`WebSocket connection error:`, error);
|
|
1820
|
+
this.debug(`WebSocket connection ${connectionId} error:`, error);
|
|
1620
1821
|
reject(error);
|
|
1621
1822
|
};
|
|
1622
1823
|
webSocket.onclose = () => {
|
|
@@ -1624,121 +1825,48 @@ var Schematic = class {
|
|
|
1624
1825
|
if (timeoutId !== null) {
|
|
1625
1826
|
clearTimeout(timeoutId);
|
|
1626
1827
|
}
|
|
1627
|
-
this.debug(`WebSocket connection closed`);
|
|
1828
|
+
this.debug(`WebSocket connection ${connectionId} closed`);
|
|
1628
1829
|
this.conn = null;
|
|
1830
|
+
if (this.currentWebSocket === webSocket) {
|
|
1831
|
+
this.currentWebSocket = null;
|
|
1832
|
+
this.isConnecting = false;
|
|
1833
|
+
}
|
|
1629
1834
|
if (!this.wsIntentionalDisconnect && this.webSocketReconnect) {
|
|
1630
1835
|
this.attemptReconnect();
|
|
1631
1836
|
}
|
|
1632
1837
|
};
|
|
1633
1838
|
});
|
|
1634
1839
|
};
|
|
1635
|
-
// Send a message on the websocket after reconnection, forcing the send even if context appears unchanged
|
|
1636
|
-
// because the server has lost all state and needs the initial context
|
|
1637
|
-
wsSendContextAfterReconnection = (socket, context) => {
|
|
1638
|
-
if (this.isOffline()) {
|
|
1639
|
-
this.debug("wsSendContextAfterReconnection: skipped (offline mode)");
|
|
1640
|
-
this.setIsPending(false);
|
|
1641
|
-
return Promise.resolve();
|
|
1642
|
-
}
|
|
1643
|
-
return new Promise((resolve) => {
|
|
1644
|
-
this.debug(`WebSocket force sending context after reconnection:`, context);
|
|
1645
|
-
this.context = context;
|
|
1646
|
-
const sendMessage = () => {
|
|
1647
|
-
let resolved = false;
|
|
1648
|
-
const messageHandler = (event) => {
|
|
1649
|
-
const message = JSON.parse(event.data);
|
|
1650
|
-
this.debug(`WebSocket message received after reconnection:`, message);
|
|
1651
|
-
if (!(contextString(context) in this.checks)) {
|
|
1652
|
-
this.checks[contextString(context)] = {};
|
|
1653
|
-
}
|
|
1654
|
-
(message.flags ?? []).forEach((flag) => {
|
|
1655
|
-
const flagCheck = CheckFlagReturnFromJSON(flag);
|
|
1656
|
-
const contextStr = contextString(context);
|
|
1657
|
-
if (this.checks[contextStr] === void 0) {
|
|
1658
|
-
this.checks[contextStr] = {};
|
|
1659
|
-
}
|
|
1660
|
-
this.checks[contextStr][flagCheck.flag] = flagCheck;
|
|
1661
|
-
});
|
|
1662
|
-
this.useWebSocket = true;
|
|
1663
|
-
socket.removeEventListener("message", messageHandler);
|
|
1664
|
-
if (!resolved) {
|
|
1665
|
-
resolved = true;
|
|
1666
|
-
resolve(this.setIsPending(false));
|
|
1667
|
-
}
|
|
1668
|
-
};
|
|
1669
|
-
socket.addEventListener("message", messageHandler);
|
|
1670
|
-
const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
|
|
1671
|
-
const messagePayload = {
|
|
1672
|
-
apiKey: this.apiKey,
|
|
1673
|
-
clientVersion,
|
|
1674
|
-
data: context
|
|
1675
|
-
};
|
|
1676
|
-
this.debug(`WebSocket sending forced message after reconnection:`, messagePayload);
|
|
1677
|
-
socket.send(JSON.stringify(messagePayload));
|
|
1678
|
-
};
|
|
1679
|
-
if (socket.readyState === WebSocket.OPEN) {
|
|
1680
|
-
this.debug(`WebSocket already open, sending forced message after reconnection`);
|
|
1681
|
-
sendMessage();
|
|
1682
|
-
} else {
|
|
1683
|
-
socket.addEventListener("open", () => {
|
|
1684
|
-
this.debug(`WebSocket opened, sending forced message after reconnection`);
|
|
1685
|
-
sendMessage();
|
|
1686
|
-
});
|
|
1687
|
-
}
|
|
1688
|
-
});
|
|
1689
|
-
};
|
|
1690
1840
|
// Send a message on the websocket indicating interest in a particular evaluation context
|
|
1691
1841
|
// and wait for the initial set of flag values to be returned
|
|
1692
|
-
wsSendMessage = (socket, context) => {
|
|
1842
|
+
wsSendMessage = (socket, context, forceContextSend = false) => {
|
|
1693
1843
|
if (this.isOffline()) {
|
|
1694
1844
|
this.debug("wsSendMessage: skipped (offline mode)");
|
|
1695
1845
|
this.setIsPending(false);
|
|
1696
1846
|
return Promise.resolve();
|
|
1697
1847
|
}
|
|
1698
1848
|
return new Promise((resolve, reject) => {
|
|
1699
|
-
if (contextString(context) == contextString(this.context)) {
|
|
1849
|
+
if (!forceContextSend && contextString(context) == contextString(this.context)) {
|
|
1700
1850
|
this.debug(`WebSocket context unchanged, skipping update`);
|
|
1701
1851
|
return resolve(this.setIsPending(false));
|
|
1702
1852
|
}
|
|
1703
|
-
this.debug(
|
|
1853
|
+
this.debug(
|
|
1854
|
+
forceContextSend ? `WebSocket force sending context (reconnection):` : `WebSocket context updated:`,
|
|
1855
|
+
context
|
|
1856
|
+
);
|
|
1704
1857
|
this.context = context;
|
|
1705
1858
|
const sendMessage = () => {
|
|
1706
1859
|
let resolved = false;
|
|
1860
|
+
const persistentMessageHandler = this.createPersistentMessageHandler(context);
|
|
1707
1861
|
const messageHandler = (event) => {
|
|
1708
|
-
|
|
1709
|
-
this.debug(`WebSocket message received:`, message);
|
|
1710
|
-
if (!(contextString(context) in this.checks)) {
|
|
1711
|
-
this.checks[contextString(context)] = {};
|
|
1712
|
-
}
|
|
1713
|
-
(message.flags ?? []).forEach((flag) => {
|
|
1714
|
-
const flagCheck = CheckFlagReturnFromJSON(flag);
|
|
1715
|
-
const contextStr = contextString(context);
|
|
1716
|
-
if (this.checks[contextStr] === void 0) {
|
|
1717
|
-
this.checks[contextStr] = {};
|
|
1718
|
-
}
|
|
1719
|
-
this.checks[contextStr][flagCheck.flag] = flagCheck;
|
|
1720
|
-
this.debug(`WebSocket flag update:`, {
|
|
1721
|
-
flag: flagCheck.flag,
|
|
1722
|
-
value: flagCheck.value,
|
|
1723
|
-
flagCheck
|
|
1724
|
-
});
|
|
1725
|
-
if (typeof flagCheck.featureUsageEvent === "string") {
|
|
1726
|
-
this.updateFeatureUsageEventMap(flagCheck);
|
|
1727
|
-
}
|
|
1728
|
-
if (this.flagCheckListeners[flag.flag]?.size > 0 || this.flagValueListeners[flag.flag]?.size > 0) {
|
|
1729
|
-
this.submitFlagCheckEvent(flagCheck.flag, flagCheck, context);
|
|
1730
|
-
}
|
|
1731
|
-
this.notifyFlagCheckListeners(flag.flag, flagCheck);
|
|
1732
|
-
this.notifyFlagValueListeners(flag.flag, flagCheck.value);
|
|
1733
|
-
});
|
|
1734
|
-
this.flushContextDependentEventQueue();
|
|
1735
|
-
this.setIsPending(false);
|
|
1862
|
+
persistentMessageHandler(event);
|
|
1736
1863
|
if (!resolved) {
|
|
1737
1864
|
resolved = true;
|
|
1738
1865
|
resolve();
|
|
1739
1866
|
}
|
|
1740
1867
|
};
|
|
1741
1868
|
socket.addEventListener("message", messageHandler);
|
|
1869
|
+
this.currentWebSocket = socket;
|
|
1742
1870
|
const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
|
|
1743
1871
|
const messagePayload = {
|
|
1744
1872
|
apiKey: this.apiKey,
|
|
@@ -1846,7 +1974,17 @@ var Schematic = class {
|
|
|
1846
1974
|
{ value }
|
|
1847
1975
|
);
|
|
1848
1976
|
}
|
|
1849
|
-
listeners.forEach((listener) =>
|
|
1977
|
+
listeners.forEach((listener, index) => {
|
|
1978
|
+
this.debug(`Calling listener ${index} for flag ${flagKey}`, {
|
|
1979
|
+
flagKey,
|
|
1980
|
+
value
|
|
1981
|
+
});
|
|
1982
|
+
notifyFlagValueListener(listener, value);
|
|
1983
|
+
this.debug(`Listener ${index} for flag ${flagKey} completed`, {
|
|
1984
|
+
flagKey,
|
|
1985
|
+
value
|
|
1986
|
+
});
|
|
1987
|
+
});
|
|
1850
1988
|
};
|
|
1851
1989
|
};
|
|
1852
1990
|
var notifyPendingListener = (listener, value) => {
|
|
@@ -1875,7 +2013,7 @@ var notifyFlagValueListener = (listener, value) => {
|
|
|
1875
2013
|
var import_react = __toESM(require("react"));
|
|
1876
2014
|
|
|
1877
2015
|
// src/version.ts
|
|
1878
|
-
var version2 = "1.2.
|
|
2016
|
+
var version2 = "1.2.10";
|
|
1879
2017
|
|
|
1880
2018
|
// src/context/schematic.tsx
|
|
1881
2019
|
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.10";
|
|
758
758
|
var anonymousIdKey = "schematicId";
|
|
759
759
|
var Schematic = class {
|
|
760
760
|
additionalHeaders = {};
|
|
@@ -784,6 +784,8 @@ var Schematic = class {
|
|
|
784
784
|
wsReconnectAttempts = 0;
|
|
785
785
|
wsReconnectTimer = null;
|
|
786
786
|
wsIntentionalDisconnect = false;
|
|
787
|
+
currentWebSocket = null;
|
|
788
|
+
isConnecting = false;
|
|
787
789
|
maxEventQueueSize = 100;
|
|
788
790
|
// Prevent memory issues with very long network outages
|
|
789
791
|
maxEventRetries = 5;
|
|
@@ -793,6 +795,8 @@ var Schematic = class {
|
|
|
793
795
|
eventRetryMaxDelay = 3e4;
|
|
794
796
|
// Maximum retry delay in ms
|
|
795
797
|
retryTimer = null;
|
|
798
|
+
flagValueDefaults = {};
|
|
799
|
+
flagCheckDefaults = {};
|
|
796
800
|
constructor(apiKey, options) {
|
|
797
801
|
this.apiKey = apiKey;
|
|
798
802
|
this.eventQueue = [];
|
|
@@ -863,6 +867,12 @@ var Schematic = class {
|
|
|
863
867
|
if (options?.eventRetryMaxDelay !== void 0) {
|
|
864
868
|
this.eventRetryMaxDelay = options.eventRetryMaxDelay;
|
|
865
869
|
}
|
|
870
|
+
if (options?.flagValueDefaults !== void 0) {
|
|
871
|
+
this.flagValueDefaults = options.flagValueDefaults;
|
|
872
|
+
}
|
|
873
|
+
if (options?.flagCheckDefaults !== void 0) {
|
|
874
|
+
this.flagCheckDefaults = options.flagCheckDefaults;
|
|
875
|
+
}
|
|
866
876
|
if (typeof window !== "undefined" && window?.addEventListener) {
|
|
867
877
|
window.addEventListener("beforeunload", () => {
|
|
868
878
|
this.flushEventQueue();
|
|
@@ -887,6 +897,62 @@ var Schematic = class {
|
|
|
887
897
|
this.debug("Initialized with debug mode enabled");
|
|
888
898
|
}
|
|
889
899
|
}
|
|
900
|
+
/**
|
|
901
|
+
* Resolve fallback value according to priority order:
|
|
902
|
+
* 1. Callsite fallback value (if provided)
|
|
903
|
+
* 2. Initialization fallback value (flagValueDefaults)
|
|
904
|
+
* 3. Default to false
|
|
905
|
+
*/
|
|
906
|
+
resolveFallbackValue(key, callsiteFallback) {
|
|
907
|
+
if (callsiteFallback !== void 0) {
|
|
908
|
+
return callsiteFallback;
|
|
909
|
+
}
|
|
910
|
+
if (key in this.flagValueDefaults) {
|
|
911
|
+
return this.flagValueDefaults[key];
|
|
912
|
+
}
|
|
913
|
+
return false;
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* Resolve complete CheckFlagReturn object according to priority order:
|
|
917
|
+
* 1. Use callsite fallback for boolean value, construct CheckFlagReturn
|
|
918
|
+
* 2. Use flagCheckDefaults if available for this flag
|
|
919
|
+
* 3. Use flagValueDefaults if available for this flag, construct CheckFlagReturn
|
|
920
|
+
* 4. Default CheckFlagReturn with value: false
|
|
921
|
+
*/
|
|
922
|
+
resolveFallbackCheckFlagReturn(key, callsiteFallback, reason = "Fallback value used", error) {
|
|
923
|
+
if (callsiteFallback !== void 0) {
|
|
924
|
+
return {
|
|
925
|
+
flag: key,
|
|
926
|
+
value: callsiteFallback,
|
|
927
|
+
reason,
|
|
928
|
+
error
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
if (key in this.flagCheckDefaults) {
|
|
932
|
+
const defaultReturn = this.flagCheckDefaults[key];
|
|
933
|
+
return {
|
|
934
|
+
...defaultReturn,
|
|
935
|
+
flag: key,
|
|
936
|
+
// Ensure flag matches the requested key
|
|
937
|
+
reason: error !== void 0 ? reason : defaultReturn.reason,
|
|
938
|
+
error
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
if (key in this.flagValueDefaults) {
|
|
942
|
+
return {
|
|
943
|
+
flag: key,
|
|
944
|
+
value: this.flagValueDefaults[key],
|
|
945
|
+
reason,
|
|
946
|
+
error
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
return {
|
|
950
|
+
flag: key,
|
|
951
|
+
value: false,
|
|
952
|
+
reason,
|
|
953
|
+
error
|
|
954
|
+
};
|
|
955
|
+
}
|
|
890
956
|
/**
|
|
891
957
|
* Get value for a single flag.
|
|
892
958
|
* In WebSocket mode, returns cached values if connection is active, otherwise establishes
|
|
@@ -895,16 +961,21 @@ var Schematic = class {
|
|
|
895
961
|
* In REST mode, makes an API call for each check.
|
|
896
962
|
*/
|
|
897
963
|
async checkFlag(options) {
|
|
898
|
-
const { fallback
|
|
964
|
+
const { fallback, key } = options;
|
|
899
965
|
const context = options.context || this.context;
|
|
900
966
|
const contextStr = contextString(context);
|
|
901
967
|
this.debug(`checkFlag: ${key}`, { context, fallback });
|
|
902
968
|
if (this.isOffline()) {
|
|
969
|
+
const resolvedFallbackResult = this.resolveFallbackCheckFlagReturn(
|
|
970
|
+
key,
|
|
971
|
+
fallback,
|
|
972
|
+
"Offline mode - using initialization defaults"
|
|
973
|
+
);
|
|
903
974
|
this.debug(`checkFlag offline result: ${key}`, {
|
|
904
|
-
value:
|
|
975
|
+
value: resolvedFallbackResult.value,
|
|
905
976
|
offlineMode: true
|
|
906
977
|
});
|
|
907
|
-
return
|
|
978
|
+
return resolvedFallbackResult.value;
|
|
908
979
|
}
|
|
909
980
|
if (!this.useWebSocket) {
|
|
910
981
|
const requestUrl = `${this.apiUrl}/flags/${key}/check`;
|
|
@@ -932,14 +1003,14 @@ var Schematic = class {
|
|
|
932
1003
|
return result.value;
|
|
933
1004
|
}).catch((error) => {
|
|
934
1005
|
console.error("There was a problem with the fetch operation:", error);
|
|
935
|
-
const errorResult =
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
error
|
|
940
|
-
|
|
1006
|
+
const errorResult = this.resolveFallbackCheckFlagReturn(
|
|
1007
|
+
key,
|
|
1008
|
+
fallback,
|
|
1009
|
+
"API request failed",
|
|
1010
|
+
error instanceof Error ? error.message : String(error)
|
|
1011
|
+
);
|
|
941
1012
|
this.submitFlagCheckEvent(key, errorResult, context);
|
|
942
|
-
return
|
|
1013
|
+
return errorResult.value;
|
|
943
1014
|
});
|
|
944
1015
|
}
|
|
945
1016
|
try {
|
|
@@ -949,7 +1020,7 @@ var Schematic = class {
|
|
|
949
1020
|
return existingVals[key].value;
|
|
950
1021
|
}
|
|
951
1022
|
if (this.isOffline()) {
|
|
952
|
-
return fallback;
|
|
1023
|
+
return this.resolveFallbackValue(key, fallback);
|
|
953
1024
|
}
|
|
954
1025
|
try {
|
|
955
1026
|
await this.setContext(context);
|
|
@@ -962,10 +1033,10 @@ var Schematic = class {
|
|
|
962
1033
|
}
|
|
963
1034
|
const contextVals = this.checks[contextStr] ?? {};
|
|
964
1035
|
const flagCheck = contextVals[key];
|
|
965
|
-
const result = flagCheck?.value ?? fallback;
|
|
1036
|
+
const result = flagCheck?.value ?? this.resolveFallbackValue(key, fallback);
|
|
966
1037
|
this.debug(
|
|
967
1038
|
`checkFlag WebSocket result: ${key}`,
|
|
968
|
-
typeof flagCheck !== "undefined" ? flagCheck : { value:
|
|
1039
|
+
typeof flagCheck !== "undefined" ? flagCheck : { value: result, fallbackUsed: true }
|
|
969
1040
|
);
|
|
970
1041
|
if (typeof flagCheck !== "undefined") {
|
|
971
1042
|
this.submitFlagCheckEvent(key, flagCheck, context);
|
|
@@ -973,14 +1044,14 @@ var Schematic = class {
|
|
|
973
1044
|
return result;
|
|
974
1045
|
} catch (error) {
|
|
975
1046
|
console.error("Unexpected error in checkFlag:", error);
|
|
976
|
-
const errorResult =
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
error
|
|
981
|
-
|
|
1047
|
+
const errorResult = this.resolveFallbackCheckFlagReturn(
|
|
1048
|
+
key,
|
|
1049
|
+
fallback,
|
|
1050
|
+
"Unexpected error in flag check",
|
|
1051
|
+
error instanceof Error ? error.message : String(error)
|
|
1052
|
+
);
|
|
982
1053
|
this.submitFlagCheckEvent(key, errorResult, context);
|
|
983
|
-
return
|
|
1054
|
+
return errorResult.value;
|
|
984
1055
|
}
|
|
985
1056
|
}
|
|
986
1057
|
/**
|
|
@@ -992,6 +1063,49 @@ var Schematic = class {
|
|
|
992
1063
|
console.log(`[Schematic] ${message}`, ...args);
|
|
993
1064
|
}
|
|
994
1065
|
}
|
|
1066
|
+
/**
|
|
1067
|
+
* Create a persistent message handler for websocket flag updates
|
|
1068
|
+
*/
|
|
1069
|
+
createPersistentMessageHandler(context) {
|
|
1070
|
+
return (event) => {
|
|
1071
|
+
const message = JSON.parse(event.data);
|
|
1072
|
+
this.debug(`WebSocket persistent message received:`, message);
|
|
1073
|
+
if (!(contextString(context) in this.checks)) {
|
|
1074
|
+
this.checks[contextString(context)] = {};
|
|
1075
|
+
}
|
|
1076
|
+
(message.flags ?? []).forEach((flag) => {
|
|
1077
|
+
const flagCheck = CheckFlagReturnFromJSON(flag);
|
|
1078
|
+
const contextStr = contextString(context);
|
|
1079
|
+
if (this.checks[contextStr] === void 0) {
|
|
1080
|
+
this.checks[contextStr] = {};
|
|
1081
|
+
}
|
|
1082
|
+
this.checks[contextStr][flagCheck.flag] = flagCheck;
|
|
1083
|
+
this.debug(`WebSocket flag update:`, {
|
|
1084
|
+
flag: flagCheck.flag,
|
|
1085
|
+
value: flagCheck.value,
|
|
1086
|
+
flagCheck
|
|
1087
|
+
});
|
|
1088
|
+
if (typeof flagCheck.featureUsageEvent === "string") {
|
|
1089
|
+
this.updateFeatureUsageEventMap(flagCheck);
|
|
1090
|
+
}
|
|
1091
|
+
if ((this.flagCheckListeners[flag.flag]?.size ?? 0) > 0 || (this.flagValueListeners[flag.flag]?.size ?? 0) > 0) {
|
|
1092
|
+
this.submitFlagCheckEvent(flagCheck.flag, flagCheck, context);
|
|
1093
|
+
}
|
|
1094
|
+
this.debug(`About to notify listeners for flag ${flag.flag}`, {
|
|
1095
|
+
flag: flag.flag,
|
|
1096
|
+
value: flagCheck.value
|
|
1097
|
+
});
|
|
1098
|
+
this.notifyFlagCheckListeners(flag.flag, flagCheck);
|
|
1099
|
+
this.notifyFlagValueListeners(flag.flag, flagCheck.value);
|
|
1100
|
+
this.debug(`Finished notifying listeners for flag ${flag.flag}`, {
|
|
1101
|
+
flag: flag.flag,
|
|
1102
|
+
value: flagCheck.value
|
|
1103
|
+
});
|
|
1104
|
+
});
|
|
1105
|
+
this.flushContextDependentEventQueue();
|
|
1106
|
+
this.setIsPending(false);
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
995
1109
|
/**
|
|
996
1110
|
* Helper function to check if client is in offline mode
|
|
997
1111
|
*/
|
|
@@ -1023,11 +1137,12 @@ var Schematic = class {
|
|
|
1023
1137
|
*/
|
|
1024
1138
|
async fallbackToRest(key, context, fallback) {
|
|
1025
1139
|
if (this.isOffline()) {
|
|
1140
|
+
const resolvedFallback = this.resolveFallbackValue(key, fallback);
|
|
1026
1141
|
this.debug(`fallbackToRest offline result: ${key}`, {
|
|
1027
|
-
value:
|
|
1142
|
+
value: resolvedFallback,
|
|
1028
1143
|
offlineMode: true
|
|
1029
1144
|
});
|
|
1030
|
-
return
|
|
1145
|
+
return resolvedFallback;
|
|
1031
1146
|
}
|
|
1032
1147
|
try {
|
|
1033
1148
|
const requestUrl = `${this.apiUrl}/flags/${key}/check`;
|
|
@@ -1054,14 +1169,14 @@ var Schematic = class {
|
|
|
1054
1169
|
return result.value;
|
|
1055
1170
|
} catch (error) {
|
|
1056
1171
|
console.error("REST API call failed, using fallback value:", error);
|
|
1057
|
-
const errorResult =
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
error
|
|
1062
|
-
|
|
1172
|
+
const errorResult = this.resolveFallbackCheckFlagReturn(
|
|
1173
|
+
key,
|
|
1174
|
+
fallback,
|
|
1175
|
+
"API request failed (fallback)",
|
|
1176
|
+
error instanceof Error ? error.message : String(error)
|
|
1177
|
+
);
|
|
1063
1178
|
this.submitFlagCheckEvent(key, errorResult, context);
|
|
1064
|
-
return
|
|
1179
|
+
return errorResult.value;
|
|
1065
1180
|
}
|
|
1066
1181
|
}
|
|
1067
1182
|
/**
|
|
@@ -1142,6 +1257,19 @@ var Schematic = class {
|
|
|
1142
1257
|
try {
|
|
1143
1258
|
this.setIsPending(true);
|
|
1144
1259
|
if (!this.conn) {
|
|
1260
|
+
if (this.isConnecting) {
|
|
1261
|
+
this.debug(
|
|
1262
|
+
`Connection already in progress, waiting for it to complete`
|
|
1263
|
+
);
|
|
1264
|
+
while (this.isConnecting && this.conn === null) {
|
|
1265
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1266
|
+
}
|
|
1267
|
+
if (this.conn !== null) {
|
|
1268
|
+
const socket2 = await this.conn;
|
|
1269
|
+
await this.wsSendMessage(socket2, context);
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1145
1273
|
if (this.wsReconnectTimer !== null) {
|
|
1146
1274
|
this.debug(
|
|
1147
1275
|
`Cancelling scheduled reconnection, connecting immediately`
|
|
@@ -1149,7 +1277,17 @@ var Schematic = class {
|
|
|
1149
1277
|
clearTimeout(this.wsReconnectTimer);
|
|
1150
1278
|
this.wsReconnectTimer = null;
|
|
1151
1279
|
}
|
|
1152
|
-
this.
|
|
1280
|
+
this.isConnecting = true;
|
|
1281
|
+
try {
|
|
1282
|
+
this.conn = this.wsConnect();
|
|
1283
|
+
const socket2 = await this.conn;
|
|
1284
|
+
this.isConnecting = false;
|
|
1285
|
+
await this.wsSendMessage(socket2, context);
|
|
1286
|
+
return;
|
|
1287
|
+
} catch (error) {
|
|
1288
|
+
this.isConnecting = false;
|
|
1289
|
+
throw error;
|
|
1290
|
+
}
|
|
1153
1291
|
}
|
|
1154
1292
|
const socket = await this.conn;
|
|
1155
1293
|
await this.wsSendMessage(socket, context);
|
|
@@ -1314,10 +1452,14 @@ var Schematic = class {
|
|
|
1314
1452
|
}
|
|
1315
1453
|
}
|
|
1316
1454
|
if (readyEvents.length === 0) {
|
|
1317
|
-
this.debug(
|
|
1455
|
+
this.debug(
|
|
1456
|
+
`No events ready for retry yet (${notReadyEvents.length} still in backoff)`
|
|
1457
|
+
);
|
|
1318
1458
|
return;
|
|
1319
1459
|
}
|
|
1320
|
-
this.debug(
|
|
1460
|
+
this.debug(
|
|
1461
|
+
`Flushing event queue: ${readyEvents.length} ready, ${notReadyEvents.length} waiting`
|
|
1462
|
+
);
|
|
1321
1463
|
this.eventQueue = notReadyEvents;
|
|
1322
1464
|
for (const event of readyEvents) {
|
|
1323
1465
|
try {
|
|
@@ -1382,7 +1524,10 @@ var Schematic = class {
|
|
|
1382
1524
|
} catch (error) {
|
|
1383
1525
|
const retryCount = (event.retry_count ?? 0) + 1;
|
|
1384
1526
|
if (retryCount <= this.maxEventRetries) {
|
|
1385
|
-
this.debug(
|
|
1527
|
+
this.debug(
|
|
1528
|
+
`Event failed to send (attempt ${retryCount}/${this.maxEventRetries}), queueing for retry:`,
|
|
1529
|
+
error
|
|
1530
|
+
);
|
|
1386
1531
|
const baseDelay = this.eventRetryInitialDelay * Math.pow(2, retryCount - 1);
|
|
1387
1532
|
const jitterDelay = Math.min(baseDelay, this.eventRetryMaxDelay);
|
|
1388
1533
|
const nextRetryAt = Date.now() + jitterDelay;
|
|
@@ -1393,15 +1538,22 @@ var Schematic = class {
|
|
|
1393
1538
|
};
|
|
1394
1539
|
if (this.eventQueue.length < this.maxEventQueueSize) {
|
|
1395
1540
|
this.eventQueue.push(retryEvent);
|
|
1396
|
-
this.debug(
|
|
1541
|
+
this.debug(
|
|
1542
|
+
`Event queued for retry in ${jitterDelay}ms (${this.eventQueue.length}/${this.maxEventQueueSize})`
|
|
1543
|
+
);
|
|
1397
1544
|
} else {
|
|
1398
|
-
this.debug(
|
|
1545
|
+
this.debug(
|
|
1546
|
+
`Event queue full (${this.maxEventQueueSize}), dropping oldest event`
|
|
1547
|
+
);
|
|
1399
1548
|
this.eventQueue.shift();
|
|
1400
1549
|
this.eventQueue.push(retryEvent);
|
|
1401
1550
|
}
|
|
1402
1551
|
this.startRetryTimer();
|
|
1403
1552
|
} else {
|
|
1404
|
-
this.debug(
|
|
1553
|
+
this.debug(
|
|
1554
|
+
`Event failed permanently after ${this.maxEventRetries} attempts, dropping:`,
|
|
1555
|
+
error
|
|
1556
|
+
);
|
|
1405
1557
|
}
|
|
1406
1558
|
}
|
|
1407
1559
|
return Promise.resolve();
|
|
@@ -1431,11 +1583,17 @@ var Schematic = class {
|
|
|
1431
1583
|
if (this.conn) {
|
|
1432
1584
|
try {
|
|
1433
1585
|
const socket = await this.conn;
|
|
1586
|
+
if (this.currentWebSocket === socket) {
|
|
1587
|
+
this.debug(`Cleaning up current websocket tracking`);
|
|
1588
|
+
this.currentWebSocket = null;
|
|
1589
|
+
}
|
|
1434
1590
|
socket.close();
|
|
1435
1591
|
} catch (error) {
|
|
1436
1592
|
console.error("Error during cleanup:", error);
|
|
1437
1593
|
} finally {
|
|
1438
1594
|
this.conn = null;
|
|
1595
|
+
this.currentWebSocket = null;
|
|
1596
|
+
this.isConnecting = false;
|
|
1439
1597
|
}
|
|
1440
1598
|
}
|
|
1441
1599
|
};
|
|
@@ -1460,7 +1618,9 @@ var Schematic = class {
|
|
|
1460
1618
|
if (this.conn !== null) {
|
|
1461
1619
|
try {
|
|
1462
1620
|
const socket = await this.conn;
|
|
1463
|
-
socket.
|
|
1621
|
+
if (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING) {
|
|
1622
|
+
socket.close();
|
|
1623
|
+
}
|
|
1464
1624
|
} catch (error) {
|
|
1465
1625
|
this.debug("Error closing connection on offline:", error);
|
|
1466
1626
|
}
|
|
@@ -1475,7 +1635,9 @@ var Schematic = class {
|
|
|
1475
1635
|
* Handle browser coming back online
|
|
1476
1636
|
*/
|
|
1477
1637
|
handleNetworkOnline = () => {
|
|
1478
|
-
this.debug(
|
|
1638
|
+
this.debug(
|
|
1639
|
+
"Network online, attempting reconnection and flushing queued events"
|
|
1640
|
+
);
|
|
1479
1641
|
this.wsReconnectAttempts = 0;
|
|
1480
1642
|
if (this.wsReconnectTimer !== null) {
|
|
1481
1643
|
clearTimeout(this.wsReconnectTimer);
|
|
@@ -1498,7 +1660,10 @@ var Schematic = class {
|
|
|
1498
1660
|
return;
|
|
1499
1661
|
}
|
|
1500
1662
|
if (this.wsReconnectTimer !== null) {
|
|
1501
|
-
|
|
1663
|
+
this.debug(
|
|
1664
|
+
`Reconnection attempt already scheduled, ignoring duplicate request`
|
|
1665
|
+
);
|
|
1666
|
+
return;
|
|
1502
1667
|
}
|
|
1503
1668
|
const delay = this.calculateReconnectDelay();
|
|
1504
1669
|
this.debug(
|
|
@@ -1511,23 +1676,57 @@ var Schematic = class {
|
|
|
1511
1676
|
`Attempting to reconnect (attempt ${this.wsReconnectAttempts}/${this.webSocketMaxReconnectAttempts})`
|
|
1512
1677
|
);
|
|
1513
1678
|
try {
|
|
1514
|
-
this.conn
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1679
|
+
if (this.conn !== null) {
|
|
1680
|
+
this.debug(`Cleaning up existing connection before reconnection`);
|
|
1681
|
+
try {
|
|
1682
|
+
const existingSocket = await this.conn;
|
|
1683
|
+
if (this.currentWebSocket === existingSocket) {
|
|
1684
|
+
this.debug(`Existing websocket is current, will be replaced`);
|
|
1685
|
+
this.currentWebSocket = null;
|
|
1686
|
+
}
|
|
1687
|
+
if (existingSocket.readyState === WebSocket.OPEN || existingSocket.readyState === WebSocket.CONNECTING) {
|
|
1688
|
+
existingSocket.close();
|
|
1689
|
+
}
|
|
1690
|
+
} catch (error) {
|
|
1691
|
+
this.debug(`Error cleaning up existing connection:`, error);
|
|
1692
|
+
}
|
|
1693
|
+
this.conn = null;
|
|
1694
|
+
this.currentWebSocket = null;
|
|
1695
|
+
this.isConnecting = false;
|
|
1696
|
+
}
|
|
1697
|
+
this.isConnecting = true;
|
|
1698
|
+
try {
|
|
1699
|
+
this.conn = this.wsConnect();
|
|
1700
|
+
const socket = await this.conn;
|
|
1701
|
+
this.isConnecting = false;
|
|
1702
|
+
this.debug(`Reconnection context check:`, {
|
|
1703
|
+
hasCompany: this.context.company !== void 0,
|
|
1704
|
+
hasUser: this.context.user !== void 0,
|
|
1705
|
+
context: this.context
|
|
1706
|
+
});
|
|
1707
|
+
if (this.context.company !== void 0 || this.context.user !== void 0) {
|
|
1708
|
+
this.debug(`Reconnected, force re-sending context`);
|
|
1709
|
+
await this.wsSendMessage(socket, this.context, true);
|
|
1710
|
+
} else {
|
|
1711
|
+
this.debug(
|
|
1712
|
+
`No context to re-send after reconnection - websocket ready for new context`
|
|
1713
|
+
);
|
|
1714
|
+
this.debug(
|
|
1715
|
+
`Setting up tracking for reconnected websocket (no context to send)`
|
|
1716
|
+
);
|
|
1717
|
+
this.currentWebSocket = socket;
|
|
1718
|
+
}
|
|
1719
|
+
this.flushEventQueue().catch((error) => {
|
|
1720
|
+
this.debug(
|
|
1721
|
+
"Error flushing event queue after websocket reconnection:",
|
|
1722
|
+
error
|
|
1723
|
+
);
|
|
1724
|
+
});
|
|
1725
|
+
this.debug(`Reconnection successful`);
|
|
1726
|
+
} catch (error) {
|
|
1727
|
+
this.isConnecting = false;
|
|
1728
|
+
throw error;
|
|
1526
1729
|
}
|
|
1527
|
-
this.flushEventQueue().catch((error) => {
|
|
1528
|
-
this.debug("Error flushing event queue after websocket reconnection:", error);
|
|
1529
|
-
});
|
|
1530
|
-
this.debug(`Reconnection successful`);
|
|
1531
1730
|
} catch (error) {
|
|
1532
1731
|
this.debug(`Reconnection attempt failed:`, error);
|
|
1533
1732
|
}
|
|
@@ -1545,6 +1744,8 @@ var Schematic = class {
|
|
|
1545
1744
|
const wsUrl = `${this.webSocketUrl}/flags/bootstrap?apiKey=${this.apiKey}`;
|
|
1546
1745
|
this.debug(`connecting to WebSocket:`, wsUrl);
|
|
1547
1746
|
const webSocket = new WebSocket(wsUrl);
|
|
1747
|
+
const connectionId = Math.random().toString(36).substring(7);
|
|
1748
|
+
this.debug(`Creating WebSocket connection ${connectionId} to ${wsUrl}`);
|
|
1548
1749
|
let timeoutId = null;
|
|
1549
1750
|
let isResolved = false;
|
|
1550
1751
|
timeoutId = setTimeout(() => {
|
|
@@ -1563,7 +1764,7 @@ var Schematic = class {
|
|
|
1563
1764
|
}
|
|
1564
1765
|
this.wsReconnectAttempts = 0;
|
|
1565
1766
|
this.wsIntentionalDisconnect = false;
|
|
1566
|
-
this.debug(`WebSocket connection opened`);
|
|
1767
|
+
this.debug(`WebSocket connection ${connectionId} opened successfully`);
|
|
1567
1768
|
resolve(webSocket);
|
|
1568
1769
|
};
|
|
1569
1770
|
webSocket.onerror = (error) => {
|
|
@@ -1571,7 +1772,7 @@ var Schematic = class {
|
|
|
1571
1772
|
if (timeoutId !== null) {
|
|
1572
1773
|
clearTimeout(timeoutId);
|
|
1573
1774
|
}
|
|
1574
|
-
this.debug(`WebSocket connection error:`, error);
|
|
1775
|
+
this.debug(`WebSocket connection ${connectionId} error:`, error);
|
|
1575
1776
|
reject(error);
|
|
1576
1777
|
};
|
|
1577
1778
|
webSocket.onclose = () => {
|
|
@@ -1579,121 +1780,48 @@ var Schematic = class {
|
|
|
1579
1780
|
if (timeoutId !== null) {
|
|
1580
1781
|
clearTimeout(timeoutId);
|
|
1581
1782
|
}
|
|
1582
|
-
this.debug(`WebSocket connection closed`);
|
|
1783
|
+
this.debug(`WebSocket connection ${connectionId} closed`);
|
|
1583
1784
|
this.conn = null;
|
|
1785
|
+
if (this.currentWebSocket === webSocket) {
|
|
1786
|
+
this.currentWebSocket = null;
|
|
1787
|
+
this.isConnecting = false;
|
|
1788
|
+
}
|
|
1584
1789
|
if (!this.wsIntentionalDisconnect && this.webSocketReconnect) {
|
|
1585
1790
|
this.attemptReconnect();
|
|
1586
1791
|
}
|
|
1587
1792
|
};
|
|
1588
1793
|
});
|
|
1589
1794
|
};
|
|
1590
|
-
// Send a message on the websocket after reconnection, forcing the send even if context appears unchanged
|
|
1591
|
-
// because the server has lost all state and needs the initial context
|
|
1592
|
-
wsSendContextAfterReconnection = (socket, context) => {
|
|
1593
|
-
if (this.isOffline()) {
|
|
1594
|
-
this.debug("wsSendContextAfterReconnection: skipped (offline mode)");
|
|
1595
|
-
this.setIsPending(false);
|
|
1596
|
-
return Promise.resolve();
|
|
1597
|
-
}
|
|
1598
|
-
return new Promise((resolve) => {
|
|
1599
|
-
this.debug(`WebSocket force sending context after reconnection:`, context);
|
|
1600
|
-
this.context = context;
|
|
1601
|
-
const sendMessage = () => {
|
|
1602
|
-
let resolved = false;
|
|
1603
|
-
const messageHandler = (event) => {
|
|
1604
|
-
const message = JSON.parse(event.data);
|
|
1605
|
-
this.debug(`WebSocket message received after reconnection:`, message);
|
|
1606
|
-
if (!(contextString(context) in this.checks)) {
|
|
1607
|
-
this.checks[contextString(context)] = {};
|
|
1608
|
-
}
|
|
1609
|
-
(message.flags ?? []).forEach((flag) => {
|
|
1610
|
-
const flagCheck = CheckFlagReturnFromJSON(flag);
|
|
1611
|
-
const contextStr = contextString(context);
|
|
1612
|
-
if (this.checks[contextStr] === void 0) {
|
|
1613
|
-
this.checks[contextStr] = {};
|
|
1614
|
-
}
|
|
1615
|
-
this.checks[contextStr][flagCheck.flag] = flagCheck;
|
|
1616
|
-
});
|
|
1617
|
-
this.useWebSocket = true;
|
|
1618
|
-
socket.removeEventListener("message", messageHandler);
|
|
1619
|
-
if (!resolved) {
|
|
1620
|
-
resolved = true;
|
|
1621
|
-
resolve(this.setIsPending(false));
|
|
1622
|
-
}
|
|
1623
|
-
};
|
|
1624
|
-
socket.addEventListener("message", messageHandler);
|
|
1625
|
-
const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
|
|
1626
|
-
const messagePayload = {
|
|
1627
|
-
apiKey: this.apiKey,
|
|
1628
|
-
clientVersion,
|
|
1629
|
-
data: context
|
|
1630
|
-
};
|
|
1631
|
-
this.debug(`WebSocket sending forced message after reconnection:`, messagePayload);
|
|
1632
|
-
socket.send(JSON.stringify(messagePayload));
|
|
1633
|
-
};
|
|
1634
|
-
if (socket.readyState === WebSocket.OPEN) {
|
|
1635
|
-
this.debug(`WebSocket already open, sending forced message after reconnection`);
|
|
1636
|
-
sendMessage();
|
|
1637
|
-
} else {
|
|
1638
|
-
socket.addEventListener("open", () => {
|
|
1639
|
-
this.debug(`WebSocket opened, sending forced message after reconnection`);
|
|
1640
|
-
sendMessage();
|
|
1641
|
-
});
|
|
1642
|
-
}
|
|
1643
|
-
});
|
|
1644
|
-
};
|
|
1645
1795
|
// Send a message on the websocket indicating interest in a particular evaluation context
|
|
1646
1796
|
// and wait for the initial set of flag values to be returned
|
|
1647
|
-
wsSendMessage = (socket, context) => {
|
|
1797
|
+
wsSendMessage = (socket, context, forceContextSend = false) => {
|
|
1648
1798
|
if (this.isOffline()) {
|
|
1649
1799
|
this.debug("wsSendMessage: skipped (offline mode)");
|
|
1650
1800
|
this.setIsPending(false);
|
|
1651
1801
|
return Promise.resolve();
|
|
1652
1802
|
}
|
|
1653
1803
|
return new Promise((resolve, reject) => {
|
|
1654
|
-
if (contextString(context) == contextString(this.context)) {
|
|
1804
|
+
if (!forceContextSend && contextString(context) == contextString(this.context)) {
|
|
1655
1805
|
this.debug(`WebSocket context unchanged, skipping update`);
|
|
1656
1806
|
return resolve(this.setIsPending(false));
|
|
1657
1807
|
}
|
|
1658
|
-
this.debug(
|
|
1808
|
+
this.debug(
|
|
1809
|
+
forceContextSend ? `WebSocket force sending context (reconnection):` : `WebSocket context updated:`,
|
|
1810
|
+
context
|
|
1811
|
+
);
|
|
1659
1812
|
this.context = context;
|
|
1660
1813
|
const sendMessage = () => {
|
|
1661
1814
|
let resolved = false;
|
|
1815
|
+
const persistentMessageHandler = this.createPersistentMessageHandler(context);
|
|
1662
1816
|
const messageHandler = (event) => {
|
|
1663
|
-
|
|
1664
|
-
this.debug(`WebSocket message received:`, message);
|
|
1665
|
-
if (!(contextString(context) in this.checks)) {
|
|
1666
|
-
this.checks[contextString(context)] = {};
|
|
1667
|
-
}
|
|
1668
|
-
(message.flags ?? []).forEach((flag) => {
|
|
1669
|
-
const flagCheck = CheckFlagReturnFromJSON(flag);
|
|
1670
|
-
const contextStr = contextString(context);
|
|
1671
|
-
if (this.checks[contextStr] === void 0) {
|
|
1672
|
-
this.checks[contextStr] = {};
|
|
1673
|
-
}
|
|
1674
|
-
this.checks[contextStr][flagCheck.flag] = flagCheck;
|
|
1675
|
-
this.debug(`WebSocket flag update:`, {
|
|
1676
|
-
flag: flagCheck.flag,
|
|
1677
|
-
value: flagCheck.value,
|
|
1678
|
-
flagCheck
|
|
1679
|
-
});
|
|
1680
|
-
if (typeof flagCheck.featureUsageEvent === "string") {
|
|
1681
|
-
this.updateFeatureUsageEventMap(flagCheck);
|
|
1682
|
-
}
|
|
1683
|
-
if (this.flagCheckListeners[flag.flag]?.size > 0 || this.flagValueListeners[flag.flag]?.size > 0) {
|
|
1684
|
-
this.submitFlagCheckEvent(flagCheck.flag, flagCheck, context);
|
|
1685
|
-
}
|
|
1686
|
-
this.notifyFlagCheckListeners(flag.flag, flagCheck);
|
|
1687
|
-
this.notifyFlagValueListeners(flag.flag, flagCheck.value);
|
|
1688
|
-
});
|
|
1689
|
-
this.flushContextDependentEventQueue();
|
|
1690
|
-
this.setIsPending(false);
|
|
1817
|
+
persistentMessageHandler(event);
|
|
1691
1818
|
if (!resolved) {
|
|
1692
1819
|
resolved = true;
|
|
1693
1820
|
resolve();
|
|
1694
1821
|
}
|
|
1695
1822
|
};
|
|
1696
1823
|
socket.addEventListener("message", messageHandler);
|
|
1824
|
+
this.currentWebSocket = socket;
|
|
1697
1825
|
const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
|
|
1698
1826
|
const messagePayload = {
|
|
1699
1827
|
apiKey: this.apiKey,
|
|
@@ -1801,7 +1929,17 @@ var Schematic = class {
|
|
|
1801
1929
|
{ value }
|
|
1802
1930
|
);
|
|
1803
1931
|
}
|
|
1804
|
-
listeners.forEach((listener) =>
|
|
1932
|
+
listeners.forEach((listener, index) => {
|
|
1933
|
+
this.debug(`Calling listener ${index} for flag ${flagKey}`, {
|
|
1934
|
+
flagKey,
|
|
1935
|
+
value
|
|
1936
|
+
});
|
|
1937
|
+
notifyFlagValueListener(listener, value);
|
|
1938
|
+
this.debug(`Listener ${index} for flag ${flagKey} completed`, {
|
|
1939
|
+
flagKey,
|
|
1940
|
+
value
|
|
1941
|
+
});
|
|
1942
|
+
});
|
|
1805
1943
|
};
|
|
1806
1944
|
};
|
|
1807
1945
|
var notifyPendingListener = (listener, value) => {
|
|
@@ -1830,7 +1968,7 @@ var notifyFlagValueListener = (listener, value) => {
|
|
|
1830
1968
|
import React, { createContext, useEffect, useMemo, useRef } from "react";
|
|
1831
1969
|
|
|
1832
1970
|
// src/version.ts
|
|
1833
|
-
var version2 = "1.2.
|
|
1971
|
+
var version2 = "1.2.10";
|
|
1834
1972
|
|
|
1835
1973
|
// src/context/schematic.tsx
|
|
1836
1974
|
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.10",
|
|
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.
|
|
34
|
+
"@schematichq/schematic-js": "^1.2.10"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@eslint/js": "^9.39.1",
|