@openfeature/react-sdk
Version:
OpenFeature React SDK
480 lines (463 loc) • 19.6 kB
JavaScript
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
;