Services

Pusher service

Introduction

The Pusher service file provides the implementation of internal notifications using the Pusher service. Pusher enables real-time communication and facilitates the establishment of private channels for users to interact with each other and receive notifications in real time. It's important to note that in order to use Pusher, developers need to set up an account and configure Pusher on pusher.com.

Pusher Advantages

  • Real-time communication: Pusher allows users to establish private channels and enables real-time communication between users and the backend. This can be utilized for various purposes, such as user-to-user communication and delivering notifications instantly.

Pusher Limitations

  • Inability to fetch old events: One limitation of Pusher is that it does not have the ability to fetch old events. It can only listen to live events triggered after establishing a connection. This means that if a user is offline during an event trigger, they won't receive the event when they come back online.
  • Handling historical data: Storing historical data locally can be a challenge when using Pusher, especially in scenarios where users uninstall and reinstall the app or use the app on multiple devices. If historical data is required, it is recommended to implement a backend solution to store events in a cloud database prior to sending them via Pusher.
  • Pusher can only trigger events on public channels from the client side. It cannot trigger events on private channels directly.
  • To send events on private channels, you need to establish a private channel and configure the necessary authorization process.
  • When using a private channel, you need to set up an onAuthorizer function and a corresponding backend implementation.
  • The onAuthorizer function sends a request to your backend server to obtain an authorization object, which will be used by Pusher to authenticate the client's access to the private channel.
  • By using the onAuthorizer function and backend implementation, you can ensure that only authorized clients can subscribe to and trigger events on private channels. This adds an extra layer of security and control over the communication.

Integration

Developers integrating Pusher-based internal notifications should follow these steps:

  1. Sign up for a Pusher account and configure Pusher on pusher.com.
  2. Verify the necessary logic to establish a connection with Pusher and handle user authentication and authorization for private channels.
  3. Ensure that you handle the management of private channels, event triggers, and event subscriptions within your main internal notifications file, while considering the limitations and challenges mentioned above.
  4. You might want to setup the user id in private channel to target specific user. When subscribing for perticular user; user id is needed so for that, logic should be implemented by developer in the init() method of this class to assign user id in the service, the logic may vary depending on the project. This user ID will be used to establish private channels for the user.
  5. Obtain the Pusher configuration details, such as the API key and cluster, and put it in your environment configuration.

Source Code

import 'dart:async';

import 'package:pusher_channels_flutter/pusher_channels_flutter.dart';

import './base_service.dart';
import '../../../../env.dart';
import '../../../logging_library/logging_library.dart';
import '../../models/notification.dart';

class InternalNotificationsWithPusher implements InternalNotificationsService {
  late final PusherChannelsFlutter _pusher;

  final StreamController<int> _pendingNotificationsCountStreamController =
      StreamController<int>.broadcast();

  @override
  late final Stream<int> pendingNotificationsCountStream;

  final StreamController<List<InternalNotification>> _notificationsStreamController =
      StreamController<List<InternalNotification>>.broadcast();

  @override
  late final Stream<List<InternalNotification>> notificationsStream;

  List<InternalNotification> _notifications = [];

  @override
  List<InternalNotification> get notifications => _notifications;

  late final String userId;

  @override
  Future<void> init() async {
    pendingNotificationsCountStream = _pendingNotificationsCountStreamController.stream;
    notificationsStream = _notificationsStreamController.stream;

    // Write your logic here to get user id
    userId = 'userId';

    final EnvironmentConfig environmentConfig = EnvironmentConfig.getEnvConfig();
    if (environmentConfig.pusherConfig == null) return;

    _pusher = PusherChannelsFlutter.getInstance();
    await _pusher.init(
      apiKey: environmentConfig.pusherConfig!.apiKey,
      cluster: environmentConfig.pusherConfig!.cluster,
      onAuthorizer: (String channelName, String socketId, dynamic options) {
        // return {"shared_secret": "11518af14b1d4acbd3b9"};
      },
      onEvent: (event) {
        Log.warning(event);
        if (event.data != null) {
          _notificationsUpdated([event.data]);
        }
      },
    );
    await _pusher.subscribe(channelName: 'private-$userId');
    await _pusher.connect(); // Connect with cluster
  }

  @override
  Future<void> dispose() async {}

  @override
  Future<void> subscribe() async {}

  @override
  Future<void> unsubscribe() async {}

  @override
  Future<void> push(List<String> userIds, InternalNotification notification) async {
    for (final id in userIds) {
      await _pusher.trigger(
        PusherEvent(
          channelName: 'private-$id',
          eventName: 'Internal Notification',
          data: notification.toJson(),
        ),
      );
    }
  }

  void _notificationsUpdated(List<Map<String, dynamic>> notifications) {
    int count = 0;
    final List<InternalNotification> updatedNotifications = [];
    for (final jsonNotification in notifications) {
      final InternalNotification notification = InternalNotification.fromJson(jsonNotification);
      if (!notification.opened) count++;
      updatedNotifications.add(notification);
    }
    _pendingNotificationsCountStreamController.add(count);
    _notificationsStreamController.add(updatedNotifications);
    _notifications = updatedNotifications;
  }
}

onAuthorizer and Backend sample code

1. Client-side Configuration (Dart/Flutter):

_pusher = PusherChannelsFlutter.getInstance();
await _pusher.init(
  // Rest of your Pusher configuration...
  onAuthorizer: (String channelName, String socketId, dynamic options) async {
    final response = await http.post(
      'https://your-backend-server.com/pusher/authorize',
      body: {
        'channelName': channelName,
        'socketId': socketId,
      },
    );
    return jsonDecode(response.body);
  },
);

2. Backend Configuration

In this example, when the client requests authorization for a private channel, the server receives the request and performs the necessary authorization logic. It generates the authorization data (authData) based on the user's permissions or any other relevant information. The server then uses the Pusher library to generate the authorization signature (auth) required by Pusher. Finally, the server sends the auth data back to the client as a response.

Please note that this is a simplified example, and you'll need to adapt it to your specific backend setup and requirements. Make sure to install the required dependencies (pusher in this case) and configure your Pusher credentials appropriately.

a. Example using Node.js and Express:

const express = require('express');
const Pusher = require('content/vaahflutter/5.directory_structure/3.vaahextendflutter/5.services/2.notification/3.internal/3.services/3.pusher');

const app = express();

// Pusher configuration
const pusher = new Pusher({
  appId: 'YOUR_APP_ID',
  key: 'YOUR_APP_KEY',
  secret: 'YOUR_APP_SECRET',
  cluster: 'YOUR_APP_CLUSTER',
});

app.post('/pusher/authorize', async (req, res) => {
  const { channelName, socketId } = req.body;

  // Implement your authorization logic here
  // This can include checking user permissions, validating session, etc.
  // Generate the necessary auth data based on your requirements

  const authData = {
    user_id: '',
    user_info: {
      name: '',
    },
  };

  const auth = pusher.authenticate(channelName, socketId, authData);
  res.send(auth);
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

a. Example using PHP:

  1. Install the Pusher PHP library:
composer require pusher/pusher-php-server
  1. Create a route and controller in your Laravel application:
  • 2 (a). Define the route in routes/web.php:
Route::post('/pusher/authorize', [PusherAuthController::class, 'authorize']);
  • 2 (b). Create a new controller using the command:
php artisan make:controller PusherAuthController
  • 2 (c). Implement the authorization logic in PusherAuthController.php:
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Pusher\Pusher;

class PusherAuthController extends Controller
{
    public function authorize(Request $request)
    {
        $channelName = $request->input('channelName');
        $socketId = $request->input('socketId');

        // Implement your authorization logic here
        // This can include checking user permissions, validating session, etc.
        // Generate the necessary auth data based on your requirements

        $pusher = new Pusher(
            config('broadcasting.connections.pusher.key'),
            config('broadcasting.connections.pusher.secret'),
            config('broadcasting.connections.pusher.app_id'),
            config('broadcasting.connections.pusher.options')
        );

        $authData = $pusher->socket_auth($channelName, $socketId);

        // Send the authorization data as a response
        return response()->json($authData);
    }
}
  1. Configure Laravel to use Pusher:

Set your Pusher credentials in the .env file:

BROADCAST_DRIVER=pusher
PUSHER_APP_ID=your-app-id
PUSHER_APP_KEY=your-app-key
PUSHER_APP_SECRET=your-app-secret
PUSHER_APP_CLUSTER=your-app-cluster

Configure the broadcasting settings in config/broadcasting.php:

'connections' => [
    'pusher' => [
        'driver' => 'pusher',
        'key' => env('PUSHER_APP_KEY'),
        'secret' => env('PUSHER_APP_SECRET'),
        'app_id' => env('PUSHER_APP_ID'),
        'options' => [
            'cluster' => env('PUSHER_APP_CLUSTER'),
        ],
    ],
    // Other broadcasting connections...
],

Copyright © 2024