launchdarkly-js-sdk-common 5.7.1 → 5.8.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.
- package/.github/workflows/dependency-scan.yml +30 -0
- package/.github/workflows/lint-pr-title.yml +3 -0
- package/.github/workflows/release-please.yml +49 -7
- package/.github/workflows/stale.yml +14 -0
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +14 -0
- package/package.json +4 -5
- package/src/AnonymousContextProcessor.js +2 -2
- package/src/EventSender.js +2 -2
- package/src/FlagStore.js +144 -0
- package/src/__tests__/LDClient-debugOverride-test.js +342 -0
- package/src/__tests__/LDClient-plugins-test.js +63 -5
- package/src/__tests__/diagnosticEvents-test.js +6 -0
- package/src/diagnosticEvents.js +2 -5
- package/src/index.js +127 -31
- package/src/plugins.js +17 -0
- package/src/uuid.js +70 -0
- package/test-types.ts +1 -1
- package/typings.d.ts +55 -3
- package/.github/workflows/manual-publish.yml +0 -42
package/src/index.js
CHANGED
|
@@ -18,7 +18,13 @@ const { checkContext, getContextKeys } = require('./context');
|
|
|
18
18
|
const { InspectorTypes, InspectorManager } = require('./InspectorManager');
|
|
19
19
|
const timedPromise = require('./timedPromise');
|
|
20
20
|
const createHookRunner = require('./HookRunner');
|
|
21
|
-
const
|
|
21
|
+
const FlagStore = require('./FlagStore');
|
|
22
|
+
const {
|
|
23
|
+
getPluginHooks,
|
|
24
|
+
registerPlugins,
|
|
25
|
+
registerPluginsForDebugOverride,
|
|
26
|
+
createPluginEnvironment,
|
|
27
|
+
} = require('./plugins');
|
|
22
28
|
const changeEvent = 'change';
|
|
23
29
|
const internalChangeEvent = 'internal-change';
|
|
24
30
|
const highTimeoutThreshold = 5;
|
|
@@ -76,7 +82,8 @@ function initialize(env, context, specifiedOptions, platform, extraOptionDefs) {
|
|
|
76
82
|
|
|
77
83
|
const requestor = Requestor(platform, options, environment);
|
|
78
84
|
|
|
79
|
-
|
|
85
|
+
const flagStore = FlagStore();
|
|
86
|
+
|
|
80
87
|
let useLocalStorage;
|
|
81
88
|
let streamActive;
|
|
82
89
|
let streamForcedState = options.streaming;
|
|
@@ -188,7 +195,7 @@ function initialize(env, context, specifiedOptions, platform, extraOptionDefs) {
|
|
|
188
195
|
function notifyInspectionFlagsChanged() {
|
|
189
196
|
if (inspectorManager.hasListeners(InspectorTypes.flagDetailsChanged)) {
|
|
190
197
|
inspectorManager.onFlags(
|
|
191
|
-
Object.entries(
|
|
198
|
+
Object.entries(flagStore.getFlagsWithOverrides())
|
|
192
199
|
.map(([key, value]) => ({ key, detail: getFlagDetail(value) }))
|
|
193
200
|
.reduce((acc, cur) => {
|
|
194
201
|
// eslint-disable-next-line no-param-reassign
|
|
@@ -232,7 +239,7 @@ function initialize(env, context, specifiedOptions, platform, extraOptionDefs) {
|
|
|
232
239
|
default: defaultValue,
|
|
233
240
|
creationDate: now.getTime(),
|
|
234
241
|
};
|
|
235
|
-
const flag =
|
|
242
|
+
const flag = flagStore.getFlags()[key];
|
|
236
243
|
if (flag) {
|
|
237
244
|
event.version = flag.flagVersion ? flag.flagVersion : flag.version;
|
|
238
245
|
event.trackEvents = flag.trackEvents;
|
|
@@ -262,7 +269,10 @@ function initialize(env, context, specifiedOptions, platform, extraOptionDefs) {
|
|
|
262
269
|
if (stateProvider) {
|
|
263
270
|
// We're being controlled by another client instance, so only that instance is allowed to change the context
|
|
264
271
|
logger.warn(messages.identifyDisabled());
|
|
265
|
-
return utils.wrapPromiseCallback(
|
|
272
|
+
return utils.wrapPromiseCallback(
|
|
273
|
+
Promise.resolve(utils.transformVersionedValuesToValues(flagStore.getFlagsWithOverrides())),
|
|
274
|
+
onDone
|
|
275
|
+
);
|
|
266
276
|
}
|
|
267
277
|
let afterIdentify;
|
|
268
278
|
const clearFirst = useLocalStorage && persistentFlagStore ? persistentFlagStore.clearFlags() : Promise.resolve();
|
|
@@ -328,10 +338,10 @@ function initialize(env, context, specifiedOptions, platform, extraOptionDefs) {
|
|
|
328
338
|
|
|
329
339
|
function variationDetailInternal(key, defaultValue, sendEvent, includeReasonInEvent, isAllFlags, notifyInspection) {
|
|
330
340
|
let detail;
|
|
331
|
-
let flag;
|
|
332
341
|
|
|
333
|
-
|
|
334
|
-
|
|
342
|
+
const flag = flagStore.get(key);
|
|
343
|
+
|
|
344
|
+
if (flag) {
|
|
335
345
|
detail = getFlagDetail(flag);
|
|
336
346
|
if (flag.value === null || flag.value === undefined) {
|
|
337
347
|
detail.value = defaultValue;
|
|
@@ -373,21 +383,14 @@ function initialize(env, context, specifiedOptions, platform, extraOptionDefs) {
|
|
|
373
383
|
function allFlags() {
|
|
374
384
|
const results = {};
|
|
375
385
|
|
|
376
|
-
|
|
386
|
+
const allFlags = flagStore.getFlagsWithOverrides();
|
|
387
|
+
|
|
388
|
+
if (!allFlags) {
|
|
377
389
|
return results;
|
|
378
390
|
}
|
|
379
391
|
|
|
380
|
-
for (const key in
|
|
381
|
-
|
|
382
|
-
results[key] = variationDetailInternal(
|
|
383
|
-
key,
|
|
384
|
-
null,
|
|
385
|
-
!options.sendEventsOnlyForVariation,
|
|
386
|
-
false,
|
|
387
|
-
true,
|
|
388
|
-
false
|
|
389
|
-
).value;
|
|
390
|
-
}
|
|
392
|
+
for (const key in allFlags) {
|
|
393
|
+
results[key] = variationDetailInternal(key, null, !options.sendEventsOnlyForVariation, false, true, false).value;
|
|
391
394
|
}
|
|
392
395
|
|
|
393
396
|
return results;
|
|
@@ -485,6 +488,7 @@ function initialize(env, context, specifiedOptions, platform, extraOptionDefs) {
|
|
|
485
488
|
// If both the flag and the patch have a version property, then the patch version must be
|
|
486
489
|
// greater than the flag version for us to accept the patch. If either one has no version
|
|
487
490
|
// then the patch always succeeds.
|
|
491
|
+
const flags = flagStore.getFlags();
|
|
488
492
|
const oldFlag = flags[data.key];
|
|
489
493
|
if (!oldFlag || !oldFlag.version || !data.version || oldFlag.version < data.version) {
|
|
490
494
|
logger.debug(messages.debugStreamPatch(data.key));
|
|
@@ -492,6 +496,7 @@ function initialize(env, context, specifiedOptions, platform, extraOptionDefs) {
|
|
|
492
496
|
const newFlag = utils.extend({}, data);
|
|
493
497
|
delete newFlag['key'];
|
|
494
498
|
flags[data.key] = newFlag;
|
|
499
|
+
flagStore.setFlags(flags);
|
|
495
500
|
const newDetail = getFlagDetail(newFlag);
|
|
496
501
|
if (oldFlag) {
|
|
497
502
|
mods[data.key] = { previous: oldFlag.value, current: newDetail };
|
|
@@ -509,6 +514,7 @@ function initialize(env, context, specifiedOptions, platform, extraOptionDefs) {
|
|
|
509
514
|
if (!data) {
|
|
510
515
|
return;
|
|
511
516
|
}
|
|
517
|
+
const flags = flagStore.getFlags();
|
|
512
518
|
if (!flags[data.key] || flags[data.key].version < data.version) {
|
|
513
519
|
logger.debug(messages.debugStreamDelete(data.key));
|
|
514
520
|
const mods = {};
|
|
@@ -516,6 +522,7 @@ function initialize(env, context, specifiedOptions, platform, extraOptionDefs) {
|
|
|
516
522
|
mods[data.key] = { previous: flags[data.key].value };
|
|
517
523
|
}
|
|
518
524
|
flags[data.key] = { version: data.version, deleted: true };
|
|
525
|
+
flagStore.setFlags(flags);
|
|
519
526
|
notifyInspectionFlagChanged(data, flags[data.key]);
|
|
520
527
|
handleFlagChanges(mods); // don't wait for this Promise to be resolved
|
|
521
528
|
} else {
|
|
@@ -542,6 +549,7 @@ function initialize(env, context, specifiedOptions, platform, extraOptionDefs) {
|
|
|
542
549
|
return Promise.resolve();
|
|
543
550
|
}
|
|
544
551
|
|
|
552
|
+
const flags = flagStore.getFlags();
|
|
545
553
|
for (const key in flags) {
|
|
546
554
|
if (utils.objectHasOwnProperty(flags, key) && flags[key]) {
|
|
547
555
|
if (newFlags[key] && !utils.deepEquals(newFlags[key].value, flags[key].value)) {
|
|
@@ -557,7 +565,7 @@ function initialize(env, context, specifiedOptions, platform, extraOptionDefs) {
|
|
|
557
565
|
}
|
|
558
566
|
}
|
|
559
567
|
|
|
560
|
-
|
|
568
|
+
flagStore.setFlags({ ...newFlags });
|
|
561
569
|
|
|
562
570
|
notifyInspectionFlagsChanged();
|
|
563
571
|
|
|
@@ -580,7 +588,7 @@ function initialize(env, context, specifiedOptions, platform, extraOptionDefs) {
|
|
|
580
588
|
});
|
|
581
589
|
|
|
582
590
|
emitter.emit(changeEvent, changeEventParams);
|
|
583
|
-
emitter.emit(internalChangeEvent,
|
|
591
|
+
emitter.emit(internalChangeEvent, flagStore.getFlagsWithOverrides());
|
|
584
592
|
|
|
585
593
|
// By default, we send feature evaluation events whenever we have received new flag values -
|
|
586
594
|
// the client has in effect evaluated these flags just by receiving them. This can be suppressed
|
|
@@ -595,7 +603,7 @@ function initialize(env, context, specifiedOptions, platform, extraOptionDefs) {
|
|
|
595
603
|
}
|
|
596
604
|
|
|
597
605
|
if (useLocalStorage && persistentFlagStore) {
|
|
598
|
-
return persistentFlagStore.saveFlags(
|
|
606
|
+
return persistentFlagStore.saveFlags(flagStore.getFlags());
|
|
599
607
|
} else {
|
|
600
608
|
return Promise.resolve();
|
|
601
609
|
}
|
|
@@ -666,7 +674,7 @@ function initialize(env, context, specifiedOptions, platform, extraOptionDefs) {
|
|
|
666
674
|
if (typeof options.bootstrap === 'object') {
|
|
667
675
|
// Set the flags as soon as possible before we get into any async code, so application code can read
|
|
668
676
|
// them even if the ready event has not yet fired.
|
|
669
|
-
|
|
677
|
+
flagStore.setFlags(readFlagsFromBootstrap(options.bootstrap));
|
|
670
678
|
}
|
|
671
679
|
|
|
672
680
|
if (stateProvider) {
|
|
@@ -718,7 +726,7 @@ function initialize(env, context, specifiedOptions, platform, extraOptionDefs) {
|
|
|
718
726
|
function finishInitWithLocalStorage() {
|
|
719
727
|
return persistentFlagStore.loadFlags().then(storedFlags => {
|
|
720
728
|
if (storedFlags === null || storedFlags === undefined) {
|
|
721
|
-
|
|
729
|
+
flagStore.setFlags({});
|
|
722
730
|
return requestor
|
|
723
731
|
.fetchFlagSettings(ident.getContext(), hash)
|
|
724
732
|
.then(requestedFlags => replaceAllFlags(requestedFlags || {}))
|
|
@@ -731,7 +739,7 @@ function initialize(env, context, specifiedOptions, platform, extraOptionDefs) {
|
|
|
731
739
|
// We're reading the flags from local storage. Signal that we're ready,
|
|
732
740
|
// then update localStorage for the next page load. We won't signal changes or update
|
|
733
741
|
// the in-memory flags unless you subscribe for changes
|
|
734
|
-
|
|
742
|
+
flagStore.setFlags(storedFlags);
|
|
735
743
|
utils.onNextTick(signalSuccessfulInit);
|
|
736
744
|
|
|
737
745
|
return requestor
|
|
@@ -746,14 +754,14 @@ function initialize(env, context, specifiedOptions, platform, extraOptionDefs) {
|
|
|
746
754
|
return requestor
|
|
747
755
|
.fetchFlagSettings(ident.getContext(), hash)
|
|
748
756
|
.then(requestedFlags => {
|
|
749
|
-
|
|
757
|
+
flagStore.setFlags(requestedFlags || {});
|
|
750
758
|
|
|
751
759
|
notifyInspectionFlagsChanged();
|
|
752
760
|
// Note, we don't need to call updateSettings here because local storage and change events are not relevant
|
|
753
761
|
signalSuccessfulInit();
|
|
754
762
|
})
|
|
755
763
|
.catch(err => {
|
|
756
|
-
|
|
764
|
+
flagStore.setFlags({});
|
|
757
765
|
signalFailedInit(err);
|
|
758
766
|
});
|
|
759
767
|
}
|
|
@@ -761,7 +769,7 @@ function initialize(env, context, specifiedOptions, platform, extraOptionDefs) {
|
|
|
761
769
|
function initFromStateProvider(state) {
|
|
762
770
|
environment = state.environment;
|
|
763
771
|
ident.setContext(state.context);
|
|
764
|
-
|
|
772
|
+
flagStore.setFlags({ ...state.flags });
|
|
765
773
|
utils.onNextTick(signalSuccessfulInit);
|
|
766
774
|
}
|
|
767
775
|
|
|
@@ -800,7 +808,7 @@ function initialize(env, context, specifiedOptions, platform, extraOptionDefs) {
|
|
|
800
808
|
}
|
|
801
809
|
const finishClose = () => {
|
|
802
810
|
closed = true;
|
|
803
|
-
|
|
811
|
+
flagStore.setFlags({});
|
|
804
812
|
};
|
|
805
813
|
const p = Promise.resolve()
|
|
806
814
|
.then(() => {
|
|
@@ -820,7 +828,7 @@ function initialize(env, context, specifiedOptions, platform, extraOptionDefs) {
|
|
|
820
828
|
|
|
821
829
|
function getFlagsInternal() {
|
|
822
830
|
// used by Electron integration
|
|
823
|
-
return
|
|
831
|
+
return flagStore.getFlagsWithOverrides();
|
|
824
832
|
}
|
|
825
833
|
|
|
826
834
|
function waitForInitializationWithTimeout(timeout) {
|
|
@@ -880,6 +888,94 @@ function initialize(env, context, specifiedOptions, platform, extraOptionDefs) {
|
|
|
880
888
|
|
|
881
889
|
registerPlugins(logger, pluginEnvironment, client, plugins);
|
|
882
890
|
|
|
891
|
+
function setOverride(key, value) {
|
|
892
|
+
const mods = {};
|
|
893
|
+
|
|
894
|
+
const currentFlag = flagStore.get(key);
|
|
895
|
+
const currentValue = currentFlag ? currentFlag.value : null;
|
|
896
|
+
|
|
897
|
+
if (currentValue === value) {
|
|
898
|
+
logger.debug(`setOverride: No change needed for ${key}, value already ${value}`);
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
flagStore.setOverride(key, value);
|
|
903
|
+
const newFlag = flagStore.get(key);
|
|
904
|
+
const newDetail = getFlagDetail(newFlag);
|
|
905
|
+
|
|
906
|
+
mods[key] = { previous: currentValue, current: newDetail };
|
|
907
|
+
|
|
908
|
+
notifyInspectionFlagChanged({ key }, newFlag);
|
|
909
|
+
handleFlagChanges(mods);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
function removeOverride(key) {
|
|
913
|
+
const flagOverrides = flagStore.getFlagOverrides();
|
|
914
|
+
if (!flagOverrides[key]) {
|
|
915
|
+
return; // No override to remove
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
const mods = {};
|
|
919
|
+
const oldOverride = flagOverrides[key];
|
|
920
|
+
const flags = flagStore.getFlags();
|
|
921
|
+
const realFlag = flags[key];
|
|
922
|
+
|
|
923
|
+
mods[key] = { previous: oldOverride.value, current: realFlag ? getFlagDetail(realFlag) : undefined };
|
|
924
|
+
|
|
925
|
+
flagStore.removeOverride(key);
|
|
926
|
+
notifyInspectionFlagChanged({ key }, realFlag);
|
|
927
|
+
handleFlagChanges(mods); // don't wait for this Promise to be resolved
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
function clearAllOverrides() {
|
|
931
|
+
const flagOverrides = flagStore.getFlagOverrides();
|
|
932
|
+
if (Object.keys(flagOverrides).length === 0) {
|
|
933
|
+
return; // No overrides to clear
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
const mods = {};
|
|
937
|
+
const flags = flagStore.getFlags();
|
|
938
|
+
Object.keys(flagOverrides).forEach(key => {
|
|
939
|
+
const oldOverride = flagOverrides[key];
|
|
940
|
+
const realFlag = flags[key];
|
|
941
|
+
|
|
942
|
+
mods[key] = { previous: oldOverride.value, current: realFlag ? getFlagDetail(realFlag) : undefined };
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
flagStore.clearAllOverrides();
|
|
946
|
+
|
|
947
|
+
if (Object.keys(mods).length > 0) {
|
|
948
|
+
handleFlagChanges(mods); // don't wait for this Promise to be resolved
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
function getAllOverrides() {
|
|
953
|
+
const flagOverrides = flagStore.getFlagOverrides();
|
|
954
|
+
|
|
955
|
+
if (!flagOverrides) {
|
|
956
|
+
return {}; // No overrides set
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
const result = {};
|
|
960
|
+
Object.keys(flagOverrides).forEach(key => {
|
|
961
|
+
const override = flagOverrides[key];
|
|
962
|
+
if (override) {
|
|
963
|
+
result[key] = override.value;
|
|
964
|
+
}
|
|
965
|
+
});
|
|
966
|
+
return result;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
const debugOverride = {
|
|
970
|
+
setOverride: setOverride,
|
|
971
|
+
removeOverride: removeOverride,
|
|
972
|
+
clearAllOverrides: clearAllOverrides,
|
|
973
|
+
getAllOverrides: getAllOverrides,
|
|
974
|
+
};
|
|
975
|
+
|
|
976
|
+
// Register plugins for debug override capabilities
|
|
977
|
+
registerPluginsForDebugOverride(logger, debugOverride, plugins);
|
|
978
|
+
|
|
883
979
|
return {
|
|
884
980
|
client: client, // The client object containing all public methods.
|
|
885
981
|
options: options, // The validated configuration object, including all defaults.
|
package/src/plugins.js
CHANGED
|
@@ -56,6 +56,22 @@ function registerPlugins(logger, environmentMetadata, client, plugins) {
|
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Registers plugins for debug override capabilities
|
|
61
|
+
* @param {{ error: (message: string) => void }} logger - The logger instance
|
|
62
|
+
* @param {Object} debugOverride - The debug override interface object
|
|
63
|
+
* @param {Array<{registerDebug?: (debugOverride: object) => void}>} plugins - Array of plugin objects that may implement registerDebug
|
|
64
|
+
*/
|
|
65
|
+
function registerPluginsForDebugOverride(logger, debugOverride, plugins) {
|
|
66
|
+
plugins.forEach(plugin => {
|
|
67
|
+
try {
|
|
68
|
+
plugin.registerDebug?.(debugOverride);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
logger.error(`Exception thrown registering debug override with plugin ${getPluginName(logger, plugin)}.`);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
59
75
|
/**
|
|
60
76
|
* Creates a plugin environment object
|
|
61
77
|
* @param {{userAgent: string, version: string}} platform - The platform object
|
|
@@ -105,5 +121,6 @@ function createPluginEnvironment(platform, env, options) {
|
|
|
105
121
|
module.exports = {
|
|
106
122
|
getPluginHooks,
|
|
107
123
|
registerPlugins,
|
|
124
|
+
registerPluginsForDebugOverride,
|
|
108
125
|
createPluginEnvironment,
|
|
109
126
|
};
|
package/src/uuid.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/* global crypto */
|
|
2
|
+
// The implementation in this file generates UUIDs in v4 format and is suitable
|
|
3
|
+
// for use as a UUID in LaunchDarkly events. It is not a rigorous implementation.
|
|
4
|
+
//
|
|
5
|
+
// Adapted from:
|
|
6
|
+
// https://github.com/launchdarkly/js-core/blob/main/packages/sdk/browser/src/platform/randomUuidV4.ts
|
|
7
|
+
|
|
8
|
+
// It uses crypto.randomUUID when available.
|
|
9
|
+
// If crypto.randomUUID is not available, then it uses random values and forms
|
|
10
|
+
// the UUID itself.
|
|
11
|
+
// When possible it uses crypto.getRandomValues, but it can use Math.random
|
|
12
|
+
// if crypto.getRandomValues is not available.
|
|
13
|
+
|
|
14
|
+
// UUIDv4 Struct definition.
|
|
15
|
+
// https://www.rfc-archive.org/getrfc.php?rfc=4122
|
|
16
|
+
// Appendix A. Appendix A - Sample Implementation
|
|
17
|
+
const timeLow = { start: 0, end: 3 };
|
|
18
|
+
const timeMid = { start: 4, end: 5 };
|
|
19
|
+
const timeHiAndVersion = { start: 6, end: 7 };
|
|
20
|
+
const clockSeqHiAndReserved = { start: 8, end: 8 };
|
|
21
|
+
const clockSeqLow = { start: 9, end: 9 };
|
|
22
|
+
const nodes = { start: 10, end: 15 };
|
|
23
|
+
|
|
24
|
+
function getRandom128bit() {
|
|
25
|
+
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
|
|
26
|
+
const typedArray = new Uint8Array(16);
|
|
27
|
+
crypto.getRandomValues(typedArray);
|
|
28
|
+
return [...typedArray.values()];
|
|
29
|
+
}
|
|
30
|
+
const values = [];
|
|
31
|
+
for (let index = 0; index < 16; index += 1) {
|
|
32
|
+
values.push(Math.floor(Math.random() * 256));
|
|
33
|
+
}
|
|
34
|
+
return values;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function hex(bytes, range) {
|
|
38
|
+
let strVal = '';
|
|
39
|
+
for (let index = range.start; index <= range.end; index += 1) {
|
|
40
|
+
strVal += bytes[index].toString(16).padStart(2, '0');
|
|
41
|
+
}
|
|
42
|
+
return strVal;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function formatDataAsUuidV4(bytes) {
|
|
46
|
+
// eslint-disable-next-line no-bitwise, no-param-reassign
|
|
47
|
+
bytes[clockSeqHiAndReserved.start] = (bytes[clockSeqHiAndReserved.start] | 0x80) & 0xbf;
|
|
48
|
+
// eslint-disable-next-line no-bitwise, no-param-reassign
|
|
49
|
+
bytes[timeHiAndVersion.start] = (bytes[timeHiAndVersion.start] & 0x0f) | 0x40;
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
`${hex(bytes, timeLow)}-${hex(bytes, timeMid)}-${hex(bytes, timeHiAndVersion)}-` +
|
|
53
|
+
`${hex(bytes, clockSeqHiAndReserved)}${hex(bytes, clockSeqLow)}-${hex(bytes, nodes)}`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function fallbackUuidV4() {
|
|
58
|
+
const bytes = getRandom128bit();
|
|
59
|
+
return formatDataAsUuidV4(bytes);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function randomUuidV4() {
|
|
63
|
+
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
|
64
|
+
return crypto.randomUUID();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return fallbackUuidV4();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = { randomUuidV4, fallbackUuidV4, formatDataAsUuidV4 };
|
package/test-types.ts
CHANGED
|
@@ -55,7 +55,7 @@ const plugin: ld.LDPlugin = {
|
|
|
55
55
|
}),
|
|
56
56
|
register(client: ld.LDClientBase, environmentMetadata: ld.LDPluginEnvironmentMetadata): void {
|
|
57
57
|
},
|
|
58
|
-
|
|
58
|
+
registerDebug(debugOverride: ld.LDDebugOverride): void {},
|
|
59
59
|
getHooks(metadata: ld.LDPluginEnvironmentMetadata): ld.Hook[] {
|
|
60
60
|
return [];
|
|
61
61
|
},
|
package/typings.d.ts
CHANGED
|
@@ -74,7 +74,7 @@ declare module 'launchdarkly-js-sdk-common' {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
/**
|
|
77
|
-
*
|
|
77
|
+
* Metadata about a hook implementation.
|
|
78
78
|
*/
|
|
79
79
|
export interface HookMetadata {
|
|
80
80
|
/**
|
|
@@ -149,7 +149,6 @@ declare module 'launchdarkly-js-sdk-common' {
|
|
|
149
149
|
readonly metricValue?: number;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
|
|
153
152
|
/**
|
|
154
153
|
* Interface for extending SDK functionality via hooks.
|
|
155
154
|
*/
|
|
@@ -251,7 +250,7 @@ declare module 'launchdarkly-js-sdk-common' {
|
|
|
251
250
|
}
|
|
252
251
|
|
|
253
252
|
/**
|
|
254
|
-
*
|
|
253
|
+
* Metadata about a plugin implementation.
|
|
255
254
|
*
|
|
256
255
|
* May be used in logs and analytics to identify the plugin.
|
|
257
256
|
*/
|
|
@@ -363,6 +362,59 @@ export interface LDPlugin {
|
|
|
363
362
|
* @param metadata
|
|
364
363
|
*/
|
|
365
364
|
getHooks?(metadata: LDPluginEnvironmentMetadata): Hook[];
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* An optional function called if the plugin wants to register debug capabilities.
|
|
368
|
+
* This method allows plugins to receive a debug override interface for
|
|
369
|
+
* temporarily overriding flag values during development and testing.
|
|
370
|
+
*
|
|
371
|
+
* @experimental This interface is experimental and intended for use by LaunchDarkly tools at this time.
|
|
372
|
+
* The API may change in future versions.
|
|
373
|
+
*
|
|
374
|
+
* @param debugOverride The debug override interface instance
|
|
375
|
+
*/
|
|
376
|
+
registerDebug?(debugOverride: LDDebugOverride): void;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Debug interface for plugins that need to override flag values during development.
|
|
381
|
+
* This interface provides methods to temporarily override flag values that take
|
|
382
|
+
* precedence over the actual flag values from LaunchDarkly. These overrides are
|
|
383
|
+
* useful for testing, development, and debugging scenarios.
|
|
384
|
+
*
|
|
385
|
+
* @experimental This interface is experimental and intended for use by LaunchDarkly tools at this time.
|
|
386
|
+
* The API may change in future versions.
|
|
387
|
+
*/
|
|
388
|
+
export interface LDDebugOverride {
|
|
389
|
+
/**
|
|
390
|
+
* Set an override value for a flag that takes precedence over the real flag value.
|
|
391
|
+
*
|
|
392
|
+
* @param flagKey The flag key.
|
|
393
|
+
* @param value The override value.
|
|
394
|
+
*/
|
|
395
|
+
setOverride(flagKey: string, value: LDFlagValue): void;
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Remove an override value for a flag, reverting to the real flag value.
|
|
399
|
+
*
|
|
400
|
+
* @param flagKey The flag key.
|
|
401
|
+
*/
|
|
402
|
+
removeOverride(flagKey: string): void;
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Clear all override values, reverting all flags to their real values.
|
|
406
|
+
*/
|
|
407
|
+
clearAllOverrides(): void;
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Get all currently active flag overrides.
|
|
411
|
+
*
|
|
412
|
+
* @returns
|
|
413
|
+
* An object containing all active overrides as key-value pairs,
|
|
414
|
+
* where keys are flag keys and values are the overridden flag values.
|
|
415
|
+
* Returns an empty object if no overrides are active.
|
|
416
|
+
*/
|
|
417
|
+
getAllOverrides(): LDFlagSet;
|
|
366
418
|
}
|
|
367
419
|
|
|
368
420
|
/**
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
name: Manual Publish Package
|
|
2
|
-
on:
|
|
3
|
-
workflow_dispatch:
|
|
4
|
-
inputs:
|
|
5
|
-
dry-run:
|
|
6
|
-
description: 'Is this a dry run. If so no package will be published.'
|
|
7
|
-
type: boolean
|
|
8
|
-
required: true
|
|
9
|
-
prerelease:
|
|
10
|
-
description: 'Is this a prerelease. If so, then the latest tag will not be updated in npm.'
|
|
11
|
-
type: boolean
|
|
12
|
-
required: true
|
|
13
|
-
|
|
14
|
-
jobs:
|
|
15
|
-
publish-package:
|
|
16
|
-
runs-on: ubuntu-latest
|
|
17
|
-
permissions:
|
|
18
|
-
id-token: write
|
|
19
|
-
contents: write
|
|
20
|
-
steps:
|
|
21
|
-
- uses: actions/checkout@v4
|
|
22
|
-
|
|
23
|
-
- uses: actions/setup-node@v4
|
|
24
|
-
with:
|
|
25
|
-
node-version: 20.x
|
|
26
|
-
registry-url: 'https://registry.npmjs.org'
|
|
27
|
-
|
|
28
|
-
- uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.2.0
|
|
29
|
-
name: 'Get NPM token'
|
|
30
|
-
with:
|
|
31
|
-
aws_assume_role: ${{ vars.AWS_ROLE_ARN }}
|
|
32
|
-
ssm_parameter_pairs: '/production/common/releasing/npm/token = NODE_AUTH_TOKEN'
|
|
33
|
-
|
|
34
|
-
- name: Install Dependencies
|
|
35
|
-
run: npm install
|
|
36
|
-
|
|
37
|
-
- id: publish-npm
|
|
38
|
-
name: Publish NPM Package
|
|
39
|
-
uses: ./.github/actions/publish-npm
|
|
40
|
-
with:
|
|
41
|
-
dry-run: ${{ inputs.dry-run }}
|
|
42
|
-
prerelease: ${{ inputs.prerelease }}
|