Skip to main content
Glama

Bucket Feature Flags MCP Server

Official
by reflagcom
index.tsx15.9 kB
"use client"; import React, { createContext, ReactNode, useContext, useEffect, useMemo, useState, } from "react"; import { CheckEvent, CompanyContext, HookArgs, InitOptions, RawFlag, ReflagClient, ReflagContext, RequestFeedbackData, TrackEvent, UnassignedFeedback, UserContext, } from "@reflag/browser-sdk"; import { version } from "../package.json"; export type { CheckEvent, CompanyContext, TrackEvent, UserContext }; export type EmptyFlagRemoteConfig = { key: undefined; payload: undefined }; export type FlagType = { config?: { payload: any; }; }; /** * A remotely managed configuration value for a feature. */ export type FlagRemoteConfig = | { /** * The key of the matched configuration value. */ key: string; /** * The optional user-supplied payload data. */ payload: any; } | EmptyFlagRemoteConfig; /** * Describes a feature */ export interface Flag< TConfig extends FlagType["config"] = EmptyFlagRemoteConfig, > { /** * The key of the feature. */ key: string; /** * If the feature is enabled. */ isEnabled: boolean; /** * If the feature is loading. */ isLoading: boolean; /* * Optional user-defined configuration. */ config: | ({ key: string; } & TConfig) | EmptyFlagRemoteConfig; /** * Track feature usage in Reflag. */ track(): Promise<Response | undefined> | undefined; /** * Request feedback from the user. */ requestFeedback: (opts: RequestFeedbackOptions) => void; } // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface Flags {} /** * Describes a collection of evaluated feature. * * @remarks * This types falls back to a generic Record<string, Flag> if the Flags interface * has not been extended. * */ export type TypedFlags = keyof Flags extends never ? Record<string, Flag> : { [TypedFlagKey in keyof Flags]: Flags[TypedFlagKey] extends FlagType ? Flag<Flags[TypedFlagKey]["config"]> : Flag; }; export type FlagKey = keyof TypedFlags; /** * Describes a collection of evaluated raw flags. */ export type RawFlags = Record<FlagKey, RawFlag>; export type BootstrappedFlags = { context: ReflagContext; flags: RawFlags; }; const SDK_VERSION = `react-sdk/${version}`; /** * Base props for the ReflagProvider and ReflagBootstrappedProvider. * @internal */ export type ReflagPropsBase = { /** * The children to render after the client is initialized. */ children?: ReactNode; /** * A React component to show while the client is initializing. */ loadingComponent?: ReactNode; /** * Set to `true` to show the loading component while the client is initializing. */ initialLoading?: boolean; /** * Set to `true` to enable debug logging to the console, */ debug?: boolean; }; /** * Base init options for the ReflagProvider and ReflagBootstrappedProvider. * @internal */ export type ReflagInitOptionsBase = Omit< InitOptions, "user" | "company" | "other" | "otherContext" | "bootstrappedFlags" >; /** * Map of clients by context key. Used to deduplicate initialization of the client. * @internal */ const reflagClients = new Map<string, ReflagClient>(); /** * Returns the ReflagClient for a given publishable key. * Only creates a new ReflagClient is not already created or if it hook is run on the server. * @internal */ function useReflagClient(initOptions: InitOptions, debug = false) { const isServer = typeof window === "undefined"; if (isServer || !reflagClients.has(initOptions.publishableKey)) { const client = new ReflagClient({ ...initOptions, logger: debug ? console : undefined, sdkVersion: SDK_VERSION, }); if (!isServer) { reflagClients.set(initOptions.publishableKey, client); } return client; } return reflagClients.get(initOptions.publishableKey)!; } type ProviderContextType = { isLoading: boolean; client: ReflagClient; }; const ProviderContext = createContext<ProviderContextType | null>(null); /** * Props for the ReflagClientProvider. */ export type ReflagClientProviderProps = Omit<ReflagPropsBase, "debug"> & { client: ReflagClient; }; export function ReflagClientProvider({ client, loadingComponent, initialLoading = true, children, }: ReflagClientProviderProps) { const [isLoading, setIsLoading] = useState( client.getState() !== "initialized" ? initialLoading : false, ); useOnEvent( "stateUpdated", (state) => { setIsLoading(state === "initializing"); }, client, ); return ( <ProviderContext.Provider value={{ isLoading, client, }} > {isLoading && typeof loadingComponent !== "undefined" ? loadingComponent : children} </ProviderContext.Provider> ); } /** * Props for the ReflagProvider. */ export type ReflagProps = ReflagPropsBase & ReflagInitOptionsBase & { /** * The context to use for the ReflagClient containing user, company, and other context. */ context?: ReflagContext; /** * Company related context. If you provide `id` Reflag will enrich the evaluation context with * company attributes on Reflag servers. * @deprecated Use `context` instead, this property will be removed in the next major version */ company?: CompanyContext; /** * User related context. If you provide `id` Reflag will enrich the evaluation context with * user attributes on Reflag servers. * @deprecated Use `context` instead, this property will be removed in the next major version */ user?: UserContext; /** * Context which is not related to a user or a company. * @deprecated Use `context` instead, this property will be removed in the next major version */ otherContext?: Record<string, string | number | undefined>; }; /** * Provider for the ReflagClient. */ export function ReflagProvider({ children, context, user, company, otherContext, loadingComponent, initialLoading = true, debug, ...config }: ReflagProps) { const resolvedContext = useMemo( () => ({ user, company, other: otherContext, ...context }), [user, company, otherContext, context], ); const client = useReflagClient( { ...config, ...resolvedContext, }, debug, ); // Initialize the client if it is not already initialized useEffect(() => { if (client.getState() !== "idle") return; void client.initialize().catch((e) => { client.logger.error("failed to initialize client", e); }); }, [client]); // Update the context if it changes useEffect(() => { void client.setContext(resolvedContext); }, [client, resolvedContext]); return ( <ReflagClientProvider client={client} initialLoading={initialLoading} loadingComponent={loadingComponent} > {children} </ReflagClientProvider> ); } /** * Props for the ReflagBootstrappedProvider. */ export type ReflagBootstrappedProps = ReflagPropsBase & ReflagInitOptionsBase & { /** * Pre-fetched flags to be used instead of fetching them from the server. */ flags: BootstrappedFlags; }; /** * Bootstrapped Provider for the ReflagClient using pre-fetched flags. */ export function ReflagBootstrappedProvider({ flags, children, loadingComponent, initialLoading = false, debug, ...config }: ReflagBootstrappedProps) { const client = useReflagClient( { ...config, ...flags.context, bootstrappedFlags: flags.flags, }, debug, ); // Initialize the client if it is not already initialized useEffect(() => { if (client.getState() !== "idle") return; void client.initialize().catch((e) => { client.logger.error("failed to initialize client", e); }); }, [client]); // Update the context if it changes on the client side useEffect(() => { void client.setContext(flags.context); }, [client, flags.context]); // Update the bootstrappedFlags if they change on the client side useEffect(() => { client.updateFlags(flags.flags); }, [client, flags.flags]); return ( <ReflagClientProvider client={client} initialLoading={initialLoading} loadingComponent={loadingComponent} > {children} </ReflagClientProvider> ); } export type RequestFeedbackOptions = Omit< RequestFeedbackData, "flagKey" | "featureId" >; /** * @deprecated use `useFlag` instead */ export function useFeature<TKey extends FlagKey>(key: TKey) { return useFlag(key); } /** * Returns the state of a given feature for the current context, e.g. * * ```ts * function HuddleButton() { * const {isEnabled, config: { payload }, track} = useFlag("huddle"); * if (isEnabled) { * return <button onClick={() => track()}>{payload?.buttonTitle ?? "Start Huddle"}</button>; * } * ``` */ export function useFlag<TKey extends FlagKey>(key: TKey): TypedFlags[TKey] { const client = useClient(); const isLoading = useIsLoading(); const [flag, setFlag] = useState(client.getFlag(key)); const track = () => client.track(key); const requestFeedback = (opts: RequestFeedbackOptions) => client.requestFeedback({ ...opts, flagKey: key }); useOnEvent( "flagsUpdated", () => { setFlag(client.getFlag(key)); }, client, ); if (isLoading || !flag) { return { key, isLoading, isEnabled: false, config: { key: undefined, payload: undefined, } as TypedFlags[TKey]["config"], track, requestFeedback, }; } return { key, isLoading, track, requestFeedback, get isEnabled() { return flag.isEnabled ?? false; }, get config() { return flag.config as TypedFlags[TKey]["config"]; }, }; } /** * Returns a function to send an event when a user performs an action * Note: When calling `useTrack`, user/company must already be set. * * ```ts * const track = useTrack(); * track("Started Huddle", { button: "cta" }); * ``` */ export function useTrack() { const client = useClient(); return (eventName: string, attributes?: Record<string, any> | null) => client.track(eventName, attributes); } /** * Returns a function to open up the feedback form * Note: When calling `useRequestFeedback`, user/company must already be set. * * See [link](../../browser-sdk/FEEDBACK.md#reflagclientrequestfeedback-options) for more information * * ```ts * const requestFeedback = useRequestFeedback(); * reflag.requestFeedback({ * flagKey: "file-uploads", * title: "How satisfied are you with file uploads?", * }); * ``` */ export function useRequestFeedback() { const client = useClient(); return (options: RequestFeedbackData) => client.requestFeedback(options); } /** * Returns a function to manually send feedback collected from a user. * Note: When calling `useSendFeedback`, user/company must already be set. * * See [link](./../../browser-sdk/FEEDBACK.md#using-your-own-ui-to-collect-feedback) for more information * * ```ts * const sendFeedback = useSendFeedback(); * sendFeedback({ * flagKey: "huddle"; * question: "How did you like the new huddle feature?"; * score: 5; * comment: "I loved it!"; * }); * ``` */ export function useSendFeedback() { const client = useClient(); return (opts: UnassignedFeedback) => client.feedback(opts); } /** * Returns a function to update the current user's information. * For example, if the user changed role or opted into a beta-feature. * * The method returned is a function which returns a promise that * resolves when after the features have been updated as a result * of the user update. * * ```ts * const updateUser = useUpdateUser(); * updateUser({ optInHuddles: "true" }).then(() => console.log("Flags updated")); * ``` */ export function useUpdateUser() { const client = useClient(); return (opts: { [key: string]: string | number | undefined }) => client.updateUser(opts); } /** * Returns a function to update the current company's information. * For example, if the company changed plan or opted into a beta-feature. * * The method returned is a function which returns a promise that * resolves when after the features have been updated as a result * of the company update. * * ```ts * const updateCompany = useUpdateCompany(); * updateCompany({ plan: "enterprise" }).then(() => console.log("Flags updated")); * ``` */ export function useUpdateCompany() { const client = useClient(); return (opts: { [key: string]: string | number | undefined }) => client.updateCompany(opts); } /** * Returns a function to update the "other" context information. * For example, if the user changed workspace, you can set the workspace id here. * * The method returned is a function which returns a promise that * resolves when after the features have been updated as a result * of the update to the "other" context. * * ```ts * const updateOtherContext = useUpdateOtherContext(); * updateOtherContext({ workspaceId: newWorkspaceId }) * .then(() => console.log("Flags updated")); * ``` */ export function useUpdateOtherContext() { const client = useClient(); return (opts: { [key: string]: string | number | undefined }) => client.updateOtherContext(opts); } /** * Returns the current `ReflagProvider` context. * @internal */ function useSafeContext() { const ctx = useContext(ProviderContext); if (!ctx) { throw new Error( `ReflagProvider is missing. Please ensure your component is wrapped with a ReflagProvider.`, ); } return ctx; } /** * Returns a boolean indicating if the Reflag client is loading. * You can use this to check if the Reflag client is loading at any point in your application. * Initially, the value will be true until the client is initialized. * * @example * ```ts * import { useIsLoading } from '@reflag/react-sdk'; * * const isLoading = useIsLoading(); * * console.log(isLoading); * ``` * * @returns A boolean indicating if the Reflag client is loading. */ export function useIsLoading() { const context = useSafeContext(); return context.isLoading; } /** * Returns the current `ReflagClient` used by the `ReflagProvider`. * * This is useful if you need to access the `ReflagClient` outside of the `ReflagProvider`. * * @example * ```ts * import { useClient } from '@reflag/react-sdk'; * * function App() { * const client = useClient(); * console.log(client.getContext()); * } * ``` * * @returns The `ReflagClient`. */ export function useClient() { const context = useSafeContext(); return context.client; } /** * Attach a callback handler to client events to act on changes. It automatically disposes itself on unmount. * * @example * ```ts * import { useOnEvent } from '@reflag/react-sdk'; * * useOnEvent("flagsUpdated", () => { * console.log("flags updated"); * }); * ``` * * @param event - The event to listen to. * @param handler - The function to call when the event is triggered. * @param client - The Reflag client to listen to. If not provided, the client will be retrieved from the context. */ export function useOnEvent<THookType extends keyof HookArgs>( event: THookType, handler: (arg0: HookArgs[THookType]) => void, client?: ReflagClient, ) { const contextClient = useContext(ProviderContext); const resolvedClient = client ?? contextClient?.client; if (!resolvedClient) { throw new Error( `ReflagProvider is missing and no client was provided. Please ensure your component is wrapped with a ReflagProvider.`, ); } useEffect(() => { return resolvedClient.on(event, handler); }, [resolvedClient, event, handler]); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/reflagcom/bucket-javascript-sdk'

If you have feedback or need assistance with the MCP directory API, please join our Discord server