query("SHOW TABLES LIKE 'push_abos'"); $da = $res && $res->num_rows > 0; } return $da; } /** * Alle Abos - ohne die Geheimnisse. * * Endpoint und Schlüssel bleiben auf dem Server: Wer den Endpoint hat, kann * dem Gerät schreiben. Die Maske braucht nur Name, Browser und Stand. */ function pushAbos() { if (!pushTabelleDa()) { return []; } $liste = []; $res = meshDb()->query("SELECT id, name, browser, angelegt, zuletzt, fehler FROM push_abos ORDER BY angelegt"); while ($z = $res->fetch_assoc()) { $liste[] = [ "id" => intval($z["id"]), "name" => $z["name"], "browser" => $z["browser"], "angelegt" => $z["angelegt"], "zuletzt" => $z["zuletzt"], "fehler" => $z["fehler"], ]; } return $liste; } /** * Ein Abo anlegen oder auffrischen. * * Der Endpoint ist der Schlüssel: Derselbe Browser meldet sich nach einem * Neustart mit demselben Endpoint, manchmal aber mit neuen Schlüsseln - * dann werden sie überschrieben, statt eine zweite Zeile anzulegen. Ein * erneutes Anmelden setzt außerdem einen alten Fehler zurück. */ function pushAboSpeichern($abo, $name, $browser) { if (!pushTabelleDa()) { throw new RuntimeException("Die Tabelle push_abos fehlt - homeMesh_push.sql einspielen."); } $endpoint = trim(strval($abo["endpoint"] ?? "")); $p256dh = trim(strval($abo["keys"]["p256dh"] ?? "")); $auth = trim(strval($abo["keys"]["auth"] ?? "")); if ($endpoint === "" || $p256dh === "" || $auth === "") { throw new InvalidArgumentException("Das Abo ist unvollständig."); } if (!preg_match('#^https://#', $endpoint) || strlen($endpoint) > 500) { throw new InvalidArgumentException("Diese Adresse taugt nicht als Push-Ziel."); } $name = mb_substr(trim(strval($name)), 0, 80); $browser = mb_substr(trim(strval($browser)), 0, 120); $db = meshDb(); $stmt = $db->prepare("INSERT INTO push_abos (endpoint, p256dh, auth, name, browser) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE p256dh = VALUES(p256dh), auth = VALUES(auth), name = VALUES(name), browser = VALUES(browser), fehler = ''"); $stmt->bind_param("sssss", $endpoint, $p256dh, $auth, $name, $browser); $stmt->execute(); $stmt->close(); return true; } /** Ein Abo nach Endpoint entfernen - das tut der Browser beim Abmelden. */ function pushAboEntfernenNachEndpoint($endpoint) { if (!pushTabelleDa()) { return 0; } $db = meshDb(); $stmt = $db->prepare("DELETE FROM push_abos WHERE endpoint = ?"); $stmt->bind_param("s", $endpoint); $stmt->execute(); $weg = $stmt->affected_rows; $stmt->close(); return $weg; } /** Ein Abo nach Nummer entfernen - das tut die Maske für ein fremdes Gerät. */ function pushAboEntfernen($id) { if (!pushTabelleDa()) { return 0; } $db = meshDb(); $stmt = $db->prepare("DELETE FROM push_abos WHERE id = ?"); $id = intval($id); $stmt->bind_param("i", $id); $stmt->execute(); $weg = $stmt->affected_rows; $stmt->close(); return $weg; } /** * Eine Probemeldung an alle Abos schicken. * * Verschickt wird nicht hier, sondern vom Runner: Er hat die Pakete * (pywebpush) und den privaten Schlüssel, und er ist ohnehin der, der im * Betrieb meldet. Die Weboberfläche legt den Auftrag nur ins MQTT - dasselbe * Topic, das auch eine Automatik benutzen würde. */ function pushProbe($titel, $text) { // MQTT_BROKER und MQTT_PORT stehen in commands.php - dort, wo auch die // Kommandos der Geraete ihren Weg zum Broker finden. require_once(__DIR__ . "/commands.php"); require_once(__DIR__ . "/../ajax/phpMQTT.php"); $mqtt = new Bluerhinos\phpMQTT(MQTT_BROKER, MQTT_PORT, "PushProbe_" . getmypid()); if (!$mqtt->connect(true, null, "", "")) { throw new RuntimeException("Keine Verbindung zum MQTT-Broker."); } $mqtt->publish("benachrichtigung/push", json_encode(["titel" => $titel, "text" => $text], JSON_UNESCAPED_UNICODE), 0, false); $mqtt->close(); return true; }