@launchdarkly/js-client-sdk-common 1.16.0 → 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 +8 -0
- package/dist/cjs/LDClientImpl.d.ts +25 -1
- 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/LDWaitForInitialization.d.ts +50 -0
- package/dist/cjs/api/LDWaitForInitialization.d.ts.map +1 -0
- package/dist/cjs/api/index.d.ts +1 -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/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 +33 -6
- package/dist/cjs/flag-manager/FlagUpdater.d.ts.map +1 -1
- package/dist/cjs/index.cjs +183 -92
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/esm/LDClientImpl.d.ts +25 -1
- 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/LDWaitForInitialization.d.ts +50 -0
- package/dist/esm/api/LDWaitForInitialization.d.ts.map +1 -0
- package/dist/esm/api/index.d.ts +1 -0
- package/dist/esm/api/index.d.ts.map +1 -1
- package/dist/esm/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/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 +33 -6
- package/dist/esm/flag-manager/FlagUpdater.d.ts.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.mjs +184 -93
- package/dist/esm/index.mjs.map +1 -1
- package/package.json +1 -1
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;
|
|
@@ -863,30 +864,30 @@ class FlagPersistence {
|
|
|
863
864
|
}
|
|
864
865
|
|
|
865
866
|
/**
|
|
866
|
-
*
|
|
867
|
+
* Creates the default implementation of the flag store.
|
|
867
868
|
*/
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
},
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
}
|
|
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
|
+
};
|
|
890
891
|
}
|
|
891
892
|
|
|
892
893
|
function calculateChangedKeys(existingObject, newObject) {
|
|
@@ -907,70 +908,66 @@ function calculateChangedKeys(existingObject, newObject) {
|
|
|
907
908
|
return changedKeys;
|
|
908
909
|
}
|
|
909
910
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
if (index > -1) {
|
|
971
|
-
this._changeCallbacks.splice(index, 1);
|
|
972
|
-
}
|
|
973
|
-
}
|
|
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
|
+
});
|
|
927
|
+
}
|
|
928
|
+
else {
|
|
929
|
+
logger.warn('Received a change event without an active context. Changes will not be propagated.');
|
|
930
|
+
}
|
|
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
|
+
};
|
|
974
971
|
}
|
|
975
972
|
|
|
976
973
|
class DefaultFlagManager {
|
|
@@ -982,8 +979,8 @@ class DefaultFlagManager {
|
|
|
982
979
|
* @param timeStamper exists for testing purposes
|
|
983
980
|
*/
|
|
984
981
|
constructor(platform, sdkKey, maxCachedContexts, logger, timeStamper = () => Date.now()) {
|
|
985
|
-
this._flagStore =
|
|
986
|
-
this._flagUpdater =
|
|
982
|
+
this._flagStore = createDefaultFlagStore();
|
|
983
|
+
this._flagUpdater = createFlagUpdater(this._flagStore, logger);
|
|
987
984
|
this._flagPersistencePromise = this._initPersistence(platform, sdkKey, maxCachedContexts, logger, timeStamper);
|
|
988
985
|
}
|
|
989
986
|
async _initPersistence(platform, sdkKey, maxCachedContexts, logger, timeStamper = () => Date.now()) {
|
|
@@ -1486,6 +1483,24 @@ class LDClientImpl {
|
|
|
1486
1483
|
if (this._inspectorManager.hasInspectors()) {
|
|
1487
1484
|
this._hookRunner.addHook(getInspectorHook(this._inspectorManager));
|
|
1488
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
|
+
}
|
|
1489
1504
|
}
|
|
1490
1505
|
allFlags() {
|
|
1491
1506
|
// extracting all flag values
|
|
@@ -1630,13 +1645,18 @@ class LDClientImpl {
|
|
|
1630
1645
|
}, identifyOptions?.sheddable ?? false)
|
|
1631
1646
|
.then((res) => {
|
|
1632
1647
|
if (res.status === 'error') {
|
|
1633
|
-
|
|
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;
|
|
1634
1652
|
}
|
|
1635
1653
|
if (res.status === 'shed') {
|
|
1636
1654
|
return { status: 'shed' };
|
|
1637
1655
|
}
|
|
1638
|
-
|
|
1639
|
-
|
|
1656
|
+
const successResult = { status: 'completed' };
|
|
1657
|
+
// Track initialization state for waitForInitialization
|
|
1658
|
+
this.maybeSetInitializationResult({ status: 'complete' });
|
|
1659
|
+
return successResult;
|
|
1640
1660
|
});
|
|
1641
1661
|
if (noTimeout) {
|
|
1642
1662
|
return callSitePromise;
|
|
@@ -1648,6 +1668,74 @@ class LDClientImpl {
|
|
|
1648
1668
|
});
|
|
1649
1669
|
return Promise.race([callSitePromise, timeoutPromise]);
|
|
1650
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
|
+
}
|
|
1651
1739
|
on(eventName, listener) {
|
|
1652
1740
|
this.emitter.on(eventName, listener);
|
|
1653
1741
|
}
|
|
@@ -1833,6 +1921,9 @@ class LDClientImpl {
|
|
|
1833
1921
|
};
|
|
1834
1922
|
}
|
|
1835
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.
|
|
1836
1927
|
if (type === 'init') {
|
|
1837
1928
|
this._inspectorManager.onFlagsChanged(details);
|
|
1838
1929
|
}
|