@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/cjs/index.cjs
CHANGED
|
@@ -260,6 +260,7 @@ const validators = {
|
|
|
260
260
|
payloadFilterKey: jsSdkCommon.TypeValidators.stringMatchingRegex(/^[a-zA-Z0-9](\w|\.|-)*$/),
|
|
261
261
|
hooks: jsSdkCommon.TypeValidators.createTypeArray('Hook[]', {}),
|
|
262
262
|
inspectors: jsSdkCommon.TypeValidators.createTypeArray('LDInspection', {}),
|
|
263
|
+
cleanOldPersistentData: jsSdkCommon.TypeValidators.Boolean,
|
|
263
264
|
};
|
|
264
265
|
|
|
265
266
|
const DEFAULT_POLLING_INTERVAL = 60 * 5;
|
|
@@ -538,6 +539,38 @@ const addAutoEnv = async (context, platform, config) => {
|
|
|
538
539
|
return context;
|
|
539
540
|
};
|
|
540
541
|
|
|
542
|
+
function createActiveContextTracker() {
|
|
543
|
+
let unwrappedContext;
|
|
544
|
+
let context;
|
|
545
|
+
return {
|
|
546
|
+
set(_unwrappedContext, _context) {
|
|
547
|
+
unwrappedContext = _unwrappedContext;
|
|
548
|
+
context = _context;
|
|
549
|
+
},
|
|
550
|
+
getContext() {
|
|
551
|
+
return context;
|
|
552
|
+
},
|
|
553
|
+
getUnwrappedContext() {
|
|
554
|
+
return unwrappedContext;
|
|
555
|
+
},
|
|
556
|
+
newIdentificationPromise() {
|
|
557
|
+
let res;
|
|
558
|
+
let rej;
|
|
559
|
+
const basePromise = new Promise((resolve, reject) => {
|
|
560
|
+
res = resolve;
|
|
561
|
+
rej = reject;
|
|
562
|
+
});
|
|
563
|
+
return { identifyPromise: basePromise, identifyResolve: res, identifyReject: rej };
|
|
564
|
+
},
|
|
565
|
+
hasContext() {
|
|
566
|
+
return context !== undefined;
|
|
567
|
+
},
|
|
568
|
+
hasValidContext() {
|
|
569
|
+
return this.hasContext() && context.valid;
|
|
570
|
+
},
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
|
|
541
574
|
const { isLegacyUser, isMultiKind, isSingleKind } = jsSdkCommon.internal;
|
|
542
575
|
/**
|
|
543
576
|
* This is the root ensureKey function. All other ensureKey functions reduce to this.
|
|
@@ -831,30 +864,30 @@ class FlagPersistence {
|
|
|
831
864
|
}
|
|
832
865
|
|
|
833
866
|
/**
|
|
834
|
-
*
|
|
867
|
+
* Creates the default implementation of the flag store.
|
|
835
868
|
*/
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
},
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
}
|
|
869
|
+
function createDefaultFlagStore() {
|
|
870
|
+
let flags = {};
|
|
871
|
+
return {
|
|
872
|
+
init(newFlags) {
|
|
873
|
+
flags = Object.entries(newFlags).reduce((acc, [key, flag]) => {
|
|
874
|
+
acc[key] = flag;
|
|
875
|
+
return acc;
|
|
876
|
+
}, {});
|
|
877
|
+
},
|
|
878
|
+
insertOrUpdate(key, update) {
|
|
879
|
+
flags[key] = update;
|
|
880
|
+
},
|
|
881
|
+
get(key) {
|
|
882
|
+
if (Object.prototype.hasOwnProperty.call(flags, key)) {
|
|
883
|
+
return flags[key];
|
|
884
|
+
}
|
|
885
|
+
return undefined;
|
|
886
|
+
},
|
|
887
|
+
getAll() {
|
|
888
|
+
return flags;
|
|
889
|
+
},
|
|
890
|
+
};
|
|
858
891
|
}
|
|
859
892
|
|
|
860
893
|
function calculateChangedKeys(existingObject, newObject) {
|
|
@@ -875,69 +908,66 @@ function calculateChangedKeys(existingObject, newObject) {
|
|
|
875
908
|
return changedKeys;
|
|
876
909
|
}
|
|
877
910
|
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
if (changed.length > 0) {
|
|
895
|
-
this._changeCallbacks.forEach((callback) => {
|
|
896
|
-
try {
|
|
897
|
-
callback(context, changed, 'init');
|
|
898
|
-
}
|
|
899
|
-
catch (err) {
|
|
900
|
-
/* intentionally empty */
|
|
901
|
-
}
|
|
902
|
-
});
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
initCached(context, newFlags) {
|
|
906
|
-
if (this._activeContextKey === context.canonicalKey) {
|
|
907
|
-
return;
|
|
908
|
-
}
|
|
909
|
-
this.init(context, newFlags);
|
|
910
|
-
}
|
|
911
|
-
upsert(context, key, item) {
|
|
912
|
-
if (this._activeContextKey !== context.canonicalKey) {
|
|
913
|
-
this._logger.warn('Received an update for an inactive context.');
|
|
914
|
-
return false;
|
|
915
|
-
}
|
|
916
|
-
const currentValue = this._flagStore.get(key);
|
|
917
|
-
if (currentValue !== undefined && currentValue.version >= item.version) {
|
|
918
|
-
// this is an out of order update that can be ignored
|
|
919
|
-
return false;
|
|
920
|
-
}
|
|
921
|
-
this._flagStore.insertOrUpdate(key, item);
|
|
922
|
-
this._changeCallbacks.forEach((callback) => {
|
|
923
|
-
try {
|
|
924
|
-
callback(context, [key], 'patch');
|
|
911
|
+
function createFlagUpdater(_flagStore, _logger) {
|
|
912
|
+
const flagStore = _flagStore;
|
|
913
|
+
const logger = _logger;
|
|
914
|
+
let activeContext;
|
|
915
|
+
const changeCallbacks = new Array();
|
|
916
|
+
return {
|
|
917
|
+
handleFlagChanges(keys, type) {
|
|
918
|
+
if (activeContext) {
|
|
919
|
+
changeCallbacks.forEach((callback) => {
|
|
920
|
+
try {
|
|
921
|
+
callback(activeContext, keys, type);
|
|
922
|
+
}
|
|
923
|
+
catch (err) {
|
|
924
|
+
/* intentionally empty */
|
|
925
|
+
}
|
|
926
|
+
});
|
|
925
927
|
}
|
|
926
|
-
|
|
927
|
-
|
|
928
|
+
else {
|
|
929
|
+
logger.warn('Received a change event without an active context. Changes will not be propagated.');
|
|
928
930
|
}
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
931
|
+
},
|
|
932
|
+
init(context, newFlags) {
|
|
933
|
+
activeContext = context;
|
|
934
|
+
const oldFlags = flagStore.getAll();
|
|
935
|
+
flagStore.init(newFlags);
|
|
936
|
+
const changed = calculateChangedKeys(oldFlags, newFlags);
|
|
937
|
+
if (changed.length > 0) {
|
|
938
|
+
this.handleFlagChanges(changed, 'init');
|
|
939
|
+
}
|
|
940
|
+
},
|
|
941
|
+
initCached(context, newFlags) {
|
|
942
|
+
if (activeContext?.canonicalKey === context.canonicalKey) {
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
this.init(context, newFlags);
|
|
946
|
+
},
|
|
947
|
+
upsert(context, key, item) {
|
|
948
|
+
if (activeContext?.canonicalKey !== context.canonicalKey) {
|
|
949
|
+
logger.warn('Received an update for an inactive context.');
|
|
950
|
+
return false;
|
|
951
|
+
}
|
|
952
|
+
const currentValue = flagStore.get(key);
|
|
953
|
+
if (currentValue !== undefined && currentValue.version >= item.version) {
|
|
954
|
+
// this is an out of order update that can be ignored
|
|
955
|
+
return false;
|
|
956
|
+
}
|
|
957
|
+
flagStore.insertOrUpdate(key, item);
|
|
958
|
+
this.handleFlagChanges([key], 'patch');
|
|
959
|
+
return true;
|
|
960
|
+
},
|
|
961
|
+
on(callback) {
|
|
962
|
+
changeCallbacks.push(callback);
|
|
963
|
+
},
|
|
964
|
+
off(callback) {
|
|
965
|
+
const index = changeCallbacks.indexOf(callback);
|
|
966
|
+
if (index > -1) {
|
|
967
|
+
changeCallbacks.splice(index, 1);
|
|
968
|
+
}
|
|
969
|
+
},
|
|
970
|
+
};
|
|
941
971
|
}
|
|
942
972
|
|
|
943
973
|
class DefaultFlagManager {
|
|
@@ -949,8 +979,8 @@ class DefaultFlagManager {
|
|
|
949
979
|
* @param timeStamper exists for testing purposes
|
|
950
980
|
*/
|
|
951
981
|
constructor(platform, sdkKey, maxCachedContexts, logger, timeStamper = () => Date.now()) {
|
|
952
|
-
this._flagStore =
|
|
953
|
-
this._flagUpdater =
|
|
982
|
+
this._flagStore = createDefaultFlagStore();
|
|
983
|
+
this._flagUpdater = createFlagUpdater(this._flagStore, logger);
|
|
954
984
|
this._flagPersistencePromise = this._initPersistence(platform, sdkKey, maxCachedContexts, logger, timeStamper);
|
|
955
985
|
}
|
|
956
986
|
async _initPersistence(platform, sdkKey, maxCachedContexts, logger, timeStamper = () => Date.now()) {
|
|
@@ -958,11 +988,26 @@ class DefaultFlagManager {
|
|
|
958
988
|
return new FlagPersistence(platform, environmentNamespace, maxCachedContexts, this._flagStore, this._flagUpdater, logger, timeStamper);
|
|
959
989
|
}
|
|
960
990
|
get(key) {
|
|
991
|
+
if (this._overrides && Object.prototype.hasOwnProperty.call(this._overrides, key)) {
|
|
992
|
+
return this._convertValueToOverrideDescripter(this._overrides[key]);
|
|
993
|
+
}
|
|
961
994
|
return this._flagStore.get(key);
|
|
962
995
|
}
|
|
963
996
|
getAll() {
|
|
997
|
+
if (this._overrides) {
|
|
998
|
+
return {
|
|
999
|
+
...this._flagStore.getAll(),
|
|
1000
|
+
...Object.entries(this._overrides).reduce((acc, [key, value]) => {
|
|
1001
|
+
acc[key] = this._convertValueToOverrideDescripter(value);
|
|
1002
|
+
return acc;
|
|
1003
|
+
}, {}),
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
964
1006
|
return this._flagStore.getAll();
|
|
965
1007
|
}
|
|
1008
|
+
presetFlags(newFlags) {
|
|
1009
|
+
this._flagStore.init(newFlags);
|
|
1010
|
+
}
|
|
966
1011
|
setBootstrap(context, newFlags) {
|
|
967
1012
|
// Bypasses the persistence as we do not want to put these flags into any cache.
|
|
968
1013
|
// Generally speaking persistence likely *SHOULD* be disabled when using bootstrap.
|
|
@@ -983,6 +1028,58 @@ class DefaultFlagManager {
|
|
|
983
1028
|
off(callback) {
|
|
984
1029
|
this._flagUpdater.off(callback);
|
|
985
1030
|
}
|
|
1031
|
+
_convertValueToOverrideDescripter(value) {
|
|
1032
|
+
return {
|
|
1033
|
+
flag: {
|
|
1034
|
+
value,
|
|
1035
|
+
version: 0,
|
|
1036
|
+
},
|
|
1037
|
+
version: 0,
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
setOverride(key, value) {
|
|
1041
|
+
if (!this._overrides) {
|
|
1042
|
+
this._overrides = {};
|
|
1043
|
+
}
|
|
1044
|
+
this._overrides[key] = value;
|
|
1045
|
+
this._flagUpdater.handleFlagChanges([key], 'override');
|
|
1046
|
+
}
|
|
1047
|
+
removeOverride(flagKey) {
|
|
1048
|
+
if (!this._overrides || !Object.prototype.hasOwnProperty.call(this._overrides, flagKey)) {
|
|
1049
|
+
return; // No override to remove
|
|
1050
|
+
}
|
|
1051
|
+
delete this._overrides[flagKey];
|
|
1052
|
+
// If no more overrides, reset to undefined for performance
|
|
1053
|
+
if (Object.keys(this._overrides).length === 0) {
|
|
1054
|
+
this._overrides = undefined;
|
|
1055
|
+
}
|
|
1056
|
+
this._flagUpdater.handleFlagChanges([flagKey], 'override');
|
|
1057
|
+
}
|
|
1058
|
+
clearAllOverrides() {
|
|
1059
|
+
if (this._overrides) {
|
|
1060
|
+
const clearedOverrides = { ...this._overrides };
|
|
1061
|
+
this._overrides = undefined; // Reset to undefined
|
|
1062
|
+
this._flagUpdater.handleFlagChanges(Object.keys(clearedOverrides), 'override');
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
getAllOverrides() {
|
|
1066
|
+
if (!this._overrides) {
|
|
1067
|
+
return {};
|
|
1068
|
+
}
|
|
1069
|
+
const result = {};
|
|
1070
|
+
Object.entries(this._overrides).forEach(([key, value]) => {
|
|
1071
|
+
result[key] = this._convertValueToOverrideDescripter(value);
|
|
1072
|
+
});
|
|
1073
|
+
return result;
|
|
1074
|
+
}
|
|
1075
|
+
getDebugOverride() {
|
|
1076
|
+
return {
|
|
1077
|
+
setOverride: this.setOverride.bind(this),
|
|
1078
|
+
removeOverride: this.removeOverride.bind(this),
|
|
1079
|
+
clearAllOverrides: this.clearAllOverrides.bind(this),
|
|
1080
|
+
getAllOverrides: this.getAllOverrides.bind(this),
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
986
1083
|
}
|
|
987
1084
|
|
|
988
1085
|
const UNKNOWN_HOOK_NAME = 'unknown hook';
|
|
@@ -1345,6 +1442,7 @@ class LDClientImpl {
|
|
|
1345
1442
|
this.sdkKey = sdkKey;
|
|
1346
1443
|
this.autoEnvAttributes = autoEnvAttributes;
|
|
1347
1444
|
this.platform = platform;
|
|
1445
|
+
this._activeContextTracker = createActiveContextTracker();
|
|
1348
1446
|
this._highTimeoutThreshold = 15;
|
|
1349
1447
|
this._eventFactoryDefault = new EventFactory(false);
|
|
1350
1448
|
this._eventFactoryWithReasons = new EventFactory(true);
|
|
@@ -1385,6 +1483,24 @@ class LDClientImpl {
|
|
|
1385
1483
|
if (this._inspectorManager.hasInspectors()) {
|
|
1386
1484
|
this._hookRunner.addHook(getInspectorHook(this._inspectorManager));
|
|
1387
1485
|
}
|
|
1486
|
+
if (options.cleanOldPersistentData &&
|
|
1487
|
+
internalOptions?.getLegacyStorageKeys &&
|
|
1488
|
+
this.platform.storage) {
|
|
1489
|
+
// NOTE: we are letting this fail silently because it's not critical and we don't want to block the client from initializing.
|
|
1490
|
+
try {
|
|
1491
|
+
this.logger.debug('Cleaning old persistent data.');
|
|
1492
|
+
Promise.all(internalOptions.getLegacyStorageKeys().map((key) => this.platform.storage?.clear(key)))
|
|
1493
|
+
.catch((error) => {
|
|
1494
|
+
this.logger.error(`Error cleaning old persistent data: ${error}`);
|
|
1495
|
+
})
|
|
1496
|
+
.finally(() => {
|
|
1497
|
+
this.logger.debug('Cleaned old persistent data.');
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
catch (error) {
|
|
1501
|
+
this.logger.error(`Error cleaning old persistent data: ${error}`);
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1388
1504
|
}
|
|
1389
1505
|
allFlags() {
|
|
1390
1506
|
// extracting all flag values
|
|
@@ -1419,19 +1535,20 @@ class LDClientImpl {
|
|
|
1419
1535
|
// code. We are returned the unchecked context so that if a consumer identifies with an invalid context
|
|
1420
1536
|
// and then calls getContext, they get back the same context they provided, without any assertion about
|
|
1421
1537
|
// validity.
|
|
1422
|
-
return this.
|
|
1538
|
+
return this._activeContextTracker.hasContext()
|
|
1539
|
+
? jsSdkCommon.clone(this._activeContextTracker.getUnwrappedContext())
|
|
1540
|
+
: undefined;
|
|
1423
1541
|
}
|
|
1424
1542
|
getInternalContext() {
|
|
1425
|
-
return this.
|
|
1426
|
-
}
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
return { identifyPromise: basePromise, identifyResolve: res, identifyReject: rej };
|
|
1543
|
+
return this._activeContextTracker.getContext();
|
|
1544
|
+
}
|
|
1545
|
+
/**
|
|
1546
|
+
* Preset flags are used to set the flags before the client is initialized. This is useful for
|
|
1547
|
+
* when client has precached flags that are ready to evaluate without full initialization.
|
|
1548
|
+
* @param newFlags - The flags to preset.
|
|
1549
|
+
*/
|
|
1550
|
+
presetFlags(newFlags) {
|
|
1551
|
+
this._flagManager.presetFlags(newFlags);
|
|
1435
1552
|
}
|
|
1436
1553
|
/**
|
|
1437
1554
|
* Identifies a context to LaunchDarkly. See {@link LDClient.identify}.
|
|
@@ -1507,11 +1624,10 @@ class LDClientImpl {
|
|
|
1507
1624
|
this.emitter.emit('error', context, error);
|
|
1508
1625
|
return Promise.reject(error);
|
|
1509
1626
|
}
|
|
1510
|
-
this.
|
|
1511
|
-
this.
|
|
1512
|
-
this.
|
|
1513
|
-
|
|
1514
|
-
this.logger.debug(`Identifying ${JSON.stringify(this._checkedContext)}`);
|
|
1627
|
+
this._activeContextTracker.set(context, checkedContext);
|
|
1628
|
+
this._eventProcessor?.sendEvent(this._eventFactoryDefault.identifyEvent(checkedContext));
|
|
1629
|
+
const { identifyPromise, identifyResolve, identifyReject } = this._activeContextTracker.newIdentificationPromise();
|
|
1630
|
+
this.logger.debug(`Identifying ${JSON.stringify(checkedContext)}`);
|
|
1515
1631
|
await this.dataManager.identify(identifyResolve, identifyReject, checkedContext, identifyOptions);
|
|
1516
1632
|
return identifyPromise;
|
|
1517
1633
|
},
|
|
@@ -1529,12 +1645,18 @@ class LDClientImpl {
|
|
|
1529
1645
|
}, identifyOptions?.sheddable ?? false)
|
|
1530
1646
|
.then((res) => {
|
|
1531
1647
|
if (res.status === 'error') {
|
|
1532
|
-
|
|
1648
|
+
const errorResult = { status: 'error', error: res.error };
|
|
1649
|
+
// Track initialization state for waitForInitialization
|
|
1650
|
+
this.maybeSetInitializationResult({ status: 'failed', error: res.error });
|
|
1651
|
+
return errorResult;
|
|
1533
1652
|
}
|
|
1534
1653
|
if (res.status === 'shed') {
|
|
1535
1654
|
return { status: 'shed' };
|
|
1536
1655
|
}
|
|
1537
|
-
|
|
1656
|
+
const successResult = { status: 'completed' };
|
|
1657
|
+
// Track initialization state for waitForInitialization
|
|
1658
|
+
this.maybeSetInitializationResult({ status: 'complete' });
|
|
1659
|
+
return successResult;
|
|
1538
1660
|
});
|
|
1539
1661
|
if (noTimeout) {
|
|
1540
1662
|
return callSitePromise;
|
|
@@ -1546,6 +1668,74 @@ class LDClientImpl {
|
|
|
1546
1668
|
});
|
|
1547
1669
|
return Promise.race([callSitePromise, timeoutPromise]);
|
|
1548
1670
|
}
|
|
1671
|
+
/**
|
|
1672
|
+
* Sets the initialization result and resolves any pending waitForInitialization promises.
|
|
1673
|
+
* This method is idempotent and will only be set by the initialization flow. Subsequent calls
|
|
1674
|
+
* should not do anything.
|
|
1675
|
+
* @param result The initialization result.
|
|
1676
|
+
*/
|
|
1677
|
+
maybeSetInitializationResult(result) {
|
|
1678
|
+
if (this.initializeResult === undefined) {
|
|
1679
|
+
this.initializeResult = result;
|
|
1680
|
+
this.emitter.emit('ready');
|
|
1681
|
+
if (result.status === 'complete') {
|
|
1682
|
+
this.emitter.emit('initialized');
|
|
1683
|
+
}
|
|
1684
|
+
if (this.initResolve) {
|
|
1685
|
+
this.initResolve(result);
|
|
1686
|
+
this.initResolve = undefined;
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
waitForInitialization(options) {
|
|
1691
|
+
const timeout = options?.timeout ?? 5;
|
|
1692
|
+
// If initialization has already completed (successfully or failed), return the result immediately.
|
|
1693
|
+
if (this.initializeResult) {
|
|
1694
|
+
return Promise.resolve(this.initializeResult);
|
|
1695
|
+
}
|
|
1696
|
+
// If waitForInitialization was previously called, then return the promise with a timeout.
|
|
1697
|
+
// This condition should only be triggered if waitForInitialization was called multiple times.
|
|
1698
|
+
if (this.initializedPromise) {
|
|
1699
|
+
return this.promiseWithTimeout(this.initializedPromise, timeout);
|
|
1700
|
+
}
|
|
1701
|
+
// Create a new promise for tracking initialization
|
|
1702
|
+
if (!this.initializedPromise) {
|
|
1703
|
+
this.initializedPromise = new Promise((resolve) => {
|
|
1704
|
+
this.initResolve = resolve;
|
|
1705
|
+
});
|
|
1706
|
+
}
|
|
1707
|
+
return this.promiseWithTimeout(this.initializedPromise, timeout);
|
|
1708
|
+
}
|
|
1709
|
+
/**
|
|
1710
|
+
* Apply a timeout promise to a base promise. This is for use with waitForInitialization.
|
|
1711
|
+
*
|
|
1712
|
+
* @param basePromise The promise to race against a timeout.
|
|
1713
|
+
* @param timeout The timeout in seconds.
|
|
1714
|
+
* @returns A promise that resolves to the initialization result or timeout.
|
|
1715
|
+
*
|
|
1716
|
+
* @privateRemarks
|
|
1717
|
+
* This method is protected because it is used by the browser SDK's `start` method.
|
|
1718
|
+
* Eventually, the start method will be moved to this common implementation and this method will
|
|
1719
|
+
* be made private.
|
|
1720
|
+
*/
|
|
1721
|
+
promiseWithTimeout(basePromise, timeout) {
|
|
1722
|
+
const cancelableTimeout = jsSdkCommon.cancelableTimedPromise(timeout, 'waitForInitialization');
|
|
1723
|
+
return Promise.race([
|
|
1724
|
+
basePromise.then((res) => {
|
|
1725
|
+
cancelableTimeout.cancel();
|
|
1726
|
+
return res;
|
|
1727
|
+
}),
|
|
1728
|
+
cancelableTimeout.promise
|
|
1729
|
+
// If the promise resolves without error, then the initialization completed successfully.
|
|
1730
|
+
// NOTE: this should never return as the resolution would only be triggered by the basePromise
|
|
1731
|
+
// being resolved.
|
|
1732
|
+
.then(() => ({ status: 'complete' }))
|
|
1733
|
+
.catch(() => ({ status: 'timeout' })),
|
|
1734
|
+
]).catch((reason) => {
|
|
1735
|
+
this.logger?.error(reason.message);
|
|
1736
|
+
return { status: 'failed', error: reason };
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1549
1739
|
on(eventName, listener) {
|
|
1550
1740
|
this.emitter.on(eventName, listener);
|
|
1551
1741
|
}
|
|
@@ -1553,7 +1743,7 @@ class LDClientImpl {
|
|
|
1553
1743
|
this.emitter.off(eventName, listener);
|
|
1554
1744
|
}
|
|
1555
1745
|
track(key, data, metricValue) {
|
|
1556
|
-
if (!this.
|
|
1746
|
+
if (!this._activeContextTracker.hasValidContext()) {
|
|
1557
1747
|
this.logger.warn(ClientMessages.MissingContextKeyNoEvent);
|
|
1558
1748
|
return;
|
|
1559
1749
|
}
|
|
@@ -1561,37 +1751,46 @@ class LDClientImpl {
|
|
|
1561
1751
|
if (metricValue !== undefined && !jsSdkCommon.TypeValidators.Number.is(metricValue)) {
|
|
1562
1752
|
this.logger?.warn(ClientMessages.invalidMetricValue(typeof metricValue));
|
|
1563
1753
|
}
|
|
1564
|
-
this._eventProcessor?.sendEvent(this._config.trackEventModifier(this._eventFactoryDefault.customEvent(key, this.
|
|
1754
|
+
this._eventProcessor?.sendEvent(this._config.trackEventModifier(this._eventFactoryDefault.customEvent(key, this._activeContextTracker.getContext(), data, metricValue)));
|
|
1565
1755
|
this._hookRunner.afterTrack({
|
|
1566
1756
|
key,
|
|
1567
1757
|
// The context is pre-checked above, so we know it can be unwrapped.
|
|
1568
|
-
context: this.
|
|
1758
|
+
context: this._activeContextTracker.getUnwrappedContext(),
|
|
1569
1759
|
data,
|
|
1570
1760
|
metricValue,
|
|
1571
1761
|
});
|
|
1572
1762
|
}
|
|
1573
1763
|
_variationInternal(flagKey, defaultValue, eventFactory, typeChecker) {
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1764
|
+
// We are letting evaulations happen without a context. The main case for this
|
|
1765
|
+
// is when cached data is loaded, but the client is not fully initialized. In this
|
|
1766
|
+
// case, we will write out a warning for each evaluation attempt.
|
|
1767
|
+
// NOTE: we will be changing this behavior soon once we have a tracker on the
|
|
1768
|
+
// client initialization state.
|
|
1769
|
+
const hasContext = this._activeContextTracker.hasContext();
|
|
1770
|
+
if (!hasContext) {
|
|
1771
|
+
this.logger?.warn('Flag evaluation called before client is fully initialized, data from this evaulation could be stale.');
|
|
1772
|
+
}
|
|
1773
|
+
const evalContext = this._activeContextTracker.getContext();
|
|
1579
1774
|
const foundItem = this._flagManager.get(flagKey);
|
|
1580
1775
|
if (foundItem === undefined || foundItem.flag.deleted) {
|
|
1581
1776
|
const defVal = defaultValue ?? null;
|
|
1582
1777
|
const error = new jsSdkCommon.LDClientError(`Unknown feature flag "${flagKey}"; returning default value ${defVal}.`);
|
|
1583
|
-
this.emitter.emit('error', this.
|
|
1584
|
-
|
|
1778
|
+
this.emitter.emit('error', this._activeContextTracker.getUnwrappedContext(), error);
|
|
1779
|
+
if (hasContext) {
|
|
1780
|
+
this._eventProcessor?.sendEvent(this._eventFactoryDefault.unknownFlagEvent(flagKey, defVal, evalContext));
|
|
1781
|
+
}
|
|
1585
1782
|
return createErrorEvaluationDetail(ErrorKinds.FlagNotFound, defaultValue);
|
|
1586
1783
|
}
|
|
1587
1784
|
const { reason, value, variation, prerequisites } = foundItem.flag;
|
|
1588
1785
|
if (typeChecker) {
|
|
1589
1786
|
const [matched, type] = typeChecker(value);
|
|
1590
1787
|
if (!matched) {
|
|
1591
|
-
|
|
1592
|
-
|
|
1788
|
+
if (hasContext) {
|
|
1789
|
+
this._eventProcessor?.sendEvent(eventFactory.evalEventClient(flagKey, defaultValue, // track default value on type errors
|
|
1790
|
+
defaultValue, foundItem.flag, evalContext, reason));
|
|
1791
|
+
}
|
|
1593
1792
|
const error = new jsSdkCommon.LDClientError(`Wrong type "${type}" for feature flag "${flagKey}"; returning default value`);
|
|
1594
|
-
this.emitter.emit('error', this.
|
|
1793
|
+
this.emitter.emit('error', this._activeContextTracker.getUnwrappedContext(), error);
|
|
1595
1794
|
return createErrorEvaluationDetail(ErrorKinds.WrongType, defaultValue);
|
|
1596
1795
|
}
|
|
1597
1796
|
}
|
|
@@ -1603,18 +1802,20 @@ class LDClientImpl {
|
|
|
1603
1802
|
prerequisites?.forEach((prereqKey) => {
|
|
1604
1803
|
this._variationInternal(prereqKey, undefined, this._eventFactoryDefault);
|
|
1605
1804
|
});
|
|
1606
|
-
|
|
1805
|
+
if (hasContext) {
|
|
1806
|
+
this._eventProcessor?.sendEvent(eventFactory.evalEventClient(flagKey, value, defaultValue, foundItem.flag, evalContext, reason));
|
|
1807
|
+
}
|
|
1607
1808
|
return successDetail;
|
|
1608
1809
|
}
|
|
1609
1810
|
variation(flagKey, defaultValue) {
|
|
1610
|
-
const { value } = this._hookRunner.withEvaluation(flagKey, this.
|
|
1811
|
+
const { value } = this._hookRunner.withEvaluation(flagKey, this._activeContextTracker.getUnwrappedContext(), defaultValue, () => this._variationInternal(flagKey, defaultValue, this._eventFactoryDefault));
|
|
1611
1812
|
return value;
|
|
1612
1813
|
}
|
|
1613
1814
|
variationDetail(flagKey, defaultValue) {
|
|
1614
|
-
return this._hookRunner.withEvaluation(flagKey, this.
|
|
1815
|
+
return this._hookRunner.withEvaluation(flagKey, this._activeContextTracker.getUnwrappedContext(), defaultValue, () => this._variationInternal(flagKey, defaultValue, this._eventFactoryWithReasons));
|
|
1615
1816
|
}
|
|
1616
1817
|
_typedEval(key, defaultValue, eventFactory, typeChecker) {
|
|
1617
|
-
return this._hookRunner.withEvaluation(key, this.
|
|
1818
|
+
return this._hookRunner.withEvaluation(key, this._activeContextTracker.getUnwrappedContext(), defaultValue, () => this._variationInternal(key, defaultValue, eventFactory, typeChecker));
|
|
1618
1819
|
}
|
|
1619
1820
|
boolVariation(key, defaultValue) {
|
|
1620
1821
|
return this._typedEval(key, defaultValue, this._eventFactoryDefault, (value) => [
|
|
@@ -1696,6 +1897,9 @@ class LDClientImpl {
|
|
|
1696
1897
|
sendEvent(event) {
|
|
1697
1898
|
this._eventProcessor?.sendEvent(event);
|
|
1698
1899
|
}
|
|
1900
|
+
getDebugOverrides() {
|
|
1901
|
+
return this._flagManager.getDebugOverride?.();
|
|
1902
|
+
}
|
|
1699
1903
|
_handleInspectionChanged(flagKeys, type) {
|
|
1700
1904
|
if (!this._inspectorManager.hasInspectors()) {
|
|
1701
1905
|
return;
|
|
@@ -1717,6 +1921,9 @@ class LDClientImpl {
|
|
|
1717
1921
|
};
|
|
1718
1922
|
}
|
|
1719
1923
|
});
|
|
1924
|
+
// NOTE: we are not tracking "override" changes because, at the time of writing,
|
|
1925
|
+
// these changes are only used for debugging purposes and are not persisted. This
|
|
1926
|
+
// may change in the future.
|
|
1720
1927
|
if (type === 'init') {
|
|
1721
1928
|
this._inspectorManager.onFlagsChanged(details);
|
|
1722
1929
|
}
|
|
@@ -1728,6 +1935,24 @@ class LDClientImpl {
|
|
|
1728
1935
|
}
|
|
1729
1936
|
}
|
|
1730
1937
|
|
|
1938
|
+
/**
|
|
1939
|
+
* Safe register debug override plugins.
|
|
1940
|
+
*
|
|
1941
|
+
* @param logger The logger to use for logging errors.
|
|
1942
|
+
* @param debugOverride The debug override to register.
|
|
1943
|
+
* @param plugins The plugins to register.
|
|
1944
|
+
*/
|
|
1945
|
+
function safeRegisterDebugOverridePlugins(logger, debugOverride, plugins) {
|
|
1946
|
+
plugins.forEach((plugin) => {
|
|
1947
|
+
try {
|
|
1948
|
+
plugin.registerDebug?.(debugOverride);
|
|
1949
|
+
}
|
|
1950
|
+
catch (error) {
|
|
1951
|
+
logger.error(`Exception thrown registering plugin ${jsSdkCommon.internal.safeGetName(logger, plugin)}.`);
|
|
1952
|
+
}
|
|
1953
|
+
});
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1731
1956
|
class DataSourceEventHandler {
|
|
1732
1957
|
constructor(_flagManager, _statusManager, _logger) {
|
|
1733
1958
|
this._flagManager = _flagManager;
|
|
@@ -2208,6 +2433,7 @@ exports.BaseDataManager = BaseDataManager;
|
|
|
2208
2433
|
exports.LDClientImpl = LDClientImpl;
|
|
2209
2434
|
exports.Requestor = Requestor;
|
|
2210
2435
|
exports.makeRequestor = makeRequestor;
|
|
2436
|
+
exports.safeRegisterDebugOverridePlugins = safeRegisterDebugOverridePlugins;
|
|
2211
2437
|
Object.keys(jsSdkCommon).forEach(function (k) {
|
|
2212
2438
|
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
2213
2439
|
enumerable: true,
|