diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
new file mode 100644
index 00000000..c625e46b
--- /dev/null
+++ b/.devcontainer/Dockerfile
@@ -0,0 +1,22 @@
+FROM mcr.microsoft.com/devcontainers/base:debian
+
+RUN apt-get update && apt-get install -y curl
+
+# install nvm
+RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash
+
+RUN export NVM_DIR="$HOME/.nvm" \
+ [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" \
+ [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
+
+# install node
+RUN nvm install
+RUN nvm use
+
+# install corepack 0.10 only one compatible with node 12
+RUN npm install -g corepack@0.10.0 serve@13.0.4
+RUN corepack prepare yarn@2.4.1 --activate
+
+# Next steps:
+# - yarn install
+# - yarn foreach-build-all
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 00000000..5d6cd2c8
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,28 @@
+// For format details, see https://aka.ms/devcontainer.json. For config options, see the
+// README at: https://github.com/devcontainers/templates/tree/main/src/alpine
+{
+ "name": "Alpine",
+ // More info: https://containers.dev/guide/dockerfile
+ "build": {
+ // Path is relative to the devcontainer.json file.
+ "dockerfile": "Dockerfile"
+ },
+ "features": {
+ // "ghcr.io/devcontainers/features/node:1": {
+ // "nodeGypDependencies": true,
+ // "version": "12",
+ // "pnpmVersion": "none",
+ // "nvmVersion": "latest"
+ // }
+ }
+ // Features to add to the dev container. More info: https://containers.dev/features.
+ // "features": {},
+ // Use 'forwardPorts' to make a list of ports inside the container available locally.
+ // "forwardPorts": [],
+ // Use 'postCreateCommand' to run commands after the container is created.
+ // "postCreateCommand": "uname -a",
+ // Configure tool-specific properties.
+ // "customizations": {},
+ // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
+ // "remoteUser": "root"
+}
\ No newline at end of file
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 00000000..05dbe874
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,19 @@
+.dockerignore
+.git
+.gitignore
+.idea
+.vscode
+**/.DS_Store
+**/node_modules
+packages/*/dist
+packages/*/lib
+packages/*/build
+**/tmp
+**/.angular
+**/.venv
+**/.storybook/
+**/coverage/
+**/.env.local
+**/.env.test
+**/.eslintcache
+**/*.log
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 181d90f2..5e93a9b1 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -13,5 +13,6 @@
"eslint.nodePath": ".yarn/sdks",
"typescript.tsdk": ".yarn/sdks/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
- "typescript.preferences.includePackageJsonAutoImports": "on"
-}
+ "typescript.preferences.includePackageJsonAutoImports": "on",
+ "js/ts.tsdk.path": ".yarn/sdks/typescript/lib"
+}
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..0420dbf1
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,64 @@
+# monolith: backend & frontend
+FROM alpine:3.23 AS monolith
+
+LABEL org.opencontainers.image.title="Timeflies" \
+ org.opencontainers.image.description="Web multiplayer game - tactical-RPG." \
+ org.opencontainers.image.url="https://github.com/Chnapy/timeflies" \
+ org.opencontainers.image.source="https://github.com/Chnapy/timeflies" \
+ org.opencontainers.image.version="0.0.1" \
+ org.opencontainers.image.licenses="MIT" \
+ org.opencontainers.image.vendor="Timeflies" \
+ org.opencontainers.image.authors="Richard Haddad" \
+ org.opencontainers.image.base.name="alpine:3.23"
+
+RUN apk add --no-cache \
+ nginx \
+ supervisor \
+ curl tar xz \
+ && curl -fsSL https://unofficial-builds.nodejs.org/download/release/v12.22.12/node-v12.22.12-linux-x64-musl.tar.xz | \
+ tar -xJ -C /usr/local --strip-components=1 \
+ && apk del curl tar xz \
+ && rm -rf /var/cache/apk/*
+
+WORKDIR /app
+
+RUN node --version && npm --version
+
+# install corepack 0.10 only one compatible with node 12
+RUN npm install -g corepack@0.10.0 serve@13.0.4
+RUN corepack prepare yarn@2.4.1 --activate
+
+COPY package.json yarn.lock .yarnrc .yarnrc.yml ./
+COPY ./.yarn/releases ./.yarn/releases
+COPY ./.yarn/plugins ./.yarn/plugins
+COPY ./.yarn/sdks ./.yarn/sdks
+COPY ./packages ./packages
+
+RUN yarn -v && yarn install
+
+COPY babel.config.js tsconfig.json ./
+
+RUN yarn workspace @timeflies/backend p:build-recursive
+
+RUN yarn workspace @timeflies/frontend p:build-recursive
+
+# should be defined before frontend final build
+ENV REACT_APP_SERVER_URL=/api
+ENV REACT_APP_WS_URL=/ws
+
+RUN yarn workspace @timeflies/frontend build
+
+# setup logs folders
+RUN mkdir -p /var/log/supervisord /var/log/nginx /var/run/nginx \
+ && chown -R 755 /var/log/nginx /var/run/nginx \
+ && chmod -R 755 /var/log/supervisord
+
+ENV PORT=40510
+ENV HOST_URL=/api
+
+COPY nginx.conf /etc/nginx/nginx.conf
+COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
+
+EXPOSE 3000
+
+CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
diff --git a/README.md b/README.md
index f60e7dcd..7afd6297 100644
--- a/README.md
+++ b/README.md
@@ -5,17 +5,15 @@
Timeflies
-
+
+
+> Project aborted since 2021
Timeflies is a web multiplayer tactical-RPG.
This game can be played on any modern browser (= chrome or firefox please).
-
-[](https://app.netlify.com/sites/timeflies-game/deploys)
-
-
## Bulk features
- :musical_note: Wonderful sounds & musics
diff --git a/nginx.conf b/nginx.conf
new file mode 100644
index 00000000..84a55e00
--- /dev/null
+++ b/nginx.conf
@@ -0,0 +1,47 @@
+events {
+ worker_connections 1024;
+}
+
+http {
+ include /etc/nginx/mime.types;
+ default_type application/octet-stream;
+
+ server {
+ listen 3000;
+
+ # static files (SPA fallback)
+ location / {
+ root /app/packages/frontend/build;
+ index index.html;
+ try_files $uri $uri/ /index.html;
+ }
+
+ # proxy backend API calls
+ location /api/ {
+ proxy_pass http://localhost:40510/;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection keep-alive;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_cache_bypass $http_upgrade;
+ }
+
+ # proxy backend websocket calls
+ location /ws {
+ proxy_pass http://localhost:40510;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+
+ proxy_read_timeout 86400s;
+ proxy_send_timeout 86400s;
+ }
+ }
+}
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 70438b90..10db6fcf 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -5,7 +5,7 @@
"main": "lib/index.js",
"types": "lib/index.d.ts",
"scripts": {
- "start": "PORT=40510 HOST_URL=https://localhost:40510 JWT_PRIVATE_KEY=foo ts-node src/index.ts",
+ "start": "PORT=40510 HOST_URL=http://localhost:40510 ts-node src/index.ts",
"build-all": "yarn p:build-recursive",
"serve": "yarn node lib/index.js"
},
diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts
index e25fe18b..bdbc6b83 100644
--- a/packages/backend/src/index.ts
+++ b/packages/backend/src/index.ts
@@ -5,30 +5,21 @@ import { json, urlencoded } from 'body-parser';
import cors from "cors";
import express from 'express';
import http from 'http';
-import https from 'https';
import WebSocket from 'ws';
import { config } from './main/config';
import { createGlobalEntities } from './main/global-entities';
import { onAllServicesSocketConnect } from './services/services';
import { getEnv } from './utils/env';
-import { readFileSync } from 'fs';
const port = Number(getEnv('PORT'));
-const isLocalhost = getEnv('HOST_URL').includes('://localhost');
-
// Express
const app = express();
-// TODO do not hardcode
-const origin = isLocalhost
- ? [ 'https://localhost:3000' ]
- : [ 'https://timeflies.fr', 'https://timeflies-game.herokuapp.com' ];
-
app.use(
cors({
- origin,
+ origin: '*',
allowedHeaders: '*'
}),
urlencoded({ extended: false }),
@@ -41,14 +32,7 @@ const globalEntities = createGlobalEntities();
app.post(authEndpoint, globalEntities.services.authService.httpAuthRoute);
-const server = isLocalhost
- // https in localhost to simulate Heroku https
- ? https.createServer({
- key: readFileSync('../../localhost-key.pem'),
- cert: readFileSync('../../localhost.pem')
- }, app)
- // Heroku do not need https server (it causes errors)
- : http.createServer(app);
+const server = http.createServer(app);
// Websocket
diff --git a/packages/backend/src/services/auth/auth-service.ts b/packages/backend/src/services/auth/auth-service.ts
index ed0fccd2..e596a1f0 100644
--- a/packages/backend/src/services/auth/auth-service.ts
+++ b/packages/backend/src/services/auth/auth-service.ts
@@ -10,8 +10,6 @@ import { PlayerCredentialsTimed } from '../../main/global-entities';
import { getEnv } from '../../utils/env';
import { Service } from '../service';
-const privateKey = getEnv('JWT_PRIVATE_KEY');
-
export class AuthService extends Service {
protected afterSocketConnect = () => { };
@@ -72,7 +70,7 @@ export class AuthService extends Service {
playerName
};
- const token = JWT.sign(tokenPayload, privateKey, { algorithm: 'HS256' });
+ const token = JWT.sign(tokenPayload, 'none', { algorithm: 'HS256' });
const playerCredentials: PlayerCredentials = {
playerId,
@@ -100,9 +98,9 @@ export class AuthService extends Service {
}
const token = new URLSearchParams(
- url.startsWith('/?') // localhost
- ? url.substring(2)
- : new URL(url).search
+ url.startsWith('http')
+ ? new URL(url).search
+ : new URL(url, 'http://localhost').search
).get('token')!;
const socketQueryParams: SocketQueryParams = { token };
diff --git a/packages/backend/src/setup-tests.ts b/packages/backend/src/setup-tests.ts
index 94199bef..f7502419 100644
--- a/packages/backend/src/setup-tests.ts
+++ b/packages/backend/src/setup-tests.ts
@@ -3,5 +3,4 @@ import { applySetupTests } from '@timeflies/devtools';
applySetupTests();
process.env.PORT = '1234';
-process.env.HOST_URL = 'https://host.com';
-process.env.JWT_PRIVATE_KEY = 'foo';
+process.env.HOST_URL = 'http://host.com';
diff --git a/packages/backend/src/utils/asset-url.test.ts b/packages/backend/src/utils/asset-url.test.ts
index a7091b98..0c190c3a 100644
--- a/packages/backend/src/utils/asset-url.test.ts
+++ b/packages/backend/src/utils/asset-url.test.ts
@@ -8,7 +8,7 @@ describe('asset url', () => {
});
it('to frontend', () => {
- expect(assetUrl.toFrontend('foo/bar')).toEqual('https://host.com/static/foo/bar');
- expect(assetUrl.toFrontend('/foo/bar')).toEqual('https://host.com/static/foo/bar');
+ expect(assetUrl.toFrontend('foo/bar')).toEqual('http://host.com/static/foo/bar');
+ expect(assetUrl.toFrontend('/foo/bar')).toEqual('http://host.com/static/foo/bar');
});
});
diff --git a/packages/backend/src/utils/env.ts b/packages/backend/src/utils/env.ts
index 45330955..942e9b7a 100644
--- a/packages/backend/src/utils/env.ts
+++ b/packages/backend/src/utils/env.ts
@@ -1,5 +1,5 @@
-const requiredKeys = [ 'PORT', 'HOST_URL', 'JWT_PRIVATE_KEY' ] as const;
+const requiredKeys = [ 'PORT', 'HOST_URL' ] as const;
console.table(requiredKeys.reduce>((acc, key) => {
acc[ key ] = process.env[ key ];
diff --git a/packages/frontend/.env b/packages/frontend/.env
index cdc177b8..2002bfaf 100644
--- a/packages/frontend/.env
+++ b/packages/frontend/.env
@@ -3,4 +3,4 @@ REACT_APP_TITLE=Timeflies | Multiplayer tactical-RPG game
REACT_APP_DESCRIPTION=Timeflies is a free multiplayer tactical-RPG game, with a time-based gameplay.
REACT_APP_AUTHOR=Richard Haddad
REACT_APP_PREVIEW_IMG=https://repository-images.githubusercontent.com/238224907/2e8aad0f-7446-4cd2-a628-577f86f47174
-REACT_APP_URL=https://timeflies.fr
+REACT_APP_URL=https://github.com/Chnapy/timeflies
diff --git a/packages/frontend/.env.local b/packages/frontend/.env.local
index 4042c2ad..e3b7933c 100644
--- a/packages/frontend/.env.local
+++ b/packages/frontend/.env.local
@@ -1 +1,2 @@
-REACT_APP_SERVER_URL=https://localhost:40510
\ No newline at end of file
+REACT_APP_SERVER_URL=http://localhost:40510
+REACT_APP_WS_URL=http://localhost:40510
diff --git a/packages/frontend/.env.test b/packages/frontend/.env.test
index f21e56e8..375a98bf 100644
--- a/packages/frontend/.env.test
+++ b/packages/frontend/.env.test
@@ -1 +1,2 @@
-REACT_APP_SERVER_URL=foo
\ No newline at end of file
+REACT_APP_SERVER_URL=foo
+REACT_APP_WS_URL=foo
\ No newline at end of file
diff --git a/packages/frontend/mock-server/index.ts b/packages/frontend/mock-server/index.ts
index 096a4e4a..f3ada0f5 100644
--- a/packages/frontend/mock-server/index.ts
+++ b/packages/frontend/mock-server/index.ts
@@ -17,13 +17,13 @@ app.use('/public', express.static('mock-server/public'));
const server = http.createServer(app);
const port = 40510;
-const wss = new WebSocketServer({ server });
+const ws = new WebSocketServer({ server });
console.log('Mock server started');
server.listen(port, () => {
console.log('server listening address', server.address());
- console.log('ws listening address', wss.address());
+ console.log('ws listening address', ws.address());
});
const turnsOrder = [ 'c2', 'c1', 'c3' ];
@@ -173,7 +173,7 @@ const battleLoadData: BattleLoadData = {
const sendAll = action => {
console.log('sendAll: %s', JSON.stringify(action));
- wss.clients.forEach(ws => {
+ ws.clients.forEach(ws => {
ws.send(JSON.stringify([ action ]));
});
};
@@ -194,7 +194,7 @@ const sendAllNextTurnInfos = () => setTimeout(() =>
BattleTurnStartMessage(cycleEngine.getNextTurnInfos())
), 1000);
-wss.on('connection', function (ws) {
+ws.on('connection', function (ws) {
const startTime = Date.now() + 5000;
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index a60576cf..57b4c899 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -5,7 +5,7 @@
"main": "lib/index.js",
"types": "lib/index.d.ts",
"scripts": {
- "start": "yarn copy-assets && HTTPS=true SSL_CRT_FILE=../../localhost.pem SSL_KEY_FILE=../../localhost-key.pem react-scripts start",
+ "start": "yarn copy-assets && react-scripts start",
"build": "yarn copy-assets && react-scripts build",
"build-all": "yarn p:build-recursive && yarn build",
"test": "react-scripts test",
diff --git a/packages/frontend/src/connected-socket/hooks/use-connected-socket-helper.ts b/packages/frontend/src/connected-socket/hooks/use-connected-socket-helper.ts
index 39e2d594..f1117362 100644
--- a/packages/frontend/src/connected-socket/hooks/use-connected-socket-helper.ts
+++ b/packages/frontend/src/connected-socket/hooks/use-connected-socket-helper.ts
@@ -7,7 +7,12 @@ import { useDispatchMessageErrorIfAny } from '../../error-list/hooks/use-dispatc
import { CredentialsLoginAction } from '../../login-page/store/credentials-actions';
import { useGameSelector } from '../../store/hooks/use-game-selector';
-const createSocketHelper = getSocketHelperCreator(getEnv('REACT_APP_SERVER_URL'));
+const socketUrl = new URL(
+ getEnv('REACT_APP_WS_URL'),
+ window.location.href
+).href;
+
+const createSocketHelper = getSocketHelperCreator(socketUrl);
export const useConnectedSocketHelper = () => {
const credentialsToken = useGameSelector(state => state.credentials?.token);
diff --git a/packages/frontend/src/env.ts b/packages/frontend/src/env.ts
index 659e3c58..48d28a81 100644
--- a/packages/frontend/src/env.ts
+++ b/packages/frontend/src/env.ts
@@ -1,5 +1,5 @@
-const requiredKeys = [ 'REACT_APP_SERVER_URL' ] as const;
+const requiredKeys = [ 'REACT_APP_SERVER_URL', 'REACT_APP_WS_URL' ] as const;
console.table(requiredKeys.reduce>((acc, key) => {
acc[ key ] = process.env[ key ];
diff --git a/packages/frontend/src/login-page/hooks/use-login-form-state.ts b/packages/frontend/src/login-page/hooks/use-login-form-state.ts
index 7edb24d4..0ba25d53 100644
--- a/packages/frontend/src/login-page/hooks/use-login-form-state.ts
+++ b/packages/frontend/src/login-page/hooks/use-login-form-state.ts
@@ -9,8 +9,8 @@ import { ErrorListAddAction } from '../../error-list/store/error-list-actions';
import { CredentialsLoginAction } from '../store/credentials-actions';
const authUrl = new URL(
- authEndpoint,
- getEnv('REACT_APP_SERVER_URL')
+ getEnv('REACT_APP_SERVER_URL') + authEndpoint,
+ window.location.href
).href;
const playerNameInputName = 'playerName';
diff --git a/packages/socket-client/src/socket/create-socket-helper.test.ts b/packages/socket-client/src/socket/create-socket-helper.test.ts
index 729587c3..e663e9fe 100644
--- a/packages/socket-client/src/socket/create-socket-helper.test.ts
+++ b/packages/socket-client/src/socket/create-socket-helper.test.ts
@@ -30,9 +30,9 @@ describe('# create socket helper', () => {
it('connect to correct endpoint', () => {
mockWebSocket();
- const socketHelper = getSocketHelperCreator('https://myendpoint.foo.bar/toto')('my_token');
+ const socketHelper = getSocketHelperCreator('http://myendpoint.foo.bar/toto')('my_token');
- expect(socketHelper.url).toEqual('wss://myendpoint.foo.bar/toto?token=my_token');
+ expect(socketHelper.url).toEqual('ws://myendpoint.foo.bar/toto?token=my_token');
});
describe('hearbeat timeout loop', () => {
diff --git a/packages/socket-client/src/socket/create-socket-helper.ts b/packages/socket-client/src/socket/create-socket-helper.ts
index d23faa47..4a1e454f 100644
--- a/packages/socket-client/src/socket/create-socket-helper.ts
+++ b/packages/socket-client/src/socket/create-socket-helper.ts
@@ -2,7 +2,7 @@ import { extractMessagesFromEvent, heartbeatMessageValue, Message } from '@timef
export type SocketHelper = ReturnType>;
-const createEndpoint = (protocol: 'https' | 'wss', url: string) => {
+const createEndpoint = (protocol: 'http' | 'ws', url: string) => {
const startIndex = url.indexOf('://');
if (startIndex !== -1) {
@@ -14,7 +14,7 @@ const createEndpoint = (protocol: 'https' | 'wss', url: string) => {
const createAuthenticatedEndpoint = (token: string, serverUrl: string): string => {
- const baseEndpoint = createEndpoint('wss', serverUrl);
+ const baseEndpoint = createEndpoint('ws', serverUrl);
const url = new URL(baseEndpoint);
url.searchParams.set('token', token);
diff --git a/supervisord.conf b/supervisord.conf
new file mode 100644
index 00000000..38a8aaf7
--- /dev/null
+++ b/supervisord.conf
@@ -0,0 +1,36 @@
+[supervisord]
+nodaemon=true
+user=root
+logfile=/dev/fd/1
+logfile_maxbytes=0
+pidfile=/var/run/supervisord.pid
+
+[program:backend]
+command=yarn node /app/packages/backend/lib/index.js
+directory=/app/packages/backend
+autostart=true
+autorestart=true
+stdout_logfile=/dev/fd/1
+stdout_logfile_maxbytes=0
+# stderr_logfile=/dev/fd/2
+redirect_stderr=true
+# environment=DOTNET_SYSTEM_GLOBALIZATION_INVARIANT="0",ASPNETCORE_ENVIRONMENT="Production",ASPNETCORE_URLS="http://+:5000"
+
+[program:nginx]
+command=nginx -g "daemon off;"
+autostart=true
+autorestart=true
+stdout_logfile=/dev/fd/1
+stdout_logfile_maxbytes=0
+# stderr_logfile=/dev/fd/2
+redirect_stderr=true
+
+[program:healthcheck] # global healthcheck
+command=curl -f http://localhost:3000 || exit 1
+autostart=false
+autorestart=true
+startsecs=1
+stdout_logfile=/dev/fd/1
+stdout_logfile_maxbytes=0
+# stderr_logfile=/dev/fd/2
+redirect_stderr=true