Fügt Unterstützung für die neue nuscore API hinzu. Aktualisiert die Backend-Routen zur Verarbeitung von Anfragen an die nuscore API und integriert die neuen Dialogkomponenten im Frontend. Ermöglicht das Erstellen lokaler Kopien von nuscore-Daten und verbessert die Benutzeroberfläche durch neue Schaltflächen und Dialoge. Entfernt veraltete Konsolenausgaben und optimiert die Logik zur PIN-Verwaltung.

This commit is contained in:
Torsten Schulz (local)
2025-10-03 15:57:57 +02:00
parent cc964da9cf
commit 4b1a046149
28 changed files with 4325 additions and 6072 deletions

View File

@@ -595,6 +595,26 @@
"node-pre-gyp": "bin/node-pre-gyp"
}
},
"node_modules/@mapbox/node-pre-gyp/node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -1278,6 +1298,15 @@
"node": ">= 10"
}
},
"node_modules/data-uri-to-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"node_modules/date-fns": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
@@ -1778,6 +1807,29 @@
"reusify": "^1.0.4"
}
},
"node_modules/fetch-blob": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "paypal",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"dependencies": {
"node-domexception": "^1.0.0",
"web-streams-polyfill": "^3.0.3"
},
"engines": {
"node": "^12.20 || >= 14.13"
}
},
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -1902,6 +1954,18 @@
"node": ">= 6"
}
},
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
"license": "MIT",
"dependencies": {
"fetch-blob": "^3.1.2"
},
"engines": {
"node": ">=12.20.0"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -2816,6 +2880,26 @@
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
"integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA=="
},
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"deprecated": "Use your platform's native DOMException instead",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "github",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"engines": {
"node": ">=10.5.0"
}
},
"node_modules/node-ensure": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/node-ensure/-/node-ensure-0.0.0.tgz",
@@ -2823,22 +2907,21 @@
"license": "MIT"
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
"data-uri-to-buffer": "^4.0.0",
"fetch-blob": "^3.1.4",
"formdata-polyfill": "^4.0.10"
},
"engines": {
"node": "4.x || >=6.0.0"
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-fetch"
}
},
"node_modules/nodemailer": {
@@ -3124,6 +3207,51 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/playwright": {
"version": "1.55.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.1.tgz",
"integrity": "sha512-cJW4Xd/G3v5ovXtJJ52MAOclqeac9S/aGGgRzLabuF8TnIb6xHvMzKIa6JmrRzUkeXJgfL1MhukP0NK6l39h3A==",
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.55.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.55.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.1.tgz",
"integrity": "sha512-Z6Mh9mkwX+zxSlHqdr5AOcJnfp+xUWLCt9uKV18fhzA8eyxUd8NUWzAjxUh55RZKSYwDGX0cfaySdhZJGMoJ+w==",
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/playwright/node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"hasInstallScript": true,
"ideallyInert": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -3792,7 +3920,8 @@
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/tslib": {
"version": "2.7.0",
@@ -3992,15 +4121,26 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"node_modules/web-streams-polyfill": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
"license": "MIT",
"engines": {
"node": ">= 8"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2016 David Frank
Copyright (c) 2016 - 2020 Node Fetch Team
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

File diff suppressed because it is too large Load Diff

View File

@@ -1,25 +0,0 @@
"use strict";
// ref: https://github.com/tc39/proposal-global
var getGlobal = function () {
// the only reliable means to get the global object is
// `Function('return this')()`
// However, this causes CSP violations in Chrome apps.
if (typeof self !== 'undefined') { return self; }
if (typeof window !== 'undefined') { return window; }
if (typeof global !== 'undefined') { return global; }
throw new Error('unable to locate global object');
}
var globalObject = getGlobal();
module.exports = exports = globalObject.fetch;
// Needed for TypeScript and Webpack.
if (globalObject.fetch) {
exports.default = globalObject.fetch.bind(globalObject);
}
exports.Headers = globalObject.Headers;
exports.Request = globalObject.Request;
exports.Response = globalObject.Response;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,89 +1,131 @@
{
"name": "node-fetch",
"version": "2.7.0",
"description": "A light-weight module that brings window.fetch to node.js",
"main": "lib/index.js",
"browser": "./browser.js",
"module": "lib/index.mjs",
"files": [
"lib/index.js",
"lib/index.mjs",
"lib/index.es.js",
"browser.js"
],
"engines": {
"node": "4.x || >=6.0.0"
},
"scripts": {
"build": "cross-env BABEL_ENV=rollup rollup -c",
"prepare": "npm run build",
"test": "cross-env BABEL_ENV=test mocha --require babel-register --throw-deprecation test/test.js",
"report": "cross-env BABEL_ENV=coverage nyc --reporter lcov --reporter text mocha -R spec test/test.js",
"coverage": "cross-env BABEL_ENV=coverage nyc --reporter json --reporter text mocha -R spec test/test.js && codecov -f coverage/coverage-final.json"
},
"repository": {
"type": "git",
"url": "https://github.com/bitinn/node-fetch.git"
},
"keywords": [
"fetch",
"http",
"promise"
],
"author": "David Frank",
"license": "MIT",
"bugs": {
"url": "https://github.com/bitinn/node-fetch/issues"
},
"homepage": "https://github.com/bitinn/node-fetch",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
},
"devDependencies": {
"@ungap/url-search-params": "^0.1.2",
"abort-controller": "^1.1.0",
"abortcontroller-polyfill": "^1.3.0",
"babel-core": "^6.26.3",
"babel-plugin-istanbul": "^4.1.6",
"babel-plugin-transform-async-generator-functions": "^6.24.1",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "1.4.0",
"babel-register": "^6.16.3",
"chai": "^3.5.0",
"chai-as-promised": "^7.1.1",
"chai-iterator": "^1.1.1",
"chai-string": "~1.3.0",
"codecov": "3.3.0",
"cross-env": "^5.2.0",
"form-data": "^2.3.3",
"is-builtin-module": "^1.0.0",
"mocha": "^5.0.0",
"nyc": "11.9.0",
"parted": "^0.1.1",
"promise": "^8.0.3",
"resumer": "0.0.0",
"rollup": "^0.63.4",
"rollup-plugin-babel": "^3.0.7",
"string-to-arraybuffer": "^1.0.2",
"teeny-request": "3.7.0"
},
"release": {
"branches": [
"+([0-9]).x",
"main",
"next",
{
"name": "beta",
"prerelease": true
}
]
"name": "node-fetch",
"version": "3.3.2",
"description": "A light-weight module that brings Fetch API to node.js",
"main": "./src/index.js",
"sideEffects": false,
"type": "module",
"files": [
"src",
"@types/index.d.ts"
],
"types": "./@types/index.d.ts",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"scripts": {
"test": "mocha",
"coverage": "c8 report --reporter=text-lcov | coveralls",
"test-types": "tsd",
"lint": "xo"
},
"repository": {
"type": "git",
"url": "https://github.com/node-fetch/node-fetch.git"
},
"keywords": [
"fetch",
"http",
"promise",
"request",
"curl",
"wget",
"xhr",
"whatwg"
],
"author": "David Frank",
"license": "MIT",
"bugs": {
"url": "https://github.com/node-fetch/node-fetch/issues"
},
"homepage": "https://github.com/node-fetch/node-fetch",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-fetch"
},
"devDependencies": {
"abort-controller": "^3.0.0",
"abortcontroller-polyfill": "^1.7.1",
"busboy": "^1.4.0",
"c8": "^7.7.2",
"chai": "^4.3.4",
"chai-as-promised": "^7.1.1",
"chai-iterator": "^3.0.2",
"chai-string": "^1.5.0",
"coveralls": "^3.1.0",
"form-data": "^4.0.0",
"formdata-node": "^4.2.4",
"mocha": "^9.1.3",
"p-timeout": "^5.0.0",
"stream-consumers": "^1.0.1",
"tsd": "^0.14.0",
"xo": "^0.39.1"
},
"dependencies": {
"data-uri-to-buffer": "^4.0.0",
"fetch-blob": "^3.1.4",
"formdata-polyfill": "^4.0.10"
},
"tsd": {
"cwd": "@types",
"compilerOptions": {
"esModuleInterop": true
}
},
"xo": {
"envs": [
"node",
"browser"
],
"ignores": [
"example.js"
],
"rules": {
"complexity": 0,
"import/extensions": 0,
"import/no-useless-path-segments": 0,
"import/no-anonymous-default-export": 0,
"import/no-named-as-default": 0,
"unicorn/import-index": 0,
"unicorn/no-array-reduce": 0,
"unicorn/prefer-node-protocol": 0,
"unicorn/numeric-separators-style": 0,
"unicorn/explicit-length-check": 0,
"capitalized-comments": 0,
"node/no-unsupported-features/es-syntax": 0,
"@typescript-eslint/member-ordering": 0
},
"overrides": [
{
"files": "test/**/*.js",
"envs": [
"node",
"mocha"
],
"rules": {
"max-nested-callbacks": 0,
"no-unused-expressions": 0,
"no-warning-comments": 0,
"new-cap": 0,
"guard-for-in": 0,
"unicorn/no-array-for-each": 0,
"unicorn/prevent-abbreviations": 0,
"promise/prefer-await-to-then": 0,
"ava/no-import-test-files": 0
}
}
]
},
"runkitExampleFilename": "example.js",
"release": {
"branches": [
"+([0-9]).x",
"main",
"next",
{
"name": "beta",
"prerelease": true
}
]
}
}

View File

@@ -22,8 +22,10 @@
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1",
"mysql2": "^3.10.3",
"node-fetch": "^3.3.2",
"nodemailer": "^6.9.14",
"pdf-parse": "^1.1.1",
"playwright": "^1.55.1",
"sequelize": "^6.37.3",
"sharp": "^0.33.5"
},
@@ -607,6 +609,26 @@
"node-pre-gyp": "bin/node-pre-gyp"
}
},
"node_modules/@mapbox/node-pre-gyp/node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -1290,6 +1312,15 @@
"node": ">= 10"
}
},
"node_modules/data-uri-to-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"node_modules/date-fns": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
@@ -1790,6 +1821,29 @@
"reusify": "^1.0.4"
}
},
"node_modules/fetch-blob": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "paypal",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"dependencies": {
"node-domexception": "^1.0.0",
"web-streams-polyfill": "^3.0.3"
},
"engines": {
"node": "^12.20 || >= 14.13"
}
},
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -1914,6 +1968,18 @@
"node": ">= 6"
}
},
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
"license": "MIT",
"dependencies": {
"fetch-blob": "^3.1.2"
},
"engines": {
"node": ">=12.20.0"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -2827,6 +2893,26 @@
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
"integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA=="
},
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"deprecated": "Use your platform's native DOMException instead",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "github",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"engines": {
"node": ">=10.5.0"
}
},
"node_modules/node-ensure": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/node-ensure/-/node-ensure-0.0.0.tgz",
@@ -2834,22 +2920,21 @@
"license": "MIT"
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
"data-uri-to-buffer": "^4.0.0",
"fetch-blob": "^3.1.4",
"formdata-polyfill": "^4.0.10"
},
"engines": {
"node": "4.x || >=6.0.0"
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-fetch"
}
},
"node_modules/nodemailer": {
@@ -3135,6 +3220,50 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/playwright": {
"version": "1.55.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.1.tgz",
"integrity": "sha512-cJW4Xd/G3v5ovXtJJ52MAOclqeac9S/aGGgRzLabuF8TnIb6xHvMzKIa6JmrRzUkeXJgfL1MhukP0NK6l39h3A==",
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.55.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.55.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.1.tgz",
"integrity": "sha512-Z6Mh9mkwX+zxSlHqdr5AOcJnfp+xUWLCt9uKV18fhzA8eyxUd8NUWzAjxUh55RZKSYwDGX0cfaySdhZJGMoJ+w==",
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/playwright/node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -3803,7 +3932,8 @@
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/tslib": {
"version": "2.7.0",
@@ -4002,15 +4132,26 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"node_modules/web-streams-polyfill": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
"license": "MIT",
"engines": {
"node": ">= 8"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"

View File

@@ -26,8 +26,10 @@
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1",
"mysql2": "^3.10.3",
"node-fetch": "^3.3.2",
"nodemailer": "^6.9.14",
"pdf-parse": "^1.1.1",
"playwright": "^1.55.1",
"sequelize": "^6.37.3",
"sharp": "^0.33.5"
},

View File

@@ -0,0 +1,193 @@
import express from 'express';
import fetch from 'node-fetch';
const router = express.Router();
// Cookie-Store für nuscore-Sessions
const cookieStore = new Map();
// Hilfsfunktion zum Extrahieren von Cookies aus Set-Cookie Headers
function extractCookies(setCookieHeaders) {
const cookies = {};
if (setCookieHeaders) {
setCookieHeaders.forEach(cookie => {
const [nameValue] = cookie.split(';');
const [name, value] = nameValue.split('=');
if (name && value) {
cookies[name.trim()] = value.trim();
}
});
}
return cookies;
}
// Hilfsfunktion zum Formatieren von Cookies für Requests
function formatCookies(cookies) {
return Object.entries(cookies)
.map(([name, value]) => `${name}=${value}`)
.join('; ');
}
// Meeting-Info API-Endpunkt
router.get('/meetinginfo/:code', async (req, res) => {
const { code } = req.params;
console.log(`📊 Meeting-Info API für Code: ${code}`);
try {
// Hole Cookies für diesen Code (falls vorhanden)
const cookies = cookieStore.get(code) || {};
const url = `https://ttde-apps.liga.nu/nuliga/rs/tt/2022/meetingentry/reports/${code}/meetinginfo`;
const response = await fetch(url, {
method: 'GET',
headers: {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:141.0) Gecko/20100101 Firefox/141.0',
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'de,en-US;q=0.7,en;q=0.3',
'Accept-Encoding': 'gzip, deflate, br, zstd',
'Referer': 'https://ttde-apps.liga.nu/nuliga/nuscore-tt/meetings-list',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'Connection': 'keep-alive',
...(Object.keys(cookies).length > 0 && { 'Cookie': formatCookies(cookies) })
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
// Speichere neue Cookies falls vorhanden
const newCookies = extractCookies(response.headers.raw()['set-cookie']);
if (Object.keys(newCookies).length > 0) {
cookieStore.set(code, { ...cookies, ...newCookies });
console.log(`🍪 Cookies für Code ${code} gespeichert:`, Object.keys(newCookies));
}
// CORS-Header setzen
res.set({
'Content-Type': 'application/json; charset=utf-8',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
'Cache-Control': 'no-cache, no-store, must-revalidate'
});
res.json(data);
console.log(`✅ Meeting-Info für Code ${code} erfolgreich abgerufen`);
} catch (error) {
console.error(`❌ Fehler beim Abrufen der Meeting-Info für Code ${code}:`, error);
res.status(500).json({
error: 'Fehler beim Abrufen der Meeting-Info',
details: error.message
});
}
});
// Cookie-Initialisierung (für den ersten Request)
router.post('/init-cookies/:code', async (req, res) => {
const { code } = req.params;
console.log(`🍪 Cookie-Initialisierung für Code: ${code}`);
try {
// Erster Request an die nuscore-Seite um Cookies zu erhalten
const response = await fetch(`https://ttde-apps.liga.nu/nuliga/nuscore-tt/meetings-list?code=${code}`, {
method: 'GET',
headers: {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:141.0) Gecko/20100101 Firefox/141.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
'Accept-Language': 'de,en-US;q=0.7,en;q=0.3',
'Accept-Encoding': 'gzip, deflate, br, zstd',
'Connection': 'keep-alive'
}
});
// Extrahiere Cookies
const cookies = extractCookies(response.headers.raw()['set-cookie']);
if (Object.keys(cookies).length > 0) {
cookieStore.set(code, cookies);
console.log(`🍪 Cookies für Code ${code} initialisiert:`, Object.keys(cookies));
}
res.json({
success: true,
cookies: Object.keys(cookies),
message: `Cookies für Code ${code} initialisiert`
});
} catch (error) {
console.error(`❌ Fehler bei Cookie-Initialisierung für Code ${code}:`, error);
res.status(500).json({
error: 'Fehler bei Cookie-Initialisierung',
details: error.message
});
}
});
// Detaillierte Meeting-Daten API-Endpunkt
router.get('/meetingdetails/:uuid', async (req, res) => {
const { uuid } = req.params;
console.log(`📊 Meeting-Details API für UUID: ${uuid}`);
try {
// Hole Cookies für diesen Code (falls vorhanden)
const cookies = cookieStore.get(uuid) || {};
const url = `https://ttde-apps.liga.nu/nuliga/rs/tt/2022/meetingentry/reports/${uuid}`;
const response = await fetch(url, {
method: 'GET',
headers: {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:141.0) Gecko/20100101 Firefox/141.0',
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'de,en-US;q=0.7,en;q=0.3',
'Accept-Encoding': 'gzip, deflate, br, zstd',
'Referer': 'https://ttde-apps.liga.nu/nuliga/nuscore-tt/meetings-list',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'Connection': 'keep-alive',
...(Object.keys(cookies).length > 0 && { 'Cookie': formatCookies(cookies) })
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
// Speichere neue Cookies falls vorhanden
const newCookies = extractCookies(response.headers.raw()['set-cookie']);
if (Object.keys(newCookies).length > 0) {
cookieStore.set(uuid, { ...cookies, ...newCookies });
console.log(`🍪 Cookies für UUID ${uuid} gespeichert:`, Object.keys(newCookies));
}
// CORS-Header setzen
res.set({
'Content-Type': 'application/json; charset=utf-8',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
'Cache-Control': 'no-cache, no-store, must-revalidate'
});
res.json(data);
console.log(`✅ Meeting-Details für UUID ${uuid} erfolgreich abgerufen`);
} catch (error) {
console.error(`❌ Fehler beim Abrufen der Meeting-Details für UUID ${uuid}:`, error);
res.status(500).json({
error: 'Fehler beim Abrufen der Meeting-Details',
details: error.message
});
}
});
export default router;

View File

@@ -0,0 +1,78 @@
import express from 'express';
import nuscoreProxyService from '../services/nuscoreProxyService.js';
const router = express.Router();
// Hauptroute für nuscore-Seite
router.get('/', async (req, res) => {
try {
const { code, pin } = req.query;
if (!code) {
return res.status(400).json({
error: 'Code-Parameter ist erforderlich'
});
}
console.log(`📊 Proxy-Anfrage für Code: ${code}, PIN: ${pin || 'nicht angegeben'}`);
const html = await nuscoreProxyService.proxyNuscorePage(code, pin);
// CORS-Header setzen für iframe-Einbettung
res.set({
'Content-Type': 'text/html; charset=utf-8',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
'X-Frame-Options': 'ALLOWALL',
'Cache-Control': 'no-cache, no-store, must-revalidate'
});
res.send(html);
} catch (error) {
console.error('❌ Proxy-Fehler:', error);
res.status(500).json({
error: 'Fehler beim Laden der nuscore-Seite',
details: error.message
});
}
});
// Asset-Proxy für CSS, JS, Bilder etc.
router.get('/assets/*', async (req, res) => {
try {
const assetPath = req.params[0];
const originalUrl = `https://ttde-apps.liga.nu/nuliga/nuscore-tt/${assetPath}`;
console.log(`📦 Lade Asset: ${originalUrl}`);
const { content, contentType } = await nuscoreProxyService.proxyAsset(originalUrl);
res.set({
'Content-Type': contentType,
'Access-Control-Allow-Origin': '*',
'Cache-Control': 'public, max-age=3600'
});
res.send(content);
} catch (error) {
console.error(`❌ Asset-Fehler für ${req.params[0]}:`, error.message);
res.status(404).json({
error: 'Asset nicht gefunden',
path: req.params[0]
});
}
});
// Health-Check Route
router.get('/health', (req, res) => {
res.json({
status: 'ok',
service: 'nuscore-proxy',
timestamp: new Date().toISOString()
});
});
export default router;

View File

@@ -0,0 +1,140 @@
import express from 'express';
import fetch from 'node-fetch';
const router = express.Router();
// Einfacher HTTP-Proxy ohne Playwright
router.get('/', async (req, res) => {
try {
const { code, pin } = req.query;
if (!code) {
return res.status(400).json({
error: 'Code-Parameter ist erforderlich'
});
}
console.log(`📊 Einfacher Proxy für Code: ${code}, PIN: ${pin || 'nicht angegeben'}`);
// Direkte HTTP-Anfrage an nuscore
const nuscoreUrl = `https://ttde-apps.liga.nu/nuliga/nuscore-tt/meetings-list?code=${encodeURIComponent(code)}`;
const response = await fetch(nuscoreUrl, {
headers: {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'de,en-US;q=0.7,en;q=0.3',
'Accept-Encoding': 'gzip, deflate, br',
'DNT': '1',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1'
},
timeout: 30000
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
let html = await response.text();
// Einfache HTML-Modifikationen
html = html
.replace(/<meta[^>]*http-equiv=["']content-security-policy["'][^>]*>/gi, '')
.replace(/<meta[^>]*http-equiv=["']x-frame-options["'][^>]*>/gi, '')
.replace(/<meta[^>]*http-equiv=["']x-content-type-options["'][^>]*>/gi, '');
// Entferne Service Worker
html = html.replace(
/navigator\.serviceWorker\.register\([^)]+\)/g,
'// Service Worker deaktiviert für Proxy'
);
// Korrigiere alle Asset-URLs zu absoluten URLs
html = html.replace(
/src="\.\//g,
'src="https://ttde-apps.liga.nu/nuliga/nuscore-tt/'
);
html = html.replace(
/href="\.\//g,
'href="https://ttde-apps.liga.nu/nuliga/nuscore-tt/'
);
// Korrigiere auch URLs die bereits auf localhost zeigen
html = html.replace(
/src="http:\/\/localhost:3000\/nuliga\/nuscore-tt\//g,
'src="https://ttde-apps.liga.nu/nuliga/nuscore-tt/'
);
html = html.replace(
/href="http:\/\/localhost:3000\/nuliga\/nuscore-tt\//g,
'href="https://ttde-apps.liga.nu/nuliga/nuscore-tt/'
);
// Entferne vorhandene base-Tags und füge unseres hinzu
html = html.replace(
/<base[^>]*>/gi,
''
);
// Füge base-Tag hinzu für korrekte Asset-Auflösung
html = html.replace(
'<head>',
'<head><base href="https://ttde-apps.liga.nu/nuliga/nuscore-tt/">'
);
// Debug: Logge die HTML-Struktur
console.log('🔍 HTML nach base-Tag:', html.substring(html.indexOf('<base'), html.indexOf('</head>') + 7));
// Füge PIN-Injektion hinzu falls PIN vorhanden
if (pin) {
const pinScript = `
<script>
document.addEventListener('DOMContentLoaded', function() {
setTimeout(function() {
const pinInput = document.querySelector('input[type="password"][placeholder*="PIN"], input[placeholder*="Pin"], input[placeholder*="pin"]');
if (pinInput) {
pinInput.value = '${pin}';
pinInput.dispatchEvent(new Event('input', { bubbles: true }));
pinInput.dispatchEvent(new Event('change', { bubbles: true }));
console.log('PIN automatisch eingefügt: ${pin}');
}
}, 2000);
});
</script>
`;
html = html.replace('</head>', `${pinScript}</head>`);
}
// CORS-Header setzen für iframe-Einbettung
res.set({
'Content-Type': 'text/html; charset=utf-8',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
'X-Frame-Options': 'ALLOWALL',
'Content-Security-Policy': "frame-ancestors *;",
'Cache-Control': 'no-cache, no-store, must-revalidate'
});
res.send(html);
console.log('✅ Einfacher Proxy erfolgreich');
} catch (error) {
console.error('❌ Einfacher Proxy-Fehler:', error);
res.status(500).json({
error: 'Fehler beim Laden der nuscore-Seite',
details: error.message
});
}
});
// Health-Check Route
router.get('/health', (req, res) => {
res.json({
status: 'ok',
service: 'nuscore-simple-proxy',
timestamp: new Date().toISOString()
});
});
export default router;

View File

@@ -38,6 +38,8 @@ import teamRoutes from './routes/teamRoutes.js';
import clubTeamRoutes from './routes/clubTeamRoutes.js';
import teamDocumentRoutes from './routes/teamDocumentRoutes.js';
import seasonRoutes from './routes/seasonRoutes.js';
import nuscoreSimpleProxyRoutes from './routes/nuscoreSimpleProxyRoutes.js';
import nuscoreApiRoutes from './routes/nuscoreApiRoutes.js';
const app = express();
const port = process.env.PORT || 3000;
@@ -87,6 +89,8 @@ app.use('/api/teams', teamRoutes);
app.use('/api/club-teams', clubTeamRoutes);
app.use('/api/team-documents', teamDocumentRoutes);
app.use('/api/seasons', seasonRoutes);
app.use('/api/proxy/nuscore', nuscoreSimpleProxyRoutes);
app.use('/api/nuscore', nuscoreApiRoutes);
app.use(express.static(path.join(__dirname, '../frontend/dist')));

View File

@@ -0,0 +1,148 @@
import { chromium } from 'playwright';
class NuscoreProxyService {
constructor() {
this.browser = null;
this.context = null;
}
async initialize() {
if (!this.browser) {
console.log('🚀 Initialisiere Playwright Browser...');
this.browser = await chromium.launch({
headless: true,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-web-security',
'--disable-features=VizDisplayCompositor'
]
});
this.context = await this.browser.newContext({
userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
viewport: { width: 1920, height: 1080 },
ignoreHTTPSErrors: true
});
console.log('✅ Playwright Browser initialisiert');
}
}
async proxyNuscorePage(code, pin = null) {
try {
await this.initialize();
console.log(`🔍 Lade nuscore-Seite für Code: ${code}`);
const page = await this.context.newPage();
// Navigiere direkt zur nuscore-Seite ohne Request-Interception
const nuscoreUrl = `https://ttde-apps.liga.nu/nuliga/nuscore-tt/meetings-list?code=${encodeURIComponent(code)}`;
await page.goto(nuscoreUrl, {
waitUntil: 'load',
timeout: 30000
});
console.log('📄 Seite geladen');
// Optional: PIN automatisch einfügen falls vorhanden
if (pin) {
console.log(`🔑 Versuche PIN ${pin} einzufügen...`);
try {
// Suche nach PIN-Eingabefeld
const pinInput = await page.locator('input[type="password"][placeholder*="PIN"], input[placeholder*="Pin"], input[placeholder*="pin"]').first();
if (await pinInput.isVisible()) {
await pinInput.fill(pin);
console.log('✅ PIN eingefügt');
// Optional: Submit-Button klicken falls vorhanden
const submitBtn = await page.locator('button[type="submit"], input[type="submit"], button:has-text("Einloggen"), button:has-text("Anmelden")').first();
if (await submitBtn.isVisible()) {
await submitBtn.click();
await page.waitForLoadState('load', { timeout: 5000 });
}
}
} catch (error) {
console.log('⚠️ PIN-Einfügung fehlgeschlagen:', error.message);
}
}
// HTML-Inhalt abrufen und modifizieren
const html = await page.content();
// HTML modifizieren für Proxy-Betrieb
const modifiedHtml = this.modifyHtmlForProxy(html, code);
await page.close();
console.log('✅ nuscore-Seite erfolgreich proxiert');
return modifiedHtml;
} catch (error) {
console.error('❌ Fehler beim Proxying der nuscore-Seite:', error);
throw error;
}
}
modifyHtmlForProxy(html, code) {
// Entferne problematische Headers/Meta-Tags
let modified = html
.replace(/<meta[^>]*http-equiv=["']content-security-policy["'][^>]*>/gi, '')
.replace(/<meta[^>]*http-equiv=["']x-frame-options["'][^>]*>/gi, '')
.replace(/<meta[^>]*http-equiv=["']x-content-type-options["'][^>]*>/gi, '');
// Entferne Service Worker Registrierung (kann Probleme verursachen)
modified = modified.replace(
/navigator\.serviceWorker\.register\([^)]+\)/g,
'// Service Worker deaktiviert für Proxy'
);
return modified;
}
async proxyAsset(url) {
try {
await this.initialize();
const page = await this.context.newPage();
const response = await page.goto(url, {
waitUntil: 'networkidle',
timeout: 15000
});
if (!response || !response.ok()) {
throw new Error(`Asset nicht erreichbar: ${url}`);
}
const content = await response.body();
const contentType = response.headers()['content-type'] || 'application/octet-stream';
await page.close();
return { content, contentType };
} catch (error) {
console.error(`❌ Fehler beim Laden von Asset ${url}:`, error.message);
throw error;
}
}
async cleanup() {
if (this.context) {
await this.context.close();
this.context = null;
}
if (this.browser) {
await this.browser.close();
this.browser = null;
}
console.log('🧹 Playwright Browser bereinigt');
}
}
// Singleton-Instanz
const nuscoreProxyService = new NuscoreProxyService();
export default nuscoreProxyService;