UNPKG

@flopflip/react

Version:

A feature toggle wrapper to use LaunchDarkly with React

578 lines (559 loc) 18.1 kB
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/adapter-context.ts import { AdapterConfigurationStatus } from "@flopflip/types"; import { createContext } from "react"; var initialReconfigureAdapter = () => void 0; var createAdapterContext = (adapterIdentifiers, reconfigure, status) => ({ adapterEffectIdentifiers: adapterIdentifiers != null ? adapterIdentifiers : [], reconfigure: reconfigure != null ? reconfigure : initialReconfigureAdapter, status }); var initialAdapterContext = createAdapterContext(); var AdapterContext = createContext(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( AdapterConfigurationStatus.Configured, adaptersStatus, adapterIdentifiers ); const isUnconfigured = hasEveryAdapterStatus( AdapterConfigurationStatus.Unconfigured, adaptersStatus, adapterIdentifiers ); const isConfiguring = hasEveryAdapterStatus( AdapterConfigurationStatus.Configuring, adaptersStatus, adapterIdentifiers ); const isConfigured = hasEveryAdapterStatus( AdapterConfigurationStatus.Configured, adaptersStatus, adapterIdentifiers ); const status = { isReady, isUnconfigured, isConfiguring, isConfigured }; return status; }; // src/configure-adapter/configure-adapter.tsx import { getAllCachedFlags } from "@flopflip/cache"; import { AdapterConfigurationStatus as AdapterConfigurationStatus2, AdapterInitializationStatus } from "@flopflip/types"; import React, { useCallback, useEffect, useRef, useState } from "react"; import warning from "tiny-warning"; // src/configure-adapter/helpers.ts import { Children } from "react"; import { merge } from "ts-deepmerge"; var isFunctionChildren = (children) => typeof children === "function"; var isEmptyChildren = (children) => !isFunctionChildren(children) && Children.count(children) === 0; var mergeAdapterArgs = (previousAdapterArgs, { adapterArgs: nextAdapterArgs, options = {} }) => options.shouldOverwrite ? nextAdapterArgs : merge(previousAdapterArgs, nextAdapterArgs); // src/configure-adapter/configure-adapter.tsx var AdapterStates = { UNCONFIGURED: "unconfigured", CONFIGURING: "configuring", CONFIGURED: "configured" }; var useAppliedAdapterArgsState = ({ initialAdapterArgs }) => { const [appliedAdapterArgs, setAppliedAdapterArgs] = useState(initialAdapterArgs); const applyAdapterArgs = useCallback( (nextAdapterArgs) => { setAppliedAdapterArgs(nextAdapterArgs); }, [setAppliedAdapterArgs] ); return [appliedAdapterArgs, applyAdapterArgs]; }; var useAdapterStateRef = () => { const adapterStateRef = useRef(AdapterStates.UNCONFIGURED); const setAdapterState = useCallback( (nextAdapterState) => { adapterStateRef.current = nextAdapterState; }, [adapterStateRef] ); const getIsAdapterConfigured = useCallback( () => adapterStateRef.current === AdapterStates.CONFIGURED, [adapterStateRef] ); const getDoesAdapterNeedInitialConfiguration = useCallback( () => adapterStateRef.current !== AdapterStates.CONFIGURED && adapterStateRef.current !== AdapterStates.CONFIGURING, [adapterStateRef] ); return [ adapterStateRef, setAdapterState, getIsAdapterConfigured, getDoesAdapterNeedInitialConfiguration ]; }; var usePendingAdapterArgsRef = (appliedAdapterArgs) => { const pendingAdapterArgsRef = useRef(void 0); const setPendingAdapterArgs = useCallback( (nextReconfiguration) => { var _a; pendingAdapterArgsRef.current = mergeAdapterArgs( (_a = pendingAdapterArgsRef.current) != null ? _a : appliedAdapterArgs, nextReconfiguration ); }, [appliedAdapterArgs, pendingAdapterArgsRef] ); const unsetPendingAdapterArgs = useCallback(() => { pendingAdapterArgsRef.current = void 0; }, [pendingAdapterArgsRef]); const getAdapterArgsForConfiguration = useCallback( () => { var _a; return (_a = pendingAdapterArgsRef.current) != null ? _a : appliedAdapterArgs; }, [appliedAdapterArgs, pendingAdapterArgsRef] ); useEffect(unsetPendingAdapterArgs, [ appliedAdapterArgs, unsetPendingAdapterArgs ]); return [ pendingAdapterArgsRef, setPendingAdapterArgs, getAdapterArgsForConfiguration ]; }; var useHandleDefaultFlagsCallback = ({ onFlagsStateChange }) => { const handleDefaultFlags = useCallback( (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 }) => { useEffect(() => { 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 === AdapterInitializationStatus.Succeeded) { setAdapterState(AdapterStates.CONFIGURED); if (pendingAdapterArgsRef.current) { applyAdapterArgs(pendingAdapterArgsRef.current); } } }).catch(() => { warning(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 === AdapterInitializationStatus.Succeeded) { setAdapterState(AdapterStates.CONFIGURED); } }).catch(() => { warning(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 }); useEffect(() => { 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 === AdapterInitializationStatus.Succeeded) { setAdapterState(AdapterStates.CONFIGURED); if (pendingAdapterArgsRef.current) { applyAdapterArgs(pendingAdapterArgsRef.current); } } }).catch(() => { warning(false, "@flopflip/react: adapter could not be configured."); }); } }, []); }; var usePendingAdapterArgsEffect = ({ adapterArgs, appliedAdapterArgs, applyAdapterArgs, getIsAdapterConfigured, setPendingAdapterArgs }) => { const reconfigureOrQueue = useCallback( (nextAdapterArgs, options) => { if (getIsAdapterConfigured()) { applyAdapterArgs( mergeAdapterArgs(appliedAdapterArgs, { adapterArgs: nextAdapterArgs, options }) ); return; } setPendingAdapterArgs({ adapterArgs: nextAdapterArgs, options }); }, [ appliedAdapterArgs, applyAdapterArgs, getIsAdapterConfigured, setPendingAdapterArgs ] ); useEffect(() => { 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), getAllCachedFlags(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__ */ React.createElement( AdapterContext.Provider, { value: createAdapterContext( adapterEffectIdentifiers, reconfigureOrQueue, adapterStatus ) }, (() => { const isAdapterConfigured = adapter.getIsConfigurationStatus( AdapterConfigurationStatus2.Configured ); if (isAdapterConfigured) { if (typeof render === "function") { return render(); } } if (children && isFunctionChildren(children)) { return children({ isAdapterConfigured }); } if (children && !isEmptyChildren(children)) { return React.Children.only(children); } return null; })() ); } ConfigureAdapter.displayName = "ConfigureAdapter"; // 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 import warning2 from "tiny-warning"; // src/get-normalized-flag-name.ts import camelCase from "lodash/camelCase.js"; var getNormalizedFlagName = (flagName) => camelCase(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); warning2( 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/reconfigure-adapter.ts import { Children as Children2, useEffect as useEffect2 } from "react"; // src/use-adapter-context.ts import { useContext } from "react"; var useAdapterContext = () => useContext(AdapterContext); // src/reconfigure-adapter.ts function ReconfigureAdapter({ shouldOverwrite = false, user, children = null }) { const adapterContext = useAdapterContext(); useEffect2(() => { adapterContext.reconfigure( { user }, { shouldOverwrite } ); }, [user, shouldOverwrite, adapterContext]); return children ? Children2.only(children) : null; } ReconfigureAdapter.displayName = "ReconfigureAdapter"; // src/set-display-name.ts var setDisplayName = (nextDisplayName) => (BaseComponent) => { BaseComponent.displayName = nextDisplayName; return BaseComponent; }; // src/toggle-feature.ts import React3 from "react"; import { isValidElementType } from "react-is"; import warning3 from "tiny-warning"; function ToggleFeature({ untoggledComponent, toggledComponent, render, children, isFeatureEnabled }) { if (untoggledComponent) { warning3( isValidElementType(untoggledComponent), `Invalid prop 'untoggledComponent' supplied to 'ToggleFeature': the prop is not a valid React component` ); } if (toggledComponent) { warning3( isValidElementType(toggledComponent), `Invalid prop 'toggledComponent' supplied to 'ToggleFeature': the prop is not a valid React component` ); } if (isFeatureEnabled) { if (toggledComponent) { return React3.createElement(toggledComponent); } if (children) { if (typeof children === "function") { return children({ isFeatureEnabled }); } return React3.Children.only(children); } if (typeof render === "function") { return render(); } } if (typeof children === "function") { return children({ isFeatureEnabled }); } if (untoggledComponent) { return React3.createElement(untoggledComponent); } return null; } ToggleFeature.displayName = "ToggleFeature"; // src/use-adapter-reconfiguration.ts import { useContext as useContext2 } from "react"; function useAdapterReconfiguration() { const adapterContext = useContext2(AdapterContext); return adapterContext.reconfigure; } // src/use-adapter-subscription.ts import { AdapterSubscriptionStatus } from "@flopflip/types"; import { useCallback as useCallback2, useEffect as useEffect3, useRef as useRef2 } from "react"; function useAdapterSubscription(adapter) { const useAdapterSubscriptionStatusRef = useRef2( AdapterSubscriptionStatus.Subscribed ); const { subscribe, unsubscribe } = adapter; useEffect3(() => { if (subscribe) { subscribe(); } useAdapterSubscriptionStatusRef.current = AdapterSubscriptionStatus.Subscribed; return () => { if (unsubscribe) { unsubscribe(); } useAdapterSubscriptionStatusRef.current = AdapterSubscriptionStatus.Unsubscribed; }; }, [subscribe, unsubscribe]); return useCallback2( (demandedAdapterSubscriptionStatus) => useAdapterSubscriptionStatusRef.current === demandedAdapterSubscriptionStatus, [useAdapterSubscriptionStatusRef] ); } // 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/index.ts var version = "__@FLOPFLIP/VERSION_OF_RELEASE__"; export { ALL_FLAGS_PROP_KEY, AdapterContext, ConfigureAdapter, DEFAULT_FLAGS_PROP_KEY, DEFAULT_FLAG_PROP_KEY, ReconfigureAdapter, ToggleFeature, createAdapterContext, getFlagVariation, getIsFeatureEnabled, isNil, selectAdapterConfigurationStatus, setDisplayName, useAdapterContext, useAdapterReconfiguration, useAdapterSubscription, version, wrapDisplayName }; //# sourceMappingURL=index.js.map