API Reference¶
Complete reference for the Notification Server module APIs and services.
NotificationServerClientInterface¶
The main service interface for interacting with the notification server.
Service Access¶
<?php
// Dependency injection (recommended)
public function __construct(
NotificationServerClientInterface $notification_client
) {
$this->notificationClient = $notification_client;
}
// Service container
$notification_client = \Drupal::service('notification_server.client');
Publishing and Notifications¶
publishNotification()¶
Publishes a message to a specific channel.
Signature:
<?php
public function publishNotification(
string $channel,
object|array|string|float|int|bool|null $message
): ?array
Parameters:
- $channel (string): The channel name to publish to
- $message (mixed): The message content (any JSON-serializable type)
Returns:
- array|null: Notification data if successful, NULL on failure
Examples:
Publishing Different Message Types
<?php
// Simple string message
$result = $notification_client->publishNotification('alerts', 'Server maintenance in 5 minutes');
// Structured data
$result = $notification_client->publishNotification('user_updates', [
'type' => 'profile_update',
'user_id' => 123,
'changes' => ['email', 'name'],
'timestamp' => time()
]);
// Complex object
$notification_data = [
'event' => 'content_published',
'content' => [
'id' => $node->id(),
'title' => $node->getTitle(),
'type' => $node->bundle(),
'author' => [
'uid' => $node->getOwner()->id(),
'name' => $node->getOwner()->getDisplayName()
]
],
'metadata' => [
'published_at' => $node->getCreatedTime(),
'workflow_state' => $node->get('moderation_state')->value
]
];
$result = $notification_client->publishNotification('content_updates', $notification_data);
getNotifications()¶
Retrieves notification history for a channel.
Signature:
Parameters:
- $channel (string): The channel name
- $limit (int): Maximum number of notifications to return (1-100, default: 10)
Returns:
- array: Array of notification objects
Examples:
Retrieving and Processing Notifications
<?php
// Get last 10 notifications
$notifications = $notification_client->getNotifications('user_alerts');
// Get last 25 notifications
$notifications = $notification_client->getNotifications('system_events', 25);
// Process notifications
foreach ($notifications as $notification) {
$message = $notification['message'];
$timestamp = $notification['timestamp'];
$channel = $notification['channel'];
// Process notification data
processNotification($message, $timestamp, $channel);
}
Client Management¶
generateClientId()¶
Generates a new client ID for WebSocket connections.
Signature:
Parameters:
- $clientId (string|null): Optional specific client ID to validate/register
- $metadata (array): Optional metadata to associate with the client
Returns:
- string|null: Generated client ID if successful, NULL on failure
Examples:
Client ID Generation Options
<?php
// Generate random client ID
$client_id = $notification_client->generateClientId();
// Generate with metadata
$client_id = $notification_client->generateClientId(null, [
'user_id' => $user->id(),
'session_id' => session_id(),
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
'ip_address' => \Drupal::request()->getClientIp(),
'created_at' => time()
]);
// Validate/register specific client ID
$client_id = $notification_client->generateClientId('user_123_session_abc', [
'user_id' => 123,
'session' => 'abc'
]);
validateClientId()¶
Validates whether a client ID is valid and active.
Signature:
Parameters:
- $clientId (string): The client ID to validate
Returns:
- bool: TRUE if valid, FALSE otherwise
Examples:
Client ID Validation Pattern
<?php
// Check if client ID is valid before WebSocket connection
$client_id = $_SESSION['notification_client_id'] ?? null;
if ($client_id && $notification_client->validateClientId($client_id)) {
// Use existing client ID
$websocket_url = "ws://localhost:8080?clientId=" . urlencode($client_id);
} else {
// Generate new client ID
$client_id = $notification_client->generateClientId();
$_SESSION['notification_client_id'] = $client_id;
$websocket_url = "ws://localhost:8080?clientId=" . urlencode($client_id);
}
removeClient()¶
Removes a client and all associated data.
Signature:
Parameters:
- $clientId (string): The client ID to remove
Returns:
- bool: TRUE if successful, FALSE otherwise
Examples:
<?php
// Clean up client on user logout
function cleanup_notification_client($user_id) {
$client_id = get_user_client_id($user_id);
if ($client_id) {
$notification_client = \Drupal::service('notification_server.client');
$success = $notification_client->removeClient($client_id);
if ($success) {
clear_user_client_id($user_id);
}
return $success;
}
return true;
}
Channel Management¶
createChannel()¶
Creates a new notification channel with access rules.
Signature:
Parameters:
- $channel (ChannelDTO): Channel configuration object
Returns:
- bool: TRUE if successful, FALSE otherwise
Examples:
<?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);
$notification_client->createChannel($channel);
// Private channel with specific client access
$rules = new ChannelRulesDTO(
allowedClientIds: ['client_123', 'client_456'],
maxSubscribers: 50
);
$channel = new ChannelDTO('private_updates', $rules);
$notification_client->createChannel($channel);
// Pattern-based access
$rules = new ChannelRulesDTO(
allowedPatterns: ['admin_.*', 'moderator_.*'],
maxSubscribers: 100
);
$channel = new ChannelDTO('staff_channel', $rules);
$notification_client->createChannel($channel);
// Using array data
$channel_data = [
'channel' => 'dynamic_channel',
'rules' => [
'isPublic' => false,
'allowedClientIds' => [$client_id],
'maxSubscribers' => 25
]
];
$channel = ChannelDTO::fromArray($channel_data);
$notification_client->createChannel($channel);
grantChannelAccess()¶
Grants a specific client access to a channel.
Signature:
Parameters:
- $channel (string): The channel name
- $clientId (string): The client ID to grant access to
Returns:
- bool: TRUE if successful, FALSE otherwise
Examples:
<?php
// Grant user access to private channel
$user_client_id = get_user_client_id($user->id());
$success = $notification_client->grantChannelAccess('vip_updates', $user_client_id);
// Grant access based on user role
if ($user->hasRole('premium_member')) {
$notification_client->grantChannelAccess('premium_channel', $user_client_id);
}
// Grant temporary access
$notification_client->grantChannelAccess('event_live_updates', $client_id);
// Set up task to revoke access after event
revokeChannelAccess()¶
Revokes a client's access to a channel.
Signature:
Parameters:
- $channel (string): The channel name
- $clientId (string): The client ID to revoke access from
Returns:
- bool: TRUE if successful, FALSE otherwise
Examples:
<?php
// Revoke access when user role changes
$user_client_id = get_user_client_id($user->id());
$notification_client->revokeChannelAccess('admin_channel', $user_client_id);
// Revoke access on user suspension
if ($user->isBlocked()) {
$channels = ['general', 'updates', 'announcements'];
foreach ($channels as $channel) {
$notification_client->revokeChannelAccess($channel, $user_client_id);
}
}
Subscription Management¶
subscribeToChannel()¶
Subscribes a client to a channel (server-side subscription).
Signature:
Parameters:
- $channel (string): The channel name to subscribe to
- $clientId (string): The WebSocket client ID
Returns:
- bool: TRUE if successful, FALSE otherwise
Examples:
<?php
// Auto-subscribe user to relevant channels
function auto_subscribe_user_channels($user, $client_id) {
$notification_client = \Drupal::service('notification_server.client');
// Subscribe to user-specific channel
$user_channel = "user_{$user->id()}";
$notification_client->subscribeToChannel($user_channel, $client_id);
// Subscribe based on user roles
foreach ($user->getRoles() as $role) {
$role_channel = "role_{$role}";
$notification_client->subscribeToChannel($role_channel, $client_id);
}
// Subscribe to general announcements
$notification_client->subscribeToChannel('announcements', $client_id);
}
unsubscribeFromChannel()¶
Unsubscribes a client from a channel (server-side unsubscription).
Signature:
Parameters:
- $channel (string): The channel name to unsubscribe from
- $clientId (string): The WebSocket client ID
Returns:
- bool: TRUE if successful, FALSE otherwise
Examples:
<?php
// Unsubscribe from role-specific channels when role changes
function update_user_subscriptions($user, $old_roles, $new_roles) {
$notification_client = \Drupal::service('notification_server.client');
$client_id = get_user_client_id($user->id());
// Unsubscribe from removed roles
$removed_roles = array_diff($old_roles, $new_roles);
foreach ($removed_roles as $role) {
$role_channel = "role_{$role}";
$notification_client->unsubscribeFromChannel($role_channel, $client_id);
}
// Subscribe to new roles
$added_roles = array_diff($new_roles, $old_roles);
foreach ($added_roles as $role) {
$role_channel = "role_{$role}";
$notification_client->subscribeToChannel($role_channel, $client_id);
}
}
getWebsocketEndpoint()¶
Gets the configured WebSocket server URL.
Signature:
Returns:
- string: The WebSocket server URL
Examples:
<?php
// Get WebSocket URL for JavaScript
$websocket_url = $notification_client->getWebsocketEndpoint();
// Add to Drupal settings
$build['#attached']['drupalSettings']['notificationServer'] = [
'websocketUrl' => $websocket_url,
'clientId' => $client_id
];
// Use in Twig template
$variables['websocket_url'] = $websocket_url;
Data Transfer Objects (DTOs)¶
ChannelDTO¶
Represents a notification channel configuration.
Constructor:
Methods:
- getChannel(): string - Get the channel name
- getRules(): ?ChannelRulesDTO - Get the channel rules
- toArray(): array - Convert to array representation
- static fromArray(array $data): self - Create from array data
Examples:
<?php
// Basic channel
$channel = new ChannelDTO('simple_channel');
// Channel with rules
$rules = new ChannelRulesDTO(isPublic: true);
$channel = new ChannelDTO('public_channel', $rules);
// From array data
$data = [
'channel' => 'array_channel',
'rules' => [
'isPublic' => false,
'allowedClientIds' => ['client1', 'client2']
]
];
$channel = ChannelDTO::fromArray($data);
// Convert to array for API calls
$array_data = $channel->toArray();
ChannelRulesDTO¶
Represents access control rules for a channel.
Constructor:
<?php
public function __construct(
?array $allowedClientIds = null,
?bool $isPublic = null,
?array $allowedPatterns = null,
?int $maxSubscribers = null
)
Methods:
- getAllowedClientIds(): ?array - Get allowed client IDs
- isPublic(): ?bool - Check if channel is public
- getAllowedPatterns(): ?array - Get allowed patterns
- getMaxSubscribers(): ?int - Get maximum subscribers
- toArray(): array - Convert to array representation
- static fromArray(array $data): self - Create from array data
Examples:
<?php
// Public channel
$rules = new ChannelRulesDTO(isPublic: true);
// Private channel with specific clients
$rules = new ChannelRulesDTO(
allowedClientIds: ['client1', 'client2', 'client3'],
maxSubscribers: 50
);
// Pattern-based access
$rules = new ChannelRulesDTO(
allowedPatterns: ['admin_.*', 'user_\d+'],
maxSubscribers: 100
);
// Complex rules
$rules = new ChannelRulesDTO(
allowedClientIds: ['special_client'],
allowedPatterns: ['premium_.*'],
isPublic: false,
maxSubscribers: 25
);
// From array
$rules = ChannelRulesDTO::fromArray([
'allowedClientIds' => ['client1'],
'isPublic' => false,
'maxSubscribers' => 10
]);
WebSocket Protocol¶
Client-Side JavaScript API¶
Connection¶
// Establish connection
const websocketUrl = drupalSettings.notificationServer.websocketUrl;
const clientId = drupalSettings.notificationServer.clientId;
const socket = new WebSocket(`${websocketUrl}?clientId=${clientId}`);
// Connection event handlers
socket.onopen = function(event) {
console.log('WebSocket connected');
// Send initial subscriptions
};
socket.onclose = function(event) {
console.log('WebSocket disconnected', event.code, event.reason);
// Implement reconnection logic
};
socket.onerror = function(error) {
console.error('WebSocket error:', error);
};
Message Handling¶
socket.onmessage = function(event) {
try {
const data = JSON.parse(event.data);
switch (data.type) {
case 'notification':
handleNotification(data.data);
break;
case 'subscription_success':
handleSubscriptionSuccess(data.channel);
break;
case 'subscription_error':
handleSubscriptionError(data.channel, data.error);
break;
case 'error':
handleError(data.message);
break;
}
} catch (error) {
console.error('Error parsing WebSocket message:', error);
}
};
function handleNotification(notificationData) {
// Process incoming notification
console.log('Received notification:', notificationData);
// Update UI
displayNotification(notificationData.message);
// Trigger custom events
document.dispatchEvent(new CustomEvent('notification-received', {
detail: notificationData
}));
}
Channel Subscription¶
// Subscribe to channel
function subscribeToChannel(channel) {
const message = {
type: 'subscribe',
clientId: clientId,
channel: channel
};
socket.send(JSON.stringify(message));
}
// Unsubscribe from channel
function unsubscribeFromChannel(channel) {
const message = {
type: 'unsubscribe',
clientId: clientId,
channel: channel
};
socket.send(JSON.stringify(message));
}
// Bulk subscribe
function subscribeToChannels(channels) {
channels.forEach(channel => {
subscribeToChannel(channel);
});
}
Message Formats¶
Subscription Messages¶
// Subscribe
{
"type": "subscribe",
"clientId": "client_123",
"channel": "channel_name"
}
// Unsubscribe
{
"type": "unsubscribe",
"clientId": "client_123",
"channel": "channel_name"
}
Notification Messages¶
// Incoming notification
{
"type": "notification",
"data": {
"channel": "channel_name",
"message": "Notification content",
"timestamp": "2023-12-01T10:00:00Z",
"metadata": {
"priority": "high",
"sender": "system"
}
}
}
Response Messages¶
// Subscription success
{
"type": "subscription_success",
"channel": "channel_name"
}
// Subscription error
{
"type": "subscription_error",
"channel": "channel_name",
"error": "Access denied"
}
// General error
{
"type": "error",
"message": "Error description"
}
Error Handling¶
Error Handling Best Practice
Always check return values from notification server methods. A null return value indicates failure, and you should implement appropriate fallback behavior.
Common Error Codes¶
| HTTP Status | Description | Common Causes |
|---|---|---|
| 400 | Bad Request | Invalid JSON, missing required fields |
| 401 | Unauthorized | Invalid client ID |
| 403 | Forbidden | No access to channel |
| 404 | Not Found | Channel doesn't exist |
| 409 | Conflict | Channel already exists |
| 500 | Internal Server Error | Server or Redis connection issues |
Error Handling Examples¶
Proper Error Handling
<?php
try {
$result = $notification_client->publishNotification('channel', 'message');
if ($result === null) {
// Handle failure
\Drupal::logger('notification_server')->error('Failed to publish notification to channel: @channel', [
'@channel' => 'channel'
]);
} else {
// Handle success
\Drupal::logger('notification_server')->info('Successfully published notification to channel: @channel', [
'@channel' => 'channel'
]);
}
} catch (Exception $e) {
// Handle exceptions
\Drupal::logger('notification_server')->error('Exception publishing notification: @message', [
'@message' => $e->getMessage()
]);
}
Best Practices¶
Performance Best Practices
- Batch operations when possible
- Cache client IDs to avoid regeneration
- Use appropriate message TTL settings
- Monitor channel subscriber counts
Security Best Practices
- Validate all input data
- Use pattern-based access for scalability
- Implement rate limiting at application level
- Monitor for suspicious patterns
Error Handling Best Practices
- Always check return values
- Implement proper logging
- Handle network failures gracefully
- Provide user feedback
Code Organization Best Practices
- Use dependency injection for services
- Create helper functions for common operations
- Document complex channel rules
- Implement proper error recovery