From 1c5ae0afebf89ee31e8f853d7c9bd520b9ad68ef Mon Sep 17 00:00:00 2001 From: Frank von Hoven Date: Fri, 12 Jul 2024 17:16:54 -0500 Subject: [PATCH] feat: create redux slice for featureFlags --- app/core/redux/slices/featureFlags/index.ts | 65 +++++++++++++++++++++ app/reducers/index.ts | 5 ++ app/store/index.ts | 3 + app/store/sagas/index.ts | 38 ++++++++++++ 4 files changed, 111 insertions(+) create mode 100644 app/core/redux/slices/featureFlags/index.ts diff --git a/app/core/redux/slices/featureFlags/index.ts b/app/core/redux/slices/featureFlags/index.ts new file mode 100644 index 00000000000..d6b07466ba4 --- /dev/null +++ b/app/core/redux/slices/featureFlags/index.ts @@ -0,0 +1,65 @@ +import { PayloadAction, createSlice } from '@reduxjs/toolkit'; + +export interface FeatureFlagsState { + featureFlags: object[] | null; + loading: boolean; + error: string | null; +} + +export const initialState: FeatureFlagsState = { + featureFlags: null, + loading: false, + error: null, +}; + +const name = 'featureFlags'; + +const slice = createSlice({ + name, + initialState, + reducers: { + /** + * Initiates the fetching of feature flags. + * @param state - The current state of the featureFlags slice. + */ + getFeatureFlags: (state: FeatureFlagsState) => { + state.loading = true; + state.error = null; + }, + /** + * Handles the successful fetching of feature flags. + * @param state - The current state of the featureFlags slice. + * @param action - An action with the fetched feature flags as payload. + */ + getFeatureFlagsSuccess: ( + state: FeatureFlagsState, + action: PayloadAction, + ) => { + state.featureFlags = action.payload; + state.loading = false; + state.error = null; + }, + /** + * Handles errors that occur during the fetching of feature flags. + * @param state - The current state of the featureFlags slice. + * @param action - An action with the error message as payload. + */ + getFeatureFlagsError: ( + state: FeatureFlagsState, + action: PayloadAction, + ) => { + state.loading = false; + state.error = action.payload; + }, + }, +}); + +const { actions, reducer } = slice; + +export default reducer; + +export const { getFeatureFlags, getFeatureFlagsSuccess, getFeatureFlagsError } = + actions; + +export const FETCH_FEATURE_FLAGS = 'getFeatureFlags'; +export type FETCH_FEATURE_FLAGS = typeof FETCH_FEATURE_FLAGS; diff --git a/app/reducers/index.ts b/app/reducers/index.ts index 45ff0dc3d38..4651d466edf 100644 --- a/app/reducers/index.ts +++ b/app/reducers/index.ts @@ -1,6 +1,7 @@ import bookmarksReducer from './bookmarks'; import browserReducer from './browser'; import engineReducer from '../core/redux/slices/engine'; +import featureFlagsReducer from '../core/redux/slices/featureFlags'; import privacyReducer from './privacy'; import modalsReducer from './modals'; import settingsReducer from './settings'; @@ -56,6 +57,9 @@ export interface RootState { engine: { backgroundState: EngineState }; // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any + featureFlags: any; + // TODO: Replace "any" with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any privacy: any; // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -132,6 +136,7 @@ const rootReducer = combineReducers({ // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any engine: engineReducer as any, + featureFlags: featureFlagsReducer, privacy: privacyReducer, bookmarks: bookmarksReducer, browser: browserReducer, diff --git a/app/store/index.ts b/app/store/index.ts index a109632138d..f80cb977940 100644 --- a/app/store/index.ts +++ b/app/store/index.ts @@ -66,6 +66,9 @@ const createStoreAndPersistor = async () => { basicFunctionalityEnabled: store.getState().settings.basicFunctionalityEnabled, }); + store.dispatch({ + type: 'FETCH_FEATURE_FLAGS', + }); EngineService.initalizeEngine(store); Authentication.init(store); LockManagerService.init(store); diff --git a/app/store/sagas/index.ts b/app/store/sagas/index.ts index 7c88acf436a..2f9ab01b393 100644 --- a/app/store/sagas/index.ts +++ b/app/store/sagas/index.ts @@ -16,6 +16,10 @@ import Logger from '../../util/Logger'; import LockManagerService from '../../core/LockManagerService'; import AppConstants from '../../../app/core/AppConstants'; import { XMLHttpRequest as _XMLHttpRequest } from 'xhr2'; +import { + getFeatureFlagsSuccess, + getFeatureFlagsError, +} from '../../core/redux/slices/featureFlags'; if (typeof global.XMLHttpRequest === 'undefined') { global.XMLHttpRequest = _XMLHttpRequest; @@ -24,6 +28,18 @@ if (typeof global.XMLHttpRequest === 'undefined') { const originalSend = XMLHttpRequest.prototype.send; const originalOpen = XMLHttpRequest.prototype.open; +interface GenericObject { + [key: string]: unknown; +} + +type DataArray = T[]; + +interface FeatureFlagResponse { + status: string; + data: DataArray; + message?: string; +} + export function* appLockStateMachine() { let biometricsListenerTask: Task | undefined; while (true) { @@ -166,8 +182,30 @@ export function* basicFunctionalityToggle() { } } +function* fetchFeatureFlags(): Generator { + try { + let response: FeatureFlagResponse = { + status: '', + data: [], + }; + yield fetch('http://localhost:3000/launch-darkly') + .then((res) => res.json()) + .then((res) => (response = res)); + + if (response.status !== 'ok') { + yield put(getFeatureFlagsError(response.message)); + return; + } + yield put(getFeatureFlagsSuccess(response.data)); + } catch (error) { + console.error(error); + yield put(getFeatureFlagsError(error)); + } +} + // Main generator function that initializes other sagas in parallel. export function* rootSaga() { yield fork(authStateMachine); yield fork(basicFunctionalityToggle); + yield fork(fetchFeatureFlags); }