@launchdarkly/js-sdk-common 2.15.0 → 2.17.0
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/CHANGELOG.md +14 -0
- package/dist/cjs/api/index.d.ts +1 -0
- package/dist/cjs/api/index.d.ts.map +1 -1
- package/dist/cjs/api/integrations/index.d.ts +2 -0
- package/dist/cjs/api/integrations/index.d.ts.map +1 -0
- package/dist/cjs/api/integrations/plugins.d.ts +128 -0
- package/dist/cjs/api/integrations/plugins.d.ts.map +1 -0
- package/dist/cjs/api/platform/EventSource.d.ts +8 -2
- package/dist/cjs/api/platform/EventSource.d.ts.map +1 -1
- package/dist/cjs/api/subsystem/DataSystem/CallbackHandler.d.ts +16 -0
- package/dist/cjs/api/subsystem/DataSystem/CallbackHandler.d.ts.map +1 -0
- package/dist/cjs/api/subsystem/DataSystem/DataSource.d.ts +21 -0
- package/dist/cjs/api/subsystem/DataSystem/DataSource.d.ts.map +1 -0
- package/dist/cjs/api/subsystem/DataSystem/index.d.ts +2 -0
- package/dist/cjs/api/subsystem/DataSystem/index.d.ts.map +1 -0
- package/dist/cjs/api/subsystem/index.d.ts +2 -1
- package/dist/cjs/api/subsystem/index.d.ts.map +1 -1
- package/dist/cjs/datasource/Backoff.d.ts +46 -0
- package/dist/cjs/datasource/Backoff.d.ts.map +1 -0
- package/dist/cjs/datasource/CompositeDataSource.d.ts +62 -0
- package/dist/cjs/datasource/CompositeDataSource.d.ts.map +1 -0
- package/dist/cjs/datasource/dataSourceList.d.ts +47 -0
- package/dist/cjs/datasource/dataSourceList.d.ts.map +1 -0
- package/dist/cjs/datasource/index.d.ts +3 -1
- package/dist/cjs/datasource/index.d.ts.map +1 -1
- package/dist/cjs/index.cjs +665 -36
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/internal/fdv2/index.d.ts +3 -2
- package/dist/cjs/internal/fdv2/index.d.ts.map +1 -1
- package/dist/cjs/internal/fdv2/{payloadReader.d.ts → payloadProcessor.d.ts} +27 -16
- package/dist/cjs/internal/fdv2/payloadProcessor.d.ts.map +1 -0
- package/dist/cjs/internal/fdv2/payloadStreamReader.d.ts +31 -0
- package/dist/cjs/internal/fdv2/payloadStreamReader.d.ts.map +1 -0
- package/dist/cjs/internal/index.d.ts +2 -0
- package/dist/cjs/internal/index.d.ts.map +1 -1
- package/dist/cjs/internal/metadata/InitMetadata.d.ts +18 -0
- package/dist/cjs/internal/metadata/InitMetadata.d.ts.map +1 -0
- package/dist/cjs/internal/metadata/index.d.ts +3 -0
- package/dist/cjs/internal/metadata/index.d.ts.map +1 -0
- package/dist/cjs/internal/plugins/index.d.ts +4 -0
- package/dist/cjs/internal/plugins/index.d.ts.map +1 -0
- package/dist/cjs/internal/plugins/safeGetHooks.d.ts +4 -0
- package/dist/cjs/internal/plugins/safeGetHooks.d.ts.map +1 -0
- package/dist/cjs/internal/plugins/safeGetName.d.ts +4 -0
- package/dist/cjs/internal/plugins/safeGetName.d.ts.map +1 -0
- package/dist/cjs/internal/plugins/safeRegisterPlugins.d.ts +4 -0
- package/dist/cjs/internal/plugins/safeRegisterPlugins.d.ts.map +1 -0
- package/dist/esm/api/index.d.ts +1 -0
- package/dist/esm/api/index.d.ts.map +1 -1
- package/dist/esm/api/integrations/index.d.ts +2 -0
- package/dist/esm/api/integrations/index.d.ts.map +1 -0
- package/dist/esm/api/integrations/plugins.d.ts +128 -0
- package/dist/esm/api/integrations/plugins.d.ts.map +1 -0
- package/dist/esm/api/platform/EventSource.d.ts +8 -2
- package/dist/esm/api/platform/EventSource.d.ts.map +1 -1
- package/dist/esm/api/subsystem/DataSystem/CallbackHandler.d.ts +16 -0
- package/dist/esm/api/subsystem/DataSystem/CallbackHandler.d.ts.map +1 -0
- package/dist/esm/api/subsystem/DataSystem/DataSource.d.ts +21 -0
- package/dist/esm/api/subsystem/DataSystem/DataSource.d.ts.map +1 -0
- package/dist/esm/api/subsystem/DataSystem/index.d.ts +2 -0
- package/dist/esm/api/subsystem/DataSystem/index.d.ts.map +1 -0
- package/dist/esm/api/subsystem/index.d.ts +2 -1
- package/dist/esm/api/subsystem/index.d.ts.map +1 -1
- package/dist/esm/datasource/Backoff.d.ts +46 -0
- package/dist/esm/datasource/Backoff.d.ts.map +1 -0
- package/dist/esm/datasource/CompositeDataSource.d.ts +62 -0
- package/dist/esm/datasource/CompositeDataSource.d.ts.map +1 -0
- package/dist/esm/datasource/dataSourceList.d.ts +47 -0
- package/dist/esm/datasource/dataSourceList.d.ts.map +1 -0
- package/dist/esm/datasource/index.d.ts +3 -1
- package/dist/esm/datasource/index.d.ts.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.mjs +664 -37
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/internal/fdv2/index.d.ts +3 -2
- package/dist/esm/internal/fdv2/index.d.ts.map +1 -1
- package/dist/esm/internal/fdv2/{payloadReader.d.ts → payloadProcessor.d.ts} +27 -16
- package/dist/esm/internal/fdv2/payloadProcessor.d.ts.map +1 -0
- package/dist/esm/internal/fdv2/payloadStreamReader.d.ts +31 -0
- package/dist/esm/internal/fdv2/payloadStreamReader.d.ts.map +1 -0
- package/dist/esm/internal/index.d.ts +2 -0
- package/dist/esm/internal/index.d.ts.map +1 -1
- package/dist/esm/internal/metadata/InitMetadata.d.ts +18 -0
- package/dist/esm/internal/metadata/InitMetadata.d.ts.map +1 -0
- package/dist/esm/internal/metadata/index.d.ts +3 -0
- package/dist/esm/internal/metadata/index.d.ts.map +1 -0
- package/dist/esm/internal/plugins/index.d.ts +4 -0
- package/dist/esm/internal/plugins/index.d.ts.map +1 -0
- package/dist/esm/internal/plugins/safeGetHooks.d.ts +4 -0
- package/dist/esm/internal/plugins/safeGetHooks.d.ts.map +1 -0
- package/dist/esm/internal/plugins/safeGetName.d.ts +4 -0
- package/dist/esm/internal/plugins/safeGetName.d.ts.map +1 -0
- package/dist/esm/internal/plugins/safeRegisterPlugins.d.ts +4 -0
- package/dist/esm/internal/plugins/safeRegisterPlugins.d.ts.map +1 -0
- package/package.json +1 -1
- package/dist/cjs/internal/fdv2/payloadReader.d.ts.map +0 -1
- package/dist/esm/internal/fdv2/payloadReader.d.ts.map +0 -1
package/dist/esm/index.mjs
CHANGED
|
@@ -811,6 +811,482 @@ class ContextFilter {
|
|
|
811
811
|
}
|
|
812
812
|
}
|
|
813
813
|
|
|
814
|
+
const MAX_RETRY_DELAY = 30 * 1000; // Maximum retry delay 30 seconds.
|
|
815
|
+
const JITTER_RATIO = 0.5; // Delay should be 50%-100% of calculated time.
|
|
816
|
+
/**
|
|
817
|
+
* Implements exponential backoff and jitter. This class tracks successful connections and failures
|
|
818
|
+
* and produces a retry delay.
|
|
819
|
+
*
|
|
820
|
+
* It does not start any timers or directly control a connection.
|
|
821
|
+
*
|
|
822
|
+
* The backoff follows an exponential backoff scheme with 50% jitter starting at
|
|
823
|
+
* initialRetryDelayMillis and capping at MAX_RETRY_DELAY. If RESET_INTERVAL has elapsed after a
|
|
824
|
+
* success, without an intervening faulure, then the backoff is reset to initialRetryDelayMillis.
|
|
825
|
+
*/
|
|
826
|
+
class DefaultBackoff {
|
|
827
|
+
constructor(initialRetryDelayMillis, _retryResetIntervalMillis, _random = Math.random) {
|
|
828
|
+
this._retryResetIntervalMillis = _retryResetIntervalMillis;
|
|
829
|
+
this._random = _random;
|
|
830
|
+
this._retryCount = 0;
|
|
831
|
+
// Initial retry delay cannot be 0.
|
|
832
|
+
this._initialRetryDelayMillis = Math.max(1, initialRetryDelayMillis);
|
|
833
|
+
this._maxExponent = Math.ceil(Math.log2(MAX_RETRY_DELAY / this._initialRetryDelayMillis));
|
|
834
|
+
}
|
|
835
|
+
_backoff() {
|
|
836
|
+
const exponent = Math.min(this._retryCount, this._maxExponent);
|
|
837
|
+
const delay = this._initialRetryDelayMillis * 2 ** exponent;
|
|
838
|
+
return Math.min(delay, MAX_RETRY_DELAY);
|
|
839
|
+
}
|
|
840
|
+
_jitter(computedDelayMillis) {
|
|
841
|
+
return computedDelayMillis - Math.trunc(this._random() * JITTER_RATIO * computedDelayMillis);
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* This function should be called when a connection attempt is successful.
|
|
845
|
+
*
|
|
846
|
+
* @param timeStampMs The time of the success. Used primarily for testing, when not provided
|
|
847
|
+
* the current time is used.
|
|
848
|
+
*/
|
|
849
|
+
success(timeStampMs = Date.now()) {
|
|
850
|
+
this._activeSince = timeStampMs;
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* This function should be called when a connection fails. It returns the a delay, in
|
|
854
|
+
* milliseconds, after which a reconnection attempt should be made.
|
|
855
|
+
*
|
|
856
|
+
* @param timeStampMs The time of the success. Used primarily for testing, when not provided
|
|
857
|
+
* the current time is used.
|
|
858
|
+
* @returns The delay before the next connection attempt.
|
|
859
|
+
*/
|
|
860
|
+
fail(timeStampMs = Date.now()) {
|
|
861
|
+
// If the last successful connection was active for more than the RESET_INTERVAL, then we
|
|
862
|
+
// return to the initial retry delay.
|
|
863
|
+
if (this._activeSince !== undefined &&
|
|
864
|
+
timeStampMs - this._activeSince > this._retryResetIntervalMillis) {
|
|
865
|
+
this._retryCount = 0;
|
|
866
|
+
}
|
|
867
|
+
this._activeSince = undefined;
|
|
868
|
+
const delay = this._jitter(this._backoff());
|
|
869
|
+
this._retryCount += 1;
|
|
870
|
+
return delay;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
/**
|
|
875
|
+
* Handler that connects the current {@link DataSource} to the {@link CompositeDataSource}. A single
|
|
876
|
+
* {@link CallbackHandler} should only be given to one {@link DataSource}. Use {@link disable()} to
|
|
877
|
+
* prevent additional callback events.
|
|
878
|
+
*/
|
|
879
|
+
class CallbackHandler {
|
|
880
|
+
constructor(_dataCallback, _statusCallback) {
|
|
881
|
+
this._dataCallback = _dataCallback;
|
|
882
|
+
this._statusCallback = _statusCallback;
|
|
883
|
+
this._disabled = false;
|
|
884
|
+
}
|
|
885
|
+
disable() {
|
|
886
|
+
this._disabled = true;
|
|
887
|
+
}
|
|
888
|
+
async dataHandler(basis, data) {
|
|
889
|
+
if (this._disabled) {
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
// TODO: SDK-1044 track selector for future synchronizer to use
|
|
893
|
+
// report data up
|
|
894
|
+
this._dataCallback(basis, data);
|
|
895
|
+
}
|
|
896
|
+
async statusHandler(status, err) {
|
|
897
|
+
if (this._disabled) {
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
this._statusCallback(status, err);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// TODO: refactor client-sdk to use this enum
|
|
905
|
+
var DataSourceState;
|
|
906
|
+
(function (DataSourceState) {
|
|
907
|
+
// Positive confirmation of connection/data receipt
|
|
908
|
+
DataSourceState[DataSourceState["Valid"] = 0] = "Valid";
|
|
909
|
+
// Spinning up to make first connection attempt
|
|
910
|
+
DataSourceState[DataSourceState["Initializing"] = 1] = "Initializing";
|
|
911
|
+
// Transient issue, automatic retry is expected
|
|
912
|
+
DataSourceState[DataSourceState["Interrupted"] = 2] = "Interrupted";
|
|
913
|
+
// Data source was closed and will not retry automatically.
|
|
914
|
+
DataSourceState[DataSourceState["Closed"] = 3] = "Closed";
|
|
915
|
+
})(DataSourceState || (DataSourceState = {}));
|
|
916
|
+
|
|
917
|
+
/**
|
|
918
|
+
* Helper class for {@link CompositeDataSource} to manage iterating on data sources and removing them on the fly.
|
|
919
|
+
*/
|
|
920
|
+
class DataSourceList {
|
|
921
|
+
/**
|
|
922
|
+
* @param circular whether to loop off the end of the list back to the start
|
|
923
|
+
* @param initialList of content
|
|
924
|
+
*/
|
|
925
|
+
constructor(circular, initialList) {
|
|
926
|
+
this._list = initialList ? [...initialList] : [];
|
|
927
|
+
this._circular = circular;
|
|
928
|
+
this._pos = 0;
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Returns the current head and then iterates.
|
|
932
|
+
*/
|
|
933
|
+
next() {
|
|
934
|
+
if (this._list.length <= 0 || this._pos >= this._list.length) {
|
|
935
|
+
return undefined;
|
|
936
|
+
}
|
|
937
|
+
const result = this._list[this._pos];
|
|
938
|
+
if (this._circular) {
|
|
939
|
+
this._pos = (this._pos + 1) % this._list.length;
|
|
940
|
+
}
|
|
941
|
+
else {
|
|
942
|
+
this._pos += 1;
|
|
943
|
+
}
|
|
944
|
+
return result;
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Replaces all elements with the provided list and resets the position of head to the start.
|
|
948
|
+
*
|
|
949
|
+
* @param input that will replace existing list
|
|
950
|
+
*/
|
|
951
|
+
replace(input) {
|
|
952
|
+
this._list = [...input];
|
|
953
|
+
this._pos = 0;
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Removes the provided element from the list. If the removed element was the head, head moves to next. Consider head may be undefined if list is empty after removal.
|
|
957
|
+
*
|
|
958
|
+
* @param element to remove
|
|
959
|
+
* @returns true if element was removed
|
|
960
|
+
*/
|
|
961
|
+
remove(element) {
|
|
962
|
+
const index = this._list.indexOf(element);
|
|
963
|
+
if (index < 0) {
|
|
964
|
+
return false;
|
|
965
|
+
}
|
|
966
|
+
this._list.splice(index, 1);
|
|
967
|
+
if (this._list.length > 0) {
|
|
968
|
+
// if removed item was before head, adjust head
|
|
969
|
+
if (index < this._pos) {
|
|
970
|
+
this._pos -= 1;
|
|
971
|
+
}
|
|
972
|
+
if (this._circular && this._pos > this._list.length - 1) {
|
|
973
|
+
this._pos = 0;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
return true;
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* Reset the head position to the start of the list.
|
|
980
|
+
*/
|
|
981
|
+
reset() {
|
|
982
|
+
this._pos = 0;
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* @returns the current head position in the list, 0 indexed.
|
|
986
|
+
*/
|
|
987
|
+
pos() {
|
|
988
|
+
return this._pos;
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* @returns the current length of the list
|
|
992
|
+
*/
|
|
993
|
+
length() {
|
|
994
|
+
return this._list.length;
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* Clears the list and resets head.
|
|
998
|
+
*/
|
|
999
|
+
clear() {
|
|
1000
|
+
this._list = [];
|
|
1001
|
+
this._pos = 0;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
const DEFAULT_FALLBACK_TIME_MS = 2 * 60 * 1000;
|
|
1006
|
+
const DEFAULT_RECOVERY_TIME_MS = 5 * 60 * 1000;
|
|
1007
|
+
/**
|
|
1008
|
+
* The {@link CompositeDataSource} can combine a number of {@link DataSystemInitializer}s and {@link DataSystemSynchronizer}s
|
|
1009
|
+
* into a single {@link DataSource}, implementing fallback and recovery logic internally to choose where data is sourced from.
|
|
1010
|
+
*/
|
|
1011
|
+
class CompositeDataSource {
|
|
1012
|
+
/**
|
|
1013
|
+
* @param initializers factories to create {@link DataSystemInitializer}s, in priority order.
|
|
1014
|
+
* @param synchronizers factories to create {@link DataSystemSynchronizer}s, in priority order.
|
|
1015
|
+
*/
|
|
1016
|
+
constructor(initializers, synchronizers, _logger, _transitionConditions = {
|
|
1017
|
+
[DataSourceState.Valid]: {
|
|
1018
|
+
durationMS: DEFAULT_RECOVERY_TIME_MS,
|
|
1019
|
+
transition: 'recover',
|
|
1020
|
+
},
|
|
1021
|
+
[DataSourceState.Interrupted]: {
|
|
1022
|
+
durationMS: DEFAULT_FALLBACK_TIME_MS,
|
|
1023
|
+
transition: 'fallback',
|
|
1024
|
+
},
|
|
1025
|
+
}, _backoff = new DefaultBackoff(1000, 30000)) {
|
|
1026
|
+
this._logger = _logger;
|
|
1027
|
+
this._transitionConditions = _transitionConditions;
|
|
1028
|
+
this._backoff = _backoff;
|
|
1029
|
+
this._stopped = true;
|
|
1030
|
+
this._cancelTokens = [];
|
|
1031
|
+
this._cancellableDelay = (delayMS) => {
|
|
1032
|
+
let timeout;
|
|
1033
|
+
const promise = new Promise((res, _) => {
|
|
1034
|
+
timeout = setTimeout(res, delayMS);
|
|
1035
|
+
});
|
|
1036
|
+
return {
|
|
1037
|
+
promise,
|
|
1038
|
+
cancel() {
|
|
1039
|
+
if (timeout) {
|
|
1040
|
+
clearTimeout(timeout);
|
|
1041
|
+
timeout = undefined;
|
|
1042
|
+
}
|
|
1043
|
+
},
|
|
1044
|
+
};
|
|
1045
|
+
};
|
|
1046
|
+
this._externalTransitionPromise = new Promise((resolveTransition) => {
|
|
1047
|
+
this._externalTransitionResolve = resolveTransition;
|
|
1048
|
+
});
|
|
1049
|
+
this._initPhaseActive = initializers.length > 0; // init phase if we have initializers
|
|
1050
|
+
this._initFactories = new DataSourceList(false, initializers);
|
|
1051
|
+
this._syncFactories = new DataSourceList(true, synchronizers);
|
|
1052
|
+
}
|
|
1053
|
+
async start(dataCallback, statusCallback) {
|
|
1054
|
+
if (!this._stopped) {
|
|
1055
|
+
// don't allow multiple simultaneous runs
|
|
1056
|
+
this._logger?.info('CompositeDataSource already running. Ignoring call to start.');
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
this._stopped = false;
|
|
1060
|
+
this._logger?.debug(`CompositeDataSource starting with (${this._initFactories.length()} initializers, ${this._syncFactories.length()} synchronizers).`);
|
|
1061
|
+
// this wrapper turns status updates from underlying data sources into a valid series of status updates for the consumer of this
|
|
1062
|
+
// composite data source
|
|
1063
|
+
const sanitizedStatusCallback = this._wrapStatusCallbackWithSanitizer(statusCallback);
|
|
1064
|
+
sanitizedStatusCallback(DataSourceState.Initializing);
|
|
1065
|
+
let lastTransition;
|
|
1066
|
+
// eslint-disable-next-line no-constant-condition
|
|
1067
|
+
while (true) {
|
|
1068
|
+
const { dataSource: currentDS, isPrimary, cullDSFactory, } = this._pickDataSource(lastTransition);
|
|
1069
|
+
const internalTransitionPromise = new Promise((transitionResolve) => {
|
|
1070
|
+
if (currentDS) {
|
|
1071
|
+
// these local variables are used for handling automatic transition related to data source status (ex: recovering to primary after
|
|
1072
|
+
// secondary has been valid for N many seconds)
|
|
1073
|
+
let lastState;
|
|
1074
|
+
let cancelScheduledTransition = () => { };
|
|
1075
|
+
// this callback handler can be disabled and ensures only one transition request occurs
|
|
1076
|
+
const callbackHandler = new CallbackHandler((basis, data) => {
|
|
1077
|
+
this._backoff.success();
|
|
1078
|
+
dataCallback(basis, data);
|
|
1079
|
+
if (basis && this._initPhaseActive) {
|
|
1080
|
+
// transition to sync if we get basis during init
|
|
1081
|
+
callbackHandler.disable();
|
|
1082
|
+
this._consumeCancelToken(cancelScheduledTransition);
|
|
1083
|
+
sanitizedStatusCallback(DataSourceState.Interrupted);
|
|
1084
|
+
transitionResolve({ transition: 'switchToSync' });
|
|
1085
|
+
}
|
|
1086
|
+
}, (state, err) => {
|
|
1087
|
+
// When we get a status update, we want to fallback if it is an error. We also want to schedule a transition for some
|
|
1088
|
+
// time in the future if this status remains for some duration (ex: Recover to primary synchronizer after the secondary
|
|
1089
|
+
// synchronizer has been Valid for some time). These scheduled transitions are configurable in the constructor.
|
|
1090
|
+
this._logger?.debug(`CompositeDataSource received state ${state} from underlying data source.`);
|
|
1091
|
+
if (err || state === DataSourceState.Closed) {
|
|
1092
|
+
callbackHandler.disable();
|
|
1093
|
+
if (err.recoverable === false) {
|
|
1094
|
+
// don't use this datasource's factory again
|
|
1095
|
+
cullDSFactory?.();
|
|
1096
|
+
}
|
|
1097
|
+
sanitizedStatusCallback(state, err);
|
|
1098
|
+
this._consumeCancelToken(cancelScheduledTransition);
|
|
1099
|
+
transitionResolve({ transition: 'fallback', err }); // unrecoverable error has occurred, so fallback
|
|
1100
|
+
}
|
|
1101
|
+
else {
|
|
1102
|
+
sanitizedStatusCallback(state);
|
|
1103
|
+
if (state !== lastState) {
|
|
1104
|
+
lastState = state;
|
|
1105
|
+
this._consumeCancelToken(cancelScheduledTransition); // cancel previously scheduled status transition if one was scheduled
|
|
1106
|
+
// primary source cannot recover to itself, so exclude it
|
|
1107
|
+
const condition = this._lookupTransitionCondition(state, isPrimary);
|
|
1108
|
+
if (condition) {
|
|
1109
|
+
const { promise, cancel } = this._cancellableDelay(condition.durationMS);
|
|
1110
|
+
cancelScheduledTransition = cancel;
|
|
1111
|
+
this._cancelTokens.push(cancelScheduledTransition);
|
|
1112
|
+
promise.then(() => {
|
|
1113
|
+
this._consumeCancelToken(cancel);
|
|
1114
|
+
callbackHandler.disable();
|
|
1115
|
+
sanitizedStatusCallback(DataSourceState.Interrupted);
|
|
1116
|
+
transitionResolve({ transition: condition.transition });
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
});
|
|
1122
|
+
currentDS.start((basis, data) => callbackHandler.dataHandler(basis, data), (status, err) => callbackHandler.statusHandler(status, err));
|
|
1123
|
+
}
|
|
1124
|
+
else {
|
|
1125
|
+
// we don't have a data source to use!
|
|
1126
|
+
transitionResolve({
|
|
1127
|
+
transition: 'stop',
|
|
1128
|
+
err: {
|
|
1129
|
+
name: 'ExhaustedDataSources',
|
|
1130
|
+
message: `CompositeDataSource has exhausted all configured initializers and synchronizers.`,
|
|
1131
|
+
},
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
});
|
|
1135
|
+
// await transition triggered by internal data source or an external stop request
|
|
1136
|
+
let transitionRequest = await Promise.race([
|
|
1137
|
+
internalTransitionPromise,
|
|
1138
|
+
this._externalTransitionPromise,
|
|
1139
|
+
]);
|
|
1140
|
+
// stop the underlying datasource before transitioning to next state
|
|
1141
|
+
currentDS?.stop();
|
|
1142
|
+
if (transitionRequest.err && transitionRequest.transition !== 'stop') {
|
|
1143
|
+
// if the transition was due to an error, throttle the transition
|
|
1144
|
+
const delay = this._backoff.fail();
|
|
1145
|
+
const { promise, cancel: cancelDelay } = this._cancellableDelay(delay);
|
|
1146
|
+
this._cancelTokens.push(cancelDelay);
|
|
1147
|
+
const delayedTransition = promise.then(() => {
|
|
1148
|
+
this._consumeCancelToken(cancelDelay);
|
|
1149
|
+
return transitionRequest;
|
|
1150
|
+
});
|
|
1151
|
+
// race the delayed transition and external transition requests to be responsive
|
|
1152
|
+
transitionRequest = await Promise.race([
|
|
1153
|
+
delayedTransition,
|
|
1154
|
+
this._externalTransitionPromise,
|
|
1155
|
+
]);
|
|
1156
|
+
// consume the delay cancel token (even if it resolved, need to stop tracking its token)
|
|
1157
|
+
this._consumeCancelToken(cancelDelay);
|
|
1158
|
+
}
|
|
1159
|
+
lastTransition = transitionRequest.transition;
|
|
1160
|
+
if (transitionRequest.transition === 'stop') {
|
|
1161
|
+
// exit the loop, this is intentionally not the sanitized status callback
|
|
1162
|
+
statusCallback(DataSourceState.Closed, transitionRequest.err);
|
|
1163
|
+
break;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
// reset so that run can be called again in the future
|
|
1167
|
+
this._reset();
|
|
1168
|
+
}
|
|
1169
|
+
async stop() {
|
|
1170
|
+
this._cancelTokens.forEach((cancel) => cancel());
|
|
1171
|
+
this._cancelTokens = [];
|
|
1172
|
+
this._externalTransitionResolve?.({ transition: 'stop' });
|
|
1173
|
+
}
|
|
1174
|
+
_reset() {
|
|
1175
|
+
this._stopped = true;
|
|
1176
|
+
this._initPhaseActive = this._initFactories.length() > 0; // init phase if we have initializers;
|
|
1177
|
+
this._initFactories.reset();
|
|
1178
|
+
this._syncFactories.reset();
|
|
1179
|
+
this._externalTransitionPromise = new Promise((tr) => {
|
|
1180
|
+
this._externalTransitionResolve = tr;
|
|
1181
|
+
});
|
|
1182
|
+
// intentionally not resetting the backoff to avoid a code path that could circumvent throttling
|
|
1183
|
+
}
|
|
1184
|
+
/**
|
|
1185
|
+
* Determines the next datasource and returns that datasource as well as a closure to cull the
|
|
1186
|
+
* datasource from the datasource lists. One example where the cull closure is invoked is if the
|
|
1187
|
+
* datasource has an unrecoverable error.
|
|
1188
|
+
*/
|
|
1189
|
+
_pickDataSource(transition) {
|
|
1190
|
+
let factory;
|
|
1191
|
+
let isPrimary;
|
|
1192
|
+
switch (transition) {
|
|
1193
|
+
case 'switchToSync':
|
|
1194
|
+
this._initPhaseActive = false; // one way toggle to false, unless this class is reset()
|
|
1195
|
+
this._syncFactories.reset();
|
|
1196
|
+
isPrimary = this._syncFactories.pos() === 0;
|
|
1197
|
+
factory = this._syncFactories.next();
|
|
1198
|
+
break;
|
|
1199
|
+
case 'recover':
|
|
1200
|
+
if (this._initPhaseActive) {
|
|
1201
|
+
this._initFactories.reset();
|
|
1202
|
+
isPrimary = this._initFactories.pos() === 0;
|
|
1203
|
+
factory = this._initFactories.next();
|
|
1204
|
+
}
|
|
1205
|
+
else {
|
|
1206
|
+
this._syncFactories.reset();
|
|
1207
|
+
isPrimary = this._syncFactories.pos() === 0;
|
|
1208
|
+
factory = this._syncFactories.next();
|
|
1209
|
+
}
|
|
1210
|
+
break;
|
|
1211
|
+
case 'fallback':
|
|
1212
|
+
default:
|
|
1213
|
+
if (this._initPhaseActive) {
|
|
1214
|
+
isPrimary = this._initFactories.pos() === 0;
|
|
1215
|
+
factory = this._initFactories.next();
|
|
1216
|
+
}
|
|
1217
|
+
else {
|
|
1218
|
+
isPrimary = this._syncFactories.pos() === 0;
|
|
1219
|
+
factory = this._syncFactories.next();
|
|
1220
|
+
}
|
|
1221
|
+
break;
|
|
1222
|
+
}
|
|
1223
|
+
if (!factory) {
|
|
1224
|
+
return { dataSource: undefined, isPrimary, cullDSFactory: undefined };
|
|
1225
|
+
}
|
|
1226
|
+
return {
|
|
1227
|
+
dataSource: factory(),
|
|
1228
|
+
isPrimary,
|
|
1229
|
+
cullDSFactory: () => {
|
|
1230
|
+
if (factory) {
|
|
1231
|
+
this._syncFactories.remove(factory);
|
|
1232
|
+
}
|
|
1233
|
+
},
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
/**
|
|
1237
|
+
* @returns the transition condition for the provided data source state or undefined
|
|
1238
|
+
* if there is no transition condition
|
|
1239
|
+
*/
|
|
1240
|
+
_lookupTransitionCondition(state, excludeRecover) {
|
|
1241
|
+
const condition = this._transitionConditions[state];
|
|
1242
|
+
// exclude recovery can happen for certain initializers/synchronizers (ex: the primary synchronizer shouldn't recover to itself)
|
|
1243
|
+
if (excludeRecover && condition?.transition === 'recover') {
|
|
1244
|
+
return undefined;
|
|
1245
|
+
}
|
|
1246
|
+
return condition;
|
|
1247
|
+
}
|
|
1248
|
+
_consumeCancelToken(cancel) {
|
|
1249
|
+
cancel();
|
|
1250
|
+
const index = this._cancelTokens.indexOf(cancel, 0);
|
|
1251
|
+
if (index > -1) {
|
|
1252
|
+
this._cancelTokens.splice(index, 1);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
/**
|
|
1256
|
+
* This wrapper will ensure the following:
|
|
1257
|
+
*
|
|
1258
|
+
* Don't report DataSourceState.Initializing except as first status callback.
|
|
1259
|
+
* Map underlying DataSourceState.Closed to interrupted.
|
|
1260
|
+
* Don't report the same status and error twice in a row.
|
|
1261
|
+
*/
|
|
1262
|
+
_wrapStatusCallbackWithSanitizer(statusCallback) {
|
|
1263
|
+
let alreadyReportedInitializing = false;
|
|
1264
|
+
let lastStatus;
|
|
1265
|
+
let lastErr;
|
|
1266
|
+
return (status, err) => {
|
|
1267
|
+
let sanitized = status;
|
|
1268
|
+
// underlying errors, closed state, or off are masked as interrupted while we transition
|
|
1269
|
+
if (status === DataSourceState.Closed) {
|
|
1270
|
+
sanitized = DataSourceState.Interrupted;
|
|
1271
|
+
}
|
|
1272
|
+
// don't report the same combination of values twice in a row
|
|
1273
|
+
if (sanitized === lastStatus && err === lastErr) {
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
if (sanitized === DataSourceState.Initializing) {
|
|
1277
|
+
// don't report initializing again if that has already been reported
|
|
1278
|
+
if (alreadyReportedInitializing) {
|
|
1279
|
+
return;
|
|
1280
|
+
}
|
|
1281
|
+
alreadyReportedInitializing = true;
|
|
1282
|
+
}
|
|
1283
|
+
lastStatus = sanitized;
|
|
1284
|
+
lastErr = err;
|
|
1285
|
+
statusCallback(sanitized, err);
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
|
|
814
1290
|
var DataSourceErrorKind;
|
|
815
1291
|
(function (DataSourceErrorKind) {
|
|
816
1292
|
/// An unexpected error, such as an uncaught exception, further
|
|
@@ -879,6 +1355,7 @@ var LDDeliveryStatus;
|
|
|
879
1355
|
|
|
880
1356
|
var index$1 = /*#__PURE__*/Object.freeze({
|
|
881
1357
|
__proto__: null,
|
|
1358
|
+
get DataSourceState () { return DataSourceState; },
|
|
882
1359
|
get LDDeliveryStatus () { return LDDeliveryStatus; },
|
|
883
1360
|
get LDEventType () { return LDEventType; }
|
|
884
1361
|
});
|
|
@@ -2327,30 +2804,29 @@ class EventFactoryBase {
|
|
|
2327
2804
|
}
|
|
2328
2805
|
|
|
2329
2806
|
/**
|
|
2330
|
-
* A FDv2
|
|
2807
|
+
* A FDv2 PayloadProcessor can be used to parse payloads from a stream of FDv2 events. It will send payloads
|
|
2331
2808
|
* to the PayloadListeners as the payloads are received. Invalid series of events may be dropped silently,
|
|
2332
|
-
* but the payload
|
|
2809
|
+
* but the payload processor will continue to operate.
|
|
2333
2810
|
*/
|
|
2334
|
-
class
|
|
2811
|
+
class PayloadProcessor {
|
|
2335
2812
|
/**
|
|
2336
|
-
* Creates a
|
|
2813
|
+
* Creates a PayloadProcessor
|
|
2337
2814
|
*
|
|
2338
|
-
* @param eventStream event stream of FDv2 events
|
|
2339
2815
|
* @param _objProcessors defines object processors for each object kind.
|
|
2340
|
-
* @param _errorHandler that will be called with errors as they are encountered
|
|
2816
|
+
* @param _errorHandler that will be called with parsing errors as they are encountered
|
|
2341
2817
|
* @param _logger for logging
|
|
2342
2818
|
*/
|
|
2343
|
-
constructor(
|
|
2819
|
+
constructor(_objProcessors, _errorHandler, _logger) {
|
|
2344
2820
|
this._objProcessors = _objProcessors;
|
|
2345
2821
|
this._errorHandler = _errorHandler;
|
|
2346
2822
|
this._logger = _logger;
|
|
2347
2823
|
this._listeners = [];
|
|
2348
2824
|
this._tempId = undefined;
|
|
2349
|
-
this._tempBasis =
|
|
2825
|
+
this._tempBasis = false;
|
|
2350
2826
|
this._tempUpdates = [];
|
|
2351
2827
|
this._processServerIntent = (data) => {
|
|
2352
2828
|
// clear state in prep for handling data
|
|
2353
|
-
this.
|
|
2829
|
+
this._resetAll();
|
|
2354
2830
|
// if there's no payloads, return
|
|
2355
2831
|
if (!data.payloads.length) {
|
|
2356
2832
|
return;
|
|
@@ -2362,8 +2838,11 @@ class PayloadReader {
|
|
|
2362
2838
|
this._tempBasis = true;
|
|
2363
2839
|
break;
|
|
2364
2840
|
case 'xfer-changes':
|
|
2841
|
+
this._tempBasis = false;
|
|
2842
|
+
break;
|
|
2365
2843
|
case 'none':
|
|
2366
2844
|
this._tempBasis = false;
|
|
2845
|
+
this._processIntentNone(payload);
|
|
2367
2846
|
break;
|
|
2368
2847
|
default:
|
|
2369
2848
|
// unrecognized intent code, return
|
|
@@ -2373,7 +2852,7 @@ class PayloadReader {
|
|
|
2373
2852
|
};
|
|
2374
2853
|
this._processPutObject = (data) => {
|
|
2375
2854
|
// if the following properties haven't been provided by now, we should ignore the event
|
|
2376
|
-
if (!this._tempId || // server intent hasn't been
|
|
2855
|
+
if (!this._tempId || // server intent hasn't been received yet.
|
|
2377
2856
|
!data.kind ||
|
|
2378
2857
|
!data.key ||
|
|
2379
2858
|
!data.version ||
|
|
@@ -2382,7 +2861,7 @@ class PayloadReader {
|
|
|
2382
2861
|
}
|
|
2383
2862
|
const obj = this._processObj(data.kind, data.object);
|
|
2384
2863
|
if (!obj) {
|
|
2385
|
-
this._logger?.warn(`Unable to
|
|
2864
|
+
this._logger?.warn(`Unable to process object for kind: '${data.kind}'`);
|
|
2386
2865
|
// ignore unrecognized kinds
|
|
2387
2866
|
return;
|
|
2388
2867
|
}
|
|
@@ -2407,13 +2886,27 @@ class PayloadReader {
|
|
|
2407
2886
|
deleted: true,
|
|
2408
2887
|
});
|
|
2409
2888
|
};
|
|
2889
|
+
this._processIntentNone = (intent) => {
|
|
2890
|
+
// if the following properties aren't present ignore the event
|
|
2891
|
+
if (!intent.id || !intent.target) {
|
|
2892
|
+
return;
|
|
2893
|
+
}
|
|
2894
|
+
const payload = {
|
|
2895
|
+
id: intent.id,
|
|
2896
|
+
version: intent.target,
|
|
2897
|
+
basis: false,
|
|
2898
|
+
updates: [], // payload with no updates to hide the intent none concept from the consumer
|
|
2899
|
+
// note: state is absent here as that only appears in payload transferred events
|
|
2900
|
+
};
|
|
2901
|
+
this._listeners.forEach((it) => it(payload));
|
|
2902
|
+
this._resetAfterEmission();
|
|
2903
|
+
};
|
|
2410
2904
|
this._processPayloadTransferred = (data) => {
|
|
2411
2905
|
// if the following properties haven't been provided by now, we should reset
|
|
2412
|
-
if (!this._tempId || // server intent hasn't been
|
|
2906
|
+
if (!this._tempId || // server intent hasn't been received yet.
|
|
2413
2907
|
!data.state ||
|
|
2414
|
-
!data.version
|
|
2415
|
-
this.
|
|
2416
|
-
this._resetState(); // a reset is best defensive action since payload transferred terminates a payload
|
|
2908
|
+
!data.version) {
|
|
2909
|
+
this._resetAll(); // a reset is best defensive action since payload transferred terminates a payload
|
|
2417
2910
|
return;
|
|
2418
2911
|
}
|
|
2419
2912
|
const payload = {
|
|
@@ -2424,22 +2917,16 @@ class PayloadReader {
|
|
|
2424
2917
|
updates: this._tempUpdates,
|
|
2425
2918
|
};
|
|
2426
2919
|
this._listeners.forEach((it) => it(payload));
|
|
2427
|
-
this.
|
|
2920
|
+
this._resetAfterEmission();
|
|
2428
2921
|
};
|
|
2429
2922
|
this._processGoodbye = (data) => {
|
|
2430
2923
|
this._logger?.info(`Goodbye was received from the LaunchDarkly connection with reason: ${data.reason}.`);
|
|
2431
|
-
this.
|
|
2924
|
+
this._resetAll();
|
|
2432
2925
|
};
|
|
2433
2926
|
this._processError = (data) => {
|
|
2434
|
-
this._logger?.info(`An issue was encountered receiving updates for payload ${this._tempId} with reason: ${data.reason}
|
|
2435
|
-
this.
|
|
2927
|
+
this._logger?.info(`An issue was encountered receiving updates for payload ${this._tempId} with reason: ${data.reason}.`);
|
|
2928
|
+
this._resetAfterError();
|
|
2436
2929
|
};
|
|
2437
|
-
this._attachHandler(eventStream, 'server-intent', this._processServerIntent);
|
|
2438
|
-
this._attachHandler(eventStream, 'put-object', this._processPutObject);
|
|
2439
|
-
this._attachHandler(eventStream, 'delete-object', this._processDeleteObject);
|
|
2440
|
-
this._attachHandler(eventStream, 'payload-transferred', this._processPayloadTransferred);
|
|
2441
|
-
this._attachHandler(eventStream, 'goodbye', this._processGoodbye);
|
|
2442
|
-
this._attachHandler(eventStream, 'error', this._processError);
|
|
2443
2930
|
}
|
|
2444
2931
|
addPayloadListener(listener) {
|
|
2445
2932
|
this._listeners.push(listener);
|
|
@@ -2450,34 +2937,169 @@ class PayloadReader {
|
|
|
2450
2937
|
this._listeners.splice(index, 1);
|
|
2451
2938
|
}
|
|
2452
2939
|
}
|
|
2453
|
-
|
|
2940
|
+
/**
|
|
2941
|
+
* Gives the {@link PayloadProcessor} a series of events that it will statefully, incrementally process.
|
|
2942
|
+
* This may lead to listeners being invoked as necessary.
|
|
2943
|
+
* @param events to be processed (can be a single element)
|
|
2944
|
+
*/
|
|
2945
|
+
processEvents(events) {
|
|
2946
|
+
events.forEach((event) => {
|
|
2947
|
+
switch (event.event) {
|
|
2948
|
+
case 'server-intent': {
|
|
2949
|
+
this._processServerIntent(event.data);
|
|
2950
|
+
break;
|
|
2951
|
+
}
|
|
2952
|
+
case 'put-object': {
|
|
2953
|
+
this._processPutObject(event.data);
|
|
2954
|
+
break;
|
|
2955
|
+
}
|
|
2956
|
+
case 'delete-object': {
|
|
2957
|
+
this._processDeleteObject(event.data);
|
|
2958
|
+
break;
|
|
2959
|
+
}
|
|
2960
|
+
case 'payload-transferred': {
|
|
2961
|
+
this._processPayloadTransferred(event.data);
|
|
2962
|
+
break;
|
|
2963
|
+
}
|
|
2964
|
+
case 'goodbye': {
|
|
2965
|
+
this._processGoodbye(event.data);
|
|
2966
|
+
break;
|
|
2967
|
+
}
|
|
2968
|
+
case 'error': {
|
|
2969
|
+
this._processError(event.data);
|
|
2970
|
+
break;
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
});
|
|
2974
|
+
}
|
|
2975
|
+
_processObj(kind, jsonObj) {
|
|
2976
|
+
return this._objProcessors[kind]?.(jsonObj);
|
|
2977
|
+
}
|
|
2978
|
+
_resetAfterEmission() {
|
|
2979
|
+
this._tempBasis = false;
|
|
2980
|
+
this._tempUpdates = [];
|
|
2981
|
+
}
|
|
2982
|
+
_resetAfterError() {
|
|
2983
|
+
this._tempUpdates = [];
|
|
2984
|
+
}
|
|
2985
|
+
_resetAll() {
|
|
2986
|
+
this._tempId = undefined;
|
|
2987
|
+
this._tempBasis = false;
|
|
2988
|
+
this._tempUpdates = [];
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
|
|
2992
|
+
/**
|
|
2993
|
+
* A FDv2 PayloadStreamReader can be used to parse payloads from a stream of FDv2 events. See {@link PayloadProcessor}
|
|
2994
|
+
* for more details.
|
|
2995
|
+
*/
|
|
2996
|
+
class PayloadStreamReader {
|
|
2997
|
+
/**
|
|
2998
|
+
* Creates a PayloadStreamReader
|
|
2999
|
+
*
|
|
3000
|
+
* @param eventStream event stream of FDv2 events
|
|
3001
|
+
* @param _objProcessors defines object processors for each object kind.
|
|
3002
|
+
* @param _errorHandler that will be called with parsing errors as they are encountered
|
|
3003
|
+
* @param _logger for logging
|
|
3004
|
+
*/
|
|
3005
|
+
constructor(eventStream, _objProcessors, _errorHandler, _logger) {
|
|
3006
|
+
this._errorHandler = _errorHandler;
|
|
3007
|
+
this._logger = _logger;
|
|
3008
|
+
this._attachHandler(eventStream, 'server-intent');
|
|
3009
|
+
this._attachHandler(eventStream, 'put-object');
|
|
3010
|
+
this._attachHandler(eventStream, 'delete-object');
|
|
3011
|
+
this._attachHandler(eventStream, 'payload-transferred');
|
|
3012
|
+
this._attachHandler(eventStream, 'goodbye');
|
|
3013
|
+
this._attachHandler(eventStream, 'error');
|
|
3014
|
+
this._payloadProcessor = new PayloadProcessor(_objProcessors, _errorHandler, _logger);
|
|
3015
|
+
}
|
|
3016
|
+
addPayloadListener(listener) {
|
|
3017
|
+
this._payloadProcessor.addPayloadListener(listener);
|
|
3018
|
+
}
|
|
3019
|
+
removePayloadListener(listener) {
|
|
3020
|
+
this._payloadProcessor.removePayloadListener(listener);
|
|
3021
|
+
}
|
|
3022
|
+
_attachHandler(stream, eventName) {
|
|
2454
3023
|
stream.addEventListener(eventName, async (event) => {
|
|
2455
3024
|
if (event?.data) {
|
|
2456
3025
|
this._logger?.debug(`Received ${eventName} event. Data is ${event.data}`);
|
|
2457
3026
|
try {
|
|
2458
|
-
|
|
3027
|
+
this._payloadProcessor.processEvents([
|
|
3028
|
+
{ event: eventName, data: JSON.parse(event.data) },
|
|
3029
|
+
]);
|
|
2459
3030
|
}
|
|
2460
3031
|
catch {
|
|
2461
3032
|
this._logger?.error(`Stream received data that was unable to be processed in "${eventName}" message`);
|
|
2462
3033
|
this._logger?.debug(`Data follows: ${event.data}`);
|
|
2463
|
-
this._errorHandler?.(DataSourceErrorKind.InvalidData, 'Malformed data in
|
|
3034
|
+
this._errorHandler?.(DataSourceErrorKind.InvalidData, 'Malformed data in EventStream.');
|
|
2464
3035
|
}
|
|
2465
3036
|
}
|
|
2466
3037
|
else {
|
|
2467
|
-
this._errorHandler?.(DataSourceErrorKind.Unknown, '
|
|
3038
|
+
this._errorHandler?.(DataSourceErrorKind.Unknown, 'Event from EventStream missing data.');
|
|
2468
3039
|
}
|
|
2469
3040
|
});
|
|
2470
3041
|
}
|
|
2471
|
-
|
|
2472
|
-
|
|
3042
|
+
}
|
|
3043
|
+
|
|
3044
|
+
/**
|
|
3045
|
+
* Creates an InitMetadata object from initialization headers.
|
|
3046
|
+
*
|
|
3047
|
+
* @param initHeaders Initialization headers received when establishing
|
|
3048
|
+
* a streaming or polling connection to LD.
|
|
3049
|
+
* @returns InitMetadata object, or undefined if initHeaders is undefined
|
|
3050
|
+
* or missing the required header values.
|
|
3051
|
+
*/
|
|
3052
|
+
function initMetadataFromHeaders(initHeaders) {
|
|
3053
|
+
if (initHeaders) {
|
|
3054
|
+
const envIdKey = Object.keys(initHeaders).find((key) => key.toLowerCase() === 'x-ld-envid');
|
|
3055
|
+
if (envIdKey) {
|
|
3056
|
+
return { environmentId: initHeaders[envIdKey] };
|
|
3057
|
+
}
|
|
2473
3058
|
}
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
3059
|
+
return undefined;
|
|
3060
|
+
}
|
|
3061
|
+
|
|
3062
|
+
const UNKNOWN_PLUGIN_NAME = 'unknown plugin';
|
|
3063
|
+
function safeGetName(logger, plugin) {
|
|
3064
|
+
try {
|
|
3065
|
+
return plugin.getMetadata().name || UNKNOWN_PLUGIN_NAME;
|
|
3066
|
+
}
|
|
3067
|
+
catch {
|
|
3068
|
+
logger.error(`Exception thrown getting metadata for plugin. Unable to get plugin name.`);
|
|
3069
|
+
return UNKNOWN_PLUGIN_NAME;
|
|
2478
3070
|
}
|
|
2479
3071
|
}
|
|
2480
3072
|
|
|
3073
|
+
function safeGetHooks(logger, environmentMetadata, plugins) {
|
|
3074
|
+
const hooks = [];
|
|
3075
|
+
plugins.forEach((plugin) => {
|
|
3076
|
+
try {
|
|
3077
|
+
const pluginHooks = plugin.getHooks?.(environmentMetadata);
|
|
3078
|
+
if (pluginHooks === undefined) {
|
|
3079
|
+
logger.error(`Plugin ${safeGetName(logger, plugin)} returned undefined from getHooks.`);
|
|
3080
|
+
}
|
|
3081
|
+
else if (pluginHooks && pluginHooks.length > 0) {
|
|
3082
|
+
hooks.push(...pluginHooks);
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
catch (error) {
|
|
3086
|
+
logger.error(`Exception thrown getting hooks for plugin ${safeGetName(logger, plugin)}. Unable to get hooks.`);
|
|
3087
|
+
}
|
|
3088
|
+
});
|
|
3089
|
+
return hooks;
|
|
3090
|
+
}
|
|
3091
|
+
|
|
3092
|
+
function safeRegisterPlugins(logger, environmentMetadata, client, plugins) {
|
|
3093
|
+
plugins.forEach((plugin) => {
|
|
3094
|
+
try {
|
|
3095
|
+
plugin.register(client, environmentMetadata);
|
|
3096
|
+
}
|
|
3097
|
+
catch (error) {
|
|
3098
|
+
logger.error(`Exception thrown registering plugin ${safeGetName(logger, plugin)}.`);
|
|
3099
|
+
}
|
|
3100
|
+
});
|
|
3101
|
+
}
|
|
3102
|
+
|
|
2481
3103
|
var index = /*#__PURE__*/Object.freeze({
|
|
2482
3104
|
__proto__: null,
|
|
2483
3105
|
ClientMessages: ClientMessages,
|
|
@@ -2489,12 +3111,17 @@ var index = /*#__PURE__*/Object.freeze({
|
|
|
2489
3111
|
InputEvalEvent: InputEvalEvent,
|
|
2490
3112
|
InputIdentifyEvent: InputIdentifyEvent,
|
|
2491
3113
|
NullEventProcessor: NullEventProcessor,
|
|
2492
|
-
|
|
3114
|
+
PayloadProcessor: PayloadProcessor,
|
|
3115
|
+
PayloadStreamReader: PayloadStreamReader,
|
|
3116
|
+
initMetadataFromHeaders: initMetadataFromHeaders,
|
|
2493
3117
|
isLegacyUser: isLegacyUser,
|
|
2494
3118
|
isMultiKind: isMultiKind,
|
|
2495
3119
|
isSingleKind: isSingleKind,
|
|
3120
|
+
safeGetHooks: safeGetHooks,
|
|
3121
|
+
safeGetName: safeGetName,
|
|
3122
|
+
safeRegisterPlugins: safeRegisterPlugins,
|
|
2496
3123
|
shouldSample: shouldSample
|
|
2497
3124
|
});
|
|
2498
3125
|
|
|
2499
|
-
export { ApplicationTags, AttributeReference, AutoEnvAttributes, BasicLogger, ClientContext, Context, ContextFilter, DataSourceErrorKind, DateValidator, FactoryOrInstance, Function, KindValidator, LDClientError, LDFileDataSourceError, LDPollingError, LDStreamingError, LDTimeoutError, LDUnexpectedResponseError, NullableBoolean, NumberWithMinimum, OptionMessages, SafeLogger, ServiceEndpoints, StringMatchingRegex, Type, TypeArray, TypeValidators, base64UrlEncode, cancelableTimedPromise, clone, createSafeLogger, debounce, deepCompact, defaultHeaders, fastDeepEqual, getEventsUri, getPollingUri, getStreamingUri, httpErrorMessage, index as internal, isHttpLocallyRecoverable, isHttpRecoverable, noop, secondsToMillis, shouldRetry, sleep, index$1 as subsystem, timedPromise };
|
|
3126
|
+
export { ApplicationTags, AttributeReference, AutoEnvAttributes, BasicLogger, ClientContext, CompositeDataSource, Context, ContextFilter, DataSourceErrorKind, DateValidator, DefaultBackoff, FactoryOrInstance, Function, KindValidator, LDClientError, LDFileDataSourceError, LDPollingError, LDStreamingError, LDTimeoutError, LDUnexpectedResponseError, NullableBoolean, NumberWithMinimum, OptionMessages, SafeLogger, ServiceEndpoints, StringMatchingRegex, Type, TypeArray, TypeValidators, base64UrlEncode, cancelableTimedPromise, clone, createSafeLogger, debounce, deepCompact, defaultHeaders, fastDeepEqual, getEventsUri, getPollingUri, getStreamingUri, httpErrorMessage, index as internal, isHttpLocallyRecoverable, isHttpRecoverable, noop, secondsToMillis, shouldRetry, sleep, index$1 as subsystem, timedPromise };
|
|
2500
3127
|
//# sourceMappingURL=index.mjs.map
|