@flopflip/react
Version:
A feature toggle wrapper to use LaunchDarkly with React
578 lines (524 loc) • 19.1 kB
JavaScript
Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }var __defProp = Object.defineProperty;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
// src/configure-adapter/configure-adapter.tsx
var _cache = require('@flopflip/cache');
var _types = require('@flopflip/types');
var _react = require('react'); var _react2 = _interopRequireDefault(_react);
var _tinywarning = require('tiny-warning'); var _tinywarning2 = _interopRequireDefault(_tinywarning);
// src/adapter-context.ts
var initialReconfigureAdapter = () => void 0;
var createAdapterContext = (adapterIdentifiers, reconfigure, status) => ({
adapterEffectIdentifiers: adapterIdentifiers != null ? adapterIdentifiers : [],
reconfigure: reconfigure != null ? reconfigure : initialReconfigureAdapter,
status
});
var initialAdapterContext = createAdapterContext();
var AdapterContext = _react.createContext.call(void 0, initialAdapterContext);
function hasEveryAdapterStatus(adapterConfigurationStatus, adaptersStatus, adapterIdentifiers) {
if (Object.keys(adaptersStatus != null ? adaptersStatus : {}).length === 0) {
return false;
}
if (Array.isArray(adapterIdentifiers)) {
return adapterIdentifiers.every(
(adapterIdentifier) => {
var _a;
return ((_a = adaptersStatus == null ? void 0 : adaptersStatus[adapterIdentifier]) == null ? void 0 : _a.configurationStatus) === adapterConfigurationStatus;
}
);
}
return Object.values(adaptersStatus != null ? adaptersStatus : {}).every(
(adapterStatus) => adapterStatus.configurationStatus === adapterConfigurationStatus
);
}
var selectAdapterConfigurationStatus = (adaptersStatus, adapterIdentifiers) => {
const isReady = hasEveryAdapterStatus(
_types.AdapterConfigurationStatus.Configured,
adaptersStatus,
adapterIdentifiers
);
const isUnconfigured = hasEveryAdapterStatus(
_types.AdapterConfigurationStatus.Unconfigured,
adaptersStatus,
adapterIdentifiers
);
const isConfiguring = hasEveryAdapterStatus(
_types.AdapterConfigurationStatus.Configuring,
adaptersStatus,
adapterIdentifiers
);
const isConfigured = hasEveryAdapterStatus(
_types.AdapterConfigurationStatus.Configured,
adaptersStatus,
adapterIdentifiers
);
const status = { isReady, isUnconfigured, isConfiguring, isConfigured };
return status;
};
// src/configure-adapter/helpers.ts
var _tsdeepmerge = require('ts-deepmerge');
var isFunctionChildren = (children) => typeof children === "function";
var isEmptyChildren = (children) => !isFunctionChildren(children) && _react.Children.count(children) === 0;
var mergeAdapterArgs = (previousAdapterArgs, { adapterArgs: nextAdapterArgs, options = {} }) => options.shouldOverwrite ? nextAdapterArgs : _tsdeepmerge.merge.call(void 0, previousAdapterArgs, nextAdapterArgs);
// src/configure-adapter/configure-adapter.tsx
var AdapterStates = {
UNCONFIGURED: "unconfigured",
CONFIGURING: "configuring",
CONFIGURED: "configured"
};
var useAppliedAdapterArgsState = ({
initialAdapterArgs
}) => {
const [appliedAdapterArgs, setAppliedAdapterArgs] = _react.useState.call(void 0, initialAdapterArgs);
const applyAdapterArgs = _react.useCallback.call(void 0,
(nextAdapterArgs) => {
setAppliedAdapterArgs(nextAdapterArgs);
},
[setAppliedAdapterArgs]
);
return [appliedAdapterArgs, applyAdapterArgs];
};
var useAdapterStateRef = () => {
const adapterStateRef = _react.useRef.call(void 0, AdapterStates.UNCONFIGURED);
const setAdapterState = _react.useCallback.call(void 0,
(nextAdapterState) => {
adapterStateRef.current = nextAdapterState;
},
[adapterStateRef]
);
const getIsAdapterConfigured = _react.useCallback.call(void 0,
() => adapterStateRef.current === AdapterStates.CONFIGURED,
[adapterStateRef]
);
const getDoesAdapterNeedInitialConfiguration = _react.useCallback.call(void 0,
() => adapterStateRef.current !== AdapterStates.CONFIGURED && adapterStateRef.current !== AdapterStates.CONFIGURING,
[adapterStateRef]
);
return [
adapterStateRef,
setAdapterState,
getIsAdapterConfigured,
getDoesAdapterNeedInitialConfiguration
];
};
var usePendingAdapterArgsRef = (appliedAdapterArgs) => {
const pendingAdapterArgsRef = _react.useRef.call(void 0, void 0);
const setPendingAdapterArgs = _react.useCallback.call(void 0,
(nextReconfiguration) => {
var _a;
pendingAdapterArgsRef.current = mergeAdapterArgs(
(_a = pendingAdapterArgsRef.current) != null ? _a : appliedAdapterArgs,
nextReconfiguration
);
},
[appliedAdapterArgs, pendingAdapterArgsRef]
);
const unsetPendingAdapterArgs = _react.useCallback.call(void 0, () => {
pendingAdapterArgsRef.current = void 0;
}, [pendingAdapterArgsRef]);
const getAdapterArgsForConfiguration = _react.useCallback.call(void 0,
() => {
var _a;
return (_a = pendingAdapterArgsRef.current) != null ? _a : appliedAdapterArgs;
},
[appliedAdapterArgs, pendingAdapterArgsRef]
);
_react.useEffect.call(void 0, unsetPendingAdapterArgs, [
appliedAdapterArgs,
unsetPendingAdapterArgs
]);
return [
pendingAdapterArgsRef,
setPendingAdapterArgs,
getAdapterArgsForConfiguration
];
};
var useHandleDefaultFlagsCallback = ({
onFlagsStateChange
}) => {
const handleDefaultFlags = _react.useCallback.call(void 0,
(defaultFlags) => {
if (Object.keys(defaultFlags).length > 0) {
onFlagsStateChange({ flags: defaultFlags });
}
},
[onFlagsStateChange]
);
return handleDefaultFlags;
};
var useConfigurationEffect = ({
adapter,
shouldDeferAdapterConfiguration,
getDoesAdapterNeedInitialConfiguration,
setAdapterState,
onFlagsStateChange,
onStatusStateChange,
applyAdapterArgs,
getAdapterArgsForConfiguration,
getIsAdapterConfigured,
pendingAdapterArgsRef,
appliedAdapterArgs
}) => {
_react.useEffect.call(void 0, () => {
if (!shouldDeferAdapterConfiguration && getDoesAdapterNeedInitialConfiguration()) {
setAdapterState(AdapterStates.CONFIGURING);
adapter.configure(getAdapterArgsForConfiguration(), {
onFlagsStateChange,
onStatusStateChange
}).then((configuration) => {
const isAdapterWithoutInitializationStatus = !(configuration == null ? void 0 : configuration.initializationStatus);
if (isAdapterWithoutInitializationStatus || configuration.initializationStatus === _types.AdapterInitializationStatus.Succeeded) {
setAdapterState(AdapterStates.CONFIGURED);
if (pendingAdapterArgsRef.current) {
applyAdapterArgs(pendingAdapterArgsRef.current);
}
}
}).catch(() => {
_tinywarning2.default.call(void 0, false, "@flopflip/react: adapter could not be configured.");
});
}
if (getIsAdapterConfigured()) {
setAdapterState(AdapterStates.CONFIGURING);
adapter.reconfigure(getAdapterArgsForConfiguration(), {
onFlagsStateChange,
onStatusStateChange
}).then((reconfiguration) => {
const isAdapterWithoutInitializationStatus = !(reconfiguration == null ? void 0 : reconfiguration.initializationStatus);
if (isAdapterWithoutInitializationStatus || reconfiguration.initializationStatus === _types.AdapterInitializationStatus.Succeeded) {
setAdapterState(AdapterStates.CONFIGURED);
}
}).catch(() => {
_tinywarning2.default.call(void 0, false, "@flopflip/react: adapter could not be reconfigured.");
});
}
}, [
adapter,
shouldDeferAdapterConfiguration,
onFlagsStateChange,
onStatusStateChange,
applyAdapterArgs,
getAdapterArgsForConfiguration,
getDoesAdapterNeedInitialConfiguration,
getIsAdapterConfigured,
setAdapterState,
pendingAdapterArgsRef,
appliedAdapterArgs
]);
};
var useDefaultFlagsEffect = ({
adapter,
defaultFlags,
onFlagsStateChange,
onStatusStateChange,
setAdapterState,
pendingAdapterArgsRef,
shouldDeferAdapterConfiguration,
applyAdapterArgs,
getAdapterArgsForConfiguration
}) => {
const handleDefaultFlags = useHandleDefaultFlagsCallback({
onFlagsStateChange
});
_react.useEffect.call(void 0, () => {
if (defaultFlags) {
handleDefaultFlags(defaultFlags);
}
if (!shouldDeferAdapterConfiguration) {
setAdapterState(AdapterStates.CONFIGURING);
adapter.configure(getAdapterArgsForConfiguration(), {
onFlagsStateChange,
onStatusStateChange
}).then((configuration) => {
const isAdapterWithoutInitializationStatus = !(configuration == null ? void 0 : configuration.initializationStatus);
if (isAdapterWithoutInitializationStatus || configuration.initializationStatus === _types.AdapterInitializationStatus.Succeeded) {
setAdapterState(AdapterStates.CONFIGURED);
if (pendingAdapterArgsRef.current) {
applyAdapterArgs(pendingAdapterArgsRef.current);
}
}
}).catch(() => {
_tinywarning2.default.call(void 0, false, "@flopflip/react: adapter could not be configured.");
});
}
}, []);
};
var usePendingAdapterArgsEffect = ({
adapterArgs,
appliedAdapterArgs,
applyAdapterArgs,
getIsAdapterConfigured,
setPendingAdapterArgs
}) => {
const reconfigureOrQueue = _react.useCallback.call(void 0,
(nextAdapterArgs, options) => {
if (getIsAdapterConfigured()) {
applyAdapterArgs(
mergeAdapterArgs(appliedAdapterArgs, {
adapterArgs: nextAdapterArgs,
options
})
);
return;
}
setPendingAdapterArgs({ adapterArgs: nextAdapterArgs, options });
},
[
appliedAdapterArgs,
applyAdapterArgs,
getIsAdapterConfigured,
setPendingAdapterArgs
]
);
_react.useEffect.call(void 0, () => {
if (!getIsAdapterConfigured()) {
reconfigureOrQueue(adapterArgs, {
shouldOverwrite: false
});
}
}, [adapterArgs, getIsAdapterConfigured, reconfigureOrQueue]);
return [reconfigureOrQueue];
};
function ConfigureAdapter({
shouldDeferAdapterConfiguration = false,
adapter,
adapterArgs,
adapterStatus,
defaultFlags = {},
onFlagsStateChange,
onStatusStateChange,
render,
children
}) {
var _a;
const [appliedAdapterArgs, applyAdapterArgs] = useAppliedAdapterArgsState({
initialAdapterArgs: adapterArgs
});
const [
pendingAdapterArgsRef,
setPendingAdapterArgs,
getAdapterArgsForConfiguration
] = usePendingAdapterArgsRef(appliedAdapterArgs);
const [
,
setAdapterState,
getIsAdapterConfigured,
getDoesAdapterNeedInitialConfiguration
] = useAdapterStateRef();
useDefaultFlagsEffect({
adapter,
defaultFlags: __spreadValues(__spreadValues({}, defaultFlags), _cache.getAllCachedFlags.call(void 0, adapter, adapterArgs.cacheIdentifier)),
onFlagsStateChange,
onStatusStateChange,
shouldDeferAdapterConfiguration,
setAdapterState,
pendingAdapterArgsRef,
getAdapterArgsForConfiguration,
applyAdapterArgs
});
const [reconfigureOrQueue] = usePendingAdapterArgsEffect({
adapterArgs,
appliedAdapterArgs,
applyAdapterArgs,
getIsAdapterConfigured,
setPendingAdapterArgs
});
useConfigurationEffect({
adapter,
shouldDeferAdapterConfiguration,
onFlagsStateChange,
onStatusStateChange,
setAdapterState,
pendingAdapterArgsRef,
getDoesAdapterNeedInitialConfiguration,
getAdapterArgsForConfiguration,
getIsAdapterConfigured,
applyAdapterArgs,
appliedAdapterArgs
});
const adapterEffectIdentifiers = (_a = adapter.effectIds) != null ? _a : [adapter.id];
return /* @__PURE__ */ _react2.default.createElement(
AdapterContext.Provider,
{
value: createAdapterContext(
adapterEffectIdentifiers,
reconfigureOrQueue,
adapterStatus
)
},
(() => {
const isAdapterConfigured = adapter.getIsConfigurationStatus(
_types.AdapterConfigurationStatus.Configured
);
if (isAdapterConfigured) {
if (typeof render === "function") {
return render();
}
}
if (children && isFunctionChildren(children)) {
return children({
isAdapterConfigured
});
}
if (children && !isEmptyChildren(children)) {
return _react2.default.Children.only(children);
}
return null;
})()
);
}
ConfigureAdapter.displayName = "ConfigureAdapter";
// src/reconfigure-adapter.ts
// src/use-adapter-context.ts
var useAdapterContext = () => _react.useContext.call(void 0, AdapterContext);
// src/reconfigure-adapter.ts
function ReconfigureAdapter({
shouldOverwrite = false,
user,
children = null
}) {
const adapterContext = useAdapterContext();
_react.useEffect.call(void 0, () => {
adapterContext.reconfigure(
{
user
},
{
shouldOverwrite
}
);
}, [user, shouldOverwrite, adapterContext]);
return children ? _react.Children.only(children) : null;
}
ReconfigureAdapter.displayName = "ReconfigureAdapter";
// src/toggle-feature.ts
var _reactis = require('react-is');
function ToggleFeature({
untoggledComponent,
toggledComponent,
render,
children,
isFeatureEnabled
}) {
if (untoggledComponent) {
_tinywarning2.default.call(void 0,
_reactis.isValidElementType.call(void 0, untoggledComponent),
`Invalid prop 'untoggledComponent' supplied to 'ToggleFeature': the prop is not a valid React component`
);
}
if (toggledComponent) {
_tinywarning2.default.call(void 0,
_reactis.isValidElementType.call(void 0, toggledComponent),
`Invalid prop 'toggledComponent' supplied to 'ToggleFeature': the prop is not a valid React component`
);
}
if (isFeatureEnabled) {
if (toggledComponent) {
return _react2.default.createElement(toggledComponent);
}
if (children) {
if (typeof children === "function") {
return children({
isFeatureEnabled
});
}
return _react2.default.Children.only(children);
}
if (typeof render === "function") {
return render();
}
}
if (typeof children === "function") {
return children({
isFeatureEnabled
});
}
if (untoggledComponent) {
return _react2.default.createElement(untoggledComponent);
}
return null;
}
ToggleFeature.displayName = "ToggleFeature";
// src/constants.ts
var DEFAULT_FLAG_PROP_KEY = "isFeatureEnabled";
var DEFAULT_FLAGS_PROP_KEY = "featureToggles";
var ALL_FLAGS_PROP_KEY = "@flopflip/flags";
// src/get-flag-variation.ts
// src/get-normalized-flag-name.ts
var _camelCase = require('lodash/camelCase'); var _camelCase2 = _interopRequireDefault(_camelCase);
var getNormalizedFlagName = (flagName) => _camelCase2.default.call(void 0, flagName);
// src/is-nil.ts
var isNil = (value) => value == null;
// src/get-flag-variation.ts
var getFlagVariation = (allFlags, adapterIdentifiers, flagName = DEFAULT_FLAG_PROP_KEY) => {
var _a;
const normalizedFlagName = getNormalizedFlagName(flagName);
_tinywarning2.default.call(void 0,
normalizedFlagName === flagName,
"@flopflip/react: passed flag name does not seem to be normalized which may result in unexpected toggling. Please refer to our readme for more information: https://github.com/tdeekens/flopflip#flag-normalization"
);
for (const adapterInterfaceIdentifier of adapterIdentifiers) {
const flagVariation = (_a = allFlags[adapterInterfaceIdentifier]) == null ? void 0 : _a[normalizedFlagName];
if (!isNil(flagVariation)) {
return flagVariation;
}
}
return false;
};
// src/get-is-feature-enabled.ts
var getIsFeatureEnabled = (allFlags, adapterIdentifiers, flagName = DEFAULT_FLAG_PROP_KEY, flagVariation = true) => getFlagVariation(allFlags, adapterIdentifiers, flagName) === flagVariation;
// src/wrap-display-name.ts
function wrapDisplayName(BaseComponent, hocName) {
var _a;
const previousDisplayName = (_a = BaseComponent.displayName) != null ? _a : BaseComponent.name;
return `${hocName}(${previousDisplayName != null ? previousDisplayName : "Component"})`;
}
// src/set-display-name.ts
var setDisplayName = (nextDisplayName) => (BaseComponent) => {
BaseComponent.displayName = nextDisplayName;
return BaseComponent;
};
// src/use-adapter-reconfiguration.ts
function useAdapterReconfiguration() {
const adapterContext = _react.useContext.call(void 0, AdapterContext);
return adapterContext.reconfigure;
}
// src/use-adapter-subscription.ts
function useAdapterSubscription(adapter) {
const useAdapterSubscriptionStatusRef = _react.useRef.call(void 0,
_types.AdapterSubscriptionStatus.Subscribed
);
const { subscribe, unsubscribe } = adapter;
_react.useEffect.call(void 0, () => {
if (subscribe) {
subscribe();
}
useAdapterSubscriptionStatusRef.current = _types.AdapterSubscriptionStatus.Subscribed;
return () => {
if (unsubscribe) {
unsubscribe();
}
useAdapterSubscriptionStatusRef.current = _types.AdapterSubscriptionStatus.Unsubscribed;
};
}, [subscribe, unsubscribe]);
return _react.useCallback.call(void 0,
(demandedAdapterSubscriptionStatus) => useAdapterSubscriptionStatusRef.current === demandedAdapterSubscriptionStatus,
[useAdapterSubscriptionStatusRef]
);
}
// src/index.ts
var version = "__@FLOPFLIP/VERSION_OF_RELEASE__";
exports.ALL_FLAGS_PROP_KEY = ALL_FLAGS_PROP_KEY; exports.AdapterContext = AdapterContext; exports.ConfigureAdapter = ConfigureAdapter; exports.DEFAULT_FLAGS_PROP_KEY = DEFAULT_FLAGS_PROP_KEY; exports.DEFAULT_FLAG_PROP_KEY = DEFAULT_FLAG_PROP_KEY; exports.ReconfigureAdapter = ReconfigureAdapter; exports.ToggleFeature = ToggleFeature; exports.createAdapterContext = createAdapterContext; exports.getFlagVariation = getFlagVariation; exports.getIsFeatureEnabled = getIsFeatureEnabled; exports.isNil = isNil; exports.selectAdapterConfigurationStatus = selectAdapterConfigurationStatus; exports.setDisplayName = setDisplayName; exports.useAdapterContext = useAdapterContext; exports.useAdapterReconfiguration = useAdapterReconfiguration; exports.useAdapterSubscription = useAdapterSubscription; exports.version = version; exports.wrapDisplayName = wrapDisplayName;
//# sourceMappingURL=index.cjs.map
;