diff --git a/.eslintrc.js b/.eslintrc.js index efff319ba..44f4a0dd6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -20,6 +20,33 @@ module.exports = { rules: { 'react/prop-types': 'off', '@typescript-eslint/no-unused-vars': ['error', { ignoreRestSiblings: true, args: 'after-used' }], + 'no-restricted-imports': [ + 'error', + { + // restrict importing auth packages methods directly + // Use of the ChromeAuthContext must be enforced + paths: [ + { + name: 'react-oidc-context', + message: 'Do not import react-oidc-context directly. Use the ChromeAuthContext instead!', + }, + { + name: 'oidc-client-ts', + message: 'Do not import oidc-client-ts directly. Use the ChromeAuthContext instead!', + }, + ], + patterns: [ + { + group: ['**/cognito/*'], + message: 'Do not import cognito auth methods directly. Use the ChromeAuthContext instead!', + }, + { + group: ['**/OIDCConnector/utils'], + message: 'Do not OIDC auth utils directly. Use the ChromeAuthContext instead!', + }, + ], + }, + ], }, }, ], diff --git a/.gitignore b/.gitignore index 82b5669b9..a436d4107 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,8 @@ Session.vim .idea/ .vscode/ coverage/ +.nyc_output +cypress-coverage # cypress diff snapshots cypress/**/*.diff.png diff --git a/.travis.yml b/.travis.yml index 3b9b016d9..234ab9f5b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ jobs: include: - stage: Test if: branch != nightly - script: npm run circular && npm run lint && npm run test && npx codecov && npm run test:ct + script: npm run circular && npm run lint && npm run test && npm run test:ct && npx codecov - stage: Deploy prod-stable if: branch = prod-stable AND type != pull_request name: deploy:prod-stable diff --git a/config/setupTests.js b/config/setupTests.js index 013d325a4..cf8999fad 100644 --- a/config/setupTests.js +++ b/config/setupTests.js @@ -1,4 +1,6 @@ import { TextDecoder, TextEncoder } from 'util'; +import 'whatwg-fetch'; + global.SVGPathElement = function () {}; global.MutationObserver = class { diff --git a/config/webpack.cy.config.js b/config/webpack.cy.config.js index ad9f6a784..925f40f3e 100644 --- a/config/webpack.cy.config.js +++ b/config/webpack.cy.config.js @@ -32,6 +32,16 @@ const JSConfig = { loader: 'swc-loader', options: { jsc: { + experimental: { + plugins: [ + [ + 'swc-plugin-coverage-instrument', + { + compact: false, + }, + ], + ], + }, parser: { syntax: 'typescript', tsx: true, diff --git a/cypress.config.ts b/cypress.config.ts index f4f28ab5c..2e04bb16b 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -22,6 +22,8 @@ export default defineConfig({ launchOptions.preferences.height = 720; } }); + require('@cypress/code-coverage/task')(on, config); + return config; }, video: false, devServer: { diff --git a/cypress/.eslintrc b/cypress/.eslintrc index 662017627..8fcc5abc9 100644 --- a/cypress/.eslintrc +++ b/cypress/.eslintrc @@ -18,6 +18,7 @@ "cy": "readonly" }, "rules": { - "react/prop-types": "off" + "react/prop-types": "off", + "no-restricted-imports": "off" } } \ No newline at end of file diff --git a/cypress/component/DefaultLayout.cy.js b/cypress/component/DefaultLayout.cy.js index ee420c70d..852fd3758 100644 --- a/cypress/component/DefaultLayout.cy.js +++ b/cypress/component/DefaultLayout.cy.js @@ -10,18 +10,7 @@ import ChromeNavItem from '../../src/components/Navigation/ChromeNavItem'; import { IntlProvider } from 'react-intl'; import { FeatureFlagsProvider } from '../../src/components/FeatureFlags'; import Footer from '../../src/components/Footer/Footer'; - -const Wrapper = ({ children, store }) => ( - - - - - {children} - - - - -); +import ChromeAuthContext from '../../src/auth/ChromeAuthContext'; const testUser = { identity: { @@ -45,6 +34,35 @@ const testUser = { }, }; +const chromeAuthContextValue = { + doOffline: () => Promise.resolve(), + getOfflineToken: () => Promise.resolve(), + getToken: () => Promise.resolve(''), + getUser: () => Promise.resolve(testUser), + login: () => Promise.resolve(), + loginAllTabs: () => Promise.resolve(), + logout: () => Promise.resolve(), + logoutAllTabs: () => Promise.resolve(), + ready: true, + token: '', + tokenExpires: 0, + user: testUser, +}; + +const Wrapper = ({ children, store }) => ( + + + + + + {children} + + + + + +); + const SidebarMock = ({ loaded, schema: { navItems: items } = {} }) => { if (!loaded) { return null; diff --git a/cypress/component/GatewayErrors.cy.tsx b/cypress/component/GatewayErrors.cy.tsx index dacc47273..af077f782 100644 --- a/cypress/component/GatewayErrors.cy.tsx +++ b/cypress/component/GatewayErrors.cy.tsx @@ -12,16 +12,32 @@ import { FeatureFlagsProvider } from '../../src/components/FeatureFlags'; import { loadModulesSchema, userLogIn } from '../../src/redux/actions'; import qe from '../../src/utils/iqeEnablement'; import { COMPLIACE_ERROR_CODES } from '../../src/utils/responseInterceptors'; -import LibtJWTContext from '../../src/components/LibJWTContext'; import testUserJson from '../fixtures/testUser.json'; import { ChromeUser } from '@redhat-cloud-services/types'; -import type { LibJWT } from '../../src/auth'; import { RemoteModule } from '../../src/@types/types'; import { BLOCK_CLEAR_GATEWAY_ERROR } from '../../src/utils/common'; import { initializeVisibilityFunctions } from '../../src/utils/VisibilitySingleton'; +import ChromeAuthContext, { ChromeAuthContextValue } from '../../src/auth/ChromeAuthContext'; const testUser: ChromeUser = testUserJson as unknown as ChromeUser; +const chromeUser: ChromeUser = testUser as unknown as ChromeUser; + +const chromeAuthContextValue: ChromeAuthContextValue = { + doOffline: () => Promise.resolve(), + getOfflineToken: () => Promise.resolve({} as any), + getToken: () => Promise.resolve(''), + getUser: () => Promise.resolve(chromeUser), + login: () => Promise.resolve(), + loginAllTabs: () => Promise.resolve(), + logout: () => Promise.resolve(), + logoutAllTabs: () => Promise.resolve(), + ready: true, + token: '', + tokenExpires: 0, + user: chromeUser, +}; + function createEnv(code?: string) { if (!code) { throw 'Enviroment must have identifier'; @@ -30,7 +46,7 @@ function createEnv(code?: string) { // initialize user object for feature flags reduxStore.dispatch(userLogIn(testUser)); // initializes request interceptors - qe.init(reduxStore); + qe.init(reduxStore, 'foo'); reduxStore.dispatch( loadModulesSchema({ [code]: { @@ -47,19 +63,9 @@ function createEnv(code?: string) { ); const Component = () => ( - Promise.resolve(), - getEncodedToken: () => '', - }, - } as LibJWT - } - > - - + + + - - - + + + ); return Component; } diff --git a/cypress/component/OIDCConnector/OIDCSecured.cy.tsx b/cypress/component/OIDCConnector/OIDCSecured.cy.tsx new file mode 100644 index 000000000..f1432eb1e --- /dev/null +++ b/cypress/component/OIDCConnector/OIDCSecured.cy.tsx @@ -0,0 +1,173 @@ +import React, { useContext } from 'react'; +import { OIDCSecured } from '../../../src/auth/OIDCConnector/OIDCSecured'; +import { Provider } from 'react-redux'; +import { Store, createStore } from 'redux'; +import { AuthContext, AuthContextProps, AuthProvider, AuthProviderProps } from 'react-oidc-context'; +import { User } from 'oidc-client-ts'; +import ChromeAuthContext, { ChromeAuthContextValue } from '../../../src/auth/ChromeAuthContext'; + +const CHILD_TEXT = 'Auth child component'; + +const ChildComponent = () => { + const chromeAuth = useContext(ChromeAuthContext); + const authContextMethods = Object.keys(chromeAuth).reduce<{ + [key in keyof ChromeAuthContextValue]: (...args: unknown[]) => unknown; + }>((acc, key) => { + const typedKey = key as keyof ChromeAuthContextValue; + if (typeof chromeAuth[typedKey] === 'function') { + acc[typedKey] = chromeAuth[typedKey] as any; + } + return acc; + }, {} as any); + + return ( +
+

{CHILD_TEXT}

+ {Object.entries(authContextMethods).map(([key, value]) => ( + + ))} +
+ ); +}; + +const Wrapper: React.FC> = ({ children, store }) => { + return {children}; +}; + +describe('ODIC Secured', () => { + let store: Store; + const testUser: User = { + access_token: 'foo', + expired: false, + expires_in: 100, + profile: { + aud: 'foo', + exp: 100, + iat: 100, + iss: 'foo', + sub: 'foo', + }, + scopes: [], + session_state: 'foo', + state: 'foo', + token_type: 'foo', + toStorageString: () => 'foo', + }; + + const authContextSettings: AuthProviderProps = { + authority: 'https://foo.bar/auth/realms/redhat-external', + client_id: 'cloud-services', + redirect_uri: 'localhost:8080', + post_logout_redirect_uri: 'http://foo.bar/auth/logout', + silent_redirect_uri: 'http://foo.bar/auth/silent-refresh', + response_type: 'code', + response_mode: 'fragment', + scope: 'openid profile email', + automaticSilentRenew: false, + loadUserInfo: true, + prompt: 'none', + metadata: { + authorization_endpoint: 'http://foo.bar/auth/realms/redhat-external/protocol/openid-connect/auth', + token_endpoint: 'http://foo.bar/auth/realms/redhat-external/protocol/openid-connect/token', + }, + }; + + const authContextValue: AuthContextProps = { + clearStaleState: () => Promise.resolve(), + settings: authContextSettings, + events: {} as AuthContextProps['events'], + removeUser: () => Promise.resolve(), + signinRedirect: () => Promise.resolve(), + isAuthenticated: true, + isLoading: false, + signinSilent: () => Promise.resolve(testUser), + signinPopup: () => Promise.resolve(testUser), + signinResourceOwnerCredentials: () => Promise.resolve(testUser), + signoutRedirect: () => Promise.resolve(), + signoutPopup: () => Promise.resolve(), + signoutSilent: () => Promise.resolve(), + querySessionStatus: () => Promise.resolve(null), + revokeTokens: () => Promise.resolve(), + startSilentRenew: () => Promise.resolve(), + stopSilentRenew: () => Promise.resolve(), + user: testUser, + }; + beforeEach(() => { + store = createStore((state = { chrome: {} }) => { + return state; + }); + }); + + it('should block rendering children if OIDC auth did not finish', () => { + cy.mount( + + + undefined}> + + + + + ); + + cy.contains(CHILD_TEXT).should('not.exist'); + }); + + it('should render children if OIDC auth did finish', () => { + cy.mount( + + + undefined}> + + + + + ); + + cy.contains(CHILD_TEXT).should('exist'); + }); + + it.only('Chrome auth context methods should be initialized and called on click', () => { + cy.mount( + + + undefined}> + + + + + ); + cy.contains(CHILD_TEXT).should('exist'); + const methodMapping = [ + ['logoutAllTabs', 'signoutRedirect'], + ['logout', 'signoutRedirect'], + ['login', 'signinRedirect'], + ['doOffline', 'signinRedirect'], + ]; + + // setup spy objects + const spies = methodMapping.reduce((acc, [, oidcName]) => { + if (!acc.find((mapped) => mapped === oidcName)) { + acc.push(oidcName); + } + + return acc; + }, []); + spies.forEach((oidcName) => { + const typedMethod = oidcName as keyof AuthContextProps; + cy.spy(authContextValue, typedMethod).as(oidcName); + }); + + const calls: { [key: string]: number } = {}; + + methodMapping.forEach(([chromeName, oidcName]) => { + cy.contains(new RegExp(`^${chromeName}$`)).click(); + calls[oidcName] = calls[oidcName] ? calls[oidcName] + 1 : 1; + }); + + spies.forEach((oidcName) => { + cy.get(`@${oidcName}`).should('have.callCount', calls[oidcName]); + }); + }); +}); diff --git a/cypress/component/UserToggle.cy.js b/cypress/component/UserToggle.cy.js index c41a20158..f38e7f207 100644 --- a/cypress/component/UserToggle.cy.js +++ b/cypress/component/UserToggle.cy.js @@ -6,16 +6,7 @@ import chromeReducer, { chromeInitialState } from '../../src/redux'; import ReducerRegistry from '@redhat-cloud-services/frontend-components-utilities/ReducerRegistry'; import { IntlProvider } from 'react-intl'; import UserToggle from '../../src/components/Header/UserToggle'; - -const Wrapper = ({ children, store }) => ( - - - - {children} - - - -); +import ChromeAuthContext from '../../src/auth/ChromeAuthContext'; const testUser = { identity: { @@ -39,6 +30,33 @@ const testUser = { }, }; +const chromeAuthContextValue = { + doOffline: () => Promise.resolve(), + getOfflineToken: () => Promise.resolve(), + getToken: () => Promise.resolve(''), + getUser: () => Promise.resolve(testUser), + login: () => Promise.resolve(), + loginAllTabs: () => Promise.resolve(), + logout: () => Promise.resolve(), + logoutAllTabs: () => Promise.resolve(), + ready: true, + token: '', + tokenExpires: 0, + user: testUser, +}; + +const Wrapper = ({ children, store }) => ( + + + + + {children} + + + + +); + describe('', () => { let store; beforeEach(() => { diff --git a/cypress/component/helptopics/HelpTopicManager.cy.tsx b/cypress/component/helptopics/HelpTopicManager.cy.tsx index 07f2a9a6c..462585992 100644 --- a/cypress/component/helptopics/HelpTopicManager.cy.tsx +++ b/cypress/component/helptopics/HelpTopicManager.cy.tsx @@ -12,38 +12,34 @@ import chromeReducer, { chromeInitialState } from '../../../src/redux'; import testUser from '../../fixtures/testUser.json'; import { Button } from '@patternfly/react-core/dist/dynamic/components/Button'; -import LibtJWTContext from '../../../src/components/LibJWTContext'; import { ChromeUser } from '@redhat-cloud-services/types'; -import { LibJWT } from '../../../src/auth'; import { initializeVisibilityFunctions } from '../../../src/utils/VisibilitySingleton'; +import ChromeAuthContext, { ChromeAuthContextValue } from '../../../src/auth/ChromeAuthContext'; -const jwt = { - getUserInfo: () => Promise.resolve(testUser as unknown as ChromeUser), - getEncodedToken: () => '', +const chromeUser: ChromeUser = testUser as unknown as ChromeUser; + +const chromeAuthContextValue: ChromeAuthContextValue = { + doOffline: () => Promise.resolve(), + getOfflineToken: () => Promise.resolve({} as any), + getToken: () => Promise.resolve(''), + getUser: () => Promise.resolve(chromeUser), + login: () => Promise.resolve(), + loginAllTabs: () => Promise.resolve(), + logout: () => Promise.resolve(), + logoutAllTabs: () => Promise.resolve(), + ready: true, + token: '', + tokenExpires: 0, + user: chromeUser, }; const Wrapper = ({ store }: { store: Store }) => ( - - - - - + + + undefined} cookieElement={null} /> + + ); @@ -81,6 +77,13 @@ describe('HelpTopicManager', () => { chrome: { modules: {}, ...chromeInitialState.chrome, + scalprumConfig: { + TestApp: { + name: 'TestApp', + appId: 'TestApp', + manifestLocation: '/foo/bar.json', + }, + }, moduleRoutes: [ { absolute: true, @@ -139,7 +142,7 @@ describe('HelpTopicManager', () => { cy.intercept('GET', '/api/chrome-service/v1/static/stable/stage/services/services.json', []); }); - it('should switch help topics drawer content', () => { + it.only('should switch help topics drawer content', () => { // change screen size cy.viewport(1280, 720); cy.window().then((win) => { diff --git a/cypress/e2e/release-gate/favorite-services.cy.tsx b/cypress/e2e/release-gate/favorite-services.cy.tsx index c409bbb52..a4ede7438 100644 --- a/cypress/e2e/release-gate/favorite-services.cy.tsx +++ b/cypress/e2e/release-gate/favorite-services.cy.tsx @@ -1,8 +1,8 @@ const service = '/application-services/api-management'; let interceptionCounter = false; -const serviceName = 'API Management'; +const serviceName = 'Red Hat Insights'; -describe('Favorite-services', () => { +describe.skip('Favorite-services', () => { it('check and uncheck favorited services', () => { cy.visit('/'); cy.login(); diff --git a/cypress/snapshots/cypress/component/Banner.cy.tsx/Banner/ -- mounts.snap.png b/cypress/snapshots/cypress/component/Banner.cy.tsx/Banner/ -- mounts.snap.png new file mode 100644 index 000000000..e771b9f29 Binary files /dev/null and b/cypress/snapshots/cypress/component/Banner.cy.tsx/Banner/ -- mounts.snap.png differ diff --git a/cypress/snapshots/cypress/component/ContextSwitcher/ContextSwitcher.cy.js/ContextSwithcer/ -- should fire cross account request for internal user and render component.snap.png b/cypress/snapshots/cypress/component/ContextSwitcher/ContextSwitcher.cy.js/ContextSwithcer/ -- should fire cross account request for internal user and render component.snap.png new file mode 100644 index 000000000..51d9c14d4 Binary files /dev/null and b/cypress/snapshots/cypress/component/ContextSwitcher/ContextSwitcher.cy.js/ContextSwithcer/ -- should fire cross account request for internal user and render component.snap.png differ diff --git a/cypress/snapshots/cypress/component/ContextSwitcher/ContextSwitcher.cy.js/ContextSwithcer/ -- should not fire cross account request for non-internal user.snap.png b/cypress/snapshots/cypress/component/ContextSwitcher/ContextSwitcher.cy.js/ContextSwithcer/ -- should not fire cross account request for non-internal user.snap.png new file mode 100644 index 000000000..02fec02f0 Binary files /dev/null and b/cypress/snapshots/cypress/component/ContextSwitcher/ContextSwitcher.cy.js/ContextSwithcer/ -- should not fire cross account request for non-internal user.snap.png differ diff --git a/cypress/snapshots/cypress/component/DefaultLayout.cy.js/Default layout/ -- render correctly with few nav items.snap.png b/cypress/snapshots/cypress/component/DefaultLayout.cy.js/Default layout/ -- render correctly with few nav items.snap.png new file mode 100644 index 000000000..d8920bb8a Binary files /dev/null and b/cypress/snapshots/cypress/component/DefaultLayout.cy.js/Default layout/ -- render correctly with few nav items.snap.png differ diff --git a/cypress/snapshots/cypress/component/DefaultLayout.cy.js/Default layout/ -- render correctly with many nav items.snap.png b/cypress/snapshots/cypress/component/DefaultLayout.cy.js/Default layout/ -- render correctly with many nav items.snap.png new file mode 100644 index 000000000..64e699b7a Binary files /dev/null and b/cypress/snapshots/cypress/component/DefaultLayout.cy.js/Default layout/ -- render correctly with many nav items.snap.png differ diff --git a/cypress/snapshots/cypress/component/DefaultLayout.cy.js/Default layout/ -- render with footer at the screen bottom.snap.png b/cypress/snapshots/cypress/component/DefaultLayout.cy.js/Default layout/ -- render with footer at the screen bottom.snap.png new file mode 100644 index 000000000..d8920bb8a Binary files /dev/null and b/cypress/snapshots/cypress/component/DefaultLayout.cy.js/Default layout/ -- render with footer at the screen bottom.snap.png differ diff --git a/cypress/snapshots/cypress/component/UserToggle.cy.js/UserToggle/ -- render correctly.snap.png b/cypress/snapshots/cypress/component/UserToggle.cy.js/UserToggle/ -- render correctly.snap.png new file mode 100644 index 000000000..ea221e674 Binary files /dev/null and b/cypress/snapshots/cypress/component/UserToggle.cy.js/UserToggle/ -- render correctly.snap.png differ diff --git a/cypress/support/component.ts b/cypress/support/component.ts index c50104685..dd8a0ff85 100644 --- a/cypress/support/component.ts +++ b/cypress/support/component.ts @@ -21,6 +21,7 @@ import './commands' import '../../src/sass/chrome.scss' import '../../src/sass/pf-5-assets.scss' import { mount } from 'cypress/react18' +import '@cypress/code-coverage/support' // Alternatively you can use CommonJS syntax: // require('./commands') diff --git a/docs/auth.md b/docs/auth.md new file mode 100644 index 000000000..3c5eb6599 --- /dev/null +++ b/docs/auth.md @@ -0,0 +1,25 @@ +# Chrome auth + +Description of how auth should be written and used within the chrome repo. + +## Multi auth support + +Chrome runs in environments with different auth connectors. To ensure compatibility, Auth connects are written and then mapped to a `ChromeAuthContext`. Aside from the auth wrappers, the rest of the source code must reference the `ChromeAuthContext` to get the auth values and methods. + +Auth connector is picked on the UI startup based on which environment it is running in. + +![chrome auth flow](./chrome-auth-init-flow.png) + +## Chrome auth context + +Interface of `ChromeAuthContext` is described by the `ChromeAuthContextValue` type. + +## Current auth connectors + +### OIDC (OpenID Connect Protocol Suite) + +Uses [oidc0client-ts](https://github.com/authts/oidc-client-ts) and [react-oidc-context](https://github.com/authts/react-oidc-context). + +The OIDC connector is designed to connect to the RH keycloack auth services. + +To learn more about how OIDC works readt the [docs](https://openid.net/). diff --git a/docs/chrome-auth-init-flow.png b/docs/chrome-auth-init-flow.png new file mode 100644 index 000000000..8c0db8605 Binary files /dev/null and b/docs/chrome-auth-init-flow.png differ diff --git a/package-lock.json b/package-lock.json index b215000db..07373a298 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,15 +44,17 @@ "classnames": "^2.3.2", "commander": "^10.0.0", "history": "^5.3.0", + "jotai": "^2.5.1", "js-cookie": "^3.0.1", "js-yaml": "^4.1.0", - "keycloak-js": "^21.0.0", "localforage": "^1.10.0", "lodash": "^4.17.21", + "oidc-client-ts": "^2.4.0", "pf-4-styles": "npm:@patternfly/patternfly@^4.224.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-intl": "^6.2.10", + "react-oidc-context": "^2.3.1", "react-redux": "^8.0.5", "react-router-dom": "^6.8.2", "redux": "^4.2.1", @@ -62,6 +64,7 @@ "urijs": "^1.19.11" }, "devDependencies": { + "@cypress/code-coverage": "^3.12.9", "@openshift/dynamic-plugin-sdk-webpack": "^3.0.1", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.10", "@redhat-cloud-services/eslint-config-redhat-cloud-services": "^1.2.6", @@ -124,6 +127,7 @@ "stream-browserify": "^3.0.0", "style-loader": "^3.3.1", "swc-loader": "^0.2.3", + "swc-plugin-coverage-instrument": "^0.0.20", "terser-webpack-plugin": "^5.3.6", "typescript": "^4.9.5", "url": "^0.11.3", @@ -133,6 +137,7 @@ "webpack-bundle-analyzer": "^4.8.0", "webpack-cli": "^5.0.1", "webpack-dev-server": "^4.11.1", + "whatwg-fetch": "^3.6.19", "yargs": "^17.7.1" }, "engines": { @@ -150,9 +155,9 @@ } }, "node_modules/@adobe/css-tools": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz", - "integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz", + "integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==", "dev": true }, "node_modules/@ampproject/remapping": { @@ -232,9 +237,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz", - "integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.3.tgz", + "integrity": "sha512-BmR4bWbDIoFJmJ9z2cZ8Gmm2MXgEDgjdWgpKmKWUt54UGFJdlj31ECtbaDvCG/qVdG3AQ1SfpZEs01lUFbzLOQ==", "dev": true, "engines": { "node": ">=6.9.0" @@ -303,14 +308,40 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", + "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.10.tgz", - "integrity": "sha512-JMSwHD4J7SLod0idLq5PKgI+6g/hLD/iuWBq08ZX49xE14VpVEojJ5rHWptpirV2j020MvypRLAXAO50igCJ5Q==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", + "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", "dev": true, "dependencies": { "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.5", + "@babel/helper-validator-option": "^7.22.15", "browserslist": "^4.21.9", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -319,6 +350,65 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz", + "integrity": "sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", + "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.3.tgz", + "integrity": "sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, "node_modules/@babel/helper-environment-visitor": { "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", @@ -353,29 +443,42 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", - "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz", - "integrity": "sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", "@babel/helper-simple-access": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.5" + "@babel/helper-validator-identifier": "^7.22.20" }, "engines": { "node": ">=6.9.0" @@ -384,6 +487,19 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-plugin-utils": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", @@ -393,6 +509,42 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", + "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-wrap-function": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", + "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-optimise-call-expression": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/helper-simple-access": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", @@ -405,6 +557,19 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-split-export-declaration": { "version": "7.22.6", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", @@ -436,10 +601,25 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", - "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", + "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", + "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-function-name": "^7.22.5", + "@babel/template": "^7.22.15", + "@babel/types": "^7.22.19" + }, "engines": { "node": ">=6.9.0" } @@ -483,6 +663,70 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz", + "integrity": "sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz", + "integrity": "sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.3.tgz", + "integrity": "sha512-XaJak1qcityzrX0/IU5nKHb34VaibwP3saKqG6a/tppelgllOH13LUann4ZCIBcVOeE6H18K4Vx9QKkVww3z/w==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-async-generators": { "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", @@ -519,6 +763,80 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz", + "integrity": "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz", + "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-import-meta": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", @@ -630,6 +948,22 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-top-level-await": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", @@ -660,6 +994,960 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz", + "integrity": "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.3.tgz", + "integrity": "sha512-59GsVNavGxAXCDDbakWSMJhajASb4kBCqDjqJsv+p5nKdbz7istmZ3HrX3L2LuiI80+zsOADCvooqQH3qGCucQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", + "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz", + "integrity": "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.3.tgz", + "integrity": "sha512-QPZxHrThbQia7UdvfpaRRlq/J9ciz1J4go0k+lPBXbgaNeY7IQrBj/9ceWjvMMI07/ZBzHl/F0R/2K0qH7jCVw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz", + "integrity": "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.3.tgz", + "integrity": "sha512-PENDVxdr7ZxKPyi5Ffc0LjXdnJyrJxyqF5T5YjlVg4a0VFfQHW0r8iAtRiDXkfHlu1wwcvdtnndGYIeJLSuRMQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.3.tgz", + "integrity": "sha512-FGEQmugvAEu2QtgtU0uTASXevfLMFfBeVCIIdcQhn/uBQsMTjBajdnAtanQlOcuihWh10PZ7+HWvc7NtBwP74w==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-split-export-declaration": "^7.22.6", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz", + "integrity": "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/template": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz", + "integrity": "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz", + "integrity": "sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz", + "integrity": "sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.3.tgz", + "integrity": "sha512-vTG+cTGxPFou12Rj7ll+eD5yWeNl5/8xvQvF08y5Gv3v4mZQoyFf8/n9zg4q5vvCWt5jmgymfzMAldO7orBn7A==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz", + "integrity": "sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.3.tgz", + "integrity": "sha512-yCLhW34wpJWRdTxxWtFZASJisihrfyMOTOQexhVzA78jlU+dH7Dw+zQgcPepQ5F3C6bAIiblZZ+qBggJdHiBAg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.3.tgz", + "integrity": "sha512-X8jSm8X1CMwxmK878qsUGJRmbysKNbdpTv/O1/v0LuY/ZkZrng5WYiekYSdg9m09OTmDDUWeEDsTE+17WYbAZw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz", + "integrity": "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.3.tgz", + "integrity": "sha512-H9Ej2OiISIZowZHaBwF0tsJOih1PftXJtE8EWqlEIwpc7LMTGq0rPOrywKLQ4nefzx8/HMR0D3JGXoMHYvhi0A==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz", + "integrity": "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.3.tgz", + "integrity": "sha512-+pD5ZbxofyOygEp+zZAfujY2ShNCXRpDRIPOiBmTO693hhyOEteZgl876Xs9SAHPQpcV0vz8LvA/T+w8AzyX8A==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz", + "integrity": "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz", + "integrity": "sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz", + "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.3.tgz", + "integrity": "sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz", + "integrity": "sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz", + "integrity": "sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.3.tgz", + "integrity": "sha512-xzg24Lnld4DYIdysyf07zJ1P+iIfJpxtVFOzX4g+bsJ3Ng5Le7rXx9KwqKzuyaUeRnt+I1EICwQITqc0E2PmpA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.3.tgz", + "integrity": "sha512-s9GO7fIBi/BLsZ0v3Rftr6Oe4t0ctJ8h4CCXfPoEJwmvAPMyNrfkOOJzm6b9PX9YXcCJWWQd/sBF/N26eBiMVw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.3.tgz", + "integrity": "sha512-VxHt0ANkDmu8TANdE9Kc0rndo/ccsmfe2Cx2y5sI4hu3AukHQ5wAu4cM7j3ba8B9548ijVyclBU+nuDQftZsog==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.23.3", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz", + "integrity": "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.3.tgz", + "integrity": "sha512-LxYSb0iLjUamfm7f1D7GpiS4j0UAC8AOiehnsGAP8BEsIX8EOi3qV6bbctw8M7ZvLtcoZfZX5Z7rN9PlWk0m5A==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.3.tgz", + "integrity": "sha512-zvL8vIfIUgMccIAK1lxjvNv572JHFJIKb4MWBz5OGdBQA0fB0Xluix5rmOby48exiJc987neOmP/m9Fnpkz3Tg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz", + "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz", + "integrity": "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.3.tgz", + "integrity": "sha512-a5m2oLNFyje2e/rGKjVfAELTVI5mbA0FeZpBnkOWWV7eSmKQ+T/XW0Vf+29ScLzSxX+rnsarvU0oie/4m6hkxA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz", + "integrity": "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz", + "integrity": "sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz", + "integrity": "sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz", + "integrity": "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz", + "integrity": "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz", + "integrity": "sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz", + "integrity": "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz", + "integrity": "sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz", + "integrity": "sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz", + "integrity": "sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz", + "integrity": "sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz", + "integrity": "sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.3.tgz", + "integrity": "sha512-ovzGc2uuyNfNAs/jyjIGxS8arOHS5FENZaNn4rtE7UdKMMkqHCvboHfcuhWLZNX5cB44QfcGNWjaevxMzzMf+Q==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.23.3", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.15", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.3", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.23.3", + "@babel/plugin-syntax-import-attributes": "^7.23.3", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.23.3", + "@babel/plugin-transform-async-generator-functions": "^7.23.3", + "@babel/plugin-transform-async-to-generator": "^7.23.3", + "@babel/plugin-transform-block-scoped-functions": "^7.23.3", + "@babel/plugin-transform-block-scoping": "^7.23.3", + "@babel/plugin-transform-class-properties": "^7.23.3", + "@babel/plugin-transform-class-static-block": "^7.23.3", + "@babel/plugin-transform-classes": "^7.23.3", + "@babel/plugin-transform-computed-properties": "^7.23.3", + "@babel/plugin-transform-destructuring": "^7.23.3", + "@babel/plugin-transform-dotall-regex": "^7.23.3", + "@babel/plugin-transform-duplicate-keys": "^7.23.3", + "@babel/plugin-transform-dynamic-import": "^7.23.3", + "@babel/plugin-transform-exponentiation-operator": "^7.23.3", + "@babel/plugin-transform-export-namespace-from": "^7.23.3", + "@babel/plugin-transform-for-of": "^7.23.3", + "@babel/plugin-transform-function-name": "^7.23.3", + "@babel/plugin-transform-json-strings": "^7.23.3", + "@babel/plugin-transform-literals": "^7.23.3", + "@babel/plugin-transform-logical-assignment-operators": "^7.23.3", + "@babel/plugin-transform-member-expression-literals": "^7.23.3", + "@babel/plugin-transform-modules-amd": "^7.23.3", + "@babel/plugin-transform-modules-commonjs": "^7.23.3", + "@babel/plugin-transform-modules-systemjs": "^7.23.3", + "@babel/plugin-transform-modules-umd": "^7.23.3", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.23.3", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.3", + "@babel/plugin-transform-numeric-separator": "^7.23.3", + "@babel/plugin-transform-object-rest-spread": "^7.23.3", + "@babel/plugin-transform-object-super": "^7.23.3", + "@babel/plugin-transform-optional-catch-binding": "^7.23.3", + "@babel/plugin-transform-optional-chaining": "^7.23.3", + "@babel/plugin-transform-parameters": "^7.23.3", + "@babel/plugin-transform-private-methods": "^7.23.3", + "@babel/plugin-transform-private-property-in-object": "^7.23.3", + "@babel/plugin-transform-property-literals": "^7.23.3", + "@babel/plugin-transform-regenerator": "^7.23.3", + "@babel/plugin-transform-reserved-words": "^7.23.3", + "@babel/plugin-transform-shorthand-properties": "^7.23.3", + "@babel/plugin-transform-spread": "^7.23.3", + "@babel/plugin-transform-sticky-regex": "^7.23.3", + "@babel/plugin-transform-template-literals": "^7.23.3", + "@babel/plugin-transform-typeof-symbol": "^7.23.3", + "@babel/plugin-transform-unicode-escapes": "^7.23.3", + "@babel/plugin-transform-unicode-property-regex": "^7.23.3", + "@babel/plugin-transform-unicode-regex": "^7.23.3", + "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.6", + "babel-plugin-polyfill-corejs3": "^0.8.5", + "babel-plugin-polyfill-regenerator": "^0.5.3", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true, + "peer": true + }, "node_modules/@babel/runtime": { "version": "7.22.10", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz", @@ -735,6 +2023,129 @@ "node": ">=0.1.90" } }, + "node_modules/@cypress/code-coverage": { + "version": "3.12.9", + "resolved": "https://registry.npmjs.org/@cypress/code-coverage/-/code-coverage-3.12.9.tgz", + "integrity": "sha512-MIVISl2WOYzV0DgRZXs9wxYRBMM5eN2n/p4PN0wPJCATllfv8nqqyMC1TGWBrf8Di+QMikUjbSZ20w/5+9w4Lg==", + "dev": true, + "dependencies": { + "@cypress/webpack-preprocessor": "^6.0.0", + "chalk": "4.1.2", + "dayjs": "1.11.10", + "debug": "4.3.4", + "execa": "4.1.0", + "globby": "11.0.4", + "istanbul-lib-coverage": "3.0.0", + "js-yaml": "4.1.0", + "nyc": "15.1.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.1", + "@babel/preset-env": "^7.0.0", + "babel-loader": "^8.3 || ^9", + "cypress": "*", + "webpack": "^4 || ^5" + } + }, + "node_modules/@cypress/code-coverage/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@cypress/code-coverage/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@cypress/code-coverage/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@cypress/code-coverage/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@cypress/code-coverage/node_modules/globby": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", + "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@cypress/code-coverage/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@cypress/code-coverage/node_modules/istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@cypress/code-coverage/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@cypress/request": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz", @@ -776,6 +2187,29 @@ "node": ">= 0.12" } }, + "node_modules/@cypress/webpack-preprocessor": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@cypress/webpack-preprocessor/-/webpack-preprocessor-6.0.0.tgz", + "integrity": "sha512-1AS1Et5CNPJii0+DdBZBS8e0hlM2BkBNmYRdZO4/16A3KS3em1sjPZtFw7jJF00m6DYAdB9iy6QW/lLZ2bN0gg==", + "dev": true, + "dependencies": { + "bluebird": "3.7.1", + "debug": "^4.3.4", + "lodash": "^4.17.20" + }, + "peerDependencies": { + "@babel/core": "^7.0.1", + "@babel/preset-env": "^7.0.0", + "babel-loader": "^8.3 || ^9", + "webpack": "^4 || ^5" + } + }, + "node_modules/@cypress/webpack-preprocessor/node_modules/bluebird": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz", + "integrity": "sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==", + "dev": true + }, "node_modules/@cypress/xvfb": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", @@ -2819,28 +4253,38 @@ } }, "node_modules/@patternfly/react-component-groups": { - "version": "1.0.17", - "resolved": "https://registry.npmjs.org/@patternfly/react-component-groups/-/react-component-groups-1.0.17.tgz", - "integrity": "sha512-SeTnlmEyk9q/TxnLs2WCNuEhpD9iKdgArW+OnW6O/rm/VDtPAeJ7O+SYT0JeZDqtNzMvAb9D02GE06k+5urchQ==", + "version": "5.0.0-prerelease.6", + "resolved": "https://registry.npmjs.org/@patternfly/react-component-groups/-/react-component-groups-5.0.0-prerelease.6.tgz", + "integrity": "sha512-xqcq3BUuk5ISpBuYDhUBalAz7SV3T5KNhM7pI4RG66Av7S7l5qRGjWJr83beLSb5rN3lbQEDhFGBWkBBuZjShw==", "dependencies": { - "@patternfly/react-core": "^5.0.0", - "@patternfly/react-icons": "^5.0.0", - "react-jss": "^10.9.2" + "@patternfly/react-core": "^5.1.1", + "@patternfly/react-icons": "^5.1.1", + "@patternfly/react-table": "^5.1.1", + "clsx": "^2.0.0", + "react-jss": "^10.10.0" }, "peerDependencies": { "react": "^17 || ^18", "react-dom": "^17 || ^18" } }, + "node_modules/@patternfly/react-component-groups/node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/@patternfly/react-core": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-5.0.1.tgz", - "integrity": "sha512-Eevd+8ACLFV733J+cpo4FRgNtRBObIgmUcrqLjf9H99jZ1hFpBgacFyHiALFi2cuoNVGmdEzskFl+4c7Uo0n+w==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-5.1.1.tgz", + "integrity": "sha512-9DbgQMXYmF8A4aCNLKXwIN1H07SIPoPaVLvx+yiDuJfDx4Qi0T+H7j5cx0VfDfxuCpqea3POJWqBQn1HnwS4wQ==", "dependencies": { - "@patternfly/react-icons": "^5.0.1", - "@patternfly/react-styles": "^5.0.1", - "@patternfly/react-tokens": "^5.0.1", - "focus-trap": "7.4.3", + "@patternfly/react-icons": "^5.1.1", + "@patternfly/react-styles": "^5.1.1", + "@patternfly/react-tokens": "^5.1.1", + "focus-trap": "7.5.2", "react-dropzone": "^14.2.3", "tslib": "^2.5.0" }, @@ -2850,29 +4294,28 @@ } }, "node_modules/@patternfly/react-icons": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-5.0.1.tgz", - "integrity": "sha512-MduetDRzve3eRlKAioM/UxmVuPyFccdeBWAKhbN4SBn7RaZWS7kO7/xZzNkpeT5pqQIeAACvz3uiV2/3uAf38w==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-5.1.1.tgz", + "integrity": "sha512-9gCxkWz2xcdi0rtXu2F0L68w4tLIlsgGTACo1ggr4aVng9jRX++o1PlCOqscOd9o0NiFnFD7BLlZUGvJWaYEZg==", "peerDependencies": { "react": "^17 || ^18", "react-dom": "^17 || ^18" } }, "node_modules/@patternfly/react-styles": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-5.0.1.tgz", - "integrity": "sha512-kHP/lbvmhBnNfWiqJJLNwOQZnkcl6wfwAesRp22s4Lj941EWe0oFIqn925/uORIOAOz2du1121t7T4UTfLZg4w==" + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-5.1.1.tgz", + "integrity": "sha512-swO9X+WixYYDsMVsEJp1V8QUfhEQY91QfFm4phfYP4jc2TQ2opIFYdUIHkc+yrZwBhrgb/pPUUfemyqAoSbZcA==" }, "node_modules/@patternfly/react-table": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-table/-/react-table-5.0.0.tgz", - "integrity": "sha512-Q3MBo9+ZmBvLJzVHxmV9f/4qQAz5Si743zVLHRwjh+tjbn/DrcbxJdT8Uxa3NGKkpvszzgi/LPeXipJOHOELug==", - "peer": true, + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-table/-/react-table-5.1.1.tgz", + "integrity": "sha512-9tAtHj16hemJ6YRBWIm2O+QRNoFWYQt8ZLQ1G0KBwpg2t2G2CbGsS2RG+BamO4IVE6IPo3Yoo39p4UCNRiGVpA==", "dependencies": { - "@patternfly/react-core": "^5.0.0", - "@patternfly/react-icons": "^5.0.0", - "@patternfly/react-styles": "^5.0.0", - "@patternfly/react-tokens": "^5.0.0", + "@patternfly/react-core": "^5.1.1", + "@patternfly/react-icons": "^5.1.1", + "@patternfly/react-styles": "^5.1.1", + "@patternfly/react-tokens": "^5.1.1", "lodash": "^4.17.19", "tslib": "^2.5.0" }, @@ -2882,9 +4325,9 @@ } }, "node_modules/@patternfly/react-tokens": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-5.0.1.tgz", - "integrity": "sha512-YafAGJYvxDP4GaQ0vMybalWmx7MJ+etUf1cGoaMh0wRD2eswltT/RckygtEBKR/M61qXbgG+CxKmMyY8leoiDw==" + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-5.1.1.tgz", + "integrity": "sha512-cHuNkzNA9IY9aDwfjSEkitQoVEvRhOJRKhH0yIRlRByEkbdoV9jJZ9xj20hNShE+bxmNuom+MCTQSkpkN1bV8A==" }, "node_modules/@pmmmwh/react-refresh-webpack-plugin": { "version": "0.5.10", @@ -3056,15 +4499,15 @@ } }, "node_modules/@redhat-cloud-services/frontend-components": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components/-/frontend-components-4.0.10.tgz", - "integrity": "sha512-xYgHwtpkPYXXByMizbraIm+M9WXS65FDwA4wIJq/hzOAiVv4WtORVCRn3Ysul3wAKlXLB9Q+M6kzxXXcbc+v4g==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components/-/frontend-components-4.1.0.tgz", + "integrity": "sha512-IsQf/DK5nA+ARFdq25o5Ndi2EAE5cgkDA80GjK3CluTvV+0ve5wRCtD/nCnVbRs9i/CbSAu8YTCvLA6tCTUKyA==", "dependencies": { - "@patternfly/react-component-groups": "^1.0.17", + "@patternfly/react-component-groups": "^5.0.0-prerelease.5", "@redhat-cloud-services/frontend-components-utilities": "^4.0.0", "@redhat-cloud-services/types": "^0.0.24", - "@scalprum/core": "^0.5.1", - "@scalprum/react-core": "^0.5.1", + "@scalprum/core": "^0.5.4", + "@scalprum/react-core": "^0.5.4", "sanitize-html": "^2.7.2" }, "peerDependencies": { @@ -3166,12 +4609,12 @@ } }, "node_modules/@redhat-cloud-services/frontend-components-notifications": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-notifications/-/frontend-components-notifications-4.0.2.tgz", - "integrity": "sha512-xivlLFmlAhn+WNwaKuHXXQhSeVW+EkuApAErhvYanC5SZ32H9cIzp4aF61ZoLv7A1pjTSu/QcZ+BxvfTKCNrUQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-notifications/-/frontend-components-notifications-4.1.0.tgz", + "integrity": "sha512-bEoAeZMVY+UuSriSeruZ1pIesiPEjScrVFJQ5Wq/w3UFc79oXSYOgpRElxblPUH/LbKqp7inbzqRm2FxMcVxYg==", "dependencies": { - "@redhat-cloud-services/frontend-components": "^4.0.0", - "@redhat-cloud-services/frontend-components-utilities": "^4.0.0", + "@redhat-cloud-services/frontend-components": "^4.0.9", + "@redhat-cloud-services/frontend-components-utilities": "^4.0.2", "redux-promise-middleware": "6.1.3" }, "peerDependencies": { @@ -9055,6 +10498,18 @@ "integrity": "sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ==", "dev": true }, + "node_modules/append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "dependencies": { + "default-require-extensions": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/arch": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", @@ -9074,6 +10529,12 @@ } ] }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "dev": true + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -9116,15 +10577,15 @@ "dev": true }, "node_modules/array-includes": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", - "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", + "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "get-intrinsic": "^1.1.3", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", "is-string": "^1.0.7" }, "engines": { @@ -9153,14 +10614,14 @@ } }, "node_modules/array.prototype.flat": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", - "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", "es-shim-unscopables": "^1.0.0" }, "engines": { @@ -9171,14 +10632,14 @@ } }, "node_modules/array.prototype.flatmap": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", - "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", "es-shim-unscopables": "^1.0.0" }, "engines": { @@ -9556,6 +11017,81 @@ "node": ">=8" } }, + "node_modules/babel-loader": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz", + "integrity": "sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==", + "dev": true, + "peer": true, + "dependencies": { + "find-cache-dir": "^4.0.0", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0", + "webpack": ">=5" + } + }, + "node_modules/babel-loader/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/babel-loader/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/babel-loader/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "peer": true + }, + "node_modules/babel-loader/node_modules/schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dev": true, + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/babel-plugin-istanbul": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", @@ -9587,6 +11123,48 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.6.tgz", + "integrity": "sha512-jhHiWVZIlnPbEUKSSNb9YoWcQGdlTLq7z1GHL4AjFxaoOUMuuEVJ+Y4pAaQUGOGk93YsVCKPbqbfw3m0SM6H8Q==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.4.3", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.8.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.6.tgz", + "integrity": "sha512-leDIc4l4tUgU7str5BWLS2h8q2N4Nf6lGZP6UrNDxdtfF2g69eJ5L0H7S8A5Ln/arfFAfHor5InAdZuIOwZdgQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.4.3", + "core-js-compat": "^3.33.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.3.tgz", + "integrity": "sha512-8sHeDOmXC8csczMrYEOf0UTNa4yE2SxV5JGeT/LP1n0OYVDUUFPxG9vdk2AlDlIit4t+Kf0xCtpgXPBwnn/9pw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.4.3" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, "node_modules/babel-preset-current-node-syntax": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", @@ -9939,9 +11517,9 @@ } }, "node_modules/browserslist": { - "version": "4.21.10", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", - "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", "dev": true, "funding": [ { @@ -9958,10 +11536,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001517", - "electron-to-chromium": "^1.4.477", + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.11" + "update-browserslist-db": "^1.0.13" }, "bin": { "browserslist": "cli.js" @@ -10045,6 +11623,48 @@ "node": ">=6" } }, + "node_modules/caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "dependencies": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/caching-transform/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caching-transform/node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -10086,9 +11706,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001520", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001520.tgz", - "integrity": "sha512-tahF5O9EiiTzwTUqAeFjIZbn4Dnqxzz7ktrgGlMYNLH43Ul26IgTMH/zvL3DG0lZxBYnlT04axvInszUsZULdA==", + "version": "1.0.30001562", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001562.tgz", + "integrity": "sha512-kfte3Hym//51EdX4239i+Rmp20EsLIYGdPkERegTgU19hQWCRhsRFGKHTliUlsry53tv17K7n077Kqa0WJU4ng==", "dev": true, "funding": [ { @@ -10628,6 +12248,20 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "dev": true }, + "node_modules/core-js-compat": { + "version": "3.33.2", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.2.tgz", + "integrity": "sha512-axfo+wxFVxnqf8RvxTzoAlzW4gRoacrHeoFlc9n0x50+7BEyZL/Rt3hicaED1/CEd7I6tPCPVUYcJwCMO5XUYw==", + "dev": true, + "peer": true, + "dependencies": { + "browserslist": "^4.22.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/core-js-pure": { "version": "3.32.0", "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.32.0.tgz", @@ -10719,6 +12353,11 @@ "node": ">= 8" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" + }, "node_modules/css-jss": { "version": "10.10.0", "resolved": "https://registry.npmjs.org/css-jss/-/css-jss-10.10.0.tgz", @@ -11367,9 +13006,9 @@ } }, "node_modules/dayjs": { - "version": "1.11.9", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz", - "integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==" + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" }, "node_modules/debounce-promise": { "version": "3.1.2", @@ -11392,6 +13031,15 @@ } } }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/decimal.js": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", @@ -11526,6 +13174,21 @@ "node": ">=10.17.0" } }, + "node_modules/default-require-extensions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", + "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", + "dev": true, + "dependencies": { + "strip-bom": "^4.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -12117,9 +13780,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.490", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.490.tgz", - "integrity": "sha512-6s7NVJz+sATdYnIwhdshx/N/9O6rvMxmhVoDSDFdj6iA45gHR8EQje70+RYsF4GeB+k0IeNSBnP7yG9ZXJFr7A==", + "version": "1.4.582", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.582.tgz", + "integrity": "sha512-89o0MGoocwYbzqUUjc+VNpeOFSOK9nIdC5wY4N+PVUarUK0MtjyTjks75AZS2bW4Kl8MdewdFsWaH0jLy+JNoA==", "dev": true }, "node_modules/emittery": { @@ -12350,6 +14013,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, "node_modules/es6-object-assign": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", @@ -13301,6 +14970,23 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, + "node_modules/find-cache-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", + "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", + "dev": true, + "peer": true, + "dependencies": { + "common-path-prefix": "^3.0.0", + "pkg-dir": "^7.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -13374,11 +15060,11 @@ "dev": true }, "node_modules/focus-trap": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.4.3.tgz", - "integrity": "sha512-BgSSbK4GPnS2VbtZ50VtOv1Sti6DIkj3+LkVjiWMNjLeAp1SH1UlLx3ULu/DCu4vq5R4/uvTm+zrvsMsuYmGLg==", + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.2.tgz", + "integrity": "sha512-p6vGNNWLDGwJCiEjkSK6oERj/hEyI9ITsSwIUICBoKLlWiTWXJRfQibCwcoi50rTZdbi87qDtUlMCmQwsGSgPw==", "dependencies": { - "tabbable": "^6.1.2" + "tabbable": "^6.2.0" } }, "node_modules/follow-redirects": { @@ -13409,6 +15095,19 @@ "is-callable": "^1.1.3" } }, + "node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -13585,6 +15284,26 @@ "node": ">= 0.6" } }, + "node_modules/fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -13624,9 +15343,12 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/function.prototype.name": { "version": "1.1.5", @@ -14094,6 +15816,42 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "dependencies": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hasha/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -14851,11 +16609,11 @@ } }, "node_modules/is-core-module": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", - "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -15362,6 +17120,18 @@ "node": ">=8" } }, + "node_modules/istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "dependencies": { + "append-transform": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/istanbul-lib-instrument": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", @@ -15378,6 +17148,50 @@ "node": ">=8" } }, + "node_modules/istanbul-lib-processinfo": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", + "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", + "dev": true, + "dependencies": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", @@ -18506,9 +20320,9 @@ } }, "node_modules/joi": { - "version": "17.9.2", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.9.2.tgz", - "integrity": "sha512-Itk/r+V4Dx0V3c7RLFdRh12IOjySm2/WGPMubBT92cQvRfYZhPM2W0hZlctjj72iES8jsRCwp7S/cRmWBnJ4nw==", + "version": "17.11.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz", + "integrity": "sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==", "dev": true, "dependencies": { "@hapi/hoek": "^9.0.0", @@ -18518,6 +20332,26 @@ "@sideway/pinpoint": "^2.0.0" } }, + "node_modules/jotai": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.5.1.tgz", + "integrity": "sha512-vanPCCSuHczUXNbVh/iUunuMfrWRL4FdBtAbTRmrfqezJcKb8ybBTg8iivyYuUHapjcDETyJe1E4inlo26bVHA==", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=17.0.0", + "react": ">=17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "node_modules/js-cookie": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", @@ -18526,11 +20360,6 @@ "node": ">=14" } }, - "node_modules/js-sha256": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", - "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==" - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -18899,14 +20728,10 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/keycloak-js": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-21.1.2.tgz", - "integrity": "sha512-+6r1BvmutWGJBtibo7bcFbHWIlA7XoXRCwcA4vopeJh59Nv2Js0ju2u+t8AYth+C6Cg7/BNfO3eCTbsl/dTBHw==", - "dependencies": { - "base64-js": "^1.5.1", - "js-sha256": "^0.9.0" - } + "node_modules/jwt-decode": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", + "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" }, "node_modules/kind-of": { "version": "6.0.3", @@ -19122,6 +20947,19 @@ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true, + "peer": true + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", + "dev": true + }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", @@ -20034,6 +21872,18 @@ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", "dev": true }, + "node_modules/node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "dependencies": { + "process-on-spawn": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/node-releases": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", @@ -20263,6 +22113,293 @@ "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==", "dev": true }, + "node_modules/nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "dependencies": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "bin": { + "nyc": "bin/nyc.js" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/nyc/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/nyc/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/nyc/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/nyc/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/nyc/node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/nyc/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nyc/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nyc/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/nyc/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "node_modules/nyc/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", @@ -20363,14 +22500,14 @@ } }, "node_modules/object.fromentries": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", - "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", + "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "engines": { "node": ">= 0.4" @@ -20393,14 +22530,14 @@ } }, "node_modules/object.values": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", - "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", + "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "engines": { "node": ">= 0.4" @@ -20420,6 +22557,18 @@ "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", "dev": true }, + "node_modules/oidc-client-ts": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-2.4.0.tgz", + "integrity": "sha512-WijhkTrlXK2VvgGoakWJiBdfIsVGz6CFzgjNNqZU1hPKV2kyeEaJgLs7RwuiSp2WhLfWBQuLvr2SxVlZnk3N1w==", + "dependencies": { + "crypto-js": "^4.2.0", + "jwt-decode": "^3.1.2" + }, + "engines": { + "node": ">=12.13.0" + } + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -20699,6 +22848,21 @@ "node": ">=6" } }, + "node_modules/package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -21051,6 +23215,110 @@ "node": ">=12.13.0" } }, + "node_modules/pkg-dir": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", + "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "dev": true, + "peer": true, + "dependencies": { + "find-up": "^6.3.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dev": true, + "peer": true, + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "peer": true, + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "peer": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "peer": true, + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "peer": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/pkg-dir/node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "peer": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/pluralize": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", @@ -21626,6 +23894,18 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, + "node_modules/process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "dependencies": { + "fromentries": "^1.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -22030,6 +24310,18 @@ "react": ">=16.8.6" } }, + "node_modules/react-oidc-context": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/react-oidc-context/-/react-oidc-context-2.3.1.tgz", + "integrity": "sha512-WdhmEU6odNzMk9pvOScxUkf6/1aduiI/nQryr7+iCl2VDnYLASDTIV/zy58KuK4VXG3fBaRKukc/mRpMjF9a3Q==", + "engines": { + "node": ">=12.13.0" + }, + "peerDependencies": { + "oidc-client-ts": "^2.2.1", + "react": ">=16.8.0" + } + }, "node_modules/react-redux": { "version": "8.1.2", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.2.tgz", @@ -22232,11 +24524,41 @@ "redux": "^2.0.0 || ^3.0.0 || ^4.0.0" } }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true, + "peer": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "dev": true, + "peer": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/regenerator-runtime": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, "node_modules/regex-parser": { "version": "2.2.11", "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", @@ -22259,6 +24581,47 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "peer": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "peer": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, "node_modules/relateurl": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", @@ -22268,6 +24631,18 @@ "node": ">= 0.10" } }, + "node_modules/release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", + "dev": true, + "dependencies": { + "es6-error": "^4.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/renderkid": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", @@ -22538,6 +24913,12 @@ "node": ">=0.10.0" } }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, "node_modules/requirejs": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz", @@ -23199,6 +25580,12 @@ "node": ">= 0.8.0" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -23447,6 +25834,53 @@ "resolved": "https://registry.npmjs.org/spark-md5/-/spark-md5-3.0.2.tgz", "integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==" }, + "node_modules/spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "dependencies": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/spawn-wrap/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/spawn-wrap/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -23884,6 +26318,12 @@ "webpack": ">=2" } }, + "node_modules/swc-plugin-coverage-instrument": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/swc-plugin-coverage-instrument/-/swc-plugin-coverage-instrument-0.0.20.tgz", + "integrity": "sha512-WXTGILCZE2hW61yrmxi6doN/UB4RT2K1JJSQVPn9JMJ6X4WJpZsesHi4lHy6qRKVsNIlHZvTWofkpuRi/WQUig==", + "dev": true + }, "node_modules/symbol-observable": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", @@ -24430,6 +26870,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, "node_modules/typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", @@ -24462,6 +26911,50 @@ "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==" }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "peer": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, "node_modules/unicode-trie": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-0.3.1.tgz", @@ -24526,9 +27019,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", - "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", "dev": true, "funding": [ { @@ -25973,16 +28466,16 @@ } }, "node_modules/wait-on": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.0.1.tgz", - "integrity": "sha512-9AnJE9qTjRQOlTZIldAaf/da2eW0eSRSgcqq85mXQja/DW3MriHxkpODDSUEg+Gri/rKEcXUZHe+cevvYItaog==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.1.0.tgz", + "integrity": "sha512-U7TF/OYYzAg+OoiT/B8opvN48UHt0QYMi4aD3PjRFpybQ+o6czQF8Ig3SKCCMJdxpBrCalIJ4O00FBof27Fu9Q==", "dev": true, "dependencies": { "axios": "^0.27.2", - "joi": "^17.7.0", + "joi": "^17.11.0", "lodash": "^4.17.21", - "minimist": "^1.2.7", - "rxjs": "^7.8.0" + "minimist": "^1.2.8", + "rxjs": "^7.8.1" }, "bin": { "wait-on": "bin/wait-on" @@ -26531,6 +29024,12 @@ "node": ">=12" } }, + "node_modules/whatwg-fetch": { + "version": "3.6.19", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.19.tgz", + "integrity": "sha512-d67JP4dHSbm2TrpFj8AbO8DnL1JXL5J9u0Kq2xW6d0TFDbCA3Muhdt8orXC22utleTVj7Prqt82baN6RBvnEgw==", + "dev": true + }, "node_modules/whatwg-mimetype": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", @@ -26598,6 +29097,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "dev": true + }, "node_modules/which-typed-array": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", @@ -26821,9 +29326,9 @@ "dev": true }, "@adobe/css-tools": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz", - "integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz", + "integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==", "dev": true }, "@ampproject/remapping": { @@ -26898,9 +29403,9 @@ } }, "@babel/compat-data": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz", - "integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.3.tgz", + "integrity": "sha512-BmR4bWbDIoFJmJ9z2cZ8Gmm2MXgEDgjdWgpKmKWUt54UGFJdlj31ECtbaDvCG/qVdG3AQ1SfpZEs01lUFbzLOQ==", "dev": true }, "@babel/core": { @@ -26949,19 +29454,83 @@ "jsesc": "^2.5.1" } }, + "@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dev": true, + "peer": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", + "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", + "dev": true, + "peer": true, + "requires": { + "@babel/types": "^7.22.15" + } + }, "@babel/helper-compilation-targets": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.10.tgz", - "integrity": "sha512-JMSwHD4J7SLod0idLq5PKgI+6g/hLD/iuWBq08ZX49xE14VpVEojJ5rHWptpirV2j020MvypRLAXAO50igCJ5Q==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", + "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", "dev": true, "requires": { "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.5", + "@babel/helper-validator-option": "^7.22.15", "browserslist": "^4.21.9", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, + "@babel/helper-create-class-features-plugin": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz", + "integrity": "sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", + "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + } + }, + "@babel/helper-define-polyfill-provider": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.3.tgz", + "integrity": "sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + } + }, "@babel/helper-environment-visitor": { "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", @@ -26987,26 +29556,46 @@ "@babel/types": "^7.22.5" } }, + "@babel/helper-member-expression-to-functions": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "dev": true, + "peer": true, + "requires": { + "@babel/types": "^7.23.0" + } + }, "@babel/helper-module-imports": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", - "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", "dev": true, "requires": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.22.15" } }, "@babel/helper-module-transforms": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz", - "integrity": "sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", "dev": true, "requires": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", "@babel/helper-simple-access": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.5" + "@babel/helper-validator-identifier": "^7.22.20" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dev": true, + "peer": true, + "requires": { + "@babel/types": "^7.22.5" } }, "@babel/helper-plugin-utils": { @@ -27015,6 +29604,30 @@ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", "dev": true }, + "@babel/helper-remap-async-to-generator": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", + "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-wrap-function": "^7.22.20" + } + }, + "@babel/helper-replace-supers": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", + "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-optimise-call-expression": "^7.22.5" + } + }, "@babel/helper-simple-access": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", @@ -27024,6 +29637,16 @@ "@babel/types": "^7.22.5" } }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dev": true, + "peer": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, "@babel/helper-split-export-declaration": { "version": "7.22.6", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", @@ -27046,11 +29669,23 @@ "dev": true }, "@babel/helper-validator-option": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", - "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", + "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", "dev": true }, + "@babel/helper-wrap-function": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", + "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-function-name": "^7.22.5", + "@babel/template": "^7.22.15", + "@babel/types": "^7.22.19" + } + }, "@babel/helpers": { "version": "7.22.10", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.10.tgz", @@ -27078,6 +29713,47 @@ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==" }, + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz", + "integrity": "sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz", + "integrity": "sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.23.3" + } + }, + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.3.tgz", + "integrity": "sha512-XaJak1qcityzrX0/IU5nKHb34VaibwP3saKqG6a/tppelgllOH13LUann4ZCIBcVOeE6H18K4Vx9QKkVww3z/w==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "peer": true, + "requires": {} + }, "@babel/plugin-syntax-async-generators": { "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", @@ -27105,6 +29781,56 @@ "@babel/helper-plugin-utils": "^7.12.13" } }, + "@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-import-assertions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz", + "integrity": "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-import-attributes": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz", + "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, "@babel/plugin-syntax-import-meta": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", @@ -27186,6 +29912,16 @@ "@babel/helper-plugin-utils": "^7.8.0" } }, + "@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, "@babel/plugin-syntax-top-level-await": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", @@ -27204,6 +29940,657 @@ "@babel/helper-plugin-utils": "^7.22.5" } }, + "@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz", + "integrity": "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-async-generator-functions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.3.tgz", + "integrity": "sha512-59GsVNavGxAXCDDbakWSMJhajASb4kBCqDjqJsv+p5nKdbz7istmZ3HrX3L2LuiI80+zsOADCvooqQH3qGCucQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20", + "@babel/plugin-syntax-async-generators": "^7.8.4" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", + "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz", + "integrity": "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.3.tgz", + "integrity": "sha512-QPZxHrThbQia7UdvfpaRRlq/J9ciz1J4go0k+lPBXbgaNeY7IQrBj/9ceWjvMMI07/ZBzHl/F0R/2K0qH7jCVw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-class-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz", + "integrity": "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-class-static-block": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.3.tgz", + "integrity": "sha512-PENDVxdr7ZxKPyi5Ffc0LjXdnJyrJxyqF5T5YjlVg4a0VFfQHW0r8iAtRiDXkfHlu1wwcvdtnndGYIeJLSuRMQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.3.tgz", + "integrity": "sha512-FGEQmugvAEu2QtgtU0uTASXevfLMFfBeVCIIdcQhn/uBQsMTjBajdnAtanQlOcuihWh10PZ7+HWvc7NtBwP74w==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-split-export-declaration": "^7.22.6", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz", + "integrity": "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/template": "^7.22.15" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz", + "integrity": "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz", + "integrity": "sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz", + "integrity": "sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-dynamic-import": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.3.tgz", + "integrity": "sha512-vTG+cTGxPFou12Rj7ll+eD5yWeNl5/8xvQvF08y5Gv3v4mZQoyFf8/n9zg4q5vvCWt5jmgymfzMAldO7orBn7A==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz", + "integrity": "sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-export-namespace-from": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.3.tgz", + "integrity": "sha512-yCLhW34wpJWRdTxxWtFZASJisihrfyMOTOQexhVzA78jlU+dH7Dw+zQgcPepQ5F3C6bAIiblZZ+qBggJdHiBAg==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.3.tgz", + "integrity": "sha512-X8jSm8X1CMwxmK878qsUGJRmbysKNbdpTv/O1/v0LuY/ZkZrng5WYiekYSdg9m09OTmDDUWeEDsTE+17WYbAZw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz", + "integrity": "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-json-strings": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.3.tgz", + "integrity": "sha512-H9Ej2OiISIZowZHaBwF0tsJOih1PftXJtE8EWqlEIwpc7LMTGq0rPOrywKLQ4nefzx8/HMR0D3JGXoMHYvhi0A==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-json-strings": "^7.8.3" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz", + "integrity": "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-logical-assignment-operators": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.3.tgz", + "integrity": "sha512-+pD5ZbxofyOygEp+zZAfujY2ShNCXRpDRIPOiBmTO693hhyOEteZgl876Xs9SAHPQpcV0vz8LvA/T+w8AzyX8A==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz", + "integrity": "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz", + "integrity": "sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz", + "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.3.tgz", + "integrity": "sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz", + "integrity": "sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz", + "integrity": "sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.3.tgz", + "integrity": "sha512-xzg24Lnld4DYIdysyf07zJ1P+iIfJpxtVFOzX4g+bsJ3Ng5Le7rXx9KwqKzuyaUeRnt+I1EICwQITqc0E2PmpA==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + } + }, + "@babel/plugin-transform-numeric-separator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.3.tgz", + "integrity": "sha512-s9GO7fIBi/BLsZ0v3Rftr6Oe4t0ctJ8h4CCXfPoEJwmvAPMyNrfkOOJzm6b9PX9YXcCJWWQd/sBF/N26eBiMVw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-transform-object-rest-spread": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.3.tgz", + "integrity": "sha512-VxHt0ANkDmu8TANdE9Kc0rndo/ccsmfe2Cx2y5sI4hu3AukHQ5wAu4cM7j3ba8B9548ijVyclBU+nuDQftZsog==", + "dev": true, + "peer": true, + "requires": { + "@babel/compat-data": "^7.23.3", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.23.3" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz", + "integrity": "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20" + } + }, + "@babel/plugin-transform-optional-catch-binding": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.3.tgz", + "integrity": "sha512-LxYSb0iLjUamfm7f1D7GpiS4j0UAC8AOiehnsGAP8BEsIX8EOi3qV6bbctw8M7ZvLtcoZfZX5Z7rN9PlWk0m5A==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + } + }, + "@babel/plugin-transform-optional-chaining": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.3.tgz", + "integrity": "sha512-zvL8vIfIUgMccIAK1lxjvNv572JHFJIKb4MWBz5OGdBQA0fB0Xluix5rmOby48exiJc987neOmP/m9Fnpkz3Tg==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz", + "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-private-methods": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz", + "integrity": "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-private-property-in-object": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.3.tgz", + "integrity": "sha512-a5m2oLNFyje2e/rGKjVfAELTVI5mbA0FeZpBnkOWWV7eSmKQ+T/XW0Vf+29ScLzSxX+rnsarvU0oie/4m6hkxA==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz", + "integrity": "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz", + "integrity": "sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "regenerator-transform": "^0.15.2" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz", + "integrity": "sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz", + "integrity": "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz", + "integrity": "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz", + "integrity": "sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz", + "integrity": "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz", + "integrity": "sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz", + "integrity": "sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-unicode-property-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz", + "integrity": "sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz", + "integrity": "sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-unicode-sets-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz", + "integrity": "sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/preset-env": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.3.tgz", + "integrity": "sha512-ovzGc2uuyNfNAs/jyjIGxS8arOHS5FENZaNn4rtE7UdKMMkqHCvboHfcuhWLZNX5cB44QfcGNWjaevxMzzMf+Q==", + "dev": true, + "peer": true, + "requires": { + "@babel/compat-data": "^7.23.3", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.15", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.3", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.23.3", + "@babel/plugin-syntax-import-attributes": "^7.23.3", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.23.3", + "@babel/plugin-transform-async-generator-functions": "^7.23.3", + "@babel/plugin-transform-async-to-generator": "^7.23.3", + "@babel/plugin-transform-block-scoped-functions": "^7.23.3", + "@babel/plugin-transform-block-scoping": "^7.23.3", + "@babel/plugin-transform-class-properties": "^7.23.3", + "@babel/plugin-transform-class-static-block": "^7.23.3", + "@babel/plugin-transform-classes": "^7.23.3", + "@babel/plugin-transform-computed-properties": "^7.23.3", + "@babel/plugin-transform-destructuring": "^7.23.3", + "@babel/plugin-transform-dotall-regex": "^7.23.3", + "@babel/plugin-transform-duplicate-keys": "^7.23.3", + "@babel/plugin-transform-dynamic-import": "^7.23.3", + "@babel/plugin-transform-exponentiation-operator": "^7.23.3", + "@babel/plugin-transform-export-namespace-from": "^7.23.3", + "@babel/plugin-transform-for-of": "^7.23.3", + "@babel/plugin-transform-function-name": "^7.23.3", + "@babel/plugin-transform-json-strings": "^7.23.3", + "@babel/plugin-transform-literals": "^7.23.3", + "@babel/plugin-transform-logical-assignment-operators": "^7.23.3", + "@babel/plugin-transform-member-expression-literals": "^7.23.3", + "@babel/plugin-transform-modules-amd": "^7.23.3", + "@babel/plugin-transform-modules-commonjs": "^7.23.3", + "@babel/plugin-transform-modules-systemjs": "^7.23.3", + "@babel/plugin-transform-modules-umd": "^7.23.3", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.23.3", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.3", + "@babel/plugin-transform-numeric-separator": "^7.23.3", + "@babel/plugin-transform-object-rest-spread": "^7.23.3", + "@babel/plugin-transform-object-super": "^7.23.3", + "@babel/plugin-transform-optional-catch-binding": "^7.23.3", + "@babel/plugin-transform-optional-chaining": "^7.23.3", + "@babel/plugin-transform-parameters": "^7.23.3", + "@babel/plugin-transform-private-methods": "^7.23.3", + "@babel/plugin-transform-private-property-in-object": "^7.23.3", + "@babel/plugin-transform-property-literals": "^7.23.3", + "@babel/plugin-transform-regenerator": "^7.23.3", + "@babel/plugin-transform-reserved-words": "^7.23.3", + "@babel/plugin-transform-shorthand-properties": "^7.23.3", + "@babel/plugin-transform-spread": "^7.23.3", + "@babel/plugin-transform-sticky-regex": "^7.23.3", + "@babel/plugin-transform-template-literals": "^7.23.3", + "@babel/plugin-transform-typeof-symbol": "^7.23.3", + "@babel/plugin-transform-unicode-escapes": "^7.23.3", + "@babel/plugin-transform-unicode-property-regex": "^7.23.3", + "@babel/plugin-transform-unicode-regex": "^7.23.3", + "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.6", + "babel-plugin-polyfill-corejs3": "^0.8.5", + "babel-plugin-polyfill-regenerator": "^0.5.3", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" + } + }, + "@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true, + "peer": true + }, "@babel/runtime": { "version": "7.22.10", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz", @@ -27264,6 +30651,94 @@ "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "optional": true }, + "@cypress/code-coverage": { + "version": "3.12.9", + "resolved": "https://registry.npmjs.org/@cypress/code-coverage/-/code-coverage-3.12.9.tgz", + "integrity": "sha512-MIVISl2WOYzV0DgRZXs9wxYRBMM5eN2n/p4PN0wPJCATllfv8nqqyMC1TGWBrf8Di+QMikUjbSZ20w/5+9w4Lg==", + "dev": true, + "requires": { + "@cypress/webpack-preprocessor": "^6.0.0", + "chalk": "4.1.2", + "dayjs": "1.11.10", + "debug": "4.3.4", + "execa": "4.1.0", + "globby": "11.0.4", + "istanbul-lib-coverage": "3.0.0", + "js-yaml": "4.1.0", + "nyc": "15.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "globby": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", + "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "@cypress/request": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz", @@ -27301,6 +30776,25 @@ } } }, + "@cypress/webpack-preprocessor": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@cypress/webpack-preprocessor/-/webpack-preprocessor-6.0.0.tgz", + "integrity": "sha512-1AS1Et5CNPJii0+DdBZBS8e0hlM2BkBNmYRdZO4/16A3KS3em1sjPZtFw7jJF00m6DYAdB9iy6QW/lLZ2bN0gg==", + "dev": true, + "requires": { + "bluebird": "3.7.1", + "debug": "^4.3.4", + "lodash": "^4.17.20" + }, + "dependencies": { + "bluebird": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz", + "integrity": "sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==", + "dev": true + } + } + }, "@cypress/xvfb": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", @@ -28906,57 +32400,65 @@ } }, "@patternfly/react-component-groups": { - "version": "1.0.17", - "resolved": "https://registry.npmjs.org/@patternfly/react-component-groups/-/react-component-groups-1.0.17.tgz", - "integrity": "sha512-SeTnlmEyk9q/TxnLs2WCNuEhpD9iKdgArW+OnW6O/rm/VDtPAeJ7O+SYT0JeZDqtNzMvAb9D02GE06k+5urchQ==", + "version": "5.0.0-prerelease.6", + "resolved": "https://registry.npmjs.org/@patternfly/react-component-groups/-/react-component-groups-5.0.0-prerelease.6.tgz", + "integrity": "sha512-xqcq3BUuk5ISpBuYDhUBalAz7SV3T5KNhM7pI4RG66Av7S7l5qRGjWJr83beLSb5rN3lbQEDhFGBWkBBuZjShw==", "requires": { - "@patternfly/react-core": "^5.0.0", - "@patternfly/react-icons": "^5.0.0", - "react-jss": "^10.9.2" + "@patternfly/react-core": "^5.1.1", + "@patternfly/react-icons": "^5.1.1", + "@patternfly/react-table": "^5.1.1", + "clsx": "^2.0.0", + "react-jss": "^10.10.0" + }, + "dependencies": { + "clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==" + } } }, "@patternfly/react-core": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-5.0.1.tgz", - "integrity": "sha512-Eevd+8ACLFV733J+cpo4FRgNtRBObIgmUcrqLjf9H99jZ1hFpBgacFyHiALFi2cuoNVGmdEzskFl+4c7Uo0n+w==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-5.1.1.tgz", + "integrity": "sha512-9DbgQMXYmF8A4aCNLKXwIN1H07SIPoPaVLvx+yiDuJfDx4Qi0T+H7j5cx0VfDfxuCpqea3POJWqBQn1HnwS4wQ==", "requires": { - "@patternfly/react-icons": "^5.0.1", - "@patternfly/react-styles": "^5.0.1", - "@patternfly/react-tokens": "^5.0.1", - "focus-trap": "7.4.3", + "@patternfly/react-icons": "^5.1.1", + "@patternfly/react-styles": "^5.1.1", + "@patternfly/react-tokens": "^5.1.1", + "focus-trap": "7.5.2", "react-dropzone": "^14.2.3", "tslib": "^2.5.0" } }, "@patternfly/react-icons": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-5.0.1.tgz", - "integrity": "sha512-MduetDRzve3eRlKAioM/UxmVuPyFccdeBWAKhbN4SBn7RaZWS7kO7/xZzNkpeT5pqQIeAACvz3uiV2/3uAf38w==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-5.1.1.tgz", + "integrity": "sha512-9gCxkWz2xcdi0rtXu2F0L68w4tLIlsgGTACo1ggr4aVng9jRX++o1PlCOqscOd9o0NiFnFD7BLlZUGvJWaYEZg==", "requires": {} }, "@patternfly/react-styles": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-5.0.1.tgz", - "integrity": "sha512-kHP/lbvmhBnNfWiqJJLNwOQZnkcl6wfwAesRp22s4Lj941EWe0oFIqn925/uORIOAOz2du1121t7T4UTfLZg4w==" + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-5.1.1.tgz", + "integrity": "sha512-swO9X+WixYYDsMVsEJp1V8QUfhEQY91QfFm4phfYP4jc2TQ2opIFYdUIHkc+yrZwBhrgb/pPUUfemyqAoSbZcA==" }, "@patternfly/react-table": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-table/-/react-table-5.0.0.tgz", - "integrity": "sha512-Q3MBo9+ZmBvLJzVHxmV9f/4qQAz5Si743zVLHRwjh+tjbn/DrcbxJdT8Uxa3NGKkpvszzgi/LPeXipJOHOELug==", - "peer": true, + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-table/-/react-table-5.1.1.tgz", + "integrity": "sha512-9tAtHj16hemJ6YRBWIm2O+QRNoFWYQt8ZLQ1G0KBwpg2t2G2CbGsS2RG+BamO4IVE6IPo3Yoo39p4UCNRiGVpA==", "requires": { - "@patternfly/react-core": "^5.0.0", - "@patternfly/react-icons": "^5.0.0", - "@patternfly/react-styles": "^5.0.0", - "@patternfly/react-tokens": "^5.0.0", + "@patternfly/react-core": "^5.1.1", + "@patternfly/react-icons": "^5.1.1", + "@patternfly/react-styles": "^5.1.1", + "@patternfly/react-tokens": "^5.1.1", "lodash": "^4.17.19", "tslib": "^2.5.0" } }, "@patternfly/react-tokens": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-5.0.1.tgz", - "integrity": "sha512-YafAGJYvxDP4GaQ0vMybalWmx7MJ+etUf1cGoaMh0wRD2eswltT/RckygtEBKR/M61qXbgG+CxKmMyY8leoiDw==" + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-5.1.1.tgz", + "integrity": "sha512-cHuNkzNA9IY9aDwfjSEkitQoVEvRhOJRKhH0yIRlRByEkbdoV9jJZ9xj20hNShE+bxmNuom+MCTQSkpkN1bV8A==" }, "@pmmmwh/react-refresh-webpack-plugin": { "version": "0.5.10", @@ -29084,15 +32586,15 @@ } }, "@redhat-cloud-services/frontend-components": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components/-/frontend-components-4.0.10.tgz", - "integrity": "sha512-xYgHwtpkPYXXByMizbraIm+M9WXS65FDwA4wIJq/hzOAiVv4WtORVCRn3Ysul3wAKlXLB9Q+M6kzxXXcbc+v4g==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components/-/frontend-components-4.1.0.tgz", + "integrity": "sha512-IsQf/DK5nA+ARFdq25o5Ndi2EAE5cgkDA80GjK3CluTvV+0ve5wRCtD/nCnVbRs9i/CbSAu8YTCvLA6tCTUKyA==", "requires": { - "@patternfly/react-component-groups": "^1.0.17", + "@patternfly/react-component-groups": "^5.0.0-prerelease.5", "@redhat-cloud-services/frontend-components-utilities": "^4.0.0", "@redhat-cloud-services/types": "^0.0.24", - "@scalprum/core": "^0.5.1", - "@scalprum/react-core": "^0.5.1", + "@scalprum/core": "^0.5.4", + "@scalprum/react-core": "^0.5.4", "sanitize-html": "^2.7.2" }, "dependencies": { @@ -29166,12 +32668,12 @@ } }, "@redhat-cloud-services/frontend-components-notifications": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-notifications/-/frontend-components-notifications-4.0.2.tgz", - "integrity": "sha512-xivlLFmlAhn+WNwaKuHXXQhSeVW+EkuApAErhvYanC5SZ32H9cIzp4aF61ZoLv7A1pjTSu/QcZ+BxvfTKCNrUQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-notifications/-/frontend-components-notifications-4.1.0.tgz", + "integrity": "sha512-bEoAeZMVY+UuSriSeruZ1pIesiPEjScrVFJQ5Wq/w3UFc79oXSYOgpRElxblPUH/LbKqp7inbzqRm2FxMcVxYg==", "requires": { - "@redhat-cloud-services/frontend-components": "^4.0.0", - "@redhat-cloud-services/frontend-components-utilities": "^4.0.0", + "@redhat-cloud-services/frontend-components": "^4.0.9", + "@redhat-cloud-services/frontend-components-utilities": "^4.0.2", "redux-promise-middleware": "6.1.3" } }, @@ -32620,11 +36122,26 @@ "integrity": "sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ==", "dev": true }, + "append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "requires": { + "default-require-extensions": "^3.0.0" + } + }, "arch": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==" }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "dev": true + }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -32661,15 +36178,15 @@ "dev": true }, "array-includes": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", - "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", + "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "get-intrinsic": "^1.1.3", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", "is-string": "^1.0.7" } }, @@ -32686,26 +36203,26 @@ "dev": true }, "array.prototype.flat": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", - "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", "es-shim-unscopables": "^1.0.0" } }, "array.prototype.flatmap": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", - "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", "es-shim-unscopables": "^1.0.0" } }, @@ -32978,6 +36495,62 @@ } } }, + "babel-loader": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz", + "integrity": "sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==", + "dev": true, + "peer": true, + "requires": { + "find-cache-dir": "^4.0.0", + "schema-utils": "^4.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "peer": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "peer": true, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "peer": true + }, + "schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dev": true, + "peer": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + } + } + } + }, "babel-plugin-istanbul": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", @@ -33003,6 +36576,39 @@ "@types/babel__traverse": "^7.0.6" } }, + "babel-plugin-polyfill-corejs2": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.6.tgz", + "integrity": "sha512-jhHiWVZIlnPbEUKSSNb9YoWcQGdlTLq7z1GHL4AjFxaoOUMuuEVJ+Y4pAaQUGOGk93YsVCKPbqbfw3m0SM6H8Q==", + "dev": true, + "peer": true, + "requires": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.4.3", + "semver": "^6.3.1" + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.8.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.6.tgz", + "integrity": "sha512-leDIc4l4tUgU7str5BWLS2h8q2N4Nf6lGZP6UrNDxdtfF2g69eJ5L0H7S8A5Ln/arfFAfHor5InAdZuIOwZdgQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.4.3", + "core-js-compat": "^3.33.1" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.3.tgz", + "integrity": "sha512-8sHeDOmXC8csczMrYEOf0UTNa4yE2SxV5JGeT/LP1n0OYVDUUFPxG9vdk2AlDlIit4t+Kf0xCtpgXPBwnn/9pw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.4.3" + } + }, "babel-preset-current-node-syntax": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", @@ -33289,15 +36895,15 @@ } }, "browserslist": { - "version": "4.21.10", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", - "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001517", - "electron-to-chromium": "^1.4.477", + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.11" + "update-browserslist-db": "^1.0.13" } }, "bser": { @@ -33352,6 +36958,41 @@ "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==" }, + "caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "requires": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, + "dependencies": { + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + } + } + }, "call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -33384,9 +37025,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001520", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001520.tgz", - "integrity": "sha512-tahF5O9EiiTzwTUqAeFjIZbn4Dnqxzz7ktrgGlMYNLH43Ul26IgTMH/zvL3DG0lZxBYnlT04axvInszUsZULdA==", + "version": "1.0.30001562", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001562.tgz", + "integrity": "sha512-kfte3Hym//51EdX4239i+Rmp20EsLIYGdPkERegTgU19hQWCRhsRFGKHTliUlsry53tv17K7n077Kqa0WJU4ng==", "dev": true }, "caseless": { @@ -33806,6 +37447,16 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "dev": true }, + "core-js-compat": { + "version": "3.33.2", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.2.tgz", + "integrity": "sha512-axfo+wxFVxnqf8RvxTzoAlzW4gRoacrHeoFlc9n0x50+7BEyZL/Rt3hicaED1/CEd7I6tPCPVUYcJwCMO5XUYw==", + "dev": true, + "peer": true, + "requires": { + "browserslist": "^4.22.1" + } + }, "core-js-pure": { "version": "3.32.0", "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.32.0.tgz", @@ -33877,6 +37528,11 @@ "which": "^2.0.1" } }, + "crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" + }, "css-jss": { "version": "10.10.0", "resolved": "https://registry.npmjs.org/css-jss/-/css-jss-10.10.0.tgz", @@ -34363,9 +38019,9 @@ } }, "dayjs": { - "version": "1.11.9", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz", - "integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==" + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" }, "debounce-promise": { "version": "3.1.2", @@ -34380,6 +38036,12 @@ "ms": "2.1.2" } }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true + }, "decimal.js": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", @@ -34482,6 +38144,15 @@ } } }, + "default-require-extensions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", + "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", + "dev": true, + "requires": { + "strip-bom": "^4.0.0" + } + }, "defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -34944,9 +38615,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.4.490", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.490.tgz", - "integrity": "sha512-6s7NVJz+sATdYnIwhdshx/N/9O6rvMxmhVoDSDFdj6iA45gHR8EQje70+RYsF4GeB+k0IeNSBnP7yG9ZXJFr7A==", + "version": "1.4.582", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.582.tgz", + "integrity": "sha512-89o0MGoocwYbzqUUjc+VNpeOFSOK9nIdC5wY4N+PVUarUK0MtjyTjks75AZS2bW4Kl8MdewdFsWaH0jLy+JNoA==", "dev": true }, "emittery": { @@ -35129,6 +38800,12 @@ "is-symbol": "^1.0.2" } }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, "es6-object-assign": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", @@ -35846,6 +39523,17 @@ } } }, + "find-cache-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", + "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", + "dev": true, + "peer": true, + "requires": { + "common-path-prefix": "^3.0.0", + "pkg-dir": "^7.0.0" + } + }, "find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -35902,11 +39590,11 @@ "dev": true }, "focus-trap": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.4.3.tgz", - "integrity": "sha512-BgSSbK4GPnS2VbtZ50VtOv1Sti6DIkj3+LkVjiWMNjLeAp1SH1UlLx3ULu/DCu4vq5R4/uvTm+zrvsMsuYmGLg==", + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.2.tgz", + "integrity": "sha512-p6vGNNWLDGwJCiEjkSK6oERj/hEyI9ITsSwIUICBoKLlWiTWXJRfQibCwcoi50rTZdbi87qDtUlMCmQwsGSgPw==", "requires": { - "tabbable": "^6.1.2" + "tabbable": "^6.2.0" } }, "follow-redirects": { @@ -35923,6 +39611,16 @@ "is-callable": "^1.1.3" } }, + "foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + } + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -36045,6 +39743,12 @@ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "dev": true }, + "fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true + }, "fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -36074,9 +39778,9 @@ "optional": true }, "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "function.prototype.name": { "version": "1.1.5", @@ -36411,6 +40115,32 @@ "has-symbols": "^1.0.2" } }, + "hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "requires": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "dependencies": { + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, + "hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "requires": { + "function-bind": "^1.1.2" + } + }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -36973,11 +40703,11 @@ } }, "is-core-module": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", - "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "requires": { - "has": "^1.0.3" + "hasown": "^2.0.0" } }, "is-date-object": { @@ -37321,6 +41051,15 @@ "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", "dev": true }, + "istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "requires": { + "append-transform": "^2.0.0" + } + }, "istanbul-lib-instrument": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", @@ -37334,6 +41073,40 @@ "semver": "^6.3.0" } }, + "istanbul-lib-processinfo": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", + "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", + "dev": true, + "requires": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^8.3.2" + }, + "dependencies": { + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, "istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", @@ -39716,9 +43489,9 @@ } }, "joi": { - "version": "17.9.2", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.9.2.tgz", - "integrity": "sha512-Itk/r+V4Dx0V3c7RLFdRh12IOjySm2/WGPMubBT92cQvRfYZhPM2W0hZlctjj72iES8jsRCwp7S/cRmWBnJ4nw==", + "version": "17.11.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz", + "integrity": "sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==", "dev": true, "requires": { "@hapi/hoek": "^9.0.0", @@ -39728,16 +43501,17 @@ "@sideway/pinpoint": "^2.0.0" } }, + "jotai": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.5.1.tgz", + "integrity": "sha512-vanPCCSuHczUXNbVh/iUunuMfrWRL4FdBtAbTRmrfqezJcKb8ybBTg8iivyYuUHapjcDETyJe1E4inlo26bVHA==", + "requires": {} + }, "js-cookie": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==" }, - "js-sha256": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", - "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==" - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -40062,14 +43836,10 @@ "safe-buffer": "^5.0.1" } }, - "keycloak-js": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-21.1.2.tgz", - "integrity": "sha512-+6r1BvmutWGJBtibo7bcFbHWIlA7XoXRCwcA4vopeJh59Nv2Js0ju2u+t8AYth+C6Cg7/BNfO3eCTbsl/dTBHw==", - "requires": { - "base64-js": "^1.5.1", - "js-sha256": "^0.9.0" - } + "jwt-decode": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", + "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" }, "kind-of": { "version": "6.0.3", @@ -40233,6 +44003,19 @@ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true, + "peer": true + }, + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", + "dev": true + }, "lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", @@ -40901,6 +44684,15 @@ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", "dev": true }, + "node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "requires": { + "process-on-spawn": "^1.0.0" + } + }, "node-releases": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", @@ -41087,6 +44879,226 @@ "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==", "dev": true }, + "nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "requires": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", @@ -41157,14 +45169,14 @@ } }, "object.fromentries": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", - "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", + "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" } }, "object.hasown": { @@ -41178,14 +45190,14 @@ } }, "object.values": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", - "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", + "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" } }, "oblivious-set": { @@ -41199,6 +45211,15 @@ "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", "dev": true }, + "oidc-client-ts": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-2.4.0.tgz", + "integrity": "sha512-WijhkTrlXK2VvgGoakWJiBdfIsVGz6CFzgjNNqZU1hPKV2kyeEaJgLs7RwuiSp2WhLfWBQuLvr2SxVlZnk3N1w==", + "requires": { + "crypto-js": "^4.2.0", + "jwt-decode": "^3.1.2" + } + }, "on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -41396,6 +45417,18 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, + "package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + } + }, "pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -41680,6 +45713,73 @@ } } }, + "pkg-dir": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", + "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "dev": true, + "peer": true, + "requires": { + "find-up": "^6.3.0" + }, + "dependencies": { + "find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dev": true, + "peer": true, + "requires": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + } + }, + "locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "peer": true, + "requires": { + "p-locate": "^6.0.0" + } + }, + "p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "peer": true, + "requires": { + "yocto-queue": "^1.0.0" + } + }, + "p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "peer": true, + "requires": { + "p-limit": "^4.0.0" + } + }, + "path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "peer": true + }, + "yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "peer": true + } + } + }, "pluralize": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", @@ -42078,6 +46178,15 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, + "process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "requires": { + "fromentries": "^1.2.0" + } + }, "prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -42387,6 +46496,12 @@ "tiny-warning": "^1.0.2" } }, + "react-oidc-context": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/react-oidc-context/-/react-oidc-context-2.3.1.tgz", + "integrity": "sha512-WdhmEU6odNzMk9pvOScxUkf6/1aduiI/nQryr7+iCl2VDnYLASDTIV/zy58KuK4VXG3fBaRKukc/mRpMjF9a3Q==", + "requires": {} + }, "react-redux": { "version": "8.1.2", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.2.tgz", @@ -42529,11 +46644,38 @@ "integrity": "sha512-B/Hi5Ct5d9y5d/KG0f6MZUXKA0nrQh5583mHCx13HY3Avte8KfpoRH/TB5QT6k/FcjT6JCxjv7jedymidy2A1A==", "requires": {} }, + "regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true, + "peer": true + }, + "regenerate-unicode-properties": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "dev": true, + "peer": true, + "requires": { + "regenerate": "^1.4.2" + } + }, "regenerator-runtime": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" }, + "regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.8.4" + } + }, "regex-parser": { "version": "2.2.11", "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", @@ -42550,12 +46692,55 @@ "functions-have-names": "^1.2.3" } }, + "regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + } + }, + "regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "peer": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "peer": true + } + } + }, "relateurl": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", "dev": true }, + "release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", + "dev": true, + "requires": { + "es6-error": "^4.0.1" + } + }, "renderkid": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", @@ -42764,6 +46949,12 @@ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, "requirejs": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz", @@ -43247,6 +47438,12 @@ "send": "0.18.0" } }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true + }, "setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -43439,6 +47636,40 @@ "resolved": "https://registry.npmjs.org/spark-md5/-/spark-md5-3.0.2.tgz", "integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==" }, + "spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "requires": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "dependencies": { + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, "spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -43779,6 +48010,12 @@ "dev": true, "requires": {} }, + "swc-plugin-coverage-instrument": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/swc-plugin-coverage-instrument/-/swc-plugin-coverage-instrument-0.0.20.tgz", + "integrity": "sha512-WXTGILCZE2hW61yrmxi6doN/UB4RT2K1JJSQVPn9JMJ6X4WJpZsesHi4lHy6qRKVsNIlHZvTWofkpuRi/WQUig==", + "dev": true + }, "symbol-observable": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", @@ -44192,6 +48429,15 @@ "is-typed-array": "^1.1.9" } }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, "typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", @@ -44214,6 +48460,38 @@ "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==" }, + "unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "peer": true + }, + "unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "peer": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true, + "peer": true + }, + "unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "peer": true + }, "unicode-trie": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-0.3.1.tgz", @@ -44268,9 +48546,9 @@ "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==" }, "update-browserslist-db": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", - "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", "dev": true, "requires": { "escalade": "^3.1.1", @@ -45619,16 +49897,16 @@ } }, "wait-on": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.0.1.tgz", - "integrity": "sha512-9AnJE9qTjRQOlTZIldAaf/da2eW0eSRSgcqq85mXQja/DW3MriHxkpODDSUEg+Gri/rKEcXUZHe+cevvYItaog==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.1.0.tgz", + "integrity": "sha512-U7TF/OYYzAg+OoiT/B8opvN48UHt0QYMi4aD3PjRFpybQ+o6czQF8Ig3SKCCMJdxpBrCalIJ4O00FBof27Fu9Q==", "dev": true, "requires": { "axios": "^0.27.2", - "joi": "^17.7.0", + "joi": "^17.11.0", "lodash": "^4.17.21", - "minimist": "^1.2.7", - "rxjs": "^7.8.0" + "minimist": "^1.2.8", + "rxjs": "^7.8.1" } }, "walkdir": { @@ -46007,6 +50285,12 @@ "iconv-lite": "0.6.3" } }, + "whatwg-fetch": { + "version": "3.6.19", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.19.tgz", + "integrity": "sha512-d67JP4dHSbm2TrpFj8AbO8DnL1JXL5J9u0Kq2xW6d0TFDbCA3Muhdt8orXC22utleTVj7Prqt82baN6RBvnEgw==", + "dev": true + }, "whatwg-mimetype": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", @@ -46056,6 +50340,12 @@ "is-weakset": "^2.0.1" } }, + "which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "dev": true + }, "which-typed-array": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", diff --git a/package.json b/package.json index e46b3b887..3e7aec0fc 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ }, "homepage": "https://github.com/RedHatInsights/insights-chrome#readme", "devDependencies": { + "@cypress/code-coverage": "^3.12.9", "@openshift/dynamic-plugin-sdk-webpack": "^3.0.1", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.10", "@redhat-cloud-services/eslint-config-redhat-cloud-services": "^1.2.6", @@ -114,6 +115,7 @@ "stream-browserify": "^3.0.0", "style-loader": "^3.3.1", "swc-loader": "^0.2.3", + "swc-plugin-coverage-instrument": "^0.0.20", "terser-webpack-plugin": "^5.3.6", "typescript": "^4.9.5", "url": "^0.11.3", @@ -123,6 +125,7 @@ "webpack-bundle-analyzer": "^4.8.0", "webpack-cli": "^5.0.1", "webpack-dev-server": "^4.11.1", + "whatwg-fetch": "^3.6.19", "yargs": "^17.7.1" }, "dependencies": { @@ -160,15 +163,17 @@ "classnames": "^2.3.2", "commander": "^10.0.0", "history": "^5.3.0", + "jotai": "^2.5.1", "js-cookie": "^3.0.1", "js-yaml": "^4.1.0", - "keycloak-js": "^21.0.0", "localforage": "^1.10.0", "lodash": "^4.17.21", + "oidc-client-ts": "^2.4.0", "pf-4-styles": "npm:@patternfly/patternfly@^4.224.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-intl": "^6.2.10", + "react-oidc-context": "^2.3.1", "react-redux": "^8.0.5", "react-router-dom": "^6.8.2", "redux": "^4.2.1", @@ -179,5 +184,8 @@ }, "insights": { "appname": "chrome" + }, + "nyc": { + "report-dir": "cypress-coverage" } } diff --git a/src/analytics/SegmentProvider.tsx b/src/analytics/SegmentProvider.tsx index f14444e7e..e77382adb 100644 --- a/src/analytics/SegmentProvider.tsx +++ b/src/analytics/SegmentProvider.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef } from 'react'; +import React, { useContext, useEffect, useRef } from 'react'; import { AnalyticsBrowser } from '@segment/analytics-next'; import Cookie from 'js-cookie'; import { ITLess, isBeta, isProd } from '../utils/common'; @@ -10,6 +10,7 @@ import { ChromeState } from '../redux/store'; import SegmentContext from './SegmentContext'; import { resetIntegrations } from './resetIntegrations'; import { getUrl } from '../hooks/useBundle'; +import ChromeAuthContext from '../auth/ChromeAuthContext'; type SegmentEnvs = 'dev' | 'prod'; type SegmentModules = 'acs' | 'openshift' | 'hacCore'; @@ -160,7 +161,7 @@ const SegmentProvider: React.FC> = const disableIntegrations = localStorage.getItem('chrome:analytics:disable') === 'true' || isITLessEnv; const analytics = useRef(); const analyticsLoaded = useRef(false); - const user = useSelector(({ chrome: { user } }: { chrome: { user: ChromeUser } }) => user); + const { user } = useContext(ChromeAuthContext); const moduleAPIKey = useSelector(({ chrome: { modules } }: { chrome: ChromeState }) => activeModule && modules?.[activeModule]?.analytics?.APIKey); const { pathname } = useLocation(); @@ -268,7 +269,8 @@ const SegmentProvider: React.FC> = useEffect(() => { handleModuleUpdate(); - }, [activeModule, user]); + // need the json stringify to prevent the effect from running on every user update if not necessary + }, [activeModule, JSON.stringify(user)]); /** * This needs to happen in a condition and during first valid render! diff --git a/src/analytics/analytics.test.js b/src/analytics/analytics.test.js deleted file mode 100644 index 9c12da904..000000000 --- a/src/analytics/analytics.test.js +++ /dev/null @@ -1,53 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -const analytics = require('./'); -const user = require('../jwt/user'); -const token = require('../../testdata/token.json'); -const externalToken = require('../../testdata/externalToken.json'); -const ibmToken = require('../../testdata/ibmToken.json'); - -describe('User + Analytics', () => { - const getPendoConf = analytics.getPendoConf; - describe('buildUser + getPendoConf internal', () => { - test('should build a valid internal Pendo config', () => { - const conf = getPendoConf(user.buildUser(token)); - expect(conf).toMatchObject({ - account: { - id: '540155', - }, - visitor: { - id: '5299389_redhat', - internal: true, - lang: 'en_US', - }, - }); - }); - - test('should build a valid external Pendo config', () => { - const conf = getPendoConf(user.buildUser(externalToken)); - expect(conf).toMatchObject({ - account: { - id: '540155', - }, - visitor: { - id: '5299389', - internal: false, - lang: 'en_US', - }, - }); - }); - - test('should build a valid IBM pendo config', () => { - const conf = getPendoConf(user.buildUser(ibmToken)); - expect(conf).toMatchObject({ - account: { - id: '540155', - }, - visitor: { - id: '5299389_ibm', - internal: false, - lang: 'en_US', - }, - }); - }); - }); -}); diff --git a/src/analytics/analytics.test.ts b/src/analytics/analytics.test.ts new file mode 100644 index 000000000..8e886e64d --- /dev/null +++ b/src/analytics/analytics.test.ts @@ -0,0 +1,77 @@ +import { getPendoConf } from '.'; +import token from '../../testdata/token.json'; +import externalToken from '../../testdata/externalToken.json'; +import ibmToken from '../../testdata/ibmToken.json'; +import { DeepRequired } from 'utility-types'; +import { ChromeUser } from '@redhat-cloud-services/types'; + +function buildUser(token: any): DeepRequired { + return { + entitlements: {}, + identity: { + account_number: token.account_number || '540155', + type: 'User', + internal: { + org_id: token.org_id || '1979710', + account_id: token.account_id || '5299389', + }, + org_id: token.org_id || '1979710', + user: { + email: token.email || '', + first_name: token.first_name || 'John', + is_active: token.is_active || true, + is_internal: token.is_internal || false, + is_org_admin: token.is_org_admin || false, + last_name: token.last_name || 'Doe', + locale: token.locale || 'en_US', + username: token.username || 'test-user', + }, + }, + }; +} + +describe('User + Analytics', () => { + describe('buildUser + getPendoConf internal', () => { + test('should build a valid internal Pendo config', () => { + const conf = getPendoConf(buildUser(token)); + expect(conf).toMatchObject({ + account: { + id: '540155', + }, + visitor: { + id: '5299389_redhat', + internal: true, + lang: 'en_US', + }, + }); + }); + + test('should build a valid external Pendo config', () => { + const conf = getPendoConf(buildUser(externalToken)); + expect(conf).toMatchObject({ + account: { + id: '540155', + }, + visitor: { + id: '5299389', + internal: false, + lang: 'en_US', + }, + }); + }); + + test('should build a valid IBM pendo config', () => { + const conf = getPendoConf(buildUser(ibmToken)); + expect(conf).toMatchObject({ + account: { + id: '540155', + }, + visitor: { + id: '5299389_ibm', + internal: false, + lang: 'en_US', + }, + }); + }); + }); +}); diff --git a/src/analytics/index.ts b/src/analytics/index.ts index 3a469ff2a..3c7f599c3 100644 --- a/src/analytics/index.ts +++ b/src/analytics/index.ts @@ -1,5 +1,3 @@ -'use strict'; - import { isBeta, isProd } from '../utils/common'; import { ChromeUser } from '@redhat-cloud-services/types'; import { DeepRequired } from 'utility-types'; diff --git a/src/auth/ChromeAuthContext.ts b/src/auth/ChromeAuthContext.ts new file mode 100644 index 000000000..05d967f3d --- /dev/null +++ b/src/auth/ChromeAuthContext.ts @@ -0,0 +1,48 @@ +import { ChromeUser } from '@redhat-cloud-services/types'; +import { AxiosResponse } from 'axios'; +import { createContext } from 'react'; +import { OfflineTokenResponse } from './offline'; + +export type ChromeAuthContextValue = { + ready: boolean; + user: ChromeUser; + getUser: () => Promise; + token: string; + logoutAllTabs: (bounce?: boolean) => void; + loginAllTabs: () => void; + logout: () => void; + login: (requiredScopes?: string[]) => Promise; + tokenExpires: number; + getToken: () => Promise; + postbackUrl?: string; + getOfflineToken: () => Promise>; + doOffline: () => Promise; +}; + +const blankUser: ChromeUser = { + entitlements: {}, + identity: { + org_id: '', + type: '', + }, +}; + +const ChromeAuthContext = createContext({ + ready: false, + logoutAllTabs: () => undefined, + loginAllTabs: () => undefined, + logout: () => undefined, + login: () => Promise.resolve(), + getToken: () => Promise.resolve(''), + getOfflineToken: () => + Promise.resolve({ + data: {}, + } as AxiosResponse), + doOffline: () => Promise.resolve(), + getUser: () => Promise.resolve(blankUser), + token: '', + tokenExpires: 0, + user: blankUser, +}); + +export default ChromeAuthContext; diff --git a/src/auth/OIDCConnector/.eslintrc.js b/src/auth/OIDCConnector/.eslintrc.js new file mode 100644 index 000000000..c3774b600 --- /dev/null +++ b/src/auth/OIDCConnector/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + rules: { + 'no-restricted-imports': 'off', + }, +}; diff --git a/src/auth/OIDCConnector/OIDCProvider.tsx b/src/auth/OIDCConnector/OIDCProvider.tsx new file mode 100644 index 000000000..218bc3def --- /dev/null +++ b/src/auth/OIDCConnector/OIDCProvider.tsx @@ -0,0 +1,81 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { DEFAULT_SSO_ROUTES, ITLess, isBeta, loadFedModules } from '../../utils/common'; +import { AuthProvider, AuthProviderProps } from 'react-oidc-context'; +import { WebStorageStateStore } from 'oidc-client-ts'; +import platformUrl from '../platformUrl'; +import { OIDCSecured } from './OIDCSecured'; +import AppPlaceholder from '../../components/AppPlaceholder'; +import { postbackUrlSetup } from '../offline'; + +const betaPartial = isBeta() ? '/beta' : ''; + +const OIDCProvider: React.FC = ({ children }) => { + const [cookieElement, setCookieElement] = useState(null); + const [state, setState] = useState< + | { + ssoUrl: string; + microFrontendConfig: Record; + } + | undefined + >(undefined); + async function setupSSO() { + const { data } = await loadFedModules(); + try { + const { + chrome: { + config: { ssoUrl }, + }, + } = data; + setState({ ssoUrl: platformUrl(DEFAULT_SSO_ROUTES, ssoUrl), microFrontendConfig: data }); + } catch (error) { + setState({ ssoUrl: platformUrl(DEFAULT_SSO_ROUTES), microFrontendConfig: data }); + } + } + useEffect(() => { + // required for offline token generation + postbackUrlSetup(); + setupSSO(); + }, []); + + const authProviderProps: AuthProviderProps = useMemo( + () => ({ + client_id: ITLess() ? 'console-dot' : 'cloud-services', + silent_redirect_uri: `https://${window.location.host}${betaPartial}/apps/chrome/silent-check-sso.html`, + automaticSilentRenew: true, + redirect_uri: `${window.location.origin}`, + authority: `${state?.ssoUrl}`, + metadataUrl: '/realms/redhat-external/protocol/openid-connect/auth', + monitorSession: true, + metadata: { + authorization_endpoint: `${state?.ssoUrl}realms/redhat-external/protocol/openid-connect/auth`, + token_endpoint: `${state?.ssoUrl}realms/redhat-external/protocol/openid-connect/token`, + end_session_endpoint: `${state?.ssoUrl}realms/redhat-external/protocol/openid-connect/logout`, + check_session_iframe: `https://${window.location.host}${betaPartial}/apps/chrome/silent-check-sso.html`, + revocation_endpoint: `${state?.ssoUrl}realms/redhat-external/protocol/openid-connect/revoke`, + }, + // removes code_challenge query param from the url + disablePKCE: true, + response_type: 'code', + response_mode: 'fragment', + onSigninCallback: () => { + window.history.replaceState({}, document.title, window.location.pathname); + }, + userStore: new WebStorageStateStore({ store: window.localStorage }), + }), + [state?.ssoUrl] + ); + + if (!state?.ssoUrl || !state?.microFrontendConfig) { + return ; + } + + return ( + + + {children} + + + ); +}; + +export default OIDCProvider; diff --git a/src/auth/OIDCConnector/OIDCSecured.tsx b/src/auth/OIDCConnector/OIDCSecured.tsx new file mode 100644 index 000000000..8d5b8503d --- /dev/null +++ b/src/auth/OIDCConnector/OIDCSecured.tsx @@ -0,0 +1,182 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { hasAuthParams, useAuth } from 'react-oidc-context'; +import { User } from 'oidc-client-ts'; +import { BroadcastChannel } from 'broadcast-channel'; +import { useDispatch, useStore } from 'react-redux'; +import { ChromeUser } from '@redhat-cloud-services/types'; +import ChromeAuthContext, { ChromeAuthContextValue } from '../ChromeAuthContext'; +import { generateRoutesList } from '../../utils/common'; +import { loadModulesSchema } from '../../redux/actions'; +import getInitialScope from '../getInitialScope'; +import { init } from '../../utils/iqeEnablement'; +import entitlementsApi from '../entitlementsApi'; +import { initializeVisibilityFunctions } from '../../utils/VisibilitySingleton'; +import sentry from '../../utils/sentry'; +import AppPlaceholder from '../../components/AppPlaceholder'; +import { FooterProps } from '../../components/Footer/Footer'; +import logger from '../logger'; +import { login, logout } from './utils'; +import createGetUserPermissions from '../createGetUserPermissions'; +import initializeAccessRequestCookies from '../initializeAccessRequestCookies'; +import { getOfflineToken, prepareOfflineRedirect } from '../offline'; + +type Entitlement = { is_entitled: boolean; is_trial: boolean }; +const serviceAPI = entitlementsApi(); +const authChannel = new BroadcastChannel('auth'); +const log = logger('OIDCSecured.tsx'); + +/* eslint-disable @typescript-eslint/no-explicit-any */ +function mapOIDCUserToChromeUser(user: User | Record, entitlements: { [entitlement: string]: Entitlement }): ChromeUser { + return { + entitlements, + identity: { + org_id: user.profile?.org_id as any, + type: user.profile?.type as any, + account_number: user.profile?.account_number as any, + internal: { + org_id: user.profile?.org_id as any, + account_id: user.profile?.account_id as any, + }, + user: { + email: user.profile?.email as any, + first_name: user.profile?.first_name as any, + last_name: user.profile?.last_name as any, + is_active: user.profile?.is_active as any, + is_org_admin: user.profile?.is_org_admin as any, + is_internal: user.profile?.is_internal as any, + locale: user.profile?.locale as any, + username: user.profile?.username as any, + }, + }, + }; +} +/* eslint-enable @typescript-eslint/no-explicit-any */ + +async function fetchEntitlements(user: User) { + let entitlements: { [entitlement: string]: Entitlement } = {}; + try { + if (user.profile.org_id) { + entitlements = (await serviceAPI.servicesGet()) as unknown as typeof entitlements; + return entitlements; + } else { + console.log('Cannot call entitlements API, no account number'); + return entitlements; + } + } catch { + // let's swallow error from services API + return entitlements; + } +} + +export function OIDCSecured({ + children, + microFrontendConfig, + cookieElement, + setCookieElement, +}: React.PropsWithChildren<{ microFrontendConfig: Record } & FooterProps>) { + const auth = useAuth(); + const authRef = useRef(auth); + const store = useStore(); + const dispatch = useDispatch(); + const [state, setState] = useState({ + ready: false, + logoutAllTabs: (bounce = true) => { + authChannel.postMessage({ type: 'logout' }); + logout(authRef.current, bounce); + }, + logout: () => { + logout(authRef.current, true); + }, + login: (requiredScopes) => login(authRef.current, requiredScopes), + loginAllTabs: () => { + authChannel.postMessage({ type: 'login' }); + }, + getToken: () => Promise.resolve(authRef.current.user?.access_token ?? ''), + getOfflineToken: () => + getOfflineToken( + authRef.current.settings.metadata?.token_endpoint ?? '', + authRef.current.settings.client_id, + encodeURIComponent((authRef.current.settings.metadata?.token_endpoint ?? '').split('#')[0]) + ), + doOffline: () => login(authRef.current, ['offline_access'], prepareOfflineRedirect()), + getUser: () => Promise.resolve(mapOIDCUserToChromeUser(authRef.current.user ?? {}, {})), + token: authRef.current.user?.access_token ?? '', + tokenExpires: authRef.current.user?.expires_at ?? 0, + user: mapOIDCUserToChromeUser(authRef.current.user ?? {}, {}), + }); + + const startChrome = async () => { + const routes = generateRoutesList(microFrontendConfig); + dispatch(loadModulesSchema(microFrontendConfig)); + + const initialModuleScope = getInitialScope(routes, window.location.pathname); + + const initialModuleConfig = initialModuleScope && microFrontendConfig[initialModuleScope]?.config; + initializeAccessRequestCookies(); + + if (!hasAuthParams() && !auth.activeNavigator && !auth.isLoading && !auth.isAuthenticated) { + login(auth, initialModuleConfig?.ssoScopes); + } + }; + + async function onUserAuthenticated(user: User) { + // order of calls is important + // init the IQE enablement first to add the necessary auth headers to the requests + init(store, user.access_token); + const entitlements = await fetchEntitlements(user); + const chromeUser = mapOIDCUserToChromeUser(user, entitlements); + const getUser = () => Promise.resolve(chromeUser); + initializeVisibilityFunctions({ + getUser, + getToken: () => Promise.resolve(user.access_token), + getUserPermissions: createGetUserPermissions(getUser, () => Promise.resolve(user.access_token)), + }); + setState((prev) => ({ + ...prev, + ready: true, + getUser, + user: chromeUser, + token: user.access_token, + tokenExpires: user.expires_at!, + })); + sentry(chromeUser); + } + + useEffect(() => { + const user = auth.user; + if (auth.isAuthenticated && user) { + onUserAuthenticated(user); + authChannel.onmessage = (e) => { + if (e && e.data && e.data.type) { + log(`BroadcastChannel, Received event : ${e.data.type}`); + + // TODO: handle scopes + switch (e.data.type) { + case 'logout': + return state.logout(); + case 'login': + return state.login(); + case 'refresh': + return auth.signinSilent(); + } + } + }; + } + }, [JSON.stringify(auth.user), auth.isAuthenticated]); + + useEffect(() => { + if (!auth.error) { + startChrome(); + } + }, [auth]); + + useEffect(() => { + authRef.current = auth; + }, [auth]); + + if (!auth.isAuthenticated || !state.ready) { + return ; + } + + return {children}; +} diff --git a/src/auth/OIDCConnector/createUUID.test.ts b/src/auth/OIDCConnector/createUUID.test.ts new file mode 100644 index 000000000..861098a35 --- /dev/null +++ b/src/auth/OIDCConnector/createUUID.test.ts @@ -0,0 +1,30 @@ +import createUUID from './createUUID'; + +describe('createUUID', () => { + it('should generate a valid UUID', () => { + const uuid = createUUID(); + expect(uuid).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i); + }); + + it('should generate a different UUID each time', () => { + const uuid1 = createUUID(); + const uuid2 = createUUID(); + expect(uuid1).not.toBe(uuid2); + }); + + it('should generate a UUID with the correct length', () => { + const uuid = createUUID(); + expect(uuid.length).toBe(36); + }); + + // test when a window.crypto is not available + it('should generate a valid UUID when window.crypto is not available', () => { + const crypto = window.crypto; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + window.crypto = null; + const uuid = createUUID(); + expect(uuid).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i); + window.crypto = crypto; + }); +}); diff --git a/src/auth/OIDCConnector/createUUID.ts b/src/auth/OIDCConnector/createUUID.ts new file mode 100644 index 000000000..48c18010e --- /dev/null +++ b/src/auth/OIDCConnector/createUUID.ts @@ -0,0 +1,44 @@ +// UUID generator required to create a nonce attribute for SSO login +// More efficient than installing the uuid package as we need just a small fragment of the package +// Same as the keycloack-js implementation + +function generateRandomData(len: number) { + // use web crypto APIs if possible + let array = null; + const crypto = window.crypto; + if (crypto && crypto.getRandomValues && window.Uint8Array) { + array = new Uint8Array(len); + crypto.getRandomValues(array); + return array; + } + + // fallback to Math random + array = new Array(len); + for (let j = 0; j < array.length; j++) { + array[j] = Math.floor(256 * Math.random()); + } + return array; +} + +function generateRandomString(len: number, alphabet: string) { + const randomData = generateRandomData(len); + const chars = new Array(len); + for (let i = 0; i < len; i++) { + chars[i] = alphabet.charCodeAt(randomData[i] % alphabet.length); + } + return String.fromCharCode.apply(null, chars); +} + +function createUUID() { + const hexDigits = '0123456789abcdef'; + const s: (string | number | boolean)[] = generateRandomString(36, hexDigits).split(''); + s[14] = '4'; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); + s[8] = s[13] = s[18] = s[23] = '-'; + const uuid = s.join(''); + return uuid; +} + +export default createUUID; diff --git a/src/auth/OIDCConnector/utils.ts b/src/auth/OIDCConnector/utils.ts new file mode 100644 index 000000000..dd90bf751 --- /dev/null +++ b/src/auth/OIDCConnector/utils.ts @@ -0,0 +1,81 @@ +import { AuthContextProps } from 'react-oidc-context'; +import { LOGIN_SCOPES_STORAGE_KEY, deleteLocalStorageItems, getRouterBasename, isBeta } from '../../utils/common'; +import { GLOBAL_FILTER_KEY, OFFLINE_REDIRECT_STORAGE_KEY } from '../../utils/consts'; +import Cookies from 'js-cookie'; +import logger from '../logger'; +import createUUID from './createUUID'; + +const log = logger('auth:utils'); + +enum AllowedPartnerScopes { + aws = 'aws', + azure = 'azure', + gcp = 'gcp', +} + +function isPartnerScope(scope: string): scope is AllowedPartnerScopes { + return Object.values(AllowedPartnerScopes).includes(scope as AllowedPartnerScopes); +} + +function getPartnerScope(pathname: string) { + // replace beta and leading "/" + const sanitizedPathname = pathname.replace(/^(\/beta\/|\/preview\/)/, '/').replace(/^\//, ''); + // check if the pathname is connect/:partner + if (sanitizedPathname.match(/^connect\/.+/)) { + // return :partner param + const fragmentScope = sanitizedPathname.split('/')[1]; + if (isPartnerScope(fragmentScope)) { + return `api.partner_link.${fragmentScope}`; + } + log(`Invalid stratosphere scope: ${fragmentScope}`); + return undefined; + } + + return undefined; +} + +export async function logout(auth: AuthContextProps, bounce?: boolean) { + const keys = Object.keys(localStorage).filter( + (key) => + key.endsWith('/api/entitlements/v1/services') || + key.endsWith('/chrome') || + key.endsWith('/chrome-store') || + key.startsWith('kc-callback') || + key.startsWith(GLOBAL_FILTER_KEY) + ); + deleteLocalStorageItems([...keys, OFFLINE_REDIRECT_STORAGE_KEY, LOGIN_SCOPES_STORAGE_KEY]); + const pathname = isBeta() ? getRouterBasename() : ''; + if (bounce) { + const eightSeconds = new Date(new Date().getTime() + 8 * 1000); + Cookies.set('cs_loggedOut', 'true', { + expires: eightSeconds, + }); + await auth.signoutRedirect({ + redirectTarget: 'top', + post_logout_redirect_uri: `${window.location.origin}${pathname}`, + id_token_hint: undefined, + }); + } else { + await auth.revokeTokens(['access_token', 'refresh_token']); + } +} + +export function login(auth: AuthContextProps, requiredScopes: string[] = [], redirectUri = location.href) { + log('Logging in'); + // Redirect to login + Cookies.set('cs_loggedOut', 'false'); + // TODO: Remove once ephemeral environment supports full and thin profile + let scope = ['openid', ...requiredScopes]; + const partner = getPartnerScope(window.location.pathname); + if (partner) { + scope.push(partner); + } + scope = Array.from(new Set(scope)); + localStorage.setItem(LOGIN_SCOPES_STORAGE_KEY, JSON.stringify(scope)); + // KC scopes are delimited by a space character, hence the join(' ') + return auth.signinRedirect({ + redirect_uri: redirectUri, + scope: scope.join(' '), + nonce: createUUID(), + }); +} diff --git a/src/auth/createGetUserPermissions.ts b/src/auth/createGetUserPermissions.ts new file mode 100644 index 000000000..be8e08c9d --- /dev/null +++ b/src/auth/createGetUserPermissions.ts @@ -0,0 +1,10 @@ +import { ChromeUser } from '@redhat-cloud-services/types'; +import { createFetchPermissionsWatcher } from './fetchPermissions'; + +export default function createGetUserPermissions(getUser: () => Promise, getToken: () => Promise) { + const fetchPermissions = createFetchPermissionsWatcher(getUser); + return async (app = '', bypassCache?: boolean) => { + const token = await getToken(); + return fetchPermissions(token || '', app, bypassCache); + }; +} diff --git a/src/auth/crossAccountBouncer.test.ts b/src/auth/crossAccountBouncer.test.ts new file mode 100644 index 000000000..eabd9d995 --- /dev/null +++ b/src/auth/crossAccountBouncer.test.ts @@ -0,0 +1,45 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import Cookies from 'js-cookie'; +import { ACCOUNT_REQUEST_TIMEOUT, ACTIVE_REMOTE_REQUEST, CROSS_ACCESS_ACCOUNT_NUMBER, CROSS_ACCESS_ORG_ID } from '../utils/consts'; +import crossAccountBouncer from './crossAccountBouncer'; + +describe('crossAccountBouncer', () => { + const mockCookiesGet = jest.spyOn(Cookies, 'get'); + const mockCookiesRemove = jest.spyOn(Cookies, 'remove'); + const reloadMock = jest.fn(); + beforeAll(() => { + // @ts-ignore + delete window.location; + // @ts-ignore + window.location = { + reload: reloadMock, + }; + }); + + beforeEach(() => { + mockCookiesGet.mockClear(); + mockCookiesRemove.mockClear(); + localStorage.removeItem(ACTIVE_REMOTE_REQUEST); + localStorage.removeItem(ACCOUNT_REQUEST_TIMEOUT); + reloadMock.mockClear(); + }); + + it('sets localStorage request timeout and removes active remote request', () => { + // @ts-ignore + mockCookiesGet.mockReturnValueOnce('some-cookie'); + crossAccountBouncer(); + expect(localStorage.getItem(ACCOUNT_REQUEST_TIMEOUT)).toEqual('some-cookie'); + expect(localStorage.getItem(ACTIVE_REMOTE_REQUEST)).toBeNull(); + expect(reloadMock).toHaveBeenCalledTimes(1); + }); + + it('does not set localStorage and calls Cookies.remove', () => { + // @ts-ignore + mockCookiesGet.mockReturnValueOnce(undefined); + crossAccountBouncer(); + expect(localStorage.getItem(ACCOUNT_REQUEST_TIMEOUT)).toBeNull(); + expect(mockCookiesRemove).toHaveBeenCalledWith(CROSS_ACCESS_ACCOUNT_NUMBER); + expect(mockCookiesRemove).toHaveBeenCalledWith(CROSS_ACCESS_ORG_ID); + expect(reloadMock).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/auth/crossAccountBouncer.ts b/src/auth/crossAccountBouncer.ts new file mode 100644 index 000000000..6978f5b98 --- /dev/null +++ b/src/auth/crossAccountBouncer.ts @@ -0,0 +1,13 @@ +import Cookies from 'js-cookie'; +import { ACCOUNT_REQUEST_TIMEOUT, ACTIVE_REMOTE_REQUEST, CROSS_ACCESS_ACCOUNT_NUMBER, CROSS_ACCESS_ORG_ID } from '../utils/consts'; + +export default function crossAccountBouncer() { + const requestCookie = Cookies.get(CROSS_ACCESS_ACCOUNT_NUMBER); + if (requestCookie) { + localStorage.setItem(ACCOUNT_REQUEST_TIMEOUT, requestCookie); + localStorage.removeItem(ACTIVE_REMOTE_REQUEST); + } + Cookies.remove(CROSS_ACCESS_ACCOUNT_NUMBER); + Cookies.remove(CROSS_ACCESS_ORG_ID); + window.location.reload(); +} diff --git a/src/jwt/entitlements.ts b/src/auth/entitlementsApi.ts similarity index 100% rename from src/jwt/entitlements.ts rename to src/auth/entitlementsApi.ts diff --git a/src/auth/fetchPermissions.test.js b/src/auth/fetchPermissions.test.js index 8f6fde5fd..1bda6bedb 100644 --- a/src/auth/fetchPermissions.test.js +++ b/src/auth/fetchPermissions.test.js @@ -13,8 +13,6 @@ jest.mock('./rbac', () => () => { import { createFetchPermissionsWatcher } from './fetchPermissions'; -jest.mock('../jwt/jwt'); - describe('fetchPermissions', () => { let fetchPermissions; let getUser = jest.fn().mockImplementation(() => diff --git a/src/auth/fetchPermissions.ts b/src/auth/fetchPermissions.ts index a09950caf..03aa73b71 100644 --- a/src/auth/fetchPermissions.ts +++ b/src/auth/fetchPermissions.ts @@ -1,6 +1,6 @@ import { Access, AccessPagination } from '@redhat-cloud-services/rbac-client'; import createRbacAPI from './rbac'; -import logger from '../jwt/logger'; +import logger from './logger'; import { ChromeUser } from '@redhat-cloud-services/types'; const log = logger('fetchPermissions.ts'); @@ -17,9 +17,9 @@ const fetchPermissions = (userToken: string, app = '') => { * We should come up with a nice pattern to work around the interceptors * */ const { data, meta } = resp as unknown as Required; - if (meta.count! > perPage) { + if (meta.count && meta.count > perPage) { return Promise.all( - [...new Array(Math.ceil(meta.count! / perPage))].map((_empty, key) => + [...new Array(Math.ceil(meta.count / perPage))].map((_empty, key) => rbacApi .getPrincipalAccess(app, undefined, undefined, perPage, (key + 1) * perPage) .then(({ data }) => data as unknown as AccessPagination['data']) diff --git a/src/auth/getInitialScope.test.ts b/src/auth/getInitialScope.test.ts new file mode 100644 index 000000000..2807955f7 --- /dev/null +++ b/src/auth/getInitialScope.test.ts @@ -0,0 +1,35 @@ +import { RouteDefinition } from '../@types/types'; +import getInitialScope from './getInitialScope'; + +describe('getInitialScope', () => { + const mockRoutes: RouteDefinition[] = [ + { + manifestLocation: 'https://some.url', + module: 'some-module', + path: '/some-module', + scope: 'some-scope', + }, + { + manifestLocation: 'https://some.url', + module: 'no-scoped-module', + path: '/no-scoped-module', + scope: 'no-scoped-module', + }, + ]; + + it('should return the scope of the first matching route', () => { + expect(getInitialScope(mockRoutes, '/some-module')).toBe('some-scope'); + }); + + it('should return the scope of the first matching route for nested routes', () => { + expect(getInitialScope(mockRoutes, '/some-module/some-path')).toBe('some-scope'); + }); + + it('should return the scope of the first matching route for nested routes with a trailing slash', () => { + expect(getInitialScope(mockRoutes, '/some-module/some-path/')).toBe('some-scope'); + }); + + it('should return undefined if no matching route is found', () => { + expect(getInitialScope(mockRoutes, '/no-module')).toBeUndefined(); + }); +}); diff --git a/src/auth/getInitialScope.ts b/src/auth/getInitialScope.ts new file mode 100644 index 000000000..e09ce42c6 --- /dev/null +++ b/src/auth/getInitialScope.ts @@ -0,0 +1,17 @@ +import { matchRoutes } from 'react-router-dom'; +import { RouteDefinition } from '../@types/types'; + +function getInitialScope(routes: RouteDefinition[], pathname: string) { + const initialModuleScope = matchRoutes( + routes.map(({ path, ...rest }) => ({ + ...rest, + path: `${path}/*`, + })), + // modules config does not include the preview fragment + pathname.replace(/^\/(preview|beta)/, '') + )?.[0]?.route?.scope; + + return initialModuleScope; +} + +export default getInitialScope; diff --git a/src/auth/index.ts b/src/auth/index.ts deleted file mode 100644 index 559fc44a0..000000000 --- a/src/auth/index.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { getOfflineToken, wipePostbackParamsThatAreNotForUs } from '../jwt/offline'; -import { AxiosResponse } from 'axios'; -import cookie from 'js-cookie'; -import { ChromeUser } from '@redhat-cloud-services/types'; -import { Store } from 'redux'; - -import * as jwt from '../jwt/jwt'; -import { createUser, getTokenWithAuthorizationCode } from '../cognito/auth'; -import { ITLessCognito } from '../utils/common'; -import consts, { defaultAuthOptions as defaultOptions } from '../utils/consts'; -import { ACCOUNT_REQUEST_TIMEOUT, ACTIVE_REMOTE_REQUEST, CROSS_ACCESS_ACCOUNT_NUMBER, CROSS_ACCESS_ORG_ID } from '../utils/consts'; -import qe from '../utils/iqeEnablement'; -import { ChromeModule } from '../@types/types'; -import { createFetchPermissionsWatcher } from './fetchPermissions'; - -export type LibJWT = { - getOfflineToken: () => Promise>; - jwt: typeof jwt; - initPromise: Promise; -}; - -const TIMER_STR = '[JWT][jwt.js] Auth time'; -const isITLessCognito = ITLessCognito(); -function bouncer() { - if (!jwt.isAuthenticated()) { - cookie.remove(defaultOptions.cookieName); - jwt.login(); - } - - console.timeEnd(TIMER_STR); // eslint-disable-line no-console -} - -export function crossAccountBouncer() { - const requestCookie = cookie.get(CROSS_ACCESS_ACCOUNT_NUMBER); - if (requestCookie) { - localStorage.setItem(ACCOUNT_REQUEST_TIMEOUT, requestCookie); - localStorage.removeItem(ACTIVE_REMOTE_REQUEST); - } - cookie.remove(CROSS_ACCESS_ACCOUNT_NUMBER); - cookie.remove(CROSS_ACCESS_ORG_ID); - window.location.reload(); -} - -export type ChromeGlobalConfig = { chrome?: ChromeModule }; - -export const createAuthObject = (libjwt: LibJWT, getUser: () => Promise, store: Store, globalConfig?: ChromeGlobalConfig) => ({ - getOfflineToken: () => libjwt.getOfflineToken(), - doOffline: () => - libjwt.jwt.doOffline(consts.noAuthParam, consts.offlineToken, globalConfig?.chrome?.ssoUrl || globalConfig?.chrome?.config?.ssoUrl), - getToken: () => libjwt.initPromise.then(() => libjwt.jwt.getUserInfo().then(() => libjwt.jwt.getEncodedToken())), - getRefreshToken: () => libjwt.jwt.getRefreshToken(), - getUser, - qe: { - ...qe, - init: () => qe.init(store, () => libjwt), - }, - logout: (bounce?: boolean) => libjwt.jwt.logoutAllTabs(bounce), - login: () => libjwt.jwt.login(), -}); - -export const createGetUser = (libjwt: LibJWT): (() => Promise) => { - if (isITLessCognito) { - return () => createUser(); - } else { - return () => - libjwt.initPromise.then(libjwt.jwt.getUserInfo).catch(() => { - libjwt.jwt.logoutAllTabs(); - }); - } -}; - -export const createGetUserPermissions = (libJwt: LibJWT, getUser: () => Promise) => { - const fetchPermissions = createFetchPermissionsWatcher(getUser); - return async (app = '', bypassCache?: boolean) => { - if (isITLessCognito) { - const cogToken = await getTokenWithAuthorizationCode(); - return fetchPermissions(cogToken || '', app, bypassCache); - } else { - await getUser(); - return fetchPermissions(libJwt.jwt.getEncodedToken() || '', app, bypassCache); - } - }; -}; - -export default ({ ssoUrl, ssoScopes }: { ssoUrl?: string; ssoScopes: string[] }): LibJWT => { - console.time(TIMER_STR); // eslint-disable-line no-console - const options = { - ...defaultOptions, - scope: ssoScopes.join(' '), - }; - - wipePostbackParamsThatAreNotForUs(); - const token = cookie.get(options.cookieName); - - // If we find an existing token, use it - // so that we dont auth even when a valid token is present - // otherwise its quick, but we bounce around and get a new token - // on every page load - if (token && token.length > 10) { - options.token = token; - } - - const promise = jwt.init(options, ssoUrl).then(bouncer); - - return { - getOfflineToken: () => (isITLessCognito ? getTokenWithAuthorizationCode() : getOfflineToken(options.realm, options.clientId, ssoUrl)), - jwt: jwt, - initPromise: promise, - }; -}; diff --git a/src/auth/initializeAccessRequestCookies.test.ts b/src/auth/initializeAccessRequestCookies.test.ts new file mode 100644 index 000000000..11fa99827 --- /dev/null +++ b/src/auth/initializeAccessRequestCookies.test.ts @@ -0,0 +1,81 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import initializeAccessRequestCookies from './initializeAccessRequestCookies'; +import * as crossAccountBouncer from './crossAccountBouncer'; +import Cookies from 'js-cookie'; +import { ACTIVE_REMOTE_REQUEST, CROSS_ACCESS_ACCOUNT_NUMBER } from '../utils/consts'; + +describe('initializeAccessRequestCookies', () => { + const mockCrossAccountBouncer = jest.spyOn(crossAccountBouncer, 'default'); + const mockCookiesGet = jest.spyOn(Cookies, 'get'); + const mockCookiesRemove = jest.spyOn(Cookies, 'remove'); + beforeAll(() => { + // @ts-ignore + delete window.location; + // @ts-ignore + window.location = {}; + }); + beforeEach(() => { + mockCrossAccountBouncer.mockClear(); + localStorage.removeItem(ACTIVE_REMOTE_REQUEST); + // @ts-ignore + window.location = {}; + }); + + it('does nothing if no initial account is set in localStorage and no Cookie is set', () => { + initializeAccessRequestCookies(); + expect(mockCrossAccountBouncer).not.toHaveBeenCalled(); + }); + + it('does nothing if Cookie is set but no initial account is set in localStorage', () => { + // @ts-ignore + mockCookiesGet.mockReturnValueOnce('some-cookie'); + initializeAccessRequestCookies(); + expect(mockCrossAccountBouncer).not.toHaveBeenCalled(); + }); + + // does nothing if localStorage is set but no Cookie is set + it('does nothing if localStorage is set but no Cookie is set', () => { + // @ts-ignore + mockCookiesGet.mockReturnValueOnce(undefined); + localStorage.setItem(ACTIVE_REMOTE_REQUEST, 'some-local-storage'); + initializeAccessRequestCookies(); + expect(mockCrossAccountBouncer).not.toHaveBeenCalled(); + }); + + it('calls Cookies.remove if the initial account fails to parse', () => { + // @ts-ignore + mockCookiesGet.mockReturnValueOnce('some-cookie'); + localStorage.setItem(ACTIVE_REMOTE_REQUEST, 'some-local-storage'); + initializeAccessRequestCookies(); + expect(mockCookiesRemove).toHaveBeenCalledWith(CROSS_ACCESS_ACCOUNT_NUMBER); + }); + + it('calls crossAccountBouncer if the initial account is expired', () => { + // @ts-ignore + mockCookiesGet.mockReturnValueOnce('some-cookie'); + localStorage.setItem( + ACTIVE_REMOTE_REQUEST, + JSON.stringify({ + // past date + end_date: '2020-01-01', + }) + ); + initializeAccessRequestCookies(); + expect(mockCrossAccountBouncer).toHaveBeenCalled(); + }); + + // does nothing if the initial account is not expired + it('does nothing if the initial account is not expired', () => { + // @ts-ignore + mockCookiesGet.mockReturnValueOnce('some-cookie'); + localStorage.setItem( + ACTIVE_REMOTE_REQUEST, + JSON.stringify({ + // future date + end_date: new Date(new Date().getTime() + 1000 * 60 * 60 * 24).toISOString(), + }) + ); + initializeAccessRequestCookies(); + expect(mockCrossAccountBouncer).not.toHaveBeenCalled(); + }); +}); diff --git a/src/auth/initializeAccessRequestCookies.ts b/src/auth/initializeAccessRequestCookies.ts new file mode 100644 index 000000000..6bd614c09 --- /dev/null +++ b/src/auth/initializeAccessRequestCookies.ts @@ -0,0 +1,21 @@ +import Cookies from 'js-cookie'; +import { ACTIVE_REMOTE_REQUEST, CROSS_ACCESS_ACCOUNT_NUMBER } from '../utils/consts'; +import crossAccountBouncer from './crossAccountBouncer'; + +export default function initializeAccessRequestCookies() { + const initialAccount = localStorage.getItem(ACTIVE_REMOTE_REQUEST); + if (Cookies.get(CROSS_ACCESS_ACCOUNT_NUMBER) && initialAccount) { + try { + const { end_date } = JSON.parse(initialAccount); + /** + * Remove cross account request if it is expired + */ + if (new Date(end_date).getTime() <= Date.now()) { + crossAccountBouncer(); + } + } catch { + console.log('Unable to parse initial account. Using default account'); + Cookies.remove(CROSS_ACCESS_ACCOUNT_NUMBER); + } + } +} diff --git a/src/jwt/logger.ts b/src/auth/logger.ts similarity index 72% rename from src/jwt/logger.ts rename to src/auth/logger.ts index 4e0e3c2ff..2edf9fa94 100644 --- a/src/jwt/logger.ts +++ b/src/auth/logger.ts @@ -3,8 +3,8 @@ export default (fileName: string) => { return (msg: string | unknown) => { if (window.console) { - if (window.localStorage && window.localStorage.getItem('chrome:jwt:debug')) { - window.console.log(`[JWT][${fileName}] ${msg}`); + if (window.localStorage && window.localStorage.getItem('chrome:auth:debug')) { + window.console.log(`[AUTH][${fileName}] ${msg}`); } } }; diff --git a/src/auth/offline.test.ts b/src/auth/offline.test.ts new file mode 100644 index 000000000..fe4a858a9 --- /dev/null +++ b/src/auth/offline.test.ts @@ -0,0 +1,123 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import { getOfflineToken, getPostDataObject, parseHashString, postbackUrlSetup, prepareOfflineRedirect } from './offline'; +import { OFFLINE_REDIRECT_STORAGE_KEY, offlineToken } from '../utils/consts'; + +jest.mock('axios', () => { + const axios = jest.requireActual('axios'); + return { + __esModule: true, + ...axios, + default: { + ...axios.default, + post: () => + Promise.resolve({ + data: { + foo: 'bar', + }, + }), + }, + }; +}); + +describe('offline', () => { + const pushStateMock = jest.fn(); + beforeAll(() => { + // @ts-ignore + delete window.location; + // @ts-ignore + delete window.history; + // @ts-ignore + window.location = {}; + // @ts-ignore + window.history = {}; + }); + beforeEach(() => { + localStorage.removeItem(OFFLINE_REDIRECT_STORAGE_KEY); + // @ts-ignore + window.location = { + href: 'http://console.redhat.com', + origin: 'http://console.redhat.com', + }; + // @ts-ignore + window.history = { + pushState: pushStateMock, + }; + pushStateMock.mockClear(); + }); + + it('creates valid postback data object', () => { + const postData = getPostDataObject('http://localhost:3000', 'clientId', 'code'); + expect(postData).toEqual({ + code: 'code', + grant_type: 'authorization_code', + client_id: 'clientId', + redirect_uri: 'http://localhost:3000', + }); + }); + + it('parses hash string', () => { + const hashString = '#code=code&state=state'; + const parsed = parseHashString(hashString); + expect(parsed).toEqual({ + code: 'code', + state: 'state', + }); + }); + + it('can parse hash string with no state', () => { + const hashString = '#code=code'; + const parsed = parseHashString(hashString); + expect(parsed).toEqual({ + code: 'code', + }); + }); + + it('can prase empty hash string', () => { + const hashString = '#'; + const parsed = parseHashString(hashString); + expect(parsed).toEqual({}); + }); + + it('can parse empty string', () => { + const hashString = ''; + const parsed = parseHashString(hashString); + expect(parsed).toEqual({}); + }); + + it('prepares offline redirect URL from current location.href', () => { + const offlineRedirectUrl = prepareOfflineRedirect(); + const redirectUri = `${window.location.origin}?noauth=${offlineToken}`; + expect(offlineRedirectUrl).toEqual(redirectUri); + expect(localStorage.getItem(OFFLINE_REDIRECT_STORAGE_KEY)).toEqual(redirectUri); + }); + + it('prepares offline redirect URL from custom url base', () => { + const base = 'https://example.com'; + const offlineRedirectUrl = prepareOfflineRedirect(base); + const redirectUri = `${base}?noauth=${offlineToken}`; + expect(offlineRedirectUrl).toEqual(redirectUri); + expect(localStorage.getItem(OFFLINE_REDIRECT_STORAGE_KEY)).toEqual(redirectUri); + }); + + it('postbackUrlSetup does nothing if URL does not contain offline token', () => { + postbackUrlSetup(); + expect(pushStateMock).not.toHaveBeenCalled(); + }); + + it('postbackUrlSetup should remove delete the offline query param from url', () => { + // prevent issues in jsdom + pushStateMock.mockImplementation(() => undefined); + window.location.href = `http://console.redhat.com?noauth=${offlineToken}`; + postbackUrlSetup(); + expect(pushStateMock).toHaveBeenCalledWith('offlinePostback', '', 'http://console.redhat.com/'); + }); + + it('getOfflineToken should retrieve offline token', async () => { + const token = await getOfflineToken('http://localhost:3000/auth/token', 'clientId', 'http://localhost:3000'); + expect(token).toEqual({ + data: { + foo: 'bar', + }, + }); + }); +}); diff --git a/src/auth/offline.ts b/src/auth/offline.ts new file mode 100644 index 000000000..b92a6fbf4 --- /dev/null +++ b/src/auth/offline.ts @@ -0,0 +1,125 @@ +import { OFFLINE_REDIRECT_STORAGE_KEY, noAuthParam, offlineToken } from '../utils/consts'; +import axios, { AxiosResponse } from 'axios'; + +export type OfflineTokenResponse = { + access_token: string; + expires_in: number; + id_token: string; + 'not-before-policy': number; + refresh_expires_in: number; + refresh_token: string; + scope: string; + session_state: string; + token_type: string; +}; + +type OfflineSingleton = { + postbackUrl?: string; + response?: AxiosResponse; +}; + +export const offline: OfflineSingleton = {}; + +export function getPostbackUrl() { + // let folks only do this once + const ret = offline.postbackUrl; + delete offline.postbackUrl; + return ret; +} + +export async function getOfflineToken(tokenUrl: string, clientId: string, redirectUrl: string) { + const postbackUrl = getPostbackUrl(); + + if (offline.response) { + return Promise.resolve(offline.response); + } + + if (!postbackUrl) { + // we need this postback URL because it contains parameters needed to + // call KC for the actual offline token + // thus we cant continue if it is missing + return Promise.reject('not available'); + } + const params = parseHashString(postbackUrl); + + return axios + .post>(tokenUrl, getPostDataString(getPostDataObject(redirectUrl, clientId, params.code)), { + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + }) + .then((response) => { + offline.response = response; + return response; + }); +} + +export function getPostDataObject(redirectUrl: string, clientId: string, code: string) { + return { + code: code, + grant_type: 'authorization_code', + client_id: clientId, + redirect_uri: redirectUrl, + }; +} + +export function parseHashString(str: string) { + try { + return str + .split('#')[1] + .split('&') + .reduce>((result, item) => { + const parts = item.split('='); + result[parts[0]] = parts[1]; + return result; + }, {}); + } catch { + console.error('failed to parse hash string', str); + return {}; + } +} + +function getPostDataString(obj: Record) { + return Object.entries(obj) + .map((entry) => { + return `${entry[0]}=${entry[1]}`; + }) + .join('&'); +} + +export function postbackUrlSetup() { + if (window.location.href.indexOf(offlineToken) !== -1) { + const { hash, origin, pathname } = window.location; + // attempt to use postback created from in previous doOffline call + const postbackUrl = new URL(localStorage.getItem(OFFLINE_REDIRECT_STORAGE_KEY) || `${origin}${pathname}`); + postbackUrl.hash = hash; + + // this is a UHC offline token postback + // we need to not let the JWT lib see this + // and try to use it + offline.postbackUrl = postbackUrl.toString(); + + // we do this because keycloak.js looks at the hash for its parameters + // and if found uses the params for its own use + // + // in the UHC offline post back case we *dont* + // want the params to be used by keycloak.js + // so we have to destroy this stuff and let regular auth routines happen + window.location.hash = ''; + + // nuke the params so that people dont see the ugly + const url = new URL(window.location.href); + url.searchParams.delete(noAuthParam); + window.history.pushState('offlinePostback', '', url.toString()); + } +} + +export function prepareOfflineRedirect(base = window.location.href) { + const url = new URL(base); + url.searchParams.delete(noAuthParam); + url.searchParams.append(noAuthParam, offlineToken); + const redirectUri = url.toString().replace('/?', '?'); + + if (redirectUri) { + localStorage.setItem(OFFLINE_REDIRECT_STORAGE_KEY, redirectUri); + } + return redirectUri; +} diff --git a/src/auth/platformUrl.test.ts b/src/auth/platformUrl.test.ts new file mode 100644 index 000000000..02f985989 --- /dev/null +++ b/src/auth/platformUrl.test.ts @@ -0,0 +1,45 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import { DEFAULT_SSO_ROUTES } from '../utils/common'; +import platformUrl from './platformUrl'; + +describe('platformUrl', () => { + beforeAll(() => { + // @ts-ignore + delete window.location; + // @ts-ignore + window.location = {}; + }); + it('should pick default platform qa sso url if no extra config was setup', () => { + const ssourl = platformUrl(DEFAULT_SSO_ROUTES); + expect(ssourl).toBe('https://sso.qa.redhat.com/auth'); + }); + + it('should return dev sso url if env is set to console.dev', () => { + window.location.hostname = 'console.dev.redhat.com'; + const ssourl = platformUrl(DEFAULT_SSO_ROUTES); + expect(ssourl).toBe(DEFAULT_SSO_ROUTES.dev.sso + '/'); + // don't forget to reset the hostname for other tests + window.location.hostname = ''; + }); + + it('should return custom sso url if provided', () => { + const customSsoUrl = 'https://custom.sso.url'; + const ssourl = platformUrl(DEFAULT_SSO_ROUTES, customSsoUrl); + expect(ssourl).toBe(customSsoUrl + '/'); + }); + + // test for all envs using the DEFAULT_SSO_ROUTES + Object.entries(DEFAULT_SSO_ROUTES).forEach(([env, { url }]) => { + url.forEach((url) => { + it(`should return ${env} sso url if env is set to ${url}`, () => { + window.location.hostname = url; + const ssourl = platformUrl(DEFAULT_SSO_ROUTES); + expect(ssourl).toMatch(new RegExp(DEFAULT_SSO_ROUTES[env as keyof typeof DEFAULT_SSO_ROUTES].sso)); + // Must always end with trailing slash + expect(ssourl).toMatch(/\/$/); + // don't forget to reset the hostname for other tests + window.location.hostname = ''; + }); + }); + }); +}); diff --git a/src/jwt/url.ts b/src/auth/platformUrl.ts similarity index 57% rename from src/jwt/url.ts rename to src/auth/platformUrl.ts index 214aca00d..617afa177 100644 --- a/src/jwt/url.ts +++ b/src/auth/platformUrl.ts @@ -1,16 +1,22 @@ import { DEFAULT_SSO_ROUTES } from '../utils/common'; import logger from './logger'; -const log = logger('insights/url.js'); +const log = logger('auth/platform.ts'); + +// add trailing slash if missing +function sanitizeUrl(url: string) { + return `${url.replace(/\/$/, '')}/`; +} // Parse through keycloak options routes -export default async (env: typeof DEFAULT_SSO_ROUTES, configSsoUrl?: string) => { +export default function platformUlr(env: typeof DEFAULT_SSO_ROUTES, configSsoUrl?: string) { // we have to use hard coded value for console.dev.redhat.com // ugly hack + if (location.hostname === 'console.dev.redhat.com') { - return DEFAULT_SSO_ROUTES.dev.sso; + return sanitizeUrl(DEFAULT_SSO_ROUTES.dev.sso); } if (configSsoUrl) { - return configSsoUrl; + return sanitizeUrl(configSsoUrl); } const ssoEnv = Object.entries(env).find(([, { url }]) => url.includes(location.hostname)); @@ -18,10 +24,10 @@ export default async (env: typeof DEFAULT_SSO_ROUTES, configSsoUrl?: string) => if (ssoEnv) { log(`SSO Url: ${ssoEnv?.[1].sso}`); log(`Current env: ${ssoEnv?.[0]}`); - return ssoEnv?.[1].sso; + return sanitizeUrl(ssoEnv?.[1].sso); } else { log('SSO url: not found, defaulting to qa'); - log('Current env: not found, defaultint to qa'); + log('Current env: not found, defaulting to qa'); return 'https://sso.qa.redhat.com/auth'; } -}; +} diff --git a/src/auth/rbac.test.ts b/src/auth/rbac.test.ts new file mode 100644 index 000000000..923df58e6 --- /dev/null +++ b/src/auth/rbac.test.ts @@ -0,0 +1,21 @@ +import { AccessApi } from '@redhat-cloud-services/rbac-client'; +import rbac from './rbac'; + +describe('rbac', () => { + it('should initialize the rbac client', () => { + const client = rbac(); + expect(client instanceof AccessApi).toBe(true); + expect(client).toHaveProperty('basePath', '/api/rbac/v1'); + }); + + it('rbac client should use interceptor to extract the response data', async () => { + const client = rbac(); + // axios automatically wraps the response in an object with key `data` + // we have and interceptor that extracts the data from the response automatically + jest.spyOn(client, 'getPrincipalAccess').mockResolvedValue({ + foo: 'data', + } as any); + const unpackedResponse = await client.getPrincipalAccess('123'); + expect(unpackedResponse).toEqual({ foo: 'data' }); + }); +}); diff --git a/src/auth/setCookie.test.ts b/src/auth/setCookie.test.ts new file mode 100644 index 000000000..517d02485 --- /dev/null +++ b/src/auth/setCookie.test.ts @@ -0,0 +1,12 @@ +import { setCookie } from './setCookie'; + +describe('setCookie', () => { + // the time is set to 2 minutes and 3 seconds by the `expiresAt` parameter + const cookieRegex = /cs_jwt=token\+token\+token;.*;secure=true;expires=Thu, 01 Jan 1970 00:02:03 GMT/; + it('should set the cookie for various API pathnames', () => { + // console.log(Cookies); + // const cookiesSetSpy = jest.spyOn(Cookies, 'set'); + setCookie('token+token+token', 123); + expect(window.document.cookie).toMatch(cookieRegex); + }); +}); diff --git a/src/auth/setCookie.ts b/src/auth/setCookie.ts new file mode 100644 index 000000000..739fd269a --- /dev/null +++ b/src/auth/setCookie.ts @@ -0,0 +1,32 @@ +import logger from './logger'; + +const log = logger('auth/setCookie.ts'); + +function setCookieWrapper(str: string) { + window.document.cookie = str; +} + +const DEFAULT_COOKIE_NAME = 'cs_jwt'; + +function getCookieExpires(exp: number) { + // we want the cookie to expire at the same time as the JWT session + // so we take the exp and get a new GTMString from that + const date = new Date(0); + date.setUTCSeconds(exp); + return date.toUTCString(); +} + +export async function setCookie(token: string, expiresAt: number) { + log('Setting the cs_jwt cookie'); + if (token && token.length > 10) { + const cookieName = DEFAULT_COOKIE_NAME; + if (cookieName) { + setCookieWrapper(`${cookieName}=${token};` + `path=/wss;` + `secure=true;` + `expires=${getCookieExpires(expiresAt)}`); + setCookieWrapper(`${cookieName}=${token};` + `path=/ws;` + `secure=true;` + `expires=${getCookieExpires(expiresAt)}`); + setCookieWrapper(`${cookieName}=${token};` + `path=/api/tasks/v1;` + `secure=true;` + `expires=${getCookieExpires(expiresAt)}`); + setCookieWrapper(`${cookieName}=${token};` + `path=/api/automation-hub;` + `secure=true;` + `expires=${getCookieExpires(expiresAt)}`); + setCookieWrapper(`${cookieName}=${token};` + `path=/api/remediations/v1;` + `secure=true;` + `expires=${getCookieExpires(expiresAt)}`); + setCookieWrapper(`${cookieName}=${token};` + `path=/api/edge/v1;` + `secure=true;` + `expires=${getCookieExpires(expiresAt)}`); + } + } +} diff --git a/src/bootstrap.tsx b/src/bootstrap.tsx index c5d971bfa..c2a7c50ac 100644 --- a/src/bootstrap.tsx +++ b/src/bootstrap.tsx @@ -1,119 +1,25 @@ import React, { useEffect, useState } from 'react'; import { createRoot } from 'react-dom/client'; -import { Provider, useSelector, useStore } from 'react-redux'; +import { Provider, useSelector } from 'react-redux'; import { IntlProvider, ReactIntlErrorCode } from 'react-intl'; -import { matchRoutes } from 'react-router-dom'; +import { Provider as JotaiProvider } from 'jotai'; import { spinUpStore } from './redux/redux-config'; import RootApp from './components/RootApp'; -import { loadModulesSchema } from './redux/actions'; -import Cookies from 'js-cookie'; -import { ACTIVE_REMOTE_REQUEST, CROSS_ACCESS_ACCOUNT_NUMBER } from './utils/consts'; -import auth, { LibJWT, createGetUserPermissions, crossAccountBouncer } from './auth'; -import sentry from './utils/sentry'; import registerAnalyticsObserver from './analytics/analyticsObserver'; -import { ITLess, ITLessCognito, generateRoutesList, getEnv, loadFedModules, noop, trustarcScriptSetup } from './utils/common'; +import { ITLess, getEnv, trustarcScriptSetup } from './utils/common'; +import { ReduxState } from './redux/store'; +import OIDCProvider from './auth/OIDCConnector/OIDCProvider'; import messages from './locales/data.json'; import ErrorBoundary from './components/ErrorComponents/ErrorBoundary'; -import LibtJWTContext from './components/LibJWTContext'; -import { ReduxState } from './redux/store'; -import qe from './utils/iqeEnablement'; -import initializeJWT from './jwt/initialize-jwt'; -import AppPlaceholder from './components/AppPlaceholder'; -import { initializeVisibilityFunctions } from './utils/VisibilitySingleton'; -import { createGetUser } from './auth'; -import { getTokenWithAuthorizationCode } from './cognito/auth'; - -const language: keyof typeof messages = 'en'; - -const initializeAccessRequestCookies = () => { - const initialAccount = localStorage.getItem(ACTIVE_REMOTE_REQUEST); - if (Cookies.get(CROSS_ACCESS_ACCOUNT_NUMBER) && initialAccount) { - try { - const { end_date } = JSON.parse(initialAccount); - /** - * Remove cross account request if it is expired - */ - if (new Date(end_date).getTime() <= Date.now()) { - crossAccountBouncer(); - } - } catch { - console.log('Unable to parse initial account. Using default account'); - Cookies.remove(CROSS_ACCESS_ACCOUNT_NUMBER); - } - } -}; - -const libjwtSetup = (chromeConfig: { ssoUrl?: string }, ssoScopes: string[] = []) => { - const libjwt = auth({ ...chromeConfig, ssoScopes } || { ssoScopes }); - - if (!ITLess()) { - libjwt.initPromise.then(() => { - return libjwt.jwt - .getUserInfo() - .then((chromeUser) => { - if (chromeUser) { - sentry(chromeUser); - } - }) - .catch(noop); - }); - } - - return libjwt; -}; +import chromeStore from './state/chromeStore'; const isITLessEnv = ITLess(); +const language: keyof typeof messages = 'en'; +const AuthProvider = OIDCProvider; -const useInitialize = () => { - const [{ isReady, libJwt }, setState] = useState<{ isReady: boolean; libJwt?: LibJWT }>({ isReady: false, libJwt: undefined }); - const store = useStore(); - - const init = async () => { - const pathname = window.location.pathname; - // We have to use `let` because we want to access it once jwt is initialized - let libJwt: LibJWT | undefined = undefined; - // init qe functions, callback for libjwt because we want it to initialize before jwt is ready - qe.init(store, () => libJwt); - - // Load federated modules before the SSO init phase to obtain scope configuration - const { data: modulesData } = await loadFedModules(); - const { chrome: chromeConfig } = modulesData; - const routes = generateRoutesList(modulesData); - store.dispatch(loadModulesSchema(modulesData)); - // ge the initial module UI identifier - const initialModuleScope = matchRoutes( - routes.map(({ path, ...rest }) => ({ - ...rest, - path: `${path}/*`, - })), - // modules config does not include the preview fragment - pathname.replace(/^\/(preview|beta)/, '') - )?.[0]?.route?.scope; - const initialModuleConfig = initialModuleScope && modulesData[initialModuleScope]?.config; - initializeAccessRequestCookies(); - // create JWT instance - libJwt = libjwtSetup({ ...chromeConfig?.config, ...chromeConfig }, initialModuleConfig?.ssoScopes); - - await initializeJWT(libJwt); - const getUser = createGetUser(libJwt); - initializeVisibilityFunctions({ - getUser, - getToken: () => - ITLessCognito() - ? getTokenWithAuthorizationCode() - : libJwt!.initPromise.then(() => libJwt!.jwt.getUserInfo().then(() => libJwt!.jwt.getEncodedToken())), - getUserPermissions: createGetUserPermissions(libJwt, getUser), - }); - - setState({ - libJwt, - isReady: true, - }); - }; - +const useInitializeAnalytics = () => { useEffect(() => { - init(); // setup trust arc trustarcScriptSetup(); // setup adobe analytics @@ -122,64 +28,48 @@ const useInitialize = () => { registerAnalyticsObserver(); } }, []); - - return { - isReady, - libJwt, - }; }; const App = () => { - const modules = useSelector(({ chrome }: ReduxState) => chrome?.modules); - const scalprumConfig = useSelector(({ chrome }: ReduxState) => chrome?.scalprumConfig); const documentTitle = useSelector(({ chrome }: ReduxState) => chrome?.documentTitle); const [cookieElement, setCookieElement] = useState(null); - const { isReady, libJwt } = useInitialize(); + + useInitializeAnalytics(); useEffect(() => { const title = typeof documentTitle === 'string' ? `${documentTitle} | ` : ''; document.title = `${title}console.redhat.com`; }, [documentTitle]); - if (ITLessCognito()) { - return isReady && modules && scalprumConfig ? ( - - ) : ( - - ); - } - - return isReady && modules && scalprumConfig && libJwt ? ( - - - - ) : ( - - ); + return ; }; const entry = document.getElementById('chrome-entry'); if (entry) { const reactRoot = createRoot(entry); reactRoot.render( - { - if ( - (getEnv() === 'stage' && !window.location.origin.includes('foo')) || - localStorage.getItem('chrome:intl:debug') === 'true' || - !(error.code === ReactIntlErrorCode.MISSING_TRANSLATION) - ) { - console.error(error); - } - }} - > + - - - + + { + if ( + (getEnv() === 'stage' && !window.location.origin.includes('foo')) || + localStorage.getItem('chrome:intl:debug') === 'true' || + !(error.code === ReactIntlErrorCode.MISSING_TRANSLATION) + ) { + console.error(error); + } + }} + > + + + + + - + ); } diff --git a/src/chrome/create-chrome.test.js b/src/chrome/create-chrome.test.js deleted file mode 100644 index 0f132b16d..000000000 --- a/src/chrome/create-chrome.test.js +++ /dev/null @@ -1,51 +0,0 @@ -import { initializeVisibilityFunctions } from '../utils/VisibilitySingleton'; -import { createChromeContext } from './create-chrome'; - -jest.mock('@scalprum/core', () => { - return { - __esModule: true, - initSharedScope: jest.fn(), - getSharedScope: jest.fn().mockReturnValue({}), - }; -}); - -jest.mock('../jwt/jwt'); -jest.mock('../auth/fetchPermissions'); - -describe('create chrome', () => { - let jwt; - beforeAll(() => { - initializeVisibilityFunctions({}); - }); - beforeEach(() => { - jwt = { - initPromise: new Promise((res) => setTimeout(() => res(), 200)), - jwt: { - getUserInfo: () => Promise.resolve({ foo: 'bar' }), - getEncodedToken: (x) => x, - }, - }; - }); - - it('should create chrome instance', () => { - const chrome = createChromeContext({ - libJwt: jwt, - store: {}, - getUser: () => Promise.resolve(), - }); - expect(chrome).toEqual(expect.any(Object)); - }); - - it('should postpone getUserPermissions resolve, after chrome cache is initialized', () => { - const promiseSpy = jest.fn(); - expect.assertions(1); - const { getUserPermissions } = createChromeContext({ - libJwt: jwt, - store: {}, - getUser: () => Promise.resolve(), - }); - return getUserPermissions(promiseSpy).then(() => { - expect(promiseSpy).toHaveBeenCalledWith('mocked-user-permissions'); - }); - }); -}); diff --git a/src/chrome/create-chrome.test.ts b/src/chrome/create-chrome.test.ts new file mode 100644 index 000000000..bc6faa432 --- /dev/null +++ b/src/chrome/create-chrome.test.ts @@ -0,0 +1,131 @@ +import { initializeVisibilityFunctions } from '../utils/VisibilitySingleton'; +import { createChromeContext } from './create-chrome'; +import { Store, createStore } from 'redux'; +import { ChromeUser } from '@redhat-cloud-services/types'; +import { ChromeAuthContextValue } from '../auth/ChromeAuthContext'; +import { AxiosResponse } from 'axios'; +import { OfflineTokenResponse } from '../auth/offline'; +import { AnalyticsBrowser } from '@segment/analytics-next'; +import QuickStartCatalog from '../components/QuickStart/QuickStartCatalog'; +import { ReduxState } from '../redux/store'; + +jest.mock('@scalprum/core', () => { + return { + __esModule: true, + initSharedScope: jest.fn(), + getSharedScope: jest.fn().mockReturnValue({}), + }; +}); + +jest.mock('../auth/fetchPermissions'); + +const mockUser: ChromeUser = { + entitlements: {}, + identity: { + org_id: '1234', + type: 'User', + account_number: '1234', + user: { + is_active: true, + is_org_admin: true, + is_internal: true, + locale: 'en_US', + username: 'test-user', + email: '', + first_name: 'John', + last_name: 'Doe', + }, + }, +}; + +describe('create chrome', () => { + const chromeAuthMock: ChromeAuthContextValue = { + doOffline() { + return Promise.resolve(); + }, + getOfflineToken() { + return Promise.resolve({ + data: { + access_token: 'string', + expires_in: 0, + id_token: 'string', + 'not-before-policy': 0, + refresh_expires_in: 0, + refresh_token: 'string', + scope: 'string', + session_state: 'string', + token_type: 'string', + }, + } as AxiosResponse); + }, + getToken() { + return Promise.resolve('string'); + }, + getUser() { + return Promise.resolve(mockUser); + }, + login() { + return Promise.resolve(); + }, + loginAllTabs() { + return; + }, + logout() { + return; + }, + logoutAllTabs() { + return; + }, + ready: true, + token: 'string', + tokenExpires: 0, + user: mockUser, + }; + + const chromeContextOptionsMock = { + store: createStore(() => ({})) as Store, + // getUser: () => Promise.resolve(mockUser), + chromeAuth: chromeAuthMock, + analytics: new AnalyticsBrowser(), + helpTopics: { + addHelpTopics: jest.fn(), + closeHelpTopic: jest.fn(), + disableTopics: jest.fn(), + enableTopics: jest.fn(), + setActiveTopic: jest.fn(), + }, + quickstartsAPI: { + Catalog: QuickStartCatalog, + set() { + return; + }, + toggle() { + return; + }, + version: 2, + }, + setPageMetadata: jest.fn(), + useGlobalFilter: jest.fn(), + }; + beforeAll(() => { + const mockAuthMethods = { + getUser: () => Promise.resolve(mockUser), + getToken: () => Promise.resolve('mocked-token'), + getUserPermissions: () => Promise.resolve([]), + }; + initializeVisibilityFunctions(mockAuthMethods); + }); + + it('should create chrome instance', () => { + const chrome = createChromeContext(chromeContextOptionsMock); + expect(chrome).toEqual(expect.any(Object)); + }); + + it('should postpone getUserPermissions resolve, after chrome cache is initialized', async () => { + const promiseSpy = jest.fn(); + expect.assertions(1); + const { getUserPermissions } = createChromeContext(chromeContextOptionsMock); + await getUserPermissions(promiseSpy as unknown as string); + expect(promiseSpy).toHaveBeenCalledWith('mocked-user-permissions'); + }); +}); diff --git a/src/chrome/create-chrome.ts b/src/chrome/create-chrome.ts index 603b60a6c..ece4f8fa9 100644 --- a/src/chrome/create-chrome.ts +++ b/src/chrome/create-chrome.ts @@ -1,6 +1,5 @@ import { createFetchPermissionsWatcher } from '../auth/fetchPermissions'; -import { LibJWT, createAuthObject } from '../auth'; -import { AppNavigationCB, ChromeAPI, ChromeUser, GenericCB, NavDOMEvent } from '@redhat-cloud-services/types'; +import { AppNavigationCB, ChromeAPI, GenericCB, NavDOMEvent } from '@redhat-cloud-services/types'; import { Store } from 'redux'; import { AnalyticsBrowser } from '@segment/analytics-next'; import get from 'lodash/get'; @@ -19,7 +18,7 @@ import { toggleFeedbackModal, toggleGlobalFilter, } from '../redux/actions'; -import { ITLess, ITLessCognito, getEnv, getEnvDetails, isBeta, isProd, updateDocumentTitle } from '../utils/common'; +import { ITLess, getEnv, getEnvDetails, isBeta, isProd, updateDocumentTitle } from '../utils/common'; import { createSupportCase } from '../utils/createCase'; import debugFunctions from '../utils/debugFunctions'; import { flatTags } from '../components/GlobalFilter/globalFilterApi'; @@ -30,39 +29,33 @@ import { clearAnsibleTrialFlag, isAnsibleTrialFlagActive, setAnsibleTrialFlag } import chromeHistory from '../utils/chromeHistory'; import { ReduxState } from '../redux/store'; import { STORE_INITIAL_HASH } from '../redux/action-types'; -import { ChromeModule, FlagTagsFilter } from '../@types/types'; -import { createCognitoAuthObject } from '../cognito'; -import { getTokenWithAuthorizationCode } from '../cognito/auth'; +import { FlagTagsFilter } from '../@types/types'; import useBundle, { getUrl } from '../hooks/useBundle'; import { warnDuplicatePkg } from './warnDuplicatePackages'; import { getVisibilityFunctions } from '../utils/VisibilitySingleton'; +import { ChromeAuthContextValue } from '../auth/ChromeAuthContext'; +import qe from '../utils/iqeEnablement'; export type CreateChromeContextConfig = { useGlobalFilter: (callback: (selectedTags?: FlagTagsFilter) => any) => ReturnType; - libJwt: LibJWT; - getUser: () => Promise; store: Store; - modulesConfig?: { - [key: string]: ChromeModule; - }; setPageMetadata: (pageOptions: any) => any; analytics: AnalyticsBrowser; quickstartsAPI: ChromeAPI['quickStarts']; helpTopics: ChromeAPI['helpTopics']; + chromeAuth: ChromeAuthContextValue; }; export const createChromeContext = ({ useGlobalFilter, - libJwt, - getUser, store, - modulesConfig, setPageMetadata, analytics, quickstartsAPI, helpTopics, + chromeAuth, }: CreateChromeContextConfig): ChromeAPI => { - const fetchPermissions = createFetchPermissionsWatcher(getUser); + const fetchPermissions = createFetchPermissionsWatcher(chromeAuth.getUser); const visibilityFunctions = getVisibilityFunctions(); const dispatch = store.dispatch; const actions = { @@ -99,11 +92,21 @@ export const createChromeContext = ({ }; const isITLessEnv = ITLess(); - const isITLessCognito = ITLessCognito(); const api: ChromeAPI = { ...actions, - auth: isITLessCognito ? createCognitoAuthObject(store) : createAuthObject(libJwt, getUser, store, modulesConfig), + auth: { + getToken: chromeAuth.getToken, + getUser: chromeAuth.getUser, + logout: chromeAuth.logout, + login: chromeAuth.login, + doOffline: chromeAuth.doOffline, + getOfflineToken: chromeAuth.getOfflineToken, + qe: { + ...qe, + init: () => qe.init(store, chromeAuth.token), + }, + }, initialized: true, isProd, forceDemo: () => Cookies.set('cs_demo', 'true'), @@ -112,15 +115,10 @@ export const createChromeContext = ({ getApp: () => getUrl('app'), getEnvironment: () => getEnv(), getEnvironmentDetails: () => getEnvDetails(), - createCase: (fields?: any) => getUser().then((user) => createSupportCase(user!.identity, libJwt, fields)), + createCase: (fields?: any) => chromeAuth.getUser().then((user) => createSupportCase(user!.identity, chromeAuth.token, fields)), getUserPermissions: async (app = '', bypassCache?: boolean) => { - if (isITLessCognito) { - const cogToken = await getTokenWithAuthorizationCode(); - return fetchPermissions(cogToken || '', app, bypassCache); - } else { - await getUser(); - return fetchPermissions(libJwt.jwt.getEncodedToken() || '', app, bypassCache); - } + const token = await chromeAuth.getToken(); + return fetchPermissions(token, app, bypassCache); }, identifyApp, hideGlobalFilter: (isHidden: boolean) => { diff --git a/src/cognito/auth.ts b/src/cognito/auth.ts deleted file mode 100644 index 2e3bbef17..000000000 --- a/src/cognito/auth.ts +++ /dev/null @@ -1,211 +0,0 @@ -import { AuthenticationDetails, CognitoUser, CognitoUserPool } from 'amazon-cognito-identity-js'; -import { getEnv } from '../utils/common'; -import { isBeta } from '../utils/common'; - -export interface CogUser { - auth_time: number; - client_id: string; - email: string; - event_id: string; - exp: number; - first_name: string; - iat: number; - id: string; - is_internal: boolean; - is_org_admin: boolean; - iss: string; - jti: string; - last_name: string; - locale: string; - org_id: string; - origin_jti: string; - scope: string; - sub: string; - token_use: string; - username: string; - version: number; -} - -async function fetchData() { - try { - const response = await fetch(`${isBeta() ? '/beta' : ''}/apps/chrome/env.json`); - const jsonData = await response.json(); - return jsonData; - } catch (error) { - console.log(error); - } -} - -function getSearchParams(url: string): { [key: string]: string } { - const searchString = new URL(url).search; - const searchParams = new URLSearchParams(searchString); - const searchParamsObject: { [key: string]: string } = {}; - for (const [key, value] of searchParams) { - searchParamsObject[key] = value; - } - return searchParamsObject; -} - -export async function getTokenWithAuthorizationCode() { - const code = getSearchParams(window.location.href).code; - const refreshToken = localStorage.getItem('REFRESH_TOKEN'); - - const data = await fetchData(); - const dataConfig = getEnv() === 'frh' ? data.poolData?.prod : getEnv() === 'frhStage' ? data.poolData?.stage : null; - const loginUrl = `${dataConfig?.ssoUrl}/login?client_id=${dataConfig?.ClientId}&response_type=code&scope=openid&redirect_uri=${dataConfig?.redirectUri}`; - - const redirectUri = dataConfig?.redirectUri; - if (!code && !refreshToken) { - localStorage.clear(); - window.location.href = loginUrl; - } - if (refreshToken) { - try { - const response = await fetch(`${dataConfig?.ssoUrl}/oauth2/token`, { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - body: `grant_type=refresh_token&client_id=${dataConfig?.ClientId}&refresh_token=${localStorage.getItem('REFRESH_TOKEN')}`, - }); - if (!response.ok) { - localStorage.clear(); - window.location.href = loginUrl; - throw new Error(`Request failed with status code ${response.status}`); - } - - const tokens = await response.json(); - - localStorage.setItem('ACCESS_TOKEN', tokens.access_token); - return tokens.access_token; - } catch (error) { - console.error(error); - } - } - try { - const response = await fetch(`${dataConfig?.ssoUrl}/oauth2/token`, { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - body: `grant_type=authorization_code&client_id=${dataConfig?.ClientId}&redirect_uri=${redirectUri}&code=${code}`, - }); - - if (!response.ok) { - throw new Error(`Request failed with status code ${response.status}`); - } - - const tokens = await response.json(); - localStorage.setItem('REFRESH_TOKEN', tokens.refresh_token); - localStorage.setItem('ACCESS_TOKEN', tokens.access_token); - return tokens.access_token; - } catch (error) { - console.error(error); - } -} - -export async function getUser(): Promise { - const token = localStorage.getItem('ACCESS_TOKEN'); - - const requestOptions = { - method: 'GET', - headers: { - Authorization: `Bearer ${token}`, - }, - }; - - try { - const response = await fetch('/api/identity/me', requestOptions); - if (!response.ok) { - throw new Error(`Request failed with status code: ${response.status}`); - } - return response.json(); - } catch (error) { - console.error(error); - throw error; - } -} - -export async function getEntitlements() { - const token = localStorage.getItem('ACCESS_TOKEN'); - - const requestOptions = { - method: 'GET', - headers: { - Authorization: `Bearer ${token}`, - }, - }; - - try { - const response = await fetch('/api/entitlements/v1/services', requestOptions); - if (!response.ok) { - throw new Error(`Request failed with status code: ${response.status}`); - } - return response.json(); - } catch (error) { - console.error(error); - throw error; - } -} - -export async function createUser() { - const userRes = await getUser(); - const entitlementRes = await getEntitlements(); - - const user = { - entitlements: entitlementRes, - identity: { - account_number: '1234', - org_id: userRes.org_id, - type: 'User', - user: { - username: userRes.username, - email: userRes.email, - first_name: userRes.first_name, - last_name: userRes.last_name, - is_active: userRes?.is_active || true, - is_org_admin: userRes.is_org_admin, - is_internal: userRes.is_internal, - locale: userRes.locale, - }, - internal: { - org_id: userRes.org_id, - account_id: userRes.id, - }, - }, - }; - return user; -} - -export async function login(username: string, password: string) { - const data = await fetchData(); - return new Promise((resolve, reject) => { - const authenticationData = { - Username: username, - Password: password, - }; - const authenticationDetails = new AuthenticationDetails(authenticationData); - const userPool = new CognitoUserPool(data.poolData.stage); - const userData = { - Username: username, - Pool: userPool, - }; - const cognitoUser = new CognitoUser(userData); - cognitoUser.authenticateUser(authenticationDetails, { - onSuccess: function (result) { - resolve(result); - }, - onFailure: function (err) { - reject(err); - }, - }); - }); -} - -export async function cogLogout() { - const data = await fetchData(); - const dataConfig = getEnv() === 'frh' ? data.poolData?.prod : getEnv() === 'frhStage' ? data.poolData?.stage : null; - const loginUrl = `${dataConfig?.ssoUrl}/login?client_id=${dataConfig?.ClientId}&response_type=code&scope=openid&redirect_uri=${dataConfig?.redirectUri}`; - localStorage.clear(); - window.location.href = loginUrl; -} diff --git a/src/cognito/index.ts b/src/cognito/index.ts deleted file mode 100644 index ff2280c24..000000000 --- a/src/cognito/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { cogLogout, createUser, getTokenWithAuthorizationCode, login } from './auth'; -import qe from '../utils/iqeEnablement'; -import { Store } from 'redux'; - -export const createCognitoAuthObject = (store: Store) => ({ - getToken: () => getTokenWithAuthorizationCode(), - getUser: () => createUser(), - logout: () => cogLogout(), - login: () => login('your_username', 'your_password'), - qe: { - ...qe, - init: () => qe.init(store), - }, -}); diff --git a/src/components/ChromeLink/ChromeLink.tsx b/src/components/ChromeLink/ChromeLink.tsx index 0c8aa3127..a6272d73c 100644 --- a/src/components/ChromeLink/ChromeLink.tsx +++ b/src/components/ChromeLink/ChromeLink.tsx @@ -7,6 +7,8 @@ import { appNavClick } from '../../redux/actions'; import NavContext, { OnLinkClick } from '../Navigation/navContext'; import { ReduxState } from '../../redux/store'; import { NavDOMEvent, RouteDefinition } from '../../@types/types'; +import { useAtomValue } from 'jotai'; +import { activeModuleAtom } from '../../state/atoms'; interface RefreshLinkProps extends React.HTMLAttributes { isExternal?: boolean; @@ -137,7 +139,7 @@ const RefreshLink: React.FC = (props) => { const ChromeLink: React.FC = ({ appId, children, ...rest }) => { const { onLinkClick, isNavOpen, inPageLayout } = useContext(NavContext); - const currAppId = useSelector(({ chrome }: ReduxState) => chrome?.appId); + const currAppId = useAtomValue(activeModuleAtom); const LinkComponent = !rest.isExternal ? LinkWrapper : RefreshLink; return ( diff --git a/src/components/ChromeRoute/ChromeRoute.tsx b/src/components/ChromeRoute/ChromeRoute.tsx index dbc64f690..7e5a271cc 100644 --- a/src/components/ChromeRoute/ChromeRoute.tsx +++ b/src/components/ChromeRoute/ChromeRoute.tsx @@ -2,7 +2,7 @@ import { ScalprumComponent } from '@scalprum/react-core'; import React, { memo, useContext, useEffect } from 'react'; import LoadingFallback from '../../utils/loading-fallback'; import { batch, useDispatch, useSelector } from 'react-redux'; -import { changeActiveModule, toggleGlobalFilter, updateDocumentTitle } from '../../redux/actions'; +import { toggleGlobalFilter, updateDocumentTitle } from '../../redux/actions'; import ErrorComponent from '../ErrorComponents/DefaultErrorComponent'; import { getPendoConf } from '../../analytics'; import classNames from 'classnames'; @@ -11,6 +11,9 @@ import GatewayErrorComponent from '../ErrorComponents/GatewayErrorComponent'; import { ReduxState } from '../../redux/store'; import { DeepRequired } from 'utility-types'; import { ChromeUser } from '@redhat-cloud-services/types'; +import ChromeAuthContext from '../../auth/ChromeAuthContext'; +import { useAtom } from 'jotai'; +import { activeModuleAtom } from '../../state/atoms'; export type ChromeRouteProps = { scope: string; @@ -26,11 +29,12 @@ const ChromeRoute = memo( ({ scope, module, scopeClass, path, props }: ChromeRouteProps) => { const dispatch = useDispatch(); const { setActiveHelpTopicByName } = useContext(HelpTopicContext); - const user = useSelector(({ chrome: { user } }: ReduxState) => user); + const { user } = useContext(ChromeAuthContext); const gatewayError = useSelector(({ chrome: { gatewayError } }: ReduxState) => gatewayError); - const activeModule = useSelector(({ chrome: { activeModule } }: ReduxState) => activeModule); const defaultTitle = useSelector(({ chrome: { modules } }: ReduxState) => modules?.[scope]?.defaultDocumentTitle || scope); + const [activeModule, setActiveModule] = useAtom(activeModuleAtom); + useEffect(() => { batch(() => { // Only trigger update on a first application render before any active module has been selected @@ -41,7 +45,7 @@ const ChromeRoute = memo( */ dispatch(updateDocumentTitle(defaultTitle || 'Hybrid Cloud Console')); } - dispatch(changeActiveModule(scope)); + setActiveModule(scope); }); /** * update pendo metadata on application change diff --git a/src/components/ErrorComponents/DefaultErrorComponent.tsx b/src/components/ErrorComponents/DefaultErrorComponent.tsx index f7b0de3c7..b574f9f33 100644 --- a/src/components/ErrorComponents/DefaultErrorComponent.tsx +++ b/src/components/ErrorComponents/DefaultErrorComponent.tsx @@ -12,12 +12,12 @@ import ExclamationCircleIcon from '@patternfly/react-icons/dist/dynamic/icons/ex import { chunkLoadErrorRefreshKey } from '../../utils/common'; import { useIntl } from 'react-intl'; import messages from '../../locales/Messages'; -import { useSelector } from 'react-redux'; -import { ReduxState } from '../../redux/store'; import './ErrorComponent.scss'; import { get3scaleError } from '../../utils/responseInterceptors'; import GatewayErrorComponent from './GatewayErrorComponent'; import { getUrl } from '../../hooks/useBundle'; +import { useAtomValue } from 'jotai'; +import { activeModuleAtom } from '../../state/atoms'; export type DefaultErrorComponentProps = { error?: any | Error; @@ -30,7 +30,7 @@ const DefaultErrorComponent = (props: DefaultErrorComponentProps) => { const intl = useIntl(); const [sentryId, setSentryId] = useState(); - const activeModule = useSelector(({ chrome: { activeModule } }: ReduxState) => activeModule); + const activeModule = useAtomValue(activeModuleAtom); const exceptionMessage = (props.error as Error)?.message ? (props.error as Error).message : 'Unhandled UI runtime error'; useEffect(() => { const sentryId = diff --git a/src/components/ErrorComponents/GatewayErrorComponent.tsx b/src/components/ErrorComponents/GatewayErrorComponent.tsx index 3a0e1c971..6b47bd87b 100644 --- a/src/components/ErrorComponents/GatewayErrorComponent.tsx +++ b/src/components/ErrorComponents/GatewayErrorComponent.tsx @@ -9,6 +9,8 @@ import { Text, TextContent } from '@patternfly/react-core/dist/dynamic/component import { useIntl } from 'react-intl'; import Messages from '../../locales/Messages'; import { ThreeScaleError } from '../../utils/responseInterceptors'; +import { useAtomValue } from 'jotai'; +import { activeModuleAtom } from '../../state/atoms'; export type GatewayErrorComponentProps = { error: ThreeScaleError; @@ -48,8 +50,10 @@ const Description = ({ detail, complianceError }: DescriptionProps) => { }; const GatewayErrorComponent = ({ error }: GatewayErrorComponentProps) => { + const activeModule = useAtomValue(activeModuleAtom); + const activeProduct = useSelector((state: ReduxState) => state.chrome.activeProduct); // get active product, fallback to module name if product is not defined - const serviceName = useSelector((state: ReduxState) => state.chrome.activeProduct || state.chrome.activeModule); + const serviceName = activeProduct || activeModule; return } serviceName={serviceName} />; }; diff --git a/src/components/ErrorComponents/IDPError.tsx b/src/components/ErrorComponents/IDPError.tsx index 40329581a..80a5b9cea 100644 --- a/src/components/ErrorComponents/IDPError.tsx +++ b/src/components/ErrorComponents/IDPError.tsx @@ -1,16 +1,17 @@ -import React from 'react'; +import React, { useContext } from 'react'; import { Bullseye } from '@patternfly/react-core/dist/dynamic/layouts/Bullseye'; import { Button } from '@patternfly/react-core/dist/dynamic/components/Button'; import { EmptyState, EmptyStateBody, EmptyStateIcon } from '@patternfly/react-core/dist/dynamic/components/EmptyState'; import { Title } from '@patternfly/react-core/dist/dynamic/components/Title'; import ExclamationCircleIcon from '@patternfly/react-icons/dist/dynamic/icons/exclamation-circle-icon'; -import { logoutAllTabs } from '../../jwt/jwt'; import { useIntl } from 'react-intl'; import messages from '../../locales/Messages'; +import ChromeAuthContext from '../../auth/ChromeAuthContext'; const IDPError = () => { const intl = useIntl(); + const { logoutAllTabs } = useContext(ChromeAuthContext); return ( diff --git a/src/components/FeatureFlags/FeatureFlagsProvider.tsx b/src/components/FeatureFlags/FeatureFlagsProvider.tsx index 3ab57089c..a9b03fbee 100644 --- a/src/components/FeatureFlags/FeatureFlagsProvider.tsx +++ b/src/components/FeatureFlags/FeatureFlagsProvider.tsx @@ -1,11 +1,9 @@ -import React, { useMemo } from 'react'; +import React, { useContext, useMemo } from 'react'; import { FlagProvider, IFlagProvider, UnleashClient } from '@unleash/proxy-client-react'; import { DeepRequired } from 'utility-types'; -import { useSelector } from 'react-redux'; import { captureException } from '@sentry/react'; -import { ReduxState } from '../../redux/store'; -import { ChromeUser } from '@redhat-cloud-services/types'; import * as Sentry from '@sentry/react'; +import ChromeAuthContext, { ChromeAuthContextValue } from '../../auth/ChromeAuthContext'; const config: IFlagProvider['config'] = { url: `${document.location.origin}/api/featureflags/v0`, @@ -63,7 +61,7 @@ export let unleashClient: UnleashClient; export const getFeatureFlagsError = () => localStorage.getItem(UNLEASH_ERROR_KEY) === 'true'; const FeatureFlagsProvider: React.FC = ({ children }) => { - const user = useSelector, DeepRequired>((state) => state.chrome.user); + const { user } = useContext(ChromeAuthContext) as DeepRequired; unleashClient = useMemo( () => new UnleashClient({ diff --git a/src/components/Feedback/FeedbackModal.tsx b/src/components/Feedback/FeedbackModal.tsx index 30c21706d..c0117439a 100644 --- a/src/components/Feedback/FeedbackModal.tsx +++ b/src/components/Feedback/FeedbackModal.tsx @@ -8,11 +8,10 @@ import { Text, TextContent, TextVariants } from '@patternfly/react-core/dist/dyn import ExternalLinkAltIcon from '@patternfly/react-icons/dist/dynamic/icons/external-link-alt-icon'; import OutlinedCommentsIcon from '@patternfly/react-icons/dist/dynamic/icons/outlined-comments-icon'; - +import { DeepRequired } from 'utility-types'; import { ChromeUser } from '@redhat-cloud-services/types'; import { useDispatch, useSelector } from 'react-redux'; import { useIntl } from 'react-intl'; -import { DeepRequired } from 'utility-types'; import feedbackIllo from '../../../static/images/feedback_illo.svg'; import FeedbackForm from './FeedbackForm'; @@ -23,13 +22,9 @@ import messages from '../../locales/Messages'; import FeedbackError from './FeedbackError'; import InternalChromeContext from '../../utils/internalChromeContext'; -import LibtJWTContext from '../LibJWTContext'; import { createSupportCase } from '../../utils/createCase'; import './Feedback.scss'; - -export type FeedbackModalProps = { - user: DeepRequired; -}; +import ChromeAuthContext from '../../auth/ChromeAuthContext'; export type FeedbackPages = | 'feedbackHome' @@ -41,14 +36,15 @@ export type FeedbackPages = | 'bugReportSuccess' | 'informDirectionSuccess'; -const FeedbackModal = memo(({ user }: FeedbackModalProps) => { +const FeedbackModal = memo(() => { const intl = useIntl(); const usePendoFeedback = useSelector(({ chrome: { usePendoFeedback } }) => usePendoFeedback); const isOpen = useSelector(({ chrome: { isFeedbackModalOpen } }) => isFeedbackModalOpen); const dispatch = useDispatch(); const [modalPage, setModalPage] = useState('feedbackHome'); const { getEnvironment } = useContext(InternalChromeContext); - const libjwt = useContext(LibtJWTContext); + const chromeAuth = useContext(ChromeAuthContext); + const user = chromeAuth.user as DeepRequired; const env = getEnvironment(); const isAvailable = env === 'prod' || env === 'stage'; const setIsModalOpen = (isOpen: boolean) => dispatch(toggleFeedbackModal(isOpen)); @@ -76,7 +72,7 @@ const FeedbackModal = memo(({ user }: FeedbackModalProps) => { {intl.formatMessage(messages.describeBugUrgentCases)}
- createSupportCase(user.identity, libjwt)}> + createSupportCase(user.identity, chromeAuth.token)}> {intl.formatMessage(messages.openSupportCase)} diff --git a/src/components/GlobalFilter/GlobalFilter.tsx b/src/components/GlobalFilter/GlobalFilter.tsx index 928a927bb..b03f4f2d9 100644 --- a/src/components/GlobalFilter/GlobalFilter.tsx +++ b/src/components/GlobalFilter/GlobalFilter.tsx @@ -11,11 +11,15 @@ import { GlobalFilterTag, GlobalFilterWorkloads, ReduxState, SID } from '../../r import { FlagTagsFilter } from '../../@types/types'; import { isGlobalFilterAllowed } from '../../utils/common'; import InternalChromeContext from '../../utils/internalChromeContext'; +import ChromeAuthContext from '../../auth/ChromeAuthContext'; +import { useAtomValue } from 'jotai'; +import { activeModuleAtom } from '../../state/atoms'; const useLoadTags = (hasAccess = false) => { const navigate = useNavigate(); const registeredWith = useSelector(({ globalFilter: { scope } }: ReduxState) => scope); - const isDisabled = useSelector(({ globalFilter: { globalFilterHidden }, chrome: { appId } }: ReduxState) => globalFilterHidden || !appId); + const activeModule = useAtomValue(activeModuleAtom); + const isDisabled = useSelector(({ globalFilter: { globalFilterHidden } }: ReduxState) => globalFilterHidden || !activeModule); const dispatch = useDispatch(); return useCallback( debounce((activeTags: any, search: any) => { @@ -62,7 +66,9 @@ const GlobalFilter = ({ hasAccess }: { hasAccess: boolean }) => { }), shallowEqual ); - const isDisabled = useSelector(({ globalFilter: { globalFilterHidden }, chrome: { appId } }: ReduxState) => globalFilterHidden || !appId); + const globalFilterHidden = useSelector(({ globalFilter: { globalFilterHidden } }: ReduxState) => globalFilterHidden); + const activeModule = useAtomValue(activeModuleAtom); + const isDisabled = globalFilterHidden || !activeModule; const { filter, chips, selectedTags, setValue, filterTagsBy } = ( useTagsFilter as unknown as ( @@ -128,7 +134,7 @@ const GlobalFilter = ({ hasAccess }: { hasAccess: boolean }) => { const GlobalFilterWrapper = () => { const [hasAccess, setHasAccess] = useState(false); const globalFilterRemoved = useSelector(({ globalFilter: { globalFilterRemoved } }: ReduxState) => globalFilterRemoved); - const userLoaded = useSelector(({ chrome: { user } }: ReduxState) => Boolean(user)); + const chromeAuth = useContext(ChromeAuthContext); const { pathname } = useLocation(); const { getUserPermissions } = useContext(InternalChromeContext); @@ -157,7 +163,7 @@ const GlobalFilterWrapper = () => { mounted = false; }; }, []); - return isGlobalFilterEnabled && userLoaded ? : null; + return isGlobalFilterEnabled && chromeAuth.ready ? : null; }; export default GlobalFilterWrapper; diff --git a/src/components/GlobalFilter/GlobalFilterMenu.tsx b/src/components/GlobalFilter/GlobalFilterMenu.tsx index 7d5220b44..1a5080c5b 100644 --- a/src/components/GlobalFilter/GlobalFilterMenu.tsx +++ b/src/components/GlobalFilter/GlobalFilterMenu.tsx @@ -1,4 +1,4 @@ -import React, { FormEvent, Fragment, MouseEventHandler, useMemo } from 'react'; +import React, { FormEvent, Fragment, MouseEventHandler, useContext, useMemo } from 'react'; import { Group, GroupFilter, GroupType } from '@redhat-cloud-services/frontend-components/ConditionalFilter'; import { useIntl } from 'react-intl'; @@ -18,6 +18,7 @@ import { CommonSelectedTag, ReduxState } from '../../redux/store'; import { updateSelected } from './globalFilterApi'; import { fetchAllTags } from '../../redux/actions'; import { FlagTagsFilter } from '../../@types/types'; +import ChromeAuthContext from '../../auth/ChromeAuthContext'; export type GlobalFilterMenuGroupKeys = GroupType; @@ -91,7 +92,7 @@ export const GlobalFilterDropdown: React.FunctionComponent undefined; const registeredWith = useSelector(({ globalFilter: { scope } }: ReduxState) => scope); - const userLoaded = useSelector(({ chrome: { user } }: ReduxState) => Boolean(user)); + const auth = useContext(ChromeAuthContext); const intl = useIntl(); const dispatch = useDispatch(); const GroupFilterWrapper = useMemo( @@ -102,7 +103,7 @@ export const GlobalFilterDropdown: React.FunctionComponent - {userLoaded && allowed !== undefined ? ( + {auth.ready && allowed !== undefined ? ( }) => { +const FeedbackRoute = () => { const paths = localStorage.getItem('chrome:experimental:feedback') === 'true' ? ['*'] @@ -32,14 +30,14 @@ const FeedbackRoute = ({ user }: { user: DeepRequired }) => { return ( {paths.map((path) => ( - } /> + } /> ))} ); }; export const Header = ({ breadcrumbsProps }: { breadcrumbsProps?: Breadcrumbsprops }) => { - const user = useSelector(({ chrome }: DeepRequired) => chrome.user); + const { user } = useContext(ChromeAuthContext) as DeepRequired; const search = new URLSearchParams(window.location.search).keys().next().value; const isActivationPath = activationRequestURLs.includes(search); const isITLessEnv = ITLess(); @@ -66,7 +64,7 @@ export const Header = ({ breadcrumbsProps }: { breadcrumbsProps?: Breadcrumbspro - {user?.identity?.account_number && !isITLessEnv && ReactDOM.createPortal(, document.body)} + {user?.identity?.account_number && !isITLessEnv && ReactDOM.createPortal(, document.body)} {user && isActivationPath && } @@ -106,8 +104,8 @@ export const Header = ({ breadcrumbsProps }: { breadcrumbsProps?: Breadcrumbspro }; export const HeaderTools = () => { - const user = useSelector(({ chrome }: ReduxState) => chrome?.user); - if (!user) { + const { ready } = useContext(ChromeAuthContext); + if (!ready) { return ; } return ; diff --git a/src/components/Header/HeaderTests/ToolbarToggle.test.js b/src/components/Header/HeaderTests/ToolbarToggle.test.js index c2e47d903..72a184441 100644 --- a/src/components/Header/HeaderTests/ToolbarToggle.test.js +++ b/src/components/Header/HeaderTests/ToolbarToggle.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import { act, fireEvent, render } from '@testing-library/react'; +import { act, fireEvent, render, screen } from '@testing-library/react'; import ToolbarToggle from '../ToolbarToggle'; describe('ToolbarToggle', () => { @@ -34,7 +34,7 @@ describe('ToolbarToggle', () => { const toggleButton = container.querySelector('#foo'); expect(toggleButton).toBeTruthy(); await act(async () => { - fireEvent.click(toggleButton); + await fireEvent.click(toggleButton); }); expect(container.querySelector('div')).toMatchSnapshot(); }); @@ -42,27 +42,45 @@ describe('ToolbarToggle', () => { it('should open/close menu correctly', async () => { const { container } = render(); const toggleButton = container.querySelector('#foo'); - expect(toggleButton).toBeTruthy(); + const expectedTexts = toolbarToggleProps.dropdownItems.filter((item) => !item.isHidden); + expect(toggleButton).toBeInTheDocument(); + await act(async () => { + await fireEvent.click(toggleButton); + }); + + // wait for async actions on toggle to complete await act(async () => { - fireEvent.click(toggleButton); + await Promise.resolve(); }); - expect(container.querySelectorAll('.pf-v5-c-menu__list-item')).toHaveLength(2); + + for (const item of expectedTexts) { + expect(screen.getByText(item.title)).toBeInTheDocument(); + } + // closes button + await act(async () => { + await fireEvent.click(toggleButton); + }); + + // wait for async actions on toggle to complete await act(async () => { - fireEvent.click(toggleButton); + await Promise.resolve(); }); - expect(container.querySelectorAll('.pf-v5-c-menu__list-item')).toHaveLength(0); + for (const item of expectedTexts) { + expect(screen.queryByText(item.title)).not.toBeInTheDocument(); + } + // expect(container.querySelectorAll('.pf-v5-c-menu__list-item')).toHaveLength(0); }); it('should call onClick menu item callback', async () => { const { container } = render(); const toggleButton = container.querySelector('#foo'); await act(async () => { - fireEvent.click(toggleButton); + await fireEvent.click(toggleButton); }); const actionButton = container.querySelector('button.pf-v5-c-menu__item'); expect(actionButton).toBeTruthy(); await act(async () => { - fireEvent.click(actionButton); + await fireEvent.click(actionButton); }); expect(clickSpy).toHaveBeenCalled(); }); diff --git a/src/components/Header/HeaderTests/UserToggle.test.js b/src/components/Header/HeaderTests/UserToggle.test.js index 95a2867e4..c6f511200 100644 --- a/src/components/Header/HeaderTests/UserToggle.test.js +++ b/src/components/Header/HeaderTests/UserToggle.test.js @@ -1,4 +1,3 @@ -/* eslint-disable camelcase */ import React from 'react'; import { render, screen } from '@testing-library/react'; import configureStore from 'redux-mock-store'; @@ -6,37 +5,30 @@ import UserToggle from '../UserToggle'; import { Provider } from 'react-redux'; import { MemoryRouter } from 'react-router-dom'; import { act } from 'react-dom/test-utils'; +import ChromeAuthContext from '../../../auth/ChromeAuthContext'; jest.mock('../UserIcon', () => () => ''); describe('UserToggle', () => { - let initialState; - let mockStore; - - beforeEach(() => { - mockStore = configureStore(); - initialState = { - chrome: { + const contextValueMock = { + user: { + identity: { + account_number: 'some accountNumber', user: { - identity: { - account_number: 'some accountNumber', - user: { - username: 'someUsername', - first_name: 'someFirstName', - last_name: 'someLastName', - is_org_admin: false, - }, - }, + username: 'someUsername', + first_name: 'someFirstName', + last_name: 'someLastName', }, }, - }; - }); + }, + }; it('should render correctly with isSmall false', async () => { - const store = mockStore(initialState); const { container } = render( - - + + + + ); @@ -47,36 +39,26 @@ describe('UserToggle', () => { }); it('should render correctly with isSmall true', () => { - const store = mockStore(initialState); const { container } = render( - + - + ); expect(container).toMatchSnapshot(); }); it('should render correctly as org admin', () => { - const store = mockStore({ - ...initialState, - chrome: { - ...initialState.chrome, - user: { - ...initialState.chrome.user, - identity: { - ...initialState.chrome.user.identity, - user: { - ...initialState.chrome.user.identity.user, - is_org_admin: true, - }, - }, - }, - }, - }); const { container } = render( - + - + ); expect(container).toMatchSnapshot(); }); diff --git a/src/components/Header/HeaderTests/__snapshots__/HeaderAlert.test.js.snap b/src/components/Header/HeaderTests/__snapshots__/HeaderAlert.test.js.snap index 2a837f516..9c3b4d27e 100644 --- a/src/components/Header/HeaderTests/__snapshots__/HeaderAlert.test.js.snap +++ b/src/components/Header/HeaderTests/__snapshots__/HeaderAlert.test.js.snap @@ -28,7 +28,7 @@ exports[`HeaderAlert should render correctly dismissable 1`] = ` class="pf-v5-c-alert__title" > Info alert: @@ -92,7 +92,7 @@ exports[`HeaderAlert should render correctly not dismissable 1`] = ` class="pf-v5-c-alert__title" > Info alert: diff --git a/src/components/Header/HeaderTests/__snapshots__/ToolbarToggle.test.js.snap b/src/components/Header/HeaderTests/__snapshots__/ToolbarToggle.test.js.snap index ec837b4cb..66a53eb71 100644 --- a/src/components/Header/HeaderTests/__snapshots__/ToolbarToggle.test.js.snap +++ b/src/components/Header/HeaderTests/__snapshots__/ToolbarToggle.test.js.snap @@ -9,7 +9,7 @@ exports[`ToolbarToggle should render correctly 1`] = ` data-popper-escaped="true" data-popper-placement="bottom-end" data-popper-reference-hidden="true" - style="position: absolute; left: 0px; top: 0px; z-index: 9999; min-width: 0px; transform: translate(0px, 0px);" + style="position: absolute; left: 0px; top: 0px; z-index: 9999; opacity: 1; transition: opacity 0ms cubic-bezier(.54, 1.5, .38, 1.11); min-width: 0px; transform: translate(0px, 0px);" >
{ const intl = useIntl(); + const { login } = useContext(ChromeAuthContext); return (
@@ -60,7 +60,7 @@ exports[`ChromeNavItem should render navigation loader if schema was not loaded class="pf-v5-c-skeleton ins-c-skeleton ins-c-skeleton__lg ins-m-dark" >
@@ -79,7 +79,7 @@ exports[`ChromeNavItem should render navigation loader if schema was not loaded class="pf-v5-c-skeleton ins-c-skeleton ins-c-skeleton__lg ins-m-dark" > @@ -98,7 +98,7 @@ exports[`ChromeNavItem should render navigation loader if schema was not loaded class="pf-v5-c-skeleton ins-c-skeleton ins-c-skeleton__lg ins-m-dark" > @@ -117,7 +117,7 @@ exports[`ChromeNavItem should render navigation loader if schema was not loaded class="pf-v5-c-skeleton ins-c-skeleton ins-c-skeleton__lg ins-m-dark" > diff --git a/src/components/NotificationsDrawer/DrawerPanelContent.tsx b/src/components/NotificationsDrawer/DrawerPanelContent.tsx index 936fddaad..2cc83af28 100644 --- a/src/components/NotificationsDrawer/DrawerPanelContent.tsx +++ b/src/components/NotificationsDrawer/DrawerPanelContent.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { PopoverPosition } from '@patternfly/react-core/dist/dynamic/components/Popover'; import { Icon } from '@patternfly/react-core/dist/dynamic/components/Icon'; import { Badge } from '@patternfly/react-core/dist/dynamic/components/Badge'; @@ -28,6 +28,7 @@ import { NotificationData, ReduxState } from '../../redux/store'; import NotificationItem from './NotificationItem'; import { markAllNotificationsAsRead, markAllNotificationsAsUnread, toggleNotificationsDrawer } from '../../redux/actions'; import { filterConfig } from './notificationDrawerUtils'; +import ChromeAuthContext from '../../auth/ChromeAuthContext'; export type DrawerPanelProps = { innerRef: React.Ref; @@ -63,7 +64,8 @@ const DrawerPanelBase = ({ innerRef }: DrawerPanelProps) => { const navigate = useNavigate(); const dispatch = useDispatch(); const notifications = useSelector(({ chrome: { notifications } }: ReduxState) => notifications?.data || []); - const isOrgAdmin = useSelector(({ chrome }: ReduxState) => chrome.user?.identity.user?.is_org_admin); + const auth = useContext(ChromeAuthContext); + const isOrgAdmin = auth?.user?.identity?.user?.is_org_admin; useEffect(() => { const modifiedNotifications = (activeFilters || []).reduce( diff --git a/src/components/QuickStart/useQuickstartsStates.stage.test.js b/src/components/QuickStart/useQuickstartsStates.stage.test.js index 66bf8db51..8a50f9622 100644 --- a/src/components/QuickStart/useQuickstartsStates.stage.test.js +++ b/src/components/QuickStart/useQuickstartsStates.stage.test.js @@ -5,6 +5,7 @@ import { Provider } from 'react-redux'; import * as axios from 'axios'; import useQuickstartsStates from './useQuickstartsStates'; +import ChromeAuthContext from '../../auth/ChromeAuthContext'; jest.mock('axios', () => { const axios = jest.requireActual('axios'); @@ -26,26 +27,27 @@ jest.mock('../../utils/common', () => { }; }); -describe('useQuickstartsStates stage', () => { - const getSpy = jest.spyOn(axios.default, 'get'); - const postSpy = jest.spyOn(axios.default, 'post'); - const accountStore = createStore(() => ({ - chrome: { - user: { - identity: { - internal: { - account_id: 666, - }, - }, +const mockChromeContextValue = { + user: { + identity: { + internal: { + account_id: 666, }, }, - })); + }, + ready: true, +}; - const emptyStore = createStore(() => ({ - chrome: { - user: undefined, - }, - })); +const emptyStore = createStore(() => ({})); +const WrapperComponent = ({ children, store = emptyStore, contextValue = mockChromeContextValue }) => ( + + {children} + +); + +describe('useQuickstartsStates stage', () => { + const getSpy = jest.spyOn(axios.default, 'get'); + const postSpy = jest.spyOn(axios.default, 'post'); afterEach(() => { getSpy.mockReset(); @@ -53,7 +55,7 @@ describe('useQuickstartsStates stage', () => { }); test('should not call API if no account Id exists', () => { - const wrapper = ({ children }) => {children}; + const wrapper = ({ children }) => {children}; const { result } = renderHook(() => useQuickstartsStates(), { wrapper }); @@ -64,7 +66,7 @@ describe('useQuickstartsStates stage', () => { test('should call quickstarts progress API if account id exists', async () => { getSpy.mockImplementationOnce(() => Promise.resolve({ data: { data: [] } })); - const wrapper = ({ children }) => {children}; + const wrapper = ({ children }) => {children}; let result; await act(async () => { const { result: resultInternal } = renderHook(() => useQuickstartsStates(), { wrapper }); @@ -91,7 +93,7 @@ describe('useQuickstartsStates stage', () => { }) ); - const wrapper = ({ children }) => {children}; + const wrapper = ({ children }) => {children}; let result; await act(async () => { const { result: resultInternal } = renderHook(() => useQuickstartsStates(), { wrapper }); @@ -108,7 +110,7 @@ describe('useQuickstartsStates stage', () => { }); test('should set active quickstart id', () => { - const wrapper = ({ children }) => {children}; + const wrapper = ({ children }) => {children}; const { result } = renderHook(() => useQuickstartsStates(), { wrapper }); @@ -119,7 +121,7 @@ describe('useQuickstartsStates stage', () => { }); test('should set quickstarts states from object', () => { - const wrapper = ({ children }) => {children}; + const wrapper = ({ children }) => {children}; const { result } = renderHook(() => useQuickstartsStates(), { wrapper }); @@ -130,7 +132,7 @@ describe('useQuickstartsStates stage', () => { }); test('should set quickstarts states from function', async () => { - const wrapper = ({ children }) => {children}; + const wrapper = ({ children }) => {children}; const { result } = renderHook(() => useQuickstartsStates(), { wrapper }); @@ -147,7 +149,22 @@ describe('useQuickstartsStates stage', () => { progress: 'updated-state', }) ); - const wrapper = ({ children }) => {children}; + const wrapper = ({ children }) => ( + + {children} + + ); const { result } = renderHook(() => useQuickstartsStates(), { wrapper }); diff --git a/src/components/QuickStart/useQuickstartsStates.ts b/src/components/QuickStart/useQuickstartsStates.ts index f52147de3..3377bacba 100644 --- a/src/components/QuickStart/useQuickstartsStates.ts +++ b/src/components/QuickStart/useQuickstartsStates.ts @@ -1,13 +1,14 @@ import axios from 'axios'; -import { useEffect, useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import { useContext, useEffect, useState } from 'react'; +import { useDispatch } from 'react-redux'; import { QuickStart, QuickStartState } from '@patternfly/quickstarts'; -import { ReduxState } from '../../redux/store'; import { populateQuickstartsCatalog } from '../../redux/actions'; +import ChromeAuthContext from '../../auth/ChromeAuthContext'; const useQuickstartsStates = () => { const dispatch = useDispatch(); - const accountId = useSelector(({ chrome }: ReduxState) => chrome?.user?.identity?.internal?.account_id); + const auth = useContext(ChromeAuthContext); + const accountId = auth.user.identity?.internal?.account_id; const [allQuickStartStates, setAllQuickStartStatesInternal] = useState<{ [key: string | number]: QuickStartState }>({}); const [activeQuickStartID, setActiveQuickStartIDInternal] = useState(''); diff --git a/src/components/RootApp/RootApp.tsx b/src/components/RootApp/RootApp.tsx index bd83445b4..07a341960 100644 --- a/src/components/RootApp/RootApp.tsx +++ b/src/components/RootApp/RootApp.tsx @@ -1,6 +1,7 @@ -import React, { Suspense, lazy, memo, useEffect } from 'react'; +import React, { Suspense, lazy, memo, useContext, useEffect } from 'react'; import { unstable_HistoryRouter as HistoryRouter, HistoryRouterProps } from 'react-router-dom'; import { HelpTopicContainer, QuickStart, QuickStartContainer, QuickStartContainerProps } from '@patternfly/quickstarts'; +import { useAtomValue } from 'jotai'; import chromeHistory from '../../utils/chromeHistory'; import { FeatureFlagsProvider } from '../FeatureFlags'; import ScalprumRoot from './ScalprumRoot'; @@ -12,25 +13,25 @@ import useHelpTopicState from '../QuickStart/useHelpTopicState'; import validateQuickstart from '../QuickStart/quickstartValidation'; import SegmentProvider from '../../analytics/SegmentProvider'; import { ReduxState } from '../../redux/store'; -import { AppsConfig } from '@scalprum/core'; import { ITLess, chunkLoadErrorRefreshKey, getRouterBasename } from '../../utils/common'; import useUserSSOScopes from '../../hooks/useUserSSOScopes'; import { DeepRequired } from 'utility-types'; import ReactDOM from 'react-dom'; import { FooterProps } from '../Footer/Footer'; +import ChromeAuthContext, { ChromeAuthContextValue } from '../../auth/ChromeAuthContext'; +import { activeModuleAtom } from '../../state/atoms'; const NotEntitledModal = lazy(() => import('../NotEntitledModal')); const Debugger = lazy(() => import('../Debugger')); -export type RootAppProps = FooterProps & { - config: AppsConfig; -}; +export type RootAppProps = FooterProps; const RootApp = memo((props: RootAppProps) => { + const config = useSelector(({ chrome }: DeepRequired) => chrome.scalprumConfig); const { activateQuickstart, allQuickStartStates, setAllQuickStartStates, activeQuickStartID, setActiveQuickStartID } = useQuickstartsStates(); const { helpTopics, addHelpTopics, disableTopics, enableTopics } = useHelpTopicState(); const dispatch = useDispatch(); - const activeModule = useSelector(({ chrome: { activeModule } }: ReduxState) => activeModule); + const activeModule = useAtomValue(activeModuleAtom); const quickStarts = useSelector( ({ chrome: { @@ -38,7 +39,7 @@ const RootApp = memo((props: RootAppProps) => { }, }: ReduxState) => Object.values(quickstarts).flat() ); - const user = useSelector(({ chrome }: DeepRequired) => chrome.user); + const { user } = useContext(ChromeAuthContext) as DeepRequired; const isDebuggerEnabled = useSelector(({ chrome: { isDebuggerEnabled } }) => isDebuggerEnabled); // verify use loged in scopes @@ -121,7 +122,7 @@ const RootApp = memo((props: RootAppProps) => { - + diff --git a/src/components/RootApp/ScalprumRoot.test.js b/src/components/RootApp/ScalprumRoot.test.js index 02a82e08a..3bc2588a0 100644 --- a/src/components/RootApp/ScalprumRoot.test.js +++ b/src/components/RootApp/ScalprumRoot.test.js @@ -45,13 +45,54 @@ window.ResizeObserver = })); import * as routerDom from 'react-router-dom'; -import LibtJWTContext from '../LibJWTContext'; import { initializeVisibilityFunctions } from '../../utils/VisibilitySingleton'; +import ChromeAuthContext from '../../auth/ChromeAuthContext'; describe('ScalprumRoot', () => { let initialState; let mockStore; let config; + const chromeContextMockValue = { + getToken() { + return Promise.resolve('a.a'); + }, + ready: true, + user: { + identity: { + account_number: '0', + type: 'User', + org_id: '123', + user: { + username: 'foo', + first_name: 'foo', + last_name: 'foo', + is_org_admin: false, + is_internal: false, + }, + }, + }, + getUser() { + return Promise.resolve({ + identity: { + account_number: '0', + type: 'User', + org_id: '123', + user: { + username: 'foo', + first_name: 'foo', + last_name: 'foo', + is_org_admin: false, + is_internal: false, + }, + }, + entitlements: { + insights: { + is_entitled: true, + }, + }, + }); + }, + }; const initialProps = { cookieElement: null, setCookieElement: () => undefined, @@ -134,21 +175,13 @@ describe('ScalprumRoot', () => { let getByLabelText; await act(async () => { const { getByLabelText: internalGetByLabelText } = await render( - Promise.resolve({}), - getEncodedToken: () => '', - }, - }} - > - + + - - + + ); getByLabelText = internalGetByLabelText; }); @@ -156,6 +189,7 @@ describe('ScalprumRoot', () => { }); it('should render GlobalFilter', async () => { + const fetchSpy = jest.spyOn(window, 'fetch').mockImplementationOnce(() => Promise.resolve({ ok: true, json: () => ({}) })); const useLocationSpy = jest.spyOn(routerDom, 'useLocation'); useLocationSpy.mockReturnValue({ pathname: '/insights', search: undefined, hash: undefined }); Object.defineProperty(window, 'location', { @@ -181,24 +215,17 @@ describe('ScalprumRoot', () => { }); const { container } = render( - Promise.resolve({}), - getEncodedToken: () => '', - }, - }} - > - + + - - + + ); await waitFor(() => expect(container.querySelector('#global-filter')).toBeTruthy()); useLocationSpy.mockRestore(); + fetchSpy.mockRestore(); }); }); diff --git a/src/components/RootApp/ScalprumRoot.tsx b/src/components/RootApp/ScalprumRoot.tsx index 931ba5789..4a1a5a2c9 100644 --- a/src/components/RootApp/ScalprumRoot.tsx +++ b/src/components/RootApp/ScalprumRoot.tsx @@ -19,8 +19,7 @@ import SegmentContext from '../../analytics/SegmentContext'; import LoadingFallback from '../../utils/loading-fallback'; import { ReduxState } from '../../redux/store'; import { FlagTagsFilter, HelpTopicsAPI, QuickstartsApi } from '../../@types/types'; -import { createGetUser } from '../../auth'; -import LibtJWTContext from '../LibJWTContext'; +// import { createGetUser } from '../../auth'; import { createChromeContext } from '../../chrome/create-chrome'; import Navigation from '../Navigation'; import useHelpTopicManager from '../QuickStart/useHelpTopicManager'; @@ -34,6 +33,7 @@ import useChromeServiceEvents from '../../hooks/useChromeServiceEvents'; import { populateNotifications } from '../../redux/actions'; import useTrackPendoUsage from '../../hooks/useTrackPendoUsage'; import useDisablePendoOnLanding from '../../hooks/useDisablePendoOnLanding'; +import ChromeAuthContext from '../../auth/ChromeAuthContext'; const ProductSelection = lazy(() => import('../Stratosphere/ProductSelection')); @@ -54,10 +54,10 @@ const ScalprumRoot = memo( const dispatch = useDispatch(); const internalFilteredTopics = useRef([]); const { analytics } = useContext(SegmentContext); + const chromeAuth = useContext(ChromeAuthContext); - const libJwt = useContext(LibtJWTContext); const store = useStore(); - const modulesConfig = useSelector(({ chrome: { modules } }: ReduxState) => modules); + const mutableChromeApi = useRef(); // initialize WS event handling useChromeServiceEvents(); @@ -127,7 +127,6 @@ const ScalprumRoot = memo( }; }, []); - const getUser = useCallback(createGetUser(libJwt), [libJwt]); const helpTopicsChromeApi = useMemo( () => ({ ...helpTopicsAPI, @@ -140,30 +139,31 @@ const ScalprumRoot = memo( }), [] ); - const chromeApi = useMemo( - () => - createChromeContext({ - analytics: analytics!, - getUser, - helpTopics: helpTopicsChromeApi, - libJwt, - modulesConfig, - quickstartsAPI, - useGlobalFilter, - store, - setPageMetadata, - }), - [] - ); + + useMemo(() => { + mutableChromeApi.current = createChromeContext({ + analytics: analytics!, + helpTopics: helpTopicsChromeApi, + quickstartsAPI, + useGlobalFilter, + store, + setPageMetadata, + chromeAuth, + }); + // reset chrome object after token (user) updates/changes + }, [chromeAuth.token]); const scalprumProviderProps: ScalprumProviderProps<{ chrome: ChromeAPI }> = useMemo(() => { + if (!mutableChromeApi.current) { + throw new Error('Chrome API failed to initialize.'); + } // set the deprecated chrome API to window // eslint-disable-next-line rulesdir/no-chrome-api-call-from-window - window.insights.chrome = chromeApiWrapper(chromeApi); + window.insights.chrome = chromeApiWrapper(mutableChromeApi.current); return { config, api: { - chrome: chromeApi, + chrome: mutableChromeApi.current, }, pluginSDKOptions: { pluginLoaderOptions: { @@ -191,7 +191,11 @@ const ScalprumRoot = memo( }, }, }; - }, []); + }, [chromeAuth.token]); + + if (!mutableChromeApi.current) { + return null; + } return ( /** @@ -200,7 +204,7 @@ const ScalprumRoot = memo( * - copy these functions to window * - add deprecation warning to the window functions */ - + diff --git a/src/components/Routes/Routes.tsx b/src/components/Routes/Routes.tsx index d8649b3ad..d7d4d5114 100644 --- a/src/components/Routes/Routes.tsx +++ b/src/components/Routes/Routes.tsx @@ -18,7 +18,7 @@ const redirects = [ }, { path: '/docs', - to: '/api/docs', + to: '/docs/api', }, { path: '/settings', diff --git a/src/hooks/useAllServices.ts b/src/hooks/useAllServices.ts index f75f82841..ad27653bb 100644 --- a/src/hooks/useAllServices.ts +++ b/src/hooks/useAllServices.ts @@ -1,4 +1,4 @@ -import axios from 'axios'; +import axios, { AxiosResponse } from 'axios'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { BundleNav, BundleNavigation, NavItem } from '../@types/types'; import { @@ -16,6 +16,16 @@ export type AvailableLinks = { [key: string]: NavItem; }; +const allServicesFetchCache: { + [qeury: string]: Promise< + AxiosResponse< + (Omit & { + links: (string | AllServicesLink | AllServicesGroup)[]; + })[] + > + >; +} = {}; + const getFirstChildRoute = (routes: NavItem[] = []): NavItem | undefined => { const firstLeaf = routes.find((item) => !item.expandable && item.href); if (firstLeaf) { @@ -197,17 +207,23 @@ const useAllServices = () => { ), [] ); - const fetchSections = useCallback( - async () => - ( - await axios.get< - (Omit & { - links: (string | AllServicesLink | AllServicesGroup)[]; - })[] - >(`${getChromeStaticPathname('services')}/services.json`) - ).data, - [] - ); + const fetchSections = useCallback(async () => { + const query = `${getChromeStaticPathname('services')}/services.json`; + let request = allServicesFetchCache[query]; + if (!request) { + request = axios.get< + (Omit & { + links: (string | AllServicesLink | AllServicesGroup)[]; + })[] + >(query); + allServicesFetchCache[query] = request; + } + + const response = await request; + // clear the cache + delete allServicesFetchCache[query]; + return response.data; + }, []); const setNavigation = useCallback(async () => { const bundleItems = await fetchNavigation(); const sections = await fetchSections(); diff --git a/src/hooks/useBundleVisitDetection.ts b/src/hooks/useBundleVisitDetection.ts index eca75f3bb..2d72c98d6 100644 --- a/src/hooks/useBundleVisitDetection.ts +++ b/src/hooks/useBundleVisitDetection.ts @@ -1,10 +1,9 @@ -import { useEffect, useMemo } from 'react'; +import { useContext, useEffect, useMemo } from 'react'; import { useLocation } from 'react-router-dom'; import { VisitedBundles, useVisitedBundles } from '@redhat-cloud-services/chrome'; import axios from 'axios'; -import { useSelector } from 'react-redux'; -import { ReduxState } from '../redux/store'; import { getUrl } from './useBundle'; +import ChromeAuthContext from '../auth/ChromeAuthContext'; // TMP Insights specific trigger const shouldSendVisit = (bundle: string, visits: VisitedBundles) => bundle === 'insights' && !visits[bundle]; @@ -18,7 +17,8 @@ const sendVisitedBundle = async (orgId: string) => { const useBundleVisitDetection = () => { const { pathname } = useLocation(); - const orgId = useSelector(({ chrome: { user } }: ReduxState) => user?.identity?.org_id); + const auth = useContext(ChromeAuthContext); + const orgId = auth.user?.identity?.org_id; const { markVisited, visitedBundles, initialized } = useVisitedBundles(); const bundle = useMemo(() => getUrl('bundle'), [pathname]); useEffect(() => { diff --git a/src/hooks/useChromeServiceEvents.ts b/src/hooks/useChromeServiceEvents.ts index 2833acaaf..88e0fd6a8 100644 --- a/src/hooks/useChromeServiceEvents.ts +++ b/src/hooks/useChromeServiceEvents.ts @@ -1,10 +1,10 @@ -import { useEffect, useMemo, useRef } from 'react'; +import { useContext, useEffect, useMemo, useRef } from 'react'; import { useDispatch } from 'react-redux'; import { useFlag } from '@unleash/proxy-client-react'; import { UPDATE_NOTIFICATIONS } from '../redux/action-types'; - -import { getEncodedToken, setCookie } from '../jwt/jwt'; import { NotificationsPayload } from '../redux/store'; +import { setCookie } from '../auth/setCookie'; +import ChromeAuthContext from '../auth/ChromeAuthContext'; const NOTIFICATION_DRAWER = 'com.redhat.console.notifications.drawer'; const SAMPLE_EVENT = 'sample.type'; @@ -30,6 +30,7 @@ const useChromeServiceEvents = () => { const connection = useRef(); const dispatch = useDispatch(); const isNotificationsEnabled = useFlag('platform.chrome.notifications-drawer'); + const { token, tokenExpires } = useContext(ChromeAuthContext); const handlerMap: { [key in EventTypes]: (payload: GenericEvent) => void } = useMemo( () => ({ @@ -46,11 +47,10 @@ const useChromeServiceEvents = () => { } const createConnection = async () => { - const token = getEncodedToken(); if (token) { const socketUrl = `${document.location.origin.replace(/^.+:\/\//, 'wss://')}/wss/chrome-service/v1/ws`; // ensure the cookie exists before we try to establish connection - await setCookie(token); + await setCookie(token, tokenExpires); // create WS URL from current origin // ensure to use the cloud events sub protocol diff --git a/src/hooks/useDisablePendoOnLanding.ts b/src/hooks/useDisablePendoOnLanding.ts index d47a00e7b..ae7cd3b06 100644 --- a/src/hooks/useDisablePendoOnLanding.ts +++ b/src/hooks/useDisablePendoOnLanding.ts @@ -1,14 +1,13 @@ import { useEffect } from 'react'; -import { useSelector } from 'react-redux'; -import { ReduxState } from '../redux/store'; -import { isProd } from '../utils/common'; import { isITLessEnv } from '../utils/consts'; +import { useAtomValue } from 'jotai'; +import { activeModuleAtom } from '../state/atoms'; // interval timing is short because we want to catch the bubble before ASAP so it does not cover the VA button -const RETRY_ATTEMPS = 500; +const RETRY_ATTEMPS = 2000; const RETRY_INTERVAL = 50; -function retry(fn: () => void, retriesLeft = 10, interval = 100) { +function retry(fn: () => void, retriesLeft = 50, interval = 100) { try { return fn(); } catch (error) { @@ -24,7 +23,7 @@ function retry(fn: () => void, retriesLeft = 10, interval = 100) { } const useDisablePendoOnLanding = () => { - const activeModule = useSelector((state: ReduxState) => state.chrome.activeModule); + const activeModule = useAtomValue(activeModuleAtom); const toggleGuides = () => { // push the call to the end of the event loop to make sure the pendo script is loaded and initialized @@ -64,7 +63,7 @@ const useDisablePendoOnLanding = () => { } return () => { - if (interval && !isProd() && !isITLessEnv) { + if (interval && !isITLessEnv) { clearInterval(interval); } }; diff --git a/src/hooks/useTrackPendoUsage.ts b/src/hooks/useTrackPendoUsage.ts index 1c0c2bea2..4f55be074 100644 --- a/src/hooks/useTrackPendoUsage.ts +++ b/src/hooks/useTrackPendoUsage.ts @@ -1,7 +1,7 @@ -import { useSelector } from 'react-redux'; -import { ReduxState } from '../redux/store'; +import { useAtomValue } from 'jotai'; import { useCallback, useEffect, useRef } from 'react'; import { useSegment } from '../analytics/useSegment'; +import { activeModuleAtom } from '../state/atoms'; const badgeQuery = 'div[id^="_pendo-badge_"]'; const RETRY_ATTEMPS = 10; @@ -9,7 +9,7 @@ const RETRY_INTERVAL = 2000; const SEGMENT_EVENT_NAME = 'pendo-badge-clicked'; const useTrackPendoUsage = () => { - const activeModule = useSelector((state) => state.chrome.activeModule); + const activeModule = useAtomValue(activeModuleAtom); const mutableData = useRef({ activeModule }); const { analytics } = useSegment(); const setupEventTracking = useCallback(() => { diff --git a/src/hooks/useUserSSOScopes.ts b/src/hooks/useUserSSOScopes.ts index 1d65da60a..6c84f2912 100644 --- a/src/hooks/useUserSSOScopes.ts +++ b/src/hooks/useUserSSOScopes.ts @@ -1,13 +1,16 @@ -import { useEffect } from 'react'; +import { useContext, useEffect } from 'react'; import { useSelector } from 'react-redux'; -import { login } from '../jwt/jwt'; import { ReduxState } from '../redux/store'; import { LOGIN_SCOPES_STORAGE_KEY } from '../utils/common'; +import ChromeAuthContext from '../auth/ChromeAuthContext'; +import { useAtomValue } from 'jotai'; +import { activeModuleAtom } from '../state/atoms'; /** * If required, attempt to reauthenticate current user with full profile login. */ const useUserSSOScopes = () => { + const { login } = useContext(ChromeAuthContext); const getCurrentScopes = (): string[] => { try { return JSON.parse(localStorage.getItem(LOGIN_SCOPES_STORAGE_KEY) || '[]'); @@ -16,23 +19,29 @@ const useUserSSOScopes = () => { return []; } }; + const activeModuleId = useAtomValue(activeModuleAtom); // get scope module definition - const activeModule = useSelector(({ chrome: { activeModule, modules } }: ReduxState) => (activeModule ? (modules || {})[activeModule] : undefined)); + const activeModule = useSelector(({ chrome: { modules } }: ReduxState) => (activeModuleId ? (modules || {})[activeModuleId] : undefined)); const requiredScopes = activeModule?.config?.ssoScopes || []; useEffect(() => { const currentScopes = getCurrentScopes(); const requiredScopes = activeModule?.config?.ssoScopes || []; const missingScope = requiredScopes.some((scope) => !currentScopes.includes(scope)); + const shouldReAuth = // normal scenario for account that was not authenticated with required scopes missingScope || // scenario accounts that were redirected from sso and might not have completed required steps (like completing full profile registration) (requiredScopes.length > 0 && !missingScope && document.referrer.match(/sso\.[a-z]+\.redhat\.com/)); + /** + * FIXME: RHFULL scope (and all legacy scopes??) are not showing up in the token response, so we don't know if the scope was authenticated + * Work with #forum-ciam and the `@ciam-s-client-integration-sre` to fix that + * */ // if current login scope is not full profile and scope requires it, trigger full profile login` if (shouldReAuth) { - login(requiredScopes); + login(Array.from(new Set([...requiredScopes, ...currentScopes]))); } }, [requiredScopes, activeModule?.fullProfile]); }; diff --git a/src/jwt/Priv.ts b/src/jwt/Priv.ts deleted file mode 100644 index f82199a78..000000000 --- a/src/jwt/Priv.ts +++ /dev/null @@ -1,121 +0,0 @@ -import Keycloak, { KeycloakConfig, KeycloakInitOptions, KeycloakLoginOptions, KeycloakLogoutOptions } from 'keycloak-js'; -import { ITLessCognito } from '../utils/common'; - -const isITLessCognito = ITLessCognito(); - -export type PrivCookie = { - cookieName: string; -}; - -export type SSOParsedToken = Keycloak['tokenParsed'] & { - account_number: string; - type: string; - idp: string; - username: string; - email: string; - first_name: string; - last_name: string; - is_active: boolean; - is_org_admin: boolean; - is_internal: boolean; - locale: string; - org_id: string; - account_id: string; - jti: string; -}; - -class Priv { - _cookie?: string; - _keycloak: Keycloak; - cookie?: PrivCookie; - logoutDelay: number; - logoutTimeout?: NodeJS.Timeout; - - constructor() { - this._cookie; - this._keycloak = {} as Keycloak; - this.logoutDelay = 3 * 60 * 60 * 1000; // 3 hours in MS - } - - setCookie(cookie: PrivCookie) { - this.cookie = cookie; - } - - setKeycloak( - options?: string | KeycloakConfig, - onTokenExpired?: Keycloak['onTokenExpired'], - onAuthSuccess?: Keycloak['onAuthSuccess'], - onAuthRefreshSuccess?: Keycloak['onAuthRefreshSuccess'] - ) { - this._keycloak = new Keycloak(options); - this._keycloak.onTokenExpired = onTokenExpired; - this._keycloak.onAuthSuccess = onAuthSuccess; - this._keycloak.onAuthRefreshSuccess = onAuthRefreshSuccess; - } - - initializeKeycloak(options: KeycloakInitOptions) { - this._keycloak?.init(options) as unknown as Promise; - } - - setToken(token: string) { - this._keycloak.authenticated = true; - this._keycloak.token = token; - } - - initialize(options: KeycloakInitOptions) { - return this._keycloak.init(options); - } - - setTokenParsed(tokenParsed: Keycloak['tokenParsed']) { - this._keycloak.tokenParsed = tokenParsed; - } - - getTokenParsed() { - return this._keycloak.tokenParsed as SSOParsedToken; - } - - getToken() { - return this._keycloak.token; - } - - getRefershToken() { - return this._keycloak.refreshToken; - } - - login(options: KeycloakLoginOptions) { - return this._keycloak.login(options).then((resp) => { - if (this.logoutTimeout) { - clearTimeout(this.logoutTimeout); - } - this.logoutTimeout = setTimeout(() => { - this.logout(); - }, this.logoutDelay); - return resp; - }); - } - - clearToken() { - this._keycloak.clearToken(); - } - - getCookie() { - return this.cookie; - } - - logout(options: KeycloakLogoutOptions = {}) { - return this._keycloak.logout(options); - } - - getAuthenticated() { - return this._keycloak.authenticated; - } - - updateToken() { - // 5 is default KC value, min validaty is required by KC byt then has a default value for some reason - if (!isITLessCognito) { - return this._keycloak.updateToken(5); - } - } -} - -export default Priv; diff --git a/src/jwt/__mocks__/entitlements.ts b/src/jwt/__mocks__/entitlements.ts deleted file mode 100644 index 20be1ca18..000000000 --- a/src/jwt/__mocks__/entitlements.ts +++ /dev/null @@ -1,13 +0,0 @@ -export const priv = {}; - -const base = jest.fn(); -const servicesGet = jest.fn(); - -base.mockReturnValue({ servicesGet }); -servicesGet.mockReturnValue({ - then: (fn: (...args: unknown[]) => unknown) => { - return fn({ foo: 'bar' }); - }, -}); - -export default base; diff --git a/src/jwt/__mocks__/keycloak-js.ts b/src/jwt/__mocks__/keycloak-js.ts deleted file mode 100644 index dc170dd4d..000000000 --- a/src/jwt/__mocks__/keycloak-js.ts +++ /dev/null @@ -1,64 +0,0 @@ -import cookie from 'js-cookie'; -import { data as encodedToken } from '../../../testdata/encodedToken.json'; -import { ITLessKeycloak } from '../../utils/common'; - -class Keycloak { - scope: any; - token: any; - tokenParsed: any; - refreshToken: any; - redirectUri: any; - callback_id: any; - authenticated: any; - useNativePromise: any; - responseMode: any; - responseType: any; - flow: any; - clientId: any; - authServerUrl: any; - realm: any; - endpoints: any; - - constructor(options: any) { - this.scope = options.scope || 'online'; - this.token = encodedToken; - this.tokenParsed = options.tokenParsed; - this.refreshToken = encodedToken; - this.redirectUri = options.redirectUri; - - this.callback_id = 0; - this.authenticated = false; - this.useNativePromise = true; - this.responseMode = 'fragment'; - this.responseType = 'code'; - this.flow = 'standard'; - this.clientId = ITLessKeycloak() ? 'console-dot' : 'cloud-services'; - this.authServerUrl = 'https://sso.qa.redhat.com/auth'; - this.realm = 'redhat-external'; - this.endpoints = {}; - } - - init = (options: any) => { - return Promise.resolve(options); - }; - login = (data: any) => { - this.redirectUri = data.redirectUri; - cookie.set('cs_jwt', 'token1'); - return Promise.resolve({}); - }; - updateToken = () => { - return new Promise((res) => { - cookie.remove('cs_jwt'); - cookie.set('cs_jwt', 'updatedToken'); - return res(true); - }); - }; - clearToken = () => { - cookie.remove('cs_jwt'); - }; - logout = () => { - cookie.remove('cs_jwt'); - }; -} - -export default Keycloak; diff --git a/src/jwt/__mocks__/urijs.ts b/src/jwt/__mocks__/urijs.ts deleted file mode 100644 index d9319cb67..000000000 --- a/src/jwt/__mocks__/urijs.ts +++ /dev/null @@ -1,20 +0,0 @@ -const urijs = (inputUrl: string) => { - const url = inputUrl; - const searchMap: { [key: string]: any } = { - foo: 'bar', - }; - return { - removeSearch: (key: string) => { - delete searchMap[key]; - }, - addSearch: (key: string, val: any) => { - searchMap[key] = val; - }, - toString: () => { - return url; - }, - }; -}; -/* eslint-enable camelcase */ - -export default urijs; diff --git a/src/jwt/docs.MD b/src/jwt/docs.MD deleted file mode 100644 index 68f2e8842..000000000 --- a/src/jwt/docs.MD +++ /dev/null @@ -1,32 +0,0 @@ -# Insights JWT docs - -options to pass to jwt/keycloak - -* realm: Managed set of user, credentials, roles, and groups. Users belong and log into Realms. -* clientId: client associated with token -* routes: environments, urls, and the sso associated with each environment - -Example object - -``` js -const options = { - realm: 'redhat-external', - clientId: 'cloud-services', - cookieName: 'cookie', - routes: { - prod: { - url: ['access.redhat.com', 'prod.foo.redhat.com'], - sso: 'https://sso.redhat.com/auth' - }, - qa: { - url: ['access.qa.redhat.com', 'access.qa.itop.redhat.com', 'qa.foo.redhat.com'], - sso: 'https://sso.qa.redhat.com/auth' - }, - ci: { - url: ['access.ci.itop.redhat.com'], - sso: 'https://sso.qa.redhat.com/auth' - } - // any unspecified route will auth through sso.qa.redhat.com/auth - } -}; -``` \ No newline at end of file diff --git a/src/jwt/initialize-jwt.ts b/src/jwt/initialize-jwt.ts deleted file mode 100644 index 885c28573..000000000 --- a/src/jwt/initialize-jwt.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ChromeUser } from '@redhat-cloud-services/types'; -import { LibJWT } from '../auth'; -import { spinUpStore } from '../redux/redux-config'; -import { ITLessCognito } from '../utils/common'; -import { createUser, getTokenWithAuthorizationCode } from '../cognito/auth'; - -const initializeJWT = async (libjwt: LibJWT) => { - const { actions } = spinUpStore(); - if (ITLessCognito()) { - try { - await getTokenWithAuthorizationCode(); - const user = await createUser(); - if (user) { - actions.userLogIn(user as ChromeUser); - } - } catch (error) { - console.error(error); - actions.userLogIn(false); - } - } else { - try { - await libjwt.initPromise; - const user = await libjwt.jwt.getUserInfo(); - if (user) { - actions.userLogIn(user as ChromeUser); - } - const encodedToken = libjwt.jwt.getEncodedToken(); - if (encodedToken) { - // chromeInstance.cache = new CacheAdapter('chrome-store', `${decodeToken(encodedToken).session_state}-chrome-store`); - } - } catch (error) { - console.error(error); - actions.userLogIn(false); - } - } -}; - -export default initializeJWT; diff --git a/src/jwt/jwt.ts b/src/jwt/jwt.ts deleted file mode 100644 index 4417b660f..000000000 --- a/src/jwt/jwt.ts +++ /dev/null @@ -1,495 +0,0 @@ -// Imports -import Keycloak, { KeycloakConfig, KeycloakInitOptions } from 'keycloak-js'; -import { BroadcastChannel } from 'broadcast-channel'; -import cookie from 'js-cookie'; -import { - DEFAULT_SSO_ROUTES, - ITLess, - ITLessCognito, - ITLessKeycloak, - LOGIN_SCOPES_STORAGE_KEY, - deleteLocalStorageItems, - getRouterBasename, - isBeta as isBetaFunction, - pageRequiresAuthentication, -} from '../utils/common'; -import * as Sentry from '@sentry/react'; -import logger from './logger'; -import { CogUser, getTokenWithAuthorizationCode, getUser } from '../cognito/auth'; - -// Insights Specific -import platformUrl from './url'; -import platformUser from './user'; -import urijs from 'urijs'; -import { GLOBAL_FILTER_KEY, OFFLINE_REDIRECT_STORAGE_KEY, defaultAuthOptions as defaultOptions } from '../utils/consts'; -import Priv from './Priv'; -import { ChromeUser } from '@redhat-cloud-services/types'; - -const log = logger('jwt.js'); -const DEFAULT_COOKIE_NAME = 'cs_jwt'; - -const priv = new Priv(); -const itLessCognito = ITLessCognito(); -const itLessKeycloakEnv = ITLessKeycloak(); - -enum AllowedPartnerScopes { - aws = 'aws', - azure = 'azure', - gcp = 'gcp', -} - -function isPartnerScope(scope: string): scope is AllowedPartnerScopes { - return Object.values(AllowedPartnerScopes).includes(scope as AllowedPartnerScopes); -} - -// Broadcast Channel -const authChannel = new BroadcastChannel('auth'); -authChannel.onmessage = (e) => { - if (e && e.data && e.data.type) { - log(`BroadcastChannel, Received event : ${e.data.type}`); - - switch (e.data.type) { - case 'logout': - return logout(); - case 'login': - return login(); - case 'refresh': - return updateToken(); - } - } -}; - -export type DecodedToken = { - exp: number; - session_state?: string; -}; - -function getPartnerScope(pathname: string) { - // replace beta and leading "/" - const sanitizedPathname = pathname.replace(/^(\/beta\/|\/preview\/)/, '/').replace(/^\//, ''); - // check if the pathname is connect/:partner - if (sanitizedPathname.match(/^connect\/.+/)) { - // return :partner param - const fragmentScope = sanitizedPathname.split('/')[1]; - if (isPartnerScope(fragmentScope)) { - return `api.partner_link.${fragmentScope}`; - } - log(`Invalid stratosphere scope: ${fragmentScope}`); - return undefined; - } - - return undefined; -} - -export function decodeToken(str: string): DecodedToken { - str = str.split('.')[1]; - str = str.replace('/-/g', '+'); - str = str.replace('/_/g', '/'); - switch (str.length % 4) { - case 0: - break; - case 2: - str += '=='; - break; - case 3: - str += '='; - break; - default: - throw 'Invalid token'; - } - - str = (str + '===').slice(0, str.length + (str.length % 4)); - str = str.replace(/-/g, '+').replace(/_/g, '/'); - str = decodeURIComponent(escape(atob(str))); - const res = JSON.parse(str); - - return res; -} - -export const doOffline = (key: string, val: string, configSsoUrl?: string) => { - // clear previous postback - localStorage.removeItem(OFFLINE_REDIRECT_STORAGE_KEY); - const url = urijs(window.location.href); - url.removeSearch(key); - url.addSearch(key, val); - const redirectUri = url.toString(); - - if (redirectUri) { - // set new postback - localStorage.setItem(OFFLINE_REDIRECT_STORAGE_KEY, redirectUri); - } - - Promise.resolve(platformUrl(DEFAULT_SSO_ROUTES, configSsoUrl)).then(async (ssoUrl) => { - const options: KeycloakInitOptions & KeycloakConfig & { promiseType: string; redirectUri: string; url: string } = { - ...defaultOptions, - promiseType: 'native', - redirectUri, - url: ssoUrl, - }; - - const kc = new Keycloak(options); - - await kc.init(options); - const partnerScope = getPartnerScope(window.location.pathname); - const ssoScopes = localStorage.getItem(LOGIN_SCOPES_STORAGE_KEY); - const scopes = ['offline_access']; - if (partnerScope) { - scopes.push(partnerScope); - } - - if (ssoScopes && !ITLess()) { - try { - // make sure add openid scope when custom scope is used - scopes.push('openid', JSON.parse(ssoScopes)); - } catch { - console.error('Unable to parse sso scopes!'); - } - } - - kc.login({ - scope: scopes.join(' '), - }); - }); -}; - -export interface JWTInitOptions extends KeycloakInitOptions { - cookieName: string; - routes?: typeof DEFAULT_SSO_ROUTES; - url?: string; - clientId: string; - realm: string; - promiseType?: string; - checkLoginIframe?: boolean; - silentCheckSsoRedirectUri?: string; - token?: string; -} - -/*** Initialization ***/ -export const init = (options: JWTInitOptions, configSsoUrl?: string) => { - log('Initializing'); - - const cookieName = options.cookieName ? options.cookieName : DEFAULT_COOKIE_NAME; - - priv.setCookie({ cookieName }); - if (itLessCognito) { - let token; - let cogUser: CogUser; - - if (token) { - getUser().then((res) => { - cogUser = res; - if (cogUser) { - const now = Date.now().toString().substr(0, 10); - const exp = cogUser?.exp - parseInt(now); - if (exp < 30) { - return getTokenWithAuthorizationCode().then((res) => { - priv.setToken(res); - token = res; - return token; - }); - } - } - }); - } - return getTokenWithAuthorizationCode().then((res) => { - priv.setToken(res); - initSuccess(); - token = res; - return token; - }); - } else { - return Promise.resolve(platformUrl(options.routes ? options.routes : DEFAULT_SSO_ROUTES, configSsoUrl)).then((ssoUrl) => { - //constructor for new Keycloak Object? - options.url = ssoUrl; - options.clientId = itLessKeycloakEnv ? 'console-dot' : 'cloud-services'; - options.realm = 'redhat-external'; - - //options for keycloak.init method - options.promiseType = 'native'; - options.onLoad = 'check-sso'; - options.checkLoginIframe = false; - - const isBeta = isBetaFunction() ? '/beta' : ''; - - options.silentCheckSsoRedirectUri = `https://${window.location.host}${isBeta}/apps/chrome/silent-check-sso.html`; - - if (window.localStorage && window.localStorage.getItem('chrome:jwt:shortSession') === 'true') { - options.realm = 'short-session'; - } - - //priv.keycloak = Keycloak(options); - priv.setKeycloak(options, updateToken, loginAllTabs, refreshTokens); - - if (options.token) { - if (isExistingValid(options.token)) { - // we still need to init async - // so that the renewal times and such fire - priv.initializeKeycloak(options); - // Here we have an existing key - // We need to set up some of the keycloak state - // so that the reset of the methods that Chrome uses - // to check if things are good get faked out - // TODO reafctor the direct access to priv.keycloak - // away from the users - priv.setToken(options.token); - return Promise.resolve(); - // return new Promise((resolve) => { - - // resolve(); - // }); - } else { - delete options.token; - } - } - - return (priv.initialize(options) as unknown as Promise).then(initSuccess).catch(initError); - }); - } -}; - -export function isExistingValid(token?: string) { - log('Checking validity of existing JWT'); - try { - if (!token) { - return false; - } - - const parsed = decodeToken(token); - if (!parsed.exp) { - return false; - } - - // Date.now() has extra precision... - // it includes milis - // we need to trim it down to valid seconds from epoch - // because we compare to KC's exp which is seconds from epoch - const now = Date.now().toString().substr(0, 10); - const exp = parsed.exp - parseInt(now); - - log(`Token expires in ${exp}`); - - // We want to invalidate tokens if they are getting close - // to the expiry time - // So that we can be someone safe from time skew - // issues on our APIs - // i.e. the client could have a slight time skew - // and the API is true (because NTP) and we could send down - // a JWT that is actually exipred - if (exp > 90) { - priv.setTokenParsed(parsed); - return true; - } else { - if (exp > 0) { - log('token is expiring in < 90 seconds'); - } else { - log('token is expired'); - } - - return false; - } - } catch (e: unknown) { - log(e); - return false; - } -} - -// keycloak init successful -export async function initSuccess() { - log('JWT Initialized'); - let cogToken; - if (itLessCognito) { - cogToken = await getTokenWithAuthorizationCode(); - } - const token = itLessCognito ? cogToken : priv.getToken(); - setCookie(token); -} - -// keycloak init failed -export function initError() { - log('JWT init error'); - logout(); -} - -/*** Login/Logout ***/ -export function login(requiredScopes: string[] = []) { - log('Logging in'); - // Redirect to login - cookie.set('cs_loggedOut', 'false'); - const redirectUri = location.href; - // TODO: Remove once ephemeral environment supports full and thin profile - const scope = ['openid', ...requiredScopes]; - const partner = getPartnerScope(window.location.pathname); - if (partner) { - scope.push(partner); - } - localStorage.setItem(LOGIN_SCOPES_STORAGE_KEY, JSON.stringify(scope)); - // KC scopes are delimited by a space character, hence the join(' ') - return priv.login({ redirectUri, scope: scope.join(' ') }); -} - -export function logout(bounce?: boolean) { - log('Logging out'); - const cookieName = priv.getCookie()?.cookieName; - if (cookieName) { - cookie.remove(cookieName); - } - cookie.remove('cs_demo'); - - const isBeta = isBetaFunction() ? getRouterBasename() : ''; - const keys = Object.keys(localStorage).filter( - (key) => - key.endsWith('/api/entitlements/v1/services') || - key.endsWith('/chrome') || - key.endsWith('/chrome-store') || - key.startsWith('kc-callback') || - key.startsWith(GLOBAL_FILTER_KEY) - ); - deleteLocalStorageItems([...keys, LOGIN_SCOPES_STORAGE_KEY]); - // Redirect to logout - if (bounce) { - const eightSeconds = new Date(new Date().getTime() + 8 * 1000); - cookie.set('cs_loggedOut', 'true', { - expires: eightSeconds, - }); - priv.logout({ - redirectUri: `https://${window.location.host}${isBeta}`, - }); - - // Clear cookies and tokens - priv.clearToken(); - } -} - -export const logoutAllTabs = (bounce?: boolean) => { - authChannel.postMessage({ type: 'logout' }); - logout(bounce); -}; - -function loginAllTabs() { - authChannel.postMessage({ type: 'login' }); -} - -/*** User Functions ***/ -// Get user information -export const getUserInfo = (): Promise => { - log('Getting User Information'); - const jwtCookie = cookie.get(DEFAULT_COOKIE_NAME); - if (jwtCookie && isExistingValid(jwtCookie) && isExistingValid(priv.getToken())) { - return platformUser(priv.getTokenParsed()); - } - - return updateToken() - .then(() => { - log('Successfully updated token'); - return platformUser(priv.getTokenParsed()); - }) - .catch(() => { - if (pageRequiresAuthentication()) { - log('Trying to log in user to refresh token'); - return login(); - } - }); -}; - -// Check to see if the user is loaded, this is what API calls should wait on -export const isAuthenticated = () => { - log(`User Ready: ${priv.getAuthenticated()}`); - return priv.getAuthenticated(); -}; - -/*** Check Token Status ***/ -// If a token is expired, logout of all tabs -export const expiredToken = () => { - log('Token has expired, trying to log out'); - logout(); -}; - -// Broadcast message to refresh tokens across tabs -function refreshTokens() { - setCookie(priv.getToken()); - authChannel.postMessage({ type: 'refresh' }); -} -export function getRefreshToken() { - return priv.getRefershToken(); -} - -// Actually update the token -export function updateToken() { - return (Promise.resolve(priv?.updateToken?.()) as unknown as Promise) - .then((refreshed) => { - // Important! after we update the token - // we have to again populate the Cookie! - // Otherwise we just update and dont send - // the updated token down stream... and things 401 - setCookie(priv.getToken()); - - log('Attempting to update token'); - - if (refreshed) { - log('Token was successfully refreshed'); - } else { - log('Token is still valid, not updating'); - } - }) - .catch((err) => { - log(err); - Sentry.captureException(err); - log('Token updated failed, trying to reauth'); - login(); - }); -} - -export function getCookieExpires(exp: number) { - // we want the cookie to expire at the same time as the JWT session - // so we take the exp and get a new GTMString from that - const date = new Date(0); - date.setUTCSeconds(exp); - return date.toUTCString(); -} - -// Set the cookie for 3scale -export async function setCookie(token?: string) { - log('Setting the cs_jwt cookie'); - let cogToken; - let cogUser; - if (itLessCognito) { - cogToken = await getTokenWithAuthorizationCode(); - cogUser = await getUser(); - } - const tok = itLessCognito ? cogToken : token; - if (tok && tok.length > 10) { - // FIXME: Fix cognito typing not to use any - const tokExpires = itLessCognito ? cogUser.exp : decodeToken(tok).exp; - const cookieName = priv.getCookie()?.cookieName; - if (cookieName) { - setCookieWrapper(`${cookieName}=${tok};` + `path=/wss;` + `secure=true;` + `expires=${getCookieExpires(tokExpires)}`); - setCookieWrapper(`${cookieName}=${tok};` + `path=/ws;` + `secure=true;` + `expires=${getCookieExpires(tokExpires)}`); - setCookieWrapper(`${cookieName}=${tok};` + `path=/api/tasks/v1;` + `secure=true;` + `expires=${getCookieExpires(tokExpires)}`); - setCookieWrapper(`${cookieName}=${tok};` + `path=/api/automation-hub;` + `secure=true;` + `expires=${getCookieExpires(decodeToken(tok).exp)}`); - setCookieWrapper(`${cookieName}=${tok};` + `path=/api/remediations/v1;` + `secure=true;` + `expires=${getCookieExpires(tokExpires)}`); - setCookieWrapper(`${cookieName}=${tok};` + `path=/api/edge/v1;` + `secure=true;` + `expires=${getCookieExpires(tokExpires)}`); - } - } -} - -// do this so we can mock out for test -export function setCookieWrapper(str: string) { - window.document.cookie = str; -} - -// Encoded WIP -export const getEncodedToken = () => { - log('Trying to get the encoded token'); - - if (!isExistingValid(priv.getToken())) { - log('Failed to get encoded token, trying to update'); - updateToken(); - } - - return priv.getToken(); -}; - -// Keycloak server URL -export const getUrl = (ssoUrl?: string) => { - return platformUrl(DEFAULT_SSO_ROUTES, ssoUrl); -}; diff --git a/src/jwt/jwt.unit.test.ts b/src/jwt/jwt.unit.test.ts deleted file mode 100644 index 685d8e421..000000000 --- a/src/jwt/jwt.unit.test.ts +++ /dev/null @@ -1,329 +0,0 @@ -/* eslint-disable @typescript-eslint/ban-ts-comment */ -import cookie from 'js-cookie'; - -import { data as encodedToken } from '../../testdata/encodedToken.json'; -import decodedToken from '../../testdata/decodedToken.json'; - -import * as jwt from './jwt'; -import * as user from './user'; - -jest.mock('keycloak-js'); -jest.mock('urijs'); - -function mockLocation(path: string) { - global.window = Object.create(window); - Object.defineProperty(window, 'location', { - value: { - pathname: path, - }, - writable: true, - }); -} - -const blankOptions = { - cookieName: '', - clientId: '', - realm: '', -}; - -describe('JWT', () => { - beforeAll(() => { - // Initialize mock keycloak in JWT - jwt.init(blankOptions); - }); - - beforeEach(() => { - window.document.cookie = ''; - }); - - describe('getCookieExpires', () => { - test('should expire at epoch if 0 is given', () => { - expect(jwt.getCookieExpires(0)).toBe('Thu, 01 Jan 1970 00:00:00 GMT'); - }); - - test('should expire at now if now is given', () => { - const now = new Date(); - const nowString = now.toUTCString(); - const nowUnix = Math.floor(now.getTime() / 1000); - expect(jwt.getCookieExpires(nowUnix)).toBe(nowString); - }); - }); - - describe('setCookie', () => { - test('sets a cookie that expires on the same second the JWT expires', () => { - jwt.setCookie(encodedToken); - expect(window.document.cookie).toEqual( - `cs_jwt=${encodedToken};` + `path=/api/edge/v1;` + `secure=true;` + `expires=Wed, 24 Apr 2019 17:13:47 GMT` - ); - }); - }); - - describe('decodeToken', () => { - test('decodes a valid token', () => { - expect(jwt.decodeToken(encodedToken)).toMatchObject(decodedToken); - }); - - test('throws an error for an invalid token', () => { - expect(() => jwt.decodeToken(encodedToken.replace('.', '.abc'))).toThrow('Invalid token'); - }); - }); - - describe('isExistingValid', () => { - beforeEach(() => { - jest.resetModules(); - }); - - test('missing token', () => { - expect(jwt.isExistingValid()).toBeFalsy(); - }); - - test('missing exp field', () => { - const missingExp = decodedToken; - // @ts-ignore - delete missingExp.exp; - - const decodeTokenSpy = jest.spyOn(jwt, 'decodeToken').mockReturnValueOnce(missingExp); - - expect(jwt.isExistingValid(encodedToken)).toBeFalsy(); - decodeTokenSpy.mockRestore(); - }); - - test('expired token', () => { - expect(jwt.isExistingValid(encodedToken)).toBeFalsy(); - }); - - test('valid token', () => { - // mock Date.now function to always be in the past. - const nowMock = jest.spyOn(global.Date, 'now').mockReturnValueOnce(1); - - expect(jwt.isExistingValid(encodedToken)).toBeTruthy(); - nowMock.mockRestore(); - }); - }); - - describe('init', () => { - test('no token', () => { - expect(jwt.init(blankOptions)).toBeTruthy(); - }); - - test('invalid token', () => { - // @ts-ignore - blankOptions.token = encodedToken; - - const isExistingValidSpy = jest.spyOn(jwt, 'isExistingValid').mockReturnValueOnce(false); - expect(jwt.init(blankOptions)).toBeTruthy(); - expect(jwt.isAuthenticated()).toBeFalsy(); - isExistingValidSpy.mockRestore(); - }); - - test('valid token', async () => { - // @ts-ignore - blankOptions.token = encodedToken; - // mock Date.now function to always be in the past. - const nowMock = jest.spyOn(global.Date, 'now').mockReturnValueOnce(1); - - const isExistingValidSpy = jest.spyOn(jwt, 'isExistingValid').mockReturnValueOnce(true); - - await jwt.init(blankOptions); - expect(jwt.isAuthenticated()).toBeTruthy(); - isExistingValidSpy.mockRestore(); - nowMock.mockRestore(); - }); - }); - - // TODO: Test doOffline more thoroughly. - // At the moment, it isn't clear how to verify its results. - describe('doOffline', () => { - test('doOffline works', () => { - expect(jwt.doOffline('foo', 'bar')).not.toBeDefined(); - }); - }); - - describe('auth channel', () => { - test('logoutAllTabs', () => { - const logoutSpy = jest.spyOn(jwt, 'logout').mockImplementationOnce(() => { - cookie.remove('cs_jwt'); - }); - - cookie.set('cs_jwt', 'token1'); - jwt.logoutAllTabs(); - expect(cookie.get('cs_jwt')).not.toBeDefined(); - - logoutSpy.mockRestore(); - }); - - // test('loginAllTabs', () => { - // cookie.remove('cs_jwt'); - // const loginAllTabs = jwt.__get__('loginAllTabs'); - // JWTRewireAPI.__Rewire__('login', () => { - // cookie.set('cs_jwt', 'token1'); - // }); - // loginAllTabs(); - // expect(cookie.get('cs_jwt')).toBeDefined(); - // }); - - // test('refreshTokens', () => { - // const refreshTokens = jwt.__get__('refreshTokens'); - // JWTRewireAPI.__Rewire__('updateToken', () => { - // cookie.remove('cs_jwt'); - // cookie.set('cs_jwt', 'updatedToken'); - // }); - - // // Log in and verify that the token is correct - // jwt.login(); - // expect(cookie.get('cs_jwt')).toBe('token1'); - - // // Refresh token and make sure it changed - // refreshTokens(); - // expect(cookie.get('cs_jwt')).toBe('updatedToken'); - - // }); - }); - - describe('init and auth functions', () => { - describe('initSuccess()', () => { - beforeEach(() => { - window.document.cookie = ''; - }); - - test('should set a cookie', () => { - jwt.initSuccess(); - expect(window.document.cookie.includes(encodedToken)).toEqual(true); - }); - }); - - test('initError', () => { - const logoutSpy = jest.spyOn(jwt, 'logout').mockImplementationOnce(() => { - cookie.remove('cs_jwt'); - }); - - cookie.set('cs_jwt', 'true'); - jwt.initError(); - expect(cookie.get('cs_jwt')).not.toBeDefined(); - - logoutSpy.mockRestore(); - }); - - test('login', () => { - cookie.remove('cs_jwt'); - jwt.login(); - expect(cookie.get('cs_jwt')).toBeDefined(); - }); - - describe('logout', () => { - test('should destroy the cookie', () => { - cookie.set('cs_jwt', 'testvalue'); - jwt.logout(); - expect(cookie.get('cs_jwt')).not.toBeDefined(); - }); - }); - - test('expiredToken', () => { - cookie.set('cs_jwt', 'testvalue'); - jwt.expiredToken(); - expect(cookie.get('cs_jwt')).not.toBeDefined(); - }); - - test('updateToken', () => { - cookie.set('cs_jwt', 'token1'); - jwt.updateToken(); - expect(cookie.get('cs_jwt')).toBe('updatedToken'); - }); - }); - - describe('helper functions', () => { - describe('getUserInfo', () => { - const updateTokenMockSpy = jest.spyOn(jwt, 'updateToken'); - const isExistingValidSpy = jest.spyOn(jwt, 'isExistingValid'); - - beforeEach(() => { - updateTokenMockSpy.mockReset(); - isExistingValidSpy.mockReset(); - cookie.set('cs_jwt', 'deadbeef'); - }); - - test('return right away if the cookie and token are good', async () => { - const mockUser = { name: 'John Guy' }; - // @ts-ignore - jest.spyOn(user, 'default').mockImplementation((data: unknown) => (data ? mockUser : null)); - // make token not expired - const nowMock = jest.spyOn(global.Date, 'now').mockReturnValue(1); - cookie.set('cs_jwt', encodedToken); - - const data = await jwt.getUserInfo(); - expect(data).toEqual(mockUser); - nowMock.mockRestore(); - }); - - describe('token update fails', () => { - const loginSpy = jest.spyOn(jwt, 'login'); - async function doTest(url: string, expectedToWork: boolean) { - isExistingValidSpy.mockReturnValueOnce(false); - mockLocation(url); - updateTokenMockSpy.mockImplementation(() => Promise.resolve()); - - return jwt.getUserInfo().then(() => { - if (expectedToWork) { - expect(loginSpy).toBeCalled(); - } else { - expect(loginSpy).not.toBeCalled(); - } - - loginSpy.mockReset(); - }); - } - - test('should call login on an authenticated page', () => { - return doTest('/insights/foobar', true); - }); - }); - - describe('token update passes', () => { - const loginSpy = jest.spyOn(jwt, 'login'); - test('should *not* call login', () => { - cookie.remove('cs_jwt'); - mockLocation('/insights/foobar'); - updateTokenMockSpy.mockReturnValue( - new Promise((res) => { - res(); - }) - ); - - return jwt.getUserInfo().then(() => { - expect(loginSpy).not.toBeCalled(); - }); - }); - }); - - test('should give you a valid user object', async () => { - const mockUser = { name: 'John Guy' }; - const options = blankOptions; - - jest.spyOn(jwt, 'isExistingValid').mockImplementation((data) => !!data); - // @ts-ignore - jest.spyOn(user, 'default').mockImplementation((data: unknown) => (data ? mockUser : null)); - // @ts-ignore - options.token = encodedToken; - // @ts-ignore - options.tokenParsed = decodedToken; - await jwt.init(options); - const userResult = await jwt.getUserInfo(); - expect(userResult).toBe(mockUser); - }); - }); - - test('getEncodedToken', () => { - expect(jwt.getEncodedToken()).toBe(encodedToken); - }); - - test('getUrl', async () => { - const url = await jwt.getUrl(); - expect(url).toBe('https://sso.qa.redhat.com/auth'); - }); - - test('getUrl with custom URL', async () => { - const url = await jwt.getUrl('https://custom-url.com/auth'); - expect(url).toBe('https://custom-url.com/auth'); - }); - }); -}); diff --git a/src/jwt/offline.test.ts b/src/jwt/offline.test.ts deleted file mode 100644 index 0ec4cf978..000000000 --- a/src/jwt/offline.test.ts +++ /dev/null @@ -1,166 +0,0 @@ -/* eslint-disable @typescript-eslint/ban-ts-comment */ -import axios from 'axios'; -import { OFFLINE_REDIRECT_STORAGE_KEY } from '../utils/consts'; - -jest.mock('axios', () => { - return { - post: jest.fn(() => Promise.resolve()), - create: jest.fn(() => ({ - interceptors: { - request: { use: jest.fn(), eject: jest.fn() }, - response: { use: jest.fn(), eject: jest.fn() }, - }, - })), - }; -}); - -jest.mock('./offline', () => { - const actual = jest.requireActual('./offline'); - return { - __esModule: true, - ...actual, - default: { - ...actual, - }, - }; -}); - -import * as offline from './offline'; - -const defaults: Record> = { - location: { - hash: '#foo=bar', - search: '?noauth=2402500adeacc30eb5c5a8a5e2e0ec1f', - href: 'https://test.com/some/path?noauth=2402500adeacc30eb5c5a8a5e2e0ec1f#foo=bar', - origin: 'https://test.com', - host: 'https://test.com', - pathname: '/some/path', - }, -}; - -function getMockWindow(location = defaults.location) { - const loc = location; - return { - location: loc, - history: { - pushState: (_one: unknown, _two: unknown, url: string) => { - loc.__foo__ = url; - }, - }, - }; -} - -describe('Offline', () => { - test('window works', () => { - // this is really just to reach 100% for this module - // getWindow was just introduced to allow for code to work - // and test code too - expect(offline.getWindow()).toBe(window); - }); - describe('getOfflineToken', () => { - beforeEach(() => { - global.window = Object.create(window); - Object.defineProperty(window, 'location', { - value: getMockWindow().location, - writable: true, - }); - Object.defineProperty(window, 'history', { - value: getMockWindow().history, - writable: true, - }); - localStorage.setItem(OFFLINE_REDIRECT_STORAGE_KEY, getMockWindow().location.href); - }); - test('fails when there is no offline postbackUrl', async () => { - try { - await offline.getOfflineToken('foo', 'bar'); - } catch (e) { - expect(e).toBe('not available'); - } - }); - - test('POSTs to /token with the right parameters when input is good', async () => { - window.location.hash = '#test=bar&code=test123'; - offline.wipePostbackParamsThatAreNotForUs(); - await offline.getOfflineToken('', 'test321'); - expect(axios.post).toHaveBeenCalledWith( - 'https://sso.qa.redhat.com/auth/realms//protocol/openid-connect/token', - 'code=test123&grant_type=authorization_code&client_id=test321&redirect_uri=https%3A%2F%2Ftest.com%2Fsome%2Fpath%3Fnoauth%3D2402500adeacc30eb5c5a8a5e2e0ec1f', - { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } } - ); - }); - }); - - describe('wipePostbackParamsThatAreNotForUs', () => { - describe('when no auth param is present', () => { - const getPostbackUrl = offline.getPostbackUrl; - beforeEach(() => { - global.window = Object.create(window); - Object.defineProperty(window, 'location', { - value: getMockWindow().location, - writable: true, - }); - Object.defineProperty(window, 'history', { - value: getMockWindow().history, - writable: true, - }); - localStorage.setItem(OFFLINE_REDIRECT_STORAGE_KEY, getMockWindow().location.href); - offline.wipePostbackParamsThatAreNotForUs(); - }); - - test('strips hash', () => { - expect(window.location.hash).toBe(''); - }); - - test('sets postbackUrl', () => { - expect(getPostbackUrl()).toBe('https://test.com/some/path?noauth=2402500adeacc30eb5c5a8a5e2e0ec1f'); - }); - - test('removes noauth query param', () => { - // @ts-ignore - expect(window.location.__foo__).not.toMatch('noauth=2402500adeacc30eb5c5a8a5e2e0ec1'); - }); - - test('removes noauth query param with others', () => { - window.location.href = 'https://example.com?noauth=2402500adeacc30eb5c5a8a5e2e0ec1f&test=bar&bar=baz'; - offline.wipePostbackParamsThatAreNotForUs(); - // @ts-ignore - expect(window.location.__foo__).toMatch('https://example.com/?test=bar&bar=baz'); - }); - }); - }); - - describe('parseHashString', () => { - const parseHashString = offline.parseHashString; - test('can parse KC form data in hash', () => { - const url = 'https://cloud.redhat.com/?foo=bar#bar=baz&foo=bar'; - expect(parseHashString(url)).toMatchObject({ - bar: 'baz', - foo: 'bar', - }); - }); - }); - - describe('getPostDataObject', () => { - const getPostDataObject = offline.getPostDataObject; - test('returns valid parameters', () => { - const o = getPostDataObject('https://example.com', 'cloud-services', 'deadbeef'); - expect(o).toHaveProperty('code', 'deadbeef'); - expect(o).toHaveProperty('grant_type', 'authorization_code'); - expect(o).toHaveProperty('redirect_uri', 'https%3A%2F%2Fexample.com'); - expect(o).toHaveProperty('client_id', 'cloud-services'); - }); - }); - - describe('getPostbackUrl', () => { - const getPostbackUrl = offline.getPostbackUrl; - test('can get the URL once', () => { - offline.wipePostbackParamsThatAreNotForUs(); - expect(getPostbackUrl()).toEqual(expect.any(String)); - }); - test('cannot get URL twice', () => { - offline.wipePostbackParamsThatAreNotForUs(); - expect(getPostbackUrl()).toEqual(expect.any(String)); - expect(getPostbackUrl()).toBe(undefined); - }); - }); -}); diff --git a/src/jwt/offline.ts b/src/jwt/offline.ts deleted file mode 100644 index 921a916d6..000000000 --- a/src/jwt/offline.ts +++ /dev/null @@ -1,118 +0,0 @@ -import consts, { OFFLINE_REDIRECT_STORAGE_KEY } from '../utils/consts'; -import insightsUrl from './url'; -import axios, { AxiosResponse } from 'axios'; -import { DEFAULT_SSO_ROUTES, ITLessKeycloak, getEnv } from '../utils/common'; - -type Priv = { - postbackUrl?: string; - response?: AxiosResponse; -}; - -const priv: Priv = {}; -// note this function is not exposed -// it is a run everytime and produce some side affect thing -// if a special condition is encountered -// -// it would be great to not have this behave this way -// but the order that this needs to run in is very specific -// so that is somewhat difficult -export function wipePostbackParamsThatAreNotForUs() { - if (getWindow().location.href.indexOf(consts.offlineToken) !== -1) { - const { hash, origin, pathname } = getWindow().location; - // attempt to use postback created from in previous doOffline call - const postbackUrl = new URL(localStorage.getItem(OFFLINE_REDIRECT_STORAGE_KEY) || `${origin}${pathname}`); - postbackUrl.hash = hash; - // this is a UHC offline token postback - // we need to not let the JWT lib see this - // and try to use it - priv.postbackUrl = postbackUrl.toString(); - - // we do this because keycloak.js looks at the hash for its parameters - // and if found uses the params for its own use - // - // in the UHC offline post back case we *dont* - // want the params to be used by keycloak.js - // so we have to destroy this stuff and let regular auth routines happen - getWindow().location.hash = ''; - - // nuke the params so that people dont see the ugly - const url = new URL(getWindow().location.href); - url.searchParams.delete(consts.noAuthParam); - getWindow().history.pushState('offlinePostback', '', url.toString()); - } -} - -export async function getOfflineToken(realm: string, clientId: string, configSsoUrl?: string) { - const postbackUrl = getPostbackUrl(); - - if (priv.response) { - return Promise.resolve(priv.response); - } - - if (!postbackUrl) { - // we need this postback URL because it contains parameters needed to - // call KC for the actual offline token - // thus we cant continue if it is missing - return Promise.reject('not available'); - } - - const ssoUrl = await insightsUrl(DEFAULT_SSO_ROUTES, configSsoUrl); - - const tokenURL = `${ssoUrl}/realms/${realm}/protocol/openid-connect/token`; - const params = parseHashString(postbackUrl); - - return axios - .post(tokenURL, getPostDataString(getPostDataObject(postbackUrl, clientId, params.code)), { - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - }) - .then((response) => { - priv.response = response; - return response; - }); -} - -export function getWindow() { - return window; -} - -export function getPostbackUrl() { - // let folks only do this once - const ret = priv.postbackUrl; - delete priv.postbackUrl; - return ret; -} - -export function getPostDataObject(url: string, clientId: string, code: string) { - const scr = getEnv() === 'scr'; - const int = getEnv() === 'int'; - const redirectUrl = scr - ? 'https://console01.stage.openshiftusgov.com/' - : int - ? 'https://console.int.openshiftusgov.com/' - : 'https://ephem.outrights.cc'; - return { - code: code, - grant_type: 'authorization_code', // eslint-disable-line camelcase - client_id: ITLessKeycloak() ? 'console-dot' : clientId, // eslint-disable-line camelcase - redirect_uri: ITLessKeycloak() ? redirectUrl : encodeURIComponent(url.split('#')[0]), // eslint-disable-line camelcase - }; -} - -export function parseHashString(str: string) { - return str - .split('#')[1] - .split('&') - .reduce>((result, item) => { - const parts = item.split('='); - result[parts[0]] = parts[1]; - return result; - }, {}); -} - -function getPostDataString(obj: Record) { - return Object.entries(obj) - .map((entry) => { - return `${entry[0]}=${entry[1]}`; - }) - .join('&'); -} diff --git a/src/jwt/user.ts b/src/jwt/user.ts deleted file mode 100644 index 0a4ef036e..000000000 --- a/src/jwt/user.ts +++ /dev/null @@ -1,246 +0,0 @@ -import { ITLessCognito, getRouterBasename, isBeta, isValidAccountNumber, pageAllowsUnentitled } from '../utils/common'; -import servicesApi from './entitlements'; -import logger from './logger'; -import { SSOParsedToken } from './Priv'; -import { ChromeUser } from '@redhat-cloud-services/types'; -import { isAnsibleTrialFlagActive } from '../utils/isAnsibleTrialFlagActive'; -import chromeHistory from '../utils/chromeHistory'; -import { createUser } from '../cognito/auth'; - -const serviceAPI = servicesApi(); - -export type SSOServiceDetails = { - is_entitled: boolean; - is_trial: boolean; -}; - -const isITLessCognito = ITLessCognito(); -const bounceInvocationLock: { [service: string]: boolean } = { - // not_entitled modal should appear only once for insights bundle - insights: false, -}; - -const log = logger('insights/user.js'); -const pathMapper = { - 'cost-management': 'cost_management', - insights: 'insights', - openshift: 'openshift', - migrations: 'migrations', - ansible: 'ansible', - subscriptions: 'subscriptions', - settings: 'settings', - 'user-preferences': 'user_preferences', - internal: 'internal', -}; - -const REDIRECT_BASE = `${document.location.origin}${isBeta() ? getRouterBasename() : ''}`; - -const unentitledPathMapper = (section: string, service: string, expired = false) => { - const search = new URLSearchParams(document.location.search); - if (!search.has('not_entitled')) { - search.append('not_entitled', service); - } - return ( - { - ansible: `${REDIRECT_BASE}/ansible/ansible-dashboard/${expired ? 'trial/expired' : 'trial'}`, - }[section] || `${document.location.origin}${document.location.pathname}?${search.toString()}` - ); -}; - -function getWindow() { - return window; -} - -/* eslint-disable camelcase */ -export function buildUser(token: SSOParsedToken) { - const user = token - ? { - identity: { - account_number: token.account_number, - org_id: token.org_id, - type: token.type, - ...(token.idp && { idp: token.idp }), - user: { - username: token.username, - email: token.email, - first_name: token.first_name, - last_name: token.last_name, - is_active: token.is_active, - is_org_admin: token.is_org_admin, - is_internal: token.is_internal, - locale: token.locale, - }, - internal: { - org_id: token.org_id, - account_id: token.account_id, - }, - }, - } - : null; - - return user; -} -/* eslint-enable camelcase */ - -function partialBounce(redirectAddress: string, section: string) { - const url = new URL(redirectAddress); - chromeHistory.replace({ - pathname: url.pathname, - search: url.search, - }); - - if (section !== 'ansible') { - bounceInvocationLock[section] = true; - } -} - -export function tryBounceIfUnentitled( - data: - | boolean - | { - [key: string]: SSOServiceDetails; - }, - section: string -) { - // only test this on the apps that are in valid sections - // we need to keep /apps and other things functional - if ( - section !== 'insights' && - section !== 'openshift' && - section !== 'cost-management' && - section !== 'migrations' && - section !== 'ansible' && - section !== 'subscriptions' && - section !== 'user-preferences' && - section !== 'internal' - ) { - return; - } - - const ansibleActive = isAnsibleTrialFlagActive(); - // test temporary ansible trial flag - if (section === 'ansible' && ansibleActive) { - return; - } - - // do not show not entitled modal repeadly for the same section - if (bounceInvocationLock[section]) { - return; - } - - const service = pathMapper[section]; - // ansibleActive can be true/false/undefined - const redirectAddress = unentitledPathMapper(section, service, ansibleActive === false); - - if (data === true) { - // this is a force bounce scenario! - if (section !== 'ansible') { - partialBounce(redirectAddress, section); - } else { - getWindow().location.replace(redirectAddress); - } - } - - if (section && typeof data === 'object') { - if (data?.[service]?.is_entitled) { - log(`Entitled to: ${service}`); - } else { - log(`Not entitled to: ${service}`); - try { - const search = new URLSearchParams(window.location.search); - // do not trigger redirect if the not_entitled param already exists - if (!search.has('not_entitled')) { - partialBounce(redirectAddress, section); - } else { - // lock the section if user landed directly on the not_entitled page - if (section !== 'ansible') { - bounceInvocationLock[section] = true; - } - } - } catch (error) { - console.error(error); - // if something goes wring with the redirect, use standard API - getWindow().location.replace(redirectAddress); - } - } - } -} - -export default async (token: SSOParsedToken): Promise => { - const user = isITLessCognito ? await createUser() : buildUser(token); - - const pathName = getWindow().location.pathname.split('/'); - pathName.shift(); - if (pathName[0] === 'beta') { - pathName.shift(); - } - if (pathName?.[1] === 'subscriptions' || pathName?.[1] === 'cost-management') { - pathName.shift(); - } - - if (user) { - log(`Account Number: ${user.identity.account_number}`); - let data: { - [key: string]: { - is_entitled: boolean; - is_trial: boolean; - }; - } = {}; - // let cogToken; - if (isITLessCognito) { - // cogToken = await getTokenWithAuthorizationCode(); - } - try { - if (user.identity.org_id) { - data = isITLessCognito - ? ((await serviceAPI.servicesGet()) as unknown as typeof data) - : ((await serviceAPI.servicesGet()) as unknown as typeof data); - } else { - console.log('Cannot call entitlements API, no account number'); - } - } catch { - // let's swallow error from services API - } - - // NOTE: Openshift supports Users with Account Number of -1 - // thus we need to bypass here - // call entitlements on / /beta /openshift or /beta/openshift, - // but swallow error - // - // Landing Page *does* support accounts with -1 - // it has to - if (pageAllowsUnentitled()) { - return { - ...user, - entitlements: data, - }; - } - - // Important this has to come after the above -1 allow checks - // Otherwise we get bounced on those paths - // - // It also needs to not go int he servicesApi call - // because 3scale 403s if the Account number is -1 - // - // we "force" a bounce here because the entitlements API - // was never called - if (!isValidAccountNumber(user.identity.account_number)) { - tryBounceIfUnentitled(true, pathName[0]); - // always return user regardless of the entitlements result - // required for insights accounts with invalid account number - return { - ...user, - entitlements: data, - }; - } - - tryBounceIfUnentitled(data as unknown as { [key: string]: SSOServiceDetails }, pathName[0]); - - return { - ...user, - entitlements: data, - }; - } else { - log('User not ready'); - } -}; diff --git a/src/jwt/user.unit.test.js b/src/jwt/user.unit.test.js deleted file mode 100644 index b1d4f427e..000000000 --- a/src/jwt/user.unit.test.js +++ /dev/null @@ -1,121 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -jest.mock('./entitlements'); - -const token = require('../../testdata/token.json'); -const userOutput = require('../../testdata/user.json'); -const user = require('./user'); -const { setAnsibleTrialFlag, ANSIBLE_TRIAL_FLAG } = require('../utils/isAnsibleTrialFlagActive'); -const replaceMock = jest.fn(); -const chromeHistory = require('../utils/chromeHistory'); - -jest.mock('../utils/chromeHistory', () => { - return { - __esModule: true, - default: { - replace: jest.fn(), - }, - }; -}); - -describe('User', () => { - const { location } = window; - - beforeAll(() => { - delete window.location; - window.location = { - pathname: '/insights/foo', - replace: replaceMock, - }; - }); - - afterAll(() => { - window.location = location; - }); - - describe('buildUser', () => { - test('transforms a token into a User object', () => { - expect(user.buildUser(token)).toEqual(userOutput); - }); - }); - - /* eslint-disable camelcase */ - describe('tryBounceIfUnentitled', () => { - const ents = { - insights: { is_entitled: false }, - openshift: { is_entitled: false }, - cost_management: { is_entitled: false }, - }; - - beforeEach(() => { - replaceMock.mockReset(); - }); - - test('should *not* bounce if the section is unkown', () => { - ents.insights.is_entitled = false; - user.tryBounceIfUnentitled(ents, 'apps'); - user.tryBounceIfUnentitled(ents, 'foo'); - user.tryBounceIfUnentitled(ents, 'test'); - expect(replaceMock).not.toBeCalled(); - }); - - test('should bounce if unentitled', () => { - const historySpy = jest.spyOn(chromeHistory.default, 'replace'); - user.tryBounceIfUnentitled(ents, 'insights'); - expect(historySpy).lastCalledWith({ pathname: '/', search: '?not_entitled=insights' }); - - user.tryBounceIfUnentitled(ents, 'cost-management'); - expect(historySpy).lastCalledWith({ pathname: '/', search: '?not_entitled=cost_management' }); - - user.tryBounceIfUnentitled(ents, 'ansible'); - expect(historySpy).lastCalledWith({ pathname: '/ansible/ansible-dashboard/trial', search: '' }); - historySpy.mockRestore(); - }); - - test('should properly bounce if unentitled user with ansible trial locastorage flags', () => { - const historySpy = jest.spyOn(chromeHistory.default, 'replace'); - jest.useFakeTimers(); - // enable ansible trial flag - setAnsibleTrialFlag(Date.now()); - // advance time by one minute. user should not be bounced - jest.advanceTimersByTime(1 * 60 * 1000); - user.tryBounceIfUnentitled(ents, 'ansible'); - expect(historySpy).not.toBeCalled(); - - // advace time by additional 10 minutes. user should be bounced to /trial/expired - jest.advanceTimersByTime(10 * 60 * 1000); - user.tryBounceIfUnentitled(ents, 'ansible'); - expect(historySpy).toBeCalledTimes(1); - expect(historySpy).toHaveBeenLastCalledWith({ pathname: '/ansible/ansible-dashboard/trial/expired', search: '' }); - historySpy.mockClear(); - - // should not be bounced at all if entitled to ansible - user.tryBounceIfUnentitled( - { - ansible: { - is_entitled: true, - }, - }, - 'ansible' - ); - expect(historySpy).not.toBeCalled(); - - // clear the ansible trial flag - localStorage.removeItem(ANSIBLE_TRIAL_FLAG); - }); - - test('should *not* bounce if entitled', () => { - ents.insights.is_entitled = true; - user.tryBounceIfUnentitled(ents, 'insights'); - expect(replaceMock).not.toBeCalled(); - }); - }); - /* eslint-enable camelcase */ - - describe('default', () => { - test('appends the entitlements data onto the user object', async () => { - const o = await user.default(token); - expect(o).toHaveProperty('entitlements', { foo: 'bar' }); - expect(o).toHaveProperty('identity'); - }); - }); -}); diff --git a/src/layouts/DefaultLayout.tsx b/src/layouts/DefaultLayout.tsx index 4995c33c0..bb1f063fd 100644 --- a/src/layouts/DefaultLayout.tsx +++ b/src/layouts/DefaultLayout.tsx @@ -1,4 +1,4 @@ -import React, { memo, useEffect, useRef, useState } from 'react'; +import React, { memo, useContext, useEffect, useRef, useState } from 'react'; import classnames from 'classnames'; import { useSelector } from 'react-redux'; import GlobalFilter from '../components/GlobalFilter/GlobalFilter'; @@ -27,6 +27,7 @@ import useNavigation from '../utils/useNavigation'; import { NavigationProps } from '../components/Navigation'; import { getUrl } from '../hooks/useBundle'; import { useFlag } from '@unleash/proxy-client-react'; +import ChromeAuthContext from '../auth/ChromeAuthContext'; type ShieldedRootProps = { hideNav?: boolean; @@ -172,7 +173,8 @@ export type RootAppProps = { const DefaultLayoutRoot = ({ Sidebar, Footer }: RootAppProps) => { const ouiaTags = useOuiaTags(); const initialized = useScalprum(({ initialized }) => initialized); - const hideNav = useSelector(({ chrome: { user } }: ReduxState) => !user || !Sidebar); + const { user } = useContext(ChromeAuthContext); + const hideNav = !user || !Sidebar; return (
diff --git a/src/redux/action-types.ts b/src/redux/action-types.ts index 16b3f49fb..08c20fb32 100644 --- a/src/redux/action-types.ts +++ b/src/redux/action-types.ts @@ -21,7 +21,6 @@ export const LOAD_NAVIGATION_LANDING_PAGE = '@@chrome/load-navigation-landing-pa export const LOAD_LEFT_NAVIGATION_SEGMENT = '@@chrome/load-navigation-segment'; export const LOAD_MODULES_SCHEMA = '@@chrome/load-modules-schema'; -export const CHANGE_ACTIVE_MODULE = '@@chrome/change-active-module'; export const SET_PENDO_FEEDBACK_FLAG = '@@chrome/set-pendo-feedback-flag'; export const TOGGLE_FEEDBACK_MODAL = '@@chrome/toggle-feedback-modal'; export const TOGGLE_DEBUGGER_MODAL = '@@chrome/toggle-debugger-modal'; diff --git a/src/redux/actions.ts b/src/redux/actions.ts index 351283917..f3b580d50 100644 --- a/src/redux/actions.ts +++ b/src/redux/actions.ts @@ -119,11 +119,6 @@ export const loadModulesSchema = (schema: { [key: string]: ChromeModule }) => ({ }, }); -export const changeActiveModule = (module: string) => ({ - type: actionTypes.CHANGE_ACTIVE_MODULE, - payload: module, -}); - /** * @deprecated */ diff --git a/src/redux/chromeReducers.ts b/src/redux/chromeReducers.ts index d01f3e627..1ef33d061 100644 --- a/src/redux/chromeReducers.ts +++ b/src/redux/chromeReducers.ts @@ -25,7 +25,6 @@ export function loginReducer(state: ChromeState, { payload }: { payload: ChromeU return { ...state, missingIDP, - user: payload, }; } @@ -155,14 +154,6 @@ export function loadModulesSchemaReducer( }; } -export function changeActiveModuleReducer(state: ChromeState, { payload }: { payload: string }): ChromeState { - return { - ...state, - activeModule: payload, - appId: payload, - }; -} - export function setPendoFeedbackFlag( state: ChromeState, { diff --git a/src/redux/index.ts b/src/redux/index.ts index 402987f0e..e7aff72f6 100644 --- a/src/redux/index.ts +++ b/src/redux/index.ts @@ -4,7 +4,6 @@ import { accessRequestsNotificationsReducer, addQuickstartstoApp, appNavClick, - changeActiveModuleReducer, clearQuickstartsReducer, contextSwitcherBannerReducer, disableQuickstartsReducer, @@ -48,7 +47,6 @@ import { import { ADD_QUICKSTARTS_TO_APP, APP_NAV_CLICK, - CHANGE_ACTIVE_MODULE, CHROME_GET_ALL_SIDS, CHROME_GET_ALL_TAGS, CHROME_GET_ALL_WORKLOADS, @@ -97,7 +95,6 @@ const reducers = { [LOAD_NAVIGATION_LANDING_PAGE]: loadNavigationLandingPageReducer, [LOAD_LEFT_NAVIGATION_SEGMENT]: loadNavigationSegmentReducer, [LOAD_MODULES_SCHEMA]: loadModulesSchemaReducer, - [CHANGE_ACTIVE_MODULE]: changeActiveModuleReducer, [SET_PENDO_FEEDBACK_FLAG]: setPendoFeedbackFlag, [TOGGLE_FEEDBACK_MODAL]: toggleFeedbackModal, [TOGGLE_DEBUGGER_MODAL]: toggleDebuggerModal, diff --git a/src/redux/store.d.ts b/src/redux/store.d.ts index beff48101..3ae8c581b 100644 --- a/src/redux/store.d.ts +++ b/src/redux/store.d.ts @@ -1,5 +1,4 @@ import { QuickStart } from '@patternfly/quickstarts'; -import { ChromeUser } from '@redhat-cloud-services/types'; import { ChromeModule, FlagTagsFilter, NavItem, Navigation, RouteDefinition } from '../@types/types'; import { ThreeScaleError } from '../utils/responseInterceptors'; @@ -40,15 +39,8 @@ export type NotificationsPayload = { export type ChromeState = { contextSwitcherOpen: boolean; activeApp?: string; - activeModule?: string; activeProduct?: string; - /** - * @deprecated - * App id is replaced by active module. It is still required until we completely remove usage of main.yml - */ - appId?: string; missingIDP?: boolean; - user?: ChromeUser; pageAction?: string; pageObjectId?: string; modules?: { [key: string]: ChromeModule }; diff --git a/src/state/atoms.ts b/src/state/atoms.ts new file mode 100644 index 000000000..873317f5b --- /dev/null +++ b/src/state/atoms.ts @@ -0,0 +1,4 @@ +import { atom } from 'jotai'; +// setup initial chrome atoms +export const contextSwitcherOpenAtom = atom(false); +export const activeModuleAtom = atom(undefined); diff --git a/src/state/chromeStore.ts b/src/state/chromeStore.ts new file mode 100644 index 000000000..d0e94f57a --- /dev/null +++ b/src/state/chromeStore.ts @@ -0,0 +1,15 @@ +import { createStore } from 'jotai'; +import { activeModuleAtom, contextSwitcherOpenAtom } from './atoms'; + +const chromeStore = createStore(); + +// setup initial chrome store state +chromeStore.set(contextSwitcherOpenAtom, false); +chromeStore.set(activeModuleAtom, undefined); + +// globally handle subscription to activeModuleAtom +chromeStore.sub(activeModuleAtom, () => { + // console.log('activeModule in store', chromeStore.get(activeModuleAtom)); +}); + +export default chromeStore; diff --git a/src/utils/common.ts b/src/utils/common.ts index 8334a0d1b..aae68ae8f 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -39,7 +39,7 @@ export const DEFAULT_SSO_ROUTES = { }, frh: { url: ['console.openshiftusgov.com'], - sso: 'https://ocm-ra-prod-domain.auth-fips.us-gov-west-1.amazoncognito.com/login', + sso: 'https://sso.openshiftusgov.com', portal: 'https://console.openshiftusgov.com', }, ephem: { @@ -215,11 +215,7 @@ export function ITLess() { } export function ITLessCognito() { - return getEnv() === 'frh'; -} - -export function ITLessKeycloak() { - return getEnv() === 'ephem' || getEnv() === 'int' || getEnv() === 'scr' || getEnv() === 'frhStage'; + return getEnv() === 'ephem'; } export function updateDocumentTitle(title?: string, noSuffix = false) { diff --git a/src/utils/consts.ts b/src/utils/consts.ts index e5c66d79e..4b13bd206 100644 --- a/src/utils/consts.ts +++ b/src/utils/consts.ts @@ -1,4 +1,4 @@ -import { ITLess, ITLessKeycloak } from './common'; +import { ITLess } from './common'; import { AppNavigationCB, ChromeAuthOptions, GenericCB, NavDOMEvent } from '../@types/types'; import { Listener } from '@redhat-cloud-services/frontend-components-utilities/MiddlewareListener'; import { APP_NAV_CLICK, GLOBAL_FILTER_UPDATE } from '../redux/action-types'; @@ -65,7 +65,7 @@ export const activationRequestURLs = [ // Global Defaults export const defaultAuthOptions: ChromeAuthOptions = { realm: 'redhat-external', - clientId: ITLessKeycloak() ? 'console-dot' : 'cloud-services', + clientId: ITLess() ? 'console-dot' : 'cloud-services', cookieName: 'cs_jwt', }; diff --git a/src/utils/createCase.ts b/src/utils/createCase.ts index a4016a50f..d84293cd0 100644 --- a/src/utils/createCase.ts +++ b/src/utils/createCase.ts @@ -1,14 +1,14 @@ import * as Sentry from '@sentry/react'; -import logger from '../jwt/logger'; +import logger from '../auth/logger'; import URI from 'urijs'; const log = logger('createCase.js'); import { getEnvDetails, isBeta, isProd } from './common'; import { HYDRA_ENDPOINT } from './consts'; -import { spinUpStore } from '../redux/redux-config'; import { ChromeUser } from '@redhat-cloud-services/types'; -import { LibJWT } from '../auth'; import { getUrl } from '../hooks/useBundle'; +import chromeStore from '../state/chromeStore'; +import { activeModuleAtom } from '../state/atoms'; // Lit of products that are bundles const BUNDLE_PRODUCTS = [ @@ -63,15 +63,14 @@ async function getAppInfo(activeModule: string) { } async function getProductData() { - const { store } = spinUpStore(); - const activeModule = store.getState().chrome.activeModule || ''; - const appData = await getAppInfo(activeModule); + const activeModule = chromeStore.get(activeModuleAtom); + const appData = await getAppInfo(activeModule ?? ''); return appData; } export async function createSupportCase( userInfo: ChromeUser['identity'], - libjwt: LibJWT, + token: string, fields?: { caseFields: Record; } @@ -85,7 +84,6 @@ export async function createSupportCase( log('Creating a support case'); - const token = await libjwt.initPromise.then(() => libjwt.jwt.getUserInfo().then(() => libjwt.jwt.getEncodedToken())); fetch(caseUrl, { method: 'POST', headers: { diff --git a/src/utils/debugFunctions.ts b/src/utils/debugFunctions.ts index 9021e2160..cd9b8c890 100644 --- a/src/utils/debugFunctions.ts +++ b/src/utils/debugFunctions.ts @@ -8,7 +8,7 @@ const debugFunctions = { remediationsDebug: () => functionBuilder('remediations:debug', true), invTags: () => functionBuilder('rhcs-tags', true), shortSession: () => functionBuilder('chrome:jwt:shortSession', true), - jwtDebug: () => functionBuilder('chrome:jwt:debug', true), + jwtDebug: () => functionBuilder('chrome:auth:debug', true), reduxDebug: () => functionBuilder('chrome:redux:debug', true), forcePendo: () => functionBuilder('forcePendo', true), disableSegment: () => functionBuilder('chrome:segment:disable', true), diff --git a/src/utils/iqeEnablement.ts b/src/utils/iqeEnablement.ts index f8d4adf27..74392d51c 100644 --- a/src/utils/iqeEnablement.ts +++ b/src/utils/iqeEnablement.ts @@ -1,23 +1,24 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable prefer-rest-params */ import type { Store } from 'redux'; -import { LibJWT, crossAccountBouncer } from '../auth'; import { setGatewayError } from '../redux/actions'; import { get3scaleError } from './responseInterceptors'; +import crossAccountBouncer from '../auth/crossAccountBouncer'; // TODO: Refactor this file to use modern JS let xhrResults: XMLHttpRequest[] = []; let fetchResults: Record = {}; -const DENINED_CROSS_CHECK = 'Access denied from RBAC on cross-access check'; +const DENIED_CROSS_CHECK = 'Access denied from RBAC on cross-access check'; +const AUTH_ALLOWED_ORIGINS = [location.origin, 'https://api.openshift.com', 'https://api.stage.openshift.com']; const checkOrigin = (path: URL | Request | string = '') => { - if (path.constructor.name === 'URL') { - return (path as URL).origin === location.origin; - } else if (path.constructor.name === 'Request') { - return (path as Request).url.includes(location.origin); - } else if (path.constructor.name === 'String') { - return (path as string).includes(location.origin) || !(path as string).startsWith('http'); + if (path instanceof URL) { + return AUTH_ALLOWED_ORIGINS.includes(path.origin); + } else if (path instanceof Request) { + return AUTH_ALLOWED_ORIGINS.some((origin) => path.url.includes(origin)); + } else if (typeof path === 'string') { + return AUTH_ALLOWED_ORIGINS.some((origin) => path.includes(origin)) || !path.startsWith('http'); } return true; @@ -38,7 +39,7 @@ const spreadAdditionalHeaders = (options: RequestInit | undefined) => { return additionalHeaders; }; -function init(store: Store, libJwt?: () => LibJWT | undefined) { +export function init(store: Store, token: string) { const open = window.XMLHttpRequest.prototype.open; const send = window.XMLHttpRequest.prototype.send; const setRequestHeader = window.XMLHttpRequest.prototype.setRequestHeader; @@ -74,10 +75,10 @@ function init(store: Store, libJwt?: () => LibJWT | undefined) { // must use function here because arrows dont "this" like functions window.XMLHttpRequest.prototype.send = function sendReplacement() { - if (checkOrigin((this as XMLHttpRequest & { _url: string })._url) && libJwt?.()?.jwt.isAuthenticated()) { + if (checkOrigin((this as XMLHttpRequest & { _url: string })._url)) { if (!authRequests.has((this as XMLHttpRequest & { _url: string })._url)) { // Send Auth header, it will be changed to Authorization later down the line - this.setRequestHeader('Auth', `Bearer ${libJwt?.()?.jwt.getEncodedToken()}`); + this.setRequestHeader('Auth', `Bearer ${token}`); } } // eslint-disable-line func-names @@ -87,7 +88,7 @@ function init(store: Store, libJwt?: () => LibJWT | undefined) { this.onload = function () { if (this.status >= 400) { const gatewayError = get3scaleError(this.response); - if (this.status === 403 && this.responseText.includes(DENINED_CROSS_CHECK)) { + if (this.status === 403 && this.responseText.includes(DENIED_CROSS_CHECK)) { crossAccountBouncer(); // check for 3scale error } else if (gatewayError) { @@ -107,8 +108,8 @@ function init(store: Store, libJwt?: () => LibJWT | undefined) { const tid = Math.random().toString(36); const request: Request = new Request(input, init); - if (checkOrigin(input) && libJwt?.()?.jwt.isAuthenticated() && !request.headers.has('Authorization')) { - request.headers.append('Authorization', `Bearer ${libJwt?.()?.jwt.getEncodedToken()}`); + if (checkOrigin(input) && !request.headers.has('Authorization')) { + request.headers.append('Authorization', `Bearer ${token}`); } const prom = oldFetch.apply(this, [request, ...rest]); diff --git a/src/utils/isAnsibleTrialFlagActive.ts b/src/utils/isAnsibleTrialFlagActive.ts index c6f5c8c7a..e7c6043ba 100644 --- a/src/utils/isAnsibleTrialFlagActive.ts +++ b/src/utils/isAnsibleTrialFlagActive.ts @@ -1,4 +1,4 @@ -import logger from '../jwt/logger'; +import logger from '../auth/logger'; export const ANSIBLE_TRIAL_FLAG = 'chrome.ansible.trial'; const TRIAL_DURATION = 10 * 60 * 1000; // 10 minutes diff --git a/src/utils/useAccessRequestNotifier.ts b/src/utils/useAccessRequestNotifier.ts index b8197c158..d26020630 100644 --- a/src/utils/useAccessRequestNotifier.ts +++ b/src/utils/useAccessRequestNotifier.ts @@ -1,12 +1,13 @@ import axios from 'axios'; -import { useEffect, useRef } from 'react'; +import { useContext, useEffect, useRef } from 'react'; import { batch, useDispatch, useSelector } from 'react-redux'; import { REQUESTS_COUNT, REQUESTS_DATA } from './consts'; import { markAccessRequestNotification, updateAccessRequestsNotifications } from '../redux/actions'; import { ReduxState } from '../redux/store'; +import ChromeAuthContext from '../auth/ChromeAuthContext'; const useAccessRequestNotifier = (): [ReduxState['chrome']['accessRequests'], (id: string | number) => void] => { - const user = useSelector(({ chrome }: ReduxState) => chrome?.user); + const { user } = useContext(ChromeAuthContext); const isMounted = useRef(false); const state = useSelector(({ chrome: { accessRequests } }: ReduxState) => accessRequests); const dispatch = useDispatch();