Leitwerk SDK

README

Direkte Ansicht der SDK-Dokumentation aus `vendor/README.md`.

# Leitwerk PHP SDK

Dieses ZIP enthält ein kleines PHP-7.4-kompatibles SDK für die serverseitige Arbeit mit Leitwerk.

## Ziel

Das SDK ist bewusst ohne Composer aufgebaut. Du kannst die `.php`-Dateien direkt in dein Projekt legen und sofort verwenden.

Aktuelle SDK-Version: `1.0.0`

## Enthalten

- Log-Ingest über `POST /api/ingest/logs`
- Namespace-Filter für Logs über `LeitwerkLogConfig`
- Push-SDK-Bootstrap über `GET /sdk/v1/bootstrap`
- VAPID-Key über `GET /api/push/vapid-public-key`
- Quell-Apps über `GET /api/push/source-apps`
- Push-Clients und Client-Keys
- Subscriptions speichern und löschen
- Trigger für Push-Versand
- Push-Nachrichten laden, anlegen und aktualisieren

## Voraussetzungen

- PHP 7.4
- cURL-Erweiterung
- JSON-Erweiterung

## Installation ohne Composer

1. ZIP herunterladen und entpacken.
2. Den Ordner `leitwerk-php-sdk` in dein Projekt kopieren.
3. Die Datei `leitwerk.php` einbinden.

```php
<?php

require __DIR__ . '/leitwerk-php-sdk/leitwerk.php';

use LeitwerkSdk\LeitwerkClient;
use LeitwerkSdk\LeitwerkLogConfig;

$leitwerk = new LeitwerkClient(
    'https://leitwerk.example.com',
    'dein-kunden-api-key',
    15,
    LeitwerkLogConfig::fromArray([
        'allowedNamespaces' => [
            'sync.orders.*',
            'billing.invoice.created',
        ],
    ])
);
```

Optional kannst du als drittes Argument ein Timeout in Sekunden und als viertes Argument eine `LeitwerkLogConfig` setzen:

```php
<?php

$leitwerk = new LeitwerkClient(
    'https://leitwerk.example.com',
    'dein-kunden-api-key',
    30
);
```

Wenn du später mit einem anderen Kunden-API-Key weiterarbeiten willst, nutze `withCustomerApiKey()`.

```php
<?php

$mandantA = new LeitwerkClient('https://leitwerk.example.com', 'api-key-a');
$mandantB = $mandantA->withCustomerApiKey('api-key-b');
```

## Schnellstart

### Log senden

```php
<?php

$response = $leitwerk->sendLog([
    'message' => 'Synchronisierung gestartet',
    'level' => 'info',
    'namespace' => 'sync.orders.import',
    'source' => 'php-worker',
    'meta' => [
        'batchId' => 42,
    ],
]);

if (!empty($response['skipped'])) {
    var_dump('Log wurde lokal uebersprungen');
}
```

### Log-Namespaces per Konfiguration begrenzen

Die Log-Konfiguration arbeitet mit einer Punktnotation fuer Namespaces.

- `billing.invoice.created` erlaubt genau diesen Namespace
- `billing.invoice.*` erlaubt alle Unterbereiche wie `billing.invoice.failed`
- `billing.*` erlaubt alle tieferen Namespaces unterhalb von `billing`

Wichtig:

- `billing.*` matcht `billing.invoice.created`, aber nicht den nackten Namespace `billing` selbst.
- Wenn du auch `billing` selbst erlauben willst, brauchst du zusaetzlich die exakte Regel `billing`.

Beispiel fuer beides zusammen:

```txt
billing
billing.*
```

```php
<?php

use LeitwerkSdk\LeitwerkClient;
use LeitwerkSdk\LeitwerkLogConfig;

$logConfig = LeitwerkLogConfig::fromArray([
    'allowedNamespaces' => [
        'auth.login.*',
        'sync.orders.*',
        'billing.invoice.created',
    ],
]);

$leitwerk = new LeitwerkClient(
    'https://leitwerk.example.com',
    'dein-kunden-api-key',
    15,
    $logConfig
);
```

Wenn ein Log nicht in die erlaubten Regeln faellt, sendet `sendLog()` nichts an Leitwerk und gibt stattdessen lokal eine Skip-Antwort zurueck:

```php
<?php

[
    'ok' => true,
    'skipped' => true,
    'sent' => false,
    'reason' => 'namespace_not_allowed',
    'namespace' => 'auth.logout',
]
```

### Push-Client mit Key anlegen

```php
<?php

$response = $leitwerk->createPushClient([
    'firstName' => '',
    'lastName' => '',
    'label' => 'Hausmeister Nord',
    'sourceApp' => 'hausmeister-app',
    'externalRecipientKey' => 'hausmeister-nord',
]);

$clientKey = $response['plainTextKey'];
```

### Trigger senden

```php
<?php

$leitwerk->trigger([
    'appId' => 'hausmeister-app',
    'title' => 'Neue Aufgabe',
    'body' => 'Bitte Eingangstür prüfen.',
    'recipients' => [
        'externalRecipientKeys' => ['hausmeister-nord'],
    ],
]);
```

## Browser-Flow

Wenn du für einen Browser einen `clientKeyEndpoint` bereitstellen willst, schau dir `examples/client-key-endpoint.php` an. Das Beispiel erzeugt serverseitig einen Push-Client-Key und gibt nur `{ clientKey }` an den Browser zurück.

## Methodenreferenz

### `__construct(string $baseUrl, ?string $customerApiKey = null, int $timeoutSeconds = 15, ?LeitwerkLogConfig $logConfig = null)`

- setzt Basis-URL, optionalen Standard-Kunden-API-Key und Timeout
- akzeptiert optional eine `LeitwerkLogConfig` fuer erlaubte Log-Namespaces
- entfernt einen abschließenden Slash in der Basis-URL automatisch

### `withCustomerApiKey(string $customerApiKey): self`

- gibt eine neue Client-Instanz mit anderem Standard-Kunden-API-Key zurück
- verändert die bestehende Instanz nicht

### `withLogConfig(LeitwerkLogConfig $logConfig): self`

- gibt eine neue Client-Instanz mit Log-Filter-Regeln zurück
- praktisch, wenn nur ein Teil deines Codes Logs senden darf

### `getVapidPublicKey(): array`

- lädt den öffentlichen VAPID-Key
- braucht keinen API-Key

### `getSdkBootstrap(array $query = []): array`

- lädt Bootstrap-Daten für SDK- oder Browser-Flows
- gibt das Query-Array unverändert als Query-Parameter weiter

### `getSourceApps(?string $apiKey = null): array`

- lädt die verfügbaren Quell-Apps
- nutzt den übergebenen oder den Standard-Kunden-API-Key

### `sendLog(array $payload, ?string $apiKey = null): array`

- sendet Logs an `POST /api/ingest/logs`
- typischer Payload: `message`, `level`, `namespace`, `source`, optional `meta`
- wenn eine `LeitwerkLogConfig` gesetzt ist, werden nur erlaubte Namespaces gesendet
- nicht erlaubte Logs werden lokal uebersprungen und nicht an Leitwerk uebertragen

### `shouldSendLog(array $payload): bool`

- prueft vorab, ob ein Log anhand der `LeitwerkLogConfig` gesendet werden darf
- liefert ohne gesetzte Log-Konfiguration immer `true`

### `createPushClient(array $payload, ?string $apiKey = null): array`

- legt einen Push-Client an
- liefert die API-Antwort unverändert zurück, inklusive `plainTextKey`, falls vorhanden

### `createBrowserClientKey(array $recipient, string $appId, ?string $apiKey = null): array`

- ergänzt automatisch `sourceApp` aus `appId`
- entfernt leere `externalRecipientKey`-Werte
- gibt eine browserfreundliche Antwort mit `clientKey`, `client`, `apiKey` und `plainTextKey` zurück

### `registerPushClient(array $payload, ?string $apiKey = null): array`

- registriert einen Client über den SDK-Endpunkt
- akzeptiert Kunden-API-Key oder Push-Client-Key

### `saveSubscription(array $payload, string $pushClientKey): array`

- speichert eine Subscription
- erwartet explizit einen Push-Client-Key

### `deleteSubscription(array $payload, string $pushClientKey): array`

- löscht eine Subscription
- erwartet ebenfalls explizit einen Push-Client-Key

### `trigger(array $payload, ?string $apiKey = null): array`

- löst Push direkt aus deinem Backend aus
- erwartet einen Kunden-API-Key

### `listNotifications(array $query = [], ?string $apiKey = null): array`

- lädt vorhandene Push-Nachrichten mit optionalen Filtern
- typische Query-Felder sind zum Beispiel `sourceApp` und `page`

### `getNotification(int $notificationId, ?string $apiKey = null): array`

- lädt eine einzelne Push-Nachricht per ID
- erwartet die numerische Notification-ID als erstes Argument

### `createNotification(array $payload, ?string $apiKey = null): array`

- legt eine neue Push-Nachricht an
- gibt den Payload unverändert an die API weiter

### `updateNotification(int $notificationId, array $payload, ?string $apiKey = null): array`

- aktualisiert eine bestehende Push-Nachricht
- erwartet zuerst die numerische Notification-ID und danach den Update-Payload

## Weitere Beispiele

- `examples/create-notification.php`
- `examples/list-notifications.php`

## Fehlerbehandlung

Bei API-Fehlern wirft das SDK `LeitwerkApiException`.

Damit bekommst du direkt:

- HTTP-Statuscode
- Raw-Response
- dekodierte JSON-Response, falls vorhanden
- `InvalidArgumentException`, wenn ein notwendiger Kunden-API-Key fehlt
- `RuntimeException` bei cURL-Fehlern, ungültigem JSON oder fehlendem `plainTextKey`

## Hinweis für diese App

Das ZIP wird beim Download direkt aus den Dateien in `resources/php-sdk/leitwerk-php-sdk` erzeugt. Wenn du dort etwas änderst, ist es sofort im nächsten Download enthalten.