@flopflip/react
Version:
A feature toggle wrapper to use LaunchDarkly with React
578 lines (559 loc) • 18.1 kB
JavaScript
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