[ 'subject' => $cfg['vapid_subject'], 'publicKey' => $cfg['vapid_public'], 'privateKey' => $cfg['vapid_private'], ], ]; $webPush = new WebPush($auth); $webPush->setDefaultOptions([ 'TTL' => (int)($cfg['ttl'] ?? 86400), ]); /** * badgeCount = msg_unread + rdv_unread * table push_badge_counters est indexée par compte_id (PK) */ function computeBadgeCount(PDO $pdo, array $n): int { $cid = (int)($n['target_compte_id'] ?? 0); if ($cid <= 0) return 0; $st = $pdo->prepare("SELECT msg_unread, rdv_unread FROM push_badge_counters WHERE compte_id = ? LIMIT 1"); $st->execute([$cid]); $row = $st->fetch(PDO::FETCH_ASSOC); if (!$row) return 0; $total = (int)($row['msg_unread'] ?? 0) + (int)($row['rdv_unread'] ?? 0); return max(0, min(99, $total)); } // 1) lot pending $notifs = $pdo->query(" SELECT * FROM push_notifications WHERE status = 'pending' ORDER BY id ASC LIMIT 50 ")->fetchAll(PDO::FETCH_ASSOC); if (!$notifs) exit(0); foreach ($notifs as $n) { $nid = (int)$n['id']; // 1bis) lock $lock = $pdo->prepare(" UPDATE push_notifications SET status='sending' WHERE id=? AND status='pending' "); $lock->execute([$nid]); if ($lock->rowCount() === 0) continue; // 2) subscriptions cibles $where = ["is_active = 1"]; $params = []; if (!empty($n['target_compte_id'])) { $where[] = "compte_id = :cid"; $params[':cid'] = (int)$n['target_compte_id']; } else { // fallback (normalement tu as toujours target_compte_id) $where[] = "role = :role"; $params[':role'] = (string)$n['target_role']; if (!empty($n['target_reference_id'])) { $where[] = "reference_id = :ref"; $params[':ref'] = (int)$n['target_reference_id']; } } $stmt = $pdo->prepare(" SELECT id, endpoint, p256dh, auth FROM push_subscriptions WHERE " . implode(' AND ', $where) . " "); $stmt->execute($params); $subs = $stmt->fetchAll(PDO::FETCH_ASSOC); if (!$subs) { $pdo->prepare(" UPDATE push_notifications SET status='failed', attempts=attempts+1, last_error='no_subscriptions' WHERE id=? ")->execute([$nid]); continue; } // 3) payload $badgeCount = computeBadgeCount($pdo, $n); $payload = [ 'type' => $n['type'], 'title' => $n['title'], 'body' => $n['body'], 'url' => $n['url'], 'data' => $n['data_json'] ? json_decode((string)$n['data_json'], true) : null, 'nid' => $nid, 'badgeCount' => $badgeCount, ]; $payloadJson = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); // map endpoint => subscription_id $subIdByEndpoint = []; foreach ($subs as $s) { $endpoint = (string)$s['endpoint']; $subIdByEndpoint[$endpoint] = (int)$s['id']; $subscription = Subscription::create([ 'endpoint' => $endpoint, 'publicKey' => (string)$s['p256dh'], 'authToken' => (string)$s['auth'], ]); $webPush->queueNotification($subscription, $payloadJson, ['urgency' => 'normal']); } // 4) flush + delivery log $successCount = 0; $lastErr = null; $insLog = $pdo->prepare(" INSERT INTO push_delivery_log (notification_id, subscription_id, http_status, error_code, error_message, created_at) VALUES (:nid, :sid, :http, :ecode, :emsg, NOW()) "); foreach ($webPush->flush() as $report) { $ok = $report->isSuccess(); $endpoint = (string)$report->getRequest()->getUri(); $sid = $subIdByEndpoint[$endpoint] ?? null; $statusCode = $report->getResponse() ? $report->getResponse()->getStatusCode() : null; $reason = $ok ? null : (string)$report->getReason(); if ($ok) $successCount++; if ($sid) { $insLog->execute([ ':nid' => $nid, ':sid' => $sid, ':http' => $statusCode, ':ecode' => (!$ok && in_array((int)$statusCode, [404, 410], true)) ? 'gone' : null, ':emsg' => $reason ? mb_substr($reason, 0, 255) : null, ]); } if (!$ok && in_array((int)$statusCode, [404, 410], true)) { $pdo->prepare("UPDATE push_subscriptions SET is_active=0 WHERE endpoint=?")->execute([$endpoint]); } if (!$ok && $reason) $lastErr = $reason; } // 5) statut notification if ($successCount > 0) { $pdo->prepare(" UPDATE push_notifications SET status='sent', sent_at=NOW(), attempts=attempts+1, last_error=NULL WHERE id=? ")->execute([$nid]); } else { $pdo->prepare(" UPDATE push_notifications SET status='failed', attempts=attempts+1, last_error=? WHERE id=? ")->execute([mb_substr((string)$lastErr, 0, 255), $nid]); } }