@schematichq/schematic-react 1.2.7 → 1.2.9
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 +267 -44
- package/dist/schematic-react.esm.js +267 -44
- package/package.json +4 -4
|
@@ -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.9";
|
|
803
803
|
var anonymousIdKey = "schematicId";
|
|
804
804
|
var Schematic = class {
|
|
805
805
|
additionalHeaders = {};
|
|
@@ -829,6 +829,17 @@ var Schematic = class {
|
|
|
829
829
|
wsReconnectAttempts = 0;
|
|
830
830
|
wsReconnectTimer = null;
|
|
831
831
|
wsIntentionalDisconnect = false;
|
|
832
|
+
maxEventQueueSize = 100;
|
|
833
|
+
// Prevent memory issues with very long network outages
|
|
834
|
+
maxEventRetries = 5;
|
|
835
|
+
// Maximum retry attempts for failed events
|
|
836
|
+
eventRetryInitialDelay = 1e3;
|
|
837
|
+
// Initial retry delay in ms
|
|
838
|
+
eventRetryMaxDelay = 3e4;
|
|
839
|
+
// Maximum retry delay in ms
|
|
840
|
+
retryTimer = null;
|
|
841
|
+
flagValueDefaults = {};
|
|
842
|
+
flagCheckDefaults = {};
|
|
832
843
|
constructor(apiKey, options) {
|
|
833
844
|
this.apiKey = apiKey;
|
|
834
845
|
this.eventQueue = [];
|
|
@@ -887,6 +898,24 @@ var Schematic = class {
|
|
|
887
898
|
if (options?.webSocketMaxRetryDelay !== void 0) {
|
|
888
899
|
this.webSocketMaxRetryDelay = options.webSocketMaxRetryDelay;
|
|
889
900
|
}
|
|
901
|
+
if (options?.maxEventQueueSize !== void 0) {
|
|
902
|
+
this.maxEventQueueSize = options.maxEventQueueSize;
|
|
903
|
+
}
|
|
904
|
+
if (options?.maxEventRetries !== void 0) {
|
|
905
|
+
this.maxEventRetries = options.maxEventRetries;
|
|
906
|
+
}
|
|
907
|
+
if (options?.eventRetryInitialDelay !== void 0) {
|
|
908
|
+
this.eventRetryInitialDelay = options.eventRetryInitialDelay;
|
|
909
|
+
}
|
|
910
|
+
if (options?.eventRetryMaxDelay !== void 0) {
|
|
911
|
+
this.eventRetryMaxDelay = options.eventRetryMaxDelay;
|
|
912
|
+
}
|
|
913
|
+
if (options?.flagValueDefaults !== void 0) {
|
|
914
|
+
this.flagValueDefaults = options.flagValueDefaults;
|
|
915
|
+
}
|
|
916
|
+
if (options?.flagCheckDefaults !== void 0) {
|
|
917
|
+
this.flagCheckDefaults = options.flagCheckDefaults;
|
|
918
|
+
}
|
|
890
919
|
if (typeof window !== "undefined" && window?.addEventListener) {
|
|
891
920
|
window.addEventListener("beforeunload", () => {
|
|
892
921
|
this.flushEventQueue();
|
|
@@ -911,6 +940,62 @@ var Schematic = class {
|
|
|
911
940
|
this.debug("Initialized with debug mode enabled");
|
|
912
941
|
}
|
|
913
942
|
}
|
|
943
|
+
/**
|
|
944
|
+
* Resolve fallback value according to priority order:
|
|
945
|
+
* 1. Callsite fallback value (if provided)
|
|
946
|
+
* 2. Initialization fallback value (flagValueDefaults)
|
|
947
|
+
* 3. Default to false
|
|
948
|
+
*/
|
|
949
|
+
resolveFallbackValue(key, callsiteFallback) {
|
|
950
|
+
if (callsiteFallback !== void 0) {
|
|
951
|
+
return callsiteFallback;
|
|
952
|
+
}
|
|
953
|
+
if (key in this.flagValueDefaults) {
|
|
954
|
+
return this.flagValueDefaults[key];
|
|
955
|
+
}
|
|
956
|
+
return false;
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* Resolve complete CheckFlagReturn object according to priority order:
|
|
960
|
+
* 1. Use callsite fallback for boolean value, construct CheckFlagReturn
|
|
961
|
+
* 2. Use flagCheckDefaults if available for this flag
|
|
962
|
+
* 3. Use flagValueDefaults if available for this flag, construct CheckFlagReturn
|
|
963
|
+
* 4. Default CheckFlagReturn with value: false
|
|
964
|
+
*/
|
|
965
|
+
resolveFallbackCheckFlagReturn(key, callsiteFallback, reason = "Fallback value used", error) {
|
|
966
|
+
if (callsiteFallback !== void 0) {
|
|
967
|
+
return {
|
|
968
|
+
flag: key,
|
|
969
|
+
value: callsiteFallback,
|
|
970
|
+
reason,
|
|
971
|
+
error
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
if (key in this.flagCheckDefaults) {
|
|
975
|
+
const defaultReturn = this.flagCheckDefaults[key];
|
|
976
|
+
return {
|
|
977
|
+
...defaultReturn,
|
|
978
|
+
flag: key,
|
|
979
|
+
// Ensure flag matches the requested key
|
|
980
|
+
reason: error !== void 0 ? reason : defaultReturn.reason,
|
|
981
|
+
error
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
if (key in this.flagValueDefaults) {
|
|
985
|
+
return {
|
|
986
|
+
flag: key,
|
|
987
|
+
value: this.flagValueDefaults[key],
|
|
988
|
+
reason,
|
|
989
|
+
error
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
return {
|
|
993
|
+
flag: key,
|
|
994
|
+
value: false,
|
|
995
|
+
reason,
|
|
996
|
+
error
|
|
997
|
+
};
|
|
998
|
+
}
|
|
914
999
|
/**
|
|
915
1000
|
* Get value for a single flag.
|
|
916
1001
|
* In WebSocket mode, returns cached values if connection is active, otherwise establishes
|
|
@@ -919,16 +1004,21 @@ var Schematic = class {
|
|
|
919
1004
|
* In REST mode, makes an API call for each check.
|
|
920
1005
|
*/
|
|
921
1006
|
async checkFlag(options) {
|
|
922
|
-
const { fallback
|
|
1007
|
+
const { fallback, key } = options;
|
|
923
1008
|
const context = options.context || this.context;
|
|
924
1009
|
const contextStr = contextString(context);
|
|
925
1010
|
this.debug(`checkFlag: ${key}`, { context, fallback });
|
|
926
1011
|
if (this.isOffline()) {
|
|
1012
|
+
const resolvedFallbackResult = this.resolveFallbackCheckFlagReturn(
|
|
1013
|
+
key,
|
|
1014
|
+
fallback,
|
|
1015
|
+
"Offline mode - using initialization defaults"
|
|
1016
|
+
);
|
|
927
1017
|
this.debug(`checkFlag offline result: ${key}`, {
|
|
928
|
-
value:
|
|
1018
|
+
value: resolvedFallbackResult.value,
|
|
929
1019
|
offlineMode: true
|
|
930
1020
|
});
|
|
931
|
-
return
|
|
1021
|
+
return resolvedFallbackResult.value;
|
|
932
1022
|
}
|
|
933
1023
|
if (!this.useWebSocket) {
|
|
934
1024
|
const requestUrl = `${this.apiUrl}/flags/${key}/check`;
|
|
@@ -956,14 +1046,14 @@ var Schematic = class {
|
|
|
956
1046
|
return result.value;
|
|
957
1047
|
}).catch((error) => {
|
|
958
1048
|
console.error("There was a problem with the fetch operation:", error);
|
|
959
|
-
const errorResult =
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
error
|
|
964
|
-
|
|
1049
|
+
const errorResult = this.resolveFallbackCheckFlagReturn(
|
|
1050
|
+
key,
|
|
1051
|
+
fallback,
|
|
1052
|
+
"API request failed",
|
|
1053
|
+
error instanceof Error ? error.message : String(error)
|
|
1054
|
+
);
|
|
965
1055
|
this.submitFlagCheckEvent(key, errorResult, context);
|
|
966
|
-
return
|
|
1056
|
+
return errorResult.value;
|
|
967
1057
|
});
|
|
968
1058
|
}
|
|
969
1059
|
try {
|
|
@@ -973,7 +1063,7 @@ var Schematic = class {
|
|
|
973
1063
|
return existingVals[key].value;
|
|
974
1064
|
}
|
|
975
1065
|
if (this.isOffline()) {
|
|
976
|
-
return fallback;
|
|
1066
|
+
return this.resolveFallbackValue(key, fallback);
|
|
977
1067
|
}
|
|
978
1068
|
try {
|
|
979
1069
|
await this.setContext(context);
|
|
@@ -986,10 +1076,10 @@ var Schematic = class {
|
|
|
986
1076
|
}
|
|
987
1077
|
const contextVals = this.checks[contextStr] ?? {};
|
|
988
1078
|
const flagCheck = contextVals[key];
|
|
989
|
-
const result = flagCheck?.value ?? fallback;
|
|
1079
|
+
const result = flagCheck?.value ?? this.resolveFallbackValue(key, fallback);
|
|
990
1080
|
this.debug(
|
|
991
1081
|
`checkFlag WebSocket result: ${key}`,
|
|
992
|
-
typeof flagCheck !== "undefined" ? flagCheck : { value:
|
|
1082
|
+
typeof flagCheck !== "undefined" ? flagCheck : { value: result, fallbackUsed: true }
|
|
993
1083
|
);
|
|
994
1084
|
if (typeof flagCheck !== "undefined") {
|
|
995
1085
|
this.submitFlagCheckEvent(key, flagCheck, context);
|
|
@@ -997,14 +1087,14 @@ var Schematic = class {
|
|
|
997
1087
|
return result;
|
|
998
1088
|
} catch (error) {
|
|
999
1089
|
console.error("Unexpected error in checkFlag:", error);
|
|
1000
|
-
const errorResult =
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
error
|
|
1005
|
-
|
|
1090
|
+
const errorResult = this.resolveFallbackCheckFlagReturn(
|
|
1091
|
+
key,
|
|
1092
|
+
fallback,
|
|
1093
|
+
"Unexpected error in flag check",
|
|
1094
|
+
error instanceof Error ? error.message : String(error)
|
|
1095
|
+
);
|
|
1006
1096
|
this.submitFlagCheckEvent(key, errorResult, context);
|
|
1007
|
-
return
|
|
1097
|
+
return errorResult.value;
|
|
1008
1098
|
}
|
|
1009
1099
|
}
|
|
1010
1100
|
/**
|
|
@@ -1047,11 +1137,12 @@ var Schematic = class {
|
|
|
1047
1137
|
*/
|
|
1048
1138
|
async fallbackToRest(key, context, fallback) {
|
|
1049
1139
|
if (this.isOffline()) {
|
|
1140
|
+
const resolvedFallback = this.resolveFallbackValue(key, fallback);
|
|
1050
1141
|
this.debug(`fallbackToRest offline result: ${key}`, {
|
|
1051
|
-
value:
|
|
1142
|
+
value: resolvedFallback,
|
|
1052
1143
|
offlineMode: true
|
|
1053
1144
|
});
|
|
1054
|
-
return
|
|
1145
|
+
return resolvedFallback;
|
|
1055
1146
|
}
|
|
1056
1147
|
try {
|
|
1057
1148
|
const requestUrl = `${this.apiUrl}/flags/${key}/check`;
|
|
@@ -1078,14 +1169,14 @@ var Schematic = class {
|
|
|
1078
1169
|
return result.value;
|
|
1079
1170
|
} catch (error) {
|
|
1080
1171
|
console.error("REST API call failed, using fallback value:", error);
|
|
1081
|
-
const errorResult =
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
error
|
|
1086
|
-
|
|
1172
|
+
const errorResult = this.resolveFallbackCheckFlagReturn(
|
|
1173
|
+
key,
|
|
1174
|
+
fallback,
|
|
1175
|
+
"API request failed (fallback)",
|
|
1176
|
+
error instanceof Error ? error.message : String(error)
|
|
1177
|
+
);
|
|
1087
1178
|
this.submitFlagCheckEvent(key, errorResult, context);
|
|
1088
|
-
return
|
|
1179
|
+
return errorResult.value;
|
|
1089
1180
|
}
|
|
1090
1181
|
}
|
|
1091
1182
|
/**
|
|
@@ -1302,11 +1393,53 @@ var Schematic = class {
|
|
|
1302
1393
|
}
|
|
1303
1394
|
}
|
|
1304
1395
|
};
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1396
|
+
startRetryTimer = () => {
|
|
1397
|
+
if (this.retryTimer !== null) {
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
1400
|
+
this.retryTimer = setInterval(() => {
|
|
1401
|
+
this.flushEventQueue().catch((error) => {
|
|
1402
|
+
this.debug("Error in retry timer flush:", error);
|
|
1403
|
+
});
|
|
1404
|
+
if (this.eventQueue.length === 0) {
|
|
1405
|
+
this.stopRetryTimer();
|
|
1406
|
+
}
|
|
1407
|
+
}, 5e3);
|
|
1408
|
+
this.debug("Started retry timer");
|
|
1409
|
+
};
|
|
1410
|
+
stopRetryTimer = () => {
|
|
1411
|
+
if (this.retryTimer !== null) {
|
|
1412
|
+
clearInterval(this.retryTimer);
|
|
1413
|
+
this.retryTimer = null;
|
|
1414
|
+
this.debug("Stopped retry timer");
|
|
1415
|
+
}
|
|
1416
|
+
};
|
|
1417
|
+
flushEventQueue = async () => {
|
|
1418
|
+
if (this.eventQueue.length === 0) {
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1421
|
+
const now = Date.now();
|
|
1422
|
+
const readyEvents = [];
|
|
1423
|
+
const notReadyEvents = [];
|
|
1424
|
+
for (const event of this.eventQueue) {
|
|
1425
|
+
if (event.next_retry_at === void 0 || event.next_retry_at <= now) {
|
|
1426
|
+
readyEvents.push(event);
|
|
1427
|
+
} else {
|
|
1428
|
+
notReadyEvents.push(event);
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
if (readyEvents.length === 0) {
|
|
1432
|
+
this.debug(`No events ready for retry yet (${notReadyEvents.length} still in backoff)`);
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
this.debug(`Flushing event queue: ${readyEvents.length} ready, ${notReadyEvents.length} waiting`);
|
|
1436
|
+
this.eventQueue = notReadyEvents;
|
|
1437
|
+
for (const event of readyEvents) {
|
|
1438
|
+
try {
|
|
1439
|
+
await this.sendEvent(event);
|
|
1440
|
+
this.debug(`Queued event sent successfully:`, event.type);
|
|
1441
|
+
} catch (error) {
|
|
1442
|
+
this.debug(`Failed to send queued event:`, error);
|
|
1310
1443
|
}
|
|
1311
1444
|
}
|
|
1312
1445
|
};
|
|
@@ -1354,12 +1487,37 @@ var Schematic = class {
|
|
|
1354
1487
|
},
|
|
1355
1488
|
body: payload
|
|
1356
1489
|
});
|
|
1490
|
+
if (!response.ok) {
|
|
1491
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
1492
|
+
}
|
|
1357
1493
|
this.debug(`event sent:`, {
|
|
1358
1494
|
status: response.status,
|
|
1359
1495
|
statusText: response.statusText
|
|
1360
1496
|
});
|
|
1361
1497
|
} catch (error) {
|
|
1362
|
-
|
|
1498
|
+
const retryCount = (event.retry_count ?? 0) + 1;
|
|
1499
|
+
if (retryCount <= this.maxEventRetries) {
|
|
1500
|
+
this.debug(`Event failed to send (attempt ${retryCount}/${this.maxEventRetries}), queueing for retry:`, error);
|
|
1501
|
+
const baseDelay = this.eventRetryInitialDelay * Math.pow(2, retryCount - 1);
|
|
1502
|
+
const jitterDelay = Math.min(baseDelay, this.eventRetryMaxDelay);
|
|
1503
|
+
const nextRetryAt = Date.now() + jitterDelay;
|
|
1504
|
+
const retryEvent = {
|
|
1505
|
+
...event,
|
|
1506
|
+
retry_count: retryCount,
|
|
1507
|
+
next_retry_at: nextRetryAt
|
|
1508
|
+
};
|
|
1509
|
+
if (this.eventQueue.length < this.maxEventQueueSize) {
|
|
1510
|
+
this.eventQueue.push(retryEvent);
|
|
1511
|
+
this.debug(`Event queued for retry in ${jitterDelay}ms (${this.eventQueue.length}/${this.maxEventQueueSize})`);
|
|
1512
|
+
} else {
|
|
1513
|
+
this.debug(`Event queue full (${this.maxEventQueueSize}), dropping oldest event`);
|
|
1514
|
+
this.eventQueue.shift();
|
|
1515
|
+
this.eventQueue.push(retryEvent);
|
|
1516
|
+
}
|
|
1517
|
+
this.startRetryTimer();
|
|
1518
|
+
} else {
|
|
1519
|
+
this.debug(`Event failed permanently after ${this.maxEventRetries} attempts, dropping:`, error);
|
|
1520
|
+
}
|
|
1363
1521
|
}
|
|
1364
1522
|
return Promise.resolve();
|
|
1365
1523
|
};
|
|
@@ -1384,6 +1542,7 @@ var Schematic = class {
|
|
|
1384
1542
|
clearTimeout(this.wsReconnectTimer);
|
|
1385
1543
|
this.wsReconnectTimer = null;
|
|
1386
1544
|
}
|
|
1545
|
+
this.stopRetryTimer();
|
|
1387
1546
|
if (this.conn) {
|
|
1388
1547
|
try {
|
|
1389
1548
|
const socket = await this.conn;
|
|
@@ -1431,16 +1590,15 @@ var Schematic = class {
|
|
|
1431
1590
|
* Handle browser coming back online
|
|
1432
1591
|
*/
|
|
1433
1592
|
handleNetworkOnline = () => {
|
|
1434
|
-
|
|
1435
|
-
this.debug("No context set, skipping reconnection");
|
|
1436
|
-
return;
|
|
1437
|
-
}
|
|
1593
|
+
this.debug("Network online, attempting reconnection and flushing queued events");
|
|
1438
1594
|
this.wsReconnectAttempts = 0;
|
|
1439
1595
|
if (this.wsReconnectTimer !== null) {
|
|
1440
1596
|
clearTimeout(this.wsReconnectTimer);
|
|
1441
1597
|
this.wsReconnectTimer = null;
|
|
1442
1598
|
}
|
|
1443
|
-
this.
|
|
1599
|
+
this.flushEventQueue().catch((error) => {
|
|
1600
|
+
this.debug("Error flushing event queue on network online:", error);
|
|
1601
|
+
});
|
|
1444
1602
|
this.attemptReconnect();
|
|
1445
1603
|
};
|
|
1446
1604
|
/**
|
|
@@ -1470,10 +1628,20 @@ var Schematic = class {
|
|
|
1470
1628
|
try {
|
|
1471
1629
|
this.conn = this.wsConnect();
|
|
1472
1630
|
const socket = await this.conn;
|
|
1631
|
+
this.debug(`Reconnection context check:`, {
|
|
1632
|
+
hasCompany: this.context.company !== void 0,
|
|
1633
|
+
hasUser: this.context.user !== void 0,
|
|
1634
|
+
context: this.context
|
|
1635
|
+
});
|
|
1473
1636
|
if (this.context.company !== void 0 || this.context.user !== void 0) {
|
|
1474
|
-
this.debug(`Reconnected, re-sending context`);
|
|
1475
|
-
await this.
|
|
1637
|
+
this.debug(`Reconnected, force re-sending context`);
|
|
1638
|
+
await this.wsSendContextAfterReconnection(socket, this.context);
|
|
1639
|
+
} else {
|
|
1640
|
+
this.debug(`No context to re-send after reconnection - websocket ready for new context`);
|
|
1476
1641
|
}
|
|
1642
|
+
this.flushEventQueue().catch((error) => {
|
|
1643
|
+
this.debug("Error flushing event queue after websocket reconnection:", error);
|
|
1644
|
+
});
|
|
1477
1645
|
this.debug(`Reconnection successful`);
|
|
1478
1646
|
} catch (error) {
|
|
1479
1647
|
this.debug(`Reconnection attempt failed:`, error);
|
|
@@ -1534,6 +1702,61 @@ var Schematic = class {
|
|
|
1534
1702
|
};
|
|
1535
1703
|
});
|
|
1536
1704
|
};
|
|
1705
|
+
// Send a message on the websocket after reconnection, forcing the send even if context appears unchanged
|
|
1706
|
+
// because the server has lost all state and needs the initial context
|
|
1707
|
+
wsSendContextAfterReconnection = (socket, context) => {
|
|
1708
|
+
if (this.isOffline()) {
|
|
1709
|
+
this.debug("wsSendContextAfterReconnection: skipped (offline mode)");
|
|
1710
|
+
this.setIsPending(false);
|
|
1711
|
+
return Promise.resolve();
|
|
1712
|
+
}
|
|
1713
|
+
return new Promise((resolve) => {
|
|
1714
|
+
this.debug(`WebSocket force sending context after reconnection:`, context);
|
|
1715
|
+
this.context = context;
|
|
1716
|
+
const sendMessage = () => {
|
|
1717
|
+
let resolved = false;
|
|
1718
|
+
const messageHandler = (event) => {
|
|
1719
|
+
const message = JSON.parse(event.data);
|
|
1720
|
+
this.debug(`WebSocket message received after reconnection:`, message);
|
|
1721
|
+
if (!(contextString(context) in this.checks)) {
|
|
1722
|
+
this.checks[contextString(context)] = {};
|
|
1723
|
+
}
|
|
1724
|
+
(message.flags ?? []).forEach((flag) => {
|
|
1725
|
+
const flagCheck = CheckFlagReturnFromJSON(flag);
|
|
1726
|
+
const contextStr = contextString(context);
|
|
1727
|
+
if (this.checks[contextStr] === void 0) {
|
|
1728
|
+
this.checks[contextStr] = {};
|
|
1729
|
+
}
|
|
1730
|
+
this.checks[contextStr][flagCheck.flag] = flagCheck;
|
|
1731
|
+
});
|
|
1732
|
+
this.useWebSocket = true;
|
|
1733
|
+
socket.removeEventListener("message", messageHandler);
|
|
1734
|
+
if (!resolved) {
|
|
1735
|
+
resolved = true;
|
|
1736
|
+
resolve(this.setIsPending(false));
|
|
1737
|
+
}
|
|
1738
|
+
};
|
|
1739
|
+
socket.addEventListener("message", messageHandler);
|
|
1740
|
+
const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
|
|
1741
|
+
const messagePayload = {
|
|
1742
|
+
apiKey: this.apiKey,
|
|
1743
|
+
clientVersion,
|
|
1744
|
+
data: context
|
|
1745
|
+
};
|
|
1746
|
+
this.debug(`WebSocket sending forced message after reconnection:`, messagePayload);
|
|
1747
|
+
socket.send(JSON.stringify(messagePayload));
|
|
1748
|
+
};
|
|
1749
|
+
if (socket.readyState === WebSocket.OPEN) {
|
|
1750
|
+
this.debug(`WebSocket already open, sending forced message after reconnection`);
|
|
1751
|
+
sendMessage();
|
|
1752
|
+
} else {
|
|
1753
|
+
socket.addEventListener("open", () => {
|
|
1754
|
+
this.debug(`WebSocket opened, sending forced message after reconnection`);
|
|
1755
|
+
sendMessage();
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1758
|
+
});
|
|
1759
|
+
};
|
|
1537
1760
|
// Send a message on the websocket indicating interest in a particular evaluation context
|
|
1538
1761
|
// and wait for the initial set of flag values to be returned
|
|
1539
1762
|
wsSendMessage = (socket, context) => {
|
|
@@ -1722,7 +1945,7 @@ var notifyFlagValueListener = (listener, value) => {
|
|
|
1722
1945
|
var import_react = __toESM(require("react"));
|
|
1723
1946
|
|
|
1724
1947
|
// src/version.ts
|
|
1725
|
-
var version2 = "1.2.
|
|
1948
|
+
var version2 = "1.2.9";
|
|
1726
1949
|
|
|
1727
1950
|
// src/context/schematic.tsx
|
|
1728
1951
|
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.9";
|
|
758
758
|
var anonymousIdKey = "schematicId";
|
|
759
759
|
var Schematic = class {
|
|
760
760
|
additionalHeaders = {};
|
|
@@ -784,6 +784,17 @@ var Schematic = class {
|
|
|
784
784
|
wsReconnectAttempts = 0;
|
|
785
785
|
wsReconnectTimer = null;
|
|
786
786
|
wsIntentionalDisconnect = false;
|
|
787
|
+
maxEventQueueSize = 100;
|
|
788
|
+
// Prevent memory issues with very long network outages
|
|
789
|
+
maxEventRetries = 5;
|
|
790
|
+
// Maximum retry attempts for failed events
|
|
791
|
+
eventRetryInitialDelay = 1e3;
|
|
792
|
+
// Initial retry delay in ms
|
|
793
|
+
eventRetryMaxDelay = 3e4;
|
|
794
|
+
// Maximum retry delay in ms
|
|
795
|
+
retryTimer = null;
|
|
796
|
+
flagValueDefaults = {};
|
|
797
|
+
flagCheckDefaults = {};
|
|
787
798
|
constructor(apiKey, options) {
|
|
788
799
|
this.apiKey = apiKey;
|
|
789
800
|
this.eventQueue = [];
|
|
@@ -842,6 +853,24 @@ var Schematic = class {
|
|
|
842
853
|
if (options?.webSocketMaxRetryDelay !== void 0) {
|
|
843
854
|
this.webSocketMaxRetryDelay = options.webSocketMaxRetryDelay;
|
|
844
855
|
}
|
|
856
|
+
if (options?.maxEventQueueSize !== void 0) {
|
|
857
|
+
this.maxEventQueueSize = options.maxEventQueueSize;
|
|
858
|
+
}
|
|
859
|
+
if (options?.maxEventRetries !== void 0) {
|
|
860
|
+
this.maxEventRetries = options.maxEventRetries;
|
|
861
|
+
}
|
|
862
|
+
if (options?.eventRetryInitialDelay !== void 0) {
|
|
863
|
+
this.eventRetryInitialDelay = options.eventRetryInitialDelay;
|
|
864
|
+
}
|
|
865
|
+
if (options?.eventRetryMaxDelay !== void 0) {
|
|
866
|
+
this.eventRetryMaxDelay = options.eventRetryMaxDelay;
|
|
867
|
+
}
|
|
868
|
+
if (options?.flagValueDefaults !== void 0) {
|
|
869
|
+
this.flagValueDefaults = options.flagValueDefaults;
|
|
870
|
+
}
|
|
871
|
+
if (options?.flagCheckDefaults !== void 0) {
|
|
872
|
+
this.flagCheckDefaults = options.flagCheckDefaults;
|
|
873
|
+
}
|
|
845
874
|
if (typeof window !== "undefined" && window?.addEventListener) {
|
|
846
875
|
window.addEventListener("beforeunload", () => {
|
|
847
876
|
this.flushEventQueue();
|
|
@@ -866,6 +895,62 @@ var Schematic = class {
|
|
|
866
895
|
this.debug("Initialized with debug mode enabled");
|
|
867
896
|
}
|
|
868
897
|
}
|
|
898
|
+
/**
|
|
899
|
+
* Resolve fallback value according to priority order:
|
|
900
|
+
* 1. Callsite fallback value (if provided)
|
|
901
|
+
* 2. Initialization fallback value (flagValueDefaults)
|
|
902
|
+
* 3. Default to false
|
|
903
|
+
*/
|
|
904
|
+
resolveFallbackValue(key, callsiteFallback) {
|
|
905
|
+
if (callsiteFallback !== void 0) {
|
|
906
|
+
return callsiteFallback;
|
|
907
|
+
}
|
|
908
|
+
if (key in this.flagValueDefaults) {
|
|
909
|
+
return this.flagValueDefaults[key];
|
|
910
|
+
}
|
|
911
|
+
return false;
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* Resolve complete CheckFlagReturn object according to priority order:
|
|
915
|
+
* 1. Use callsite fallback for boolean value, construct CheckFlagReturn
|
|
916
|
+
* 2. Use flagCheckDefaults if available for this flag
|
|
917
|
+
* 3. Use flagValueDefaults if available for this flag, construct CheckFlagReturn
|
|
918
|
+
* 4. Default CheckFlagReturn with value: false
|
|
919
|
+
*/
|
|
920
|
+
resolveFallbackCheckFlagReturn(key, callsiteFallback, reason = "Fallback value used", error) {
|
|
921
|
+
if (callsiteFallback !== void 0) {
|
|
922
|
+
return {
|
|
923
|
+
flag: key,
|
|
924
|
+
value: callsiteFallback,
|
|
925
|
+
reason,
|
|
926
|
+
error
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
if (key in this.flagCheckDefaults) {
|
|
930
|
+
const defaultReturn = this.flagCheckDefaults[key];
|
|
931
|
+
return {
|
|
932
|
+
...defaultReturn,
|
|
933
|
+
flag: key,
|
|
934
|
+
// Ensure flag matches the requested key
|
|
935
|
+
reason: error !== void 0 ? reason : defaultReturn.reason,
|
|
936
|
+
error
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
if (key in this.flagValueDefaults) {
|
|
940
|
+
return {
|
|
941
|
+
flag: key,
|
|
942
|
+
value: this.flagValueDefaults[key],
|
|
943
|
+
reason,
|
|
944
|
+
error
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
return {
|
|
948
|
+
flag: key,
|
|
949
|
+
value: false,
|
|
950
|
+
reason,
|
|
951
|
+
error
|
|
952
|
+
};
|
|
953
|
+
}
|
|
869
954
|
/**
|
|
870
955
|
* Get value for a single flag.
|
|
871
956
|
* In WebSocket mode, returns cached values if connection is active, otherwise establishes
|
|
@@ -874,16 +959,21 @@ var Schematic = class {
|
|
|
874
959
|
* In REST mode, makes an API call for each check.
|
|
875
960
|
*/
|
|
876
961
|
async checkFlag(options) {
|
|
877
|
-
const { fallback
|
|
962
|
+
const { fallback, key } = options;
|
|
878
963
|
const context = options.context || this.context;
|
|
879
964
|
const contextStr = contextString(context);
|
|
880
965
|
this.debug(`checkFlag: ${key}`, { context, fallback });
|
|
881
966
|
if (this.isOffline()) {
|
|
967
|
+
const resolvedFallbackResult = this.resolveFallbackCheckFlagReturn(
|
|
968
|
+
key,
|
|
969
|
+
fallback,
|
|
970
|
+
"Offline mode - using initialization defaults"
|
|
971
|
+
);
|
|
882
972
|
this.debug(`checkFlag offline result: ${key}`, {
|
|
883
|
-
value:
|
|
973
|
+
value: resolvedFallbackResult.value,
|
|
884
974
|
offlineMode: true
|
|
885
975
|
});
|
|
886
|
-
return
|
|
976
|
+
return resolvedFallbackResult.value;
|
|
887
977
|
}
|
|
888
978
|
if (!this.useWebSocket) {
|
|
889
979
|
const requestUrl = `${this.apiUrl}/flags/${key}/check`;
|
|
@@ -911,14 +1001,14 @@ var Schematic = class {
|
|
|
911
1001
|
return result.value;
|
|
912
1002
|
}).catch((error) => {
|
|
913
1003
|
console.error("There was a problem with the fetch operation:", error);
|
|
914
|
-
const errorResult =
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
error
|
|
919
|
-
|
|
1004
|
+
const errorResult = this.resolveFallbackCheckFlagReturn(
|
|
1005
|
+
key,
|
|
1006
|
+
fallback,
|
|
1007
|
+
"API request failed",
|
|
1008
|
+
error instanceof Error ? error.message : String(error)
|
|
1009
|
+
);
|
|
920
1010
|
this.submitFlagCheckEvent(key, errorResult, context);
|
|
921
|
-
return
|
|
1011
|
+
return errorResult.value;
|
|
922
1012
|
});
|
|
923
1013
|
}
|
|
924
1014
|
try {
|
|
@@ -928,7 +1018,7 @@ var Schematic = class {
|
|
|
928
1018
|
return existingVals[key].value;
|
|
929
1019
|
}
|
|
930
1020
|
if (this.isOffline()) {
|
|
931
|
-
return fallback;
|
|
1021
|
+
return this.resolveFallbackValue(key, fallback);
|
|
932
1022
|
}
|
|
933
1023
|
try {
|
|
934
1024
|
await this.setContext(context);
|
|
@@ -941,10 +1031,10 @@ var Schematic = class {
|
|
|
941
1031
|
}
|
|
942
1032
|
const contextVals = this.checks[contextStr] ?? {};
|
|
943
1033
|
const flagCheck = contextVals[key];
|
|
944
|
-
const result = flagCheck?.value ?? fallback;
|
|
1034
|
+
const result = flagCheck?.value ?? this.resolveFallbackValue(key, fallback);
|
|
945
1035
|
this.debug(
|
|
946
1036
|
`checkFlag WebSocket result: ${key}`,
|
|
947
|
-
typeof flagCheck !== "undefined" ? flagCheck : { value:
|
|
1037
|
+
typeof flagCheck !== "undefined" ? flagCheck : { value: result, fallbackUsed: true }
|
|
948
1038
|
);
|
|
949
1039
|
if (typeof flagCheck !== "undefined") {
|
|
950
1040
|
this.submitFlagCheckEvent(key, flagCheck, context);
|
|
@@ -952,14 +1042,14 @@ var Schematic = class {
|
|
|
952
1042
|
return result;
|
|
953
1043
|
} catch (error) {
|
|
954
1044
|
console.error("Unexpected error in checkFlag:", error);
|
|
955
|
-
const errorResult =
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
error
|
|
960
|
-
|
|
1045
|
+
const errorResult = this.resolveFallbackCheckFlagReturn(
|
|
1046
|
+
key,
|
|
1047
|
+
fallback,
|
|
1048
|
+
"Unexpected error in flag check",
|
|
1049
|
+
error instanceof Error ? error.message : String(error)
|
|
1050
|
+
);
|
|
961
1051
|
this.submitFlagCheckEvent(key, errorResult, context);
|
|
962
|
-
return
|
|
1052
|
+
return errorResult.value;
|
|
963
1053
|
}
|
|
964
1054
|
}
|
|
965
1055
|
/**
|
|
@@ -1002,11 +1092,12 @@ var Schematic = class {
|
|
|
1002
1092
|
*/
|
|
1003
1093
|
async fallbackToRest(key, context, fallback) {
|
|
1004
1094
|
if (this.isOffline()) {
|
|
1095
|
+
const resolvedFallback = this.resolveFallbackValue(key, fallback);
|
|
1005
1096
|
this.debug(`fallbackToRest offline result: ${key}`, {
|
|
1006
|
-
value:
|
|
1097
|
+
value: resolvedFallback,
|
|
1007
1098
|
offlineMode: true
|
|
1008
1099
|
});
|
|
1009
|
-
return
|
|
1100
|
+
return resolvedFallback;
|
|
1010
1101
|
}
|
|
1011
1102
|
try {
|
|
1012
1103
|
const requestUrl = `${this.apiUrl}/flags/${key}/check`;
|
|
@@ -1033,14 +1124,14 @@ var Schematic = class {
|
|
|
1033
1124
|
return result.value;
|
|
1034
1125
|
} catch (error) {
|
|
1035
1126
|
console.error("REST API call failed, using fallback value:", error);
|
|
1036
|
-
const errorResult =
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
error
|
|
1041
|
-
|
|
1127
|
+
const errorResult = this.resolveFallbackCheckFlagReturn(
|
|
1128
|
+
key,
|
|
1129
|
+
fallback,
|
|
1130
|
+
"API request failed (fallback)",
|
|
1131
|
+
error instanceof Error ? error.message : String(error)
|
|
1132
|
+
);
|
|
1042
1133
|
this.submitFlagCheckEvent(key, errorResult, context);
|
|
1043
|
-
return
|
|
1134
|
+
return errorResult.value;
|
|
1044
1135
|
}
|
|
1045
1136
|
}
|
|
1046
1137
|
/**
|
|
@@ -1257,11 +1348,53 @@ var Schematic = class {
|
|
|
1257
1348
|
}
|
|
1258
1349
|
}
|
|
1259
1350
|
};
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1351
|
+
startRetryTimer = () => {
|
|
1352
|
+
if (this.retryTimer !== null) {
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
this.retryTimer = setInterval(() => {
|
|
1356
|
+
this.flushEventQueue().catch((error) => {
|
|
1357
|
+
this.debug("Error in retry timer flush:", error);
|
|
1358
|
+
});
|
|
1359
|
+
if (this.eventQueue.length === 0) {
|
|
1360
|
+
this.stopRetryTimer();
|
|
1361
|
+
}
|
|
1362
|
+
}, 5e3);
|
|
1363
|
+
this.debug("Started retry timer");
|
|
1364
|
+
};
|
|
1365
|
+
stopRetryTimer = () => {
|
|
1366
|
+
if (this.retryTimer !== null) {
|
|
1367
|
+
clearInterval(this.retryTimer);
|
|
1368
|
+
this.retryTimer = null;
|
|
1369
|
+
this.debug("Stopped retry timer");
|
|
1370
|
+
}
|
|
1371
|
+
};
|
|
1372
|
+
flushEventQueue = async () => {
|
|
1373
|
+
if (this.eventQueue.length === 0) {
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
const now = Date.now();
|
|
1377
|
+
const readyEvents = [];
|
|
1378
|
+
const notReadyEvents = [];
|
|
1379
|
+
for (const event of this.eventQueue) {
|
|
1380
|
+
if (event.next_retry_at === void 0 || event.next_retry_at <= now) {
|
|
1381
|
+
readyEvents.push(event);
|
|
1382
|
+
} else {
|
|
1383
|
+
notReadyEvents.push(event);
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
if (readyEvents.length === 0) {
|
|
1387
|
+
this.debug(`No events ready for retry yet (${notReadyEvents.length} still in backoff)`);
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
this.debug(`Flushing event queue: ${readyEvents.length} ready, ${notReadyEvents.length} waiting`);
|
|
1391
|
+
this.eventQueue = notReadyEvents;
|
|
1392
|
+
for (const event of readyEvents) {
|
|
1393
|
+
try {
|
|
1394
|
+
await this.sendEvent(event);
|
|
1395
|
+
this.debug(`Queued event sent successfully:`, event.type);
|
|
1396
|
+
} catch (error) {
|
|
1397
|
+
this.debug(`Failed to send queued event:`, error);
|
|
1265
1398
|
}
|
|
1266
1399
|
}
|
|
1267
1400
|
};
|
|
@@ -1309,12 +1442,37 @@ var Schematic = class {
|
|
|
1309
1442
|
},
|
|
1310
1443
|
body: payload
|
|
1311
1444
|
});
|
|
1445
|
+
if (!response.ok) {
|
|
1446
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
1447
|
+
}
|
|
1312
1448
|
this.debug(`event sent:`, {
|
|
1313
1449
|
status: response.status,
|
|
1314
1450
|
statusText: response.statusText
|
|
1315
1451
|
});
|
|
1316
1452
|
} catch (error) {
|
|
1317
|
-
|
|
1453
|
+
const retryCount = (event.retry_count ?? 0) + 1;
|
|
1454
|
+
if (retryCount <= this.maxEventRetries) {
|
|
1455
|
+
this.debug(`Event failed to send (attempt ${retryCount}/${this.maxEventRetries}), queueing for retry:`, error);
|
|
1456
|
+
const baseDelay = this.eventRetryInitialDelay * Math.pow(2, retryCount - 1);
|
|
1457
|
+
const jitterDelay = Math.min(baseDelay, this.eventRetryMaxDelay);
|
|
1458
|
+
const nextRetryAt = Date.now() + jitterDelay;
|
|
1459
|
+
const retryEvent = {
|
|
1460
|
+
...event,
|
|
1461
|
+
retry_count: retryCount,
|
|
1462
|
+
next_retry_at: nextRetryAt
|
|
1463
|
+
};
|
|
1464
|
+
if (this.eventQueue.length < this.maxEventQueueSize) {
|
|
1465
|
+
this.eventQueue.push(retryEvent);
|
|
1466
|
+
this.debug(`Event queued for retry in ${jitterDelay}ms (${this.eventQueue.length}/${this.maxEventQueueSize})`);
|
|
1467
|
+
} else {
|
|
1468
|
+
this.debug(`Event queue full (${this.maxEventQueueSize}), dropping oldest event`);
|
|
1469
|
+
this.eventQueue.shift();
|
|
1470
|
+
this.eventQueue.push(retryEvent);
|
|
1471
|
+
}
|
|
1472
|
+
this.startRetryTimer();
|
|
1473
|
+
} else {
|
|
1474
|
+
this.debug(`Event failed permanently after ${this.maxEventRetries} attempts, dropping:`, error);
|
|
1475
|
+
}
|
|
1318
1476
|
}
|
|
1319
1477
|
return Promise.resolve();
|
|
1320
1478
|
};
|
|
@@ -1339,6 +1497,7 @@ var Schematic = class {
|
|
|
1339
1497
|
clearTimeout(this.wsReconnectTimer);
|
|
1340
1498
|
this.wsReconnectTimer = null;
|
|
1341
1499
|
}
|
|
1500
|
+
this.stopRetryTimer();
|
|
1342
1501
|
if (this.conn) {
|
|
1343
1502
|
try {
|
|
1344
1503
|
const socket = await this.conn;
|
|
@@ -1386,16 +1545,15 @@ var Schematic = class {
|
|
|
1386
1545
|
* Handle browser coming back online
|
|
1387
1546
|
*/
|
|
1388
1547
|
handleNetworkOnline = () => {
|
|
1389
|
-
|
|
1390
|
-
this.debug("No context set, skipping reconnection");
|
|
1391
|
-
return;
|
|
1392
|
-
}
|
|
1548
|
+
this.debug("Network online, attempting reconnection and flushing queued events");
|
|
1393
1549
|
this.wsReconnectAttempts = 0;
|
|
1394
1550
|
if (this.wsReconnectTimer !== null) {
|
|
1395
1551
|
clearTimeout(this.wsReconnectTimer);
|
|
1396
1552
|
this.wsReconnectTimer = null;
|
|
1397
1553
|
}
|
|
1398
|
-
this.
|
|
1554
|
+
this.flushEventQueue().catch((error) => {
|
|
1555
|
+
this.debug("Error flushing event queue on network online:", error);
|
|
1556
|
+
});
|
|
1399
1557
|
this.attemptReconnect();
|
|
1400
1558
|
};
|
|
1401
1559
|
/**
|
|
@@ -1425,10 +1583,20 @@ var Schematic = class {
|
|
|
1425
1583
|
try {
|
|
1426
1584
|
this.conn = this.wsConnect();
|
|
1427
1585
|
const socket = await this.conn;
|
|
1586
|
+
this.debug(`Reconnection context check:`, {
|
|
1587
|
+
hasCompany: this.context.company !== void 0,
|
|
1588
|
+
hasUser: this.context.user !== void 0,
|
|
1589
|
+
context: this.context
|
|
1590
|
+
});
|
|
1428
1591
|
if (this.context.company !== void 0 || this.context.user !== void 0) {
|
|
1429
|
-
this.debug(`Reconnected, re-sending context`);
|
|
1430
|
-
await this.
|
|
1592
|
+
this.debug(`Reconnected, force re-sending context`);
|
|
1593
|
+
await this.wsSendContextAfterReconnection(socket, this.context);
|
|
1594
|
+
} else {
|
|
1595
|
+
this.debug(`No context to re-send after reconnection - websocket ready for new context`);
|
|
1431
1596
|
}
|
|
1597
|
+
this.flushEventQueue().catch((error) => {
|
|
1598
|
+
this.debug("Error flushing event queue after websocket reconnection:", error);
|
|
1599
|
+
});
|
|
1432
1600
|
this.debug(`Reconnection successful`);
|
|
1433
1601
|
} catch (error) {
|
|
1434
1602
|
this.debug(`Reconnection attempt failed:`, error);
|
|
@@ -1489,6 +1657,61 @@ var Schematic = class {
|
|
|
1489
1657
|
};
|
|
1490
1658
|
});
|
|
1491
1659
|
};
|
|
1660
|
+
// Send a message on the websocket after reconnection, forcing the send even if context appears unchanged
|
|
1661
|
+
// because the server has lost all state and needs the initial context
|
|
1662
|
+
wsSendContextAfterReconnection = (socket, context) => {
|
|
1663
|
+
if (this.isOffline()) {
|
|
1664
|
+
this.debug("wsSendContextAfterReconnection: skipped (offline mode)");
|
|
1665
|
+
this.setIsPending(false);
|
|
1666
|
+
return Promise.resolve();
|
|
1667
|
+
}
|
|
1668
|
+
return new Promise((resolve) => {
|
|
1669
|
+
this.debug(`WebSocket force sending context after reconnection:`, context);
|
|
1670
|
+
this.context = context;
|
|
1671
|
+
const sendMessage = () => {
|
|
1672
|
+
let resolved = false;
|
|
1673
|
+
const messageHandler = (event) => {
|
|
1674
|
+
const message = JSON.parse(event.data);
|
|
1675
|
+
this.debug(`WebSocket message received after reconnection:`, message);
|
|
1676
|
+
if (!(contextString(context) in this.checks)) {
|
|
1677
|
+
this.checks[contextString(context)] = {};
|
|
1678
|
+
}
|
|
1679
|
+
(message.flags ?? []).forEach((flag) => {
|
|
1680
|
+
const flagCheck = CheckFlagReturnFromJSON(flag);
|
|
1681
|
+
const contextStr = contextString(context);
|
|
1682
|
+
if (this.checks[contextStr] === void 0) {
|
|
1683
|
+
this.checks[contextStr] = {};
|
|
1684
|
+
}
|
|
1685
|
+
this.checks[contextStr][flagCheck.flag] = flagCheck;
|
|
1686
|
+
});
|
|
1687
|
+
this.useWebSocket = true;
|
|
1688
|
+
socket.removeEventListener("message", messageHandler);
|
|
1689
|
+
if (!resolved) {
|
|
1690
|
+
resolved = true;
|
|
1691
|
+
resolve(this.setIsPending(false));
|
|
1692
|
+
}
|
|
1693
|
+
};
|
|
1694
|
+
socket.addEventListener("message", messageHandler);
|
|
1695
|
+
const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
|
|
1696
|
+
const messagePayload = {
|
|
1697
|
+
apiKey: this.apiKey,
|
|
1698
|
+
clientVersion,
|
|
1699
|
+
data: context
|
|
1700
|
+
};
|
|
1701
|
+
this.debug(`WebSocket sending forced message after reconnection:`, messagePayload);
|
|
1702
|
+
socket.send(JSON.stringify(messagePayload));
|
|
1703
|
+
};
|
|
1704
|
+
if (socket.readyState === WebSocket.OPEN) {
|
|
1705
|
+
this.debug(`WebSocket already open, sending forced message after reconnection`);
|
|
1706
|
+
sendMessage();
|
|
1707
|
+
} else {
|
|
1708
|
+
socket.addEventListener("open", () => {
|
|
1709
|
+
this.debug(`WebSocket opened, sending forced message after reconnection`);
|
|
1710
|
+
sendMessage();
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1713
|
+
});
|
|
1714
|
+
};
|
|
1492
1715
|
// Send a message on the websocket indicating interest in a particular evaluation context
|
|
1493
1716
|
// and wait for the initial set of flag values to be returned
|
|
1494
1717
|
wsSendMessage = (socket, context) => {
|
|
@@ -1677,7 +1900,7 @@ var notifyFlagValueListener = (listener, value) => {
|
|
|
1677
1900
|
import React, { createContext, useEffect, useMemo, useRef } from "react";
|
|
1678
1901
|
|
|
1679
1902
|
// src/version.ts
|
|
1680
|
-
var version2 = "1.2.
|
|
1903
|
+
var version2 = "1.2.9";
|
|
1681
1904
|
|
|
1682
1905
|
// src/context/schematic.tsx
|
|
1683
1906
|
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.9",
|
|
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.9"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@eslint/js": "^9.39.1",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@testing-library/dom": "^10.4.1",
|
|
40
40
|
"@testing-library/jest-dom": "^6.9.1",
|
|
41
41
|
"@testing-library/react": "^16.3.0",
|
|
42
|
-
"@types/react": "^19.2.
|
|
42
|
+
"@types/react": "^19.2.6",
|
|
43
43
|
"@vitest/browser": "^4.0.8",
|
|
44
44
|
"esbuild": "^0.27.0",
|
|
45
45
|
"eslint": "^9.39.1",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"react": "^19.2.0",
|
|
55
55
|
"react-dom": "^19.2.0",
|
|
56
56
|
"typescript": "^5.9.3",
|
|
57
|
-
"typescript-eslint": "^8.
|
|
57
|
+
"typescript-eslint": "^8.47.0",
|
|
58
58
|
"vitest": "^4.0.8"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|