or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

cli-tools.mdconfiguration.mdindex.mdlow-level-communication.mdmodule-setup.mdpush-notifications.mdtesting.mdupdate-management.md
tile.json

push-notifications.mddocs/

Push Notifications

Complete push notification system with subscription management, message handling, and notification click events for Angular service worker integration.

Capabilities

SwPush Service

Injectable service for managing Web Push Notifications through the Angular Service Worker.

/**
 * Service for subscribing to and handling Web Push Notifications
 * Provides observables for messages and notification interactions
 */
@Injectable()
class SwPush {
  /**
   * Observable that emits payloads of received push notification messages
   */
  readonly messages: Observable<object>;
  
  /**
   * Observable that emits when user interacts with notifications
   * Includes both the action taken and notification details
   */
  readonly notificationClicks: Observable<{
    action: string;
    notification: NotificationOptions & { title: string };
  }>;
  
  /**
   * Observable that emits the current push subscription state
   * Emits null when no subscription exists
   */
  readonly subscription: Observable<PushSubscription | null>;
  
  /**
   * Whether the Service Worker is enabled and push is supported
   */
  get isEnabled(): boolean;
  
  /**
   * Request push notification subscription from the user
   * @param options - Configuration with VAPID public key
   * @returns Promise resolving to PushSubscription object
   * @throws Error if service worker not supported or user denies permission
   */
  requestSubscription(options: {serverPublicKey: string}): Promise<PushSubscription>;
  
  /**
   * Unsubscribe from push notifications
   * @returns Promise resolving when unsubscribe completes
   * @throws Error if not currently subscribed or unsubscribe fails
   */
  unsubscribe(): Promise<void>;
}

Usage Examples:

import { Component, inject, OnInit } from '@angular/core';
import { SwPush } from '@angular/service-worker';

@Component({...})
export class NotificationComponent implements OnInit {
  private swPush = inject(SwPush);
  
  async subscribeToNotifications() {
    if (!this.swPush.isEnabled) {
      console.log('Push notifications not supported');
      return;
    }
    
    try {
      const subscription = await this.swPush.requestSubscription({
        serverPublicKey: 'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U'
      });
      
      // Send subscription to your server
      await this.sendSubscriptionToServer(subscription);
      console.log('Successfully subscribed to push notifications');
    } catch (error) {
      console.error('Could not subscribe to notifications', error);
    }
  }
  
  async unsubscribeFromNotifications() {
    try {
      await this.swPush.unsubscribe();
      console.log('Successfully unsubscribed from push notifications');
    } catch (error) {
      console.error('Could not unsubscribe', error);
    }
  }
  
  ngOnInit() {
    // Listen for push messages
    this.swPush.messages.subscribe(message => {
      console.log('Received push message:', message);
      // Handle incoming push data
    });
    
    // Listen for notification clicks
    this.swPush.notificationClicks.subscribe(click => {
      console.log('Notification clicked:', click);
      // Handle user interaction with notification
      if (click.action === 'open-url') {
        window.open(click.notification.data.url);
      }
    });
    
    // Monitor subscription state
    this.swPush.subscription.subscribe(subscription => {
      if (subscription) {
        console.log('Currently subscribed to push notifications');
      } else {
        console.log('Not subscribed to push notifications');
      }
    });
  }
  
  private async sendSubscriptionToServer(subscription: PushSubscription): Promise<void> {
    const response = await fetch('/api/push/subscribe', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(subscription.toJSON())
    });
    
    if (!response.ok) {
      throw new Error('Failed to save subscription');
    }
  }
}

Push Message Observable

Observable that emits when push messages are received by the service worker.

/**
 * Observable that emits the data payload of received push messages
 * Messages are delivered even when the app is not in focus
 */
readonly messages: Observable<object>;

Usage Examples:

// Simple message handling
this.swPush.messages.subscribe(message => {
  console.log('Push message received:', message);
});

// Typed message handling
interface PushMessage {
  title: string;
  body: string;
  data?: any;
}

this.swPush.messages.subscribe((message: PushMessage) => {
  this.showNotification(message.title, message.body);
});

Notification Click Observable

Observable that emits when users interact with push notifications.

/**
 * Observable that emits notification interaction events
 * Includes the action taken and full notification details
 */
readonly notificationClicks: Observable<{
  action: string;
  notification: NotificationOptions & { title: string };
}>;

Usage Examples:

// Handle notification clicks
this.swPush.notificationClicks.subscribe(event => {
  const { action, notification } = event;
  
  switch (action) {
    case 'open-post':
      this.router.navigate(['/posts', notification.data.postId]);
      break;
    case 'mark-read':
      this.markAsRead(notification.data.messageId);
      break;
    case '': // Default click (no action button)
      this.openApp();
      break;
  }
});

Subscription Management

Request and manage push notification subscriptions.

/**
 * Request push notification subscription with VAPID key
 * @param options - Object containing serverPublicKey (VAPID public key)
 * @returns Promise<PushSubscription> - Browser push subscription
 */
requestSubscription(options: {serverPublicKey: string}): Promise<PushSubscription>;

/**
 * Unsubscribe from push notifications
 * @returns Promise<void> - Resolves when unsubscription completes
 */
unsubscribe(): Promise<void>;

/**
 * Observable of current subscription state
 * Emits PushSubscription when subscribed, null when not subscribed
 */
readonly subscription: Observable<PushSubscription | null>;

VAPID Key Format:

The serverPublicKey must be a base64url-encoded VAPID public key. Special characters are automatically converted:

  • Underscores (_) become forward slashes (/)
  • Hyphens (-) become plus signs (+)

Server Integration Example:

async subscribeToNotifications() {
  const subscription = await this.swPush.requestSubscription({
    serverPublicKey: this.vapidPublicKey
  });
  
  // Send subscription to your backend
  const response = await fetch('/api/notifications/subscribe', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      subscription: subscription.toJSON(),
      userId: this.currentUser.id
    })
  });
  
  if (!response.ok) {
    throw new Error('Failed to save subscription on server');
  }
}

Push Message Format

Push messages sent from your server should follow this structure:

{
  "notification": {
    "title": "Message Title",
    "body": "Message body text",
    "icon": "/assets/icon-192x192.png",
    "badge": "/assets/badge-72x72.png",
    "image": "/assets/notification-image.jpg",
    "data": {
      "url": "https://example.com/page",
      "id": "unique-message-id"
    },
    "actions": [
      {
        "action": "open-url",
        "title": "Open",
        "icon": "/assets/open-icon.png"
      },
      {
        "action": "dismiss",
        "title": "Dismiss",
        "icon": "/assets/dismiss-icon.png"
      }
    ],
    "requireInteraction": false,
    "silent": false,
    "tag": "message-category",
    "renotify": true,
    "timestamp": 1640995200000
  }
}

Required Properties:

  • title: Notification title (required)

Optional Properties:

  • body: Main notification text
  • icon: Small icon displayed with notification
  • badge: Small monochrome icon for mobile status bar
  • image: Large image displayed in notification
  • data: Custom data passed to click handlers
  • actions: Array of action buttons
  • requireInteraction: Keep notification visible until user interacts
  • silent: Suppress notification sound/vibration
  • tag: Grouping identifier for replacing notifications
  • renotify: Whether to alert user if notification with same tag exists
  • timestamp: When the event occurred

Error Handling

// Runtime error codes for push notifications
enum RuntimeErrorCode {
  UNKNOWN_REGISTRATION_STRATEGY = 5600,
  SERVICE_WORKER_DISABLED_OR_NOT_SUPPORTED_BY_THIS_BROWSER = 5601,
  NOT_SUBSCRIBED_TO_PUSH_NOTIFICATIONS = 5602,
  PUSH_SUBSCRIPTION_UNSUBSCRIBE_FAILED = 5603,
  SERVICE_WORKER_REGISTRATION_FAILED = 5604
}

// Error message constant
const ERR_SW_NOT_SUPPORTED = 'Service workers are disabled or not supported by this browser';

Common Error Scenarios:

  1. User denies permission: requestSubscription() rejects with permission error
  2. Service worker not supported: Check swPush.isEnabled before operations
  3. Not subscribed: unsubscribe() throws NOT_SUBSCRIBED_TO_PUSH_NOTIFICATIONS
  4. Unsubscribe fails: unsubscribe() throws PUSH_SUBSCRIPTION_UNSUBSCRIBE_FAILED
  5. Invalid VAPID key: requestSubscription() rejects with key format error

Error Handling Example:

async handlePushSubscription() {
  if (!this.swPush.isEnabled) {
    this.showError('Push notifications not supported in this browser');
    return;
  }
  
  try {
    await this.swPush.requestSubscription({
      serverPublicKey: this.vapidKey
    });
  } catch (error) {
    if (error.message.includes('permission')) {
      this.showError('Please enable notifications in browser settings');
    } else {
      this.showError('Failed to subscribe to notifications');
    }
  }
}