|
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'; |
@@ -64,12 +64,67 @@ export interface FlagManager { |
64 | 64 | * Unregister a flag change callback. |
65 | 65 | */ |
66 | 66 | off(callback: FlagsChangeCallback): void; |
| 67 | + |
| 68 | + // REVIEWER: My reasoning here is to have the flagmanager implementation determine |
| 69 | + // whether or not we can support debug plugins so I put the override methods here. |
| 70 | + // Would like some thoughts on this as it is a deviation from previous implementation. |
| 71 | + |
| 72 | + /** |
| 73 | + * Obtain debug override functions that allows plugins |
| 74 | + * to manipulate the outcome of the flags managed by |
| 75 | + * this manager |
| 76 | + * |
| 77 | + * @experimental This function is experimental and intended for use by LaunchDarkly tools at this time. |
| 78 | + */ |
| 79 | + getDebugOverride?(): LDDebugOverride |
| 80 | +} |
| 81 | + |
| 82 | +/** |
| 83 | + * Debug interface for plugins that need to override flag values during development. |
| 84 | + * This interface provides methods to temporarily override flag values that take |
| 85 | + * precedence over the actual flag values from LaunchDarkly. These overrides are |
| 86 | + * useful for testing, development, and debugging scenarios. |
| 87 | + * |
| 88 | + * @experimental This interface is experimental and intended for use by LaunchDarkly tools at this time. |
| 89 | + * The API may change in future versions. |
| 90 | + */ |
| 91 | +export interface LDDebugOverride { |
| 92 | + /** |
| 93 | + * Set an override value for a flag that takes precedence over the real flag value. |
| 94 | + * |
| 95 | + * @param flagKey The flag key. |
| 96 | + * @param value The override value. |
| 97 | + */ |
| 98 | + setOverride(flagKey: string, value: LDFlagValue): void; |
| 99 | + |
| 100 | + /** |
| 101 | + * Remove an override value for a flag, reverting to the real flag value. |
| 102 | + * |
| 103 | + * @param flagKey The flag key. |
| 104 | + */ |
| 105 | + removeOverride(flagKey: string): void; |
| 106 | + |
| 107 | + /** |
| 108 | + * Clear all override values, reverting all flags to their real values. |
| 109 | + */ |
| 110 | + clearAllOverrides(): void; |
| 111 | + |
| 112 | + /** |
| 113 | + * Get all currently active flag overrides. |
| 114 | + * |
| 115 | + * @returns |
| 116 | + * An object containing all active overrides as key-value pairs, |
| 117 | + * where keys are flag keys and values are the overridden flag values. |
| 118 | + * Returns an empty object if no overrides are active. |
| 119 | + */ |
| 120 | + getAllOverrides(): { [key: string]: ItemDescriptor }; |
67 | 121 | } |
68 | 122 |
|
69 | 123 | export default class DefaultFlagManager implements FlagManager { |
70 | 124 | private _flagStore = new DefaultFlagStore(); |
71 | 125 | private _flagUpdater: FlagUpdater; |
72 | 126 | private _flagPersistencePromise: Promise<FlagPersistence>; |
| 127 | + private _overrides?: { [key: string]: LDFlagValue }; |
73 | 128 |
|
74 | 129 | /** |
75 | 130 | * @param platform implementation of various platform provided functionality |
@@ -116,11 +171,25 @@ export default class DefaultFlagManager implements FlagManager { |
116 | 171 | } |
117 | 172 |
|
118 | 173 | get(key: string): ItemDescriptor | undefined { |
| 174 | + if (this._overrides && Object.prototype.hasOwnProperty.call(this._overrides, key)) { |
| 175 | + return this._convertValueToOverrideDescripter(this._overrides[key]); |
| 176 | + } |
| 177 | + |
119 | 178 | return this._flagStore.get(key); |
120 | 179 | } |
121 | 180 |
|
122 | 181 | getAll(): { [key: string]: ItemDescriptor } { |
123 | | - return this._flagStore.getAll(); |
| 182 | + if (this._overrides) { |
| 183 | + return { |
| 184 | + ...this._flagStore.getAll(), |
| 185 | + ...Object.entries(this._overrides).reduce((acc: {[key: string]: ItemDescriptor}, [key, value]) => { |
| 186 | + acc[key] = this._convertValueToOverrideDescripter(value); |
| 187 | + return acc |
| 188 | + }, {}) |
| 189 | + } |
| 190 | + } else { |
| 191 | + return this._flagStore.getAll(); |
| 192 | + } |
124 | 193 | } |
125 | 194 |
|
126 | 195 | presetFlags(newFlags: { [key: string]: ItemDescriptor }): void { |
@@ -152,4 +221,63 @@ export default class DefaultFlagManager implements FlagManager { |
152 | 221 | off(callback: FlagsChangeCallback): void { |
153 | 222 | this._flagUpdater.off(callback); |
154 | 223 | } |
| 224 | + |
| 225 | + private _convertValueToOverrideDescripter(value: LDFlagValue): ItemDescriptor { |
| 226 | + return { |
| 227 | + flag: { |
| 228 | + value: value, |
| 229 | + version: 0 |
| 230 | + }, |
| 231 | + version: 0 |
| 232 | + }; |
| 233 | + } |
| 234 | + |
| 235 | + setOverride(key: string, value: LDFlagValue) { |
| 236 | + if (!this._overrides) { |
| 237 | + this._overrides = {}; |
| 238 | + } |
| 239 | + this._overrides[key] = value; |
| 240 | + this._flagUpdater.handleFlagChanges(null, [key], 'override'); |
| 241 | + } |
| 242 | + |
| 243 | + removeOverride(flagKey: string) { |
| 244 | + if (!this._overrides || !Object.prototype.hasOwnProperty.call(this._overrides, flagKey)) { |
| 245 | + return; // No override to remove |
| 246 | + } |
| 247 | + |
| 248 | + delete this._overrides[flagKey]; |
| 249 | + |
| 250 | + // If no more overrides, reset to undefined for performance |
| 251 | + if (Object.keys(this._overrides).length === 0) { |
| 252 | + this._overrides = undefined; |
| 253 | + } |
| 254 | + |
| 255 | + this._flagUpdater.handleFlagChanges(null, [flagKey], 'override'); |
| 256 | + } |
| 257 | + |
| 258 | + clearAllOverrides() { |
| 259 | + if (!this._overrides) { |
| 260 | + return {}; // No overrides to clear, return empty object for consistency |
| 261 | + } |
| 262 | + |
| 263 | + const clearedOverrides = { ...this._overrides }; |
| 264 | + this._overrides = undefined; // Reset to undefined |
| 265 | + this._flagUpdater.handleFlagChanges(null, Object.keys(clearedOverrides), 'override'); |
| 266 | + return clearedOverrides; |
| 267 | + } |
| 268 | + |
| 269 | + getAllOverrides() { |
| 270 | + if (!this._overrides) { |
| 271 | + return {}; |
| 272 | + } |
| 273 | + const result = {} as { [key: string]: ItemDescriptor }; |
| 274 | + Object.entries(this._overrides).forEach(([key, value]) => { |
| 275 | + result[key] = this._convertValueToOverrideDescripter(value); |
| 276 | + }); |
| 277 | + return result; |
| 278 | + } |
| 279 | + |
| 280 | + getDebugOverride(): LDDebugOverride { |
| 281 | + return this as LDDebugOverride; |
| 282 | + } |
155 | 283 | } |
0 commit comments