Coordination message about Push Notifications modules

There seems to be 5 different projects that are doing the same thing, each one focusing on a different part, but overall they are overlapping:

  1. Web Push API : https://www.drupal.org/project/web_push_api
  2. WebPush : https://www.drupal.org/project/webpush
  3. Web Push Notification : https://www.drupal.org/project/web_push_notification
  4. Browser push notification : https://www.drupal.org/project/browser_push_notification
  5. Advanced Progressive Web App : https://www.drupal.org/project/advanced_pwa

All these 5 modules are offering native support for push notifications, implementing Drupal integration of the same PHP library, without using any external service.

Don't you think that it would be better to merge all the work in one single module, and join efforts there to create a better experience for the drupalists?
I believe this would be more close to drupal's philosophy.

I'm starting this discussion to see what are the intentions.

PS: I will cross-publish this message to all the relevant projects, so please do not mark it as spam.
PS2: I am the maintainer of the WebPush module (2nd in the list) which only exists for D7.

Comments

efpapado created an issue. See original summary.

br0ken’s picture

Hi there. I'm happy to somehow merge the efforts.

For now this projects uses forked web-push-php lib (https://github.com/BR0kEN-/web-push-php) that includes https://github.com/web-push-libs/web-push-php/pull/268. The patch allows a Drupal entity to be a WebPush subscription. I hope the forked package won't be taking place for too long and the maintainer respond soon.

Now about the things I do not want this project to have (they're listed on project's page).

  • A client-side implementation for requesting notification permission and/or subscription to push notifications.
  • A UI for creating and/or sending notifications.
  • A UI for managing configurations that might be needed for API.
  • A queue and a worker to handle notifications dispatch.

Therefore, this project provides the API only. It does not give the logic for registering service workers, configuring Web Push API authorization, subscribing/unsubscribing (no single line of JS) etc. Only storage for subscriptions and the API to send notifications to them.

I'm a big fan of composing applications from small parts and believe this project can be used as a dependency for the other listed projects since they provide extended functionality (which I do not want to maintain/be responsible for). Leveraging this module's API allows building a notification dispatcher service with the logic that fits specific needs. JavaScript is also on someone since there are plenty possible behaviors.

Here's the example of WebPushClient service I've built for one of the real projects:

namespace Drupal\MODULE;

use Drupal\Component\Serialization\Json;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\Core\Site\Settings;
use Drupal\Core\Utility\Error;
use Drupal\web_push_api\Component\WebPush;
use Drupal\web_push_api\Component\WebPushAuthVapid;
use Drupal\web_push_api\Component\WebPushNotification;

/**
 * The client for dispatching Push API notifications.
 */
class WebPushClient {

  protected const BATCH_SIZE = 500;

  /**
   * An instance of the "entity_type.manager" service.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected EntityTypeManagerInterface $entityTypeManager;

  /**
   * The logger channel.
   *
   * @var \Drupal\Core\Logger\LoggerChannelInterface
   */
  protected LoggerChannelInterface $loggerChannel;

  /**
   * The Push API VAPID authorization.
   *
   * @var \Drupal\web_push_api\Component\WebPushAuthVapid
   */
  protected WebPushAuthVapid $webPushAuth;

  /**
   * {@inheritdoc}
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager, LoggerChannelFactoryInterface $logger_factory) {
    $settings = Settings::get('web_push_api', []);

    $this->entityTypeManager = $entity_type_manager;
    $this->loggerChannel = $logger_factory->get('web-push-notification');
    $this->webPushAuth = new WebPushAuthVapid($settings['public_key'] ?? '', $settings['private_key'] ?? '');
  }

  /**
   * Sends the notification to given subscriptions.
   *
   * @param \Drupal\web_push_api\Component\WebPush $webpush
   *   The dispatcher.
   * @param \Drupal\web_push_api\Entity\WebPushSubscription[] $subscriptions
   *   The list of subscriptions.
   * @param string $notification
   *   The notification to send.
   *
   * @throws \ErrorException
   */
  public function send(WebPush $webpush, iterable $subscriptions, string $notification): void {
    $storage = $webpush->getSubscriptionsStorage();

    foreach ($subscriptions as $subscription) {
      $webpush->sendNotification($subscription, $notification);
    }

    foreach ($webpush->flush(static::BATCH_SIZE) as $report) {
      if (!$report->isSuccess()) {
        $this->loggerChannel->error('[fail] @report', [
          '@report' => Json::encode($report),
        ]);

        try {
          $storage->deleteByEndpoint($report->getEndpoint());
        }
        catch (\Exception $e) {
          $this->loggerChannel->error(Error::renderExceptionSafe($e));
        }
      }
    }
  }

  /**
   * Sends the notification to all subscriptions.
   *
   * @param \Drupal\web_push_api\Component\WebPushNotification $notification
   *   The notification to send.
   *
   * @throws \ErrorException
   */
  public function sendToAll(WebPushNotification $notification): void {
    $webpush = new WebPush($this->entityTypeManager, $this->webPushAuth);
    $storage = $webpush->getSubscriptionsStorage();
    $count = $storage
      ->getQuery()
      ->count()
      ->execute();

    $batches_count = $count > static::BATCH_SIZE ? \ceil($count / static::BATCH_SIZE) : 1;
    $notification_string = (string) $notification;

    for ($batch_number = 0; $batch_number < $batches_count; $batch_number++) {
      $ids = $storage
        ->getQuery()
        ->range($batch_number * static::BATCH_SIZE, static::BATCH_SIZE)
        ->execute();

      $this->send($webpush, $storage->loadMultiple($ids), $notification_string);
    }
  }

  /**
   * Sends the notification to the given user.
   *
   * @param int $uid
   *   The ID of a Drupal user.
   * @param \Drupal\web_push_api\Component\WebPushNotification $notification
   *   The notification to send.
   *
   * @throws \ErrorException
   */
  public function sendToUser(int $uid, WebPushNotification $notification): void {
    $webpush = new WebPush($this->entityTypeManager, $this->webPushAuth);

    $this->send($webpush, $webpush->getSubscriptionsStorage()->loadByUserId($uid), (string) $notification);
  }

}
ahmed-ayman’s picture

Status: Active » Closed (won't fix)

I think that this module is just good for an API that other projects can be using for their custom logic.
like, to send notification for custom entities presave or whenever a specific event happens.

So, I think that we can mark it as Won't fix?