Skip to content

Commit 1d5f76e

Browse files
committed
feat(plugin): add configurable clear modes for overrides
1 parent fba957a commit 1d5f76e

File tree

1 file changed

+74
-13
lines changed

1 file changed

+74
-13
lines changed

flag-url-override-plugin.js

Lines changed: 74 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
11
import { FlagOverridePlugin } from '@launchdarkly/toolbar';
22

3+
// Clear mode symbols for type-safe configuration
4+
export const CLEAR_MODE_EXPLICIT = Symbol('explicit');
5+
export const CLEAR_MODE_ALWAYS = Symbol('always');
6+
export const CLEAR_MODE_AUTO = Symbol('auto');
7+
38
/**
49
* Creates a FlagOverridePlugin wrapper that syncs overrides to/from URL parameters
510
*
611
* @param {Object} options - Configuration options
712
* @param {string} options.parameterPrefix - Prefix for URL parameters (default: 'ld_override_')
13+
* @param {Symbol} options.clearMode - When to clear existing overrides (default: CLEAR_MODE_AUTO)
14+
* - CLEAR_MODE_EXPLICIT: Only clear if ld_override__clear is present
15+
* - CLEAR_MODE_ALWAYS: Always clear before loading from URL
16+
* - CLEAR_MODE_AUTO: Auto-clear if any override parameters are present
817
* @param {Object} options.overrideOptions - Options to pass to the underlying FlagOverridePlugin
918
* @param {Function} options.logger - Optional logger function for debugging
1019
* @returns {Object} A wrapped FlagOverridePlugin with URL sync capabilities
1120
*/
1221
export function createFlagUrlOverridePlugin(options = {}) {
1322
const {
1423
parameterPrefix = 'ld_override_',
24+
clearMode = CLEAR_MODE_AUTO,
1525
overrideOptions = {},
1626
logger = console
1727
} = options;
@@ -21,25 +31,57 @@ export function createFlagUrlOverridePlugin(options = {}) {
2131

2232
/**
2333
* Load overrides from URL query parameters
34+
* Returns an object with:
35+
* - overrides: map of flag keys to values
36+
* - shouldClear: boolean indicating if all overrides should be cleared first
37+
* - removeFlags: array of flag keys to explicitly remove
2438
*/
2539
function loadOverridesFromUrl() {
2640
const urlParams = new URLSearchParams(window.location.search);
2741
const overrides = {};
42+
const removeFlags = [];
43+
let hasOverrideParams = false;
44+
45+
// Check for special _clear flag (just needs to be present)
46+
const hasClearFlag = urlParams.has(`${parameterPrefix}_clear`);
2847

2948
for (const [key, value] of urlParams.entries()) {
3049
if (key.startsWith(parameterPrefix)) {
3150
const flagKey = key.replace(parameterPrefix, '');
32-
try {
33-
// Try to parse as JSON
34-
overrides[flagKey] = JSON.parse(value);
35-
} catch (e) {
36-
// If parsing fails, treat as string
37-
overrides[flagKey] = value;
51+
52+
// Skip the special _clear flag
53+
if (flagKey === '_clear') {
54+
continue;
55+
}
56+
57+
hasOverrideParams = true;
58+
59+
// Empty value means remove this override
60+
if (value === '') {
61+
removeFlags.push(flagKey);
62+
} else {
63+
try {
64+
// Try to parse as JSON
65+
overrides[flagKey] = JSON.parse(value);
66+
} catch (e) {
67+
// If parsing fails, treat as string
68+
overrides[flagKey] = value;
69+
}
3870
}
3971
}
4072
}
4173

42-
return overrides;
74+
// Determine if we should clear based on clearMode
75+
let shouldClear = false;
76+
if (clearMode === CLEAR_MODE_ALWAYS) {
77+
shouldClear = true;
78+
} else if (clearMode === CLEAR_MODE_AUTO) {
79+
shouldClear = hasOverrideParams || hasClearFlag;
80+
} else if (clearMode === CLEAR_MODE_EXPLICIT) {
81+
shouldClear = hasClearFlag;
82+
}
83+
84+
return { overrides, shouldClear, removeFlags };
4385
}
4486

4587
/**
@@ -118,18 +160,37 @@ export function createFlagUrlOverridePlugin(options = {}) {
118160
// Monkey patch registerDebug to load URL overrides
119161
const originalRegisterDebug = plugin.registerDebug.bind(plugin);
120162
plugin.registerDebug = function(debugOverride) {
121-
// Call original implementation first
163+
// Call original implementation first (loads from localStorage)
122164
originalRegisterDebug(debugOverride);
123165

124-
// Load overrides from URL and apply them (using original method to avoid recursion)
125-
const urlOverrides = loadOverridesFromUrl();
126-
Object.entries(urlOverrides).forEach(([flagKey, value]) => {
166+
// Load override instructions from URL
167+
const { overrides, shouldClear, removeFlags } = loadOverridesFromUrl();
168+
169+
// Clear all existing overrides if needed (based on clearMode)
170+
if (shouldClear) {
171+
const reason = clearMode === CLEAR_MODE_ALWAYS
172+
? 'clearMode=ALWAYS'
173+
: clearMode === CLEAR_MODE_AUTO
174+
? 'clearMode=AUTO (URL params present)'
175+
: 'ld_override__clear flag';
176+
logger.info(`Clearing all existing overrides (${reason})`);
177+
originalClearAllOverrides();
178+
}
179+
180+
// Remove specific flags if requested
181+
removeFlags.forEach(flagKey => {
182+
logger.info(`Removing override from URL: ${flagKey}`);
183+
originalRemoveOverride(flagKey);
184+
});
185+
186+
// Apply overrides from URL (using original method to avoid recursion)
187+
Object.entries(overrides).forEach(([flagKey, value]) => {
127188
logger.info(`Loading override from URL: ${flagKey} = ${JSON.stringify(value)}`);
128189
originalSetOverride(flagKey, value);
129190
});
130191

131-
if (Object.keys(urlOverrides).length > 0) {
132-
logger.info(`Loaded ${Object.keys(urlOverrides).length} overrides from URL`);
192+
if (Object.keys(overrides).length > 0) {
193+
logger.info(`Loaded ${Object.keys(overrides).length} overrides from URL`);
133194
}
134195
};
135196

0 commit comments

Comments
 (0)