source === null || $this->source === '') { return self::HEARTBEAT_NAME; } $name = self::HEARTBEAT_NAME.' '.strtolower($this->source); if (strlen($name) <= self::MAX_NAME) { return $name; } // zu lang: Anfang des Hostnamens und ein kurzer Fingerabdruck, damit zwei lange Namen verschieden bleiben $hash = substr(sha1(strtolower($this->source)), 0, 8); return rtrim(substr($name, 0, self::MAX_NAME - 9), ' .-').'-'.$hash; } /** * Meldet die Innensicht an eine überwachte Domain. * * @param array $payload Meldung aus InsightPayload::build() * @return array{domain: ?string, reported: bool, message: Message} */ public function report(string $hostname, array $payload): array { $domainId = $this->domainId($hostname); if ($domainId === null) { return ['domain' => null, 'reported' => false, 'message' => Message::of('report.not_monitored')]; } $this->api->reportInsight($domainId, $payload); return ['domain' => $domainId, 'reported' => true, 'message' => Message::of('report.reported')]; } /** * Sendet den Heartbeat für diese Domain und liefert die verwendete Ping-Adresse zurück, damit der Aufrufer sie * sich merken kann. Ohne bekannte Adresse sucht sie die Erweiterung in DomainWarn oder legt den Heartbeat an. */ public function heartbeat(string $domainId, ?string $knownPingUrl = null): ?string { if ($knownPingUrl !== null) { try { $this->api->ping($knownPingUrl); return $knownPingUrl; } catch (TransportException $e) { if ($e->status() !== 404) { throw $e; } // die gemerkte Adresse gibt es nicht mehr (Heartbeat gelöscht, Domain verschoben): vergessen und im // selben Lauf neu einrichten – sonst bliebe der Herzschlag aus, bis jemand die Einstellung von Hand leert } } $pingUrl = $this->heartbeatUrl($domainId); if ($pingUrl === null) { return null; } $this->api->ping($pingUrl); return $pingUrl; } /** Die Kennung der Domain in DomainWarn, oder null, wenn sie dort nicht überwacht wird. */ public function domainId(string $hostname): ?string { return $this->api->findDomains([$hostname])[strtolower($hostname)]['id'] ?? null; } /** * Ping-Adresse des eigenen Heartbeats: vorhandenen suchen, sonst anlegen. Liefert null, wenn der Tarif keine * weiteren Prüfungen zulässt – die Innensicht wird dann trotzdem gemeldet. */ public function heartbeatUrl(string $domainId): ?string { $name = $this->heartbeatName(); $legacy = null; foreach ($this->api->monitors($domainId) as $monitor) { if ($monitor['type'] !== 'heartbeat') { continue; } if ($monitor['target'] === $name) { return $monitor['ping_url']; } if ($monitor['target'] === self::HEARTBEAT_NAME) { $legacy = $monitor['ping_url']; } } if ($legacy !== null) { // Heartbeat aus der Zeit vor der Quelle im Namen: weiter benutzen, statt ihn verwaisen zu lassen – sonst // meldete er nach dem nächsten Tokenwechsel einen Ausfall, den es nicht gibt return $legacy; } try { return $this->api->createHeartbeat($domainId, $name, self::PERIOD_SECONDS, self::GRACE_SECONDS); } catch (TransportException) { // kein Platz im Tarif oder Schreibrecht fehlt: der Rest der Meldung soll deshalb nicht ausfallen return null; } } }