diff --git a/nginx.conf b/nginx.conf index 12e7282f1..2da48c6af 100644 --- a/nginx.conf +++ b/nginx.conf @@ -102,25 +102,6 @@ server { add_header Cache-Control "public, max-age=86400"; # 24 hours } - # /api/public_lobbies endpoint - Cache for 1 second to handle high request volume - location = /api/public_lobbies { - proxy_pass http://127.0.0.1:3000; - proxy_http_version 1.1; - - # Cache configuration - proxy_cache API_CACHE; - proxy_cache_valid 200 1s; - proxy_cache_use_stale updating error timeout http_500 http_502 http_503 http_504; - proxy_cache_lock on; - add_header X-Cache-Status $upstream_cache_status; - - # Standard proxy headers - 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; - } - # /api/env endpoint - Cache for 1 hour location = /api/env { proxy_pass http://127.0.0.1:3000; diff --git a/package-lock.json b/package-lock.json index 8169f6eec..45bbe6db1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -348,595 +348,628 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.830.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.830.0.tgz", - "integrity": "sha512-Cti+zj1lqvQIScXFQv8/t1xo3pvcvk/ObmGIbyLzfgcYpKMHaIWhzhi6aN+z4dYEv1EwrukC9tNoqScyShc5tw==", + "version": "3.982.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.982.0.tgz", + "integrity": "sha512-k0ANYAtPiON9BwLXcDgJXkmmCAGEuSk2pZOvrMej2kNhs3xTXoPshIUR5UMCD9apYiWtXJJfXMZSgaME+iWNaQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.826.0", - "@aws-sdk/credential-provider-node": "3.830.0", - "@aws-sdk/middleware-bucket-endpoint": "3.830.0", - "@aws-sdk/middleware-expect-continue": "3.821.0", - "@aws-sdk/middleware-flexible-checksums": "3.826.0", - "@aws-sdk/middleware-host-header": "3.821.0", - "@aws-sdk/middleware-location-constraint": "3.821.0", - "@aws-sdk/middleware-logger": "3.821.0", - "@aws-sdk/middleware-recursion-detection": "3.821.0", - "@aws-sdk/middleware-sdk-s3": "3.826.0", - "@aws-sdk/middleware-ssec": "3.821.0", - "@aws-sdk/middleware-user-agent": "3.828.0", - "@aws-sdk/region-config-resolver": "3.821.0", - "@aws-sdk/signature-v4-multi-region": "3.826.0", - "@aws-sdk/types": "3.821.0", - "@aws-sdk/util-endpoints": "3.828.0", - "@aws-sdk/util-user-agent-browser": "3.821.0", - "@aws-sdk/util-user-agent-node": "3.828.0", - "@aws-sdk/xml-builder": "3.821.0", - "@smithy/config-resolver": "^4.1.4", - "@smithy/core": "^3.5.3", - "@smithy/eventstream-serde-browser": "^4.0.4", - "@smithy/eventstream-serde-config-resolver": "^4.1.2", - "@smithy/eventstream-serde-node": "^4.0.4", - "@smithy/fetch-http-handler": "^5.0.4", - "@smithy/hash-blob-browser": "^4.0.4", - "@smithy/hash-node": "^4.0.4", - "@smithy/hash-stream-node": "^4.0.4", - "@smithy/invalid-dependency": "^4.0.4", - "@smithy/md5-js": "^4.0.4", - "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.11", - "@smithy/middleware-retry": "^4.1.12", - "@smithy/middleware-serde": "^4.0.8", - "@smithy/middleware-stack": "^4.0.4", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/node-http-handler": "^4.0.6", - "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.3", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.19", - "@smithy/util-defaults-mode-node": "^4.0.19", - "@smithy/util-endpoints": "^3.0.6", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.5", - "@smithy/util-stream": "^4.2.2", - "@smithy/util-utf8": "^4.0.0", - "@smithy/util-waiter": "^4.0.5", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/credential-provider-node": "^3.972.5", + "@aws-sdk/middleware-bucket-endpoint": "^3.972.3", + "@aws-sdk/middleware-expect-continue": "^3.972.3", + "@aws-sdk/middleware-flexible-checksums": "^3.972.4", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-location-constraint": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-sdk-s3": "^3.972.6", + "@aws-sdk/middleware-ssec": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.6", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/signature-v4-multi-region": "3.982.0", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.982.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.4", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.22.0", + "@smithy/eventstream-serde-browser": "^4.2.8", + "@smithy/eventstream-serde-config-resolver": "^4.3.8", + "@smithy/eventstream-serde-node": "^4.2.8", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-blob-browser": "^4.2.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/hash-stream-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/md5-js": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.12", + "@smithy/middleware-retry": "^4.4.29", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.28", + "@smithy/util-defaults-mode-node": "^4.2.31", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-stream": "^4.5.10", + "@smithy/util-utf8": "^4.2.0", + "@smithy/util-waiter": "^4.2.8", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.830.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.830.0.tgz", - "integrity": "sha512-5zCEpfI+zwX2SIa258L+TItNbBoAvQQ6w74qdFM6YJufQ1F9tvwjTX8T+eSTT9nsFIvfYnUaGalWwJVfmJUgVQ==", + "version": "3.982.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.982.0.tgz", + "integrity": "sha512-qJrIiivmvujdGqJ0ldSUvhN3k3N7GtPesoOI1BSt0fNXovVnMz4C/JmnkhZihU7hJhDvxJaBROLYTU+lpild4w==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.826.0", - "@aws-sdk/middleware-host-header": "3.821.0", - "@aws-sdk/middleware-logger": "3.821.0", - "@aws-sdk/middleware-recursion-detection": "3.821.0", - "@aws-sdk/middleware-user-agent": "3.828.0", - "@aws-sdk/region-config-resolver": "3.821.0", - "@aws-sdk/types": "3.821.0", - "@aws-sdk/util-endpoints": "3.828.0", - "@aws-sdk/util-user-agent-browser": "3.821.0", - "@aws-sdk/util-user-agent-node": "3.828.0", - "@smithy/config-resolver": "^4.1.4", - "@smithy/core": "^3.5.3", - "@smithy/fetch-http-handler": "^5.0.4", - "@smithy/hash-node": "^4.0.4", - "@smithy/invalid-dependency": "^4.0.4", - "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.11", - "@smithy/middleware-retry": "^4.1.12", - "@smithy/middleware-serde": "^4.0.8", - "@smithy/middleware-stack": "^4.0.4", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/node-http-handler": "^4.0.6", - "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.3", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.19", - "@smithy/util-defaults-mode-node": "^4.0.19", - "@smithy/util-endpoints": "^3.0.6", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.5", - "@smithy/util-utf8": "^4.0.0", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.6", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.982.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.4", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.22.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.12", + "@smithy/middleware-retry": "^4.4.29", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.28", + "@smithy/util-defaults-mode-node": "^4.2.31", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/core": { - "version": "3.826.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.826.0.tgz", - "integrity": "sha512-BGbQYzWj3ps+dblq33FY5tz/SsgJCcXX0zjQlSC07tYvU1jHTUvsefphyig+fY38xZ4wdKjbTop+KUmXUYrOXw==", + "version": "3.973.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.6.tgz", + "integrity": "sha512-pz4ZOw3BLG0NdF25HoB9ymSYyPbMiIjwQJ2aROXRhAzt+b+EOxStfFv8s5iZyP6Kiw7aYhyWxj5G3NhmkoOTKw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.821.0", - "@aws-sdk/xml-builder": "3.821.0", - "@smithy/core": "^3.5.3", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/property-provider": "^4.0.4", - "@smithy/protocol-http": "^5.1.2", - "@smithy/signature-v4": "^5.1.2", - "@smithy/smithy-client": "^4.4.3", - "@smithy/types": "^4.3.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-utf8": "^4.0.0", - "fast-xml-parser": "4.4.1", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/xml-builder": "^3.972.4", + "@smithy/core": "^3.22.0", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/signature-v4": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/crc64-nvme": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.972.0.tgz", + "integrity": "sha512-ThlLhTqX68jvoIVv+pryOdb5coP1cX1/MaTbB9xkGDCbWbsqQcLqzPxuSoW1DCnAAIacmXCWpzUNOB9pv+xXQw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.826.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.826.0.tgz", - "integrity": "sha512-DK3pQY8+iKK3MGDdC3uOZQ2psU01obaKlTYhEwNu4VWzgwQL4Vi3sWj4xSWGEK41vqZxiRLq6fOq7ysRI+qEZA==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.4.tgz", + "integrity": "sha512-/8dnc7+XNMmViEom2xsNdArQxQPSgy4Z/lm6qaFPTrMFesT1bV3PsBhb19n09nmxHdrtQskYmViddUIjUQElXg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.826.0", - "@aws-sdk/types": "3.821.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/types": "^4.3.1", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.826.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.826.0.tgz", - "integrity": "sha512-N+IVZBh+yx/9GbMZTKO/gErBi/FYZQtcFRItoLbY+6WU+0cSWyZYfkoeOxHmQV3iX9k65oljERIWUmL9x6OSQg==", + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.6.tgz", + "integrity": "sha512-5ERWqRljiZv44AIdvIRQ3k+EAV0Sq2WeJHvXuK7gL7bovSxOf8Al7MLH7Eh3rdovH4KHFnlIty7J71mzvQBl5Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.826.0", - "@aws-sdk/types": "3.821.0", - "@smithy/fetch-http-handler": "^5.0.4", - "@smithy/node-http-handler": "^4.0.6", - "@smithy/property-provider": "^4.0.4", - "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.3", - "@smithy/types": "^4.3.1", - "@smithy/util-stream": "^4.2.2", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/types": "^3.973.1", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/util-stream": "^4.5.10", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.830.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.830.0.tgz", - "integrity": "sha512-zeQenzvh8JRY5nULd8izdjVGoCM1tgsVVsrLSwDkHxZTTW0hW/bmOmXfvdaE0wDdomXW7m2CkQDSmP7XdvNXZg==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.4.tgz", + "integrity": "sha512-eRUg+3HaUKuXWn/lEMirdiA5HOKmEl8hEHVuszIDt2MMBUKgVX5XNGmb3XmbgU17h6DZ+RtjbxQpjhz3SbTjZg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.826.0", - "@aws-sdk/credential-provider-env": "3.826.0", - "@aws-sdk/credential-provider-http": "3.826.0", - "@aws-sdk/credential-provider-process": "3.826.0", - "@aws-sdk/credential-provider-sso": "3.830.0", - "@aws-sdk/credential-provider-web-identity": "3.830.0", - "@aws-sdk/nested-clients": "3.830.0", - "@aws-sdk/types": "3.821.0", - "@smithy/credential-provider-imds": "^4.0.6", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/credential-provider-env": "^3.972.4", + "@aws-sdk/credential-provider-http": "^3.972.6", + "@aws-sdk/credential-provider-login": "^3.972.4", + "@aws-sdk/credential-provider-process": "^3.972.4", + "@aws-sdk/credential-provider-sso": "^3.972.4", + "@aws-sdk/credential-provider-web-identity": "^3.972.4", + "@aws-sdk/nested-clients": "3.982.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.4.tgz", + "integrity": "sha512-nLGjXuvWWDlQAp505xIONI7Gam0vw2p7Qu3P6on/W2q7rjJXtYjtpHbcsaOjJ/pAju3eTvEQuSuRedcRHVQIAQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/nested-clients": "3.982.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.830.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.830.0.tgz", - "integrity": "sha512-X/2LrTgwtK1pkWrvofxQBI8VTi6QVLtSMpsKKPPnJQ0vgqC0e4czSIs3ZxiEsOkCBaQ2usXSiKyh0ccsQ6k2OA==", + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.5.tgz", + "integrity": "sha512-VWXKgSISQCI2GKN3zakTNHSiZ0+mux7v6YHmmbLQp/o3fvYUQJmKGcLZZzg2GFA+tGGBStplra9VFNf/WwxpYg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.826.0", - "@aws-sdk/credential-provider-http": "3.826.0", - "@aws-sdk/credential-provider-ini": "3.830.0", - "@aws-sdk/credential-provider-process": "3.826.0", - "@aws-sdk/credential-provider-sso": "3.830.0", - "@aws-sdk/credential-provider-web-identity": "3.830.0", - "@aws-sdk/types": "3.821.0", - "@smithy/credential-provider-imds": "^4.0.6", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", + "@aws-sdk/credential-provider-env": "^3.972.4", + "@aws-sdk/credential-provider-http": "^3.972.6", + "@aws-sdk/credential-provider-ini": "^3.972.4", + "@aws-sdk/credential-provider-process": "^3.972.4", + "@aws-sdk/credential-provider-sso": "^3.972.4", + "@aws-sdk/credential-provider-web-identity": "^3.972.4", + "@aws-sdk/types": "^3.973.1", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.826.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.826.0.tgz", - "integrity": "sha512-kURrc4amu3NLtw1yZw7EoLNEVhmOMRUTs+chaNcmS+ERm3yK0nKjaJzmKahmwlTQTSl3wJ8jjK7x962VPo+zWw==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.4.tgz", + "integrity": "sha512-TCZpWUnBQN1YPk6grvd5x419OfXjHvhj5Oj44GYb84dOVChpg/+2VoEj+YVA4F4E/6huQPNnX7UYbTtxJqgihw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.826.0", - "@aws-sdk/types": "3.821.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.830.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.830.0.tgz", - "integrity": "sha512-+VdRpZmfekzpySqZikAKx6l5ndnLGluioIgUG4ZznrButgFD/iogzFtGmBDFB3ZLViX1l4pMXru0zFwJEZT21Q==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.4.tgz", + "integrity": "sha512-wzsGwv9mKlwJ3vHLyembBvGE/5nPUIwRR2I51B1cBV4Cb4ql9nIIfpmHzm050XYTY5fqTOKJQnhLj7zj89VG8g==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.830.0", - "@aws-sdk/core": "3.826.0", - "@aws-sdk/token-providers": "3.830.0", - "@aws-sdk/types": "3.821.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", + "@aws-sdk/client-sso": "3.982.0", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/token-providers": "3.982.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.830.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.830.0.tgz", - "integrity": "sha512-hPYrKsZeeOdLROJ59T6Y8yZ0iwC/60L3qhZXjapBFjbqBtMaQiMTI645K6xVXBioA6vxXq7B4aLOhYqk6Fy/Ww==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.4.tgz", + "integrity": "sha512-hIzw2XzrG8jzsUSEatehmpkd5rWzASg5IHUfA+m01k/RtvfAML7ZJVVohuKdhAYx+wV2AThLiQJVzqn7F0khrw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.826.0", - "@aws-sdk/nested-clients": "3.830.0", - "@aws-sdk/types": "3.821.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/types": "^4.3.1", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/nested-clients": "3.982.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.830.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.830.0.tgz", - "integrity": "sha512-ElVeCReZSH5Ds+/pkL5ebneJjuo8f49e9JXV1cYizuH0OAOQfYaBU9+M+7+rn61pTttOFE8W//qKzrXBBJhfMg==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.972.3.tgz", + "integrity": "sha512-fmbgWYirF67YF1GfD7cg5N6HHQ96EyRNx/rDIrTF277/zTWVuPI2qS/ZHgofwR1NZPe/NWvoppflQY01LrbVLg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.821.0", - "@aws-sdk/util-arn-parser": "3.804.0", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "@smithy/util-config-provider": "^4.0.0", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-arn-parser": "^3.972.2", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-config-provider": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.821.0.tgz", - "integrity": "sha512-zAOoSZKe1njOrtynvK6ZORU57YGv5I7KP4+rwOvUN3ZhJbQ7QPf8gKtFUCYAPRMegaXCKF/ADPtDZBAmM+zZ9g==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.972.3.tgz", + "integrity": "sha512-4msC33RZsXQpUKR5QR4HnvBSNCPLGHmB55oDiROqqgyOc+TOfVu2xgi5goA7ms6MdZLeEh2905UfWMnMMF4mRg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.821.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", + "@aws-sdk/types": "^3.973.1", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.826.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.826.0.tgz", - "integrity": "sha512-Fz9w8CFYPfSlHEB6feSsi06hdS+s+FB8k5pO4L7IV0tUa78mlhxF/VNlAJaVWYyOkZXl4HPH2K48aapACSQOXw==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.972.4.tgz", + "integrity": "sha512-xOxsUkF3O3BtIe3tf54OpPo94eZepjFm3z0Dd2TZKbsPxMiRTFXurC04wJ58o/wPW9YHVO9VqZik3MfoPfrKlw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", - "@aws-sdk/core": "3.826.0", - "@aws-sdk/types": "3.821.0", - "@smithy/is-array-buffer": "^4.0.0", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-stream": "^4.2.2", - "@smithy/util-utf8": "^4.0.0", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/crc64-nvme": "3.972.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-stream": "^4.5.10", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.821.0.tgz", - "integrity": "sha512-xSMR+sopSeWGx5/4pAGhhfMvGBHioVBbqGvDs6pG64xfNwM5vq5s5v6D04e2i+uSTj4qGa71dLUs5I0UzAK3sw==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.3.tgz", + "integrity": "sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.821.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", + "@aws-sdk/types": "^3.973.1", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/middleware-location-constraint": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.821.0.tgz", - "integrity": "sha512-sKrm80k0t3R0on8aA/WhWFoMaAl4yvdk+riotmMElLUpcMcRXAd1+600uFVrxJqZdbrKQ0mjX0PjT68DlkYXLg==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.972.3.tgz", + "integrity": "sha512-nIg64CVrsXp67vbK0U1/Is8rik3huS3QkRHn2DRDx4NldrEFMgdkZGI/+cZMKD9k4YOS110Dfu21KZLHrFA/1g==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.821.0", - "@smithy/types": "^4.3.1", + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.821.0.tgz", - "integrity": "sha512-0cvI0ipf2tGx7fXYEEN5fBeZDz2RnHyb9xftSgUsEq7NBxjV0yTZfLJw6Za5rjE6snC80dRN8+bTNR1tuG89zA==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.3.tgz", + "integrity": "sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.821.0", - "@smithy/types": "^4.3.1", + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.821.0.tgz", - "integrity": "sha512-efmaifbhBoqKG3bAoEfDdcM8hn1psF+4qa7ykWuYmfmah59JBeqHLfz5W9m9JoTwoKPkFcVLWZxnyZzAnVBOIg==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.3.tgz", + "integrity": "sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.821.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", + "@aws-sdk/types": "^3.973.1", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.826.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.826.0.tgz", - "integrity": "sha512-8F0qWaYKfvD/de1AKccXuigM+gb/IZSncCqxdnFWqd+TFzo9qI9Hh+TpUhWOMYSgxsMsYQ8ipmLzlD/lDhjrmA==", + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.6.tgz", + "integrity": "sha512-Xq7wM6kbgJN1UO++8dvH/efPb1nTwWqFCpZCR7RCLOETP7xAUAhVo7JmsCnML5Di/iC4Oo5VrJ4QmkYcMZniLw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.826.0", - "@aws-sdk/types": "3.821.0", - "@aws-sdk/util-arn-parser": "3.804.0", - "@smithy/core": "^3.5.3", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/protocol-http": "^5.1.2", - "@smithy/signature-v4": "^5.1.2", - "@smithy/smithy-client": "^4.4.3", - "@smithy/types": "^4.3.1", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-stream": "^4.2.2", - "@smithy/util-utf8": "^4.0.0", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-arn-parser": "^3.972.2", + "@smithy/core": "^3.22.0", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/signature-v4": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-stream": "^4.5.10", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/middleware-ssec": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.821.0.tgz", - "integrity": "sha512-YYi1Hhr2AYiU/24cQc8HIB+SWbQo6FBkMYojVuz/zgrtkFmALxENGF/21OPg7f/QWd+eadZJRxCjmRwh5F2Cxg==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.972.3.tgz", + "integrity": "sha512-dU6kDuULN3o3jEHcjm0c4zWJlY1zWVkjG9NPe9qxYLLpcbdj5kRYBS2DdWYD+1B9f910DezRuws7xDEqKkHQIg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.821.0", - "@smithy/types": "^4.3.1", + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.828.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.828.0.tgz", - "integrity": "sha512-nixvI/SETXRdmrVab4D9LvXT3lrXkwAWGWk2GVvQvzlqN1/M/RfClj+o37Sn4FqRkGH9o9g7Fqb1YqZ4mqDAtA==", + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.6.tgz", + "integrity": "sha512-TehLN8W/kivl0U9HcS+keryElEWORROpghDXZBLfnb40DXM7hx/i+7OOjkogXQOF3QtUraJVRkHQ07bPhrWKlw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.826.0", - "@aws-sdk/types": "3.821.0", - "@aws-sdk/util-endpoints": "3.828.0", - "@smithy/core": "^3.5.3", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.982.0", + "@smithy/core": "^3.22.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.830.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.830.0.tgz", - "integrity": "sha512-5N5YTlBr1vtxf7+t+UaIQ625KEAmm7fY9o1e3MgGOi/paBoI0+axr3ud24qLIy0NSzFlAHEaxUSWxcERNjIoZw==", + "version": "3.982.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.982.0.tgz", + "integrity": "sha512-VVkaH27digrJfdVrT64rjkllvOp4oRiZuuJvrylLXAKl18ujToJR7AqpDldL/LS63RVne3QWIpkygIymxFtliQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.826.0", - "@aws-sdk/middleware-host-header": "3.821.0", - "@aws-sdk/middleware-logger": "3.821.0", - "@aws-sdk/middleware-recursion-detection": "3.821.0", - "@aws-sdk/middleware-user-agent": "3.828.0", - "@aws-sdk/region-config-resolver": "3.821.0", - "@aws-sdk/types": "3.821.0", - "@aws-sdk/util-endpoints": "3.828.0", - "@aws-sdk/util-user-agent-browser": "3.821.0", - "@aws-sdk/util-user-agent-node": "3.828.0", - "@smithy/config-resolver": "^4.1.4", - "@smithy/core": "^3.5.3", - "@smithy/fetch-http-handler": "^5.0.4", - "@smithy/hash-node": "^4.0.4", - "@smithy/invalid-dependency": "^4.0.4", - "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.11", - "@smithy/middleware-retry": "^4.1.12", - "@smithy/middleware-serde": "^4.0.8", - "@smithy/middleware-stack": "^4.0.4", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/node-http-handler": "^4.0.6", - "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.3", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.19", - "@smithy/util-defaults-mode-node": "^4.0.19", - "@smithy/util-endpoints": "^3.0.6", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.5", - "@smithy/util-utf8": "^4.0.0", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.6", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.982.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.4", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.22.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.12", + "@smithy/middleware-retry": "^4.4.29", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.28", + "@smithy/util-defaults-mode-node": "^4.2.31", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.821.0.tgz", - "integrity": "sha512-t8og+lRCIIy5nlId0bScNpCkif8sc0LhmtaKsbm0ZPm3sCa/WhCbSZibjbZ28FNjVCV+p0D9RYZx0VDDbtWyjw==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.3.tgz", + "integrity": "sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.821.0", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", + "@aws-sdk/types": "^3.973.1", + "@smithy/config-resolver": "^4.4.6", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.826.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.826.0.tgz", - "integrity": "sha512-3fEi/zy6tpMzomYosksGtu7jZqGFcdBXoL7YRsG7OEeQzBbOW9B+fVaQZ4jnsViSjzA/yKydLahMrfPnt+iaxg==", + "version": "3.982.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.982.0.tgz", + "integrity": "sha512-AWqjMAH848aNwnLCtIKM3WO00eHuUoYVfQMP4ccrUHhnEduGOusVgdHQ5mLNQZZNZzREuBwnPPhIP55cy0gFSg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.826.0", - "@aws-sdk/types": "3.821.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/signature-v4": "^5.1.2", - "@smithy/types": "^4.3.1", + "@aws-sdk/middleware-sdk-s3": "^3.972.6", + "@aws-sdk/types": "^3.973.1", + "@smithy/protocol-http": "^5.3.8", + "@smithy/signature-v4": "^5.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.830.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.830.0.tgz", - "integrity": "sha512-aJ4guFwj92nV9D+EgJPaCFKK0I3y2uMchiDfh69Zqnmwfxxxfxat6F79VA7PS0BdbjRfhLbn+Ghjftnomu2c1g==", + "version": "3.982.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.982.0.tgz", + "integrity": "sha512-v3M0KYp2TVHYHNBT7jHD9lLTWAdS9CaWJ2jboRKt0WAB65bA7iUEpR+k4VqKYtpQN4+8kKSc4w+K6kUNZkHKQw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.826.0", - "@aws-sdk/nested-clients": "3.830.0", - "@aws-sdk/types": "3.821.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/nested-clients": "3.982.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/types": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.821.0.tgz", - "integrity": "sha512-Znroqdai1a90TlxGaJ+FK1lwC0fHpo97Xjsp5UKGR5JODYm7f9+/fF17ebO1KdoBr/Rm0UIFiF5VmI8ts9F1eA==", + "version": "3.973.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.1.tgz", + "integrity": "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/util-arn-parser": { - "version": "3.804.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.804.0.tgz", - "integrity": "sha512-wmBJqn1DRXnZu3b4EkE6CWnoWMo1ZMvlfkqU5zPz67xx1GMaXlDCchFvKAXMjk4jn/L1O3tKnoFDNsoLV1kgNQ==", + "version": "3.972.2", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.2.tgz", + "integrity": "sha512-VkykWbqMjlSgBFDyrY3nOSqupMc6ivXuGmvci6Q3NnLq5kC+mKQe2QBZ4nrWRE/jqOxeFP2uYzLtwncYYcvQDg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.828.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.828.0.tgz", - "integrity": "sha512-RvKch111SblqdkPzg3oCIdlGxlQs+k+P7Etory9FmxPHyPDvsP1j1c74PmgYqtzzMWmoXTjd+c9naUHh9xG8xg==", + "version": "3.982.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.982.0.tgz", + "integrity": "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.821.0", - "@smithy/types": "^4.3.1", - "@smithy/util-endpoints": "^3.0.6", + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/util-locate-window": { @@ -952,31 +985,31 @@ } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.821.0.tgz", - "integrity": "sha512-irWZHyM0Jr1xhC+38OuZ7JB6OXMLPZlj48thElpsO1ZSLRkLZx5+I7VV6k3sp2yZ7BYbKz/G2ojSv4wdm7XTLw==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.3.tgz", + "integrity": "sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.821.0", - "@smithy/types": "^4.3.1", + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.828.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.828.0.tgz", - "integrity": "sha512-LdN6fTBzTlQmc8O8f1wiZN0qF3yBWVGis7NwpWK7FUEzP9bEZRxYfIkV9oV9zpt6iNRze1SedK3JQVB/udxBoA==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.4.tgz", + "integrity": "sha512-3WFCBLiM8QiHDfosQq3Py+lIMgWlFWwFQliUHUqwEiRqLnKyhgbU3AKa7AWJF7lW2Oc/2kFNY4MlAYVnVc0i8A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.828.0", - "@aws-sdk/types": "3.821.0", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", + "@aws-sdk/middleware-user-agent": "^3.972.6", + "@aws-sdk/types": "^3.973.1", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "peerDependencies": { "aws-crt": ">=1.0.0" @@ -988,14 +1021,24 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.821.0.tgz", - "integrity": "sha512-DIIotRnefVL6DiaHtO6/21DhJ4JZnnIwdNbpwiAhdt/AVbttcE4yw925gsjur0OGv5BTYXQXU3YnANBYnZjuQA==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.4.tgz", + "integrity": "sha512-0zJ05ANfYqI6+rGqj8samZBFod0dPPousBjLEqg8WdxSgbMAkRgLyn81lP215Do0rFJ/17LIXwr7q0yK24mP6Q==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", + "@smithy/types": "^4.12.0", + "fast-xml-parser": "5.3.4", "tslib": "^2.6.2" }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz", + "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==", + "license": "Apache-2.0", "engines": { "node": ">=18.0.0" } @@ -1189,7 +1232,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -1233,7 +1275,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -2014,9 +2055,9 @@ } }, "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", + "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2167,7 +2208,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=8.0.0" } @@ -3000,12 +3040,12 @@ } }, "node_modules/@smithy/abort-controller": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", - "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.8.tgz", + "integrity": "sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3013,9 +3053,9 @@ } }, "node_modules/@smithy/chunked-blob-reader": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.0.0.tgz", - "integrity": "sha512-+sKqDBQqb036hh4NPaUiEkYFkTUGYzRsn3EuFhyfQfMy6oGHEUJDurLP9Ufb5dasr/XiAmPNMr6wa9afjQB+Gw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.0.tgz", + "integrity": "sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -3025,12 +3065,12 @@ } }, "node_modules/@smithy/chunked-blob-reader-native": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.0.0.tgz", - "integrity": "sha512-R9wM2yPmfEMsUmlMlIgSzOyICs0x9uu7UTHoccMyt7BWw8shcGM8HqB355+BZCPBcySvbTYMs62EgEQkNxz2ig==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.2.1.tgz", + "integrity": "sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-base64": "^4.0.0", + "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" }, "engines": { @@ -3038,16 +3078,16 @@ } }, "node_modules/@smithy/config-resolver": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.5.tgz", - "integrity": "sha512-HAGoUAFYsUkoSckuKbCPayECeMim8pOu+yLy1zOxt1sifzEbrsRpYa+mKcMdiHKMeiqOibyPG0sFJnmaV/OGEg==", + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.6.tgz", + "integrity": "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.7", - "@smithy/types": "^4.11.0", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-endpoints": "^3.2.7", - "@smithy/util-middleware": "^4.2.7", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" }, "engines": { @@ -3055,19 +3095,20 @@ } }, "node_modules/@smithy/core": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.5.3.tgz", - "integrity": "sha512-xa5byV9fEguZNofCclv6v9ra0FYh5FATQW/da7FQUVTic94DfrN/NvmKZjrMyzbpqfot9ZjBaO8U1UeTbmSLuA==", + "version": "3.22.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.22.1.tgz", + "integrity": "sha512-x3ie6Crr58MWrm4viHqqy2Du2rHYZjwu8BekasrQx4ca+Y24dzVAwq3yErdqIbc2G3I0kLQA13PQ+/rde+u65g==", "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^4.0.8", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-stream": "^4.2.2", - "@smithy/util-utf8": "^4.0.0", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-stream": "^4.5.11", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" }, "engines": { @@ -3075,15 +3116,15 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.6.tgz", - "integrity": "sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.8.tgz", + "integrity": "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.1.3", - "@smithy/property-provider": "^4.0.4", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", "tslib": "^2.6.2" }, "engines": { @@ -3091,14 +3132,14 @@ } }, "node_modules/@smithy/eventstream-codec": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.0.4.tgz", - "integrity": "sha512-7XoWfZqWb/QoR/rAU4VSi0mWnO2vu9/ltS6JZ5ZSZv0eovLVfDfu0/AX4ub33RsJTOth3TiFWSHS5YdztvFnig==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.8.tgz", + "integrity": "sha512-jS/O5Q14UsufqoGhov7dHLOPCzkYJl9QDzusI2Psh4wyYx/izhzvX9P4D69aTxcdfVhEPhjK+wYyn/PzLjKbbw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^4.3.1", - "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/types": "^4.12.0", + "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -3106,13 +3147,13 @@ } }, "node_modules/@smithy/eventstream-serde-browser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.0.4.tgz", - "integrity": "sha512-3fb/9SYaYqbpy/z/H3yIi0bYKyAa89y6xPmIqwr2vQiUT2St+avRt8UKwsWt9fEdEasc5d/V+QjrviRaX1JRFA==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.8.tgz", + "integrity": "sha512-MTfQT/CRQz5g24ayXdjg53V0mhucZth4PESoA5IhvaWVDTOQLfo8qI9vzqHcPsdd2v6sqfTYqF5L/l+pea5Uyw==", "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^4.0.4", - "@smithy/types": "^4.3.1", + "@smithy/eventstream-serde-universal": "^4.2.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3120,12 +3161,12 @@ } }, "node_modules/@smithy/eventstream-serde-config-resolver": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.1.2.tgz", - "integrity": "sha512-JGtambizrWP50xHgbzZI04IWU7LdI0nh/wGbqH3sJesYToMi2j/DcoElqyOcqEIG/D4tNyxgRuaqBXWE3zOFhQ==", + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.8.tgz", + "integrity": "sha512-ah12+luBiDGzBruhu3efNy1IlbwSEdNiw8fOZksoKoWW1ZHvO/04MQsdnws/9Aj+5b0YXSSN2JXKy/ClIsW8MQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3133,13 +3174,13 @@ } }, "node_modules/@smithy/eventstream-serde-node": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.0.4.tgz", - "integrity": "sha512-RD6UwNZ5zISpOWPuhVgRz60GkSIp0dy1fuZmj4RYmqLVRtejFqQ16WmfYDdoSoAjlp1LX+FnZo+/hkdmyyGZ1w==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.8.tgz", + "integrity": "sha512-cYpCpp29z6EJHa5T9WL0KAlq3SOKUQkcgSoeRfRVwjGgSFl7Uh32eYGt7IDYCX20skiEdRffyDpvF2efEZPC0A==", "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^4.0.4", - "@smithy/types": "^4.3.1", + "@smithy/eventstream-serde-universal": "^4.2.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3147,13 +3188,13 @@ } }, "node_modules/@smithy/eventstream-serde-universal": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.0.4.tgz", - "integrity": "sha512-UeJpOmLGhq1SLox79QWw/0n2PFX+oPRE1ZyRMxPIaFEfCqWaqpB7BU9C8kpPOGEhLF7AwEqfFbtwNxGy4ReENA==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.8.tgz", + "integrity": "sha512-iJ6YNJd0bntJYnX6s52NC4WFYcZeKrPUr1Kmmr5AwZcwCSzVpS7oavAmxMR7pMq7V+D1G4s9F5NJK0xwOsKAlQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-codec": "^4.0.4", - "@smithy/types": "^4.3.1", + "@smithy/eventstream-codec": "^4.2.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3161,15 +3202,15 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.4.tgz", - "integrity": "sha512-AMtBR5pHppYMVD7z7G+OlHHAcgAN7v0kVKEpHuTO4Gb199Gowh0taYi9oDStFeUhetkeP55JLSVlTW1n9rFtUw==", + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.9.tgz", + "integrity": "sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/querystring-builder": "^4.0.4", - "@smithy/types": "^4.3.1", - "@smithy/util-base64": "^4.0.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/querystring-builder": "^4.2.8", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" }, "engines": { @@ -3177,14 +3218,14 @@ } }, "node_modules/@smithy/hash-blob-browser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.0.4.tgz", - "integrity": "sha512-WszRiACJiQV3QG6XMV44i5YWlkrlsM5Yxgz4jvsksuu7LDXA6wAtypfPajtNTadzpJy3KyJPoWehYpmZGKUFIQ==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.9.tgz", + "integrity": "sha512-m80d/iicI7DlBDxyQP6Th7BW/ejDGiF0bgI754+tiwK0lgMkcaIBgvwwVc7OFbY4eUzpGtnig52MhPAEJ7iNYg==", "license": "Apache-2.0", "dependencies": { - "@smithy/chunked-blob-reader": "^5.0.0", - "@smithy/chunked-blob-reader-native": "^4.0.0", - "@smithy/types": "^4.3.1", + "@smithy/chunked-blob-reader": "^5.2.0", + "@smithy/chunked-blob-reader-native": "^4.2.1", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3192,14 +3233,14 @@ } }, "node_modules/@smithy/hash-node": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.4.tgz", - "integrity": "sha512-qnbTPUhCVnCgBp4z4BUJUhOEkVwxiEi1cyFM+Zj6o+aY8OFGxUQleKWq8ltgp3dujuhXojIvJWdoqpm6dVO3lQ==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.8.tgz", + "integrity": "sha512-7ZIlPbmaDGxVoxErDZnuFG18WekhbA/g2/i97wGj+wUBeS6pcUeAym8u4BXh/75RXWhgIJhyC11hBzig6MljwA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@smithy/types": "^4.12.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -3207,13 +3248,13 @@ } }, "node_modules/@smithy/hash-stream-node": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.0.4.tgz", - "integrity": "sha512-wHo0d8GXyVmpmMh/qOR0R7Y46/G1y6OR8U+bSTB4ppEzRxd1xVAQ9xOE9hOc0bSjhz0ujCPAbfNLkLrpa6cevg==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.8.tgz", + "integrity": "sha512-v0FLTXgHrTeheYZFGhR+ehX5qUm4IQsjAiL9qehad2cyjMWcN2QG6/4mSwbSgEQzI7jwfoXj7z4fxZUx/Mhj2w==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", - "@smithy/util-utf8": "^4.0.0", + "@smithy/types": "^4.12.0", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -3221,12 +3262,12 @@ } }, "node_modules/@smithy/invalid-dependency": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.4.tgz", - "integrity": "sha512-bNYMi7WKTJHu0gn26wg8OscncTt1t2b8KcsZxvOv56XA6cyXtOAAAaNP7+m45xfppXfOatXF3Sb1MNsLUgVLTw==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.8.tgz", + "integrity": "sha512-N9iozRybwAQ2dn9Fot9kI6/w9vos2oTXLhtK7ovGqwZjlOcxu6XhPlpLpC+INsxktqHinn5gS2DXDjDF2kG5sQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3234,9 +3275,9 @@ } }, "node_modules/@smithy/is-array-buffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", - "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -3246,13 +3287,13 @@ } }, "node_modules/@smithy/md5-js": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.0.4.tgz", - "integrity": "sha512-uGLBVqcOwrLvGh/v/jw423yWHq/ofUGK1W31M2TNspLQbUV1Va0F5kTxtirkoHawODAZcjXTSGi7JwbnPcDPJg==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.8.tgz", + "integrity": "sha512-oGMaLj4tVZzLi3itBa9TCswgMBr7k9b+qKYowQ6x1rTyTuO1IU2YHdHUa+891OsOH+wCsH7aTPRsTJO3RMQmjQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", - "@smithy/util-utf8": "^4.0.0", + "@smithy/types": "^4.12.0", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -3260,13 +3301,13 @@ } }, "node_modules/@smithy/middleware-content-length": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.4.tgz", - "integrity": "sha512-F7gDyfI2BB1Kc+4M6rpuOLne5LOcEknH1n6UQB69qv+HucXBR1rkzXBnQTB2q46sFy1PM/zuSJOB532yc8bg3w==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.8.tgz", + "integrity": "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3274,18 +3315,18 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.11.tgz", - "integrity": "sha512-zDogwtRLzKl58lVS8wPcARevFZNBOOqnmzWWxVe9XiaXU2CADFjvJ9XfNibgkOWs08sxLuSr81NrpY4mgp9OwQ==", + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.13.tgz", + "integrity": "sha512-x6vn0PjYmGdNuKh/juUJJewZh7MoQ46jYaJ2mvekF4EesMuFfrl4LaW/k97Zjf8PTCPQmPgMvwewg7eNoH9n5w==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.5.3", - "@smithy/middleware-serde": "^4.0.8", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-middleware": "^4.0.4", + "@smithy/core": "^3.22.1", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" }, "engines": { @@ -3293,46 +3334,33 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.12.tgz", - "integrity": "sha512-wvIH70c4e91NtRxdaLZF+mbLZ/HcC6yg7ySKUiufL6ESp6zJUSnJucZ309AvG9nqCFHSRB5I6T3Ez1Q9wCh0Ww==", + "version": "4.4.30", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.30.tgz", + "integrity": "sha512-CBGyFvN0f8hlnqKH/jckRDz78Snrp345+PVk8Ux7pnkUCW97Iinse59lY78hBt04h1GZ6hjBN94BRwZy1xC8Bg==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.1.3", - "@smithy/protocol-http": "^5.1.2", - "@smithy/service-error-classification": "^4.0.5", - "@smithy/smithy-client": "^4.4.3", - "@smithy/types": "^4.3.1", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.5", - "tslib": "^2.6.2", - "uuid": "^9.0.1" + "@smithy/node-config-provider": "^4.3.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/service-error-classification": "^4.2.8", + "@smithy/smithy-client": "^4.11.2", + "@smithy/types": "^4.12.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@smithy/middleware-retry/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/@smithy/middleware-serde": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.8.tgz", - "integrity": "sha512-iSSl7HJoJaGyMIoNn2B7czghOVwJ9nD7TMvLhMWeSB5vt0TnEYyRRqPJu/TqW76WScaNvYYB8nRoiBHR9S1Ddw==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.9.tgz", + "integrity": "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3340,12 +3368,12 @@ } }, "node_modules/@smithy/middleware-stack": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.4.tgz", - "integrity": "sha512-kagK5ggDrBUCCzI93ft6DjteNSfY8Ulr83UtySog/h09lTIOAJ/xUSObutanlPT0nhoHAkpmW9V5K8oPyLh+QA==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.8.tgz", + "integrity": "sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3353,14 +3381,14 @@ } }, "node_modules/@smithy/node-config-provider": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.7.tgz", - "integrity": "sha512-7r58wq8sdOcrwWe+klL9y3bc4GW1gnlfnFOuL7CXa7UzfhzhxKuzNdtqgzmTV+53lEp9NXh5hY/S4UgjLOzPfw==", + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.8.tgz", + "integrity": "sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.7", - "@smithy/shared-ini-file-loader": "^4.4.2", - "@smithy/types": "^4.11.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3368,15 +3396,15 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", - "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.9.tgz", + "integrity": "sha512-KX5Wml5mF+luxm1szW4QDz32e3NObgJ4Fyw+irhph4I/2geXwUy4jkIMUs5ZPGflRBeR6BUkC2wqIab4Llgm3w==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.0.4", - "@smithy/protocol-http": "^5.1.2", - "@smithy/querystring-builder": "^4.0.4", - "@smithy/types": "^4.3.1", + "@smithy/abort-controller": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/querystring-builder": "^4.2.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3384,12 +3412,12 @@ } }, "node_modules/@smithy/property-provider": { - "version": "4.2.7", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.7.tgz", - "integrity": "sha512-jmNYKe9MGGPoSl/D7JDDs1C8b3dC8f/w78LbaVfoTtWy4xAd5dfjaFG9c9PWPihY4ggMQNQSMtzU77CNgAJwmA==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.8.tgz", + "integrity": "sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.11.0", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3397,12 +3425,12 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", - "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.8.tgz", + "integrity": "sha512-QNINVDhxpZ5QnP3aviNHQFlRogQZDfYlCkQT+7tJnErPQbDhysondEjhikuANxgMsZrkGeiAxXy4jguEGsDrWQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3410,13 +3438,13 @@ } }, "node_modules/@smithy/querystring-builder": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.4.tgz", - "integrity": "sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.8.tgz", + "integrity": "sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", - "@smithy/util-uri-escape": "^4.0.0", + "@smithy/types": "^4.12.0", + "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -3424,12 +3452,12 @@ } }, "node_modules/@smithy/querystring-parser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.4.tgz", - "integrity": "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.8.tgz", + "integrity": "sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3437,24 +3465,24 @@ } }, "node_modules/@smithy/service-error-classification": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.5.tgz", - "integrity": "sha512-LvcfhrnCBvCmTee81pRlh1F39yTS/+kYleVeLCwNtkY8wtGg8V/ca9rbZZvYIl8OjlMtL6KIjaiL/lgVqHD2nA==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.8.tgz", + "integrity": "sha512-mZ5xddodpJhEt3RkCjbmUQuXUOaPNTkbMGR0bcS8FE0bJDLMZlhmpgrvPNCYglVw5rsYTpSnv19womw9WWXKQQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1" + "@smithy/types": "^4.12.0" }, "engines": { "node": ">=18.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.2.tgz", - "integrity": "sha512-M7iUUff/KwfNunmrgtqBfvZSzh3bmFgv/j/t1Y1dQ+8dNo34br1cqVEqy6v0mYEgi0DkGO7Xig0AnuOaEGVlcg==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.3.tgz", + "integrity": "sha512-DfQjxXQnzC5UbCUPeC3Ie8u+rIWZTvuDPAGU/BxzrOGhRvgUanaP68kDZA+jaT3ZI+djOf+4dERGlm9mWfFDrg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.11.0", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3462,18 +3490,18 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.2.tgz", - "integrity": "sha512-d3+U/VpX7a60seHziWnVZOHuEgJlclufjkS6zhXvxcJgkJq4UWdH5eOBLzHRMx6gXjsdT9h6lfpmLzbrdupHgQ==", + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.8.tgz", + "integrity": "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-uri-escape": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -3481,17 +3509,17 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.3.tgz", - "integrity": "sha512-xxzNYgA0HD6ETCe5QJubsxP0hQH3QK3kbpJz3QrosBCuIWyEXLR/CO5hFb2OeawEKUxMNhz3a1nuJNN2np2RMA==", + "version": "4.11.2", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.11.2.tgz", + "integrity": "sha512-SCkGmFak/xC1n7hKRsUr6wOnBTJ3L22Qd4e8H1fQIuKTAjntwgU8lrdMe7uHdiT2mJAOWA/60qaW9tiMu69n1A==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.5.3", - "@smithy/middleware-endpoint": "^4.1.11", - "@smithy/middleware-stack": "^4.0.4", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "@smithy/util-stream": "^4.2.2", + "@smithy/core": "^3.22.1", + "@smithy/middleware-endpoint": "^4.4.13", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-stream": "^4.5.11", "tslib": "^2.6.2" }, "engines": { @@ -3499,9 +3527,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.11.0.tgz", - "integrity": "sha512-mlrmL0DRDVe3mNrjTcVcZEgkFmufITfUAPBEA+AHYiIeYyJebso/He1qLbP3PssRe22KUzLRpQSdBPbXdgZ2VA==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -3511,13 +3539,13 @@ } }, "node_modules/@smithy/url-parser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.4.tgz", - "integrity": "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.8.tgz", + "integrity": "sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA==", "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.0.4", - "@smithy/types": "^4.3.1", + "@smithy/querystring-parser": "^4.2.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3525,13 +3553,13 @@ } }, "node_modules/@smithy/util-base64": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", - "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -3539,9 +3567,9 @@ } }, "node_modules/@smithy/util-body-length-browser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", - "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", + "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -3551,9 +3579,9 @@ } }, "node_modules/@smithy/util-body-length-node": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", - "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", + "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -3563,12 +3591,12 @@ } }, "node_modules/@smithy/util-buffer-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", - "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", + "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -3588,15 +3616,14 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.0.19", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.19.tgz", - "integrity": "sha512-mvLMh87xSmQrV5XqnUYEPoiFFeEGYeAKIDDKdhE2ahqitm8OHM3aSvhqL6rrK6wm1brIk90JhxDf5lf2hbrLbQ==", + "version": "4.3.29", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.29.tgz", + "integrity": "sha512-nIGy3DNRmOjaYaaKcQDzmWsro9uxlaqUOhZDHQed9MW/GmkBZPtnU70Pu1+GT9IBmUXwRdDuiyaeiy9Xtpn3+Q==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.0.4", - "@smithy/smithy-client": "^4.4.3", - "@smithy/types": "^4.3.1", - "bowser": "^2.11.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/smithy-client": "^4.11.2", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3604,17 +3631,17 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.0.19", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.19.tgz", - "integrity": "sha512-8tYnx+LUfj6m+zkUUIrIQJxPM1xVxfRBvoGHua7R/i6qAxOMjqR6CpEpDwKoIs1o0+hOjGvkKE23CafKL0vJ9w==", + "version": "4.2.32", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.32.tgz", + "integrity": "sha512-7dtFff6pu5fsjqrVve0YMhrnzJtccCWDacNKOkiZjJ++fmjGExmmSu341x+WU6Oc1IccL7lDuaUj7SfrHpWc5Q==", "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^4.1.4", - "@smithy/credential-provider-imds": "^4.0.6", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/property-provider": "^4.0.4", - "@smithy/smithy-client": "^4.4.3", - "@smithy/types": "^4.3.1", + "@smithy/config-resolver": "^4.4.6", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/smithy-client": "^4.11.2", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3622,13 +3649,13 @@ } }, "node_modules/@smithy/util-endpoints": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.7.tgz", - "integrity": "sha512-s4ILhyAvVqhMDYREeTS68R43B1V5aenV5q/V1QpRQJkCXib5BPRo4s7uNdzGtIKxaPHCfU/8YkvPAEvTpxgspg==", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.8.tgz", + "integrity": "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.7", - "@smithy/types": "^4.11.0", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3636,9 +3663,9 @@ } }, "node_modules/@smithy/util-hex-encoding": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", - "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -3648,12 +3675,12 @@ } }, "node_modules/@smithy/util-middleware": { - "version": "4.2.7", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.7.tgz", - "integrity": "sha512-i1IkpbOae6NvIKsEeLLM9/2q4X+M90KV3oCFgWQI4q0Qz+yUZvsr+gZPdAEAtFhWQhAHpTsJO8DRJPuwVyln+w==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.8.tgz", + "integrity": "sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.11.0", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3661,13 +3688,13 @@ } }, "node_modules/@smithy/util-retry": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.5.tgz", - "integrity": "sha512-V7MSjVDTlEt/plmOFBn1762Dyu5uqMrV2Pl2X0dYk4XvWfdWJNe9Bs5Bzb56wkCuiWjSfClVMGcsuKrGj7S/yg==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.8.tgz", + "integrity": "sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg==", "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.0.5", - "@smithy/types": "^4.3.1", + "@smithy/service-error-classification": "^4.2.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3675,18 +3702,18 @@ } }, "node_modules/@smithy/util-stream": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.2.tgz", - "integrity": "sha512-aI+GLi7MJoVxg24/3J1ipwLoYzgkB4kUfogZfnslcYlynj3xsQ0e7vk4TnTro9hhsS5PvX1mwmkRqqHQjwcU7w==", + "version": "4.5.11", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.11.tgz", + "integrity": "sha512-lKmZ0S/3Qj2OF5H1+VzvDLb6kRxGzZHq6f3rAsoSu5cTLGsn3v3VQBA8czkNNXlLjoFEtVu3OQT2jEeOtOE2CA==", "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^5.0.4", - "@smithy/node-http-handler": "^4.0.6", - "@smithy/types": "^4.3.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/node-http-handler": "^4.4.9", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -3694,9 +3721,9 @@ } }, "node_modules/@smithy/util-uri-escape": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", - "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -3706,12 +3733,12 @@ } }, "node_modules/@smithy/util-utf8": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", - "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -3719,13 +3746,25 @@ } }, "node_modules/@smithy/util-waiter": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.0.5.tgz", - "integrity": "sha512-4QvC49HTteI1gfemu0I1syWovJgPvGn7CVUoN9ZFkdvr/cCFkrEL7qNCdx/2eICqDWEGnnr68oMdSIPCLAriSQ==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.8.tgz", + "integrity": "sha512-n+lahlMWk+aejGuax7DPWtqav8HYnWxQwR+LCG2BgCUmaGcTe9qZCFsmw8TMg9iG75HOwhrJCX9TCJRLH+Yzqg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", + "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.0.4", - "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { @@ -4562,7 +4601,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.32.tgz", "integrity": "sha512-3jigKqgSjsH6gYZv2nEsqdXfZqIFGAV36XYYjf9KGZ3PSG+IhLecqPnI310RvjutyMwifE2hhhNEklOUrvx/wA==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -4728,7 +4766,6 @@ "integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.34.1", "@typescript-eslint/types": "8.34.1", @@ -5208,7 +5245,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5561,9 +5597,9 @@ "license": "ISC" }, "node_modules/bowser": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", - "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", + "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==", "license": "MIT" }, "node_modules/brace-expansion": { @@ -5608,7 +5644,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001718", "electron-to-chromium": "^1.5.160", @@ -5762,7 +5797,6 @@ "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", @@ -6684,7 +6718,6 @@ "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "dev": true, "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -7254,7 +7287,6 @@ "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -7696,22 +7728,18 @@ "license": "MIT" }, "node_modules/fast-xml-parser": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", - "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.4.tgz", + "integrity": "sha512-EFd6afGmXlCx8H8WTZHhAoDaWaGyuIBoZJ2mknrNxug+aZKjkp0a0dlars9Izl+jF+7Gu1/5f/2h68cQpe0IiA==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" - }, - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" } ], "license": "MIT", "dependencies": { - "strnum": "^1.0.5" + "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" @@ -8589,7 +8617,6 @@ "integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@acemir/cssom": "^0.9.28", "@asamuzakjp/dom-selector": "^6.7.6", @@ -10185,7 +10212,6 @@ "integrity": "sha512-dyuThzncsgEgJZnvd/A/5x6IkUERbK+phXqUQrI+0C6WE+8xqGH5VChRTLecemhgZF0kQ+gZOM3tJTX9937xpg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@pixi/colord": "^2.9.6", "@types/css-font-loading-module": "^0.0.12", @@ -10230,7 +10256,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -10333,7 +10358,6 @@ "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -11156,7 +11180,6 @@ "integrity": "sha512-Z0NVCW45W8Mg5oC/27/+fCqIHFnW8kpkFOq0j9XJIev4Ld0mKmERaZv5DMLAb9fGCevjKwaEeIQz5+MBXfZcDw==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@sinonjs/commons": "^3.0.1", "@sinonjs/fake-timers": "^15.1.0", @@ -11329,9 +11352,9 @@ } }, "node_modules/strnum": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", - "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", + "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", "funding": [ { "type": "github", @@ -11578,7 +11601,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -11821,7 +11843,6 @@ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.3.tgz", "integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" @@ -11890,7 +11911,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -12037,7 +12057,6 @@ "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -12787,7 +12806,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -12801,7 +12819,6 @@ "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/expect": "4.0.16", "@vitest/mocker": "4.0.16", diff --git a/resources/lang/en.json b/resources/lang/en.json index 9960066dc..a71672a2f 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -447,7 +447,8 @@ "gold_multiplier": "Gold multiplier", "gold_multiplier_placeholder": "2.0x", "starting_gold": "Starting gold", - "starting_gold_placeholder": "5000000" + "starting_gold_placeholder": "5000000", + "crowded": "Crowded modifier" }, "team_colors": { "red": "Red", @@ -772,6 +773,8 @@ "attack_cancelled_retreat": "Attack cancelled, {troops} soldiers killed during retreat", "received_gold_from_captured_ship": "Received {gold} gold from ship captured from {name}", "received_gold_from_trade": "Received {gold} gold from trade with {name}", + "received_gold_from_conquest": "Conquered {name}, received {gold} gold", + "conquered_no_gold": "Conquered {name} (didn't play, no gold awarded)", "missile_intercepted": "Missile intercepted {unit}", "mirv_warheads_intercepted": "{count, plural, one {{count} MIRV warhead intercepted} other {{count} MIRV warheads intercepted}}", "sent_troops_to_player": "Sent {troops} troops to {name}", diff --git a/src/client/Api.ts b/src/client/Api.ts index d128ad2f2..7a18dea8a 100644 --- a/src/client/Api.ts +++ b/src/client/Api.ts @@ -235,13 +235,6 @@ export async function fetchPlayerLeaderboard( }); if (!res.ok) { - // Handle "Page must be between X and Y" error as end of list - if (res.status === 400) { - const errorJson = await res.json().catch(() => null); - if (errorJson?.message?.includes("Page must be between")) { - return "reached_limit"; - } - } console.warn( "fetchPlayerLeaderboard: unexpected status", res.status, @@ -253,6 +246,10 @@ export async function fetchPlayerLeaderboard( const json = await res.json(); const parsed = RankedLeaderboardResponseSchema.safeParse(json); if (!parsed.success) { + // Handle "Page must be between X and Y" error as end of list + if (json?.message?.includes?.("Page must be between")) { + return "reached_limit"; + } console.warn( "fetchPlayerLeaderboard: Zod validation failed", parsed.error.toString(), diff --git a/src/client/Auth.ts b/src/client/Auth.ts index c5fdef981..6dd5f4992 100644 --- a/src/client/Auth.ts +++ b/src/client/Auth.ts @@ -2,12 +2,16 @@ import { decodeJwt } from "jose"; import { z } from "zod"; import { TokenPayload, TokenPayloadSchema } from "../core/ApiSchemas"; import { base64urlToUuid } from "../core/Base64"; +import { ID } from "../core/Schemas"; +import { generateID } from "../core/Util"; import { getApiBase, getAudience } from "./Api"; import { generateCryptoRandomUUID } from "./Utils"; export type UserAuth = { jwt: string; claims: TokenPayload } | false; const PERSISTENT_ID_KEY = "player_persistent_id"; +const CLIENT_ID_KEY = "client_join_id"; +const CLIENT_GAME_ID_KEY = "client_join_game_id"; let __jwt: string | null = null; @@ -209,6 +213,22 @@ export function getPersistentID(): string { return base64urlToUuid(sub); } +export function getClientIDForGame(gameID: string): string { + const storedGameID = sessionStorage.getItem(CLIENT_GAME_ID_KEY); + const storedClientID = sessionStorage.getItem(CLIENT_ID_KEY); + if ( + storedGameID === gameID && + storedClientID && + ID.safeParse(storedClientID).success + ) { + return storedClientID; + } + const newID = generateID(); + sessionStorage.setItem(CLIENT_GAME_ID_KEY, gameID); + sessionStorage.setItem(CLIENT_ID_KEY, newID); + return newID; +} + // WARNING: DO NOT EXPOSE THIS ID function getPersistentIDFromLocalStorage(): string { // Try to get existing localStorage diff --git a/src/client/FlagInputModal.ts b/src/client/FlagInputModal.ts index fe2ea7c6b..4b864bb15 100644 --- a/src/client/FlagInputModal.ts +++ b/src/client/FlagInputModal.ts @@ -73,7 +73,7 @@ export class FlagInputModal extends BaseModal { }} /> ${country.name} diff --git a/src/client/HostLobbyModal.ts b/src/client/HostLobbyModal.ts index 8b3e314f4..6bca2ca8c 100644 --- a/src/client/HostLobbyModal.ts +++ b/src/client/HostLobbyModal.ts @@ -21,6 +21,7 @@ import { isValidGameID, } from "../core/Schemas"; import { generateID } from "../core/Util"; +import { getClientIDForGame } from "./Auth"; import "./components/baseComponents/Modal"; import { BaseModal } from "./components/BaseModal"; import "./components/CopyButton"; @@ -635,9 +636,10 @@ export class HostLobbyModal extends BaseModal { } protected onOpen(): void { - this.lobbyCreatorClientID = generateID(); + this.lobbyId = generateID(); + this.lobbyCreatorClientID = getClientIDForGame(this.lobbyId); - createLobby(this.lobbyCreatorClientID) + createLobby(this.lobbyCreatorClientID, this.lobbyId) .then(async (lobby) => { this.lobbyId = lobby.gameID; if (!isValidGameID(this.lobbyId)) { @@ -653,6 +655,7 @@ export class HostLobbyModal extends BaseModal { detail: { gameID: this.lobbyId, clientID: this.lobbyCreatorClientID, + source: "host", } as JoinLobbyEvent, bubbles: true, composed: true, @@ -1080,12 +1083,14 @@ export class HostLobbyModal extends BaseModal { } } -async function createLobby(creatorClientID: string): Promise { +async function createLobby( + creatorClientID: string, + gameID: string, +): Promise { const config = await getServerConfigFromClient(); try { - const id = generateID(); const response = await fetch( - `/${config.workerPath(id)}/api/create_game/${id}?creatorClientID=${encodeURIComponent(creatorClientID)}`, + `/${config.workerPath(gameID)}/api/create_game/${gameID}?creatorClientID=${encodeURIComponent(creatorClientID)}`, { method: "POST", headers: { diff --git a/src/client/JoinLobbyModal.ts b/src/client/JoinLobbyModal.ts index 20e0e79b0..0f1c5e2da 100644 --- a/src/client/JoinLobbyModal.ts +++ b/src/client/JoinLobbyModal.ts @@ -17,7 +17,6 @@ import { GameRecordSchema, LobbyInfoEvent, } from "../core/Schemas"; -import { generateID } from "../core/Util"; import { getServerConfigFromClient } from "../core/configuration/ConfigLoader"; import { GameMapSize, @@ -26,6 +25,7 @@ import { HumansVsNations, } from "../core/game/Game"; import { getApiBase } from "./Api"; +import { getClientIDForGame } from "./Auth"; import { crazyGamesSDK } from "./CrazyGamesSDK"; import { JoinLobbyEvent } from "./Main"; import { terrainMapFileLoader } from "./TerrainMapFileLoader"; @@ -55,6 +55,10 @@ export class JoinLobbyModal extends BaseModal { private countdownTimerId: number | null = null; private handledJoinTimeout = false; + private isPrivateLobby(): boolean { + return this.gameConfig?.gameType === GameType.Private; + } + private readonly handleLobbyInfo = (event: LobbyInfoEvent) => { const lobby = event.lobby; if (!this.currentLobbyId || lobby.gameID !== this.currentLobbyId) { @@ -66,7 +70,7 @@ export class JoinLobbyModal extends BaseModal { } this.updateFromLobby({ ...lobby, - msUntilStart: lobby.msUntilStart ?? undefined, + startsAt: lobby.startsAt ?? undefined, }); }; @@ -90,7 +94,10 @@ export class JoinLobbyModal extends BaseModal { }) : translateText("public_lobby.started"); const maxPlayers = this.gameConfig?.maxPlayers ?? 0; - const playerCount = this.playerCount; + const playerCount = this.players?.length ?? 0; + const hostClientID = this.isPrivateLobby() + ? (this.lobbyCreatorClientID ?? "") + : ""; const content = html`
this.closeAndLeave(), ariaLabel: translateText("common.close"), rightContent: - this.currentLobbyId && - this.gameConfig?.gameType === GameType.Private + this.currentLobbyId && this.isPrivateLobby() ? html` ` @@ -129,7 +135,7 @@ export class JoinLobbyModal extends BaseModal { class="mt-6" .gameMode=${this.gameConfig?.gameMode ?? GameMode.FFA} .clients=${this.players} - .lobbyCreatorClientID=${this.lobbyCreatorClientID} + .lobbyCreatorClientID=${hostClientID} .currentClientID=${this.currentClientID} .teamCount=${this.gameConfig?.playerTeams ?? 2} .nationCount=${this.nationCount} @@ -143,7 +149,7 @@ export class JoinLobbyModal extends BaseModal { `}
- ${this.gameConfig?.gameType === GameType.Private + ${this.isPrivateLobby() ? html`
${modalHeader({ titleContent: html` diff --git a/src/client/LobbySocket.ts b/src/client/LobbySocket.ts index f398da054..7b71dc21a 100644 --- a/src/client/LobbySocket.ts +++ b/src/client/LobbySocket.ts @@ -1,6 +1,5 @@ -import { GameInfo } from "../core/Schemas"; - -type LobbyUpdateHandler = (lobbies: GameInfo[]) => void; +import { getServerConfigFromClient } from "../core/configuration/ConfigLoader"; +import { PublicGames, PublicGamesSchema } from "../core/Schemas"; interface LobbySocketOptions { reconnectDelay?: number; @@ -8,36 +7,39 @@ interface LobbySocketOptions { pollIntervalMs?: number; } +function getRandomWorkerPath(numWorkers: number): string { + const workerIndex = Math.floor(Math.random() * numWorkers); + return `/w${workerIndex}`; +} + export class PublicLobbySocket { private ws: WebSocket | null = null; private wsReconnectTimeout: number | null = null; - private fallbackPollInterval: number | null = null; private wsConnectionAttempts = 0; private wsAttemptCounted = false; + private workerPath: string = ""; private readonly reconnectDelay: number; private readonly maxWsAttempts: number; - private readonly pollIntervalMs: number; - private readonly onLobbiesUpdate: LobbyUpdateHandler; constructor( - onLobbiesUpdate: LobbyUpdateHandler, + private onLobbiesUpdate: (data: PublicGames) => void, options?: LobbySocketOptions, ) { - this.onLobbiesUpdate = onLobbiesUpdate; this.reconnectDelay = options?.reconnectDelay ?? 3000; this.maxWsAttempts = options?.maxWsAttempts ?? 3; - this.pollIntervalMs = options?.pollIntervalMs ?? 1000; } - start() { + async start() { this.wsConnectionAttempts = 0; + // Get config to determine number of workers, then pick a random one + const config = await getServerConfigFromClient(); + this.workerPath = getRandomWorkerPath(config.numWorkers()); this.connectWebSocket(); } stop() { this.disconnectWebSocket(); - this.stopFallbackPolling(); } private connectWebSocket() { @@ -49,7 +51,7 @@ export class PublicLobbySocket { } const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; - const wsUrl = `${protocol}//${window.location.host}/lobbies`; + const wsUrl = `${protocol}//${window.location.host}${this.workerPath}/lobbies`; this.ws = new WebSocket(wsUrl); this.wsAttemptCounted = false; @@ -70,15 +72,14 @@ export class PublicLobbySocket { clearTimeout(this.wsReconnectTimeout); this.wsReconnectTimeout = null; } - this.stopFallbackPolling(); } private handleMessage(event: MessageEvent) { try { - const message = JSON.parse(event.data as string); - if (message.type === "lobbies_update") { - this.onLobbiesUpdate(message.data?.lobbies ?? []); - } + const publicGames = PublicGamesSchema.parse( + JSON.parse(event.data as string), + ); + this.onLobbiesUpdate(publicGames); } catch (error) { console.error("Error parsing WebSocket message:", error); if (this.ws && this.ws.readyState === WebSocket.OPEN) { @@ -101,10 +102,7 @@ export class PublicLobbySocket { this.wsConnectionAttempts++; } if (this.wsConnectionAttempts >= this.maxWsAttempts) { - console.log( - "Max WebSocket attempts reached, falling back to HTTP polling", - ); - this.startFallbackPolling(); + console.error("Max WebSocket attempts reached"); } else { this.scheduleReconnect(); } @@ -121,7 +119,7 @@ export class PublicLobbySocket { this.wsConnectionAttempts++; } if (this.wsConnectionAttempts >= this.maxWsAttempts) { - this.startFallbackPolling(); + alert("error connecting to game service"); } else { this.scheduleReconnect(); } @@ -145,33 +143,4 @@ export class PublicLobbySocket { this.wsReconnectTimeout = null; } } - - private startFallbackPolling() { - if (this.fallbackPollInterval !== null) return; - console.log("Starting HTTP fallback polling"); - this.fetchLobbiesHTTP(); - this.fallbackPollInterval = window.setInterval(() => { - this.fetchLobbiesHTTP(); - }, this.pollIntervalMs); - } - - private stopFallbackPolling() { - if (this.fallbackPollInterval !== null) { - clearInterval(this.fallbackPollInterval); - this.fallbackPollInterval = null; - } - } - - private async fetchLobbiesHTTP() { - try { - const response = await fetch(`/api/public_lobbies`); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const data = await response.json(); - this.onLobbiesUpdate(data.lobbies as GameInfo[]); - } catch (error) { - console.error("Error fetching lobbies via HTTP:", error); - } - } } diff --git a/src/client/Main.ts b/src/client/Main.ts index c48d0f06f..ba6569889 100644 --- a/src/client/Main.ts +++ b/src/client/Main.ts @@ -1,12 +1,7 @@ import version from "resources/version.txt?raw"; import { UserMeResponse } from "../core/ApiSchemas"; import { EventBus } from "../core/EventBus"; -import { - GAME_ID_REGEX, - GameInfo, - GameRecord, - GameStartInfo, -} from "../core/Schemas"; +import { GAME_ID_REGEX, GameRecord, GameStartInfo } from "../core/Schemas"; import { GameEnv } from "../core/configuration/Config"; import { getServerConfigFromClient } from "../core/configuration/ConfigLoader"; import { GameType } from "../core/game/Game"; @@ -223,7 +218,6 @@ export interface JoinLobbyEvent { // GameRecord exists when replaying an archived game. gameRecord?: GameRecord; source?: "public" | "private" | "host" | "matchmaking" | "singleplayer"; - publicLobbyInfo?: GameInfo; } class Client { @@ -773,7 +767,7 @@ class Client { } const config = await getServerConfigFromClient(); // Only update URL immediately for private lobbies, not public ones - if (!lobby.publicLobbyInfo && lobby.source !== "public") { + if (lobby.source !== "public") { this.updateJoinUrlForShare(lobby.gameID, config); } @@ -908,7 +902,7 @@ class Client { // Open the join lobby modal page and pass the lobby info window.showPage?.("page-join-lobby"); - this.joinModal?.open(lobby.gameID, lobby); + this.joinModal?.open(lobby.gameID, true); } private async handleLeaveLobby(/* event: CustomEvent */) { diff --git a/src/client/Matchmaking.ts b/src/client/Matchmaking.ts index c29931896..3fa4af738 100644 --- a/src/client/Matchmaking.ts +++ b/src/client/Matchmaking.ts @@ -2,9 +2,8 @@ import { html, LitElement } from "lit"; import { customElement, query, state } from "lit/decorators.js"; import { UserMeResponse } from "../core/ApiSchemas"; import { getServerConfigFromClient } from "../core/configuration/ConfigLoader"; -import { generateID } from "../core/Util"; import { getUserMe, hasLinkedAccount } from "./Api"; -import { getPlayToken } from "./Auth"; +import { getClientIDForGame, getPlayToken } from "./Auth"; import { BaseModal } from "./components/BaseModal"; import "./components/Difficulties"; import "./components/PatternButton"; @@ -231,7 +230,8 @@ export class MatchmakingModal extends BaseModal { new CustomEvent("join-lobby", { detail: { gameID: this.gameID, - clientID: generateID(), + clientID: getClientIDForGame(this.gameID), + source: "matchmaking", } as JoinLobbyEvent, bubbles: true, composed: true, diff --git a/src/client/PublicLobby.ts b/src/client/PublicLobby.ts index dc60087fd..ef9729c93 100644 --- a/src/client/PublicLobby.ts +++ b/src/client/PublicLobby.ts @@ -1,7 +1,7 @@ import { html, LitElement } from "lit"; import { customElement, state } from "lit/decorators.js"; import { GameMapType } from "../core/game/Game"; -import { GameID, GameInfo } from "../core/Schemas"; +import { GameID, PublicGameInfo, PublicGames } from "../core/Schemas"; import { PublicLobbySocket } from "./LobbySocket"; import { terrainMapFileLoader } from "./TerrainMapFileLoader"; import { @@ -13,16 +13,19 @@ import { } from "./Utils"; export interface ShowPublicLobbyModalEvent { - lobby: GameInfo; + lobby: PublicGameInfo; } @customElement("public-lobby") export class PublicLobby extends LitElement { - @state() private lobbies: GameInfo[] = []; + @state() private publicGames: PublicGames | null = null; + @state() public isLobbyHighlighted: boolean = false; @state() private mapImages: Map = new Map(); + private lobbyIDToStart = new Map(); - private lobbySocket = new PublicLobbySocket((lobbies) => - this.handleLobbiesUpdate(lobbies), + private serverTimeOffset = 0; + private lobbySocket = new PublicLobbySocket((data) => + this.handleLobbiesUpdate(data), ); createRenderRoot() { @@ -39,12 +42,18 @@ export class PublicLobby extends LitElement { this.lobbySocket.stop(); } - private handleLobbiesUpdate(lobbies: GameInfo[]) { - this.lobbies = lobbies; - this.lobbies.forEach((l) => { + private handleLobbiesUpdate(publicGames: PublicGames) { + this.publicGames = publicGames; + + // Calculate offset between server time and client time + if (this.publicGames) { + this.serverTimeOffset = this.publicGames.serverTime - Date.now(); + } + this.publicGames.games.forEach((l) => { if (!this.lobbyIDToStart.has(l.gameID)) { - const msUntilStart = l.msUntilStart ?? 0; - this.lobbyIDToStart.set(l.gameID, msUntilStart + Date.now()); + // Convert server's startsAt to client time by subtracting offset + const startsAt = l.startsAt ?? Date.now(); + this.lobbyIDToStart.set(l.gameID, startsAt - this.serverTimeOffset); } if (l.gameConfig && !this.mapImages.has(l.gameID)) { @@ -66,9 +75,9 @@ export class PublicLobby extends LitElement { } render() { - if (this.lobbies.length === 0) return html``; + if (!this.publicGames) return html``; - const lobby = this.lobbies[0]; + const lobby = this.publicGames.games[0]; if (!lobby?.gameConfig) return html``; const start = this.lobbyIDToStart.get(lobby.gameID) ?? 0; @@ -200,7 +209,7 @@ export class PublicLobby extends LitElement { this.lobbySocket.stop(); } - private lobbyClicked(lobby: GameInfo) { + private lobbyClicked(lobby: PublicGameInfo) { // Validate username before opening the modal const usernameInput = document.querySelector("username-input") as any; if ( diff --git a/src/client/SinglePlayerModal.ts b/src/client/SinglePlayerModal.ts index 7a10f21aa..1623d996e 100644 --- a/src/client/SinglePlayerModal.ts +++ b/src/client/SinglePlayerModal.ts @@ -967,6 +967,7 @@ export class SinglePlayerModal extends BaseModal { }, lobbyCreatedAt: Date.now(), // ms; server should be authoritative in MP }, + source: "singleplayer", } satisfies JoinLobbyEvent, bubbles: true, composed: true, diff --git a/src/client/UsernameInput.ts b/src/client/UsernameInput.ts index cfff06a1f..11889cd67 100644 --- a/src/client/UsernameInput.ts +++ b/src/client/UsernameInput.ts @@ -147,8 +147,8 @@ export class UsernameInput extends LitElement { } private validateAndStore() { - // Prevent empty username even if clan tag is present - if (!this.baseUsername.trim()) { + // Validate base username meets minimum length (clan tag doesn't count) + if (this.baseUsername.trim().length < MIN_USERNAME_LENGTH) { this._isValid = false; this.validationError = translateText("username.too_short", { min: MIN_USERNAME_LENGTH, diff --git a/src/client/components/baseComponents/Button.ts b/src/client/components/baseComponents/Button.ts index dd0818c5a..e3dd03898 100644 --- a/src/client/components/baseComponents/Button.ts +++ b/src/client/components/baseComponents/Button.ts @@ -13,7 +13,7 @@ export class OButton extends LitElement { @property({ type: Boolean }) disable = false; @property({ type: Boolean }) fill = false; private static readonly BASE_CLASS = - "bg-blue-600 hover:bg-blue-700 text-white font-bold uppercase tracking-wider px-4 py-3 rounded-xl transition-all duration-300 transform hover:-translate-y-px outline-none border border-transparent text-center text-base lg:text-lg"; + "bg-blue-600 hover:bg-blue-700 text-white font-bold uppercase tracking-wider px-4 py-3 rounded-xl transition-all duration-300 transform hover:-translate-y-px outline-none border border-transparent text-center text-base lg:text-lg whitespace-normal break-words leading-tight overflow-hidden relative"; createRenderRoot() { return this; @@ -39,9 +39,11 @@ export class OButton extends LitElement { class=${classMap(this.getButtonClasses())} ?disabled=${this.disable} > - ${this.translationKey === "" - ? this.title - : translateText(this.translationKey)} + + ${this.translationKey === "" + ? this.title + : translateText(this.translationKey)} + `; } diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index 7a374a527..5fa6dd80f 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -270,7 +270,7 @@ export function createRenderer( structureLayer, samRadiusLayer, new UnitLayer(game, eventBus, transformHandler), - new FxLayer(game), + new FxLayer(game, transformHandler), new UILayer(game, eventBus, transformHandler), new NukeTrajectoryPreviewLayer(game, eventBus, transformHandler, uiState), new StructureIconsLayer(game, eventBus, uiState, transformHandler), diff --git a/src/client/graphics/layers/ControlPanel.ts b/src/client/graphics/layers/ControlPanel.ts index c6f000405..c45a31b00 100644 --- a/src/client/graphics/layers/ControlPanel.ts +++ b/src/client/graphics/layers/ControlPanel.ts @@ -261,7 +261,7 @@ export class ControlPanel extends LitElement implements Layer { return html`
e.preventDefault()} > diff --git a/src/client/graphics/layers/EventsDisplay.ts b/src/client/graphics/layers/EventsDisplay.ts index db68fe987..3d0bad609 100644 --- a/src/client/graphics/layers/EventsDisplay.ts +++ b/src/client/graphics/layers/EventsDisplay.ts @@ -1038,7 +1038,9 @@ export class EventsDisplay extends LitElement implements Layer { class="relative w-full min-[1200px]:bottom-4 min-[1200px]:right-4 z-50 min-[1200px]:w-96 backdrop-blur-sm" > -
+
${this.renderToggleButton( @@ -1081,7 +1083,7 @@ export class EventsDisplay extends LitElement implements Layer {
this.lastRefresh + this.refreshRate) { - const delta = now - this.lastRefresh; - this.renderAllFx(context, delta); - this.lastRefresh = now; + const nowMs = performance.now(); + + const hasFx = this.allFx.length > 0; + if (!this.game.config().userSettings()?.fxLayer() || !hasFx) { + if (this.hasBufferedFrame) { + // Clear stale pixels once when fx ends/disabled so re-enabling doesn't + // flash old frames. + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + this.hasBufferedFrame = false; } - context.drawImage( - this.canvas, - -this.game.width() / 2, - -this.game.height() / 2, - this.game.width(), - this.game.height(), - ); + this.lastRefreshMs = nowMs; + return; } + + const needsRefresh = + !this.hasBufferedFrame || nowMs > this.lastRefreshMs + this.refreshRate; + if (needsRefresh) { + const delta = this.hasBufferedFrame ? nowMs - this.lastRefreshMs : 0; + this.renderAllFx(delta); + this.lastRefreshMs = nowMs; + this.hasBufferedFrame = true; + } + + this.drawVisibleFx(context); } - renderAllFx(context: CanvasRenderingContext2D, delta: number) { - if (this.allFx.length > 0) { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - this.renderContextFx(delta); - } + private drawVisibleFx(context: CanvasRenderingContext2D) { + const mapW = this.game.width(); + const mapH = this.game.height(); + + const [topLeft, bottomRight] = this.transformHandler.screenBoundingRect(); + const pad = 2; + + const left = Math.max(0, Math.floor(topLeft.x - pad)); + const top = Math.max(0, Math.floor(topLeft.y - pad)); + const right = Math.min(mapW, Math.ceil(bottomRight.x + pad)); + const bottom = Math.min(mapH, Math.ceil(bottomRight.y + pad)); + + const width = Math.max(0, right - left); + const height = Math.max(0, bottom - top); + if (width === 0 || height === 0) return; + + context.drawImage( + this.canvas, + left, + top, + width, + height, + -mapW / 2 + left, + -mapH / 2 + top, + width, + height, + ); + } + + private renderAllFx(delta: number) { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + this.renderContextFx(delta); } renderContextFx(duration: number) { diff --git a/src/client/graphics/layers/HeadsUpMessage.ts b/src/client/graphics/layers/HeadsUpMessage.ts index cdb69b2a4..0239e2f15 100644 --- a/src/client/graphics/layers/HeadsUpMessage.ts +++ b/src/client/graphics/layers/HeadsUpMessage.ts @@ -126,7 +126,7 @@ export class HeadsUpMessage extends LitElement implements Layer { ${this.isVisible ? html`
@@ -452,7 +452,7 @@ export class PlayerInfoOverlay extends LitElement implements Layer { return html`
e.preventDefault()} >
; export type PlayerColor = z.infer; export type Flag = z.infer; export type GameStartInfo = z.infer; +export type GameInfo = z.infer; +export type PublicGames = z.infer; +export type PublicGameInfo = z.infer; const ClientInfoSchema = z.object({ clientID: z.string(), @@ -145,18 +148,22 @@ const ClientInfoSchema = z.object({ export const GameInfoSchema = z.object({ gameID: z.string(), clients: z.array(ClientInfoSchema).optional(), - numClients: z.number().optional(), - msUntilStart: z.number().optional(), + startsAt: z.number().optional(), + serverTime: z.number(), gameConfig: z.lazy(() => GameConfigSchema).optional(), }); -export interface GameInfo { - gameID: GameID; - clients?: ClientInfo[]; - numClients?: number; - msUntilStart?: number; - gameConfig?: GameConfig; -} +export const PublicGameInfoSchema = z.object({ + gameID: z.string(), + numClients: z.number(), + startsAt: z.number(), + gameConfig: z.lazy(() => GameConfigSchema).optional(), +}); + +export const PublicGamesSchema = z.object({ + serverTime: z.number(), + games: PublicGameInfoSchema.array(), +}); export class LobbyInfoEvent implements GameEvent { constructor(public lobby: GameInfo) {} diff --git a/src/core/Util.ts b/src/core/Util.ts index 330359021..a79dda1a8 100644 --- a/src/core/Util.ts +++ b/src/core/Util.ts @@ -80,13 +80,14 @@ export function calculateBoundingBox( maxX = -Infinity, maxY = -Infinity; - borderTiles.forEach((tile: TileRef) => { - const cell = gm.cell(tile); - minX = Math.min(minX, cell.x); - minY = Math.min(minY, cell.y); - maxX = Math.max(maxX, cell.x); - maxY = Math.max(maxY, cell.y); - }); + for (const tile of borderTiles) { + const x = gm.x(tile); + const y = gm.y(tile); + minX = Math.min(minX, x); + minY = Math.min(minY, y); + maxX = Math.max(maxX, x); + maxY = Math.max(maxY, y); + } return { min: new Cell(minX, minY), max: new Cell(maxX, maxY) }; } diff --git a/src/core/execution/BotExecution.ts b/src/core/execution/BotExecution.ts index 4f9f178f9..85fabb34a 100644 --- a/src/core/execution/BotExecution.ts +++ b/src/core/execution/BotExecution.ts @@ -1,7 +1,8 @@ -import { Execution, Game, Player } from "../game/Game"; +import { Execution, Game, isStructureType, Player } from "../game/Game"; import { PseudoRandom } from "../PseudoRandom"; import { simpleHash } from "../Util"; import { AllianceExtensionExecution } from "./alliance/AllianceExtensionExecution"; +import { DeleteUnitExecution } from "./DeleteUnitExecution"; import { AiAttackBehavior } from "./utils/AiAttackBehavior"; export class BotExecution implements Execution { @@ -58,6 +59,7 @@ export class BotExecution implements Execution { } this.acceptAllAllianceRequests(); + this.deleteAllStructures(); this.maybeAttack(); } @@ -80,6 +82,14 @@ export class BotExecution implements Execution { } } + private deleteAllStructures() { + for (const unit of this.bot.units()) { + if (isStructureType(unit.type()) && this.bot.canDeleteUnit()) { + this.mg.addExecution(new DeleteUnitExecution(this.bot, unit.id())); + } + } + } + private maybeAttack() { if (this.attackBehavior === null) { throw new Error("not initialized"); diff --git a/src/core/execution/PlayerExecution.ts b/src/core/execution/PlayerExecution.ts index c7e452e5d..e1d80d0b4 100644 --- a/src/core/execution/PlayerExecution.ts +++ b/src/core/execution/PlayerExecution.ts @@ -1,5 +1,5 @@ import { Config } from "../configuration/Config"; -import { Execution, Game, Player, UnitType } from "../game/Game"; +import { Cell, Execution, Game, Player, UnitType } from "../game/Game"; import { TileRef } from "../game/GameMap"; import { calculateBoundingBox, getMode, inscribed, simpleHash } from "../Util"; @@ -139,11 +139,12 @@ export class PlayerExecution implements Execution { const largestCluster = clusters[largestIndex]; if (largestCluster === undefined) throw new Error("No clusters"); - this.player.largestClusterBoundingBox = calculateBoundingBox( - this.mg, + const largestClusterBox = calculateBoundingBox(this.mg, largestCluster); + this.player.largestClusterBoundingBox = largestClusterBox; + const surroundedBy = this.surroundedBySamePlayer( largestCluster, + largestClusterBox, ); - const surroundedBy = this.surroundedBySamePlayer(largestCluster); if (surroundedBy && !surroundedBy.isFriendly(this.player)) { this.removeCluster(largestCluster); } @@ -158,7 +159,10 @@ export class PlayerExecution implements Execution { } } - private surroundedBySamePlayer(cluster: Set): false | Player { + private surroundedBySamePlayer( + cluster: Set, + clusterBox: { min: Cell; max: Cell }, + ): false | Player { const enemies = new Set(); for (const tile of cluster) { let hasUnownedNeighbor = false; @@ -187,7 +191,6 @@ export class PlayerExecution implements Execution { } const enemy = this.mg.playerBySmallID(Array.from(enemies)[0]) as Player; const enemyBox = calculateBoundingBox(this.mg, enemy.borderTiles()); - const clusterBox = calculateBoundingBox(this.mg, cluster); if (inscribed(enemyBox, clusterBox)) { return enemy; } @@ -195,7 +198,11 @@ export class PlayerExecution implements Execution { } private isSurrounded(cluster: Set): boolean { - const enemyTiles = new Set(); + let hasEnemy = false; + let minX = Infinity, + minY = Infinity, + maxX = -Infinity, + maxY = -Infinity; for (const tr of cluster) { if (this.mg.isShore(tr) || this.mg.isOnEdgeOfMap(tr)) { return false; @@ -203,27 +210,31 @@ export class PlayerExecution implements Execution { this.mg.forEachNeighbor(tr, (n) => { const owner = this.mg.owner(n); if (owner.isPlayer() && this.mg.ownerID(n) !== this.player.smallID()) { - enemyTiles.add(n); + hasEnemy = true; + const x = this.mg.x(n); + const y = this.mg.y(n); + minX = Math.min(minX, x); + minY = Math.min(minY, y); + maxX = Math.max(maxX, x); + maxY = Math.max(maxY, y); } }); } - if (enemyTiles.size === 0) { + if (!hasEnemy) { return false; } - const enemyBox = calculateBoundingBox(this.mg, enemyTiles); const clusterBox = calculateBoundingBox(this.mg, cluster); + const enemyBox = { min: new Cell(minX, minY), max: new Cell(maxX, maxY) }; return inscribed(enemyBox, clusterBox); } private removeCluster(cluster: Set) { - if ( - Array.from(cluster).some( - (t) => this.mg?.ownerID(t) !== this.player?.smallID(), - ) - ) { - // Other removeCluster operations could change tile owners, - // so double check. - return; + for (const t of cluster) { + if (this.mg?.ownerID(t) !== this.player?.smallID()) { + // Other removeCluster operations could change tile owners, + // so double check. + return; + } } const capturing = this.getCapturingPlayer(cluster); diff --git a/src/core/execution/TrainStationExecution.ts b/src/core/execution/TrainStationExecution.ts index af8a5bd3f..5f029a0cc 100644 --- a/src/core/execution/TrainStationExecution.ts +++ b/src/core/execution/TrainStationExecution.ts @@ -45,7 +45,9 @@ export class TrainStationExecution implements Execution { this.active = false; return; } - this.spawnTrain(this.station, ticks); + if (this.spawnTrains) { + this.spawnTrain(this.station, ticks); + } } private shouldSpawnTrain(): boolean { @@ -69,8 +71,8 @@ export class TrainStationExecution implements Execution { if (cluster === null) { return; } - const availableForTrade = cluster.availableForTrade(this.unit.owner()); - if (availableForTrade.size === 0) { + const owner = this.unit.owner(); + if (!cluster.hasAnyTradeDestination(owner)) { return; } if (!this.shouldSpawnTrain()) { @@ -79,20 +81,20 @@ export class TrainStationExecution implements Execution { // Pick a destination randomly. // Could be improved to pick a lucrative trip - const destination: TrainStation = - this.random.randFromSet(availableForTrade); - if (destination !== station) { - this.mg.addExecution( - new TrainExecution( - this.mg.railNetwork(), - this.unit.owner(), - station, - destination, - this.numCars, - ), - ); - this.lastSpawnTick = currentTick; - } + const destination = cluster.randomTradeDestination(owner, this.random); + if (destination === null) return; + if (destination === station) return; + + this.mg.addExecution( + new TrainExecution( + this.mg.railNetwork(), + owner, + station, + destination, + this.numCars, + ), + ); + this.lastSpawnTick = currentTick; } activeDuringSpawnPhase(): boolean { diff --git a/src/core/game/GameImpl.ts b/src/core/game/GameImpl.ts index 6d409fc58..6dd76beff 100644 --- a/src/core/game/GameImpl.ts +++ b/src/core/game/GameImpl.ts @@ -7,6 +7,7 @@ import { import { AStarWaterHierarchical } from "../pathfinding/algorithms/AStar.WaterHierarchical"; import { PathFinder } from "../pathfinding/types"; import { AllPlayersStats, ClientID, Winner } from "../Schemas"; +import { ATTACK_INDEX_SENT } from "../StatsSchemas"; import { simpleHash } from "../Util"; import { AllianceImpl } from "./AllianceImpl"; import { AllianceRequestImpl } from "./AllianceRequestImpl"; @@ -621,31 +622,46 @@ export class GameImpl implements Game { } private updateBorders(tile: TileRef) { - const tiles: TileRef[] = []; - tiles.push(tile); - this.neighbors(tile).forEach((t) => tiles.push(t)); - - for (const t of tiles) { + const updateBorderStatus = (t: TileRef) => { if (!this.hasOwner(t)) { - continue; + return; } + const owner = this.owner(t) as PlayerImpl; if (this.calcIsBorder(t)) { - (this.owner(t) as PlayerImpl)._borderTiles.add(t); + owner._borderTiles.add(t); } else { - (this.owner(t) as PlayerImpl)._borderTiles.delete(t); + owner._borderTiles.delete(t); } - } + }; + + updateBorderStatus(tile); + this.forEachNeighbor(tile, updateBorderStatus); } private calcIsBorder(tile: TileRef): boolean { if (!this.hasOwner(tile)) { return false; } - for (const neighbor of this.neighbors(tile)) { - const bordersEnemy = this.owner(tile) !== this.owner(neighbor); - if (bordersEnemy) { - return true; - } + const ownerId = this.ownerID(tile); + const x = this.x(tile); + const y = this.y(tile); + if (x > 0 && this.ownerID(this._map.ref(x - 1, y)) !== ownerId) { + return true; + } + if ( + x + 1 < this._width && + this.ownerID(this._map.ref(x + 1, y)) !== ownerId + ) { + return true; + } + if (y > 0 && this.ownerID(this._map.ref(x, y - 1)) !== ownerId) { + return true; + } + if ( + y + 1 < this._height && + this.ownerID(this._map.ref(x, y + 1)) !== ownerId + ) { + return true; } return false; } @@ -1097,26 +1113,48 @@ export class GameImpl implements Game { } } - const gold = conquered.gold(); - this.displayMessage( - `Conquered ${conquered.displayName()} received ${renderNumber( + // Don't transfer gold when the conquered player didn't play (never attacked anyone) + // This is especially important when starting gold is enabled + const stats = this._stats.getPlayerStats(conquered); + const attacksSent = stats?.attacks?.[ATTACK_INDEX_SENT] ?? 0n; + const skipGoldTransfer = + attacksSent === 0n && conquered.type() === PlayerType.Human; + const gold = skipGoldTransfer ? 0n : conquered.gold(); + + if (skipGoldTransfer) { + this.displayMessage( + "events_display.conquered_no_gold", + MessageType.CONQUERED_PLAYER, + conqueror.id(), + undefined, + { + name: conquered.displayName(), + }, + ); + } else { + this.displayMessage( + "events_display.received_gold_from_conquest", + MessageType.CONQUERED_PLAYER, + conqueror.id(), gold, - )} gold`, - MessageType.CONQUERED_PLAYER, - conqueror.id(), - gold, - ); - conqueror.addGold(gold); - conquered.removeGold(gold); + { + gold: renderNumber(gold), + name: conquered.displayName(), + }, + ); + conqueror.addGold(gold); + conquered.removeGold(gold); + + // Record stats + this.stats().goldWar(conqueror, conquered, gold); + } + this.addUpdate({ type: GameUpdateType.ConquestEvent, conquerorId: conqueror.id(), conqueredId: conquered.id(), gold, }); - - // Record stats - this.stats().goldWar(conqueror, conquered, gold); } } diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts index fae23df8e..64cdf70c8 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -20,6 +20,7 @@ import { ColoredTeams, Embargo, EmojiMessage, + GameMode, Gold, MessageType, MutableAlliance, @@ -29,6 +30,7 @@ import { PlayerProfile, PlayerType, Relation, + StructureTypes, Team, TerraNullius, Tick, @@ -994,10 +996,10 @@ export class PlayerImpl implements Player { if (!this.mg.hasOwner(targetTile)) { return false; } - return this.nukeSpawn(targetTile); + return this.nukeSpawn(targetTile, unitType); case UnitType.AtomBomb: case UnitType.HydrogenBomb: - return this.nukeSpawn(targetTile); + return this.nukeSpawn(targetTile, unitType); case UnitType.MIRVWarhead: return targetTile; case UnitType.Port: @@ -1024,7 +1026,7 @@ export class PlayerImpl implements Player { } } - nukeSpawn(tile: TileRef): TileRef | false { + nukeSpawn(tile: TileRef, nukeType: UnitType): TileRef | false { if (this.mg.isSpawnImmunityActive()) { return false; } @@ -1034,6 +1036,24 @@ export class PlayerImpl implements Player { return false; } } + + // Prevent launching nukes that would hit teammate structures (only in team games) + if ( + this.mg.config().gameConfig().gameMode === GameMode.Team && + nukeType !== UnitType.MIRV + ) { + const magnitude = this.mg.config().nukeMagnitudes(nukeType); + const wouldHitTeammate = this.mg.anyUnitNearby( + tile, + magnitude.outer, + StructureTypes, + (unit) => unit.owner().isPlayer() && this.isOnSameTeam(unit.owner()), + ); + if (wouldHitTeammate) { + return false; + } + } + // only get missilesilos that are not on cooldown and not under construction const spawns = this.units(UnitType.MissileSilo) .filter((silo) => { diff --git a/src/core/game/TrainStation.ts b/src/core/game/TrainStation.ts index 9917737d9..6e9b57f0e 100644 --- a/src/core/game/TrainStation.ts +++ b/src/core/game/TrainStation.ts @@ -155,6 +155,12 @@ export class TrainStation { */ export class Cluster { public stations: Set = new Set(); + private tradeStations: Set = new Set(); + + private isTradeStation(station: TrainStation): boolean { + const type = station.unit.type(); + return type === UnitType.City || type === UnitType.Port; + } has(station: TrainStation) { return this.stations.has(station); @@ -162,11 +168,15 @@ export class Cluster { addStation(station: TrainStation) { this.stations.add(station); + if (this.isTradeStation(station)) { + this.tradeStations.add(station); + } station.setCluster(this); } removeStation(station: TrainStation) { this.stations.delete(station); + this.tradeStations.delete(station); } addStations(stations: Set) { @@ -181,14 +191,39 @@ export class Cluster { } } + hasAnyTradeDestination(player: Player): boolean { + for (const station of this.tradeStations) { + if (station.tradeAvailable(player)) { + return true; + } + } + return false; + } + + randomTradeDestination( + player: Player, + random: PseudoRandom, + ): TrainStation | null { + let selected: TrainStation | null = null; + let eligibleSeen = 0; + + for (const station of this.tradeStations) { + if (!station.tradeAvailable(player)) continue; + eligibleSeen++; + + // Reservoir sampling: keep each eligible station with probability 1/eligibleSeen. + if (random.nextInt(0, eligibleSeen) === 0) { + selected = station; + } + } + + return selected; + } + availableForTrade(player: Player): Set { const tradingStations = new Set(); - for (const station of this.stations) { - if ( - (station.unit.type() === UnitType.City || - station.unit.type() === UnitType.Port) && - station.tradeAvailable(player) - ) { + for (const station of this.tradeStations) { + if (station.tradeAvailable(player)) { tradingStations.add(station); } } @@ -201,6 +236,7 @@ export class Cluster { clear() { this.stations.clear(); + this.tradeStations.clear(); } } diff --git a/src/server/GameManager.ts b/src/server/GameManager.ts index d2588ae00..7b7e4358a 100644 --- a/src/server/GameManager.ts +++ b/src/server/GameManager.ts @@ -26,6 +26,12 @@ export class GameManager { return this.games.get(id) ?? null; } + public publicLobbies(): GameServer[] { + return Array.from(this.games.values()).filter( + (g) => g.phase() === GamePhase.Lobby && g.isPublic(), + ); + } + joinClient(client: Client, gameID: GameID): boolean { const game = this.games.get(gameID); if (game) { @@ -52,6 +58,7 @@ export class GameManager { id: GameID, gameConfig: GameConfig | undefined, creatorClientID?: string, + startsAt?: number, ) { const game = new GameServer( id, @@ -77,6 +84,7 @@ export class GameManager { ...gameConfig, }, creatorClientID, + startsAt, ); this.games.set(id, game); return game; diff --git a/src/server/GamePreviewBuilder.ts b/src/server/GamePreviewBuilder.ts index a68c335c2..ea9916025 100644 --- a/src/server/GamePreviewBuilder.ts +++ b/src/server/GamePreviewBuilder.ts @@ -147,8 +147,7 @@ export function buildPreview( activePlayers = countActivePlayers(players); } else { activePlayers = - countActivePlayers(players) || - (lobby?.numClients ?? lobby?.clients?.length ?? 0); + countActivePlayers(players) || (lobby?.clients?.length ?? 0); } const map = lobby?.gameConfig?.gameMap ?? config.gameMap; let mode = lobby?.gameConfig?.gameMode ?? config.gameMode ?? GameMode.FFA; diff --git a/src/server/GameServer.ts b/src/server/GameServer.ts index ef476f1ec..f8b183dce 100644 --- a/src/server/GameServer.ts +++ b/src/server/GameServer.ts @@ -88,6 +88,7 @@ export class GameServer { private config: ServerConfig, public gameConfig: GameConfig, private lobbyCreatorID?: string, + private startsAt?: number, ) { this.log = log_.child({ gameID: id }); } @@ -506,15 +507,6 @@ export class GameServer { return this.activeClients.length; } - public startTime(): number { - if (this._startTime !== null && this._startTime > 0) { - return this._startTime; - } else { - //game hasn't started yet, only works for public games - return this.createdAt + this.config.gameCreationRate(); - } - } - public prestart() { if (this.hasStarted()) { return; @@ -787,8 +779,9 @@ export class GameServer { } } - const msSinceCreation = now - this.createdAt; - const lessThanLifetime = msSinceCreation < this.config.gameCreationRate(); + // Public Games + + const lessThanLifetime = Date.now() < this.startsAt!; const notEnoughPlayers = this.gameConfig.gameType === GameType.Public && this.gameConfig.maxPlayers && @@ -796,8 +789,7 @@ export class GameServer { if (lessThanLifetime && notEnoughPlayers) { return GamePhase.Lobby; } - const warmupOver = - now > this.createdAt + this.config.gameCreationRate() + 30 * 1000; + const warmupOver = now > this.startsAt! + 30 * 1000; if (noActive && warmupOver && noRecentPings) { return GamePhase.Finished; } @@ -817,15 +809,11 @@ export class GameServer { clientID: c.clientID, })), gameConfig: this.gameConfig, - msUntilStart: this.isPublic() ? this.getMsUntilStart() : undefined, + startsAt: this.startsAt, + serverTime: Date.now(), }; } - private getMsUntilStart(): number { - const startTime = this.createdAt + this.config.gameCreationRate(); - return Math.max(0, startTime - Date.now()); - } - public isPublic(): boolean { return this.gameConfig.gameType === GameType.Public; } diff --git a/src/server/IPCBridgeSchema.ts b/src/server/IPCBridgeSchema.ts new file mode 100644 index 000000000..e6034091e --- /dev/null +++ b/src/server/IPCBridgeSchema.ts @@ -0,0 +1,56 @@ +import { z } from "zod"; +import { + GameConfigSchema, + PublicGameInfoSchema, + PublicGamesSchema, +} from "../core/Schemas"; + +export type WorkerLobbyList = z.infer; +export type WorkerReady = z.infer; +export type MasterLobbiesBroadcast = z.infer< + typeof MasterLobbiesBroadcastSchema +>; +export type MasterCreateGame = z.infer; +export type WorkerMessage = z.infer; +export type MasterMessage = z.infer; + +// --- Worker Messages --- + +// Worker tells the master about its lobbies. +const WorkerLobbyListSchema = z.object({ + type: z.literal("lobbyList"), + lobbies: z.array(PublicGameInfoSchema), +}); + +const WorkerReadySchema = z.object({ + type: z.literal("workerReady"), + workerId: z.number(), +}); + +export const WorkerMessageSchema = z.discriminatedUnion("type", [ + WorkerLobbyListSchema, + WorkerReadySchema, +]); + +// --- Master Messages --- + +// Broadcasts all public game info to all workers. +// Workers need information on all public lobbies so +// it can send it to the client. +const MasterLobbiesBroadcastSchema = z.object({ + type: z.literal("lobbiesBroadcast"), + publicGames: PublicGamesSchema, +}); + +// Master sends a message to worker to schedule a new public game/lobby. +const MasterCreateGameSchema = z.object({ + type: z.literal("createGame"), + gameID: z.string(), + gameConfig: GameConfigSchema, + startsAt: z.number(), +}); + +export const MasterMessageSchema = z.discriminatedUnion("type", [ + MasterLobbiesBroadcastSchema, + MasterCreateGameSchema, +]); diff --git a/src/server/Master.ts b/src/server/Master.ts index c2b131c2d..a9322d8ff 100644 --- a/src/server/Master.ts +++ b/src/server/Master.ts @@ -5,19 +5,16 @@ import rateLimit from "express-rate-limit"; import http from "http"; import path from "path"; import { fileURLToPath } from "url"; -import { WebSocket, WebSocketServer } from "ws"; import { GameEnv } from "../core/configuration/Config"; import { getServerConfigFromServer } from "../core/configuration/ConfigLoader"; -import { GameInfo } from "../core/Schemas"; -import { generateID } from "../core/Util"; import { logger } from "./Logger"; import { MapPlaylist } from "./MapPlaylist"; -import { startPolling } from "./PollingLoop"; +import { MasterLobbyService } from "./MasterLobbyService"; import { renderHtml } from "./RenderHtml"; const config = getServerConfigFromServer(); const playlist = new MapPlaylist(); -const readyWorkers = new Set(); +let lobbyService: MasterLobbyService; const app = express(); const server = http.createServer(app); @@ -68,33 +65,6 @@ app.use( }), ); -let publicLobbiesData: { lobbies: GameInfo[] } = { lobbies: [] }; - -const publicLobbyIDs: Set = new Set(); -const connectedClients: Set = new Set(); - -// Broadcast lobbies to all connected clients -function broadcastLobbies() { - const message = JSON.stringify({ - type: "lobbies_update", - data: publicLobbiesData, - }); - - const clientsToRemove: WebSocket[] = []; - - connectedClients.forEach((client) => { - if (client.readyState === WebSocket.OPEN) { - client.send(message); - } else { - clientsToRemove.push(client); - } - }); - - clientsToRemove.forEach((client) => { - connectedClients.delete(client); - }); -} - // Start the master process export async function startMaster() { if (!cluster.isPrimary) { @@ -106,36 +76,7 @@ export async function startMaster() { log.info(`Primary ${process.pid} is running`); log.info(`Setting up ${config.numWorkers()} workers...`); - // Setup WebSocket server for clients - const wss = new WebSocketServer({ server, path: "/lobbies" }); - - wss.on("connection", (ws: WebSocket) => { - connectedClients.add(ws); - - // Send current lobbies immediately (always send, even if empty) - ws.send( - JSON.stringify({ type: "lobbies_update", data: publicLobbiesData }), - ); - - ws.on("close", () => { - connectedClients.delete(ws); - }); - - ws.on("error", (error) => { - log.error(`WebSocket error:`, error); - connectedClients.delete(ws); - try { - if ( - ws.readyState === WebSocket.OPEN || - ws.readyState === WebSocket.CONNECTING - ) { - ws.close(1011, "WebSocket internal error"); - } - } catch (closeError) { - log.error("Error while closing WebSocket after error:", closeError); - } - }); - }); + lobbyService = new MasterLobbyService(config, playlist, log); // Generate admin token for worker authentication const ADMIN_TOKEN = crypto.randomBytes(16).toString("hex"); @@ -157,44 +98,21 @@ export async function startMaster() { INSTANCE_ID, }); + lobbyService.registerWorker(i, worker); log.info(`Started worker ${i} (PID: ${worker.process.pid})`); } - cluster.on("message", (worker, message) => { - if (message.type === "WORKER_READY") { - const workerId = message.workerId; - readyWorkers.add(workerId); - log.info( - `Worker ${workerId} is ready. (${readyWorkers.size}/${config.numWorkers()} ready)`, - ); - // Start scheduling when all workers are ready - if (readyWorkers.size === config.numWorkers()) { - log.info("All workers ready, starting game scheduling"); - - const scheduleLobbies = () => { - schedulePublicGame(playlist).catch((error) => { - log.error("Error scheduling public game:", error); - }); - }; - - startPolling(async () => { - const lobbies = await fetchLobbies(); - if (lobbies === 0) { - scheduleLobbies(); - } - }, 100); - } - } - }); - // Handle worker crashes cluster.on("exit", (worker, code, signal) => { const workerId = (worker as any).process?.env?.WORKER_ID; - if (!workerId) { + if (workerId === undefined) { log.error(`worker crashed could not find id`); return; } + const workerIdNum = parseInt(workerId); + lobbyService.removeWorker(workerIdNum); + log.warn( `Worker ${workerId} (PID: ${worker.process.pid}) died with code: ${code} and signal: ${signal}`, ); @@ -207,6 +125,7 @@ export async function startMaster() { INSTANCE_ID, }); + lobbyService.registerWorker(workerIdNum, newWorker); log.info( `Restarted worker ${workerId} (New PID: ${newWorker.process.pid})`, ); @@ -226,115 +145,6 @@ app.get("/api/env", async (req, res) => { res.json(envConfig); }); -// Add lobbies endpoint to list public games for this worker -app.get("/api/public_lobbies", async (req, res) => { - res.json(publicLobbiesData); -}); - -async function fetchLobbies(): Promise { - const fetchPromises: Promise[] = []; - - for (const gameID of new Set(publicLobbyIDs)) { - const controller = new AbortController(); - setTimeout(() => controller.abort(), 5000); // 5 second timeout - const port = config.workerPort(gameID); - const promise = fetch(`http://localhost:${port}/api/game/${gameID}`, { - headers: { [config.adminHeader()]: config.adminToken() }, - signal: controller.signal, - }) - .then((resp) => resp.json()) - .then((json) => { - return json as GameInfo; - }) - .catch((error) => { - log.error(`Error fetching game ${gameID}:`, error); - // Return null or a placeholder if fetch fails - publicLobbyIDs.delete(gameID); - return null; - }); - - fetchPromises.push(promise); - } - - // Wait for all promises to resolve - const results = await Promise.all(fetchPromises); - - // Filter out any null results from failed fetches - const lobbyInfos: GameInfo[] = results - .filter((result) => result !== null) - .map((gi: GameInfo) => { - return { - gameID: gi.gameID, - numClients: gi?.clients?.length ?? 0, - gameConfig: gi.gameConfig, - msUntilStart: gi.msUntilStart, - } as GameInfo; - }); - - lobbyInfos.forEach((l) => { - if ( - "msUntilStart" in l && - l.msUntilStart !== undefined && - l.msUntilStart <= 250 - ) { - publicLobbyIDs.delete(l.gameID); - return; - } - - if ( - "gameConfig" in l && - l.gameConfig !== undefined && - "maxPlayers" in l.gameConfig && - l.gameConfig.maxPlayers !== undefined && - "numClients" in l && - l.numClients !== undefined && - l.gameConfig.maxPlayers <= l.numClients - ) { - publicLobbyIDs.delete(l.gameID); - return; - } - }); - - // Update the lobbies data - publicLobbiesData = { - lobbies: lobbyInfos, - }; - - broadcastLobbies(); - - return publicLobbyIDs.size; -} - -// Function to schedule a new public game -async function schedulePublicGame(playlist: MapPlaylist) { - const gameID = generateID(); - publicLobbyIDs.add(gameID); - - const workerPath = config.workerPath(gameID); - - // Send request to the worker to start the game - try { - const response = await fetch( - `http://localhost:${config.workerPort(gameID)}/api/create_game/${gameID}`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - [config.adminHeader()]: config.adminToken(), - }, - body: JSON.stringify(await playlist.gameConfig()), - }, - ); - - if (!response.ok) { - throw new Error(`Failed to schedule public game: ${response.statusText}`); - } - } catch (error) { - log.error(`Failed to schedule public game on worker ${workerPath}:`, error); - throw error; - } -} - // SPA fallback route app.get("*", async function (_req, res) { try { diff --git a/src/server/MasterLobbyService.ts b/src/server/MasterLobbyService.ts new file mode 100644 index 000000000..c82684cd9 --- /dev/null +++ b/src/server/MasterLobbyService.ts @@ -0,0 +1,135 @@ +import { Worker } from "cluster"; +import winston from "winston"; +import { ServerConfig } from "../core/configuration/Config"; +import { PublicGameInfo } from "../core/Schemas"; +import { generateID } from "../core/Util"; +import { + MasterCreateGame, + MasterLobbiesBroadcast, + WorkerMessageSchema, +} from "./IPCBridgeSchema"; +import { logger } from "./Logger"; +import { MapPlaylist } from "./MapPlaylist"; +import { startPolling } from "./PollingLoop"; + +export interface MasterLobbyServiceOptions { + config: ServerConfig; + playlist: MapPlaylist; + log: typeof logger; +} + +export class MasterLobbyService { + private readonly workers = new Map(); + // Worker id => the lobbies it owns. + private readonly workerLobbies = new Map(); + private readonly readyWorkers = new Set(); + private started = false; + + constructor( + private config: ServerConfig, + private playlist: MapPlaylist, + private log: winston.Logger, + ) {} + + registerWorker(workerId: number, worker: Worker) { + this.workers.set(workerId, worker); + + worker.on("message", (raw: unknown) => { + const result = WorkerMessageSchema.safeParse(raw); + if (!result.success) { + this.log.error("Invalid IPC message from worker:", raw); + return; + } + + const msg = result.data; + switch (msg.type) { + case "workerReady": + this.handleWorkerReady(msg.workerId); + break; + case "lobbyList": + this.workerLobbies.set(workerId, msg.lobbies); + break; + } + }); + } + + removeWorker(workerId: number) { + this.workers.delete(workerId); + this.workerLobbies.delete(workerId); + this.readyWorkers.delete(workerId); + } + + private handleWorkerReady(workerId: number) { + this.readyWorkers.add(workerId); + this.log.info( + `Worker ${workerId} is ready. (${this.readyWorkers.size}/${this.config.numWorkers()} ready)`, + ); + if (this.readyWorkers.size === this.config.numWorkers() && !this.started) { + this.started = true; + this.log.info("All workers ready, starting game scheduling"); + startPolling(async () => this.broadcastLobbies(), 250); + startPolling(async () => await this.maybeScheduleLobby(), 1000); + } + } + + private getAllLobbies(): PublicGameInfo[] { + const lobbies = Array.from(this.workerLobbies.values()) + .flat() + .sort((a, b) => a.startsAt! - b.startsAt); + return lobbies; + } + + private broadcastLobbies() { + const msg = { + type: "lobbiesBroadcast", + publicGames: { + serverTime: Date.now(), + games: this.getAllLobbies(), + }, + } satisfies MasterLobbiesBroadcast; + for (const worker of this.workers.values()) { + worker.send(msg, (e) => { + if (e) { + this.log.error("Failed to send lobbies broadcast to worker:", e); + } + }); + } + } + + private async maybeScheduleLobby() { + const lobbies = this.getAllLobbies(); + if (lobbies.length >= 2) { + return; + } + + const lastStart = lobbies.reduce( + (max, pb) => Math.max(max, pb.startsAt), + Date.now(), + ); + + const gameID = generateID(); + const workerId = this.config.workerIndex(gameID); + + const gameConfig = await this.playlist.gameConfig(); + const worker = this.workers.get(workerId); + if (!worker) { + this.log.error(`Worker ${workerId} not found`); + return; + } + + worker.send( + { + type: "createGame", + gameID, + gameConfig, + startsAt: lastStart + this.config.gameCreationRate(), + } satisfies MasterCreateGame, + (e) => { + if (e) { + this.log.error("Failed to schedule lobby on worker:", e); + } + }, + ); + this.log.info(`Scheduled public game ${gameID} on worker ${workerId}`); + } +} diff --git a/src/server/Worker.ts b/src/server/Worker.ts index e9f340c27..2d8d8dcea 100644 --- a/src/server/Worker.ts +++ b/src/server/Worker.ts @@ -30,6 +30,7 @@ import { MapPlaylist } from "./MapPlaylist"; import { startPolling } from "./PollingLoop"; import { PrivilegeRefresher } from "./PrivilegeRefresher"; import { verifyTurnstileToken } from "./Turnstile"; +import { WorkerLobbyService } from "./WorkerLobbyService"; import { initWorkerMetrics } from "./WorkerMetrics"; const config = getServerConfigFromServer(); @@ -42,6 +43,18 @@ const playlist = new MapPlaylist(true); export async function startWorker() { log.info(`Worker starting...`); + const __filename = fileURLToPath(import.meta.url); + const __dirname = path.dirname(__filename); + + const app = express(); + const server = http.createServer(app); + const wss = new WebSocketServer({ noServer: true }); + + const gm = new GameManager(config, log); + + // Initialize lobby service (handles WebSocket upgrade routing) + const lobbyService = new WorkerLobbyService(server, wss, gm, log); + setTimeout( () => { startMatchmakingPolling(gm); @@ -49,15 +62,6 @@ export async function startWorker() { 1000 + Math.random() * 2000, ); - const __filename = fileURLToPath(import.meta.url); - const __dirname = path.dirname(__filename); - - const app = express(); - const server = http.createServer(app); - const wss = new WebSocketServer({ server }); - - const gm = new GameManager(config, log); - if (config.otelEnabled()) { initWorkerMetrics(gm); } @@ -459,13 +463,8 @@ export async function startWorker() { log.info(`running on http://localhost:${PORT}`); log.info(`Handling requests with path prefix /w${workerId}/`); // Signal to the master process that this worker is ready - if (process.send) { - process.send({ - type: "WORKER_READY", - workerId: workerId, - }); - log.info(`signaled ready state to master`); - } + lobbyService.sendReady(workerId); + log.info(`signaled ready state to master`); }); // Global error handler diff --git a/src/server/WorkerLobbyService.ts b/src/server/WorkerLobbyService.ts new file mode 100644 index 000000000..d06609294 --- /dev/null +++ b/src/server/WorkerLobbyService.ts @@ -0,0 +1,136 @@ +import http from "http"; +import { WebSocket, WebSocketServer } from "ws"; +import { PublicGameInfo, PublicGames } from "../core/Schemas"; +import { GameManager } from "./GameManager"; +import { + MasterMessageSchema, + WorkerLobbyList, + WorkerReady, +} from "./IPCBridgeSchema"; +import { logger } from "./Logger"; + +export class WorkerLobbyService { + private readonly lobbiesWss: WebSocketServer; + private readonly lobbyClients: Set = new Set(); + + constructor( + private readonly server: http.Server, + private readonly gameWss: WebSocketServer, + private readonly gm: GameManager, + private readonly log: typeof logger, + ) { + this.lobbiesWss = new WebSocketServer({ noServer: true }); + this.setupUpgradeHandler(); + this.setupLobbiesWebSocket(); + this.setupIPCListener(); + } + + private setupIPCListener() { + process.on("message", (raw: unknown) => { + const result = MasterMessageSchema.safeParse(raw); + if (!result.success) { + this.log.error("Invalid IPC message from master:", raw); + return; + } + + const msg = result.data; + switch (msg.type) { + case "lobbiesBroadcast": + // Forward message to all clients + this.broadcastLobbiesToClients(msg.publicGames); + // Update master with my lobby info + this.sendMyLobbiesToMaster(); + break; + case "createGame": + if (this.gm.game(msg.gameID) !== null) { + this.log.warn(`Game ${msg.gameID} already exists, skipping create`); + return; + } + this.log.info(`Creating public game ${msg.gameID} from master`); + this.gm.createGame( + msg.gameID, + msg.gameConfig, + undefined, + msg.startsAt, + ); + break; + } + }); + } + + sendReady(workerId: number) { + const msg: WorkerReady = { type: "workerReady", workerId }; + process.send?.(msg); + } + + private sendMyLobbiesToMaster() { + const lobbies = this.gm + .publicLobbies() + .map((g) => g.gameInfo()) + .map((gi) => { + return { + gameID: gi.gameID, + numClients: gi.clients?.length ?? 0, + startsAt: gi.startsAt!, + gameConfig: gi.gameConfig, + } satisfies PublicGameInfo; + }); + process.send?.({ type: "lobbyList", lobbies } satisfies WorkerLobbyList); + } + + private setupUpgradeHandler() { + this.server.on("upgrade", (request, socket, head) => { + const pathname = request.url ?? ""; + if (pathname === "/lobbies" || pathname.endsWith("/lobbies")) { + this.lobbiesWss.handleUpgrade(request, socket, head, (ws) => { + this.lobbiesWss.emit("connection", ws, request); + }); + } else { + this.gameWss.handleUpgrade(request, socket, head, (ws) => { + this.gameWss.emit("connection", ws, request); + }); + } + }); + } + + private setupLobbiesWebSocket() { + this.lobbiesWss.on("connection", (ws: WebSocket) => { + this.lobbyClients.add(ws); + ws.on("close", () => { + this.lobbyClients.delete(ws); + }); + + ws.on("error", (error) => { + this.log.error(`Lobbies WebSocket error:`, error); + this.lobbyClients.delete(ws); + try { + if ( + ws.readyState === WebSocket.OPEN || + ws.readyState === WebSocket.CONNECTING + ) { + ws.close(1011, "WebSocket internal error"); + } + } catch (closeError) { + this.log.error("Error closing lobbies WebSocket:", closeError); + } + }); + }); + } + + private broadcastLobbiesToClients(publicGames: PublicGames) { + const message = JSON.stringify(publicGames); + + const clientsToRemove: WebSocket[] = []; + this.lobbyClients.forEach((client) => { + if (client.readyState === WebSocket.OPEN) { + client.send(message); + } else { + clientsToRemove.push(client); + } + }); + + clientsToRemove.forEach((client) => { + this.lobbyClients.delete(client); + }); + } +} diff --git a/tests/AttackStats.test.ts b/tests/AttackStats.test.ts index 10cb236c3..23e0ef98f 100644 --- a/tests/AttackStats.test.ts +++ b/tests/AttackStats.test.ts @@ -34,11 +34,36 @@ describe("AttackStats", () => { test("should increase war gold stat when a player is eliminated", () => { expect(player1.sharesBorderWith(player2)).toBeTruthy(); + // Player2 must attack to be considered active (otherwise gold won't transfer) + game.addExecution( + new AttackExecution(1, player2, game.terraNullius().id()), + ); + game.executeNextTick(); performAttack(game, player1, player2); expectWarGoldStatIsIncreasedAfterKill(game, player1, player2); }); + test("should NOT increase war gold stat when a inactive player is eliminated", () => { + expect(player1.sharesBorderWith(player2)).toBeTruthy(); + + const attackerStatsBefore = game.stats().stats()[player1.clientID()!]; + const warGoldBefore = attackerStatsBefore?.gold?.[GOLD_INDEX_WAR] ?? 0n; + + performAttack(game, player1, player2); + + const attackerStatsAfter = game.stats().stats()[player1.clientID()!]; + const warGoldAfter = attackerStatsAfter?.gold?.[GOLD_INDEX_WAR] ?? 0n; + + expect(warGoldAfter).toBe(warGoldBefore); + }); + test("should increase war gold stat when elimination occurs via territory annexation", () => { + // Player2 must attack to be considered active (otherwise gold won't transfer) + game.addExecution( + new AttackExecution(1, player2, game.terraNullius().id()), + ); + game.executeNextTick(); + // Mark every tile on the map as owned by player1 for (let x = 0; x < game.map().width(); x++) { for (let y = 0; y < game.map().height(); y++) { diff --git a/tests/client/LobbySocket.test.ts b/tests/client/LobbySocket.test.ts deleted file mode 100644 index 3e6e8d901..000000000 --- a/tests/client/LobbySocket.test.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { PublicLobbySocket } from "../../src/client/LobbySocket"; - -class MockWebSocket extends EventTarget { - static instances: MockWebSocket[] = []; - static readonly OPEN = 1; - static readonly CLOSED = 3; - - readonly url: string; - readyState = MockWebSocket.OPEN; - - constructor(url: string) { - super(); - this.url = url; - MockWebSocket.instances.push(this); - } - - addEventListener( - type: string, - listener: EventListenerOrEventListenerObject, - options?: boolean | AddEventListenerOptions, - ): void { - super.addEventListener(type, listener, options); - } - - close(code?: number, reason?: string) { - this.readyState = MockWebSocket.CLOSED; - this.dispatchEvent(new CloseEvent("close", { code, reason })); - } - - send(_data: unknown) {} -} - -describe("PublicLobbySocket", () => { - const originalWebSocket = globalThis.WebSocket; - const originalFetch = globalThis.fetch; - - beforeEach(() => { - MockWebSocket.instances = []; - // @ts-expect-error assign test mock - globalThis.WebSocket = MockWebSocket; - }); - - afterEach(() => { - globalThis.WebSocket = originalWebSocket; - globalThis.fetch = originalFetch; - vi.useRealTimers(); - }); - - it("delivers lobby updates from websocket messages", () => { - const updates: unknown[][] = []; - const socket = new PublicLobbySocket((lobbies) => updates.push(lobbies)); - - socket.start(); - const ws = MockWebSocket.instances.at(-1); - expect(ws?.url).toContain("/lobbies"); - - ws?.dispatchEvent( - new MessageEvent("message", { - data: JSON.stringify({ - type: "lobbies_update", - data: { - lobbies: [ - { - gameID: "g1", - numClients: 1, - gameConfig: { - maxPlayers: 2, - gameMode: 0, - gameMap: "Earth", - }, - }, - ], - }, - }), - }), - ); - - expect(updates).toHaveLength(1); - expect((updates[0][0] as { gameID: string }).gameID).toBe("g1"); - - socket.stop(); - }); - - it("falls back to HTTP polling after max websocket attempts", async () => { - vi.useFakeTimers(); - - const fetchMock = vi.fn().mockResolvedValue({ - ok: true, - json: async () => ({ lobbies: [] }), - }); - - globalThis.fetch = fetchMock as unknown as typeof fetch; - - const socket = new PublicLobbySocket(() => {}, { - maxWsAttempts: 1, - reconnectDelay: 0, - pollIntervalMs: 50, - }); - - socket.start(); - const ws = MockWebSocket.instances.at(-1); - ws?.dispatchEvent(new CloseEvent("close")); - - await Promise.resolve(); - expect(fetchMock).toHaveBeenCalledTimes(1); - - vi.advanceTimersByTime(60); - await Promise.resolve(); - expect(fetchMock).toHaveBeenCalledTimes(2); - - socket.stop(); - }); -}); diff --git a/tests/core/game/Cluster.test.ts b/tests/core/game/Cluster.test.ts index 138e3968e..f64d73d09 100644 --- a/tests/core/game/Cluster.test.ts +++ b/tests/core/game/Cluster.test.ts @@ -1,9 +1,13 @@ import { vi, type Mocked } from "vitest"; +import { UnitType } from "../../../src/core/game/Game"; import { Cluster, TrainStation } from "../../../src/core/game/TrainStation"; const createMockStation = (id: string): Mocked => { return { id, + unit: { + type: vi.fn(() => UnitType.City), + } as any, setCluster: vi.fn(), getCluster: vi.fn(() => null), } as any; diff --git a/tests/core/game/RailNetwork.test.ts b/tests/core/game/RailNetwork.test.ts index fc39db81d..70be4febd 100644 --- a/tests/core/game/RailNetwork.test.ts +++ b/tests/core/game/RailNetwork.test.ts @@ -1,4 +1,4 @@ -import { Unit } from "../../../src/core/game/Game"; +import { Unit, UnitType } from "../../../src/core/game/Game"; import { RailNetworkImpl, StationManagerImpl, @@ -14,6 +14,7 @@ const createMockStation = (unitId: number): any => { unit: { id: unitId, setTrainStation: vi.fn(), + type: vi.fn(() => UnitType.City), }, tile: vi.fn(), neighbors: vi.fn(() => []),