diff --git a/front/Dockerfile b/front/Dockerfile
index 80d525a9..14914ebf 100644
--- a/front/Dockerfile
+++ b/front/Dockerfile
@@ -1,23 +1,35 @@
FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d as builder
-WORKDIR /usr/src
+
+WORKDIR /usr/src/messages
COPY messages .
RUN yarn install && yarn ts-proto
-# we are rebuilding on each deploy to cope with the PUSHER_URL environment URL
-FROM thecodingmachine/nodejs:14-apache
+WORKDIR /usr/src/front
+COPY front .
-COPY --chown=docker:docker front .
-COPY --from=builder --chown=docker:docker /usr/src/ts-proto-generated/protos /var/www/html/src/Messages/ts-proto-generated
-RUN sed -i 's/import { Observable } from "rxjs";/import type { Observable } from "rxjs";/g' /var/www/html/src/Messages/ts-proto-generated/messages.ts
-COPY --from=builder --chown=docker:docker /usr/src/JsonMessages /var/www/html/src/Messages/JsonMessages
+# move messages to front
+RUN cp -r ../messages/ts-proto-generated/protos/* src/Messages/ts-proto-generated
+RUN sed -i 's/import { Observable } from "rxjs";/import type { Observable } from "rxjs";/g' src/Messages/ts-proto-generated/messages.ts
+RUN cp -r ../messages/JsonMessages/* src/Messages/JsonMessages
+
+RUN yarn install && yarn run typesafe-i18n && yarn build
# Removing the iframe.html file from the final image as this adds a XSS attack.
# iframe.html is only in dev mode to circumvent a limitation
RUN rm dist/iframe.html
-RUN yarn install
+FROM thecodingmachine/nodejs:14-apache
+
+COPY --from=builder --chown=docker:docker /usr/src/front/dist dist
+COPY front/templater.sh .
+
+USER root
+RUN DEBIAN_FRONTEND=noninteractive apt-get update \
+ && apt-get install -y \
+ gettext-base \
+ && rm -rf /var/lib/apt/lists/*
+USER docker
-ENV NODE_ENV=production
ENV STARTUP_COMMAND_0="./templater.sh"
-ENV STARTUP_COMMAND_1="yarn run build"
+ENV STARTUP_COMMAND_1="envsubst < dist/env-config.template.js > dist/env-config.js"
ENV APACHE_DOCUMENT_ROOT=dist/
diff --git a/front/dist/.gitignore b/front/dist/.gitignore
index 785f2eb9..0561f7a5 100644
--- a/front/dist/.gitignore
+++ b/front/dist/.gitignore
@@ -1,4 +1,4 @@
index.html
-index.tmpl.html.tmp
/js/
style.*.css
+!env-config.template.js
diff --git a/front/dist/env-config.template.js b/front/dist/env-config.template.js
new file mode 100644
index 00000000..e672d7aa
--- /dev/null
+++ b/front/dist/env-config.template.js
@@ -0,0 +1,27 @@
+window.env = {
+ SKIP_RENDER_OPTIMIZATIONS: '${SKIP_RENDER_OPTIMIZATIONS}',
+ DISABLE_NOTIFICATIONS: '${DISABLE_NOTIFICATIONS}',
+ PUSHER_URL: '${PUSHER_URL}',
+ UPLOADER_URL: '${UPLOADER_URL}',
+ ADMIN_URL: '${ADMIN_URL}',
+ CONTACT_URL: '${CONTACT_URL}',
+ PROFILE_URL: '${PROFILE_URL}',
+ ICON_URL: '${ICON_URL}',
+ DEBUG_MODE: '${DEBUG_MODE}',
+ STUN_SERVER: '${STUN_SERVER}',
+ TURN_SERVER: '${TURN_SERVER}',
+ TURN_USER: '${TURN_USER}',
+ TURN_PASSWORD: '${TURN_PASSWORD}',
+ JITSI_URL: '${JITSI_URL}',
+ JITSI_PRIVATE_MODE: '${JITSI_PRIVATE_MODE}',
+ START_ROOM_URL: '${START_ROOM_URL}',
+ MAX_USERNAME_LENGTH: '${MAX_USERNAME_LENGTH}',
+ MAX_PER_GROUP: '${MAX_PER_GROUP}',
+ DISPLAY_TERMS_OF_USE: '${DISPLAY_TERMS_OF_USE}',
+ POSTHOG_API_KEY: '${POSTHOG_API_KEY}',
+ POSTHOG_URL: '${POSTHOG_URL}',
+ NODE_ENV: '${NODE_ENV}',
+ DISABLE_ANONYMOUS: '${DISABLE_ANONYMOUS}',
+ OPID_LOGIN_SCREEN_PROVIDER: '${OPID_LOGIN_SCREEN_PROVIDER}',
+ FALLBACK_LOCALE: '${FALLBACK_LOCALE}',
+};
diff --git a/front/dist/index.tmpl.html b/front/dist/index.ejs
similarity index 99%
rename from front/dist/index.tmpl.html
rename to front/dist/index.ejs
index 3b43a5ef..29b8e6cb 100644
--- a/front/dist/index.tmpl.html
+++ b/front/dist/index.ejs
@@ -27,7 +27,7 @@
-
+
diff --git a/front/src/Enum/EnvironmentVariable.ts b/front/src/Enum/EnvironmentVariable.ts
index ad1117f9..211308c5 100644
--- a/front/src/Enum/EnvironmentVariable.ts
+++ b/front/src/Enum/EnvironmentVariable.ts
@@ -1,31 +1,46 @@
-const DEBUG_MODE: boolean = process.env.DEBUG_MODE == "true";
-const START_ROOM_URL: string =
- process.env.START_ROOM_URL || "/_/global/maps.workadventure.localhost/Floor1/floor1.json";
-const PUSHER_URL = process.env.PUSHER_URL || "//pusher.workadventure.localhost";
-export const ADMIN_URL = process.env.ADMIN_URL || "//workadventu.re";
-const UPLOADER_URL = process.env.UPLOADER_URL || "//uploader.workadventure.localhost";
-const ICON_URL = process.env.ICON_URL || "//icon.workadventure.localhost";
-const STUN_SERVER: string = process.env.STUN_SERVER || "stun:stun.l.google.com:19302";
-const TURN_SERVER: string = process.env.TURN_SERVER || "";
-const SKIP_RENDER_OPTIMIZATIONS: boolean = process.env.SKIP_RENDER_OPTIMIZATIONS == "true";
-const DISABLE_NOTIFICATIONS: boolean = process.env.DISABLE_NOTIFICATIONS == "true";
-const TURN_USER: string = process.env.TURN_USER || "";
-const TURN_PASSWORD: string = process.env.TURN_PASSWORD || "";
-const JITSI_URL: string | undefined = process.env.JITSI_URL === "" ? undefined : process.env.JITSI_URL;
-const JITSI_PRIVATE_MODE: boolean = process.env.JITSI_PRIVATE_MODE == "true";
+declare global {
+ interface Window {
+ env?: Record;
+ }
+}
+
+const getEnv = (key: string): string | undefined => {
+ if (global.window?.env) {
+ return global.window.env[key];
+ }
+ if (global.process?.env) {
+ return global.process.env[key];
+ }
+ return;
+};
+
+const DEBUG_MODE: boolean = getEnv("DEBUG_MODE") == "true";
+const START_ROOM_URL: string = getEnv("START_ROOM_URL") || "/_/global/maps.workadventure.localhost/Floor1/floor1.json";
+const PUSHER_URL = getEnv("PUSHER_URL") || "//pusher.workadventure.localhost";
+export const ADMIN_URL = getEnv("ADMIN_URL") || "//workadventu.re";
+const UPLOADER_URL = getEnv("UPLOADER_URL") || "//uploader.workadventure.localhost";
+const ICON_URL = getEnv("ICON_URL") || "//icon.workadventure.localhost";
+const STUN_SERVER: string = getEnv("STUN_SERVER") || "stun:stun.l.google.com:19302";
+const TURN_SERVER: string = getEnv("TURN_SERVER") || "";
+const SKIP_RENDER_OPTIMIZATIONS: boolean = getEnv("SKIP_RENDER_OPTIMIZATIONS") == "true";
+const DISABLE_NOTIFICATIONS: boolean = getEnv("DISABLE_NOTIFICATIONS") == "true";
+const TURN_USER: string = getEnv("TURN_USER") || "";
+const TURN_PASSWORD: string = getEnv("TURN_PASSWORD") || "";
+const JITSI_URL: string | undefined = getEnv("JITSI_URL") === "" ? undefined : getEnv("JITSI_URL");
+const JITSI_PRIVATE_MODE: boolean = getEnv("JITSI_PRIVATE_MODE") == "true";
const POSITION_DELAY = 200; // Wait 200ms between sending position events
const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player
-export const MAX_USERNAME_LENGTH = parseInt(process.env.MAX_USERNAME_LENGTH || "") || 8;
-export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || "4");
-export const DISPLAY_TERMS_OF_USE = process.env.DISPLAY_TERMS_OF_USE == "true";
-export const NODE_ENV = process.env.NODE_ENV || "development";
-export const CONTACT_URL = process.env.CONTACT_URL || undefined;
-export const PROFILE_URL = process.env.PROFILE_URL || undefined;
-export const POSTHOG_API_KEY: string = (process.env.POSTHOG_API_KEY as string) || "";
-export const POSTHOG_URL = process.env.POSTHOG_URL || undefined;
-export const DISABLE_ANONYMOUS: boolean = process.env.DISABLE_ANONYMOUS === "true";
-export const OPID_LOGIN_SCREEN_PROVIDER = process.env.OPID_LOGIN_SCREEN_PROVIDER;
-const FALLBACK_LOCALE = process.env.FALLBACK_LOCALE || undefined;
+export const MAX_USERNAME_LENGTH = parseInt(getEnv("MAX_USERNAME_LENGTH") || "") || 8;
+export const MAX_PER_GROUP = parseInt(getEnv("MAX_PER_GROUP") || "4");
+export const DISPLAY_TERMS_OF_USE = getEnv("DISPLAY_TERMS_OF_USE") == "true";
+export const NODE_ENV = getEnv("NODE_ENV") || "development";
+export const CONTACT_URL = getEnv("CONTACT_URL") || undefined;
+export const PROFILE_URL = getEnv("PROFILE_URL") || undefined;
+export const POSTHOG_API_KEY: string = (getEnv("POSTHOG_API_KEY") as string) || "";
+export const POSTHOG_URL = getEnv("POSTHOG_URL") || undefined;
+export const DISABLE_ANONYMOUS: boolean = getEnv("DISABLE_ANONYMOUS") === "true";
+export const OPID_LOGIN_SCREEN_PROVIDER = getEnv("OPID_LOGIN_SCREEN_PROVIDER");
+const FALLBACK_LOCALE = getEnv("FALLBACK_LOCALE") || undefined;
export const isMobile = (): boolean => window.innerWidth <= 800 || window.innerHeight <= 600;
diff --git a/front/templater.sh b/front/templater.sh
index a5a28c9c..e6a78e08 100755
--- a/front/templater.sh
+++ b/front/templater.sh
@@ -1,8 +1,7 @@
#!/usr/bin/env bash
set -x
set -o nounset errexit
-template_file_index=dist/index.tmpl.html
-generated_file_index=dist/index.tmpl.html.tmp
+index_file=dist/index.html
tmp_trackcodefile=/tmp/trackcode
# To inject tracking code, you have two choices:
@@ -21,6 +20,6 @@ if [[ "${GA_TRACKING_ID:-}" != "" || "${INSERT_ANALYTICS:-NO}" != "NO" ]]; then
sed "s##${GA_TRACKING_ID:-}#g" "${ANALYTICS_CODE_PATH}" > "$tmp_trackcodefile"
fi
-echo "Templating ${generated_file_index} from ${template_file_index}"
-sed "//r ${tmp_trackcodefile}" "${template_file_index}" > "${generated_file_index}"
+echo "Templating ${index_file}"
+sed --in-place "//r ${tmp_trackcodefile}" "${index_file}"
rm "${tmp_trackcodefile}"
diff --git a/front/webpack.config.ts b/front/webpack.config.ts
index 929af9f6..e5b338fc 100644
--- a/front/webpack.config.ts
+++ b/front/webpack.config.ts
@@ -1,4 +1,5 @@
import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin";
+import fs from 'fs/promises';
import HtmlWebpackPlugin from "html-webpack-plugin";
import MiniCssExtractPlugin from "mini-css-extract-plugin";
import NodePolyfillPlugin from "node-polyfill-webpack-plugin";
@@ -33,6 +34,35 @@ module.exports = {
disableDotRule: true,
},
liveReload: process.env.LIVE_RELOAD != "0" && process.env.LIVE_RELOAD != "false",
+ before: (app) => {
+ let appConfigContent = '';
+ const TEMPLATE_PATH = path.join(__dirname, 'dist', 'env-config.template.js');
+
+ function renderTemplateWithEnvVars(content: string): string {
+ let result = content;
+ const regex = /\$\{([a-zA-Z_]+[a-zA-Z0-9_]*?)\}/g;
+
+ let matched: RegExpExecArray | null;
+ while ((matched = regex.exec(content))) {
+ result = result.replace(`\${${matched[1]}}`, process.env[matched[1]] || '');
+ }
+
+ return result;
+ }
+
+ void (async () => {
+ const content = (await fs.readFile(TEMPLATE_PATH)).toString();
+ appConfigContent = renderTemplateWithEnvVars(content);
+ })();
+
+ app.get('/env-config.js', (_, response) => {
+ response.setHeader('Content-Type', 'application/javascript; charset=utf-8');
+ response.setHeader('Cache-Control', 'no-cache');
+ response.setHeader('Content-Length', Buffer.byteLength(appConfigContent, 'utf8'));
+
+ response.send(appConfigContent);
+ });
+ },
},
module: {
rules: [
@@ -173,7 +203,7 @@ module.exports = {
}),
new MiniCssExtractPlugin({ filename: "[name].[contenthash].css" }),
new HtmlWebpackPlugin({
- template: "./dist/index.tmpl.html.tmp",
+ template: "./dist/index.ejs",
minify: {
collapseWhitespace: true,
keepClosingSlash: true,
@@ -189,33 +219,5 @@ module.exports = {
Phaser: "phaser",
}),
new NodePolyfillPlugin(),
- new webpack.EnvironmentPlugin({
- API_URL: null,
- SKIP_RENDER_OPTIMIZATIONS: false,
- DISABLE_NOTIFICATIONS: false,
- PUSHER_URL: undefined,
- UPLOADER_URL: null,
- ADMIN_URL: null,
- CONTACT_URL: null,
- PROFILE_URL: null,
- ICON_URL: null,
- DEBUG_MODE: null,
- STUN_SERVER: null,
- TURN_SERVER: null,
- TURN_USER: null,
- TURN_PASSWORD: null,
- JITSI_URL: null,
- JITSI_PRIVATE_MODE: null,
- START_ROOM_URL: null,
- MAX_USERNAME_LENGTH: 8,
- MAX_PER_GROUP: 4,
- DISPLAY_TERMS_OF_USE: false,
- POSTHOG_API_KEY: null,
- POSTHOG_URL: null,
- NODE_ENV: mode,
- DISABLE_ANONYMOUS: false,
- OPID_LOGIN_SCREEN_PROVIDER: null,
- FALLBACK_LANGUAGE: null,
- }),
],
} as Configuration & WebpackDevServer.Configuration;