UNPKG

@openfeature/react-sdk

Version:
480 lines (463 loc) 19.6 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __getProtoOf = Object.getPrototypeOf; 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 __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default")); var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); 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/index.ts var index_exports = {}; __export(index_exports, { OpenFeatureProvider: () => OpenFeatureProvider, OpenFeatureTestProvider: () => OpenFeatureTestProvider, useBooleanFlagDetails: () => useBooleanFlagDetails, useBooleanFlagValue: () => useBooleanFlagValue, useContextMutator: () => useContextMutator, useFlag: () => useFlag, useNumberFlagDetails: () => useNumberFlagDetails, useNumberFlagValue: () => useNumberFlagValue, useObjectFlagDetails: () => useObjectFlagDetails, useObjectFlagValue: () => useObjectFlagValue, useOpenFeatureClient: () => useOpenFeatureClient, useOpenFeatureClientStatus: () => useOpenFeatureClientStatus, useStringFlagDetails: () => useStringFlagDetails, useStringFlagValue: () => useStringFlagValue, useSuspenseFlag: () => useSuspenseFlag, useTrack: () => useTrack, useWhenProviderReady: () => useWhenProviderReady }); module.exports = __toCommonJS(index_exports); // src/evaluation/use-feature-flag.ts var import_web_sdk4 = require("@openfeature/web-sdk"); var import_react4 = require("react"); // src/internal/context.ts var import_react = __toESM(require("react")); var Context = import_react.default.createContext(void 0); function useProviderOptions() { const { options } = import_react.default.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 var import_web_sdk = require("@openfeature/web-sdk"); function suspendUntilReady(client) { let resolve; let reject; throw new Promise((_resolve, _reject) => { resolve = _resolve; reject = _reject; client.addHandler(import_web_sdk.ProviderEvents.Ready, resolve); client.addHandler(import_web_sdk.ProviderEvents.Error, reject); }).finally(() => { client.removeHandler(import_web_sdk.ProviderEvents.Ready, resolve); client.removeHandler(import_web_sdk.ProviderEvents.Ready, reject); }); } // src/provider/use-open-feature-client.ts var import_react2 = __toESM(require("react")); function useOpenFeatureClient() { const { client } = import_react2.default.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 var import_react3 = require("react"); var import_web_sdk2 = require("@openfeature/web-sdk"); function useOpenFeatureClientStatus() { const client = useOpenFeatureClient(); const [status, setStatus] = (0, import_react3.useState)(client.providerStatus); (0, import_react3.useEffect)(() => { const updateStatus = () => setStatus(client.providerStatus); client.addHandler(import_web_sdk2.ProviderEvents.ConfigurationChanged, updateStatus); client.addHandler(import_web_sdk2.ProviderEvents.ContextChanged, updateStatus); client.addHandler(import_web_sdk2.ProviderEvents.Error, updateStatus); client.addHandler(import_web_sdk2.ProviderEvents.Ready, updateStatus); client.addHandler(import_web_sdk2.ProviderEvents.Stale, updateStatus); client.addHandler(import_web_sdk2.ProviderEvents.Reconciling, updateStatus); return () => { client.removeHandler(import_web_sdk2.ProviderEvents.ConfigurationChanged, updateStatus); client.removeHandler(import_web_sdk2.ProviderEvents.ContextChanged, updateStatus); client.removeHandler(import_web_sdk2.ProviderEvents.Error, updateStatus); client.removeHandler(import_web_sdk2.ProviderEvents.Ready, updateStatus); client.removeHandler(import_web_sdk2.ProviderEvents.Stale, updateStatus); client.removeHandler(import_web_sdk2.ProviderEvents.Reconciling, updateStatus); }; }, [client]); return status; } // src/evaluation/hook-flag-query.ts var import_web_sdk3 = require("@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 == import_web_sdk3.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 != import_web_sdk3.StandardResolutionReasons.STALE && this._details.reason != import_web_sdk3.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 === import_web_sdk4.ProviderStatus.NOT_READY) { suspendUntilReady(client); } if (defaultedOptions.suspendWhileReconciling && status === import_web_sdk4.ProviderStatus.RECONCILING) { suspendUntilReady(client); } const [evaluationDetails, setEvaluationDetails] = (0, import_react4.useState)( resolver(client).call(client, flagKey, defaultValue, options) ); const evaluationDetailsRef = (0, import_react4.useRef)(evaluationDetails); (0, import_react4.useEffect)(() => { 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(); } }; (0, import_react4.useEffect)(() => { if (status === import_web_sdk4.ProviderStatus.NOT_READY) { client.addHandler(import_web_sdk4.ProviderEvents.Ready, updateEvaluationDetailsCallback); } if (defaultedOptions.updateOnContextChanged) { client.addHandler(import_web_sdk4.ProviderEvents.ContextChanged, updateEvaluationDetailsCallback); } return () => { client.removeHandler(import_web_sdk4.ProviderEvents.Ready, updateEvaluationDetailsCallback); client.removeHandler(import_web_sdk4.ProviderEvents.ContextChanged, updateEvaluationDetailsCallback); }; }, []); (0, import_react4.useEffect)(() => { if (defaultedOptions.updateOnConfigurationChanged) { client.addHandler(import_web_sdk4.ProviderEvents.ConfigurationChanged, configurationChangeCallback); } return () => { client.removeHandler(import_web_sdk4.ProviderEvents.ConfigurationChanged, configurationChangeCallback); }; }, []); return evaluationDetails; } // src/provider/provider.tsx var import_web_sdk5 = require("@openfeature/web-sdk"); var React3 = __toESM(require("react")); function OpenFeatureProvider(_a) { var _b = _a, { client, domain, children } = _b, options = __objRest(_b, ["client", "domain", "children"]); if (!client) { client = import_web_sdk5.OpenFeature.getClient(domain); } return /* @__PURE__ */ React3.createElement(Context.Provider, { value: { client, options, domain } }, children); } // src/provider/use-when-provider-ready.ts var import_web_sdk6 = require("@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 === import_web_sdk6.ProviderStatus.NOT_READY) { suspendUntilReady(client); } return status === import_web_sdk6.ProviderStatus.READY; } // src/provider/test-provider.tsx var import_web_sdk7 = require("@openfeature/web-sdk"); var import_react5 = __toESM(require("react")); var TEST_VARIANT = "test-variant"; var TEST_PROVIDER = "test-provider"; var TestProvider = class extends import_web_sdk7.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) || import_web_sdk7.NOOP_PROVIDER; testProviderOptions.domain ? import_web_sdk7.OpenFeature.setProvider(testProviderOptions.domain, effectiveProvider) : import_web_sdk7.OpenFeature.setProvider(effectiveProvider); return /* @__PURE__ */ import_react5.default.createElement(OpenFeatureProvider, __spreadProps(__spreadValues({}, testProviderOptions), { domain: testProviderOptions.domain }), testProviderOptions.children); } function mixInNoop(provider = {}) { for (const prop of Object.getOwnPropertyNames(Object.getPrototypeOf(import_web_sdk7.NOOP_PROVIDER)).filter((prop2) => prop2 !== "constructor")) { const patchedProvider = provider; if (!Object.getPrototypeOf(patchedProvider)[prop] && !patchedProvider[prop]) { patchedProvider[prop] = Object.getPrototypeOf(import_web_sdk7.NOOP_PROVIDER)[prop]; } } if (!provider.metadata || !provider.metadata.name) { provider.metadata = { name: TEST_PROVIDER }; } return provider; } // src/context/use-context-mutator.ts var import_react6 = require("react"); var import_web_sdk8 = require("@openfeature/web-sdk"); function useContextMutator(options = { defaultContext: false }) { const { domain } = (0, import_react6.useContext)(Context) || {}; const previousContext = (0, import_react6.useRef)(null); const setContext = (0, import_react6.useCallback)((updatedContext) => __async(this, null, function* () { if (previousContext.current !== updatedContext) { if (!domain || (options == null ? void 0 : options.defaultContext)) { import_web_sdk8.OpenFeature.setContext(updatedContext); } else { import_web_sdk8.OpenFeature.setContext(domain, updatedContext); } previousContext.current = updatedContext; } }), [domain]); return { setContext }; } // src/tracking/use-track.ts var import_react7 = require("react"); function useTrack() { const client = useOpenFeatureClient(); const track = (0, import_react7.useCallback)((trackingEventName, trackingEventDetails) => { client.track(trackingEventName, trackingEventDetails); }, []); return { track }; } // src/index.ts __reExport(index_exports, require("@openfeature/web-sdk"), module.exports); //# sourceMappingURL=index.js.map