@launchdarkly/js-client-sdk-common 1.15.2 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/dist/cjs/LDClientImpl.d.ts +37 -4
- package/dist/cjs/LDClientImpl.d.ts.map +1 -1
- package/dist/cjs/LDEmitter.d.ts +1 -1
- package/dist/cjs/LDEmitter.d.ts.map +1 -1
- package/dist/cjs/api/LDClient.d.ts +33 -0
- package/dist/cjs/api/LDClient.d.ts.map +1 -1
- package/dist/cjs/api/LDOptions.d.ts +7 -0
- package/dist/cjs/api/LDOptions.d.ts.map +1 -1
- package/dist/cjs/api/LDPlugin.d.ts +16 -0
- package/dist/cjs/api/LDPlugin.d.ts.map +1 -0
- package/dist/cjs/api/LDWaitForInitialization.d.ts +50 -0
- package/dist/cjs/api/LDWaitForInitialization.d.ts.map +1 -0
- package/dist/cjs/api/index.d.ts +2 -0
- package/dist/cjs/api/index.d.ts.map +1 -1
- package/dist/cjs/configuration/Configuration.d.ts +1 -0
- package/dist/cjs/configuration/Configuration.d.ts.map +1 -1
- package/dist/cjs/configuration/validators.d.ts.map +1 -1
- package/dist/cjs/context/createActiveContextTracker.d.ts +49 -0
- package/dist/cjs/context/createActiveContextTracker.d.ts.map +1 -0
- package/dist/cjs/flag-manager/FlagManager.d.ts +70 -1
- package/dist/cjs/flag-manager/FlagManager.d.ts.map +1 -1
- package/dist/cjs/flag-manager/FlagPersistence.d.ts +1 -1
- package/dist/cjs/flag-manager/FlagPersistence.d.ts.map +1 -1
- package/dist/cjs/flag-manager/FlagStore.d.ts +15 -13
- package/dist/cjs/flag-manager/FlagStore.d.ts.map +1 -1
- package/dist/cjs/flag-manager/FlagUpdater.d.ts +35 -7
- package/dist/cjs/flag-manager/FlagUpdater.d.ts.map +1 -1
- package/dist/cjs/index.cjs +347 -121
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.ts +3 -2
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/plugins/safeRegisterDebugOverridePlugins.d.ts +12 -0
- package/dist/cjs/plugins/safeRegisterDebugOverridePlugins.d.ts.map +1 -0
- package/dist/esm/LDClientImpl.d.ts +37 -4
- package/dist/esm/LDClientImpl.d.ts.map +1 -1
- package/dist/esm/LDEmitter.d.ts +1 -1
- package/dist/esm/LDEmitter.d.ts.map +1 -1
- package/dist/esm/api/LDClient.d.ts +33 -0
- package/dist/esm/api/LDClient.d.ts.map +1 -1
- package/dist/esm/api/LDOptions.d.ts +7 -0
- package/dist/esm/api/LDOptions.d.ts.map +1 -1
- package/dist/esm/api/LDPlugin.d.ts +16 -0
- package/dist/esm/api/LDPlugin.d.ts.map +1 -0
- package/dist/esm/api/LDWaitForInitialization.d.ts +50 -0
- package/dist/esm/api/LDWaitForInitialization.d.ts.map +1 -0
- package/dist/esm/api/index.d.ts +2 -0
- package/dist/esm/api/index.d.ts.map +1 -1
- package/dist/esm/configuration/Configuration.d.ts +1 -0
- package/dist/esm/configuration/Configuration.d.ts.map +1 -1
- package/dist/esm/configuration/validators.d.ts.map +1 -1
- package/dist/esm/context/createActiveContextTracker.d.ts +49 -0
- package/dist/esm/context/createActiveContextTracker.d.ts.map +1 -0
- package/dist/esm/flag-manager/FlagManager.d.ts +70 -1
- package/dist/esm/flag-manager/FlagManager.d.ts.map +1 -1
- package/dist/esm/flag-manager/FlagPersistence.d.ts +1 -1
- package/dist/esm/flag-manager/FlagPersistence.d.ts.map +1 -1
- package/dist/esm/flag-manager/FlagStore.d.ts +15 -13
- package/dist/esm/flag-manager/FlagStore.d.ts.map +1 -1
- package/dist/esm/flag-manager/FlagUpdater.d.ts +35 -7
- package/dist/esm/flag-manager/FlagUpdater.d.ts.map +1 -1
- package/dist/esm/index.d.ts +3 -2
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.mjs +348 -123
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/plugins/safeRegisterDebugOverridePlugins.d.ts +12 -0
- package/dist/esm/plugins/safeRegisterDebugOverridePlugins.d.ts.map +1 -0
- package/package.json +2 -2
package/dist/esm/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getPollingUri, TypeValidators, createSafeLogger, ServiceEndpoints, ApplicationTags, OptionMessages, NumberWithMinimum, SafeLogger, internal, deepCompact, clone, secondsToMillis, ClientContext, fastDeepEqual, defaultHeaders, Context, LDTimeoutError, AutoEnvAttributes, LDClientError, 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, LDTimeoutError, AutoEnvAttributes, cancelableTimedPromise, 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 };
|
|
@@ -242,6 +242,7 @@ const validators = {
|
|
|
242
242
|
payloadFilterKey: TypeValidators.stringMatchingRegex(/^[a-zA-Z0-9](\w|\.|-)*$/),
|
|
243
243
|
hooks: TypeValidators.createTypeArray('Hook[]', {}),
|
|
244
244
|
inspectors: TypeValidators.createTypeArray('LDInspection', {}),
|
|
245
|
+
cleanOldPersistentData: TypeValidators.Boolean,
|
|
245
246
|
};
|
|
246
247
|
|
|
247
248
|
const DEFAULT_POLLING_INTERVAL = 60 * 5;
|
|
@@ -520,6 +521,38 @@ const addAutoEnv = async (context, platform, config) => {
|
|
|
520
521
|
return context;
|
|
521
522
|
};
|
|
522
523
|
|
|
524
|
+
function createActiveContextTracker() {
|
|
525
|
+
let unwrappedContext;
|
|
526
|
+
let context;
|
|
527
|
+
return {
|
|
528
|
+
set(_unwrappedContext, _context) {
|
|
529
|
+
unwrappedContext = _unwrappedContext;
|
|
530
|
+
context = _context;
|
|
531
|
+
},
|
|
532
|
+
getContext() {
|
|
533
|
+
return context;
|
|
534
|
+
},
|
|
535
|
+
getUnwrappedContext() {
|
|
536
|
+
return unwrappedContext;
|
|
537
|
+
},
|
|
538
|
+
newIdentificationPromise() {
|
|
539
|
+
let res;
|
|
540
|
+
let rej;
|
|
541
|
+
const basePromise = new Promise((resolve, reject) => {
|
|
542
|
+
res = resolve;
|
|
543
|
+
rej = reject;
|
|
544
|
+
});
|
|
545
|
+
return { identifyPromise: basePromise, identifyResolve: res, identifyReject: rej };
|
|
546
|
+
},
|
|
547
|
+
hasContext() {
|
|
548
|
+
return context !== undefined;
|
|
549
|
+
},
|
|
550
|
+
hasValidContext() {
|
|
551
|
+
return this.hasContext() && context.valid;
|
|
552
|
+
},
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
|
|
523
556
|
const { isLegacyUser, isMultiKind, isSingleKind } = internal;
|
|
524
557
|
/**
|
|
525
558
|
* This is the root ensureKey function. All other ensureKey functions reduce to this.
|
|
@@ -813,30 +846,30 @@ class FlagPersistence {
|
|
|
813
846
|
}
|
|
814
847
|
|
|
815
848
|
/**
|
|
816
|
-
*
|
|
849
|
+
* Creates the default implementation of the flag store.
|
|
817
850
|
*/
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
},
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
}
|
|
851
|
+
function createDefaultFlagStore() {
|
|
852
|
+
let flags = {};
|
|
853
|
+
return {
|
|
854
|
+
init(newFlags) {
|
|
855
|
+
flags = Object.entries(newFlags).reduce((acc, [key, flag]) => {
|
|
856
|
+
acc[key] = flag;
|
|
857
|
+
return acc;
|
|
858
|
+
}, {});
|
|
859
|
+
},
|
|
860
|
+
insertOrUpdate(key, update) {
|
|
861
|
+
flags[key] = update;
|
|
862
|
+
},
|
|
863
|
+
get(key) {
|
|
864
|
+
if (Object.prototype.hasOwnProperty.call(flags, key)) {
|
|
865
|
+
return flags[key];
|
|
866
|
+
}
|
|
867
|
+
return undefined;
|
|
868
|
+
},
|
|
869
|
+
getAll() {
|
|
870
|
+
return flags;
|
|
871
|
+
},
|
|
872
|
+
};
|
|
840
873
|
}
|
|
841
874
|
|
|
842
875
|
function calculateChangedKeys(existingObject, newObject) {
|
|
@@ -857,69 +890,66 @@ function calculateChangedKeys(existingObject, newObject) {
|
|
|
857
890
|
return changedKeys;
|
|
858
891
|
}
|
|
859
892
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
if (changed.length > 0) {
|
|
877
|
-
this._changeCallbacks.forEach((callback) => {
|
|
878
|
-
try {
|
|
879
|
-
callback(context, changed, 'init');
|
|
880
|
-
}
|
|
881
|
-
catch (err) {
|
|
882
|
-
/* intentionally empty */
|
|
883
|
-
}
|
|
884
|
-
});
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
initCached(context, newFlags) {
|
|
888
|
-
if (this._activeContextKey === context.canonicalKey) {
|
|
889
|
-
return;
|
|
890
|
-
}
|
|
891
|
-
this.init(context, newFlags);
|
|
892
|
-
}
|
|
893
|
-
upsert(context, key, item) {
|
|
894
|
-
if (this._activeContextKey !== context.canonicalKey) {
|
|
895
|
-
this._logger.warn('Received an update for an inactive context.');
|
|
896
|
-
return false;
|
|
897
|
-
}
|
|
898
|
-
const currentValue = this._flagStore.get(key);
|
|
899
|
-
if (currentValue !== undefined && currentValue.version >= item.version) {
|
|
900
|
-
// this is an out of order update that can be ignored
|
|
901
|
-
return false;
|
|
902
|
-
}
|
|
903
|
-
this._flagStore.insertOrUpdate(key, item);
|
|
904
|
-
this._changeCallbacks.forEach((callback) => {
|
|
905
|
-
try {
|
|
906
|
-
callback(context, [key], 'patch');
|
|
893
|
+
function createFlagUpdater(_flagStore, _logger) {
|
|
894
|
+
const flagStore = _flagStore;
|
|
895
|
+
const logger = _logger;
|
|
896
|
+
let activeContext;
|
|
897
|
+
const changeCallbacks = new Array();
|
|
898
|
+
return {
|
|
899
|
+
handleFlagChanges(keys, type) {
|
|
900
|
+
if (activeContext) {
|
|
901
|
+
changeCallbacks.forEach((callback) => {
|
|
902
|
+
try {
|
|
903
|
+
callback(activeContext, keys, type);
|
|
904
|
+
}
|
|
905
|
+
catch (err) {
|
|
906
|
+
/* intentionally empty */
|
|
907
|
+
}
|
|
908
|
+
});
|
|
907
909
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
+
else {
|
|
911
|
+
logger.warn('Received a change event without an active context. Changes will not be propagated.');
|
|
910
912
|
}
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
913
|
+
},
|
|
914
|
+
init(context, newFlags) {
|
|
915
|
+
activeContext = context;
|
|
916
|
+
const oldFlags = flagStore.getAll();
|
|
917
|
+
flagStore.init(newFlags);
|
|
918
|
+
const changed = calculateChangedKeys(oldFlags, newFlags);
|
|
919
|
+
if (changed.length > 0) {
|
|
920
|
+
this.handleFlagChanges(changed, 'init');
|
|
921
|
+
}
|
|
922
|
+
},
|
|
923
|
+
initCached(context, newFlags) {
|
|
924
|
+
if (activeContext?.canonicalKey === context.canonicalKey) {
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
this.init(context, newFlags);
|
|
928
|
+
},
|
|
929
|
+
upsert(context, key, item) {
|
|
930
|
+
if (activeContext?.canonicalKey !== context.canonicalKey) {
|
|
931
|
+
logger.warn('Received an update for an inactive context.');
|
|
932
|
+
return false;
|
|
933
|
+
}
|
|
934
|
+
const currentValue = flagStore.get(key);
|
|
935
|
+
if (currentValue !== undefined && currentValue.version >= item.version) {
|
|
936
|
+
// this is an out of order update that can be ignored
|
|
937
|
+
return false;
|
|
938
|
+
}
|
|
939
|
+
flagStore.insertOrUpdate(key, item);
|
|
940
|
+
this.handleFlagChanges([key], 'patch');
|
|
941
|
+
return true;
|
|
942
|
+
},
|
|
943
|
+
on(callback) {
|
|
944
|
+
changeCallbacks.push(callback);
|
|
945
|
+
},
|
|
946
|
+
off(callback) {
|
|
947
|
+
const index = changeCallbacks.indexOf(callback);
|
|
948
|
+
if (index > -1) {
|
|
949
|
+
changeCallbacks.splice(index, 1);
|
|
950
|
+
}
|
|
951
|
+
},
|
|
952
|
+
};
|
|
923
953
|
}
|
|
924
954
|
|
|
925
955
|
class DefaultFlagManager {
|
|
@@ -931,8 +961,8 @@ class DefaultFlagManager {
|
|
|
931
961
|
* @param timeStamper exists for testing purposes
|
|
932
962
|
*/
|
|
933
963
|
constructor(platform, sdkKey, maxCachedContexts, logger, timeStamper = () => Date.now()) {
|
|
934
|
-
this._flagStore =
|
|
935
|
-
this._flagUpdater =
|
|
964
|
+
this._flagStore = createDefaultFlagStore();
|
|
965
|
+
this._flagUpdater = createFlagUpdater(this._flagStore, logger);
|
|
936
966
|
this._flagPersistencePromise = this._initPersistence(platform, sdkKey, maxCachedContexts, logger, timeStamper);
|
|
937
967
|
}
|
|
938
968
|
async _initPersistence(platform, sdkKey, maxCachedContexts, logger, timeStamper = () => Date.now()) {
|
|
@@ -940,11 +970,26 @@ class DefaultFlagManager {
|
|
|
940
970
|
return new FlagPersistence(platform, environmentNamespace, maxCachedContexts, this._flagStore, this._flagUpdater, logger, timeStamper);
|
|
941
971
|
}
|
|
942
972
|
get(key) {
|
|
973
|
+
if (this._overrides && Object.prototype.hasOwnProperty.call(this._overrides, key)) {
|
|
974
|
+
return this._convertValueToOverrideDescripter(this._overrides[key]);
|
|
975
|
+
}
|
|
943
976
|
return this._flagStore.get(key);
|
|
944
977
|
}
|
|
945
978
|
getAll() {
|
|
979
|
+
if (this._overrides) {
|
|
980
|
+
return {
|
|
981
|
+
...this._flagStore.getAll(),
|
|
982
|
+
...Object.entries(this._overrides).reduce((acc, [key, value]) => {
|
|
983
|
+
acc[key] = this._convertValueToOverrideDescripter(value);
|
|
984
|
+
return acc;
|
|
985
|
+
}, {}),
|
|
986
|
+
};
|
|
987
|
+
}
|
|
946
988
|
return this._flagStore.getAll();
|
|
947
989
|
}
|
|
990
|
+
presetFlags(newFlags) {
|
|
991
|
+
this._flagStore.init(newFlags);
|
|
992
|
+
}
|
|
948
993
|
setBootstrap(context, newFlags) {
|
|
949
994
|
// Bypasses the persistence as we do not want to put these flags into any cache.
|
|
950
995
|
// Generally speaking persistence likely *SHOULD* be disabled when using bootstrap.
|
|
@@ -965,6 +1010,58 @@ class DefaultFlagManager {
|
|
|
965
1010
|
off(callback) {
|
|
966
1011
|
this._flagUpdater.off(callback);
|
|
967
1012
|
}
|
|
1013
|
+
_convertValueToOverrideDescripter(value) {
|
|
1014
|
+
return {
|
|
1015
|
+
flag: {
|
|
1016
|
+
value,
|
|
1017
|
+
version: 0,
|
|
1018
|
+
},
|
|
1019
|
+
version: 0,
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
setOverride(key, value) {
|
|
1023
|
+
if (!this._overrides) {
|
|
1024
|
+
this._overrides = {};
|
|
1025
|
+
}
|
|
1026
|
+
this._overrides[key] = value;
|
|
1027
|
+
this._flagUpdater.handleFlagChanges([key], 'override');
|
|
1028
|
+
}
|
|
1029
|
+
removeOverride(flagKey) {
|
|
1030
|
+
if (!this._overrides || !Object.prototype.hasOwnProperty.call(this._overrides, flagKey)) {
|
|
1031
|
+
return; // No override to remove
|
|
1032
|
+
}
|
|
1033
|
+
delete this._overrides[flagKey];
|
|
1034
|
+
// If no more overrides, reset to undefined for performance
|
|
1035
|
+
if (Object.keys(this._overrides).length === 0) {
|
|
1036
|
+
this._overrides = undefined;
|
|
1037
|
+
}
|
|
1038
|
+
this._flagUpdater.handleFlagChanges([flagKey], 'override');
|
|
1039
|
+
}
|
|
1040
|
+
clearAllOverrides() {
|
|
1041
|
+
if (this._overrides) {
|
|
1042
|
+
const clearedOverrides = { ...this._overrides };
|
|
1043
|
+
this._overrides = undefined; // Reset to undefined
|
|
1044
|
+
this._flagUpdater.handleFlagChanges(Object.keys(clearedOverrides), 'override');
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
getAllOverrides() {
|
|
1048
|
+
if (!this._overrides) {
|
|
1049
|
+
return {};
|
|
1050
|
+
}
|
|
1051
|
+
const result = {};
|
|
1052
|
+
Object.entries(this._overrides).forEach(([key, value]) => {
|
|
1053
|
+
result[key] = this._convertValueToOverrideDescripter(value);
|
|
1054
|
+
});
|
|
1055
|
+
return result;
|
|
1056
|
+
}
|
|
1057
|
+
getDebugOverride() {
|
|
1058
|
+
return {
|
|
1059
|
+
setOverride: this.setOverride.bind(this),
|
|
1060
|
+
removeOverride: this.removeOverride.bind(this),
|
|
1061
|
+
clearAllOverrides: this.clearAllOverrides.bind(this),
|
|
1062
|
+
getAllOverrides: this.getAllOverrides.bind(this),
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
968
1065
|
}
|
|
969
1066
|
|
|
970
1067
|
const UNKNOWN_HOOK_NAME = 'unknown hook';
|
|
@@ -1327,6 +1424,7 @@ class LDClientImpl {
|
|
|
1327
1424
|
this.sdkKey = sdkKey;
|
|
1328
1425
|
this.autoEnvAttributes = autoEnvAttributes;
|
|
1329
1426
|
this.platform = platform;
|
|
1427
|
+
this._activeContextTracker = createActiveContextTracker();
|
|
1330
1428
|
this._highTimeoutThreshold = 15;
|
|
1331
1429
|
this._eventFactoryDefault = new EventFactory(false);
|
|
1332
1430
|
this._eventFactoryWithReasons = new EventFactory(true);
|
|
@@ -1367,6 +1465,24 @@ class LDClientImpl {
|
|
|
1367
1465
|
if (this._inspectorManager.hasInspectors()) {
|
|
1368
1466
|
this._hookRunner.addHook(getInspectorHook(this._inspectorManager));
|
|
1369
1467
|
}
|
|
1468
|
+
if (options.cleanOldPersistentData &&
|
|
1469
|
+
internalOptions?.getLegacyStorageKeys &&
|
|
1470
|
+
this.platform.storage) {
|
|
1471
|
+
// NOTE: we are letting this fail silently because it's not critical and we don't want to block the client from initializing.
|
|
1472
|
+
try {
|
|
1473
|
+
this.logger.debug('Cleaning old persistent data.');
|
|
1474
|
+
Promise.all(internalOptions.getLegacyStorageKeys().map((key) => this.platform.storage?.clear(key)))
|
|
1475
|
+
.catch((error) => {
|
|
1476
|
+
this.logger.error(`Error cleaning old persistent data: ${error}`);
|
|
1477
|
+
})
|
|
1478
|
+
.finally(() => {
|
|
1479
|
+
this.logger.debug('Cleaned old persistent data.');
|
|
1480
|
+
});
|
|
1481
|
+
}
|
|
1482
|
+
catch (error) {
|
|
1483
|
+
this.logger.error(`Error cleaning old persistent data: ${error}`);
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1370
1486
|
}
|
|
1371
1487
|
allFlags() {
|
|
1372
1488
|
// extracting all flag values
|
|
@@ -1401,19 +1517,20 @@ class LDClientImpl {
|
|
|
1401
1517
|
// code. We are returned the unchecked context so that if a consumer identifies with an invalid context
|
|
1402
1518
|
// and then calls getContext, they get back the same context they provided, without any assertion about
|
|
1403
1519
|
// validity.
|
|
1404
|
-
return this.
|
|
1520
|
+
return this._activeContextTracker.hasContext()
|
|
1521
|
+
? clone(this._activeContextTracker.getUnwrappedContext())
|
|
1522
|
+
: undefined;
|
|
1405
1523
|
}
|
|
1406
1524
|
getInternalContext() {
|
|
1407
|
-
return this.
|
|
1408
|
-
}
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
return { identifyPromise: basePromise, identifyResolve: res, identifyReject: rej };
|
|
1525
|
+
return this._activeContextTracker.getContext();
|
|
1526
|
+
}
|
|
1527
|
+
/**
|
|
1528
|
+
* Preset flags are used to set the flags before the client is initialized. This is useful for
|
|
1529
|
+
* when client has precached flags that are ready to evaluate without full initialization.
|
|
1530
|
+
* @param newFlags - The flags to preset.
|
|
1531
|
+
*/
|
|
1532
|
+
presetFlags(newFlags) {
|
|
1533
|
+
this._flagManager.presetFlags(newFlags);
|
|
1417
1534
|
}
|
|
1418
1535
|
/**
|
|
1419
1536
|
* Identifies a context to LaunchDarkly. See {@link LDClient.identify}.
|
|
@@ -1489,11 +1606,10 @@ class LDClientImpl {
|
|
|
1489
1606
|
this.emitter.emit('error', context, error);
|
|
1490
1607
|
return Promise.reject(error);
|
|
1491
1608
|
}
|
|
1492
|
-
this.
|
|
1493
|
-
this.
|
|
1494
|
-
this.
|
|
1495
|
-
|
|
1496
|
-
this.logger.debug(`Identifying ${JSON.stringify(this._checkedContext)}`);
|
|
1609
|
+
this._activeContextTracker.set(context, checkedContext);
|
|
1610
|
+
this._eventProcessor?.sendEvent(this._eventFactoryDefault.identifyEvent(checkedContext));
|
|
1611
|
+
const { identifyPromise, identifyResolve, identifyReject } = this._activeContextTracker.newIdentificationPromise();
|
|
1612
|
+
this.logger.debug(`Identifying ${JSON.stringify(checkedContext)}`);
|
|
1497
1613
|
await this.dataManager.identify(identifyResolve, identifyReject, checkedContext, identifyOptions);
|
|
1498
1614
|
return identifyPromise;
|
|
1499
1615
|
},
|
|
@@ -1511,12 +1627,18 @@ class LDClientImpl {
|
|
|
1511
1627
|
}, identifyOptions?.sheddable ?? false)
|
|
1512
1628
|
.then((res) => {
|
|
1513
1629
|
if (res.status === 'error') {
|
|
1514
|
-
|
|
1630
|
+
const errorResult = { status: 'error', error: res.error };
|
|
1631
|
+
// Track initialization state for waitForInitialization
|
|
1632
|
+
this.maybeSetInitializationResult({ status: 'failed', error: res.error });
|
|
1633
|
+
return errorResult;
|
|
1515
1634
|
}
|
|
1516
1635
|
if (res.status === 'shed') {
|
|
1517
1636
|
return { status: 'shed' };
|
|
1518
1637
|
}
|
|
1519
|
-
|
|
1638
|
+
const successResult = { status: 'completed' };
|
|
1639
|
+
// Track initialization state for waitForInitialization
|
|
1640
|
+
this.maybeSetInitializationResult({ status: 'complete' });
|
|
1641
|
+
return successResult;
|
|
1520
1642
|
});
|
|
1521
1643
|
if (noTimeout) {
|
|
1522
1644
|
return callSitePromise;
|
|
@@ -1528,6 +1650,74 @@ class LDClientImpl {
|
|
|
1528
1650
|
});
|
|
1529
1651
|
return Promise.race([callSitePromise, timeoutPromise]);
|
|
1530
1652
|
}
|
|
1653
|
+
/**
|
|
1654
|
+
* Sets the initialization result and resolves any pending waitForInitialization promises.
|
|
1655
|
+
* This method is idempotent and will only be set by the initialization flow. Subsequent calls
|
|
1656
|
+
* should not do anything.
|
|
1657
|
+
* @param result The initialization result.
|
|
1658
|
+
*/
|
|
1659
|
+
maybeSetInitializationResult(result) {
|
|
1660
|
+
if (this.initializeResult === undefined) {
|
|
1661
|
+
this.initializeResult = result;
|
|
1662
|
+
this.emitter.emit('ready');
|
|
1663
|
+
if (result.status === 'complete') {
|
|
1664
|
+
this.emitter.emit('initialized');
|
|
1665
|
+
}
|
|
1666
|
+
if (this.initResolve) {
|
|
1667
|
+
this.initResolve(result);
|
|
1668
|
+
this.initResolve = undefined;
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
waitForInitialization(options) {
|
|
1673
|
+
const timeout = options?.timeout ?? 5;
|
|
1674
|
+
// If initialization has already completed (successfully or failed), return the result immediately.
|
|
1675
|
+
if (this.initializeResult) {
|
|
1676
|
+
return Promise.resolve(this.initializeResult);
|
|
1677
|
+
}
|
|
1678
|
+
// If waitForInitialization was previously called, then return the promise with a timeout.
|
|
1679
|
+
// This condition should only be triggered if waitForInitialization was called multiple times.
|
|
1680
|
+
if (this.initializedPromise) {
|
|
1681
|
+
return this.promiseWithTimeout(this.initializedPromise, timeout);
|
|
1682
|
+
}
|
|
1683
|
+
// Create a new promise for tracking initialization
|
|
1684
|
+
if (!this.initializedPromise) {
|
|
1685
|
+
this.initializedPromise = new Promise((resolve) => {
|
|
1686
|
+
this.initResolve = resolve;
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1689
|
+
return this.promiseWithTimeout(this.initializedPromise, timeout);
|
|
1690
|
+
}
|
|
1691
|
+
/**
|
|
1692
|
+
* Apply a timeout promise to a base promise. This is for use with waitForInitialization.
|
|
1693
|
+
*
|
|
1694
|
+
* @param basePromise The promise to race against a timeout.
|
|
1695
|
+
* @param timeout The timeout in seconds.
|
|
1696
|
+
* @returns A promise that resolves to the initialization result or timeout.
|
|
1697
|
+
*
|
|
1698
|
+
* @privateRemarks
|
|
1699
|
+
* This method is protected because it is used by the browser SDK's `start` method.
|
|
1700
|
+
* Eventually, the start method will be moved to this common implementation and this method will
|
|
1701
|
+
* be made private.
|
|
1702
|
+
*/
|
|
1703
|
+
promiseWithTimeout(basePromise, timeout) {
|
|
1704
|
+
const cancelableTimeout = cancelableTimedPromise(timeout, 'waitForInitialization');
|
|
1705
|
+
return Promise.race([
|
|
1706
|
+
basePromise.then((res) => {
|
|
1707
|
+
cancelableTimeout.cancel();
|
|
1708
|
+
return res;
|
|
1709
|
+
}),
|
|
1710
|
+
cancelableTimeout.promise
|
|
1711
|
+
// If the promise resolves without error, then the initialization completed successfully.
|
|
1712
|
+
// NOTE: this should never return as the resolution would only be triggered by the basePromise
|
|
1713
|
+
// being resolved.
|
|
1714
|
+
.then(() => ({ status: 'complete' }))
|
|
1715
|
+
.catch(() => ({ status: 'timeout' })),
|
|
1716
|
+
]).catch((reason) => {
|
|
1717
|
+
this.logger?.error(reason.message);
|
|
1718
|
+
return { status: 'failed', error: reason };
|
|
1719
|
+
});
|
|
1720
|
+
}
|
|
1531
1721
|
on(eventName, listener) {
|
|
1532
1722
|
this.emitter.on(eventName, listener);
|
|
1533
1723
|
}
|
|
@@ -1535,7 +1725,7 @@ class LDClientImpl {
|
|
|
1535
1725
|
this.emitter.off(eventName, listener);
|
|
1536
1726
|
}
|
|
1537
1727
|
track(key, data, metricValue) {
|
|
1538
|
-
if (!this.
|
|
1728
|
+
if (!this._activeContextTracker.hasValidContext()) {
|
|
1539
1729
|
this.logger.warn(ClientMessages.MissingContextKeyNoEvent);
|
|
1540
1730
|
return;
|
|
1541
1731
|
}
|
|
@@ -1543,37 +1733,46 @@ class LDClientImpl {
|
|
|
1543
1733
|
if (metricValue !== undefined && !TypeValidators.Number.is(metricValue)) {
|
|
1544
1734
|
this.logger?.warn(ClientMessages.invalidMetricValue(typeof metricValue));
|
|
1545
1735
|
}
|
|
1546
|
-
this._eventProcessor?.sendEvent(this._config.trackEventModifier(this._eventFactoryDefault.customEvent(key, this.
|
|
1736
|
+
this._eventProcessor?.sendEvent(this._config.trackEventModifier(this._eventFactoryDefault.customEvent(key, this._activeContextTracker.getContext(), data, metricValue)));
|
|
1547
1737
|
this._hookRunner.afterTrack({
|
|
1548
1738
|
key,
|
|
1549
1739
|
// The context is pre-checked above, so we know it can be unwrapped.
|
|
1550
|
-
context: this.
|
|
1740
|
+
context: this._activeContextTracker.getUnwrappedContext(),
|
|
1551
1741
|
data,
|
|
1552
1742
|
metricValue,
|
|
1553
1743
|
});
|
|
1554
1744
|
}
|
|
1555
1745
|
_variationInternal(flagKey, defaultValue, eventFactory, typeChecker) {
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1746
|
+
// We are letting evaulations happen without a context. The main case for this
|
|
1747
|
+
// is when cached data is loaded, but the client is not fully initialized. In this
|
|
1748
|
+
// case, we will write out a warning for each evaluation attempt.
|
|
1749
|
+
// NOTE: we will be changing this behavior soon once we have a tracker on the
|
|
1750
|
+
// client initialization state.
|
|
1751
|
+
const hasContext = this._activeContextTracker.hasContext();
|
|
1752
|
+
if (!hasContext) {
|
|
1753
|
+
this.logger?.warn('Flag evaluation called before client is fully initialized, data from this evaulation could be stale.');
|
|
1754
|
+
}
|
|
1755
|
+
const evalContext = this._activeContextTracker.getContext();
|
|
1561
1756
|
const foundItem = this._flagManager.get(flagKey);
|
|
1562
1757
|
if (foundItem === undefined || foundItem.flag.deleted) {
|
|
1563
1758
|
const defVal = defaultValue ?? null;
|
|
1564
1759
|
const error = new LDClientError(`Unknown feature flag "${flagKey}"; returning default value ${defVal}.`);
|
|
1565
|
-
this.emitter.emit('error', this.
|
|
1566
|
-
|
|
1760
|
+
this.emitter.emit('error', this._activeContextTracker.getUnwrappedContext(), error);
|
|
1761
|
+
if (hasContext) {
|
|
1762
|
+
this._eventProcessor?.sendEvent(this._eventFactoryDefault.unknownFlagEvent(flagKey, defVal, evalContext));
|
|
1763
|
+
}
|
|
1567
1764
|
return createErrorEvaluationDetail(ErrorKinds.FlagNotFound, defaultValue);
|
|
1568
1765
|
}
|
|
1569
1766
|
const { reason, value, variation, prerequisites } = foundItem.flag;
|
|
1570
1767
|
if (typeChecker) {
|
|
1571
1768
|
const [matched, type] = typeChecker(value);
|
|
1572
1769
|
if (!matched) {
|
|
1573
|
-
|
|
1574
|
-
|
|
1770
|
+
if (hasContext) {
|
|
1771
|
+
this._eventProcessor?.sendEvent(eventFactory.evalEventClient(flagKey, defaultValue, // track default value on type errors
|
|
1772
|
+
defaultValue, foundItem.flag, evalContext, reason));
|
|
1773
|
+
}
|
|
1575
1774
|
const error = new LDClientError(`Wrong type "${type}" for feature flag "${flagKey}"; returning default value`);
|
|
1576
|
-
this.emitter.emit('error', this.
|
|
1775
|
+
this.emitter.emit('error', this._activeContextTracker.getUnwrappedContext(), error);
|
|
1577
1776
|
return createErrorEvaluationDetail(ErrorKinds.WrongType, defaultValue);
|
|
1578
1777
|
}
|
|
1579
1778
|
}
|
|
@@ -1585,18 +1784,20 @@ class LDClientImpl {
|
|
|
1585
1784
|
prerequisites?.forEach((prereqKey) => {
|
|
1586
1785
|
this._variationInternal(prereqKey, undefined, this._eventFactoryDefault);
|
|
1587
1786
|
});
|
|
1588
|
-
|
|
1787
|
+
if (hasContext) {
|
|
1788
|
+
this._eventProcessor?.sendEvent(eventFactory.evalEventClient(flagKey, value, defaultValue, foundItem.flag, evalContext, reason));
|
|
1789
|
+
}
|
|
1589
1790
|
return successDetail;
|
|
1590
1791
|
}
|
|
1591
1792
|
variation(flagKey, defaultValue) {
|
|
1592
|
-
const { value } = this._hookRunner.withEvaluation(flagKey, this.
|
|
1793
|
+
const { value } = this._hookRunner.withEvaluation(flagKey, this._activeContextTracker.getUnwrappedContext(), defaultValue, () => this._variationInternal(flagKey, defaultValue, this._eventFactoryDefault));
|
|
1593
1794
|
return value;
|
|
1594
1795
|
}
|
|
1595
1796
|
variationDetail(flagKey, defaultValue) {
|
|
1596
|
-
return this._hookRunner.withEvaluation(flagKey, this.
|
|
1797
|
+
return this._hookRunner.withEvaluation(flagKey, this._activeContextTracker.getUnwrappedContext(), defaultValue, () => this._variationInternal(flagKey, defaultValue, this._eventFactoryWithReasons));
|
|
1597
1798
|
}
|
|
1598
1799
|
_typedEval(key, defaultValue, eventFactory, typeChecker) {
|
|
1599
|
-
return this._hookRunner.withEvaluation(key, this.
|
|
1800
|
+
return this._hookRunner.withEvaluation(key, this._activeContextTracker.getUnwrappedContext(), defaultValue, () => this._variationInternal(key, defaultValue, eventFactory, typeChecker));
|
|
1600
1801
|
}
|
|
1601
1802
|
boolVariation(key, defaultValue) {
|
|
1602
1803
|
return this._typedEval(key, defaultValue, this._eventFactoryDefault, (value) => [
|
|
@@ -1678,6 +1879,9 @@ class LDClientImpl {
|
|
|
1678
1879
|
sendEvent(event) {
|
|
1679
1880
|
this._eventProcessor?.sendEvent(event);
|
|
1680
1881
|
}
|
|
1882
|
+
getDebugOverrides() {
|
|
1883
|
+
return this._flagManager.getDebugOverride?.();
|
|
1884
|
+
}
|
|
1681
1885
|
_handleInspectionChanged(flagKeys, type) {
|
|
1682
1886
|
if (!this._inspectorManager.hasInspectors()) {
|
|
1683
1887
|
return;
|
|
@@ -1699,6 +1903,9 @@ class LDClientImpl {
|
|
|
1699
1903
|
};
|
|
1700
1904
|
}
|
|
1701
1905
|
});
|
|
1906
|
+
// NOTE: we are not tracking "override" changes because, at the time of writing,
|
|
1907
|
+
// these changes are only used for debugging purposes and are not persisted. This
|
|
1908
|
+
// may change in the future.
|
|
1702
1909
|
if (type === 'init') {
|
|
1703
1910
|
this._inspectorManager.onFlagsChanged(details);
|
|
1704
1911
|
}
|
|
@@ -1710,6 +1917,24 @@ class LDClientImpl {
|
|
|
1710
1917
|
}
|
|
1711
1918
|
}
|
|
1712
1919
|
|
|
1920
|
+
/**
|
|
1921
|
+
* Safe register debug override plugins.
|
|
1922
|
+
*
|
|
1923
|
+
* @param logger The logger to use for logging errors.
|
|
1924
|
+
* @param debugOverride The debug override to register.
|
|
1925
|
+
* @param plugins The plugins to register.
|
|
1926
|
+
*/
|
|
1927
|
+
function safeRegisterDebugOverridePlugins(logger, debugOverride, plugins) {
|
|
1928
|
+
plugins.forEach((plugin) => {
|
|
1929
|
+
try {
|
|
1930
|
+
plugin.registerDebug?.(debugOverride);
|
|
1931
|
+
}
|
|
1932
|
+
catch (error) {
|
|
1933
|
+
logger.error(`Exception thrown registering plugin ${internal.safeGetName(logger, plugin)}.`);
|
|
1934
|
+
}
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1713
1938
|
class DataSourceEventHandler {
|
|
1714
1939
|
constructor(_flagManager, _statusManager, _logger) {
|
|
1715
1940
|
this._flagManager = _flagManager;
|
|
@@ -2185,5 +2410,5 @@ class BaseDataManager {
|
|
|
2185
2410
|
}
|
|
2186
2411
|
}
|
|
2187
2412
|
|
|
2188
|
-
export { BaseDataManager, DataSourceState, LDClientImpl, Requestor, makeRequestor };
|
|
2413
|
+
export { BaseDataManager, DataSourceState, LDClientImpl, Requestor, makeRequestor, safeRegisterDebugOverridePlugins };
|
|
2189
2414
|
//# sourceMappingURL=index.mjs.map
|