@openfeature/react-sdk
Version:
OpenFeature React SDK
458 lines (442 loc) • 16.9 kB
JavaScript
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