kreait / laravel-firebase

A Laravel package for the Firebase PHP Admin SDK
https://github.com/kreait/firebase-php
MIT License
987 stars 162 forks source link

SenderId mismatch #195

Closed JamesPJ closed 10 months ago

JamesPJ commented 10 months ago

Describe the bug

I've 3 projects configured in firebase.php config

But, if I send notification for any project apart from the first one, I'm getting SenderId mismatch error. I've cross checked the issue by moving up and down the projects in the array. I've no idea why it is only taking the first project.

Installed packages

{
        "php": "^8.0",
        "abrigham/laravel-email-exceptions": "^4.0",
        "barryvdh/laravel-dompdf": "^2.0.1",
        "bonecms/laravel-captcha": "^2.0.1",
        "danhunsaker/laravel-flysystem-others": "*",
        "doctrine/dbal": "^2.10",
        "gecche/laravel-multidomain": "4.2.1",
        "genealabs/laravel-model-caching": "~0.11.7",
        "guzzlehttp/guzzle": "^7.0",
        "intervention/image": "^2.7",
        "kreait/laravel-firebase": "^4.2",
        "laravel-notification-channels/fcm": "^2.7.0",
        "laravel/framework": "8.*",
        "laravel/helpers": "*",
        "laravel/socialite": "^5.8",
        "laravel/tinker": "^2.8",
        "laravelcollective/html": "^6.4.1",
        "league/flysystem": "^1.1",
        "league/flysystem-aws-s3-v3": "^1.0.30",
        "maatwebsite/excel": "^3.1.48",
        "picqer/php-barcode-generator": "^2.2.4",
        "predis/predis": "^2.2",
        "rap2hpoutre/laravel-log-viewer": "^2.3",
        "romanzipp/laravel-turnstile": "^1.1",
        "spatie/laravel-activitylog": "^4.7",
        "spatie/laravel-backup": "7.8.0",
        "spatie/laravel-medialibrary": "9.12.*",
        "sqits/laravel-userstamps": "^0.0.10",
        "tymon/jwt-auth": "^1.0.2"
    }

Steps to reproduce the issue.

<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;
use Kreait\Firebase\Exception\Messaging\NotFound;
use Log;
use NotificationChannels\Fcm\FcmChannel;
use NotificationChannels\Fcm\FcmMessage;
use NotificationChannels\Fcm\Resources\AndroidConfig;
use NotificationChannels\Fcm\Resources\AndroidFcmOptions;
use NotificationChannels\Fcm\Resources\AndroidNotification;
use NotificationChannels\Fcm\Resources\ApnsConfig;
use NotificationChannels\Fcm\Resources\ApnsFcmOptions;

class FirebasePush extends Notification implements ShouldQueue
{ 
    use Queueable;

    public $tries = 3;

    protected $msg_title = '';
    protected $msg_text = '';
    protected $data = [];
    protected $image = NULL;
    protected $project = 'myproject';

    /**
     * Create a new notification instance.
     *
     * @return void
     */
    public function __construct($msg_title, $msg_text, $data=[], $image=NULL)
    {
        $this->msg_title = $msg_title;
        $this->msg_text = $msg_text;
        $this->data = $data;
        $this->image = $image;
        $this->project = config('firebase.default');
    }
    public function via($notifiable)
    {
        return [FcmChannel::class];
    }

    public function toFcm($notifiable)
    {
        try {
            $notification = \NotificationChannels\Fcm\Resources\Notification::create()
            ->setTitle($this->msg_title)
            ->setBody($this->msg_text);
            if($this->image) {
                $notification->setImage($this->image);
            }
            if(empty($this->data)) {
                $this->data = ['route' => 'notification'];
            }
            return FcmMessage::create()
                ->setData($this->data)
                ->setNotification($notification)
                ->setAndroid(
                    AndroidConfig::create()
                        ->setFcmOptions(AndroidFcmOptions::create()->setAnalyticsLabel('analytics'))
                        ->setNotification(AndroidNotification::create()->setColor('#324398'))
                )->setApns(
                    ApnsConfig::create()
                        ->setFcmOptions(ApnsFcmOptions::create()->setAnalyticsLabel('analytics_ios')));
        } catch (NotFound $th) {
            $t = $th->errors();
            Log::error($t);
        }
    }

    // optional method when using kreait/laravel-firebase:^3.0, this method can be omitted, defaults to the default project
    public function fcmProject($notifiable, $message)
    {
        return $this->project; // name of the firebase project to use
    }
}

firebase.php

<?php

declare(strict_types=1);

return [
    /*
     * ------------------------------------------------------------------------
     * Default Firebase project
     * ------------------------------------------------------------------------
     */
    'default' => env('FIREBASE_PROJECT', 'myproject'),

    /*
     * ------------------------------------------------------------------------
     * Firebase project configurations
     * ------------------------------------------------------------------------
     */
    'projects' => [
        'myproject' => [
                    'credentials' => [
                        'file' => env('FIREBASE_CREDENTIALS', 'config/myproject.json'),
                        'auto_discovery' => true,
                    ],
                    'auth' => [
                        'tenant_id' => env('FIREBASE_AUTH_TENANT_ID'),
                    ],
                    'database' => [
                        'url' => env('FIREBASE_DATABASE_URL', 'https://myproject.firebaseio.com'),
                    ],

                    'dynamic_links' => [
                        'default_domain' => env('FIREBASE_DYNAMIC_LINKS_DEFAULT_DOMAIN'),
                    ],

                    'storage' => [
                        'default_bucket' => env('FIREBASE_STORAGE_DEFAULT_BUCKET'),
                    ],
                    'cache_store' => env('FIREBASE_CACHE_STORE', 'file'),
                    'logging' => [
                        'http_log_channel' => env('FIREBASE_HTTP_LOG_CHANNEL'),
                        'http_debug_log_channel' => env('FIREBASE_HTTP_DEBUG_LOG_CHANNEL'),
                    ],
                    'http_client_options' => [
                        'timeout' => env('FIREBASE_HTTP_CLIENT_TIMEOUT'),
                    ],
                ],
        'myprojectOne' => [
                    'credentials' => [
                        'file' => env('FIREBASE_CREDENTIALS', 'config/myprojectOne.json'),
                        'auto_discovery' => true,
                    ],
                    'auth' => [
                        'tenant_id' => env('FIREBASE_AUTH_TENANT_ID'),
                    ],
                    'database' => [
                        'url' => env('FIREBASE_DATABASE_URL', 'https://myproject.firebaseio.com'),
                    ],

                    'dynamic_links' => [
                        'default_domain' => env('FIREBASE_DYNAMIC_LINKS_DEFAULT_DOMAIN'),
                    ],

                    'storage' => [
                        'default_bucket' => env('FIREBASE_STORAGE_DEFAULT_BUCKET'),
                    ],
                    'cache_store' => env('FIREBASE_CACHE_STORE', 'file'),
                    'logging' => [
                        'http_log_channel' => env('FIREBASE_HTTP_LOG_CHANNEL'),
                        'http_debug_log_channel' => env('FIREBASE_HTTP_DEBUG_LOG_CHANNEL'),
                    ],
                    'http_client_options' => [
                        'timeout' => env('FIREBASE_HTTP_CLIENT_TIMEOUT'),
                    ],
                ]
          ]
];

Error message/Stack trace

Throwable Class:    NotificationChannels\Fcm\Exceptions\CouldNotSendNotification
Throwable Message:  SenderId mismatch

#0 /app/vendor/laravel-notification-channels/fcm/src/FcmChannel.php(84): NotificationChannels\Fcm\Exceptions\CouldNotSendNotification::serviceRespondedWithAnError(Object(Kreait\Firebase\Exception\Messaging\AuthenticationError))
kreait/firebase-php#1 /app/vendor/laravel/framework/src/Illuminate/Notifications/NotificationSender.php(148): NotificationChannels\Fcm\FcmChannel->send(Object(App\User), Object(App\Notifications\FirebasePush))
kreait/firebase-php#2 /app/vendor/laravel/framework/src/Illuminate/Notifications/NotificationSender.php(106): Illuminate\Notifications\NotificationSender->sendToNotifiable(Object(App\User), '4115cac3-e95b-4...', Object(App\Notifications\FirebasePush), 'NotificationCha...')
kreait/firebase-php#3 /app/vendor/laravel/framework/src/Illuminate/Support/Traits/Localizable.php(19): Illuminate\Notifications\NotificationSender->Illuminate\Notifications\{closure}()

Additional information

Application is hosted for multiple domains using https://github.com/gecche/laravel-multidomain

jeromegamez commented 10 months ago

Thank you for providing details for the issue, much appreciated (many don't do it 😅).

When you commented to that other issue, I didn't know that this problem with occurred in the context of/while using the FCM notification channel package.

Unfortunately, I'm not familiar with the package and it's not in scope of my package. Using multiple projects "vanilla-style" with my package should work, but I don't know why it doesn't with the other package. I believe you should open an issue on their package instead.

What I did notice is that you're using the same FIREBASE_CREDENTIALS and other environment variable for both project configurations, could that be the reason?

JamesPJ commented 10 months ago

@jeromegamez Thank you for taking time to look into it.

As I have multiple domains using same codebase, each domain use dedicated .env file. For Example: If I've configured www.example.com so the env file would be .env.www.example.com.

So, I've different domains using different firebase projects. I did tried logging the firebase config values to the log to see whether it is has the correct values. Unfortunately, I'm seeing correct values.

As of now, I'm suspecting the service provider, as it is using the singleton instance. However, I do not have enough info to support my hypothesis.

$this->app->singleton(Firebase\Contract\Messaging::class, static fn (Container $app) => $app->make(FirebaseProjectManager::class)->project()->messaging());

jeromegamez commented 10 months ago

Thank you for the additional insights!

You're right - if you type-hint the Messaging interface, it will (should) inject the messaging component of the default project defined in the firebase.default config.

Just to be sure, are you setting the FIREBASE_PROJECT environment variable differently on each host, in your example to myprojectOne?

JamesPJ commented 10 months ago

Just to be sure, are you setting the FIREBASE_PROJECT environment variable differently on each host, in your example to myprojectOne?

Yes, Correct. It is different.

Firebase has app limit between 30-50. So, to avoid the limit. I've different projects set for different host.

To be clear. .env.example.com may have FIREBASE_PROJECT= myprojectOne and .env.newhost.com may have FIREBASE_PROJECT= myproject.

JamesPJ commented 10 months ago

@jeromegamez Any suggestion on this?

jeromegamez commented 10 months ago

I just tested this in a fresh Laravel 8 project with version 4.2 of this package (please note, I don't support versions older than the current one, but I haven't added a supported versions matrix to the README yet, so here we are 😅)

To make my manual tests simple, I have this config/firebase.php file - in order to test that the correct project is picked up, we can get a database reference and show its URL (at this point, no request is executed, but we can see if the correct FIREBASE_DATABASE_URL is picked up.

I noticed that, in the config you provided, the database URL is

<?php

return [
    'default' => env('FIREBASE_PROJECT', 'myproject'),
    'projects' => [
        'myproject' => [
                    'credentials' => [
                        'file' => env('FIREBASE_CREDENTIALS', 'config/myproject.json'),
                    ],
                    'database' => [
                        'url' => env('FIREBASE_DATABASE_URL', 'https://myproject.firebaseio.com'),
                    ],
                ],
        'myprojectOne' => [
                    'credentials' => [
                        'file' => env('FIREBASE_CREDENTIALS', 'config/myprojectOne.json'),
                    ],
                    'database' => [
                        'url' => env('FIREBASE_DATABASE_URL', 'https://myprojectOne.firebaseio.com'),
                    ],
                ]
    ],
];

(Note that I have changed the FIREBASE_DATABASE_URL for myprojectOne - in your config, both have the same value.)

In a tinker session, we can now test with

> app('firebase.manager')->project()->database()->getReference()->getUri()->__toString()
= "https://myproject.firebaseio.com/"

if the expected Database URI is printed out.

In my tests, it worked with the following setups:

Here are my suggestions:

Long story short: I can't reproduce the problem and must assume that this is caused by something else, probably in the usage of the FCM Notification Channel package.

I mentioned earlier that versions < 5.x are not supported anymore - the laravel-notification-channels/fcm package has also a newer major release (3.x). Perhaps there is something that doesn't add up. (PHP 8.0.* is EOLed as well)

I hope this helps.

JamesPJ commented 10 months ago

Replace 'default' => env('FIREBASE_PROJECT', 'myproject'), with 'default' => env('FIREBASE_PROJECT), - with this, you will get an error if the FIREBASE_PROJECT environment variable is not set.

This did the trick. It was always reading the default value instead of the env value. Not sure why.

Thank you for your help!