Files
AgrarmonitorConnector/README.md
T
bjoernpoettkerandClaude Opus 5.5 6db6c5b278 Stammdaten: getTaetigkeiten und getAlleMaschinen
getTaetigkeiten liest alle Taetigkeiten (aktiv und inaktiv) mit Bereich
und den Flags Maschinen zuordnen, Abwesenheit und Notiz erzwingen.
getAlleMaschinen liest alle Kategorien und Seiten und fasst Maschinen,
die in mehreren Kategorien stehen, zu einem Eintrag zusammen.
Paging ueber .pagination[data-pages], Abbruch sobald eine Seite keine
neuen Zeilen bringt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 11:11:25 +02:00

6.5 KiB

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.

Login uses Agrarmonitor's redirect flow only: the connector loads /, extracts the nonce, and posts credentials to /redirect.php?id=benutzerverwaltung&action=login.

Installation

npm install

Build

npm run build

Example

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);

Login-Logging

Der Login-Flow protokolliert jeden Schritt auf debug-Level, sofern der uebergebene Logger eine debug-Methode hat. Ohne debug-Methode bleiben diese Details still - ein reiner info-Logger sieht davon nichts.

Protokolliert werden der Aufruf der Loginseite, die gelesene Nonce, der POST /login/api/login.php, die Session-Pruefung und jeder Request, der wegen abgelaufener Session wiederholt wird. Zu jedem dieser Requests steht ein Cookie-Snapshot vor dem Absenden und einer danach im Log, dazu ein Delta mit neuen, geaenderten und entfernten Cookies:

[debug] Login-POST: POST /login/api/login.php
        {"phase":"request","method":"POST","url":"/login/api/login.php","cookies":[],
         "body":{"username":"demo","passwort":"***","nonce":"9f3a1c7b2e...","ssoAction":"","ssoReturn":""}}
[debug] Login-POST: POST /login/api/login.php -> 200
        {"phase":"response","status":200,"istLoginSeite":false,
         "setCookie":["PHPSESSID=f81d…(len=32); Path=/; HttpOnly"],
         "cookies":[{"name":"PHPSESSID","value":"f81d…(len=32)","domain":"127.0.0.1","path":"/",
                     "expires":"Session","httpOnly":true,"secure":false}],
         "cookieChanges":{"added":["PHPSESSID"],"changed":[],"removed":[]}}

Passwort und Cookie-Werte stehen nie im Klartext im Log: das Passwort wird durch *** ersetzt, Cookie-Werte und der rohe Set-Cookie-Header werden auf die ersten vier Zeichen plus Laenge gekuerzt. Damit bleibt erkennbar, ob sich eine Session-ID geaendert hat, ohne dass das Log selbst die Session preisgibt.

Das phase-Feld ordnet jeden Eintrag zu: login-start, request, response, nonce, session-check, session-expired, retry, error, login-done.

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.
  • getKunden2() loads all customer pages and returns normalized customer/supplier fields.
  • getCustomerById(id) loads one customer from the token-based Agrarmonitor API.
  • eingangsrechnungenLivesearch(suchstring) searches invoice files and enriches matching Eingangsrechnungen with edit/detail metadata.
  • eingangsrechnungVorhanden(suchstring) checks whether invoice file search has rows.
  • eingangsrechnungImDateieingangVorhanden(suchstring) checks whether file inbox search has rows.
  • getRechnungsdaten(rechnungId) reads editable invoice fields.
  • setRechnungsdaten(rechnungId, daten) updates editable invoice fields.
  • setLieferscheinNummer(rechnungId, nummer) updates only the delivery note number.
  • setEingangsdatum(rechnungId, datum) updates the received date.
  • getMaschinenKategorien() reads /maschinen/kategorien and returns ID, name, tractor flag, and mobile visibility.
  • getMaschinen(gruppenId, suchstring?) reads /module/maschinen/livesearch.php and returns ID, number, name, license plate, and active flag.
  • getAlleMaschinen() reads all machine categories (maschinengruppe=0), active and inactive machines, all livesearch pages, and returns each machine once with ID, number, name, license plate, active flag, its first category (gruppenId, gruppe) and all categories (gruppenIds); a machine can be listed in several categories. getMaschinen() already sends maschinen_aktiv=1, which in Agrarmonitor means active and inactive.
  • getFirmen() reads /einstellungen and returns company ID and description.
  • getArtikelEinheiten() reads /artikel/einheiten and returns unit ID, description, and short label.
  • getArtikelKategorien() reads /artikel/kategorien and returns product group ID and name.
  • getArtikel(artikelGruppe, suchstring?) reads /module/artikel/livesearch.php and returns article ID, number, and description.
  • getMitarbeiter(suchstring?, mitarbeitergruppe?) reads /module/mitarbeiter/livesearch.php and returns employee ID, number, last name, first name, and active flag.
  • getTaetigkeiten() reads /module/arbeitszeit/taetigkeiten-livesearch.php (active and inactive) and returns activity ID, UUID, number, name, active flag, area (bereichId, bereich) and the flags maschineZuordnen, abwesenheit, notizErzwingen.
  • Agrarmonitor has no cost center master data (Kostenstelle is only a free-text field on machines, employees and articles), so there is no getKostenstellen(). See docs/zeitbuchung-analyse.md.
  • saveSession() explicitly persists the current cookie jar.