UNPKG

@openfeature/react-sdk

Version:
458 lines (442 loc) 16.9 kB
var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; 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; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); var __objRest = (source, exclude) => { var target = {}; for (var prop in source) if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0) target[prop] = source[prop]; if (source != null && __getOwnPropSymbols) for (var prop of __getOwnPropSymbols(source)) { if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop)) target[prop] = source[prop]; } return target; }; var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; // src/evaluation/use-feature-flag.ts import { ProviderEvents as ProviderEvents3, ProviderStatus } from "@openfeature/web-sdk"; import { useEffect as useEffect2, useRef, useState as useState2 } from "react"; // src/internal/context.ts import React from "react"; var Context = React.createContext(void 0); function useProviderOptions() { const { options } = React.useContext(Context) || {}; return normalizeOptions(options); } // src/internal/is-equal.ts function isEqual(value, other) { if (value === other) { return true; } if (typeof value !== typeof other) { return false; } if (typeof value === "object" && value !== null && other !== null) { const valueKeys = Object.keys(value); const otherKeys = Object.keys(other); if (valueKeys.length !== otherKeys.length) { return false; } for (const key of valueKeys) { if (!isEqual(value[key], other[key])) { return false; } } return true; } return false; } // src/internal/options.ts var DEFAULT_OPTIONS = { updateOnContextChanged: true, updateOnConfigurationChanged: true, suspendUntilReady: false, suspendWhileReconciling: false }; var normalizeOptions = (options = {}) => { const updateOnContextChanged = options.updateOnContextChanged; const updateOnConfigurationChanged = options.updateOnConfigurationChanged; const suspendUntilReady2 = "suspendUntilReady" in options ? options.suspendUntilReady : options.suspend; const suspendWhileReconciling = "suspendWhileReconciling" in options ? options.suspendWhileReconciling : options.suspend; return __spreadValues(__spreadValues(__spreadValues(__spreadValues({}, typeof suspendUntilReady2 === "boolean" && { suspendUntilReady: suspendUntilReady2 }), typeof suspendWhileReconciling === "boolean" && { suspendWhileReconciling }), typeof updateOnContextChanged === "boolean" && { updateOnContextChanged }), typeof updateOnConfigurationChanged === "boolean" && { updateOnConfigurationChanged }); }; // src/internal/suspense.ts import { ProviderEvents } from "@openfeature/web-sdk"; function suspendUntilReady(client) { let resolve; let reject; throw new Promise((_resolve, _reject) => { resolve = _resolve; reject = _reject; client.addHandler(ProviderEvents.Ready, resolve); client.addHandler(ProviderEvents.Error, reject); }).finally(() => { client.removeHandler(ProviderEvents.Ready, resolve); client.removeHandler(ProviderEvents.Ready, reject); }); } // src/provider/use-open-feature-client.ts import React2 from "react"; function useOpenFeatureClient() { const { client } = React2.useContext(Context) || {}; if (!client) { throw new Error( "No OpenFeature client available - components using OpenFeature must be wrapped with an <OpenFeatureProvider>. If you are seeing this in a test, see: https://openfeature.dev/docs/reference/technologies/client/web/react#testing" ); } return client; } // src/provider/use-open-feature-client-status.ts import { useEffect, useState } from "react"; import { ProviderEvents as ProviderEvents2 } from "@openfeature/web-sdk"; function useOpenFeatureClientStatus() { const client = useOpenFeatureClient(); const [status, setStatus] = useState(client.providerStatus); useEffect(() => { const updateStatus = () => setStatus(client.providerStatus); client.addHandler(ProviderEvents2.ConfigurationChanged, updateStatus); client.addHandler(ProviderEvents2.ContextChanged, updateStatus); client.addHandler(ProviderEvents2.Error, updateStatus); client.addHandler(ProviderEvents2.Ready, updateStatus); client.addHandler(ProviderEvents2.Stale, updateStatus); client.addHandler(ProviderEvents2.Reconciling, updateStatus); return () => { client.removeHandler(ProviderEvents2.ConfigurationChanged, updateStatus); client.removeHandler(ProviderEvents2.ContextChanged, updateStatus); client.removeHandler(ProviderEvents2.Error, updateStatus); client.removeHandler(ProviderEvents2.Ready, updateStatus); client.removeHandler(ProviderEvents2.Stale, updateStatus); client.removeHandler(ProviderEvents2.Reconciling, updateStatus); }; }, [client]); return status; } // src/evaluation/hook-flag-query.ts import { StandardResolutionReasons } from "@openfeature/web-sdk"; var HookFlagQuery = class { constructor(_details) { this._details = _details; } get details() { return this._details; } get value() { var _a; return (_a = this._details) == null ? void 0 : _a.value; } get variant() { return this._details.variant; } get flagMetadata() { return this._details.flagMetadata; } get reason() { return this._details.reason; } get isError() { var _a; return !!((_a = this._details) == null ? void 0 : _a.errorCode) || this._details.reason == StandardResolutionReasons.ERROR; } get errorCode() { var _a; return (_a = this._details) == null ? void 0 : _a.errorCode; } get errorMessage() { var _a; return (_a = this._details) == null ? void 0 : _a.errorMessage; } get isAuthoritative() { return !this.isError && this._details.reason != StandardResolutionReasons.STALE && this._details.reason != StandardResolutionReasons.DISABLED; } get type() { return typeof this._details.value; } }; // src/evaluation/use-feature-flag.ts function useFlag(flagKey, defaultValue, options) { const query = typeof defaultValue === "boolean" ? new HookFlagQuery(useBooleanFlagDetails(flagKey, defaultValue, options)) : typeof defaultValue === "number" ? new HookFlagQuery(useNumberFlagDetails(flagKey, defaultValue, options)) : typeof defaultValue === "string" ? new HookFlagQuery(useStringFlagDetails(flagKey, defaultValue, options)) : new HookFlagQuery(useObjectFlagDetails(flagKey, defaultValue, options)); return query; } function useSuspenseFlag(flagKey, defaultValue, options) { return useFlag(flagKey, defaultValue, __spreadProps(__spreadValues({}, options), { suspendUntilReady: true, suspendWhileReconciling: true })); } function useBooleanFlagValue(flagKey, defaultValue, options) { return useBooleanFlagDetails(flagKey, defaultValue, options).value; } function useBooleanFlagDetails(flagKey, defaultValue, options) { return attachHandlersAndResolve( flagKey, defaultValue, (client) => { return client.getBooleanDetails; }, options ); } function useStringFlagValue(flagKey, defaultValue, options) { return useStringFlagDetails(flagKey, defaultValue, options).value; } function useStringFlagDetails(flagKey, defaultValue, options) { return attachHandlersAndResolve( flagKey, defaultValue, (client) => { return client.getStringDetails; }, options ); } function useNumberFlagValue(flagKey, defaultValue, options) { return useNumberFlagDetails(flagKey, defaultValue, options).value; } function useNumberFlagDetails(flagKey, defaultValue, options) { return attachHandlersAndResolve( flagKey, defaultValue, (client) => { return client.getNumberDetails; }, options ); } function useObjectFlagValue(flagKey, defaultValue, options) { return useObjectFlagDetails(flagKey, defaultValue, options).value; } function useObjectFlagDetails(flagKey, defaultValue, options) { return attachHandlersAndResolve( flagKey, defaultValue, (client) => { return client.getObjectDetails; }, options ); } function shouldEvaluateFlag(flagKey, flagsChanged) { return !flagsChanged || flagsChanged.includes(flagKey); } function attachHandlersAndResolve(flagKey, defaultValue, resolver, options) { const defaultedOptions = __spreadValues(__spreadValues(__spreadValues({}, DEFAULT_OPTIONS), useProviderOptions()), normalizeOptions(options)); const client = useOpenFeatureClient(); const status = useOpenFeatureClientStatus(); if (defaultedOptions.suspendUntilReady && status === ProviderStatus.NOT_READY) { suspendUntilReady(client); } if (defaultedOptions.suspendWhileReconciling && status === ProviderStatus.RECONCILING) { suspendUntilReady(client); } const [evaluationDetails, setEvaluationDetails] = useState2( resolver(client).call(client, flagKey, defaultValue, options) ); const evaluationDetailsRef = useRef(evaluationDetails); useEffect2(() => { evaluationDetailsRef.current = evaluationDetails; }, [evaluationDetails]); const updateEvaluationDetailsCallback = () => { const updatedEvaluationDetails = resolver(client).call(client, flagKey, defaultValue, options); if (!isEqual(updatedEvaluationDetails.value, evaluationDetailsRef.current.value)) { setEvaluationDetails(updatedEvaluationDetails); } }; const configurationChangeCallback = (eventDetails) => { if (shouldEvaluateFlag(flagKey, eventDetails == null ? void 0 : eventDetails.flagsChanged)) { updateEvaluationDetailsCallback(); } }; useEffect2(() => { if (status === ProviderStatus.NOT_READY) { client.addHandler(ProviderEvents3.Ready, updateEvaluationDetailsCallback); } if (defaultedOptions.updateOnContextChanged) { client.addHandler(ProviderEvents3.ContextChanged, updateEvaluationDetailsCallback); } return () => { client.removeHandler(ProviderEvents3.Ready, updateEvaluationDetailsCallback); client.removeHandler(ProviderEvents3.ContextChanged, updateEvaluationDetailsCallback); }; }, []); useEffect2(() => { if (defaultedOptions.updateOnConfigurationChanged) { client.addHandler(ProviderEvents3.ConfigurationChanged, configurationChangeCallback); } return () => { client.removeHandler(ProviderEvents3.ConfigurationChanged, configurationChangeCallback); }; }, []); return evaluationDetails; } // src/provider/provider.tsx import { OpenFeature } from "@openfeature/web-sdk"; import * as React3 from "react"; function OpenFeatureProvider(_a) { var _b = _a, { client, domain, children } = _b, options = __objRest(_b, ["client", "domain", "children"]); if (!client) { client = OpenFeature.getClient(domain); } return /* @__PURE__ */ React3.createElement(Context.Provider, { value: { client, options, domain } }, children); } // src/provider/use-when-provider-ready.ts import { ProviderStatus as ProviderStatus2 } from "@openfeature/web-sdk"; function useWhenProviderReady(options) { const client = useOpenFeatureClient(); const status = useOpenFeatureClientStatus(); const defaultedOptions = __spreadValues(__spreadValues(__spreadValues({}, DEFAULT_OPTIONS), useProviderOptions()), normalizeOptions(options)); if (defaultedOptions.suspendUntilReady && status === ProviderStatus2.NOT_READY) { suspendUntilReady(client); } return status === ProviderStatus2.READY; } // src/provider/test-provider.tsx import { InMemoryProvider, NOOP_PROVIDER, OpenFeature as OpenFeature2 } from "@openfeature/web-sdk"; import React4 from "react"; var TEST_VARIANT = "test-variant"; var TEST_PROVIDER = "test-provider"; var TestProvider = class extends InMemoryProvider { constructor(flagValueMap, delay = 0) { const flagConfig = Object.entries(flagValueMap).reduce((acc, flag) => { return __spreadProps(__spreadValues({}, acc), { [flag[0]]: { variants: { [TEST_VARIANT]: flag[1] }, defaultVariant: TEST_VARIANT, disabled: false } }); }, {}); super(flagConfig); this.delay = delay; // initially make this undefined, we still set it if a delay is specified // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - For maximum compatibility with previous versions, we ignore a possible TS error here, // since "initialize" was previously defined in superclass. // We can safely remove this ts-ignore in a few versions this.initialize = void 0; // "place-holder" init function which we only assign if want a delay this.delayedInitialize = () => __async(this, null, function* () { yield new Promise((resolve) => setTimeout(resolve, this.delay)); }); this.initialize = this.delay ? this.delayedInitialize.bind(this) : void 0; } onContextChange() { return __async(this, null, function* () { return new Promise((resolve) => setTimeout(resolve, this.delay)); }); } }; function OpenFeatureTestProvider(testProviderOptions) { const { flagValueMap, provider } = testProviderOptions; const effectiveProvider = flagValueMap ? new TestProvider(flagValueMap, testProviderOptions.delayMs) : mixInNoop(provider) || NOOP_PROVIDER; testProviderOptions.domain ? OpenFeature2.setProvider(testProviderOptions.domain, effectiveProvider) : OpenFeature2.setProvider(effectiveProvider); return /* @__PURE__ */ React4.createElement(OpenFeatureProvider, __spreadProps(__spreadValues({}, testProviderOptions), { domain: testProviderOptions.domain }), testProviderOptions.children); } function mixInNoop(provider = {}) { for (const prop of Object.getOwnPropertyNames(Object.getPrototypeOf(NOOP_PROVIDER)).filter((prop2) => prop2 !== "constructor")) { const patchedProvider = provider; if (!Object.getPrototypeOf(patchedProvider)[prop] && !patchedProvider[prop]) { patchedProvider[prop] = Object.getPrototypeOf(NOOP_PROVIDER)[prop]; } } if (!provider.metadata || !provider.metadata.name) { provider.metadata = { name: TEST_PROVIDER }; } return provider; } // src/context/use-context-mutator.ts import { useCallback, useContext, useRef as useRef2 } from "react"; import { OpenFeature as OpenFeature3 } from "@openfeature/web-sdk"; function useContextMutator(options = { defaultContext: false }) { const { domain } = useContext(Context) || {}; const previousContext = useRef2(null); const setContext = useCallback((updatedContext) => __async(this, null, function* () { if (previousContext.current !== updatedContext) { if (!domain || (options == null ? void 0 : options.defaultContext)) { OpenFeature3.setContext(updatedContext); } else { OpenFeature3.setContext(domain, updatedContext); } previousContext.current = updatedContext; } }), [domain]); return { setContext }; } // src/tracking/use-track.ts import { useCallback as useCallback2 } from "react"; function useTrack() { const client = useOpenFeatureClient(); const track = useCallback2((trackingEventName, trackingEventDetails) => { client.track(trackingEventName, trackingEventDetails); }, []); return { track }; } // src/index.ts export * from "@openfeature/web-sdk"; export { OpenFeatureProvider, OpenFeatureTestProvider, useBooleanFlagDetails, useBooleanFlagValue, useContextMutator, useFlag, useNumberFlagDetails, useNumberFlagValue, useObjectFlagDetails, useObjectFlagValue, useOpenFeatureClient, useOpenFeatureClientStatus, useStringFlagDetails, useStringFlagValue, useSuspenseFlag, useTrack, useWhenProviderReady }; //# sourceMappingURL=index.js.map