diff --git a/src/components/clock.js b/src/components/clock.js
index e5fc4fa7..e88deb3a 100644
--- a/src/components/clock.js
+++ b/src/components/clock.js
@@ -13,7 +13,7 @@
import React from 'react';
import moment from "moment-timezone";
import FragmentParser from "./fragment-parser";
-import {getTimeServiceUrl} from '../utils/methods';
+import {getTimeServiceUrl} from '../utils/config';
/**
* class Clock
@@ -113,6 +113,9 @@ class Clock extends React.Component {
getServerTime = () => {
const timeServiceUrl = getTimeServiceUrl();
+ // No endpoint configured: go straight to the local-clock fallback
+ // instead of fetching '' or "undefined".
+ if (!timeServiceUrl) return Promise.reject(null);
return fetch(`${timeServiceUrl}`).then(async (response) => {
if (response.status === 200) {
return response.json();
diff --git a/src/components/exclusive-wrapper.js b/src/components/exclusive-wrapper.js
index 5b3a4b7b..7da5e502 100644
--- a/src/components/exclusive-wrapper.js
+++ b/src/components/exclusive-wrapper.js
@@ -12,6 +12,7 @@
**/
import React from 'react'
+import { getExclusiveSections } from '../utils/config';
export default class Exclusive extends React.Component {
@@ -24,7 +25,7 @@ export default class Exclusive extends React.Component {
showField() {
let {name} = this.props;
- let exclusiveSections = window.EXCLUSIVE_SECTIONS;
+ let exclusiveSections = getExclusiveSections();
return exclusiveSections ? exclusiveSections.includes(name) : false;
}
diff --git a/src/components/security/__tests__/get-user-info.test.js b/src/components/security/__tests__/get-user-info.test.js
index 669fb02e..9e50baf1 100644
--- a/src/components/security/__tests__/get-user-info.test.js
+++ b/src/components/security/__tests__/get-user-info.test.js
@@ -11,7 +11,8 @@ jest.mock("../../../utils/actions", () => ({
startLoading: jest.fn(() => ({ type: "START_LOADING" })),
stopLoading: jest.fn(() => ({ type: "STOP_LOADING" })),
}));
-jest.mock("../../../utils/methods", () => ({
+jest.mock("../../../utils/config", () => ({
+ ...jest.requireActual("../../../utils/config"),
buildAPIBaseUrl: jest.fn((p) => `BASE${p}`),
getAllowedUserGroups: jest.fn(() => ""),
}));
diff --git a/src/components/security/abstract-auth-callback-route-v2.js b/src/components/security/abstract-auth-callback-route-v2.js
index 5374001f..5ff63453 100644
--- a/src/components/security/abstract-auth-callback-route-v2.js
+++ b/src/components/security/abstract-auth-callback-route-v2.js
@@ -17,12 +17,12 @@ import URI from "urijs";
import {
doLogin,
emitAccessToken,
- getOAuth2Flow,
RESPONSE_TYPE_IMPLICIT,
RESPONSE_TYPE_CODE,
validateIdToken, getAuthInfo
} from "./methods";
import {getCurrentPathName, getCurrentHref} from '../../utils/methods';
+import {getOAuth2Flow} from '../../utils/config';
const AbstractAuthorizationCallbackRouteV2 = ({issuer, audience, location, callback, redirectToError, onUserAuth}) => {
// we only use this for the redirectToError, so its initial state should be true
diff --git a/src/components/security/abstract-auth-callback-route.js b/src/components/security/abstract-auth-callback-route.js
index 880e811b..624fd0eb 100644
--- a/src/components/security/abstract-auth-callback-route.js
+++ b/src/components/security/abstract-auth-callback-route.js
@@ -15,12 +15,12 @@ import URI from "urijs";
import {
doLogin,
emitAccessToken,
- getOAuth2Flow,
RESPONSE_TYPE_IMPLICIT,
RESPONSE_TYPE_CODE,
validateIdToken
} from "./methods";
import {getCurrentPathName, getCurrentHref} from '../../utils/methods';
+import {getOAuth2Flow} from '../../utils/config';
class AbstractAuthorizationCallbackRoute extends React.Component {
diff --git a/src/components/security/actions.js b/src/components/security/actions.js
index 65a84d8d..b417699b 100644
--- a/src/components/security/actions.js
+++ b/src/components/security/actions.js
@@ -13,7 +13,7 @@
import T from "i18n-react/dist/i18n-react";
import {authErrorHandler, createAction, getRequest, showMessage, startLoading, stopLoading} from "../../utils/actions";
-import {buildAPIBaseUrl, getAllowedUserGroups} from '../../utils/methods';
+import {buildAPIBaseUrl, getAllowedUserGroups, parseUserGroups} from '../../utils/config';
import { getAccessToken, storeAuthInfo, initLogOut} from './methods';
/**
@@ -50,8 +50,7 @@ export const doLogout = (backUrl) => (dispatch, getState) => {
export const getUserInfo = (expand = 'groups', fields='', backUrl = null, history = null, errorHandler = null ) =>
async (dispatch, getState) => {
- let AllowedUserGroups = getAllowedUserGroups();
- AllowedUserGroups = AllowedUserGroups !== '' ? AllowedUserGroups.split(' ') : [];
+ let AllowedUserGroups = parseUserGroups(getAllowedUserGroups());
let {loggedUserState} = getState();
let {member} = loggedUserState;
diff --git a/src/components/security/methods.js b/src/components/security/methods.js
index 729a2772..7cc55e14 100644
--- a/src/components/security/methods.js
+++ b/src/components/security/methods.js
@@ -18,6 +18,13 @@ import URI from "urijs";
import IdTokenVerifier from "idtoken-verifier";
import {SET_LOGGED_USER} from "./actions";
import {getRandomBytes, getSHA256} from "../../utils/crypto";
+import {
+ getOAuth2ClientId,
+ getOAuth2Flow,
+ useOAuth2RefreshToken,
+ getOAuth2IDPBaseUrl,
+ getOAuth2Scopes,
+} from "../../utils/config";
import {
AUTH_ERROR_ACCESS_TOKEN_EXPIRED,
@@ -32,6 +39,8 @@ import {
AUTH_ERROR_MISSING_NONCE_PARAM,
} from "./constants";
+export { getOAuth2ClientId, getOAuth2Flow, useOAuth2RefreshToken, getOAuth2IDPBaseUrl, getOAuth2Scopes } from "../../utils/config";
+
/**
* @ignore
*/
@@ -51,7 +60,6 @@ const PKCE = 'pkce';
const ID_TOKEN = 'idToken';
const BACK_ULR_PARAM_NAME = 'BackUrl';
-
/**
*
* @param backUrl
@@ -448,7 +456,6 @@ export const clearAccessToken = async () => {
}
}
-
export const refreshAccessToken = async (refresh_token) => {
let baseUrl = getOAuth2IDPBaseUrl();
@@ -569,41 +576,6 @@ export const getIdToken = () => {
return null;
};
-export const getOAuth2ClientId = () => {
- if (typeof window !== 'undefined') {
- return window.OAUTH2_CLIENT_ID;
- }
- return null;
-};
-
-export const getOAuth2Flow = () => {
- if (typeof window !== 'undefined') {
- return window.OAUTH2_FLOW || "token id_token";
- }
- return "token id_token";
-}
-
-export const useOAuth2RefreshToken = () => {
- if (typeof window !== 'undefined') {
- return new Boolean(window.OAUTH2_USE_REFRESH_TOKEN || true);
- }
- return true;
-}
-
-export const getOAuth2IDPBaseUrl = () => {
- if (typeof window !== 'undefined') {
- return window.IDP_BASE_URL;
- }
- return null;
-};
-
-export const getOAuth2Scopes = () => {
- if (typeof window !== 'undefined') {
- return window.SCOPES;
- }
- return null;
-};
-
export const initLogOut = () => {
let location = getCurrentLocation();
location.replace(getLogoutUrl(getIdToken()).toString());
diff --git a/src/components/security/reducers.js b/src/components/security/reducers.js
index bfffbcca..6285651d 100644
--- a/src/components/security/reducers.js
+++ b/src/components/security/reducers.js
@@ -23,7 +23,8 @@ import {
import IdTokenVerifier from 'idtoken-verifier';
-import {clearAuthInfo, getIdToken, getOAuth2ClientId, getOAuth2IDPBaseUrl} from './methods';
+import {clearAuthInfo, getIdToken} from './methods';
+import {getOAuth2ClientId, getOAuth2IDPBaseUrl} from '../../utils/config';
const DEFAULT_STATE = {
isLoggedUser: false,
diff --git a/src/utils/__tests__/config-no-window.test.js b/src/utils/__tests__/config-no-window.test.js
new file mode 100644
index 00000000..d8649e6d
--- /dev/null
+++ b/src/utils/__tests__/config-no-window.test.js
@@ -0,0 +1,54 @@
+/**
+ * @jest-environment node
+ *
+ * With no window (server rendering) every getter returns its documented
+ * default, and a configured value is still honored.
+ */
+import {
+ setConfig,
+ buildAPIBaseUrl,
+ getTimeServiceUrl,
+ getAllowedUserGroups,
+ getOAuth2ClientId,
+ getOAuth2Flow,
+ useOAuth2RefreshToken,
+ getOAuth2IDPBaseUrl,
+ getOAuth2Scopes,
+ getExclusiveSections,
+} from "../config";
+
+describe("config getters without a window", () => {
+ // The jest config plants a `window` global even in the node environment;
+ // the getters check `typeof window` at call time, so remove it here.
+ let savedWindow;
+ beforeAll(() => {
+ savedWindow = global.window;
+ delete global.window;
+ });
+ afterAll(() => {
+ global.window = savedWindow;
+ });
+ afterEach(() => setConfig({}));
+
+ test("there is no window in this environment", () => {
+ expect(typeof window).toBe("undefined");
+ });
+
+ test("each getter returns its no-window default", () => {
+ expect(buildAPIBaseUrl("/x")).toBe(null);
+ expect(getTimeServiceUrl()).toBe(null);
+ expect(getAllowedUserGroups()).toBe(null);
+ expect(getOAuth2ClientId()).toBe(null);
+ expect(getOAuth2Flow()).toBe("token id_token");
+ expect(useOAuth2RefreshToken()).toBe(true);
+ expect(getOAuth2IDPBaseUrl()).toBe(null);
+ expect(getOAuth2Scopes()).toBe(null);
+ expect(getExclusiveSections()).toBe(undefined);
+ });
+
+ test("a configured value is returned without a window", () => {
+ setConfig({ apiBaseUrl: "https://cfg.test", oauth2Flow: "code" });
+ expect(buildAPIBaseUrl("/x")).toBe("https://cfg.test/x");
+ expect(getOAuth2Flow()).toBe("code");
+ });
+});
diff --git a/src/utils/__tests__/config-reads.test.js b/src/utils/__tests__/config-reads.test.js
new file mode 100644
index 00000000..8337b77a
--- /dev/null
+++ b/src/utils/__tests__/config-reads.test.js
@@ -0,0 +1,109 @@
+/**
+ * Every app-level setting uicore needs is read from a window global. These
+ * tests pin which global each getter reads and what it returns when the
+ * global is unset.
+ */
+import React from "react";
+import Enzyme, { mount } from "enzyme";
+import Adapter from "enzyme-adapter-react-16";
+import {
+ buildAPIBaseUrl,
+ getTimeServiceUrl,
+ getAllowedUserGroups,
+ getOAuth2ClientId,
+ getOAuth2Flow,
+ useOAuth2RefreshToken,
+ getOAuth2IDPBaseUrl,
+ getOAuth2Scopes,
+} from "../config";
+import * as utilsMethods from "../methods";
+import * as securityMethods from "../../components/security/methods";
+import Exclusive from "../../components/exclusive-wrapper";
+
+Enzyme.configure({ adapter: new Adapter() });
+
+const GLOBALS = [
+ "API_BASE_URL", "TIMEINTERVALSINCE1970_API_URL", "ALLOWED_USER_GROUPS",
+ "OAUTH2_CLIENT_ID", "OAUTH2_FLOW", "OAUTH2_USE_REFRESH_TOKEN", "IDP_BASE_URL",
+ "SCOPES", "EXCLUSIVE_SECTIONS",
+];
+
+describe("config reads from window globals", () => {
+ let origEnvTime;
+
+ beforeEach(() => {
+ origEnvTime = process.env.TIMEINTERVALSINCE1970_API_URL;
+ delete process.env.TIMEINTERVALSINCE1970_API_URL;
+ GLOBALS.forEach((k) => delete window[k]);
+ });
+
+ afterEach(() => {
+ if (origEnvTime === undefined) delete process.env.TIMEINTERVALSINCE1970_API_URL;
+ else process.env.TIMEINTERVALSINCE1970_API_URL = origEnvTime;
+ GLOBALS.forEach((k) => delete window[k]);
+ });
+
+ test("buildAPIBaseUrl prepends window.API_BASE_URL", () => {
+ window.API_BASE_URL = "https://api.test";
+ expect(buildAPIBaseUrl("/api/v1/summits")).toBe("https://api.test/api/v1/summits");
+ });
+
+ test("getTimeServiceUrl reads window.TIMEINTERVALSINCE1970_API_URL, else the env var", () => {
+ window.TIMEINTERVALSINCE1970_API_URL = "https://time.test";
+ expect(getTimeServiceUrl()).toBe("https://time.test");
+ delete window.TIMEINTERVALSINCE1970_API_URL;
+ process.env.TIMEINTERVALSINCE1970_API_URL = "https://time.env";
+ expect(getTimeServiceUrl()).toBe("https://time.env");
+ });
+
+ test("getAllowedUserGroups reads window.ALLOWED_USER_GROUPS, else ''", () => {
+ expect(getAllowedUserGroups()).toBe("");
+ window.ALLOWED_USER_GROUPS = "admins";
+ expect(getAllowedUserGroups()).toBe("admins");
+ });
+
+ test("getOAuth2ClientId reads window.OAUTH2_CLIENT_ID", () => {
+ window.OAUTH2_CLIENT_ID = "cid";
+ expect(getOAuth2ClientId()).toBe("cid");
+ });
+
+ test("getOAuth2Flow reads window.OAUTH2_FLOW, else 'token id_token'", () => {
+ expect(getOAuth2Flow()).toBe("token id_token");
+ window.OAUTH2_FLOW = "code";
+ expect(getOAuth2Flow()).toBe("code");
+ });
+
+ test("useOAuth2RefreshToken is truthy from the window global, even when it is false", () => {
+ expect(useOAuth2RefreshToken()).toBeTruthy();
+ window.OAUTH2_USE_REFRESH_TOKEN = false;
+ expect(useOAuth2RefreshToken()).toBeTruthy();
+ });
+
+ test("getOAuth2IDPBaseUrl reads window.IDP_BASE_URL", () => {
+ window.IDP_BASE_URL = "https://idp.test";
+ expect(getOAuth2IDPBaseUrl()).toBe("https://idp.test");
+ });
+
+ test("getOAuth2Scopes reads window.SCOPES", () => {
+ window.SCOPES = "openid profile";
+ expect(getOAuth2Scopes()).toBe("openid profile");
+ });
+
+ test("Exclusive renders its children only when window.EXCLUSIVE_SECTIONS lists the name", () => {
+ const tree = () => mount(x);
+ expect(tree().find("span")).toHaveLength(0);
+ window.EXCLUSIVE_SECTIONS = ["sponsors"];
+ expect(tree().find("span")).toHaveLength(1);
+ });
+
+ test("the getters stay exported from utils/methods and security/methods", () => {
+ expect(utilsMethods.buildAPIBaseUrl).toBe(buildAPIBaseUrl);
+ expect(utilsMethods.getTimeServiceUrl).toBe(getTimeServiceUrl);
+ expect(utilsMethods.getAllowedUserGroups).toBe(getAllowedUserGroups);
+ expect(securityMethods.getOAuth2ClientId).toBe(getOAuth2ClientId);
+ expect(securityMethods.getOAuth2Flow).toBe(getOAuth2Flow);
+ expect(securityMethods.useOAuth2RefreshToken).toBe(useOAuth2RefreshToken);
+ expect(securityMethods.getOAuth2IDPBaseUrl).toBe(getOAuth2IDPBaseUrl);
+ expect(securityMethods.getOAuth2Scopes).toBe(getOAuth2Scopes);
+ });
+});
diff --git a/src/utils/__tests__/config.test.js b/src/utils/__tests__/config.test.js
new file mode 100644
index 00000000..0202b8f7
--- /dev/null
+++ b/src/utils/__tests__/config.test.js
@@ -0,0 +1,144 @@
+/**
+ * setConfig lets a consumer hand uicore its app settings directly. A value
+ * set this way wins over the matching window global; anything not set keeps
+ * reading the global, so consumers that only plant globals are unaffected.
+ */
+import React from "react";
+import Enzyme, { mount } from "enzyme";
+import Adapter from "enzyme-adapter-react-16";
+import {
+ setConfig,
+ getConfig,
+ buildAPIBaseUrl,
+ getTimeServiceUrl,
+ getAllowedUserGroups,
+ parseUserGroups,
+ getOAuth2ClientId,
+ getOAuth2Flow,
+ useOAuth2RefreshToken,
+ getOAuth2IDPBaseUrl,
+ getOAuth2Scopes,
+ getExclusiveSections,
+} from "../config";
+import Exclusive from "../../components/exclusive-wrapper";
+
+Enzyme.configure({ adapter: new Adapter() });
+
+const WINDOW_GLOBALS = {
+ API_BASE_URL: "https://window.test",
+ TIMEINTERVALSINCE1970_API_URL: "https://time.window",
+ ALLOWED_USER_GROUPS: "window-group",
+ OAUTH2_CLIENT_ID: "window-cid",
+ OAUTH2_FLOW: "window-flow",
+ OAUTH2_USE_REFRESH_TOKEN: true,
+ IDP_BASE_URL: "https://idp.window",
+ SCOPES: "window-scopes",
+ EXCLUSIVE_SECTIONS: ["window-section"],
+};
+
+describe("setConfig", () => {
+ beforeEach(() => Object.assign(window, WINDOW_GLOBALS));
+
+ afterEach(() => {
+ setConfig({});
+ Object.keys(WINDOW_GLOBALS).forEach((k) => delete window[k]);
+ });
+
+ test("getConfig returns a copy of what was set; setConfig({}) clears it", () => {
+ setConfig({ apiBaseUrl: "https://cfg.test" });
+ expect(getConfig()).toEqual({ apiBaseUrl: "https://cfg.test" });
+ getConfig().apiBaseUrl = "mutated";
+ expect(getConfig()).toEqual({ apiBaseUrl: "https://cfg.test" });
+ setConfig({});
+ expect(getConfig()).toEqual({});
+ });
+
+ test("setConfig replaces the previous object instead of merging", () => {
+ setConfig({ apiBaseUrl: "https://a.test" });
+ setConfig({ scopes: "b" });
+ expect(getConfig()).toEqual({ scopes: "b" });
+ expect(buildAPIBaseUrl("/x")).toBe("https://window.test/x");
+ });
+
+ test("setConfig with null, undefined or an array clears the config", () => {
+ setConfig({ apiBaseUrl: "https://a.test" });
+ setConfig(null);
+ expect(getConfig()).toEqual({});
+ setConfig({ apiBaseUrl: "https://a.test" });
+ setConfig();
+ expect(getConfig()).toEqual({});
+ setConfig(["x"]);
+ expect(getConfig()).toEqual({});
+ });
+
+ test("a key that is not configured keeps reading the window global", () => {
+ setConfig({ scopes: "openid" });
+ expect(buildAPIBaseUrl("/x")).toBe("https://window.test/x");
+ expect(getOAuth2Flow()).toBe("window-flow");
+ });
+
+ test("every configured value wins over its window global", () => {
+ setConfig({
+ apiBaseUrl: "https://cfg.test",
+ timeApiUrl: "https://time.cfg",
+ allowedUserGroups: "cfg-group",
+ oauth2ClientId: "cfg-cid",
+ oauth2Flow: "cfg-flow",
+ oauth2UseRefreshToken: false,
+ idpBaseUrl: "https://idp.cfg",
+ scopes: "cfg-scopes",
+ exclusiveSections: ["sponsors"],
+ });
+ expect(buildAPIBaseUrl("/x")).toBe("https://cfg.test/x");
+ expect(getTimeServiceUrl()).toBe("https://time.cfg");
+ expect(getAllowedUserGroups()).toBe("cfg-group");
+ expect(getOAuth2ClientId()).toBe("cfg-cid");
+ expect(getOAuth2Flow()).toBe("cfg-flow");
+ expect(useOAuth2RefreshToken()).toBe(false);
+ expect(getOAuth2IDPBaseUrl()).toBe("https://idp.cfg");
+ expect(getOAuth2Scopes()).toBe("cfg-scopes");
+ expect(getExclusiveSections()).toEqual(["sponsors"]);
+ const tree = mount(x);
+ expect(tree.find("span")).toHaveLength(1);
+ });
+
+ test("falsy configured values (0, false, null) win over a truthy global", () => {
+ setConfig({ scopes: null, oauth2UseRefreshToken: false, oauth2ClientId: 0 });
+ expect(getOAuth2Scopes()).toBe(null);
+ expect(useOAuth2RefreshToken()).toBe(false);
+ expect(getOAuth2ClientId()).toBe(0);
+ });
+
+ test("undefined and empty-string values are treated as not set", () => {
+ setConfig({ apiBaseUrl: undefined, allowedUserGroups: "" });
+ expect(buildAPIBaseUrl("/x")).toBe("https://window.test/x");
+ expect(getAllowedUserGroups()).toBe(window.ALLOWED_USER_GROUPS || "");
+ });
+
+ test("a config set through one module copy is visible to a second copy", () => {
+ // The state rides globalThis under Symbol.for, so duplicate installs of
+ // the package share it.
+ setConfig({ oauth2ClientId: "shared-client" });
+ let secondCopy;
+ jest.isolateModules(() => {
+ secondCopy = require("../config");
+ });
+ expect(secondCopy.getOAuth2ClientId()).toBe("shared-client");
+ });
+});
+
+describe("parseUserGroups", () => {
+ test("splits a space-separated string into a list", () => {
+ expect(parseUserGroups("a b c")).toEqual(["a", "b", "c"]);
+ });
+
+ test("a single value yields a one-item list", () => {
+ expect(parseUserGroups("staff")).toEqual(["staff"]);
+ });
+
+ test("a falsy value (empty string, null, undefined) yields an empty list", () => {
+ expect(parseUserGroups("")).toEqual([]);
+ expect(parseUserGroups(null)).toEqual([]);
+ expect(parseUserGroups(undefined)).toEqual([]);
+ });
+});
diff --git a/src/utils/config.js b/src/utils/config.js
new file mode 100644
index 00000000..4dcedb27
--- /dev/null
+++ b/src/utils/config.js
@@ -0,0 +1,84 @@
+/**
+ * App settings handed to uicore by the consumer via setConfig. Each getter
+ * below returns the configured value when one is set and otherwise falls
+ * back to the window global uicore has always read.
+ *
+ * A key set to undefined or an empty string is UNSET: the fallback applies.
+ * null, false and 0 are values and win over the global (a configured
+ * `oauth2UseRefreshToken: false` is the way to turn the refresh flow off).
+ * setConfig replaces the whole object; setConfig({}) or setConfig() clears it.
+ *
+ * useOAuth2RefreshToken: the window fallback keeps its historical shape
+ * (always truthy), so a configured `oauth2UseRefreshToken: false` is the
+ * way to turn the refresh-token flow off.
+ *
+ * The state lives on a global under a Symbol.for key so every copy of
+ * this module shares it.
+ */
+const _global =
+ typeof globalThis !== 'undefined'
+ ? globalThis
+ : typeof window !== 'undefined'
+ ? window
+ : {};
+
+const CONFIG_KEY = Symbol.for('openstack-uicore-foundation.config');
+
+const readConfig = () => _global[CONFIG_KEY] || {};
+
+export const setConfig = (next) => {
+ const source = next && typeof next === 'object' && !Array.isArray(next) ? next : {};
+ _global[CONFIG_KEY] = Object.fromEntries(
+ Object.entries(source).filter(([, value]) => value !== undefined && value !== ''),
+ );
+};
+
+export const getConfig = () => ({ ...readConfig() });
+
+const hasWindow = () => typeof window !== 'undefined';
+
+const configuredOr = (key, ambient) => {
+ const value = readConfig()[key];
+ return value !== undefined ? value : ambient();
+};
+
+const getApiBaseUrl = () =>
+ configuredOr('apiBaseUrl', () => (hasWindow() ? window.API_BASE_URL : null));
+
+export const buildAPIBaseUrl = (relativeUrl) => {
+ const base = getApiBaseUrl();
+ if (base === null) return null;
+ return `${base}${relativeUrl}`;
+};
+
+export const getTimeServiceUrl = () =>
+ configuredOr('timeApiUrl', () =>
+ (hasWindow() ? window.TIMEINTERVALSINCE1970_API_URL || process.env.TIMEINTERVALSINCE1970_API_URL : null));
+
+export const getAllowedUserGroups = () =>
+ configuredOr('allowedUserGroups', () => (hasWindow() ? window.ALLOWED_USER_GROUPS || '' : null));
+
+/**
+ * Split the space-separated allowed-user-groups string into a list. A falsy
+ * value yields an empty list.
+ */
+export const parseUserGroups = (value) => (value ? value.split(' ') : []);
+
+export const getOAuth2ClientId = () =>
+ configuredOr('oauth2ClientId', () => (hasWindow() ? window.OAUTH2_CLIENT_ID : null));
+
+export const getOAuth2Flow = () =>
+ configuredOr('oauth2Flow', () => (hasWindow() ? window.OAUTH2_FLOW || 'token id_token' : 'token id_token'));
+
+export const useOAuth2RefreshToken = () =>
+ configuredOr('oauth2UseRefreshToken', () =>
+ (hasWindow() ? new Boolean(window.OAUTH2_USE_REFRESH_TOKEN || true) : true));
+
+export const getOAuth2IDPBaseUrl = () =>
+ configuredOr('idpBaseUrl', () => (hasWindow() ? window.IDP_BASE_URL : null));
+
+export const getOAuth2Scopes = () =>
+ configuredOr('scopes', () => (hasWindow() ? window.SCOPES : null));
+
+export const getExclusiveSections = () =>
+ configuredOr('exclusiveSections', () => (hasWindow() ? window.EXCLUSIVE_SECTIONS : undefined));
diff --git a/src/utils/methods.js b/src/utils/methods.js
index fb3a51a4..f4de0eb0 100644
--- a/src/utils/methods.js
+++ b/src/utils/methods.js
@@ -116,19 +116,7 @@ export const getCurrentHref = () => {
return null;
};
-export const getAllowedUserGroups = () => {
- if(typeof window !== 'undefined') {
- return window.ALLOWED_USER_GROUPS || '';
- }
- return null;
-};
-
-export const buildAPIBaseUrl = (relativeUrl) => {
- if(typeof window !== 'undefined'){
- return `${window.API_BASE_URL}${relativeUrl}`;
- }
- return null``;
-};
+export { buildAPIBaseUrl, getAllowedUserGroups, getTimeServiceUrl } from './config';
export const putOnLocalStorage = (key, value) => {
if(typeof window !== 'undefined') {
@@ -236,13 +224,6 @@ export const retryPromise = async (
return false;
}
-export const getTimeServiceUrl = () => {
- if(typeof window !== 'undefined') {
- return window.TIMEINTERVALSINCE1970_API_URL || process.env.TIMEINTERVALSINCE1970_API_URL;
- }
- return null;
-};
-
export const getEventLocation = (event, summitVenueCount, summitShowLocDate = null, nowUtc = null) => {
const shouldShowVenues = (summitShowLocDate && nowUtc) ? summitShowLocDate * 1000 < nowUtc : true;
const locationName = [];
diff --git a/src/utils/query-actions.js b/src/utils/query-actions.js
index f52012b7..f8769641 100644
--- a/src/utils/query-actions.js
+++ b/src/utils/query-actions.js
@@ -13,7 +13,7 @@
import { fetchErrorHandler, fetchResponseHandler, escapeFilterValue } from "./actions";
import { getAccessToken } from '../components/security/methods';
-import { buildAPIBaseUrl } from "./methods";
+import { buildAPIBaseUrl } from "./config";
import debounce from 'lodash/debounce';
export const RECEIVE_COUNTRIES = 'RECEIVE_COUNTRIES';
const callDelay = 500; // milliseconds
diff --git a/webpack.common.js b/webpack.common.js
index ed288c6c..43e3509d 100644
--- a/webpack.common.js
+++ b/webpack.common.js
@@ -86,6 +86,7 @@ module.exports = {
'utils/fragment-parser': './src/components/fragment-parser.js',
'utils/use-fit-text': './src/components/use-fit-text.js',
'utils/actions': './src/utils/actions.js',
+ 'utils/config': './src/utils/config.js',
'utils/methods': './src/utils/methods.js',
'utils/query-actions': './src/utils/query-actions.js',
'utils/reducers': './src/utils/reducers.js',