Skip to content

Commit

Permalink
Simplify environment variables on frontend (#491)
Browse files Browse the repository at this point in the history
  • Loading branch information
antoinejaussoin authored Mar 8, 2023
1 parent 1c8b05c commit 9aeda90
Showing 18 changed files with 107 additions and 129 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/alpha.yml
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ name: 'Alpha Build'

on:
push:
branches: [v501/add-aw]
branches: [v502/simplify-fe-env]

jobs:
frontend:
16 changes: 8 additions & 8 deletions docs/docs/self-hosting/optionals.md
Original file line number Diff line number Diff line change
@@ -107,18 +107,18 @@ services:
- '80:80' # Change the first 80 to whatever port you want to access Retrospected from
environment:
# -- Optional --
GA_ID: '' # Optional, Google Analytics ID (UA-1234456-7)
GOOGLE_AD_WORDS_ID: '' # Optional, Google Adwords ID (AW-1234456)
GOOGLE_AD_WORDS_EVENT: '' # Optional, Google Adwords Event ID (AW-1234456/1234456)
SENTRY_URL: '' # Optional, Sentry URL (https://[email protected]/1234567)
GIPHY_API_KEY: '' # Optional, can be obtained here: https://developers.giphy.com/
DEFAULT_LANGUAGE: 'en-GB' # Set the default language for new users
MARKETING_ROOT: 'https://www.retrospected.com' # URL of the marketing website
FRONTEND_GOOGLE_ANALYTICS_ID: '' # Optional, Google Analytics ID (UA-1234456-7)
FRONTEND_GOOGLE_AD_WORDS_ID: '' # Optional, Google Adwords ID (AW-1234456)
FRONTEND_GOOGLE_AD_WORDS_CONVERSION_ID: '' # Optional, Google Adwords Event ID (AW-1234456/1234456)
FRONTEND_SENTRY_URL: '' # Optional, Sentry URL (https://[email protected]/1234567)
FRONTEND_GIPHY_API_KEY: '' # Optional, can be obtained here: https://developers.giphy.com/
FRONTEND_DEFAULT_LANGUAGE: 'en-GB' # Set the default language for new users
FRONTEND_MARKETING_ROOT: 'https://www.retrospected.com' # URL of the marketing website

# -- Do Not Change --
BACKEND_HOST: backend # This should be the name of the backend service
BACKEND_PORT: 3201 # This should be the same as BACKEND_PORT on backend
STRIPE_KEY: '' # Stripe publishable key (for frontend)
FRONTEND_STRIPE_KEY: '' # Stripe publishable key (for frontend)

restart: unless-stopped
logging:
6 changes: 6 additions & 0 deletions frontend/.env
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
VITE_VERSION=$npm_package_version
VITE_STRIPE_KEY=
VITE_GIPHY_API_KEY=
VITE_MARKETING_ROOT=http://localhost:3001
VITE_GOOGLE_ANALYTICS_ID=
VITE_GOOGLE_AD_WORDS_ID=
VITE_GOOGLE_AD_WORDS_CONVERSION_ID=
1 change: 1 addition & 0 deletions frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -32,6 +32,7 @@ FROM nginx:latest

RUN apt update
RUN apt -y remove libfreetype6
RUN apt -y install jq

COPY --from=Node /home/node/app/dist /usr/share/nginx/html
COPY ./docker/nginx.conf.template /etc/nginx/conf.d/default.conf.template
30 changes: 20 additions & 10 deletions frontend/docker/frontend-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
#!/usr/bin/env sh
set -eu

# Configure Nginx with backend host and port
BACKEND_HOST="${BACKEND_HOST:-backend}" \
BACKEND_PORT="${BACKEND_PORT:-3201}" \
envsubst '${BACKEND_HOST} ${BACKEND_PORT}' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf

# Replacing Google Analytics and Sentry IDs. Separator is # because the sentry URL contains a /
sed -i "s#NO_GA#${GA_ID:-}#g" /usr/share/nginx/html/index.html
sed -i "s#NO_AD_WORDS_ID#${GOOGLE_AD_WORDS_ID:-}#g" /usr/share/nginx/html/index.html
sed -i "s#NO_AD_WORDS_EVENT#${GOOGLE_AD_WORDS_EVENT:-}#g" /usr/share/nginx/html/index.html
sed -i "s#NO_SENTRY#${SENTRY_URL:-}#g" /usr/share/nginx/html/index.html
sed -i "s#NO_GIPHY#${GIPHY_API_KEY:-}#g" /usr/share/nginx/html/index.html
sed -i "s#NO_STRIPE#${STRIPE_KEY:-}#g" /usr/share/nginx/html/index.html
sed -i "s#NO_DEFAULT_LANGUAGE#${DEFAULT_LANGUAGE:-en-GB}#g" /usr/share/nginx/html/index.html
sed -i "s#NO_MARKETING_ROOT#${MARKETING_ROOT:-https://www.retrospected.com}#g" /usr/share/nginx/html/index.html
# Configure the frontend with environment variables
CONFIG_FILE='/usr/share/nginx/html/config.tmp'
HTML_FILE='/usr/share/nginx/html/index.html'
PREFIX='FRONTEND_'

exec "$@"
# Creates a file with the environment variables that start with FRONTEND_
echo " window.__env__ = {" > "${CONFIG_FILE}"
jq -n 'env' | { grep "\"$PREFIX" || true; }>> "${CONFIG_FILE}"
echo " };" >> "${CONFIG_FILE}"

# Removes the prefix from the environment variables
sed -i "s#\"${PREFIX}# \"#g" "${CONFIG_FILE}"

# Injects the configuration file into the HTML file
sed -i -e "/RUN-TIME CONFIGURATION/r ${CONFIG_FILE}" ${HTML_FILE}

# Removes the temporary configuration file
rm $CONFIG_FILE

exec "$@"
15 changes: 3 additions & 12 deletions frontend/index.html
Original file line number Diff line number Diff line change
@@ -20,18 +20,6 @@
<meta property="og:url" content="https://www.retrospected.com" />
<meta property="og:type" content="app" />

<script>
window.__env__ = {
GOOGLE_ANALYTICS_ID: 'NO_GA',
GOOGLE_AD_WORDS_ID: 'NO_AD_WORDS_ID',
GOOGLE_AD_WORDS_EVENT: 'NO_AD_WORDS_EVENT',
SENTRY_URL: 'NO_SENTRY',
GIPHY_API_KEY: 'NO_GIPHY',
STRIPE_KEY: 'NO_STRIPE',
DEFAULT_LANGUAGE: 'NO_DEFAULT_LANGUAGE',
MARKETING_ROOT: 'NO_MARKETING_ROOT',
};
</script>
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
@@ -76,6 +64,9 @@
}
});
</script>
<script>
// RUN-TIME CONFIGURATION
</script>
</head>

<body ontouchstart="">
2 changes: 1 addition & 1 deletion frontend/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -254,7 +254,7 @@ export async function deleteUser(
export async function getGiphyUrl(giphyId: string): Promise<string | null> {
try {
const response = await fetch(
`//api.giphy.com/v1/gifs/${giphyId}?api_key=${config.GiphyApiKey}`,
`//api.giphy.com/v1/gifs/${giphyId}?api_key=${config.GIPHY_API_KEY}`,
{ credentials: 'omit' }
);
if (response.ok) {
2 changes: 1 addition & 1 deletion frontend/src/stripe/get-stripe.ts
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ import config from '../utils/getConfig';
let stripePromise: Promise<Stripe | null>;
export const getStripe = () => {
if (!stripePromise) {
stripePromise = loadStripe(config.StripeKey);
stripePromise = loadStripe(config.STRIPE_KEY);
}
return stripePromise;
};
25 changes: 14 additions & 11 deletions frontend/src/track.ts
Original file line number Diff line number Diff line change
@@ -8,16 +8,19 @@ import { InitOptions } from 'react-ga4/types/ga4';

let sentryErrorCount = 0;

const hasGA = !!config.GOOGLE_ANALYTICS_ID;
const hasSentry = !!config.SENTRY_URL;

export const initialiseAnalytics = () => {
if (isGAEnabled()) {
ReactGA.initialize(
[
{
trackingId: config.GoogleAnalyticsId,
trackingId: config.GOOGLE_ANALYTICS_ID,
},
config.googleAdWordsId
config.GOOGLE_AD_WORDS_ID
? {
trackingId: config.googleAdWordsId,
trackingId: config.GOOGLE_AD_WORDS_ID,
gaOptions: {
name: 'aw',
},
@@ -29,24 +32,24 @@ export const initialiseAnalytics = () => {
};

export const initialiseSentry = () => {
if (config.hasSentry) {
if (hasSentry) {
Sentry.init({
dsn: config.SentryUrl,
release: `frontend@${config.version}`,
dsn: config.SENTRY_URL,
release: `frontend@${config.VERSION}`,
});
}
};

export const setScope = (fn: (scope: Sentry.Scope | null) => void) => {
if (config.hasSentry) {
if (hasSentry) {
Sentry.configureScope(fn);
} else {
fn(null);
}
};

export const recordManualError = (message: string) => {
if (config.hasSentry) {
if (hasSentry) {
sentryErrorCount += 1;
if (sentryErrorCount > 100) {
console.error(
@@ -98,14 +101,14 @@ export const trackPageView = (path: string) => {
};

export const trackAdWordsConversion = () => {
if (isGAEnabled() && config.googleAdWordsEvent) {
if (isGAEnabled() && config.GOOGLE_AD_WORDS_CONVERSION_ID) {
ReactGA._gtag('event', 'conversion', {
send_to: config.googleAdWordsEvent,
send_to: config.GOOGLE_AD_WORDS_CONVERSION_ID,
event_callback: noop,
});
}
};

const isGAEnabled = () => {
return isProduction() && config.hasGA;
return isProduction() && hasGA;
};
2 changes: 1 addition & 1 deletion frontend/src/translations/i18n.ts
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@ i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: config.defaultLanguage,
fallbackLng: config.DEFAULT_LANGUAGE,
debug: !isProduction(),
defaultNS: 'ns1',
ns: 'ns1',
95 changes: 29 additions & 66 deletions frontend/src/utils/getConfig.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
interface HtmlConfig {
interface Config {
GOOGLE_ANALYTICS_ID: string;
GOOGLE_AD_WORDS_ID: string;
GOOGLE_AD_WORDS_EVENT: string;
GOOGLE_AD_WORDS_CONVERSION_ID: string;
SENTRY_URL: string;
GIPHY_API_KEY: string;
STRIPE_KEY: string;
@@ -10,89 +10,52 @@ interface HtmlConfig {
MARKETING_ROOT: string;
}

interface Config {
hasGA: boolean;
hasSentry: boolean;
hasGiphy: boolean;
GoogleAnalyticsId: string;
googleAdWordsId: string;
googleAdWordsEvent: string;
SentryUrl: string;
GiphyApiKey: string;
StripeKey: string;
defaultLanguage: string;
version: string;
marketingRoot: string;
}
const ALL_KEYS: (keyof Config)[] = [
'GOOGLE_ANALYTICS_ID',
'GOOGLE_AD_WORDS_ID',
'GOOGLE_AD_WORDS_CONVERSION_ID',
'SENTRY_URL',
'GIPHY_API_KEY',
'STRIPE_KEY',
'DEFAULT_LANGUAGE',
'VERSION',
'MARKETING_ROOT',
];

declare global {
interface Window {
__env__: HtmlConfig;
__env__: Partial<Config>;
}
}

window.__env__ = window.__env__ || {};

function getKey(
key:
| 'GOOGLE_ANALYTICS_ID'
| 'GOOGLE_AD_WORDS_ID'
| 'GOOGLE_AD_WORDS_EVENT'
| 'SENTRY_URL'
| 'GIPHY_API_KEY'
| 'STRIPE_KEY'
| 'MARKETING_ROOT'
| 'DEFAULT_LANGUAGE',
noValue: string,
defaultValue?: string
): string {
function getKey(key: keyof Config): string {
if (import.meta.env[`VITE_${key}`]) {
return import.meta.env[`VITE_${key}`] || '';
}
if (!!window.__env__[key] && window.__env__[key] !== noValue) {
return window.__env__[key];
const winObj = window.__env__[key];
if (winObj) {
return winObj;
}
return defaultValue || '';
return '';
}

function getConfig(): Config {
const googleAnalyticsId = getKey('GOOGLE_ANALYTICS_ID', 'NO_GA');
const googleAdWordsId = getKey('GOOGLE_AD_WORDS_ID', 'NO_AD_WORDS_ID');
const googleAdWordsEvent = getKey(
'GOOGLE_AD_WORDS_EVENT',
'NO_AD_WORDS_EVENT'
);
const sentryUrl = getKey('SENTRY_URL', 'NO_SENTRY');
const giphyApiKey = getKey('GIPHY_API_KEY', 'NO_GIPHY');
const stripeKey = getKey('STRIPE_KEY', 'NO_STRIPE');
const marketingRoot = getKey('MARKETING_ROOT', 'NO_MARKETING_ROOT');
let defaultLanguage = getKey(
'DEFAULT_LANGUAGE',
'NO_DEFAULT_LANGUAGE',
'en-GB'
);

if (defaultLanguage.length !== 5) {
console.error(
const config: Config = {} as Config;
ALL_KEYS.forEach((key) => {
config[key] = getKey(key);
});

// Special cases
if (!config.DEFAULT_LANGUAGE || config.DEFAULT_LANGUAGE.length !== 5) {
console.warn(
'Your default language (DEFAULT_LANGUAGE) is not in the right format. The right format should be a string of 5 characters, for example: en-GB, fr-FR, etc.'
);
defaultLanguage = 'en-GB';
config.DEFAULT_LANGUAGE = 'en-GB';
}

return {
hasGA: !!googleAnalyticsId,
hasSentry: !!sentryUrl,
hasGiphy: !!giphyApiKey,
GoogleAnalyticsId: googleAnalyticsId,
googleAdWordsId,
googleAdWordsEvent,
SentryUrl: sentryUrl,
GiphyApiKey: giphyApiKey,
StripeKey: stripeKey,
defaultLanguage: defaultLanguage,
version: APP_VERSION,
marketingRoot,
};
return config;
}

export default getConfig();
4 changes: 2 additions & 2 deletions frontend/src/views/Panel.tsx
Original file line number Diff line number Diff line change
@@ -61,7 +61,7 @@ function Panel() {
<Typography variant="h6">Legal Stuff</Typography>
{policies.map((policy) => (
<ExternalLink
href={config.marketingRoot + policy.url}
href={config.MARKETING_ROOT + policy.url}
target="_blank"
key={policy.name}
>
@@ -74,7 +74,7 @@ function Panel() {
component="div"
style={{ color: colors.grey[700], textAlign: 'center' }}
>
<Content>Version {config.version}</Content>
<Content>Version {config.VERSION}</Content>
</Typography>
</Bottom>
</DrawerContent>
4 changes: 2 additions & 2 deletions frontend/src/views/game/board/post/Post.tsx
Original file line number Diff line number Diff line change
@@ -279,7 +279,7 @@ const PostItem = ({
onClick={toggleAction}
/>
)}
{canEdit && config.hasGiphy && canUseGiphy && (
{canEdit && !!config.GIPHY_API_KEY && canUseGiphy && (
<ActionButton
ariaLabel={t('Post.setGiphyButton')}
tooltip={t('Post.setGiphyButton')!}
@@ -359,7 +359,7 @@ const PostItem = ({
<Card>
<CardContent>
<GiphySearchBox
apiKey={config.GiphyApiKey}
apiKey={config.GIPHY_API_KEY}
onSelect={handleChooseGiphyEditor}
/>
</CardContent>
1 change: 1 addition & 0 deletions integration/Makefile
Original file line number Diff line number Diff line change
@@ -13,4 +13,5 @@ local:
docker-compose down

alpha:
docker-compose -f ./docker-compose.ci.alpha.yml pull
docker-compose -f ./docker-compose.ci.alpha.yml up --exit-code-from cypress
3 changes: 3 additions & 0 deletions integration/docker-compose.ci.alpha.yml
Original file line number Diff line number Diff line change
@@ -46,6 +46,9 @@ services:
image: retrospected/frontend:alpha
depends_on:
- backend
environment:
BACKEND_HOST: backend
BACKEND_PORT: 3201
restart: unless-stopped
logging:
driver: 'json-file'
Loading

0 comments on commit 9aeda90

Please sign in to comment.