diff --git a/.circleci/config.yml b/.circleci/config.yml index 2a4e1d6..4e47a6d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -142,7 +142,7 @@ jobs: name: Build Docker local image command: | echo "Building Docker image: local" - docker build -t $DOCKER_ORG/$CIRCLE_PROJECT_REPONAME:local . + docker build -t $DOCKER_ORG/$CIRCLE_PROJECT_REPONAME:local . --build-arg REACT_APP_VERSION=`npm run version --silent` --build-arg REACT_APP_COMMIT=`git rev-parse HEAD` --build-arg PUBLIC_PATH=https://localhost:8080/ - run: name: Save docker image to workspace command: docker save -o /tmp/docker-image.tar $DOCKER_ORG/$CIRCLE_PROJECT_REPONAME:local diff --git a/.env b/.env index 51a3255..8bb515d 100644 --- a/.env +++ b/.env @@ -1,6 +1,8 @@ -REACT_APP_API_BASE_URL=/api REACT_APP_AUTH_ENABLED=true -REACT_APP_MOCK_API=true +REACT_APP_AUTH_API_BASE_URL=/api +REACT_APP_AUTH_MOCK_API=true +REACT_APP_REMOTE_API_BASE_URL=/remote +REACT_APP_REMOTE_MOCK_API=true REMOTE_1_URL=http://localhost:3012 REMOTE_2_URL=http://localhost:3013 DEV_PORT=3010 diff --git a/Dockerfile b/Dockerfile index a74e56b..8e55253 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,15 +20,6 @@ ENV REACT_APP_COMMIT=$REACT_APP_COMMIT ARG PUBLIC_PATH ENV PUBLIC_PATH=$PUBLIC_PATH -# TODO: hard coding these for now since there is no api that -# handles the microfrontend remote locations -ARG REMOTE_1_URL -ENV REMOTE_1_URL=$REMOTE_1_URL - -ARG REMOTE_2_URL -ENV REMOTE_2_URL=$REMOTE_2_URL - - RUN yarn build # Second part, create a config at boostrap via entrypoint and and serve it @@ -41,13 +32,26 @@ COPY --from=0 dist/ . COPY docker/Caddyfile /srv/Caddyfile COPY docker/entrypoint.sh /entrypoint.sh COPY docker/createJSONConfig.sh /createJSONConfig.sh +COPY docker/createRemoteConfig.sh /createRemoteConfig.sh +COPY docker/loadRuntimeConfig.sh /loadRuntimeConfig.sh RUN chmod +x /entrypoint.sh RUN chmod +x /createJSONConfig.sh +RUN chmod +x /createRemoteConfig.sh +RUN chmod +x /loadRuntimeConfig.sh # Provide environment variables for setting endpoints dynamically -ARG API_BASE_URL -ENV API_BASE_URL=$API_BASE_URL +ARG REMOTE_API_BASE_URL +ENV REMOTE_API_BASE_URL=$REMOTE_API_BASE_URL + +ARG REMOTE_MOCK_API +ENV REMOTE_MOCK_API=$REMOTE_MOCK_API + +ARG AUTH_API_BASE_URL +ENV AUTH_API_BASE_URL=$AUTH_API_BASE_URL + +ARG AUTH_MOCK_API +ENV AUTH_MOCK_API=$AUTH_MOCK_API ARG AUTH_ENABLED ENV AUTH_ENABLED=$AUTH_ENABLED @@ -58,8 +62,11 @@ ENV LOGIN_URL=$LOGIN_URL ARG LOGOUT_URL ENV LOGOUT_URL=$LOGOUT_URL -ARG MOCK_API -ENV MOCK_API=$MOCK_API +ARG REMOTE_1_URL +ENV REMOTE_1_URL=$REMOTE_1_URL + +ARG REMOTE_2_URL +ENV REMOTE_2_URL=$REMOTE_2_URL EXPOSE 8080 diff --git a/docker-compose.yaml b/docker-compose.yaml index 127c9d7..dd93de3 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -14,14 +14,16 @@ services: - mojaloop/reporting-hub-bop-shell args: - PUBLIC_PATH=https://localhost:8080/ - - REMOTE_1_URL=https://localhost:8081 - - REMOTE_2_URL=https://localhost:8082 environment: - - API_BASE_URL=/api + - AUTH_API_BASE_URL=/ + - AUTH_MOCK_API=true + - REMOTE_API_BASE_URL=/ + - REMOTE_MOCK_API=false - LOGIN_URL=https://your-login-url - LOGOUT_URL=https://your-logout-url - AUTH_ENABLED=true - - MOCK_API=true + - REMOTE_1_URL=https://localhost:8081 + - REMOTE_2_URL=https://localhost:8082 ports: - "8080:8080" networks: diff --git a/docker/createJSONConfig.sh b/docker/createJSONConfig.sh index a69fa2d..60ca3dc 100644 --- a/docker/createJSONConfig.sh +++ b/docker/createJSONConfig.sh @@ -2,12 +2,14 @@ # Creates the JSON config based on environment variables echo "{ - \"API_BASE_URL\": \"${API_BASE_URL}\", + \"AUTH_API_BASE_URL\": \"${AUTH_API_BASE_URL}\", + \"AUTH_MOCK_API\": \"${AUTH_MOCK_API}\", + \"REMOTE_API_BASE_URL\": \"${REMOTE_API_BASE_URL}\", + \"REMOTE_MOCK_API\": \"${REMOTE_MOCK_API}\", \"AUTH_ENABLED\": \"${AUTH_ENABLED}\", \"LOGIN_URL\": \"${LOGIN_URL}\", - \"LOGOUT_URL\": \"${LOGOUT_URL}\", - \"MOCK_API\": \"${MOCK_API}\" + \"LOGOUT_URL\": \"${LOGOUT_URL}\" }" | jq '.' > config.json # This will exec the CMD from your Dockerfile, i.e. "npm start" -exec "$@" \ No newline at end of file +exec "$@" diff --git a/docker/createRemoteConfig.sh b/docker/createRemoteConfig.sh new file mode 100644 index 0000000..1f4c5a0 --- /dev/null +++ b/docker/createRemoteConfig.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +# Creates the Remotes config based on environment variables +echo "[ + { + \"path\": \"/iam\", + \"label\": \"Roles Microfrontend\", + \"menuComponent\": \"Menu\", + \"appComponent\": \"App\", + \"url\": \"${REMOTE_1_URL}/app.js\", + \"appName\": \"reporting_hub_bop_role_ui\" + }, + { + \"path\": \"/transfers\", + \"label\": \"Transfers Microfrontend\", + \"menuComponent\": \"Menu\", + \"appComponent\": \"App\", + \"url\": \"${REMOTE_2_URL}/app.js\", + \"appName\": \"reporting_hub_bop_trx_ui\" + } +]" | jq '.' > remotes.json + +# This will exec the CMD from your Dockerfile, i.e. "npm start" +exec "$@" diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 2cc1f40..ff95d93 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -2,6 +2,8 @@ # Run the script before starting the server sh /createJSONConfig.sh +sh /createRemoteConfig.sh +sh /loadRuntimeConfig.sh # This will exec the CMD from your Dockerfile, i.e. "npm start" -exec "$@" \ No newline at end of file +exec "$@" diff --git a/docker/loadRuntimeConfig.sh b/docker/loadRuntimeConfig.sh new file mode 100644 index 0000000..83e5310 --- /dev/null +++ b/docker/loadRuntimeConfig.sh @@ -0,0 +1,5 @@ +#!/bin/bash +sed -i 's#__REMOTE_1_URL__#'"$REMOTE_1_URL"'#g' runtime-env.js +sed -i 's#__REMOTE_1_URL__#'"$REMOTE_1_URL"'#g' index.html + +exec "$@" diff --git a/package.json b/package.json index 8fda666..dba68a1 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "bootstrap-icons": "^1.5.0", "classnames": "^2.2.6", "connected-react-router": "^6.8.0", + "copy-webpack-plugin": "^9.0.1", "css-loader": "4.2.1", "dotenv": "^10.0.0", "dotenv-webpack": "^7.0.3", diff --git a/public/index.html b/public/index.html index 4397b5d..35010bc 100644 --- a/public/index.html +++ b/public/index.html @@ -2,6 +2,8 @@ + +
diff --git a/public/runtime-env.js b/public/runtime-env.js new file mode 100644 index 0000000..5a57a13 --- /dev/null +++ b/public/runtime-env.js @@ -0,0 +1,5 @@ +// public/env.js +// https://medium.com/@hasniarif/how-to-handle-runtime-environment-variables-with-react-ec809cb07831 +window.env = { + REMOTE_1_URL: '__REMOTE_1_URL__', +}; diff --git a/src/App/sagas.ts b/src/App/sagas.ts index 0bcd024..9c78bfe 100644 --- a/src/App/sagas.ts +++ b/src/App/sagas.ts @@ -6,7 +6,6 @@ import { actions } from './slice'; function* requestRemotes() { try { const { status, data } = yield call(apis.remotes.read, {}); - if (is200(status)) { yield put(actions.requestRemotesSuccess(data)); } else { diff --git a/src/Config/build.ts b/src/Config/build.ts index 830949b..751b2cf 100644 --- a/src/Config/build.ts +++ b/src/Config/build.ts @@ -8,18 +8,26 @@ export default async (): Promise => { loginEndpoint: `${baseUrl}/auth/auth/login`, logoutEndpoint: `${baseUrl}/kratos/self-service/browser/flows/logout`, tokenEndpoint: `${baseUrl}/kratos/sessions/whoami`, - apiBaseUrl: `${process.env.REACT_APP_API_BASE_URL}`, isAuthEnabled: process.env.REACT_APP_AUTH_ENABLED !== 'false', basename: baseUrl, - mockApi: process.env.REACT_APP_MOCK_API === 'true', + authApiBaseUrl: `${process.env.REACT_APP_AUTH_API_BASE_URL}`, + authMockApi: process.env.REACT_APP_AUTH_MOCK_API === 'true', + remoteApiBaseUrl: `${process.env.REACT_APP_REMOTE_API_BASE_URL}`, + remoteMockApi: process.env.REACT_APP_REMOTE_MOCK_API === 'true', }; const config = { ...defaults }; try { - const { API_BASE_URL, AUTH_ENABLED, LOGIN_URL, LOGOUT_URL, MOCK_API } = await fetch( - `${baseUrl}/config.json`, - ).then((response) => response.json()); + const { + AUTH_API_BASE_URL, + AUTH_MOCK_API, + REMOTE_API_BASE_URL, + REMOTE_MOCK_API, + AUTH_ENABLED, + LOGIN_URL, + LOGOUT_URL, + } = await fetch(`${baseUrl}/config.json`).then((response) => response.json()); if (LOGIN_URL !== undefined) { config.loginEndpoint = LOGIN_URL; @@ -27,11 +35,19 @@ export default async (): Promise => { if (LOGOUT_URL !== undefined) { config.logoutEndpoint = LOGOUT_URL; } - if (API_BASE_URL !== undefined) { - config.apiBaseUrl = API_BASE_URL; + if (AUTH_API_BASE_URL !== undefined) { + config.authApiBaseUrl = AUTH_API_BASE_URL; } - if (MOCK_API !== undefined) { - config.mockApi = MOCK_API === 'true'; + if (AUTH_MOCK_API !== undefined) { + config.authMockApi = AUTH_MOCK_API === 'true'; + } + if (REMOTE_API_BASE_URL !== undefined) { + // NOTE: instead of an actual api, using local json file to store + // remote microfrontend config + config.remoteApiBaseUrl = baseUrl; + } + if (REMOTE_MOCK_API !== undefined) { + config.remoteMockApi = REMOTE_MOCK_API === 'true'; } if (AUTH_ENABLED !== undefined) { config.isAuthEnabled = AUTH_ENABLED !== 'false'; diff --git a/src/Config/slice.ts b/src/Config/slice.ts index bfe4cee..3364da8 100644 --- a/src/Config/slice.ts +++ b/src/Config/slice.ts @@ -6,8 +6,10 @@ export const initialState: ConfigState = { basename: '', }, api: { - apiBaseUrl: '', - mockApi: false, + authApiBaseUrl: '', + authMockApi: false, + remoteApiBaseUrl: '', + remoteMockApi: false, }, auth: { tokenEndpoint: '', diff --git a/src/Config/types.ts b/src/Config/types.ts index 07da826..5dabd05 100644 --- a/src/Config/types.ts +++ b/src/Config/types.ts @@ -3,8 +3,10 @@ export type AppConfig = { }; export type ApiConfig = { - apiBaseUrl: string; - mockApi: boolean; + authApiBaseUrl: string; + authMockApi: boolean; + remoteApiBaseUrl: string; + remoteMockApi: boolean; }; export interface AuthConfig { diff --git a/src/bootstrap.tsx b/src/bootstrap.tsx index cec62ef..9adc306 100644 --- a/src/bootstrap.tsx +++ b/src/bootstrap.tsx @@ -22,8 +22,10 @@ async function boot() { basename: config.basename, }, api: { - apiBaseUrl: config.apiBaseUrl, - mockApi: config.mockApi, + authApiBaseUrl: config.authApiBaseUrl, + authMockApi: config.authMockApi, + remoteApiBaseUrl: config.remoteApiBaseUrl, + remoteMockApi: config.remoteMockApi, }, auth: { loginEndpoint: config.loginEndpoint, diff --git a/src/utils/api.ts b/src/utils/api.ts index 5a3af49..c9cba05 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -5,12 +5,12 @@ import buildApis, { buildEndpointBuilder } from '@modusbox/redux-utils/lib/api'; const services = { kratos: { - baseUrl: '', - mock: (state: State) => state.config.api.mockApi, + baseUrl: (state: State) => state.config.api.authApiBaseUrl, + mock: (state: State) => state.config.api.authMockApi, }, mainApi: { - baseUrl: (state: State) => state.config.api.apiBaseUrl, - mock: (state: State) => state.config.api.mockApi, + baseUrl: (state: State) => state.config.api.remoteApiBaseUrl, + mock: (state: State) => state.config.api.remoteMockApi, }, }; @@ -22,9 +22,11 @@ export default buildApis({ url: (state: State) => state.config.auth.tokenEndpoint, mock: authMock, }), + // NOTE: instead of an actual api, using local json file to store + // remote microfrontend config remotes: builder({ service: services.mainApi, - url: '/remotes', + url: '/remotes.json', mock: remotesMock, }), }); diff --git a/webpack.config.js b/webpack.config.js index ec8d7e2..f0ddeee 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -4,6 +4,7 @@ const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); const { ModuleFederationPlugin } = require('webpack').container; const DotenvPlugin = require('dotenv-webpack'); const path = require('path'); +const CopyPlugin = require('copy-webpack-plugin'); require('dotenv').config({ path: './.env', @@ -121,6 +122,9 @@ module.exports = { ], }, plugins: [ + new CopyPlugin({ + patterns: [{ from: 'public/runtime-env.js', to: 'runtime-env.js' }], + }), new EslintWebpackPlugin({ extensions: ['ts', 'js', 'tsx', 'jsx'], exclude: [`/node_modules/`], diff --git a/yarn.lock b/yarn.lock index ada732b..354ada2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3688,6 +3688,19 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= +copy-webpack-plugin@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-9.0.1.tgz#b71d21991599f61a4ee00ba79087b8ba279bbb59" + integrity sha512-14gHKKdYIxF84jCEgPgYXCPpldbwpxxLbCmA7LReY7gvbaT555DgeBWBgBZM116tv/fO6RRJrsivBqRyRlukhw== + dependencies: + fast-glob "^3.2.5" + glob-parent "^6.0.0" + globby "^11.0.3" + normalize-path "^3.0.0" + p-limit "^3.1.0" + schema-utils "^3.0.0" + serialize-javascript "^6.0.0" + core-js-compat@^3.14.0, core-js-compat@^3.16.0: version "3.17.2" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.17.2.tgz#f461ab950c0a0ffedfc327debf28b7e518950936" @@ -5517,6 +5530,13 @@ glob-parent@^5.0.0, glob-parent@^5.1.2, glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" +glob-parent@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + glob-to-regexp@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" @@ -6315,6 +6335,13 @@ is-glob@^4.0.1, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" +is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + is-hexadecimal@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7"