@flagship.io/react-sdk
Version:
Flagship REACT SDK
209 lines (208 loc) • 8.58 kB
JavaScript
"use client";
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
import { useState, useEffect, useCallback, useMemo, useRef, } from "react";
import { Flagship, DecisionMode, FSSdkStatus, } from "./deps.js";
import { FlagshipContext, initStat } from "./FlagshipContext.js";
import { INTERNAL_EVENTS } from "./internalType.js";
import { version as SDK_VERSION } from "./sdkVersion.js";
import { useNonInitialEffect, logError, extractFlagsMap } from "./utils.js";
import { useLatestRef, shouldRecreateVisitor, updateVisitorData, } from "./hooks.js";
export function FlagshipProvider({ children, envId, apiKey, decisionMode = DecisionMode.DECISION_API, visitorData, loadingComponent, onSdkStatusChanged, onBucketingUpdated, initialCampaigns, initialFlagsData, fetchFlagsOnBucketingUpdated, hitDeduplicationTime = 2, fetchNow = true, language = 1, sdkVersion = SDK_VERSION, onFlagsStatusChanged, shouldSaveInstance, ...props }) {
const flags = useMemo(() => extractFlagsMap(initialFlagsData, initialCampaigns), [initialFlagsData, initialCampaigns]);
const [flagshipState, setFlagshipState] = useState({
...initStat,
flags,
hasVisitorData: !!visitorData,
});
const [lastModified, setLastModified] = useState();
const flagshipVisitorRef = useRef();
useEffect(() => {
flagshipVisitorRef.current = flagshipState.visitor;
}, [flagshipState.visitor]);
const propsRef = useLatestRef(props);
const configRef = useLatestRef({
fetchNow,
hitDeduplicationTime,
language,
sdkVersion,
});
const callbacksRef = useLatestRef({
onSdkStatusChanged,
onBucketingUpdated,
onFlagsStatusChanged,
});
const visitorDataMemo = useMemo(() => visitorData, [
visitorData?.id,
visitorData?.isAuthenticated,
visitorData?.hasConsented,
JSON.stringify(visitorData?.context),
]);
// #region functions
const handleBucketingUpdate = useCallback((lastUpdate) => {
if (callbacksRef.current.onBucketingUpdated) {
callbacksRef.current.onBucketingUpdated(lastUpdate);
}
setLastModified(lastUpdate);
}, []);
const handleFlagsStatusChange = useCallback(({ status, reason }) => {
callbacksRef.current.onFlagsStatusChanged?.({ status, reason });
setFlagshipState((currentState) => ({
...currentState,
flagsStatus: { status, reason },
}));
}, []);
const initializeVisitorState = useCallback((param) => {
setFlagshipState((currentState) => ({
...currentState,
visitor: param.fsVisitor,
config: Flagship.getConfig(),
isInitializing: false,
hasVisitorData: !!visitorDataMemo,
sdkStatus: param.sdkStatus,
}));
}, [!!visitorDataMemo]);
const handleVisitorReady = useCallback((fsVisitor, error, sdkStatus) => {
if (error) {
logError(Flagship.getConfig(), error.message || error, "onReady");
}
initializeVisitorState({ fsVisitor, sdkStatus });
}, [initializeVisitorState]);
const createFlagshipVisitor = useCallback((sdkStatus) => {
if (!visitorDataMemo) {
return;
}
const fsVisitor = Flagship.newVisitor({
visitorId: visitorDataMemo.id,
context: visitorDataMemo.context,
isAuthenticated: visitorDataMemo.isAuthenticated,
hasConsented: visitorDataMemo.hasConsented,
initialCampaigns,
initialFlagsData,
onFlagsStatusChanged: handleFlagsStatusChange,
shouldSaveInstance,
});
fsVisitor?.on("ready", (error) => {
handleVisitorReady(fsVisitor, error, sdkStatus);
});
if (!fetchNow) {
initializeVisitorState({ fsVisitor, sdkStatus });
}
}, [
initialCampaigns,
initialFlagsData,
handleFlagsStatusChange,
shouldSaveInstance,
fetchNow,
handleVisitorReady,
initializeVisitorState,
visitorDataMemo,
]);
const handleSdkStatusChange = useCallback((sdkStatus) => {
if (callbacksRef.current.onSdkStatusChanged) {
callbacksRef.current.onSdkStatusChanged(sdkStatus);
}
switch (sdkStatus) {
case FSSdkStatus.SDK_PANIC:
case FSSdkStatus.SDK_INITIALIZED:
createFlagshipVisitor(sdkStatus);
break;
case FSSdkStatus.SDK_NOT_INITIALIZED:
setFlagshipState((prev) => ({
...prev,
config: Flagship.getConfig(),
isInitializing: false,
sdkStatus,
}));
break;
}
}, [createFlagshipVisitor]);
const handleSdkStatusChangeRef = useLatestRef(handleSdkStatusChange);
const handleBucketingUpdateRef = useLatestRef(handleBucketingUpdate);
const initializeFlagshipSdk = useCallback(() => {
Flagship.start(envId, apiKey, {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
decisionMode: decisionMode,
fetchNow: configRef.current.fetchNow,
onSdkStatusChanged: (...args) => handleSdkStatusChangeRef.current(...args),
onBucketingUpdated: (...args) => handleBucketingUpdateRef.current(...args),
hitDeduplicationTime: configRef.current.hitDeduplicationTime,
language: configRef.current.language,
sdkVersion: configRef.current.sdkVersion,
...propsRef.current,
});
}, [envId, apiKey, decisionMode, props.isQAModeEnabled]);
const updateFlagshipVisitor = useCallback(() => {
if (!visitorDataMemo ||
Flagship.getStatus() !== FSSdkStatus.SDK_INITIALIZED) {
return;
}
const currentVisitor = flagshipVisitorRef.current;
if (shouldRecreateVisitor(currentVisitor, visitorDataMemo.id, visitorDataMemo.isAuthenticated)) {
currentVisitor?.cleanup();
createFlagshipVisitor(Flagship.getStatus());
return;
}
// Update existing visitor
if (currentVisitor) {
updateVisitorData(currentVisitor, visitorDataMemo.id, visitorDataMemo.context, visitorDataMemo.hasConsented, visitorDataMemo.isAuthenticated);
}
}, [createFlagshipVisitor, visitorDataMemo]);
// #endregion
useNonInitialEffect(() => {
if (fetchFlagsOnBucketingUpdated) {
flagshipVisitorRef.current?.fetchFlags();
}
}, [lastModified, fetchFlagsOnBucketingUpdated]);
useNonInitialEffect(() => {
updateFlagshipVisitor();
}, [updateFlagshipVisitor]);
useEffect(() => {
initializeFlagshipSdk();
}, [initializeFlagshipSdk]);
useEffect(() => {
return () => {
flagshipVisitorRef.current?.cleanup();
Flagship.close();
};
}, []);
const handleDisplay = useMemo(() => {
const isFirstInit = !flagshipState.visitor;
if (flagshipState.isInitializing &&
loadingComponent &&
isFirstInit &&
fetchNow) {
return _jsx(_Fragment, { children: loadingComponent });
}
return _jsx(_Fragment, { children: children });
}, [
flagshipState.isInitializing,
flagshipState.visitor,
loadingComponent,
fetchNow,
children,
]);
const handleForcedVariations = useCallback((e) => {
const { detail } = e;
if (detail.forcedReFetchFlags) {
flagshipVisitorRef.current?.fetchFlags();
}
else {
setFlagshipState((state) => ({
...state,
toggleForcedVariations: !state.toggleForcedVariations,
}));
}
}, []);
useEffect(() => {
window?.addEventListener?.(INTERNAL_EVENTS.FsTriggerRendering, handleForcedVariations);
globalThis.__abTastyOnTriggerRender__ = (arg) => {
const event = {
detail: arg,
};
handleForcedVariations(event);
};
return () => window?.removeEventListener?.(INTERNAL_EVENTS.FsTriggerRendering, handleForcedVariations);
}, [handleForcedVariations]);
const contextValue = useMemo(() => ({ state: flagshipState, setState: setFlagshipState }), [flagshipState]);
return (_jsx(FlagshipContext.Provider, { value: contextValue, children: handleDisplay }));
}