UNPKG

@flopflip/react

Version:

A feature toggle wrapper to use LaunchDarkly with React

578 lines (524 loc) 19.1 kB
"use strict";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