@flagship.io/react-sdk
Version:
Flagship REACT SDK
157 lines (156 loc) • 6.21 kB
JavaScript
'use client';
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
import { useState, useRef, useEffect } from 'react';
import Flagship, { DecisionMode, FSSdkStatus } from '@flagship.io/js-sdk';
import { FlagshipContext, initStat } from './FlagshipContext';
import { INTERNAL_EVENTS } from './internalType';
import { version as SDK_VERSION } from './sdkVersion';
import { useNonInitialEffect, logError, extractFlagsMap } from './utils';
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 = extractFlagsMap(initialFlagsData, initialCampaigns);
const [state, setState] = useState({
...initStat,
flags,
hasVisitorData: !!visitorData
});
const [lastModified, setLastModified] = useState();
const stateRef = useRef();
stateRef.current = state;
const visitorDataRef = useRef(visitorData);
// #region functions
const onBucketingLastModified = (lastUpdate) => {
if (onBucketingUpdated) {
onBucketingUpdated(lastUpdate);
}
setLastModified(lastUpdate);
};
const statusChanged = (status) => {
if (onSdkStatusChanged) {
onSdkStatusChanged(status);
}
switch (status) {
case FSSdkStatus.SDK_PANIC:
case FSSdkStatus.SDK_INITIALIZED:
createVisitor();
break;
case FSSdkStatus.SDK_NOT_INITIALIZED:
setState((prev) => ({
...prev,
config: Flagship.getConfig(),
isInitializing: false
}));
break;
}
};
const initSdk = () => {
Flagship.start(envId, apiKey, {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
decisionMode: decisionMode,
fetchNow,
onSdkStatusChanged: statusChanged,
onBucketingUpdated: onBucketingLastModified,
hitDeduplicationTime,
language,
sdkVersion,
...props
});
};
function initializeState(param) {
setState((currentState) => ({
...currentState,
visitor: param.fsVisitor,
config: Flagship.getConfig(),
isInitializing: false,
hasVisitorData: !!visitorData
}));
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const onVisitorReady = (fsVisitor, error) => {
if (error) {
logError(Flagship.getConfig(), error.message || error, 'onReady');
}
initializeState({ fsVisitor });
};
const createVisitor = () => {
if (!visitorDataRef.current) {
return;
}
const fsVisitor = Flagship.newVisitor({
visitorId: visitorDataRef.current.id,
context: visitorDataRef.current.context,
isAuthenticated: visitorDataRef.current.isAuthenticated,
hasConsented: visitorDataRef.current.hasConsented,
initialCampaigns,
initialFlagsData,
onFlagsStatusChanged,
shouldSaveInstance
});
fsVisitor?.on('ready', (error) => {
onVisitorReady(fsVisitor, error);
});
if (!fetchNow) {
initializeState({ fsVisitor });
}
};
function updateVisitor() {
if (!visitorDataRef.current || Flagship.getStatus() !== FSSdkStatus.SDK_INITIALIZED) {
return;
}
if (!state.visitor ||
(state.visitor.visitorId !== visitorDataRef.current.id &&
(!visitorDataRef.current.isAuthenticated || (visitorDataRef.current.isAuthenticated && state.visitor.anonymousId)))) {
state.visitor?.cleanup();
createVisitor();
return;
}
if (visitorDataRef.current.hasConsented !== state.visitor.hasConsented) {
state.visitor.setConsent(visitorDataRef.current.hasConsented ?? true);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
state.visitor.updateContext(visitorDataRef.current.context);
if (!state.visitor.anonymousId && visitorDataRef.current.isAuthenticated) {
state.visitor.authenticate(visitorDataRef.current.id);
}
if (state.visitor.anonymousId && !visitorDataRef.current.isAuthenticated) {
state.visitor.unauthenticate();
}
state.visitor.fetchFlags();
}
// #endregion
useNonInitialEffect(() => {
if (fetchFlagsOnBucketingUpdated) {
state.visitor?.fetchFlags();
}
}, [lastModified]);
useNonInitialEffect(() => {
visitorDataRef.current = visitorData;
updateVisitor();
}, [JSON.stringify(visitorData)]);
useEffect(() => {
initSdk();
}, [envId, apiKey, decisionMode]);
const handleDisplay = () => {
const isFirstInit = !state.visitor;
if (state.isInitializing &&
loadingComponent &&
isFirstInit &&
fetchNow) {
return _jsx(_Fragment, { children: loadingComponent });
}
return _jsx(_Fragment, { children: children });
};
useEffect(() => {
window?.addEventListener?.(INTERNAL_EVENTS.FsTriggerRendering, onVariationsForced);
return () => window?.removeEventListener?.(INTERNAL_EVENTS.FsTriggerRendering, onVariationsForced);
}, [state.config?.isQAModeEnabled]);
const onVariationsForced = (e) => {
const { detail } = e;
if (detail.forcedReFetchFlags) {
stateRef.current?.visitor?.fetchFlags();
}
else {
setState(state => ({ ...state, toggleForcedVariations: !state.toggleForcedVariations }));
}
};
return (_jsx(FlagshipContext.Provider, { value: { state, setState }, children: handleDisplay() }));
}