Skip to content

Quick Reference

Fast reference guide for common tasks and code snippets.

Installation

Easiest Installation Method

Bash
# Add notification server addon
ddev add-on get d34dman/ddev-notification-server
ddev restart

# Install Drupal module
ddev composer require drupal/notification_server
ddev drush en notification_server

# Optional: Enable demo module
ddev drush en notification_server_demo

Via Docker Compose

Docker Compose Setup

YAML
# docker-compose.yml
version: "3.8"
services:
  notification-server:
    image: ghcr.io/d34dman/notification-server:main
    ports: ["3000:3000", "8080:8080"]
    environment:
      - REDIS_URL=redis://redis:6379
      - CORS_ORIGIN=*
    depends_on: [redis]
  redis:
    image: redis:alpine
    ports: ["6379:6379"]
Bash
docker-compose up -d
composer require drupal/notification_server
drush en notification_server

Configuration

Module Configuration

Quick Configuration Steps

  1. Visit: /admin/config/system/notification-server
  2. Set URLs:
  3. DDEV: Server URL: http://notification-server:3000, WebSocket: ws://notification-server:8080
  4. Local: Server URL: http://localhost:3000, WebSocket: ws://localhost:8080
  5. Save configuration

Permissions

Grant these permissions as needed: - administer notification_server configuration - Admin access - access notification_server http demo - HTTP demo access - access notification_server websocket demo - WebSocket demo access

Basic Usage

Publishing Notifications

Basic Notification Publishing

PHP
<?php
// Get service
$client = \Drupal::service('notification_server.client');

// Simple message
$client->publishNotification('alerts', 'Server maintenance starting');

// Structured data
$client->publishNotification('updates', [
  'type' => 'content_update',
  'title' => 'New Article Published',
  'data' => ['id' => 123, 'author' => 'John']
]);

Client Management

Client ID Operations

PHP
<?php
// Generate client ID
$client_id = $client->generateClientId(null, [
  'user_id' => $user->id(),
  'session' => session_id()
]);

// Validate client ID
$is_valid = $client->validateClientId($client_id);

// Remove client
$client->removeClient($client_id);

Channel Management

Channel Creation and Access Control

PHP
<?php
use Drupal\notification_server\DTO\ChannelDTO;
use Drupal\notification_server\DTO\ChannelRulesDTO;

// Public channel
$rules = new ChannelRulesDTO(isPublic: true);
$channel = new ChannelDTO('public_announcements', $rules);
$client->createChannel($channel);

// Private channel
$rules = new ChannelRulesDTO(
  allowedClientIds: [$client_id],
  maxSubscribers: 50
);
$channel = new ChannelDTO('private_updates', $rules);
$client->createChannel($channel);

// Grant/revoke access
$client->grantChannelAccess('channel_name', $client_id);
$client->revokeChannelAccess('channel_name', $client_id);

WebSocket Integration

JavaScript Setup

WebSocket Client Implementation

JavaScript
// Get WebSocket URL from Drupal settings
const websocketUrl = drupalSettings.notificationServer.websocketUrl;
const clientId = drupalSettings.notificationServer.clientId;

// Connect
const socket = new WebSocket(`${websocketUrl}?clientId=${clientId}`);

// Event handlers
socket.onopen = () => console.log('Connected');
socket.onclose = () => console.log('Disconnected');
socket.onerror = (error) => console.error('Error:', error);

// Handle messages
socket.onmessage = function(event) {
  const data = JSON.parse(event.data);
  if (data.type === 'notification') {
    handleNotification(data.data);
  }
};

// Subscribe to channel
socket.send(JSON.stringify({
  type: 'subscribe',
  clientId: clientId,
  channel: 'channel_name'
}));

Passing Data to JavaScript

PHP
<?php
// In controller or form
$build['#attached']['drupalSettings']['notificationServer'] = [
  'websocketUrl' => $client->getWebsocketEndpoint(),
  'clientId' => $client_id
];

// In hook_preprocess_page()
$variables['#attached']['drupalSettings']['notificationServer'] = [
  'websocketUrl' => \Drupal::service('notification_server.client')->getWebsocketEndpoint(),
  'clientId' => $_SESSION['notification_client_id'] ?? null
];

Common Integration Patterns

Entity Hooks

PHP
<?php
// In .module file
use Drupal\node\NodeInterface;

/**
 * Implements hook_entity_insert().
 */
function mymodule_entity_insert(EntityInterface $entity) {
  if ($entity instanceof NodeInterface) {
    $client = \Drupal::service('notification_server.client');

    $client->publishNotification('content_updates', [
      'action' => 'created',
      'type' => $entity->bundle(),
      'title' => $entity->getTitle(),
      'id' => $entity->id(),
      'author' => $entity->getOwner()->getDisplayName()
    ]);
  }
}

/**
 * Implements hook_entity_update().
 */
function mymodule_entity_update(EntityInterface $entity) {
  if ($entity instanceof NodeInterface) {
    $client = \Drupal::service('notification_server.client');

    $client->publishNotification('content_updates', [
      'action' => 'updated',
      'type' => $entity->bundle(),
      'title' => $entity->getTitle(),
      'id' => $entity->id(),
      'changed' => $entity->getChangedTime()
    ]);
  }
}

User Activity Tracking

PHP
<?php
/**
 * Implements hook_user_login().
 */
function mymodule_user_login($account) {
  $client = \Drupal::service('notification_server.client');

  // Generate client ID for this session
  $client_id = $client->generateClientId(null, [
    'user_id' => $account->id(),
    'login_time' => time()
  ]);

  // Store in session
  $_SESSION['notification_client_id'] = $client_id;

  // Auto-subscribe to user channels
  $client->subscribeToChannel("user_{$account->id()}", $client_id);
  $client->subscribeToChannel('announcements', $client_id);

  // Subscribe based on roles
  foreach ($account->getRoles() as $role) {
    $client->subscribeToChannel("role_{$role}", $client_id);
  }
}

/**
 * Implements hook_user_logout().
 */
function mymodule_user_logout($account) {
  $client_id = $_SESSION['notification_client_id'] ?? null;

  if ($client_id) {
    $client = \Drupal::service('notification_server.client');
    $client->removeClient($client_id);
    unset($_SESSION['notification_client_id']);
  }
}

Form Integration

PHP
<?php
// In form submit handler
public function submitForm(array &$form, FormStateInterface $form_state) {
  // Process form data
  $data = $form_state->getValues();

  // Send notification
  $client = \Drupal::service('notification_server.client');
  $client->publishNotification('form_submissions', [
    'form_id' => $form['#id'],
    'user_id' => \Drupal::currentUser()->id(),
    'data' => $data,
    'timestamp' => time()
  ]);

  $this->messenger()->addStatus($this->t('Form submitted and notification sent.'));
}

Queue Integration

PHP
<?php
// Queue worker for sending notifications
class NotificationQueueWorker extends QueueWorkerBase {

  /**
   * {@inheritdoc}
   */
  public function processItem($data) {
    $client = \Drupal::service('notification_server.client');

    try {
      $result = $client->publishNotification($data['channel'], $data['message']);

      if ($result === null) {
        throw new SuspendQueueException('Failed to send notification');
      }
    } catch (Exception $e) {
      \Drupal::logger('mymodule')->error('Queue notification failed: @error', [
        '@error' => $e->getMessage()
      ]);

      throw new SuspendQueueException($e->getMessage());
    }
  }
}

// Adding items to queue
```php
<?php
$queue = \Drupal::queue('notification_queue');
$queue->createItem([
  'channel' => 'batch_updates',
  'message' => [
    'type' => 'batch_complete',
    'items_processed' => 100,
    'completed_at' => time()
  ]
]);

Drupal 10/11 Compatibility

Service Definition

YAML
# mymodule.services.yml
services:
  mymodule.notification_handler:
    class: Drupal\mymodule\Service\NotificationHandler
    arguments: ['@notification_server.client', '@current_user', '@logger.factory']

Service Class

PHP
<?php

namespace Drupal\mymodule\Service;

use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\notification_server\Service\NotificationServerClientInterface;

class NotificationHandler {

  public function __construct(
    private readonly NotificationServerClientInterface $notificationClient,
    private readonly AccountInterface $currentUser,
    LoggerChannelFactoryInterface $loggerFactory
  ) {
    $this->logger = $loggerFactory->get('mymodule');
  }

  public function sendUserNotification(string $message, int $userId = null): bool {
    $userId = $userId ?? $this->currentUser->id();

    try {
      $result = $this->notificationClient->publishNotification(
        "user_{$userId}",
        [
          'message' => $message,
          'timestamp' => time(),
          'user_id' => $userId
        ]
      );

      return $result !== null;
    } catch (\Exception $e) {
      $this->logger->error('Failed to send notification: @error', [
        '@error' => $e->getMessage()
      ]);
      return false;
    }
  }
}

Testing

PHPUnit Tests

PHP
<?php

namespace Drupal\Tests\mymodule\Unit;

use Drupal\Tests\UnitTestCase;
use Drupal\notification_server\Service\NotificationServerClientInterface;
use Drupal\mymodule\Service\NotificationHandler;

class NotificationHandlerTest extends UnitTestCase {

  public function testSendUserNotification() {
    // Mock the notification client
    $client = $this->createMock(NotificationServerClientInterface::class);
    $client->expects($this->once())
           ->method('publishNotification')
           ->willReturn(['success' => true]);

    // Test the handler
    $handler = new NotificationHandler($client, $this->createMock(AccountInterface::class), $this->createMock(LoggerChannelFactoryInterface::class));
    $result = $handler->sendUserNotification('Test message', 123);

    $this->assertTrue($result);
  }
}

Functional Tests

PHP
<?php

namespace Drupal\Tests\mymodule\Functional;

use Drupal\Tests\BrowserTestBase;

class NotificationIntegrationTest extends BrowserTestBase {

  protected static $modules = ['notification_server', 'mymodule'];

  public function testNotificationConfiguration() {
    // Create admin user
    $admin = $this->drupalCreateUser(['administer notification_server configuration']);
    $this->drupalLogin($admin);

    // Visit configuration page
    $this->drupalGet('/admin/config/system/notification-server');
    $this->assertSession()->statusCodeEquals(200);

    // Test configuration form
    $this->submitForm([
      'server_url' => 'http://localhost:3000',
      'websocket_url' => 'ws://localhost:8080'
    ], 'Save configuration');

    $this->assertSession()->pageTextContains('The configuration options have been saved.');
  }
}

Debugging

Enable Debug Logging

PHP
<?php
// In settings.php
$config['system.logging']['error_level'] = 'verbose';

// Check Drupal logs
tail -f /var/log/drupal/drupal.log | grep notification_server

Test Connectivity

Bash
# Test HTTP API
curl http://localhost:3000/health

# Test WebSocket (install wscat: npm install -g wscat)
wscat -c ws://localhost:8080?clientId=test123

# Test Redis
redis-cli ping

Debug WebSocket in Browser

JavaScript
// Enable debug logging
socket.onmessage = function(event) {
  console.log('WebSocket message received:', event.data);
  try {
    const data = JSON.parse(event.data);
    console.log('Parsed data:', data);
  } catch (e) {
    console.error('Failed to parse message:', e);
  }
};

// Debug connection state
setInterval(() => {
  console.log('WebSocket state:', socket.readyState);
}, 5000);

Troubleshooting Quick Fixes

Common Issues and Quick Solutions

Bash
# Service not running
docker ps | grep notification-server
docker-compose up -d

# Permission denied
drush upwd admin --password=admin
drush user-login

# Configuration not saved
drush cr
drush cex -y

# WebSocket connection fails
# Check CORS settings in notification server
# Verify WebSocket URL format (ws:// not http://)

# Redis connection issues
docker logs redis
redis-cli ping

Reset Configuration

Bash
# Reset module configuration
drush config-delete notification_server.settings

# Reinstall module
drush pmu notification_server
drush en notification_server

# Clear all caches
drush cr

Production Checklist

Production Deployment Checklist

Security

  • Use wss:// for WebSocket connections
  • Set appropriate CORS origins
  • Implement rate limiting
  • Monitor for suspicious activity
  • Use secure Redis configuration

Performance

  • Configure Redis memory limits
  • Set appropriate message TTL
  • Monitor connection counts
  • Implement connection pooling
  • Use load balancer for multiple servers

Monitoring

  • Set up health checks
  • Monitor server logs
  • Track WebSocket connection metrics
  • Monitor Redis performance
  • Set up alerts for failures

Backup

  • Backup Redis data
  • Backup module configuration
  • Document server settings
  • Test recovery procedures