diff --git a/USEntryCoach.Server/Program.cs b/USEntryCoach.Server/Program.cs index d553cc2..d9ffab1 100644 --- a/USEntryCoach.Server/Program.cs +++ b/USEntryCoach.Server/Program.cs @@ -51,7 +51,11 @@ builder.Services.AddAuthentication(config => builder.Services.AddAuthorization(options => { options.AddPolicy(nameof(UserRole.Developer), policy => policy.RequireRole(nameof(UserRole.Developer))); - options.AddPolicy(nameof(UserRole.User), policy => policy.RequireRole(nameof(UserRole.User))); + options.AddPolicy(nameof(UserRole.User), policy => + { + // Also allow Developers to do anything a user can do. + policy.RequireRole(nameof(UserRole.User), nameof(UserRole.Developer)); + }); }); var app = builder.Build(); @@ -112,7 +116,7 @@ async Task Login(LoginCredentials credentials, TokenService tokenServic var token = tokenService.GenerateToken(user); - return Results.Ok(new { user = user, token = token }); + return Results.Ok(new { token }); } app.MapGet("/developer", (ClaimsPrincipal user) => @@ -173,7 +177,7 @@ app.MapGet("/ephemeral_token", async () => } return null; -}).WithName("GetEphemeralToken"); +}).WithName("GetEphemeralToken").RequireAuthorization(nameof(UserRole.User)); app.MapFallbackToFile("/index.html"); diff --git a/usentrycoach.client/eslint.config.js b/usentrycoach.client/eslint.config.js index 092408a..67c3506 100644 --- a/usentrycoach.client/eslint.config.js +++ b/usentrycoach.client/eslint.config.js @@ -3,26 +3,56 @@ import globals from 'globals' import reactHooks from 'eslint-plugin-react-hooks' import reactRefresh from 'eslint-plugin-react-refresh' import tseslint from 'typescript-eslint' +import stylistic from '@stylistic/eslint-plugin' export default tseslint.config( - { ignores: ['dist'] }, - { - extends: [js.configs.recommended, ...tseslint.configs.recommended], - files: ['**/*.{ts,tsx}'], - languageOptions: { - ecmaVersion: 2020, - globals: globals.browser, + {ignores: ['dist']}, + { + extends: [ + js.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + ...tseslint.configs.stylisticTypeChecked, + ], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + //'stylistic/js': stylisticJs + stylistic + }, + rules: { + ...reactHooks.configs.recommended.rules, + //'brace-style': ['error', 'allman'], + 'react-refresh/only-export-components': [ + 'warn', + {allowConstantExport: true}, + ], + "@typescript-eslint/no-unused-vars": ["warn", { + "vars": "all", + "args": "all", + "caughtErrors": "all", + "ignoreRestSiblings": false, + "reportUsedIgnorePattern": false + }], + "prefer-const": ["warn", { + "destructuring": "any", + "ignoreReadBeforeAssign": false + }], + + // Formatting: + "stylistic/brace-style": [ + 'error', + 'allman', + { allowSingleLine: false } + ] + }, }, - plugins: { - 'react-hooks': reactHooks, - 'react-refresh': reactRefresh, - }, - rules: { - ...reactHooks.configs.recommended.rules, - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], - }, - }, ) diff --git a/usentrycoach.client/package-lock.json b/usentrycoach.client/package-lock.json index c2f4bdd..91e54be 100644 --- a/usentrycoach.client/package-lock.json +++ b/usentrycoach.client/package-lock.json @@ -13,6 +13,7 @@ }, "devDependencies": { "@eslint/js": "^9.25.0", + "@stylistic/eslint-plugin": "^4.2.0", "@types/node": "^20", "@types/react": "^19.1.2", "@types/react-dom": "^19.1.2", @@ -302,390 +303,6 @@ "node": ">=6.9.0" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", - "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", - "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", - "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", - "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", - "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", - "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", - "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", - "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", - "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", - "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", - "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", - "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", - "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", - "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", - "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", - "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", - "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", - "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", - "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", - "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", - "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", - "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", - "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", - "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@esbuild/win32-x64": { "version": "0.25.4", "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", @@ -1007,253 +624,6 @@ "node": ">= 8" } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.2.tgz", - "integrity": "sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.2.tgz", - "integrity": "sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.2.tgz", - "integrity": "sha512-Gzf1Hn2Aoe8VZzevHostPX23U7N5+4D36WJNHK88NZHCJr7aVMG4fadqkIf72eqVPGjGc0HJHNuUaUcxiR+N/w==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.2.tgz", - "integrity": "sha512-47N4hxa01a4x6XnJoskMKTS8XZ0CZMd8YTbINbi+w03A2w4j1RTlnGHOz/P0+Bg1LaVL6ufZyNprSg+fW5nYQQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.2.tgz", - "integrity": "sha512-8t6aL4MD+rXSHHZUR1z19+9OFJ2rl1wGKvckN47XFRVO+QL/dUSpKA2SLRo4vMg7ELA8pzGpC+W9OEd1Z/ZqoQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.2.tgz", - "integrity": "sha512-C+AyHBzfpsOEYRFjztcYUFsH4S7UsE9cDtHCtma5BK8+ydOZYgMmWg1d/4KBytQspJCld8ZIujFMAdKG1xyr4Q==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.2.tgz", - "integrity": "sha512-de6TFZYIvJwRNjmW3+gaXiZ2DaWL5D5yGmSYzkdzjBDS3W+B9JQ48oZEsmMvemqjtAFzE16DIBLqd6IQQRuG9Q==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.2.tgz", - "integrity": "sha512-urjaEZubdIkacKc930hUDOfQPysezKla/O9qV+O89enqsqUmQm8Xj8O/vh0gHg4LYfv7Y7UsE3QjzLQzDYN1qg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.2.tgz", - "integrity": "sha512-KlE8IC0HFOC33taNt1zR8qNlBYHj31qGT1UqWqtvR/+NuCVhfufAq9fxO8BMFC22Wu0rxOwGVWxtCMvZVLmhQg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.2.tgz", - "integrity": "sha512-j8CgxvfM0kbnhu4XgjnCWJQyyBOeBI1Zq91Z850aUddUmPeQvuAy6OiMdPS46gNFgy8gN1xkYyLgwLYZG3rBOg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.2.tgz", - "integrity": "sha512-Ybc/1qUampKuRF4tQXc7G7QY9YRyeVSykfK36Y5Qc5dmrIxwFhrOzqaVTNoZygqZ1ZieSWTibfFhQ5qK8jpWxw==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.2.tgz", - "integrity": "sha512-3FCIrnrt03CCsZqSYAOW/k9n625pjpuMzVfeI+ZBUSDT3MVIFDSPfSUgIl9FqUftxcUXInvFah79hE1c9abD+Q==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.2.tgz", - "integrity": "sha512-QNU7BFHEvHMp2ESSY3SozIkBPaPBDTsfVNGx3Xhv+TdvWXFGOSH2NJvhD1zKAT6AyuuErJgbdvaJhYVhVqrWTg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.2.tgz", - "integrity": "sha512-5W6vNYkhgfh7URiXTO1E9a0cy4fSgfE4+Hl5agb/U1sa0kjOLMLC1wObxwKxecE17j0URxuTrYZZME4/VH57Hg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.2.tgz", - "integrity": "sha512-B7LKIz+0+p348JoAL4X/YxGx9zOx3sR+o6Hj15Y3aaApNfAshK8+mWZEf759DXfRLeL2vg5LYJBB7DdcleYCoQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.2.tgz", - "integrity": "sha512-lG7Xa+BmBNwpjmVUbmyKxdQJ3Q6whHjMjzQplOs5Z+Gj7mxPtWakGHqzMqNER68G67kmCX9qX57aRsW5V0VOng==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.2.tgz", - "integrity": "sha512-tD46wKHd+KJvsmije4bUskNuvWKFcTOIM9tZ/RrmIvcXnbi0YK/cKS9FzFtAm7Oxi2EhV5N2OpfFB348vSQRXA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.2.tgz", - "integrity": "sha512-Bjv/HG8RRWLNkXwQQemdsWw4Mg+IJ29LK+bJPW2SCzPKOUaMmPEppQlu/Fqk1d7+DX3V7JbFdbkh/NMmurT6Pg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.2.tgz", - "integrity": "sha512-dt1llVSGEsGKvzeIO76HToiYPNPYPkmjhMHhP00T9S4rDern8P2ZWvWAQUEJ+R1UdMWJ/42i/QqJ2WV765GZcA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@rollup/rollup-win32-x64-msvc": { "version": "4.40.2", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.2.tgz", @@ -1267,6 +637,37 @@ "win32" ] }, + "node_modules/@stylistic/eslint-plugin": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-4.2.0.tgz", + "integrity": "sha512-8hXezgz7jexGHdo5WN6JBEIPHCSFyyU4vgbxevu4YLVS5vl+sxqAAGyXSzfNDyR6xMNSH5H1x67nsXcYMOHtZA==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "^8.23.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "estraverse": "^5.3.0", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": ">=9.0.0" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -2489,20 +1890,6 @@ "node": ">= 0.8" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", diff --git a/usentrycoach.client/package.json b/usentrycoach.client/package.json index fd2090b..ed1db9f 100644 --- a/usentrycoach.client/package.json +++ b/usentrycoach.client/package.json @@ -15,6 +15,8 @@ }, "devDependencies": { "@eslint/js": "^9.25.0", + "@stylistic/eslint-plugin": "^4.2.0", + "@types/node": "^20", "@types/react": "^19.1.2", "@types/react-dom": "^19.1.2", "@vitejs/plugin-react": "^4.4.1", @@ -24,7 +26,6 @@ "globals": "^16.0.0", "typescript": "~5.8.3", "typescript-eslint": "^8.30.1", - "vite": "^6.3.5", - "@types/node": "^20" + "vite": "^6.3.5" } -} \ No newline at end of file +} diff --git a/usentrycoach.client/src/App.tsx b/usentrycoach.client/src/App.tsx index 15ab88f..505a3db 100644 --- a/usentrycoach.client/src/App.tsx +++ b/usentrycoach.client/src/App.tsx @@ -1,415 +1,16 @@ -import { useEffect, useRef, useState } from 'react'; import './App.css'; import {ChatControl} from "./ChatClient/ChatControl.tsx"; +import Login from './Components/Login.tsx'; +import useLoginToken from "./Hooks/useLoginToken.tsx"; -interface Forecast { - date: string; - temperatureC: number; - temperatureF: number; - summary: string; -} +export default function App() +{ + const {token, setToken} = useLoginToken(); -interface OpenAiToken { - ephemeralToken: string; - expiresAt: number; -} - - -export default function App() { + if (!token) + { + return + } return - - // const [isSessionActive, setIsSessionActive] = useState(false); - // const [events, setEvents] = useState([]); - // const [dataChannel, setDataChannel] = useState(null); - // const peerConnection = useRef(null); - // const audioElement = useRef(null); - // - // async function startSession() { - // // Get a session token for OpenAI Realtime API - // const tokenResponse = await fetch("/ephemeral_token"); - // const data: OpenAiToken = await tokenResponse.json(); - // const EPHEMERAL_KEY = data.ephemeralToken; - // - // // Create a peer connection - // const pc = new RTCPeerConnection(); - // - // // Set up to play remote audio from the model - // audioElement.current = document.createElement("audio"); - // audioElement.current.autoplay = true; - // pc.ontrack = (e) => { - // if (audioElement?.current !== null) { - // audioElement.current.srcObject = e.streams[0] - // } - // }; - // - // // Add local audio track for microphone input in the browser - // const ms = await navigator.mediaDevices.getUserMedia({ - // audio: true, - // }); - // pc.addTrack(ms.getTracks()[0]); - // - // // Set up data channel for sending and receiving events - // const dc = pc.createDataChannel("oai-events"); - // setDataChannel(dc); - // - // // Start the session using the Session Description Protocol (SDP) - // const offer = await pc.createOffer(); - // await pc.setLocalDescription(offer); - // - // const baseUrl = "https://api.openai.com/v1/realtime"; - // const model = "gpt-4o-realtime-preview-2024-12-17"; - // const sdpResponse = await fetch(`${baseUrl}?model=${model}`, { - // method: "POST", - // body: offer.sdp, - // headers: { - // Authorization: `Bearer ${EPHEMERAL_KEY}`, - // "Content-Type": "application/sdp", - // }, - // }); - // - // const answer: RTCSessionDescriptionInit = { - // type: "answer", - // sdp: await sdpResponse.text(), - // }; - // await pc.setRemoteDescription(answer); - // - // peerConnection.current = pc; - // } - // - // // Stop current session, clean up peer connection and data channel - // function stopSession() { - // if (dataChannel) { - // dataChannel.close(); - // } - // - // if (peerConnection?.current !== null) - // { - // peerConnection.current.getSenders().forEach((sender) => { - // if (sender.track) { - // sender.track.stop(); - // } - // }); - // - // if (peerConnection.current) { - // peerConnection.current.close(); - // } - // } - // - // setIsSessionActive(false); - // setDataChannel(null); - // peerConnection.current = null; - // } - // - // // Send a message to the model - // function sendClientEvent(message) { - // if (dataChannel) { - // const timestamp = new Date().toLocaleTimeString(); - // message.event_id = message.event_id || crypto.randomUUID(); - // - // // send event before setting timestamp since the backend peer doesn't expect this field - // dataChannel.send(JSON.stringify(message)); - // - // // if guard just in case the timestamp exists by miracle - // if (!message.timestamp) { - // message.timestamp = timestamp; - // } - // setEvents((prev) => [message, ...prev]); - // } else { - // console.error( - // "Failed to send message - no data channel available", - // message, - // ); - // } - // } - // - // // Send a text message to the model - // function sendTextMessage(message) { - // const event = { - // type: "conversation.item.create", - // item: { - // type: "message", - // role: "user", - // content: [ - // { - // type: "input_text", - // text: message, - // }, - // ], - // }, - // }; - // - // sendClientEvent(event); - // sendClientEvent({ type: "response.create" }); - // } - // - // // Attach event listeners to the data channel when a new one is created - // useEffect(() => { - // if (dataChannel) { - // // Append new server events to the list - // dataChannel.addEventListener("message", (e) => { - // const event = JSON.parse(e.data); - // if (!event.timestamp) { - // event.timestamp = new Date().toLocaleTimeString(); - // } - // - // setEvents((prev) => [event, ...prev]); - // }); - // - // // Set session active when the data channel is opened - // dataChannel.addEventListener("open", () => { - // setIsSessionActive(true); - // setEvents([]); - // }); - // } - // }, [dataChannel]); - // - // return ( - // <> - // - //
- //
- //
- // - //
- //
- // - //
- //
- //
- // - //
- //
- // - // ); } - -// export default function App() { -// const [openAiToken, setOpenAiToken] = useState(); -// -// useEffect(() => { -// get_the_fucking_token_already(); -// }, []); -// -// const contents = openAiToken === undefined -// ?

Connecting to Open AI...

-// :
-// { -// openAiToken === null -// ?

Failed to connect to OpenAI, please reload the page.

-// :

-// {openAiToken.ephemeralToken}
-// {new Date(openAiToken.expiresAt * 1000).toLocaleString()} -//

-// } -//
; -// return ( -//
-//

Weather forecast

-//

This component demonstrates fetching data from the server.

-// {contents} -//
-// ); -// -// async function get_the_fucking_token_already() { -// const response = await fetch('ephemeral_token'); -// if (response.ok) { -// const data = await response.json(); -// setOpenAiToken(data); -// } -// } -// } -// -// function Chatter({ openAiToken }: { openAiToken: OpenAiToken }) { -// const [isSessionActive, setIsSessionActive] = useState(false); -// const [events, setEvents] = useState([]); -// const [dataChannel, setDataChannel] = useState(null); -// const peerConnection = useRef(null); -// const audioElement = useRef(null); -// -// async function startSession() { -// // Get a session token for OpenAI Realtime API -// const tokenResponse = await fetch("/token"); -// const data = await tokenResponse.json(); -// const EPHEMERAL_KEY = data.client_secret.value; -// -// // Create a peer connection -// const pc = new RTCPeerConnection(); -// -// // Set up to play remote audio from the model -// audioElement.current = document.createElement("audio"); -// audioElement.current.autoplay = true; -// pc.ontrack = (e) => (audioElement.current.srcObject = e.streams[0]); -// -// // Add local audio track for microphone input in the browser -// const ms = await navigator.mediaDevices.getUserMedia({ -// audio: true, -// }); -// pc.addTrack(ms.getTracks()[0]); -// -// // Set up data channel for sending and receiving events -// const dc = pc.createDataChannel("oai-events"); -// setDataChannel(dc); -// -// // Start the session using the Session Description Protocol (SDP) -// const offer = await pc.createOffer(); -// await pc.setLocalDescription(offer); -// -// const baseUrl = "https://api.openai.com/v1/realtime"; -// const model = "gpt-4o-realtime-preview-2024-12-17"; -// const sdpResponse = await fetch(`${baseUrl}?model=${model}`, { -// method: "POST", -// body: offer.sdp, -// headers: { -// Authorization: `Bearer ${EPHEMERAL_KEY}`, -// "Content-Type": "application/sdp", -// }, -// }); -// -// const answer = { -// type: "answer", -// sdp: await sdpResponse.text(), -// }; -// await pc.setRemoteDescription(answer); -// -// peerConnection.current = pc; -// } -// -// // Stop current session, clean up peer connection and data channel -// function stopSession() { -// if (dataChannel) { -// dataChannel.close(); -// } -// -// peerConnection.current.getSenders().forEach((sender) => { -// if (sender.track) { -// sender.track.stop(); -// } -// }); -// -// if (peerConnection.current) { -// peerConnection.current.close(); -// } -// -// setIsSessionActive(false); -// setDataChannel(null); -// peerConnection.current = null; -// } -// -// // Send a message to the model -// function sendClientEvent(message) { -// if (dataChannel) { -// const timestamp = new Date().toLocaleTimeString(); -// message.event_id = message.event_id || crypto.randomUUID(); -// -// // send event before setting timestamp since the backend peer doesn't expect this field -// dataChannel.send(JSON.stringify(message)); -// -// // if guard just in case the timestamp exists by miracle -// if (!message.timestamp) { -// message.timestamp = timestamp; -// } -// setEvents((prev) => [message, ...prev]); -// } else { -// console.error( -// "Failed to send message - no data channel available", -// message, -// ); -// } -// } -// -// // Send a text message to the model -// function sendTextMessage(message) { -// const event = { -// type: "conversation.item.create", -// item: { -// type: "message", -// role: "user", -// content: [ -// { -// type: "input_text", -// text: message, -// }, -// ], -// }, -// }; -// -// sendClientEvent(event); -// sendClientEvent({ type: "response.create" }); -// } -// -// // Attach event listeners to the data channel when a new one is created -// useEffect(() => { -// if (dataChannel) { -// // Append new server events to the list -// dataChannel.addEventListener("message", (e) => { -// const event = JSON.parse(e.data); -// if (!event.timestamp) { -// event.timestamp = new Date().toLocaleTimeString(); -// } -// -// setEvents((prev) => [event, ...prev]); -// }); -// -// // Set session active when the data channel is opened -// dataChannel.addEventListener("open", () => { -// setIsSessionActive(true); -// setEvents([]); -// }); -// } -// }, [dataChannel]); -// -// return ( -// <> -// -//
-//
-//
-// -//
-//
-// -//
-//
-//
-// -//
-//
-// -// ); -// } - diff --git a/usentrycoach.client/src/ChatClient/ChatControl.tsx b/usentrycoach.client/src/ChatClient/ChatControl.tsx index e84a710..2a82471 100644 --- a/usentrycoach.client/src/ChatClient/ChatControl.tsx +++ b/usentrycoach.client/src/ChatClient/ChatControl.tsx @@ -1,10 +1,14 @@ import {useState, useRef, useEffect} from 'react'; +import useLoginToken from "../Hooks/useLoginToken.tsx"; +import { z } from 'zod'; -function SessionActive({stopSession} : {stopSession: () => void}) { +function SessionActive({stopSession} : {stopSession: () => void}) +{ return } -function SessionStopped({startSession} : {startSession: () => void}) { +function SessionStopped({startSession} : {startSession: () => void}) +{ return } @@ -15,26 +19,37 @@ function SessionControl( { isSessionActive, startSession, stopSession }: { isSes : ); } -interface OpenAiToken { - ephemeralToken: string; - expiresAt: number; -} +const EphemeralTokenResponse = z.object({ + ephemeralToken: z.string(), + expiresAt: z.number(), +}); + +export function ChatControl() +{ + const { token } = useLoginToken(); -export function ChatControl() { const [isSessionActive, setSessionActive] = useState(false); const [dataChannel, setDataChannel] = useState(null); const audioElement = useRef(null); const peerConnection = useRef(null); - async function StartSession() { + async function StartSession() + { // Get a session token for OpenAI Realtime API - const response = await fetch('ephemeral_token'); - if (!response.ok) { + const response = await fetch('ephemeral_token', { + method: "GET", + headers: { + Authorization: `Bearer ${token}asdasd` + }, + }); + if (!response.ok) + { throw new Error(response.statusText); } - const data: OpenAiToken = await response.json(); - const ephemeralToken = data.ephemeralToken; + const responseJson:unknown = await response.json(); + const parsedToken = EphemeralTokenResponse.parse(responseJson); + const ephemeralToken = parsedToken.ephemeralToken; // Create a peer connection const pc = new RTCPeerConnection(); @@ -42,7 +57,8 @@ export function ChatControl() { // Set up to play remote audio from the model audioElement.current = document.createElement("audio"); audioElement.current.autoplay = true; - pc.ontrack = (e) => { + pc.ontrack = (e) => + { if (audioElement.current !== null) { audioElement.current.srcObject = e.streams[0]; @@ -82,8 +98,10 @@ export function ChatControl() { peerConnection.current = pc; } - function stopSession() { - if (dataChannel) { + function stopSession() + { + if (dataChannel) + { dataChannel.close(); setDataChannel(null); } @@ -97,13 +115,16 @@ export function ChatControl() { setSessionActive(false); } - useEffect(() => { - if (dataChannel) { - dataChannel.addEventListener("open", () => { + useEffect(() => + { + if (dataChannel) + { + dataChannel.addEventListener("open", () => + { setSessionActive(true); }); } }, [dataChannel]) - return ; + return void StartSession()} stopSession={stopSession}/>; } \ No newline at end of file diff --git a/usentrycoach.client/src/Components/Login.tsx b/usentrycoach.client/src/Components/Login.tsx new file mode 100644 index 0000000..b2eed8d --- /dev/null +++ b/usentrycoach.client/src/Components/Login.tsx @@ -0,0 +1,60 @@ +import { useState, type FormEvent } from 'react'; +import { z } from 'zod'; + +export default function Login({ setToken } : {setToken: (token: string) => void}) +{ + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + + async function doLogin(event: FormEvent) + { + event.preventDefault(); + + const token = await loginUser({username, password}); + + setToken(token); + } + + return ( +
+
void doLogin(e)} method="action"> + + + + +
+
+ ); +} + +const LoginResponse = z.object({ + token: z.string() +}); + +async function loginUser(credentials : {username: string, password: string}): Promise +{ + // Get a session token for OpenAI Realtime API + const response = await fetch('login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(credentials) + }); + + if (!response.ok) + { + throw new Error(response.statusText); + } + + const responseJson:unknown = await response.json(); + const parsedToken = LoginResponse.parse(responseJson); + + return parsedToken.token; +} diff --git a/usentrycoach.client/src/Hooks/useLoginToken.tsx b/usentrycoach.client/src/Hooks/useLoginToken.tsx new file mode 100644 index 0000000..2f42618 --- /dev/null +++ b/usentrycoach.client/src/Hooks/useLoginToken.tsx @@ -0,0 +1,27 @@ +import {useState} from "react"; + +/** + * A React hook that allows to access the session token (which might be in session or local storage). + * It also allows to set the token. + */ +export default function useLoginToken() +{ + const getToken = () => + { + const tokenJson = sessionStorage.getItem('token'); + + return tokenJson ? JSON.parse(tokenJson) as string : null; + }; + + const [token, setToken] = useState(getToken()); + + const saveToken = (token: string): void => + { + sessionStorage.setItem('token', JSON.stringify(token)); + setToken(token); + } + + // In React it is common for hooks to return arrays. However, returning an object allows for partial destruction + // (that is, user can grab only the values they need). + return { token, setToken: saveToken }; +} diff --git a/usentrycoach.client/vite.config.ts b/usentrycoach.client/vite.config.ts index 21a9418..474e604 100644 --- a/usentrycoach.client/vite.config.ts +++ b/usentrycoach.client/vite.config.ts @@ -1,6 +1,6 @@ import { fileURLToPath, URL } from 'node:url'; -import { defineConfig } from 'vite'; +import { defineConfig, type ProxyOptions } from 'vite'; import plugin from '@vitejs/plugin-react'; import fs from 'fs'; import path from 'path'; @@ -16,11 +16,13 @@ const certificateName = "usentrycoach.client"; const certFilePath = path.join(baseFolder, `${certificateName}.pem`); const keyFilePath = path.join(baseFolder, `${certificateName}.key`); -if (!fs.existsSync(baseFolder)) { +if (!fs.existsSync(baseFolder)) +{ fs.mkdirSync(baseFolder, { recursive: true }); } -if (!fs.existsSync(certFilePath) || !fs.existsSync(keyFilePath)) { +if (!fs.existsSync(certFilePath) || !fs.existsSync(keyFilePath)) +{ if (0 !== child_process.spawnSync('dotnet', [ 'dev-certs', 'https', @@ -29,7 +31,8 @@ if (!fs.existsSync(certFilePath) || !fs.existsSync(keyFilePath)) { '--format', 'Pem', '--no-password', - ], { stdio: 'inherit', }).status) { + ], { stdio: 'inherit', }).status) +{ throw new Error("Could not create certificate."); } } @@ -37,6 +40,23 @@ if (!fs.existsSync(certFilePath) || !fs.existsSync(keyFilePath)) { const target = env.ASPNETCORE_HTTPS_PORT ? `https://localhost:${env.ASPNETCORE_HTTPS_PORT}` : env.ASPNETCORE_URLS ? env.ASPNETCORE_URLS.split(';')[0] : 'https://localhost:7085'; +// Define a list of all existing backend routes. +const backendRoutes = ['/login', '/ephemeral_token']; + +// For development, we have a node.js server running that delivers our frontend. +// We have to configure proxies for the backend calls, so that node.js will forward them to the backend. +const proxyConfig = backendRoutes.reduce((acc, path) => +{ + acc[path] = { + target: target, + // disable certificate verification because the development certificate is self-signed + secure: false, + changeOrigin: true + } + return acc; +}, {} as Record); + + // https://vitejs.dev/config/ export default defineConfig({ plugins: [plugin()], @@ -46,13 +66,8 @@ export default defineConfig({ } }, server: { - proxy: { - '^/ephemeral_token': { - target, - secure: false - } - }, - port: parseInt(env.DEV_SERVER_PORT || '54044'), + proxy: proxyConfig, + port: parseInt(env.DEV_SERVER_PORT ?? '54044'), https: { key: fs.readFileSync(keyFilePath), cert: fs.readFileSync(certFilePath),