From 89d944cd6e5e9a80da47b0564c7ecbee2dc25b7d Mon Sep 17 00:00:00 2001 From: Tom Date: Thu, 17 Oct 2024 08:48:15 +0000 Subject: [PATCH] Twitch login, TTS Login, and basic policies ui. --- angular.json | 10 +- package-lock.json | 467 ++++++++++++------ package.json | 8 +- src/app/app.component.html | 338 +------------ src/app/app.component.scss | 4 + src/app/app.component.ts | 57 ++- src/app/app.config.server.ts | 4 +- src/app/app.config.ts | 18 +- src/app/app.routes.ts | 26 +- src/app/hermes-client.service.spec.ts | 16 + src/app/hermes-client.service.ts | 105 ++++ src/app/hermes-socket.service.spec.ts | 16 + src/app/hermes-socket.service.ts | 52 ++ src/app/login/login.component.html | 21 + src/app/login/login.component.scss | 13 + src/app/login/login.component.spec.ts | 23 + src/app/login/login.component.ts | 33 ++ src/app/navigation/navigation.component.html | 28 ++ src/app/navigation/navigation.component.scss | 37 ++ .../navigation/navigation.component.spec.ts | 23 + src/app/navigation/navigation.component.ts | 24 + .../policy-add-form.component.html | 26 + .../policy-add-form.component.scss | 0 .../policy-add-form.component.spec.ts | 23 + .../policy-add-form.component.ts | 75 +++ .../policy-table/policy-table.component.html | 48 ++ .../policy-table/policy-table.component.scss | 0 .../policy-table.component.spec.ts | 23 + .../policy-table/policy-table.component.ts | 66 +++ src/app/policy/policy.component.html | 8 + src/app/policy/policy.component.scss | 0 src/app/policy/policy.component.spec.ts | 23 + src/app/policy/policy.component.ts | 53 ++ src/app/shared/auth/auth.guard.ts | 21 + src/app/shared/models/policy.ts | 10 + src/app/shared/services/EventService.ts | 21 + .../api/api-authentication.service.spec.ts | 16 + .../api/api-authentication.service.ts | 42 ++ src/app/tts-login/tts-login.component.html | 13 + src/app/tts-login/tts-login.component.scss | 0 src/app/tts-login/tts-login.component.spec.ts | 23 + src/app/tts-login/tts-login.component.ts | 55 +++ .../twitch-auth-callback.component.html | 0 .../twitch-auth-callback.component.scss | 0 .../twitch-auth-callback.component.spec.ts | 23 + .../twitch-auth-callback.component.ts | 48 ++ src/index.html | 4 +- src/styles.scss | 3 + 48 files changed, 1443 insertions(+), 504 deletions(-) create mode 100644 src/app/hermes-client.service.spec.ts create mode 100644 src/app/hermes-client.service.ts create mode 100644 src/app/hermes-socket.service.spec.ts create mode 100644 src/app/hermes-socket.service.ts create mode 100644 src/app/login/login.component.html create mode 100644 src/app/login/login.component.scss create mode 100644 src/app/login/login.component.spec.ts create mode 100644 src/app/login/login.component.ts create mode 100644 src/app/navigation/navigation.component.html create mode 100644 src/app/navigation/navigation.component.scss create mode 100644 src/app/navigation/navigation.component.spec.ts create mode 100644 src/app/navigation/navigation.component.ts create mode 100644 src/app/policy-add-form/policy-add-form.component.html create mode 100644 src/app/policy-add-form/policy-add-form.component.scss create mode 100644 src/app/policy-add-form/policy-add-form.component.spec.ts create mode 100644 src/app/policy-add-form/policy-add-form.component.ts create mode 100644 src/app/policy-table/policy-table.component.html create mode 100644 src/app/policy-table/policy-table.component.scss create mode 100644 src/app/policy-table/policy-table.component.spec.ts create mode 100644 src/app/policy-table/policy-table.component.ts create mode 100644 src/app/policy/policy.component.html create mode 100644 src/app/policy/policy.component.scss create mode 100644 src/app/policy/policy.component.spec.ts create mode 100644 src/app/policy/policy.component.ts create mode 100644 src/app/shared/auth/auth.guard.ts create mode 100644 src/app/shared/models/policy.ts create mode 100644 src/app/shared/services/EventService.ts create mode 100644 src/app/shared/services/api/api-authentication.service.spec.ts create mode 100644 src/app/shared/services/api/api-authentication.service.ts create mode 100644 src/app/tts-login/tts-login.component.html create mode 100644 src/app/tts-login/tts-login.component.scss create mode 100644 src/app/tts-login/tts-login.component.spec.ts create mode 100644 src/app/tts-login/tts-login.component.ts create mode 100644 src/app/twitch-auth-callback/twitch-auth-callback.component.html create mode 100644 src/app/twitch-auth-callback/twitch-auth-callback.component.scss create mode 100644 src/app/twitch-auth-callback/twitch-auth-callback.component.spec.ts create mode 100644 src/app/twitch-auth-callback/twitch-auth-callback.component.ts diff --git a/angular.json b/angular.json index 4c55ecf..2c425b7 100644 --- a/angular.json +++ b/angular.json @@ -32,6 +32,7 @@ } ], "styles": [ + "@angular/material/prebuilt-themes/azure-blue.css", "src/styles.scss" ], "scripts": [], @@ -60,7 +61,13 @@ "development": { "optimization": false, "extractLicenses": false, - "sourceMap": true + "sourceMap": true, + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.development.ts" + } + ] } }, "defaultConfiguration": "production" @@ -96,6 +103,7 @@ } ], "styles": [ + "@angular/material/prebuilt-themes/azure-blue.css", "src/styles.scss" ], "scripts": [] diff --git a/package-lock.json b/package-lock.json index e435f63..4dba9c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,18 +9,24 @@ "version": "0.0.0", "dependencies": { "@angular/animations": "^18.0.0", + "@angular/cdk": "^18.2.8", "@angular/common": "^18.0.0", "@angular/compiler": "^18.0.0", "@angular/core": "^18.0.0", "@angular/forms": "^18.0.0", + "@angular/material": "^18.2.8", "@angular/platform-browser": "^18.0.0", "@angular/platform-browser-dynamic": "^18.0.0", "@angular/platform-server": "^18.0.0", "@angular/router": "^18.0.0", "@angular/ssr": "^18.0.5", + "angular-oauth2-oidc": "^17.0.2", "express": "^4.18.2", + "ngx-socket-io": "^4.7.0", "rxjs": "~7.8.0", + "rxjs-websockets": "^9.0.0", "tslib": "^2.3.0", + "uuidv4": "^6.2.13", "zone.js": "~0.14.3" }, "devDependencies": { @@ -358,6 +364,23 @@ } } }, + "node_modules/@angular/cdk": { + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.8.tgz", + "integrity": "sha512-J8A2FkwTBzLleAEWz6EgW73dEoeq87GREBPjTv8+2JV09LX+V3hnbgNk6zWq5k4OXtQNg9WrWP9QyRbUyA597g==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "optionalDependencies": { + "parse5": "^7.1.2" + }, + "peerDependencies": { + "@angular/common": "^18.0.0 || ^19.0.0", + "@angular/core": "^18.0.0 || ^19.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@angular/cli": { "version": "18.0.7", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.0.7.tgz", @@ -555,6 +578,24 @@ "rxjs": "^6.5.3 || ^7.4.0" } }, + "node_modules/@angular/material": { + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-18.2.8.tgz", + "integrity": "sha512-wQGMVsfQ9lQfih2VsWAvV4z3S3uBxrxc61owlE+K0T1BxH9u/jo3A/rnRitIdvR/L4NnYlfhCnmrW9K+Pl+WCg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/animations": "^18.0.0 || ^19.0.0", + "@angular/cdk": "18.2.8", + "@angular/common": "^18.0.0 || ^19.0.0", + "@angular/core": "^18.0.0 || ^19.0.0", + "@angular/forms": "^18.0.0 || ^19.0.0", + "@angular/platform-browser": "^18.0.0 || ^19.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@angular/platform-browser": { "version": "18.0.6", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.0.6.tgz", @@ -3850,9 +3891,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", - "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", + "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", "cpu": [ "arm" ], @@ -3864,9 +3905,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", - "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", + "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", "cpu": [ "arm64" ], @@ -3878,9 +3919,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", - "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", + "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", "cpu": [ "arm64" ], @@ -3892,9 +3933,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", - "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", + "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", "cpu": [ "x64" ], @@ -3906,9 +3947,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", - "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", + "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", "cpu": [ "arm" ], @@ -3920,9 +3961,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", - "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", + "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", "cpu": [ "arm" ], @@ -3934,9 +3975,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", - "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", + "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", "cpu": [ "arm64" ], @@ -3948,9 +3989,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", - "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", + "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", "cpu": [ "arm64" ], @@ -3962,9 +4003,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", - "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", + "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", "cpu": [ "ppc64" ], @@ -3976,9 +4017,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", - "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", + "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", "cpu": [ "riscv64" ], @@ -3990,9 +4031,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", - "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", + "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", "cpu": [ "s390x" ], @@ -4004,9 +4045,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", - "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", + "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", "cpu": [ "x64" ], @@ -4018,9 +4059,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", - "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz", + "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==", "cpu": [ "x64" ], @@ -4032,9 +4073,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", - "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", + "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", "cpu": [ "arm64" ], @@ -4046,9 +4087,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", - "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", + "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", "cpu": [ "ia32" ], @@ -4060,9 +4101,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", - "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", + "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", "cpu": [ "x64" ], @@ -4174,7 +4215,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", - "dev": true, "license": "MIT" }, "node_modules/@tufjs/canonical-json": { @@ -4273,14 +4313,12 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", - "dev": true, "license": "MIT" }, "node_modules/@types/cors": { "version": "2.8.17", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -4309,9 +4347,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true, "license": "MIT" }, @@ -4383,7 +4421,6 @@ "version": "18.19.39", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.39.tgz", "integrity": "sha512-nPwTRDKUctxw3di5b4TfT3I0sWDiWoPQCZjXhvdkINntwr8lcoVCKsTgnXeRubKIlfnV+eN/HYk6Jb40tbcEAQ==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~5.26.4" @@ -4463,6 +4500,12 @@ "@types/node": "*" } }, + "node_modules/@types/uuid": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", + "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", + "license": "MIT" + }, "node_modules/@types/ws": { "version": "8.5.10", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", @@ -4818,6 +4861,19 @@ "ajv": "^8.8.2" } }, + "node_modules/angular-oauth2-oidc": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/angular-oauth2-oidc/-/angular-oauth2-oidc-17.0.2.tgz", + "integrity": "sha512-zYgeLmAnu1g8XAYZK+csAsCQBDhgp9ffBv/eArEnujGxNPTeK00bREHWObtehflpQdSn+k9rY2D15ChCSydyVw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.5.2" + }, + "peerDependencies": { + "@angular/common": ">=14.0.0", + "@angular/core": ">=14.0.0" + } + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -5060,7 +5116,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", - "dev": true, "license": "MIT", "engines": { "node": "^4.5.0 || >= 5.9" @@ -5109,9 +5164,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "license": "MIT", "dependencies": { "bytes": "3.1.2", @@ -5122,7 +5177,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -5844,9 +5899,9 @@ "license": "MIT" }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -5909,6 +5964,17 @@ "node": ">=10.13.0" } }, + "node_modules/core-js": { + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.1.tgz", + "integrity": "sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/core-js-compat": { "version": "3.37.1", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.1.tgz", @@ -5934,7 +6000,6 @@ "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dev": true, "license": "MIT", "dependencies": { "object-assign": "^4", @@ -6185,7 +6250,6 @@ "version": "4.3.5", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, "license": "MIT", "dependencies": { "ms": "2.1.2" @@ -6491,10 +6555,9 @@ } }, "node_modules/engine.io": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", - "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", - "dev": true, + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", + "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", "license": "MIT", "dependencies": { "@types/cookie": "^0.4.1", @@ -6502,7 +6565,7 @@ "@types/node": ">=10.0.0", "accepts": "~1.3.4", "base64id": "2.0.0", - "cookie": "~0.4.1", + "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", @@ -6512,26 +6575,28 @@ "node": ">=10.2.0" } }, + "node_modules/engine.io-client": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz", + "integrity": "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.0.0" + } + }, "node_modules/engine.io-parser": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", - "dev": true, "license": "MIT", "engines": { "node": ">=10.0.0" } }, - "node_modules/engine.io/node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/enhanced-resolve": { "version": "5.17.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz", @@ -6833,37 +6898,37 @@ "license": "Apache-2.0" }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -6874,6 +6939,15 @@ "node": ">= 0.10.0" } }, + "node_modules/express/node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -6883,6 +6957,15 @@ "ms": "2.0.0" } }, + "node_modules/express/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -6979,13 +7062,13 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "license": "MIT", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -7005,6 +7088,15 @@ "ms": "2.0.0" } }, + "node_modules/finalhandler/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -9114,10 +9206,13 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "license": "MIT" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -9146,9 +9241,9 @@ } }, "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", "dependencies": { @@ -9468,7 +9563,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, "license": "MIT" }, "node_modules/msgpackr": { @@ -9610,6 +9704,31 @@ "dev": true, "license": "MIT" }, + "node_modules/ngx-socket-io": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/ngx-socket-io/-/ngx-socket-io-4.7.0.tgz", + "integrity": "sha512-2uwc5OWPOXM2EmiTrsMpWKm97gE6K3Zuzo1JeScncWQ3r/VxLQuGismVXtGF4Dv3h1Orvurt3ISiNztqOuL3gQ==", + "license": "MIT", + "dependencies": { + "core-js": "^3.0.0", + "reflect-metadata": "^0.1.10", + "socket.io": "^4.7.2", + "socket.io-client": "^4.7.2", + "tslib": "^2.3.0", + "zone.js": "~0.14.0" + }, + "peerDependencies": { + "@angular/common": "^18.0.0", + "@angular/core": "^18.0.0", + "rxjs": "^7.0.0" + } + }, + "node_modules/ngx-socket-io/node_modules/reflect-metadata": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", + "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==", + "license": "Apache-2.0" + }, "node_modules/nice-napi": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", @@ -9978,7 +10097,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -10354,7 +10472,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "entities": "^4.4.0" @@ -10465,9 +10583,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", "license": "MIT" }, "node_modules/path-type": { @@ -10763,12 +10881,12 @@ } }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -11121,13 +11239,13 @@ } }, "node_modules/rollup": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", - "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", + "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.5" + "@types/estree": "1.0.6" }, "bin": { "rollup": "dist/bin/rollup" @@ -11137,22 +11255,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.18.0", - "@rollup/rollup-android-arm64": "4.18.0", - "@rollup/rollup-darwin-arm64": "4.18.0", - "@rollup/rollup-darwin-x64": "4.18.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", - "@rollup/rollup-linux-arm-musleabihf": "4.18.0", - "@rollup/rollup-linux-arm64-gnu": "4.18.0", - "@rollup/rollup-linux-arm64-musl": "4.18.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", - "@rollup/rollup-linux-riscv64-gnu": "4.18.0", - "@rollup/rollup-linux-s390x-gnu": "4.18.0", - "@rollup/rollup-linux-x64-gnu": "4.18.0", - "@rollup/rollup-linux-x64-musl": "4.18.0", - "@rollup/rollup-win32-arm64-msvc": "4.18.0", - "@rollup/rollup-win32-ia32-msvc": "4.18.0", - "@rollup/rollup-win32-x64-msvc": "4.18.0", + "@rollup/rollup-android-arm-eabi": "4.24.0", + "@rollup/rollup-android-arm64": "4.24.0", + "@rollup/rollup-darwin-arm64": "4.24.0", + "@rollup/rollup-darwin-x64": "4.24.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", + "@rollup/rollup-linux-arm-musleabihf": "4.24.0", + "@rollup/rollup-linux-arm64-gnu": "4.24.0", + "@rollup/rollup-linux-arm64-musl": "4.24.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", + "@rollup/rollup-linux-riscv64-gnu": "4.24.0", + "@rollup/rollup-linux-s390x-gnu": "4.24.0", + "@rollup/rollup-linux-x64-gnu": "4.24.0", + "@rollup/rollup-linux-x64-musl": "4.24.0", + "@rollup/rollup-win32-arm64-msvc": "4.24.0", + "@rollup/rollup-win32-ia32-msvc": "4.24.0", + "@rollup/rollup-win32-x64-msvc": "4.24.0", "fsevents": "~2.3.2" } }, @@ -11212,6 +11330,15 @@ "tslib": "^2.1.0" } }, + "node_modules/rxjs-websockets": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/rxjs-websockets/-/rxjs-websockets-9.0.0.tgz", + "integrity": "sha512-COglkihJWOYqVeQOn00xShYGb8KZ9va0eoTKmxV76D3j6LuM0S8RKBUcZSq+ebiSu1ZiS6tr2OEJnQL78ZB9MA==", + "license": "ISC", + "peerDependencies": { + "rxjs": "^7.0.0" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -11378,9 +11505,9 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "license": "MIT", "dependencies": { "debug": "2.6.9", @@ -11531,20 +11658,29 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "license": "MIT", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" } }, + "node_modules/serve-static/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -11682,17 +11818,16 @@ } }, "node_modules/socket.io": { - "version": "4.7.5", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", - "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", - "dev": true, + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.0.tgz", + "integrity": "sha512-8U6BEgGjQOfGz3HHTYaC/L1GaxDCJ/KM0XTkJly0EhZ5U/du9uNEZy4ZgYzEzIqlx2CMm25CrCqr1ck899eLNA==", "license": "MIT", "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.3.2", - "engine.io": "~6.5.2", + "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" }, @@ -11704,18 +11839,31 @@ "version": "2.5.5", "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", - "dev": true, "license": "MIT", "dependencies": { "debug": "~4.3.4", "ws": "~8.17.1" } }, + "node_modules/socket.io-client": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz", + "integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/socket.io-parser": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", - "dev": true, "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", @@ -12456,7 +12604,6 @@ "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true, "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { @@ -12619,12 +12766,21 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } }, + "node_modules/uuidv4": { + "version": "6.2.13", + "resolved": "https://registry.npmjs.org/uuidv4/-/uuidv4-6.2.13.tgz", + "integrity": "sha512-AXyzMjazYB3ovL3q051VLH06Ixj//Knx7QnUSi1T//Ie3io6CpsPu9nVMOx5MoLWh6xV0B9J0hIaxungxXUbPQ==", + "license": "MIT", + "dependencies": { + "@types/uuid": "8.3.4", + "uuid": "8.3.2" + } + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -13751,7 +13907,6 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=10.0.0" @@ -13778,6 +13933,14 @@ "node": ">= 6" } }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 588f9ee..93b29cd 100644 --- a/package.json +++ b/package.json @@ -12,18 +12,24 @@ "private": true, "dependencies": { "@angular/animations": "^18.0.0", + "@angular/cdk": "^18.2.8", "@angular/common": "^18.0.0", "@angular/compiler": "^18.0.0", "@angular/core": "^18.0.0", "@angular/forms": "^18.0.0", + "@angular/material": "^18.2.8", "@angular/platform-browser": "^18.0.0", "@angular/platform-browser-dynamic": "^18.0.0", "@angular/platform-server": "^18.0.0", "@angular/router": "^18.0.0", "@angular/ssr": "^18.0.5", + "angular-oauth2-oidc": "^17.0.2", "express": "^4.18.2", + "ngx-socket-io": "^4.7.0", "rxjs": "~7.8.0", + "rxjs-websockets": "^9.0.0", "tslib": "^2.3.0", + "uuidv4": "^6.2.13", "zone.js": "~0.14.3" }, "devDependencies": { @@ -41,4 +47,4 @@ "karma-jasmine-html-reporter": "~2.1.0", "typescript": "~5.4.2" } -} \ No newline at end of file +} diff --git a/src/app/app.component.html b/src/app/app.component.html index 36093e1..bf4f620 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,336 +1,4 @@ - - - - - - - - - - -
-
-
- -

Hello, {{ title }}

-

Congratulations! Your app is running. 🎉

-
- -
-
- @for (item of [ - { title: 'Explore the Docs', link: 'https://angular.dev' }, - { title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' }, - { title: 'CLI Docs', link: 'https://angular.dev/tools/cli' }, - { title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' }, - { title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' }, - ]; track item.title) { - - {{ item.title }} - - - - - } -
- -
-
-
- - - - - - - - - - - + + + \ No newline at end of file diff --git a/src/app/app.component.scss b/src/app/app.component.scss index e69de29..da0455e 100644 --- a/src/app/app.component.scss +++ b/src/app/app.component.scss @@ -0,0 +1,4 @@ +.main { + display: grid; + grid-template-columns: 20em 0px 1fr; +} \ No newline at end of file diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 121b12b..d816890 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,13 +1,50 @@ -import { Component } from '@angular/core'; -import { RouterOutlet } from '@angular/router'; +import { CommonModule, DatePipe, isPlatformBrowser } from '@angular/common'; +import { Component, OnInit, Inject, PLATFORM_ID, NgZone, OnDestroy } from '@angular/core'; +import { Router, RouterModule, RouterOutlet, Routes } from '@angular/router'; +import { FormsModule } from '@angular/forms' +import { HermesClientService } from './hermes-client.service'; +import { AuthGuard } from './shared/auth/auth.guard' +import { Subscription } from 'rxjs'; +import { Policy, PolicyScope } from './shared/models/policy'; +import { PolicyComponent } from "./policy/policy.component"; +import { NavigationComponent } from "./navigation/navigation.component"; +import EventService from './shared/services/EventService'; +import { HttpClient } from '@angular/common/http'; +import { ApiAuthenticationService } from './shared/services/api/api-authentication.service'; @Component({ - selector: 'app-root', - standalone: true, - imports: [RouterOutlet], - templateUrl: './app.component.html', - styleUrl: './app.component.scss' + selector: 'app-root', + standalone: true, + imports: [RouterOutlet, CommonModule, FormsModule, PolicyComponent, NavigationComponent], + providers: [AuthGuard], + templateUrl: './app.component.html', + styleUrl: './app.component.scss' }) -export class AppComponent { - title = 'hermes-web-angular'; -} +export class AppComponent implements OnInit, OnDestroy { + private isBrowser: boolean; + private ngZone: NgZone; + private subscription: Subscription | undefined; + pipe = new DatePipe('en-US') + + + constructor(private auth: ApiAuthenticationService, private client: HermesClientService, private events: EventService, private http: HttpClient, ngZone: NgZone, @Inject(PLATFORM_ID) private platformId: Object) { + this.ngZone = ngZone; + this.isBrowser = isPlatformBrowser(this.platformId) + } + + ngOnInit(): void { + if (!this.isBrowser) + return; + + this.auth.update(); + + const rand = Math.random() * 100000 | 0; + this.client.subscribe(4, (data) => console.log("Request ack received", rand, data)); + + this.client.connect(); + this.ngZone.runOutsideAngular(() => setInterval(() => this.client.heartbeat(), 15000)); + } + + ngOnDestroy() { + } +} \ No newline at end of file diff --git a/src/app/app.config.server.ts b/src/app/app.config.server.ts index b4d57c9..ea249a4 100644 --- a/src/app/app.config.server.ts +++ b/src/app/app.config.server.ts @@ -1,10 +1,12 @@ import { mergeApplicationConfig, ApplicationConfig } from '@angular/core'; import { provideServerRendering } from '@angular/platform-server'; import { appConfig } from './app.config'; +import { provideHttpClient, withFetch } from '@angular/common/http'; const serverConfig: ApplicationConfig = { providers: [ - provideServerRendering() + provideServerRendering(), + provideHttpClient(withFetch()) ] }; diff --git a/src/app/app.config.ts b/src/app/app.config.ts index 52cd710..02f66d3 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -1,9 +1,23 @@ import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; import { provideRouter } from '@angular/router'; +import { provideOAuthClient } from 'angular-oauth2-oidc'; +import { HttpHandlerFn, HttpRequest, provideHttpClient, withInterceptors } from '@angular/common/http'; +import { provideAnimationsAsync } from '@angular/platform-browser/animations/async'; import { routes } from './app.routes'; import { provideClientHydration } from '@angular/platform-browser'; export const appConfig: ApplicationConfig = { - providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideClientHydration()] -}; + providers: [ + provideZoneChangeDetection({ eventCoalescing: true }), + provideRouter(routes), + provideHttpClient( + withInterceptors([(req: HttpRequest, next: HttpHandlerFn) => { + console.log(req.url); + return next(req); + }]) + ), + provideOAuthClient(), + provideClientHydration(), provideAnimationsAsync() + ] +}; \ No newline at end of file diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index dc39edb..585ecc8 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -1,3 +1,27 @@ import { Routes } from '@angular/router'; +import { PolicyComponent } from './policy/policy.component'; +import { AuthGuard } from './shared/auth/auth.guard'; +import { LoginComponent } from './login/login.component'; +import { TtsLoginComponent } from './tts-login/tts-login.component'; +import { TwitchAuthCallbackComponent } from './twitch-auth-callback/twitch-auth-callback.component'; -export const routes: Routes = []; +export const routes: Routes = [ + { + path: 'policies', + component: PolicyComponent, + canActivate: [AuthGuard], + }, + { + path: 'login', + component: LoginComponent, + }, + { + path: 'tts-login', + component: TtsLoginComponent, + canActivate: [AuthGuard], + }, + { + path: 'auth', + component: TwitchAuthCallbackComponent + } +]; diff --git a/src/app/hermes-client.service.spec.ts b/src/app/hermes-client.service.spec.ts new file mode 100644 index 0000000..168358e --- /dev/null +++ b/src/app/hermes-client.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { HermesClientService } from './hermes-client.service'; + +describe('HermesClientService', () => { + let service: HermesClientService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(HermesClientService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/hermes-client.service.ts b/src/app/hermes-client.service.ts new file mode 100644 index 0000000..a055f11 --- /dev/null +++ b/src/app/hermes-client.service.ts @@ -0,0 +1,105 @@ +import { Injectable } from '@angular/core'; +import { DatePipe } from '@angular/common'; +import { Subscription } from 'rxjs'; +import { HermesSocketService } from './hermes-socket.service'; +import EventService from './shared/services/EventService'; + +export interface Message { + d: object, + t: object, + o: object +} + +@Injectable({ + providedIn: 'root' +}) +export class HermesClientService { + pipe = new DatePipe('en-US') + connected: boolean; + logged_in: boolean; + + private subscriptions: { [key: number]: ((data: any) => void)[] } + + constructor(private socket: HermesSocketService, private events: EventService) { + this.subscriptions = {}; + this.connected = false; + this.logged_in = false; + + this.events.listen('tts_login', (payload) => { + this.login(payload); + }); + } + + public connect() { + if (this.connected) + return; + + this.socket.connect(); + this.connected = true; + return this.listen(); + } + + private send(op: number, data: any) { + if (op != 0) + console.log("TX:", data); + + this.socket.sendMessage({ + d: data, + op + }); + } + + public login(api_key: string) { + if (this.logged_in) + return; + + this.send(1, { + api_key, + web_login: true, + major_version: 0, + minor_version: 1 + }); + } + + public heartbeat() { + const date = new Date() + this.send(0, { + date_time: this.pipe.transform(date.getTime() + date.getUTCDate(), "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSS'Z'") + }); + } + + public subscribe(code: number, action: (data: any) => void) { + if (!(code in this.subscriptions)) { + this.subscriptions[code] = [] + } + this.subscriptions[code].push(action); + } + + private listen() { + return this.socket.subscribe({ + next: (message: any) => { + console.log("RX:", message); + switch (message.op) { + case 0: // Heartbeat + console.log("Heartbeat received. Potential connection problem?"); + break; + case 2: // Login Ack + console.log("Login successful."); + this.logged_in = true; + this.events.emit('tts_login_ack', null); + break; + case 4: // Request Ack + console.log("Request received."); + break; + } + if (message.op in this.subscriptions) { + console.log('found #' + message.op + ' subscription for ' + message.op); + for (let action of this.subscriptions[message.op]) + action(message.d); + } + }, + error: (err: any) => console.error('Websocket error', err), + complete: () => console.log('Websocket disconnected.') + }); + } +} diff --git a/src/app/hermes-socket.service.spec.ts b/src/app/hermes-socket.service.spec.ts new file mode 100644 index 0000000..0888e0b --- /dev/null +++ b/src/app/hermes-socket.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { HermesSocketService } from './hermes-socket.service'; + +describe('HermesSocketService', () => { + let service: HermesSocketService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(HermesSocketService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/hermes-socket.service.ts b/src/app/hermes-socket.service.ts new file mode 100644 index 0000000..5e511e2 --- /dev/null +++ b/src/app/hermes-socket.service.ts @@ -0,0 +1,52 @@ +import { Component, OnInit, Injectable } from '@angular/core'; +import { webSocket, WebSocketSubject } from 'rxjs/webSocket'; +import { catchError, tap, switchAll } from 'rxjs/operators'; +import { EMPTY, Observer, Subject } from 'rxjs'; +import { environment } from '../environments/environment'; + + +@Injectable({ + providedIn: 'root' +}) +export class HermesSocketService implements OnInit { + private WS_ENDPOINT = environment.WSS_ENDPOINT; + private socket: WebSocketSubject | undefined = undefined + + constructor() { } + + ngOnInit(): void { + } + + public connect(): void { + if (!this.socket || this.socket.closed) { + this.socket = this.getNewWebSocket(); + } + } + + private getNewWebSocket() { + return webSocket({ + url: WS_ENDPOINT + }); + } + + public sendMessage(msg: any) { + if (!this.socket || this.socket.closed) + return + + this.socket.next(msg); + } + + public subscribe(subscriptions: any) { + if (!this.socket || this.socket.closed) + return + + return this.socket.subscribe(subscriptions); + } + + public close() { + if (!this.socket || this.socket.closed) + return + + this.socket.complete(); + } +} diff --git a/src/app/login/login.component.html b/src/app/login/login.component.html new file mode 100644 index 0000000..02669bf --- /dev/null +++ b/src/app/login/login.component.html @@ -0,0 +1,21 @@ + \ No newline at end of file diff --git a/src/app/login/login.component.scss b/src/app/login/login.component.scss new file mode 100644 index 0000000..dd6c8cd --- /dev/null +++ b/src/app/login/login.component.scss @@ -0,0 +1,13 @@ +.login { + display: grid; + grid-template-columns: 1fr max-content 1fr; + place-items: center; +} + +.mat-mdc-card-header { + align-self: center; +} + +.mat-mdc-card-content { + align-self: center; +} \ No newline at end of file diff --git a/src/app/login/login.component.spec.ts b/src/app/login/login.component.spec.ts new file mode 100644 index 0000000..18f3685 --- /dev/null +++ b/src/app/login/login.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LoginComponent } from './login.component'; + +describe('LoginComponent', () => { + let component: LoginComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [LoginComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(LoginComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/login/login.component.ts b/src/app/login/login.component.ts new file mode 100644 index 0000000..87de2ba --- /dev/null +++ b/src/app/login/login.component.ts @@ -0,0 +1,33 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { MatCardModule } from '@angular/material/card'; +import { Router, RouterModule } from '@angular/router'; +import { Subscription } from 'rxjs'; +import { environment } from '../../environments/environment'; + +@Component({ + selector: 'login', + standalone: true, + imports: [MatCardModule, RouterModule], + templateUrl: './login.component.html', + styleUrl: './login.component.scss' +}) +export class LoginComponent implements OnInit, OnDestroy { + subscription: Subscription | null; + + constructor(private router: Router) { + this.subscription = null; + } + + ngOnInit(): void { + + } + + ngOnDestroy(): void { + if (this.subscription) + this.subscription.unsubscribe() + } + + login() { + document.location.replace(environment.API_HOST + '/auth'); + } +} diff --git a/src/app/navigation/navigation.component.html b/src/app/navigation/navigation.component.html new file mode 100644 index 0000000..beadf50 --- /dev/null +++ b/src/app/navigation/navigation.component.html @@ -0,0 +1,28 @@ + \ No newline at end of file diff --git a/src/app/navigation/navigation.component.scss b/src/app/navigation/navigation.component.scss new file mode 100644 index 0000000..fb51965 --- /dev/null +++ b/src/app/navigation/navigation.component.scss @@ -0,0 +1,37 @@ +$primary_background_color: #EEEEEE; +$primary_font_color: #111111; + +$secondary_background_color: #DDDDDD; +$secondary_font_color: #333333; + + +ul { + padding: 0; +} + +li { + list-style: none; + width: 100%; + display: flex; + flex-grow: 1; + justify-content: center; + flex-direction: column; +} + +a { + background-color: transparent; + padding: 1em; + border: 0; + margin: 0; + font-size: large; + text-decoration: none; + color: $primary_font_color; +} + +a:hover { + background-color: #FCFCFC; +} + +a.active { + background-color: #F5F5F5; +} \ No newline at end of file diff --git a/src/app/navigation/navigation.component.spec.ts b/src/app/navigation/navigation.component.spec.ts new file mode 100644 index 0000000..a161d31 --- /dev/null +++ b/src/app/navigation/navigation.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NavigationComponent } from './navigation.component'; + +describe('NavigationComponent', () => { + let component: NavigationComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [NavigationComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(NavigationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/navigation/navigation.component.ts b/src/app/navigation/navigation.component.ts new file mode 100644 index 0000000..3712df4 --- /dev/null +++ b/src/app/navigation/navigation.component.ts @@ -0,0 +1,24 @@ +import { Component } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { CommonModule } from '@angular/common'; +import { HermesClientService } from '../hermes-client.service'; +import { ApiAuthenticationService } from '../shared/services/api/api-authentication.service'; + +@Component({ + selector: 'navigation', + standalone: true, + imports: [CommonModule, RouterModule], + templateUrl: './navigation.component.html', + styleUrl: './navigation.component.scss' +}) +export class NavigationComponent { + constructor(private auth: ApiAuthenticationService, private hermes: HermesClientService) { } + + get twitch_logged_in() { + return this.auth.isAuthenticated(); + } + + get tts_logged_in() { + return this.hermes?.logged_in ?? false; + } +} diff --git a/src/app/policy-add-form/policy-add-form.component.html b/src/app/policy-add-form/policy-add-form.component.html new file mode 100644 index 0000000..f91c343 --- /dev/null +++ b/src/app/policy-add-form/policy-add-form.component.html @@ -0,0 +1,26 @@ +
+
+ + + + @for (option of filteredPolicies | async; track option) { + {{option}} + } + + + +
+
\ No newline at end of file diff --git a/src/app/policy-add-form/policy-add-form.component.scss b/src/app/policy-add-form/policy-add-form.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/policy-add-form/policy-add-form.component.spec.ts b/src/app/policy-add-form/policy-add-form.component.spec.ts new file mode 100644 index 0000000..e4e59b1 --- /dev/null +++ b/src/app/policy-add-form/policy-add-form.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PolicyAddFormComponent } from './policy-add-form.component'; + +describe('PolicyAddFormComponent', () => { + let component: PolicyAddFormComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [PolicyAddFormComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(PolicyAddFormComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/policy-add-form/policy-add-form.component.ts b/src/app/policy-add-form/policy-add-form.component.ts new file mode 100644 index 0000000..0e69cac --- /dev/null +++ b/src/app/policy-add-form/policy-add-form.component.ts @@ -0,0 +1,75 @@ +import { AsyncPipe } from '@angular/common'; +import { Component, OnInit, Output, EventEmitter } from '@angular/core'; +import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms' +import { MatAutocompleteModule } from '@angular/material/autocomplete'; +import { MatButtonModule } from '@angular/material/button'; +import { MatInputModule } from '@angular/material/input'; +import { Policy } from '../shared/models/policy'; +import EventService from '../shared/services/EventService'; +import { map, Observable, startWith } from 'rxjs'; + +const Policies = [ + { path: "tts", description: "Anything to do with TTS" }, + { path: "tts.chat", description: "Anything to do with chat" }, + { path: "tts.chat.bits.read", description: "To read chat messages with bits via TTS" }, + { path: "tts.chat.messages.read", description: "To read chat messages via TTS" }, + { path: "tts.chat.redemptions.read", description: "To read channel point redemption messages via TTS" }, + //{ path: "tts.chat.subscriptions.read", description: "To read chat messages from subscriptions via TTS" }, + { path: "tts.commands", description: "To execute commands for TTS" }, + { path: "tts.commands.nightbot", description: "To use !nightbot command" }, + { path: "tts.commands.obs", description: "To use !obs command" }, + { path: "tts.commands.refresh", description: "To use !refresh command" }, + { path: "tts.commands.skip", description: "To use !skip command" }, + { path: "tts.commands.skipall", description: "To use !skipall command" }, + { path: "tts.commands.tts", description: "To use !tts command" }, + { path: "tts.commands.tts.join", description: "To use !tts join command" }, + { path: "tts.commands.tts.leave", description: "To use !tts leave command" }, + { path: "tts.commands.version", description: "To use !version command" }, + { path: "tts.commands.voice", description: "To use !voice command" }, + { path: "tts.commands.voice.admin", description: "To use !voice command on others" }, +] + +@Component({ + selector: 'policy-add-form', + standalone: true, + imports: [ + AsyncPipe, + FormsModule, + MatAutocompleteModule, + MatButtonModule, + MatInputModule, + ReactiveFormsModule, + ], + templateUrl: './policy-add-form.component.html', + styleUrl: './policy-add-form.component.scss' +}) +export class PolicyAddFormComponent { + myControl = new FormControl(''); + newPolicyName: string = ''; + filteredPolicies: Observable; + + constructor(private events: EventService) { + this.filteredPolicies = this.myControl.valueChanges.pipe( + startWith(''), + map(value => this._filter(value || '')), + ); + } + + ngOnInit() { + this.filteredPolicies = this.myControl.valueChanges.pipe( + startWith(''), + map(value => this._filter(value || '')), + ); + } + + private _filter(value: string): string[] { + const filterValue = value.toLowerCase(); + + return Policies.map(p => p.path).filter(option => option.toLowerCase().includes(filterValue)); + } + + addNewPolicy() { + console.log('new policy name given', this.newPolicyName) + this.events.emit('addPolicy', this.newPolicyName) + } +} diff --git a/src/app/policy-table/policy-table.component.html b/src/app/policy-table/policy-table.component.html new file mode 100644 index 0000000..d4720a8 --- /dev/null +++ b/src/app/policy-table/policy-table.component.html @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Path + @if (policy.editing) { + + } + @if (!policy.editing) { + {{policy.path}} + } + Usage {{policy.name}} Span {{policy.weight}} Actions + @if (!policy.editing) { + + + } + @if (policy.editing) { + + + } +
\ No newline at end of file diff --git a/src/app/policy-table/policy-table.component.scss b/src/app/policy-table/policy-table.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/policy-table/policy-table.component.spec.ts b/src/app/policy-table/policy-table.component.spec.ts new file mode 100644 index 0000000..2793e66 --- /dev/null +++ b/src/app/policy-table/policy-table.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PolicyTableComponent } from './policy-table.component'; + +describe('PolicyTableComponent', () => { + let component: PolicyTableComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [PolicyTableComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(PolicyTableComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/policy-table/policy-table.component.ts b/src/app/policy-table/policy-table.component.ts new file mode 100644 index 0000000..4ee3beb --- /dev/null +++ b/src/app/policy-table/policy-table.component.ts @@ -0,0 +1,66 @@ +import { Component, ElementRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { MatTable, MatTableModule } from '@angular/material/table'; +import { MatIconModule } from '@angular/material/icon'; +import EventService from '../shared/services/EventService'; +import { Policy } from '../shared/models/policy'; +import { Subscription } from 'rxjs'; +import { FormsModule } from '@angular/forms'; + +@Component({ + selector: 'policy-table', + standalone: true, + imports: [FormsModule, MatTableModule, MatIconModule], + templateUrl: './policy-table.component.html', + styleUrl: './policy-table.component.scss' +}) +export class PolicyTableComponent implements OnInit, OnDestroy { + @Input() policies: Policy[] = [] + displayedColumns = ['path', 'usage', 'span', 'actions'] + + @ViewChild(MatTable) table: MatTable; + + private subscription: Subscription | undefined; + + constructor(private events: EventService) { + this.table = {} as MatTable; + } + + ngOnInit(): void { + this.subscription = this.events.listen('addPolicy', (payload) => { + console.log('adding policy', payload); + this.policies.push(new Policy(payload, 1, 5000, true, true)); + console.log(this.policies); + this.table.renderRows(); + }); + } + + ngOnDestroy(): void { + if (this.subscription) + this.subscription.unsubscribe(); + } + + cancel(policy: Policy) { + policy.editing = false; + this.table.renderRows(); + } + + delete(policy: Policy) { + const index = this.policies.indexOf(policy); + if (index >= 0) { + this.policies.splice(index, 1); + this.table.renderRows(); + } + } + + edit(policy: Policy) { + console.log('prior', policy.editing) + policy.editing = true; + + } + + save(policy: Policy) { + policy.editing = false; + policy.isNew = false; + this.table.renderRows(); + } +} diff --git a/src/app/policy/policy.component.html b/src/app/policy/policy.component.html new file mode 100644 index 0000000..a4593c5 --- /dev/null +++ b/src/app/policy/policy.component.html @@ -0,0 +1,8 @@ +Policies +
+ +
+ +
+ +
\ No newline at end of file diff --git a/src/app/policy/policy.component.scss b/src/app/policy/policy.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/policy/policy.component.spec.ts b/src/app/policy/policy.component.spec.ts new file mode 100644 index 0000000..72cf9ce --- /dev/null +++ b/src/app/policy/policy.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PolicyComponent } from './policy.component'; + +describe('PolicyComponent', () => { + let component: PolicyComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [PolicyComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(PolicyComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/policy/policy.component.ts b/src/app/policy/policy.component.ts new file mode 100644 index 0000000..2ea502a --- /dev/null +++ b/src/app/policy/policy.component.ts @@ -0,0 +1,53 @@ +import { Component, Inject, NgZone, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core'; +import { PolicyAddFormComponent } from "../policy-add-form/policy-add-form.component"; +import { PolicyTableComponent } from "../policy-table/policy-table.component"; +import { Policy, PolicyScope } from '../shared/models/policy'; +import { DatePipe, isPlatformBrowser } from '@angular/common'; +import { OAuthService } from 'angular-oauth2-oidc'; +import { Subscription } from 'rxjs'; +import { HermesClientService } from '../hermes-client.service'; +import { Router, RouterModule } from '@angular/router'; + +@Component({ + selector: 'policy', + standalone: true, + imports: [RouterModule, PolicyAddFormComponent, PolicyTableComponent], + templateUrl: './policy.component.html', + styleUrl: './policy.component.scss' +}) +export class PolicyComponent implements OnInit, OnDestroy { + private isBrowser: boolean; + private ngZone: NgZone; + private subscription: Subscription | undefined; + items: Policy[]; + pipe = new DatePipe('en-US') + + + constructor(private client: HermesClientService, private oauthService: OAuthService, private router: Router, ngZone: NgZone, @Inject(PLATFORM_ID) private platformId: Object) { + this.ngZone = ngZone; + this.isBrowser = isPlatformBrowser(this.platformId) + + this.items = [] + } + + get policies() { + return this.items; + } + + ngOnInit(): void { + if (!this.isBrowser) + return; + + if (!this.client.logged_in) { + this.router.navigate(["/tts-login"]); + return; + } + + this.subscription = this.client.connect(); + } + + ngOnDestroy() { + if (this.subscription) + this.subscription.unsubscribe() + } +} \ No newline at end of file diff --git a/src/app/shared/auth/auth.guard.ts b/src/app/shared/auth/auth.guard.ts new file mode 100644 index 0000000..5043a29 --- /dev/null +++ b/src/app/shared/auth/auth.guard.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; +import { ApiAuthenticationService } from '../services/api/api-authentication.service'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthGuard implements CanActivate { + + constructor(private auth: ApiAuthenticationService, private router: Router) {} + + async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { + if (this.auth.isAuthenticated()) { + console.log('Valid OAuth'); + return true; + } + + console.log("Invalid OAuth"); + return false; + } +} \ No newline at end of file diff --git a/src/app/shared/models/policy.ts b/src/app/shared/models/policy.ts new file mode 100644 index 0000000..ae31cb5 --- /dev/null +++ b/src/app/shared/models/policy.ts @@ -0,0 +1,10 @@ +export enum PolicyScope { + Global, + Local +} + +export class Policy { + constructor(public path: string, public usage: number, public span: number, public editing: boolean = false, public isNew: boolean = false) { + + } +} \ No newline at end of file diff --git a/src/app/shared/services/EventService.ts b/src/app/shared/services/EventService.ts new file mode 100644 index 0000000..665187a --- /dev/null +++ b/src/app/shared/services/EventService.ts @@ -0,0 +1,21 @@ +import { Injectable } from "@angular/core"; +import { Subject } from "rxjs" + +@Injectable({ + providedIn: 'root' +}) +export default class EventService { + private subject = new Subject(); + + emit(eventName: string, payload: any) { + this.subject.next({ eventName, payload }) + } + + listen(eventName: string, callback: (event: any) => void) { + return this.subject.asObservable().subscribe((next: any) => { + if (eventName == next.eventName) { + callback(next.payload) + } + }) + } +} \ No newline at end of file diff --git a/src/app/shared/services/api/api-authentication.service.spec.ts b/src/app/shared/services/api/api-authentication.service.spec.ts new file mode 100644 index 0000000..ca09c0a --- /dev/null +++ b/src/app/shared/services/api/api-authentication.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { ApiAuthenticationService } from './api-authentication.service'; + +describe('ApiAuthServiceService', () => { + let service: ApiAuthenticationService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ApiAuthenticationService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/shared/services/api/api-authentication.service.ts b/src/app/shared/services/api/api-authentication.service.ts new file mode 100644 index 0000000..79cfa32 --- /dev/null +++ b/src/app/shared/services/api/api-authentication.service.ts @@ -0,0 +1,42 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class ApiAuthenticationService { + private authenticated: boolean; + private lastCheck: Date; + + constructor(private http: HttpClient) { + this.authenticated = false; + this.lastCheck = new Date(); + } + + isAuthenticated() { + return this.authenticated; + } + + update() { + const jwt = localStorage.getItem('jwt'); + if (!jwt) { + this.updateAuthenticated(false); + return; + } + + // /api/auth/jwt + this.http.get('/api/auth/jwt', { + headers: { + 'Authorization': 'Bearer ' + jwt + } + }).subscribe((data: any) => { + console.log('jwt validation', data); + this.updateAuthenticated(data?.authenticated); + }); + } + + private updateAuthenticated(value: boolean) { + this.authenticated = value; + this.lastCheck = new Date(); + } +} diff --git a/src/app/tts-login/tts-login.component.html b/src/app/tts-login/tts-login.component.html new file mode 100644 index 0000000..b5b2d7a --- /dev/null +++ b/src/app/tts-login/tts-login.component.html @@ -0,0 +1,13 @@ +
+

TTS Login

+ + API Key + + @for (key of api_keys; track key) { + {{key}} + } + + + +
\ No newline at end of file diff --git a/src/app/tts-login/tts-login.component.scss b/src/app/tts-login/tts-login.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/tts-login/tts-login.component.spec.ts b/src/app/tts-login/tts-login.component.spec.ts new file mode 100644 index 0000000..144be0b --- /dev/null +++ b/src/app/tts-login/tts-login.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TtsLoginComponent } from './tts-login.component'; + +describe('TtsLoginComponent', () => { + let component: TtsLoginComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TtsLoginComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(TtsLoginComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/tts-login/tts-login.component.ts b/src/app/tts-login/tts-login.component.ts new file mode 100644 index 0000000..12c158b --- /dev/null +++ b/src/app/tts-login/tts-login.component.ts @@ -0,0 +1,55 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { MatInputModule } from '@angular/material/input'; +import { MatSelectModule } from '@angular/material/select'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatButtonModule } from '@angular/material/button'; +import EventService from '../shared/services/EventService'; +import { HttpClient } from '@angular/common/http'; +import { Router } from '@angular/router'; +import { Subscription } from 'rxjs'; +import { environment } from '../../environments/environment'; + +@Component({ + selector: 'tts-login', + standalone: true, + imports: [MatButtonModule, MatFormFieldModule, MatSelectModule, MatInputModule, FormsModule], + templateUrl: './tts-login.component.html', + styleUrl: './tts-login.component.scss' +}) +export class TtsLoginComponent implements OnInit, OnDestroy { + api_keys: string[]; + selected_api_key: string|undefined; + + private subscription: Subscription|undefined; + + constructor(private events: EventService, private http: HttpClient, private router: Router) { + this.api_keys = []; + } + + ngOnInit(): void { + this.http.get(environment.API_HOST + '/keys', { + headers: { + 'Authorization': 'Bearer ' + localStorage.getItem('jwt') + } + }).subscribe((data: any) => this.api_keys = data.map((d: any) => d.id)) + + this.subscription = this.events.listen('tts_login_ack', _ => { + if (document.location.href.includes('/tts-login')) { + this.router.navigate(['/policies']) + } + }); + } + + ngOnDestroy(): void { + if (this.subscription) + this.subscription.unsubscribe(); + } + + login() { + if (!this.selected_api_key) + return; + + this.events.emit('tts_login', this.selected_api_key); + } +} diff --git a/src/app/twitch-auth-callback/twitch-auth-callback.component.html b/src/app/twitch-auth-callback/twitch-auth-callback.component.html new file mode 100644 index 0000000..e69de29 diff --git a/src/app/twitch-auth-callback/twitch-auth-callback.component.scss b/src/app/twitch-auth-callback/twitch-auth-callback.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/twitch-auth-callback/twitch-auth-callback.component.spec.ts b/src/app/twitch-auth-callback/twitch-auth-callback.component.spec.ts new file mode 100644 index 0000000..fdc0996 --- /dev/null +++ b/src/app/twitch-auth-callback/twitch-auth-callback.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TwitchAuthCallbackComponent } from './twitch-auth-callback.component'; + +describe('TwitchAuthCallbackComponent', () => { + let component: TwitchAuthCallbackComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TwitchAuthCallbackComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(TwitchAuthCallbackComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/twitch-auth-callback/twitch-auth-callback.component.ts b/src/app/twitch-auth-callback/twitch-auth-callback.component.ts new file mode 100644 index 0000000..cefff63 --- /dev/null +++ b/src/app/twitch-auth-callback/twitch-auth-callback.component.ts @@ -0,0 +1,48 @@ +import { isPlatformBrowser } from '@angular/common'; +import { HttpClient } from '@angular/common/http'; +import { Component, Inject, OnInit, PLATFORM_ID } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ApiAuthenticationService } from '../shared/services/api/api-authentication.service'; +import { environment } from '../../environments/environment'; + +@Component({ + selector: 'app-twitch-auth-callback', + standalone: true, + imports: [], + templateUrl: './twitch-auth-callback.component.html', + styleUrl: './twitch-auth-callback.component.scss' +}) +export class TwitchAuthCallbackComponent implements OnInit { + private isBrowser: boolean; + + constructor(private http: HttpClient, private auth: ApiAuthenticationService, private route: ActivatedRoute, private router: Router, @Inject(PLATFORM_ID) private platformId: Object) { + this.isBrowser = isPlatformBrowser(this.platformId) + } + + ngOnInit(): void { + if (!this.isBrowser) { + return; + } + + const code = this.route.snapshot.queryParamMap.get('code'); + const scope = this.route.snapshot.queryParamMap.get('scope'); + const state = this.route.snapshot.queryParamMap.get('state'); + + console.log('twitch callback', code, scope, state); + if (!code || !scope || !state) + return; + + this.http.post(environment.API_HOST + '/auth/twitch/callback', { code, scope, state }) + .subscribe((data: any) => { + console.log('twitch callback response', code, scope, state, data); + if (!data?.authenticated) { + this.router.navigate(['/login?error=callback_error']); + return; + } + + localStorage.setItem('jwt', data.token); + this.auth.update(); + this.router.navigate(['/tts-login']); + }); + } +} diff --git a/src/index.html b/src/index.html index a7ee2a0..4729efc 100644 --- a/src/index.html +++ b/src/index.html @@ -6,8 +6,10 @@ + + - + diff --git a/src/styles.scss b/src/styles.scss index 90d4ee0..7e7239a 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -1 +1,4 @@ /* You can add global styles to this file, and also import other style files */ + +html, body { height: 100%; } +body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }