Merge upstream/main into public-achivements-ui

This commit is contained in:
Aotumuri
2026-04-03 14:42:57 +09:00
47 changed files with 1234 additions and 655 deletions
+3 -3
View File
@@ -126,8 +126,6 @@ jobs:
chmod 600 ~/.ssh/id_rsa
- name: 🚢 Deploy
env:
CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
GHCR_REPO: ${{ vars.GHCR_REPO }}
GHCR_USERNAME: ${{ vars.GHCR_USERNAME }}
ENV: ${{ inputs.target_domain == 'openfront.io' && 'prod' || 'staging' }}
@@ -147,10 +145,12 @@ jobs:
echo "Deployment created in ${SECONDS} seconds" >> $GITHUB_STEP_SUMMARY
echo "::endgroup::"
- name: ⏳ Wait for deployment to start
env:
API_KEY: ${{ secrets.API_KEY }}
run: |
echo "::group::Wait for deployment to start"
set -euxo pipefail
while [ "$(curl -s https://${FQDN}/commit.txt)" != "${GITHUB_SHA}" ]; do
while [ "$(curl -s -H "X-API-Key: ${API_KEY}" https://${FQDN}/commit.txt)" != "${GITHUB_SHA}" ]; do
if [ "$SECONDS" -ge 300 ]; then
echo "Timeout: deployment did not start within 5 minutes"
exit 1
+8 -12
View File
@@ -64,8 +64,6 @@ jobs:
chmod 600 ~/.ssh/id_rsa
- name: 🚀 Deploy image
env:
CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
GHCR_REPO: openfront-prod
GHCR_USERNAME: ${{ vars.GHCR_USERNAME }}
DOMAIN: ${{ vars.DOMAIN }}
@@ -82,10 +80,11 @@ jobs:
- name: ⏳ Wait for deployment to start
env:
FQDN: alpha.${{ vars.DOMAIN }}
API_KEY: ${{ secrets.API_KEY }}
run: |
echo "::group::Wait for deployment to start"
set -euxo pipefail
while [ "$(curl -s https://${FQDN}/commit.txt)" != "${GITHUB_SHA}" ]; do
while [ "$(curl -s -H "X-API-Key: ${API_KEY}" https://${FQDN}/commit.txt)" != "${GITHUB_SHA}" ]; do
if [ "$SECONDS" -ge 300 ]; then
echo "Timeout: deployment did not start within 5 minutes"
exit 1
@@ -115,8 +114,6 @@ jobs:
chmod 600 ~/.ssh/id_rsa
- name: 🚀 Deploy image
env:
CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
GHCR_REPO: ${{ vars.GHCR_REPO }}
GHCR_USERNAME: ${{ vars.GHCR_USERNAME }}
DOMAIN: ${{ vars.DOMAIN }}
@@ -133,10 +130,11 @@ jobs:
- name: ⏳ Wait for deployment to start
env:
FQDN: beta.${{ vars.DOMAIN }}
API_KEY: ${{ secrets.API_KEY }}
run: |
echo "::group::Wait for deployment to start"
set -euxo pipefail
while [ "$(curl -s https://${FQDN}/commit.txt)" != "${GITHUB_SHA}" ]; do
while [ "$(curl -s -H "X-API-Key: ${API_KEY}" https://${FQDN}/commit.txt)" != "${GITHUB_SHA}" ]; do
if [ "$SECONDS" -ge 300 ]; then
echo "Timeout: deployment did not start within 5 minutes"
exit 1
@@ -166,8 +164,6 @@ jobs:
chmod 600 ~/.ssh/id_rsa
- name: 🚀 Deploy image
env:
CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
GHCR_REPO: ${{ vars.GHCR_REPO }}
GHCR_USERNAME: ${{ vars.GHCR_USERNAME }}
DOMAIN: ${{ vars.DOMAIN }}
@@ -184,10 +180,11 @@ jobs:
- name: ⏳ Wait for deployment to start
env:
FQDN: blue.${{ vars.DOMAIN }}
API_KEY: ${{ secrets.API_KEY }}
run: |
echo "::group::Wait for deployment to start"
set -euxo pipefail
while [ "$(curl -s https://${FQDN}/commit.txt)" != "${GITHUB_SHA}" ]; do
while [ "$(curl -s -H "X-API-Key: ${API_KEY}" https://${FQDN}/commit.txt)" != "${GITHUB_SHA}" ]; do
if [ "$SECONDS" -ge 300 ]; then
echo "Timeout: deployment did not start within 5 minutes"
exit 1
@@ -217,8 +214,6 @@ jobs:
chmod 600 ~/.ssh/id_rsa
- name: 🚀 Deploy image
env:
CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
GHCR_REPO: ${{ vars.GHCR_REPO }}
GHCR_USERNAME: ${{ vars.GHCR_USERNAME }}
DOMAIN: ${{ vars.DOMAIN }}
@@ -235,10 +230,11 @@ jobs:
- name: ⏳ Wait for deployment to start
env:
FQDN: green.${{ vars.DOMAIN }}
API_KEY: ${{ secrets.API_KEY }}
run: |
echo "::group::Wait for deployment to start"
set -euxo pipefail
while [ "$(curl -s https://${FQDN}/commit.txt)" != "${GITHUB_SHA}" ]; do
while [ "$(curl -s -H "X-API-Key: ${API_KEY}" https://${FQDN}/commit.txt)" != "${GITHUB_SHA}" ]; do
if [ "$SECONDS" -ge 300 ]; then
echo "Timeout: deployment did not start within 5 minutes"
exit 1
+10 -18
View File
@@ -38,24 +38,14 @@ FROM base
RUN apt-get update && apt-get install -y \
nginx \
curl \
jq \
wget \
supervisor \
apache2-utils \
&& rm -rf /var/lib/apt/lists/*
RUN curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb > cloudflared.deb \
&& dpkg -i cloudflared.deb \
&& rm cloudflared.deb
# Update worker_connections in nginx.conf
RUN sed -i 's/worker_connections [0-9]*/worker_connections 8192/' /etc/nginx/nginx.conf
# Create cloudflared directory with proper permissions
RUN mkdir -p /etc/cloudflared && \
chown -R node:node /etc/cloudflared && \
chmod -R 755 /etc/cloudflared
# Setup supervisor configuration
RUN mkdir -p /var/log/supervisor
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
@@ -64,10 +54,6 @@ COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
COPY nginx.conf /etc/nginx/conf.d/default.conf
RUN rm -f /etc/nginx/sites-enabled/default
# Copy and make executable the startup script
COPY startup.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/startup.sh
# Copy production node_modules from prod-deps stage (cached separately from build)
COPY --from=prod-deps /usr/src/app/node_modules ./node_modules
COPY package*.json ./
@@ -87,8 +73,14 @@ ARG GIT_COMMIT=unknown
RUN echo "$GIT_COMMIT" > static/commit.txt
ENV GIT_COMMIT="$GIT_COMMIT"
ENV CF_CONFIG_PATH=/etc/cloudflared/config.yml
ENV CF_CREDS_PATH=/etc/cloudflared/creds.json
# Use the startup script as the entrypoint
ENTRYPOINT ["/usr/local/bin/startup.sh"]
RUN <<'EOF' tee /usr/local/bin/start.sh
#!/bin/sh
if [ "$DOMAIN" = openfront.dev ] && [ "$SUBDOMAIN" != main ]; then
exec timeout 18h /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
else
exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
fi
EOF
RUN chmod +x /usr/local/bin/start.sh
ENTRYPOINT ["/usr/local/bin/start.sh"]
-2
View File
@@ -134,8 +134,6 @@ ENV=$ENV
HOST=$HOST
GHCR_IMAGE=$GHCR_IMAGE
GHCR_TOKEN=$GHCR_TOKEN
CF_ACCOUNT_ID=$CF_ACCOUNT_ID
CF_API_TOKEN=$CF_API_TOKEN
TURNSTILE_SECRET_KEY=$TURNSTILE_SECRET_KEY
API_KEY=$API_KEY
DOMAIN=$DOMAIN
-3
View File
@@ -6,9 +6,6 @@ GHCR_USERNAME=username
GHCR_REPO=your-repo-name
GHCR_TOKEN=your_docker_token_here
# Cloudflare Configuration
CF_ACCOUNT_ID=your_cloudflare_account_id
CF_API_TOKEN=your_cloudflare_api_token
DOMAIN=your-domain.com
# API Key
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

@@ -0,0 +1,195 @@
{
"name": "mediterranean",
"nations": [
{
"coordinates": [141, 574],
"name": "Lusitania",
"flag": "West Roman Empire"
},
{
"coordinates": [464, 519],
"name": "Terraconensis",
"flag": "West Roman Empire"
},
{
"coordinates": [353, 787],
"name": "Baetica",
"flag": "West Roman Empire"
},
{
"coordinates": [699, 340],
"name": "Narbonensis",
"flag": "West Roman Empire"
},
{
"coordinates": [596, 91],
"name": "Aquitania",
"flag": "West Roman Empire"
},
{
"coordinates": [910, 218],
"name": "Alpes",
"flag": "West Roman Empire"
},
{
"coordinates": [1053, 39],
"name": "Raetia",
"flag": "West Roman Empire"
},
{
"coordinates": [1316, 88],
"name": "Noricum",
"flag": "West Roman Empire"
},
{
"coordinates": [1307, 479],
"name": "Italia",
"flag": "West Roman Empire"
},
{
"coordinates": [1058, 534],
"name": "Corsica et Sardinia",
"flag": "West Roman Empire"
},
{
"coordinates": [1350, 762],
"name": "Sicilia",
"flag": "West Roman Empire"
},
{
"coordinates": [1553, 42],
"name": "Pannonia",
"flag": "West Roman Empire"
},
{
"coordinates": [1522, 301],
"name": "Dalmatia",
"flag": "West Roman Empire"
},
{
"coordinates": [1910, 119],
"name": "Dacia",
"flag": "West Roman Empire"
},
{
"coordinates": [1811, 402],
"name": "Macedonia",
"flag": "West Roman Empire"
},
{
"coordinates": [1654, 560],
"name": "Epirus",
"flag": "West Roman Empire"
},
{
"coordinates": [1774, 774],
"name": "Achaia",
"flag": "West Roman Empire"
},
{
"coordinates": [2030, 457],
"name": "Thracia",
"flag": "West Roman Empire"
},
{
"coordinates": [2114, 250],
"name": "Moesia",
"flag": "West Roman Empire"
},
{
"coordinates": [2428, 200],
"name": "Bosporan Kingdom",
"flag": ""
},
{
"coordinates": [2065, 690],
"name": "Asia",
"flag": "West Roman Empire"
},
{
"coordinates": [2354, 505],
"name": "Pontus",
"flag": "West Roman Empire"
},
{
"coordinates": [2325, 669],
"name": "Galatia",
"flag": "West Roman Empire"
},
{
"coordinates": [2447, 809],
"name": "Cilicia",
"flag": "West Roman Empire"
},
{
"coordinates": [2196, 828],
"name": "Lycia",
"flag": "West Roman Empire"
},
{
"coordinates": [2379, 942],
"name": "Cyprus",
"flag": "West Roman Empire"
},
{
"coordinates": [2516, 648],
"name": "Cappadocia",
"flag": "West Roman Empire"
},
{
"coordinates": [2772, 845],
"name": "Mesopotamia",
"flag": "West Roman Empire"
},
{
"coordinates": [2584, 990],
"name": "Syria",
"flag": "West Roman Empire"
},
{
"coordinates": [2490, 1141],
"name": "Judaea",
"flag": "West Roman Empire"
},
{
"coordinates": [2481, 1292],
"name": "Arabia Petraea",
"flag": "West Roman Empire"
},
{
"coordinates": [2263, 1240],
"name": "Aegyptus",
"flag": "West Roman Empire"
},
{
"coordinates": [1799, 1096],
"name": "Cyrenaica et Creta",
"flag": "West Roman Empire"
},
{
"coordinates": [2815, 659],
"name": "Sassanid Empire",
"flag": "Sassanid Empire"
},
{
"coordinates": [1091, 989],
"name": "Africa Proconsularis",
"flag": "West Roman Empire"
},
{
"coordinates": [597, 910],
"name": "Mauretania Caesariensis",
"flag": "West Roman Empire"
},
{
"coordinates": [264, 1028],
"name": "Mauretania Tingitania",
"flag": "West Roman Empire"
},
{
"coordinates": [584, 1199],
"name": "Numidia",
"flag": "Amazigh flag"
}
]
}
+1
View File
@@ -84,6 +84,7 @@ var maps = []struct {
{Name: "sanfrancisco"},
{Name: "aegean"},
{Name: "milkyway"},
{Name: "mediterranean"},
{Name: "big_plains", IsTest: true},
{Name: "half_land_half_ocean", IsTest: true},
{Name: "ocean_and_land", IsTest: true},
+7 -1
View File
@@ -12,7 +12,13 @@ proxy_cache_path /var/cache/nginx/api levels=1:2 keys_zone=API_CACHE:10m inactiv
server {
listen 80 default_server;
# Large cookie support
large_client_header_buffers 4 32k;
proxy_buffer_size 32k;
proxy_busy_buffers_size 48k;
proxy_buffers 4 32k;
# Logging
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
+247 -376
View File
@@ -24,7 +24,7 @@
"dompurify": "^3.3.2",
"dotenv": "^16.5.0",
"ejs": "^3.1.10",
"express": "^4.22.1",
"express": "^5.2.1",
"express-rate-limit": "^7.5.0",
"fastpriorityqueue": "^0.7.5",
"howler": "^2.2.4",
@@ -52,7 +52,7 @@
"@types/benchmark": "^2.1.5",
"@types/chai": "^4.3.17",
"@types/d3": "^7.4.3",
"@types/express": "^4.17.23",
"@types/express": "^5.0.6",
"@types/google-protobuf": "^3.15.12",
"@types/hammerjs": "^2.0.46",
"@types/howler": "^2.2.12",
@@ -4504,21 +4504,20 @@
"license": "MIT"
},
"node_modules/@types/express": {
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz",
"integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==",
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz",
"integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
"license": "MIT",
"dependencies": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "^4.17.33",
"@types/qs": "*",
"@types/serve-static": "*"
"@types/express-serve-static-core": "^5.0.0",
"@types/serve-static": "^2"
}
},
"node_modules/@types/express-serve-static-core": {
"version": "4.19.6",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz",
"integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==",
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz",
"integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==",
"license": "MIT",
"dependencies": {
"@types/node": "*",
@@ -4592,12 +4591,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/mime": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
"license": "MIT"
},
"node_modules/@types/msgpack5": {
"version": "3.4.6",
"resolved": "https://registry.npmjs.org/@types/msgpack5/-/msgpack5-3.4.6.tgz",
@@ -4632,9 +4625,9 @@
}
},
"node_modules/@types/qs": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
"integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
"version": "6.15.0",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz",
"integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==",
"license": "MIT"
},
"node_modules/@types/range-parser": {
@@ -4661,24 +4654,22 @@
"license": "MIT"
},
"node_modules/@types/send": {
"version": "0.17.5",
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz",
"integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",
"integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==",
"license": "MIT",
"dependencies": {
"@types/mime": "^1",
"@types/node": "*"
}
},
"node_modules/@types/serve-static": {
"version": "1.15.8",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz",
"integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz",
"integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==",
"license": "MIT",
"dependencies": {
"@types/http-errors": "*",
"@types/node": "*",
"@types/send": "*"
"@types/node": "*"
}
},
"node_modules/@types/sinon": {
@@ -5219,9 +5210,9 @@
"license": "BSD-3-Clause"
},
"node_modules/@xmldom/xmldom": {
"version": "0.8.11",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz",
"integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==",
"version": "0.8.12",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.12.tgz",
"integrity": "sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -5242,13 +5233,13 @@
}
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
"integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
"license": "MIT",
"dependencies": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
"mime-types": "^3.0.0",
"negotiator": "^1.0.0"
},
"engines": {
"node": ">= 0.6"
@@ -5361,12 +5352,6 @@
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0"
},
"node_modules/array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT"
},
"node_modules/assertion-error": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
@@ -5525,52 +5510,23 @@
}
},
"node_modules/body-parser": {
"version": "1.20.4",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
"integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
"integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
"license": "MIT",
"dependencies": {
"bytes": "~3.1.2",
"content-type": "~1.0.5",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "~1.2.0",
"http-errors": "~2.0.1",
"iconv-lite": "~0.4.24",
"on-finished": "~2.4.1",
"qs": "~6.14.0",
"raw-body": "~2.5.3",
"type-is": "~1.6.18",
"unpipe": "~1.0.0"
"bytes": "^3.1.2",
"content-type": "^1.0.5",
"debug": "^4.4.3",
"http-errors": "^2.0.0",
"iconv-lite": "^0.7.0",
"on-finished": "^2.4.1",
"qs": "^6.14.1",
"raw-body": "^3.0.1",
"type-is": "^2.0.1"
},
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/body-parser/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/body-parser/node_modules/http-errors": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
"license": "MIT",
"dependencies": {
"depd": "~2.0.0",
"inherits": "~2.0.4",
"setprototypeof": "~1.2.0",
"statuses": "~2.0.2",
"toidentifier": "~1.0.1"
},
"engines": {
"node": ">= 0.8"
"node": ">=18"
},
"funding": {
"type": "opencollective",
@@ -5578,30 +5534,19 @@
}
},
"node_modules/body-parser/node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
"integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3"
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/body-parser/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/body-parser/node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/boolbase": {
@@ -5617,9 +5562,9 @@
"license": "MIT"
},
"node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz",
"integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
@@ -6258,15 +6203,16 @@
"license": "MIT"
},
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
"integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==",
"license": "MIT",
"dependencies": {
"safe-buffer": "5.2.1"
},
"engines": {
"node": ">= 0.6"
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/content-type": {
@@ -6288,10 +6234,13 @@
}
},
"node_modules/cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
"license": "MIT"
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
"integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
"license": "MIT",
"engines": {
"node": ">=6.6.0"
}
},
"node_modules/create-require": {
"version": "1.1.1",
@@ -6861,10 +6810,9 @@
}
},
"node_modules/debug": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"dev": true,
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@@ -6956,16 +6904,6 @@
"node": ">= 0.8"
}
},
"node_modules/destroy": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
"license": "MIT",
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/detect-libc": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
@@ -7625,45 +7563,42 @@
}
},
"node_modules/express": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
"integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
"license": "MIT",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "~1.20.3",
"content-disposition": "~0.5.4",
"content-type": "~1.0.4",
"cookie": "~0.7.1",
"cookie-signature": "~1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "~1.3.1",
"fresh": "~0.5.2",
"http-errors": "~2.0.0",
"merge-descriptors": "1.0.3",
"methods": "~1.1.2",
"on-finished": "~2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "~0.1.12",
"proxy-addr": "~2.0.7",
"qs": "~6.14.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "~0.19.0",
"serve-static": "~1.16.2",
"setprototypeof": "1.2.0",
"statuses": "~2.0.1",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
"accepts": "^2.0.0",
"body-parser": "^2.2.1",
"content-disposition": "^1.0.0",
"content-type": "^1.0.5",
"cookie": "^0.7.1",
"cookie-signature": "^1.2.1",
"debug": "^4.4.0",
"depd": "^2.0.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
"finalhandler": "^2.1.0",
"fresh": "^2.0.0",
"http-errors": "^2.0.0",
"merge-descriptors": "^2.0.0",
"mime-types": "^3.0.0",
"on-finished": "^2.4.1",
"once": "^1.4.0",
"parseurl": "^1.3.3",
"proxy-addr": "^2.0.7",
"qs": "^6.14.0",
"range-parser": "^1.2.1",
"router": "^2.2.0",
"send": "^1.1.0",
"serve-static": "^2.2.0",
"statuses": "^2.0.1",
"type-is": "^2.0.1",
"vary": "^1.1.2"
},
"engines": {
"node": ">= 0.10.0"
"node": ">= 18"
},
"funding": {
"type": "opencollective",
@@ -7685,21 +7620,6 @@
"express": "^4.11 || 5 || ^5.0.0-beta.1"
}
},
"node_modules/express/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/express/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -7863,38 +7783,26 @@
}
},
"node_modules/finalhandler": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
"integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"statuses": "2.0.1",
"unpipe": "~1.0.0"
"debug": "^4.4.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"on-finished": "^2.4.1",
"parseurl": "^1.3.3",
"statuses": "^2.0.1"
},
"engines": {
"node": ">= 0.8"
"node": ">= 18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/finalhandler/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/finalhandler/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/flat-cache": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
@@ -7946,12 +7854,12 @@
}
},
"node_modules/fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
"integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
"node": ">= 0.8"
}
},
"node_modules/fs-constants": {
@@ -8130,9 +8038,9 @@
}
},
"node_modules/glob/node_modules/brace-expansion": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz",
"integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==",
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -8274,19 +8182,23 @@
"license": "MIT"
},
"node_modules/http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
"license": "MIT",
"dependencies": {
"depd": "2.0.0",
"inherits": "2.0.4",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"toidentifier": "1.0.1"
"depd": "~2.0.0",
"inherits": "~2.0.4",
"setprototypeof": "~1.2.0",
"statuses": "~2.0.2",
"toidentifier": "~1.0.1"
},
"engines": {
"node": ">= 0.8"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/http-proxy-agent": {
@@ -8540,6 +8452,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/is-promise": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
"license": "MIT"
},
"node_modules/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
@@ -9618,19 +9536,22 @@
"license": "CC0-1.0"
},
"node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
"node": ">= 0.8"
}
},
"node_modules/merge-descriptors": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
"integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
@@ -9645,15 +9566,6 @@
"node": ">= 8"
}
},
"node_modules/methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
@@ -9668,37 +9580,29 @@
"node": ">=8.6"
}
},
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"license": "MIT",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
"integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
"mime-db": "^1.54.0"
},
"engines": {
"node": ">= 0.6"
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/mimic-function": {
@@ -9741,9 +9645,9 @@
}
},
"node_modules/minimatch/node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
"integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9850,9 +9754,9 @@
"license": "MIT"
},
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
@@ -10001,7 +9905,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dev": true,
"license": "ISC",
"dependencies": {
"wrappy": "1"
@@ -10200,10 +10103,14 @@
}
},
"node_modules/path-to-regexp": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
"license": "MIT"
"version": "8.4.1",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.1.tgz",
"integrity": "sha512-fvU78fIjZ+SBM9YwCknCvKOUKkLVqtWDVctl0s7xIqfmfb38t2TT4ZU2gHm+Z8xGwgW+QWEU3oQSAzIbo89Ggw==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/pathe": {
"version": "0.2.0",
@@ -10573,9 +10480,9 @@
}
},
"node_modules/qs": {
"version": "6.14.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
"integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
"version": "6.15.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz",
"integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
@@ -10618,61 +10525,36 @@
}
},
"node_modules/raw-body": {
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
"integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
"integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
"license": "MIT",
"dependencies": {
"bytes": "~3.1.2",
"http-errors": "~2.0.1",
"iconv-lite": "~0.4.24",
"iconv-lite": "~0.7.0",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
"node": ">= 0.10"
}
},
"node_modules/raw-body/node_modules/http-errors": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
"node_modules/raw-body/node_modules/iconv-lite": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
"integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
"license": "MIT",
"dependencies": {
"depd": "~2.0.0",
"inherits": "~2.0.4",
"setprototypeof": "~1.2.0",
"statuses": "~2.0.2",
"toidentifier": "~1.0.1"
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">= 0.8"
"node": ">=0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/raw-body/node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/raw-body/node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/rc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
@@ -10871,6 +10753,22 @@
"fsevents": "~2.3.2"
}
},
"node_modules/router": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
"integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"depd": "^2.0.0",
"is-promise": "^4.0.0",
"parseurl": "^1.3.3",
"path-to-regexp": "^8.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -11002,66 +10900,48 @@
"license": "MIT"
},
"node_modules/send": {
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
"integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"mime": "1.6.0",
"ms": "2.1.3",
"on-finished": "2.4.1",
"range-parser": "~1.2.1",
"statuses": "2.0.1"
"debug": "^4.4.3",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
"fresh": "^2.0.0",
"http-errors": "^2.0.1",
"mime-types": "^3.0.2",
"ms": "^2.1.3",
"on-finished": "^2.4.1",
"range-parser": "^1.2.1",
"statuses": "^2.0.2"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/send/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/send/node_modules/debug/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/send/node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/serve-static": {
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
"integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
"license": "MIT",
"dependencies": {
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "0.19.0"
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"parseurl": "^1.3.3",
"send": "^1.2.0"
},
"engines": {
"node": ">= 0.8.0"
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/setprototypeof": {
@@ -11393,9 +11273,9 @@
"license": "MIT"
},
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
@@ -12006,13 +11886,14 @@
}
},
"node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
"license": "MIT",
"dependencies": {
"media-typer": "0.3.0",
"mime-types": "~2.1.24"
"content-type": "^1.0.5",
"media-typer": "^1.1.0",
"mime-types": "^3.0.0"
},
"engines": {
"node": ">= 0.6"
@@ -12126,15 +12007,6 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
"license": "MIT",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/uuid": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
@@ -13195,7 +13067,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true,
"license": "ISC"
},
"node_modules/ws": {
+2 -2
View File
@@ -36,7 +36,7 @@
"@types/benchmark": "^2.1.5",
"@types/chai": "^4.3.17",
"@types/d3": "^7.4.3",
"@types/express": "^4.17.23",
"@types/express": "^5.0.6",
"@types/google-protobuf": "^3.15.12",
"@types/hammerjs": "^2.0.46",
"@types/howler": "^2.2.12",
@@ -107,7 +107,7 @@
"dompurify": "^3.3.2",
"dotenv": "^16.5.0",
"ejs": "^3.1.10",
"express": "^4.22.1",
"express": "^5.2.1",
"express-rate-limit": "^7.5.0",
"fastpriorityqueue": "^0.7.5",
"howler": "^2.2.4",
+5
View File
@@ -1231,6 +1231,11 @@
"continent": "North America",
"name": "Maine"
},
{
"code": "Massachusetts",
"continent": "North America",
"name": "Massachusetts"
},
{
"code": "mw",
"continent": "Africa",
+5 -3
View File
@@ -6,6 +6,7 @@
"lang_code": "en"
},
"common": {
"not_logged_in": "Not logged in",
"close": "Close",
"copy": "Copy",
"paste": "Paste",
@@ -362,7 +363,8 @@
"arctic": "Arctic",
"sanfrancisco": "San Francisco",
"aegean": "Aegean",
"milkyway": "Milky Way"
"milkyway": "Milky Way",
"mediterranean": "Mediterranean"
},
"map_categories": {
"featured": "Featured",
@@ -950,12 +952,12 @@
"territory_patterns": {
"title": "Skins",
"purchase": "Purchase",
"not_logged_in": "Not logged in",
"pattern": {
"default": "Default"
},
"select_skin": "Select Skin",
"selected": "selected"
"selected": "selected",
"search": "Search..."
},
"cosmetics": {
"artist_label": "Artist:"
+210
View File
@@ -0,0 +1,210 @@
{
"map": {
"height": 1448,
"num_land_tiles": 2644620,
"width": 2848
},
"map16x": {
"height": 362,
"num_land_tiles": 159261,
"width": 712
},
"map4x": {
"height": 724,
"num_land_tiles": 652563,
"width": 1424
},
"name": "mediterranean",
"nations": [
{
"coordinates": [141, 574],
"flag": "West Roman Empire",
"name": "Lusitania"
},
{
"coordinates": [464, 519],
"flag": "West Roman Empire",
"name": "Terraconensis"
},
{
"coordinates": [353, 787],
"flag": "West Roman Empire",
"name": "Baetica"
},
{
"coordinates": [699, 340],
"flag": "West Roman Empire",
"name": "Narbonensis"
},
{
"coordinates": [596, 91],
"flag": "West Roman Empire",
"name": "Aquitania"
},
{
"coordinates": [910, 218],
"flag": "West Roman Empire",
"name": "Alpes"
},
{
"coordinates": [1053, 39],
"flag": "West Roman Empire",
"name": "Raetia"
},
{
"coordinates": [1316, 88],
"flag": "West Roman Empire",
"name": "Noricum"
},
{
"coordinates": [1307, 479],
"flag": "West Roman Empire",
"name": "Italia"
},
{
"coordinates": [1058, 534],
"flag": "West Roman Empire",
"name": "Corsica et Sardinia"
},
{
"coordinates": [1350, 762],
"flag": "West Roman Empire",
"name": "Sicilia"
},
{
"coordinates": [1553, 42],
"flag": "West Roman Empire",
"name": "Pannonia"
},
{
"coordinates": [1522, 301],
"flag": "West Roman Empire",
"name": "Dalmatia"
},
{
"coordinates": [1910, 119],
"flag": "West Roman Empire",
"name": "Dacia"
},
{
"coordinates": [1811, 402],
"flag": "West Roman Empire",
"name": "Macedonia"
},
{
"coordinates": [1654, 560],
"flag": "West Roman Empire",
"name": "Epirus"
},
{
"coordinates": [1774, 774],
"flag": "West Roman Empire",
"name": "Achaia"
},
{
"coordinates": [2030, 457],
"flag": "West Roman Empire",
"name": "Thracia"
},
{
"coordinates": [2114, 250],
"flag": "West Roman Empire",
"name": "Moesia"
},
{
"coordinates": [2428, 200],
"name": "Bosporan Kingdom",
"flag": ""
},
{
"coordinates": [2065, 690],
"flag": "West Roman Empire",
"name": "Asia"
},
{
"coordinates": [2354, 505],
"flag": "West Roman Empire",
"name": "Pontus"
},
{
"coordinates": [2325, 669],
"flag": "West Roman Empire",
"name": "Galatia"
},
{
"coordinates": [2447, 809],
"flag": "West Roman Empire",
"name": "Cilicia"
},
{
"coordinates": [2196, 828],
"flag": "West Roman Empire",
"name": "Lycia"
},
{
"coordinates": [2379, 942],
"flag": "West Roman Empire",
"name": "Cyprus"
},
{
"coordinates": [2516, 648],
"flag": "West Roman Empire",
"name": "Cappadocia"
},
{
"coordinates": [2772, 845],
"flag": "West Roman Empire",
"name": "Mesopotamia"
},
{
"coordinates": [2584, 990],
"flag": "West Roman Empire",
"name": "Syria"
},
{
"coordinates": [2490, 1141],
"flag": "West Roman Empire",
"name": "Judaea"
},
{
"coordinates": [2481, 1292],
"flag": "West Roman Empire",
"name": "Arabia Petraea"
},
{
"coordinates": [2263, 1240],
"flag": "West Roman Empire",
"name": "Aegyptus"
},
{
"coordinates": [1799, 1096],
"flag": "West Roman Empire",
"name": "Cyrenaica et Creta"
},
{
"coordinates": [2815, 659],
"flag": "Sassanid Empire",
"name": "Sassanid Empire"
},
{
"coordinates": [1091, 989],
"flag": "West Roman Empire",
"name": "Africa Proconsularis"
},
{
"coordinates": [597, 910],
"flag": "West Roman Empire",
"name": "Mauretania Caesariensis"
},
{
"coordinates": [264, 1028],
"flag": "West Roman Empire",
"name": "Mauretania Tingitania"
},
{
"coordinates": [584, 1199],
"flag": "Amazigh flag",
"name": "Numidia"
}
]
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

+34 -1
View File
@@ -1,6 +1,7 @@
import { html, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators.js";
import {
AchievementsResponse,
PlayerGame,
PlayerStatsTree,
UserMeResponse,
@@ -29,6 +30,7 @@ export class AccountModal extends BaseModal {
private userMeResponse: UserMeResponse | null = null;
private statsTree: PlayerStatsTree | null = null;
private recentGames: PlayerGame[] = [];
private achievementGroups: AchievementsResponse = [];
constructor() {
super();
@@ -40,15 +42,39 @@ export class AccountModal extends BaseModal {
if (this.userMeResponse?.player?.publicId === undefined) {
this.statsTree = null;
this.recentGames = [];
this.achievementGroups = [];
} else {
this.achievementGroups = this.getUserMeAchievementGroups(
this.userMeResponse,
);
}
} else {
this.statsTree = null;
this.recentGames = [];
this.achievementGroups = [];
this.requestUpdate();
}
});
}
private getUserMeAchievementGroups(
userMeResponse: UserMeResponse | null,
): AchievementsResponse {
const achievements = userMeResponse?.player?.achievements;
if (!achievements) return [];
return [
{
type: "singleplayer-map",
data: achievements.singleplayerMap,
},
{
type: "player",
data: achievements.player ?? [],
},
];
}
private hasAnyStats(): boolean {
if (!this.statsTree) return false;
// Check if statsTree has any data
@@ -133,7 +159,10 @@ export class AccountModal extends BaseModal {
private renderAccountInfo() {
const me = this.userMeResponse?.user;
const isLinked = me?.discord ?? me?.email;
const achievements = this.userMeResponse?.player?.achievements ?? [];
const achievements =
this.achievementGroups.length > 0
? this.achievementGroups
: this.getUserMeAchievementGroups(this.userMeResponse);
if (!isLinked) {
return this.renderLoginOptions();
@@ -379,6 +408,7 @@ export class AccountModal extends BaseModal {
.then((userMe) => {
if (userMe) {
this.userMeResponse = userMe;
this.achievementGroups = this.getUserMeAchievementGroups(userMe);
if (this.userMeResponse?.player?.publicId) {
this.loadPlayerProfile(this.userMeResponse.player.publicId);
}
@@ -417,6 +447,9 @@ export class AccountModal extends BaseModal {
this.recentGames = data.games;
this.statsTree = data.stats;
this.achievementGroups =
data.achievements ??
this.getUserMeAchievementGroups(this.userMeResponse);
this.requestUpdate();
} catch (err) {
+74 -2
View File
@@ -25,6 +25,16 @@ declare global {
},
) => void;
};
banner: {
requestBanner: (options: {
id: string;
width: number;
height: number;
}) => Promise<void>;
requestResponsiveBanner: (containerId: string) => Promise<void>;
clearBanner: (containerId: string) => void;
clearAllBanners: () => void;
};
game: {
gameplayStart: () => Promise<void>;
gameplayStop: () => Promise<void>;
@@ -76,11 +86,9 @@ export class CrazyGamesSDK {
}
return false;
} catch (e) {
console.log("[CrazyGames]: ", e);
// If we get a cross-origin error, we're definitely iframed
// Check our own referrer as fallback
const isCrazyGames = document.referrer.includes("crazygames");
console.log("[CrazyGames], contains referrer: ", isCrazyGames);
if (isCrazyGames) {
return true;
}
@@ -323,6 +331,70 @@ export class CrazyGamesSDK {
}
}
private bottomLeftContainerId = "cg-bottom-left-ad";
private bottomLeftAdVisible = false;
createBottomLeftAd(): void {
console.log(
`[CrazyGames] createBottomLeftAd called, isReady=${this.isReady()}`,
);
if (!this.isReady()) {
console.log("[CrazyGames] SDK not ready, skipping bottom-left ad");
return;
}
if (this.bottomLeftAdVisible) {
console.log("[CrazyGames] Bottom-left ad already visible");
return;
}
// Remove existing container if any
document.getElementById(this.bottomLeftContainerId)?.remove();
const container = document.createElement("div");
container.id = this.bottomLeftContainerId;
container.style.cssText = `
position: fixed;
bottom: 0;
left: 0;
width: 300px;
height: 250px;
z-index: 9999;
pointer-events: auto;
`;
document.body.appendChild(container);
console.log("[CrazyGames] Created bottom-left ad container");
(async () => {
try {
await window.CrazyGames!.SDK.banner.requestBanner({
id: this.bottomLeftContainerId,
width: 300,
height: 250,
});
console.log("[CrazyGames] Bottom-left banner loaded");
} catch (e) {
console.log("[CrazyGames] Bottom-left banner error:", e);
}
})();
this.bottomLeftAdVisible = true;
}
clearBottomLeftAd(): void {
if (!this.bottomLeftAdVisible) return;
try {
window.CrazyGames!.SDK.banner.clearBanner(this.bottomLeftContainerId);
} catch (e) {
console.error("[CrazyGames] Error clearing bottom-left banner:", e);
}
document.getElementById(this.bottomLeftContainerId)?.remove();
this.bottomLeftAdVisible = false;
console.log("[CrazyGames] Bottom-left ad cleared");
}
requestMidgameAd(): Promise<void> {
return new Promise((resolve) => {
if (!this.isReady()) {
+13
View File
@@ -10,6 +10,7 @@ import { fetchCosmetics, flagRelationship } from "./Cosmetics";
import { translateText } from "./Utils";
import { BaseModal } from "./components/BaseModal";
import "./components/FlagButton";
import "./components/NotLoggedInWarning";
import { modalHeader } from "./components/ui/ModalHeader";
@customElement("flag-input-modal")
@@ -104,6 +105,7 @@ export class FlagInputModal extends BaseModal {
title: translateText("flag_input.title"),
onBack: () => this.close(),
ariaLabel: translateText("common.back"),
rightContent: html`<not-logged-in-warning></not-logged-in-warning>`,
})}
<div class="md:flex items-center gap-2 justify-center mt-4">
@@ -119,6 +121,17 @@ export class FlagInputModal extends BaseModal {
/>
</div>
</div>
<div class="flex justify-center py-3 shrink-0">
<button
class="px-4 py-2 text-sm font-bold uppercase tracking-wider rounded-lg bg-blue-600 hover:bg-blue-700 text-white cursor-pointer transition-colors"
@click=${() => {
this.close();
window.showPage?.("page-item-store");
}}
>
${translateText("main.store")}
</button>
</div>
<div
class="flex-1 overflow-y-auto px-3 pb-3 scrollbar-thin scrollbar-thumb-white/20 scrollbar-track-transparent mr-1"
+70 -4
View File
@@ -266,6 +266,19 @@ export class InputHandler {
this.eventBus.emit(new MouseMoveEvent(e.clientX, e.clientY));
}
});
// Clear all tracked keys when the window loses focus so keys that had
// their keyup swallowed by the browser (e.g. cmd+zoom) don't stay stuck.
// Also release the hold-to-view state and any active pointer/drag state
// so the alternate view and drags aren't left latched when focus returns.
window.addEventListener("blur", () => {
this.activeKeys.clear();
if (this.alternateView) {
this.alternateView = false;
this.eventBus.emit(new AlternateViewEvent(false));
}
this.pointerDown = false;
this.pointers.clear();
});
this.pointers.clear();
this.moveInterval = setInterval(() => {
@@ -310,13 +323,15 @@ export class InputHandler {
if (
this.activeKeys.has(this.keybinds.zoomOut) ||
this.activeKeys.has("Minus")
this.activeKeys.has("Minus") ||
this.activeKeys.has("NumpadSubtract")
) {
this.eventBus.emit(new ZoomEvent(cx, cy, this.ZOOM_SPEED));
}
if (
this.activeKeys.has(this.keybinds.zoomIn) ||
this.activeKeys.has("Equal")
this.activeKeys.has("Equal") ||
this.activeKeys.has("NumpadAdd")
) {
this.eventBus.emit(new ZoomEvent(cx, cy, -this.ZOOM_SPEED));
}
@@ -358,7 +373,19 @@ export class InputHandler {
this.eventBus.emit(new ConfirmGhostStructureEvent());
}
// Don't track zoom keys when a meta/ctrl modifier is held — that means
// the browser is handling its own zoom (cmd+/cmd-) and the keyup will
// never fire, which would leave the key stuck in activeKeys forever.
// Also covers numpad zoom shortcuts (Ctrl+NumpadAdd/NumpadSubtract).
const isBrowserZoomCombo =
(e.metaKey || e.ctrlKey) &&
(e.code === "Minus" ||
e.code === "Equal" ||
e.code === "NumpadAdd" ||
e.code === "NumpadSubtract");
if (
!isBrowserZoomCombo &&
[
this.keybinds.moveUp,
this.keybinds.moveDown,
@@ -372,6 +399,8 @@ export class InputHandler {
"ArrowRight",
"Minus",
"Equal",
"NumpadAdd",
"NumpadSubtract",
this.keybinds.attackRatioDown,
this.keybinds.attackRatioUp,
this.keybinds.centerCamera,
@@ -390,6 +419,24 @@ export class InputHandler {
return;
}
// When the meta (cmd) or ctrl key is released, any keys that were held
// simultaneously will have had their keyup swallowed by the browser
// (e.g. cmd+Plus for browser zoom). Clear zoom-related keys to
// prevent them staying stuck in activeKeys.
if (
e.code === "MetaLeft" ||
e.code === "MetaRight" ||
e.code === "ControlLeft" ||
e.code === "ControlRight"
) {
this.activeKeys.delete("Minus");
this.activeKeys.delete("Equal");
this.activeKeys.delete("NumpadAdd");
this.activeKeys.delete("NumpadSubtract");
this.activeKeys.delete(this.keybinds.zoomIn);
this.activeKeys.delete(this.keybinds.zoomOut);
}
if (e.code === this.keybinds.toggleView) {
e.preventDefault();
this.alternateView = false;
@@ -537,8 +584,27 @@ export class InputHandler {
const realCtrl =
this.activeKeys.has("ControlLeft") ||
this.activeKeys.has("ControlRight");
const ratio = event.ctrlKey && !realCtrl ? 10 : 1; // Compensate pinch-zoom low sensitivity
this.eventBus.emit(new ZoomEvent(event.x, event.y, event.deltaY * ratio));
if (event.ctrlKey) {
if (!realCtrl) {
// Pinch-to-zoom gesture (trackpad): small deltas, amplify.
// Ignore large deltas — those are browser zoom shortcuts (cmd+/cmd-)
// which fire synthetic wheel events we don't want to handle.
if (Math.abs(event.deltaY) <= 10) {
this.eventBus.emit(
new ZoomEvent(event.x, event.y, event.deltaY * 10),
);
}
}
// Always return when ctrlKey is set — whether it's a real ctrl scroll,
// a pinch gesture, or a browser zoom event, none should reach the
// regular scroll path below.
return;
}
// Regular scroll wheel: ignore tiny residual momentum events that macOS
// keeps sending after a gesture ends (especially after browser zoom changes
// devicePixelRatio, which can cause these to accumulate into runaway zoom).
if (Math.abs(event.deltaY) < 2) return;
this.eventBus.emit(new ZoomEvent(event.x, event.y, event.deltaY));
}
}
+2 -10
View File
@@ -101,17 +101,9 @@ function updateAccountNavButton(userMeResponse: UserMeResponse | false) {
// If the avatar fails to load (bad URL / CDN issue / offline), fall back
// to the default sign-in UI instead of leaving a broken image.
avatarEl.onerror = () => {
// Only handle if this is the latest update
if (avatarEl._navToken !== navToken) return;
avatarEl.src = "";
// If the user is still logged in via email, show the email badge state.
const email =
userMeResponse !== false ? userMeResponse.user.email : undefined;
if (email) {
showEmailLoggedIn();
} else {
showSignIn();
}
avatarEl.onerror = null;
avatarEl.src = "https://cdn.discordapp.com/embed/avatars/0.png";
};
avatarEl.onload = () => {
// Only handle if this is the latest update
+12 -3
View File
@@ -19,11 +19,20 @@ export function initNavigation() {
// Close mobile sidebar if a nav item was clicked
closeMobileSidebar();
// Hide only the currently visible modal
// Close the currently visible modal properly
const visibleModal = document.querySelector(".page-content:not(.hidden)");
if (visibleModal) {
visibleModal.classList.add("hidden");
visibleModal.classList.remove("block");
// If it's an open modal component, call close() for proper cleanup (onClose callback, etc.)
if (
typeof (visibleModal as any).isOpen === "function" &&
(visibleModal as any).isOpen() &&
typeof (visibleModal as any).close === "function"
) {
(visibleModal as any).close();
} else {
visibleModal.classList.add("hidden");
visibleModal.classList.remove("block");
}
}
// Handle page-play separately (it's not a page-content element)
+1 -8
View File
@@ -145,14 +145,7 @@ export class SinglePlayerModal extends BaseModal {
return;
}
const achievements = Array.isArray(userMe.player.achievements)
? userMe.player.achievements
: [];
const completions =
achievements.find(
(achievement) => achievement?.type === "singleplayer-map",
)?.data ?? [];
const completions = userMe.player.achievements.singleplayerMap;
const winsMap = new Map<GameMapType, Set<Difficulty>>();
for (const entry of completions) {
+2 -18
View File
@@ -5,9 +5,9 @@ import { UserMeResponse } from "../core/ApiSchemas";
import { ColorPalette, Cosmetics, Pattern } from "../core/CosmeticSchemas";
import { UserSettings } from "../core/game/UserSettings";
import { PlayerPattern } from "../core/Schemas";
import { hasLinkedAccount } from "./Api";
import { BaseModal } from "./components/BaseModal";
import "./components/FlagButton";
import "./components/NotLoggedInWarning";
import "./components/PatternButton";
import { modalHeader } from "./components/ui/ModalHeader";
import {
@@ -77,11 +77,7 @@ export class StoreModal extends BaseModal {
title: translateText("store.title"),
onBack: () => this.close(),
ariaLabel: translateText("common.back"),
rightContent: !hasLinkedAccount(this.userMeResponse)
? html`<div class="flex items-center">
${this.renderNotLoggedInWarning()}
</div>`
: undefined,
rightContent: html`<not-logged-in-warning></not-logged-in-warning>`,
})}
<div class="flex items-center gap-2 justify-center pt-2">
<button
@@ -214,18 +210,6 @@ export class StoreModal extends BaseModal {
`;
}
private renderNotLoggedInWarning(): TemplateResult {
return html`<button
class="px-4 py-2 text-xs font-bold uppercase tracking-wider transition-colors duration-200 rounded-lg bg-red-500/20 text-red-400 border border-red-500/30 cursor-pointer hover:bg-red-500/30"
@click=${() => {
this.close();
window.showPage?.("page-account");
}}
>
${translateText("territory_patterns.not_logged_in")}
</button>`;
}
render() {
if (!this.isActive && !this.inline) return html``;
+46 -18
View File
@@ -5,8 +5,8 @@ import { UserMeResponse } from "../core/ApiSchemas";
import { Cosmetics, Pattern } from "../core/CosmeticSchemas";
import { UserSettings } from "../core/game/UserSettings";
import { PlayerPattern } from "../core/Schemas";
import { hasLinkedAccount } from "./Api";
import { BaseModal } from "./components/BaseModal";
import "./components/NotLoggedInWarning";
import "./components/PatternButton";
import { modalHeader } from "./components/ui/ModalHeader";
import {
@@ -22,6 +22,7 @@ export class TerritoryPatternsModal extends BaseModal {
@state() private selectedPattern: PlayerPattern | null;
@state() private selectedColor: string | null = null;
@state() private search = "";
private cosmetics: Cosmetics | null = null;
private userSettings: UserSettings = new UserSettings();
@@ -67,6 +68,15 @@ export class TerritoryPatternsModal extends BaseModal {
this.refresh();
}
private includedInSearch(name: string): boolean {
const displayName = name.replace(/_/g, " ");
return displayName.toLowerCase().includes(this.search.toLowerCase());
}
private handleSearch(event: Event) {
this.search = (event.target as HTMLInputElement).value;
}
private renderPatternGrid(): TemplateResult {
const buttons: TemplateResult[] = [];
const patterns: (Pattern | null)[] = [
@@ -74,6 +84,12 @@ export class TerritoryPatternsModal extends BaseModal {
...Object.values(this.cosmetics?.patterns ?? {}),
];
for (const pattern of patterns) {
if (pattern === null && this.search) {
continue;
}
if (pattern !== null && !this.includedInSearch(pattern.name)) {
continue;
}
const colorPalettes = pattern
? [...(pattern.colorPalettes ?? []), null]
: [null];
@@ -123,18 +139,6 @@ export class TerritoryPatternsModal extends BaseModal {
`;
}
private renderNotLoggedInWarning(): TemplateResult {
return html`<button
class="px-4 py-2 text-xs font-bold uppercase tracking-wider transition-colors duration-200 rounded-lg bg-red-500/20 text-red-400 border border-red-500/30 cursor-pointer hover:bg-red-500/30"
@click=${() => {
this.close();
window.showPage?.("page-account");
}}
>
${translateText("territory_patterns.not_logged_in")}
</button>`;
}
render() {
const content = html`
<div class="${this.modalContainerClass}">
@@ -145,12 +149,32 @@ export class TerritoryPatternsModal extends BaseModal {
title: translateText("territory_patterns.title"),
onBack: () => this.close(),
ariaLabel: translateText("common.back"),
rightContent: !hasLinkedAccount(this.userMeResponse)
? html`<div class="flex items-center">
${this.renderNotLoggedInWarning()}
</div>`
: undefined,
rightContent: html`<not-logged-in-warning></not-logged-in-warning>`,
})}
<div class="md:flex items-center gap-2 justify-center mt-4">
<input
class="h-12 w-full max-w-md border border-white/10 bg-black/60
rounded-xl shadow-inner text-xl text-center focus:outline-none
focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 text-white placeholder-white/30 transition-all"
type="text"
placeholder=${translateText("territory_patterns.search")}
.value=${this.search}
@change=${this.handleSearch}
@keyup=${this.handleSearch}
/>
</div>
</div>
<div class="flex justify-center py-3 shrink-0">
<button
class="px-4 py-2 text-sm font-bold uppercase tracking-wider rounded-lg bg-blue-600 hover:bg-blue-700 text-white cursor-pointer transition-colors"
@click=${() => {
this.close();
window.showPage?.("page-item-store");
}}
>
${translateText("main.store")}
</button>
</div>
<div
class="flex-1 overflow-y-auto px-3 pb-3 scrollbar-thin scrollbar-thumb-white/20 scrollbar-track-transparent mr-1"
@@ -181,6 +205,10 @@ export class TerritoryPatternsModal extends BaseModal {
await this.refresh();
}
protected onClose(): void {
this.search = "";
}
private selectPattern(pattern: PlayerPattern | null) {
this.selectedColor = null;
this.userSettings.setSelectedColor(undefined);
@@ -0,0 +1,49 @@
import { LitElement, html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { UserMeResponse } from "../../core/ApiSchemas";
import { hasLinkedAccount } from "../Api";
@customElement("not-logged-in-warning")
export class NotLoggedInWarning extends LitElement {
@state() private linked = false;
private _onUserMe = (event: CustomEvent<UserMeResponse | false>) => {
this.linked = hasLinkedAccount(event.detail);
};
createRenderRoot() {
return this;
}
connectedCallback() {
super.connectedCallback();
document.addEventListener(
"userMeResponse",
this._onUserMe as EventListener,
);
}
disconnectedCallback() {
super.disconnectedCallback();
document.removeEventListener(
"userMeResponse",
this._onUserMe as EventListener,
);
}
render() {
if (this.linked) return html``;
return html`<div class="flex items-center">
<button
class="px-4 py-2 text-xs font-bold uppercase tracking-wider transition-colors duration-200 rounded-lg bg-red-500/20 text-red-400 border border-red-500/30 cursor-pointer hover:bg-red-500/30"
data-i18n="common.not_logged_in"
@click=${() => {
window.showPage?.("page-account");
}}
>
Not logged in
</button>
</div>`;
}
}
@@ -31,15 +31,20 @@ export class DiscordUserHeader extends LitElement {
}
render() {
const defaultAvatar = "https://cdn.discordapp.com/embed/avatars/0.png";
const imgSrc = this.avatarUrl ?? defaultAvatar;
return html`
<div class="flex items-center gap-2">
${this.avatarUrl
${this._data
? html`
<div class="p-[3px] rounded-full bg-gray-500">
<img
class="w-12 h-12 rounded-full block"
src="${this.avatarUrl}"
src="${imgSrc}"
alt="${translateText("discord_user_header.avatar_alt")}"
@error=${(e: Event) => {
(e.target as HTMLImageElement).src = defaultAvatar;
}}
/>
</div>
`
+39 -1
View File
@@ -1,6 +1,7 @@
import { LitElement, html } from "lit";
import { customElement } from "lit/decorators.js";
import { GameView } from "../../../core/game/GameView";
import { crazyGamesSDK } from "../../CrazyGamesSDK";
import { Layer } from "./Layer";
const AD_TYPE = "standard_iab_left1";
@@ -30,6 +31,7 @@ export class InGamePromo extends LitElement implements Layer {
}
if (!this.cornerAdShown) {
this.cornerAdShown = true;
console.log("[InGamePromo] Spawn phase ended, triggering showAd");
this.showAd();
}
}
@@ -73,10 +75,19 @@ export class InGamePromo extends LitElement implements Layer {
}
private showAd(): void {
if (!window.adsEnabled) return;
console.log(
`[InGamePromo] showAd called, isOnCrazyGames=${crazyGamesSDK.isOnCrazyGames()}`,
);
if (window.innerWidth < 1100) return;
if (window.innerHeight < 750) return;
if (crazyGamesSDK.isOnCrazyGames()) {
this.showCrazyGamesAd();
return;
}
if (!window.adsEnabled) return;
this.shouldShow = true;
this.requestUpdate();
@@ -85,6 +96,25 @@ export class InGamePromo extends LitElement implements Layer {
});
}
private showCrazyGamesAd(): void {
console.log(
`[InGamePromo] showCrazyGamesAd called, isReady=${crazyGamesSDK.isReady()}, width=${window.innerWidth}, height=${window.innerHeight}`,
);
if (!crazyGamesSDK.isReady()) {
console.log(
"[InGamePromo] CrazyGames SDK not ready, skipping in-game ad",
);
return;
}
this.requestUpdate();
this.updateComplete.then(() => {
console.log("[InGamePromo] DOM updated, calling createBottomLeftAd");
crazyGamesSDK.createBottomLeftAd();
});
}
private loadAd(): void {
if (!window.ramp) {
console.warn("Playwire RAMP not available for in-game ad");
@@ -112,6 +142,14 @@ export class InGamePromo extends LitElement implements Layer {
public hideAd(): void {
this.destroyBottomRail();
if (crazyGamesSDK.isOnCrazyGames()) {
crazyGamesSDK.clearBottomLeftAd();
this.shouldShow = false;
this.requestUpdate();
return;
}
if (!window.ramp) {
console.warn("Playwire RAMP not available for in-game ad");
return;
@@ -246,12 +246,6 @@ export class NukeTrajectoryPreviewLayer implements Layer {
game: this.game,
targetTile,
magnitude: this.game.config().nukeMagnitudes(ghostStructure),
allySmallIds: new Set(
this.game
.myPlayer()
?.allies()
.map((a) => a.smallID()),
),
threshold: this.game.config().nukeAllianceBreakThreshold(),
});
// Find the point where SAM can intercept
@@ -420,18 +420,19 @@ function createMenuElements(
: !BuildableAttacks.has(item.unitType)),
)
.map((item: BuildItemDisplay) => {
const canBuildOrUpgrade = params.buildMenu.canBuildOrUpgrade(item);
return {
id: `${elementIdPrefix}_${item.unitType}`,
name: item.key
? item.key.replace("unit_type.", "")
: item.unitType.toString(),
disabled: () => !canBuildOrUpgrade,
color: canBuildOrUpgrade
? filterType === "attack"
? COLORS.attack
: COLORS.building
: undefined,
disabled: (p: MenuElementParams) =>
!p.buildMenu.canBuildOrUpgrade(item),
color: (p: MenuElementParams) =>
p.buildMenu.canBuildOrUpgrade(item)
? filterType === "attack"
? COLORS.attack
: COLORS.building
: COLORS.building,
icon: item.icon,
tooltipItems: [
{ text: translateText(item.key ?? ""), className: "title" },
@@ -456,7 +457,7 @@ function createMenuElements(
if (buildableUnit === undefined) {
return;
}
if (canBuildOrUpgrade) {
if (params.buildMenu.canBuildOrUpgrade(item)) {
params.buildMenu.sendBuildOrUpgrade(buildableUnit, params.tile);
}
params.closeMenu();
+6 -1
View File
@@ -83,6 +83,11 @@ export const AchievementsResponseSchema = z.array(
);
export type AchievementsResponse = z.infer<typeof AchievementsResponseSchema>;
const UserMeAchievementsSchema = z.object({
singleplayerMap: z.array(SingleplayerMapAchievementSchema),
player: z.array(PlayerAchievementSchema).optional(),
});
export const UserMeResponseSchema = z.object({
user: z.object({
discord: DiscordUserSchema.optional(),
@@ -92,7 +97,7 @@ export const UserMeResponseSchema = z.object({
publicId: z.string(),
roles: z.string().array().optional(),
flares: z.string().array().optional(),
achievements: AchievementsResponseSchema.optional(),
achievements: UserMeAchievementsSchema,
leaderboard: z
.object({
oneVone: z
-1
View File
@@ -89,7 +89,6 @@ export class NukeExecution implements Execution {
game: this.mg,
targetTile: this.dst,
magnitude,
allySmallIds: new Set(this.player.allies().map((a) => a.smallID())),
threshold: this.mg.config().nukeAllianceBreakThreshold(),
});
+2 -2
View File
@@ -39,7 +39,7 @@ export interface NukeAllianceCheckParams {
game: Game | GameView;
targetTile: TileRef;
magnitude: NukeMagnitude;
allySmallIds: Set<number>;
allySmallIds?: Set<number>;
threshold: number;
}
@@ -52,7 +52,7 @@ export function wouldNukeBreakAlliance(
): boolean {
const { game, targetTile, magnitude, allySmallIds, threshold } = params;
if (allySmallIds.size === 0) {
if (!allySmallIds || allySmallIds.size === 0) {
return false;
}
+2
View File
@@ -144,6 +144,7 @@ export enum GameMapType {
SanFrancisco = "San Francisco",
Aegean = "Aegean",
MilkyWay = "MilkyWay",
Mediterranean = "Mediterranean",
}
export type GameMapName = keyof typeof GameMapType;
@@ -195,6 +196,7 @@ export const mapCategories: Record<string, GameMapType[]> = {
GameMapType.Arctic,
GameMapType.SanFrancisco,
GameMapType.Aegean,
GameMapType.Mediterranean,
],
fantasy: [
GameMapType.Pangaea,
+1
View File
@@ -59,6 +59,7 @@ export async function readGameRecord(
method: "GET",
headers: {
"Content-Type": "application/json",
"x-api-key": config.apiKey(),
},
});
const record = await response.json();
+3
View File
@@ -55,6 +55,9 @@ export function registerGamePreviewRoute(opts: {
const apiDomain = config.jwtIssuer();
const encodedID = encodeURIComponent(gameID);
const response = await fetch(`${apiDomain}/game/${encodedID}`, {
headers: {
"x-api-key": config.apiKey(),
},
signal: controller.signal,
});
if (!response.ok) return null;
+1
View File
@@ -85,6 +85,7 @@ const frequency: Partial<Record<GameMapName, number>> = {
SanFrancisco: 3,
Aegean: 6,
MilkyWay: 8,
Mediterranean: 6,
};
const TEAM_WEIGHTS: { config: TeamCountConfig; weight: number }[] = [
+1 -1
View File
@@ -158,7 +158,7 @@ app.get("/api/instance", (_req, res) => {
});
// SPA fallback route
app.get("*", async function (_req, res) {
app.get("/{*splat}", async function (_req, res) {
try {
const htmlPath = path.join(__dirname, "../../static/index.html");
await renderAppShell(res, htmlPath);
-3
View File
@@ -109,9 +109,6 @@ export async function startWorker() {
app.use(compression());
app.use(express.json());
// Configure MIME types for webp files
express.static.mime.define({ "image/webp": ["webp"] });
app.use(
express.static(path.join(__dirname, "../../out"), {
setHeaders: (res) => {
+1
View File
@@ -74,6 +74,7 @@ export async function getUserMe(
const response = await fetch(config.jwtIssuer() + "/users/@me", {
headers: {
authorization: `Bearer ${token}`,
"x-api-key": config.apiKey(),
},
});
if (response.status !== 200) {
-92
View File
@@ -1,92 +0,0 @@
#!/bin/bash
set -e
# Check if required environment variables are set
if [ -z "$CF_API_TOKEN" ] || [ -z "$CF_ACCOUNT_ID" ] || [ -z "$SUBDOMAIN" ] || [ -z "$DOMAIN" ]; then
echo "Error: Required environment variables not set"
echo "Please set CF_API_TOKEN, CF_ACCOUNT_ID, SUBDOMAIN, and DOMAIN"
exit 1
fi
# Generate a unique tunnel name using timestamp
TIMESTAMP=$(date +%Y%m%d%H%M%S)
TUNNEL_NAME="${SUBDOMAIN}-tunnel-${TIMESTAMP}"
echo "Using unique tunnel name: ${TUNNEL_NAME}"
# Create a new tunnel
echo "Creating Cloudflare tunnel for subdomain ${SUBDOMAIN}..."
TUNNEL_RESPONSE=$(curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/cfd_tunnel" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json" \
--data "{\"name\":\"${TUNNEL_NAME}\"}")
# Extract tunnel ID and token
TUNNEL_ID=$(echo $TUNNEL_RESPONSE | jq -r '.result.id')
TUNNEL_TOKEN=$(echo $TUNNEL_RESPONSE | jq -r '.result.token')
if [ -z "$TUNNEL_ID" ] || [ "$TUNNEL_ID" == "null" ]; then
echo "Failed to create tunnel"
echo $TUNNEL_RESPONSE
exit 1
fi
echo "Tunnel created with ID: ${TUNNEL_ID}"
# Configure the tunnel with hostname
echo "Configuring tunnel to point to tunnel-${SUBDOMAIN}.${DOMAIN}..."
curl -s -X PUT "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/cfd_tunnel/${TUNNEL_ID}/configurations" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json" \
--data "{\"config\":{\"ingress\":[{\"hostname\":\"tunnel-${SUBDOMAIN}.${DOMAIN}\",\"service\":\"http://localhost:80\"},{\"service\":\"http_status:404\"}]}}"
# Update DNS record to point to the new tunnel
echo "Updating DNS record to point to the new tunnel..."
# First check if DNS record exists
DNS_RECORDS=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=${DOMAIN}" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json")
ZONE_ID=$(echo $DNS_RECORDS | jq -r '.result[0].id')
if [ -z "$ZONE_ID" ] || [ "$ZONE_ID" == "null" ]; then
echo "Could not find zone ID for domain ${DOMAIN}"
exit 1
fi
# Check for existing record
EXISTING_RECORDS=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records?name=tunnel-${SUBDOMAIN}.${DOMAIN}" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json")
RECORD_ID=$(echo $EXISTING_RECORDS | jq -r '.result[0].id')
# Create or update the DNS record
if [ -z "$RECORD_ID" ] || [ "$RECORD_ID" == "null" ]; then
# Create new record
echo "Creating new DNS record..."
DNS_RESPONSE=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json" \
--data "{\"type\":\"CNAME\",\"name\":\"tunnel-${SUBDOMAIN}.${DOMAIN}\",\"content\":\"${TUNNEL_ID}.cfargotunnel.com\",\"ttl\":1,\"proxied\":true}")
else
# Update existing record
echo "Updating existing DNS record..."
DNS_RESPONSE=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records/${RECORD_ID}" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json" \
--data "{\"type\":\"CNAME\",\"name\":\"tunnel-${SUBDOMAIN}.${DOMAIN}\",\"content\":\"${TUNNEL_ID}.cfargotunnel.com\",\"ttl\":1,\"proxied\":true}")
fi
# Log the tunnel information
echo "Tunnel configuration is set up! Site will be available at: https://tunnel-${SUBDOMAIN}.${DOMAIN}"
# Export the tunnel token for supervisord
export CLOUDFLARE_TUNNEL_TOKEN=${TUNNEL_TOKEN}
# Start supervisord
if [ "$DOMAIN" = openfront.dev ] && [ "$SUBDOMAIN" != main ]; then
exec timeout 18h /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
else
exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
fi
-8
View File
@@ -23,11 +23,3 @@ stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:cloudflared]
command=cloudflared tunnel run --token %(ENV_CLOUDFLARE_TUNNEL_TOKEN)s
autostart=true
autorestart=true
user=node
stdout_logfile=/var/log/cloudflared.log
stderr_logfile=/var/log/cloudflared-err.log
@@ -557,7 +557,11 @@ describe("RadialMenuElements", () => {
const subMenu = buildMenuElement.subMenu!(mockParams);
const cityElement = subMenu.find((item) => item.id === "build_City");
expect(cityElement!.color).toBe(COLORS.building);
expect(
(cityElement!.color as (params: MenuElementParams) => string)(
mockParams,
),
).toBe(COLORS.building);
});
it("should use correct colors for attack elements", () => {
@@ -572,16 +576,24 @@ describe("RadialMenuElements", () => {
(item) => item.id === "attack_Atom Bomb",
);
expect(atomBombElement!.color).toBe(COLORS.attack);
expect(
(atomBombElement!.color as (params: MenuElementParams) => string)(
mockParams,
),
).toBe(COLORS.attack);
});
it("should not set color when element is disabled", () => {
it("should use disabled color when element is disabled", () => {
mockBuildMenu.canBuildOrUpgrade = vi.fn(() => false);
const subMenu = buildMenuElement.subMenu!(mockParams);
const cityElement = subMenu.find((item) => item.id === "build_City");
expect(cityElement!.color).toBeUndefined();
expect(
(cityElement!.color as (params: MenuElementParams) => string)(
mockParams,
),
).toBe(COLORS.building);
});
});
+48 -38
View File
@@ -44,50 +44,56 @@ app.get("/api/maps", (req: Request, res: Response) => {
* GET /api/maps/:name
* Get map metadata (map data, dimensions)
*/
app.get("/api/maps/:name", async (req: Request, res: Response) => {
try {
const { name } = req.params;
const metadata = await getMapMetadata(name);
res.json(metadata);
} catch (error) {
console.error(`Error loading map ${req.params.name}:`, error);
app.get(
"/api/maps/:name",
async (req: Request<{ name: string }>, res: Response) => {
try {
const { name } = req.params;
const metadata = await getMapMetadata(name);
res.json(metadata);
} catch (error) {
console.error(`Error loading map ${req.params.name}:`, error);
if (error instanceof Error && error.message.includes("ENOENT")) {
res.status(404).json({
error: "Map not found",
message: `Map "${req.params.name}" does not exist`,
});
} else {
res.status(500).json({
error: "Failed to load map",
message: error instanceof Error ? error.message : String(error),
});
if (error instanceof Error && error.message.includes("ENOENT")) {
res.status(404).json({
error: "Map not found",
message: `Map "${req.params.name}" does not exist`,
});
} else {
res.status(500).json({
error: "Failed to load map",
message: error instanceof Error ? error.message : String(error),
});
}
}
}
});
},
);
/**
* GET /api/maps/:name/thumbnail
* Get map thumbnail image
*/
app.get("/api/maps/:name/thumbnail", (req: Request, res: Response) => {
try {
const { name } = req.params;
const thumbnailPath = join(
dirname(fileURLToPath(import.meta.url)),
"../../../resources/maps",
name,
"thumbnail.webp",
);
res.sendFile(thumbnailPath);
} catch (error) {
console.error(`Error loading thumbnail for ${req.params.name}:`, error);
res.status(404).json({
error: "Thumbnail not found",
message: error instanceof Error ? error.message : String(error),
});
}
});
app.get(
"/api/maps/:name/thumbnail",
(req: Request<{ name: string }>, res: Response) => {
try {
const { name } = req.params;
const thumbnailPath = join(
dirname(fileURLToPath(import.meta.url)),
"../../../resources/maps",
name,
"thumbnail.webp",
);
res.sendFile(thumbnailPath);
} catch (error) {
console.error(`Error loading thumbnail for ${req.params.name}:`, error);
res.status(404).json({
error: "Thumbnail not found",
message: error instanceof Error ? error.message : String(error),
});
}
},
);
/**
* POST /api/pathfind
@@ -237,7 +243,11 @@ app.use((err: Error, req: Request, res: Response, next: any) => {
});
// Start server
app.listen(PORT, () => {
app.listen(PORT, (error?: Error) => {
if (error) {
console.error("Failed to start server", error);
process.exit(1);
}
console.log(`
Pathfinding Playground Server
-4
View File
@@ -62,14 +62,10 @@ echo "Starting new container for ${HOST} environment..."
# Ensure the traefik network exists
docker network create web 2> /dev/null || true
# Remove any existing volume for this container if it exists
docker volume rm "cloudflared-${CONTAINER_NAME}" 2> /dev/null || true
docker run -d \
--restart="${RESTART}" \
--env-file "$ENV_FILE" \
--name "${CONTAINER_NAME}" \
-v "cloudflared-${CONTAINER_NAME}:/etc/cloudflared" \
--network web \
--label "traefik.enable=true" \
--label "traefik.http.routers.${CONTAINER_NAME}.rule=Host(\`${SUBDOMAIN}.${DOMAIN}\`)" \