Initial Agrarmonitor connector
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
node_modules/
|
||||
dist/
|
||||
data/
|
||||
*.log
|
||||
.DS_Store
|
||||
.env
|
||||
@@ -0,0 +1,65 @@
|
||||
# Agrarmonitor Connector
|
||||
|
||||
TypeScript MVP connector for Agrarmonitor with shared cookie persistence, optional AES-GCM cookie encryption, automatic login, retry after expired sessions, device registration checks, and customer detail extraction.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
AesGcmCookieEncryptor,
|
||||
FileCookieStore,
|
||||
createAgrarmonitorClient,
|
||||
} from 'agrarmonitor-connector';
|
||||
|
||||
const agrarmonitor = await createAgrarmonitorClient({
|
||||
baseUrl: 'https://admin7.agrarmonitor.de',
|
||||
apiToken: process.env.AGRARMONITOR_API_TOKEN,
|
||||
username: process.env.AGRARMONITOR_USERNAME ?? '',
|
||||
password: process.env.AGRARMONITOR_PASSWORD ?? '',
|
||||
cookieStore: new FileCookieStore(
|
||||
process.env.AGRARMONITOR_COOKIE_PATH ?? './data/agrarmonitor-cookies.json',
|
||||
{
|
||||
encryptor: process.env.AGRARMONITOR_ENCRYPTION_KEY
|
||||
? new AesGcmCookieEncryptor(process.env.AGRARMONITOR_ENCRYPTION_KEY)
|
||||
: undefined,
|
||||
logger: console,
|
||||
}
|
||||
),
|
||||
logger: console,
|
||||
});
|
||||
|
||||
const freischaltung = await agrarmonitor.checkFreigeschaltet();
|
||||
console.log(freischaltung.freigeschaltet);
|
||||
|
||||
const registrierung = await agrarmonitor.checkRegistriert();
|
||||
console.log(registrierung.registriert);
|
||||
|
||||
const response = await agrarmonitor.http.get('/kunden/detail/123');
|
||||
console.log(response.status);
|
||||
```
|
||||
|
||||
## Cookie Persistence
|
||||
|
||||
`FileCookieStore` keeps one shared `CookieJar` per file path inside the Node process. Multiple connector instances that use the same cookie file therefore reuse the same session and every successful request saves the latest cookies back to disk.
|
||||
|
||||
The store can read both the connector format and the older Telefonbuch cookie-array format.
|
||||
|
||||
## Useful Methods
|
||||
|
||||
- `checkFreigeschaltet()` checks whether Agrarmonitor redirects to `/freischaltung/`.
|
||||
- `checkRegistriert()` checks whether the page still contains `Neues Gerät registrieren`.
|
||||
- `registerDevice({ agrarmonitorId, pcName })` loads `/freischaltung/`, extracts the nonce, and posts the registration request.
|
||||
- `fetchCustomers()` loads customers from `https://api.agrarmonitor.de/v1/kunden`.
|
||||
- `saveSession()` explicitly persists the current cookie jar.
|
||||
@@ -0,0 +1,35 @@
|
||||
import {
|
||||
AesGcmCookieEncryptor,
|
||||
FileCookieStore,
|
||||
createAgrarmonitorClient,
|
||||
} from '../src';
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const agrarmonitor = await createAgrarmonitorClient({
|
||||
baseUrl: 'https://admin7.agrarmonitor.de',
|
||||
apiToken: process.env.AGRARMONITOR_API_TOKEN,
|
||||
username: process.env.AGRARMONITOR_USERNAME ?? '',
|
||||
password: process.env.AGRARMONITOR_PASSWORD ?? '',
|
||||
cookieStore: new FileCookieStore(
|
||||
process.env.AGRARMONITOR_COOKIE_PATH ?? './data/agrarmonitor-cookies.json',
|
||||
{
|
||||
encryptor: process.env.AGRARMONITOR_ENCRYPTION_KEY
|
||||
? new AesGcmCookieEncryptor(process.env.AGRARMONITOR_ENCRYPTION_KEY)
|
||||
: undefined,
|
||||
logger: console,
|
||||
}
|
||||
),
|
||||
logger: console,
|
||||
});
|
||||
|
||||
const freischaltung = await agrarmonitor.checkFreigeschaltet();
|
||||
console.log('Freigeschaltet:', freischaltung.freigeschaltet);
|
||||
|
||||
const registrierung = await agrarmonitor.checkRegistriert();
|
||||
console.log('Registriert:', registrierung.registriert);
|
||||
|
||||
const response = await agrarmonitor.http.get('/kunden/detail/123');
|
||||
console.log(response.status);
|
||||
}
|
||||
|
||||
void main();
|
||||
Generated
+989
@@ -0,0 +1,989 @@
|
||||
{
|
||||
"name": "agrarmonitor-connector",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "agrarmonitor-connector",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^1.7.9",
|
||||
"axios-cookiejar-support": "^5.0.5",
|
||||
"jsdom": "^29.1.1",
|
||||
"tough-cookie": "^4.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jsdom": "^28.0.3",
|
||||
"@types/node": "^22.10.2",
|
||||
"@types/tough-cookie": "^4.0.5",
|
||||
"typescript": "^5.7.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@asamuzakjp/css-color": {
|
||||
"version": "5.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz",
|
||||
"integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@asamuzakjp/generational-cache": "^1.0.1",
|
||||
"@csstools/css-calc": "^3.2.0",
|
||||
"@csstools/css-color-parser": "^4.1.0",
|
||||
"@csstools/css-parser-algorithms": "^4.0.0",
|
||||
"@csstools/css-tokenizer": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@asamuzakjp/dom-selector": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz",
|
||||
"integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@asamuzakjp/generational-cache": "^1.0.1",
|
||||
"@asamuzakjp/nwsapi": "^2.3.9",
|
||||
"bidi-js": "^1.0.3",
|
||||
"css-tree": "^3.2.1",
|
||||
"is-potential-custom-element-name": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@asamuzakjp/generational-cache": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz",
|
||||
"integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@asamuzakjp/nwsapi": {
|
||||
"version": "2.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz",
|
||||
"integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@bramus/specificity": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz",
|
||||
"integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"css-tree": "^3.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"specificity": "bin/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/color-helpers": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz",
|
||||
"integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT-0",
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/css-calc": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.1.tgz",
|
||||
"integrity": "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@csstools/css-parser-algorithms": "^4.0.0",
|
||||
"@csstools/css-tokenizer": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/css-color-parser": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.1.tgz",
|
||||
"integrity": "sha512-eZ5XOtyhK+mggRafYUWzA0tvaYOFgdY8AkgQiCJF9qNAePnUo/zmsqqYubBBb3sQ8uNUaSKTY9s9klfRaAXL0g==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@csstools/color-helpers": "^6.0.2",
|
||||
"@csstools/css-calc": "^3.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@csstools/css-parser-algorithms": "^4.0.0",
|
||||
"@csstools/css-tokenizer": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/css-parser-algorithms": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz",
|
||||
"integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@csstools/css-tokenizer": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/css-syntax-patches-for-csstree": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.4.tgz",
|
||||
"integrity": "sha512-wgsqt92b7C7tQhIdPNxj0n9zuUbQlvAuI1exyzeNrOKOi62SD7ren8zqszmpVREjAOqg8cD2FqYhQfAuKjk4sw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT-0",
|
||||
"peerDependencies": {
|
||||
"css-tree": "^3.2.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"css-tree": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/css-tokenizer": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz",
|
||||
"integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@exodus/bytes": {
|
||||
"version": "1.15.1",
|
||||
"resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz",
|
||||
"integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@noble/hashes": "^1.8.0 || ^2.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@noble/hashes": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@types/jsdom": {
|
||||
"version": "28.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-28.0.3.tgz",
|
||||
"integrity": "sha512-/HQ2uFoetFTXuye8vzIcHw2z6Fwi7Hi/qcgC+RoS9NCyewiqxhVGqlG+ViGB6lkax481R6dmhf1I7lIGlzJStQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"@types/tough-cookie": "*",
|
||||
"parse5": "^8.0.0",
|
||||
"undici-types": "^7.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/jsdom/node_modules/undici-types": {
|
||||
"version": "7.25.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.25.0.tgz",
|
||||
"integrity": "sha512-AXNgS1Byr27fTI+2bsPEkV9CxkT8H6xNyRI68b3TatlZo3RkzlqQBLL+w7SmGPVpokjHbcuNVQUWE7FRTg+LRA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.19.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.18.tgz",
|
||||
"integrity": "sha512-9v00a+dn2yWVsYDEunWC4g/TcRKVq3r8N5FuZp7u0SGrPvdN9c2yXI9bBuf5Fl0hNCb+QTIePTn5pJs2pwBOQQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/tough-cookie": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
|
||||
"integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "7.1.4",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
|
||||
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.16.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz",
|
||||
"integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.16.0",
|
||||
"form-data": "^4.0.5",
|
||||
"proxy-from-env": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios-cookiejar-support": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/axios-cookiejar-support/-/axios-cookiejar-support-5.0.5.tgz",
|
||||
"integrity": "sha512-jJG+p7JnOYxkVrYkCDKBrLqUmcpwHZTNQrEcIEKr5qe7YVTyPAD9nCsi1cO5LDmQpQApfS430czO+oceI3g/3g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"http-cookie-agent": "^6.0.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/3846masa"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"axios": ">=0.20.0",
|
||||
"tough-cookie": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bidi-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
|
||||
"integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"require-from-string": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/css-tree": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
|
||||
"integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mdn-data": "2.27.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/data-urls": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz",
|
||||
"integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"whatwg-mimetype": "^5.0.0",
|
||||
"whatwg-url": "^16.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/decimal.js": {
|
||||
"version": "10.6.0",
|
||||
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
|
||||
"integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz",
|
||||
"integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.16.0",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
|
||||
"integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
|
||||
"integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/html-encoding-sniffer": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz",
|
||||
"integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@exodus/bytes": "^1.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/http-cookie-agent": {
|
||||
"version": "6.0.8",
|
||||
"resolved": "https://registry.npmjs.org/http-cookie-agent/-/http-cookie-agent-6.0.8.tgz",
|
||||
"integrity": "sha512-qnYh3yLSr2jBsTYkw11elq+T361uKAJaZ2dR4cfYZChw1dt9uL5t3zSUwehoqqVb4oldk1BpkXKm2oat8zV+oA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"agent-base": "^7.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/3846masa"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tough-cookie": "^4.0.0 || ^5.0.0",
|
||||
"undici": "^5.11.0 || ^6.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"undici": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/is-potential-custom-element-name": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
|
||||
"integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jsdom": {
|
||||
"version": "29.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz",
|
||||
"integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@asamuzakjp/css-color": "^5.1.11",
|
||||
"@asamuzakjp/dom-selector": "^7.1.1",
|
||||
"@bramus/specificity": "^2.4.2",
|
||||
"@csstools/css-syntax-patches-for-csstree": "^1.1.3",
|
||||
"@exodus/bytes": "^1.15.0",
|
||||
"css-tree": "^3.2.1",
|
||||
"data-urls": "^7.0.0",
|
||||
"decimal.js": "^10.6.0",
|
||||
"html-encoding-sniffer": "^6.0.0",
|
||||
"is-potential-custom-element-name": "^1.0.1",
|
||||
"lru-cache": "^11.3.5",
|
||||
"parse5": "^8.0.1",
|
||||
"saxes": "^6.0.0",
|
||||
"symbol-tree": "^3.2.4",
|
||||
"tough-cookie": "^6.0.1",
|
||||
"undici": "^7.25.0",
|
||||
"w3c-xmlserializer": "^5.0.0",
|
||||
"webidl-conversions": "^8.0.1",
|
||||
"whatwg-mimetype": "^5.0.0",
|
||||
"whatwg-url": "^16.0.1",
|
||||
"xml-name-validator": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"canvas": "^3.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"canvas": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/jsdom/node_modules/tough-cookie": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz",
|
||||
"integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"tldts": "^7.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/jsdom/node_modules/undici": {
|
||||
"version": "7.25.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz",
|
||||
"integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.18.1"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "11.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.0.tgz",
|
||||
"integrity": "sha512-5YgH9UJd7wVb9hIouI2adWpgqrrICkt070Dnj8EUY1+B4B2P9eRLPAkAAo6NICA7CEhOIeBHl46u9zSNpNu7zA==",
|
||||
"license": "BlueOak-1.0.0",
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mdn-data": {
|
||||
"version": "2.27.1",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
|
||||
"integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==",
|
||||
"license": "CC0-1.0"
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/parse5": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz",
|
||||
"integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"entities": "^8.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
|
||||
"integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/psl": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
|
||||
"integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"punycode": "^2.3.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/lupomontero"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/querystringify": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
|
||||
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/require-from-string": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/requires-port": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/saxes": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
|
||||
"integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"xmlchars": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v12.22.7"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/symbol-tree": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
||||
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tldts": {
|
||||
"version": "7.0.30",
|
||||
"resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.30.tgz",
|
||||
"integrity": "sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tldts-core": "^7.0.30"
|
||||
},
|
||||
"bin": {
|
||||
"tldts": "bin/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/tldts-core": {
|
||||
"version": "7.0.30",
|
||||
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.30.tgz",
|
||||
"integrity": "sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tough-cookie": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
|
||||
"integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"psl": "^1.1.33",
|
||||
"punycode": "^2.1.1",
|
||||
"universalify": "^0.2.0",
|
||||
"url-parse": "^1.5.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz",
|
||||
"integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"punycode": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "6.25.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.25.0.tgz",
|
||||
"integrity": "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
|
||||
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/url-parse": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
|
||||
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"querystringify": "^2.1.1",
|
||||
"requires-port": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/w3c-xmlserializer": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
|
||||
"integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"xml-name-validator": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz",
|
||||
"integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-mimetype": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz",
|
||||
"integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "16.0.1",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz",
|
||||
"integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@exodus/bytes": "^1.11.0",
|
||||
"tr46": "^6.0.0",
|
||||
"webidl-conversions": "^8.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xml-name-validator": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
|
||||
"integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlchars": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
|
||||
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "agrarmonitor-connector",
|
||||
"version": "0.1.0",
|
||||
"description": "TypeScript connector MVP for Agrarmonitor with cookie persistence.",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"clean": "rm -rf dist",
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"keywords": [
|
||||
"agrarmonitor",
|
||||
"connector",
|
||||
"typescript"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^1.7.9",
|
||||
"axios-cookiejar-support": "^5.0.5",
|
||||
"jsdom": "^29.1.1",
|
||||
"tough-cookie": "^4.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jsdom": "^28.0.3",
|
||||
"@types/node": "^22.10.2",
|
||||
"@types/tough-cookie": "^4.0.5",
|
||||
"typescript": "^5.7.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
import axios, { type AxiosInstance, type AxiosRequestConfig, type AxiosResponse } from 'axios';
|
||||
import { wrapper } from 'axios-cookiejar-support';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import { CookieJar } from 'tough-cookie';
|
||||
import type {
|
||||
AgrarmonitorApiCustomer,
|
||||
AgrarmonitorConnectorOptions,
|
||||
AgrarmonitorConnectorResult,
|
||||
AgrarmonitorDeviceRegistrationOptions,
|
||||
AgrarmonitorDeviceRegistrationResult,
|
||||
AgrarmonitorFetchCustomersOptions,
|
||||
AgrarmonitorFreischaltungStatus,
|
||||
AgrarmonitorLoginStrategy,
|
||||
AgrarmonitorRegistrierungStatus,
|
||||
Logger,
|
||||
} from './types';
|
||||
|
||||
type RetryableAxiosRequestConfig = AxiosRequestConfig & {
|
||||
_agrarmonitorRetry?: boolean;
|
||||
};
|
||||
|
||||
export class AgrarmonitorConnector implements AgrarmonitorConnectorResult {
|
||||
public http!: AxiosInstance;
|
||||
|
||||
private readonly baseUrl: string;
|
||||
private readonly apiBaseUrl: string;
|
||||
private readonly timeoutMs: number;
|
||||
private readonly autoLogin: boolean;
|
||||
private readonly autoRetry: boolean;
|
||||
private readonly loginStrategy: AgrarmonitorLoginStrategy;
|
||||
private readonly logger?: Logger;
|
||||
private cookieJar!: CookieJar;
|
||||
private loginInProgress: Promise<void> | null = null;
|
||||
|
||||
constructor(private readonly options: AgrarmonitorConnectorOptions) {
|
||||
this.baseUrl = options.baseUrl ?? 'https://admin7.agrarmonitor.de';
|
||||
this.apiBaseUrl = options.apiBaseUrl ?? 'https://api.agrarmonitor.de';
|
||||
this.timeoutMs = options.timeoutMs ?? 15000;
|
||||
this.autoLogin = options.autoLogin ?? true;
|
||||
this.autoRetry = options.autoRetry ?? true;
|
||||
this.loginStrategy = options.loginStrategy ?? 'auto';
|
||||
this.logger = options.logger;
|
||||
}
|
||||
|
||||
async init(): Promise<this> {
|
||||
this.cookieJar = await this.options.cookieStore.load();
|
||||
this.http = this.createHttpClient();
|
||||
|
||||
if (this.autoLogin) {
|
||||
const valid = await this.isSessionValid();
|
||||
|
||||
if (!valid) {
|
||||
await this.login();
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
async login(): Promise<void> {
|
||||
if (this.loginInProgress) {
|
||||
return this.loginInProgress;
|
||||
}
|
||||
|
||||
this.loginInProgress = this.performLogin().finally(() => {
|
||||
this.loginInProgress = null;
|
||||
});
|
||||
|
||||
return this.loginInProgress;
|
||||
}
|
||||
|
||||
async clearSession(): Promise<void> {
|
||||
this.cookieJar = new CookieJar();
|
||||
await this.options.cookieStore.clear();
|
||||
this.http = this.createHttpClient();
|
||||
}
|
||||
|
||||
async saveSession(): Promise<void> {
|
||||
await this.options.cookieStore.save(this.cookieJar);
|
||||
}
|
||||
|
||||
async getCookieCount(url = this.baseUrl): Promise<number> {
|
||||
return this.cookieJar.getCookiesSync(url).length;
|
||||
}
|
||||
|
||||
async checkFreigeschaltet(): Promise<AgrarmonitorFreischaltungStatus> {
|
||||
const response = await this.http.get('/', {
|
||||
maxRedirects: 0,
|
||||
validateStatus: status => status >= 200 && status < 400,
|
||||
});
|
||||
|
||||
await this.saveSession();
|
||||
|
||||
const redirectLocation = this.getHeader(response, 'location');
|
||||
const redirected = response.status >= 300 && response.status < 400 && this.isFreischaltungUrl(redirectLocation);
|
||||
|
||||
return {
|
||||
freigeschaltet: !redirected,
|
||||
status: response.status,
|
||||
redirected,
|
||||
redirectLocation,
|
||||
timestamp: new Date().toISOString(),
|
||||
cookies: await this.getCookieCount(),
|
||||
};
|
||||
}
|
||||
|
||||
async checkRegistriert(): Promise<AgrarmonitorRegistrierungStatus> {
|
||||
const response = await this.http.get('/', {
|
||||
maxRedirects: 5,
|
||||
validateStatus: status => status >= 200 && status < 500,
|
||||
});
|
||||
|
||||
await this.saveSession();
|
||||
|
||||
const pageContent = typeof response.data === 'string' ? response.data : '';
|
||||
const hasRegistrationText = pageContent.includes('Neues Gerät registrieren');
|
||||
|
||||
return {
|
||||
registriert: !hasRegistrationText,
|
||||
status: response.status,
|
||||
hasRegistrationText,
|
||||
timestamp: new Date().toISOString(),
|
||||
cookies: await this.getCookieCount(),
|
||||
};
|
||||
}
|
||||
|
||||
async registerDevice(
|
||||
registration: AgrarmonitorDeviceRegistrationOptions
|
||||
): Promise<AgrarmonitorDeviceRegistrationResult> {
|
||||
const agrarmonitorId = registration.agrarmonitorId.trim();
|
||||
const pcName = registration.pcName.trim();
|
||||
|
||||
if (!agrarmonitorId || !pcName) {
|
||||
throw new Error('AgrarmonitorID und PC-Name sind erforderlich');
|
||||
}
|
||||
|
||||
const freischaltungResponse = await this.http.get('/freischaltung/');
|
||||
const responseContent = typeof freischaltungResponse.data === 'string' ? freischaltungResponse.data : '';
|
||||
const nonce = this.extractNonce(responseContent, '#nonce, input[name="nonce"]');
|
||||
|
||||
const registerResponse = await this.http.post(
|
||||
'/freischaltung/api/register.php',
|
||||
{
|
||||
firma: agrarmonitorId,
|
||||
name: pcName,
|
||||
nonce,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
validateStatus: status => status >= 200 && status < 500,
|
||||
}
|
||||
);
|
||||
|
||||
await this.saveSession();
|
||||
|
||||
const success = registerResponse.status >= 200 && registerResponse.status < 300;
|
||||
|
||||
return {
|
||||
success,
|
||||
status: registerResponse.status,
|
||||
message: success ? 'Registrierung erfolgreich' : 'Registrierung fehlgeschlagen',
|
||||
data: {
|
||||
agrarmonitorId,
|
||||
pcName,
|
||||
nonce: this.maskNonce(nonce),
|
||||
},
|
||||
timestamp: new Date().toISOString(),
|
||||
cookies: await this.getCookieCount(),
|
||||
};
|
||||
}
|
||||
|
||||
async fetchCustomers(options: AgrarmonitorFetchCustomersOptions = {}): Promise<AgrarmonitorApiCustomer[]> {
|
||||
const apiToken = options.apiToken ?? this.options.apiToken;
|
||||
|
||||
if (!apiToken) {
|
||||
throw new Error('Agrarmonitor API-Token nicht konfiguriert');
|
||||
}
|
||||
|
||||
const response = await this.http.get(`${this.apiBaseUrl}/v1/kunden`, {
|
||||
params: {
|
||||
per_page: options.perPage ?? 99999,
|
||||
api_token: apiToken,
|
||||
},
|
||||
});
|
||||
|
||||
await this.saveSession();
|
||||
|
||||
const responseData = response.data as { data?: unknown };
|
||||
|
||||
if (!responseData || !Array.isArray(responseData.data)) {
|
||||
throw new Error('Ungueltige Agrarmonitor API-Antwort');
|
||||
}
|
||||
|
||||
return responseData.data as AgrarmonitorApiCustomer[];
|
||||
}
|
||||
|
||||
private createHttpClient(): AxiosInstance {
|
||||
const client = wrapper(
|
||||
axios.create({
|
||||
baseURL: this.baseUrl,
|
||||
jar: this.cookieJar,
|
||||
withCredentials: true,
|
||||
timeout: this.timeoutMs,
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
|
||||
},
|
||||
maxRedirects: 5,
|
||||
validateStatus: status => status >= 200 && status < 400,
|
||||
})
|
||||
);
|
||||
|
||||
client.interceptors.response.use(
|
||||
async response => {
|
||||
await this.options.cookieStore.save(this.cookieJar);
|
||||
|
||||
if (this.autoRetry && this.isLoginRequiredResponse(response)) {
|
||||
return this.retryAfterLogin(response.config);
|
||||
}
|
||||
|
||||
return response;
|
||||
},
|
||||
async error => {
|
||||
const response = error.response as AxiosResponse | undefined;
|
||||
|
||||
if (this.autoRetry && response && this.isLoginRequiredResponse(response)) {
|
||||
return this.retryAfterLogin(error.config);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
private async performLogin(): Promise<void> {
|
||||
if (!this.options.username || !this.options.password) {
|
||||
throw new Error('Agrarmonitor-Credentials nicht konfiguriert');
|
||||
}
|
||||
|
||||
this.logger?.info?.('Fuehre Agrarmonitor-Login durch');
|
||||
|
||||
if (this.loginStrategy === 'auth') {
|
||||
await this.performAuthLogin();
|
||||
} else if (this.loginStrategy === 'legacy') {
|
||||
await this.performLegacyLogin();
|
||||
} else {
|
||||
await this.performAutoLogin();
|
||||
}
|
||||
|
||||
await this.options.cookieStore.save(this.cookieJar);
|
||||
this.logger?.info?.('Agrarmonitor-Login erfolgreich');
|
||||
}
|
||||
|
||||
private async performAutoLogin(): Promise<void> {
|
||||
try {
|
||||
await this.performAuthLogin();
|
||||
} catch (authError) {
|
||||
this.logger?.warn?.('Agrarmonitor-Login via /auth/login fehlgeschlagen, versuche Legacy-Login', authError);
|
||||
await this.performLegacyLogin();
|
||||
}
|
||||
}
|
||||
|
||||
private async performAuthLogin(): Promise<void> {
|
||||
await this.http.get('/auth/login');
|
||||
|
||||
const loginData = new URLSearchParams({
|
||||
email: this.options.username,
|
||||
password: this.options.password,
|
||||
remember: 'on',
|
||||
});
|
||||
|
||||
const response = await this.http.post('/auth/login', loginData, {
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
});
|
||||
|
||||
const responseText = typeof response.data === 'string' ? response.data : '';
|
||||
|
||||
if (responseText.includes('Anmeldung fehlgeschlagen')) {
|
||||
throw new Error('Agrarmonitor-Login fehlgeschlagen');
|
||||
}
|
||||
}
|
||||
|
||||
private async performLegacyLogin(): Promise<void> {
|
||||
const loginPageResponse = await this.http.get('/');
|
||||
const loginPageText = typeof loginPageResponse.data === 'string' ? loginPageResponse.data : '';
|
||||
|
||||
if (!this.isLoginPageText(loginPageText)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nonce = this.extractNonce(loginPageText, 'input[name="nonce"]');
|
||||
const loginData = new URLSearchParams({
|
||||
username: this.options.username,
|
||||
passwort: this.options.password,
|
||||
nonce,
|
||||
});
|
||||
|
||||
const response = await this.http.post('/redirect.php?id=benutzerverwaltung&action=login', loginData, {
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
});
|
||||
|
||||
const responseText = typeof response.data === 'string' ? response.data : '';
|
||||
|
||||
if (this.isLoginPageText(responseText)) {
|
||||
throw new Error('Agrarmonitor-Legacy-Login fehlgeschlagen');
|
||||
}
|
||||
}
|
||||
|
||||
private async isSessionValid(): Promise<boolean> {
|
||||
try {
|
||||
const response = await this.http.get('/');
|
||||
return !this.isLoginRequiredResponse(response);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private isLoginRequiredResponse(response: AxiosResponse): boolean {
|
||||
const responseUrl = this.getResponseUrl(response);
|
||||
const responseText = typeof response.data === 'string' ? response.data : '';
|
||||
|
||||
return (
|
||||
response.status === 401 ||
|
||||
response.status === 403 ||
|
||||
responseUrl.includes('/auth/login') ||
|
||||
responseText.includes('/auth/login') ||
|
||||
this.isLoginPageText(responseText) ||
|
||||
responseText.includes('Anmeldung') ||
|
||||
responseText.includes('Einloggen')
|
||||
);
|
||||
}
|
||||
|
||||
private async retryAfterLogin(config: RetryableAxiosRequestConfig): Promise<AxiosResponse> {
|
||||
if (config._agrarmonitorRetry) {
|
||||
throw new Error('Agrarmonitor-Request nach erneutem Login weiterhin nicht autorisiert');
|
||||
}
|
||||
|
||||
config._agrarmonitorRetry = true;
|
||||
|
||||
this.logger?.info?.('Agrarmonitor-Session abgelaufen, erneuter Login wird ausgefuehrt');
|
||||
await this.login();
|
||||
|
||||
return this.http.request(config);
|
||||
}
|
||||
|
||||
private getResponseUrl(response: AxiosResponse): string {
|
||||
const request = response.request as { res?: { responseUrl?: string } } | undefined;
|
||||
return request?.res?.responseUrl ?? '';
|
||||
}
|
||||
|
||||
private getHeader(response: AxiosResponse, header: string): string | null {
|
||||
const value = response.headers[header.toLowerCase()];
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value[0] ?? null;
|
||||
}
|
||||
|
||||
return typeof value === 'string' ? value : null;
|
||||
}
|
||||
|
||||
private isFreischaltungUrl(value: string | null): boolean {
|
||||
return Boolean(value?.includes('freischaltung'));
|
||||
}
|
||||
|
||||
private isLoginPageText(responseText: string): boolean {
|
||||
return responseText.includes('Anmeldung - AGRARMONITOR');
|
||||
}
|
||||
|
||||
private extractNonce(html: string, selector: string): string {
|
||||
const dom = new JSDOM(html);
|
||||
const element = dom.window.document.querySelector<HTMLInputElement>(selector);
|
||||
const nonce = element?.getAttribute('value') ?? element?.value ?? '';
|
||||
|
||||
if (!nonce) {
|
||||
throw new Error('Nonce-Element nicht gefunden oder leer');
|
||||
}
|
||||
|
||||
return nonce;
|
||||
}
|
||||
|
||||
private maskNonce(nonce: string): string {
|
||||
return nonce.length <= 10 ? nonce : `${nonce.slice(0, 10)}...`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { Cookie, CookieJar } from 'tough-cookie';
|
||||
import type { CookieEncryptor, CookieStore, Logger } from '../types';
|
||||
|
||||
interface FileCookieStoreOptions {
|
||||
encryptor?: CookieEncryptor;
|
||||
logger?: Logger;
|
||||
}
|
||||
|
||||
type PlainCookieFile = ReturnType<CookieJar['toJSON']>;
|
||||
|
||||
type EncryptedCookieFile = {
|
||||
encrypted: true;
|
||||
algorithm: 'aes-256-gcm';
|
||||
data: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export class FileCookieStore implements CookieStore {
|
||||
private static readonly sharedJars = new Map<string, CookieJar>();
|
||||
|
||||
constructor(
|
||||
private readonly filePath: string,
|
||||
private readonly options: FileCookieStoreOptions = {}
|
||||
) {}
|
||||
|
||||
async load(): Promise<CookieJar> {
|
||||
const sharedJar = FileCookieStore.sharedJars.get(this.filePath);
|
||||
if (sharedJar) {
|
||||
return sharedJar;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!fs.existsSync(this.filePath)) {
|
||||
this.options.logger?.info?.('Neuer Cookie-Store wird erstellt');
|
||||
return this.remember(new CookieJar());
|
||||
}
|
||||
|
||||
const raw = fs.readFileSync(this.filePath, 'utf8');
|
||||
const parsed = JSON.parse(raw) as PlainCookieFile | EncryptedCookieFile;
|
||||
|
||||
if (this.isEncryptedCookieFile(parsed)) {
|
||||
if (!this.options.encryptor) {
|
||||
throw new Error('Cookie-Datei ist verschluesselt, aber kein Encryptor wurde konfiguriert');
|
||||
}
|
||||
|
||||
const decrypted = this.options.encryptor.decrypt(parsed.data);
|
||||
const cookieJson = JSON.parse(decrypted);
|
||||
return this.remember(this.cookieJarFromJson(cookieJson));
|
||||
}
|
||||
|
||||
return this.remember(this.cookieJarFromJson(parsed));
|
||||
} catch (error) {
|
||||
this.options.logger?.warn?.('Cookies konnten nicht geladen werden, neuer Cookie-Store wird erstellt', error);
|
||||
return this.remember(new CookieJar());
|
||||
}
|
||||
}
|
||||
|
||||
async save(cookieJar: CookieJar): Promise<void> {
|
||||
FileCookieStore.sharedJars.set(this.filePath, cookieJar);
|
||||
fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
|
||||
|
||||
const cookieJson = cookieJar.toJSON();
|
||||
|
||||
let fileContent: string;
|
||||
|
||||
if (this.options.encryptor) {
|
||||
fileContent = JSON.stringify(
|
||||
{
|
||||
encrypted: true,
|
||||
algorithm: 'aes-256-gcm',
|
||||
data: this.options.encryptor.encrypt(JSON.stringify(cookieJson)),
|
||||
updatedAt: new Date().toISOString(),
|
||||
} satisfies EncryptedCookieFile,
|
||||
null,
|
||||
2
|
||||
);
|
||||
} else {
|
||||
fileContent = JSON.stringify(cookieJson, null, 2);
|
||||
}
|
||||
|
||||
fs.writeFileSync(this.filePath, fileContent, {
|
||||
mode: 0o600,
|
||||
});
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
FileCookieStore.sharedJars.set(this.filePath, new CookieJar());
|
||||
|
||||
if (fs.existsSync(this.filePath)) {
|
||||
fs.unlinkSync(this.filePath);
|
||||
}
|
||||
}
|
||||
|
||||
private isEncryptedCookieFile(value: unknown): value is EncryptedCookieFile {
|
||||
return (
|
||||
typeof value === 'object' &&
|
||||
value !== null &&
|
||||
(value as EncryptedCookieFile).encrypted === true &&
|
||||
(value as EncryptedCookieFile).algorithm === 'aes-256-gcm' &&
|
||||
typeof (value as EncryptedCookieFile).data === 'string'
|
||||
);
|
||||
}
|
||||
|
||||
private cookieJarFromJson(value: unknown): CookieJar {
|
||||
if (Array.isArray(value)) {
|
||||
const cookieJar = new CookieJar();
|
||||
|
||||
for (const cookieData of value) {
|
||||
const cookie = Cookie.fromJSON(cookieData);
|
||||
|
||||
if (cookie) {
|
||||
const domain = cookie.domain ?? 'admin7.agrarmonitor.de';
|
||||
const url = domain.startsWith('http') ? domain : `https://${domain}`;
|
||||
cookieJar.setCookieSync(cookie, url);
|
||||
}
|
||||
}
|
||||
|
||||
return cookieJar;
|
||||
}
|
||||
|
||||
return CookieJar.fromJSON(JSON.stringify(value));
|
||||
}
|
||||
|
||||
private remember(cookieJar: CookieJar): CookieJar {
|
||||
FileCookieStore.sharedJars.set(this.filePath, cookieJar);
|
||||
return cookieJar;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { CookieJar } from 'tough-cookie';
|
||||
import type { CookieStore } from '../types';
|
||||
|
||||
export class MemoryCookieStore implements CookieStore {
|
||||
private cookieJar = new CookieJar();
|
||||
|
||||
async load(): Promise<CookieJar> {
|
||||
return this.cookieJar;
|
||||
}
|
||||
|
||||
async save(cookieJar: CookieJar): Promise<void> {
|
||||
this.cookieJar = cookieJar;
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
this.cookieJar = new CookieJar();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { AgrarmonitorConnector } from './AgrarmonitorConnector';
|
||||
import type { AgrarmonitorConnectorOptions, AgrarmonitorConnectorResult } from './types';
|
||||
|
||||
export async function createAgrarmonitorClient(
|
||||
options: AgrarmonitorConnectorOptions
|
||||
): Promise<AgrarmonitorConnectorResult> {
|
||||
const connector = new AgrarmonitorConnector(options);
|
||||
await connector.init();
|
||||
return connector;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import * as crypto from 'crypto';
|
||||
import type { CookieEncryptor } from '../types';
|
||||
|
||||
export class AesGcmCookieEncryptor implements CookieEncryptor {
|
||||
private readonly key: Buffer;
|
||||
|
||||
constructor(secret: string) {
|
||||
if (!secret || secret.trim().length < 16) {
|
||||
throw new Error('Cookie encryption secret muss mindestens 16 Zeichen lang sein');
|
||||
}
|
||||
|
||||
this.key = crypto.createHash('sha256').update(secret).digest();
|
||||
}
|
||||
|
||||
encrypt(text: string): string {
|
||||
const iv = crypto.randomBytes(12);
|
||||
const cipher = crypto.createCipheriv('aes-256-gcm', this.key, iv);
|
||||
|
||||
let encrypted = cipher.update(text, 'utf8', 'hex');
|
||||
encrypted += cipher.final('hex');
|
||||
|
||||
const authTag = cipher.getAuthTag();
|
||||
|
||||
return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
|
||||
}
|
||||
|
||||
decrypt(encryptedText: string): string {
|
||||
const [ivHex, authTagHex, encrypted] = encryptedText.split(':');
|
||||
|
||||
if (!ivHex || !authTagHex || !encrypted) {
|
||||
throw new Error('Ungueltiges verschluesseltes Cookie-Format');
|
||||
}
|
||||
|
||||
const iv = Buffer.from(ivHex, 'hex');
|
||||
const authTag = Buffer.from(authTagHex, 'hex');
|
||||
|
||||
const decipher = crypto.createDecipheriv('aes-256-gcm', this.key, iv);
|
||||
decipher.setAuthTag(authTag);
|
||||
|
||||
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
||||
decrypted += decipher.final('utf8');
|
||||
|
||||
return decrypted;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
export { AesGcmCookieEncryptor } from './crypto/AesGcmCookieEncryptor';
|
||||
export { FileCookieStore } from './cookie-store/FileCookieStore';
|
||||
export { MemoryCookieStore } from './cookie-store/MemoryCookieStore';
|
||||
export { AgrarmonitorConnector } from './AgrarmonitorConnector';
|
||||
export { createAgrarmonitorClient } from './createAgrarmonitorClient';
|
||||
export type {
|
||||
AgrarmonitorConnectorOptions,
|
||||
AgrarmonitorConnectorResult,
|
||||
CookieEncryptor,
|
||||
CookieStore,
|
||||
Logger,
|
||||
} from './types';
|
||||
@@ -0,0 +1,98 @@
|
||||
import type { AxiosInstance } from 'axios';
|
||||
import type { CookieJar } from 'tough-cookie';
|
||||
|
||||
export interface Logger {
|
||||
debug?(message: string, meta?: unknown): void;
|
||||
info?(message: string, meta?: unknown): void;
|
||||
warn?(message: string, meta?: unknown): void;
|
||||
error?(message: string, meta?: unknown): void;
|
||||
}
|
||||
|
||||
export interface CookieEncryptor {
|
||||
encrypt(text: string): string;
|
||||
decrypt(encryptedText: string): string;
|
||||
}
|
||||
|
||||
export interface CookieStore {
|
||||
load(): Promise<CookieJar>;
|
||||
save(cookieJar: CookieJar): Promise<void>;
|
||||
clear(): Promise<void>;
|
||||
}
|
||||
|
||||
export type AgrarmonitorLoginStrategy = 'auto' | 'auth' | 'legacy';
|
||||
|
||||
export interface AgrarmonitorConnectorOptions {
|
||||
baseUrl?: string;
|
||||
apiBaseUrl?: string;
|
||||
apiToken?: string;
|
||||
username: string;
|
||||
password: string;
|
||||
cookieStore: CookieStore;
|
||||
timeoutMs?: number;
|
||||
autoLogin?: boolean;
|
||||
autoRetry?: boolean;
|
||||
loginStrategy?: AgrarmonitorLoginStrategy;
|
||||
logger?: Logger;
|
||||
}
|
||||
|
||||
export interface AgrarmonitorConnectorResult {
|
||||
http: AxiosInstance;
|
||||
login(): Promise<void>;
|
||||
clearSession(): Promise<void>;
|
||||
saveSession(): Promise<void>;
|
||||
getCookieCount(url?: string): Promise<number>;
|
||||
checkFreigeschaltet(): Promise<AgrarmonitorFreischaltungStatus>;
|
||||
checkRegistriert(): Promise<AgrarmonitorRegistrierungStatus>;
|
||||
registerDevice(options: AgrarmonitorDeviceRegistrationOptions): Promise<AgrarmonitorDeviceRegistrationResult>;
|
||||
fetchCustomers(options?: AgrarmonitorFetchCustomersOptions): Promise<AgrarmonitorApiCustomer[]>;
|
||||
}
|
||||
|
||||
export interface AgrarmonitorFreischaltungStatus {
|
||||
freigeschaltet: boolean;
|
||||
status: number;
|
||||
redirected: boolean;
|
||||
redirectLocation: string | null;
|
||||
timestamp: string;
|
||||
cookies: number;
|
||||
}
|
||||
|
||||
export interface AgrarmonitorRegistrierungStatus {
|
||||
registriert: boolean;
|
||||
status: number;
|
||||
hasRegistrationText: boolean;
|
||||
timestamp: string;
|
||||
cookies: number;
|
||||
}
|
||||
|
||||
export interface AgrarmonitorDeviceRegistrationOptions {
|
||||
agrarmonitorId: string;
|
||||
pcName: string;
|
||||
}
|
||||
|
||||
export interface AgrarmonitorDeviceRegistrationResult {
|
||||
success: boolean;
|
||||
status: number;
|
||||
message: string;
|
||||
data: {
|
||||
agrarmonitorId: string;
|
||||
pcName: string;
|
||||
nonce: string;
|
||||
};
|
||||
timestamp: string;
|
||||
cookies: number;
|
||||
}
|
||||
|
||||
export interface AgrarmonitorFetchCustomersOptions {
|
||||
perPage?: number;
|
||||
apiToken?: string;
|
||||
}
|
||||
|
||||
export interface AgrarmonitorApiCustomer {
|
||||
id: string | number;
|
||||
vorname?: string;
|
||||
nachname?: string;
|
||||
firma?: string;
|
||||
ist_aktiv?: number | boolean;
|
||||
bearbeitet_am?: string | number;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "Node",
|
||||
"lib": [
|
||||
"ES2022",
|
||||
"DOM"
|
||||
],
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"dist",
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user