import { type KasseneckAuth } from './auth.js'; /** * Transport zum Kasseneck-Backend: ein Aufruf ist ein POST an * `/` mit dem JSON-Rumpf `{ "params": { … } }` — wie im * Flutter-Zwilling `kasseneck_api`. * * Der Transport macht drei Dinge und sonst nichts: Anfrage bauen (Kopfzeilen * von der Anmeldung, Nutzlast aus Auth-Parametern und Aufruferparametern), * Zeitlimit ueberwachen, und die Antworthuelle aufloesen. Er kennt keine * einzelne Backend-Funktion und keine Details der Infrastruktur. * * Es gibt **zwei Einstiegspunkte** auf demselben Kern: [createTransport] fuer * die JSON-Aufrufe und [createBinaryTransport] fuer die beiden Bericht- * Downloads, die ein PDF liefern. Anmeldung, Zeitlimit, Rumpfaufbau und die * Fehlerarten sind bei beiden dieselben; sie unterscheiden sich nur darin, wie * der Antwortrumpf gelesen und ausgewertet wird. * * **Nur `/v3`, fail closed.** Jede Antwort muss das Kennzeichen * `Kasseneck-Api-Version: v3` tragen; fehlt es, wirft der Transport * `dialect_mismatch`, **bevor** er den Rumpf liest, und tut nichts weiter * (Nachtrag §5.4, Risiko R1: eine Route auf eine Function mit altem Rand * haette die englischen Parameter deutsch gedeutet). Eine HTML-Antwort ist * `route_missing`: die Hosting-Auffangregel hat den Aufruf abgefangen, bevor * ihn eine Function sah. * * **Kasseneck-Kopfzeilen nur an Kasseneck-Basen** (Nachtrag §6, Risiko R3): * `Kasseneck-Api-Version: v3` und `Kasseneck-Client: /` * setzt der Transport selbst, abhaengig von der Basis, und nie ueber die * `fetch`-Umsetzung. Dieselbe Umsetzung darf darum auch Connect, ePOS-Drucker * und Terminals bedienen, ohne dass dort eine fremde Kopfzeile den Vorflug * scheitern laesst. * * **Kein Wiederholen fehlgeschlagener Aufrufe.** Ein Beleg ist nicht folgenlos * wiederholbar; ohne entschiedene Idempotenz waere ein automatischer zweiter * Versuch ein zweiter Beleg. * * **Ausgang je Aufruf.** Ob ein Scheitern nach dem Senden `outcome: 'unknown'` * traegt, haengt am Aufruf: mit Wirkung ([UNKNOWN_OUTCOME_CALLS] in * aufrufe.ts) ja, Lesen und Probelauf nein. Ein Probelauf traegt denselben * Namen wie der echte Aufruf; die Huelle sagt es darum beim Aufruf * (`hasEffect: false`, [TransportCallOptions]). */ /** Basis-URL der oeffentlichen API (Produktion). */ export declare const DEFAULT_BASE_URL = "https://api.kasseneck.at/v3"; /** * Basis-URL des Kassenwegs (Kanal `app`, Nachtrag §5.2). Die Web-Kasse ruft * denselben Weg im gleichen Ursprung als `/api/v3`. */ export declare const POS_BASE_URL = "https://kasse.kasseneck.at/api/v3"; /** * Zeitlimit je Aufruf (wie im Flutter-Zwilling). Ohne Zeitlimit bleibt eine * haengende Anfrage fuer immer offen — der Aufrufer bekaeme weder Ergebnis * noch Fehler. */ export declare const DEFAULT_TIMEOUT_MS = 30000; /** * Der Teil einer `fetch`-Antwort, den dieser Transport braucht. Bewusst * minimal, damit Tests ohne echtes Netz eine Antwort stellen koennen; das * globale `fetch` erfuellt diese Form. */ export interface HttpResponseLike { status: number; headers: { get(name: string): string | null; }; text(): Promise; /** * Rohe Bytes der Antwort — der Binaerweg ([createBinaryTransport]) liest * ausschliesslich sie. Bewusst **verpflichtend**: jede `fetch`-Antwort bringt * die Methode mit, betroffen sind nur Attrappen — und eine Attrappe ohne * Bytes bringt einen Test dazu, den Binaerweg nie zu erreichen und trotzdem * gruen zu melden. Dieser Fehler gehoert an die Bauzeit, nicht in die * Laufzeit. */ arrayBuffer(): Promise; } export interface HttpRequestInit { method: string; headers: Record; body: string; signal: AbortSignal; } /** * Austauschbare `fetch`-Umsetzung. Vorgabe ist das globale `fetch` (Node * >= 20.18 und jeder Browser bringen es mit) — dieses Paket haengt an keiner * HTTP-Bibliothek. */ export type FetchLike = (url: string, init: HttpRequestInit) => Promise; export interface TransportOptions { /** Anmeldung; wird pro Aufruf befragt. */ auth: KasseneckAuth; /** * Abweichende Basis der **oeffentlichen** Aufrufe (Vorgabe * [DEFAULT_BASE_URL]): alles ausser den 33 Aufrufen des Kassenwegs, und die * sechs oeffentlichen davon nur, wenn nicht mit `registerUserAuth` * angemeldet. Muss auf `/v3` enden (eigene Proxys erlaubt), sonst wirft das * Anlegen; `/v1` oder `/api` gibt es in der 1.x-Linie nicht. */ baseUrl?: string; /** * Abweichende Basis des **Kassenwegs** (Vorgabe [POS_BASE_URL]): die 27 * reinen Kassenaufrufe (Kopplung, Anmeldung, Einstellungen, Artikel, * Drucker, ...) und mit `registerUserAuth` alle 33 Aufrufe des Kassenwegs. * Die Web-Kasse gibt `'/api/v3'` (gleicher Ursprung). Muss auf `/v3` enden * (in der Regel `/api/v3`), sonst wirft das Anlegen. */ posBaseUrl?: string; /** Zeitlimit je Aufruf in Millisekunden. */ timeoutMs?: number; /** Eigene `fetch`-Umsetzung (Tests, Proxys). */ fetch?: FetchLike; /** * Wert der Kopfzeile `Kasseneck-Client`, Vorgabe `kasseneck-api/`. * Eine App nennt sich selbst (`kasse-web/`, `kasse-app/`). * Erlaubt sind nur die Produkte `kasse-web`, `kasse-app`, `kasseneck-api`, * `kasseneck_api` mit einer Version aus `[0-9A-Za-z.+-]` (hoechstens 40 * Zeichen, gesamt hoechstens 64); sonst wirft das Anlegen. */ clientHeader?: string; /** * `true` laesst `Kasseneck-Api-Version` und `Kasseneck-Client` auch an * Kasseneck-Basen weg. Fuer Browser, die die oeffentliche API von einem * fremden Ursprung aus rufen: solange der Vorflug unter `/v3` die beiden * Kopfzeilen nicht erlaubt, scheiterte sonst jeder Aufruf schon dort. Die * Pruefung des Kennzeichens in der **Antwort** bleibt unberuehrt; es entfaellt * nur die Gegenpruefung des Rands (`createReceipt`/`cancelReceipt` weisen * eine als v3 gekennzeichnete Anfrage auf einem Nicht-v3-Pfad ab). */ omitKasseneckHeaders?: boolean; } /** * Ruft eine Backend-Funktion auf und liefert deren Nutzlast **ohne Huelle**. * Ein Verbraucher prueft nie selbst auf `status`: Misserfolg kommt als * geworfener Fehler. * * `extraBodyFields` legt zusaetzliche Felder **neben** `params` auf die oberste * Rumpfebene. Genau ein Endpunkt braucht das: `financeWebService` erwartet * seine `method` neben `params` und nicht darin (siehe Flutter-Vorbild * `_financeWebServicePostRequest`). Fuer alle anderen Aufrufe bleibt der Rumpf * unveraendert `{params:{…}}`. * * `secretParams` nennt Werte, die dieser Aufruf in der **Nutzlast** sendet und * die in keinem Fehler auftauchen duerfen. Die gesendeten Kopfzeilen sind * ohnehin geschuetzt (siehe unten); PIN, Geraetegeheimnis und Kopplungs-Code * der Kassen-Anmeldung reisen dagegen im Rumpf, und ein Geraetegeheimnis ist * bezeichner-foermig genug, um durch die Ursachen-Verdichtung zu kommen. Wer * ein Geheimnis im Rumpf sendet, nennt es hier — sonst gilt es als * unbedenklich. * * Der Typ nennt darum genau dieses eine Feld und ist kein offener Beutel: * ein `params` von aussen wuerde die Nutzlast samt Auth-Parametern * ueberschreiben — mit `registerUserAuth` verschwaende dabei still die * Kassenbindung. Zusaetzlich setzt der Rumpfaufbau `params` als letztes, damit * auch ein Verbraucher ohne Typen nicht daran vorbeikommt. */ export interface TransportBodyFields { /** Vorgangsart von `financeWebService` (z. B. `status_cashbox`). */ method?: string; } /** * Einstellungen fuer **einen** Aufruf, die nicht mitgesendet werden. */ export interface TransportCallOptions { /** * Hat dieser Aufruf Wirkung? Entscheidet den Ausgang, wenn er scheitert, * nachdem die Anfrage unterwegs war (Netzfehler, Zeitlimit, HTTP 5xx, * unlesbare Erfolgsantwort, HTML mit Kennzeichen): `true` ergibt * `outcome: 'unknown'`. Ohne Angabe gilt die Liste des Pakets * (`UNKNOWN_OUTCOME_CALLS`); ein Name, den sie nicht kennt, gilt dann als * ohne Wirkung. * * `true` fuer einen Aufruf mit Wirkung, den dieses Paket nicht kennt. * `false` gilt **nur zusammen mit `params.dryRun === true`** (Probelauf * unter dem Namen des echten Aufrufs, schreibt nichts) und ergibt dann * `'rejected'`; ohne `dryRun: true` bleibt ein Aufruf aus der Liste * `'unknown'`, was immer hier steht. */ hasEffect?: boolean; } export type KasseneckTransport = (functionName: string, params?: Record, extraBodyFields?: TransportBodyFields, secretParams?: readonly string[], options?: TransportCallOptions) => Promise; /** * Ruft eine Backend-Funktion auf, die **Binaerdaten** liefert (die beiden * Bericht-PDFs), und gibt sie als `Uint8Array` zurueck. */ export type KasseneckBinaryTransport = (functionName: string, params?: Record) => Promise; export declare function createTransport(options: TransportOptions): KasseneckTransport; /** * Zweiter Einstiegspunkt fuer die Bericht-Downloads. Er liest die Antwort als * Bytes und **nie** als Zeichenkette: ein PDF ist keine UTF-8-Folge, und eine * Textdeutung ersetzt jedes Byte ueber 0x7F durch U+FFFD — die Datei waere * kaputt, ohne dass es jemand merkt. (Das Flutter-Vorbild nimmt hier * `response.body.codeUnits`; das ist eine bewusste Abweichung, siehe * reports.ts.) */ export declare function createBinaryTransport(options: TransportOptions): KasseneckBinaryTransport; /** * Ergebnis eines Aufrufs, der eine Datei **oder** eine Nutzlast liefert: das * PDF als Bytes oder die `data` einer Erfolgshuelle (etwa ein Lese-Link, wenn * die Datei zu gross fuer die Antwort ist). */ export type PdfOrData = { pdf: Uint8Array; } | { data: unknown; }; /** * Ruft eine Backend-Funktion auf, die ein PDF **oder** eine JSON-Nutzlast * liefert (Inventurprotokoll: die Datei bis 9 MiB, darueber ein Lese-Link). */ export type KasseneckPdfOrDataTransport = (functionName: string, params?: Record) => Promise; /** * Dritter Einstiegspunkt, fuer Aufrufe, die eine Datei oder eine Nutzlast * liefern (`getStocktakePdf` aus `./inventory`). Wie [createBinaryTransport] * wird die Antwort nur als Bytes gelesen; die ersten Bytes entscheiden: `%PDF` * ergibt `{ pdf }`, eine Erfolgshuelle `{ data }`, eine Fehlerhuelle denselben * fachlichen Fehler wie auf dem JSON-Weg. Leer, kein JSON oder ohne Statusfeld * ist unlesbar mit denselben Gruenden wie dort. */ export declare function createPdfOrDataTransport(options: TransportOptions): KasseneckPdfOrDataTransport;