client ??= $this->buildClient(); if (! $client) { return null; } try { $body = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR); } catch (\JsonException $exception) { Log::channel('notifications')->warning('Unable to encode admin push payload', [ 'reason' => $exception->getMessage(), ]); $body = '{}'; } try { return $client->sendOneNotification( WebPushSubscription::create([ 'endpoint' => $subscription->endpoint, 'publicKey' => $subscription->public_key, 'authToken' => $subscription->auth_token, 'contentEncoding' => $subscription->content_encoding ?? 'aes128gcm', ]), $body ); } catch (\Throwable $exception) { Log::channel('notifications')->warning('Admin web push transport error', [ 'tenant_id' => $subscription->tenant_id, 'subscription_id' => $subscription->id, 'reason' => $exception->getMessage(), ]); return null; } } private function buildClient(): ?WebPush { $vapid = config('push.vapid', []); if (empty($vapid['public_key']) || empty($vapid['private_key'])) { Log::channel('notifications')->warning('Admin web push skipped because VAPID keys are missing.'); return null; } $client = new WebPush([ 'VAPID' => [ 'subject' => $vapid['subject'] ?? config('app.url'), 'publicKey' => $vapid['public_key'], 'privateKey' => $vapid['private_key'], ], ]); $client->setDefaultOptions([ 'TTL' => (int) config('push.ttl', 900), ]); return $client; } }