diff --git a/.madgerc b/.madgerc
new file mode 100644
index 0000000..4a7c725
--- /dev/null
+++ b/.madgerc
@@ -0,0 +1,7 @@
+{
+ "detectiveOptions": {
+ "ts": {
+ "skipTypeImports": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/CONFIG.md b/CONFIG.md
index 4af1da9..b2d351d 100644
--- a/CONFIG.md
+++ b/CONFIG.md
@@ -3,7 +3,7 @@
[<-- Click to return to README.md](./README.md)
We recommend that you store the configuration file `config.json` in a safe place where Galvanic Corrosion can access it (the current directory).
-No other user on your server system should be able to access the file.
+No other user on your host system should be able to access the configuration.
## Redis
Redis is database software and must be installed for Galvanic Corrosion.
@@ -19,26 +19,30 @@ If you are unsure of what this does, leave it unchanged.
## Network
-Galvanic Corrosion listens on two ports:
-* 13370/tcp(http) - for web endpoints
-* 13371/tcp(http+ws) - for websockets
+### Some issues may appear when connecting directly to a GC server's listening address.
+Sockets behave erratically when connected directly to clients. This is a suspected issue with Deno websockets.
+For now, it is recommended that you use a middleman/proxy with your server. (see below)
+
+Galvanic Corrosion listens on two ports by default:
+* 13370/tcp (http)
+* 13371/tcp (http+ws)
Currently, HTTPS and WSS are unsupported *directly* on GC. You can use a compatible reverse proxy solution to secure your server.
[Cloudflare Tunnels](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) (requires a domain with Cloudflare) are recommended.
Port-forward or expose your server in some way. HTTPS is **strongly** recommended for your public address.
-Once your server is reachable, the nameserver (and similar functions) need to know what the public "official" address of your server is.
-For example, your server listens on 10.0.0.6:13370(+13371) but is tunneled to my-gc-server(-socket).coolguy.xyz:
+Once your server is reachable locally, the nameserver (and similar functions) need to know what the public "official" address of your server is.
+For example, your server listens on 10.0.0.6:13370 and 10.0.0.6:13371, but is tunneled to my-gc-server.coolguy.xyz and my-gc-server-socket.coolguy.xyz:
- Set the "public host" for `web` and `socket` in `config.json` to the "official" address of your server
* In the example, my-gc-server.coolguy.xyz and my-gc-server-socket.coolguy.xyz
* This includes port numbers, but not the protocol
- If your public address uses HTTPS (it should for proper authentication), enable `securepublichost`
-You can test your configuration by navigating to `https://your-server.coolguy.xyz/ns`.
+You can test your configuration by navigating to `https://my-gc-server.coolguy.xyz/ns`. (use your server host)
Each field should contain your server's public address with an optional path at the end.
-## Public Configuration
+## Public
This section contains basic information regarding your server.
`serverName`: Somewhat invisible to players, but is an official label your server could appear as (to future server lists?)
@@ -63,8 +67,12 @@ this can be anything *except* for "none" or 4, since there is only one server to
`initialRoom`: On game startup, redirects the player to this room name instead of their DormRoom. Set to null if a "natural" startup is preferred.
This room must not be private and must be matchmakeable.
+## General
+`watchdogTimeout`: Terminate the server process after this number of milliseconds when SIGINT is emitted.
+This can help when your server does not shut down gracefully.
+
## Logging
-These three values expose booleans you can change to enable/disable logging various messages used for debugging or troubleshooting purposes.
+These three booleans enable/disable logging various messages used for debugging or troubleshooting purposes.
## Discord
Can be `null`. Currently unused.
@@ -77,6 +85,8 @@ Parameters used by the server's authentication mechanisms.
`secret`: Used to generate tokens. Should never be shared (the entire file) and can be a string of characters containing no words or patterns.
Use secure cryptography APIs in programming languages to generate random strings.
+`console`: Key used to connect to the server console. Must be different than your `auth.secret`.
+
`timeout`: The maximum age for a token.
`steamkey`: When not `null`, checks the Steam authentication ticket given by the client with the Steam User Auth API. Recommended for public servers.
diff --git a/deno.json b/deno.json
index 26b7433..221af73 100644
--- a/deno.json
+++ b/deno.json
@@ -7,17 +7,17 @@
"cross-compile": "deno run prebuild && deno run compile-win-a && deno run compile-linux-a && deno run postbuild",
"dev": "deno run -A src/main.ts --dev",
"prebuild": "deno run -A ./prebuild.ts",
- "postbuild": "deno run -A ./postbuild.ts"
+ "postbuild": "deno run -A ./postbuild.ts",
+ "depcheck": "deno run -A npm:madge --circular --extensions ts ./src"
},
"imports": {
"@gz/jwt": "jsr:@gz/jwt@^0.1.0",
- "@proxnet/undead-logging": "jsr:@proxnet/undead-logging@^1.2.0",
+ "@proxnet/undead-logging": "jsr:@proxnet/undead-logging@^1.3.0",
"@types/cookie-parser": "npm:@types/cookie-parser@^1.4.8",
"@types/express": "npm:@types/express@^5.0.0",
"@types/multer": "npm:@types/multer@^1.4.12",
"@types/validator": "npm:@types/validator@^13.12.2",
"cookie-parser": "npm:cookie-parser@^1.4.7",
- "discord.js": "npm:discord.js@^14.16.3",
"express": "npm:express@^4.21.2",
"ioredis": "npm:ioredis@^5.5.0",
"multer": "npm:multer@^1.4.5-lts.2",
@@ -31,5 +31,5 @@
"./src/types/http.ts"
]
},
- "version": "0.1.0"
+ "version": "0.2.0"
}
diff --git a/deno.lock b/deno.lock
index 256dcc4..02a979d 100644
--- a/deno.lock
+++ b/deno.lock
@@ -2,128 +2,107 @@
"version": "5",
"specifiers": {
"jsr:@gz/jwt@0.1": "0.1.0",
- "jsr:@proxnet/undead-logging@^1.2.0": "1.2.0",
- "jsr:@std/bytes@^1.0.2": "1.0.4",
- "jsr:@std/crypto@^1.0.3": "1.0.3",
- "jsr:@std/uuid@*": "1.0.4",
- "npm:@imagemagick/magick-wasm@0.0.31": "0.0.31",
- "npm:@types/cookie-parser@*": "1.4.8_@types+express@5.0.0",
- "npm:@types/cookie-parser@^1.4.8": "1.4.8_@types+express@5.0.0",
- "npm:@types/express@*": "5.0.0",
- "npm:@types/express@5": "5.0.0",
+ "jsr:@proxnet/undead-logging@^1.3.0": "1.3.0",
+ "npm:@types/cookie-parser@^1.4.8": "1.4.8_@types+express@5.0.3",
+ "npm:@types/express@5": "5.0.3",
"npm:@types/multer@^1.4.12": "1.4.12",
- "npm:@types/node@*": "22.5.4",
+ "npm:@types/node@*": "22.15.15",
"npm:@types/validator@^13.12.2": "13.12.2",
- "npm:chalk@^5.3.0": "5.3.0",
+ "npm:chalk@^5.3.0": "5.4.1",
"npm:cookie-parser@^1.4.7": "1.4.7",
- "npm:discord.js@^14.16.3": "14.16.3",
"npm:express@^4.21.2": "4.21.2",
- "npm:ioredis@^5.5.0": "5.5.0",
+ "npm:ioredis@^5.5.0": "5.6.0",
+ "npm:madge@*": "8.0.0",
"npm:multer@^1.4.5-lts.2": "1.4.5-lts.2",
"npm:validator@^13.12.0": "13.12.0",
- "npm:zod@^3.24.2": "3.24.2"
+ "npm:zod@^3.24.2": "3.25.8"
},
"jsr": {
"@gz/jwt@0.1.0": {
"integrity": "32b0235cebcb85d363459b20ccaab0d8424fab89883c9f65caa1e2ad37e78e8f"
},
- "@proxnet/undead-logging@1.2.0": {
- "integrity": "59a4db428b5b848b7f51189b173b100ddabf7d86bb9de1a095e5d97b4a867e2c",
+ "@proxnet/undead-logging@1.3.0": {
+ "integrity": "313ac97bbbae9bab67d6220b18fd3cdb1e75bcdf47bf543c066c8b1a3375621a",
"dependencies": [
"npm:chalk"
]
- },
- "@std/bytes@1.0.4": {
- "integrity": "11a0debe522707c95c7b7ef89b478c13fb1583a7cfb9a85674cd2cc2e3a28abc"
- },
- "@std/crypto@1.0.3": {
- "integrity": "a2a32f51ddef632d299e3879cd027c630dcd4d1d9a5285d6e6788072f4e51e7f"
- },
- "@std/uuid@1.0.4": {
- "integrity": "f4233149cc8b4753cc3763fd83a7c4101699491f55c7be78dc7b30281946d7a0",
- "dependencies": [
- "jsr:@std/bytes",
- "jsr:@std/crypto"
- ]
}
},
"npm": {
- "@discordjs/builders@1.9.0": {
- "integrity": "sha512-0zx8DePNVvQibh5ly5kCEei5wtPBIUbSoE9n+91Rlladz4tgtFbJ36PZMxxZrTEOQ7AHMZ/b0crT/0fCy6FTKg==",
+ "@babel/helper-string-parser@7.27.1": {
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="
+ },
+ "@babel/helper-validator-identifier@7.27.1": {
+ "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="
+ },
+ "@babel/parser@7.27.2": {
+ "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==",
"dependencies": [
- "@discordjs/formatters",
- "@discordjs/util",
- "@sapphire/shapeshift",
- "discord-api-types@0.37.97",
- "fast-deep-equal",
- "ts-mixer",
- "tslib"
+ "@babel/types"
+ ],
+ "bin": true
+ },
+ "@babel/types@7.27.1": {
+ "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==",
+ "dependencies": [
+ "@babel/helper-string-parser",
+ "@babel/helper-validator-identifier"
]
},
- "@discordjs/collection@1.5.3": {
- "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ=="
- },
- "@discordjs/collection@2.1.1": {
- "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="
- },
- "@discordjs/formatters@0.5.0": {
- "integrity": "sha512-98b3i+Y19RFq1Xke4NkVY46x8KjJQjldHUuEbCqMvp1F5Iq9HgnGpu91jOi/Ufazhty32eRsKnnzS8n4c+L93g==",
+ "@dependents/detective-less@5.0.1": {
+ "integrity": "sha512-Y6+WUMsTFWE5jb20IFP4YGa5IrGY/+a/FbOSjDF/wz9gepU2hwCYSXRHP/vPwBvwcY3SVMASt4yXxbXNXigmZQ==",
"dependencies": [
- "discord-api-types@0.37.97"
+ "gonzales-pe",
+ "node-source-walk"
]
},
- "@discordjs/rest@2.4.0": {
- "integrity": "sha512-Xb2irDqNcq+O8F0/k/NaDp7+t091p+acb51iA4bCKfIn+WFWd6HrNvcsSbMMxIR9NjcMZS6NReTKygqiQN+ntw==",
- "dependencies": [
- "@discordjs/collection@2.1.1",
- "@discordjs/util",
- "@sapphire/async-queue",
- "@sapphire/snowflake",
- "@vladfrangu/async_event_emitter",
- "discord-api-types@0.37.97",
- "magic-bytes.js",
- "tslib",
- "undici"
- ]
- },
- "@discordjs/util@1.1.1": {
- "integrity": "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g=="
- },
- "@discordjs/ws@1.1.1": {
- "integrity": "sha512-PZ+vLpxGCRtmr2RMkqh8Zp+BenUaJqlS6xhgWKEZcgC/vfHLEzpHtKkB0sl3nZWpwtcKk6YWy+pU3okL2I97FA==",
- "dependencies": [
- "@discordjs/collection@2.1.1",
- "@discordjs/rest",
- "@discordjs/util",
- "@sapphire/async-queue",
- "@types/ws",
- "@vladfrangu/async_event_emitter",
- "discord-api-types@0.37.83",
- "tslib",
- "ws"
- ]
- },
- "@imagemagick/magick-wasm@0.0.31": {
- "integrity": "sha512-QNivAUxSaItuiY8ziI/vRy6TtoecD7TOsD1LGZCG3wv8lfbdGbIj2QiJk0FlGkGwAVR966NlD3mkxPNvQrvq0w=="
- },
"@ioredis/commands@1.2.0": {
"integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg=="
},
- "@sapphire/async-queue@1.5.5": {
- "integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg=="
+ "@jridgewell/sourcemap-codec@1.5.0": {
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
},
- "@sapphire/shapeshift@4.0.0": {
- "integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==",
+ "@nodelib/fs.scandir@2.1.5": {
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
"dependencies": [
- "fast-deep-equal",
- "lodash"
+ "@nodelib/fs.stat",
+ "run-parallel"
]
},
- "@sapphire/snowflake@3.5.3": {
- "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ=="
+ "@nodelib/fs.stat@2.0.5": {
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="
},
- "@types/body-parser@1.19.5": {
- "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
+ "@nodelib/fs.walk@1.2.8": {
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dependencies": [
+ "@nodelib/fs.scandir",
+ "fastq"
+ ]
+ },
+ "@ts-graphviz/adapter@2.0.6": {
+ "integrity": "sha512-kJ10lIMSWMJkLkkCG5gt927SnGZcBuG0s0HHswGzcHTgvtUe7yk5/3zTEr0bafzsodsOq5Gi6FhQeV775nC35Q==",
+ "dependencies": [
+ "@ts-graphviz/common"
+ ]
+ },
+ "@ts-graphviz/ast@2.0.7": {
+ "integrity": "sha512-e6+2qtNV99UT6DJSoLbHfkzfyqY84aIuoV8Xlb9+hZAjgpum8iVHprGeAMQ4rF6sKUAxrmY8rfF/vgAwoPc3gw==",
+ "dependencies": [
+ "@ts-graphviz/common"
+ ]
+ },
+ "@ts-graphviz/common@2.1.5": {
+ "integrity": "sha512-S6/9+T6x8j6cr/gNhp+U2olwo1n0jKj/682QVqsh7yXWV6ednHYqxFw0ZsY3LyzT0N8jaZ6jQY9YD99le3cmvg=="
+ },
+ "@ts-graphviz/core@2.0.7": {
+ "integrity": "sha512-w071DSzP94YfN6XiWhOxnLpYT3uqtxJBDYdh6Jdjzt+Ce6DNspJsPQgpC7rbts/B8tEkq0LHoYuIF/O5Jh5rPg==",
+ "dependencies": [
+ "@ts-graphviz/ast",
+ "@ts-graphviz/common"
+ ]
+ },
+ "@types/body-parser@1.19.6": {
+ "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
"dependencies": [
"@types/connect",
"@types/node"
@@ -135,14 +114,14 @@
"@types/node"
]
},
- "@types/cookie-parser@1.4.8_@types+express@5.0.0": {
+ "@types/cookie-parser@1.4.8_@types+express@5.0.3": {
"integrity": "sha512-l37JqFrOJ9yQfRQkljb41l0xVphc7kg5JTjjr+pLRZ0IyZ49V4BQ8vbF4Ut2C2e+WH4al3xD3ZwYwIUfnbT4NQ==",
"dependencies": [
"@types/express"
]
},
- "@types/express-serve-static-core@5.0.1": {
- "integrity": "sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==",
+ "@types/express-serve-static-core@5.0.6": {
+ "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==",
"dependencies": [
"@types/node",
"@types/qs",
@@ -150,17 +129,16 @@
"@types/send"
]
},
- "@types/express@5.0.0": {
- "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==",
+ "@types/express@5.0.3": {
+ "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==",
"dependencies": [
"@types/body-parser",
"@types/express-serve-static-core",
- "@types/qs",
"@types/serve-static"
]
},
- "@types/http-errors@2.0.4": {
- "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA=="
+ "@types/http-errors@2.0.5": {
+ "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="
},
"@types/mime@1.3.5": {
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="
@@ -171,27 +149,27 @@
"@types/express"
]
},
- "@types/node@22.5.4": {
- "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
+ "@types/node@22.15.15": {
+ "integrity": "sha512-R5muMcZob3/Jjchn5LcO8jdKwSCbzqmPB6ruBxMcf9kbxtniZHP327s6C37iOfuw8mbKK3cAQa7sEl7afLrQ8A==",
"dependencies": [
"undici-types"
]
},
- "@types/qs@6.9.17": {
- "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ=="
+ "@types/qs@6.14.0": {
+ "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ=="
},
"@types/range-parser@1.2.7": {
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="
},
- "@types/send@0.17.4": {
- "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
+ "@types/send@0.17.5": {
+ "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==",
"dependencies": [
"@types/mime",
"@types/node"
]
},
- "@types/serve-static@1.15.7": {
- "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
+ "@types/serve-static@1.15.8": {
+ "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==",
"dependencies": [
"@types/http-errors",
"@types/node",
@@ -201,14 +179,70 @@
"@types/validator@13.12.2": {
"integrity": "sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA=="
},
- "@types/ws@8.5.13": {
- "integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==",
+ "@typescript-eslint/types@8.32.1": {
+ "integrity": "sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg=="
+ },
+ "@typescript-eslint/typescript-estree@8.32.1_typescript@5.8.3": {
+ "integrity": "sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg==",
"dependencies": [
- "@types/node"
+ "@typescript-eslint/types",
+ "@typescript-eslint/visitor-keys",
+ "debug@4.4.1",
+ "fast-glob",
+ "is-glob",
+ "minimatch@9.0.5",
+ "semver",
+ "ts-api-utils",
+ "typescript"
]
},
- "@vladfrangu/async_event_emitter@2.4.6": {
- "integrity": "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA=="
+ "@typescript-eslint/visitor-keys@8.32.1": {
+ "integrity": "sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w==",
+ "dependencies": [
+ "@typescript-eslint/types",
+ "eslint-visitor-keys"
+ ]
+ },
+ "@vue/compiler-core@3.5.14": {
+ "integrity": "sha512-k7qMHMbKvoCXIxPhquKQVw3Twid3Kg4s7+oYURxLGRd56LiuHJVrvFKI4fm2AM3c8apqODPfVJGoh8nePbXMRA==",
+ "dependencies": [
+ "@babel/parser",
+ "@vue/shared",
+ "entities",
+ "estree-walker",
+ "source-map-js"
+ ]
+ },
+ "@vue/compiler-dom@3.5.14": {
+ "integrity": "sha512-1aOCSqxGOea5I80U2hQJvXYpPm/aXo95xL/m/mMhgyPUsKe9jhjwWpziNAw7tYRnbz1I61rd9Mld4W9KmmRoug==",
+ "dependencies": [
+ "@vue/compiler-core",
+ "@vue/shared"
+ ]
+ },
+ "@vue/compiler-sfc@3.5.14": {
+ "integrity": "sha512-9T6m/9mMr81Lj58JpzsiSIjBgv2LiVoWjIVa7kuXHICUi8LiDSIotMpPRXYJsXKqyARrzjT24NAwttrMnMaCXA==",
+ "dependencies": [
+ "@babel/parser",
+ "@vue/compiler-core",
+ "@vue/compiler-dom",
+ "@vue/compiler-ssr",
+ "@vue/shared",
+ "estree-walker",
+ "magic-string",
+ "postcss",
+ "source-map-js"
+ ]
+ },
+ "@vue/compiler-ssr@3.5.14": {
+ "integrity": "sha512-Y0G7PcBxr1yllnHuS/NxNCSPWnRGH4Ogrp0tsLA5QemDZuJLs99YjAKQ7KqkHE0vCg4QTKlQzXLKCMF7WPSl7Q==",
+ "dependencies": [
+ "@vue/compiler-dom",
+ "@vue/shared"
+ ]
+ },
+ "@vue/shared@3.5.14": {
+ "integrity": "sha512-oXTwNxVfc9EtP1zzXAlSlgARLXNC84frFYkS0HHz0h3E4WZSP9sywqjqzGCP9Y34M8ipNmd380pVgmMuwELDyQ=="
},
"accepts@1.3.8": {
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
@@ -217,12 +251,44 @@
"negotiator"
]
},
+ "ansi-regex@5.0.1": {
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+ },
+ "ansi-styles@4.3.0": {
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dependencies": [
+ "color-convert"
+ ]
+ },
+ "any-promise@1.3.0": {
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="
+ },
+ "app-module-path@2.2.0": {
+ "integrity": "sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ=="
+ },
"append-field@1.0.0": {
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw=="
},
"array-flatten@1.1.1": {
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
},
+ "ast-module-types@6.0.1": {
+ "integrity": "sha512-WHw67kLXYbZuHTmcdbIrVArCq5wxo6NEuj3hiYAWr8mwJeC+C2mMCIBIWCiDoCye/OF/xelc+teJ1ERoWmnEIA=="
+ },
+ "balanced-match@1.0.2": {
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
+ },
+ "base64-js@1.5.1": {
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
+ },
+ "bl@4.1.0": {
+ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
+ "dependencies": [
+ "buffer",
+ "inherits",
+ "readable-stream@3.6.2"
+ ]
+ },
"body-parser@1.20.3": {
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
"dependencies": [
@@ -240,9 +306,35 @@
"unpipe"
]
},
+ "brace-expansion@1.1.11": {
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dependencies": [
+ "balanced-match",
+ "concat-map"
+ ]
+ },
+ "brace-expansion@2.0.1": {
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dependencies": [
+ "balanced-match"
+ ]
+ },
+ "braces@3.0.3": {
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dependencies": [
+ "fill-range"
+ ]
+ },
"buffer-from@1.1.2": {
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
},
+ "buffer@5.7.1": {
+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+ "dependencies": [
+ "base64-js",
+ "ieee754"
+ ]
+ },
"busboy@1.6.0": {
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
"dependencies": [
@@ -252,32 +344,72 @@
"bytes@3.1.2": {
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="
},
- "call-bind-apply-helpers@1.0.1": {
- "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==",
+ "call-bind-apply-helpers@1.0.2": {
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"dependencies": [
"es-errors",
"function-bind"
]
},
- "call-bound@1.0.3": {
- "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==",
+ "call-bound@1.0.4": {
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"dependencies": [
"call-bind-apply-helpers",
"get-intrinsic"
]
},
- "chalk@5.3.0": {
- "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w=="
+ "chalk@4.1.2": {
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dependencies": [
+ "ansi-styles",
+ "supports-color"
+ ]
+ },
+ "chalk@5.4.1": {
+ "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="
+ },
+ "cli-cursor@3.1.0": {
+ "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
+ "dependencies": [
+ "restore-cursor"
+ ]
+ },
+ "cli-spinners@2.9.2": {
+ "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="
+ },
+ "clone@1.0.4": {
+ "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="
},
"cluster-key-slot@1.1.2": {
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="
},
+ "color-convert@2.0.1": {
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dependencies": [
+ "color-name"
+ ]
+ },
+ "color-name@1.1.4": {
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ },
+ "commander@12.1.0": {
+ "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="
+ },
+ "commander@7.2.0": {
+ "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="
+ },
+ "commondir@1.0.1": {
+ "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="
+ },
+ "concat-map@0.0.1": {
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
+ },
"concat-stream@1.6.2": {
"integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
"dependencies": [
"buffer-from",
"inherits",
- "readable-stream",
+ "readable-stream@2.3.8",
"typedarray"
]
},
@@ -315,45 +447,108 @@
"ms@2.0.0"
]
},
- "debug@4.4.0": {
- "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+ "debug@4.4.1": {
+ "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"dependencies": [
"ms@2.1.3"
]
},
+ "deep-extend@0.6.0": {
+ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
+ },
+ "defaults@1.0.4": {
+ "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
+ "dependencies": [
+ "clone"
+ ]
+ },
"denque@2.1.0": {
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="
},
"depd@2.0.0": {
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
},
+ "dependency-tree@11.1.1": {
+ "integrity": "sha512-pnkCd8VGOq70EVaEQxDC9mZCjCwYj4yG4j8h+PEJswuWp+rdE6p8zbtVvWk+yPwaVimOjlhNi782U9K5KOU9MQ==",
+ "dependencies": [
+ "commander@12.1.0",
+ "filing-cabinet",
+ "precinct",
+ "typescript"
+ ],
+ "bin": true
+ },
"destroy@1.2.0": {
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="
},
- "discord-api-types@0.37.100": {
- "integrity": "sha512-a8zvUI0GYYwDtScfRd/TtaNBDTXwP5DiDVX7K5OmE+DRT57gBqKnwtOC5Ol8z0mRW8KQfETIgiB8U0YZ9NXiCA=="
- },
- "discord-api-types@0.37.83": {
- "integrity": "sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA=="
- },
- "discord-api-types@0.37.97": {
- "integrity": "sha512-No1BXPcVkyVD4ZVmbNgDKaBoqgeQ+FJpzZ8wqHkfmBnTZig1FcH3iPPersiK1TUIAzgClh2IvOuVUYfcWLQAOA=="
- },
- "discord.js@14.16.3": {
- "integrity": "sha512-EPCWE9OkA9DnFFNrO7Kl1WHHDYFXu3CNVFJg63bfU7hVtjZGyhShwZtSBImINQRWxWP2tgo2XI+QhdXx28r0aA==",
+ "detective-amd@6.0.1": {
+ "integrity": "sha512-TtyZ3OhwUoEEIhTFoc1C9IyJIud3y+xYkSRjmvCt65+ycQuc3VcBrPRTMWoO/AnuCyOB8T5gky+xf7Igxtjd3g==",
"dependencies": [
- "@discordjs/builders",
- "@discordjs/collection@1.5.3",
- "@discordjs/formatters",
- "@discordjs/rest",
- "@discordjs/util",
- "@discordjs/ws",
- "@sapphire/snowflake",
- "discord-api-types@0.37.100",
- "fast-deep-equal",
- "lodash.snakecase",
- "tslib",
- "undici"
+ "ast-module-types",
+ "escodegen",
+ "get-amd-module-type",
+ "node-source-walk"
+ ],
+ "bin": true
+ },
+ "detective-cjs@6.0.1": {
+ "integrity": "sha512-tLTQsWvd2WMcmn/60T2inEJNhJoi7a//PQ7DwRKEj1yEeiQs4mrONgsUtEJKnZmrGWBBmE0kJ1vqOG/NAxwaJw==",
+ "dependencies": [
+ "ast-module-types",
+ "node-source-walk"
+ ]
+ },
+ "detective-es6@5.0.1": {
+ "integrity": "sha512-XusTPuewnSUdoxRSx8OOI6xIA/uld/wMQwYsouvFN2LAg7HgP06NF1lHRV3x6BZxyL2Kkoih4ewcq8hcbGtwew==",
+ "dependencies": [
+ "node-source-walk"
+ ]
+ },
+ "detective-postcss@7.0.1_postcss@8.5.3": {
+ "integrity": "sha512-bEOVpHU9picRZux5XnwGsmCN4+8oZo7vSW0O0/Enq/TO5R2pIAP2279NsszpJR7ocnQt4WXU0+nnh/0JuK4KHQ==",
+ "dependencies": [
+ "is-url",
+ "postcss",
+ "postcss-values-parser"
+ ]
+ },
+ "detective-sass@6.0.1": {
+ "integrity": "sha512-jSGPO8QDy7K7pztUmGC6aiHkexBQT4GIH+mBAL9ZyBmnUIOFbkfZnO8wPRRJFP/QP83irObgsZHCoDHZ173tRw==",
+ "dependencies": [
+ "gonzales-pe",
+ "node-source-walk"
+ ]
+ },
+ "detective-scss@5.0.1": {
+ "integrity": "sha512-MAyPYRgS6DCiS6n6AoSBJXLGVOydsr9huwXORUlJ37K3YLyiN0vYHpzs3AdJOgHobBfispokoqrEon9rbmKacg==",
+ "dependencies": [
+ "gonzales-pe",
+ "node-source-walk"
+ ]
+ },
+ "detective-stylus@5.0.1": {
+ "integrity": "sha512-Dgn0bUqdGbE3oZJ+WCKf8Dmu7VWLcmRJGc6RCzBgG31DLIyai9WAoEhYRgIHpt/BCRMrnXLbGWGPQuBUrnF0TA=="
+ },
+ "detective-typescript@14.0.0_typescript@5.8.3": {
+ "integrity": "sha512-pgN43/80MmWVSEi5LUuiVvO/0a9ss5V7fwVfrJ4QzAQRd3cwqU1SfWGXJFcNKUqoD5cS+uIovhw5t/0rSeC5Mw==",
+ "dependencies": [
+ "@typescript-eslint/typescript-estree",
+ "ast-module-types",
+ "node-source-walk",
+ "typescript"
+ ]
+ },
+ "detective-vue2@2.2.0_typescript@5.8.3": {
+ "integrity": "sha512-sVg/t6O2z1zna8a/UIV6xL5KUa2cMTQbdTIIvqNM0NIPswp52fe43Nwmbahzj3ww4D844u/vC2PYfiGLvD3zFA==",
+ "dependencies": [
+ "@dependents/detective-less",
+ "@vue/compiler-sfc",
+ "detective-es6",
+ "detective-sass",
+ "detective-scss",
+ "detective-stylus",
+ "detective-typescript",
+ "typescript"
]
},
"dunder-proto@1.0.1": {
@@ -373,6 +568,16 @@
"encodeurl@2.0.0": {
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="
},
+ "enhanced-resolve@5.18.1": {
+ "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==",
+ "dependencies": [
+ "graceful-fs",
+ "tapable"
+ ]
+ },
+ "entities@4.5.0": {
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="
+ },
"es-define-property@1.0.1": {
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="
},
@@ -388,6 +593,34 @@
"escape-html@1.0.3": {
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
},
+ "escodegen@2.1.0": {
+ "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
+ "dependencies": [
+ "esprima",
+ "estraverse",
+ "esutils"
+ ],
+ "optionalDependencies": [
+ "source-map"
+ ],
+ "bin": true
+ },
+ "eslint-visitor-keys@4.2.0": {
+ "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="
+ },
+ "esprima@4.0.1": {
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "bin": true
+ },
+ "estraverse@5.3.0": {
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="
+ },
+ "estree-walker@2.0.2": {
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+ },
+ "esutils@2.0.3": {
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="
+ },
"etag@1.8.1": {
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="
},
@@ -427,8 +660,44 @@
"vary"
]
},
- "fast-deep-equal@3.1.3": {
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+ "fast-glob@3.3.3": {
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dependencies": [
+ "@nodelib/fs.stat",
+ "@nodelib/fs.walk",
+ "glob-parent",
+ "merge2",
+ "micromatch"
+ ]
+ },
+ "fastq@1.19.1": {
+ "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
+ "dependencies": [
+ "reusify"
+ ]
+ },
+ "filing-cabinet@5.0.3": {
+ "integrity": "sha512-PlPcMwVWg60NQkhvfoxZs4wEHjhlOO/y7OAm4sKM60o1Z9nttRY4mcdQxp/iZ+kg/Vv6Hw1OAaTbYVM9DA9pYg==",
+ "dependencies": [
+ "app-module-path",
+ "commander@12.1.0",
+ "enhanced-resolve",
+ "module-definition",
+ "module-lookup-amd",
+ "resolve",
+ "resolve-dependency-path",
+ "sass-lookup",
+ "stylus-lookup",
+ "tsconfig-paths",
+ "typescript"
+ ],
+ "bin": true
+ },
+ "fill-range@7.1.1": {
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dependencies": [
+ "to-regex-range"
+ ]
},
"finalhandler@1.3.1": {
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
@@ -448,11 +717,21 @@
"fresh@0.5.2": {
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="
},
+ "fs.realpath@1.0.0": {
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
+ },
"function-bind@1.1.2": {
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
},
- "get-intrinsic@1.2.7": {
- "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==",
+ "get-amd-module-type@6.0.1": {
+ "integrity": "sha512-MtjsmYiCXcYDDrGqtNbeIYdAl85n+5mSv2r3FbzER/YV3ZILw4HNNIw34HuV5pyl0jzs6GFYU1VHVEefhgcNHQ==",
+ "dependencies": [
+ "ast-module-types",
+ "node-source-walk"
+ ]
+ },
+ "get-intrinsic@1.3.0": {
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"dependencies": [
"call-bind-apply-helpers",
"es-define-property",
@@ -466,6 +745,9 @@
"math-intrinsics"
]
},
+ "get-own-enumerable-property-symbols@3.0.2": {
+ "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g=="
+ },
"get-proto@1.0.1": {
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"dependencies": [
@@ -473,9 +755,40 @@
"es-object-atoms"
]
},
+ "glob-parent@5.1.2": {
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dependencies": [
+ "is-glob"
+ ]
+ },
+ "glob@7.2.3": {
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "dependencies": [
+ "fs.realpath",
+ "inflight",
+ "inherits",
+ "minimatch@3.1.2",
+ "once",
+ "path-is-absolute"
+ ],
+ "deprecated": true
+ },
+ "gonzales-pe@4.3.0": {
+ "integrity": "sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==",
+ "dependencies": [
+ "minimist"
+ ],
+ "bin": true
+ },
"gopd@1.2.0": {
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="
},
+ "graceful-fs@4.2.11": {
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
+ },
+ "has-flag@4.0.0": {
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
+ },
"has-symbols@1.1.0": {
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="
},
@@ -501,15 +814,29 @@
"safer-buffer"
]
},
+ "ieee754@1.2.1": {
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
+ },
+ "inflight@1.0.6": {
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "dependencies": [
+ "once",
+ "wrappy"
+ ],
+ "deprecated": true
+ },
"inherits@2.0.4": {
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
- "ioredis@5.5.0": {
- "integrity": "sha512-7CutT89g23FfSa8MDoIFs2GYYa0PaNiW/OrT+nRyjRXHDZd17HmIgy+reOQ/yhh72NznNjGuS8kbCAcA4Ro4mw==",
+ "ini@1.3.8": {
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
+ },
+ "ioredis@5.6.0": {
+ "integrity": "sha512-tBZlIIWbndeWBWCXWZiqtOF/yxf6yZX3tAlTJ7nfo5jhd6dctNxF7QnYlZLZ1a0o0pDoen7CgZqO+zjNaFbJAg==",
"dependencies": [
"@ioredis/commands",
"cluster-key-slot",
- "debug@4.4.0",
+ "debug@4.4.1",
"denque",
"lodash.defaults",
"lodash.isarguments",
@@ -521,23 +848,85 @@
"ipaddr.js@1.9.1": {
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
},
+ "is-core-module@2.16.1": {
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "dependencies": [
+ "hasown"
+ ]
+ },
+ "is-extglob@2.1.1": {
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="
+ },
+ "is-glob@4.0.3": {
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dependencies": [
+ "is-extglob"
+ ]
+ },
+ "is-interactive@1.0.0": {
+ "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w=="
+ },
+ "is-number@7.0.0": {
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
+ },
+ "is-obj@1.0.1": {
+ "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg=="
+ },
+ "is-regexp@1.0.0": {
+ "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA=="
+ },
+ "is-unicode-supported@0.1.0": {
+ "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="
+ },
+ "is-url-superb@4.0.0": {
+ "integrity": "sha512-GI+WjezhPPcbM+tqE9LnmsY5qqjwHzTvjJ36wxYX5ujNXefSUJ/T17r5bqDV8yLhcgB59KTPNOc9O9cmHTPWsA=="
+ },
+ "is-url@1.2.4": {
+ "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww=="
+ },
"isarray@1.0.0": {
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
},
+ "json5@2.2.3": {
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "bin": true
+ },
"lodash.defaults@4.2.0": {
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="
},
"lodash.isarguments@3.1.0": {
"integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="
},
- "lodash.snakecase@4.1.1": {
- "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="
+ "log-symbols@4.1.0": {
+ "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
+ "dependencies": [
+ "chalk@4.1.2",
+ "is-unicode-supported"
+ ]
},
- "lodash@4.17.21": {
- "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ "madge@8.0.0": {
+ "integrity": "sha512-9sSsi3TBPhmkTCIpVQF0SPiChj1L7Rq9kU2KDG1o6v2XH9cCw086MopjVCD+vuoL5v8S77DTbVopTO8OUiQpIw==",
+ "dependencies": [
+ "chalk@4.1.2",
+ "commander@7.2.0",
+ "commondir",
+ "debug@4.4.1",
+ "dependency-tree",
+ "ora",
+ "pluralize",
+ "pretty-ms",
+ "rc",
+ "stream-to-array",
+ "ts-graphviz",
+ "walkdir"
+ ],
+ "bin": true
},
- "magic-bytes.js@1.10.0": {
- "integrity": "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ=="
+ "magic-string@0.30.17": {
+ "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
+ "dependencies": [
+ "@jridgewell/sourcemap-codec"
+ ]
},
"math-intrinsics@1.1.0": {
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="
@@ -548,9 +937,19 @@
"merge-descriptors@1.0.3": {
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="
},
+ "merge2@1.4.1": {
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="
+ },
"methods@1.1.2": {
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="
},
+ "micromatch@4.0.8": {
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dependencies": [
+ "braces",
+ "picomatch"
+ ]
+ },
"mime-db@1.52.0": {
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
},
@@ -564,6 +963,21 @@
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"bin": true
},
+ "mimic-fn@2.1.0": {
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
+ },
+ "minimatch@3.1.2": {
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dependencies": [
+ "brace-expansion@1.1.11"
+ ]
+ },
+ "minimatch@9.0.5": {
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dependencies": [
+ "brace-expansion@2.0.1"
+ ]
+ },
"minimist@1.2.8": {
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="
},
@@ -574,6 +988,24 @@
],
"bin": true
},
+ "module-definition@6.0.1": {
+ "integrity": "sha512-FeVc50FTfVVQnolk/WQT8MX+2WVcDnTGiq6Wo+/+lJ2ET1bRVi3HG3YlJUfqagNMc/kUlFSoR96AJkxGpKz13g==",
+ "dependencies": [
+ "ast-module-types",
+ "node-source-walk"
+ ],
+ "bin": true
+ },
+ "module-lookup-amd@9.0.4": {
+ "integrity": "sha512-DWJEuLVvjxh5b8wrvJC5wr2a7qo7pOWXIgdCBNazU416kcIyzO4drxvlqKhsHzYwxcC4cWuhoK+MiWCKCGnv7A==",
+ "dependencies": [
+ "commander@12.1.0",
+ "glob",
+ "requirejs",
+ "requirejs-config-file"
+ ],
+ "bin": true
+ },
"ms@2.0.0": {
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
@@ -592,14 +1024,24 @@
"xtend"
]
},
+ "nanoid@3.3.11": {
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "bin": true
+ },
"negotiator@0.6.3": {
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
},
+ "node-source-walk@7.0.1": {
+ "integrity": "sha512-3VW/8JpPqPvnJvseXowjZcirPisssnBuDikk6JIZ8jQzF7KJQX52iPFX4RYYxLycYH7IbMRSPUOga/esVjy5Yg==",
+ "dependencies": [
+ "@babel/parser"
+ ]
+ },
"object-assign@4.1.1": {
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
},
- "object-inspect@1.13.3": {
- "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA=="
+ "object-inspect@1.13.4": {
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="
},
"on-finished@2.4.1": {
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
@@ -607,12 +1049,100 @@
"ee-first"
]
},
+ "once@1.4.0": {
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dependencies": [
+ "wrappy"
+ ]
+ },
+ "onetime@5.1.2": {
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dependencies": [
+ "mimic-fn"
+ ]
+ },
+ "ora@5.4.1": {
+ "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==",
+ "dependencies": [
+ "bl",
+ "chalk@4.1.2",
+ "cli-cursor",
+ "cli-spinners",
+ "is-interactive",
+ "is-unicode-supported",
+ "log-symbols",
+ "strip-ansi",
+ "wcwidth"
+ ]
+ },
+ "parse-ms@2.1.0": {
+ "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA=="
+ },
"parseurl@1.3.3": {
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
},
+ "path-is-absolute@1.0.1": {
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="
+ },
+ "path-parse@1.0.7": {
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
+ },
"path-to-regexp@0.1.12": {
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="
},
+ "picocolors@1.1.1": {
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
+ },
+ "picomatch@2.3.1": {
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
+ },
+ "pluralize@8.0.0": {
+ "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA=="
+ },
+ "postcss-values-parser@6.0.2_postcss@8.5.3": {
+ "integrity": "sha512-YLJpK0N1brcNJrs9WatuJFtHaV9q5aAOj+S4DI5S7jgHlRfm0PIbDCAFRYMQD5SHq7Fy6xsDhyutgS0QOAs0qw==",
+ "dependencies": [
+ "color-name",
+ "is-url-superb",
+ "postcss",
+ "quote-unquote"
+ ]
+ },
+ "postcss@8.5.3": {
+ "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
+ "dependencies": [
+ "nanoid",
+ "picocolors",
+ "source-map-js"
+ ]
+ },
+ "precinct@12.2.0_postcss@8.5.3_typescript@5.8.3": {
+ "integrity": "sha512-NFBMuwIfaJ4SocE9YXPU/n4AcNSoFMVFjP72nvl3cx69j/ke61/hPOWFREVxLkFhhEGnA8ZuVfTqJBa+PK3b5w==",
+ "dependencies": [
+ "@dependents/detective-less",
+ "commander@12.1.0",
+ "detective-amd",
+ "detective-cjs",
+ "detective-es6",
+ "detective-postcss",
+ "detective-sass",
+ "detective-scss",
+ "detective-stylus",
+ "detective-typescript",
+ "detective-vue2",
+ "module-definition",
+ "node-source-walk",
+ "postcss",
+ "typescript"
+ ],
+ "bin": true
+ },
+ "pretty-ms@7.0.1": {
+ "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==",
+ "dependencies": [
+ "parse-ms"
+ ]
+ },
"process-nextick-args@2.0.1": {
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
@@ -629,6 +1159,12 @@
"side-channel"
]
},
+ "queue-microtask@1.2.3": {
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="
+ },
+ "quote-unquote@1.0.0": {
+ "integrity": "sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg=="
+ },
"range-parser@1.2.1": {
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
},
@@ -641,6 +1177,16 @@
"unpipe"
]
},
+ "rc@1.2.8": {
+ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+ "dependencies": [
+ "deep-extend",
+ "ini",
+ "minimist",
+ "strip-json-comments"
+ ],
+ "bin": true
+ },
"readable-stream@2.3.8": {
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"dependencies": [
@@ -653,6 +1199,14 @@
"util-deprecate"
]
},
+ "readable-stream@3.6.2": {
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "dependencies": [
+ "inherits",
+ "string_decoder",
+ "util-deprecate"
+ ]
+ },
"redis-errors@1.2.0": {
"integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w=="
},
@@ -662,6 +1216,45 @@
"redis-errors"
]
},
+ "requirejs-config-file@4.0.0": {
+ "integrity": "sha512-jnIre8cbWOyvr8a5F2KuqBnY+SDA4NXr/hzEZJG79Mxm2WiFQz2dzhC8ibtPJS7zkmBEl1mxSwp5HhC1W4qpxw==",
+ "dependencies": [
+ "esprima",
+ "stringify-object"
+ ]
+ },
+ "requirejs@2.3.7": {
+ "integrity": "sha512-DouTG8T1WanGok6Qjg2SXuCMzszOo0eHeH9hDZ5Y4x8Je+9JB38HdTLT4/VA8OaUhBa0JPVHJ0pyBkM1z+pDsw==",
+ "bin": true
+ },
+ "resolve-dependency-path@4.0.1": {
+ "integrity": "sha512-YQftIIC4vzO9UMhO/sCgXukNyiwVRCVaxiWskCBy7Zpqkplm8kTAISZ8O1MoKW1ca6xzgLUBjZTcDgypXvXxiQ=="
+ },
+ "resolve@1.22.10": {
+ "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
+ "dependencies": [
+ "is-core-module",
+ "path-parse",
+ "supports-preserve-symlinks-flag"
+ ],
+ "bin": true
+ },
+ "restore-cursor@3.1.0": {
+ "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
+ "dependencies": [
+ "onetime",
+ "signal-exit"
+ ]
+ },
+ "reusify@1.1.0": {
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="
+ },
+ "run-parallel@1.2.0": {
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dependencies": [
+ "queue-microtask"
+ ]
+ },
"safe-buffer@5.1.2": {
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
@@ -671,6 +1264,18 @@
"safer-buffer@2.1.2": {
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
+ "sass-lookup@6.1.0": {
+ "integrity": "sha512-Zx+lVyoWqXZxHuYWlTA17Z5sczJ6braNT2C7rmClw+c4E7r/n911Zwss3h1uHI9reR5AgHZyNHF7c2+VIp5AUA==",
+ "dependencies": [
+ "commander@12.1.0",
+ "enhanced-resolve"
+ ],
+ "bin": true
+ },
+ "semver@7.7.1": {
+ "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
+ "bin": true
+ },
"send@0.19.0": {
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
"dependencies": [
@@ -737,12 +1342,27 @@
"side-channel-weakmap"
]
},
+ "signal-exit@3.0.7": {
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
+ },
+ "source-map-js@1.2.1": {
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
+ },
+ "source-map@0.6.1": {
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+ },
"standard-as-callback@2.1.0": {
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="
},
"statuses@2.0.1": {
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
},
+ "stream-to-array@2.3.0": {
+ "integrity": "sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==",
+ "dependencies": [
+ "any-promise"
+ ]
+ },
"streamsearch@1.1.0": {
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="
},
@@ -752,14 +1372,76 @@
"safe-buffer@5.1.2"
]
},
+ "stringify-object@3.3.0": {
+ "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==",
+ "dependencies": [
+ "get-own-enumerable-property-symbols",
+ "is-obj",
+ "is-regexp"
+ ]
+ },
+ "strip-ansi@6.0.1": {
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dependencies": [
+ "ansi-regex"
+ ]
+ },
+ "strip-bom@3.0.0": {
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="
+ },
+ "strip-json-comments@2.0.1": {
+ "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="
+ },
+ "stylus-lookup@6.1.0": {
+ "integrity": "sha512-5QSwgxAzXPMN+yugy61C60PhoANdItfdjSEZR8siFwz7yL9jTmV0UBKDCfn3K8GkGB4g0Y9py7vTCX8rFu4/pQ==",
+ "dependencies": [
+ "commander@12.1.0"
+ ],
+ "bin": true
+ },
+ "supports-color@7.2.0": {
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dependencies": [
+ "has-flag"
+ ]
+ },
+ "supports-preserve-symlinks-flag@1.0.0": {
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="
+ },
+ "tapable@2.2.2": {
+ "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg=="
+ },
+ "to-regex-range@5.0.1": {
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dependencies": [
+ "is-number"
+ ]
+ },
"toidentifier@1.0.1": {
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
},
- "ts-mixer@6.0.4": {
- "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="
+ "ts-api-utils@2.1.0_typescript@5.8.3": {
+ "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
+ "dependencies": [
+ "typescript"
+ ]
},
- "tslib@2.8.1": {
- "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
+ "ts-graphviz@2.1.6": {
+ "integrity": "sha512-XyLVuhBVvdJTJr2FJJV2L1pc4MwSjMhcunRVgDE9k4wbb2ee7ORYnPewxMWUav12vxyfUM686MSGsqnVRIInuw==",
+ "dependencies": [
+ "@ts-graphviz/adapter",
+ "@ts-graphviz/ast",
+ "@ts-graphviz/common",
+ "@ts-graphviz/core"
+ ]
+ },
+ "tsconfig-paths@4.2.0": {
+ "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==",
+ "dependencies": [
+ "json5",
+ "minimist",
+ "strip-bom"
+ ]
},
"type-is@1.6.18": {
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
@@ -771,11 +1453,12 @@
"typedarray@0.0.6": {
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
},
- "undici-types@6.19.8": {
- "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="
+ "typescript@5.8.3": {
+ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
+ "bin": true
},
- "undici@6.19.8": {
- "integrity": "sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g=="
+ "undici-types@6.21.0": {
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="
},
"unpipe@1.0.0": {
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="
@@ -792,120 +1475,26 @@
"vary@1.1.2": {
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="
},
- "ws@8.18.0": {
- "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="
+ "walkdir@0.4.1": {
+ "integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ=="
+ },
+ "wcwidth@1.0.1": {
+ "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==",
+ "dependencies": [
+ "defaults"
+ ]
+ },
+ "wrappy@1.0.2": {
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"xtend@4.0.2": {
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
},
- "zod@3.24.2": {
- "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="
+ "zod@3.25.8": {
+ "integrity": "sha512-iJPWX8HoZ2VE21VrhHGU9jVo/kVDUQyqM9vF0MxDhW/fp2sAl1eVwGJgiYZdHGiMwQJImXIW80lKk0MnfDxqiQ=="
}
},
- "redirects": {
- "https://deno.land/x/imagemagick_deno/mod.ts": "https://deno.land/x/imagemagick_deno@0.0.31/mod.ts"
- },
"remote": {
- "https://deno.land/std@0.140.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74",
- "https://deno.land/std@0.140.0/_util/os.ts": "3b4c6e27febd119d36a416d7a97bd3b0251b77c88942c8f16ee5953ea13e2e49",
- "https://deno.land/std@0.140.0/bytes/bytes_list.ts": "67eb118e0b7891d2f389dad4add35856f4ad5faab46318ff99653456c23b025d",
- "https://deno.land/std@0.140.0/bytes/equals.ts": "fc16dff2090cced02497f16483de123dfa91e591029f985029193dfaa9d894c9",
- "https://deno.land/std@0.140.0/bytes/mod.ts": "763f97d33051cc3f28af1a688dfe2830841192a9fea0cbaa55f927b49d49d0bf",
- "https://deno.land/std@0.140.0/fmt/colors.ts": "30455035d6d728394781c10755351742dd731e3db6771b1843f9b9e490104d37",
- "https://deno.land/std@0.140.0/fs/_util.ts": "0fb24eb4bfebc2c194fb1afdb42b9c3dda12e368f43e8f2321f84fc77d42cb0f",
- "https://deno.land/std@0.140.0/fs/ensure_dir.ts": "9dc109c27df4098b9fc12d949612ae5c9c7169507660dcf9ad90631833209d9d",
- "https://deno.land/std@0.140.0/hash/sha256.ts": "803846c7a5a8a5a97f31defeb37d72f519086c880837129934f5d6f72102a8e8",
- "https://deno.land/std@0.140.0/io/buffer.ts": "bd0c4bf53db4b4be916ca5963e454bddfd3fcd45039041ea161dbf826817822b",
- "https://deno.land/std@0.140.0/path/_constants.ts": "df1db3ffa6dd6d1252cc9617e5d72165cd2483df90e93833e13580687b6083c3",
- "https://deno.land/std@0.140.0/path/_interface.ts": "ee3b431a336b80cf445441109d089b70d87d5e248f4f90ff906820889ecf8d09",
- "https://deno.land/std@0.140.0/path/_util.ts": "c1e9686d0164e29f7d880b2158971d805b6e0efc3110d0b3e24e4b8af2190d2b",
- "https://deno.land/std@0.140.0/path/common.ts": "bee563630abd2d97f99d83c96c2fa0cca7cee103e8cb4e7699ec4d5db7bd2633",
- "https://deno.land/std@0.140.0/path/glob.ts": "cb5255638de1048973c3e69e420c77dc04f75755524cb3b2e160fe9277d939ee",
- "https://deno.land/std@0.140.0/path/mod.ts": "d3e68d0abb393fb0bf94a6d07c46ec31dc755b544b13144dee931d8d5f06a52d",
- "https://deno.land/std@0.140.0/path/posix.ts": "293cdaec3ecccec0a9cc2b534302dfe308adb6f10861fa183275d6695faace44",
- "https://deno.land/std@0.140.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9",
- "https://deno.land/std@0.140.0/path/win32.ts": "31811536855e19ba37a999cd8d1b62078235548d67902ece4aa6b814596dd757",
- "https://deno.land/std@0.140.0/streams/conversion.ts": "712585bfa0172a97fb68dd46e784ae8ad59d11b88079d6a4ab098ff42e697d21",
- "https://deno.land/std@0.186.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462",
- "https://deno.land/std@0.186.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3",
- "https://deno.land/std@0.186.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0",
- "https://deno.land/std@0.186.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b",
- "https://deno.land/std@0.186.0/path/_util.ts": "d7abb1e0dea065f427b89156e28cdeb32b045870acdf865833ba808a73b576d0",
- "https://deno.land/std@0.186.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000",
- "https://deno.land/std@0.186.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1",
- "https://deno.land/std@0.186.0/path/mod.ts": "ee161baec5ded6510ee1d1fb6a75a0f5e4b41f3f3301c92c716ecbdf7dae910d",
- "https://deno.land/std@0.186.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d",
- "https://deno.land/std@0.186.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1",
- "https://deno.land/std@0.186.0/path/win32.ts": "d186344e5583bcbf8b18af416d13d82b35a317116e6460a5a3953508c3de5bba",
- "https://deno.land/std@0.197.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3",
- "https://deno.land/std@0.197.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee",
- "https://deno.land/std@0.197.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56",
- "https://deno.land/std@0.197.0/fs/_util.ts": "fbf57dcdc9f7bc8128d60301eece608246971a7836a3bb1e78da75314f08b978",
- "https://deno.land/std@0.197.0/fs/copy.ts": "b4f7fe87190d7b310c88a2d9ff845210c0a2b7b0a094ec509747359023beb7d6",
- "https://deno.land/std@0.197.0/fs/empty_dir.ts": "c3d2da4c7352fab1cf144a1ecfef58090769e8af633678e0f3fabaef98594688",
- "https://deno.land/std@0.197.0/fs/ensure_dir.ts": "dc64c4c75c64721d4e3fb681f1382f803ff3d2868f08563ff923fdd20d071c40",
- "https://deno.land/std@0.197.0/fs/ensure_file.ts": "c38602670bfaf259d86ca824a94e6cb9e5eb73757fefa4ebf43a90dd017d53d9",
- "https://deno.land/std@0.197.0/fs/ensure_link.ts": "c0f5b2f0ec094ed52b9128eccb1ee23362a617457aa0f699b145d4883f5b2fb4",
- "https://deno.land/std@0.197.0/fs/ensure_symlink.ts": "5006ab2f458159c56d689b53b1e48d57e05eeb1eaf64e677f7f76a30bc4fdba1",
- "https://deno.land/std@0.197.0/fs/eol.ts": "f1f2eb348a750c34500741987b21d65607f352cf7205f48f4319d417fff42842",
- "https://deno.land/std@0.197.0/fs/exists.ts": "29c26bca8584a22876be7cb8844f1b6c8fc35e9af514576b78f5c6884d7ed02d",
- "https://deno.land/std@0.197.0/fs/expand_glob.ts": "3e427436f4b3768727bd7de84169f10db75fe50b32e6dde567b8ae558a8d857a",
- "https://deno.land/std@0.197.0/fs/mod.ts": "bc3d0acd488cc7b42627044caf47d72019846d459279544e1934418955ba4898",
- "https://deno.land/std@0.197.0/fs/move.ts": "b4f8f46730b40c32ea3c0bc8eb0fd0e8139249a698883c7b3756424cf19785c9",
- "https://deno.land/std@0.197.0/fs/walk.ts": "21a3cc5ff39c38acc93575213f54d5f1d44c5c6614ed97603d171eb0bf56a565",
- "https://deno.land/std@0.197.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0",
- "https://deno.land/std@0.197.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b",
- "https://deno.land/std@0.197.0/path/_util.ts": "d7abb1e0dea065f427b89156e28cdeb32b045870acdf865833ba808a73b576d0",
- "https://deno.land/std@0.197.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000",
- "https://deno.land/std@0.197.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1",
- "https://deno.land/std@0.197.0/path/mod.ts": "f065032a7189404fdac3ad1a1551a9ac84751d2f25c431e101787846c86c79ef",
- "https://deno.land/std@0.197.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d",
- "https://deno.land/std@0.197.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1",
- "https://deno.land/std@0.197.0/path/win32.ts": "4fca292f8d116fd6d62f243b8a61bd3d6835a9f0ede762ba5c01afe7c3c0aa12",
- "https://deno.land/std@0.85.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58",
- "https://deno.land/std@0.85.0/_util/os.ts": "e282950a0eaa96760c0cf11e7463e66babd15ec9157d4c9ed49cc0925686f6a7",
- "https://deno.land/std@0.85.0/fs/_util.ts": "f2ce811350236ea8c28450ed822a5f42a0892316515b1cd61321dec13569c56b",
- "https://deno.land/std@0.85.0/fs/copy.ts": "acc21e2569c92e715be48f40665a299cb995a4dce04145c3dd624791b885114c",
- "https://deno.land/std@0.85.0/fs/empty_dir.ts": "2edd70ff6405e1893e781a82aec8c574dfc748a7bb9d9ce8f0abdf002cdbba3f",
- "https://deno.land/std@0.85.0/fs/ensure_dir.ts": "f21262e788a707aaa2dd22064da7cd40e3b2f0f067e9b2aed1b288091170cc05",
- "https://deno.land/std@0.85.0/fs/ensure_file.ts": "84c7cff81ecedef3969e3fcd2d0c2aecd9bafea246cd18847deba7a54126134f",
- "https://deno.land/std@0.85.0/fs/ensure_link.ts": "e48abe5bf639389ee6f42bb8bdd8b7b2a4c93701cd618b12cdcad83ccea44f2e",
- "https://deno.land/std@0.85.0/fs/ensure_symlink.ts": "cbb2c908135808c0545c6304046b6ab5c024b0bb1832e69c819b58d9feee66ef",
- "https://deno.land/std@0.85.0/fs/eol.ts": "afaebaaac36f48c423b920c836551997715672b80a0fee9aa7667c181a94f2df",
- "https://deno.land/std@0.85.0/fs/exists.ts": "b0d2e31654819cc2a8d37df45d6b14686c0cc1d802e9ff09e902a63e98b85a00",
- "https://deno.land/std@0.85.0/fs/expand_glob.ts": "b5a8fcadf40eb7b034a1f807349cbace0ddb28c4e5a6b6aaf2d8ca925ba02f9f",
- "https://deno.land/std@0.85.0/fs/mod.ts": "26eee4b52a8c516e37d464094b080ff6822883e7f01ff0ba0a72b8dcd54b9927",
- "https://deno.land/std@0.85.0/fs/move.ts": "36697916a5cf2ebc7d298089a9a3ccc6b3af1eaecc173e57a9f5eb10f1f04221",
- "https://deno.land/std@0.85.0/fs/walk.ts": "8d37f2164a7397668842a7cb5d53b9e7bcd216462623b1b96abe519f76d7f8b9",
- "https://deno.land/std@0.85.0/path/_constants.ts": "1247fee4a79b70c89f23499691ef169b41b6ccf01887a0abd131009c5581b853",
- "https://deno.land/std@0.85.0/path/_interface.ts": "1fa73b02aaa24867e481a48492b44f2598cd9dfa513c7b34001437007d3642e4",
- "https://deno.land/std@0.85.0/path/_util.ts": "f4fa69aa3cbbd8568763bfc43c7236875015ba343602d8bafd332b4b4243681b",
- "https://deno.land/std@0.85.0/path/common.ts": "eaf03d08b569e8a87e674e4e265e099f237472b6fd135b3cbeae5827035ea14a",
- "https://deno.land/std@0.85.0/path/glob.ts": "4a524c1c9da3e79a9fdabdc6e850cd9e41bdf31e442856ffa19c5b123268ca95",
- "https://deno.land/std@0.85.0/path/mod.ts": "4465dc494f271b02569edbb4a18d727063b5dbd6ed84283ff906260970a15d12",
- "https://deno.land/std@0.85.0/path/posix.ts": "1408f8ba482a4dc5fc0a7cd7be28bbbff9608d2b3b5ffdcf288ae1228d959add",
- "https://deno.land/std@0.85.0/path/separator.ts": "8fdcf289b1b76fd726a508f57d3370ca029ae6976fcde5044007f062e643ff1c",
- "https://deno.land/std@0.85.0/path/win32.ts": "6ca052f54500f00cd7a5172fde62900626ab620dcd5bdcf4e6f5695d001ddef6",
- "https://deno.land/x/bcrypt@v0.3.0/mod.ts": "ff09bdae282583cf5f7d87efe37ddcecef7f14f6d12e8b8066a3058db8c6c2f7",
- "https://deno.land/x/bcrypt@v0.3.0/src/bcrypt/base64.ts": "b8266450a4f1eb6960f60f2f7986afc4dde6b45bd2d7ee7ba10789e67e17b9f7",
- "https://deno.land/x/bcrypt@v0.3.0/src/bcrypt/bcrypt.ts": "65819ce8e32d6e6a68f8753931237c58baa39b2573c1d7fac42f03d51499f242",
- "https://deno.land/x/bcrypt@v0.3.0/src/main.ts": "08d201b289c8d9c46f8839c69cd6625b213863db29775c7a200afc3b540e64f8",
- "https://deno.land/x/bcrypt@v0.3.0/src/worker.ts": "5a73bdfee9c9e622f47c9733d374b627dce52fb3ec1e74c8226698b3fc57ffac",
- "https://deno.land/x/deno_cache@0.4.1/auth_tokens.ts": "5fee7e9155e78cedf3f6ff3efacffdb76ac1a76c86978658d9066d4fb0f7326e",
- "https://deno.land/x/deno_cache@0.4.1/cache.ts": "51f72f4299411193d780faac8c09d4e8cbee951f541121ef75fcc0e94e64c195",
- "https://deno.land/x/deno_cache@0.4.1/deno_dir.ts": "f2a9044ce8c7fe1109004cda6be96bf98b08f478ce77e7a07f866eff1bdd933f",
- "https://deno.land/x/deno_cache@0.4.1/deps.ts": "8974097d6c17e65d9a82d39377ae8af7d94d74c25c0cbb5855d2920e063f2343",
- "https://deno.land/x/deno_cache@0.4.1/dirs.ts": "d2fa473ef490a74f2dcb5abb4b9ab92a48d2b5b6320875df2dee64851fa64aa9",
- "https://deno.land/x/deno_cache@0.4.1/disk_cache.ts": "1f3f5232cba4c56412d93bdb324c624e95d5dd179d0578d2121e3ccdf55539f9",
- "https://deno.land/x/deno_cache@0.4.1/file_fetcher.ts": "07a6c5f8fd94bf50a116278cc6012b4921c70d2251d98ce1c9f3c352135c39f7",
- "https://deno.land/x/deno_cache@0.4.1/http_cache.ts": "f632e0d6ec4a5d61ae3987737a72caf5fcdb93670d21032ddb78df41131360cd",
- "https://deno.land/x/deno_cache@0.4.1/mod.ts": "ef1cda9235a93b89cb175fe648372fc0f785add2a43aa29126567a05e3e36195",
- "https://deno.land/x/deno_cache@0.4.1/util.ts": "8cb686526f4be5205b92c819ca2ce82220aa0a8dd3613ef0913f6dc269dbbcfe",
- "https://deno.land/x/dir@1.5.1/data_local_dir/mod.ts": "91eb1c4bfadfbeda30171007bac6d85aadacd43224a5ed721bbe56bc64e9eb66",
- "https://deno.land/x/emit@0.25.0/_utils.ts": "98412edc7aa29e77d592b54fbad00bdec1b05d0c25eb772a5f8edc9813e08d88",
- "https://deno.land/x/emit@0.25.0/emit.generated.js": "0728e0cd293b930db2532f8cb5087fdb77aee1f30a059207533780f40250fd6a",
- "https://deno.land/x/emit@0.25.0/mod.ts": "66ef8ddaedcfca033eeee851379af59ed3f0e0aa6e025e7cdd24e4e158d874f3",
- "https://deno.land/x/imagemagick_deno@0.0.31/mod.ts": "124d7f045429f6e6c486b86e72d025410d09576bc0d8075e69f97118a1a33413",
"https://deno.land/x/imagescript@1.3.0/ImageScript.js": "cf90773c966031edd781ed176c598f7ed495e7694cd9b86c986d2d97f783cca0",
"https://deno.land/x/imagescript@1.3.0/mod.ts": "18a6cb83c55e690c873505f6fe867364c678afb64934fe7aef593a6b92f79995",
"https://deno.land/x/imagescript@1.3.0/png/src/crc.mjs": "5cf50de181d61dd00e66a240d811018ba5070afa8bba302f393604404604de84",
@@ -933,26 +1522,17 @@
"https://deno.land/x/imagescript@1.3.0/v2/ops/overlay.mjs": "7e6e2c2ffd25006d52597ab8babc5f8f503d388a3fdf2fbc0eaea02799a020c9",
"https://deno.land/x/imagescript@1.3.0/v2/ops/resize.mjs": "814e78ebce8eaf8f1f918688db7b52a141405e06a36ed4b25d04413d69e7d17b",
"https://deno.land/x/imagescript@1.3.0/v2/ops/rotate.mjs": "a1b65616717bd2eed8db406affea3263b4674dada46b56441ef38167a187455d",
- "https://deno.land/x/imagescript@1.3.0/v2/util/mem.mjs": "4968d400dae069b4bf0ef4767c1802fd2cc7d15d90eda4cfadf5b4cd19b96c6d",
- "https://deno.land/x/leaf@v1.0.4/constants.ts": "2b18c5be5a57cea4d3d6298d7c4c636e5db821c580c3197f9c9bcab65f8c3bf0",
- "https://deno.land/x/leaf@v1.0.4/functions/getFileInMem.ts": "cec6c3c6add22c0c3316d8301994ab583feac5c3052df3072ad12976ea2aeec4",
- "https://deno.land/x/leaf@v1.0.4/functions/getFilePath.ts": "80ce141c1bd9735d3b7961b6ec8736070475c296c342be6bb4e189483f020801",
- "https://deno.land/x/leaf@v1.0.4/functions/methods.ts": "b8beebdcc1c0fbae00cc61dc3fdca7209a5b5e08c1b955ccf0e6b04c85c6ee46",
- "https://deno.land/x/leaf@v1.0.4/leafCompiler.ts": "155ac29c04fe3a0f4d336a95058eebb1a14e291c31862356d11cd280e67563ce",
- "https://deno.land/x/leaf@v1.0.4/mod.ts": "2f7a4d2c804978c342a2092ff1d2ec05ffb0e52dfc57bdeac939d56df8254983",
- "https://deno.land/x/wasmbuild@0.14.1/cache.ts": "89eea5f3ce6035a1164b3e655c95f21300498920575ade23161421f5b01967f4",
- "https://deno.land/x/wasmbuild@0.14.1/loader.ts": "d98d195a715f823151cbc8baa3f32127337628379a02d9eb2a3c5902dbccfc02"
+ "https://deno.land/x/imagescript@1.3.0/v2/util/mem.mjs": "4968d400dae069b4bf0ef4767c1802fd2cc7d15d90eda4cfadf5b4cd19b96c6d"
},
"workspace": {
"dependencies": [
"jsr:@gz/jwt@0.1",
- "jsr:@proxnet/undead-logging@^1.2.0",
+ "jsr:@proxnet/undead-logging@^1.3.0",
"npm:@types/cookie-parser@^1.4.8",
"npm:@types/express@5",
"npm:@types/multer@^1.4.12",
"npm:@types/validator@^13.12.2",
"npm:cookie-parser@^1.4.7",
- "npm:discord.js@^14.16.3",
"npm:express@^4.21.2",
"npm:ioredis@^5.5.0",
"npm:multer@^1.4.5-lts.2",
diff --git a/postbuild.ts b/postbuild.ts
index 1851e03..2ae9ed3 100644
--- a/postbuild.ts
+++ b/postbuild.ts
@@ -16,8 +16,8 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
try {
- Deno.removeSync('./ver.ts');
- Deno.renameSync('./ver.ts.bak', 'ver.ts');
+ await Deno.remove('./ver.ts');
+ await Deno.rename('./ver.ts.bak', 'ver.ts');
} catch (err) {
console.error(`Cannot post-build version information: ${err}`);
Deno.exit(1);
diff --git a/prebuild.ts b/prebuild.ts
index 47b8dfb..f7c60d8 100644
--- a/prebuild.ts
+++ b/prebuild.ts
@@ -28,9 +28,9 @@ try {
const newVerString = `${file.version}-${new TextDecoder().decode(commitHash.stdout).trim()}`;
if (file.version) {
- Deno.writeTextFileSync('./ver.ts.bak', devVer);
- Deno.writeTextFileSync('./ver.ts', devVer.replace('development', newVerString));
- console.info('Built version information');
+ await Deno.writeTextFile('./ver.ts.bak', devVer);
+ await Deno.writeTextFile('./ver.ts', devVer.replace('development', newVerString));
+ console.info(`Built version information: Commit ${newVerString}`);
}
} catch (err) {
console.error(`Cannot build version information: ${err}`);
diff --git a/src/apiutils.ts b/src/apiutils.ts
index 52ad9d7..f108a1e 100644
--- a/src/apiutils.ts
+++ b/src/apiutils.ts
@@ -19,12 +19,14 @@ along with this program. If not, see . */
import express from "express";
import Logging from "@proxnet/undead-logging";
import { decode } from "@gz/jwt";
-import { Config } from "./config.ts";
-import { AuthType, User, UserTokenFormat } from "./data/users.ts";
-import { ProfileTokenFormat } from "./data/profiles.ts";
+import { Config } from "./config/config.ts";
+import { User } from "./data/users.ts";
+import { AuthType } from "./data/UserTypes.ts";
import z from "zod";
import Matchmaking from "./data/live/base.ts";
-import Server from "./data/server.ts";
+import Server from "./data/server/server.ts";
+import { TokenSchema } from "./data/auth/TokenSchema.ts";
+import type { TokenFormat } from "./data/auth/TokenBaseFormat.ts";
const config = Config.getConfig();
@@ -48,19 +50,6 @@ export function setCacheAllowed(_rq: express.Request, rs: express.Response, nxt:
nxt();
}
-export function generateRandomString(length: number) {
- const characters =
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
- let randomString = "";
-
- for (let i = 0; i < length; i++) {
- const randomIndex = Math.floor(Math.random() * characters.length);
- randomString += characters.charAt(randomIndex);
- }
-
- return randomString;
-}
-
export function checkQueryTypes(typeDef: T) {
return (
rq: express.Request,
@@ -251,31 +240,6 @@ export class RateLimiter {
}
}
-export interface TokenBaseFormat {
- typ: AuthType;
- iss: string;
- exp: number;
-}
-export type TokenFormat = UserTokenFormat | ProfileTokenFormat;
-
-const TokenBaseSchema = z.object({
- typ: z.nativeEnum(AuthType),
- iss: z.string().url(),
- exp: z.number()
-});
-export const UserTokenSchema = TokenBaseSchema.extend({
- sub: z.string(),
- typ: z.literal(AuthType.Web)
-});
-export const ProfileTokenSchema = TokenBaseSchema.extend({
- sub: z.number(),
- typ: z.literal(AuthType.Game)
-});
-export const TokenSchema = z.discriminatedUnion('typ', [
- UserTokenSchema,
- ProfileTokenSchema
-]);
-
export async function Authentication(
rq: express.Request,
rs: express.Response,
@@ -375,4 +339,12 @@ export function stopTimer(_rq: express.Request, rs: express.Response) {
log.n(`(${rs.locals.reqId}) Middleware took ${(performance.now() - rs.locals.timer).toString().substring(0, 6)} ms`);
}
+export function requestDebug(rq: express.Request, _rs: express.Response, nxt: express.NextFunction) {
+ log.d(`URL: ${rq.originalUrl}`);
+ log.d(`From IP: ${getSrcIpDefault(rq)}`);
+ log.d(`Headers: ${Object.keys(rq.headers).map(val => `\n ${val}: ${rq.headers[val]}`)}`);
+
+ nxt();
+}
+
export * as APIUtils from "./apiutils.ts";
diff --git a/src/config/GalvanicConfiguration.ts b/src/config/GalvanicConfiguration.ts
new file mode 100644
index 0000000..9051308
--- /dev/null
+++ b/src/config/GalvanicConfiguration.ts
@@ -0,0 +1,73 @@
+/* Galvanic Corrosion - Rec Room custom server for communities.
+
+Copyright (C) 2025 @zombieb (Discord / proxnet Gitea)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see . */
+
+import type { PhotonRegionCodeNumber, PhotonRegionCodeString } from "../data/live/PhotonTypes.ts";
+
+type RedisConfiguration = {
+ host: string;
+ port: number;
+ username: string;
+ password: string;
+ db: number;
+};
+type WebConfiguration = {
+ port: number;
+ host: string;
+ publichost: string;
+ securepublichost: boolean;
+};
+type WebRootConfiguration = {
+ api: WebConfiguration;
+ socket: WebConfiguration;
+};
+type PublicConfiguration = {
+ serverName: string;
+ serverId: string;
+ owner: string;
+ motd: string;
+ levelScale: number;
+ maxLevels: number;
+ patches: string[];
+ photonRegionId: PhotonRegionCodeNumber | PhotonRegionCodeString;
+ initialRoom: string | null;
+};
+type LoggingConfiguration = {
+ notfound: boolean;
+ debug: boolean;
+ network: boolean;
+};
+type AuthConfiguration = {
+ secret: string;
+ /**
+ * In Hours
+ */
+ timeout: number;
+ steamkey: string | null;
+};
+type GeneralConfiguration = {
+ /** In milliseconds */
+ watchdogTimeout: number;
+}
+
+export type GalvanicConfiguration = {
+ redis: RedisConfiguration;
+ web: WebRootConfiguration;
+ public: PublicConfiguration;
+ general: GeneralConfiguration;
+ logging: LoggingConfiguration;
+ auth: AuthConfiguration;
+};
\ No newline at end of file
diff --git a/src/config.ts b/src/config/config.ts
similarity index 66%
rename from src/config.ts
rename to src/config/config.ts
index c192a22..4dc99a0 100644
--- a/src/config.ts
+++ b/src/config/config.ts
@@ -17,72 +17,11 @@ along with this program. If not, see . */
import Logging from "@proxnet/undead-logging";
import * as fs from "node:fs";
-import { PhotonRegionCodeNumber, PhotonRegionCodeString } from "./data/live/types.ts";
+import { GalvanicConfiguration } from "./GalvanicConfiguration.ts";
+import { PhotonRegionCodeNumber } from "../data/live/PhotonTypes.ts";
const log = new Logging("Config");
-type RedisConfiguration = {
- host: string;
- port: number;
- username: string;
- password: string;
- db: number;
-};
-
-type WebConfiguration = {
- port: number;
- host: string;
- publichost: string;
- securepublichost: boolean;
-}
-
-type WebRootConfiguration = {
- api: WebConfiguration,
- socket: WebConfiguration
-};
-
-type PublicConfiguration = {
- serverName: string;
- serverId: string;
- owner: string;
- motd: string;
- levelScale: number;
- maxLevels: number;
- patches: string[];
- photonRegionId: PhotonRegionCodeString | PhotonRegionCodeNumber;
- initialRoom: string | null;
-};
-
-type LoggingConfiguration = {
- notfound: boolean;
- debug: boolean;
- network: boolean;
-};
-
-type DiscordConfiguration = {
- token: string;
- clientId: string;
- guildId: string;
-};
-
-type AuthConfiguration = {
- secret: string;
- /**
- * In Hours
- */
- timeout: number;
- steamkey: string | null;
-};
-
-export type GalvanicConfiguration = {
- redis: RedisConfiguration;
- web: WebRootConfiguration;
- public: PublicConfiguration;
- logging: LoggingConfiguration;
- discord: DiscordConfiguration | null;
- auth: AuthConfiguration;
-};
-
export const defaultConfig: GalvanicConfiguration = {
redis: {
host: "127.0.0.1",
@@ -116,12 +55,14 @@ export const defaultConfig: GalvanicConfiguration = {
photonRegionId: PhotonRegionCodeNumber.us,
initialRoom: null
},
+ general: {
+ watchdogTimeout: 60000
+ },
logging: {
notfound: false,
debug: false,
network: false,
},
- discord: null,
auth: {
secret: "CHANGE-ME-PLEASE",
timeout: 3,
@@ -140,12 +81,12 @@ try {
}
/** Does the configuration file exist on the disk? */
-export function configurationExists() {
+function configurationExists() {
return fs.existsSync("./config.json");
}
/** Place [or overwrite] the [existing] default configuration in the current directory */
-export function generateDefaultConfig() {
+function generateDefaultConfig() {
fs.writeFileSync(
"./config.json",
JSON.stringify(defaultConfig, undefined, " "),
diff --git a/src/data/profile/base/events.ts b/src/data/UserTypes.ts
similarity index 88%
rename from src/data/profile/base/events.ts
rename to src/data/UserTypes.ts
index 45cdee2..9fc2b3f 100644
--- a/src/data/profile/base/events.ts
+++ b/src/data/UserTypes.ts
@@ -15,8 +15,12 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
-export class ProfileEventsManager {
-
-
+export type UserInitOptions = {
+ client_id: string;
+ pubkey: string;
+};
+export enum AuthType {
+ Game,
+ Web
}
\ No newline at end of file
diff --git a/src/data/auth/TokenBaseFormat.ts b/src/data/auth/TokenBaseFormat.ts
new file mode 100644
index 0000000..309ed08
--- /dev/null
+++ b/src/data/auth/TokenBaseFormat.ts
@@ -0,0 +1,43 @@
+/* Galvanic Corrosion - Rec Room custom server for communities.
+
+Copyright (C) 2025 @zombieb (Discord / proxnet Gitea)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see . */
+
+import { AuthType } from "../UserTypes.ts";
+
+export type TokenFormat = UserTokenFormat | ProfileTokenFormat;
+
+export interface TokenBaseFormat {
+ typ: AuthType;
+ iss: string;
+ exp: number;
+}
+
+export interface TokenBaseFormat {
+ typ: AuthType;
+ iss: string;
+ exp: number;
+}
+
+export interface UserTokenFormat extends TokenBaseFormat {
+ sub: string;
+ typ: AuthType.Web;
+}
+
+export interface ProfileTokenFormat extends TokenBaseFormat {
+ sub: number;
+ role: "developer" | "user";
+ typ: AuthType.Game;
+}
\ No newline at end of file
diff --git a/src/data/auth/TokenSchema.ts b/src/data/auth/TokenSchema.ts
new file mode 100644
index 0000000..d482198
--- /dev/null
+++ b/src/data/auth/TokenSchema.ts
@@ -0,0 +1,37 @@
+/* Galvanic Corrosion - Rec Room custom server for communities.
+
+Copyright (C) 2025 @zombieb (Discord / proxnet Gitea)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see . */
+
+import z from "zod";
+import { AuthType } from "../UserTypes.ts";
+
+const TokenBaseSchema = z.object({
+ typ: z.nativeEnum(AuthType),
+ iss: z.string().url(),
+ exp: z.number()
+});
+export const UserTokenSchema = TokenBaseSchema.extend({
+ sub: z.string(),
+ typ: z.literal(AuthType.Web)
+});
+export const ProfileTokenSchema = TokenBaseSchema.extend({
+ sub: z.number(),
+ typ: z.literal(AuthType.Game)
+});
+export const TokenSchema = z.discriminatedUnion('typ', [
+ UserTokenSchema,
+ ProfileTokenSchema
+]);
\ No newline at end of file
diff --git a/src/data/baseevent.ts b/src/data/baseevent.ts
index 531ca3d..bbce790 100644
--- a/src/data/baseevent.ts
+++ b/src/data/baseevent.ts
@@ -15,51 +15,26 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
-import Logging from "@proxnet/undead-logging";
+type Callback = (event: T) => void;
-const log = new Logging("BaseEvent");
+export class EventManager {
+ #listeners: {
+ [K in keyof Events]?: Set>
+ } = {};
-export interface Event {
- time: Date
-}
-
-export class EventManager {
-
- private eventCallbacks: Map void>> = new Map();
-
- private getSubSet(event: string): Set<(ev: unknown) => void> {
- let subset = this.eventCallbacks.get(event);
- if (!subset) {
- subset = new Set();
- this.eventCallbacks.set(event, subset);
- }
- return subset;
+ on(eventName: K, callback: Callback): void {
+ if (!this.#listeners[eventName])
+ this.#listeners[eventName] = new Set();
+ this.#listeners[eventName]!.add(callback);
}
-
- on(event: string, cb: (ev: T) => void) {
- const typeSafeCallback = ((ev: unknown) => {
- cb(ev as T);
- });
-
- this.getSubSet(event).add(typeSafeCallback);
- return typeSafeCallback;
+ off(eventName: K, callback: Callback): void {
+ this.#listeners[eventName]?.delete(callback);
+ if (this.#listeners[eventName]?.size === 0)
+ delete this.#listeners[eventName];
}
-
- off(event: string, cb: (ev: unknown) => void) {
- const subset = this.getSubSet(event);
- subset.delete(cb);
- }
-
- emit(event: string, ev: T) {
- const subset = this.getSubSet(event);
- for (const cb of subset.values()) {
- try {
- cb(ev);
- } catch (err) {
- if (err instanceof Error) log.e(`Error when executing callback: ${err.stack}`);
- else log.e(`Error when executing callback: ${err}`);
- }
- }
+
+ emit(eventName: K, payload: Events[K]): void {
+ this.#listeners[eventName]?.forEach((callback) => callback(payload));
}
}
\ No newline at end of file
diff --git a/src/data/config.ts b/src/data/config.ts
deleted file mode 100644
index 4d80935..0000000
--- a/src/data/config.ts
+++ /dev/null
@@ -1,164 +0,0 @@
-/* Galvanic Corrosion - Rec Room custom server for communities.
-
-Copyright (C) 2025 @zombieb (Discord / proxnet Gitea)
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as published
-by the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see . */
-
-import { Config } from "../config.ts";
-import { Redis } from "../db.ts";
-import { Objectives, ObjectiveType } from "./objectives.ts";
-
-export type LevelProgressionItem = {
- Level: number;
- RequiredXp: number;
-};
-export type AutoMicMutingConfig = {
- MicSpamVolumeThreshold: number,
- MicVolumeSampleInterval: number,
- MicVolumeSampleRollingWindowLength: number,
- MicSpamSamplePercentageForWarning: number,
- MicSpamSamplePercentageForWarningToEnd: number,
- MicSpamSamplePercentageForForceMute: number,
- MicSpamSamplePercentageForForceMuteToEnd: number,
- MicSpamWarningStateVolumeMultiplier: number
-}
-export type PublicConfig = {
- ShareBaseUrl: string
- ServerMaintenance: {
- StartsInMinutes: number;
- };
- LevelProgressionMaps: LevelProgressionItem[];
- DailyObjectives: Objectives.Objective[][];
- AutoMicMutingConfig: AutoMicMutingConfig,
-};
-
-/**
- * Plain public config, NOT GameConfigs
- */
-export function getConfig() {
- const c = Config.getConfig();
- if (typeof c == "undefined") return null;
- const config = c as Config.GalvanicConfiguration;
-
- function generateLevelProgressionMap() {
- const m: LevelProgressionItem[] = [];
- for (let i = 0; i < config.public.maxLevels + 1; i++) {
- m.push({
- Level: i,
- RequiredXp: Math.round(i * config.public.levelScale * 20),
- });
- }
- return m;
- }
-
- const conf: PublicConfig = {
- ServerMaintenance: {
- StartsInMinutes: 0,
- },
- LevelProgressionMaps: generateLevelProgressionMap(),
- DailyObjectives: [
- [
- {type: ObjectiveType.Default, score: 0},
- {type: ObjectiveType.Default, score: 0},
- {type: ObjectiveType.Default, score: 0}
- ],
- [
- {type: ObjectiveType.Default, score: 0},
- {type: ObjectiveType.Default, score: 0},
- {type: ObjectiveType.Default, score: 0}
- ],
- [
- {type: ObjectiveType.Default, score: 0},
- {type: ObjectiveType.Default, score: 0},
- {type: ObjectiveType.Default, score: 0}
- ],
- [
- {type: ObjectiveType.Default, score: 0},
- {type: ObjectiveType.Default, score: 0},
- {type: ObjectiveType.Default, score: 0}
- ],
- [
- {type: ObjectiveType.Default, score: 0},
- {type: ObjectiveType.Default, score: 0},
- {type: ObjectiveType.Default, score: 0}
- ],
- [
- {type: ObjectiveType.Default, score: 0},
- {type: ObjectiveType.Default, score: 0},
- {type: ObjectiveType.Default, score: 0}
- ],
- [
- {type: ObjectiveType.Default, score: 0},
- {type: ObjectiveType.Default, score: 0},
- {type: ObjectiveType.Default, score: 0}
- ]
- ],
- AutoMicMutingConfig: {
- MicSpamVolumeThreshold: 1.125,
- MicVolumeSampleInterval: 0.25,
- MicVolumeSampleRollingWindowLength: 7.0,
- MicSpamSamplePercentageForWarning: 0.8,
- MicSpamSamplePercentageForWarningToEnd: 0.2,
- MicSpamSamplePercentageForForceMute: 0.8,
- MicSpamSamplePercentageForForceMuteToEnd: 0.2,
- MicSpamWarningStateVolumeMultiplier: 0.25
- },
- ShareBaseUrl: `${config.web.api.securepublichost ? 'https' : 'http'}://${config.web.api.publichost}/{0}` // {0} is replaced by the game
- };
-
- return conf;
-}
-
-export async function getAllGameConfigs() {
- try {
- const gameConfigs = new Map();
- const val = await Redis.Database.hgetall(
- Redis.buildKey(
- Redis.KeyGroups.Config.Root,
- Redis.KeyGroups.Config.Game,
- ),
- );
-
- for (const key of Object.keys(val)) {
- gameConfigs.set(key, val[key]);
- }
-
- return gameConfigs;
- } catch (error) {
- console.error("Error fetching game configs:", error);
- throw error;
- }
-}
-
-export function setGameConfig(key: string, value: string) {
- return Redis.Database.hset(
- Redis.buildKey(
- Redis.KeyGroups.Config.Root,
- Redis.KeyGroups.Config.Game,
- ),
- key,
- value,
- );
-}
-export function getGameConfig(key: string) {
- return Redis.Database.hget(
- Redis.buildKey(
- Redis.KeyGroups.Config.Root,
- Redis.KeyGroups.Config.Game,
- ),
- key,
- );
-}
-
-export * as GameConfigs from "./config.ts";
diff --git a/src/data/config/GameConfigs.ts b/src/data/config/GameConfigs.ts
new file mode 100644
index 0000000..6097621
--- /dev/null
+++ b/src/data/config/GameConfigs.ts
@@ -0,0 +1,61 @@
+/* Galvanic Corrosion - Rec Room custom server for communities.
+
+Copyright (C) 2025 @zombieb (Discord / proxnet Gitea)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see . */
+
+import { Redis } from "../../db.ts";
+
+export async function getAllGameConfigs() {
+ try {
+ const gameConfigs = new Map();
+ const val = await Redis.Database.hgetall(
+ Redis.buildKey(
+ Redis.KeyGroups.Config.Root,
+ Redis.KeyGroups.Config.Game,
+ ),
+ );
+
+ for (const key of Object.keys(val)) {
+ gameConfigs.set(key, val[key]);
+ }
+
+ return gameConfigs;
+ } catch (error) {
+ console.error("Error fetching game configs:", error);
+ throw error;
+ }
+}
+
+export function setGameConfig(key: string, value: string) {
+ return Redis.Database.hset(
+ Redis.buildKey(
+ Redis.KeyGroups.Config.Root,
+ Redis.KeyGroups.Config.Game,
+ ),
+ key,
+ value,
+ );
+}
+export function getGameConfig(key: string) {
+ return Redis.Database.hget(
+ Redis.buildKey(
+ Redis.KeyGroups.Config.Root,
+ Redis.KeyGroups.Config.Game,
+ ),
+ key,
+ );
+}
+
+export * as GameConfigs from "./GameConfigs.ts";
diff --git a/src/data/config/PublicConfig.ts b/src/data/config/PublicConfig.ts
new file mode 100644
index 0000000..05eeb29
--- /dev/null
+++ b/src/data/config/PublicConfig.ts
@@ -0,0 +1,95 @@
+/* Galvanic Corrosion - Rec Room custom server for communities.
+
+Copyright (C) 2025 @zombieb (Discord / proxnet Gitea)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see . */
+
+import { Config } from "../../config/config.ts";
+import type { GalvanicConfiguration } from "../../config/GalvanicConfiguration.ts";
+import { ObjectiveType } from "../content/ObjectiveTypes.ts";
+import { PublicConfig, LevelProgressionItem } from "./PublicConfigTypes.ts";
+
+export function getPublicConfig() {
+ const c = Config.getConfig();
+ if (typeof c == "undefined") return null;
+ const config = c as GalvanicConfiguration;
+
+ function generateLevelProgressionMap() {
+ const m: LevelProgressionItem[] = [];
+ for (let i = 0; i < config.public.maxLevels + 1; i++) {
+ m.push({
+ Level: i,
+ RequiredXp: Math.round(i * config.public.levelScale * 20),
+ });
+ }
+ return m;
+ }
+
+ const conf: PublicConfig = {
+ ServerMaintenance: {
+ StartsInMinutes: 0,
+ },
+ LevelProgressionMaps: generateLevelProgressionMap(),
+ DailyObjectives: [
+ [
+ { type: ObjectiveType.Default, score: 0 },
+ { type: ObjectiveType.Default, score: 0 },
+ { type: ObjectiveType.Default, score: 0 }
+ ],
+ [
+ { type: ObjectiveType.Default, score: 0 },
+ { type: ObjectiveType.Default, score: 0 },
+ { type: ObjectiveType.Default, score: 0 }
+ ],
+ [
+ { type: ObjectiveType.Default, score: 0 },
+ { type: ObjectiveType.Default, score: 0 },
+ { type: ObjectiveType.Default, score: 0 }
+ ],
+ [
+ { type: ObjectiveType.Default, score: 0 },
+ { type: ObjectiveType.Default, score: 0 },
+ { type: ObjectiveType.Default, score: 0 }
+ ],
+ [
+ { type: ObjectiveType.Default, score: 0 },
+ { type: ObjectiveType.Default, score: 0 },
+ { type: ObjectiveType.Default, score: 0 }
+ ],
+ [
+ { type: ObjectiveType.Default, score: 0 },
+ { type: ObjectiveType.Default, score: 0 },
+ { type: ObjectiveType.Default, score: 0 }
+ ],
+ [
+ { type: ObjectiveType.Default, score: 0 },
+ { type: ObjectiveType.Default, score: 0 },
+ { type: ObjectiveType.Default, score: 0 }
+ ]
+ ],
+ AutoMicMutingConfig: {
+ MicSpamVolumeThreshold: 1.125,
+ MicVolumeSampleInterval: 0.25,
+ MicVolumeSampleRollingWindowLength: 7.0,
+ MicSpamSamplePercentageForWarning: 0.8,
+ MicSpamSamplePercentageForWarningToEnd: 0.2,
+ MicSpamSamplePercentageForForceMute: 0.8,
+ MicSpamSamplePercentageForForceMuteToEnd: 0.2,
+ MicSpamWarningStateVolumeMultiplier: 0.25
+ },
+ ShareBaseUrl: `${config.web.api.securepublichost ? 'https' : 'http'}://${config.web.api.publichost}/{0}` // {0} is replaced by the game
+ };
+
+ return conf;
+}
\ No newline at end of file
diff --git a/src/data/config/PublicConfigTypes.ts b/src/data/config/PublicConfigTypes.ts
new file mode 100644
index 0000000..35bcc8a
--- /dev/null
+++ b/src/data/config/PublicConfigTypes.ts
@@ -0,0 +1,43 @@
+/* Galvanic Corrosion - Rec Room custom server for communities.
+
+Copyright (C) 2025 @zombieb (Discord / proxnet Gitea)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see . */
+
+import type { Objective } from "../content/ObjectiveTypes.ts";
+
+export interface LevelProgressionItem {
+ Level: number;
+ RequiredXp: number;
+};
+interface AutoMicMutingConfig {
+ MicSpamVolumeThreshold: number;
+ MicVolumeSampleInterval: number;
+ MicVolumeSampleRollingWindowLength: number;
+ MicSpamSamplePercentageForWarning: number;
+ MicSpamSamplePercentageForWarningToEnd: number;
+ MicSpamSamplePercentageForForceMute: number;
+ MicSpamSamplePercentageForForceMuteToEnd: number;
+ MicSpamWarningStateVolumeMultiplier: number;
+};
+
+export type PublicConfig = {
+ ShareBaseUrl: string;
+ ServerMaintenance: {
+ StartsInMinutes: number;
+ };
+ LevelProgressionMaps: LevelProgressionItem[];
+ DailyObjectives: Objective[][];
+ AutoMicMutingConfig: AutoMicMutingConfig;
+};
\ No newline at end of file
diff --git a/src/data/objectives.ts b/src/data/content/ObjectiveTypes.ts
similarity index 98%
rename from src/data/objectives.ts
rename to src/data/content/ObjectiveTypes.ts
index 0aea0e2..794c30d 100644
--- a/src/data/objectives.ts
+++ b/src/data/content/ObjectiveTypes.ts
@@ -108,6 +108,4 @@ export enum ObjectiveType {
export type Objective = {
type: ObjectiveType;
score: number;
-};
-
-export * as Objectives from "./objectives.ts";
+}
diff --git a/src/data/content/cdn.ts b/src/data/content/cdn.ts
index a7a7679..b930c21 100644
--- a/src/data/content/cdn.ts
+++ b/src/data/content/cdn.ts
@@ -16,10 +16,10 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
import Logging from "@proxnet/undead-logging";
-import { generateRandomString } from "../../apiutils.ts";
-import { Profile } from "../profiles.ts";
+import { generateRandomString } from "../../utils.ts";
+import type { Profile } from "../profile/base/profiles.ts";
import * as fs from "node:fs";
-import Server from "../server.ts";
+import Server from "../server/server.ts";
const log = new Logging("CDN");
@@ -68,7 +68,6 @@ export class CDNBase {
pathParts.pop();
const dirPath = pathParts.join('/');
- log.d(dirPath);
if (dirPath) await Deno.mkdir(dirPath, { recursive: true });
}
@@ -113,6 +112,7 @@ export class CDNBase {
const metaData = await Deno.readTextFile(`${path}.gcmeta`);
const parsedMeta = JSON.parse(metaData);
+
const meta: MetaFile = {
creationPlayer: Server.UnifiedProfile.get(parsedMeta.creationPlayer) || undefined,
dateCreated: new Date(parsedMeta.dateCreated),
diff --git a/src/data/server.ts b/src/data/content/rooms/RoomEvents.ts
similarity index 72%
rename from src/data/server.ts
rename to src/data/content/rooms/RoomEvents.ts
index a6d5a75..bcdf46f 100644
--- a/src/data/server.ts
+++ b/src/data/content/rooms/RoomEvents.ts
@@ -15,14 +15,13 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
-import { EventManager } from "./baseevent.ts";
-import { CDNBase } from "./content/cdn.ts";
-import { UnifiedProfileBase } from "./profiles.ts";
+import { RoomFactory } from "./RoomFactory.ts";
+import { SubroomFactory } from "./SubroomFactory.ts";
-class ServerBase extends EventManager {
- CDN = new CDNBase();
- UnifiedProfile = new UnifiedProfileBase();
+export interface SubroomUpdatedEvent {
+ subroom: SubroomFactory
}
-const Server = new ServerBase();
-export default Server;
+export interface RoomUpdatedEvent {
+ room: RoomFactory
+}
\ No newline at end of file
diff --git a/src/data/content/rooms/RoomFactory.ts b/src/data/content/rooms/RoomFactory.ts
index 99c0444..5e065e7 100644
--- a/src/data/content/rooms/RoomFactory.ts
+++ b/src/data/content/rooms/RoomFactory.ts
@@ -15,11 +15,16 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
+import Logging from "@proxnet/undead-logging";
import { Redis } from "../../../db.ts";
-import Rooms from "../rooms.ts";
-import { FactoryMode, HardwareSupport, HardwareSupportStrings, RoomAccessibility, RoomDataTypes, RoomDetails, RoomState, WriteMode } from "./DataTypes.ts";
+import Server from "../../server/server.ts";
+import { RoomDataTypes } from "./base/DataTypes.ts";
import { SubroomFactory } from "./SubroomFactory.ts";
+export const roomdebug = false;
+
+const log = new Logging("RoomFactory");
+
interface RoomFactoryOptions {
id?: number;
name?: string;
@@ -29,6 +34,14 @@ interface RoomFactoryOptions {
export class RoomFactory {
+ static async getIdFromName(name: string) {
+ const unparsedId = await Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Room_Names, name));
+ if (!unparsedId) return null;
+ const parsedId = parseInt(unparsedId);
+ if (isNaN(parsedId)) return null;
+ return parsedId;
+ }
+
static Keys = {
Meta: "roommeta",
Subrooms: "subrooms",
@@ -68,7 +81,7 @@ export class RoomFactory {
async init() {
- if (this.factoryMode !== FactoryMode.Fetch) {
+ if (this.factoryMode !== RoomDataTypes.FactoryMode.Fetch) {
if (!this.#specifiedId) throw this.#mustSpecifyIdInWriteModeError;
this.#resolvedId = this.#specifiedId;
return this;
@@ -76,7 +89,7 @@ export class RoomFactory {
if (!this.#specifiedId) {
if (!this.#specifiedName) throw this.#mustSpecifyEitherIdOrNameError;
- const id = await Rooms.getIdFromName(this.#specifiedName);
+ const id = await RoomFactory.getIdFromName(this.#specifiedName);
if (!id) return null;
this.#specifiedId = id;
}
@@ -89,6 +102,8 @@ export class RoomFactory {
RoomFactory.Keys.Meta
));
+ if (roomdebug) log.d(`Init success, specifiedId: ${this.#specifiedId}`);
+
return this;
}
@@ -119,6 +134,8 @@ export class RoomFactory {
}
if (this.Name !== 'DormRoom') await Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Room_Names, this.Name), this.RoomId);
+
+ Server.emit('room.updated', { room: this });
}
async export() {
@@ -128,7 +145,7 @@ export class RoomFactory {
const subroomPromises = subroomIds.map(id => this.getSubroom(id).init());
const subrooms = (await Promise.all(subroomPromises)).map(subroom => subroom.export());
- const details: RoomDetails = {
+ const details: RoomDataTypes.RoomDetails = {
Room: {
RoomId: this.RoomId,
Name: this.Name,
@@ -160,13 +177,13 @@ export class RoomFactory {
Tags: []
}
+ if (roomdebug) log.d(`Exported details for room ${this.RoomId}`);
return details;
}
- getSubroom(id: number, factoryMode?: FactoryMode, writeMode?: WriteMode) {
+ getSubroom(id: number, factoryMode?: RoomDataTypes.FactoryMode, writeMode?: RoomDataTypes.WriteMode) {
if (!this.#resolvedId) throw this.#unresolvedError;
return new SubroomFactory({
- roomId: this.#resolvedId,
subroomId: id,
factoryMode: factoryMode ? factoryMode : undefined,
writeMode : writeMode ? writeMode : undefined
@@ -240,11 +257,11 @@ export class RoomFactory {
set ImageName(data) { this.#setHashValue(this.#imageKey, data) }
#stateKey = 'State';
- get State(): RoomState { return this.#fetchNumberKey(this.#stateKey, RoomState.Active) }
+ get State(): RoomDataTypes.RoomState { return this.#fetchNumberKey(this.#stateKey, RoomDataTypes.RoomState.Active) }
set State(data) { this.#setHashValue(this.#stateKey, data) }
#accessKey = 'RoomAccessibility';
- get RoomAccessibility(): RoomAccessibility { return this.#fetchNumberKey(this.#accessKey, RoomAccessibility.Unlisted) }
+ get RoomAccessibility(): RoomDataTypes.RoomAccessibility { return this.#fetchNumberKey(this.#accessKey, RoomDataTypes.RoomAccessibility.Unlisted) }
set RoomAccessibility(data) { this.#setHashValue(this.#accessKey, data) }
#votingKey = 'SupportsLevelVoting';
@@ -279,20 +296,20 @@ export class RoomFactory {
get DisableMicAutoMute() { return this.#fetchBooleanKey(this.#muteKey, false) }
set DisableMicAutoMute(data) { this.#setHashValue(this.#muteKey, data) }
- async getHardwareSupport(): Promise {
+ async getHardwareSupport(): Promise {
if (!this.#resolvedId) throw this.#unresolvedError;
return (await Redis.Database.smembers(Redis.buildKey(
Redis.KeyGroups.Rooms.Root,
this.#resolvedId.toString(),
RoomFactory.Keys.HardwareSupport
- ))) as HardwareSupport[];
+ ))) as RoomDataTypes.HardwareSupport[];
}
- async addHardwareSupport(hardware: HardwareSupport | HardwareSupport[] | '*') {
+ async addHardwareSupport(hardware: RoomDataTypes.HardwareSupport | RoomDataTypes.HardwareSupport[] | '*') {
if (!this.#resolvedId) throw this.#unresolvedError;
if (hardware === '*') {
- await Promise.all(HardwareSupportStrings.map(str => this.addHardwareSupport(str as HardwareSupport) ));
+ await Promise.all(RoomDataTypes.HardwareSupportStrings.map(str => this.addHardwareSupport(str as RoomDataTypes.HardwareSupport) ));
return;
}
@@ -314,7 +331,7 @@ export class RoomFactory {
}
}
- async removeHardwareSupport(hardware: HardwareSupport) {
+ async removeHardwareSupport(hardware: RoomDataTypes.HardwareSupport) {
if (!this.#resolvedId) throw this.#unresolvedError;
await Redis.Database.srem(Redis.buildKey(
Redis.KeyGroups.Rooms.Root,
diff --git a/src/data/content/rooms.ts b/src/data/content/rooms/Rooms.ts
similarity index 66%
rename from src/data/content/rooms.ts
rename to src/data/content/rooms/Rooms.ts
index 624dd37..148daff 100644
--- a/src/data/content/rooms.ts
+++ b/src/data/content/rooms/Rooms.ts
@@ -15,21 +15,27 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
-import { Redis } from "../../db.ts";
-import { Profile } from "../profiles.ts";
+import { Redis } from "../../../db.ts";
+import { Profile } from "../../profile/base/profiles.ts";
import Logging from "@proxnet/undead-logging";
-import { BuiltinRoom, FactoryMode, IntegratedRoomScene, RoomAccessibility, RoomDataTypes, RoomDetails, RoomState, WriteMode } from "./rooms/DataTypes.ts";
-import { RoomFactory } from "./rooms/RoomFactory.ts";
-import { SubroomFactory } from "./rooms/SubroomFactory.ts";
-import { RootPath } from "../../path.ts";
-import { Instance } from "../live/instances.ts";
-import { PushNotificationId } from "../../socket/types.ts";
+import { SubroomFactory } from "./SubroomFactory.ts";
+import { RootPath } from "../../../path.ts";
+import { RoomFactory } from "./RoomFactory.ts";
+import { RoomDataTypes } from "../rooms/base/DataTypes.ts";
+import Rooms from "./base/RoomsBase.ts";
const log = new Logging("Rooms");
-const rooms = JSON.parse(Deno.readTextFileSync(`${RootPath}/res/rooms.json`)) as BuiltinRoom[];
+const builtinRooms = JSON.parse(Deno.readTextFileSync(`${RootPath}/res/rooms.json`)) as RoomDataTypes.BuiltinRoom[];
-class RoomsBase {
+const baseImageChanges = [
+ { room: "DodgeballVR", image: "Dodgeball" },
+ { room: "PaintballVR", image: "Paintball" },
+ { room: "StuntRunnerBaseRoom", image: "StuntRunner" },
+ { room: "BowlingAlley", image: "Bowling" },
+];
+
+class RoomsMiscBase {
static Keys = {
BuiltinGenerated: "builtinrooms-done",
@@ -37,33 +43,13 @@ class RoomsBase {
}
getAllBuiltinRooms() {
- return rooms;
- }
-
- async get(id: number) {
- try {
- const factory = await new RoomFactory({ id: id }).init();
- if (!factory) return null;
- return factory.export();
- } catch {
- return null;
- }
- }
-
- async getByName(name: string) {
- try {
- const factory = await new RoomFactory({ name: name }).init();
- if (!factory) return null;
- return factory.export();
- } catch {
- return null;
- }
+ return builtinRooms;
}
async getAllBuiltinRoomGenerations() {
- const ids = await Redis.Database.smembers(Redis.buildKey(Redis.KeyGroups.Rooms.Root, RoomsBase.Keys.AGRooms));
+ const ids = await Redis.Database.smembers(Redis.buildKey(Redis.KeyGroups.Rooms.Root, RoomsMiscBase.Keys.AGRooms));
const parsedIds = ids.map(val => parseInt(val)).filter(val => !isNaN(val));
- return (await Promise.all(parsedIds.map(id => this.get(id)))).filter(val => val !== null);
+ return (await Promise.all(parsedIds.map(id => Rooms.get(id)))).filter(val => val !== null);
}
async #getAvailableRoomId() {
@@ -77,9 +63,7 @@ class RoomsBase {
let id = Math.round(Math.random() * Math.pow(2, 31));
while ((await Redis.Database.exists(
Redis.buildKey(
- Redis.KeyGroups.Rooms.Root,
- roomid.toString(),
- RoomFactory.Keys.Subrooms,
+ Redis.KeyGroups.Subrooms.Root,
id.toString(),
SubroomFactory.Keys.Meta
))) >= 1)
@@ -93,20 +77,20 @@ class RoomsBase {
result: RoomDataTypes.CreateModifyRoomStatus;
}
- const factory = await new RoomFactory({ id: roomid, factoryMode: FactoryMode.Fetch }).init();
+ const factory = await new RoomFactory({ id: roomid, factoryMode: RoomDataTypes.FactoryMode.Fetch }).init();
if (!factory || !factory.CloningAllowed) return { result: RoomDataTypes.CreateModifyRoomStatus.PermissionDenied } as RoomClone;
if (factory.Name == 'DormRoom') return { result: RoomDataTypes.CreateModifyRoomStatus.ReservedName } as RoomClone;
if (factory.Name == newname) return { result: RoomDataTypes.CreateModifyRoomStatus.DuplicateName } as RoomClone;
- const newFactory = await new RoomFactory({ id: await Rooms.#getAvailableRoomId(), factoryMode: FactoryMode.Write }).init();
+ const newFactory = await new RoomFactory({ id: await RoomsMisc.#getAvailableRoomId(), factoryMode: RoomDataTypes.FactoryMode.Write }).init();
if (!newFactory) return { result: RoomDataTypes.CreateModifyRoomStatus.Unknown } as RoomClone;
newFactory.CreatorPlayerId = newowner.getId();
newFactory.Description = factory.Description;
newFactory.Name = newname;
newFactory.ImageName = factory.ImageName;
- newFactory.State = RoomState.Active;
- newFactory.RoomAccessibility = RoomAccessibility.Private;
+ newFactory.State = RoomDataTypes.RoomState.Active;
+ newFactory.RoomAccessibility = RoomDataTypes.RoomDataTypes.RoomAccessibility.Private;
newFactory.SupportsLevelVoting = factory.SupportsLevelVoting;
newFactory.IsAGRoom = false;
newFactory.IsDormRoom = factory.IsDormRoom;
@@ -122,9 +106,10 @@ class RoomsBase {
const oldSubroomIds = await factory.getAllSubroomIds();
const promises = oldSubroomIds.map(async (id) => {
- const newSubroomFactory = await newFactory.getSubroom(id, FactoryMode.Write, WriteMode.Overwrite).init();
- const oldSubroomFactory = await factory.getSubroom(id, FactoryMode.Fetch).init();
+ const newSubroomFactory = await newFactory.getSubroom(id, RoomDataTypes.RoomDataTypes.FactoryMode.Write, RoomDataTypes.RoomDataTypes.WriteMode.Overwrite).init();
+ const oldSubroomFactory = await factory.getSubroom(id, RoomDataTypes.RoomDataTypes.FactoryMode.Fetch).init();
+ newSubroomFactory.RoomId = newFactory.RoomId;
newSubroomFactory.RoomSceneLocationId = oldSubroomFactory.RoomSceneLocationId;
newSubroomFactory.Name = oldSubroomFactory.Name;
newSubroomFactory.IsSandbox = oldSubroomFactory.IsSandbox;
@@ -137,7 +122,7 @@ class RoomsBase {
await Promise.all(promises);
await newFactory.write();
- newFactory.factoryMode = FactoryMode.Fetch;
+ newFactory.factoryMode = RoomDataTypes.RoomDataTypes.FactoryMode.Fetch;
return {
factory: newFactory,
@@ -176,15 +161,15 @@ class RoomsBase {
async generateNewDorm(player: Profile) {
const id = await this.#getAvailableRoomId();
- const factory = await new RoomFactory({ id: id, factoryMode: FactoryMode.Write, writeMode: WriteMode.WriteIfFree }).init();
+ const factory = await new RoomFactory({ id: id, factoryMode: RoomDataTypes.FactoryMode.Write, writeMode: RoomDataTypes.WriteMode.WriteIfFree }).init();
if (!factory) return null;
factory.Name = "DormRoom";
factory.Description = "Your private room.";
factory.CreatorPlayerId = player.getId();
factory.ImageName = "DefaultProfileImage.png";
- factory.State = RoomState.Active;
- factory.RoomAccessibility = RoomAccessibility.Private;
+ factory.State = RoomDataTypes.RoomState.Active;
+ factory.RoomAccessibility = RoomDataTypes.RoomAccessibility.Private;
factory.SupportsLevelVoting = false;
factory.IsAGRoom = false;
factory.IsDormRoom = true;
@@ -195,10 +180,11 @@ class RoomsBase {
factory.addHardwareSupport('*');
- const subroomFactory = await factory.getSubroom(await this.#getAvailableSubRoomId(id), FactoryMode.Write, WriteMode.WriteIfFree).init();
+ const subroomFactory = await factory.getSubroom(await this.#getAvailableSubRoomId(id), RoomDataTypes.FactoryMode.Write, RoomDataTypes.WriteMode.WriteIfFree).init();
if (!subroomFactory) return null;
- subroomFactory.RoomSceneLocationId = IntegratedRoomScene.DormRoom;
+ subroomFactory.RoomId = id;
+ subroomFactory.RoomSceneLocationId = RoomDataTypes.IntegratedRoomScene.DormRoom;
subroomFactory.Name = "Home";
subroomFactory.IsSandbox = true;
subroomFactory.DataBlobName = "";
@@ -212,26 +198,20 @@ class RoomsBase {
}
async generateBuiltinRooms() {
-
- if ((await Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Rooms.Root, RoomsBase.Keys.BuiltinGenerated))) !== null) return true;
- await Promise.all(rooms.map(async builtinRoom => {
+ if ((await Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Rooms.Root, RoomsMiscBase.Keys.BuiltinGenerated))) !== null) return true;
+
+ await Promise.all(builtinRooms.map(async builtinRoom => {
if (builtinRoom.Name == 'DormRoom') return;
const newId = await this.#getAvailableRoomId();
- await Redis.Database.sadd(Redis.buildKey(Redis.KeyGroups.Rooms.Root, RoomsBase.Keys.AGRooms), newId);
+ await Redis.Database.sadd(Redis.buildKey(Redis.KeyGroups.Rooms.Root, RoomsMiscBase.Keys.AGRooms), newId);
- const factory = await new RoomFactory({ id: newId, factoryMode: FactoryMode.Write, writeMode: WriteMode.Overwrite }).init();
+ const factory = await new RoomFactory({ id: newId, factoryMode: RoomDataTypes.FactoryMode.Write, writeMode: RoomDataTypes.WriteMode.Overwrite }).init();
if (!factory) return;
factory.Name = builtinRoom.Name;
factory.Description = builtinRoom.Description;
factory.CreatorPlayerId = 1;
- const baseImageChanges = [
- { room: "DodgeballVR", image: "Dodgeball" },
- { room: "PaintballVR", image: "Paintball" },
- { room: "StuntRunnerBaseRoom", image: "StuntRunner" },
- { room: "BowlingAlley", image: "Bowling" },
- ]
if (baseImageChanges.find(change => change.room == builtinRoom.Name)) {
const image = baseImageChanges.find(change => change.room == builtinRoom.Name)!;
@@ -239,7 +219,7 @@ class RoomsBase {
}
else factory.ImageName = `${builtinRoom.Name}.png`;
- factory.State = RoomState.Active;
+ factory.State = RoomDataTypes.RoomState.Active;
factory.RoomAccessibility = builtinRoom.Accessibility;
factory.SupportsLevelVoting = builtinRoom.SupportsLevelVoting;
factory.IsAGRoom = true;
@@ -254,9 +234,10 @@ class RoomsBase {
await Promise.all(builtinRoom.Scenes.map(async subroom => {
const newSubroomId = await this.#getAvailableSubRoomId(newId);
- const subroomFactory = await factory.getSubroom(newSubroomId, FactoryMode.Write, WriteMode.Overwrite).init();
+ const subroomFactory = await factory.getSubroom(newSubroomId, RoomDataTypes.FactoryMode.Write, RoomDataTypes.WriteMode.Overwrite).init();
if (!subroomFactory) return;
+ subroomFactory.RoomId = newId;
subroomFactory.RoomSceneLocationId = subroom.RoomSceneLocationId;
subroomFactory.Name = subroom.Name;
subroomFactory.IsSandbox = subroom.IsSandbox;
@@ -269,53 +250,13 @@ class RoomsBase {
await factory.write();
}));
- Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Rooms.Root, RoomsBase.Keys.BuiltinGenerated), "1");
+ Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Rooms.Root, RoomsMiscBase.Keys.BuiltinGenerated), "1");
return false;
}
- async getIdFromName(name: string) {
- const unparsedId = await Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Room_Names, name));
- if (!unparsedId) return null;
- const parsedId = parseInt(unparsedId);
- if (isNaN(parsedId)) return null;
- return parsedId;
- }
-
- async getSubroomIdsFromRoom(id: number): Promise;
- async getSubroomIdsFromRoom(id: number, stringify: false): Promise;
- async getSubroomIdsFromRoom(id: number, stringify: boolean | undefined = false): Promise {
- const ids = await Redis.Database.smembers(Redis.buildKey(
- Redis.KeyGroups.Rooms.Root,
- id.toString(),
- RoomFactory.Keys.Subrooms
- ));
- const parsedIds = ids.map(val => parseInt(val)).filter(val => !isNaN(val));
-
- if (!stringify) return parsedIds;
- else return parsedIds.map(val => val.toString());
- }
-
- getSubroomNameFromId(room: RoomDetails, subroomId: number) {
- const subroom = room.Scenes.find(scene => scene.RoomSceneId == subroomId);
- if (subroom) return subroom.Name;
- else return null;
- }
-
- async socketUpdateRoom(instance: Instance) {
- const room = await this.get(instance.roomId);
- if (!room) return;
-
- for (const player of instance.getAllPlayers()) {
- const sock = player.getSocketHandler();
- if (!sock) continue;
- sock.sendNotification("RoomInstanceUpdate", instance.snapshot());
- sock.sendNotification(PushNotificationId.SubscriptionUpdateRoom, room);
- }
- }
-
}
-const Rooms = new RoomsBase();
+const RoomsMisc = new RoomsMiscBase();
-export { rooms as BuiltinRooms };
-export default Rooms;
\ No newline at end of file
+export { builtinRooms as BuiltinRooms };
+export default RoomsMisc;
\ No newline at end of file
diff --git a/src/data/content/rooms/SubroomFactory.ts b/src/data/content/rooms/SubroomFactory.ts
index 874f905..9fc7b33 100644
--- a/src/data/content/rooms/SubroomFactory.ts
+++ b/src/data/content/rooms/SubroomFactory.ts
@@ -15,15 +15,18 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
+import Logging from "@proxnet/undead-logging";
import { Redis } from "../../../db.ts";
-import { RoomDataTypes, IntegratedRoomScene, RoomScene, WriteMode, FactoryMode } from "./DataTypes.ts";
-import { RoomFactory } from "./RoomFactory.ts";
+import Server from "../../server/server.ts";
+import { RoomDataTypes } from "./base/DataTypes.ts";
+import { RoomFactory, roomdebug } from "./RoomFactory.ts";
+
+const log = new Logging("SubroomFactory");
interface SubroomFactoryOptions {
- roomId: number;
subroomId: number;
- writeMode?: WriteMode;
- factoryMode?: FactoryMode;
+ writeMode?: RoomDataTypes.WriteMode;
+ factoryMode?: RoomDataTypes.FactoryMode;
}
export class SubroomFactory {
@@ -42,13 +45,11 @@ export class SubroomFactory {
#writeMode: RoomDataTypes.WriteMode = RoomDataTypes.WriteMode.WriteIfFree;
factoryMode: RoomDataTypes.FactoryMode = RoomDataTypes.FactoryMode.Fetch;
- #roomId: number;
#subroomId: number;
#hash: Record | null = null;
constructor(options: SubroomFactoryOptions) {
- this.#roomId = options.roomId;
this.#subroomId = options.subroomId;
if (options.writeMode) this.#writeMode = options.writeMode;
if (options.factoryMode) this.factoryMode = options.factoryMode;
@@ -56,12 +57,10 @@ export class SubroomFactory {
async init() {
- if (!this.#roomId || !this.#subroomId) throw this.#unspecifiedArguments;
+ if (!this.#subroomId) throw this.#unspecifiedArguments;
this.#hash = await Redis.Database.hgetall(Redis.buildKey(
- Redis.KeyGroups.Rooms.Root,
- this.#roomId.toString(),
- RoomFactory.Keys.Subrooms,
+ Redis.KeyGroups.Subrooms.Root,
this.#subroomId.toString(),
SubroomFactory.Keys.Meta
));
@@ -76,9 +75,7 @@ export class SubroomFactory {
else {
const dbkey = Redis.buildKey(
- Redis.KeyGroups.Rooms.Root,
- this.#roomId.toString(),
- RoomFactory.Keys.Subrooms,
+ Redis.KeyGroups.Subrooms.Root,
this.#subroomId.toString(),
SubroomFactory.Keys.Meta
);
@@ -89,21 +86,25 @@ export class SubroomFactory {
}
if (!this.#hash) throw this.#hashValuesNotSetError;
- this.#hash['DataModifiedAt'] = new Date().toISOString();
+ this.#hash[this.#modifiedKey] = new Date().toISOString();
await Redis.Database.hset(dbkey, this.#hash);
}
- await Redis.Database.sadd(Redis.buildKey(
- Redis.KeyGroups.Rooms.Root,
- this.#roomId.toString(),
- RoomFactory.Keys.Subrooms
- ), this.RoomSceneId);
+ if (this.#hash[this.#roomIdKey])
+ await Redis.Database.sadd(Redis.buildKey(
+ Redis.KeyGroups.Rooms.Root,
+ this.#hash[this.#roomIdKey].toString(),
+ RoomFactory.Keys.Subrooms
+ ), this.RoomSceneId);
+ if (roomdebug) log.d(`Writing subroom ${this.RoomSceneId}: ${JSON.stringify(this.#hash)}`);
+ Server.emit('room.subroom.updated', { subroom: this });
}
- export(): RoomScene {
+ export(): RoomDataTypes.RoomScene {
+ if (roomdebug) log.d(`Exported subroom details for room:subroom ${this.RoomId}:${this.RoomSceneId}`);
return {
RoomSceneId: this.RoomSceneId,
RoomId: this.RoomId,
@@ -144,7 +145,7 @@ export class SubroomFactory {
#setHashValue(key: string, value: string | number | boolean) {
if (!this.#hash && this.factoryMode == RoomDataTypes.FactoryMode.Fetch) throw this.#mustFetchSubroomFirstError;
-
+
if (!this.#hash) this.#hash = {};
if (typeof value === 'object' && value !== null) {
@@ -158,10 +159,17 @@ export class SubroomFactory {
get RoomSceneId() { return this.#subroomId }
- get RoomId() { return this.#roomId }
+ #roomIdKey = 'RoomId';
+ get RoomId() { return this.#fetchNumberKey(this.#roomIdKey, 0) }
+ set RoomId(data) { this.#setHashValue(this.#roomIdKey, data) }
#locationKey = 'RoomSceneLocationId';
- get RoomSceneLocationId(): IntegratedRoomScene { return this.#fetchStringKey(this.#locationKey, IntegratedRoomScene.PerformanceHall) as IntegratedRoomScene }
+ get RoomSceneLocationId(): RoomDataTypes.IntegratedRoomScene {
+ return this.#fetchStringKey(
+ this.#locationKey,
+ RoomDataTypes.IntegratedRoomScene.PerformanceHall
+ ) as RoomDataTypes.IntegratedRoomScene;
+ }
set RoomSceneLocationId(data) { this.#setHashValue(this.#locationKey, data) }
#nameKey = 'Name';
@@ -189,9 +197,7 @@ export class SubroomFactory {
async addBlobHistory(date: Date, filename: string) {
await Redis.Database.hset(Redis.buildKey(
- Redis.KeyGroups.Rooms.Root,
- this.#roomId.toString(),
- RoomFactory.Keys.Subrooms,
+ Redis.KeyGroups.Subrooms.Root,
this.#subroomId.toString(),
SubroomFactory.Keys.Blobs
), date.toISOString(), filename);
@@ -204,9 +210,7 @@ export class SubroomFactory {
}
const hist = await Redis.Database.hgetall(Redis.buildKey(
- Redis.KeyGroups.Rooms.Root,
- this.#roomId.toString(),
- RoomFactory.Keys.Subrooms,
+ Redis.KeyGroups.Subrooms.Root,
this.#subroomId.toString(),
SubroomFactory.Keys.Blobs
));
diff --git a/src/data/content/rooms/DataTypes.ts b/src/data/content/rooms/base/DataTypes.ts
similarity index 100%
rename from src/data/content/rooms/DataTypes.ts
rename to src/data/content/rooms/base/DataTypes.ts
diff --git a/src/data/content/rooms/base/RoomsBase.ts b/src/data/content/rooms/base/RoomsBase.ts
new file mode 100644
index 0000000..3816989
--- /dev/null
+++ b/src/data/content/rooms/base/RoomsBase.ts
@@ -0,0 +1,68 @@
+/* Galvanic Corrosion - Rec Room custom server for communities.
+
+Copyright (C) 2025 @zombieb (Discord / proxnet Gitea)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see . */
+
+import { Redis } from "../../../../db.ts";
+import { RoomDetails } from "./DataTypes.ts";
+import { RoomFactory } from "../RoomFactory.ts";
+
+export class RoomsBase {
+
+ async get(id: number) {
+ try {
+ const factory = await new RoomFactory({ id: id }).init();
+ if (!factory) return null;
+ return factory.export();
+ } catch {
+ return null;
+ }
+ }
+
+ async getByName(name: string) {
+ try {
+ const factory = await new RoomFactory({ name: name }).init();
+ if (!factory) return null;
+ return factory.export();
+ } catch {
+ return null;
+ }
+ }
+
+ async getSubroomIdsFromRoom(id: number): Promise;
+ async getSubroomIdsFromRoom(id: number, stringify: false): Promise;
+ async getSubroomIdsFromRoom(id: number, stringify: boolean | undefined = false): Promise {
+ const ids = await Redis.Database.smembers(Redis.buildKey(
+ Redis.KeyGroups.Rooms.Root,
+ id.toString(),
+ RoomFactory.Keys.Subrooms
+ ));
+ const parsedIds = ids.map(val => parseInt(val)).filter(val => !isNaN(val));
+
+ if (!stringify) return parsedIds;
+ else return parsedIds.map(val => val.toString());
+ }
+
+ getSubroomNameFromId(room: RoomDetails, subroomId: number) {
+ const subroom = room.Scenes.find(scene => scene.RoomSceneId == subroomId);
+ if (subroom) return subroom.Name;
+ else return null;
+ }
+
+}
+
+const Rooms = new RoomsBase();
+
+export default Rooms;
\ No newline at end of file
diff --git a/src/data/live/Instance.ts b/src/data/live/Instance.ts
new file mode 100644
index 0000000..c4fdb5e
--- /dev/null
+++ b/src/data/live/Instance.ts
@@ -0,0 +1,188 @@
+/* Galvanic Corrosion - Rec Room custom server for communities.
+
+Copyright (C) 2025 @zombieb (Discord / proxnet Gitea)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see . */
+
+import { Config } from "../../config/config.ts";
+import { PushNotificationId } from "../../socket/types.ts";
+import { RoomDataTypes } from "../content/rooms/base/DataTypes.ts";
+import { RoomFactory } from "../content/rooms/RoomFactory.ts";
+import type { Profile } from "../profile/base/profiles.ts";
+import Server from "../server/server.ts";
+import Instances from "./instances.ts";
+import Presence from "./presence.ts";
+import type { InstanceOptions, RoomInstance } from "./types.ts";
+import Logging from "@proxnet/undead-logging";
+
+const config = Config.getConfig();
+
+const log = new Logging("InstanceBase");
+
+export class Instance {
+
+ #players = new Set();
+ timeCreated = new Date().toISOString();
+
+ #id: number;
+ #room: RoomDataTypes.RoomDetails | undefined;
+ #subroom: RoomDataTypes.RoomScene | undefined;
+
+ #eventId?: number; // not yet implemented
+ #name?: string;
+ #priv?: boolean;
+ #inProgress?: boolean;
+ #blob?: string;
+
+ constructor(id: number) {
+ this.#id = id;
+ }
+
+ async init(options: InstanceOptions) {
+ const scene = options.Room.Scenes[options.SceneIndex];
+ if (!scene) throw new Error("The specified scene does not exist.");
+
+ let instanceName;
+ if (scene.Name == 'Home' || scene.Name === options.Room.Room.Name) instanceName = `^${options.Room.Room.Name}`;
+ else instanceName = `^${options.Room.Room.Name}.${scene.Name}`;
+ if (options.IsDorm) {
+ const dormCreatorPlayer = Server.UnifiedProfile.get(options.Room.Room.CreatorPlayerId);
+ if (!dormCreatorPlayer) throw new Error("Creator of dorm does not exist.");
+ const player = await dormCreatorPlayer.export();
+ if (player) instanceName = `@${player.displayName}'s Dorm`;
+ }
+
+ this.#room = options.Room;
+ this.#subroom = scene;
+ this.#name = instanceName;
+ this.#blob = scene.DataBlobName;
+ this.#inProgress = false;
+ this.#priv = options.Private ? options.Private : false;
+
+ return this;
+
+ }
+
+ equalInstance(instance: RoomInstance) {
+ return instance.roomInstanceId == this.#id;
+ }
+
+ getAllPlayers() {
+ return this.#players.values().toArray();
+ }
+
+ hasPlayer(player: Profile) {
+ return this.getAllPlayers().includes(player);
+ }
+
+ removePlayer(player: Profile) {
+ if (!this.hasPlayer(player)) throw new Error(`Cannot remove player ${player.getId()} from instance ${this.#id} they are not in`);
+ this.#players.delete(player);
+ player.setInstance(null);
+ }
+
+ /**
+ * The client has a push notification for game session updates, but the client
+ * is based on instances, not game sessions. Possibly simply just an alias for
+ * 'RoomInstanceUpdate' but was never (or not yet) renamed. It is unknown
+ * whether the game uses this or not, but it's better to send it just in case.
+ */
+ updatePlayers() {
+ for (const player of this.#players.values()) player.getSocketHandler()?.sendNotification(PushNotificationId.SubscriptionUpdateGameSession, this.snapshot());
+ }
+
+ async addPlayer(player: Profile) {
+ const currentInstance = player.getInstance();
+ if (currentInstance && currentInstance.equalInstance(this)) return;
+
+ if (currentInstance) currentInstance.removePlayer(player);
+
+ if (!this.isFull) {
+ const instancePlayers = this.getAllPlayers();
+ const profileExport = await player.export();
+ log.i(`Player ${player.getId()} "${profileExport?.displayName}" went to '${this.name}' with ${instancePlayers.length} other players`);
+
+ this.#players.add(player);
+ player.setInstance(this);
+
+ const pres = await Presence.get(player);
+ pres.update();
+
+ const room = await new RoomFactory({ id: this.roomId }).init();
+ await room?.addVisit();
+
+ // move some of this to a dedicated "onPlayerMove" function
+ } else log.w(`Instance ${this.roomInstanceId} is full. Cannot add player ${player.getId()}`);
+
+ log.d(`Players in instance ${this.#id}: ${this.#players.values().toArray().map(prof => prof.getId()).join(',')}`);
+ }
+
+ get roomInstanceId() { return this.#id }
+
+ get roomId() { return this.#room ? this.#room?.Room.RoomId : 0 }
+
+ get subRoomId() { return this.#subroom ? this.#subroom?.RoomSceneId : 0 }
+
+ get location() { return this.#subroom ? this.#subroom?.RoomSceneLocationId : "" }
+
+ get dataBlob() { return this.#blob ? this.#blob : undefined }
+ set dataBlob(data) { this.#blob = data }
+
+ get eventId() { return this.#eventId }
+
+ get photonRegionId() { return config.public.photonRegionId }
+
+ get photonRoomId() { return `GC20200306-${this.#id}` }
+
+ get name() { return this.#name ? this.#name : "InstanceNameError" }
+
+ get maxCapacity() { return this.#subroom ? this.#subroom.MaxPlayers : 8 }
+
+ get isFull() { return this.#players.size >= this.maxCapacity }
+
+ get isPrivate() { return this.#priv ? this.#priv : false }
+ set isPrivate(data) { this.#priv = data }
+
+ get isInProgress() { return this.#inProgress ? this.#inProgress : false }
+ set isInProgress(data) { this.#inProgress = data }
+
+ snapshot() {
+ const inst: RoomInstance = {
+ roomInstanceId: this.roomInstanceId,
+ roomId: this.roomId,
+ subRoomId: this.subRoomId,
+ location: this.location,
+ dataBlob: this.dataBlob,
+ eventId: this.eventId,
+ photonRegionId: this.photonRegionId,
+ photonRoomId: this.photonRoomId,
+ name: this.name,
+ maxCapacity: this.maxCapacity,
+ isFull: this.isFull,
+ isPrivate: this.isPrivate,
+ isInProgress: this.isInProgress
+ }
+ return inst;
+ }
+
+ destroy() {
+ Instances.getAllInstances(true).delete(this);
+ if (this.#players.size !== 0) for (const player of this.#players) player.getSocketHandler()?.sendNotification(PushNotificationId.Logout);
+ }
+
+ updateSubroom(subroom: RoomDataTypes.RoomScene) {
+ this.#subroom = subroom;
+ }
+
+}
\ No newline at end of file
diff --git a/src/data/live/PhotonTypes.ts b/src/data/live/PhotonTypes.ts
new file mode 100644
index 0000000..863c840
--- /dev/null
+++ b/src/data/live/PhotonTypes.ts
@@ -0,0 +1,48 @@
+/* Galvanic Corrosion - Rec Room custom server for communities.
+
+Copyright (C) 2025 @zombieb (Discord / proxnet Gitea)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see . */
+
+export enum PhotonRegionCodeString {
+ Europe = "eu",
+ UnitedStates = "us",
+ Asia = "asia",
+ Japan = "jp",
+ Australia = "au",
+ UnitedStates_West = "usw",
+ SouthAmerica = "sa",
+ CanadaEast = "cae",
+ SouthKorea = "kr",
+ India = "@in",
+ Russia = "ru",
+ RussiaEast = "rue",
+ None = "none"
+}
+
+export enum PhotonRegionCodeNumber {
+ eu,
+ us,
+ asia,
+ jp,
+ au = 5,
+ usw,
+ sa,
+ cae,
+ kr,
+ "@in",
+ ru,
+ rue,
+ none = 4
+}
\ No newline at end of file
diff --git a/src/data/live/base.ts b/src/data/live/base.ts
index 21e5c09..9a07258 100644
--- a/src/data/live/base.ts
+++ b/src/data/live/base.ts
@@ -15,12 +15,16 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
-import Rooms from "../content/rooms.ts";
-import { RoomDataTypes } from "../content/rooms/DataTypes.ts";
-import { Profile } from "../profiles.ts";
+import Logging from "@proxnet/undead-logging";
+import RoomsMisc from "../content/rooms/Rooms.ts";
+import { RoomDataTypes } from "../content/rooms/base/DataTypes.ts";
+import Rooms from "../content/rooms/base/RoomsBase.ts";
+import { Profile } from "../profile/base/profiles.ts";
import Instances from "./instances.ts";
import { MatchmakingErrorCode, RoomInstance } from "./types.ts";
+const log = new Logging("MatchmakingBase");
+
const loginLocks: Map = new Map();
interface MatchmakingOptions {
@@ -49,6 +53,9 @@ class MatchmakingBase {
else return null;
}
+ /**
+ * @deprecated This will be removed as login locks will be saved to the database and cannot then be changed
+ */
deleteLoginLock(prof: Profile) {
loginLocks.delete(prof.getId());
}
@@ -75,7 +82,7 @@ class MatchmakingBase {
} else {
// check to make sure room exists, is not private, and is active
- const targetRoom = options.roomName !== 'DormRoom' ? await Rooms.getByName(options.roomName) : await Rooms.getProfileDormDefault(options.profile);
+ const targetRoom = options.roomName !== 'DormRoom' ? await Rooms.getByName(options.roomName) : await RoomsMisc.getProfileDormDefault(options.profile);
if (!targetRoom) return { errorCode: MatchmakingErrorCode.NoSuchRoom };
if (targetRoom.Room.Accessibility == RoomDataTypes.RoomAccessibility.Private && targetRoom.Room.CreatorPlayerId !== options.profile.getId())
return { errorCode: MatchmakingErrorCode.RoomIsPrivate };
@@ -90,7 +97,7 @@ class MatchmakingBase {
if (subroomId) allInstances = allInstances.filter(instance => instance.subRoomId == subroomId);
// filter instances that do not support join in progress and are in progress
- const builtinRooms = Rooms.getAllBuiltinRooms();
+ const builtinRooms = RoomsMisc.getAllBuiltinRooms();
const joinInProgressSubrooms = builtinRooms.map(room =>
({Name: room.Name, Scenes: room.Scenes.map(scene =>
({Name: scene.Name, Supported: scene.SupportsJoinInProgress})
@@ -117,9 +124,11 @@ class MatchmakingBase {
if (!foundInstance) {
const matchmakeableSubrooms = targetRoom.Scenes.filter(scene => scene.CanMatchmakeInto);
+ const index = Math.floor(Math.random() * matchmakeableSubrooms.length);
+ log.d(`Scene index ${index} (${targetRoom.Scenes[index] ? targetRoom.Scenes[index].RoomSceneId : "unknown"}) was chosen for matchmaking into ${targetRoom.Room.RoomId}`);
const newInstance = await Instances.createInstance({
Room: targetRoom,
- SceneIndex: Math.floor(Math.random() * matchmakeableSubrooms.length),
+ SceneIndex: index,
FirstPlayer: options.profile,
Private: options.private,
IsDorm: options.roomName == 'DormRoom'
diff --git a/src/data/live/instances.ts b/src/data/live/instances.ts
index 88ad94f..0540ab8 100644
--- a/src/data/live/instances.ts
+++ b/src/data/live/instances.ts
@@ -16,171 +16,30 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
import Logging from "@proxnet/undead-logging";
-import { Profile } from "../profiles.ts";
-import { RoomInstance, InstanceOptions } from "./types.ts";
-import { Config } from "../../config.ts";
-import Presence from "./presence.ts";
-import { RoomFactory } from "../content/rooms/RoomFactory.ts";
-import { RoomDataTypes } from "../content/rooms/DataTypes.ts";
-import { PushNotificationId } from "../../socket/types.ts";
-import Server from "../server.ts";
+import { InstanceOptions } from "./types.ts";
+import Server from "../server/server.ts";
+import { Instance } from "./Instance.ts";
const log = new Logging("Instances");
-const config = Config.getConfig();
-
/**
* `Map`
*/
const instanceSet: Set = new Set();
-export class Instance {
+Server.on('room.subroom.updated', (ev) => {
+ if (instanceSet.values().map(inst => inst.subRoomId).toArray().includes(ev.subroom.RoomSceneId)) {
+ const instance = instanceSet.values().toArray().find(inst => inst.subRoomId == ev.subroom.RoomSceneId);
+ if (!instance) return;
- #players = new Set();
- timeCreated = new Date().toISOString();
-
- #id: number;
- #room: RoomDataTypes.RoomDetails | undefined;
- #subroom: RoomDataTypes.RoomScene | undefined;
-
- #eventId?: number; // not yet implemented
- #name?: string;
- #priv?: boolean;
- #inProgress?: boolean;
- #blob?: string;
-
- constructor(id: number) {
- this.#id = id;
+ instance.dataBlob = ev.subroom.DataBlobName;
+ instance.updateSubroom(ev.subroom.export());
+ for (const profile of instance.getAllPlayers())
+ profile.getSocketHandler()?.sendNotification('RoomInstanceUpdate', instance.snapshot());
+
+ instance.updatePlayers(); // legacy
}
-
- async init(options: InstanceOptions) {
-
- const scene = options.Room.Scenes[options.SceneIndex];
- if (!scene) throw new Error("The specified scene does not exist.");
-
- let instanceName;
- if (scene.Name == 'Home' || scene.Name === options.Room.Room.Name) instanceName = `^${options.Room.Room.Name}`;
- else instanceName = `^${options.Room.Room.Name}.${scene.Name}`;
- if (options.IsDorm) {
- const dormCreatorPlayer = Server.UnifiedProfile.get(options.Room.Room.CreatorPlayerId);
- if (!dormCreatorPlayer) throw new Error("Creator of dorm does not exist.");
- const player = await dormCreatorPlayer.export();
- if (player) instanceName = `@${player.displayName}'s Dorm`;
- }
-
- this.#room = options.Room;
- this.#subroom = scene;
- this.#name = instanceName;
- this.#blob = scene.DataBlobName;
- this.#inProgress = false;
- this.#priv = options.Private ? options.Private : false;
-
- return this;
-
- }
-
- equalInstance(instance: RoomInstance) {
- return instance.roomInstanceId == this.#id;
- }
-
- getAllPlayers() {
- return this.#players.values().toArray();
- }
-
- hasPlayer(player: Profile) {
- return this.getAllPlayers().includes(player);
- }
-
- removePlayer(player: Profile) {
- if (!this.hasPlayer(player)) throw new Error(`Cannot remove player ${player.getId()} from instance ${this.#id} they are not in`);
- this.#players.delete(player);
- player.setInstance(null);
- }
-
- updatePlayers() {
- for (const player of this.#players.values()) player.getSocketHandler()?.sendNotification(PushNotificationId.SubscriptionUpdateGameSession, this.snapshot());
- }
-
- async addPlayer(player: Profile) {
- const currentInstance = player.getInstance();
- if (currentInstance && currentInstance.equalInstance(this)) return;
-
- if (currentInstance) currentInstance.removePlayer(player);
-
- if (!this.isFull) {
- const instancePlayers = this.getAllPlayers();
- const profileExport = await player.export();
- log.i(`Player ${player.getId()} "${profileExport?.displayName}" went to '${this.name}' with ${instancePlayers.length} other players`);
-
- this.#players.add(player);
- player.setInstance(this);
-
- const pres = await Presence.get(player);
- pres.update();
-
- const room = await new RoomFactory({ id: this.roomId }).init();
- await room?.addVisit();
-
- // move some of this to a dedicated "onPlayerMove" function
- } else log.w(`Instance ${this.roomInstanceId} is full. Cannot add player ${player.getId()}`);
-
- log.d(`Players in instance ${this.#id}: ${this.#players.values().toArray().map(prof => prof.getId()).join(',')}`);
- }
-
- get roomInstanceId() { return this.#id }
-
- get roomId() { return this.#room ? this.#room?.Room.RoomId : 0 }
-
- get subRoomId() { return this.#subroom ? this.#subroom?.RoomSceneId : 0 }
-
- get location() { return this.#subroom ? this.#subroom?.RoomSceneLocationId : "" }
-
- get dataBlob() { return this.#blob ? this.#blob : undefined }
- set dataBlob(data) { this.#blob = data }
-
- get eventId() { return this.#eventId }
-
- get photonRegionId() { return config.public.photonRegionId }
-
- get photonRoomId() { return `GC20200306-${this.#id}` }
-
- get name() { return this.#name ? this.#name : "InstanceNameError" }
-
- get maxCapacity() { return this.#subroom ? this.#subroom.MaxPlayers : 8 }
-
- get isFull() { return this.#players.size >= this.maxCapacity }
-
- get isPrivate() { return this.#priv ? this.#priv : false }
- set isPrivate(data) { this.#priv = data }
-
- get isInProgress() { return this.#inProgress ? this.#inProgress : false }
- set isInProgress(data) { this.#inProgress = data }
-
- snapshot() {
- const inst: RoomInstance = {
- roomInstanceId: this.roomInstanceId,
- roomId: this.roomId,
- subRoomId: this.subRoomId,
- location: this.location,
- dataBlob: this.dataBlob,
- eventId: this.eventId,
- photonRegionId: this.photonRegionId,
- photonRoomId: this.photonRoomId,
- name: this.name,
- maxCapacity: this.maxCapacity,
- isFull: this.isFull,
- isPrivate: this.isPrivate,
- isInProgress: this.isInProgress
- }
- return inst;
- }
-
- destroy() {
- instanceSet.delete(this);
- if (this.#players.size !== 0) for (const player of this.#players) player.getSocketHandler()?.sendNotification(PushNotificationId.Logout);
- }
-
-}
+});
class InstancesBase {
@@ -191,8 +50,9 @@ class InstancesBase {
else return null;
}
- getAllInstances() {
- return new Set([...instanceSet.values().toArray()]);
+ getAllInstances(asSet: boolean | undefined = false) {
+ if (!asSet) return new Set([...instanceSet.values().toArray()]);
+ else return instanceSet;
}
getAllRoomInstances(roomId: number) {
diff --git a/src/data/live/presence.ts b/src/data/live/presence.ts
index f68607a..e3ee02b 100644
--- a/src/data/live/presence.ts
+++ b/src/data/live/presence.ts
@@ -17,10 +17,13 @@ along with this program. If not, see . */
import { z } from "zod";
import { SettingKey } from "../content/settings.ts";
-import { Profile } from "../profiles.ts";
+import type { Profile } from "../profile/base/profiles.ts";
import { DeviceClass, PlayerStatusVisibility, RoomInstance, VRMovementMode } from "./types.ts";
import Logging from "@proxnet/undead-logging";
-import { Instance } from "./instances.ts";
+import { Instance } from "./Instance.ts";
+import { RoomUpdatedEvent } from "../content/rooms/RoomEvents.ts";
+import Server from "../server/server.ts";
+import { PushNotificationId } from "../../socket/types.ts";
const log = new Logging("Presence");
@@ -57,6 +60,7 @@ class PlayerPresence {
this.updateOffline();
}, 80000);
+ Server.on('room.updated', this.#roomUpdatedEventCallback);
}
offline: boolean;
@@ -69,6 +73,20 @@ class PlayerPresence {
lastSeen: Date;
+ #roomUpdatedEventCallback = (ev: RoomUpdatedEvent) => {
+ //log.d(`Room ${ev.room.RoomId} updated, notifying client.`);
+ ev.room.export().then(roomDetails => {
+ log.d(`${this.roomInstance?.roomId} == ${ev.room.RoomId}, P:${this.playerId}`);
+ if (this.roomInstance?.roomId == ev.room.RoomId) {
+ const socket = this.getProfile().getSocketHandler();
+ if (!socket) return;
+
+ socket.sendNotification(PushNotificationId.SubscriptionUpdateRoom, roomDetails);
+ socket.presenceUpdate();
+ }
+ });
+ }
+
async updateStatusVisibility() {
const PlayerStatusVisibilityEnum = z.nativeEnum(PlayerStatusVisibility);
@@ -122,6 +140,16 @@ class PlayerPresence {
this.lastSeen = new Date();
}
+ getProfile() {
+ return this.#profile;
+ }
+
+ destroy() {
+ presence.delete(this);
+ clearInterval(this.intervalId);
+ Server.off('room.updated', this.#roomUpdatedEventCallback);
+ }
+
}
const presence: Set = new Set();
@@ -164,10 +192,8 @@ class PresenceBase {
deleteDeadPresences() {
for (const pres of presence.values())
- if (Math.round(new Date().getTime() / 1000) - Math.round(pres.lastSeen.getTime() / 1000) >= 60) {
- presence.delete(pres);
- clearInterval(pres.intervalId);
- }
+ if (Math.round(new Date().getTime() / 1000) - Math.round(pres.lastSeen.getTime() / 1000) >= 60)
+ pres.destroy();
}
}
diff --git a/src/data/live/types.ts b/src/data/live/types.ts
index 3ab657a..e3cff17 100644
--- a/src/data/live/types.ts
+++ b/src/data/live/types.ts
@@ -15,40 +15,9 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
-import { RoomDataTypes } from "../content/rooms/DataTypes.ts";
-import { Profile } from "../profiles.ts";
-
-export enum PhotonRegionCodeString {
- Europe = "eu",
- UnitedStates = "us",
- Asia = "asia",
- Japan = "jp",
- Australia = "au",
- UnitedStates_West = "usw",
- SouthAmerica = "sa",
- CanadaEast = "cae",
- SouthKorea = "kr",
- India = "@in",
- Russia = "ru",
- RussiaEast = "rue",
- None = "none"
-}
-
-export enum PhotonRegionCodeNumber {
- eu,
- us,
- asia,
- jp,
- au = 5,
- usw,
- sa,
- cae,
- kr,
- "@in",
- ru,
- rue,
- none = 4
-}
+import { RoomDataTypes } from "../content/rooms/base/DataTypes.ts";
+import type { Profile } from "../profile/base/profiles.ts";
+import { PhotonRegionCodeString, PhotonRegionCodeNumber } from "./PhotonTypes.ts";
export interface RoomInstance {
diff --git a/src/data/profile/ProfileAuth.ts b/src/data/profile/ProfileAuth.ts
new file mode 100644
index 0000000..5b96710
--- /dev/null
+++ b/src/data/profile/ProfileAuth.ts
@@ -0,0 +1,44 @@
+/* Galvanic Corrosion - Rec Room custom server for communities.
+
+Copyright (C) 2025 @zombieb (Discord / proxnet Gitea)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see . */
+
+import { Config } from "../../config/config.ts";
+import { Redis } from "../../db.ts";
+import type { ProfileTokenFormat } from "../auth/TokenBaseFormat.ts";
+import { AuthType } from "../UserTypes.ts";
+import { ProfileContentManager } from "./base/profilemanagerbase.ts";
+import * as JsonWebToken from "@gz/jwt";
+
+const config = Config.getConfig();
+
+export class ProfileAuth extends ProfileContentManager {
+
+ async getToken() {
+ const payload: ProfileTokenFormat = {
+ iss: `${config.web.api.securepublichost ? 'https' : 'http'}://${config.web.api.publichost}`,
+ sub: this.profile.getId(),
+ role: (await this.getIsOperator()) ? 'developer' : 'user',
+ exp: Math.round(Date.now() / 1000) + Math.round(config.auth.timeout * 60 * 60),
+ typ: AuthType.Game,
+ };
+ return await JsonWebToken.encode(payload, config.auth.secret, { algorithm: "HS512" });
+ }
+
+ async getIsOperator() {
+ return (await Redis.Database.sismember(Redis.buildKey(Redis.KeyGroups.Operators), this.profile.getId().toString())) >= 1;
+ }
+
+}
\ No newline at end of file
diff --git a/src/data/profile/avatar.ts b/src/data/profile/avatar.ts
index 199ddf1..7a85abf 100644
--- a/src/data/profile/avatar.ts
+++ b/src/data/profile/avatar.ts
@@ -29,7 +29,7 @@ export class ProfileAvatarManager extends ProfileContentManager {
#rootKey = Redis.buildKey(
Redis.KeyGroups.Profiles.Root,
- this.profileId.toString(),
+ this.profile.getId().toString(),
Redis.KeyGroups.Profiles.Avatar.Root
);
diff --git a/src/data/profile/base/profilemanagerbase.ts b/src/data/profile/base/profilemanagerbase.ts
index 7e27d79..487124d 100644
--- a/src/data/profile/base/profilemanagerbase.ts
+++ b/src/data/profile/base/profilemanagerbase.ts
@@ -15,16 +15,18 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
+import type { Profile } from "./profiles.ts";
+
export class ProfileContentManager {
- constructor(profileId: number) {
- this.profileId = profileId;
+ constructor(profile: Profile) {
+ this.profile = profile;
}
onProfileInit() {
return;
}
- profileId: number;
+ profile: Profile;
}
\ No newline at end of file
diff --git a/src/data/profiles.ts b/src/data/profile/base/profiles.ts
similarity index 82%
rename from src/data/profiles.ts
rename to src/data/profile/base/profiles.ts
index b2eed5a..fa5eb51 100644
--- a/src/data/profiles.ts
+++ b/src/data/profile/base/profiles.ts
@@ -15,28 +15,23 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
-import { Redis } from "../db.ts";
+import { Redis } from "../../../db.ts";
import Logging from "@proxnet/undead-logging";
-import Dictionary from "./usernames.ts";
-import { Config } from "../config.ts";
-import { AuthType } from "./users.ts";
-import * as JsonWebToken from "@gz/jwt";
-import { TokenBaseFormat } from "../apiutils.ts";
-import { DeviceClass, VRMovementMode } from "./live/types.ts";
-import { SettingKey } from "./content/settings.ts";
+import Dictionary from "../../usernames.ts";
+import { DeviceClass, VRMovementMode } from "../../live/types.ts";
+import { SettingKey } from "../../content/settings.ts";
import { z } from "zod";
-import { SignalRSocketHandler } from "../socket/socket.ts";
-import { ProfileSettingsManager } from "./profile/settings.ts";
-import { ProfileProgressionManager } from "./profile/progression.ts";
-import { ProfileReputationManager } from "./profile/reputation.ts";
-import { ProfileRelationshipManager } from "./profile/relationships.ts";
-import { ProfileAvatarManager } from "./profile/avatar.ts";
-import { EventManager } from "./baseevent.ts";
-import { ProfileEvents, ProfileUpdatedEvent } from "./profileevents.ts";
-import { Instance } from "./live/instances.ts";
-import { ProfileRoomsManager } from "./profile/rooms.ts";
-
-const config = Config.getConfig();
+import { SignalRSocketHandler } from "../../../socket/socket.ts";
+import { ProfileSettingsManager } from "../settings.ts";
+import { ProfileProgressionManager } from "../progression.ts";
+import { ProfileReputationManager } from "../reputation.ts";
+import { ProfileRelationshipManager } from "../relationships.ts";
+import { ProfileAvatarManager } from "../avatar.ts";
+import type { ProfileUpdatedEvent } from "../../profileevents.ts";
+import { Instance } from "../../live/Instance.ts";
+import { ProfileRoomsManager } from "../rooms.ts";
+import { type ServerBase } from "../../server/server.ts";
+import { ProfileAuth as ProfileAuthManager } from "../ProfileAuth.ts";
const log = new Logging("Profiles");
@@ -63,15 +58,10 @@ export interface SelfAccountExport extends AccountExport {
juniorState?: number,
parentAccountId?: number
}
-export interface ProfileTokenFormat extends TokenBaseFormat {
- sub: number;
- role: "developer" | "user";
- typ: AuthType.Game;
-}
const reservedIds = [1, 2];
-class Profile extends EventManager {
+class Profile {
static async exists(id: number) {
return (await Redis.Database.exists(
Redis.buildKey(
@@ -202,26 +192,25 @@ class Profile extends EventManager {
Relationships: ProfileRelationshipManager;
Avatar: ProfileAvatarManager;
Rooms: ProfileRoomsManager;
+ Auth: ProfileAuthManager;
constructor(id: number) {
- super();
-
this.#id = id;
- this.Settings = new ProfileSettingsManager(this.#id);
- this.Progression = new ProfileProgressionManager(this.#id);
- this.Reputation = new ProfileReputationManager(this.#id);
- this.Relationships = new ProfileRelationshipManager(this.#id);
- this.Avatar = new ProfileAvatarManager(this.#id);
- this.Rooms = new ProfileRoomsManager(this.#id);
+ this.Settings = new ProfileSettingsManager(this);
+ this.Progression = new ProfileProgressionManager(this);
+ this.Reputation = new ProfileReputationManager(this);
+ this.Relationships = new ProfileRelationshipManager(this);
+ this.Avatar = new ProfileAvatarManager(this);
+ this.Rooms = new ProfileRoomsManager(this);
+ this.Auth = new ProfileAuthManager(this);
}
#emitProfileUpdated() {
const ev: ProfileUpdatedEvent = {
- time: new Date(),
profile: this
}
- this.emit(ProfileEvents.BaseUpdated, ev);
+ if (Server) Server.emit('profile.updated', ev);
}
setInstance(instance: Instance | null) {
@@ -236,10 +225,6 @@ class Profile extends EventManager {
return this.#id;
}
- async getIsOperator() {
- return (await Redis.Database.sismember(Redis.buildKey(Redis.KeyGroups.Operators), this.#id.toString())) >= 1;
- }
-
async getBio() {
const bio = await Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.#id.toString(), Redis.KeyGroups.Profiles.Bio));
if (!bio) return "";
@@ -330,26 +315,20 @@ class Profile extends EventManager {
getSocketHandler() {
return this.#socket;
}
-
- // get, set instance
- // this.#instance: RoomInstance
-
- async getToken() {
- const payload: ProfileTokenFormat = {
- iss: `${config.web.api.securepublichost ? 'https' : 'http'}://${config.web.api.publichost}`,
- sub: this.#id,
- role: (await this.getIsOperator()) ? 'developer' : 'user',
- exp: Math.round(Date.now() / 1000) + Math.round(config.auth.timeout * 60 * 60),
- typ: AuthType.Game,
- };
- return await JsonWebToken.encode(payload, config.auth.secret, { algorithm: "HS512" });
- }
+
}
-const profiles: Map = new Map()
+const profiles: Map = new Map();
+
+// surely this can be fixed
+let Server: ServerBase | undefined;
export class UnifiedProfileBase {
+ constructor(server: ServerBase) {
+ Server = server;
+ }
+
get(id: number) {
let profile = profiles.get(id);
if (!profile) {
@@ -357,7 +336,8 @@ export class UnifiedProfileBase {
const inst = new Profile(id);
profiles.set(id, inst);
profile = inst;
- } catch {
+ } catch (err) {
+ log.e(`Could not fetch profile: ${(err as Error).stack}`);
return null;
}
}
@@ -365,7 +345,10 @@ export class UnifiedProfileBase {
}
async create(options: ProfileInitOptions) {
- return await Profile.init(options);
+ const profile = await Profile.init(options);
+ if (!profile) return null;
+ profiles.set(profile.getId(), profile);
+ return profile;
}
async exists(id: number) {
diff --git a/src/data/profile/progression.ts b/src/data/profile/progression.ts
index 33ebe26..0605957 100644
--- a/src/data/profile/progression.ts
+++ b/src/data/profile/progression.ts
@@ -16,13 +16,13 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
import Logging from "@proxnet/undead-logging";
-import { GameConfigs } from "../config.ts";
import { ProfileContentManager } from "./base/profilemanagerbase.ts";
import { Redis } from "../../db.ts";
+import { getPublicConfig } from "../config/PublicConfig.ts";
const log = new Logging("ProfileProgression");
-const config = GameConfigs.getConfig();
+const config = getPublicConfig();
interface PlayerProgressionExport {
PlayerId: number,
@@ -34,7 +34,7 @@ export class ProfileProgressionManager extends ProfileContentManager {
async export() {
const ex: PlayerProgressionExport = {
- PlayerId: this.profileId,
+ PlayerId: this.profile.getId(),
Level: await this.getLevel(),
XP: await this.getXp()
}
@@ -47,7 +47,7 @@ export class ProfileProgressionManager extends ProfileContentManager {
* @returns The new # of XP
*/
async setXp(xp: number) {
- await Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId.toString(), Redis.KeyGroups.Profiles.Xp), xp.toString());
+ await Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profile.getId().toString(), Redis.KeyGroups.Profiles.Xp), xp.toString());
return xp;
}
@@ -85,12 +85,12 @@ export class ProfileProgressionManager extends ProfileContentManager {
}
async getXp() {
- let data = await Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId.toString(), Redis.KeyGroups.Profiles.Xp));
+ let data = await Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profile.getId().toString(), Redis.KeyGroups.Profiles.Xp));
if (data == null) data = (await this.setXp(0)).toString();
const parsedData = parseInt(data);
if (isNaN(parsedData)) {
- log.w(`Parsed xp data for ${this.profileId} is NaN!`);
+ log.w(`Parsed xp data for ${this.profile.getId()} is NaN!`);
const one = config?.LevelProgressionMaps[1];
if (typeof one == 'undefined' && !one) return 0; // fallback since progression data is required
else return one.RequiredXp;
diff --git a/src/data/profile/relationships.ts b/src/data/profile/relationships.ts
index 7eb62d7..9d50d60 100644
--- a/src/data/profile/relationships.ts
+++ b/src/data/profile/relationships.ts
@@ -16,9 +16,9 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
import { Redis } from "../../db.ts";
-import { Profile } from "../profiles.ts";
+import type { Profile } from "./base/profiles.ts";
import { ProfileContentManager } from "./base/profilemanagerbase.ts";
-import Server from "../server.ts";
+//import Server from "../server.ts"; // see circular dep comment at bottom
enum RelationshipType {
None,
@@ -64,7 +64,7 @@ export class ProfileRelationshipManager extends ProfileContentManager {
#rootKey = Redis.buildKey(
Redis.KeyGroups.Profiles.Root,
- this.profileId.toString(),
+ this.profile.getId().toString(),
Redis.KeyGroups.Profiles.Relationships.Root
);
@@ -144,8 +144,8 @@ export class ProfileRelationshipManager extends ProfileContentManager {
async #clearAssociationWithRemote(remoteProfileId: number) {
const remoteRootKey = this.#createRemoteRootKey(remoteProfileId);
await Redis.Database.srem(this.#incomingFriends, remoteProfileId);
- await Redis.Database.srem(Redis.buildKey(remoteRootKey, Redis.KeyGroups.Profiles.Relationships.OutgoingFriendRequests), this.profileId);
- await Redis.Database.srem(Redis.buildKey(remoteRootKey, Redis.KeyGroups.Profiles.Relationships.IncomingFriendRequests), this.profileId);
+ await Redis.Database.srem(Redis.buildKey(remoteRootKey, Redis.KeyGroups.Profiles.Relationships.OutgoingFriendRequests), this.profile.getId());
+ await Redis.Database.srem(Redis.buildKey(remoteRootKey, Redis.KeyGroups.Profiles.Relationships.IncomingFriendRequests), this.profile.getId());
await Redis.Database.srem(this.#outgoingFriends, remoteProfileId);
}
@@ -158,7 +158,7 @@ export class ProfileRelationshipManager extends ProfileContentManager {
const remoteRootKey = this.#createRemoteRootKey(remoteProfileId);
await Redis.Database.sadd(this.#friendsKey, remoteProfileId);
- await Redis.Database.sadd(Redis.buildKey(remoteRootKey, Redis.KeyGroups.Profiles.Relationships.Friends), this.profileId);
+ await Redis.Database.sadd(Redis.buildKey(remoteRootKey, Redis.KeyGroups.Profiles.Relationships.Friends), this.profile.getId());
}
async denyRequest(remoteProfileId: number) {
@@ -182,7 +182,7 @@ export class ProfileRelationshipManager extends ProfileContentManager {
const remoteKey = this.#createRemoteRootKey(remoteProfileId);
const localMuted = (await this.getAllMuted()).includes(remoteProfileId);
- const remoteMuted = await Redis.Database.sismember(Redis.buildKey(remoteKey, Redis.KeyGroups.Profiles.Relationships.Muted), this.profileId);
+ const remoteMuted = await Redis.Database.sismember(Redis.buildKey(remoteKey, Redis.KeyGroups.Profiles.Relationships.Muted), this.profile.getId());
if (localMuted && remoteMuted) return ReciprocalStatus.Mutual;
else if (localMuted) return ReciprocalStatus.Local;
@@ -194,7 +194,7 @@ export class ProfileRelationshipManager extends ProfileContentManager {
const remoteKey = this.#createRemoteRootKey(remoteProfileId);
const localIgnored = (await this.getAllMuted()).includes(remoteProfileId);
- const remoteIgnored = await Redis.Database.sismember(Redis.buildKey(remoteKey, Redis.KeyGroups.Profiles.Relationships.Ignoring), this.profileId);
+ const remoteIgnored = await Redis.Database.sismember(Redis.buildKey(remoteKey, Redis.KeyGroups.Profiles.Relationships.Ignoring), this.profile.getId());
if (localIgnored && remoteIgnored) return ReciprocalStatus.Mutual;
else if (localIgnored) return ReciprocalStatus.Local;
@@ -213,22 +213,23 @@ export class ProfileRelationshipManager extends ProfileContentManager {
}
async sendPlayerFriendRequest(player: Profile) {
- await Redis.Database.sadd(Redis.buildKey(this.#createRemoteRootKey(player.getId()), Redis.KeyGroups.Profiles.Relationships.IncomingFriendRequests), this.profileId);
+ await Redis.Database.sadd(Redis.buildKey(this.#createRemoteRootKey(player.getId()), Redis.KeyGroups.Profiles.Relationships.IncomingFriendRequests), this.profile.getId());
await Redis.Database.sadd(this.#outgoingFriends, player.getId());
}
async removePlayerFriendRequest(player: Profile) {
- await Redis.Database.srem(Redis.buildKey(this.#createRemoteRootKey(player.getId()), Redis.KeyGroups.Profiles.Relationships.IncomingFriendRequests), this.profileId);
+ await Redis.Database.srem(Redis.buildKey(this.#createRemoteRootKey(player.getId()), Redis.KeyGroups.Profiles.Relationships.IncomingFriendRequests), this.profile.getId());
await Redis.Database.srem(this.#outgoingFriends, player.getId());
}
- async ignoreAllAssociatedPlatformUsers(platformid: string) {
- const ids = (await Redis.Database.smembers(Redis.buildKey(Redis.KeyGroups.PlatformAssociations, platformid))).map(val => parseInt(val)).filter(val => !isNaN(val));
+ async ignoreAllAssociatedPlatformUsers(_platformid: string) {
+ /*const ids = (await Redis.Database.smembers(Redis.buildKey(Redis.KeyGroups.PlatformAssociations, platformid))).map(val => parseInt(val)).filter(val => !isNaN(val));
for (const id of ids) {
const profile = Server.UnifiedProfile.get(id);
if (!profile) continue;
this.setPlayerIgnored(profile);
- }
+ }*/
+ // circular dep here. this will do nothing for now.
}
}
\ No newline at end of file
diff --git a/src/data/profile/reputation.ts b/src/data/profile/reputation.ts
index a04d74e..6df12b8 100644
--- a/src/data/profile/reputation.ts
+++ b/src/data/profile/reputation.ts
@@ -22,7 +22,7 @@ export class ProfileReputationManager extends ProfileContentManager {
// deno-lint-ignore require-await
async getReputation() { // async temporary
return {
- AccountId: this.profileId,
+ AccountId: this.profile.getId(),
Noteriety: 0.0,
CheerGeneral: 0,
CheerHelpful: 0,
diff --git a/src/data/profile/rooms.ts b/src/data/profile/rooms.ts
index e1dfbe1..c824e07 100644
--- a/src/data/profile/rooms.ts
+++ b/src/data/profile/rooms.ts
@@ -22,7 +22,7 @@ export class ProfileRoomsManager extends ProfileContentManager {
#rootKey = Redis.buildKey(
Redis.KeyGroups.Profiles.Root,
- this.profileId.toString(),
+ this.profile.getId().toString(),
Redis.KeyGroups.Profiles.Rooms
);
diff --git a/src/data/profile/settings.ts b/src/data/profile/settings.ts
index 767f2b6..c6b1669 100644
--- a/src/data/profile/settings.ts
+++ b/src/data/profile/settings.ts
@@ -31,30 +31,30 @@ export class ProfileSettingsManager extends ProfileContentManager {
}
async getSettings() {
- const settings = await Redis.Database.hgetall(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId.toString(), Redis.KeyGroups.Profiles.Settings));
+ const settings = await Redis.Database.hgetall(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profile.getId().toString(), Redis.KeyGroups.Profiles.Settings));
const returnSettings: Setting[] = [];
for (const key of Object.keys(settings)) returnSettings.push({ Key: key, Value: settings[key] });
return returnSettings;
}
async getSetting(key: SettingKey) {
- return await Redis.Database.hget(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId.toString(), Redis.KeyGroups.Profiles.Settings), key);
+ return await Redis.Database.hget(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profile.getId().toString(), Redis.KeyGroups.Profiles.Settings), key);
}
async setSetting(key: SettingKey, value: string | number) {
- await Redis.Database.hset(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId.toString(), Redis.KeyGroups.Profiles.Settings), key, value);
+ await Redis.Database.hset(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profile.getId().toString(), Redis.KeyGroups.Profiles.Settings), key, value);
}
async setSettingRaw(key: string, value: string | number) {
- await Redis.Database.hset(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId.toString(), Redis.KeyGroups.Profiles.Settings), key, value);
+ await Redis.Database.hset(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profile.getId().toString(), Redis.KeyGroups.Profiles.Settings), key, value);
}
async delSetting(key: SettingKey) {
- await Redis.Database.hdel(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId.toString(), Redis.KeyGroups.Profiles.Settings), key);
+ await Redis.Database.hdel(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profile.getId().toString(), Redis.KeyGroups.Profiles.Settings), key);
}
async delAllSettings() {
- await Redis.Database.del(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId.toString(), Redis.KeyGroups.Profiles.Settings));
+ await Redis.Database.del(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profile.getId().toString(), Redis.KeyGroups.Profiles.Settings));
}
}
\ No newline at end of file
diff --git a/src/data/profileevents.ts b/src/data/profileevents.ts
index 26fec25..90661b7 100644
--- a/src/data/profileevents.ts
+++ b/src/data/profileevents.ts
@@ -15,13 +15,8 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
-import { Event } from "./baseevent.ts";
-import { Profile } from "./profiles.ts";
+import { Profile } from "./profile/base/profiles.ts";
-export enum ProfileEvents {
- BaseUpdated = "profile.updated"
-}
-
-export interface ProfileUpdatedEvent extends Event {
+export interface ProfileUpdatedEvent {
profile: Profile
}
\ No newline at end of file
diff --git a/src/data/server/server.ts b/src/data/server/server.ts
new file mode 100644
index 0000000..7b50cfc
--- /dev/null
+++ b/src/data/server/server.ts
@@ -0,0 +1,29 @@
+/* Galvanic Corrosion - Rec Room custom server for communities.
+
+Copyright (C) 2025 @zombieb (Discord / proxnet Gitea)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see . */
+
+import { EventManager } from "../baseevent.ts";
+import { CDNBase } from "../content/cdn.ts";
+import { UnifiedProfileBase } from "../profile/base/profiles.ts";
+import { ServerEvents } from "../server/serverevents.ts";
+
+export class ServerBase extends EventManager {
+ CDN = new CDNBase();
+ UnifiedProfile = new UnifiedProfileBase(this);
+}
+
+const Server = new ServerBase();
+export default Server;
\ No newline at end of file
diff --git a/src/data/server/serverevents.ts b/src/data/server/serverevents.ts
new file mode 100644
index 0000000..d47bb3b
--- /dev/null
+++ b/src/data/server/serverevents.ts
@@ -0,0 +1,25 @@
+/* Galvanic Corrosion - Rec Room custom server for communities.
+
+Copyright (C) 2025 @zombieb (Discord / proxnet Gitea)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see . */
+
+import type { RoomUpdatedEvent, SubroomUpdatedEvent } from "../content/rooms/RoomEvents.ts";
+import { ProfileUpdatedEvent } from "../profileevents.ts";
+
+export interface ServerEvents {
+ 'profile.updated': ProfileUpdatedEvent
+ 'room.subroom.updated': SubroomUpdatedEvent
+ 'room.updated': RoomUpdatedEvent
+}
\ No newline at end of file
diff --git a/src/data/steam.ts b/src/data/steam.ts
deleted file mode 100644
index 64d92e0..0000000
--- a/src/data/steam.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-/* Galvanic Corrosion - Rec Room custom server for communities.
-
-Copyright (C) 2025 @zombieb (Discord / proxnet Gitea)
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as published
-by the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see . */
-
-import Logging from "@proxnet/undead-logging";
-import { Config } from "../config.ts";
-
-const log = new Logging("Steam");
-
-const config = Config.getConfig();
-
-interface AuthenticateUserTicketSuccess {
- result: 'OK',
- steamid: string,
- ownersteamid: string,
- vacbanned: boolean,
- publisherbanned: boolean
-}
-interface AuthenticateUserTicketError {
- errorcode: number,
- errordesc: string
-}
-
-interface SteamRes {
- response: {
- error?: AuthenticateUserTicketError,
- params?: AuthenticateUserTicketSuccess
- }
-}
-
-export async function AuthenticateUserTicket(ticket: string, userid: string) {
- if (!config.auth.steamkey) return true; // always authenticate if no steam API key was found
-
- const params = new URLSearchParams();
- params.append('key', config.auth.steamkey);
- params.append('appid', "471710");
- params.append('ticket', ticket);
-
- try {
- const res = await fetch(`https://api.steampowered.com/ISteamUserAuth/AuthenticateUserTicket/v1?${params}`);
- const resjson = (await res.json()) as SteamRes;
-
- if (resjson.response.error) {
- log.w(`Steam Authentication failed: (${resjson.response.error.errorcode}) ${resjson.response.error.errordesc}`);
-
- // add more error codes later if needed
- const conditions = [
- resjson.response.error.errorcode == 100
- ].includes(true);
- if (conditions) log.w('This error indicates a client problem.');
- return false;
- }
-
- log.d(JSON.stringify(resjson.response));
- if (resjson.response.params) return resjson.response.params.steamid === userid && resjson.response.params.ownersteamid === userid;
- else {
- log.w("Steam Authentication failed: Steam response did not contain params or error! This should never be logged!");
- return false;
- }
- } catch (err) {
- log.w(`Steam Authentication failed: ${(err as Error).message}`);
- return false;
- }
-}
-
-export * as Steam from "./steam.ts";
\ No newline at end of file
diff --git a/src/data/steam/SteamAuthTypes.ts b/src/data/steam/SteamAuthTypes.ts
new file mode 100644
index 0000000..f77243c
--- /dev/null
+++ b/src/data/steam/SteamAuthTypes.ts
@@ -0,0 +1,55 @@
+/* Galvanic Corrosion - Rec Room custom server for communities.
+
+Copyright (C) 2025 @zombieb (Discord / proxnet Gitea)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see . */
+
+interface AuthenticateUserTicketSuccess {
+ result: 'OK';
+ steamid: string;
+ ownersteamid: string;
+ vacbanned: boolean;
+ publisherbanned: boolean;
+}
+interface AuthenticateUserTicketError {
+ errorcode: number;
+ errordesc: string;
+}
+export interface SteamAuthRes {
+ response: {
+ error?: AuthenticateUserTicketError;
+ params?: AuthenticateUserTicketSuccess;
+ };
+}
+
+export enum SteamAuthResult {
+ Success,
+ Failure,
+ NotConfigured
+}
+interface SteamAuthBase {
+ valid: SteamAuthResult;
+}
+interface SteamAuthSuccess extends SteamAuthBase {
+ valid: SteamAuthResult.Success;
+ res: AuthenticateUserTicketSuccess;
+}
+interface SteamAuthFailure extends SteamAuthBase {
+ valid: SteamAuthResult.Failure;
+ res: AuthenticateUserTicketError;
+}
+interface SteamAuthNotConfigured extends SteamAuthBase {
+ valid: SteamAuthResult.NotConfigured;
+}
+export type SteamAuth = SteamAuthSuccess | SteamAuthFailure | SteamAuthNotConfigured;
\ No newline at end of file
diff --git a/src/data/steam/SteamCommonTypes.ts b/src/data/steam/SteamCommonTypes.ts
new file mode 100644
index 0000000..d33bb3f
--- /dev/null
+++ b/src/data/steam/SteamCommonTypes.ts
@@ -0,0 +1,34 @@
+/* Galvanic Corrosion - Rec Room custom server for communities.
+
+Copyright (C) 2025 @zombieb (Discord / proxnet Gitea)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see . */
+
+import type { PersonaState, CommunityVisibilityState } from "./steam.ts";
+
+export interface SteamPlayer {
+ steamid: string;
+ personaname: string;
+ profileurl: string;
+ avatar: string;
+ avatarmedium: string;
+ avatarfull: string;
+ personastate: PersonaState;
+ communityvisibilitystate: CommunityVisibilityState;
+ profilestate?: 1;
+ lastlogoff?: number;
+ commentpermission?: 1;
+ loccountrycode?: string; // undocumented but is seen in API responses - may or may not be undefined
+ locstatecode?: string; // undocumented but is seen in API responses - may or may not be undefined
+}
\ No newline at end of file
diff --git a/src/data/steam/steam.ts b/src/data/steam/steam.ts
new file mode 100644
index 0000000..53bb773
--- /dev/null
+++ b/src/data/steam/steam.ts
@@ -0,0 +1,111 @@
+/* Galvanic Corrosion - Rec Room custom server for communities.
+
+Copyright (C) 2025 @zombieb (Discord / proxnet Gitea)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see . */
+
+import Logging from "@proxnet/undead-logging";
+import { Config } from "../../config/config.ts";
+import { SteamAuth, SteamAuthResult, SteamAuthRes } from "./SteamAuthTypes.ts";
+import { SteamPlayer } from "./SteamCommonTypes.ts";
+
+const log = new Logging("Steam");
+
+const config = Config.getConfig();
+
+function buildSteamUrl(steaminterface: string, endpoint: string) {
+ return `https://api.steampowered.com/${steaminterface}/${endpoint}`;
+}
+
+export enum PersonaState {
+ Offline,
+ Online,
+ Busy,
+ Away,
+ Snooze,
+ LookingToTrade,
+ LookingToPlay
+}
+
+export enum CommunityVisibilityState {
+ NotVisible,
+ PubliclyVisible = 3
+}
+
+class SteamBase {
+
+ async GetPlayerSummaries(steamids: string[]) {
+ if (!config.auth.steamkey) return null;
+
+ const params = new URLSearchParams();
+ params.append('key', config.auth.steamkey);
+ params.append('steamids', steamids.join(','))
+
+ try {
+ const res = await fetch(`${buildSteamUrl('ISteamUser', 'GetPlayerSummaries/v2')}?${params}`);
+ if (res.status !== 200) return null;
+
+ const resjson = await res.json() as { response: { players: SteamPlayer[] } };
+ return resjson.response.players;
+ } catch (err) {
+ log.e(`Could not fetch Steam player summaries: ${(err as Error).stack}`);
+ return null;
+ }
+ }
+
+ async AuthenticateUserTicket(ticket: string, userid: string): Promise {
+ if (!config.auth.steamkey) return { valid: SteamAuthResult.NotConfigured }; // always authenticate if no steam API key was found
+
+ const params = new URLSearchParams();
+ params.append('key', config.auth.steamkey);
+ params.append('appid', "471710");
+ params.append('ticket', ticket);
+
+ try {
+ const res = await fetch(`${buildSteamUrl('ISteamUserAuth', 'AuthenticateUserTicket/v1')}?${params}`);
+ const resjson = (await res.json()) as SteamAuthRes;
+
+ if (resjson.response.error) {
+ log.w(`Steam Authentication failed: (${resjson.response.error.errorcode}) ${resjson.response.error.errordesc}`);
+
+ // add more error codes later if needed
+ const conditions = [
+ resjson.response.error.errorcode == 100
+ ].includes(true);
+ if (conditions) log.w('This error indicates a client problem.');
+ return { valid: SteamAuthResult.Failure, res: resjson.response.error };
+ }
+
+ //log.d(JSON.stringify(resjson.response));
+ if (resjson.response.params) {
+ // since rec room is not eligible for family sharing on Steam
+ const valid = resjson.response.params.steamid === userid && resjson.response.params.ownersteamid === userid;
+ if (valid) return { valid: SteamAuthResult.Success, res: resjson.response.params }
+ else throw new Error('`ownersteamid` is not equal to `steamid`, report me to GC devs!');
+ }
+ else {
+ log.w("Steam Authentication failed: Steam response did not contain params or error! This should never be logged!");
+ return { valid: SteamAuthResult.Failure, res: { errorcode: -1, errordesc: 'Steam response error' } };
+ }
+ } catch (err) {
+ log.w(`Steam Authentication failed: ${(err as Error).message}`);
+ return { valid: SteamAuthResult.Failure, res: { errorcode: -1, errordesc: 'Steam response error' } };
+ }
+ }
+
+}
+
+const Steam = new SteamBase();
+
+export default Steam;
\ No newline at end of file
diff --git a/src/data/users.ts b/src/data/users.ts
index 0d5b8c0..f1b5fcf 100644
--- a/src/data/users.ts
+++ b/src/data/users.ts
@@ -17,24 +17,10 @@ along with this program. If not, see . */
import { Redis } from "../db.ts";
import * as JsonWebToken from "@gz/jwt";
-import { Config } from "../config.ts";
-import { Profile } from "./profiles.ts";
-import { TokenBaseFormat } from "../apiutils.ts";
-
-type UserInitOptions = {
- client_id: string;
- pubkey: string;
-};
-
-export enum AuthType {
- Game,
- Web,
-}
-
-export interface UserTokenFormat extends TokenBaseFormat {
- sub: string;
- typ: AuthType.Web;
-}
+import { Config } from "../config/config.ts";
+import { Profile } from "./profile/base/profiles.ts";
+import type { UserTokenFormat } from "./auth/TokenBaseFormat.ts";
+import { UserInitOptions, AuthType } from "./UserTypes.ts";
const config = Config.getConfig();
diff --git a/src/db.ts b/src/db.ts
index 40273af..1a83bd8 100644
--- a/src/db.ts
+++ b/src/db.ts
@@ -16,7 +16,7 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
import { Redis } from "ioredis";
-import * as Config from "./config.ts";
+import * as Config from "./config/config.ts";
import Logging from "@proxnet/undead-logging";
import chalk from "npm:chalk@^5.3.0";
@@ -110,6 +110,9 @@ export const KeyGroups = {
Root: "room",
PlayerDorms: "player-dormids"
},
+ Subrooms: {
+ Root: "subroom"
+ },
Operators: "operators",
Users: {
Root: "users",
diff --git a/src/discord.ts b/src/discord.ts
deleted file mode 100644
index 940224d..0000000
--- a/src/discord.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-/* Galvanic Corrosion - Rec Room custom server for communities.
-
-Copyright (C) 2025 @zombieb (Discord / proxnet Gitea)
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as published
-by the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see . */
-
-import * as discord from "discord.js";
-import { Config } from "./config.ts";
-import Logging from "@proxnet/undead-logging";
-
-const log = new Logging("Discord");
-
-const config = Config.getConfig();
-if (typeof config == "undefined") {
- log.e(`Cannot start: Discord configuration is unavailable`);
- Deno.exit(1);
-}
-
-export const client = new discord.Client({
- intents: [
- discord.GatewayIntentBits.Guilds,
- discord.GatewayIntentBits.GuildPresences,
- ],
-});
-
-client.once(discord.Events.ClientReady, (client) => {
- log.i(`Logged in to Discord as "${client.user.tag}"`);
- client.user?.setActivity(config.public.motd, {
- type: discord.ActivityType.Custom,
- });
-});
-
-let shuttingDown = false;
-Deno.addSignalListener("SIGINT", () => {
- if (client.readyTimestamp == null) return;
- if (shuttingDown) return;
- shuttingDown = true;
- log.n("Disconnecting from Discord");
- client.destroy();
-});
-
-export function login() {
- if (config.discord?.token == Config.defaultConfig.discord?.token) {
- log.i("Discord not configured, ignoring");
- return;
- }
- log.i(`Creating Discord connection..`);
- client.login(config.discord?.token);
-}
-
-export * as Discord from "./discord.ts";
diff --git a/src/main.ts b/src/main.ts
index 169cb99..b72bfd7 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -16,42 +16,44 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
import * as Log from "@proxnet/undead-logging";
-import * as Config from "./config.ts";
+import * as Config from "./config/config.ts";
import { Database } from "./db.ts";
-import { APIUtils, ProfileTokenSchema } from "./apiutils.ts";
-import { Discord } from "./discord.ts";
-import { generateRandomString } from "./apiutils.ts";
+import { APIUtils } from "./apiutils.ts";
+import { ProfileTokenSchema } from "./data/auth/TokenSchema.ts";
+import { generateRandomString } from "./utils.ts";
import express from "express";
import { decode } from "@gz/jwt";
-import { ProfileTokenFormat } from "./data/profiles.ts";
import { SocketHandoff } from "./socket/handoff.ts";
import { SignalRSocketHandler } from "./socket/socket.ts";
-import { GameConfigs } from "./data/config.ts";
+import { GameConfigs } from "./data/config/GameConfigs.ts";
import { getVersion } from "./ver.ts";
-import Rooms from "./data/content/rooms.ts";
+import RoomsMisc from "./data/content/rooms/Rooms.ts";
import { PushNotificationId } from "./socket/types.ts";
-import Server from "./data/server.ts";
+import Server from "./data/server/server.ts";
+import type { ProfileTokenFormat } from "./data/auth/TokenBaseFormat.ts";
+import { addWatchdogListener } from "./watchdog.ts";
const instanceId = generateRandomString(64);
const log = new Log.default("Main");
+Log.LoggingConfiguration.logTiming = Log.LogTiming.Deferred;
+
log.i(`Galvanic Corrosion '${await getVersion()}'`);
const config = Config.getConfig();
-if (typeof config == "undefined") {
- log.e("Cannot start: Configuration was not found.");
- Deno.exit(5);
-}
-if (config.auth.secret == Config.defaultConfig.auth.secret) {
- log.e(`Cannot start: Auth secret is default. Please change 'auth.secret' in 'config.json'`);
- Deno.exit(5);
-}
-if (config.public.serverId == Config.defaultConfig.public.serverId) {
- log.e(`Cannot start: Server ID is default. Please change 'public.serverId' in 'config.json'`);
- Deno.exit(5);
+function exitError(err: string, code?: number) {
+ log.e(`Cannot start: ${err}`);
+ if (code) Deno.exit(code);
+ else Deno.exit(5);
}
+if (typeof config == "undefined")
+ exitError("Configuration was not found.");
+if (config.auth.secret == Config.defaultConfig.auth.secret)
+ exitError(`Auth secret is default. Please change 'auth.secret' in 'config.json'`);
+if (config.public.serverId == Config.defaultConfig.public.serverId)
+ exitError(`Server ID is default. Please change 'public.serverId' in 'config.json'`);
Log.MessageTypeVisibility.Network = config.logging.network;
Log.MessageTypeVisibility.Debug = config.logging.debug;
@@ -59,8 +61,7 @@ Log.MessageTypeVisibility.Debug = config.logging.debug;
try {
Database.connect();
} catch (err) {
- log.e(`Cannot start: Redis could not be initialized. ${err}`);
- Deno.exit(1);
+ exitError(`Redis could not be initialized. ${(err as Error).stack}`);
}
const app = express();
@@ -118,7 +119,7 @@ app.use((rq: express.Request, rs: express.Response) => {
rs.json(APIUtils.genericResponseFormat(true, "Endpoint not found. Check your syntax and/or method."));
});
-if (!(await Rooms.generateBuiltinRooms())) log.i(`Generated built-in rooms`);
+if (!(await RoomsMisc.generateBuiltinRooms())) log.i(`Generated built-in rooms`);
await Server.CDN.ensureUserDirectory();
try {
@@ -227,6 +228,7 @@ try {
Deno.addSignalListener("SIGINT", () => {
for (const socket of Server.UnifiedProfile.getAllSockets()) socket.sendNotification(PushNotificationId.ModerationQuitGame); // untested
});
+ addWatchdogListener();
if (!(await Server.UnifiedProfile.existsByName("Coach"))) Server.UnifiedProfile.create({ username: "Coach", id: 1 }); // create Coach id 1 if they do not exist
if (!(await Server.UnifiedProfile.existsByName("Server"))) Server.UnifiedProfile.create({ username: "Server", id: 2 }); // create Server id 2 if they do not exist
@@ -245,6 +247,4 @@ try {
} catch (err) {
log.e(`Cannot start: Network could not be initalized. ${err}`);
Deno.exit(1);
-}
-
-Discord.login();
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/routes/account/account.ts b/src/routes/account/account.ts
index c5f5035..2e00fda 100644
--- a/src/routes/account/account.ts
+++ b/src/routes/account/account.ts
@@ -17,10 +17,10 @@ along with this program. If not, see . */
import { APIUtils, NoBody } from "../../apiutils.ts";
import express from "express";
-import { Profile } from "../../data/profiles.ts";
+import { Profile } from "../../data/profile/base/profiles.ts";
import { z } from "zod";
-import { AuthType } from "../../data/users.ts";
-import Server from "../../data/server.ts";
+import { AuthType } from "../../data/UserTypes.ts";
+import Server from "../../data/server/server.ts";
export const route = APIUtils.createRouter("/account");
diff --git a/src/routes/account/parentalcontrol.ts b/src/routes/account/parentalcontrol.ts
index 590df79..a0123c5 100644
--- a/src/routes/account/parentalcontrol.ts
+++ b/src/routes/account/parentalcontrol.ts
@@ -16,7 +16,7 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
import { APIUtils } from "../../apiutils.ts";
-import { AuthType } from "../../data/users.ts";
+import { AuthType } from "../../data/UserTypes.ts";
export const route = APIUtils.createRouter('/parentalcontrol');
diff --git a/src/routes/api/announcement.ts b/src/routes/api/announcement.ts
index 45ae93b..083c01f 100644
--- a/src/routes/api/announcement.ts
+++ b/src/routes/api/announcement.ts
@@ -16,7 +16,7 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
import { APIUtils } from "../../apiutils.ts";
-import { AuthType } from "../../data/users.ts";
+import { AuthType } from "../../data/UserTypes.ts";
export const route = APIUtils.createRouter("/announcement");
diff --git a/src/routes/api/avatar.ts b/src/routes/api/avatar.ts
index cc4cfc0..2fea6c7 100644
--- a/src/routes/api/avatar.ts
+++ b/src/routes/api/avatar.ts
@@ -17,7 +17,7 @@ along with this program. If not, see . */
import { z } from "zod";
import { APIUtils, NoBody } from "../../apiutils.ts";
-import { AuthType } from "../../data/users.ts";
+import { AuthType } from "../../data/UserTypes.ts";
import express from "express";
import { AvatarSettings } from "../../data/profile/avatar.ts";
diff --git a/src/routes/api/challenge.ts b/src/routes/api/challenge.ts
index 67140a1..0f2ac87 100644
--- a/src/routes/api/challenge.ts
+++ b/src/routes/api/challenge.ts
@@ -16,7 +16,7 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
import { APIUtils } from "../../apiutils.ts";
-import { AuthType } from "../../data/users.ts";
+import { AuthType } from "../../data/UserTypes.ts";
export const route = APIUtils.createRouter("/challenge");
diff --git a/src/routes/api/checklist.ts b/src/routes/api/checklist.ts
index dc09ec4..ac6fcd7 100644
--- a/src/routes/api/checklist.ts
+++ b/src/routes/api/checklist.ts
@@ -16,8 +16,8 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
import { APIUtils } from "../../apiutils.ts";
-import { ObjectiveType } from "../../data/objectives.ts";
-import { AuthType } from "../../data/users.ts";
+import { ObjectiveType } from "../../data/content/ObjectiveTypes.ts";
+import { AuthType } from "../../data/UserTypes.ts";
export const route = APIUtils.createRouter('/checklist');
diff --git a/src/routes/api/communityboard.ts b/src/routes/api/communityboard.ts
index 6e80c3f..7db6415 100644
--- a/src/routes/api/communityboard.ts
+++ b/src/routes/api/communityboard.ts
@@ -16,8 +16,8 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
import { APIUtils } from "../../apiutils.ts";
-import { Config } from "../../config.ts";
-import { AuthType } from "../../data/users.ts";
+import { Config } from "../../config/config.ts";
+import { AuthType } from "../../data/UserTypes.ts";
const config = Config.getConfig();
diff --git a/src/routes/api/config.ts b/src/routes/api/config.ts
index a939084..6eef47d 100644
--- a/src/routes/api/config.ts
+++ b/src/routes/api/config.ts
@@ -16,14 +16,14 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
import { APIUtils } from "../../apiutils.ts";
-import { GameConfigs } from "../../data/config.ts";
+import { getPublicConfig } from "../../data/config/PublicConfig.ts";
export const route = APIUtils.createRouter("/config");
const rateLimit = new APIUtils.RateLimiter();
route.router.get("/v2", rateLimit.middle(), (_rq, rs) => {
- const config = GameConfigs.getConfig();
+ const config = getPublicConfig();
if (config == null) rs.sendStatus(500);
else rs.json(config);
});
diff --git a/src/routes/api/consumables.ts b/src/routes/api/consumables.ts
index 339358c..464b787 100644
--- a/src/routes/api/consumables.ts
+++ b/src/routes/api/consumables.ts
@@ -16,7 +16,7 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
import { APIUtils } from "../../apiutils.ts";
-import { AuthType } from "../../data/users.ts";
+import { AuthType } from "../../data/UserTypes.ts";
export const route = APIUtils.createRouter('/consumables');
diff --git a/src/routes/api/equipment.ts b/src/routes/api/equipment.ts
index 601c726..bdfc43d 100644
--- a/src/routes/api/equipment.ts
+++ b/src/routes/api/equipment.ts
@@ -16,7 +16,7 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
import { APIUtils } from "../../apiutils.ts";
-import { AuthType } from "../../data/users.ts";
+import { AuthType } from "../../data/UserTypes.ts";
export const route = APIUtils.createRouter('/equipment');
diff --git a/src/routes/api/gameconfigs.ts b/src/routes/api/gameconfigs.ts
index a442247..7b8f50b 100644
--- a/src/routes/api/gameconfigs.ts
+++ b/src/routes/api/gameconfigs.ts
@@ -16,7 +16,7 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
import { APIUtils } from "../../apiutils.ts";
-import { GameConfigs } from "../../data/config.ts";
+import { GameConfigs } from "../../data/config/GameConfigs.ts";
export const route = APIUtils.createRouter("/gameconfigs");
diff --git a/src/routes/api/images.ts b/src/routes/api/images.ts
index fb9f50d..b2f3881 100644
--- a/src/routes/api/images.ts
+++ b/src/routes/api/images.ts
@@ -16,7 +16,7 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
import { APIUtils } from "../../apiutils.ts";
-import { AuthType } from "../../data/users.ts";
+import { AuthType } from "../../data/UserTypes.ts";
export const route = APIUtils.createRouter('/images');
@@ -27,4 +27,17 @@ route.router.get('/v2/named',
APIUtils.emptyArrayResponse
+);
+
+route.router.post('/v4/uploadsaved',
+
+ APIUtils.Authentication,
+ APIUtils.AuthenticationType(AuthType.Game),
+
+ APIUtils.requestDebug,
+
+ (_rq, rs) => {
+ rs.json({ImageName:"notsaved.png"});
+ },
+
);
\ No newline at end of file
diff --git a/src/routes/api/objectives.ts b/src/routes/api/objectives.ts
index 766188c..aa96559 100644
--- a/src/routes/api/objectives.ts
+++ b/src/routes/api/objectives.ts
@@ -17,7 +17,7 @@ along with this program. If not, see . */
import { z } from "zod";
import { APIUtils, NoBody } from "../../apiutils.ts";
-import { AuthType } from "../../data/users.ts";
+import { AuthType } from "../../data/UserTypes.ts";
import express from "express";
export const route = APIUtils.createRouter('/objectives');
diff --git a/src/routes/api/playerReputation.ts b/src/routes/api/playerReputation.ts
index ea67530..c7f5d71 100644
--- a/src/routes/api/playerReputation.ts
+++ b/src/routes/api/playerReputation.ts
@@ -17,9 +17,9 @@ along with this program. If not, see . */
import { z } from "zod";
import { APIUtils, NoBody } from "../../apiutils.ts";
-import { AuthType } from "../../data/users.ts";
+import { AuthType } from "../../data/UserTypes.ts";
import express from "express";
-import Server from "../../data/server.ts";
+import Server from "../../data/server/server.ts";
export const route = APIUtils.createRouter("/playerReputation");
diff --git a/src/routes/api/playerevents.ts b/src/routes/api/playerevents.ts
index 116fa39..38003cf 100644
--- a/src/routes/api/playerevents.ts
+++ b/src/routes/api/playerevents.ts
@@ -16,7 +16,7 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
import { APIUtils } from "../../apiutils.ts";
-import { AuthType } from "../../data/users.ts";
+import { AuthType } from "../../data/UserTypes.ts";
export const route = APIUtils.createRouter('/playerevents');
diff --git a/src/routes/api/players.ts b/src/routes/api/players.ts
index 3a62dd7..303c0a0 100644
--- a/src/routes/api/players.ts
+++ b/src/routes/api/players.ts
@@ -18,9 +18,9 @@ along with this program. If not, see . */
import Logging from "@proxnet/undead-logging";
import { APIUtils, NoBody } from "../../apiutils.ts";
import express from "express";
-import { AuthType } from "../../data/users.ts";
+import { AuthType } from "../../data/UserTypes.ts";
import { z } from "zod";
-import Server from "../../data/server.ts";
+import Server from "../../data/server/server.ts";
const log = new Logging("ProgressionRoute");
diff --git a/src/routes/api/quickplay.ts b/src/routes/api/quickplay.ts
index a897ae0..80daab4 100644
--- a/src/routes/api/quickplay.ts
+++ b/src/routes/api/quickplay.ts
@@ -16,8 +16,8 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
import { APIUtils } from "../../apiutils.ts";
-import { Config } from "../../config.ts";
-import { AuthType } from "../../data/users.ts";
+import { Config } from "../../config/config.ts";
+import { AuthType } from "../../data/UserTypes.ts";
export const route = APIUtils.createRouter("/quickPlay");
diff --git a/src/routes/api/relationships.ts b/src/routes/api/relationships.ts
index fde0d68..8264277 100644
--- a/src/routes/api/relationships.ts
+++ b/src/routes/api/relationships.ts
@@ -17,7 +17,7 @@ along with this program. If not, see . */
import { z } from "zod";
import { APIUtils, NoBody } from "../../apiutils.ts";
-import { AuthType } from "../../data/users.ts";
+import { AuthType } from "../../data/UserTypes.ts";
import express from "express";
export const route = APIUtils.createRouter("/relationships");
diff --git a/src/routes/api/rooms.ts b/src/routes/api/rooms.ts
index 139db41..7d07543 100644
--- a/src/routes/api/rooms.ts
+++ b/src/routes/api/rooms.ts
@@ -17,13 +17,14 @@ along with this program. If not, see . */
import { z } from "zod";
import { APIUtils, NoBody } from "../../apiutils.ts";
-import Rooms from "../../data/content/rooms.ts";
-import { FactoryMode, RoomDataTypes, WriteMode } from "../../data/content/rooms/DataTypes.ts";
-import { AuthType } from "../../data/users.ts";
+import RoomsMisc from "../../data/content/rooms/Rooms.ts";
+import { FactoryMode, RoomDataTypes, WriteMode } from "../../data/content/rooms/base/DataTypes.ts";
+import { AuthType } from "../../data/UserTypes.ts";
import express from "express";
import { RoomFactory } from "../../data/content/rooms/RoomFactory.ts";
import { SubroomFactory } from "../../data/content/rooms/SubroomFactory.ts";
import Logging from "@proxnet/undead-logging";
+import Rooms from "../../data/content/rooms/base/RoomsBase.ts";
const log = new Logging("RoomsRoute");
@@ -83,7 +84,7 @@ route.router.get('/v1/hot',
async (_rq, rs) => {
// temporary: return all public AG rooms for testing
- const rooms = await Rooms.getAllBuiltinRoomGenerations();
+ const rooms = await RoomsMisc.getAllBuiltinRoomGenerations();
rs.json(rooms.map(room => room.Room).filter(room => room.Accessibility == RoomDataTypes.RoomAccessibility.Public));
},
@@ -95,7 +96,7 @@ route.router.get('/v2/baserooms',
APIUtils.AuthenticationType(AuthType.Game),
async (_rq, rs) => {
- const rooms = await Rooms.getAllBuiltinRoomGenerations();
+ const rooms = await RoomsMisc.getAllBuiltinRoomGenerations();
rs.json(rooms.map(room => room.Room).filter(room => room.CloningAllowed));
},
@@ -120,7 +121,7 @@ route.router.get('/v2/name/:name',
rs.json(room.Room);
return;
} else if (rq.params.name == 'DormRoom') {
- const dorm = await Rooms.getProfileDormDefault(rs.locals.profile);
+ const dorm = await RoomsMisc.getProfileDormDefault(rs.locals.profile);
if (dorm) rs.json(dorm.Room);
else rs.sendStatus(404);
return;
@@ -150,7 +151,7 @@ route.router.get('/v1/agRoomIds',
async (_rq, rs) => {
- const rooms = await Rooms.getAllBuiltinRoomGenerations();
+ const rooms = await RoomsMisc.getAllBuiltinRoomGenerations();
rs.json(rooms.map(det => det.Room.RoomId));
},
@@ -175,7 +176,7 @@ route.router.post('/v1/clone',
async (rq: express.Request, rs: express.Response) => {
- const room = await Rooms.cloneRoom(rq.body.RoomId, rq.body.Name, rs.locals.profile);
+ const room = await RoomsMisc.cloneRoom(rq.body.RoomId, rq.body.Name, rs.locals.profile);
const masterRoomFactory = await new RoomFactory({ id: rq.body.RoomId }).init();
@@ -231,7 +232,6 @@ route.router.post('/v4/saveData',
}
const subroomFactory = await new SubroomFactory({
- roomId: currentInstance.roomId,
subroomId: rq.body.RoomSceneId,
factoryMode: FactoryMode.Write,
writeMode: WriteMode.Overwrite
@@ -249,10 +249,6 @@ route.router.post('/v4/saveData',
await subroomFactory.write();
rs.json(subroomFactory.export());
-
- currentInstance.dataBlob = newFilename;
- currentInstance.updatePlayers();
- Rooms.socketUpdateRoom(currentInstance);
}
},
diff --git a/src/routes/api/settings.ts b/src/routes/api/settings.ts
index e0893b0..5775cc9 100644
--- a/src/routes/api/settings.ts
+++ b/src/routes/api/settings.ts
@@ -17,7 +17,7 @@ along with this program. If not, see . */
import Logging from "@proxnet/undead-logging";
import { APIUtils, NoBody } from "../../apiutils.ts";
-import { AuthType } from "../../data/users.ts";
+import { AuthType } from "../../data/UserTypes.ts";
import z from "zod";
import express from "express";
diff --git a/src/routes/api/storefronts.ts b/src/routes/api/storefronts.ts
index 4353b6d..a668ccd 100644
--- a/src/routes/api/storefronts.ts
+++ b/src/routes/api/storefronts.ts
@@ -17,7 +17,7 @@ along with this program. If not, see . */
import { APIUtils } from "../../apiutils.ts";
import express from "express";
-import { AuthType } from "../../data/users.ts";
+import { AuthType } from "../../data/UserTypes.ts";
import { StorefrontBalanceType } from "../../data/content/storefronts.ts";
export const route = APIUtils.createRouter('/storefronts');
diff --git a/src/routes/auth/account.ts b/src/routes/auth/account.ts
index b92e768..9cea42c 100644
--- a/src/routes/auth/account.ts
+++ b/src/routes/auth/account.ts
@@ -16,7 +16,7 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
import { APIUtils } from "../../apiutils.ts";
-import { AuthType } from "../../data/users.ts";
+import { AuthType } from "../../data/UserTypes.ts";
export const route = APIUtils.createRouter("/account");
diff --git a/src/routes/auth/cachedlogin.ts b/src/routes/auth/cachedlogin.ts
index 728eca8..5331042 100644
--- a/src/routes/auth/cachedlogin.ts
+++ b/src/routes/auth/cachedlogin.ts
@@ -16,7 +16,7 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
import { APIUtils } from "../../apiutils.ts";
-import { AuthType } from "../../data/users.ts";
+import { AuthType } from "../../data/UserTypes.ts";
export const route = APIUtils.createRouter("/cachedlogin");
diff --git a/src/routes/auth/connect.ts b/src/routes/auth/connect.ts
index 829f2b8..f14df58 100644
--- a/src/routes/auth/connect.ts
+++ b/src/routes/auth/connect.ts
@@ -18,14 +18,16 @@ along with this program. If not, see . */
import { APIUtils, NoBody } from "../../apiutils.ts";
import express from "express";
import { decode } from "@gz/jwt";
-import { Config } from "../../config.ts";
+import { Config } from "../../config/config.ts";
import Logging from "@proxnet/undead-logging";
import { z } from "zod";
-import { AuthType } from "../../data/users.ts";
+import { AuthType } from "../../data/UserTypes.ts";
import { Redis } from "../../db.ts";
import { validVersions } from "../api/versioncheck.ts";
-import { Steam } from "../../data/steam.ts";
-import Server from "../../data/server.ts";
+import Steam from "../../data/steam/steam.ts";
+import Server from "../../data/server/server.ts";
+import { DeviceClass } from "../../data/live/types.ts";
+import { SteamAuthResult } from "../../data/steam/SteamAuthTypes.ts";
const config = Config.getConfig();
@@ -124,7 +126,7 @@ route.router.post("/token",
});
}
- const conditionsMet = ![
+ const conditions = [
rq.body.client_id === "recroom",
rq.body.platform === "0",
validVersions.includes(rq.body.ver),
@@ -136,7 +138,8 @@ route.router.post("/token",
!(rq.body.time.length > 32),
!(rq.body.asid.length > 32),
SteamPlatformParamsSchema.safeParse(JSON.parse(rq.body.platform_auth)).success
- ].includes(false);
+ ];
+ const conditionsMet = !conditions.includes(false);
if (!conditionsMet) {
requestFailed();
@@ -164,48 +167,56 @@ route.router.post("/token",
targetAccount = parseInt(decodedToken.sub ? decodedToken.sub : "NaN");
}
-
+
const platformAuth = (JSON.parse(rq.body.platform_auth)) as SteamPlatformParams;
+ let platformid: string | null;
if (config.auth.steamkey) {
- const steamAuthed = await Steam.AuthenticateUserTicket(platformAuth.Ticket, rq.body.platform_id);
- if (!steamAuthed) {
+ const steamAuth = await Steam.AuthenticateUserTicket(platformAuth.Ticket, rq.body.platform_id);
+ if (steamAuth.valid == SteamAuthResult.Failure) {
requestFailed();
return;
- }
- }
+ } else if (steamAuth.valid == SteamAuthResult.Success) platformid = steamAuth.res.steamid;
+ else platformid = null;
+ } else platformid = null;
if (isNaN(targetAccount)) {
requestFailed();
return;
}
-
+
const accounts = await rs.locals.user.getAssociatedProfiles();
if (!accounts.has(targetAccount)) {
requestFailed("access_denied");
return;
}
-
+
rs.locals.user.addAssociatedDeviceId(rq.body.device_id);
- rs.locals.user.addAssociatedPlatformId(rq.body.platform_id);
- Redis.Database.sadd(Redis.buildKey(Redis.KeyGroups.PlatformAssociations, rq.body.platform_id), targetAccount);
-
+ if (platformid) rs.locals.user.addAssociatedPlatformId(platformid);
+ if (platformid) Redis.Database.sadd(Redis.buildKey(Redis.KeyGroups.PlatformAssociations, platformid), targetAccount);
+
const profile = Server.UnifiedProfile.get(targetAccount);
if (!profile) {
requestFailed();
return;
}
+ const deviceClass = Number(rq.body.device_class);
+ if (typeof DeviceClass[deviceClass] == 'undefined') {
+ requestFailed();
+ return;
+ }
+
const details = await profile.export();
log.i(`Player ${details?.username} "${details?.displayName}" (${profile.getId()}) logged in`);
- const token = await profile.getToken();
+ const token = await profile.Auth.getToken();
rs.json({
access_token: token,
refresh_token: token,
});
- await profile.setKnownDeviceClass(Number(rq.body.device_class));
+ await profile.setKnownDeviceClass(deviceClass);
nxt();
},
diff --git a/src/routes/cdn.ts b/src/routes/cdn.ts
index 1c5809a..698d50b 100644
--- a/src/routes/cdn.ts
+++ b/src/routes/cdn.ts
@@ -17,8 +17,8 @@ along with this program. If not, see . */
import { APIUtils } from "../apiutils.ts";
import { File } from "../data/content/cdn.ts";
-import Server from "../data/server.ts";
-import { AuthType } from "../data/users.ts";
+import Server from "../data/server/server.ts";
+import { AuthType } from "../data/UserTypes.ts";
import { route as ConfigRoute } from "./cdn/config.ts";
import express from "express";
import { Buffer } from "node:buffer";
diff --git a/src/routes/match/goto.ts b/src/routes/match/goto.ts
index f4d3397..1dc0f05 100644
--- a/src/routes/match/goto.ts
+++ b/src/routes/match/goto.ts
@@ -19,7 +19,7 @@ import Logging from "@proxnet/undead-logging";
import { APIUtils, NoBody } from "../../apiutils.ts";
import Matchmaking from "../../data/live/base.ts";
import { MatchmakingErrorCode } from "../../data/live/types.ts";
-import { AuthType } from "../../data/users.ts";
+import { AuthType } from "../../data/UserTypes.ts";
import express from "express";
import { z } from "zod";
@@ -33,10 +33,10 @@ interface MatchmakingParams {
}
const ProperCaseBooleanSchema = z.preprocess((val) => {
- if (val === "True") return true;
- if (val === "False") return false;
- if (typeof val === "boolean") return val; // allow raw booleans too
- return val; // will fail validation
+ if (val === "True") return true;
+ if (val === "False") return false;
+ if (typeof val === "boolean") return val;
+ return val;
}, z.boolean());
interface MatchmakingOptions {
diff --git a/src/routes/match/player.ts b/src/routes/match/player.ts
index e773ebe..fbfe3ca 100644
--- a/src/routes/match/player.ts
+++ b/src/routes/match/player.ts
@@ -20,10 +20,10 @@ import { APIUtils, NoBody } from "../../apiutils.ts";
import express from "express";
import Matchmaking from "../../data/live/base.ts";
import Presence, { PresenceExport } from "../../data/live/presence.ts";
-import { AuthType } from "../../data/users.ts";
+import { AuthType } from "../../data/UserTypes.ts";
import { PlayerStatusVisibility, VRMovementMode } from "../../data/live/types.ts";
import { SettingKey } from "../../data/content/settings.ts";
-import Server from "../../data/server.ts";
+import Server from "../../data/server/server.ts";
export const route = APIUtils.createRouter('/player');
diff --git a/src/routes/match/room.ts b/src/routes/match/room.ts
index 974f3f0..0a859fb 100644
--- a/src/routes/match/room.ts
+++ b/src/routes/match/room.ts
@@ -17,7 +17,7 @@ along with this program. If not, see . */
import { APIUtils } from "../../apiutils.ts";
import Instances from "../../data/live/instances.ts";
-import { AuthType } from "../../data/users.ts";
+import { AuthType } from "../../data/UserTypes.ts";
import express from "express";
export const route = APIUtils.createRouter('/room');
diff --git a/src/routes/nameserver.ts b/src/routes/nameserver.ts
index 9b0744a..0b578fe 100644
--- a/src/routes/nameserver.ts
+++ b/src/routes/nameserver.ts
@@ -16,9 +16,10 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
import { APIUtils } from "../apiutils.ts";
-import { Config } from "../config.ts";
+import { Config } from "../config/config.ts";
+import type { GalvanicConfiguration } from "../config/GalvanicConfiguration.ts";
-const config = Config.getConfig() as Config.GalvanicConfiguration;
+const config = Config.getConfig() as GalvanicConfiguration;
const protocol = config.web.api.securepublichost ? "https" : "http";
export const route = APIUtils.createRouter("/ns");
diff --git a/src/routes/storage.ts b/src/routes/storage.ts
index ef755b5..351c82d 100644
--- a/src/routes/storage.ts
+++ b/src/routes/storage.ts
@@ -17,12 +17,12 @@ along with this program. If not, see . */
import { z } from "zod";
import { APIUtils, NoBody } from "../apiutils.ts";
-import { AuthType } from "../data/users.ts";
import { Buffer } from "node:buffer";
import multer from "multer";
import { FileType } from "../data/content/cdn.ts";
import express from "express";
-import Server from "../data/server.ts";
+import { AuthType } from "../data/UserTypes.ts";
+import Server from "../data/server/server.ts";
export const route = APIUtils.createRouter("/storage");
diff --git a/src/routes/user.ts b/src/routes/user.ts
index 04da4b4..b6417d8 100644
--- a/src/routes/user.ts
+++ b/src/routes/user.ts
@@ -18,12 +18,13 @@ along with this program. If not, see . */
import { APIUtils, getSrcIpDefault, NoBody } from "../apiutils.ts";
// @ts-types = "npm:@types/express"
import express from "express";
-import { User, UserTokenFormat } from "../data/users.ts";
-import { Config } from "../config.ts";
+import { User } from "../data/users.ts";
+import { Config } from "../config/config.ts";
import crypto from "node:crypto";
import Logging from "@proxnet/undead-logging";
import { decode } from "@gz/jwt";
import z from "zod";
+import type { UserTokenFormat } from "../data/auth/TokenBaseFormat.ts";
const log = new Logging("UserRoute");
diff --git a/src/socket/route.ts b/src/socket/route.ts
index 5c7f5a6..53e8d5d 100644
--- a/src/socket/route.ts
+++ b/src/socket/route.ts
@@ -16,8 +16,8 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
import { APIUtils } from "../apiutils.ts";
-import { Config } from "../config.ts";
-import { AuthType } from "../data/users.ts";
+import { Config } from "../config/config.ts";
+import { AuthType } from "../data/UserTypes.ts";
import { SocketHandoff } from "./handoff.ts";
const config = Config.getConfig();
diff --git a/src/socket/socket.ts b/src/socket/socket.ts
index 239772c..01ee7cd 100644
--- a/src/socket/socket.ts
+++ b/src/socket/socket.ts
@@ -15,7 +15,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
-import { Profile } from "../data/profiles.ts";
+import type { Profile } from "../data/profile/base/profiles.ts";
import Logging from "@proxnet/undead-logging";
import {
CompletionMessage,
@@ -34,9 +34,8 @@ import {
import { SocketTarget } from "./targets/targetbase.ts";
import { PlayerSocketSubscriptionTarget } from "./targets/SubscribeToPlayers.ts";
import Presence from "../data/live/presence.ts";
-import Matchmaking from "../data/live/base.ts";
-const logmessages = false;
+const logmessages = true;
export class SignalRSocketHandler {
@@ -62,13 +61,11 @@ export class SignalRSocketHandler {
this.#Targets.set('SubscribeToPlayers', new PlayerSocketSubscriptionTarget(this));
+ for (const target of this.#Targets.values()) target.onInit();
+
this.#PeriodicalId = setInterval(async () => {
if (this.#killed) return;
- if (this.#socket.readyState !== this.#socket.CLOSED) {
- const pres = await Presence.get(this.#profile);
- this.sendNotification("PresenceUpdate", await pres.export());
- this.sendRaw({ type: 6 });
- }
+ if (this.#socket.readyState == this.#socket.OPEN) await this.presenceUpdate();
}, 8000);
this.#socket.onclose = (ev) => {
@@ -77,6 +74,12 @@ export class SignalRSocketHandler {
}
+ async presenceUpdate() {
+ const pres = await Presence.get(this.#profile);
+ this.sendNotification("PresenceUpdate", await pres.export());
+ this.sendRaw({ type: 6 });
+ }
+
async #dispatchTarget(target: string, args: unknown): Promise {
if (this.#killed) {
const error = "Tried to dispatch socket target on dead socket";
@@ -161,19 +164,24 @@ export class SignalRSocketHandler {
this.#socket.addEventListener('close', this.destroy(this, true));
}
- destroy(sock: SignalRSocketHandler, internal: boolean | undefined = false) {
- return () => {
- sock.#killed = true;
- clearInterval(sock.#PeriodicalId);
- sock.sendRaw({ type: 7, error: "Socket closed" });
- if (!internal) sock.#socket.close();
- sock.#log.i(`Closed socket`);
- sock.#profile.clearSocketHandler();
+ destroy(handler: SignalRSocketHandler, internal: boolean | undefined = false) {
+ return (ev: CloseEvent) => {
+ handler.#killed = true;
+ clearInterval(handler.#PeriodicalId);
- for (const target of sock.#Targets.values()) target.destroy();
+ let errorReason = "Socket closed by server";
+ this.#log.d(`Socket close code: ${ev.code}`);
+ if (ev.reason.includes('Bye!')) errorReason = "Socket closed by client request";
+
+ handler.sendRaw({ type: 7, error: errorReason });
+
+ if (!internal) handler.#socket.close();
+ handler.#log.i(`Closed socket`);
+ handler.#profile.clearSocketHandler();
+
+ for (const target of handler.#Targets.values()) target.onDestroy();
this.#profile.getInstance()?.removePlayer(this.#profile);
- Matchmaking.deleteLoginLock(this.#profile);
}
}
diff --git a/src/socket/targets/SubscribeToPlayers.ts b/src/socket/targets/SubscribeToPlayers.ts
index f94fb47..18d7cf3 100644
--- a/src/socket/targets/SubscribeToPlayers.ts
+++ b/src/socket/targets/SubscribeToPlayers.ts
@@ -17,10 +17,10 @@ along with this program. If not, see . */
import { z } from "zod";
import { SocketTarget } from "./targetbase.ts";
-import { SelfAccountExport } from "../../data/profiles.ts";
-import { ProfileEvents, ProfileUpdatedEvent } from "../../data/profileevents.ts";
+import type { Profile } from "../../data/profile/base/profiles.ts";
+import type { ProfileUpdatedEvent } from "../../data/profileevents.ts";
import { PushNotificationId } from "../types.ts";
-import Server from "../../data/server.ts";
+import Server from "../../data/server/server.ts";
const ArgumentSchema = z.object({
PlayerIds: z.array(z.number())
@@ -28,46 +28,30 @@ const ArgumentSchema = z.object({
export class PlayerSocketSubscriptionTarget extends SocketTarget {
- updateSocket(profile: SelfAccountExport) {
- this.socket.sendNotification(PushNotificationId.SubscriptionUpdateProfile, profile);
+ async updateSocket(profile: Profile) {
+ this.socket.sendNotification(PushNotificationId.SubscriptionUpdateProfile, await profile.export() || undefined);
}
- subscriptions: { id: number, callback: (ev: unknown) => void }[] = [];
-
- setSubscriptions(subs: number[]) {
-
- this.clearSubscriptions();
-
- for (const id of subs) {
- const profile = Server.UnifiedProfile.get(id);
- if (!profile) continue;
- this.subscriptions.push({ id: id, callback: profile
- .on(ProfileEvents.BaseUpdated, (async ev => {
- const exported = await ev.profile.export();
- if (exported) this.updateSocket(exported);
- }
- )) });
- }
+ subscriptions: number[] = [];
+ #callback = (ev: ProfileUpdatedEvent) => {
+ if (this.subscriptions.includes(ev.profile.getId()))
+ this.updateSocket(ev.profile);
}
- clearSubscriptions() {
- for (const sub of this.subscriptions) {
- const profile = Server.UnifiedProfile.get(sub.id);
- if (profile)
- profile.off(ProfileEvents.BaseUpdated, sub.callback);
- }
+ override onInit() {
+ Server.on('profile.updated', this.#callback);
}
- override destroy() {
- this.clearSubscriptions();
+ override onDestroy() {
+ Server.off('profile.updated', this.#callback);
}
// deno-lint-ignore require-await
override async exec(args: unknown) {
const parsed = ArgumentSchema.safeParse(args);
if (parsed.success) {
- this.setSubscriptions(parsed.data.PlayerIds);
+ this.subscriptions = parsed.data.PlayerIds;
return;
} else throw new Error("Invalid arguments");
}
diff --git a/src/socket/targets/targetbase.ts b/src/socket/targets/targetbase.ts
index 9dedfdb..e4949c8 100644
--- a/src/socket/targets/targetbase.ts
+++ b/src/socket/targets/targetbase.ts
@@ -15,7 +15,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
-import { SignalRSocketHandler } from "../socket.ts";
+import type { SignalRSocketHandler } from "../socket.ts";
export class SocketTarget {
@@ -25,7 +25,11 @@ export class SocketTarget {
this.socket = socket;
}
- destroy() {
+ onInit() {
+ return;
+ }
+
+ onDestroy() {
return;
}
diff --git a/src/types/express.ts b/src/types/express.ts
index 9ab4dec..c9b648e 100644
--- a/src/types/express.ts
+++ b/src/types/express.ts
@@ -15,7 +15,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
-import { Profile } from "../data/profiles.ts";
+import { Profile } from "../data/profile/base/profiles.ts";
import { User } from "../data/users.ts";
declare global {
diff --git a/src/types/http.ts b/src/types/http.ts
index 5b2908e..d63ccf2 100644
--- a/src/types/http.ts
+++ b/src/types/http.ts
@@ -15,7 +15,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
-import { ProfileTokenFormat } from "../data/profiles.ts";
+import type { ProfileTokenFormat } from "../data/auth/TokenBaseFormat.ts";
declare module 'node:http' {
interface IncomingMessage {
diff --git a/src/utils.ts b/src/utils.ts
new file mode 100644
index 0000000..de5290d
--- /dev/null
+++ b/src/utils.ts
@@ -0,0 +1,28 @@
+/* Galvanic Corrosion - Rec Room custom server for communities.
+
+Copyright (C) 2025 @zombieb (Discord / proxnet Gitea)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see . */
+
+export function generateRandomString(length: number) {
+ const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ let randomString = "";
+
+ for (let i = 0; i < length; i++) {
+ const randomIndex = Math.floor(Math.random() * characters.length);
+ randomString += characters.charAt(randomIndex);
+ }
+
+ return randomString;
+}
\ No newline at end of file
diff --git a/src/watchdog.ts b/src/watchdog.ts
new file mode 100644
index 0000000..2aa62d7
--- /dev/null
+++ b/src/watchdog.ts
@@ -0,0 +1,34 @@
+/* Galvanic Corrosion - Rec Room custom server for communities.
+
+Copyright (C) 2025 @zombieb (Discord / proxnet Gitea)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see . */
+
+import Logging, { LoggingConfiguration, LogTiming } from "@proxnet/undead-logging";
+
+const log = new Logging("Watchdog");
+
+let added = false;
+export function addWatchdogListener() {
+ if (added) return;
+ added = true;
+ Deno.addSignalListener('SIGINT', () => {
+ LoggingConfiguration.logTiming = LogTiming.Sync;
+
+ setTimeout(() => {
+ log.e(`Server took too long (60s) to shut down! Exiting.`);
+ Deno.exit(2);
+ }, 60000);
+ });
+}
\ No newline at end of file