@launchdarkly/js-client-sdk-common 1.9.0 → 1.9.1-beta.1

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 (63) hide show
  1. package/dist/DataManager.d.ts +6 -5
  2. package/dist/DataManager.d.ts.map +1 -1
  3. package/dist/HookRunner.d.ts +3 -3
  4. package/dist/HookRunner.d.ts.map +1 -1
  5. package/dist/LDClientImpl.d.ts +19 -17
  6. package/dist/LDClientImpl.d.ts.map +1 -1
  7. package/dist/LDEmitter.d.ts +4 -4
  8. package/dist/LDEmitter.d.ts.map +1 -1
  9. package/dist/api/LDClient.d.ts +1 -1
  10. package/dist/api/LDClient.d.ts.map +1 -1
  11. package/dist/api/LDInspection.d.ts +105 -0
  12. package/dist/api/LDInspection.d.ts.map +1 -0
  13. package/dist/api/LDOptions.d.ts +12 -2
  14. package/dist/api/LDOptions.d.ts.map +1 -1
  15. package/dist/configuration/Configuration.d.ts +4 -1
  16. package/dist/configuration/Configuration.d.ts.map +1 -1
  17. package/dist/configuration/validators.d.ts.map +1 -1
  18. package/dist/context/addAutoEnv.d.ts +2 -2
  19. package/dist/context/addAutoEnv.d.ts.map +1 -1
  20. package/dist/context/ensureKey.d.ts +1 -1
  21. package/dist/context/ensureKey.d.ts.map +1 -1
  22. package/dist/{streaming → datasource}/DataSourceConfig.d.ts +1 -0
  23. package/dist/datasource/DataSourceConfig.d.ts.map +1 -0
  24. package/dist/datasource/DataSourceEventHandler.d.ts +4 -4
  25. package/dist/datasource/DataSourceEventHandler.d.ts.map +1 -1
  26. package/dist/datasource/DataSourceStatusManager.d.ts +7 -7
  27. package/dist/datasource/DataSourceStatusManager.d.ts.map +1 -1
  28. package/dist/datasource/Requestor.d.ts +26 -0
  29. package/dist/datasource/Requestor.d.ts.map +1 -0
  30. package/dist/evaluation/evaluationDetail.d.ts.map +1 -1
  31. package/dist/flag-manager/FlagManager.d.ts +4 -5
  32. package/dist/flag-manager/FlagManager.d.ts.map +1 -1
  33. package/dist/flag-manager/FlagPersistence.d.ts +13 -13
  34. package/dist/flag-manager/FlagPersistence.d.ts.map +1 -1
  35. package/dist/flag-manager/FlagStore.d.ts +1 -1
  36. package/dist/flag-manager/FlagStore.d.ts.map +1 -1
  37. package/dist/flag-manager/FlagUpdater.d.ts +6 -5
  38. package/dist/flag-manager/FlagUpdater.d.ts.map +1 -1
  39. package/dist/index.cjs +581 -374
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.ts +2 -2
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.mjs +582 -376
  44. package/dist/index.mjs.map +1 -1
  45. package/dist/inspection/InspectorManager.d.ts +41 -0
  46. package/dist/inspection/InspectorManager.d.ts.map +1 -0
  47. package/dist/inspection/createSafeInspector.d.ts +8 -0
  48. package/dist/inspection/createSafeInspector.d.ts.map +1 -0
  49. package/dist/inspection/getInspectorHook.d.ts +4 -0
  50. package/dist/inspection/getInspectorHook.d.ts.map +1 -0
  51. package/dist/inspection/messages.d.ts +3 -0
  52. package/dist/inspection/messages.d.ts.map +1 -0
  53. package/dist/polling/PollingProcessor.d.ts.map +1 -1
  54. package/dist/streaming/StreamingProcessor.d.ts +18 -16
  55. package/dist/streaming/StreamingProcessor.d.ts.map +1 -1
  56. package/dist/streaming/index.d.ts +1 -1
  57. package/dist/streaming/index.d.ts.map +1 -1
  58. package/dist/types/index.d.ts +1 -0
  59. package/dist/types/index.d.ts.map +1 -1
  60. package/package.json +2 -2
  61. package/dist/polling/Requestor.d.ts +0 -21
  62. package/dist/polling/Requestor.d.ts.map +0 -1
  63. package/dist/streaming/DataSourceConfig.d.ts.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { TypeValidators, createSafeLogger, ServiceEndpoints, ApplicationTags, OptionMessages, NumberWithMinimum, SafeLogger, internal, deepCompact, clone, secondsToMillis, ClientContext, fastDeepEqual, defaultHeaders, Context, timedPromise, AutoEnvAttributes, LDClientError, getPollingUri, isHttpRecoverable, httpErrorMessage, LDPollingError, DataSourceErrorKind, getStreamingUri, shouldRetry, LDStreamingError } from '@launchdarkly/js-sdk-common';
1
+ import { getPollingUri, TypeValidators, createSafeLogger, ServiceEndpoints, ApplicationTags, OptionMessages, NumberWithMinimum, SafeLogger, internal, deepCompact, clone, secondsToMillis, ClientContext, fastDeepEqual, defaultHeaders, Context, timedPromise, AutoEnvAttributes, LDClientError, isHttpRecoverable, httpErrorMessage, LDPollingError, DataSourceErrorKind, getStreamingUri, shouldRetry, LDStreamingError } from '@launchdarkly/js-sdk-common';
2
2
  export * from '@launchdarkly/js-sdk-common';
3
3
  import * as jsSdkCommon from '@launchdarkly/js-sdk-common';
4
4
  export { jsSdkCommon as platform };
@@ -14,6 +14,72 @@ var DataSourceState;
14
14
  // NetworkUnavailable,
15
15
  })(DataSourceState || (DataSourceState = {}));
16
16
 
17
+ // eslint-disable-next-line max-classes-per-file
18
+ function isOk(status) {
19
+ return status >= 200 && status <= 299;
20
+ }
21
+ class LDRequestError extends Error {
22
+ constructor(message, status) {
23
+ super(message);
24
+ this.status = status;
25
+ this.name = 'LaunchDarklyRequestError';
26
+ }
27
+ }
28
+ /**
29
+ * Note: The requestor is implemented independently from polling such that it can be used to
30
+ * make a one-off request.
31
+ */
32
+ class Requestor {
33
+ constructor(_requests, _uri, _headers, _method, _body) {
34
+ this._requests = _requests;
35
+ this._uri = _uri;
36
+ this._headers = _headers;
37
+ this._method = _method;
38
+ this._body = _body;
39
+ }
40
+ async requestPayload() {
41
+ let status;
42
+ try {
43
+ const res = await this._requests.fetch(this._uri, {
44
+ method: this._method,
45
+ headers: this._headers,
46
+ body: this._body,
47
+ });
48
+ if (isOk(res.status)) {
49
+ return await res.text();
50
+ }
51
+ // Assigning so it can be thrown after the try/catch.
52
+ status = res.status;
53
+ }
54
+ catch (err) {
55
+ throw new LDRequestError(err?.message);
56
+ }
57
+ throw new LDRequestError(`Unexpected status code: ${status}`, status);
58
+ }
59
+ }
60
+ function makeRequestor(plainContextString, serviceEndpoints, paths, requests, encoding, baseHeaders, baseQueryParams, withReasons, useReport, secureModeHash) {
61
+ let body;
62
+ let method = 'GET';
63
+ const headers = { ...baseHeaders };
64
+ if (useReport) {
65
+ method = 'REPORT';
66
+ headers['content-type'] = 'application/json';
67
+ body = plainContextString; // context is in body for REPORT
68
+ }
69
+ const path = useReport
70
+ ? paths.pathReport(encoding, plainContextString)
71
+ : paths.pathGet(encoding, plainContextString);
72
+ const parameters = [...(baseQueryParams ?? [])];
73
+ if (withReasons) {
74
+ parameters.push({ key: 'withReasons', value: 'true' });
75
+ }
76
+ if (secureModeHash) {
77
+ parameters.push({ key: 'h', value: secureModeHash });
78
+ }
79
+ const uri = getPollingUri(serviceEndpoints, path, parameters);
80
+ return new Requestor(requests, uri, headers, method, body);
81
+ }
82
+
17
83
  // eslint-disable-next-line max-classes-per-file
18
84
  const validators = {
19
85
  logger: TypeValidators.Object,
@@ -38,6 +104,7 @@ const validators = {
38
104
  wrapperVersion: TypeValidators.String,
39
105
  payloadFilterKey: TypeValidators.stringMatchingRegex(/^[a-zA-Z0-9](\w|\.|-)*$/),
40
106
  hooks: TypeValidators.createTypeArray('Hook[]', {}),
107
+ inspectors: TypeValidators.createTypeArray('LDInspection', {}),
41
108
  };
42
109
 
43
110
  const DEFAULT_POLLING_INTERVAL = 60 * 5;
@@ -53,8 +120,13 @@ function ensureSafeLogger(logger) {
53
120
  class ConfigurationImpl {
54
121
  constructor(pristineOptions = {}, internalOptions = {}) {
55
122
  this.logger = createSafeLogger();
123
+ // Naming conventions is not followed for these lines because the config validation
124
+ // accesses members based on the keys of the options. (sdk-763)
125
+ // eslint-disable-next-line @typescript-eslint/naming-convention
56
126
  this.baseUri = DEFAULT_POLLING;
127
+ // eslint-disable-next-line @typescript-eslint/naming-convention
57
128
  this.eventsUri = ServiceEndpoints.DEFAULT_EVENTS;
129
+ // eslint-disable-next-line @typescript-eslint/naming-convention
58
130
  this.streamUri = DEFAULT_STREAM;
59
131
  this.maxCachedContexts = 5;
60
132
  this.capacity = 100;
@@ -71,8 +143,9 @@ class ConfigurationImpl {
71
143
  this.privateAttributes = [];
72
144
  this.pollInterval = DEFAULT_POLLING_INTERVAL;
73
145
  this.hooks = [];
146
+ this.inspectors = [];
74
147
  this.logger = ensureSafeLogger(pristineOptions.logger);
75
- const errors = this.validateTypesAndNames(pristineOptions);
148
+ const errors = this._validateTypesAndNames(pristineOptions);
76
149
  errors.forEach((e) => this.logger.warn(e));
77
150
  this.serviceEndpoints = new ServiceEndpoints(this.streamUri, this.baseUri, this.eventsUri, internalOptions.analyticsEventPath, internalOptions.diagnosticEventPath, internalOptions.includeAuthorizationHeader, pristineOptions.payloadFilterKey);
78
151
  this.useReport = pristineOptions.useReport ?? false;
@@ -80,7 +153,7 @@ class ConfigurationImpl {
80
153
  this.userAgentHeaderName = internalOptions.userAgentHeaderName ?? 'user-agent';
81
154
  this.trackEventModifier = internalOptions.trackEventModifier ?? ((event) => event);
82
155
  }
83
- validateTypesAndNames(pristineOptions) {
156
+ _validateTypesAndNames(pristineOptions) {
84
157
  const errors = [];
85
158
  Object.entries(pristineOptions).forEach(([k, v]) => {
86
159
  const validator = validators[k];
@@ -388,11 +461,12 @@ function createErrorEvaluationDetail(errorKind, def) {
388
461
  };
389
462
  }
390
463
  function createSuccessEvaluationDetail(value, variationIndex, reason) {
391
- return {
464
+ const res = {
392
465
  value,
393
466
  variationIndex: variationIndex ?? null,
394
467
  reason: reason ?? null,
395
468
  };
469
+ return res;
396
470
  }
397
471
 
398
472
  const createEventProcessor = (clientSideID, config, platform, baseHeaders, diagnosticsManager) => {
@@ -490,23 +564,23 @@ class ContextIndex {
490
564
  * then persists changes after the updater has completed.
491
565
  */
492
566
  class FlagPersistence {
493
- constructor(platform, environmentNamespace, maxCachedContexts, flagStore, flagUpdater, logger, timeStamper = () => Date.now()) {
494
- this.platform = platform;
495
- this.environmentNamespace = environmentNamespace;
496
- this.maxCachedContexts = maxCachedContexts;
497
- this.flagStore = flagStore;
498
- this.flagUpdater = flagUpdater;
499
- this.logger = logger;
500
- this.timeStamper = timeStamper;
501
- this.indexKeyPromise = namespaceForContextIndex(this.environmentNamespace);
567
+ constructor(_platform, _environmentNamespace, _maxCachedContexts, _flagStore, _flagUpdater, _logger, _timeStamper = () => Date.now()) {
568
+ this._platform = _platform;
569
+ this._environmentNamespace = _environmentNamespace;
570
+ this._maxCachedContexts = _maxCachedContexts;
571
+ this._flagStore = _flagStore;
572
+ this._flagUpdater = _flagUpdater;
573
+ this._logger = _logger;
574
+ this._timeStamper = _timeStamper;
575
+ this._indexKeyPromise = namespaceForContextIndex(this._environmentNamespace);
502
576
  }
503
577
  /**
504
578
  * Inits flag persistence for the provided context with the provided flags. This will result
505
579
  * in the underlying {@link FlagUpdater} switching its active context.
506
580
  */
507
581
  async init(context, newFlags) {
508
- this.flagUpdater.init(context, newFlags);
509
- await this.storeCache(context);
582
+ this._flagUpdater.init(context, newFlags);
583
+ await this._storeCache(context);
510
584
  }
511
585
  /**
512
586
  * Upserts a flag into the {@link FlagUpdater} and stores that to persistence if the upsert
@@ -514,8 +588,8 @@ class FlagPersistence {
514
588
  * the active context.
515
589
  */
516
590
  async upsert(context, key, item) {
517
- if (this.flagUpdater.upsert(context, key, item)) {
518
- await this.storeCache(context);
591
+ if (this._flagUpdater.upsert(context, key, item)) {
592
+ await this._storeCache(context);
519
593
  return true;
520
594
  }
521
595
  return false;
@@ -525,19 +599,19 @@ class FlagPersistence {
525
599
  * {@link FlagUpdater} this {@link FlagPersistence} was constructed with.
526
600
  */
527
601
  async loadCached(context) {
528
- const storageKey = await namespaceForContextData(this.platform.crypto, this.environmentNamespace, context);
529
- let flagsJson = await this.platform.storage?.get(storageKey);
602
+ const storageKey = await namespaceForContextData(this._platform.crypto, this._environmentNamespace, context);
603
+ let flagsJson = await this._platform.storage?.get(storageKey);
530
604
  if (flagsJson === null || flagsJson === undefined) {
531
605
  // Fallback: in version <10.3.1 flag data was stored under the canonical key, check
532
606
  // to see if data is present and migrate the data if present.
533
- flagsJson = await this.platform.storage?.get(context.canonicalKey);
607
+ flagsJson = await this._platform.storage?.get(context.canonicalKey);
534
608
  if (flagsJson === null || flagsJson === undefined) {
535
609
  // return false indicating cache did not load if flag json is still absent
536
610
  return false;
537
611
  }
538
612
  // migrate data from version <10.3.1 and cleanup data that was under canonical key
539
- await this.platform.storage?.set(storageKey, flagsJson);
540
- await this.platform.storage?.clear(context.canonicalKey);
613
+ await this._platform.storage?.set(storageKey, flagsJson);
614
+ await this._platform.storage?.clear(context.canonicalKey);
541
615
  }
542
616
  try {
543
617
  const flags = JSON.parse(flagsJson);
@@ -546,43 +620,43 @@ class FlagPersistence {
546
620
  acc[key] = { version: flag.version, flag };
547
621
  return acc;
548
622
  }, {});
549
- this.flagUpdater.initCached(context, descriptors);
550
- this.logger.debug('Loaded cached flag evaluations from persistent storage');
623
+ this._flagUpdater.initCached(context, descriptors);
624
+ this._logger.debug('Loaded cached flag evaluations from persistent storage');
551
625
  return true;
552
626
  }
553
627
  catch (e) {
554
- this.logger.warn(`Could not load cached flag evaluations from persistent storage: ${e.message}`);
628
+ this._logger.warn(`Could not load cached flag evaluations from persistent storage: ${e.message}`);
555
629
  return false;
556
630
  }
557
631
  }
558
- async loadIndex() {
559
- if (this.contextIndex !== undefined) {
560
- return this.contextIndex;
632
+ async _loadIndex() {
633
+ if (this._contextIndex !== undefined) {
634
+ return this._contextIndex;
561
635
  }
562
- const json = await this.platform.storage?.get(await this.indexKeyPromise);
636
+ const json = await this._platform.storage?.get(await this._indexKeyPromise);
563
637
  if (!json) {
564
- this.contextIndex = new ContextIndex();
565
- return this.contextIndex;
638
+ this._contextIndex = new ContextIndex();
639
+ return this._contextIndex;
566
640
  }
567
641
  try {
568
- this.contextIndex = ContextIndex.fromJson(json);
569
- this.logger.debug('Loaded context index from persistent storage');
642
+ this._contextIndex = ContextIndex.fromJson(json);
643
+ this._logger.debug('Loaded context index from persistent storage');
570
644
  }
571
645
  catch (e) {
572
- this.logger.warn(`Could not load index from persistent storage: ${e.message}`);
573
- this.contextIndex = new ContextIndex();
646
+ this._logger.warn(`Could not load index from persistent storage: ${e.message}`);
647
+ this._contextIndex = new ContextIndex();
574
648
  }
575
- return this.contextIndex;
576
- }
577
- async storeCache(context) {
578
- const index = await this.loadIndex();
579
- const storageKey = await namespaceForContextData(this.platform.crypto, this.environmentNamespace, context);
580
- index.notice(storageKey, this.timeStamper());
581
- const pruned = index.prune(this.maxCachedContexts);
582
- await Promise.all(pruned.map(async (it) => this.platform.storage?.clear(it.id)));
649
+ return this._contextIndex;
650
+ }
651
+ async _storeCache(context) {
652
+ const index = await this._loadIndex();
653
+ const storageKey = await namespaceForContextData(this._platform.crypto, this._environmentNamespace, context);
654
+ index.notice(storageKey, this._timeStamper());
655
+ const pruned = index.prune(this._maxCachedContexts);
656
+ await Promise.all(pruned.map(async (it) => this._platform.storage?.clear(it.id)));
583
657
  // store index
584
- await this.platform.storage?.set(await this.indexKeyPromise, index.toJson());
585
- const allFlags = this.flagStore.getAll();
658
+ await this._platform.storage?.set(await this._indexKeyPromise, index.toJson());
659
+ const allFlags = this._flagStore.getAll();
586
660
  // mapping item descriptors to flags
587
661
  const flags = Object.entries(allFlags).reduce((acc, [key, descriptor]) => {
588
662
  if (descriptor.flag !== null && descriptor.flag !== undefined) {
@@ -592,7 +666,7 @@ class FlagPersistence {
592
666
  }, {});
593
667
  const jsonAll = JSON.stringify(flags);
594
668
  // store flag data
595
- await this.platform.storage?.set(storageKey, jsonAll);
669
+ await this._platform.storage?.set(storageKey, jsonAll);
596
670
  }
597
671
  }
598
672
 
@@ -601,25 +675,25 @@ class FlagPersistence {
601
675
  */
602
676
  class DefaultFlagStore {
603
677
  constructor() {
604
- this.flags = {};
678
+ this._flags = {};
605
679
  }
606
680
  init(newFlags) {
607
- this.flags = Object.entries(newFlags).reduce((acc, [key, flag]) => {
681
+ this._flags = Object.entries(newFlags).reduce((acc, [key, flag]) => {
608
682
  acc[key] = flag;
609
683
  return acc;
610
684
  }, {});
611
685
  }
612
686
  insertOrUpdate(key, update) {
613
- this.flags[key] = update;
687
+ this._flags[key] = update;
614
688
  }
615
689
  get(key) {
616
- if (Object.prototype.hasOwnProperty.call(this.flags, key)) {
617
- return this.flags[key];
690
+ if (Object.prototype.hasOwnProperty.call(this._flags, key)) {
691
+ return this._flags[key];
618
692
  }
619
693
  return undefined;
620
694
  }
621
695
  getAll() {
622
- return this.flags;
696
+ return this._flags;
623
697
  }
624
698
  }
625
699
 
@@ -648,19 +722,19 @@ function calculateChangedKeys(existingObject, newObject) {
648
722
  */
649
723
  class FlagUpdater {
650
724
  constructor(flagStore, logger) {
651
- this.changeCallbacks = new Array();
652
- this.flagStore = flagStore;
653
- this.logger = logger;
725
+ this._changeCallbacks = new Array();
726
+ this._flagStore = flagStore;
727
+ this._logger = logger;
654
728
  }
655
729
  init(context, newFlags) {
656
- this.activeContextKey = context.canonicalKey;
657
- const oldFlags = this.flagStore.getAll();
658
- this.flagStore.init(newFlags);
730
+ this._activeContextKey = context.canonicalKey;
731
+ const oldFlags = this._flagStore.getAll();
732
+ this._flagStore.init(newFlags);
659
733
  const changed = calculateChangedKeys(oldFlags, newFlags);
660
734
  if (changed.length > 0) {
661
- this.changeCallbacks.forEach((callback) => {
735
+ this._changeCallbacks.forEach((callback) => {
662
736
  try {
663
- callback(context, changed);
737
+ callback(context, changed, 'init');
664
738
  }
665
739
  catch (err) {
666
740
  /* intentionally empty */
@@ -669,25 +743,25 @@ class FlagUpdater {
669
743
  }
670
744
  }
671
745
  initCached(context, newFlags) {
672
- if (this.activeContextKey === context.canonicalKey) {
746
+ if (this._activeContextKey === context.canonicalKey) {
673
747
  return;
674
748
  }
675
749
  this.init(context, newFlags);
676
750
  }
677
751
  upsert(context, key, item) {
678
- if (this.activeContextKey !== context.canonicalKey) {
679
- this.logger.warn('Received an update for an inactive context.');
752
+ if (this._activeContextKey !== context.canonicalKey) {
753
+ this._logger.warn('Received an update for an inactive context.');
680
754
  return false;
681
755
  }
682
- const currentValue = this.flagStore.get(key);
756
+ const currentValue = this._flagStore.get(key);
683
757
  if (currentValue !== undefined && currentValue.version >= item.version) {
684
758
  // this is an out of order update that can be ignored
685
759
  return false;
686
760
  }
687
- this.flagStore.insertOrUpdate(key, item);
688
- this.changeCallbacks.forEach((callback) => {
761
+ this._flagStore.insertOrUpdate(key, item);
762
+ this._changeCallbacks.forEach((callback) => {
689
763
  try {
690
- callback(context, [key]);
764
+ callback(context, [key], 'patch');
691
765
  }
692
766
  catch (err) {
693
767
  /* intentionally empty */
@@ -696,12 +770,12 @@ class FlagUpdater {
696
770
  return true;
697
771
  }
698
772
  on(callback) {
699
- this.changeCallbacks.push(callback);
773
+ this._changeCallbacks.push(callback);
700
774
  }
701
775
  off(callback) {
702
- const index = this.changeCallbacks.indexOf(callback);
776
+ const index = this._changeCallbacks.indexOf(callback);
703
777
  if (index > -1) {
704
- this.changeCallbacks.splice(index, 1);
778
+ this._changeCallbacks.splice(index, 1);
705
779
  }
706
780
  }
707
781
  }
@@ -715,40 +789,39 @@ class DefaultFlagManager {
715
789
  * @param timeStamper exists for testing purposes
716
790
  */
717
791
  constructor(platform, sdkKey, maxCachedContexts, logger, timeStamper = () => Date.now()) {
718
- this.timeStamper = timeStamper;
719
- this.flagStore = new DefaultFlagStore();
720
- this.flagUpdater = new FlagUpdater(this.flagStore, logger);
721
- this.flagPersistencePromise = this.initPersistence(platform, sdkKey, maxCachedContexts, logger, timeStamper);
792
+ this._flagStore = new DefaultFlagStore();
793
+ this._flagUpdater = new FlagUpdater(this._flagStore, logger);
794
+ this._flagPersistencePromise = this._initPersistence(platform, sdkKey, maxCachedContexts, logger, timeStamper);
722
795
  }
723
- async initPersistence(platform, sdkKey, maxCachedContexts, logger, timeStamper = () => Date.now()) {
796
+ async _initPersistence(platform, sdkKey, maxCachedContexts, logger, timeStamper = () => Date.now()) {
724
797
  const environmentNamespace = await namespaceForEnvironment(platform.crypto, sdkKey);
725
- return new FlagPersistence(platform, environmentNamespace, maxCachedContexts, this.flagStore, this.flagUpdater, logger, timeStamper);
798
+ return new FlagPersistence(platform, environmentNamespace, maxCachedContexts, this._flagStore, this._flagUpdater, logger, timeStamper);
726
799
  }
727
800
  get(key) {
728
- return this.flagStore.get(key);
801
+ return this._flagStore.get(key);
729
802
  }
730
803
  getAll() {
731
- return this.flagStore.getAll();
804
+ return this._flagStore.getAll();
732
805
  }
733
806
  setBootstrap(context, newFlags) {
734
807
  // Bypasses the persistence as we do not want to put these flags into any cache.
735
808
  // Generally speaking persistence likely *SHOULD* be disabled when using bootstrap.
736
- this.flagUpdater.init(context, newFlags);
809
+ this._flagUpdater.init(context, newFlags);
737
810
  }
738
811
  async init(context, newFlags) {
739
- return (await this.flagPersistencePromise).init(context, newFlags);
812
+ return (await this._flagPersistencePromise).init(context, newFlags);
740
813
  }
741
814
  async upsert(context, key, item) {
742
- return (await this.flagPersistencePromise).upsert(context, key, item);
815
+ return (await this._flagPersistencePromise).upsert(context, key, item);
743
816
  }
744
817
  async loadCached(context) {
745
- return (await this.flagPersistencePromise).loadCached(context);
818
+ return (await this._flagPersistencePromise).loadCached(context);
746
819
  }
747
820
  on(callback) {
748
- this.flagUpdater.on(callback);
821
+ this._flagUpdater.on(callback);
749
822
  }
750
823
  off(callback) {
751
- this.flagUpdater.off(callback);
824
+ this._flagUpdater.off(callback);
752
825
  }
753
826
  }
754
827
 
@@ -798,39 +871,186 @@ function executeAfterIdentify(logger, hooks, hookContext, updatedData, result) {
798
871
  }
799
872
  }
800
873
  class HookRunner {
801
- constructor(logger, initialHooks) {
802
- this.logger = logger;
803
- this.hooks = [];
804
- this.hooks.push(...initialHooks);
874
+ constructor(_logger, initialHooks) {
875
+ this._logger = _logger;
876
+ this._hooks = [];
877
+ this._hooks.push(...initialHooks);
805
878
  }
806
879
  withEvaluation(key, context, defaultValue, method) {
807
- if (this.hooks.length === 0) {
880
+ if (this._hooks.length === 0) {
808
881
  return method();
809
882
  }
810
- const hooks = [...this.hooks];
883
+ const hooks = [...this._hooks];
811
884
  const hookContext = {
812
885
  flagKey: key,
813
886
  context,
814
887
  defaultValue,
815
888
  };
816
- const hookData = executeBeforeEvaluation(this.logger, hooks, hookContext);
889
+ const hookData = executeBeforeEvaluation(this._logger, hooks, hookContext);
817
890
  const result = method();
818
- executeAfterEvaluation(this.logger, hooks, hookContext, hookData, result);
891
+ executeAfterEvaluation(this._logger, hooks, hookContext, hookData, result);
819
892
  return result;
820
893
  }
821
894
  identify(context, timeout) {
822
- const hooks = [...this.hooks];
895
+ const hooks = [...this._hooks];
823
896
  const hookContext = {
824
897
  context,
825
898
  timeout,
826
899
  };
827
- const hookData = executeBeforeIdentify(this.logger, hooks, hookContext);
900
+ const hookData = executeBeforeIdentify(this._logger, hooks, hookContext);
828
901
  return (result) => {
829
- executeAfterIdentify(this.logger, hooks, hookContext, hookData, result);
902
+ executeAfterIdentify(this._logger, hooks, hookContext, hookData, result);
830
903
  };
831
904
  }
832
905
  addHook(hook) {
833
- this.hooks.push(hook);
906
+ this._hooks.push(hook);
907
+ }
908
+ }
909
+
910
+ function getInspectorHook(inspectorManager) {
911
+ return {
912
+ getMetadata() {
913
+ return {
914
+ name: 'LaunchDarkly-Inspector-Adapter',
915
+ };
916
+ },
917
+ afterEvaluation: (hookContext, data, detail) => {
918
+ inspectorManager.onFlagUsed(hookContext.flagKey, detail, hookContext.context);
919
+ return data;
920
+ },
921
+ afterIdentify(hookContext, data, _result) {
922
+ inspectorManager.onIdentityChanged(hookContext.context);
923
+ return data;
924
+ },
925
+ };
926
+ }
927
+
928
+ function invalidInspector(type, name) {
929
+ return `an inspector: "${name}" of an invalid type (${type}) was configured`;
930
+ }
931
+ function inspectorMethodError(type, name) {
932
+ return `an inspector: "${name}" of type: "${type}" generated an exception`;
933
+ }
934
+
935
+ /**
936
+ * Wrap an inspector ensuring that calling its methods are safe.
937
+ * @param inspector Inspector to wrap.
938
+ */
939
+ function createSafeInspector(inspector, logger) {
940
+ let errorLogged = false;
941
+ const wrapper = {
942
+ method: (...args) => {
943
+ try {
944
+ // We are proxying arguments here to the underlying method. Typescript doesn't care
945
+ // for this as it cannot validate the parameters are correct, but we are also the caller
946
+ // in this case and will dispatch things with the correct arguments. The dispatch to this
947
+ // will itself happen with a type guard.
948
+ // @ts-ignore
949
+ inspector.method(...args);
950
+ }
951
+ catch {
952
+ // If something goes wrong in an inspector we want to log that something
953
+ // went wrong. We don't want to flood the logs, so we only log something
954
+ // the first time that something goes wrong.
955
+ // We do not include the exception in the log, because we do not know what
956
+ // kind of data it may contain.
957
+ if (!errorLogged) {
958
+ errorLogged = true;
959
+ logger.warn(inspectorMethodError(wrapper.type, wrapper.name));
960
+ }
961
+ // Prevent errors.
962
+ }
963
+ },
964
+ type: inspector.type,
965
+ name: inspector.name,
966
+ synchronous: inspector.synchronous,
967
+ };
968
+ return wrapper;
969
+ }
970
+
971
+ const FLAG_USED_TYPE = 'flag-used';
972
+ const FLAG_DETAILS_CHANGED_TYPE = 'flag-details-changed';
973
+ const FLAG_DETAIL_CHANGED_TYPE = 'flag-detail-changed';
974
+ const IDENTITY_CHANGED_TYPE = 'client-identity-changed';
975
+ const VALID__TYPES = [
976
+ FLAG_USED_TYPE,
977
+ FLAG_DETAILS_CHANGED_TYPE,
978
+ FLAG_DETAIL_CHANGED_TYPE,
979
+ IDENTITY_CHANGED_TYPE,
980
+ ];
981
+ function validateInspector(inspector, logger) {
982
+ const valid = VALID__TYPES.includes(inspector.type) &&
983
+ inspector.method &&
984
+ typeof inspector.method === 'function';
985
+ if (!valid) {
986
+ logger.warn(invalidInspector(inspector.type, inspector.name));
987
+ }
988
+ return valid;
989
+ }
990
+ /**
991
+ * Manages dispatching of inspection data to registered inspectors.
992
+ */
993
+ class InspectorManager {
994
+ constructor(inspectors, logger) {
995
+ this._safeInspectors = [];
996
+ const validInspectors = inspectors.filter((inspector) => validateInspector(inspector, logger));
997
+ this._safeInspectors = validInspectors.map((inspector) => createSafeInspector(inspector, logger));
998
+ }
999
+ hasInspectors() {
1000
+ return this._safeInspectors.length !== 0;
1001
+ }
1002
+ /**
1003
+ * Notify registered inspectors of a flag being used.
1004
+ *
1005
+ * @param flagKey The key for the flag.
1006
+ * @param detail The LDEvaluationDetail for the flag.
1007
+ * @param context The LDContext for the flag.
1008
+ */
1009
+ onFlagUsed(flagKey, detail, context) {
1010
+ this._safeInspectors.forEach((inspector) => {
1011
+ if (inspector.type === FLAG_USED_TYPE) {
1012
+ inspector.method(flagKey, detail, context);
1013
+ }
1014
+ });
1015
+ }
1016
+ /**
1017
+ * Notify registered inspectors that the flags have been replaced.
1018
+ *
1019
+ * @param flags The current flags as a Record<string, LDEvaluationDetail>.
1020
+ */
1021
+ onFlagsChanged(flags) {
1022
+ this._safeInspectors.forEach((inspector) => {
1023
+ if (inspector.type === FLAG_DETAILS_CHANGED_TYPE) {
1024
+ inspector.method(flags);
1025
+ }
1026
+ });
1027
+ }
1028
+ /**
1029
+ * Notify registered inspectors that a flag value has changed.
1030
+ *
1031
+ * @param flagKey The key for the flag that changed.
1032
+ * @param flag An `LDEvaluationDetail` for the flag.
1033
+ */
1034
+ onFlagChanged(flagKey, flag) {
1035
+ this._safeInspectors.forEach((inspector) => {
1036
+ if (inspector.type === FLAG_DETAIL_CHANGED_TYPE) {
1037
+ inspector.method(flagKey, flag);
1038
+ }
1039
+ });
1040
+ }
1041
+ /**
1042
+ * Notify the registered inspectors that the context identity has changed.
1043
+ *
1044
+ * The notification itself will be dispatched asynchronously.
1045
+ *
1046
+ * @param context The `LDContext` which is now identified.
1047
+ */
1048
+ onIdentityChanged(context) {
1049
+ this._safeInspectors.forEach((inspector) => {
1050
+ if (inspector.type === IDENTITY_CHANGED_TYPE) {
1051
+ inspector.method(context);
1052
+ }
1053
+ });
834
1054
  }
835
1055
  }
836
1056
 
@@ -841,16 +1061,16 @@ class HookRunner {
841
1061
  * a system to allow listeners which have counts independent of the primary listener counts.
842
1062
  */
843
1063
  class LDEmitter {
844
- constructor(logger) {
845
- this.logger = logger;
846
- this.listeners = new Map();
1064
+ constructor(_logger) {
1065
+ this._logger = _logger;
1066
+ this._listeners = new Map();
847
1067
  }
848
1068
  on(name, listener) {
849
- if (!this.listeners.has(name)) {
850
- this.listeners.set(name, [listener]);
1069
+ if (!this._listeners.has(name)) {
1070
+ this._listeners.set(name, [listener]);
851
1071
  }
852
1072
  else {
853
- this.listeners.get(name)?.push(listener);
1073
+ this._listeners.get(name)?.push(listener);
854
1074
  }
855
1075
  }
856
1076
  /**
@@ -860,7 +1080,7 @@ class LDEmitter {
860
1080
  * @param listener Optional. If unspecified, all listeners for the event will be removed.
861
1081
  */
862
1082
  off(name, listener) {
863
- const existingListeners = this.listeners.get(name);
1083
+ const existingListeners = this._listeners.get(name);
864
1084
  if (!existingListeners) {
865
1085
  return;
866
1086
  }
@@ -868,33 +1088,33 @@ class LDEmitter {
868
1088
  // remove from internal cache
869
1089
  const updated = existingListeners.filter((fn) => fn !== listener);
870
1090
  if (updated.length === 0) {
871
- this.listeners.delete(name);
1091
+ this._listeners.delete(name);
872
1092
  }
873
1093
  else {
874
- this.listeners.set(name, updated);
1094
+ this._listeners.set(name, updated);
875
1095
  }
876
1096
  return;
877
1097
  }
878
1098
  // listener was not specified, so remove them all for that event
879
- this.listeners.delete(name);
1099
+ this._listeners.delete(name);
880
1100
  }
881
- invokeListener(listener, name, ...detail) {
1101
+ _invokeListener(listener, name, ...detail) {
882
1102
  try {
883
1103
  listener(...detail);
884
1104
  }
885
1105
  catch (err) {
886
- this.logger?.error(`Encountered error invoking handler for "${name}", detail: "${err}"`);
1106
+ this._logger?.error(`Encountered error invoking handler for "${name}", detail: "${err}"`);
887
1107
  }
888
1108
  }
889
1109
  emit(name, ...detail) {
890
- const listeners = this.listeners.get(name);
891
- listeners?.forEach((listener) => this.invokeListener(listener, name, ...detail));
1110
+ const listeners = this._listeners.get(name);
1111
+ listeners?.forEach((listener) => this._invokeListener(listener, name, ...detail));
892
1112
  }
893
1113
  eventNames() {
894
- return [...this.listeners.keys()];
1114
+ return [...this._listeners.keys()];
895
1115
  }
896
1116
  listenerCount(name) {
897
- return this.listeners.get(name)?.length ?? 0;
1117
+ return this._listeners.get(name)?.length ?? 0;
898
1118
  }
899
1119
  }
900
1120
 
@@ -907,40 +1127,45 @@ class LDClientImpl {
907
1127
  this.sdkKey = sdkKey;
908
1128
  this.autoEnvAttributes = autoEnvAttributes;
909
1129
  this.platform = platform;
910
- this.identifyTimeout = 5;
911
- this.highTimeoutThreshold = 15;
912
- this.eventFactoryDefault = new EventFactory(false);
913
- this.eventFactoryWithReasons = new EventFactory(true);
914
- this.eventSendingEnabled = false;
1130
+ this._identifyTimeout = 5;
1131
+ this._highTimeoutThreshold = 15;
1132
+ this._eventFactoryDefault = new EventFactory(false);
1133
+ this._eventFactoryWithReasons = new EventFactory(true);
1134
+ this._eventSendingEnabled = false;
915
1135
  if (!sdkKey) {
916
1136
  throw new Error('You must configure the client with a client-side SDK key');
917
1137
  }
918
1138
  if (!platform.encoding) {
919
1139
  throw new Error('Platform must implement Encoding because btoa is required.');
920
1140
  }
921
- this.config = new ConfigurationImpl(options, internalOptions);
922
- this.logger = this.config.logger;
923
- this.baseHeaders = defaultHeaders(this.sdkKey, this.platform.info, this.config.tags, this.config.serviceEndpoints.includeAuthorizationHeader, this.config.userAgentHeaderName);
924
- this.flagManager = new DefaultFlagManager(this.platform, sdkKey, this.config.maxCachedContexts, this.config.logger);
925
- this.diagnosticsManager = createDiagnosticsManager(sdkKey, this.config, platform);
926
- this.eventProcessor = createEventProcessor(sdkKey, this.config, platform, this.baseHeaders, this.diagnosticsManager);
1141
+ this._config = new ConfigurationImpl(options, internalOptions);
1142
+ this.logger = this._config.logger;
1143
+ this._baseHeaders = defaultHeaders(this.sdkKey, this.platform.info, this._config.tags, this._config.serviceEndpoints.includeAuthorizationHeader, this._config.userAgentHeaderName);
1144
+ this._flagManager = new DefaultFlagManager(this.platform, sdkKey, this._config.maxCachedContexts, this._config.logger);
1145
+ this._diagnosticsManager = createDiagnosticsManager(sdkKey, this._config, platform);
1146
+ this._eventProcessor = createEventProcessor(sdkKey, this._config, platform, this._baseHeaders, this._diagnosticsManager);
927
1147
  this.emitter = new LDEmitter();
928
1148
  this.emitter.on('error', (c, err) => {
929
1149
  this.logger.error(`error: ${err}, context: ${JSON.stringify(c)}`);
930
1150
  });
931
- this.flagManager.on((context, flagKeys) => {
1151
+ this._flagManager.on((context, flagKeys, type) => {
1152
+ this._handleInspectionChanged(flagKeys, type);
932
1153
  const ldContext = Context.toLDContext(context);
933
1154
  this.emitter.emit('change', ldContext, flagKeys);
934
1155
  flagKeys.forEach((it) => {
935
1156
  this.emitter.emit(`change:${it}`, ldContext);
936
1157
  });
937
1158
  });
938
- this.dataManager = dataManagerFactory(this.flagManager, this.config, this.baseHeaders, this.emitter, this.diagnosticsManager);
939
- this.hookRunner = new HookRunner(this.logger, this.config.hooks);
1159
+ this.dataManager = dataManagerFactory(this._flagManager, this._config, this._baseHeaders, this.emitter, this._diagnosticsManager);
1160
+ this._hookRunner = new HookRunner(this.logger, this._config.hooks);
1161
+ this._inspectorManager = new InspectorManager(this._config.inspectors, this.logger);
1162
+ if (this._inspectorManager.hasInspectors()) {
1163
+ this._hookRunner.addHook(getInspectorHook(this._inspectorManager));
1164
+ }
940
1165
  }
941
1166
  allFlags() {
942
1167
  // extracting all flag values
943
- const result = Object.entries(this.flagManager.getAll()).reduce((acc, [key, descriptor]) => {
1168
+ const result = Object.entries(this._flagManager.getAll()).reduce((acc, [key, descriptor]) => {
944
1169
  if (descriptor.flag !== null && descriptor.flag !== undefined && !descriptor.flag.deleted) {
945
1170
  acc[key] = descriptor.flag.value;
946
1171
  }
@@ -950,13 +1175,13 @@ class LDClientImpl {
950
1175
  }
951
1176
  async close() {
952
1177
  await this.flush();
953
- this.eventProcessor?.close();
954
- this.updateProcessor?.close();
1178
+ this._eventProcessor?.close();
1179
+ this._updateProcessor?.close();
955
1180
  this.logger.debug('Closed event processor and data source.');
956
1181
  }
957
1182
  async flush() {
958
1183
  try {
959
- await this.eventProcessor?.flush();
1184
+ await this._eventProcessor?.flush();
960
1185
  this.logger.debug('Successfully flushed event processor.');
961
1186
  }
962
1187
  catch (e) {
@@ -971,12 +1196,12 @@ class LDClientImpl {
971
1196
  // code. We are returned the unchecked context so that if a consumer identifies with an invalid context
972
1197
  // and then calls getContext, they get back the same context they provided, without any assertion about
973
1198
  // validity.
974
- return this.uncheckedContext ? clone(this.uncheckedContext) : undefined;
1199
+ return this._uncheckedContext ? clone(this._uncheckedContext) : undefined;
975
1200
  }
976
1201
  getInternalContext() {
977
- return this.checkedContext;
1202
+ return this._checkedContext;
978
1203
  }
979
- createIdentifyPromise(timeout) {
1204
+ _createIdentifyPromise(timeout) {
980
1205
  let res;
981
1206
  let rej;
982
1207
  const slow = new Promise((resolve, reject) => {
@@ -1009,16 +1234,16 @@ class LDClientImpl {
1009
1234
  */
1010
1235
  async identify(pristineContext, identifyOptions) {
1011
1236
  if (identifyOptions?.timeout) {
1012
- this.identifyTimeout = identifyOptions.timeout;
1237
+ this._identifyTimeout = identifyOptions.timeout;
1013
1238
  }
1014
- if (this.identifyTimeout > this.highTimeoutThreshold) {
1239
+ if (this._identifyTimeout > this._highTimeoutThreshold) {
1015
1240
  this.logger.warn('The identify function was called with a timeout greater than ' +
1016
- `${this.highTimeoutThreshold} seconds. We recommend a timeout of less than ` +
1017
- `${this.highTimeoutThreshold} seconds.`);
1241
+ `${this._highTimeoutThreshold} seconds. We recommend a timeout of less than ` +
1242
+ `${this._highTimeoutThreshold} seconds.`);
1018
1243
  }
1019
1244
  let context = await ensureKey(pristineContext, this.platform);
1020
1245
  if (this.autoEnvAttributes === AutoEnvAttributes.Enabled) {
1021
- context = await addAutoEnv(context, this.platform, this.config);
1246
+ context = await addAutoEnv(context, this.platform, this._config);
1022
1247
  }
1023
1248
  const checkedContext = Context.fromLDContext(context);
1024
1249
  if (!checkedContext.valid) {
@@ -1026,12 +1251,12 @@ class LDClientImpl {
1026
1251
  this.emitter.emit('error', context, error);
1027
1252
  return Promise.reject(error);
1028
1253
  }
1029
- this.uncheckedContext = context;
1030
- this.checkedContext = checkedContext;
1031
- this.eventProcessor?.sendEvent(this.eventFactoryDefault.identifyEvent(this.checkedContext));
1032
- const { identifyPromise, identifyResolve, identifyReject } = this.createIdentifyPromise(this.identifyTimeout);
1033
- this.logger.debug(`Identifying ${JSON.stringify(this.checkedContext)}`);
1034
- const afterIdentify = this.hookRunner.identify(context, identifyOptions?.timeout);
1254
+ this._uncheckedContext = context;
1255
+ this._checkedContext = checkedContext;
1256
+ this._eventProcessor?.sendEvent(this._eventFactoryDefault.identifyEvent(this._checkedContext));
1257
+ const { identifyPromise, identifyResolve, identifyReject } = this._createIdentifyPromise(this._identifyTimeout);
1258
+ this.logger.debug(`Identifying ${JSON.stringify(this._checkedContext)}`);
1259
+ const afterIdentify = this._hookRunner.identify(context, identifyOptions?.timeout);
1035
1260
  await this.dataManager.identify(identifyResolve, identifyReject, checkedContext, identifyOptions);
1036
1261
  return identifyPromise.then((res) => {
1037
1262
  afterIdentify({ status: 'completed' });
@@ -1048,38 +1273,38 @@ class LDClientImpl {
1048
1273
  this.emitter.off(eventName, listener);
1049
1274
  }
1050
1275
  track(key, data, metricValue) {
1051
- if (!this.checkedContext || !this.checkedContext.valid) {
1052
- this.logger.warn(ClientMessages.missingContextKeyNoEvent);
1276
+ if (!this._checkedContext || !this._checkedContext.valid) {
1277
+ this.logger.warn(ClientMessages.MissingContextKeyNoEvent);
1053
1278
  return;
1054
1279
  }
1055
1280
  // 0 is valid, so do not truthy check the metric value
1056
1281
  if (metricValue !== undefined && !TypeValidators.Number.is(metricValue)) {
1057
1282
  this.logger?.warn(ClientMessages.invalidMetricValue(typeof metricValue));
1058
1283
  }
1059
- this.eventProcessor?.sendEvent(this.config.trackEventModifier(this.eventFactoryDefault.customEvent(key, this.checkedContext, data, metricValue)));
1284
+ this._eventProcessor?.sendEvent(this._config.trackEventModifier(this._eventFactoryDefault.customEvent(key, this._checkedContext, data, metricValue)));
1060
1285
  }
1061
- variationInternal(flagKey, defaultValue, eventFactory, typeChecker) {
1062
- if (!this.uncheckedContext) {
1063
- this.logger.debug(ClientMessages.missingContextKeyNoEvent);
1286
+ _variationInternal(flagKey, defaultValue, eventFactory, typeChecker) {
1287
+ if (!this._uncheckedContext) {
1288
+ this.logger.debug(ClientMessages.MissingContextKeyNoEvent);
1064
1289
  return createErrorEvaluationDetail(ErrorKinds.UserNotSpecified, defaultValue);
1065
1290
  }
1066
- const evalContext = Context.fromLDContext(this.uncheckedContext);
1067
- const foundItem = this.flagManager.get(flagKey);
1291
+ const evalContext = Context.fromLDContext(this._uncheckedContext);
1292
+ const foundItem = this._flagManager.get(flagKey);
1068
1293
  if (foundItem === undefined || foundItem.flag.deleted) {
1069
1294
  const defVal = defaultValue ?? null;
1070
1295
  const error = new LDClientError(`Unknown feature flag "${flagKey}"; returning default value ${defVal}.`);
1071
- this.emitter.emit('error', this.uncheckedContext, error);
1072
- this.eventProcessor?.sendEvent(this.eventFactoryDefault.unknownFlagEvent(flagKey, defVal, evalContext));
1296
+ this.emitter.emit('error', this._uncheckedContext, error);
1297
+ this._eventProcessor?.sendEvent(this._eventFactoryDefault.unknownFlagEvent(flagKey, defVal, evalContext));
1073
1298
  return createErrorEvaluationDetail(ErrorKinds.FlagNotFound, defaultValue);
1074
1299
  }
1075
- const { reason, value, variation } = foundItem.flag;
1300
+ const { reason, value, variation, prerequisites } = foundItem.flag;
1076
1301
  if (typeChecker) {
1077
1302
  const [matched, type] = typeChecker(value);
1078
1303
  if (!matched) {
1079
- this.eventProcessor?.sendEvent(eventFactory.evalEventClient(flagKey, defaultValue, // track default value on type errors
1304
+ this._eventProcessor?.sendEvent(eventFactory.evalEventClient(flagKey, defaultValue, // track default value on type errors
1080
1305
  defaultValue, foundItem.flag, evalContext, reason));
1081
1306
  const error = new LDClientError(`Wrong type "${type}" for feature flag "${flagKey}"; returning default value`);
1082
- this.emitter.emit('error', this.uncheckedContext, error);
1307
+ this.emitter.emit('error', this._uncheckedContext, error);
1083
1308
  return createErrorEvaluationDetail(ErrorKinds.WrongType, defaultValue);
1084
1309
  }
1085
1310
  }
@@ -1088,21 +1313,24 @@ class LDClientImpl {
1088
1313
  this.logger.debug('Result value is null. Providing default value.');
1089
1314
  successDetail.value = defaultValue;
1090
1315
  }
1091
- this.eventProcessor?.sendEvent(eventFactory.evalEventClient(flagKey, value, defaultValue, foundItem.flag, evalContext, reason));
1316
+ prerequisites?.forEach((prereqKey) => {
1317
+ this.variation(prereqKey, undefined);
1318
+ });
1319
+ this._eventProcessor?.sendEvent(eventFactory.evalEventClient(flagKey, value, defaultValue, foundItem.flag, evalContext, reason));
1092
1320
  return successDetail;
1093
1321
  }
1094
1322
  variation(flagKey, defaultValue) {
1095
- const { value } = this.hookRunner.withEvaluation(flagKey, this.uncheckedContext, defaultValue, () => this.variationInternal(flagKey, defaultValue, this.eventFactoryDefault));
1323
+ const { value } = this._hookRunner.withEvaluation(flagKey, this._uncheckedContext, defaultValue, () => this._variationInternal(flagKey, defaultValue, this._eventFactoryDefault));
1096
1324
  return value;
1097
1325
  }
1098
1326
  variationDetail(flagKey, defaultValue) {
1099
- return this.hookRunner.withEvaluation(flagKey, this.uncheckedContext, defaultValue, () => this.variationInternal(flagKey, defaultValue, this.eventFactoryWithReasons));
1327
+ return this._hookRunner.withEvaluation(flagKey, this._uncheckedContext, defaultValue, () => this._variationInternal(flagKey, defaultValue, this._eventFactoryWithReasons));
1100
1328
  }
1101
- typedEval(key, defaultValue, eventFactory, typeChecker) {
1102
- return this.hookRunner.withEvaluation(key, this.uncheckedContext, defaultValue, () => this.variationInternal(key, defaultValue, eventFactory, typeChecker));
1329
+ _typedEval(key, defaultValue, eventFactory, typeChecker) {
1330
+ return this._hookRunner.withEvaluation(key, this._uncheckedContext, defaultValue, () => this._variationInternal(key, defaultValue, eventFactory, typeChecker));
1103
1331
  }
1104
1332
  boolVariation(key, defaultValue) {
1105
- return this.typedEval(key, defaultValue, this.eventFactoryDefault, (value) => [
1333
+ return this._typedEval(key, defaultValue, this._eventFactoryDefault, (value) => [
1106
1334
  TypeValidators.Boolean.is(value),
1107
1335
  TypeValidators.Boolean.getType(),
1108
1336
  ]).value;
@@ -1111,31 +1339,31 @@ class LDClientImpl {
1111
1339
  return this.variation(key, defaultValue);
1112
1340
  }
1113
1341
  numberVariation(key, defaultValue) {
1114
- return this.typedEval(key, defaultValue, this.eventFactoryDefault, (value) => [
1342
+ return this._typedEval(key, defaultValue, this._eventFactoryDefault, (value) => [
1115
1343
  TypeValidators.Number.is(value),
1116
1344
  TypeValidators.Number.getType(),
1117
1345
  ]).value;
1118
1346
  }
1119
1347
  stringVariation(key, defaultValue) {
1120
- return this.typedEval(key, defaultValue, this.eventFactoryDefault, (value) => [
1348
+ return this._typedEval(key, defaultValue, this._eventFactoryDefault, (value) => [
1121
1349
  TypeValidators.String.is(value),
1122
1350
  TypeValidators.String.getType(),
1123
1351
  ]).value;
1124
1352
  }
1125
1353
  boolVariationDetail(key, defaultValue) {
1126
- return this.typedEval(key, defaultValue, this.eventFactoryWithReasons, (value) => [
1354
+ return this._typedEval(key, defaultValue, this._eventFactoryWithReasons, (value) => [
1127
1355
  TypeValidators.Boolean.is(value),
1128
1356
  TypeValidators.Boolean.getType(),
1129
1357
  ]);
1130
1358
  }
1131
1359
  numberVariationDetail(key, defaultValue) {
1132
- return this.typedEval(key, defaultValue, this.eventFactoryWithReasons, (value) => [
1360
+ return this._typedEval(key, defaultValue, this._eventFactoryWithReasons, (value) => [
1133
1361
  TypeValidators.Number.is(value),
1134
1362
  TypeValidators.Number.getType(),
1135
1363
  ]);
1136
1364
  }
1137
1365
  stringVariationDetail(key, defaultValue) {
1138
- return this.typedEval(key, defaultValue, this.eventFactoryWithReasons, (value) => [
1366
+ return this._typedEval(key, defaultValue, this._eventFactoryWithReasons, (value) => [
1139
1367
  TypeValidators.String.is(value),
1140
1368
  TypeValidators.String.getType(),
1141
1369
  ]);
@@ -1144,7 +1372,7 @@ class LDClientImpl {
1144
1372
  return this.variationDetail(key, defaultValue);
1145
1373
  }
1146
1374
  addHook(hook) {
1147
- this.hookRunner.addHook(hook);
1375
+ this._hookRunner.addHook(hook);
1148
1376
  }
1149
1377
  /**
1150
1378
  * Enable/Disable event sending.
@@ -1152,13 +1380,13 @@ class LDClientImpl {
1152
1380
  * @param flush True to flush while disabling. Useful to flush on certain state transitions.
1153
1381
  */
1154
1382
  setEventSendingEnabled(enabled, flush) {
1155
- if (this.eventSendingEnabled === enabled) {
1383
+ if (this._eventSendingEnabled === enabled) {
1156
1384
  return;
1157
1385
  }
1158
- this.eventSendingEnabled = enabled;
1386
+ this._eventSendingEnabled = enabled;
1159
1387
  if (enabled) {
1160
1388
  this.logger.debug('Starting event processor');
1161
- this.eventProcessor?.start();
1389
+ this._eventProcessor?.start();
1162
1390
  }
1163
1391
  else if (flush) {
1164
1392
  this.logger?.debug('Flushing event processor before disabling.');
@@ -1166,92 +1394,70 @@ class LDClientImpl {
1166
1394
  this.flush().then(() => {
1167
1395
  // While waiting for the flush event sending could be re-enabled, in which case
1168
1396
  // we do not want to close the event processor.
1169
- if (!this.eventSendingEnabled) {
1397
+ if (!this._eventSendingEnabled) {
1170
1398
  this.logger?.debug('Stopping event processor.');
1171
- this.eventProcessor?.close();
1399
+ this._eventProcessor?.close();
1172
1400
  }
1173
1401
  });
1174
1402
  }
1175
1403
  else {
1176
1404
  // Just disabled.
1177
1405
  this.logger?.debug('Stopping event processor.');
1178
- this.eventProcessor?.close();
1406
+ this._eventProcessor?.close();
1179
1407
  }
1180
1408
  }
1181
1409
  sendEvent(event) {
1182
- this.eventProcessor?.sendEvent(event);
1183
- }
1184
- }
1185
-
1186
- function isOk(status) {
1187
- return status >= 200 && status <= 299;
1188
- }
1189
- class LDRequestError extends Error {
1190
- constructor(message, status) {
1191
- super(message);
1192
- this.status = status;
1193
- this.name = 'LaunchDarklyRequestError';
1410
+ this._eventProcessor?.sendEvent(event);
1194
1411
  }
1195
- }
1196
- /**
1197
- * Note: The requestor is implemented independently from polling such that it can be used to
1198
- * make a one-off request.
1199
- */
1200
- class Requestor {
1201
- constructor(requests, uri, headers, method, body) {
1202
- this.requests = requests;
1203
- this.uri = uri;
1204
- this.headers = headers;
1205
- this.method = method;
1206
- this.body = body;
1207
- }
1208
- async requestPayload() {
1209
- let status;
1210
- try {
1211
- const res = await this.requests.fetch(this.uri, {
1212
- method: this.method,
1213
- headers: this.headers,
1214
- body: this.body,
1215
- });
1216
- if (isOk(res.status)) {
1217
- return await res.text();
1412
+ _handleInspectionChanged(flagKeys, type) {
1413
+ if (!this._inspectorManager.hasInspectors()) {
1414
+ return;
1415
+ }
1416
+ const details = {};
1417
+ flagKeys.forEach((flagKey) => {
1418
+ const item = this._flagManager.get(flagKey);
1419
+ if (item?.flag && !item.flag.deleted) {
1420
+ const { reason, value, variation } = item.flag;
1421
+ details[flagKey] = createSuccessEvaluationDetail(value, variation, reason);
1218
1422
  }
1219
- // Assigning so it can be thrown after the try/catch.
1220
- status = res.status;
1423
+ });
1424
+ if (type === 'init') {
1425
+ this._inspectorManager.onFlagsChanged(details);
1221
1426
  }
1222
- catch (err) {
1223
- throw new LDRequestError(err?.message);
1427
+ else if (type === 'patch') {
1428
+ Object.entries(details).forEach(([flagKey, detail]) => {
1429
+ this._inspectorManager.onFlagChanged(flagKey, detail);
1430
+ });
1224
1431
  }
1225
- throw new LDRequestError(`Unexpected status code: ${status}`, status);
1226
1432
  }
1227
1433
  }
1228
1434
 
1229
1435
  class DataSourceEventHandler {
1230
- constructor(flagManager, statusManager, logger) {
1231
- this.flagManager = flagManager;
1232
- this.statusManager = statusManager;
1233
- this.logger = logger;
1436
+ constructor(_flagManager, _statusManager, _logger) {
1437
+ this._flagManager = _flagManager;
1438
+ this._statusManager = _statusManager;
1439
+ this._logger = _logger;
1234
1440
  }
1235
1441
  async handlePut(context, flags) {
1236
- this.logger.debug(`Got PUT: ${Object.keys(flags)}`);
1442
+ this._logger.debug(`Got PUT: ${Object.keys(flags)}`);
1237
1443
  // mapping flags to item descriptors
1238
1444
  const descriptors = Object.entries(flags).reduce((acc, [key, flag]) => {
1239
1445
  acc[key] = { version: flag.version, flag };
1240
1446
  return acc;
1241
1447
  }, {});
1242
- await this.flagManager.init(context, descriptors);
1243
- this.statusManager.requestStateUpdate(DataSourceState.Valid);
1448
+ await this._flagManager.init(context, descriptors);
1449
+ this._statusManager.requestStateUpdate(DataSourceState.Valid);
1244
1450
  }
1245
1451
  async handlePatch(context, patchFlag) {
1246
- this.logger.debug(`Got PATCH ${JSON.stringify(patchFlag, null, 2)}`);
1247
- this.flagManager.upsert(context, patchFlag.key, {
1452
+ this._logger.debug(`Got PATCH ${JSON.stringify(patchFlag, null, 2)}`);
1453
+ this._flagManager.upsert(context, patchFlag.key, {
1248
1454
  version: patchFlag.version,
1249
1455
  flag: patchFlag,
1250
1456
  });
1251
1457
  }
1252
1458
  async handleDelete(context, deleteFlag) {
1253
- this.logger.debug(`Got DELETE ${JSON.stringify(deleteFlag, null, 2)}`);
1254
- this.flagManager.upsert(context, deleteFlag.key, {
1459
+ this._logger.debug(`Got DELETE ${JSON.stringify(deleteFlag, null, 2)}`);
1460
+ this._flagManager.upsert(context, deleteFlag.key, {
1255
1461
  version: deleteFlag.version,
1256
1462
  flag: {
1257
1463
  ...deleteFlag,
@@ -1266,10 +1472,10 @@ class DataSourceEventHandler {
1266
1472
  });
1267
1473
  }
1268
1474
  handleStreamingError(error) {
1269
- this.statusManager.reportError(error.kind, error.message, error.code, error.recoverable);
1475
+ this._statusManager.reportError(error.kind, error.message, error.code, error.recoverable);
1270
1476
  }
1271
1477
  handlePollingError(error) {
1272
- this.statusManager.reportError(error.kind, error.message, error.status, error.recoverable);
1478
+ this._statusManager.reportError(error.kind, error.message, error.status, error.recoverable);
1273
1479
  }
1274
1480
  }
1275
1481
 
@@ -1277,17 +1483,17 @@ class DataSourceEventHandler {
1277
1483
  * Tracks the current data source status and emits updates when the status changes.
1278
1484
  */
1279
1485
  class DataSourceStatusManager {
1280
- constructor(emitter, timeStamper = () => Date.now()) {
1281
- this.emitter = emitter;
1282
- this.state = DataSourceState.Closed;
1283
- this.stateSinceMillis = timeStamper();
1284
- this.timeStamper = timeStamper;
1486
+ constructor(_emitter, timeStamper = () => Date.now()) {
1487
+ this._emitter = _emitter;
1488
+ this._state = DataSourceState.Closed;
1489
+ this._stateSinceMillis = timeStamper();
1490
+ this._timeStamper = timeStamper;
1285
1491
  }
1286
1492
  get status() {
1287
1493
  return {
1288
- state: this.state,
1289
- stateSince: this.stateSinceMillis,
1290
- lastError: this.errorInfo,
1494
+ state: this._state,
1495
+ stateSince: this._stateSinceMillis,
1496
+ lastError: this._errorInfo,
1291
1497
  };
1292
1498
  }
1293
1499
  /**
@@ -1296,17 +1502,17 @@ class DataSourceStatusManager {
1296
1502
  * @param requestedState to track
1297
1503
  * @param isError to indicate that the state update is a result of an error occurring.
1298
1504
  */
1299
- updateState(requestedState, isError = false) {
1300
- const newState = requestedState === DataSourceState.Interrupted && this.state === DataSourceState.Initializing // don't go to interrupted from initializing (recoverable errors when initializing are not noteworthy)
1505
+ _updateState(requestedState, isError = false) {
1506
+ const newState = requestedState === DataSourceState.Interrupted && this._state === DataSourceState.Initializing // don't go to interrupted from initializing (recoverable errors when initializing are not noteworthy)
1301
1507
  ? DataSourceState.Initializing
1302
1508
  : requestedState;
1303
- const changedState = this.state !== newState;
1509
+ const changedState = this._state !== newState;
1304
1510
  if (changedState) {
1305
- this.state = newState;
1306
- this.stateSinceMillis = this.timeStamper();
1511
+ this._state = newState;
1512
+ this._stateSinceMillis = this._timeStamper();
1307
1513
  }
1308
1514
  if (changedState || isError) {
1309
- this.emitter.emit('dataSourceStatus', this.status);
1515
+ this._emitter.emit('dataSourceStatus', this.status);
1310
1516
  }
1311
1517
  }
1312
1518
  /**
@@ -1315,7 +1521,7 @@ class DataSourceStatusManager {
1315
1521
  * @param state that is requested
1316
1522
  */
1317
1523
  requestStateUpdate(state) {
1318
- this.updateState(state);
1524
+ this._updateState(state);
1319
1525
  }
1320
1526
  /**
1321
1527
  * Reports a datasource error to this manager. Since the {@link DataSourceStatus} includes error
@@ -1332,10 +1538,10 @@ class DataSourceStatusManager {
1332
1538
  kind,
1333
1539
  message,
1334
1540
  statusCode,
1335
- time: this.timeStamper(),
1541
+ time: this._timeStamper(),
1336
1542
  };
1337
- this.errorInfo = errorInfo;
1338
- this.updateState(recoverable ? DataSourceState.Interrupted : DataSourceState.Closed, true);
1543
+ this._errorInfo = errorInfo;
1544
+ this._updateState(recoverable ? DataSourceState.Interrupted : DataSourceState.Closed, true);
1339
1545
  }
1340
1546
  }
1341
1547
 
@@ -1343,54 +1549,34 @@ class DataSourceStatusManager {
1343
1549
  * @internal
1344
1550
  */
1345
1551
  class PollingProcessor {
1346
- constructor(plainContextString, dataSourceConfig, requests, encoding, dataHandler, errorHandler, logger) {
1347
- this.plainContextString = plainContextString;
1348
- this.dataSourceConfig = dataSourceConfig;
1349
- this.dataHandler = dataHandler;
1350
- this.errorHandler = errorHandler;
1351
- this.logger = logger;
1352
- this.stopped = false;
1353
- const path = dataSourceConfig.useReport
1354
- ? dataSourceConfig.paths.pathReport(encoding, plainContextString)
1355
- : dataSourceConfig.paths.pathGet(encoding, plainContextString);
1356
- const parameters = [
1357
- ...(dataSourceConfig.queryParameters ?? []),
1358
- ];
1359
- if (this.dataSourceConfig.withReasons) {
1360
- parameters.push({ key: 'withReasons', value: 'true' });
1361
- }
1362
- const uri = getPollingUri(dataSourceConfig.serviceEndpoints, path, parameters);
1363
- this.pollInterval = dataSourceConfig.pollInterval;
1364
- let method = 'GET';
1365
- const headers = { ...dataSourceConfig.baseHeaders };
1366
- let body;
1367
- if (dataSourceConfig.useReport) {
1368
- method = 'REPORT';
1369
- headers['content-type'] = 'application/json';
1370
- body = plainContextString; // context is in body for REPORT
1371
- }
1372
- this.requestor = new Requestor(requests, uri, headers, method, body);
1373
- }
1374
- async poll() {
1375
- if (this.stopped) {
1552
+ constructor(_requestor, _pollIntervalSeconds, _dataHandler, _errorHandler, _logger) {
1553
+ this._requestor = _requestor;
1554
+ this._pollIntervalSeconds = _pollIntervalSeconds;
1555
+ this._dataHandler = _dataHandler;
1556
+ this._errorHandler = _errorHandler;
1557
+ this._logger = _logger;
1558
+ this._stopped = false;
1559
+ }
1560
+ async _poll() {
1561
+ if (this._stopped) {
1376
1562
  return;
1377
1563
  }
1378
1564
  const reportJsonError = (data) => {
1379
- this.logger?.error('Polling received invalid data');
1380
- this.logger?.debug(`Invalid JSON follows: ${data}`);
1381
- this.errorHandler?.(new LDPollingError(DataSourceErrorKind.InvalidData, 'Malformed JSON data in polling response'));
1565
+ this._logger?.error('Polling received invalid data');
1566
+ this._logger?.debug(`Invalid JSON follows: ${data}`);
1567
+ this._errorHandler?.(new LDPollingError(DataSourceErrorKind.InvalidData, 'Malformed JSON data in polling response'));
1382
1568
  };
1383
- this.logger?.debug('Polling LaunchDarkly for feature flag updates');
1569
+ this._logger?.debug('Polling LaunchDarkly for feature flag updates');
1384
1570
  const startTime = Date.now();
1385
1571
  try {
1386
- const res = await this.requestor.requestPayload();
1572
+ const res = await this._requestor.requestPayload();
1387
1573
  try {
1388
1574
  const flags = JSON.parse(res);
1389
1575
  try {
1390
- this.dataHandler?.(flags);
1576
+ this._dataHandler?.(flags);
1391
1577
  }
1392
1578
  catch (err) {
1393
- this.logger?.error(`Exception from data handler: ${err}`);
1579
+ this._logger?.error(`Exception from data handler: ${err}`);
1394
1580
  }
1395
1581
  }
1396
1582
  catch {
@@ -1401,29 +1587,29 @@ class PollingProcessor {
1401
1587
  const requestError = err;
1402
1588
  if (requestError.status !== undefined) {
1403
1589
  if (!isHttpRecoverable(requestError.status)) {
1404
- this.logger?.error(httpErrorMessage(err, 'polling request'));
1405
- this.errorHandler?.(new LDPollingError(DataSourceErrorKind.ErrorResponse, requestError.message, requestError.status));
1590
+ this._logger?.error(httpErrorMessage(err, 'polling request'));
1591
+ this._errorHandler?.(new LDPollingError(DataSourceErrorKind.ErrorResponse, requestError.message, requestError.status));
1406
1592
  return;
1407
1593
  }
1408
1594
  }
1409
- this.logger?.error(httpErrorMessage(err, 'polling request', 'will retry'));
1595
+ this._logger?.error(httpErrorMessage(err, 'polling request', 'will retry'));
1410
1596
  }
1411
1597
  const elapsed = Date.now() - startTime;
1412
- const sleepFor = Math.max(this.pollInterval * 1000 - elapsed, 0);
1413
- this.logger?.debug('Elapsed: %d ms, sleeping for %d ms', elapsed, sleepFor);
1414
- this.timeoutHandle = setTimeout(() => {
1415
- this.poll();
1598
+ const sleepFor = Math.max(this._pollIntervalSeconds * 1000 - elapsed, 0);
1599
+ this._logger?.debug('Elapsed: %d ms, sleeping for %d ms', elapsed, sleepFor);
1600
+ this._timeoutHandle = setTimeout(() => {
1601
+ this._poll();
1416
1602
  }, sleepFor);
1417
1603
  }
1418
1604
  start() {
1419
- this.poll();
1605
+ this._poll();
1420
1606
  }
1421
1607
  stop() {
1422
- if (this.timeoutHandle) {
1423
- clearTimeout(this.timeoutHandle);
1424
- this.timeoutHandle = undefined;
1608
+ if (this._timeoutHandle) {
1609
+ clearTimeout(this._timeoutHandle);
1610
+ this._timeoutHandle = undefined;
1425
1611
  }
1426
- this.stopped = true;
1612
+ this._stopped = true;
1427
1613
  }
1428
1614
  close() {
1429
1615
  this.stop();
@@ -1436,40 +1622,43 @@ const reportJsonError = (type, data, logger, errorHandler) => {
1436
1622
  errorHandler?.(new LDStreamingError(DataSourceErrorKind.InvalidData, 'Malformed JSON data in event stream'));
1437
1623
  };
1438
1624
  class StreamingProcessor {
1439
- constructor(plainContextString, dataSourceConfig, listeners, requests, encoding, diagnosticsManager, errorHandler, logger) {
1440
- this.plainContextString = plainContextString;
1441
- this.dataSourceConfig = dataSourceConfig;
1442
- this.listeners = listeners;
1443
- this.requests = requests;
1444
- this.diagnosticsManager = diagnosticsManager;
1445
- this.errorHandler = errorHandler;
1446
- this.logger = logger;
1447
- // TODO: SC-255969 Implement better REPORT fallback logic
1448
- if (dataSourceConfig.useReport && !requests.getEventSourceCapabilities().customMethod) {
1449
- logger?.error("Configuration option useReport is true, but platform's EventSource does not support custom HTTP methods. Streaming may not work.");
1450
- }
1451
- const path = dataSourceConfig.useReport
1452
- ? dataSourceConfig.paths.pathReport(encoding, plainContextString)
1453
- : dataSourceConfig.paths.pathGet(encoding, plainContextString);
1625
+ constructor(_plainContextString, _dataSourceConfig, _listeners, _requests, encoding, _pollingRequestor, _diagnosticsManager, _errorHandler, _logger) {
1626
+ this._plainContextString = _plainContextString;
1627
+ this._dataSourceConfig = _dataSourceConfig;
1628
+ this._listeners = _listeners;
1629
+ this._requests = _requests;
1630
+ this._pollingRequestor = _pollingRequestor;
1631
+ this._diagnosticsManager = _diagnosticsManager;
1632
+ this._errorHandler = _errorHandler;
1633
+ this._logger = _logger;
1634
+ let path;
1635
+ if (_dataSourceConfig.useReport && !_requests.getEventSourceCapabilities().customMethod) {
1636
+ path = _dataSourceConfig.paths.pathPing(encoding, _plainContextString);
1637
+ }
1638
+ else {
1639
+ path = _dataSourceConfig.useReport
1640
+ ? _dataSourceConfig.paths.pathReport(encoding, _plainContextString)
1641
+ : _dataSourceConfig.paths.pathGet(encoding, _plainContextString);
1642
+ }
1454
1643
  const parameters = [
1455
- ...(dataSourceConfig.queryParameters ?? []),
1644
+ ...(_dataSourceConfig.queryParameters ?? []),
1456
1645
  ];
1457
- if (this.dataSourceConfig.withReasons) {
1646
+ if (this._dataSourceConfig.withReasons) {
1458
1647
  parameters.push({ key: 'withReasons', value: 'true' });
1459
1648
  }
1460
- this.requests = requests;
1461
- this.headers = { ...dataSourceConfig.baseHeaders };
1462
- this.logger = logger;
1463
- this.streamUri = getStreamingUri(dataSourceConfig.serviceEndpoints, path, parameters);
1649
+ this._requests = _requests;
1650
+ this._headers = { ..._dataSourceConfig.baseHeaders };
1651
+ this._logger = _logger;
1652
+ this._streamUri = getStreamingUri(_dataSourceConfig.serviceEndpoints, path, parameters);
1464
1653
  }
1465
- logConnectionStarted() {
1466
- this.connectionAttemptStartTime = Date.now();
1654
+ _logConnectionStarted() {
1655
+ this._connectionAttemptStartTime = Date.now();
1467
1656
  }
1468
- logConnectionResult(success) {
1469
- if (this.connectionAttemptStartTime && this.diagnosticsManager) {
1470
- this.diagnosticsManager.recordStreamInit(this.connectionAttemptStartTime, !success, Date.now() - this.connectionAttemptStartTime);
1657
+ _logConnectionResult(success) {
1658
+ if (this._connectionAttemptStartTime && this._diagnosticsManager) {
1659
+ this._diagnosticsManager.recordStreamInit(this._connectionAttemptStartTime, !success, Date.now() - this._connectionAttemptStartTime);
1471
1660
  }
1472
- this.connectionAttemptStartTime = undefined;
1661
+ this._connectionAttemptStartTime = undefined;
1473
1662
  }
1474
1663
  /**
1475
1664
  * This is a wrapper around the passed errorHandler which adds additional
@@ -1480,75 +1669,101 @@ class StreamingProcessor {
1480
1669
  *
1481
1670
  * @private
1482
1671
  */
1483
- retryAndHandleError(err) {
1672
+ _retryAndHandleError(err) {
1484
1673
  if (!shouldRetry(err)) {
1485
- this.logConnectionResult(false);
1486
- this.errorHandler?.(new LDStreamingError(DataSourceErrorKind.ErrorResponse, err.message, err.status, false));
1487
- this.logger?.error(httpErrorMessage(err, 'streaming request'));
1674
+ this._logConnectionResult(false);
1675
+ this._errorHandler?.(new LDStreamingError(DataSourceErrorKind.ErrorResponse, err.message, err.status, false));
1676
+ this._logger?.error(httpErrorMessage(err, 'streaming request'));
1488
1677
  return false;
1489
1678
  }
1490
- this.logger?.warn(httpErrorMessage(err, 'streaming request', 'will retry'));
1491
- this.logConnectionResult(false);
1492
- this.logConnectionStarted();
1679
+ this._logger?.warn(httpErrorMessage(err, 'streaming request', 'will retry'));
1680
+ this._logConnectionResult(false);
1681
+ this._logConnectionStarted();
1493
1682
  return true;
1494
1683
  }
1495
1684
  start() {
1496
- this.logConnectionStarted();
1685
+ this._logConnectionStarted();
1497
1686
  let methodAndBodyOverrides;
1498
- if (this.dataSourceConfig.useReport) {
1687
+ if (this._dataSourceConfig.useReport) {
1499
1688
  // REPORT will include a body, so content type is required.
1500
- this.headers['content-type'] = 'application/json';
1689
+ this._headers['content-type'] = 'application/json';
1501
1690
  // orverrides default method with REPORT and adds body.
1502
- methodAndBodyOverrides = { method: 'REPORT', body: this.plainContextString };
1691
+ methodAndBodyOverrides = { method: 'REPORT', body: this._plainContextString };
1503
1692
  }
1504
1693
  else {
1505
1694
  // no method or body override
1506
1695
  methodAndBodyOverrides = {};
1507
1696
  }
1508
1697
  // TLS is handled by the platform implementation.
1509
- const eventSource = this.requests.createEventSource(this.streamUri, {
1510
- headers: this.headers,
1698
+ const eventSource = this._requests.createEventSource(this._streamUri, {
1699
+ headers: this._headers,
1511
1700
  ...methodAndBodyOverrides,
1512
- errorFilter: (error) => this.retryAndHandleError(error),
1513
- initialRetryDelayMillis: this.dataSourceConfig.initialRetryDelayMillis,
1701
+ errorFilter: (error) => this._retryAndHandleError(error),
1702
+ initialRetryDelayMillis: this._dataSourceConfig.initialRetryDelayMillis,
1514
1703
  readTimeoutMillis: 5 * 60 * 1000,
1515
1704
  retryResetIntervalMillis: 60 * 1000,
1516
1705
  });
1517
- this.eventSource = eventSource;
1706
+ this._eventSource = eventSource;
1518
1707
  eventSource.onclose = () => {
1519
- this.logger?.info('Closed LaunchDarkly stream connection');
1708
+ this._logger?.info('Closed LaunchDarkly stream connection');
1520
1709
  };
1521
1710
  eventSource.onerror = () => {
1522
1711
  // The work is done by `errorFilter`.
1523
1712
  };
1524
1713
  eventSource.onopen = () => {
1525
- this.logger?.info('Opened LaunchDarkly stream connection');
1714
+ this._logger?.info('Opened LaunchDarkly stream connection');
1526
1715
  };
1527
1716
  eventSource.onretrying = (e) => {
1528
- this.logger?.info(`Will retry stream connection in ${e.delayMillis} milliseconds`);
1717
+ this._logger?.info(`Will retry stream connection in ${e.delayMillis} milliseconds`);
1529
1718
  };
1530
- this.listeners.forEach(({ deserializeData, processJson }, eventName) => {
1719
+ this._listeners.forEach(({ deserializeData, processJson }, eventName) => {
1531
1720
  eventSource.addEventListener(eventName, (event) => {
1532
- this.logger?.debug(`Received ${eventName} event`);
1721
+ this._logger?.debug(`Received ${eventName} event`);
1533
1722
  if (event?.data) {
1534
- this.logConnectionResult(true);
1723
+ this._logConnectionResult(true);
1535
1724
  const { data } = event;
1536
1725
  const dataJson = deserializeData(data);
1537
1726
  if (!dataJson) {
1538
- reportJsonError(eventName, data, this.logger, this.errorHandler);
1727
+ reportJsonError(eventName, data, this._logger, this._errorHandler);
1539
1728
  return;
1540
1729
  }
1541
1730
  processJson(dataJson);
1542
1731
  }
1543
1732
  else {
1544
- this.errorHandler?.(new LDStreamingError(DataSourceErrorKind.InvalidData, 'Unexpected payload from event stream'));
1733
+ this._errorHandler?.(new LDStreamingError(DataSourceErrorKind.InvalidData, 'Unexpected payload from event stream'));
1545
1734
  }
1546
1735
  });
1547
1736
  });
1737
+ // here we set up a listener that will poll when ping is received
1738
+ eventSource.addEventListener('ping', async () => {
1739
+ this._logger?.debug('Got PING, going to poll LaunchDarkly for feature flag updates');
1740
+ try {
1741
+ const res = await this._pollingRequestor.requestPayload();
1742
+ try {
1743
+ const payload = JSON.parse(res);
1744
+ try {
1745
+ // forward the payload on to the PUT listener
1746
+ this._listeners.get('put')?.processJson(payload);
1747
+ }
1748
+ catch (err) {
1749
+ this._logger?.error(`Exception from data handler: ${err}`);
1750
+ }
1751
+ }
1752
+ catch {
1753
+ this._logger?.error('Polling after ping received invalid data');
1754
+ this._logger?.debug(`Invalid JSON follows: ${res}`);
1755
+ this._errorHandler?.(new LDPollingError(DataSourceErrorKind.InvalidData, 'Malformed JSON data in ping polling response'));
1756
+ }
1757
+ }
1758
+ catch (err) {
1759
+ const requestError = err;
1760
+ this._errorHandler?.(new LDPollingError(DataSourceErrorKind.ErrorResponse, requestError.message, requestError.status));
1761
+ }
1762
+ });
1548
1763
  }
1549
1764
  stop() {
1550
- this.eventSource?.close();
1551
- this.eventSource = undefined;
1765
+ this._eventSource?.close();
1766
+ this._eventSource = undefined;
1552
1767
  }
1553
1768
  close() {
1554
1769
  this.stop();
@@ -1568,35 +1783,26 @@ class BaseDataManager {
1568
1783
  this.diagnosticsManager = diagnosticsManager;
1569
1784
  this.logger = config.logger;
1570
1785
  this.dataSourceStatusManager = new DataSourceStatusManager(emitter);
1571
- this.dataSourceEventHandler = new DataSourceEventHandler(flagManager, this.dataSourceStatusManager, this.config.logger);
1786
+ this._dataSourceEventHandler = new DataSourceEventHandler(flagManager, this.dataSourceStatusManager, this.config.logger);
1572
1787
  }
1573
1788
  /**
1574
1789
  * Set additional connection parameters for requests polling/streaming.
1575
1790
  */
1576
1791
  setConnectionParams(connectionParams) {
1577
- this.connectionParams = connectionParams;
1792
+ this._connectionParams = connectionParams;
1578
1793
  }
1579
- createPollingProcessor(context, checkedContext, identifyResolve, identifyReject) {
1580
- const processor = new PollingProcessor(JSON.stringify(context), {
1581
- credential: this.credential,
1582
- serviceEndpoints: this.config.serviceEndpoints,
1583
- paths: this.getPollingPaths(),
1584
- baseHeaders: this.baseHeaders,
1585
- pollInterval: this.config.pollInterval,
1586
- withReasons: this.config.withReasons,
1587
- useReport: this.config.useReport,
1588
- queryParameters: this.connectionParams?.queryParameters,
1589
- }, this.platform.requests, this.platform.encoding, async (flags) => {
1590
- await this.dataSourceEventHandler.handlePut(checkedContext, flags);
1794
+ createPollingProcessor(context, checkedContext, requestor, identifyResolve, identifyReject) {
1795
+ const processor = new PollingProcessor(requestor, this.config.pollInterval, async (flags) => {
1796
+ await this._dataSourceEventHandler.handlePut(checkedContext, flags);
1591
1797
  identifyResolve?.();
1592
1798
  }, (err) => {
1593
1799
  this.emitter.emit('error', context, err);
1594
- this.dataSourceEventHandler.handlePollingError(err);
1800
+ this._dataSourceEventHandler.handlePollingError(err);
1595
1801
  identifyReject?.(err);
1596
- });
1597
- this.updateProcessor = this.decorateProcessorWithStatusReporting(processor, this.dataSourceStatusManager);
1802
+ }, this.logger);
1803
+ this.updateProcessor = this._decorateProcessorWithStatusReporting(processor, this.dataSourceStatusManager);
1598
1804
  }
1599
- createStreamingProcessor(context, checkedContext, identifyResolve, identifyReject) {
1805
+ createStreamingProcessor(context, checkedContext, pollingRequestor, identifyResolve, identifyReject) {
1600
1806
  const processor = new StreamingProcessor(JSON.stringify(context), {
1601
1807
  credential: this.credential,
1602
1808
  serviceEndpoints: this.config.serviceEndpoints,
@@ -1605,38 +1811,38 @@ class BaseDataManager {
1605
1811
  initialRetryDelayMillis: this.config.streamInitialReconnectDelay * 1000,
1606
1812
  withReasons: this.config.withReasons,
1607
1813
  useReport: this.config.useReport,
1608
- queryParameters: this.connectionParams?.queryParameters,
1609
- }, this.createStreamListeners(checkedContext, identifyResolve), this.platform.requests, this.platform.encoding, this.diagnosticsManager, (e) => {
1814
+ queryParameters: this._connectionParams?.queryParameters,
1815
+ }, this.createStreamListeners(checkedContext, identifyResolve), this.platform.requests, this.platform.encoding, pollingRequestor, this.diagnosticsManager, (e) => {
1610
1816
  this.emitter.emit('error', context, e);
1611
- this.dataSourceEventHandler.handleStreamingError(e);
1817
+ this._dataSourceEventHandler.handleStreamingError(e);
1612
1818
  identifyReject?.(e);
1613
- });
1614
- this.updateProcessor = this.decorateProcessorWithStatusReporting(processor, this.dataSourceStatusManager);
1819
+ }, this.logger);
1820
+ this.updateProcessor = this._decorateProcessorWithStatusReporting(processor, this.dataSourceStatusManager);
1615
1821
  }
1616
1822
  createStreamListeners(context, identifyResolve) {
1617
1823
  const listeners = new Map();
1618
1824
  listeners.set('put', {
1619
1825
  deserializeData: JSON.parse,
1620
1826
  processJson: async (flags) => {
1621
- await this.dataSourceEventHandler.handlePut(context, flags);
1827
+ await this._dataSourceEventHandler.handlePut(context, flags);
1622
1828
  identifyResolve?.();
1623
1829
  },
1624
1830
  });
1625
1831
  listeners.set('patch', {
1626
1832
  deserializeData: JSON.parse,
1627
1833
  processJson: async (patchFlag) => {
1628
- this.dataSourceEventHandler.handlePatch(context, patchFlag);
1834
+ this._dataSourceEventHandler.handlePatch(context, patchFlag);
1629
1835
  },
1630
1836
  });
1631
1837
  listeners.set('delete', {
1632
1838
  deserializeData: JSON.parse,
1633
1839
  processJson: async (deleteFlag) => {
1634
- this.dataSourceEventHandler.handleDelete(context, deleteFlag);
1840
+ this._dataSourceEventHandler.handleDelete(context, deleteFlag);
1635
1841
  },
1636
1842
  });
1637
1843
  return listeners;
1638
1844
  }
1639
- decorateProcessorWithStatusReporting(processor, statusManager) {
1845
+ _decorateProcessorWithStatusReporting(processor, statusManager) {
1640
1846
  return {
1641
1847
  start: () => {
1642
1848
  // update status before starting processor to ensure potential errors are reported after initializing
@@ -1655,5 +1861,5 @@ class BaseDataManager {
1655
1861
  }
1656
1862
  }
1657
1863
 
1658
- export { BaseDataManager, DataSourceState, LDClientImpl, Requestor };
1864
+ export { BaseDataManager, DataSourceState, LDClientImpl, Requestor, makeRequestor };
1659
1865
  //# sourceMappingURL=index.mjs.map