@launchdarkly/js-sdk-common 2.16.0 → 2.18.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.
Files changed (116) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/cjs/AttributeReference.d.ts +1 -0
  3. package/dist/cjs/AttributeReference.d.ts.map +1 -1
  4. package/dist/cjs/Context.d.ts +9 -0
  5. package/dist/cjs/Context.d.ts.map +1 -1
  6. package/dist/cjs/api/index.d.ts +1 -0
  7. package/dist/cjs/api/index.d.ts.map +1 -1
  8. package/dist/cjs/api/integrations/index.d.ts +2 -0
  9. package/dist/cjs/api/integrations/index.d.ts.map +1 -0
  10. package/dist/cjs/api/integrations/plugins.d.ts +128 -0
  11. package/dist/cjs/api/integrations/plugins.d.ts.map +1 -0
  12. package/dist/cjs/api/subsystem/DataSystem/CallbackHandler.d.ts +16 -0
  13. package/dist/cjs/api/subsystem/DataSystem/CallbackHandler.d.ts.map +1 -0
  14. package/dist/cjs/api/subsystem/DataSystem/DataSource.d.ts +21 -0
  15. package/dist/cjs/api/subsystem/DataSystem/DataSource.d.ts.map +1 -0
  16. package/dist/cjs/api/subsystem/DataSystem/index.d.ts +2 -0
  17. package/dist/cjs/api/subsystem/DataSystem/index.d.ts.map +1 -0
  18. package/dist/cjs/api/subsystem/index.d.ts +2 -1
  19. package/dist/cjs/api/subsystem/index.d.ts.map +1 -1
  20. package/dist/cjs/datasource/Backoff.d.ts +46 -0
  21. package/dist/cjs/datasource/Backoff.d.ts.map +1 -0
  22. package/dist/cjs/datasource/CompositeDataSource.d.ts +62 -0
  23. package/dist/cjs/datasource/CompositeDataSource.d.ts.map +1 -0
  24. package/dist/cjs/datasource/dataSourceList.d.ts +47 -0
  25. package/dist/cjs/datasource/dataSourceList.d.ts.map +1 -0
  26. package/dist/cjs/datasource/index.d.ts +3 -1
  27. package/dist/cjs/datasource/index.d.ts.map +1 -1
  28. package/dist/cjs/index.cjs +777 -47
  29. package/dist/cjs/index.cjs.map +1 -1
  30. package/dist/cjs/index.d.ts +2 -2
  31. package/dist/cjs/index.d.ts.map +1 -1
  32. package/dist/cjs/internal/events/EventProcessor.d.ts +1 -1
  33. package/dist/cjs/internal/events/EventProcessor.d.ts.map +1 -1
  34. package/dist/cjs/internal/events/LDEventSummarizer.d.ts +32 -0
  35. package/dist/cjs/internal/events/LDEventSummarizer.d.ts.map +1 -0
  36. package/dist/cjs/internal/events/MultiEventSummarizer.d.ts +13 -0
  37. package/dist/cjs/internal/events/MultiEventSummarizer.d.ts.map +1 -0
  38. package/dist/cjs/internal/fdv2/index.d.ts +3 -2
  39. package/dist/cjs/internal/fdv2/index.d.ts.map +1 -1
  40. package/dist/cjs/internal/fdv2/{payloadReader.d.ts → payloadProcessor.d.ts} +27 -16
  41. package/dist/cjs/internal/fdv2/payloadProcessor.d.ts.map +1 -0
  42. package/dist/cjs/internal/fdv2/payloadStreamReader.d.ts +31 -0
  43. package/dist/cjs/internal/fdv2/payloadStreamReader.d.ts.map +1 -0
  44. package/dist/cjs/internal/index.d.ts +2 -0
  45. package/dist/cjs/internal/index.d.ts.map +1 -1
  46. package/dist/cjs/internal/json/canonicalize.d.ts +10 -0
  47. package/dist/cjs/internal/json/canonicalize.d.ts.map +1 -0
  48. package/dist/cjs/internal/json/index.d.ts +2 -0
  49. package/dist/cjs/internal/json/index.d.ts.map +1 -0
  50. package/dist/cjs/internal/plugins/index.d.ts +4 -0
  51. package/dist/cjs/internal/plugins/index.d.ts.map +1 -0
  52. package/dist/cjs/internal/plugins/safeGetHooks.d.ts +4 -0
  53. package/dist/cjs/internal/plugins/safeGetHooks.d.ts.map +1 -0
  54. package/dist/cjs/internal/plugins/safeGetName.d.ts +4 -0
  55. package/dist/cjs/internal/plugins/safeGetName.d.ts.map +1 -0
  56. package/dist/cjs/internal/plugins/safeRegisterPlugins.d.ts +4 -0
  57. package/dist/cjs/internal/plugins/safeRegisterPlugins.d.ts.map +1 -0
  58. package/dist/esm/AttributeReference.d.ts +1 -0
  59. package/dist/esm/AttributeReference.d.ts.map +1 -1
  60. package/dist/esm/Context.d.ts +9 -0
  61. package/dist/esm/Context.d.ts.map +1 -1
  62. package/dist/esm/api/index.d.ts +1 -0
  63. package/dist/esm/api/index.d.ts.map +1 -1
  64. package/dist/esm/api/integrations/index.d.ts +2 -0
  65. package/dist/esm/api/integrations/index.d.ts.map +1 -0
  66. package/dist/esm/api/integrations/plugins.d.ts +128 -0
  67. package/dist/esm/api/integrations/plugins.d.ts.map +1 -0
  68. package/dist/esm/api/subsystem/DataSystem/CallbackHandler.d.ts +16 -0
  69. package/dist/esm/api/subsystem/DataSystem/CallbackHandler.d.ts.map +1 -0
  70. package/dist/esm/api/subsystem/DataSystem/DataSource.d.ts +21 -0
  71. package/dist/esm/api/subsystem/DataSystem/DataSource.d.ts.map +1 -0
  72. package/dist/esm/api/subsystem/DataSystem/index.d.ts +2 -0
  73. package/dist/esm/api/subsystem/DataSystem/index.d.ts.map +1 -0
  74. package/dist/esm/api/subsystem/index.d.ts +2 -1
  75. package/dist/esm/api/subsystem/index.d.ts.map +1 -1
  76. package/dist/esm/datasource/Backoff.d.ts +46 -0
  77. package/dist/esm/datasource/Backoff.d.ts.map +1 -0
  78. package/dist/esm/datasource/CompositeDataSource.d.ts +62 -0
  79. package/dist/esm/datasource/CompositeDataSource.d.ts.map +1 -0
  80. package/dist/esm/datasource/dataSourceList.d.ts +47 -0
  81. package/dist/esm/datasource/dataSourceList.d.ts.map +1 -0
  82. package/dist/esm/datasource/index.d.ts +3 -1
  83. package/dist/esm/datasource/index.d.ts.map +1 -1
  84. package/dist/esm/index.d.ts +2 -2
  85. package/dist/esm/index.d.ts.map +1 -1
  86. package/dist/esm/index.mjs +776 -48
  87. package/dist/esm/index.mjs.map +1 -1
  88. package/dist/esm/internal/events/EventProcessor.d.ts +1 -1
  89. package/dist/esm/internal/events/EventProcessor.d.ts.map +1 -1
  90. package/dist/esm/internal/events/LDEventSummarizer.d.ts +32 -0
  91. package/dist/esm/internal/events/LDEventSummarizer.d.ts.map +1 -0
  92. package/dist/esm/internal/events/MultiEventSummarizer.d.ts +13 -0
  93. package/dist/esm/internal/events/MultiEventSummarizer.d.ts.map +1 -0
  94. package/dist/esm/internal/fdv2/index.d.ts +3 -2
  95. package/dist/esm/internal/fdv2/index.d.ts.map +1 -1
  96. package/dist/esm/internal/fdv2/{payloadReader.d.ts → payloadProcessor.d.ts} +27 -16
  97. package/dist/esm/internal/fdv2/payloadProcessor.d.ts.map +1 -0
  98. package/dist/esm/internal/fdv2/payloadStreamReader.d.ts +31 -0
  99. package/dist/esm/internal/fdv2/payloadStreamReader.d.ts.map +1 -0
  100. package/dist/esm/internal/index.d.ts +2 -0
  101. package/dist/esm/internal/index.d.ts.map +1 -1
  102. package/dist/esm/internal/json/canonicalize.d.ts +10 -0
  103. package/dist/esm/internal/json/canonicalize.d.ts.map +1 -0
  104. package/dist/esm/internal/json/index.d.ts +2 -0
  105. package/dist/esm/internal/json/index.d.ts.map +1 -0
  106. package/dist/esm/internal/plugins/index.d.ts +4 -0
  107. package/dist/esm/internal/plugins/index.d.ts.map +1 -0
  108. package/dist/esm/internal/plugins/safeGetHooks.d.ts +4 -0
  109. package/dist/esm/internal/plugins/safeGetHooks.d.ts.map +1 -0
  110. package/dist/esm/internal/plugins/safeGetName.d.ts +4 -0
  111. package/dist/esm/internal/plugins/safeGetName.d.ts.map +1 -0
  112. package/dist/esm/internal/plugins/safeRegisterPlugins.d.ts +4 -0
  113. package/dist/esm/internal/plugins/safeRegisterPlugins.d.ts.map +1 -0
  114. package/package.json +1 -1
  115. package/dist/cjs/internal/fdv2/payloadReader.d.ts.map +0 -1
  116. package/dist/esm/internal/fdv2/payloadReader.d.ts.map +0 -1
@@ -117,6 +117,9 @@ class AttributeReference {
117
117
  return (this.depth === other.depth &&
118
118
  this._components.every((value, index) => value === other.getComponent(index)));
119
119
  }
120
+ get components() {
121
+ return [...this._components];
122
+ }
120
123
  }
121
124
  /**
122
125
  * For use as invalid references when deserializing Flag/Segment data.
@@ -316,6 +319,41 @@ function isLegacyUser(context) {
316
319
  return !('kind' in context) || context.kind === null || context.kind === undefined;
317
320
  }
318
321
 
322
+ /**
323
+ * Given some object to serialize product a canonicalized JSON string.
324
+ * https://www.rfc-editor.org/rfc/rfc8785.html
325
+ *
326
+ * We do not support custom toJSON methods on objects. Objects should be limited to basic types.
327
+ *
328
+ * @param object The object to serialize.
329
+ */
330
+ function canonicalize(object, visited = []) {
331
+ // For JavaScript the default JSON serialization will produce canonicalized output for basic types.
332
+ if (object === null || typeof object !== 'object') {
333
+ return JSON.stringify(object);
334
+ }
335
+ if (visited.includes(object)) {
336
+ throw new Error('Cycle detected');
337
+ }
338
+ if (Array.isArray(object)) {
339
+ const values = object
340
+ .map((item) => canonicalize(item, [...visited, object]))
341
+ .map((item) => (item === undefined ? 'null' : item));
342
+ return `[${values.join(',')}]`;
343
+ }
344
+ const values = Object.keys(object)
345
+ .sort()
346
+ .map((key) => {
347
+ const value = canonicalize(object[key], [...visited, object]);
348
+ if (value !== undefined) {
349
+ return `${JSON.stringify(key)}:${value}`;
350
+ }
351
+ return undefined;
352
+ })
353
+ .filter((item) => item !== undefined);
354
+ return `{${values.join(',')}}`;
355
+ }
356
+
319
357
  // The general strategy for the context is to transform the passed in context
320
358
  // as little as possible. We do convert the legacy users to a single kind
321
359
  // context, but we do not translate all passed contexts into a rigid structure.
@@ -690,6 +728,28 @@ class Context {
690
728
  get legacy() {
691
729
  return this._wasLegacy;
692
730
  }
731
+ /**
732
+ * Get the serialized canonical JSON for this context. This is not filtered for use in events.
733
+ *
734
+ * This method will cache the result.
735
+ *
736
+ * @returns The serialized canonical JSON or undefined if it cannot be serialized.
737
+ */
738
+ canonicalUnfilteredJson() {
739
+ if (!this.valid) {
740
+ return undefined;
741
+ }
742
+ if (this._cachedCanonicalJson) {
743
+ return this._cachedCanonicalJson;
744
+ }
745
+ try {
746
+ this._cachedCanonicalJson = canonicalize(Context.toLDContext(this));
747
+ }
748
+ catch {
749
+ // Indicated by undefined being returned.
750
+ }
751
+ return this._cachedCanonicalJson;
752
+ }
693
753
  }
694
754
  Context.UserKind = DEFAULT_KIND;
695
755
 
@@ -813,6 +873,482 @@ class ContextFilter {
813
873
  }
814
874
  }
815
875
 
876
+ const MAX_RETRY_DELAY = 30 * 1000; // Maximum retry delay 30 seconds.
877
+ const JITTER_RATIO = 0.5; // Delay should be 50%-100% of calculated time.
878
+ /**
879
+ * Implements exponential backoff and jitter. This class tracks successful connections and failures
880
+ * and produces a retry delay.
881
+ *
882
+ * It does not start any timers or directly control a connection.
883
+ *
884
+ * The backoff follows an exponential backoff scheme with 50% jitter starting at
885
+ * initialRetryDelayMillis and capping at MAX_RETRY_DELAY. If RESET_INTERVAL has elapsed after a
886
+ * success, without an intervening faulure, then the backoff is reset to initialRetryDelayMillis.
887
+ */
888
+ class DefaultBackoff {
889
+ constructor(initialRetryDelayMillis, _retryResetIntervalMillis, _random = Math.random) {
890
+ this._retryResetIntervalMillis = _retryResetIntervalMillis;
891
+ this._random = _random;
892
+ this._retryCount = 0;
893
+ // Initial retry delay cannot be 0.
894
+ this._initialRetryDelayMillis = Math.max(1, initialRetryDelayMillis);
895
+ this._maxExponent = Math.ceil(Math.log2(MAX_RETRY_DELAY / this._initialRetryDelayMillis));
896
+ }
897
+ _backoff() {
898
+ const exponent = Math.min(this._retryCount, this._maxExponent);
899
+ const delay = this._initialRetryDelayMillis * 2 ** exponent;
900
+ return Math.min(delay, MAX_RETRY_DELAY);
901
+ }
902
+ _jitter(computedDelayMillis) {
903
+ return computedDelayMillis - Math.trunc(this._random() * JITTER_RATIO * computedDelayMillis);
904
+ }
905
+ /**
906
+ * This function should be called when a connection attempt is successful.
907
+ *
908
+ * @param timeStampMs The time of the success. Used primarily for testing, when not provided
909
+ * the current time is used.
910
+ */
911
+ success(timeStampMs = Date.now()) {
912
+ this._activeSince = timeStampMs;
913
+ }
914
+ /**
915
+ * This function should be called when a connection fails. It returns the a delay, in
916
+ * milliseconds, after which a reconnection attempt should be made.
917
+ *
918
+ * @param timeStampMs The time of the success. Used primarily for testing, when not provided
919
+ * the current time is used.
920
+ * @returns The delay before the next connection attempt.
921
+ */
922
+ fail(timeStampMs = Date.now()) {
923
+ // If the last successful connection was active for more than the RESET_INTERVAL, then we
924
+ // return to the initial retry delay.
925
+ if (this._activeSince !== undefined &&
926
+ timeStampMs - this._activeSince > this._retryResetIntervalMillis) {
927
+ this._retryCount = 0;
928
+ }
929
+ this._activeSince = undefined;
930
+ const delay = this._jitter(this._backoff());
931
+ this._retryCount += 1;
932
+ return delay;
933
+ }
934
+ }
935
+
936
+ /**
937
+ * Handler that connects the current {@link DataSource} to the {@link CompositeDataSource}. A single
938
+ * {@link CallbackHandler} should only be given to one {@link DataSource}. Use {@link disable()} to
939
+ * prevent additional callback events.
940
+ */
941
+ class CallbackHandler {
942
+ constructor(_dataCallback, _statusCallback) {
943
+ this._dataCallback = _dataCallback;
944
+ this._statusCallback = _statusCallback;
945
+ this._disabled = false;
946
+ }
947
+ disable() {
948
+ this._disabled = true;
949
+ }
950
+ async dataHandler(basis, data) {
951
+ if (this._disabled) {
952
+ return;
953
+ }
954
+ // TODO: SDK-1044 track selector for future synchronizer to use
955
+ // report data up
956
+ this._dataCallback(basis, data);
957
+ }
958
+ async statusHandler(status, err) {
959
+ if (this._disabled) {
960
+ return;
961
+ }
962
+ this._statusCallback(status, err);
963
+ }
964
+ }
965
+
966
+ // TODO: refactor client-sdk to use this enum
967
+ var DataSourceState;
968
+ (function (DataSourceState) {
969
+ // Positive confirmation of connection/data receipt
970
+ DataSourceState[DataSourceState["Valid"] = 0] = "Valid";
971
+ // Spinning up to make first connection attempt
972
+ DataSourceState[DataSourceState["Initializing"] = 1] = "Initializing";
973
+ // Transient issue, automatic retry is expected
974
+ DataSourceState[DataSourceState["Interrupted"] = 2] = "Interrupted";
975
+ // Data source was closed and will not retry automatically.
976
+ DataSourceState[DataSourceState["Closed"] = 3] = "Closed";
977
+ })(DataSourceState || (DataSourceState = {}));
978
+
979
+ /**
980
+ * Helper class for {@link CompositeDataSource} to manage iterating on data sources and removing them on the fly.
981
+ */
982
+ class DataSourceList {
983
+ /**
984
+ * @param circular whether to loop off the end of the list back to the start
985
+ * @param initialList of content
986
+ */
987
+ constructor(circular, initialList) {
988
+ this._list = initialList ? [...initialList] : [];
989
+ this._circular = circular;
990
+ this._pos = 0;
991
+ }
992
+ /**
993
+ * Returns the current head and then iterates.
994
+ */
995
+ next() {
996
+ if (this._list.length <= 0 || this._pos >= this._list.length) {
997
+ return undefined;
998
+ }
999
+ const result = this._list[this._pos];
1000
+ if (this._circular) {
1001
+ this._pos = (this._pos + 1) % this._list.length;
1002
+ }
1003
+ else {
1004
+ this._pos += 1;
1005
+ }
1006
+ return result;
1007
+ }
1008
+ /**
1009
+ * Replaces all elements with the provided list and resets the position of head to the start.
1010
+ *
1011
+ * @param input that will replace existing list
1012
+ */
1013
+ replace(input) {
1014
+ this._list = [...input];
1015
+ this._pos = 0;
1016
+ }
1017
+ /**
1018
+ * 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.
1019
+ *
1020
+ * @param element to remove
1021
+ * @returns true if element was removed
1022
+ */
1023
+ remove(element) {
1024
+ const index = this._list.indexOf(element);
1025
+ if (index < 0) {
1026
+ return false;
1027
+ }
1028
+ this._list.splice(index, 1);
1029
+ if (this._list.length > 0) {
1030
+ // if removed item was before head, adjust head
1031
+ if (index < this._pos) {
1032
+ this._pos -= 1;
1033
+ }
1034
+ if (this._circular && this._pos > this._list.length - 1) {
1035
+ this._pos = 0;
1036
+ }
1037
+ }
1038
+ return true;
1039
+ }
1040
+ /**
1041
+ * Reset the head position to the start of the list.
1042
+ */
1043
+ reset() {
1044
+ this._pos = 0;
1045
+ }
1046
+ /**
1047
+ * @returns the current head position in the list, 0 indexed.
1048
+ */
1049
+ pos() {
1050
+ return this._pos;
1051
+ }
1052
+ /**
1053
+ * @returns the current length of the list
1054
+ */
1055
+ length() {
1056
+ return this._list.length;
1057
+ }
1058
+ /**
1059
+ * Clears the list and resets head.
1060
+ */
1061
+ clear() {
1062
+ this._list = [];
1063
+ this._pos = 0;
1064
+ }
1065
+ }
1066
+
1067
+ const DEFAULT_FALLBACK_TIME_MS = 2 * 60 * 1000;
1068
+ const DEFAULT_RECOVERY_TIME_MS = 5 * 60 * 1000;
1069
+ /**
1070
+ * The {@link CompositeDataSource} can combine a number of {@link DataSystemInitializer}s and {@link DataSystemSynchronizer}s
1071
+ * into a single {@link DataSource}, implementing fallback and recovery logic internally to choose where data is sourced from.
1072
+ */
1073
+ class CompositeDataSource {
1074
+ /**
1075
+ * @param initializers factories to create {@link DataSystemInitializer}s, in priority order.
1076
+ * @param synchronizers factories to create {@link DataSystemSynchronizer}s, in priority order.
1077
+ */
1078
+ constructor(initializers, synchronizers, _logger, _transitionConditions = {
1079
+ [DataSourceState.Valid]: {
1080
+ durationMS: DEFAULT_RECOVERY_TIME_MS,
1081
+ transition: 'recover',
1082
+ },
1083
+ [DataSourceState.Interrupted]: {
1084
+ durationMS: DEFAULT_FALLBACK_TIME_MS,
1085
+ transition: 'fallback',
1086
+ },
1087
+ }, _backoff = new DefaultBackoff(1000, 30000)) {
1088
+ this._logger = _logger;
1089
+ this._transitionConditions = _transitionConditions;
1090
+ this._backoff = _backoff;
1091
+ this._stopped = true;
1092
+ this._cancelTokens = [];
1093
+ this._cancellableDelay = (delayMS) => {
1094
+ let timeout;
1095
+ const promise = new Promise((res, _) => {
1096
+ timeout = setTimeout(res, delayMS);
1097
+ });
1098
+ return {
1099
+ promise,
1100
+ cancel() {
1101
+ if (timeout) {
1102
+ clearTimeout(timeout);
1103
+ timeout = undefined;
1104
+ }
1105
+ },
1106
+ };
1107
+ };
1108
+ this._externalTransitionPromise = new Promise((resolveTransition) => {
1109
+ this._externalTransitionResolve = resolveTransition;
1110
+ });
1111
+ this._initPhaseActive = initializers.length > 0; // init phase if we have initializers
1112
+ this._initFactories = new DataSourceList(false, initializers);
1113
+ this._syncFactories = new DataSourceList(true, synchronizers);
1114
+ }
1115
+ async start(dataCallback, statusCallback) {
1116
+ if (!this._stopped) {
1117
+ // don't allow multiple simultaneous runs
1118
+ this._logger?.info('CompositeDataSource already running. Ignoring call to start.');
1119
+ return;
1120
+ }
1121
+ this._stopped = false;
1122
+ this._logger?.debug(`CompositeDataSource starting with (${this._initFactories.length()} initializers, ${this._syncFactories.length()} synchronizers).`);
1123
+ // this wrapper turns status updates from underlying data sources into a valid series of status updates for the consumer of this
1124
+ // composite data source
1125
+ const sanitizedStatusCallback = this._wrapStatusCallbackWithSanitizer(statusCallback);
1126
+ sanitizedStatusCallback(DataSourceState.Initializing);
1127
+ let lastTransition;
1128
+ // eslint-disable-next-line no-constant-condition
1129
+ while (true) {
1130
+ const { dataSource: currentDS, isPrimary, cullDSFactory, } = this._pickDataSource(lastTransition);
1131
+ const internalTransitionPromise = new Promise((transitionResolve) => {
1132
+ if (currentDS) {
1133
+ // these local variables are used for handling automatic transition related to data source status (ex: recovering to primary after
1134
+ // secondary has been valid for N many seconds)
1135
+ let lastState;
1136
+ let cancelScheduledTransition = () => { };
1137
+ // this callback handler can be disabled and ensures only one transition request occurs
1138
+ const callbackHandler = new CallbackHandler((basis, data) => {
1139
+ this._backoff.success();
1140
+ dataCallback(basis, data);
1141
+ if (basis && this._initPhaseActive) {
1142
+ // transition to sync if we get basis during init
1143
+ callbackHandler.disable();
1144
+ this._consumeCancelToken(cancelScheduledTransition);
1145
+ sanitizedStatusCallback(DataSourceState.Interrupted);
1146
+ transitionResolve({ transition: 'switchToSync' });
1147
+ }
1148
+ }, (state, err) => {
1149
+ // When we get a status update, we want to fallback if it is an error. We also want to schedule a transition for some
1150
+ // time in the future if this status remains for some duration (ex: Recover to primary synchronizer after the secondary
1151
+ // synchronizer has been Valid for some time). These scheduled transitions are configurable in the constructor.
1152
+ this._logger?.debug(`CompositeDataSource received state ${state} from underlying data source.`);
1153
+ if (err || state === DataSourceState.Closed) {
1154
+ callbackHandler.disable();
1155
+ if (err.recoverable === false) {
1156
+ // don't use this datasource's factory again
1157
+ cullDSFactory?.();
1158
+ }
1159
+ sanitizedStatusCallback(state, err);
1160
+ this._consumeCancelToken(cancelScheduledTransition);
1161
+ transitionResolve({ transition: 'fallback', err }); // unrecoverable error has occurred, so fallback
1162
+ }
1163
+ else {
1164
+ sanitizedStatusCallback(state);
1165
+ if (state !== lastState) {
1166
+ lastState = state;
1167
+ this._consumeCancelToken(cancelScheduledTransition); // cancel previously scheduled status transition if one was scheduled
1168
+ // primary source cannot recover to itself, so exclude it
1169
+ const condition = this._lookupTransitionCondition(state, isPrimary);
1170
+ if (condition) {
1171
+ const { promise, cancel } = this._cancellableDelay(condition.durationMS);
1172
+ cancelScheduledTransition = cancel;
1173
+ this._cancelTokens.push(cancelScheduledTransition);
1174
+ promise.then(() => {
1175
+ this._consumeCancelToken(cancel);
1176
+ callbackHandler.disable();
1177
+ sanitizedStatusCallback(DataSourceState.Interrupted);
1178
+ transitionResolve({ transition: condition.transition });
1179
+ });
1180
+ }
1181
+ }
1182
+ }
1183
+ });
1184
+ currentDS.start((basis, data) => callbackHandler.dataHandler(basis, data), (status, err) => callbackHandler.statusHandler(status, err));
1185
+ }
1186
+ else {
1187
+ // we don't have a data source to use!
1188
+ transitionResolve({
1189
+ transition: 'stop',
1190
+ err: {
1191
+ name: 'ExhaustedDataSources',
1192
+ message: `CompositeDataSource has exhausted all configured initializers and synchronizers.`,
1193
+ },
1194
+ });
1195
+ }
1196
+ });
1197
+ // await transition triggered by internal data source or an external stop request
1198
+ let transitionRequest = await Promise.race([
1199
+ internalTransitionPromise,
1200
+ this._externalTransitionPromise,
1201
+ ]);
1202
+ // stop the underlying datasource before transitioning to next state
1203
+ currentDS?.stop();
1204
+ if (transitionRequest.err && transitionRequest.transition !== 'stop') {
1205
+ // if the transition was due to an error, throttle the transition
1206
+ const delay = this._backoff.fail();
1207
+ const { promise, cancel: cancelDelay } = this._cancellableDelay(delay);
1208
+ this._cancelTokens.push(cancelDelay);
1209
+ const delayedTransition = promise.then(() => {
1210
+ this._consumeCancelToken(cancelDelay);
1211
+ return transitionRequest;
1212
+ });
1213
+ // race the delayed transition and external transition requests to be responsive
1214
+ transitionRequest = await Promise.race([
1215
+ delayedTransition,
1216
+ this._externalTransitionPromise,
1217
+ ]);
1218
+ // consume the delay cancel token (even if it resolved, need to stop tracking its token)
1219
+ this._consumeCancelToken(cancelDelay);
1220
+ }
1221
+ lastTransition = transitionRequest.transition;
1222
+ if (transitionRequest.transition === 'stop') {
1223
+ // exit the loop, this is intentionally not the sanitized status callback
1224
+ statusCallback(DataSourceState.Closed, transitionRequest.err);
1225
+ break;
1226
+ }
1227
+ }
1228
+ // reset so that run can be called again in the future
1229
+ this._reset();
1230
+ }
1231
+ async stop() {
1232
+ this._cancelTokens.forEach((cancel) => cancel());
1233
+ this._cancelTokens = [];
1234
+ this._externalTransitionResolve?.({ transition: 'stop' });
1235
+ }
1236
+ _reset() {
1237
+ this._stopped = true;
1238
+ this._initPhaseActive = this._initFactories.length() > 0; // init phase if we have initializers;
1239
+ this._initFactories.reset();
1240
+ this._syncFactories.reset();
1241
+ this._externalTransitionPromise = new Promise((tr) => {
1242
+ this._externalTransitionResolve = tr;
1243
+ });
1244
+ // intentionally not resetting the backoff to avoid a code path that could circumvent throttling
1245
+ }
1246
+ /**
1247
+ * Determines the next datasource and returns that datasource as well as a closure to cull the
1248
+ * datasource from the datasource lists. One example where the cull closure is invoked is if the
1249
+ * datasource has an unrecoverable error.
1250
+ */
1251
+ _pickDataSource(transition) {
1252
+ let factory;
1253
+ let isPrimary;
1254
+ switch (transition) {
1255
+ case 'switchToSync':
1256
+ this._initPhaseActive = false; // one way toggle to false, unless this class is reset()
1257
+ this._syncFactories.reset();
1258
+ isPrimary = this._syncFactories.pos() === 0;
1259
+ factory = this._syncFactories.next();
1260
+ break;
1261
+ case 'recover':
1262
+ if (this._initPhaseActive) {
1263
+ this._initFactories.reset();
1264
+ isPrimary = this._initFactories.pos() === 0;
1265
+ factory = this._initFactories.next();
1266
+ }
1267
+ else {
1268
+ this._syncFactories.reset();
1269
+ isPrimary = this._syncFactories.pos() === 0;
1270
+ factory = this._syncFactories.next();
1271
+ }
1272
+ break;
1273
+ case 'fallback':
1274
+ default:
1275
+ if (this._initPhaseActive) {
1276
+ isPrimary = this._initFactories.pos() === 0;
1277
+ factory = this._initFactories.next();
1278
+ }
1279
+ else {
1280
+ isPrimary = this._syncFactories.pos() === 0;
1281
+ factory = this._syncFactories.next();
1282
+ }
1283
+ break;
1284
+ }
1285
+ if (!factory) {
1286
+ return { dataSource: undefined, isPrimary, cullDSFactory: undefined };
1287
+ }
1288
+ return {
1289
+ dataSource: factory(),
1290
+ isPrimary,
1291
+ cullDSFactory: () => {
1292
+ if (factory) {
1293
+ this._syncFactories.remove(factory);
1294
+ }
1295
+ },
1296
+ };
1297
+ }
1298
+ /**
1299
+ * @returns the transition condition for the provided data source state or undefined
1300
+ * if there is no transition condition
1301
+ */
1302
+ _lookupTransitionCondition(state, excludeRecover) {
1303
+ const condition = this._transitionConditions[state];
1304
+ // exclude recovery can happen for certain initializers/synchronizers (ex: the primary synchronizer shouldn't recover to itself)
1305
+ if (excludeRecover && condition?.transition === 'recover') {
1306
+ return undefined;
1307
+ }
1308
+ return condition;
1309
+ }
1310
+ _consumeCancelToken(cancel) {
1311
+ cancel();
1312
+ const index = this._cancelTokens.indexOf(cancel, 0);
1313
+ if (index > -1) {
1314
+ this._cancelTokens.splice(index, 1);
1315
+ }
1316
+ }
1317
+ /**
1318
+ * This wrapper will ensure the following:
1319
+ *
1320
+ * Don't report DataSourceState.Initializing except as first status callback.
1321
+ * Map underlying DataSourceState.Closed to interrupted.
1322
+ * Don't report the same status and error twice in a row.
1323
+ */
1324
+ _wrapStatusCallbackWithSanitizer(statusCallback) {
1325
+ let alreadyReportedInitializing = false;
1326
+ let lastStatus;
1327
+ let lastErr;
1328
+ return (status, err) => {
1329
+ let sanitized = status;
1330
+ // underlying errors, closed state, or off are masked as interrupted while we transition
1331
+ if (status === DataSourceState.Closed) {
1332
+ sanitized = DataSourceState.Interrupted;
1333
+ }
1334
+ // don't report the same combination of values twice in a row
1335
+ if (sanitized === lastStatus && err === lastErr) {
1336
+ return;
1337
+ }
1338
+ if (sanitized === DataSourceState.Initializing) {
1339
+ // don't report initializing again if that has already been reported
1340
+ if (alreadyReportedInitializing) {
1341
+ return;
1342
+ }
1343
+ alreadyReportedInitializing = true;
1344
+ }
1345
+ lastStatus = sanitized;
1346
+ lastErr = err;
1347
+ statusCallback(sanitized, err);
1348
+ };
1349
+ }
1350
+ }
1351
+
816
1352
  exports.DataSourceErrorKind = void 0;
817
1353
  (function (DataSourceErrorKind) {
818
1354
  /// An unexpected error, such as an uncaught exception, further
@@ -881,6 +1417,7 @@ var LDDeliveryStatus;
881
1417
 
882
1418
  var index$1 = /*#__PURE__*/Object.freeze({
883
1419
  __proto__: null,
1420
+ get DataSourceState () { return DataSourceState; },
884
1421
  get LDDeliveryStatus () { return LDDeliveryStatus; },
885
1422
  get LDEventType () { return LDEventType; }
886
1423
  });
@@ -1876,7 +2413,9 @@ function counterKey(event) {
1876
2413
  * @internal
1877
2414
  */
1878
2415
  class EventSummarizer {
1879
- constructor() {
2416
+ constructor(_singleContext = false, _contextFilter) {
2417
+ this._singleContext = _singleContext;
2418
+ this._contextFilter = _contextFilter;
1880
2419
  this._startDate = 0;
1881
2420
  this._endDate = 0;
1882
2421
  this._counters = {};
@@ -1884,6 +2423,9 @@ class EventSummarizer {
1884
2423
  }
1885
2424
  summarizeEvent(event) {
1886
2425
  if (isFeature(event) && !event.excludeFromSummaries) {
2426
+ if (!this._context) {
2427
+ this._context = event.context;
2428
+ }
1887
2429
  const countKey = counterKey(event);
1888
2430
  const counter = this._counters[countKey];
1889
2431
  let kinds = this._contextKinds[event.key];
@@ -1933,14 +2475,19 @@ class EventSummarizer {
1933
2475
  flagSummary.counters.push(counterOut);
1934
2476
  return acc;
1935
2477
  }, {});
1936
- return {
2478
+ const event = {
1937
2479
  startDate: this._startDate,
1938
2480
  endDate: this._endDate,
1939
2481
  features,
1940
2482
  kind: 'summary',
2483
+ context: this._context !== undefined && this._singleContext
2484
+ ? this._contextFilter?.filter(this._context)
2485
+ : undefined,
1941
2486
  };
2487
+ this._clearSummary();
2488
+ return event;
1942
2489
  }
1943
- clearSummary() {
2490
+ _clearSummary() {
1944
2491
  this._startDate = 0;
1945
2492
  this._endDate = 0;
1946
2493
  this._counters = {};
@@ -1955,6 +2502,38 @@ class LDInvalidSDKKeyError extends Error {
1955
2502
  }
1956
2503
  }
1957
2504
 
2505
+ class MultiEventSummarizer {
2506
+ constructor(_contextFilter, _logger) {
2507
+ this._contextFilter = _contextFilter;
2508
+ this._logger = _logger;
2509
+ this._summarizers = {};
2510
+ }
2511
+ summarizeEvent(event) {
2512
+ if (isFeature(event)) {
2513
+ const key = event.context.canonicalUnfilteredJson();
2514
+ if (!key) {
2515
+ if (event.context.valid) {
2516
+ // The context appeared valid, but it could not be hashed.
2517
+ // This is likely because of a cycle in the data.
2518
+ this._logger?.error('Unable to serialize context, likely the context contains a cycle.');
2519
+ }
2520
+ return;
2521
+ }
2522
+ let summarizer = this._summarizers[key];
2523
+ if (!summarizer) {
2524
+ this._summarizers[key] = new EventSummarizer(true, this._contextFilter);
2525
+ summarizer = this._summarizers[key];
2526
+ }
2527
+ summarizer.summarizeEvent(event);
2528
+ }
2529
+ }
2530
+ getSummaries() {
2531
+ const summarizersToFlush = this._summarizers;
2532
+ this._summarizers = {};
2533
+ return Object.values(summarizersToFlush).map((summarizer) => summarizer.getSummary());
2534
+ }
2535
+ }
2536
+
1958
2537
  /**
1959
2538
  * The contents of this file are for event sampling. They are not used for
1960
2539
  * any purpose requiring cryptographic security.
@@ -1975,12 +2554,14 @@ function shouldSample(ratio) {
1975
2554
  return Math.floor(Math.random() * truncated) === 0;
1976
2555
  }
1977
2556
 
2557
+ function isMultiEventSummarizer(summarizer) {
2558
+ return summarizer.getSummaries !== undefined;
2559
+ }
1978
2560
  class EventProcessor {
1979
- constructor(_config, clientContext, baseHeaders, _contextDeduplicator, _diagnosticsManager, start = true) {
2561
+ constructor(_config, clientContext, baseHeaders, _contextDeduplicator, _diagnosticsManager, start = true, summariesPerContext = false) {
1980
2562
  this._config = _config;
1981
2563
  this._contextDeduplicator = _contextDeduplicator;
1982
2564
  this._diagnosticsManager = _diagnosticsManager;
1983
- this._summarizer = new EventSummarizer();
1984
2565
  this._queue = [];
1985
2566
  this._lastKnownPastTime = 0;
1986
2567
  this._droppedEvents = 0;
@@ -1993,6 +2574,12 @@ class EventProcessor {
1993
2574
  this._logger = clientContext.basicConfiguration.logger;
1994
2575
  this._eventSender = new EventSender(clientContext, baseHeaders);
1995
2576
  this._contextFilter = new ContextFilter(_config.allAttributesPrivate, _config.privateAttributes.map((ref) => new AttributeReference(ref)));
2577
+ if (summariesPerContext) {
2578
+ this._summarizer = new MultiEventSummarizer(this._contextFilter, this._logger);
2579
+ }
2580
+ else {
2581
+ this._summarizer = new EventSummarizer();
2582
+ }
1996
2583
  if (start) {
1997
2584
  this.start();
1998
2585
  }
@@ -2044,10 +2631,19 @@ class EventProcessor {
2044
2631
  }
2045
2632
  const eventsToFlush = this._queue;
2046
2633
  this._queue = [];
2047
- const summary = this._summarizer.getSummary();
2048
- this._summarizer.clearSummary();
2049
- if (Object.keys(summary.features).length) {
2050
- eventsToFlush.push(summary);
2634
+ if (isMultiEventSummarizer(this._summarizer)) {
2635
+ const summaries = this._summarizer.getSummaries();
2636
+ summaries.forEach((summary) => {
2637
+ if (Object.keys(summary.features).length) {
2638
+ eventsToFlush.push(summary);
2639
+ }
2640
+ });
2641
+ }
2642
+ else {
2643
+ const summary = this._summarizer.getSummary();
2644
+ if (Object.keys(summary.features).length) {
2645
+ eventsToFlush.push(summary);
2646
+ }
2051
2647
  }
2052
2648
  if (!eventsToFlush.length) {
2053
2649
  return;
@@ -2329,30 +2925,29 @@ class EventFactoryBase {
2329
2925
  }
2330
2926
 
2331
2927
  /**
2332
- * A FDv2 PayloadReader can be used to parse payloads from a stream of FDv2 events. It will send payloads
2928
+ * A FDv2 PayloadProcessor can be used to parse payloads from a stream of FDv2 events. It will send payloads
2333
2929
  * to the PayloadListeners as the payloads are received. Invalid series of events may be dropped silently,
2334
- * but the payload reader will continue to operate.
2930
+ * but the payload processor will continue to operate.
2335
2931
  */
2336
- class PayloadReader {
2932
+ class PayloadProcessor {
2337
2933
  /**
2338
- * Creates a PayloadReader
2934
+ * Creates a PayloadProcessor
2339
2935
  *
2340
- * @param eventStream event stream of FDv2 events
2341
2936
  * @param _objProcessors defines object processors for each object kind.
2342
- * @param _errorHandler that will be called with errors as they are encountered
2937
+ * @param _errorHandler that will be called with parsing errors as they are encountered
2343
2938
  * @param _logger for logging
2344
2939
  */
2345
- constructor(eventStream, _objProcessors, _errorHandler, _logger) {
2940
+ constructor(_objProcessors, _errorHandler, _logger) {
2346
2941
  this._objProcessors = _objProcessors;
2347
2942
  this._errorHandler = _errorHandler;
2348
2943
  this._logger = _logger;
2349
2944
  this._listeners = [];
2350
2945
  this._tempId = undefined;
2351
- this._tempBasis = undefined;
2946
+ this._tempBasis = false;
2352
2947
  this._tempUpdates = [];
2353
2948
  this._processServerIntent = (data) => {
2354
2949
  // clear state in prep for handling data
2355
- this._resetState();
2950
+ this._resetAll();
2356
2951
  // if there's no payloads, return
2357
2952
  if (!data.payloads.length) {
2358
2953
  return;
@@ -2364,8 +2959,11 @@ class PayloadReader {
2364
2959
  this._tempBasis = true;
2365
2960
  break;
2366
2961
  case 'xfer-changes':
2962
+ this._tempBasis = false;
2963
+ break;
2367
2964
  case 'none':
2368
2965
  this._tempBasis = false;
2966
+ this._processIntentNone(payload);
2369
2967
  break;
2370
2968
  default:
2371
2969
  // unrecognized intent code, return
@@ -2375,7 +2973,7 @@ class PayloadReader {
2375
2973
  };
2376
2974
  this._processPutObject = (data) => {
2377
2975
  // if the following properties haven't been provided by now, we should ignore the event
2378
- if (!this._tempId || // server intent hasn't been recieved yet.
2976
+ if (!this._tempId || // server intent hasn't been received yet.
2379
2977
  !data.kind ||
2380
2978
  !data.key ||
2381
2979
  !data.version ||
@@ -2384,7 +2982,7 @@ class PayloadReader {
2384
2982
  }
2385
2983
  const obj = this._processObj(data.kind, data.object);
2386
2984
  if (!obj) {
2387
- this._logger?.warn(`Unable to prcoess object for kind: '${data.kind}'`);
2985
+ this._logger?.warn(`Unable to process object for kind: '${data.kind}'`);
2388
2986
  // ignore unrecognized kinds
2389
2987
  return;
2390
2988
  }
@@ -2409,13 +3007,27 @@ class PayloadReader {
2409
3007
  deleted: true,
2410
3008
  });
2411
3009
  };
3010
+ this._processIntentNone = (intent) => {
3011
+ // if the following properties aren't present ignore the event
3012
+ if (!intent.id || !intent.target) {
3013
+ return;
3014
+ }
3015
+ const payload = {
3016
+ id: intent.id,
3017
+ version: intent.target,
3018
+ basis: false,
3019
+ updates: [], // payload with no updates to hide the intent none concept from the consumer
3020
+ // note: state is absent here as that only appears in payload transferred events
3021
+ };
3022
+ this._listeners.forEach((it) => it(payload));
3023
+ this._resetAfterEmission();
3024
+ };
2412
3025
  this._processPayloadTransferred = (data) => {
2413
3026
  // if the following properties haven't been provided by now, we should reset
2414
- if (!this._tempId || // server intent hasn't been recieved yet.
3027
+ if (!this._tempId || // server intent hasn't been received yet.
2415
3028
  !data.state ||
2416
- !data.version ||
2417
- this._tempBasis === undefined) {
2418
- this._resetState(); // a reset is best defensive action since payload transferred terminates a payload
3029
+ !data.version) {
3030
+ this._resetAll(); // a reset is best defensive action since payload transferred terminates a payload
2419
3031
  return;
2420
3032
  }
2421
3033
  const payload = {
@@ -2426,22 +3038,16 @@ class PayloadReader {
2426
3038
  updates: this._tempUpdates,
2427
3039
  };
2428
3040
  this._listeners.forEach((it) => it(payload));
2429
- this._resetState();
3041
+ this._resetAfterEmission();
2430
3042
  };
2431
3043
  this._processGoodbye = (data) => {
2432
3044
  this._logger?.info(`Goodbye was received from the LaunchDarkly connection with reason: ${data.reason}.`);
2433
- this._resetState();
3045
+ this._resetAll();
2434
3046
  };
2435
3047
  this._processError = (data) => {
2436
- this._logger?.info(`An issue was encountered receiving updates for payload ${this._tempId} with reason: ${data.reason}. Automatic retry will occur.`);
2437
- this._resetState();
3048
+ this._logger?.info(`An issue was encountered receiving updates for payload ${this._tempId} with reason: ${data.reason}.`);
3049
+ this._resetAfterError();
2438
3050
  };
2439
- this._attachHandler(eventStream, 'server-intent', this._processServerIntent);
2440
- this._attachHandler(eventStream, 'put-object', this._processPutObject);
2441
- this._attachHandler(eventStream, 'delete-object', this._processDeleteObject);
2442
- this._attachHandler(eventStream, 'payload-transferred', this._processPayloadTransferred);
2443
- this._attachHandler(eventStream, 'goodbye', this._processGoodbye);
2444
- this._attachHandler(eventStream, 'error', this._processError);
2445
3051
  }
2446
3052
  addPayloadListener(listener) {
2447
3053
  this._listeners.push(listener);
@@ -2452,32 +3058,108 @@ class PayloadReader {
2452
3058
  this._listeners.splice(index, 1);
2453
3059
  }
2454
3060
  }
2455
- _attachHandler(stream, eventName, processor) {
3061
+ /**
3062
+ * Gives the {@link PayloadProcessor} a series of events that it will statefully, incrementally process.
3063
+ * This may lead to listeners being invoked as necessary.
3064
+ * @param events to be processed (can be a single element)
3065
+ */
3066
+ processEvents(events) {
3067
+ events.forEach((event) => {
3068
+ switch (event.event) {
3069
+ case 'server-intent': {
3070
+ this._processServerIntent(event.data);
3071
+ break;
3072
+ }
3073
+ case 'put-object': {
3074
+ this._processPutObject(event.data);
3075
+ break;
3076
+ }
3077
+ case 'delete-object': {
3078
+ this._processDeleteObject(event.data);
3079
+ break;
3080
+ }
3081
+ case 'payload-transferred': {
3082
+ this._processPayloadTransferred(event.data);
3083
+ break;
3084
+ }
3085
+ case 'goodbye': {
3086
+ this._processGoodbye(event.data);
3087
+ break;
3088
+ }
3089
+ case 'error': {
3090
+ this._processError(event.data);
3091
+ break;
3092
+ }
3093
+ }
3094
+ });
3095
+ }
3096
+ _processObj(kind, jsonObj) {
3097
+ return this._objProcessors[kind]?.(jsonObj);
3098
+ }
3099
+ _resetAfterEmission() {
3100
+ this._tempBasis = false;
3101
+ this._tempUpdates = [];
3102
+ }
3103
+ _resetAfterError() {
3104
+ this._tempUpdates = [];
3105
+ }
3106
+ _resetAll() {
3107
+ this._tempId = undefined;
3108
+ this._tempBasis = false;
3109
+ this._tempUpdates = [];
3110
+ }
3111
+ }
3112
+
3113
+ /**
3114
+ * A FDv2 PayloadStreamReader can be used to parse payloads from a stream of FDv2 events. See {@link PayloadProcessor}
3115
+ * for more details.
3116
+ */
3117
+ class PayloadStreamReader {
3118
+ /**
3119
+ * Creates a PayloadStreamReader
3120
+ *
3121
+ * @param eventStream event stream of FDv2 events
3122
+ * @param _objProcessors defines object processors for each object kind.
3123
+ * @param _errorHandler that will be called with parsing errors as they are encountered
3124
+ * @param _logger for logging
3125
+ */
3126
+ constructor(eventStream, _objProcessors, _errorHandler, _logger) {
3127
+ this._errorHandler = _errorHandler;
3128
+ this._logger = _logger;
3129
+ this._attachHandler(eventStream, 'server-intent');
3130
+ this._attachHandler(eventStream, 'put-object');
3131
+ this._attachHandler(eventStream, 'delete-object');
3132
+ this._attachHandler(eventStream, 'payload-transferred');
3133
+ this._attachHandler(eventStream, 'goodbye');
3134
+ this._attachHandler(eventStream, 'error');
3135
+ this._payloadProcessor = new PayloadProcessor(_objProcessors, _errorHandler, _logger);
3136
+ }
3137
+ addPayloadListener(listener) {
3138
+ this._payloadProcessor.addPayloadListener(listener);
3139
+ }
3140
+ removePayloadListener(listener) {
3141
+ this._payloadProcessor.removePayloadListener(listener);
3142
+ }
3143
+ _attachHandler(stream, eventName) {
2456
3144
  stream.addEventListener(eventName, async (event) => {
2457
3145
  if (event?.data) {
2458
3146
  this._logger?.debug(`Received ${eventName} event. Data is ${event.data}`);
2459
3147
  try {
2460
- processor(JSON.parse(event.data));
3148
+ this._payloadProcessor.processEvents([
3149
+ { event: eventName, data: JSON.parse(event.data) },
3150
+ ]);
2461
3151
  }
2462
3152
  catch {
2463
3153
  this._logger?.error(`Stream received data that was unable to be processed in "${eventName}" message`);
2464
3154
  this._logger?.debug(`Data follows: ${event.data}`);
2465
- this._errorHandler?.(exports.DataSourceErrorKind.InvalidData, 'Malformed data in event stream');
3155
+ this._errorHandler?.(exports.DataSourceErrorKind.InvalidData, 'Malformed data in EventStream.');
2466
3156
  }
2467
3157
  }
2468
3158
  else {
2469
- this._errorHandler?.(exports.DataSourceErrorKind.Unknown, 'Unexpected message from event stream');
3159
+ this._errorHandler?.(exports.DataSourceErrorKind.Unknown, 'Event from EventStream missing data.');
2470
3160
  }
2471
3161
  });
2472
3162
  }
2473
- _processObj(kind, jsonObj) {
2474
- return this._objProcessors[kind]?.(jsonObj);
2475
- }
2476
- _resetState() {
2477
- this._tempId = undefined;
2478
- this._tempBasis = undefined;
2479
- this._tempUpdates = [];
2480
- }
2481
3163
  }
2482
3164
 
2483
3165
  /**
@@ -2498,6 +3180,47 @@ function initMetadataFromHeaders(initHeaders) {
2498
3180
  return undefined;
2499
3181
  }
2500
3182
 
3183
+ const UNKNOWN_PLUGIN_NAME = 'unknown plugin';
3184
+ function safeGetName(logger, plugin) {
3185
+ try {
3186
+ return plugin.getMetadata().name || UNKNOWN_PLUGIN_NAME;
3187
+ }
3188
+ catch {
3189
+ logger.error(`Exception thrown getting metadata for plugin. Unable to get plugin name.`);
3190
+ return UNKNOWN_PLUGIN_NAME;
3191
+ }
3192
+ }
3193
+
3194
+ function safeGetHooks(logger, environmentMetadata, plugins) {
3195
+ const hooks = [];
3196
+ plugins.forEach((plugin) => {
3197
+ try {
3198
+ const pluginHooks = plugin.getHooks?.(environmentMetadata);
3199
+ if (pluginHooks === undefined) {
3200
+ logger.error(`Plugin ${safeGetName(logger, plugin)} returned undefined from getHooks.`);
3201
+ }
3202
+ else if (pluginHooks && pluginHooks.length > 0) {
3203
+ hooks.push(...pluginHooks);
3204
+ }
3205
+ }
3206
+ catch (error) {
3207
+ logger.error(`Exception thrown getting hooks for plugin ${safeGetName(logger, plugin)}. Unable to get hooks.`);
3208
+ }
3209
+ });
3210
+ return hooks;
3211
+ }
3212
+
3213
+ function safeRegisterPlugins(logger, environmentMetadata, client, plugins) {
3214
+ plugins.forEach((plugin) => {
3215
+ try {
3216
+ plugin.register(client, environmentMetadata);
3217
+ }
3218
+ catch (error) {
3219
+ logger.error(`Exception thrown registering plugin ${safeGetName(logger, plugin)}.`);
3220
+ }
3221
+ });
3222
+ }
3223
+
2501
3224
  var index = /*#__PURE__*/Object.freeze({
2502
3225
  __proto__: null,
2503
3226
  ClientMessages: ClientMessages,
@@ -2509,11 +3232,16 @@ var index = /*#__PURE__*/Object.freeze({
2509
3232
  InputEvalEvent: InputEvalEvent,
2510
3233
  InputIdentifyEvent: InputIdentifyEvent,
2511
3234
  NullEventProcessor: NullEventProcessor,
2512
- PayloadReader: PayloadReader,
3235
+ PayloadProcessor: PayloadProcessor,
3236
+ PayloadStreamReader: PayloadStreamReader,
3237
+ canonicalize: canonicalize,
2513
3238
  initMetadataFromHeaders: initMetadataFromHeaders,
2514
3239
  isLegacyUser: isLegacyUser,
2515
3240
  isMultiKind: isMultiKind,
2516
3241
  isSingleKind: isSingleKind,
3242
+ safeGetHooks: safeGetHooks,
3243
+ safeGetName: safeGetName,
3244
+ safeRegisterPlugins: safeRegisterPlugins,
2517
3245
  shouldSample: shouldSample
2518
3246
  });
2519
3247
 
@@ -2521,9 +3249,11 @@ exports.ApplicationTags = ApplicationTags;
2521
3249
  exports.AttributeReference = AttributeReference;
2522
3250
  exports.BasicLogger = BasicLogger;
2523
3251
  exports.ClientContext = ClientContext;
3252
+ exports.CompositeDataSource = CompositeDataSource;
2524
3253
  exports.Context = Context;
2525
3254
  exports.ContextFilter = ContextFilter;
2526
3255
  exports.DateValidator = DateValidator;
3256
+ exports.DefaultBackoff = DefaultBackoff;
2527
3257
  exports.FactoryOrInstance = FactoryOrInstance;
2528
3258
  exports.Function = Function;
2529
3259
  exports.KindValidator = KindValidator;