|
1 | | -import { Context, LDLogger, Platform } from '@launchdarkly/js-sdk-common'; |
| 1 | +import { Context, LDFlagValue, LDLogger, Platform } from '@launchdarkly/js-sdk-common'; |
2 | 2 |
|
3 | 3 | import { namespaceForEnvironment } from '../storage/namespaceUtils'; |
4 | 4 | import FlagPersistence from './FlagPersistence'; |
@@ -55,12 +55,67 @@ export interface FlagManager { |
55 | 55 | * Unregister a flag change callback. |
56 | 56 | */ |
57 | 57 | off(callback: FlagsChangeCallback): void; |
| 58 | + |
| 59 | + // REVIEWER: My reasoning here is to have the flagmanager implementation determine |
| 60 | + // whether or not we can support debug plugins so I put the override methods here. |
| 61 | + // Would like some thoughts on this as it is a deviation from previous implementation. |
| 62 | + |
| 63 | + /** |
| 64 | + * Obtain debug override functions that allows plugins |
| 65 | + * to manipulate the outcome of the flags managed by |
| 66 | + * this manager |
| 67 | + * |
| 68 | + * @experimental This function is experimental and intended for use by LaunchDarkly tools at this time. |
| 69 | + */ |
| 70 | + getDebugOverride?(): LDDebugOverride |
| 71 | +} |
| 72 | + |
| 73 | +/** |
| 74 | + * Debug interface for plugins that need to override flag values during development. |
| 75 | + * This interface provides methods to temporarily override flag values that take |
| 76 | + * precedence over the actual flag values from LaunchDarkly. These overrides are |
| 77 | + * useful for testing, development, and debugging scenarios. |
| 78 | + * |
| 79 | + * @experimental This interface is experimental and intended for use by LaunchDarkly tools at this time. |
| 80 | + * The API may change in future versions. |
| 81 | + */ |
| 82 | +export interface LDDebugOverride { |
| 83 | + /** |
| 84 | + * Set an override value for a flag that takes precedence over the real flag value. |
| 85 | + * |
| 86 | + * @param flagKey The flag key. |
| 87 | + * @param value The override value. |
| 88 | + */ |
| 89 | + setOverride(flagKey: string, value: LDFlagValue): void; |
| 90 | + |
| 91 | + /** |
| 92 | + * Remove an override value for a flag, reverting to the real flag value. |
| 93 | + * |
| 94 | + * @param flagKey The flag key. |
| 95 | + */ |
| 96 | + removeOverride(flagKey: string): void; |
| 97 | + |
| 98 | + /** |
| 99 | + * Clear all override values, reverting all flags to their real values. |
| 100 | + */ |
| 101 | + clearAllOverrides(): void; |
| 102 | + |
| 103 | + /** |
| 104 | + * Get all currently active flag overrides. |
| 105 | + * |
| 106 | + * @returns |
| 107 | + * An object containing all active overrides as key-value pairs, |
| 108 | + * where keys are flag keys and values are the overridden flag values. |
| 109 | + * Returns an empty object if no overrides are active. |
| 110 | + */ |
| 111 | + getAllOverrides(): { [key: string]: ItemDescriptor }; |
58 | 112 | } |
59 | 113 |
|
60 | 114 | export default class DefaultFlagManager implements FlagManager { |
61 | 115 | private _flagStore = new DefaultFlagStore(); |
62 | 116 | private _flagUpdater: FlagUpdater; |
63 | 117 | private _flagPersistencePromise: Promise<FlagPersistence>; |
| 118 | + private _overrides?: { [key: string]: LDFlagValue }; |
64 | 119 |
|
65 | 120 | /** |
66 | 121 | * @param platform implementation of various platform provided functionality |
@@ -107,11 +162,25 @@ export default class DefaultFlagManager implements FlagManager { |
107 | 162 | } |
108 | 163 |
|
109 | 164 | get(key: string): ItemDescriptor | undefined { |
| 165 | + if (this._overrides && Object.prototype.hasOwnProperty.call(this._overrides, key)) { |
| 166 | + return this._convertValueToOverrideDescripter(this._overrides[key]); |
| 167 | + } |
| 168 | + |
110 | 169 | return this._flagStore.get(key); |
111 | 170 | } |
112 | 171 |
|
113 | 172 | getAll(): { [key: string]: ItemDescriptor } { |
114 | | - return this._flagStore.getAll(); |
| 173 | + if (this._overrides) { |
| 174 | + return { |
| 175 | + ...this._flagStore.getAll(), |
| 176 | + ...Object.entries(this._overrides).reduce((acc: {[key: string]: ItemDescriptor}, [key, value]) => { |
| 177 | + acc[key] = this._convertValueToOverrideDescripter(value); |
| 178 | + return acc |
| 179 | + }, {}) |
| 180 | + } |
| 181 | + } else { |
| 182 | + return this._flagStore.getAll(); |
| 183 | + } |
115 | 184 | } |
116 | 185 |
|
117 | 186 | setBootstrap(context: Context, newFlags: { [key: string]: ItemDescriptor }): void { |
@@ -139,4 +208,63 @@ export default class DefaultFlagManager implements FlagManager { |
139 | 208 | off(callback: FlagsChangeCallback): void { |
140 | 209 | this._flagUpdater.off(callback); |
141 | 210 | } |
| 211 | + |
| 212 | + private _convertValueToOverrideDescripter(value: LDFlagValue): ItemDescriptor { |
| 213 | + return { |
| 214 | + flag: { |
| 215 | + value: value, |
| 216 | + version: 0 |
| 217 | + }, |
| 218 | + version: 0 |
| 219 | + }; |
| 220 | + } |
| 221 | + |
| 222 | + setOverride(key: string, value: LDFlagValue) { |
| 223 | + if (!this._overrides) { |
| 224 | + this._overrides = {}; |
| 225 | + } |
| 226 | + this._overrides[key] = value; |
| 227 | + this._flagUpdater.handleFlagChanges(null, [key], 'override'); |
| 228 | + } |
| 229 | + |
| 230 | + removeOverride(flagKey: string) { |
| 231 | + if (!this._overrides || !Object.prototype.hasOwnProperty.call(this._overrides, flagKey)) { |
| 232 | + return; // No override to remove |
| 233 | + } |
| 234 | + |
| 235 | + delete this._overrides[flagKey]; |
| 236 | + |
| 237 | + // If no more overrides, reset to undefined for performance |
| 238 | + if (Object.keys(this._overrides).length === 0) { |
| 239 | + this._overrides = undefined; |
| 240 | + } |
| 241 | + |
| 242 | + this._flagUpdater.handleFlagChanges(null, [flagKey], 'override'); |
| 243 | + } |
| 244 | + |
| 245 | + clearAllOverrides() { |
| 246 | + if (!this._overrides) { |
| 247 | + return {}; // No overrides to clear, return empty object for consistency |
| 248 | + } |
| 249 | + |
| 250 | + const clearedOverrides = { ...this._overrides }; |
| 251 | + this._overrides = undefined; // Reset to undefined |
| 252 | + this._flagUpdater.handleFlagChanges(null, Object.keys(clearedOverrides), 'override'); |
| 253 | + return clearedOverrides; |
| 254 | + } |
| 255 | + |
| 256 | + getAllOverrides() { |
| 257 | + if (!this._overrides) { |
| 258 | + return {}; |
| 259 | + } |
| 260 | + const result = {} as { [key: string]: ItemDescriptor }; |
| 261 | + Object.entries(this._overrides).forEach(([key, value]) => { |
| 262 | + result[key] = this._convertValueToOverrideDescripter(value); |
| 263 | + }); |
| 264 | + return result; |
| 265 | + } |
| 266 | + |
| 267 | + getDebugOverride(): LDDebugOverride { |
| 268 | + return this as LDDebugOverride; |
| 269 | + } |
142 | 270 | } |
0 commit comments