Source

services/ConfigManager.tsx

import * as React from 'react'
import {useState, useEffect, useContext} from "react";
import initialiseApplication from "../tools/AppInitialiser";

type Config = {
    environment: any
    appearance?: any
    header?: any
    footer?: any
    banner?: any
    support: any
    apis: any
    authentication: any
    env: string
    basePath: string,
    application?: any
}

export const ConfigContext = React.createContext<Config | null>(null)

/**
 * The `ConfigContextProvider` loads the application config using the {@link initialiseApplication} function from the
 * `AppInitialiser`. The config is then made available to the rest of the application through the context provider.
 *
 * @property {string} basePath Base path of the application (e.g. App-specific hosting URL)
 * @property {*} children React child components that the context has been wrapped around
 * @component
 *
 * @category Context Providers
 */
export const ConfigContextProvider = (props: any) => {
    // State for each of the configuration sections
    const [environment, setEnvironment] = useState<any>(undefined)
    const [appearance, setAppearance] = useState<any>(undefined)
    const [header, setHeader] = useState(undefined)
    const [footer, setFooter] = useState(undefined)
    const [banner, setBanner] = useState(undefined)
    const [support, setSupport] = useState<any>(undefined)
    const [apis, setApis] = useState<any>([])
    const [application, setApplication] = useState<any>(undefined)
    const [authentication, setAuthentication] = useState<any>(undefined)
    const [basePath, setBasePath] = useState<any>(props.basePath || '/')

    // Separate state for environment type (as there is also an environment config)
    const [env, setEnv] = useState('test')

    // Flag for whether the config loaded successfully or not
    const [configLoaded, setConfigLoaded] = useState(false)
    const [configLoadFailed, setConfigLoadFailed] = useState(false)

    //TODO: Add config fixing to handle legacy oidc client keys (e.g. make them come out of this process in a single place)
    useEffect(() => {
        // Define async config load
        async function loadConfig() {
            setBasePath(props.basePath)

            initialiseApplication(props.basePath).then(config => {
                if (config) {
                    // Load the config root object
                    //@ts-ignore
                    let configuration: Config = config.configuration

                    // Set the environment type (test, uat, prod)
                    setEnv(configuration.environment.type)

                    // Set API configurations
                    setApis(configuration.apis)

                    // Set app configurations
                    setAppearance(configuration.appearance)
                    setEnvironment(configuration.environment)
                    setHeader(configuration.header)
                    setFooter(configuration.footer)
                    setBanner(configuration.banner)
                    setSupport(configuration.support)

                    // Set the application specific config
                    // i.e. config specific to the app internals
                    setApplication(configuration.application)

                    // Load authentication configuration
                    // Defaults client and authority to '' if missing
                    // Handles legacy declarations (environment.oidcClient)
                    let authConfig = configuration.authentication
                    if (authConfig) {
                        setAuthentication(authConfig)
                    } else {
                        setAuthentication({})
                    }

                    setConfigLoaded(true)
                }
            }).catch((err) => {
                console.log("Failed to load config")
                console.log(err)
                setConfigLoadFailed(true)
            })
        }

        // Call config load function
        loadConfig()
    }, [])

    // Write out state changes to new object, triggers context rerenders as the object changes
    const contextValue: Config = configLoaded ? {
        environment,
        appearance,
        header,
        footer,
        support,
        apis,
        application,
        authentication,
        env,
        basePath,
        banner
    } : null

    return (
        <ConfigContext.Provider value={contextValue}>
            {configLoaded || configLoadFailed ? props.children : null}
        </ConfigContext.Provider>
    )
}

/**
 * Custom hook for access SPA config data.
 *
 * This hook exposes the application configuration data.
 *
 * The structure of the config data is clarified [here]{@tutorial configfile}
 *
 * ```
 * const config = useJcuConfig()
 * ```
 *
 * @category Hooks
 */
export function useJcuConfig(): Config {
    //TODO: JSDOC

    //TODO: Add config load checks to hook
    const config = useContext(ConfigContext)

    return config
}