Send push notifications using Laravel and AWS SNS

1. Mở đầu

Push notification là cách thức chuyển phát thông tin từ phía ứng dụng backend đến các thiết bị mobile mà không yêu cầu request từ người dùng cuối. Chúng ta có thể push notification từ ứng dụng Laravel thông qua service của Amazon là SNS. AWS SNS (Simple Notification Service) là một dịch vụ push notification một cách linh hoạt được cung cấp bởi Amazon web services. Để tìm hiểu rõ hơn về SNS thì các bạn có thể vào link này Về chi phí thì các bạn có thể tham khảo link này Bài viết này mình sẽ hướng dẫn các bạn từng bước để push notification trong ứng dụng Laravel sử dụng AWS SNS.

2. Config AWS SNS

Đầu tiên bạn cài package aws-sdk-php-laravel https://github.com/aws/aws-sdk-php-laravel để cho phép ứng dụng Laravel thao tác với AWS. Cấu hình đầy đủ key vào file aws.php. Để bắn về cho các thiết bị Android bạn cần tạo một ứng dụng trên Firebase, lấy Legacy server key để tạo một push notification platform, với Android thì chọn GCM, API key thì cho cái Legacy server key ở trên vào. Sau khi tạo thành công thì bạn lấy ARN cấu hình vào file .env của ứng dụng ANDROID_APPLICATION_ARN=arn_here

3. Nhận device_token từ mobile để request lấy endpointArn

Bước đầu tiên thì mình sẽ tạo bảng user_device_tokens nhé, quan hệ giữa bảng user và bảng này là: 1 - n.

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateUserDeviceTokensTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('user_device_tokens', function (Blueprint $table) {
            $table->increments('id');
            $table->char('platform', 20)->default('android');
            $table->string('device_token')->nullable();
            $table->string('arn');
            $table->string('subscription_arn')->nullable();
            $table->integer('user_id')->index();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('user_device_tokens');
    }
}

Bây giờ trong controller thì mình sẽ viết một hàm để nhận device_token mà mobile gửi lên rồi request lên SNS service để lấy endpointArn.

public function getDeviceToken(Request $request)
{
    $input = $request->only('user_id', 'platform', 'device_token');
    try {
        $deviceToken = UserDeviceToken::whereDeviceToken($input['device_token'])->first();

        if ($deviceToken == null) {
            $platformApplicationArn = '';

            if (isset($input['platform']) && $input['platform'] == 'android') {
                $platformApplicationArn = env('ANDROID_APPLICATION_ARN');
            }

            $client = App::make('aws')->createClient('sns');
            $result = $client->createPlatformEndpoint(array(
                'PlatformApplicationArn' => $platformApplicationArn,
                'Token' => $input['device_token'],
            ));

            $endPointArn = isset($result['EndpointArn']) ? $result['EndpointArn'] : '';
            $deviceToken = new UserDeviceToken();
            $deviceToken->platform = $input['platform'];
            $deviceToken->device_token = $input['device_token'];
            $deviceToken->arn = $endPointArn;
        }
        $deviceToken->user_id = $input['user_id'];
        $deviceToken->save();
    } catch (SnsException $e) {
        return response()->json(['error' => "Unexpected Error"], 500);
    }
    return response()->json(["status" => "Device token processed"], 200);
}

Có thể bạn thắc mắc hàm createPlatformEndpoint phải không, Tham khảo link này nhé, đây là những hàm mà mình có thể dùng với thằng SNS này, cứ theo docs của nó mà tán thôi =))

4. Send push notification từ endpointArn

Sau khi request lên aws để lấy về endpointArn, mình sẽ lưu giá trị này vào DB, và khi cần push notification thì publish đến các endpointArn này. Tham khảo method này nhé. OK, bây giờ mình sẽ viết hàm để send push notification đến tất cả người dùng.

    public function sendPushNotification()
    {
        $notificationTitle = "Test Notification";
        $notificationMessage = "Hi all";
        $data = [
            'type' => 'Notification'
        ];
        $userDeviceTokens = UserDeviceToken::get();
        
        foreach ($userDeviceTokens as $userDeviceToken) {
            $deviceToken = $userDeviceToken->device_token;
            $endPointArn = [
                'EndpointArn' => $deviceToken->arn
            ];
            try {
                $sns = App::make('aws')->createClient('sns');
                $endpointAtt = $sns->getEndpointAttributes($endPointArn);
                
                if ($endpointAtt != 'failed' && $endpointAtt['Attributes']['Enabled']) {
                    if ($deviceToken->platform == 'android') {
                        $fcmPayload = json_encode(
                            [
                                'notification'=>
                                    [
                                        'title' => $notificationTitle,
                                        "body" => $notificationMessage,
                                        'sound' => 'default'
                                    ],
                                'data' => $data
                            ]
                        );
                        $message = json_encode([
                            'default' => $notificationMessage,
                            'GCM' => $fcmPayload
                        ]);
                        $sns->publish([
                            'TargetArn' => $deviceToken->arn,
                            'Message' => $message,
                            'MessageStructure' => 'json'
                        ]);
                    }
                }
            } catch (SnsException $e) {
                Log::info($e->getMessage());
            }
        }
    }

Đọc đến đây, có bạn nghĩ thầm rằng, cái thằng ngáo này, lấy tất cả records trong bảng user_device_tokens rồi foreach để gửi cho từng thằng thì có mà chờ đến mùa quýt mới xong à. Ukm, các bạn đúng, không ai làm như thế cả, thực ra SNS nó hổ trợ 2 cơ chế push notification, cơ chế đầu tiên là publish đến từng endpointArn, cái mà mình đã làm ở trên, cơ chế thứ 2 là tạo một topic, và sử dụng endpointArn để subscribe topic đó, khi muốn push notification thì mình cứ publish lên topic đó thì những thằng subscribe topic này sẽ nhận được. Bây giờ triển theo cách này thôi.

Bạn tạo một topic rồi lấy topic arn cấu hình vào file .env TOPIC_ARN=topic_arn_here

Bây giờ mình sẽ viết hàm subscribeDeviceTokenToTopic, unsubscribeDeviceTokenToTopic và publishToTopic như sau, các bạn để ý là sau khi subscribe topic thành công thì nó sẽ trả về cho chúng ta SubscriptionArn dùng để unsubscribe sau này, ở trên khi tạo table user_device_tokens thì mình có thêm field này vào rồi.

    public function subscribeDeviceTokenToTopic($endPointArn)
    {
         $sns = App::make('aws')->createClient('sns');
         $result = $sns->subscribe([
             'Endpoint' => $endPointArn,
             'Protocol' => 'application',
             'TopicArn' => env('TOPIC_ARN'),
         ]);

         return $result['SubscriptionArn'] ?? '';
    }
    
    public function unsubscribeDeviceTokenToTopic($subscriptionArn)
    {
         $sns = App::make('aws')->createClient('sns');
         $sns->unsubscribe([
             'SubscriptionArn' => $subscriptionArn,
         ]);
    }
    
    public function publishToTopic($message)
    {
        $sns = App::make('aws')->createClient('sns');
        $result = $sns->publish([
            'Message' => $message,
            'MessageStructure' => 'json',
            'TopicArn' => env('TOPIC_ARN'),
        ]);

        return $result['MessageId'] ?? '';
    }

Bây giờ cứ mỗi khi mobile gửi device_token lên thì sau khi request để lấy endpointArn thì dùng endpointArn này để subscribe topic luôn, để send push notification bạn gọi hàm publishToTopic.

5. Kết luận

Trên đây là chia sẽ sử dụng AWS SNS trong Laravel. Chi tiết các API mà SNS cung cấp bạn có thể tham khảo ở link này. Cảm ơn các bạn đã đọc


All Rights Reserved