Skip to content
This repository has been archived by the owner on May 3, 2024. It is now read-only.

Latest commit

 

History

History
629 lines (516 loc) · 23 KB

App-Configuration.md

File metadata and controls

629 lines (516 loc) · 23 KB

👈 Return to Overview

App Configuration

The App Configuration API, appConfig allows a module to specify a selection of configuration options for Holocron Modules.

// Force tree shaking appConfig away in client bundles
if (!global.BROWSER) {
  MyModule.appConfig = {
    /* Root Module Specific */
    provideStateConfig,
    csp,
    corsOrigins,
    configureRequestLog,
    extendSafeRequestRestrictedAttributes,
    pwa,
    createSsrFetch,
    eventLoopDelayThreshold,
    eventLoopDelayPercentile,
    errorPageUrl,
    dnsCache,
    redirectAllowList,
    /* Child Module Specific */
    validateStateConfig,
    requiredSafeRequestRestrictedAttributes,
  };
}

In practice, we declare an appConfig as a static attached to the parent React Component in a One App Module. The appConfig settings are intended for the Server only and is invoked and validated on the initial load of the Module on the Server.

⚠️ Please Consider

For performance and security purposes, we recommend wrapping this logic in an if (!global.BROWSER) block, to only bundle appConfig inside the Node Bundle (e.g.mymodule.node.js) rather than the Browser Bundles (e.g. mymodule.browser.js or mymodule.legacy.js). This is good practice for security and bundle size considerations.

src/index.js

import MyModule from './components/MyModule';

if (!global.BROWSER) {
  // To prevent server side configuration from being exposed,
  // make sure to isolate your configuration since it may be
  // sensitive and should be guarded with `!global.BROWSER`

  // eslint-disable-next-line global-require -- do not include in browser bundle
  MyModule.appConfig = require('./appConfig').default;
}

export default MyModule;

Contents

provideStateConfig

Module Type

  • ✅ Root Module
  • 🚫 Child Module

Shape

if (!global.BROWSER) {
  Module.appConfig = {
    provideStateConfig: {
      [settingName]: {
        client: {
          [environmentLevel]: String,
        },
        server: {
          [environmentLevel]: String,
        },
      },
    },
  };
}

The provideStateConfig directive is useful for supplying string-based key value settings per runtime (e.g. client or server) and per environmentLevel (e.g. QA, Prod, etc). The environmentLevel is specified in the ONE_CONFIG_ENV environment variable when running the Server.

⚠️ Please Consider

Server specific values used within state config are not isolated from the client side, as they are used while rendering server side Holocron modules. Please use caution not to expose any sensitive data in a Holocron module while using state.get('config') for any server side rendering in your module.

In practice, the state config supplied by a Root Module may look like this shape:

if (!global.BROWSER) {
  Module.appConfig = {
    provideStateConfig: {
      someApiUrl: {
        client: {
          development: 'https://internet-origin-dev.example.com/some-api/v1',
          qa: 'https://internet-origin-qa.example.com/some-api/v1',
          production: 'https://internet-origin.example.com/some-api/v1',
        },
        server: {
          development: 'https://intranet-origin-dev.example.com/some-api/v1',
          qa: 'https://intranet-origin-qa.example.com/some-api/v1',
          production: 'https://intranet-origin.example.com/some-api/v1',
        },
      },
      someBooleanValue: {
        client: true,
        server: true,
      },
      someNumberValue: {
        client: {
          development: 480000,
          qa: 480000,
          production: 480000,
        },
        server: {
          development: 480000,
          qa: 480000,
          production: 480000,
        },
      },
    },
  };
}

Based on environmentLevel, the String values are injected into the global config reducer in One App's global Redux state. These values may be accessed by Modules using Redux's mapStateToProps.

📘 More Information

csp

Module Type

  • ✅ Root Module
  • 🚫 Child Module

⚠️ Required Directive

👮Security Feature: Limits the scripts and assets allowed to load.

Shape

if (!global.BROWSER) {
  RootModule.appConfig = {
    csp: String,
  };
}

The csp static String should be a valid Content Security Policy (CSP) for your application which will be passed on to the HTML markup rendered by the Browser.

We recommend using something like content-security-policy-builder to create your CSP string. This is set up automatically when you use the One App module generator.

You'll still want the ability to run One App and serve modules locally without running into CSP errors. When NODE_ENV=development, One App will dynamically add both your computer's IP address and localhost to the root module's CSP. Please note that the script-src and connect-src directives must already be defined in your CSP in order for this to work properly.

📘 More Information

corsOrigins

Module Type

  • ✅ Root Module
  • 🚫 Child Module

👮Security Feature: Limits the reachable origins for fetching data and assets.

Shape

if (!global.BROWSER) {
  Module.appConfig = {
    corsOrigins: [String],
  };
}

The corsOrigins directive accepts an array of String URL origin domains. This will allow requests from those origins to make POST requests to the server.

In practice, this allows POST requests from given origins to return partially rendered Modules.

📘 More Information

pwa

Module Type

  • ✅ Root Module
  • 🚫 Child Module

⚠️ The PWA feature is behind a feature flag ONE_SERVICE_WORKER that needs to be set to "true" during runtime.

The pwa directive is how we configure the service worker and various PWA features. There are three distinct service workers with their given purpose. To use the standard service worker, we can set the serviceWorker to true. If there was any failure caused by the service worker, we can use the recovery service worker with recoveryMode set to true if we wish to reset browser clients. In the event we want to purge the service worker and remove any existing service workers on browser clients, We can use the escape hatch worker by setting escapeHatch to true.

While in normal circumstances we would want to use the standard serviceWorker, recoveryMode and escapeHatch are there to help the origin that one-app is deployed on; to safely transition their service worker installation on existing clients that have them. If we have a healthy service worker in place and simply want to start removing our service worker for any given reason, we would enable the escapeHatch to segway clients out of the current service worker that is registered. In the case that there is a malfunction with the service worker, we can use recoveryMode to reinstall the service worker with a safe alternative to avoid zombie workers from persisting. Afterwards, we can either opt back in to using the standard service worker, or the escapeHatch to remove it altogether.

There is a precedence to which of the three flags is respected and the order goes:

  1. serviceWorker
  2. escapeHatch
  3. recoveryMode

What this means is, if we set { recoveryMode: true, escapeHatch: true }, escapeHatch will be honored (and enabled) instead of recoveryMode. To enable either of these modes, make sure to omit { serviceWorker: true } when desiring to set either of the two optional modes since it is applied.

For the variety of service workers available, we have control to set its scope with the desired pathname and assign what url base the service worker can oversee.

Web App Manifest

The webManifest key is used to set up a Web App Manifest as part of the PWA group of technologies. It allows one-app to be installed onto a device with support for a more native experience using web technologies. The webManifest can also be a Function and is passed the clientConfig as the only argument.

App Install and Offline Capability

One App supports offline navigation capabilities when the network is unavailable and the service worker is enabled. With a configured web app manifest and root module, One App clients can be installed as a PWA across devices and platforms. To enable installing an app, please set the value for start_url, icons and display in the web manifest. If desired, a route can be used to match the start_url and used when an installed PWA is opened directly from the device.

Caching

When the service worker is enabled, both Holocron module and One App resources are cached in the browser using Cache Storage and Cache API. The cached resources are available when offline and each resource in the cache is validated (or invalidated) by meta data associated with each resource that is cached and/or requested. There are four meta properties that are used for invalidation on a per module basis in tandem to One App static resources; version, locale (if applicable - language packs, etc) and the Holocron module map clientCacheRevision key. If a resource is invalidated for a newer or older version for example, the service worker will remove the stale resource from the cache and place in the recently requested resource.

Shape

if (!global.BROWSER) {
  RootModule.appConfig = {
    pwa: {
      // having enabled set to true will enable the service worker and will be
      // registered when one-app is loaded in the browser
      serviceWorker: true,
      // in the case we need to reset our clients from a faulty service worker
      // we can use the noop worker to replace the older worker
      recoveryMode: false,
      // if we want to remove the service worker altogether, we can deploy
      // an escape hatch worker to immediately remove itself on install
      escapeHatch: false,
      // we can optionally define a scope to use with the service worker
      scope: '/',
      // the web app manifest can be directly incorporated in the PWA config
      webManifest: (clientConfig) => ({
        // the full name is the official name of a given PWA
        name: 'My App Name',
        // the short name is used by mobile devices to label your home screen icon
        short_name: 'My App',
        // the description is a good piece of meta-data to include for a short description
        // which can be used with presenting your PWA
        description: 'My PWA app.',
        // relative to the root of the domain
        start_url: '/home',
        // when installing your PWA, standalone display will have a native feel
        // and removes the browser bar for full screen
        display: 'standalone',
        // the background color
        background_color: '#fff',
        // the theme color is what covers native UI elements that host the PWA
        theme_color: '#000',
        // icons can perform many purposes, including the splash screen when a web app is loading
        icons: [
          {
            src: `${clientConfig.cdnUrl}/my-splash-icon.png`,
            sizes: '48x48',
            type: 'image/png',
          },
        ],
      }),
    },
  };
}

📘 More Information

configureRequestLog

Module Type

  • ✅ Root Module
  • 🚫 Child Module

Shape

if (!global.BROWSER) {
  Module.appConfig = {
    configureRequestLog: ({
      req, // Fastify request object
      log, // One App Log Shape
    }) => ({
      // returns reshaped log object
    }),
  };
}

The configureRequestLog directive accepts a callback that takes Express's req and One App's log object. This allows for customizing the log object based on req parameters to add additional metadata to the logger.

Log Shape

{
  type: 'request',
  request: {
    direction,
    protocol,
    address: {
      uri,
    },
    metaData: {
      method,
      correlationId,
      host,
      referrer,
      userAgent,
      location,
      forwarded,
      forwardedFor,
      locale,
    },
    timings: {
      // See values in https://www.w3.org/TR/navigation-timing/
    },
    statusCode,
    statusText,
  },
};

📘 More Information

extendSafeRequestRestrictedAttributes

Module Type

  • ✅ Root Module
  • 🚫 Child Module

👮Security Feature: Limits headers and cookies from being passed to Redux's initial state.

Shape

if (!global.BROWSER) {
  Module.appConfig = {
    extendSafeRequestRestrictedAttributes: {
      headers: [String],
      cookies: [String],
    },
  };
}

The extendSafeRequestRestrictedAttributes directive accepts a list of cookie names in cookies and header identifiers in headers. By default all cookies and headers are removed from the Express req object as a security precaution. Named cookie and header identifiers may be added to extendSafeRequestRestrictedAttributes to allow whitelisted cookies and headers to remain on the Express req object. The sanitized req object will be passed into the Vitruvius method, buildInitialState when constructing Redux's initial state on server-side render.

📘 More Information

createSsrFetch

Module Type

  • ✅ Root Module
  • 🚫 Child Module

Shape

if (!global.BROWSER) {
  RootModule.appConfig = {
    createSsrFetch: ({
      req, // Fastify request object
      res, // Fastify reply object
    }) => (fetch) => (fetchUrl, fetchOpts) => Promise,
  };
}

createSsrFetch allows for customizing the fetch client used in one-app to perform server-side requests.

For example, you may wish to forward cookies or headers from the initial page load request to all the requisite SSR API requests.

📘 More Information

eventLoopDelayThreshold

Module Type

  • ✅ Root Module
  • 🚫 Child Module

Shape

if (!global.BROWSER) {
  Module.appConfig = {
    eventLoopDelayThreshold: Number,
  };
}

The eventLoopDelayThreshold directive accepts a number representing the threshold of the event loop delay (in milliseconds) before opening the circuit. Once the circuit is open, it will remain open for 10 seconds and close at that time pending the event loop delay. The default value is 250. If you desire to disable the event loop delay portion of the circuit breaker, set this value to Infinity. The circuit will also open if the error rate exceeds 10%. In practice, eventLoopDelayThreshold allows for tuning server side rendering (SSR) of Modules. We may increase request throughput by temporarily disabling SSR at high load through event loop delay monitoring.

This is disabled when NODE_ENV=development

📘 More Information

eventLoopDelayPercentile

Module Type

  • ✅ Root Module
  • 🚫 Child Module

Shape

if (!global.BROWSER) {
  Module.appConfig = {
    eventLoopDelayPercentile: Number,
  };
}

The eventLoopDelayPercentile directive accepts an integer 1-100 representing the percentile upon which the eventLoopDelayThreshold must be crossed before opening the circuit. The default value is 100 which represents the maximum event loop delay. This default will likely change in future major versions as to not base the circuit breaker state on an outlier, but rather a trend.

errorPageUrl

Module Type

  • ✅ Root Module
  • 🚫 Child Module

Shape

if (!global.BROWSER) {
  Module.appConfig = {
    errorPageUrl: String,
  };
}

The errorPageUrl directive is useful for supplying a URL to a custom error page. This URL should return a Content-Type of text/html and a Content-Length of less than 244000. The URL will get called when your root module is loaded and is rendered if and when an error occurs. It is recommended that you keep the custom error page as small as possible.

dnsCache

Module Type

  • ✅ Root Module
  • 🚫 Child Module

Shape

if (!global.BROWSER) {
  Module.appConfig = {
    dnsCache: {
      enabled: Boolean,
      maxTtl: Number,
    },
  };
}

The dnsCache option allows for enabling application-level DNS caching. It is disabled by default. When enabled, the default max TTL is Infinity.

maxTtl affects the maximum lifetime of the entries received from the specified DNS server (TTL in seconds). If set to 0, it will make a new DNS query each time. It is not recommended to be lower than the DNS server response time in order to prevent bottlenecks.

redirectAllowList

Module Type

  • ✅ Root Module
  • 🚫 Child Module

Shape

if (!global.BROWSER) {
  Module.appConfig = {
    redirectAllowList: ['string'],
  };
}

The redirectAllowList config option allows you to configure a list of domains that the server is allowed to redirect to when the abortComposeModules option is used.. Each URL in this list should include the protocol (https://).

You can also provide wildcard matches (*) to allow any subdomain. Example:

if (!global.BROWSER) {
  Module.appConfig = {
    redirectAllowList: ['https://*.example.com'],
  };
}

If a redirect is attempted to a URL that is not in the allow list, One App will return with an error page, and subsequently throw an error in the server.

If this list is not configured, the server will always allow redirects. In future versions of One App, this behavior will change to always require a redirectAllowList.

validateStateConfig

Module Type

  • 🚫 Root Module
  • ✅ Child Module

Shape

if (!global.BROWSER) {
  Module.appConfig = {
    validateStateConfig: {
      [settingName]: {
        server: {
          validate(settingValue) {
            // Throw an error or return undefined
          },
        },
        client: {
          validate(settingValue) {
            // Throw an error or return undefined
          },
        },
      },
    },
  };
}

The validateStateConfig allows a Child Module to validate settings passed from provideStateConfig. Each settingName object accepts a validate(settingValue) method per server and client key. The validate function may throw an Error or return undefined depending on validity of the value supplied to the Module on load.

If an Error is thrown, the Server will fail to startup or if already running will prevent Holocron from loading the Module dynamically.

📘 More Information

requiredSafeRequestRestrictedAttributes

Module Type

  • 🚫 Root Module
  • ✅ Child Module

👮Security Feature: Limits headers and cookies from being passed to Redux's initial state.

Shape

if (!global.BROWSER) {
  Module.appConfig = {
    requiredSafeRequestRestrictedAttributes: {
      headers: [String],
      cookies: [String],
    },
  };
}

The requiredSafeRequestRestrictedAttributes allows a Child Module to validate settings passed from extendSafeRequestRestrictedAttributes. Each whitelisted header in headers array and cookie in cookies array will be checked against the Root Module's extendSafeRequestRestrictedAttributes on the loading of the Child Module. If this does not match entries previously made in Root Module's extendSafeRequestRestrictedAttributes, the Child Module will fail to load.

If an Error is thrown due to missing required cookies or headers, the Server will either fail to startup or if already running will prevent Holocron from loading the Module dynamically.

📘 More Information

📘 More Information

☝️ Return To Top