From 4aa5ab86c8ab2be6a1b762f9af553d7cfacaffcb Mon Sep 17 00:00:00 2001 From: Antonio <34042064+Desvelao@users.noreply.github.com> Date: Tue, 7 Jan 2025 15:13:14 +0100 Subject: [PATCH] Add frontend state service (#7037) * feat(state): create state core service - Create state service based on AppState from main plugin - Create state containers - server_host - server_host_cluster_info - data_source_alerts - Add documentation for state service * chore: remove unused methods and no-effect functions - Remove unused methods: - AppState.setCreatedAt - AppState.getCreatedAt - AppState.getAPISelector - AppState.getPatternSelector - AppState.setPatternSelector (no-effect) - Remove usage of AppState.setPatternSelector because this has no effect in the current application * feat(state): add tests * feat(state): wrap the subscrition handler to allow they can unsubscribed when the plugin stops - Remove console.log - Dispatch new values when remove from the state containers * fix(state): enhance README.md examples * Apply suggestions from code review Co-authored-by: Guido Modarelli <38738725+guidomodarelli@users.noreply.github.com> * fix(state): typos * fix(state): minor fixes * fix(state): unsubscribe from state container * fix: upgrade react-cookie dependency to remove vulnerability in package * Apply suggestions from code review Co-authored-by: Guido Modarelli <38738725+guidomodarelli@users.noreply.github.com> * fix(state): lint and prettier * fix(state): lint and prettier * fix(core): dependencies * fix(settings): improve types for SettingsComponent props * fix(eslint): disable unicorn/no-static-only-class rule * fix(types): improve type annotations for decoratorCheckIsEnabled callback function in index-patterns.ts * fix(eslint): remove redundant eslint directive for unicorn/no-static-only-class in app-state.js * fix(types): set default type parameters for State and LifecycleService interfaces in state and services types files * fix(eslint): remove unnecessary eslint directive for unicorn/no-static-only-class in wz-api-check.js * fix(eslint): add consistent-function-scoping rule to enforce function scoping standards across the project * fix(eslint): replace Promise.reject with throw for error handling in wz-api-check.js to improve code readability and flow * fix(eslint): update type definition for WrappedComponent in createHOCs for better clarity and type safety in creator.tsx * fix(types): update remove method type in StateContainer for improved type safety in state management functionalities * fix(types): update createHooks to handle optional updater$ for better robustness in state management functionality * fix(logging): improve error handling by explicitly casting error to Error for better clarity in data source alerts handling * fix(logging): enhance error logging by explicitly casting to Error for clearer messages in server host cluster info handling * fix(types): improve typing for set method and enhance error logging clarity with explicit Error casting in server host state management * fix: optimizing error due to usage of number separator * feat(state): replace state getters in http server * feat(state): enhance state containers emitting the errors * feat(state): enhance types * fix: typo * fix(initialization): get username in user endpoint --------- Co-authored-by: Guido Modarelli <38738725+guidomodarelli@users.noreply.github.com> Co-authored-by: Guido Modarelli --- .eslintrc.js | 7 + .../container/health-check.container.test.tsx | 22 +- .../container/health-check.container.tsx | 169 ++++--- .../public/components/settings/settings.tsx | 135 +++-- .../main/public/react-services/app-state.js | 153 ++---- .../public/react-services/wz-api-check.js | 95 ++-- plugins/wazuh-core/package.json | 3 +- plugins/wazuh-core/public/plugin.ts | 47 +- .../public/services/http/server-client.ts | 15 - .../public/services/state/README.md | 86 ++++ .../state/containers/data-source-alerts.ts | 116 +++++ .../containers/server-host-cluster-info.ts | 88 ++++ .../services/state/containers/server-host.ts | 87 ++++ .../public/services/state/hocs/creator.tsx | 22 + .../public/services/state/hooks/creator.ts | 25 + .../wazuh-core/public/services/state/index.ts | 2 + .../public/services/state/state.test.ts | 104 ++++ .../wazuh-core/public/services/state/state.ts | 81 +++ .../wazuh-core/public/services/state/types.ts | 51 ++ plugins/wazuh-core/public/services/types.ts | 12 + plugins/wazuh-core/public/types.ts | 15 +- .../server/initialization/index-patterns.ts | 7 +- .../server/services/initialization/routes.ts | 8 +- plugins/wazuh-core/yarn.lock | 464 +++++++++++++++++- 24 files changed, 1492 insertions(+), 322 deletions(-) create mode 100644 plugins/wazuh-core/public/services/state/README.md create mode 100644 plugins/wazuh-core/public/services/state/containers/data-source-alerts.ts create mode 100644 plugins/wazuh-core/public/services/state/containers/server-host-cluster-info.ts create mode 100644 plugins/wazuh-core/public/services/state/containers/server-host.ts create mode 100644 plugins/wazuh-core/public/services/state/hocs/creator.tsx create mode 100644 plugins/wazuh-core/public/services/state/hooks/creator.ts create mode 100644 plugins/wazuh-core/public/services/state/index.ts create mode 100644 plugins/wazuh-core/public/services/state/state.test.ts create mode 100644 plugins/wazuh-core/public/services/state/state.ts create mode 100644 plugins/wazuh-core/public/services/state/types.ts create mode 100644 plugins/wazuh-core/public/services/types.ts diff --git a/.eslintrc.js b/.eslintrc.js index 84f328f7c2..52b51c1614 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -169,6 +169,13 @@ module.exports = { /* -------------------------------------------------------------------------- */ /* unicorn */ /* -------------------------------------------------------------------------- */ + 'unicorn/consistent-function-scoping': [ + 'error', + { + checkArrowFunctions: false, + }, + ], + 'unicorn/no-static-only-class': 'off', 'unicorn/prefer-module': 'off', 'unicorn/prefer-ternary': 'off', 'unicorn/numeric-separators-style': 'off', diff --git a/plugins/main/public/components/health-check/container/health-check.container.test.tsx b/plugins/main/public/components/health-check/container/health-check.container.test.tsx index 8ef5f8458c..90ee7d2e06 100644 --- a/plugins/main/public/components/health-check/container/health-check.container.test.tsx +++ b/plugins/main/public/components/health-check/container/health-check.container.test.tsx @@ -53,14 +53,14 @@ jest.mock('../../../components/common/hooks', () => ({ })); jest.mock('../services', () => ({ - checkPatternService: appInfo => () => undefined, - checkTemplateService: appInfo => () => undefined, - checkApiService: appInfo => () => undefined, - checkSetupService: appInfo => () => undefined, - checkFieldsService: appInfo => () => undefined, - checkPluginPlatformSettings: appInfo => () => undefined, - checkPatternSupportService: appInfo => () => undefined, - checkIndexPatternService: appInfo => () => undefined, + checkPatternService: appInfo => () => {}, + checkTemplateService: appInfo => () => {}, + checkApiService: appInfo => () => {}, + checkSetupService: appInfo => () => {}, + checkFieldsService: appInfo => () => {}, + checkPluginPlatformSettings: appInfo => () => {}, + checkPatternSupportService: appInfo => () => {}, + checkIndexPatternService: appInfo => () => {}, })); jest.mock('../components/check-result', () => ({ @@ -68,9 +68,7 @@ jest.mock('../components/check-result', () => ({ })); jest.mock('../../../react-services', () => ({ - AppState: { - setPatternSelector: () => {}, - }, + AppState: {}, ErrorHandler: { handle: error => error, }, @@ -123,6 +121,7 @@ describe('Health Check container', () => { ]); // invoke is wrapped with act to await for setState const callOutError = component.find('EuiCallOut'); + expect(callOutError.text()).toBe('[API version] Test error'); }); @@ -134,6 +133,7 @@ describe('Health Check container', () => { ]); const callOutWarning = component.find('EuiCallOut'); + expect(callOutWarning.text()).toBe('[API version] Test warning'); }); }); diff --git a/plugins/main/public/components/health-check/container/health-check.container.tsx b/plugins/main/public/components/health-check/container/health-check.container.tsx index 009d03139f..91df644add 100644 --- a/plugins/main/public/components/health-check/container/health-check.container.tsx +++ b/plugins/main/public/components/health-check/container/health-check.container.tsx @@ -17,10 +17,12 @@ import { EuiCallOut, EuiDescriptionList, EuiSpacer, + EuiFlexGroup, + EuiFlexItem, } from '@elastic/eui'; import React, { Fragment, useState, useEffect, useRef } from 'react'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { AppState, ErrorHandler } from '../../../react-services'; +import { compose } from 'redux'; +import { ErrorHandler } from '../../../react-services'; import { useAppConfig, useRouterSearch, @@ -33,7 +35,7 @@ import { } from '../services'; import { CheckResult } from '../components/check-result'; import { withErrorBoundary, withRouteResolvers } from '../../common/hocs'; -import { getCore, getHttp, getWzCurrentAppID } from '../../../kibana-services'; +import { getCore, getHttp } from '../../../kibana-services'; import { HEALTH_CHECK_REDIRECTION_TIME, WAZUH_INDEX_TYPE_MONITORING, @@ -43,7 +45,6 @@ import { getThemeAssetURL, getAssetURL } from '../../../utils/assets'; import { serverApis } from '../../../utils/applications'; import { RedirectAppLinks } from '../../../../../../src/plugins/opensearch_dashboards_react/public'; import { ip, wzConfig } from '../../../services/resolves'; -import { compose } from 'redux'; import NavigationService from '../../../react-services/navigation-service'; const checks = { @@ -94,12 +95,51 @@ const checks = { }, }; +const addTagsToUrl = (error: string) => { + const words = error.split(' '); + + for (const [index, word] of words.entries()) { + if (word.includes('http://') || word.includes('https://')) { + if (words[index - 1] === 'guide:') { + if (word.endsWith('.') || word.endsWith(',')) { + words[index - 2] = `${ + words[index - 2] + } ${words[index - 1].slice(0, -1)}${word.slice(-1)}`; + } else { + words[index - 2] = + `${ + words[index - 2] + } ${words[index - 1].slice(0, -1)} `; + } + + words.splice(index - 1, 2); + } else { + if (word.endsWith('.') || word.endsWith(',')) { + words[index] = `${word.slice( + 0, + -1, + )}${word.slice(-1)}`; + } else { + words[index] = + `${word}`; + } + } + } + } + + return words.join(' '); +}; + function HealthCheckComponent() { - const [checkWarnings, setCheckWarnings] = useState<{ [key: string]: [] }>({}); - const [checkErrors, setCheckErrors] = useState<{ [key: string]: [] }>({}); - const [checksReady, setChecksReady] = useState<{ [key: string]: boolean }>( - {}, - ); + const [checkWarnings, setCheckWarnings] = useState>({}); + const [checkErrors, setCheckErrors] = useState>({}); + const [checksReady, setChecksReady] = useState>({}); const [isDebugMode, setIsDebugMode] = useState(false); const appConfig = useAppConfig(); const checksInitiated = useRef(false); @@ -119,6 +159,7 @@ function HealthCheckComponent() { .pathname + '?' + searchParams; + NavigationService.getInstance().navigate(relativePath); } else { NavigationService.getInstance().navigate('/'); @@ -131,25 +172,23 @@ function HealthCheckComponent() { useEffect(() => { if (appConfig.isReady && !checksInitiated.current) { checksInitiated.current = true; - AppState.setPatternSelector(appConfig.data['ip.selector']); } }, [appConfig]); useEffect(() => { // Redirect to app when all checks are ready - Object.keys(checks).every(check => checksReady[check]) && + if ( + Object.keys(checks).every(check => checksReady[check]) && !isDebugMode && - !thereAreWarnings && - (() => - setTimeout( - redirectionPassHealthcheck, - HEALTH_CHECK_REDIRECTION_TIME, - ))(); + !thereAreWarnings + ) { + setTimeout(redirectionPassHealthcheck, HEALTH_CHECK_REDIRECTION_TIME); + } }, [checksReady]); useEffect(() => { // Check if Health should not redirect automatically (Debug mode) - setIsDebugMode(typeof search.debug !== 'undefined'); + setIsDebugMode(search.debug !== undefined); }, []); const handleWarnings = (checkID, warnings, parsed) => { @@ -161,6 +200,7 @@ function HealthCheckComponent() { }), ) : warnings; + setCheckWarnings(prev => ({ ...prev, [checkID]: newWarnings })); }; @@ -173,6 +213,7 @@ function HealthCheckComponent() { }), ) : errors; + setCheckErrors(prev => ({ ...prev, [checkID]: newErrors })); }; @@ -199,73 +240,32 @@ function HealthCheckComponent() { const renderChecks = () => { const showLogButton = thereAreErrors || thereAreWarnings || isDebugMode; - return Object.keys(checks).map((check, index) => { - return ( - - ); - }); - }; - const addTagsToUrl = error => { - const words = error.split(' '); - words.forEach((word, index) => { - if (word.includes('http://') || word.includes('https://')) { - if (words[index - 1] === 'guide:') { - if (word.endsWith('.') || word.endsWith(',')) { - words[index - 2] = `${ - words[index - 2] - } ${words[index - 1].slice(0, -1)}${word.slice(-1)}`; - } else { - words[ - index - 2 - ] = `${ - words[index - 2] - } ${words[index - 1].slice(0, -1)} `; - } - words.splice(index - 1, 2); - } else { - if (word.endsWith('.') || word.endsWith(',')) { - words[index] = `${word.slice( - 0, - -1, - )}${word.slice(-1)}`; - } else { - words[ - index - ] = `${word}`; - } + return Object.keys(checks).map(check => ( + + )); }; - const renderWarnings = () => { - return Object.keys(checkWarnings).map(checkID => + const renderWarnings = () => + Object.keys(checkWarnings).map(checkID => checkWarnings[checkID].map((warning, index) => ( )), ); - }; - - const renderErrors = () => { - return Object.keys(checkErrors).map(checkID => + const renderErrors = () => + Object.keys(checkErrors).map(checkID => checkErrors[checkID].map((error, index) => ( )), ); - }; return (
diff --git a/plugins/main/public/components/settings/settings.tsx b/plugins/main/public/components/settings/settings.tsx index 418de2ed08..77d8357994 100644 --- a/plugins/main/public/components/settings/settings.tsx +++ b/plugins/main/public/components/settings/settings.tsx @@ -11,6 +11,8 @@ */ import React from 'react'; import { EuiProgress, EuiTabs, EuiTab } from '@elastic/eui'; +import { compose } from 'redux'; +import { connect } from 'react-redux'; import { AppState } from '../../react-services/app-state'; import { GenericRequest } from '../../react-services/generic-request'; import { WzMisc } from '../../factories/misc'; @@ -33,9 +35,7 @@ import { serverApis, appSettings, } from '../../utils/applications'; -import { compose } from 'redux'; import { withErrorBoundary, withRouteResolvers } from '../common/hocs'; -import { connect } from 'react-redux'; import { enableMenu, ip, @@ -46,29 +46,25 @@ import { Route, Switch } from '../router-search'; import { useRouterSearch } from '../common/hooks'; import NavigationService from '../../react-services/navigation-service'; -const configurationTabID = 'configuration'; - +const CONFIGURATION_TAB_ID = 'configuration'; const mapStateToProps = state => ({ configurationUIEditable: state.appConfig.data['configuration.ui_api_editable'], configurationIPSelector: state.appConfig.data['ip.selector'], }); - const mapDispatchToProps = dispatch => ({ updateGlobalBreadcrumb: breadcrumb => dispatch(updateGlobalBreadcrumb(breadcrumb)), }); -export const Settings = compose( - withErrorBoundary, - withRouteResolvers({ enableMenu, ip, nestedResolve, savedSearch }), - connect(mapStateToProps, mapDispatchToProps), -)(props => { - const { tab } = useRouterSearch(); - return ; -}); +interface SettingsComponentProps { + configurationUIEditable: boolean; + configurationIPSelector: string; + updateGlobalBreadcrumb: (breadcrumb: string) => void; + tab: string; +} -class SettingsComponent extends React.Component { +class SettingsComponent extends React.Component { state: { tabs: { id: string; name: string }[] | null; load: boolean; @@ -76,22 +72,17 @@ class SettingsComponent extends React.Component { indexPatterns; apiEntries; }; - wzMisc: WzMisc; - tabsConfiguration: { id: string; name: string }[]; - apiIsDown; - googleGroupsSVG; - currentDefault; - appInfo; - constructor(props) { + constructor(props: SettingsComponentProps) { super(props); this.wzMisc = new WzMisc(); if (this.wzMisc.getWizard()) { - window.sessionStorage.removeItem('healthCheck'); + globalThis.sessionStorage.removeItem('healthCheck'); this.wzMisc.setWizard(false); } + this.apiIsDown = this.wzMisc.getApiIsDown(); this.state = { currentApiEntryIndex: false, @@ -105,7 +96,7 @@ class SettingsComponent extends React.Component { getAssetURL('images/icons/google_groups.svg'), ); this.tabsConfiguration = [ - { id: configurationTabID, name: 'Configuration' }, + { id: CONFIGURATION_TAB_ID, name: 'Configuration' }, { id: 'miscellaneous', name: 'Miscellaneous' }, ]; } @@ -119,9 +110,11 @@ class SettingsComponent extends React.Component { ({ id }) => getWzCurrentAppID() === id, ).breadcrumbLabel; const breadcrumb = [{ text: tabActiveName }]; + this.props.updateGlobalBreadcrumb(breadcrumb); } else { const breadcrumb = [{ text: serverApis.breadcrumbLabel }]; + this.props.updateGlobalBreadcrumb(breadcrumb); } @@ -134,6 +127,7 @@ class SettingsComponent extends React.Component { await this.getAppInfo(); } catch (error) { const options = { + // eslint-disable-next-line no-use-before-define context: `${Settings.name}.onInit`, level: UI_LOGGER_LEVELS.ERROR, severity: UI_ERROR_SEVERITIES.BUSINESS, @@ -144,32 +138,44 @@ class SettingsComponent extends React.Component { title: `${error.name}: Cannot initialize Settings`, }, }; + getErrorOrchestrator().handleError(options); } } + wzMisc: WzMisc; + tabsConfiguration: { id: string; name: string }[]; + apiIsDown; + googleGroupsSVG; + currentDefault; + appInfo; + isConfigurationUIEditable() { return this.props.configurationUIEditable; } + /** * Sets the component props */ - setComponentProps(currentTab: string = 'api') { + setComponentProps(currentTab = 'api') { const isConfigurationUIEditable = this.isConfigurationUIEditable(); - if (currentTab === configurationTabID && !isConfigurationUIEditable) { + + if (currentTab === CONFIGURATION_TAB_ID && !isConfigurationUIEditable) { // Change the inaccessible configuration to another accessible NavigationService.getInstance().replace( `/settings?tab=${ - this.tabsConfiguration.find(({ id }) => id !== configurationTabID)!.id + this.tabsConfiguration.find(({ id }) => id !== CONFIGURATION_TAB_ID) + .id }`, ); } + this.setState({ tabs: getWzCurrentAppID() === appSettings.id ? // WORKAROUND: This avoids the configuration tab is displayed this.tabsConfiguration.filter(({ id }) => - !isConfigurationUIEditable ? id !== configurationTabID : true, + isConfigurationUIEditable ? true : id !== CONFIGURATION_TAB_ID, ) : null, }); @@ -177,10 +183,11 @@ class SettingsComponent extends React.Component { // Get current API index getCurrentAPIIndex() { - if (this.state.apiEntries.length) { + if (this.state.apiEntries.length > 0) { const idx = this.state.apiEntries .map(entry => entry.id) .indexOf(this.currentDefault); + this.setState({ currentApiEntryIndex: idx }); } } @@ -200,9 +207,10 @@ class SettingsComponent extends React.Component { this.setState({ indexPatterns: await SavedObject.getListOfWazuhValidIndexPatterns(), }); - } catch (error) { + } catch { this.wzMisc.setBlankScr('Sorry but no valid index patterns were found'); NavigationService.getInstance().navigate('/blank-screen'); + return; } @@ -212,8 +220,10 @@ class SettingsComponent extends React.Component { if (currentApi) { const { id } = JSON.parse(currentApi); + this.currentDefault = id; } + this.getCurrentAPIIndex(); // TODO: what is the purpose of this? @@ -225,6 +235,7 @@ class SettingsComponent extends React.Component { } } catch (error) { const options = { + // eslint-disable-next-line no-use-before-define context: `${Settings.name}.getSettings`, level: UI_LOGGER_LEVELS.ERROR, severity: UI_ERROR_SEVERITIES.BUSINESS, @@ -234,8 +245,10 @@ class SettingsComponent extends React.Component { title: `${error.name}: Error getting API entries`, }, }; + getErrorOrchestrator().handleError(options); } + return; } @@ -244,7 +257,6 @@ class SettingsComponent extends React.Component { try { // Get the index of the API in the entries const index = isIndex ? item : this.getApiIndex(item); - // Get the Api information const api = this.state.apiEntries[index]; const { username, url, port, id } = api; @@ -256,21 +268,31 @@ class SettingsComponent extends React.Component { insecure: 'true', id: id, }; - // Test the connection const data = await ApiCheck.checkApi(tmpData, true); + tmpData.cluster_info = data?.data; - const { cluster_info } = tmpData; + + const { cluster_info: clusterInfo } = tmpData; + // Updates the cluster-information in the registry - this.state.apiEntries[index].cluster_info = cluster_info; + // eslint-disable-next-line react/no-direct-mutation-state + this.state.apiEntries[index].cluster_info = clusterInfo; + // eslint-disable-next-line react/no-direct-mutation-state this.state.apiEntries[index].status = 'online'; + // eslint-disable-next-line react/no-direct-mutation-state this.state.apiEntries[index].allow_run_as = data.data.allow_run_as; this.wzMisc.setApiIsDown(false); - !silent && ErrorHandler.info('Connection success', 'Settings'); + + if (!silent) { + ErrorHandler.info('Connection success', 'Settings'); + } } catch (error) { this.setState({ load: false }); + if (!silent) { const options = { + // eslint-disable-next-line no-use-before-define context: `${Settings.name}.checkManager`, level: UI_LOGGER_LEVELS.ERROR, severity: UI_ERROR_SEVERITIES.BUSINESS, @@ -280,9 +302,11 @@ class SettingsComponent extends React.Component { title: error.name || error, }, }; + getErrorOrchestrator().handleError(options); } - return Promise.reject(error); + + throw error; } } @@ -293,17 +317,18 @@ class SettingsComponent extends React.Component { try { const data = await GenericRequest.request('GET', '/api/setup'); const response = data.data.data; + this.appInfo = { 'app-version': response['app-version'], revision: response['revision'], }; this.setState({ load: false }); - // TODO: this seems not to be used to display or not the index pattern selector - AppState.setPatternSelector(this.props.configurationIPSelector); - const pattern = AppState.getCurrentPattern(); + + AppState.getCurrentPattern(); this.getCurrentAPIIndex(); + if ( (this.state.currentApiEntryIndex || this.state.currentApiEntryIndex === 0) && @@ -313,7 +338,9 @@ class SettingsComponent extends React.Component { } } catch (error) { AppState.removeNavigation(); + const options = { + // eslint-disable-next-line no-use-before-define context: `${Settings.name}.getAppInfo`, level: UI_LOGGER_LEVELS.ERROR, severity: UI_ERROR_SEVERITIES.BUSINESS, @@ -323,6 +350,7 @@ class SettingsComponent extends React.Component { title: error.name || error, }, }; + getErrorOrchestrator().handleError(options); } } @@ -331,26 +359,25 @@ class SettingsComponent extends React.Component { * Get the API hosts */ async getHosts() { - try { - const result = await GenericRequest.request('GET', '/hosts/apis', {}); - const hosts = result.data || []; - this.setState({ - apiEntries: hosts, - }); - return hosts; - } catch (error) { - return Promise.reject(error); - } + const result = await GenericRequest.request('GET', '/hosts/apis', {}); + const hosts = result.data || []; + + this.setState({ + apiEntries: hosts, + }); + + return hosts; } renderView() { // WORKAROUND: This avoids the configuration view is displayed if ( - this.props.tab === configurationTabID && + this.props.tab === CONFIGURATION_TAB_ID && !this.isConfigurationUIEditable() ) { return null; } + return ( @@ -421,3 +448,13 @@ class SettingsComponent extends React.Component { ); } } + +export const Settings = compose( + withErrorBoundary, + withRouteResolvers({ enableMenu, ip, nestedResolve, savedSearch }), + connect(mapStateToProps, mapDispatchToProps), +)(props => { + const { tab } = useRouterSearch(); + + return ; +}); diff --git a/plugins/main/public/react-services/app-state.js b/plugins/main/public/react-services/app-state.js index cbbfe23719..4caef3141a 100644 --- a/plugins/main/public/react-services/app-state.js +++ b/plugins/main/public/react-services/app-state.js @@ -18,9 +18,9 @@ import { import { CSVRequest } from '../services/csv-request'; import { getToasts, getCookies } from '../kibana-services'; import * as FileSaver from '../services/file-saver'; +import { UI_LOGGER_LEVELS } from '../../common/constants'; import { WzAuthentication } from './wz-authentication'; import { UI_ERROR_SEVERITIES } from './error-orchestrator/types'; -import { UI_LOGGER_LEVELS } from '../../common/constants'; import { getErrorOrchestrator } from './common-services'; export class AppState { @@ -32,6 +32,7 @@ export class AppState { const clusterInfo = getCookies().get('clusterInfo') ? decodeURI(getCookies().get('clusterInfo')) : false; + return clusterInfo ? JSON.parse(clusterInfo) : {}; } catch (error) { const options = { @@ -44,6 +45,7 @@ export class AppState { title: `${error.name}: Error get cluster info`, }, }; + getErrorOrchestrator().handleError(options); throw error; } @@ -51,14 +53,16 @@ export class AppState { /** * Sets a new value to the cookie 'clusterInfo' object - * @param {*} cluster_info + * @param {*} clusterInfo */ - static setClusterInfo(cluster_info) { + static setClusterInfo(clusterInfo) { try { - const encodedClusterInfo = encodeURI(JSON.stringify(cluster_info)); + const encodedClusterInfo = encodeURI(JSON.stringify(clusterInfo)); const exp = new Date(); + exp.setDate(exp.getDate() + 365); - if (cluster_info) { + + if (clusterInfo) { getCookies().set('clusterInfo', encodedClusterInfo, { expires: exp, }); @@ -74,59 +78,7 @@ export class AppState { title: `${error.name}: Error set cluster info`, }, }; - getErrorOrchestrator().handleError(options); - throw error; - } - } - /** - * Set a new value to the 'createdAt' cookie - * @param {*} date - */ - static setCreatedAt(date) { - try { - const createdAt = encodeURI(date); - const exp = new Date(); - exp.setDate(exp.getDate() + 365); - getCookies().set('createdAt', createdAt, { - expires: exp, - }); - } catch (error) { - const options = { - context: `${AppState.name}.setCreatedAt`, - level: UI_LOGGER_LEVELS.ERROR, - severity: UI_ERROR_SEVERITIES.UI, - error: { - error: error, - message: error.message || error, - title: `${error.name}: Error set createdAt date`, - }, - }; - getErrorOrchestrator().handleError(options); - throw error; - } - } - - /** - * Get 'createdAt' value - */ - static getCreatedAt() { - try { - const createdAt = getCookies().get('createdAt') - ? decodeURI(getCookies().get('createdAt')) - : false; - return createdAt ? createdAt : false; - } catch (error) { - const options = { - context: `${AppState.name}.getCreatedAt`, - level: UI_LOGGER_LEVELS.ERROR, - severity: UI_ERROR_SEVERITIES.UI, - error: { - error: error, - message: error.message || error, - title: `${error.name}: Error get createdAt date`, - }, - }; getErrorOrchestrator().handleError(options); throw error; } @@ -136,12 +88,9 @@ export class AppState { * Get 'API' value */ static getCurrentAPI() { - try { - const currentAPI = getCookies().get('currentApi'); - return currentAPI ? decodeURI(currentAPI) : false; - } catch (error) { - throw error; - } + const currentAPI = getCookies().get('currentApi'); + + return currentAPI ? decodeURI(currentAPI) : false; } /** @@ -149,7 +98,9 @@ export class AppState { */ static removeCurrentAPI() { const updateApiMenu = updateCurrentApi(false); + store.dispatch(updateApiMenu); + return getCookies().remove('currentApi'); } @@ -161,18 +112,18 @@ export class AppState { try { const encodedApi = encodeURI(API); const exp = new Date(); + exp.setDate(exp.getDate() + 365); + if (API) { getCookies().set('currentApi', encodedApi, { expires: exp, }); - try { - const updateApiMenu = updateCurrentApi(JSON.parse(API).id); - store.dispatch(updateApiMenu); - WzAuthentication.refresh(); - } catch (error) { - throw error; - } + + const updateApiMenu = updateCurrentApi(JSON.parse(API).id); + + store.dispatch(updateApiMenu); + WzAuthentication.refresh(); } } catch (error) { const options = { @@ -185,38 +136,12 @@ export class AppState { title: `${error.name}: Error set current API`, }, }; + getErrorOrchestrator().handleError(options); throw error; } } - /** - * Get 'APISelector' value - */ - static getAPISelector() { - return getCookies().get('APISelector') - ? decodeURI(getCookies().get('APISelector')) == 'true' - : false; - } - - /** - * Get 'patternSelector' value - */ - static getPatternSelector() { - return getCookies().get('patternSelector') - ? decodeURI(getCookies().get('patternSelector')) == 'true' - : false; - } - - /** - * Set a new value to the 'patternSelector' cookie - * @param {*} value - */ - static setPatternSelector(value) { - const encodedPattern = encodeURI(value); - getCookies().set('patternSelector', encodedPattern, {}); - } - /** * Set a new value to the 'currentPattern' cookie * @param {*} newPattern @@ -224,7 +149,9 @@ export class AppState { static setCurrentPattern(newPattern) { const encodedPattern = encodeURI(newPattern); const exp = new Date(); + exp.setDate(exp.getDate() + 365); + if (newPattern) { getCookies().set('currentPattern', encodedPattern, { expires: exp, @@ -239,15 +166,18 @@ export class AppState { const currentPattern = getCookies().get('currentPattern') ? decodeURI(getCookies().get('currentPattern')) : ''; + // check if the current Cookie has the format of 3.11 and previous versions, in that case we remove the extra " " characters if ( currentPattern && currentPattern[0] === '"' && - currentPattern[currentPattern.length - 1] === '"' + currentPattern.at(-1) === '"' ) { - const newPattern = currentPattern.substring(1, currentPattern.length - 1); + const newPattern = currentPattern.slice(1, -1); + this.setCurrentPattern(newPattern); } + return getCookies().get('currentPattern') ? decodeURI(getCookies().get('currentPattern')) : ''; @@ -265,14 +195,14 @@ export class AppState { * @param {*} current **/ static setCurrentDevTools(current) { - window.localStorage.setItem('currentDevTools', current); + globalThis.localStorage.setItem('currentDevTools', current); } /** * Get 'currentDevTools' value **/ static getCurrentDevTools() { - return window.localStorage.getItem('currentDevTools'); + return globalThis.localStorage.getItem('currentDevTools'); } /** @@ -281,7 +211,7 @@ export class AppState { * @param {*} value */ static setSessionStorageItem(key, value) { - window.sessionStorage.setItem(key, value); + globalThis.sessionStorage.setItem(key, value); } /** @@ -289,7 +219,7 @@ export class AppState { * @param {*} key */ static getSessionStorageItem(key) { - return window.sessionStorage.getItem(key); + return globalThis.sessionStorage.getItem(key); } /** @@ -297,19 +227,22 @@ export class AppState { * @param {*} key */ static removeSessionStorageItem(key) { - window.sessionStorage.removeItem(key); + globalThis.sessionStorage.removeItem(key); } static setNavigation(params) { const decodedNavigation = getCookies().get('navigate') ? decodeURI(getCookies().get('navigate')) : false; - var navigate = decodedNavigation ? JSON.parse(decodedNavigation) : {}; - for (var key in params) { + let navigate = decodedNavigation ? JSON.parse(decodedNavigation) : {}; + + for (let key in params) { navigate[key] = params[key]; } + if (navigate) { const encodedURI = encodeURI(JSON.stringify(navigate)); + getCookies().set('navigate', encodedURI); } } @@ -319,6 +252,7 @@ export class AppState { ? decodeURI(getCookies().get('navigate')) : false; const navigation = decodedNavigation ? JSON.parse(decodedNavigation) : {}; + return navigation; } @@ -328,21 +262,24 @@ export class AppState { static setWzMenu(isVisible = true) { const showMenu = updateShowMenu(isVisible); + store.dispatch(showMenu); } static async downloadCsv(path, fileName, filters = []) { try { const csvReq = new CSVRequest(); + getToasts().add({ color: 'success', title: 'CSV', text: 'Your download should begin automatically...', toastLifeTimeMs: 4000, }); + const currentApi = JSON.parse(this.getCurrentAPI()).id; const output = await csvReq.fetch(path, currentApi, filters); - const blob = new Blob([output], { type: 'text/csv' }); // eslint-disable-line + const blob = new Blob([output], { type: 'text/csv' }); FileSaver.saveAs(blob, fileName); } catch (error) { @@ -356,12 +293,14 @@ export class AppState { title: `${error.name}: Error generating CSV`, }, }; + getErrorOrchestrator().handleError(options); } } static checkCookies() { getCookies().set('appName', 'wazuh'); + return !!getCookies().get('appName'); } } diff --git a/plugins/main/public/react-services/wz-api-check.js b/plugins/main/public/react-services/wz-api-check.js index 30761a277a..e57aa09a82 100644 --- a/plugins/main/public/react-services/wz-api-check.js +++ b/plugins/main/public/react-services/wz-api-check.js @@ -9,12 +9,11 @@ * * Find more information about this on the LICENSE file. */ -import { WazuhConfig } from './wazuh-config'; -import { AppState } from './app-state'; import { WzMisc } from '../factories/misc'; import { getHttp } from '../kibana-services'; import { PLUGIN_PLATFORM_REQUEST_HEADERS } from '../../common/constants'; import { request } from '../services/request-handler'; +import { WazuhConfig } from './wazuh-config'; export class ApiCheck { static async checkStored(data, idChanged = false) { @@ -23,6 +22,7 @@ export class ApiCheck { const configuration = wazuhConfig.getConfig(); const timeout = configuration ? configuration.timeout : 20000; const payload = { id: data }; + if (idChanged) { payload.idChanged = data; } @@ -30,33 +30,39 @@ export class ApiCheck { const url = getHttp().basePath.prepend('/api/check-stored-api'); const options = { method: 'POST', - headers: { ...PLUGIN_PLATFORM_REQUEST_HEADERS, 'content-type': 'application/json' }, + headers: { + ...PLUGIN_PLATFORM_REQUEST_HEADERS, + 'content-type': 'application/json', + }, url: url, data: payload, - timeout: timeout || 20000 + timeout: timeout || 20000, }; - - if (Object.keys(configuration).length) { - AppState.setPatternSelector(configuration['ip.selector']); - } - const response = await request(options); if (response.error) { - return Promise.reject(this.returnErrorInstance(response)); + throw this.returnErrorInstance(response); } return response; - } catch (err) { - if (err.response) { + } catch (error) { + if (error.response) { const wzMisc = new WzMisc(); + wzMisc.setApiIsDown(true); - const response = (err.response.data || {}).message || err.message; - return Promise.reject(this.returnErrorInstance(response)); + + const response = error.response.data?.message || error.message; + + throw this.returnErrorInstance(response); } else { - return (err || {}).message || false - ? Promise.reject(this.returnErrorInstance(err,err.message)) - : Promise.reject(this.returnErrorInstance(err,err || 'Server did not respond')); + return error?.message || false + ? Promise.reject(this.returnErrorInstance(error, error.message)) + : Promise.reject( + this.returnErrorInstance( + error, + error || 'Server did not respond', + ), + ); } } } @@ -65,50 +71,59 @@ export class ApiCheck { * Check the status of an API entry * @param {String} apiObject */ - static async checkApi(apiEntry, forceRefresh=false) { + static async checkApi(apiEntry, forceRefresh = false) { try { const wazuhConfig = new WazuhConfig(); const { timeout } = wazuhConfig.getConfig(); const url = getHttp().basePath.prepend('/api/check-api'); - const options = { method: 'POST', - headers: { ...PLUGIN_PLATFORM_REQUEST_HEADERS, 'content-type': 'application/json' }, + headers: { + ...PLUGIN_PLATFORM_REQUEST_HEADERS, + 'content-type': 'application/json', + }, url: url, - data: {...apiEntry, forceRefresh}, - timeout: timeout || 20000 + data: { ...apiEntry, forceRefresh }, + timeout: timeout || 20000, }; - const response = await request(options); if (response.error) { - return Promise.reject(this.returnErrorInstance(response)); + throw this.returnErrorInstance(response); } return response; - } catch (err) { - if (err.response) { - const response = (err.response.data || {}).message || err.message; - return Promise.reject(this.returnErrorInstance(response)); + } catch (error) { + if (error.response) { + const response = error.response?.data?.message || error.message; + + throw this.returnErrorInstance(response); } else { - return (err || {}).message || false - ? Promise.reject(this.returnErrorInstance(err,err.message)) - : Promise.reject(this.returnErrorInstance(err,err || 'Server did not respond')); + return error?.message || false + ? Promise.reject(this.returnErrorInstance(error, error.message)) + : Promise.reject( + this.returnErrorInstance( + error, + error || 'Server did not respond', + ), + ); } } } - /** + /** * Customize message and return an error object - * @param error - * @param message + * @param error + * @param message * @returns error */ - static returnErrorInstance(error, message){ - if(!error || typeof error === 'string'){ - return new Error(message || error); - } - error.message = message - return error + static returnErrorInstance(error, message) { + if (!error || typeof error === 'string') { + return new Error(message || error); } + + error.message = message; + + return error; + } } diff --git a/plugins/wazuh-core/package.json b/plugins/wazuh-core/package.json index 611462350e..0fd21e06e5 100644 --- a/plugins/wazuh-core/package.json +++ b/plugins/wazuh-core/package.json @@ -24,7 +24,8 @@ "json2csv": "^4.1.2", "jwt-decode": "^3.1.2", "md5": "^2.3.0", - "node-cron": "^3.0.2" + "node-cron": "^3.0.2", + "react-cookie": "^7.2.2" }, "devDependencies": { "@testing-library/user-event": "^14.5.2", diff --git a/plugins/wazuh-core/public/plugin.ts b/plugins/wazuh-core/public/plugin.ts index ea44edb8c1..cd50d1a18c 100644 --- a/plugins/wazuh-core/public/plugin.ts +++ b/plugins/wazuh-core/public/plugin.ts @@ -4,6 +4,7 @@ import { Plugin, PluginInitializerContext, } from 'opensearch-dashboards/public'; +import { Cookies } from 'react-cookie'; import { ConfigurationStore } from '../common/services/configuration/configuration-store'; import { EConfigurationProviders } from '../common/constants'; import { API_USER_STATUS_RUN_AS } from '../common/api-user-status-run-as'; @@ -16,6 +17,10 @@ import * as utils from './utils'; import * as uiComponents from './components'; import { DashboardSecurity } from './services/dashboard-security'; import * as hooks from './hooks'; +import { CoreState, State } from './services/state'; +import { ServerHostClusterInfoStateContainer } from './services/state/containers/server-host-cluster-info'; +import { ServerHostStateContainer } from './services/state/containers/server-host'; +import { DataSourceAlertsStateContainer } from './services/state/containers/data-source-alerts'; import { CoreServerSecurity } from './services'; import { CoreHTTPClient } from './services/http/http-client'; @@ -24,9 +29,16 @@ const noop = () => {}; export class WazuhCorePlugin implements Plugin { - runtime: Record = { setup: {}, start: {} }; + runtime: Record = { + setup: {}, + start: {}, + }; internal: Record = {}; - services: Record = {}; + services: { + [key: string]: any; + dashboardSecurity?: DashboardSecurity; + state?: State; + } = {}; constructor(private readonly initializerContext: PluginInitializerContext) { this.services = {}; @@ -68,6 +80,24 @@ export class WazuhCorePlugin // Create dashboardSecurity this.services.dashboardSecurity = new DashboardSecurity(logger, core.http); + // Create state + this.services.state = new CoreState(logger); + + const cookiesStore = new Cookies(); + + this.services.state.register( + 'server_host_cluster_info', + new ServerHostClusterInfoStateContainer(logger, { store: cookiesStore }), + ); + this.services.state.register( + 'server_host', + new ServerHostStateContainer(logger, { store: cookiesStore }), + ); + this.services.state.register( + 'data_source_alerts', + new DataSourceAlertsStateContainer(logger, { store: cookiesStore }), + ); + this.services.serverSecurity = new CoreServerSecurity(logger); // Create http @@ -75,12 +105,14 @@ export class WazuhCorePlugin getTimeout: async () => (await this.services.configuration.get('timeout')) as number, getURL: (path: string) => core.http.basePath.prepend(path), - getServerAPI: () => 'imposter', // TODO: implement - getIndexPatternTitle: async () => 'wazuh-alerts-*', // TODO: implement + getServerAPI: () => this.services.state.get('server_host').id, + getIndexPatternTitle: async () => + this.services.state.get('data_source_alerts'), http: core.http, }); // Setup services + this.runtime.setup.state = this.services.state.setup(); this.runtime.setup.dashboardSecurity = await this.services.dashboardSecurity.setup({ updateData$: this.services.http.server.auth$, @@ -103,6 +135,7 @@ export class WazuhCorePlugin ...hooks, ...this.runtime.setup.dashboardSecurity.hooks, ...this.runtime.setup.serverSecurity.hooks, + ...this.runtime.setup.state.hooks, }, hocs: { ...this.runtime.setup.dashboardSecurity.hocs, @@ -123,6 +156,7 @@ export class WazuhCorePlugin // Start services await this.services.configuration.start({ http: core.http }); + this.services.state.start(); await this.services.dashboardSecurity.start(); await this.services.http.start(); @@ -138,6 +172,7 @@ export class WazuhCorePlugin ...hooks, ...this.runtime.setup.dashboardSecurity.hooks, ...this.runtime.setup.serverSecurity.hooks, + ...this.runtime.setup.state.hooks, }, hocs: { ...this.runtime.setup.dashboardSecurity.hocs, @@ -151,5 +186,7 @@ export class WazuhCorePlugin }; } - public stop() {} + public stop() { + this.services.state.stop(); + } } diff --git a/plugins/wazuh-core/public/services/http/server-client.ts b/plugins/wazuh-core/public/services/http/server-client.ts index 7b18739119..0adba58ec6 100644 --- a/plugins/wazuh-core/public/services/http/server-client.ts +++ b/plugins/wazuh-core/public/services/http/server-client.ts @@ -316,18 +316,6 @@ export class WzRequest implements HTTPClientServer { return data; } catch (error) { - // TODO: implement - // const options: UIErrorLog = { - // context: `${WzAuthentication.name}.refresh`, - // level: UI_LOGGER_LEVELS.ERROR as UILogLevel, - // severity: UI_ERROR_SEVERITIES.BUSINESS as UIErrorSeverity, - // error: { - // error: error, - // message: error.message || error, - // title: `${error.name}: Error getting the authorization token`, - // }, - // }; - // getErrorOrchestrator().handleError(options); this.auth$.next({ token: null, policies: null, @@ -419,9 +407,6 @@ export class WzRequest implements HTTPClientServer { return response; } catch (error) { if (error.response) { - // TODO: implement - // const wzMisc = new WzMisc(); - // wzMisc.setApiIsDown(true); const response: string = error.response.data?.message || error.message; throw this.returnErrorInstance(response); diff --git a/plugins/wazuh-core/public/services/state/README.md b/plugins/wazuh-core/public/services/state/README.md new file mode 100644 index 0000000000..d683a9a89b --- /dev/null +++ b/plugins/wazuh-core/public/services/state/README.md @@ -0,0 +1,86 @@ +# State + +The `state` service manages the shared state of the Wazuh plugins and it is a HUB of state containers. Features: + +- Extensible +- Register state containers +- Ability to get, set, remove data and subscribe to changes of state containers + +The state containers provides a mechanism to manage a specific state. For example, some data is stored in cookies, others could be managed in-memory, local storage, session storage. + +Others plugins can register new state containers. + +The service creates hooks and HOCs that are exposed through the plugin lifecycle. + +## Usage + +### Register a state container + +```ts +state.register('my_state', new MyStateContainer(logger, deps)); +``` + +### Get data of a state container + +```ts +state.get('my_state'); +``` + +### Set data of a state container + +```ts +state.set('my_state', newMyState); +``` + +### Remove data of a state container + +```ts +state.remove('my_state'); +``` + +### Subscribe to a state container + +```ts +const unsubscribe = state.subscribe('my_state', data => { + // Do something with the data +}); + +// Unsubscribe +unsubscribe(); +``` + +### Hooks + +#### useStateContainer + +Use the state container. + +```ts +const [value, { set: setValue, remove: removeValue }] = + useStateContainer('my_state_container'); +``` + +### HOCs + +#### withStateContainer + +Use the state container. + +```tsx +const MyComponent = withStateContainer('my_state_container')(props => { + const getStateContainerValue = () => { + // access to state container value + return props['stateContainer:my_state_container'].value; + }; + + const setStateContainterValue = newValue => { + // set a new value + props['stateContainer:my_state_container'].set(newValue); + }; + + const removeStateContainerValue = () => { + // remove the value + props['stateContainer:my_state_container'].remove(); + }; +}); +``` diff --git a/plugins/wazuh-core/public/services/state/containers/data-source-alerts.ts b/plugins/wazuh-core/public/services/state/containers/data-source-alerts.ts new file mode 100644 index 0000000000..70aa8ebe68 --- /dev/null +++ b/plugins/wazuh-core/public/services/state/containers/data-source-alerts.ts @@ -0,0 +1,116 @@ +import { BehaviorSubject } from 'rxjs'; +import { Logger } from '../../../../common/services/configuration'; +import { StateContainer } from '../types'; + +export class DataSourceAlertsStateContainer implements StateContainer { + private readonly store: any; + private readonly STORE_KEY = 'currentPattern'; + updater$: BehaviorSubject; + + constructor( + private readonly logger: Logger, + { store }, + ) { + this.store = store; + this.updater$ = new BehaviorSubject(this.get()); + } + + get() { + try { + this.logger.debug('Getting data'); + + const rawData = this.store.get(this.STORE_KEY); + let result = ''; + + if (rawData) { + this.logger.debug('Getting decoded data'); + + let decodedData = decodeURI(rawData); + + this.logger.debug(`Decoded data: ${decodedData}`); + + /* I assume in previous versions, the selected index pattern could be wrapped with " characters. + This could probably removed if we confirm it is unused anymore. I will leave for historical reasons. + */ + if ( + decodedData && + decodedData[0] === '"' && + decodedData.at(-1) === '"' + ) { + const newPattern = decodedData.slice(1, -1); + + this.set(newPattern); + decodedData = decodeURI(this.store.get('currentPattern')); + } + + result = decodedData; + } else { + this.logger.debug('No raw data'); + result = ''; + } + + this.logger.debug(`Data: ${result}`); + + return result; + } catch (error) { + this.logger.error(`Error getting data: ${(error as Error).message}`); + // Emit the error + this.updater$.next({ error, method: 'get' }); + throw error; + } + } + + set(data: any) { + try { + this.logger.debug(`Setting data: ${data}`); + + const encodedData = encodeURI(data); + + this.logger.debug(`Setting encoded data: ${encodedData}`); + + const exp = new Date(); + + exp.setDate(exp.getDate() + 365); + + if (data) { + this.store.set(this.STORE_KEY, encodedData, { + expires: exp, + }); + this.updater$.next(encodedData); + this.logger.debug(`Encoded data was set: ${encodedData}`); + } + } catch (error) { + this.logger.error(`Error setting data: ${(error as Error).message}`); + // Emit the error + this.updater$.next({ error, method: 'set' }); + throw error; + } + } + + remove() { + try { + this.logger.debug('Removing'); + + const result = this.store.remove(this.STORE_KEY); + + this.updater$.next(); + this.logger.debug('Removed'); + + return result; + } catch (error) { + this.logger.error((error as Error).message); + this.updater$.next({ error, method: 'remove' }); + throw error; + } + } + + subscribe(callback: (value: any) => void) { + this.logger.debug('Subscribing'); + + const subscription = this.updater$.subscribe(callback); + + this.logger.debug('Subscribed'); + + return subscription; + } +} diff --git a/plugins/wazuh-core/public/services/state/containers/server-host-cluster-info.ts b/plugins/wazuh-core/public/services/state/containers/server-host-cluster-info.ts new file mode 100644 index 0000000000..1c9e3fb92e --- /dev/null +++ b/plugins/wazuh-core/public/services/state/containers/server-host-cluster-info.ts @@ -0,0 +1,88 @@ +import { BehaviorSubject } from 'rxjs'; +import { Logger } from '../../../../common/services/configuration'; +import { StateContainer } from '../types'; + +export class ServerHostClusterInfoStateContainer implements StateContainer { + private readonly store: any; + private readonly STORE_KEY = 'clusterInfo'; + updater$: BehaviorSubject; + + constructor( + private readonly logger: Logger, + { store }, + ) { + this.store = store; + this.updater$ = new BehaviorSubject(this.get()); + } + + get() { + try { + this.logger.debug('Getting data'); + + const rawData = this.store.get(this.STORE_KEY); + let result = {}; + + if (rawData) { + this.logger.debug('Getting decoded data'); + + const decodedData = decodeURI(rawData); + + this.logger.debug(`Decoded data: ${decodedData}`); + result = JSON.parse(decodedData); + } else { + this.logger.debug('No raw data'); + result = {}; + } + + this.logger.debug(`Data: ${result}`); + + return result; + } catch (error) { + this.logger.error(`Error getting data: ${(error as Error).message}`); + // Emit the error + this.updater$.next({ error, method: 'get' }); + throw error; + } + } + + set(data: object) { + try { + const stringifyData = JSON.stringify(data); + + this.logger.debug(`Setting data: ${stringifyData}`); + + const encodedData = encodeURI(stringifyData); + + this.logger.debug(`Setting encoded data: ${encodedData}`); + + const exp = new Date(); + + exp.setDate(exp.getDate() + 365); + + if (data) { + this.store.set(this.STORE_KEY, encodedData, { + expires: exp, + }); + this.updater$.next(data); + this.logger.debug(`Encoded data was set: ${encodedData}`); + } + } catch (error) { + this.logger.error(`Error setting data: ${(error as Error).message}`); + // Emit the error + this.updater$.next({ error, method: 'set' }); + throw error; + } + } + + remove() {} + + subscribe(callback: (value: any) => void) { + this.logger.debug('Subscribing'); + + const subscription = this.updater$.subscribe(callback); + + this.logger.debug('Subscribed'); + + return subscription; + } +} diff --git a/plugins/wazuh-core/public/services/state/containers/server-host.ts b/plugins/wazuh-core/public/services/state/containers/server-host.ts new file mode 100644 index 0000000000..dc1aa19a05 --- /dev/null +++ b/plugins/wazuh-core/public/services/state/containers/server-host.ts @@ -0,0 +1,87 @@ +import { BehaviorSubject } from 'rxjs'; +import { Logger } from '../../../../common/services/configuration'; +import { StateContainer } from '../types'; + +export class ServerHostStateContainer implements StateContainer { + private readonly store: any; + private readonly STORE_KEY = 'currentApi'; + updater$: BehaviorSubject; + + constructor( + private readonly logger: Logger, + { store }, + ) { + this.store = store; + this.updater$ = new BehaviorSubject(this.get()); + } + + get() { + this.logger.debug('Getting data'); + + const currentAPI = this.store.get(this.STORE_KEY); + + this.logger.debug(`Raw data: ${currentAPI}`); + + if (currentAPI) { + this.logger.debug('Decoding data'); + + const decodedData = decodeURI(currentAPI); + + this.logger.debug(`Decoded data: ${decodedData}`); + + return decodedData ? JSON.parse(decodedData) : {}; + } + + return false; + } + + set(data: object) { + try { + const stringifyData = JSON.stringify(data); + + this.logger.debug(`Setting data: ${stringifyData}`); + + const encodedData = encodeURI(stringifyData); + + this.logger.debug(`Setting encoded data: ${encodedData}`); + + const exp = new Date(); + + exp.setDate(exp.getDate() + 365); + + if (data) { + this.store.set(this.STORE_KEY, encodedData, { + expires: exp, + }); + this.updater$.next(data); + this.logger.debug(`Encoded data was set: ${encodedData}`); + } + } catch (error) { + this.logger.error(`Error setting data: ${(error as Error).message}`); + // Emit the error + this.updater$.next({ error, method: 'set' }); + throw error; + } + } + + remove() { + this.logger.debug('Removing'); + + const result = this.store.remove(this.STORE_KEY); + + this.updater$.next(); + this.logger.debug('Removed'); + + return result; + } + + subscribe(callback: (value: any) => void) { + this.logger.debug('Subscribing'); + + const subscription = this.updater$.subscribe(callback); + + this.logger.debug('Subscribed'); + + return subscription; + } +} diff --git a/plugins/wazuh-core/public/services/state/hocs/creator.tsx b/plugins/wazuh-core/public/services/state/hocs/creator.tsx new file mode 100644 index 0000000000..84e6c83277 --- /dev/null +++ b/plugins/wazuh-core/public/services/state/hocs/creator.tsx @@ -0,0 +1,22 @@ +import React from 'react'; + +export const createHOCs = ({ useStateContainer }) => ({ + withStateContainer: (name: string) => (WrappedComponent: React.ElementType) => + function WithStateContainer(props: any) { + const [state, { set: setState, remove: removeState }] = + useStateContainer(name); + + return ( + + ); + }, +}); diff --git a/plugins/wazuh-core/public/services/state/hooks/creator.ts b/plugins/wazuh-core/public/services/state/hooks/creator.ts new file mode 100644 index 0000000000..f8417e4ecb --- /dev/null +++ b/plugins/wazuh-core/public/services/state/hooks/creator.ts @@ -0,0 +1,25 @@ +import useObservable from 'react-use/lib/useObservable'; +import { State } from '../types'; + +export const createHooks = ({ state }: { state: State }) => { + function useStateContainer(name: string) { + const value: T = useObservable( + state.getStateContainer(name)?.updater$, + state.get(name), + ); + + function setValue(value: any) { + state.set(name, value); + } + + function removeValue() { + state.remove(name); + } + + return [value, { set: setValue, remove: removeValue }]; + } + + return { + useStateContainer, + }; +}; diff --git a/plugins/wazuh-core/public/services/state/index.ts b/plugins/wazuh-core/public/services/state/index.ts new file mode 100644 index 0000000000..8a4e7cbf4f --- /dev/null +++ b/plugins/wazuh-core/public/services/state/index.ts @@ -0,0 +1,2 @@ +export * from './types'; +export { CoreState } from './state'; diff --git a/plugins/wazuh-core/public/services/state/state.test.ts b/plugins/wazuh-core/public/services/state/state.test.ts new file mode 100644 index 0000000000..074bec4cbd --- /dev/null +++ b/plugins/wazuh-core/public/services/state/state.test.ts @@ -0,0 +1,104 @@ +import { BehaviorSubject } from 'rxjs'; +import { CoreState } from './state'; + +const noop = () => {}; + +const logger = { + info: noop, + warn: noop, + error: noop, + debug: noop, +}; + +describe('State', () => { + it('Throw error accessing to non-existent state container', () => { + const state = new CoreState(logger); + + expect(() => { + state.get('no_existent_state_container'); + }).toThrow( + 'State container [no_existent_state_container] does not exist. Did you forget to register it?', + ); + + expect(() => { + state.set('no_existent_state_container', {}); + }).toThrow( + 'State container [no_existent_state_container] does not exist. Did you forget to register it?', + ); + + expect(() => { + state.remove('no_existent_state_container'); + }).toThrow( + 'State container [no_existent_state_container] does not exist. Did you forget to register it?', + ); + + expect(() => { + state.subscribe('no_existent_state_container', () => {}); + }).toThrow( + 'State container [no_existent_state_container] does not exist. Did you forget to register it?', + ); + }); + + it('Register a state container, get value, set new value and get new value', () => { + const state = new CoreState(logger); + const subscribe = jest.fn(); + + state.register('state_container', { + _value: true, // mock state. This does not exist in the StateContainer type + get() { + return this._value; + }, + set(newValue) { + this._value = newValue; + }, + remove() { + /* empty */ + }, + subscribe: subscribe, + }); + + expect(state.get('state_container')).toBe(true); + + state.set('state_container', false); + + expect(state.get('state_container')).toBe(false); + }); + + it('Register a state container, subscribe, set new value and remove the subscription', () => { + const state = new CoreState(logger); + const subscriber = jest.fn(); + + state.register('state_container', { + _value: true, // mock state. This does not exist in the StateContainer type + get() { + return this._value; + }, + set(newValue) { + this._value = newValue; + this.updater$.next(this._value); + }, + remove() { + /* empty */ + }, + updater$: new BehaviorSubject(), + subscribe(cb) { + return this.updater$.subscribe(cb); + }, + }); + + expect(state._subscriptions._subscriptions).toEqual(null); + + const unsubscribe = state.subscribe('state_container', subscriber); + + expect(state._subscriptions._subscriptions).toHaveLength(1); + + state.set('state_container', false); + expect(subscriber).toHaveBeenCalledTimes(2); + + unsubscribe(); + expect(state._subscriptions._subscriptions).toHaveLength(0); + + state.set('state_container', false); + expect(subscriber).toHaveBeenCalledTimes(2); + }); +}); diff --git a/plugins/wazuh-core/public/services/state/state.ts b/plugins/wazuh-core/public/services/state/state.ts new file mode 100644 index 0000000000..0cd67ded10 --- /dev/null +++ b/plugins/wazuh-core/public/services/state/state.ts @@ -0,0 +1,81 @@ +import { Subscription } from 'rxjs'; +import { Logger } from '../../../common/services/configuration'; +import { createHOCs } from './hocs/creator'; +import { createHooks } from './hooks/creator'; +import { State, StateContainer } from './types'; + +export class CoreState implements State { + private readonly _subscriptions: Subscription = new Subscription(); + private readonly stateContainers: Map; + + constructor(private readonly logger: Logger) { + this.stateContainers = new Map(); + } + + setup() { + this.logger.debug('Setup'); + + const hooks = createHooks({ state: this }); + const hocs = createHOCs(hooks); + + return { + hooks, + hocs, + }; + } + + start() { + this.logger.debug('Start'); + } + + stop() { + this.logger.debug('Stop'); + this.logger.debug('Unsubscribing'); + this._subscriptions.unsubscribe(); + this.logger.debug('Unsubscribed'); + } + + getStateContainer(name: string) { + if (!this.stateContainers.has(name)) { + const error = new Error( + `State container [${name}] does not exist. Did you forget to register it?`, + ); + + this.logger.error(error.message); + throw error; + } + + return this.stateContainers.get(name); + } + + register(name: string, value: StateContainer) { + this.stateContainers.set(name, value); + } + + get(name: string) { + return this.getStateContainer(name)?.get(); + } + + set(name: string, value: any) { + return this.getStateContainer(name)?.set(value); + } + + remove(name: string) { + return this.getStateContainer(name)?.remove(); + } + + subscribe(name: string, callback: (value: any) => void) { + const stateContainerSubscription = + this.getStateContainer(name)?.subscribe(callback); + + // Create a wrapper of the original subscription to remove all in the stop plugin lifecycle + const stateContainerUnsub = () => { + stateContainerSubscription.unsubscribe(); + this._subscriptions.remove(stateContainerSubscription); + }; + + this._subscriptions.add(stateContainerSubscription); + + return stateContainerUnsub; + } +} diff --git a/plugins/wazuh-core/public/services/state/types.ts b/plugins/wazuh-core/public/services/state/types.ts new file mode 100644 index 0000000000..443917c541 --- /dev/null +++ b/plugins/wazuh-core/public/services/state/types.ts @@ -0,0 +1,51 @@ +import { Subscription, BehaviourSubject } from 'rxjs'; +import { LifecycleService } from '../types'; + +export interface StateContainer { + get: () => T; + set: (value: T) => T; + remove: () => T; + updater$: BehaviourSubject; + subscribe: (callback: (value: T) => void) => Subscription; +} + +export interface StateSetupReturn { + hooks: { + useStateContainer: ( + name: string, + ) => [any, { set: (value: any) => void; remove: () => void }]; + }; + hocs: { + withStateContainer: ( + name: string, + ) => ( + WrappedComponent: React.ComponentType, + ) => (props: any) => React.ElementType; + }; +} + +export interface State< + SetupDeps = any, + SetupReturn = StateSetupReturn, + StartDeps = any, + StartReturn = any, + StopDeps = any, + StopReturn = any, +> extends LifecycleService< + SetupDeps, + SetupReturn, + StartDeps, + StartReturn, + StopDeps, + StopReturn + > { + get: (name: string) => any; + set: (name: string, value: any) => any; + remove: (name: string) => any; + getStateContainer: (name: string) => StateContainer | undefined; + register: (name: string, value: StateContainer) => any; + subscribe: ( + name: string, + callback: StateContainer['subscribe'], + ) => () => void; +} diff --git a/plugins/wazuh-core/public/services/types.ts b/plugins/wazuh-core/public/services/types.ts new file mode 100644 index 0000000000..75147d110f --- /dev/null +++ b/plugins/wazuh-core/public/services/types.ts @@ -0,0 +1,12 @@ +export interface LifecycleService< + SetupDeps = any, + SetupReturn = any, + StartDeps = any, + StartReturn = any, + StopDeps = any, + StopReturn = any, +> { + setup: (deps: SetupDeps) => SetupReturn; + start: (deps: StartDeps) => StartReturn; + stop: (deps: StopDeps) => StopReturn; +} diff --git a/plugins/wazuh-core/public/types.ts b/plugins/wazuh-core/public/types.ts index 8a4cab144a..6339a8cb7f 100644 --- a/plugins/wazuh-core/public/types.ts +++ b/plugins/wazuh-core/public/types.ts @@ -1,6 +1,7 @@ import React from 'react'; import { API_USER_STATUS_RUN_AS } from '../common/api-user-status-run-as'; import { Configuration } from '../common/services/configuration'; +import { State, StateSetupReturn } from './services/state'; import { ServerSecurity, ServerSecuritySetupReturn } from './services'; import { TableDataProps } from './components'; import { UseStateStorageHook } from './hooks'; @@ -18,14 +19,17 @@ export interface WazuhCorePluginSetup { API_USER_STATUS_RUN_AS: typeof API_USER_STATUS_RUN_AS; configuration: Configuration; dashboardSecurity: DashboardSecurityService; + state: State; http: HTTPClient; serverSecurity: ServerSecurity; hooks: { useDockedSideNav: () => boolean; } & DashboardSecurityServiceSetupReturn['hooks'] & - ServerSecuritySetupReturn['hooks']; + ServerSecuritySetupReturn['hooks'] & + StateSetupReturn['hooks']; hocs: {} & DashboardSecurityServiceSetupReturn['hocs'] & - ServerSecuritySetupReturn['hocs']; + ServerSecuritySetupReturn['hocs'] & + StateSetupReturn['hocs']; ui: { TableData: ( prop: TableDataProps, @@ -42,15 +46,18 @@ export interface WazuhCorePluginStart { API_USER_STATUS_RUN_AS: typeof API_USER_STATUS_RUN_AS; configuration: Configuration; dashboardSecurity: DashboardSecurityService; + state: State; http: HTTPClient; serverSecurity: ServerSecurity; hooks: { useDockedSideNav: UseDockedSideNav; useStateStorage: UseStateStorageHook; // TODO: enhance } & DashboardSecurityServiceSetupReturn['hooks'] & - ServerSecuritySetupReturn['hooks']; + ServerSecuritySetupReturn['hooks'] & + StateSetupReturn['hooks']; hocs: {} & DashboardSecurityServiceSetupReturn['hocs'] & - ServerSecuritySetupReturn['hocs']; + ServerSecuritySetupReturn['hocs'] & + StateSetupReturn['hocs']; ui: { TableData: ( prop: TableDataProps, diff --git a/plugins/wazuh-core/server/initialization/index-patterns.ts b/plugins/wazuh-core/server/initialization/index-patterns.ts index 26fef596c5..a2c704d5b0 100644 --- a/plugins/wazuh-core/server/initialization/index-patterns.ts +++ b/plugins/wazuh-core/server/initialization/index-patterns.ts @@ -16,7 +16,12 @@ interface EnsureIndexPatternExistenceContextTaskWithConfigurationSetting // eslint-disable-next-line @typescript-eslint/no-unused-vars const decoratorCheckIsEnabled = - callback => + ( + callback: ( + ctx: InitializationTaskRunContext, + ctxTask: EnsureIndexPatternExistenceContextTask, + ) => Promise, + ) => async ( ctx: InitializationTaskRunContext, { diff --git a/plugins/wazuh-core/server/services/initialization/routes.ts b/plugins/wazuh-core/server/services/initialization/routes.ts index 2be1b8905e..b4dbed60d3 100644 --- a/plugins/wazuh-core/server/services/initialization/routes.ts +++ b/plugins/wazuh-core/server/services/initialization/routes.ts @@ -157,7 +157,11 @@ export function addRoutes(router, { initialization }) { ? getTaskList(request.query.tasks) : undefined; const logger = context.wazuh_core.logger; - const username = ''; // TODO: get value + const { username } = + await context.wazuh_core.dashboardSecurity.getCurrentUser( + request, + context, + ); const scope = 'user'; logger.debug( @@ -185,7 +189,7 @@ export function addRoutes(router, { initialization }) { : allUserTasks; logger.debug( - `Initialzation tasks related to user [${username}] scope [${scope}]: [${tasks + `Initialization tasks related to user [${username}] scope [${scope}]: [${tasks .map(({ name }) => name) .join(', ')}]`, ); diff --git a/plugins/wazuh-core/yarn.lock b/plugins/wazuh-core/yarn.lock index e9f9798d69..83791d1f58 100644 --- a/plugins/wazuh-core/yarn.lock +++ b/plugins/wazuh-core/yarn.lock @@ -41,11 +41,153 @@ version "0.0.0-semantically-released" resolved "https://codeload.github.com/testing-library/user-event/tar.gz/4be87b3452f524bcc256d43cfb891ba1f0e236d6" +"@types/cookie@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.6.0.tgz#eac397f28bf1d6ae0ae081363eca2f425bedf0d5" + integrity sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA== + +"@types/hoist-non-react-statics@^3.3.5": + version "3.3.5" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz#dab7867ef789d87e2b4b0003c9d65c49cc44a494" + integrity sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg== + dependencies: + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + +"@types/json-schema@^7.0.12": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== + "@types/md5@^2.3.5": version "2.3.5" resolved "https://registry.yarnpkg.com/@types/md5/-/md5-2.3.5.tgz#481cef0a896e3a5dcbfc5a8a8b02c05958af48a5" integrity sha512-/i42wjYNgE6wf0j2bcTX6kuowmdL/6PE4IVitMpm2eYKBUuYCprdcWVK+xEF0gcV6ufMCRhtxmReGfc6hIK7Jw== +"@types/prop-types@*": + version "15.7.13" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.13.tgz#2af91918ee12d9d32914feb13f5326658461b451" + integrity sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA== + +"@types/react@*": + version "18.3.12" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.12.tgz#99419f182ccd69151813b7ee24b792fe08774f60" + integrity sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw== + dependencies: + "@types/prop-types" "*" + csstype "^3.0.2" + +"@types/semver@^7.5.0": + version "7.5.8" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" + integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== + +"@typescript-eslint/eslint-plugin@^6.2.1": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz#30830c1ca81fd5f3c2714e524c4303e0194f9cd3" + integrity sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA== + dependencies: + "@eslint-community/regexpp" "^4.5.1" + "@typescript-eslint/scope-manager" "6.21.0" + "@typescript-eslint/type-utils" "6.21.0" + "@typescript-eslint/utils" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" + debug "^4.3.4" + graphemer "^1.4.0" + ignore "^5.2.4" + natural-compare "^1.4.0" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/parser@^6.2.1": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.21.0.tgz#af8fcf66feee2edc86bc5d1cf45e33b0630bf35b" + integrity sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ== + dependencies: + "@typescript-eslint/scope-manager" "6.21.0" + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/typescript-estree" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz#ea8a9bfc8f1504a6ac5d59a6df308d3a0630a2b1" + integrity sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg== + dependencies: + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" + +"@typescript-eslint/type-utils@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz#6473281cfed4dacabe8004e8521cee0bd9d4c01e" + integrity sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag== + dependencies: + "@typescript-eslint/typescript-estree" "6.21.0" + "@typescript-eslint/utils" "6.21.0" + debug "^4.3.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/types@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.21.0.tgz#205724c5123a8fef7ecd195075fa6e85bac3436d" + integrity sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg== + +"@typescript-eslint/typescript-estree@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz#c47ae7901db3b8bddc3ecd73daff2d0895688c46" + integrity sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ== + dependencies: + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + minimatch "9.0.3" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/utils@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.21.0.tgz#4714e7a6b39e773c1c8e97ec587f520840cd8134" + integrity sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@types/json-schema" "^7.0.12" + "@types/semver" "^7.5.0" + "@typescript-eslint/scope-manager" "6.21.0" + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/typescript-estree" "6.21.0" + semver "^7.5.4" + +"@typescript-eslint/visitor-keys@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz#87a99d077aa507e20e238b11d56cc26ade45fe47" + integrity sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A== + dependencies: + "@typescript-eslint/types" "6.21.0" + eslint-visitor-keys "^3.4.1" + +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.9.0: + version "8.12.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.0.tgz#1627bfa2e058148036133b8d9b51a700663c294c" + integrity sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw== + aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" @@ -117,11 +259,79 @@ commander@^4.1.1: resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== +compose-function@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/compose-function/-/compose-function-3.0.3.tgz#9ed675f13cc54501d30950a486ff6a7ba3ab185f" + integrity sha512-xzhzTJ5eC+gmIzvZq+C3kCJHsp9os6tJkrigDRZclyGtOKINbZtE8n1Tzmeh32jW+BUDPbvZpibwvJHBLGMVwg== + dependencies: + arity-n "^1.0.4" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +cookie@^0.7.2: + version "0.7.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" + integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== + crypt@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== +csstype@^3.0.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + +data-view-buffer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" + integrity sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +data-view-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz#90721ca95ff280677eb793749fce1011347669e2" + integrity sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +data-view-byte-offset@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz#5e0bbfb4828ed2d1b9b400cd8a7d119bca0ff18a" + integrity sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: + version "4.3.5" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" + integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== + dependencies: + ms "2.1.2" + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + defaults@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a" @@ -202,7 +412,70 @@ graceful-fs@^4.2.4: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== -ignore@^5.1.8: +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +has-bigints@^1.0.1, has-bigints@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-own-property@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/has-own-property/-/has-own-property-0.1.0.tgz#992b0f5bb3a25416f8d4d0cde53f497b9d7b1ea5" + integrity sha512-14qdBKoonU99XDhWcFKZTShK+QV47qU97u8zzoVo9cL5TZ3BmBHXogItSt9qJjR0KUMFRhcCW8uGIGl8nkl7Aw== + +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.0.1, has-proto@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" + integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== + +has-symbols@^1.0.2, has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + +hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + +identity-function@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/identity-function/-/identity-function-1.0.0.tgz#bea1159f0985239be3ca348edf40ce2f0dd2c21d" + integrity sha512-kNrgUK0qI+9qLTBidsH85HjDLpZfrrS0ElquKKe/fJFdB3D7VeKdXXEvOPDUHSHOzdZKCAAaQIWWyp0l2yq6pw== + +ignore@^5.1.8, ignore@^5.2.0, ignore@^5.2.4: version "5.3.1" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== @@ -382,6 +655,71 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +react-cookie@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/react-cookie/-/react-cookie-7.2.2.tgz#a7559e552ea9cca39a4b3686723a5acf504b8f84" + integrity sha512-e+hi6axHcw9VODoeVu8WyMWyoosa1pzpyjfvrLdF7CexfU+WSGZdDuRfHa4RJgTpfv3ZjdIpHE14HpYBieHFhg== + dependencies: + "@types/hoist-non-react-statics" "^3.3.5" + hoist-non-react-statics "^3.3.2" + universal-cookie "^7.0.0" + +react-is@^16.13.1, react-is@^16.7.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +reflect.getprototypeof@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz#3ab04c32a8390b770712b7a8633972702d278859" + integrity sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.1" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + globalthis "^1.0.3" + which-builtin-type "^1.1.3" + +regexp.prototype.flags@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" + integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== + dependencies: + call-bind "^1.0.6" + define-properties "^1.2.1" + es-errors "^1.3.0" + set-function-name "^2.0.1" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-pkg-maps@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" + integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== + +resolve@^1.22.4, resolve@^1.22.8: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +resolve@^2.0.0-next.5: + version "2.0.0-next.5" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.5.tgz#6b0ec3107e671e52b68cd068ef327173b90dc03c" + integrity sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -421,11 +759,135 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +to-space-case@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/to-space-case/-/to-space-case-1.0.0.tgz#b052daafb1b2b29dc770cea0163e5ec0ebc9fc17" + integrity sha512-rLdvwXZ39VOn1IxGL3V6ZstoTbwLRckQmn/U8ZDLuWwIXNpuZDhQ3AiRUlhTbOXFVE9C+dR51wM0CBDhk31VcA== + dependencies: + to-no-case "^1.0.0" + +ts-api-utils@^1.0.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" + integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== + +tsconfig-paths@^3.15.0: + version "3.15.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" + integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tsconfig-paths@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c" + integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== + dependencies: + json5 "^2.2.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tslib@^2.6.2: + version "2.6.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" + integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +typed-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" + integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + is-typed-array "^1.1.13" + +typed-array-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" + integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + +typed-array-byte-offset@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" + integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + +typed-array-length@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" + integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + possible-typed-array-names "^1.0.0" + typescript@^5.7.2: version "5.7.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6" integrity sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg== +unbox-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" + integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== + dependencies: + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" + which-boxed-primitive "^1.0.2" + +unescape-js@^1.0.5: + version "1.1.4" + resolved "https://registry.yarnpkg.com/unescape-js/-/unescape-js-1.1.4.tgz#4bc6389c499cb055a98364a0b3094e1c3d5da395" + integrity sha512-42SD8NOQEhdYntEiUQdYq/1V/YHwr1HLwlHuTJB5InVVdOSbgI6xu8jK5q65yIzuFCfczzyDF/7hbGzVbyCw0g== + dependencies: + string.fromcodepoint "^0.2.1" + +universal-cookie@^7.0.0: + version "7.2.2" + resolved "https://registry.yarnpkg.com/universal-cookie/-/universal-cookie-7.2.2.tgz#93ae9ec55baab89b24300473543170bb8112773c" + integrity sha512-fMiOcS3TmzP2x5QV26pIH3mvhexLIT0HmPa3V7Q7knRfT9HG6kTwq02HZGLPw0sAOXrAmotElGRvTLCMbJsvxQ== + dependencies: + "@types/cookie" "^0.6.0" + cookie "^0.7.2" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + uuid@8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"