Closed nicolasvahidzein closed 2 years ago
It is possible if you have a look at https://github.com/kreait/laravel-firebase#multiple-projects and then at https://github.com/kreait/laravel-firebase/blob/main/config/firebase.php
Here you can make a copy of the app
array and configure it with the settings of your second app (you can use other keys and don't have to stick with app
, instead you could use project_1
and project_2
, to give a bad example 😅)
If you want to provide both with paths to different json files, you can either hardcode them or introduce new environment variables.
I'm currently not at my computer so I can't provide no full examples right now (writing code blocks on a phone is hard), but I hope I could shed some light 🤞
Thank you so much @jeromegamez.
I will look at that tonight and revert back tomorrow. I am about to launch and worried about this so i can totally pay for an hour of your time for some hand holding tomorrow at any time of your choosing :).
I am using a totally different version i see. Mine just has the credentials array. I'm on "kreait/laravel-firebase": "1.3",
That's a really old version which keeps you at a 4.x release of the SDK which is also really old 😅. I definitely would recommend trying to upgrade those dependencies - depending how closely you stayed with the intended usage and what components you're using, upgrading shouldn't be too much of a hassle - which, I know, is easier said than done 😅, but I do think it's worth it.
About the "hand-holding" session - I'm afraid my time doesn't allow this at the moment, but a GitHub sponsorship starting at a certain tier gives you access to a sponsors-only Slack Channel, in which I can give support, and although it is asynchronous, I usually answer quite promptly during CEST working hours up until late evenings 😅.
Either way I'll try if I can jot down a way how you can set up multiple projects with the 1.x package/4.x SDK in the next hours 🤞 (no promises though 😬)
Brilliant Idea, i will do just that. Speak soon.
I'm curious, I am on PHP 7.3, is there a way to modify the package to run on 7.3 instead of 7.4? That would be a lifesaver. Upgrading anything on my system before i launch v1 would be suicide but this requirement would be easy because on my prod server i can switch out for at least 7.4. Let me know thanks. PS i am officially a sponsor. Thank you for your amazing work Jerome!
Thank you for your donation, it's much appreciated!
I totally get why and that you can't upgrade anything right before launch. Downgrading the new features to PHP 7.3 isn't something I can do, though - for one it would be a massive undertaking, on the other hand I don't support PHP versions that aren't supported anymore themselves 😬.
I'm back at my computer in about half an hour and will work on an example on how you can add multi-project support using the 4.x SDK/1.x Package in your project - it will most likely be creating your own ServiceProvider based on the one provided by the package.
Some questions about your current setup:
app('firebase.xxx')
way, be prepared to change all calls to app('firebase.<project>.xxx')
, and if you're using Dependency Injection, I'll have to look up again how conditional dependency injection works or find a better solution.Hey Jerome, i meant in the sense that if i went into the package file and changed the minimum version. Would it work you think? Or would that version break the package because it is so different than 7.4?
To answer your questions these are the packages i am using:
"require": {
"php": "^7.1.3",
"barryvdh/laravel-cors": "^0.11.3",
"ankurk91/fcm-notification-channel": "1.0.0",
"beyondcode/laravel-websockets": "^1.1",
"fideloper/proxy": "^4.0",
"giggsey/libphonenumber-for-php": "^8.12",
"guzzlehttp/guzzle": "^6.5",
"intervention/image": "^2.5",
"jenssegers/mongodb": "^3.5",
"kreait/laravel-firebase": "1.3",
"krossroad/laravel-union-paginator": "^5.5",
"laravel/framework": "5.8.*",
"laravel/passport": "7.3",
"laravel/tinker": "^1.0",
"laravolt/avatar": "^2.2",
"lasserafn/php-initial-avatar-generator": "^4.0",
"lcobucci/jwt": "3.3.3",
"predis/predis": "^1.1",
"prettus/l5-repository": "^2.6",
"pusher/pusher-php-server": "3.4.1",
"ramsey/uuid": "^3.8",
"sayeed/custom-migrate": "^1.0",
"spatie/laravel-permission": "^2.37",
"twilio/sdk": "^5.34"
},
"require-dev": {
"barryvdh/laravel-ide-helper": "^2.6",
"beyondcode/laravel-dump-server": "^1.0",
"filp/whoops": "^2.0",
"fzaninotto/faker": "^1.4",
"mockery/mockery": "^1.0",
"nunomaduro/collision": "^3.0",
"phpunit/phpunit": "^7.5"
},
I am on Laravel 5.8 at the moment.
I am only using the component in a single class because it was a simple and straightforward way for me and i only use the raw component this way:
$message = new RawMessageFromArray([
'token' => $deviceToken,
//'name' => $name,
//'topic' => $topic,
//'condition' => $condition,
'notification' => $notificationPayload,
'data' => $data,
'android' => $androidSettings,
'apns' => $apnsSettings,
'webpush' => $webpushSettings,
'fcm_options' => $fcmOptions
]);
$messaging = app('firebase.messaging');
$sendingInfo = $messaging->send($message);
The full class is a bit longer but just in case you need to see it here it is:
<?php
namespace App\Jobs\PushMessaging;
//system classes
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Kreait\Firebase\Messaging\RawMessageFromArray;
use Kreait\Firebase\Exception\Messaging\InvalidArgument;
use Kreait\Firebase\Exception\Messaging\NotFound;
use Kreait\Firebase\Exception\Messaging\InvalidMessage;
use Ramsey\Uuid\Uuid;
//use Kreait\Firebase\Exception\FirebaseException;
//use Kreait\Firebase\Exception\Messaging\UnknowError;
//use Kreait\Firebase\Messaging;
//use Kreait\Firebase\Messaging\CloudMessage;
//use Kreait\Firebase\Messaging\Notification;
use Illuminate\Support\Facades\Notification;
use Kreait\Firebase\Messaging\MessageTarget;
use App\Notifications\SendFcmNotification;
use Exception;
use Log;
//models
use App\Models\Endpoints;
//Custom methods
use App\Methods\Endpoints\EndpointOperations;
//Issues
//AUDIT_00001-CHECKED
class SendPushNotification implements ShouldQueue {
private $className = 'App\Jobs\PushMessaging\SendPushNotification.php';
private $deviceIdentifier;
private $title;
private $message;
private $messageImage;
private $messagePriority; //unused for now
private $totalNotifications;
private $extraPayload;
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(
$deviceIdentifier,
$title,
$message,
$messageImage,
$messagePriority,
$totalNotifications,
$extraPayload
) {
$this->deviceIdentifier = $deviceIdentifier;
$this->title = $title;
$this->message = $message;
$this->messageImage = $messageImage;
$this->messagePriority = $messagePriority;
$this->totalNotifications = $totalNotifications;
$this->extraPayload = $extraPayload;
}
public function handle() {
if (config('settings.DebugTraceLevelFunctions') == true) { Log::channel('merlin_debug')->info('handle function in '.$this->className); }//this is a special log function for queue job classes
//this is a provider agnostic process as it should be. Here though we need to determine which provider or process to use before moving forwarding and doing provider specific tasks
$pushNotificationsProvider = null;
//check first for the desired main provider
switch (config('settings.pushnotificationsPrefered')) {
case 'pushnotifications_provider_firebase':
$pushNotificationsProvider = 'pushnotifications_provider_firebase';
break;
default:
$pushNotificationsProvider = config('settings.pushnotificationsdefault');
break;
}
if ($pushNotificationsProvider == 'pushnotifications_provider_firebase') {
$endpointUID = $this->deviceIdentifier;
//NEXT_UPDATE EXTRACT THIS TO IT'S OWN CLASS IN THE FUTURE
//go here for docs for the REST API
//https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#AndroidNotification
//FIX_THIS_SOON WE NEED A TRY CATCH LOOP IN THE EVENT THE TOKEN IS DELETED OR THERE IS AN ERROR TO SEE WHAT HAPPENS
$endpoint = Endpoints::where('UID', '=', $endpointUID)->first();
Log::channel('merlin_debug')->info('$endpoint:');
Log::channel('merlin_debug')->info($endpoint);
$deviceToken = $endpoint->firebase_token;
Log::channel('merlin_debug')->info('$deviceToken:');
Log::channel('merlin_debug')->info($deviceToken);
//FIX_THIS_SOON figure out later how to upgrade to Kreiat version 4 that supports multiple authentication accounts which i cannot do at this time
if ($endpoint->foreignUID_type == 'client') {
//we are good to go it is a client endpoint
Log::channel('merlin_debug')->info('we are good to go it is a client endpoint');
//keep going
} else {
//we are in trouble, it is an admin endpoint (seller, supplier, partner, consultant, employee)
Log::channel('merlin_debug')->info('we are in trouble, it is an admin endpoint');
Notification::route('FCM', [$deviceToken/*, 'token_2'*/])
->route('FCMTargetType', MessageTarget::TOKEN)
->notify(new SendFcmNotification($this->title, $this->message));
//abort
Log::channel('merlin_debug')->info('abort');
return;
}
//$name = 'Notification name'; //FIX_THIS_SOON figure this out soon
$topic = 'Notification topic'; //FIX_THIS_SOON figure this out soon
$condition = 'Notification condition'; //FIX_THIS_SOON figure this out soon
$notificationTitle = $this->title;
$notificationBody = $this->message;
$notificationIcon = 'https://unsplash.it/200/200'; //FIX_THIS_SOON is this being used?
$notificationImageUrl = $this->messageImage;
$notificationPriority = 'high';
$apnsPayloadBadge = $this->totalNotifications;
$analyticsLabel = 'zandu-notification'; //FIX_THIS_SOON what do i need to fix here?
//BYPASS
$apnsPayloadBadge = 0;//FIX_THIS_SOON until we figure out how to handle changing the notifications on the device when they are clicked on or marked as read or unread in the app, we will no longer send a notification count because we have no way to change it later
//https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#notification
$notificationPayload = [
'title' => $notificationTitle,
'body' => $notificationBody,
'image' => $notificationImageUrl,
];
//generate the notification UID
$pushNotificationUID = Uuid::uuid4();
//payload needs to be configured from the parent function as an array, don't pass individual elements
$data = [
'click_action' => 'FLUTTER_NOTIFICATION_CLICK',
'priority' => 'high',
'sound' => 'default',
//ALL THE OTHER VARIABLES NEEDED HERE WILL BE PASSED IN THE EXTRA PAYLOAD AND APPENDED TO THIS ARRAY
];
Log::channel('merlin_debug')->info('$this->extraPayload:');
Log::channel('merlin_debug')->info($this->extraPayload);
Log::channel('merlin_debug')->info('$data before appending any other extra payload variables:');
Log::channel('merlin_debug')->info($data);
$extraPayload = $this->extraPayload;
//LARAVEL-SENSEI-0007
$extraPayload->each(function ($item, $key) use (&$data) {
// Do stuff
//Log::channel('merlin_debug')->info('$key:');
//Log::channel('merlin_debug')->info($key);
//Log::channel('merlin_debug')->info('$item:');
//Log::channel('merlin_debug')->info($item);
//check to make sure no element in the new property is null, firebase does not like null elements
if (is_null($key)) {
//key or item is INDEED null
Log::channel('merlin_debug')->info('key or item is INDEED null');
Log::channel('merlin_debug')->info('$key:');
Log::channel('merlin_debug')->info($key);
Log::channel('merlin_debug')->info('$item:');
Log::channel('merlin_debug')->info($item);
} else {
//neither key nor item is null, we can proceed
//Log::channel('merlin_debug')->info('neither key nor item is null, we can proceed');
$itemData = $item;
if (is_null($item)) {
$itemData = '';//because firebase does not like null values
Log::channel('merlin_debug')->info('key or item is INDEED null');
Log::channel('merlin_debug')->info('$key:');
Log::channel('merlin_debug')->info($key);
Log::channel('merlin_debug')->info('$item:');
Log::channel('merlin_debug')->info($item);
}
//merge the array to the array
$data = array_merge($data, array($key => $itemData));
}
});
Log::channel('merlin_debug')->info('$data after integrating the extra payload variables: 333');
Log::channel('merlin_debug')->info($data);
$androidTtl = '3600s';
$androidCollapseKey = '';
$androidRestrictedPackageName = '';
$androidNotificationPayload = [
'title' => $notificationTitle,
'body' => $notificationBody,
//'icon' => 'string',
//'color' => 'string',
'sound' => 'default',
//'tag' => 'string',
'click_action' => 'FLUTTER_NOTIFICATION_CLICK',
//'body_loc_key' => 'string',
/* 'body_loc_args' => [
'string'
], */
//'title_loc_key' => 'string',
/* 'title_loc_args' => [
'string'
], */
'channel_id' => 'notifications_001',
//'ticker' => 'string',
'sticky' => false, //When set to false or unset, the notification is automatically dismissed when the user clicks it in the panel. When set to true, the notification persists even when the user clicks it.
//'event_time' => 'string',
'local_only' => true,
'notification_priority' => 'priority_high', //also priority_max
'default_sound' => true,
'default_vibrate_timings' => true,
'default_light_settings' => true,
/* 'vibrate_timings' => [
'string'
], */
//'visibility' => 'private',
//'notification_count' => integer,
/* 'light_settings' => {
"color" => {
object (Color)
},
"light_on_duration": string,
"light_off_duration": string
}, */ //i can't get to write the object properly
//'image' => $notificationImageUrl
];
$androidFcmOptions = [
'analytics_label' => $analyticsLabel //optional
];
$webPushHeaders = [
'Urgency' => 'high',
];
$webPushFcmOptions = [
//'link' => '',
'analytics_label' => $analyticsLabel //optional
];
$apnsHeaders = [
'apns-priority' => '10',
//'mutable-content' => 1,//FIX_THIS_SOON find out if i need this later for displaying images in ios if not remove it
];
$apnsPayload = [
'aps' => [
'alert' => [
'title' => $notificationTitle,
'body' => $notificationBody,
],
'badge' => $apnsPayloadBadge,
],
];
$apnsFcmOptions = [
//'image' => '',
'analytics_label' => $analyticsLabel //optional
];
//https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#androidconfig
$androidSettings = [
'ttl' => $androidTtl,
'priority' => $notificationPriority,
//'collapse_key' => $androidCollapseKey,
//'restricted_package_name' => $androidRestrictedPackageName,
'notification' => $androidNotificationPayload,
'data' => $data,
'fcm_options' => $androidFcmOptions,
];
//https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#apnsconfig
$apnsSettings = [
'headers' => $apnsHeaders,
'payload' => $apnsPayload,
'fcm_options' => $apnsFcmOptions,
];
//https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#webpushconfig
$webpushSettings = [
'headers' => $webPushHeaders,
'notification' => $androidNotificationPayload,
'data' => $data,
'fcm_options' => $webPushFcmOptions,
];
//hide fcm_options if you are leaving it empty
//https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#fcmoptions
$fcmOptions = [
'analytics_label' => $analyticsLabel //optional
];
$message = new RawMessageFromArray([
'token' => $deviceToken,
//'name' => $name,
//'topic' => $topic,
//'condition' => $condition,
'notification' => $notificationPayload,
'data' => $data,
'android' => $androidSettings,
'apns' => $apnsSettings,
'webpush' => $webpushSettings,
'fcm_options' => $fcmOptions
]);
$messaging = app('firebase.messaging');
try {
$sendingInfo = $messaging->send($message);
Log::channel('merlin_debug')->info('$sendingInfo:');
Log::channel('merlin_debug')->info($sendingInfo);
} /*catch (FirebaseException $error) {//NotFound error is more explicit
//FirebaseException error
Log::channel('merlin_debug')->info('FirebaseException error');
Log::channel('merlin_debug')->info('error message:');
Log::channel('merlin_debug')->info($error);
} */catch (InvalidArgument $error) {
//InvalidArgument error
Log::channel('merlin_debug')->info('InvalidArgument error');
Log::channel('merlin_debug')->info('error message:');
Log::channel('merlin_debug')->info($error);
//FIX_THIS_SOON WE NEED TO BE SURE ABOUT WHAT ACTION TO TAKE HERE BECAUSE THE ERRORS ARE AMBIGUOUS, WE MIGHT DELETE A TOKEN WHEN THE ERROR IS RELATED TO SOMETHING ELSE, THINK ABOUT THIS CAREFULLY
} catch (InvalidMessage $error) {
//InvalidMessage error
Log::channel('merlin_debug')->info('InvalidMessage error');
Log::channel('merlin_debug')->info('error message:');
Log::channel('merlin_debug')->info($error);
//FIX_THIS_SOON WE NEED TO BE SURE ABOUT WHAT ACTION TO TAKE HERE BECAUSE THE ERRORS ARE AMBIGUOUS, WE MIGHT DELETE A TOKEN WHEN THE ERROR IS RELATED TO SOMETHING ELSE, THINK ABOUT THIS CAREFULLY
} catch (NotFound $error) {
//NotFound error, token is not registered to the project (any more) delete the token
Log::channel('merlin_debug')->info('token is not registered to the project (any more) delete the token');
Log::channel('merlin_debug')->info('error message:');
Log::channel('merlin_debug')->info($error);
//remove this from the list of endpoints for this user
Log::channel('merlin_debug')->info('remove this from the list of endpoints for this user');
//check the endpoint
$endpointOperations = new EndpointOperations();
$endpointOperationsResponse = $endpointOperations->delete($endpointUID);
if ($endpointOperationsResponse['status'] == 'success') {
//we are good, endpoint was deleted
Log::channel('merlin_debug')->info('we are good, endpoint was deleted');
} else {
//bad news, endpoint was not deleted successfully
Log::channel('merlin_debug')->info('bad news, endpoint was not deleted successfully');
}
} catch (Exception $error) {
//generic error
Log::channel('merlin_debug')->info('generic error');
Log::channel('merlin_debug')->info('error message:');
Log::channel('merlin_debug')->info($error);
}
} else {
//do nothing for now, there are no other push notifications service provider in this system that we can handle
}
}
}
Hey Jerome, I meant in the sense that if I went into the package file and changed the minimum version. Would it work you think? Or would that version break the package because it is so different than 7.4?
I think it would break your application because PHP 7.4 introduced typed properties, which aren't supported in PHP 7.3 :/
Thank you for sharing your composer.json
- one thing I noticed is that you specified "kreait/laravel-firebase": "1.3",
- although it will not help with the current challenge, it should be save to specify it as "kreait/laravel-firebase": "^1.3",
because this would upgrade the package to the latest 1.x version (= 1.5).
I'm working on an example of how you can use two Firebase projects in the same application - thanks for the info that you're only using the messaging component, this will make it easier to whip it up 😅
Ok understood! Thanks a ton!.
Alright, I haven't tested this, but I hope at least the general direction is already the right one - and if that does work, we can talk about optimizing it further.
Here we go…
my_firebase.php
with the following contents (adapted to your project)<?php
return [
'projects' => [
'project_1' => [
'credentials' => env('PROJECT_1_FIREBASE_CREDENTIALS'),
],
'project_2' => [
'credentials' => env('PROJECT_2_FIREBASE_CREDENTIALS'),
],
],
'cache_store' => env('FIREBASE_CACHE_STORE', 'file'),
];
MyFirebaseServiceProvider
in app/Providers
:<?php
namespace App\Providers;
use Kreait\Firebase;
class MyFirebaseServiceProvider extends \Illuminate\Support\ServiceProvider
{
public function boot()
{
$firebaseConfig = config('my_firebase');
$cache = $this->app->make('cache')->store($firebaseConfig['cache_store']);
foreach ($firebaseConfig as $projectId => $projectConfig) {
$this->registerProject($projectId, $projectConfig, $cache);
}
}
/**
* @param string $projectId
* @param array $config
* @param \Illuminate\Contracts\Cache\Repository $cacheStore
* @return void
*/
private function registerProject($projectId, $config, $cacheStore)
{
$credentials = $this->resolveCredentials($config['credentials']);
$factory = (new Firebase\Factory())
->withServiceAccount($credentials)
->withVerifierCache($cacheStore);
$this->app->singleton('firebase.'.$projectId.'.auth', function ($app) use ($factory) {
return $factory->createAuth();
});
$this->app->singleton('firebase.'.$projectId.'.messaging', function ($app) use ($factory) {
return $factory->createMessaging();
});
}
private function resolveCredentials(string $credentials): string
{
$isJsonString = strpos($credentials, '{') === 0;
$isAbsoluteLinuxPath = strpos($credentials, '/') === 0;
$isAbsoluteWindowsPath = strpos($credentials, ':\\') !== false;
$isRelativePath = !$isJsonString && !$isAbsoluteLinuxPath && !$isAbsoluteWindowsPath;
return $isRelativePath ? $this->app->basePath($credentials) : $credentials;
}
}
After this, you should be able to use app('firebase.project_1.messaging')
and app('firebase.project_2.messaging')
throughout your app 🤞
Bad news, i followed the steps exactly but not getting any notifications.
Is there a way to find out where the error happened?
$message = new RawMessageFromArray([
'token' => $deviceToken,
//'name' => $name,
//'topic' => $topic,
//'condition' => $condition,
'notification' => $notificationPayload,
'data' => $data,
'android' => $androidSettings,
'apns' => $apnsSettings,
'webpush' => $webpushSettings,
'fcm_options' => $fcmOptions
]);
$messaging = null;
if ($endpoint->foreignUID_type == 'client') {
$messaging = app('firebase.client_mobile_app.messaging');
} else {
$messaging = app('firebase.admin_mobile_app.messaging');
}
$sendingInfo = $messaging->send($message);
Don't I have to call the MyFirebaseServiceProvider class somewhere?
$messaging = app('firebase.messaging') which is now app('firebase.project_1.messaging') might be the issue no?
I indeed found this in the logs:
Class firebase.client_mobile_app.messaging does not exist
Aaah, right, I think you have to add it to the 'providers' array in the config/app.php file
sorry, I didn't try the changes myself because I currently don't have PHP 7.3 and a Laravel 5.8 project around 🙈
No that's my job. I'm ecstatic you are helping. Don't sweat it.
Do i keep the default firebase.php in config?
Still not working. I added it to the app/config.php
//Application Service Providers
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
App\Providers\MyFirebaseServiceProvider::class,
I have an error on this line:
$credentials = $this->resolveCredentials($config['credentials']);
It says undefined index: credentials
Error 100% is coming from that credentials line. Not sure why. Very strange.
Give me a bit, I'll set up a project and try to reproduce it. 🤞
DON'T STRESS. I figured it out.
There was a tiny mistake in the boot function:
$firebase = config('my_firebase');
$firebaseConfig = $firebase['projects'];
$cache = $this->app->make('cache')->store($firebase['cache_store']);
foreach ($firebaseConfig as $projectId => $projectConfig) {
$this->registerProject(
$projectId,
$projectConfig,
$cache
);
}
YOU ARE A GENIUS AND AN AMAZING MAINTAINER!!!!!
You fixed everything. I am definitely supporting you big time when this app takes off.
A THOUSAND THANK YOU!!!!
Dangit, I just wanted to send you THE SAME MESSAGE!!! 😂
But I can add something - if this is working fine with you, and since you now have your own custom integration, you don't actually need the Laravel package anymore 😅
I published my implementation at https://github.com/jeromegamez/laravel58-with-firebase for reference.
But the important part is that you made it work 🚀 , glad I could help!
I did nothing, you did the heavy lifting. I pushed you over the edge!! THANKS!!!!!!!!!!!!
Hello,
Thank you first of all for this wonderful package that is a lifesaver!
I wanted to know how i can configure my laravel instance to send to 2 different apps each with their own google services json file.
I'm guessing it needs to be configured in the firebase.php settings file and i need to add a new entry in the env file but is there a more complete documentation i can look up or a tutorial?
Thanks.